{"id":23441848,"url":"https://github.com/drpicox/egg-hatchery","last_synced_at":"2026-04-16T00:32:30.165Z","repository":{"id":43983292,"uuid":"244692453","full_name":"drpicox/egg-hatchery","owner":"drpicox","description":"🐣 Combine and hatch redux eggs and more","archived":false,"fork":false,"pushed_at":"2022-12-10T20:19:59.000Z","size":1184,"stargazers_count":0,"open_issues_count":17,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-05T07:51:34.861Z","etag":null,"topics":["dependency-injection","ducks","redux"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/drpicox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-03T16:56:43.000Z","updated_at":"2020-06-07T12:44:51.000Z","dependencies_parsed_at":"2023-01-26T07:45:27.759Z","dependency_job_id":null,"html_url":"https://github.com/drpicox/egg-hatchery","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/drpicox/egg-hatchery","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drpicox%2Fegg-hatchery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drpicox%2Fegg-hatchery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drpicox%2Fegg-hatchery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drpicox%2Fegg-hatchery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/drpicox","download_url":"https://codeload.github.com/drpicox/egg-hatchery/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drpicox%2Fegg-hatchery/sbom","scorecard":{"id":356950,"data":{"date":"2025-08-11","repo":{"name":"github.com/drpicox/egg-hatchery","commit":"d7fbdd7d00e43035724ec3bed38e5254bb3a15ad"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/15 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"62 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-wrw9-m778-g6mc","Warn: Project is vulnerable to: GHSA-pp7h-53gx-mx7r","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-x9w5-v3q2-3rhw","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-vh7m-p724-62c2","Warn: Project is vulnerable to: GHSA-r9p9-mrjm-926w","Warn: Project is vulnerable to: GHSA-434g-2637-qmqr","Warn: Project is vulnerable to: GHSA-49q7-c7j4-3p7m","Warn: Project is vulnerable to: GHSA-977x-g7h5-7qgw","Warn: Project is vulnerable to: GHSA-f7q4-pwc6-w24p","Warn: Project is vulnerable to: GHSA-fc9h-whq2-v747","Warn: Project is vulnerable to: GHSA-vjh7-7g9h-fjfh","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-3rfm-jhwj-7488","Warn: Project is vulnerable to: GHSA-hhq3-ff78-jv3g","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-h7cp-r72f-jxh6","Warn: Project is vulnerable to: GHSA-v62p-rq8g-8h59","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-x6fg-f45m-jf5q","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-44c6-4v22-4mhx","Warn: Project is vulnerable to: GHSA-4x5v-gmq8-25ch","Warn: Project is vulnerable to: GHSA-hxcc-f52p-wc94","Warn: Project is vulnerable to: GHSA-g4rg-993r-mgx7","Warn: Project is vulnerable to: GHSA-vx3p-948g-6vhq","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-w5p7-h5w8-2hfq","Warn: Project is vulnerable to: GHSA-cf4h-3jhx-xvhq","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T09:50:20.461Z","repository_id":43983292,"created_at":"2025-08-18T09:50:20.461Z","updated_at":"2025-08-18T09:50:20.461Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31866330,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"ssl_error","status_checked_at":"2026-04-15T15:24:39.138Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","ducks","redux"],"created_at":"2024-12-23T17:19:00.468Z","updated_at":"2026-04-16T00:32:30.137Z","avatar_url":"https://github.com/drpicox.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Egg Hatchery 🐣\n\n\u003e 🥚 Eggs are the new 🦆 ducks.\n\n```javascript\nimport hatch from 'egg-hatchery';\nimport reduxEgg from 'redux-egg';\nimport counterEgg, { increment, getCount } from '@my/counter-egg';\n\ntest('counter egg increments in one', () =\u003e {\n  const { store } = hatch(reduxEgg, counterEgg);\n  store.dispatch(increment());\n  expect(getCount(store.getState())).toBe(1);\n});\n```\n\n## Table of Content\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n- [Redux](#redux)\n- [How an egg works?](#how-an-egg-works)\n- [How the redux-egg works?](#how-the-redux-egg-works)\n- [Why are eggs better than ducks?](#why-are-eggs-better-than-ducks)\n  - [REASON 1: Combine eggs and solve dependencies](#reason-1-combine-eggs-and-solve-dependencies)\n  - [REASON 2: Thunks sucks](#reason-2-thunks-sucks)\n  - [REASON 3: They are still ducks](#reason-3-they-are-still-ducks)\n- [More than redux](#more-than-redux)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Redux\n\nPlease, refer to the [redux-egg](https://github.com/drpicox/redux-egg)\nto know how redux works with eggs.\n\n- https://github.com/drpicox/redux-egg/README.md\n\n## How an egg works?\n\nAn egg is function that receives an object with tools.\nUse those tools to create your eggs.\n\n```javascript\nexport const INCREMENT = '@my/counter/INCREMENT';\nexport const increment = () =\u003e ({ type: INCREMENT });\nexport const getCount = ({ '@my/counter': counter }) =\u003e counter;\n\nfunction counterReducer(state = 0, action) {\n  switch (action.type) {\n    case INCREMENT:\n      return state + 1;\n    default:\n      return state;\n  }\n}\n\nexport default function counterEgg({ combineReducer }) {\n  combineReducer('@my/counter', counterReducer);\n}\n```\n\n## How the redux-egg works?\n\nThe most simple redux-egg should look like:\n\n```javascript\nimport { createStore, combineReducers } from 'redux';\n\nexport default function storeEgg({ tool, breed }) {\n  const reducers = {};\n\n  tool('combineReducer', (name, reducer) =\u003e {\n    reducers[name] = reducer;\n  });\n\n  breed('store', () =\u003e createStore(combineReducers(reducers)));\n}\n```\n\n## Why are eggs better than ducks?\n\n### REASON 1: Combine eggs and solve dependencies\n\nDependencies are hard with ducks.\nEach duck is independent and must be configured independently.\nThe user of the duck must know its dependencies and any change will break\nan existing application.\n\nBut eggs solve the dependencies by themselves.\nTheir use ages of human thinking about what comes first ducks or eggs\nand they conclude that dependency inversion is cool.\nIf you have a dependency just use it.\n\n```javascript\nimport counterEgg, { INCREMENT } from 'counter-egg';\n\nexport const getParity = ({ '@my/parity': parity }) =\u003e parity;\n\nfunction parityReducer(state = true, action) {\n  switch (action.type) {\n    case INCREMENT:\n      return !state;\n    default:\n      return state;\n  }\n}\n\nfunction parityEgg({ combineReducer }) {\n  combineReducer('@my/parity', parityReducer);\n}\n\nexport default [counterEgg, parityEgg];\n```\n\nAnd you can forgot to include the dependency in your app.\n\n```javascript\nimport hatch from 'egg-hatchery';\nimport storeEgg from 'store-egg';\nimport { increment } from '@my/counter-egg';\nimport parityEgg, { getParity } from '@my/parity-egg';\n\ntest('the parity changes with increment', () =\u003e {\n  const { store } = hatch(storeEgg, counterEgg, parityEgg);\n  store.dispatch(increment());\n  expect(getParity(store.getState())).toBe(false);\n});\n```\n\nOr you can include it. It is not repeated.\n\n```javascript\nimport hatch from 'egg-hatchery';\nimport storeEgg from 'store-egg';\nimport counterEgg, { increment, getCount } from '@my/counter-egg';\nimport parityEgg, { getParity } from '@my/parity-egg';\n\ntest('the parity is still correct when the counter egg is added twice', () =\u003e {\n  const { store } = hatch(storeEgg, counterEgg, parityEgg);\n  store.dispatch(increment());\n  expect(getParity(store.getState())).toBe(false);\n});\n```\n\n### REASON 2: Thunks sucks\n\nWell, not exactly. There is one and only one reason to use a thunk: you need the state before dispatching a new action from a component. If you remember the redux connect, it does not inject the state into dispatcher properties. The thunk middleware gives you access to that state. That limitation was because of performance. Nowadays, you can use hooks, but they are still more efficient if you use thunks.\n\nThe problem is the frequent use of thunks: launch subsequent actions to complement the current one. We were all thrilled with the ping pong example, but it was a lousy example. When we do these kinds of concatenated actions, we are looking for repercussions of the current action. In our duck, thanks to action creators, we can decouple and maintain it easily. The problem is, what happens when we want to intercept an action from an external duck? We need to use middleware, a redux observable, a saga, or something similar, but ducks are not ready for them. Like the reducers, if a duck needs a middleware or an equivalent, we have to prepare it manually.\n\nThe fiveEgg:\n\n```javascript\nimport counterEgg, { getCount, INCREMENT } from 'counter-egg';\n\nexport const FIVE = '@my/counter/FIVE';\nexport const getFives = ({ '@my/five': five }) =\u003e five;\nconst five = () =\u003e ({ type: FIVE });\n\nfunction fiveReducer(state = 0, action) {\n  switch (action.type) {\n    case FIVE:\n      return state + 1;\n    default:\n      return state;\n  }\n}\n\nconst fiveMiddleware = store =\u003e next =\u003e action =\u003e {\n  next(action);\n  switch (action.type) {\n    case INCREMENT:\n      if (getCount(store.getState()) % 5 === 0) store.dispatch(five());\n    default:\n  }\n};\n\nfunction fiveEgg({ combineReducer, addMiddleware }) {\n  combineReducer('@my/five', fiveReducer);\n  addMiddleware(fiveMiddleware);\n}\n\nexport default [counterEgg, fiveEgg];\n```\n\nAnd how your program would look:\n\n```javascript\nimport hatch from 'egg-hatchery';\nimport storeEgg from 'store-egg';\nimport { increment } from '@my/counter-egg';\nimport fiveEgg, { getFives } from '@my/five-egg';\n\ntest('the five changes with increment', () =\u003e {\n  const { store } = hatch(storeEgg, fiveEgg);\n  store.dispatch(increment());\n  store.dispatch(increment());\n  store.dispatch(increment());\n  store.dispatch(increment());\n  store.dispatch(increment());\n  expect(getFives(store.getState())).toBe(1);\n});\n```\n\n### REASON 3: They are still ducks\n\nWell, they are almost ducks. There is only one change: instead of exporting by default, a reducer they export by default the egg. Everything else is the well-known old duck.\n\n## More than redux\n\nThe egg-hatchery is more about redux: it is a first-order dependency injection library. Look tests for more details at:\n\n- [egg](./src/__tests__/eggs.spec.js)\n- [breed](./src/__tests__/breeds.spec.js)\n- [tool](./src/__tests__/tools.spec.js)\n- [the breeds object](./src/__tests__/breedsObject.spec.js)\n- [rebreed](./src/__tests__/rebreed.spec.js)\n- [isHatching](./src/__tests__/isHatching.spec.js)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrpicox%2Fegg-hatchery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrpicox%2Fegg-hatchery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrpicox%2Fegg-hatchery/lists"}