{"id":21648691,"url":"https://github.com/phallguy/scorpion-ioc-js","last_synced_at":"2026-05-10T02:36:34.758Z","repository":{"id":138306332,"uuid":"146670697","full_name":"phallguy/scorpion-ioc-js","owner":"phallguy","description":"Simple IoC for node","archived":false,"fork":false,"pushed_at":"2018-09-12T16:44:35.000Z","size":251,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-23T21:36:53.904Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://phallguy.github.io/scorpion-ioc-js/","language":"TypeScript","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/phallguy.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-29T23:39:05.000Z","updated_at":"2018-09-12T16:44:36.000Z","dependencies_parsed_at":"2023-06-10T15:45:41.871Z","dependency_job_id":null,"html_url":"https://github.com/phallguy/scorpion-ioc-js","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phallguy%2Fscorpion-ioc-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phallguy%2Fscorpion-ioc-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phallguy%2Fscorpion-ioc-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phallguy%2Fscorpion-ioc-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phallguy","download_url":"https://codeload.github.com/phallguy/scorpion-ioc-js/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244538311,"owners_count":20468682,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-11-25T07:00:00.932Z","updated_at":"2026-05-10T02:36:29.715Z","avatar_url":"https://github.com/phallguy.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Add IoC to TypeScript projects in node with minimal fuss and ceremony.\n\n[![Package Version](https://badge.fury.io/js/scorpion-ioc.svg)](https://www.npmjs.com/package/scorpion-ioc)\n[![GitHub version](https://badge.fury.io/gh/phallguy%2Fscorpion-ioc-js.svg)](https://badge.fury.io/gh/phallguy%2Fscorpion-ioc-js)\n[![Circle CI](https://circleci.com/gh/phallguy/scorpion-ioc-js.svg?style=svg)](https://circleci.com/gh/phallguy/scorpion-ioc-js)\n\n\u003c!-- vim-markdown-toc GFM --\u003e\n\n* [Dependency Injection](#dependency-injection)\n  * [Why might you _Want_ a DI FRamework?](#why-might-you-_want_-a-di-framework)\n    * [Property/Default Injection](#propertydefault-injection)\n    * [Constructor/Ignorant Injection](#constructorignorant-injection)\n  * [Using a Framework...like Scorpion](#using-a-frameworklike-scorpion)\n* [Getting Started](#getting-started)\n* [Using Scorpion](#using-scorpion)\n  * [Decoration](#decoration)\n  * [Configuration](#configuration)\n    * [Builders](#builders)\n    * [Singletons and Object Lifetimes](#singletons-and-object-lifetimes)\n    * [Nests](#nests)\n  * [Contributing](#contributing)\n  * [License](#license)\n\n\u003c!-- vim-markdown-toc --\u003e\n\n# Dependency Injection\n\nDependency injection helps to break explicit dependencies between objects making\nit much easier to maintain a [single\nresponsibility](https://en.wikipedia.org/wiki/Single_responsibility_principle)\nand reduce [coupling](https://en.wikipedia.org/wiki/Coupling_(computer_programming))\nin our class designs. This leads to more testable code and code that is more\nresilient to change.\n\nMost arguments for or against DI focus on testing, and given how easy it is to\nmock objects in JavaScript, you don't really need a framework. If testing were the\nonly virtue they'd be spot on. Despite its virtues DI doesn't come without its\nown problems. However for larger projects that you expect to be long-lived, a DI\nframework may help manage the complexity.\n\nFor a deeper background on Dependency Injection consider the\n[Wikipedia](https://en.wikipedia.org/wiki/Dependency_injection) article on the\nsubject.\n\n## Why might you _Want_ a DI FRamework?\n\nAssuming you've embraced the general concept of DI why would you want to use a\nframework. Lets consider the alternatives.\n\n### Property/Default Injection\n\n```typescript\nclass Hunter {\n  private weapon: Weapon = new Weapon()\n}\n```\n\nIn this scenario the Hunter class knows how to create a weapon and provides a\nsane default, but allows the dependency to be overridden if needed.\n\n**PROS**\n\n- Very simple to understand and debug.\n- Provides basic flexibility.\n- The dependency is clearly defined.\n\n**CONS**\n\n- Still coupled to a specific _type_ of Weapon.\n- If multiple classes use this approach and you decide to upgrade your armory,\n  you'd have to modify every line that creates new weapons. The factory pattern\n  can be used to solve that problem.\n- No global method of replacing a Weapon class with a specialized or\n  instrumented version. For example a ThreadLockedWeapon.\n\n### Constructor/Ignorant Injection\n\n```typescript\nclass Hunter {\n  constructor( private readonly weapon: Weapon ) {}\n}\n```\n\nHere Hunters can use any weapon and can be designed to an interface Weapon that\ndoes not have an implementation yet.\n\n**PROS**\n\n- Provides flexibility\n- Work can proceed concurrently on Hunter and Weapon classes by different\n  engineers  on the team.\n\n**CONS**\n\n- Hard to reason about Hunters and Weapons as a whole.\n- It pushes the responsibility of constructing dependencies onto the consumer of\n  the class. If the class is used in multiple places this becomes a maintenance\n  chore when changes are required.\n- It becomes tedious to use classes resulting in repeated boilerplate code that\n  distracts from the primary responsibility of the calling code.\n\n\n## Using a Framework...like Scorpion\n\nUsing a good framework preserves the benefits of each method while minimizing\nthe cons. A DI framework works like an automatic factory system resolving\ndependencies cleanly like a factory but without all the effort to create custom\nfactories.\n\nA good framework should\n\n- Make dependencies clear\n- Require a minimal amount of configuration or ceremony\n\n```typescript\nclass Hunter {\n  // Must await to access injected resource\n  @Inject private weapon?: Promise\u003cWeapon\u003e\n\n  // or use constructor that always receives resolved instances\n  constructor( @Inject private weapon: Weapon ) {}\n}\n```\n\nHere the dependency is clearly defined - and even creates accessors for getting\nand setting the weapon. When a Hunter is created its dependencies are also\ncreated - and any of their dependencies and so on. Usage is equally simple\n\n```typescript\nconst hunter = await scorpion.fetch( Hunter )\nhunter.weapon   // =\u003e a Weapon\n```\n\nOverriding the kind of weapons used by hunters.\n\n```typescript\nclass Axe extends Weapon {}\n\nscorpion.prepare(map =\u003e {\n  map.bind(Axe)\n})\n\nhunter = await scorpion.fetch( Hunter )\nhunter.weapon // =\u003e an Axe\n```\n\nOverriding hunters!\n\n```typescript\nclass Axe extends Weapon {}\nclass Predator extends Hunter {}\n\nscorpion.prepare(map =\u003e {\n  map.bind(Predator)\n  map.bind(Axe)\n})\n\nhunter = await scorpion.fetch( Hunter )\nhunter        // =\u003e Predator\nhunter.weapon // =\u003e an Axe\n```\n\n# Getting Started\n\nAdd scorpion to your project\n\n```\nnpm install scorpion-ioc\n\n# or using yarn\nyarn add scorpion-ioc\n```\n\n# Using Scorpion\n\nOut of the box Scorpion does not need any configuration and will work\nimmediately. You can hunt for any Class even if it hasn't been configured.\n\n```typescript\n  const now = await scorpion.fetch( Date )\n  now // =\u003e Date\n```\n\n## Decoration\n\nScorpions feed their prey - any object that should be fed its dependencies when\nit is created. Simply add the [[Inject @Inject]] annotation for any dependency\nthat you want resolved.\n\n```typescript\nclass Keeper {\n  constructor( @Inject private readonly lunch?: FastFood ) {}\n}\n\nclass Vet {}\n\nclass Zoo {\n  constructor(\n    @Inject private readonly keeper: Keeper,\n    @Inject private readonly vet: Vet,\n  ) {}\n}\n\nconst zoo = await scorpion.fetch( Zoo )\nzoo.keeper       // =\u003e an instance of a Keeper\nzoo.vet          // =\u003e an instance of a Vet\nzoo.keeper.lunch // =\u003e an instance of FastFood\n```\n\nAll of your classes should be objects! And any dependency that is also an Object will\nbe fed.\n\n## Configuration\n\nA good scorpion should be prepared to hunt. An effort that describes _what_ the\nscorpion can find for and _how_ it should be found. Scorpion uses Classes as\nthe primary means of identifying dependency in favor of opaque labels or\nstrings.  This serves two benefits:\n\n1. The type of object expected by the dependency is clearly identified making it\n   easier to understand what the concrete dependencies really are.\n2. Types explicitly declare the expected behavioral contract of an object's\n   dependencies.\n\nMost scorpion hunts will be for an instance of a specific class (or a more\nderived class). If you bind a more concrete implementation and ask for the base\nclass, the more concrete version will be used.\n\n```typescript\nclass User {}\nclass Employee extends User {}\n\nawait scorpion.fetch( User )   // =\u003e new User()\n\nscorpion.prepare( map =\u003e {\n  map.bind( Employee )\n})\n\nawait scorpion.fetch( User )   // =\u003e Employee.new()\n```\n\n### Builders\n\nSometimes resolving the correct dependencies is a bit more dynamic. In those\ncases you can use a builder block to hunt for dependency.\n\n```typescript\nclass Sword {}\nclass Samurai extends Sword {}\nclass Broad extends Sword {}\n\nscorpion.prepare( map =\u003e {\n  map.bind( Sword, async (fetcher, ...args) =\u003e\n    scorpion.fetch( Math.random() * 2 \u003e 1 ? Samurai : Broad )\n  )\n})\n```\n\nObjects may also define their own static `.create` methods that receive a\n[[Fetcher fetcher]] and arguments.\n\n```typescript\nclass City {\n  static async create( fetcher, name ): Promise\u003cCity\u003e {\n    let klass\n\n    if( name == \"New York\" ) {\n      klass = BigCity\n    } else {\n      klass = SmallCity\n    }\n\n    return fetcher.fetch( klass, name )\n  }\n\n  constructor( private readonly name: string ) {}\n}\n\nclass BigCity extends City {}\nclass SmallCity extends City {}\n```\n\n\n### Singletons and Object Lifetimes\n\nScorpion allows you to capture dependency and feed the same instance to everyone that\nasks for a matching dependency.\n\nDI singletons are different then global singletons in that each scorpion can\nhave a unique instance of the class that it shares with all of its objects. This\nallows, for example, global variable like support per HTTP request without polluting\nthe global namespace or dealing with thread concurrency issues.\n\n```typescript\nclass Logger {}\n\nscorpion.prepare( map =\u003e {\n  map.capture( Logger )\n}\n\nawait scorpion.fetch( Logger ) // =\u003e Logger.new\nawait scorpion.fetch( Logger ) // =\u003e Previously captured logger\n```\n\n\u003e Captured dependencies are not shared with child scorpions (for example when\n\u003e conceiving scorpions from a [[Nest]]. To share captured dependency with\n\u003e children use [[BindingMap.share share]].\n\n### Nests\n\nA scorpion nest is where a mother scorpion lives and conceives young -\nduplicates of the mother but maintaining their own captured singletons. You\nmight prepare a module scoped nest and then [[Nest.conceive conceive]] a new\nScorpion for each request. That way all preparation  performed by the mother is\nshared with all the children it conceives so that configuration is established\nwhen the application starts.\n\n```typescript\nconst Logger {}\nconst SystemLogger extends Logger {}\n\nconst nest = new Nest( map =\u003e {\n  map.bind( SystemLogger )\n})\n\n// In HTTP request startup code\nawait scorpion = nest.conceive()\nawait scorpion.fetch( Logger  ) // =\u003e SystemLogger.new\n```\n\n## Contributing\n\n1. Fork it ( https://github.com/phallguy/scorpion-js/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n\n## License\n\n[The MIT License (MIT)](http://opensource.org/licenses/MIT)\n\nCopyright (c) 2018 Paul Alexander\n\n[@phallguy](http://twitter.com/phallguy) / http://phallguy.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphallguy%2Fscorpion-ioc-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphallguy%2Fscorpion-ioc-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphallguy%2Fscorpion-ioc-js/lists"}