{"id":30113744,"url":"https://github.com/lppedd/di-wise-neo","last_synced_at":"2026-02-04T01:14:19.207Z","repository":{"id":300475569,"uuid":"1006266646","full_name":"lppedd/di-wise-neo","owner":"lppedd","description":"Lightweight, type-safe, flexible dependency injection library for TypeScript and JavaScript","archived":false,"fork":false,"pushed_at":"2026-01-14T13:44:02.000Z","size":3575,"stargazers_count":4,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-14T17:33:19.074Z","etag":null,"topics":["decorators","dependency","dependency-injection","di","injection","ioc","javascript","js","ts","typescript"],"latest_commit_sha":null,"homepage":"https://lppedd.github.io/di-wise-neo/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"exuanbo/di-wise","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lppedd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-21T21:40:21.000Z","updated_at":"2026-01-14T13:42:58.000Z","dependencies_parsed_at":null,"dependency_job_id":"1ac1d55b-4fc5-4f93-85fc-905bc359ff0c","html_url":"https://github.com/lppedd/di-wise-neo","commit_stats":null,"previous_names":["lppedd/di-wise-neo"],"tags_count":54,"template":false,"template_full_name":null,"purl":"pkg:github/lppedd/di-wise-neo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lppedd%2Fdi-wise-neo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lppedd%2Fdi-wise-neo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lppedd%2Fdi-wise-neo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lppedd%2Fdi-wise-neo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lppedd","download_url":"https://codeload.github.com/lppedd/di-wise-neo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lppedd%2Fdi-wise-neo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478349,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"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":["decorators","dependency","dependency-injection","di","injection","ioc","javascript","js","ts","typescript"],"created_at":"2025-08-10T07:27:50.328Z","updated_at":"2026-01-16T11:36:02.293Z","avatar_url":"https://github.com/lppedd.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--suppress HtmlDeprecatedAttribute --\u003e\n\u003ch1 align=\"center\"\u003edi-wise-neo\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\nLightweight, type-safe, flexible dependency injection library for TypeScript and JavaScript\u003cbr\u003e\nwith a strong focus on type correctness and clear error reporting\n\u003c/p\u003e\n\u003cdiv align=\"center\"\u003e\n\n[![npm](https://img.shields.io/npm/v/@lppedd/di-wise-neo?color=%23de1f1f\u0026logo=npm)](https://www.npmjs.com/package/@lppedd/di-wise-neo)\n[![ecmascript](https://img.shields.io/badge/ES-2022-blue?logo=javascript)](https://en.wikipedia.org/wiki/ECMAScript_version_history#13th_edition_%E2%80%93_ECMAScript_2022)\n[![status](https://img.shields.io/badge/status-beta-AC29EC)](https://github.com/lppedd/di-wise-neo/blob/main/CHANGELOG.md#0100)\n[![build](https://img.shields.io/github/actions/workflow/status/lppedd/di-wise-neo/test.yml.svg?branch=main)](https://github.com/lppedd/di-wise-neo/actions/workflows/test.yml)\n[![coverage](https://img.shields.io/codecov/c/github/lppedd/di-wise-neo/main?token=R9XZFTQ0BA)](https://app.codecov.io/gh/lppedd/di-wise-neo/tree/main/src)\n[![minified size](https://img.shields.io/bundlejs/size/@lppedd/di-wise-neo)](https://bundlejs.com/?q=@lppedd/di-wise-neo)\n[![license](https://img.shields.io/badge/license-MIT-F7F7F7)](https://github.com/lppedd/di-wise-neo/blob/main/LICENSE)\n\n\u003c/div\u003e\n\u003cimg align=\"center\" src=\"./.github/images/neo-wall.jpg\" alt=\"di-wise-neo\" style=\"border: 3px solid black; border-radius: 15px;\" /\u003e\n\n\u003e [!NOTE]\n\u003e **di-wise-neo** is a fork of [di-wise][di-wise], aiming to provide a simpler yet more powerful API,\n\u003e in part thanks to TypeScript's experimental decorators. Shout out to [@exuanbo](https://github.com/exuanbo)\n\u003e for the strong foundations!\n\n### Table of Contents\n\n- [Installation](#installation)\n- [API reference](#api-reference)\n- [Ergonomics \u0026 Requirements](#ergonomics)\n- [Quickstart](#quickstart)\n- [Container scopes](#container-scopes)\n- [Token registration](#token-registration)\n- [Function-based injection](#function-based-injection)\n- [Decorator-based injection](#decorator-based-injection)\n- [Behavioral decorators](#behavioral-decorators)\n- [Testing support](#testing-support)\n\n### Why yet another library\n\nI've been developing VS Code extensions for a while as part of my daily work.\nIt's enjoyable work! However, extensions always reach that tipping point where\nfeature bloat, and the many different UI interactions which arise from that,\nmake writing, reading, and understanding the codebase a challenge.\n\nPart of the problem is the crazy amount of parameter passing, and the many exported\nglobal values floating around waiting to be imported and to generate yet another\ncoupling point.\n\nMy background with Java is full of such cases that have been (partially) mitigated\nby introducing dependency-injection libraries based on Java's powerful Contexts and\nDependency Injection (see [Weld][cdi-weld], the reference implementation).\n\nSo why not apply the same concept to our TypeScript projects?  \nI've posted on Reddit just to get a feel of what the ecosystem offers, and was\npointed to libraries such as [tsyringe][tsyringe], [InversifyJS][InversifyJS], or [Awilix][Awilix].\nI've also explored on my own and discovered [redi][redi] and [di-wise][di-wise].\n\nWhat I was looking for is a lightweight solution that offers:\n\n- Full type safety.\n- Scoped resolution of dependencies.\n- Optional decorator support for constructor and method injection.  \n  Yes, I know, forget type-safety with decorators, but they are extremely intuitive to pick up for Java devs.\n- No dependency on [reflect-metadata][reflect-metadata], as I'm an ESBuild user and ESBuild\n  [does not][esbuild-issue] support `emitDecoratorMetadata`.\n\nUnfortunately, both [tsyringe][tsyringe] and [InversifyJS][InversifyJS] require\n[reflect-metadata][reflect-metadata] to run correctly. [Awilix][Awilix] looks good,\nbut it's probably too much for what I need to do, and it does not support decorators.\nPlus, the API just didn't click for me.\n\n[redi][redi] focuses _only_ on constructor injection via decorators, which is nice.\nHowever, it falls short when it comes to type safety and resolution scopes:\nit only supports singletons with a decorator-based trick to create fresh instances.\n\nAnd lastly, [di-wise][di-wise]. This small library was quite a surprise! Easy to pick up,\nno scope creep, injection context support, and full type safety via Angular-like\n`inject\u003cT\u003e()` functions (that's more like a service locator, but whatever).\nThe only problems are the slightly overcomplicated API - especially regarding typings - and\nthe use of ECMAScript Stage 3 decorators, which do not support decorating method parameters :sob:\n\nSo what's the right move? Forking the best pick and refactoring it to suite my\nproduction needs.\n\n### Installation\n\n```sh\nnpm i @lppedd/di-wise-neo\n```\n\n```sh\npnpm add @lppedd/di-wise-neo\n```\n\n```sh\nyarn add @lppedd/di-wise-neo\n```\n\n### API reference\n\nYou can find the complete API reference at [lppedd.github.io/di-wise-neo][di-wise-neo].\n\n### Ergonomics\n\n- Does **not** depend on other libraries.\n- Does **not** use [reflect-metadata][reflect-metadata] to drive decorators.\n- **Can** be used from JavaScript with function-based injection.\n\n### Requirements\n\n- The JS environment must support features such as `Array.at`, `Array.flat`, `WeakRef`, `WeakMap`, `Set`, `Map`.\n\n#### Decorator-based injection\n\n- When using decorator-based injection, `experimentalDecorators` must be enabled in your `tsconfig.json` file.\n\n## Quickstart\n\n```ts\n//\n// A couple of classes to cover the example\n//\n\nexport class ExtensionContext { /* ... */ }\n\n// Both the secret store and the contribution registrar\n// require the extension context to read and set values\n\nexport class SecretStore {\n  // We can use function-based injection, which gives us type safety\n  readonly context = inject(ExtensionContext);\n\n  // Or even\n  // constructor(readonly context = inject(ExtensionContext)) {}\n\n  /* ... */\n}\n\nexport class ContributionRegistrar {\n  // We can also opt to use decorator-based constructor injection\n  constructor(@Inject(ExtensionContext) readonly context: ExtensionContext) {}\n\n  /* ... */\n\n  // Or method injection. The @Optional decorator injects \"T | undefined\".\n  protected withSecretStore(@Optional(SecretStore) store: SecretStore | undefined): void {\n    if (store?.isSet(\"key\")) {\n      /* ... */\n    }\n  }\n}\n\n//\n// Using di-wise-neo\n//\n\n// Create a new DI container\nconst container = createContainer({\n  // Optionally override the default \"Transient\" registration scope.\n  // I prefer to use \"Container\" (a.k.a. Singleton) scope, but \"Transient\" is the better default.\n  defaultScope: \"Container\",\n});\n\n// Register our managed dependencies in the container\ncontainer.register(ExtensionContext)\n         .register(SecretStore)\n         .register(ContributionRegistrar);\n\n// Get the contribution registrar.\n// The container will create a new managed instance for us, with all dependencies injected.\nconst registrar = container.resolve(ContributionRegistrar);\nregistrar.registerCommand(\"my.command\", () =\u003e { console.log(\"hey!\"); });\n```\n\n## Container scopes\n\nThe [Container][source-container] supports three **scope** types that determine how and when\nvalues are cached and reused.\n\n### Transient\n\nCreates a new value every time the dependency is resolved, which means values are never cached.\n\n- A class registered via `ClassProvider` is instantiated on each resolution\n- A factory function registered via `FactoryProvider` is invoked on each resolution\n- A value registered via `ValueProvider` is always returned as-is\n\n\u003e [!NOTE]\n\u003e When a **Transient** or **Resolution**-scoped value is injected into a **Container**-scoped\n\u003e instance, it effectively inherits the lifecycle of that instance. The value will live as long\n\u003e as the containing instance, even though it is not cached by the container itself.\n\n### Resolution\n\nCreates and caches a single value per resolution graph.  \nThe same value is reused during a single resolution request, but a new one is created for each request.\n\n### Container\n\nCreates and caches a single value per container.  \nIf the value is not found in the current container, it is looked up in the parent container,\nand so on.\n\nIt effectively behaves like a **singleton** scope but allows container-specific overrides.\n\n## Token registration\n\nThe container allows registering tokens via _providers_. The generic usage is:\n\n```ts\ncontainer.register(/* ... */);\n```\n\nAn explicit **scope** can be specified using the third argument, when applicable.  \nIf omitted, the default scope is **Transient**.\n\n```ts\ncontainer.register(token, provider, { scope: \"Resolution\" });\n```\n\n### ClassProvider\n\nYou can register a class by passing it directly to the `register` method:\n\n```ts\ncontainer.register(SecretStore);\n```\n\nAlternatively, use an explicit `ClassProvider` object - useful when registering\nan interface or abstract type:\n\n```ts\nconst IStore = createType\u003cStore\u003e(\"Store\");\ncontainer.register(IStore, {\n  useClass: SecretStore, // class SecretStore implements Store\n});\n```\n\nUpon resolving `IStore` - which represents the `Store` interface - the container\ncreates an instance of `SecretStore`, caching it according to the configured scope.\n\n### FactoryProvider\n\nA lazily computed value can be registered using a factory function:\n\n```ts\nconst Env = createType\u003cstring\u003e(\"Env\")\ncontainer.register(Env, {\n  useFactory: () =\u003e isNode() ? \"Node.js\" : \"browser\",\n});\n```\n\nThe factory function is invoked upon token resolution, and its result is cached\naccording to the configured scope.\n\n### ValueProvider\n\nA fixed value - always taken as-is and unaffected by scopes - can be registered using:\n\n```ts\nconst PID = createType\u003cnumber\u003e(\"PID\");\nconst processId = spawnProcess();\ncontainer.register(PID, {\n  useValue: processId,\n});\n```\n\nThis is especially useful when injecting third-party values that are not created\nthrough the DI container.\n\n### ExistingProvider\n\nRegisters an alias to another token, allowing multiple identifiers to resolve to the same value.  \nUsing the previous `PID` example, we can register a `TaskID` alias:\n\n```ts\nconst TaskID = createType\u003cnumber\u003e(\"TaskID\");\ncontainer.register(TaskID, {\n  useExisting: PID,\n});\n```\n\nThe container will translate `TaskID` to `PID` before resolving the value.\n\n## Function-based injection\n\nThe primary way to perform dependency injection in **di-wise-neo** is through\nfunctions like `inject(T)`, `injectAll(T)`, `optional(T)`, and `optionalAll(T)`.\n\n\u003e [!TIP]\n\u003e Using injection functions is recommended because it preserves type safety.\n\n### Injection context\n\nAll injection functions must be invoked inside an _injection context_, which stores\nthe currently active container.  \nThe _injection context_ is available in these situations:\n\n- Inside the `constructor` of a class instantiated by the DI container.\n- In property initializers of such classes.\n- Within factory functions used by `FactoryProvider`.\n- Within functions passed to `Injector.runInContext`.\n\n### `inject\u003cT\u003e(Token): T`\n\nInjects the value associated with a token, throwing an error if the token is not\nregistered in the container.\n\n```ts\nexport class ProcessManager {\n  constructor(readonly rootPID /*: number */ = inject(PID)) {}\n\n  /* ... */\n}\n```\n\nIf `PID` cannot be resolved, a resolution error with detailed information is thrown.\n\n### `injectAll\u003cT\u003e(Token): T[]`\n\nInjects all values associated with a token, throwing an error if the token has\nnever been registered in the container.\n\n```ts\nexport class ExtensionContext {\n  readonly stores /*: Store[] */ = injectAll(IStore);\n\n  /* ... */\n\n  clearStorage(): void {\n    this.stores.forEach((store) =\u003e store.clear());\n  }\n}\n```\n\n### `optional\u003cT\u003e(Token): T`\n\nInjects the value associated with a token, or `undefined` if the token is not\nregistered in the container.\n\n```ts\nexport class ProcessManager {\n  constructor(readonly rootPID /*: number | undefined */ = optional(PID)) {}\n\n  /* ... */\n}\n```\n\n### `optionalAll\u003cT\u003e(Token): T[]`\n\nInjects all values associated with a token or an **empty array** if the token\nhas never been registered in the container.\n\n```ts\nexport class ExtensionContext {\n  // The type does not change compared to injectAll(T), but the call does not fail\n  readonly stores /*: Store[] */ = optionalAll(IStore);\n\n  /* ... */\n}\n```\n\n## Decorator-based injection\n\nYou can also perform dependency injection using TypeScript's experimental decorators.  \n**di-wise-neo** supports decorating constructor and instance method parameters.\n\n\u003e [!NOTE]\n\u003e Property injection is **not** supported by design.\n\n### `@Inject(Token)`\n\nInjects the value associated with a token, throwing an error if the token is not\nregistered in the container.\n\n```ts\nexport class ProcessManager {\n  constructor(@Inject(PID) readonly rootPID: number) {}\n\n  /* ... */\n\n  // The method is called immediately after instance construction\n  notifyListener(@Inject(ProcessListener) listener: ProcessListener): void {\n    listener.processStarted(this.rootPID);\n  }\n}\n```\n\nIf `PID` cannot be resolved, a resolution error with detailed information is thrown.\n\n### `@InjectAll(Token)`\n\nInjects all values associated with a token, throwing an error if the token has\nnever been registered in the container.\n\n```ts\nexport class ExtensionContext {\n  constructor(@InjectAll(IStore) readonly stores: Store[]) {}\n\n  /* ... */\n\n  clearStorage(): void {\n    this.stores.forEach((store) =\u003e store.clear());\n  }\n}\n```\n\n### `@Optional(Token)`\n\nInjects the value associated with a token, or `undefined` if the token is not\nregistered in the container.\n\n```ts\nexport class ProcessManager {\n  constructor(@Optional(PID) readonly rootPID: number | undefined) {}\n\n  /* ... */\n}\n```\n\n### `@OptionalAll(Token)`\n\nInjects all values associated with a token or an **empty array** if the token\nhas never been registered in the container.\n\n```ts\nexport class ExtensionContext {\n  // The type does not change compared to @InjectAll, but construction does not fail\n  constructor(@OptionalAll(IStore) readonly stores: Store[]) {}\n\n  /* ... */\n}\n```\n\n### Forward references\n\nSometimes you may need to reference a token or class declared later in the file.  \nNormally, attempting to do that would result in a `ReferenceError`:\n\n\u003e ReferenceError: Cannot access 'IStore' before initialization\n\nWe can work around this problem by using the `tokenRef` helper function:\n\n```ts\nexport class ExtensionContext {\n  constructor(@OptionalAll(tokenRef(() =\u003e IStore)) readonly stores: Store[]) {}\n\n  /* ... */\n}\n```\n\n## Behavioral decorators\n\nThe library includes four behavioral decorators that influence how classes are registered in the container.\nThese decorators attach metadata to the class type, which is then interpreted by the container during registration.\n\n### `@Scoped`\n\nSpecifies a default scope for the decorated class:\n\n```ts\n@Scoped(\"Container\")\nexport class ExtensionContext {\n  /* ... */\n}\n```\n\nApplying `@Scoped(\"Container\")` to the `ExtensionContext` class instructs the DI container\nto register it with the **Container** scope by default.\n\nThis default can be overridden by explicitly providing registration options:\n\n```ts\ncontainer.register(\n  ExtensionContext,\n  { useClass: ExtensionContext },\n  { scope: \"Resolution\" },\n);\n```\n\nIn this example, `ExtensionContext` will be registered with **Resolution** scope instead.\n\n### `@Named`\n\nMarks a class or injected dependency with a unique name (qualifier), allowing the container\nto distinguish between multiple implementations of the same type.\n\n```ts\n@Named(\"persistent\")\n@Scoped(\"Container\")\nexport class PersistentSecretStorage implements SecretStorage {\n  /* ... */\n}\n\n// Register the class with Type\u003cSecretStorage\u003e.\n// The container will automatically qualify the registration with 'persistent'.\ncontainer.register(ISecretStorage, { useClass: PersistentSecretStorage });\n\n// Inject the SecretStorage dependency by name\nexport class ExtensionContext {\n  constructor(@Inject(ISecretStorage) @Named(\"persistent\") readonly secretStorage: SecretStorage) {}\n\n  /* ... */\n}\n```\n\nThe container will throw an error at registration time if the name is already taken by another registration.\n\n### `@AutoRegister`\n\nEnables automatic registration of the decorated class when it is resolved if it has not\nbeen registered beforehand.\n\n```ts\n@AutoRegister()\nexport class ExtensionContext {\n  /* ... */\n}\n\n// Resolve the class without prior registration. It works!\ncontainer.resolve(ExtensionContext);\n```\n\n### `@EagerInstantiate`\n\nSets the default class scope to **Container** and marks the class for eager instantiation upon registration.\n\nThis causes the container to immediately create and cache the instance of the class\nat registration time, instead of deferring instantiation until the first resolution.\n\n```ts\n@EagerInstantiate()\nexport class ExtensionContext {\n  /* ... */\n}\n\n// ExtensionContext is registered with Container scope,\n// and an instance is immediately created and cached by the container\ncontainer.register(ExtensionContext);\n```\n\n\u003e [!WARNING]\n\u003e Eager instantiation requires that all dependencies of the class are already registered in the container.  \n\u003e If they are not, registration will fail.\n\n## Testing support\n\nTesting is an important part of software development, and dependency injection is meant to make it easier.  \nThe container API exposes methods to more easily integrate with testing scenarios.\n\n### `resetRegistry`\n\nRemoves all registrations from the container's internal registry, effectively resetting it to its initial state.  \nThis is useful for ensuring isolation between tests.\n\n```ts\ndescribe(\"My test suite\", () =\u003e {\n  const container = createContainer();\n\n  afterEach(() =\u003e {\n    container.resetRegistry();\n  });\n\n  /* ... */\n});\n```\n\n### `dispose`\n\nAnother way to ensure isolation between tests is to completely replace the DI container after each test run.  \nThe **di-wise-neo** container supports being _disposed_, preventing further registrations or resolutions.\n\n```ts\ndescribe(\"My test suite\", () =\u003e {\n  let container = createContainer();\n\n  afterEach(() =\u003e {\n    container.dispose();\n    container = createContainer();\n  });\n\n  /* ... */\n});\n```\n\n## Credits\n\n**di-wise-neo** is a fork of [di-wise][di-wise].  \nAll credits to the original author for focusing on a clean architecture and on code quality.\n\n## License\n\n[MIT license](https://github.com/lppedd/di-wise-neo/blob/main/LICENSE)\n\n2025-present [Edoardo Luppi](https://github.com/lppedd)  \n2024-2025 [Xuanbo Cheng](https://github.com/exuanbo)\n\n\u003c!-- @formatter:off --\u003e\n[cdi-weld]: https://weld.cdi-spec.org\n[tsyringe]: https://github.com/microsoft/tsyringe\n[Awilix]: https://github.com/jeffijoe/awilix\n[InversifyJS]: https://github.com/inversify/InversifyJS\n[redi]: https://github.com/wzhudev/redi\n[di-wise]: https://github.com/exuanbo/di-wise\n[di-wise-neo]: https://lppedd.github.io/di-wise-neo\n[reflect-metadata]: https://github.com/microsoft/reflect-metadata\n[esbuild-issue]: https://github.com/evanw/esbuild/issues/257\n[source-container]: https://github.com/lppedd/di-wise-neo/blob/main/src/container.ts#L29\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flppedd%2Fdi-wise-neo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flppedd%2Fdi-wise-neo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flppedd%2Fdi-wise-neo/lists"}