{"id":13469459,"url":"https://github.com/zheksoon/dioma","last_synced_at":"2025-04-05T17:02:55.202Z","repository":{"id":229035576,"uuid":"775592537","full_name":"zheksoon/dioma","owner":"zheksoon","description":"Elegant dependency injection container for vanilla JavaScript and TypeScript","archived":false,"fork":false,"pushed_at":"2024-04-26T03:15:24.000Z","size":246,"stargazers_count":240,"open_issues_count":5,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-17T19:49:27.365Z","etag":null,"topics":["1kb","awilix","dependency-injection","di","di-container","dioma","inversify","inversion-of-control","ioc","ioc-container","javascript","tiny","tsyringe","typescript"],"latest_commit_sha":null,"homepage":"","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/zheksoon.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":"2024-03-21T17:12:32.000Z","updated_at":"2025-03-15T10:50:07.000Z","dependencies_parsed_at":"2024-04-02T17:27:33.224Z","dependency_job_id":"1d43d7e3-34f7-46d1-8330-0ebd760f6bb3","html_url":"https://github.com/zheksoon/dioma","commit_stats":null,"previous_names":["zheksoon/dioma"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zheksoon%2Fdioma","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zheksoon%2Fdioma/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zheksoon%2Fdioma/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zheksoon%2Fdioma/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zheksoon","download_url":"https://codeload.github.com/zheksoon/dioma/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247369950,"owners_count":20927928,"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":["1kb","awilix","dependency-injection","di","di-container","dioma","inversify","inversion-of-control","ioc","ioc-container","javascript","tiny","tsyringe","typescript"],"created_at":"2024-07-31T15:01:40.844Z","updated_at":"2025-04-05T17:02:55.158Z","avatar_url":"https://github.com/zheksoon.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eDioma\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e  \n  \u003cimg src=\"https://github.com/zheksoon/dioma/blob/main/assets/dioma-logo.webp?raw=true\" alt=\"dioma\" width=\"200\" /\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eElegant dependency injection container for vanilla JavaScript and TypeScript\u003c/b\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"NPM Version\" src=\"https://img.shields.io/npm/v/dioma?style=flat-square\u0026color=%2364d4c1\u0026link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fdioma\"\u003e\n  \u003cimg alt=\"NPM package gzipped size\" src=\"https://img.shields.io/bundlejs/size/dioma?style=flat-square\u0026label=gzip\u0026color=%2364d4c1\"\u003e\n  \u003cimg alt=\"Codecov\" src=\"https://img.shields.io/codecov/c/github/zheksoon/dioma?style=flat-square\u0026color=%2364d4c1\"\u003e\n\u003c/p\u003e\n\n## Features\n\n- \u003cb\u003eJust do it\u003c/b\u003e - no decorators, no annotations, no magic\n- \u003cb\u003eTokens\u003c/b\u003e for class, value, and factory injection\n- \u003cb\u003eAsync\u003c/b\u003e injection and dependency cycle detection\n- \u003cb\u003eTypeScript\u003c/b\u003e support\n- \u003cb\u003eNo\u003c/b\u003e dependencies\n- \u003cb\u003eTiny\u003c/b\u003e size\n\n## Installation\n\n```sh\nnpm install --save dioma\n\nyarn add dioma\n```\n\n## Usage\n\nTo start injecting dependencies, you just need to add the `static scope` property to your class and use the `inject` function to get the instance of it. By default, `inject` makes classes \"stick\" to the container where they were first injected (more details in the [Class registration](#Class-registration) section).\n\nHere's an example of using it for [Singleton](#singleton-scope) and [Transient](#transient-scope) scopes:\n\n```typescript\nimport { inject, Scopes } from \"dioma\";\n\nclass Garage {\n  open() {\n    console.log(\"garage opened\");\n  }\n\n  // Single instance of the class for the entire application\n  static scope = Scopes.Singleton();\n}\n\nclass Car {\n  // injects instance of Garage\n  constructor(private garage = inject(Garage)) {}\n\n  park() {\n    this.garage.open();\n    console.log(\"car parked\");\n  }\n\n  // New instance of the class on every injection\n  static scope = Scopes.Transient();\n}\n\n// Creates a new Car and injects Garage\nconst car = inject(Car);\n\ncar.park();\n```\n\n## Scopes\n\nDioma supports the following scopes:\n\n- `Scopes.Singleton()` - creates a single instance of the class\n- `Scopes.Transient()` - creates a new instance of the class on every injection\n- `Scopes.Container()` - creates a single instance of the class per container\n- `Scopes.Resolution()` - creates a new instance of the class every time, but the instance is the same for the entire resolution\n- `Scopes.Scoped()` is the same as `Scopes.Container()`\n\n### Singleton scope\n\nSingleton scope creates a single instance of the class for the entire application.\nThe instances are stored in the global container, so anyone can access them.\nIf you want to isolate the class to a specific container, use the [Container](#Container-scope) scope.\n\nA simple example you can see in the [Usage](#Usage) section.\n\nMultiple singletons can be cross-referenced with each other using [async injection](#async-injection-and-circular-dependencies).\n\n### Transient scope\n\nTransient scope creates a new instance of the class on every injection:\n\n```typescript\nimport { inject, Scopes } from \"dioma\";\n\nclass Engine {\n  start() {\n    console.log(\"Engine started\");\n  }\n\n  static scope = Scopes.Singleton();\n}\n\nclass Vehicle {\n  constructor(private engine = inject(Engine)) {}\n\n  drive() {\n    this.engine.start();\n    console.log(\"Vehicle driving\");\n  }\n\n  static scope = Scopes.Transient();\n}\n\n// New vehicle every time\nconst vehicle = inject(Vehicle);\n\nvehicle.drive();\n```\n\nGenerally, transient scope instances can't be cross-referenced by the [async injection](#Async-injection-and-injection-cycles) with some exceptions.\n\n### Container scope\n\nContainer scope creates a single instance of the class per container. It's the same as the singleton, but relative to the custom container.\n\nThe usage is the same as for the singleton scope, but you need to create a container first and use `container.inject` instead of `inject`:\n\n```typescript\nimport { Container, Scopes } from \"dioma\";\n\nconst container = new Container();\n\nclass Garage {\n  open() {\n    console.log(\"garage opened\");\n  }\n\n  // Single instance of the class for the container\n  static scope = Scopes.Container();\n}\n\n// Register Garage on the container\ncontainer.register({ class: Garage });\n\nclass Car {\n  // Use inject method of the container for Garage\n  constructor(private garage = container.inject(Garage)) {}\n\n  park() {\n    this.garage.open();\n    console.log(\"car parked\");\n  }\n\n  // New instance on every injection\n  static scope = Scopes.Transient();\n}\n\nconst car = container.inject(Car);\n\ncar.park();\n```\n\nContainer-scoped classes usually are [registered in the container](#class-registration) first. Without it, the class will \"stick\" to the container it's used in.\n\n### Resolution scope\n\nResolution scope creates a new instance of the class every time, but the instance is the same for the entire resolution:\n\n```typescript\nimport { inject, Scopes } from \"dioma\";\n\nclass Query {\n  static scope = Scopes.Resolution();\n}\n\nclass RequestHandler {\n  constructor(public query = inject(Query)) {}\n\n  static scope = Scopes.Resolution();\n}\n\nclass RequestUser {\n  constructor(\n    public request = inject(RequestHandler),\n    public query = inject(Query)\n  ) {}\n\n  static scope = Scopes.Transient();\n}\n\nconst requestUser = inject(RequestUser);\n\n// The same instance of Query is used for each of them\nrequestUser.query === requestUser.request.query;\n```\n\nResolution scope instances can be cross-referenced by the [async injection](#async-injection-and-circular-dependencies) without any issues.\n\n## Injection with arguments\n\nYou can pass arguments to the constructor when injecting a class:\n\n```typescript\nimport { inject, Scopes } from \"dioma\";\n\nclass Owner {\n  static scope = Scopes.Singleton();\n\n  petSomebody(pet: Pet) {\n    console.log(`${pet.name} petted`);\n  }\n}\n\nclass Pet {\n  constructor(public name: string, public owner = inject(Owner)) {}\n\n  pet() {\n    this.owner.petSomebody(this);\n  }\n\n  static scope = Scopes.Transient();\n}\n\nconst pet = inject(Pet, \"Fluffy\");\n\npet.pet(); // Fluffy petted\n```\n\nOnly transient and resolution scopes support argument injection.\nResolution scope instances are cached for the entire resolution, so the arguments are passed only once.\n\n## Class registration\n\nBy default, `Scopes.Container` class injection is \"sticky\" - the class sticks to the container where it was first injected.\n\nIf you want to make a class save its instance in some specific parent container (see [Child containers](#Child-containers)), you can use class registration:\n\n```typescript\nconst container = new Container();\n\nconst child = container.childContainer();\n\nclass FooBar {\n  static scope = Scopes.Container();\n}\n\n// Register the Foo class in the parent container\ncontainer.register({ class: FooBar });\n\n// Returns and cache the instance on parent container\nconst foo = container.inject(FooBar);\n\n// Returns the FooBar instance from the parent container\nconst bar = child.inject(FooBar);\n\nfoo === bar; // true\n```\n\nYou can override the scope of the registered class:\n\n```typescript\ncontainer.register({ class: FooBar, scope: Scopes.Transient() });\n```\n\nTo unregister a class, use the `unregister` method:\n\n```typescript\ncontainer.unregister(FooBar);\n```\n\nAfter that, the class will be removed from the container and all its child containers, and the next injection will return a new instance.\n\n## Injection with tokens\n\nInstead of passing a class to the `inject`, you can use **tokens** instead.\nThe token injection can be used for **class, value, and factory** injection.\nHere's detailed information about each type.\n\n### Class tokens\n\nClass tokens are useful to inject an abstract class or interface that has multiple implementations:\n\n\u003cdetails\u003e\n\n\u003csummary\u003e\u003cb\u003eHere is an example of injecting an abstract interface\u003c/b\u003e\u003c/summary\u003e\n\n```typescript\nimport { Token, Scopes, globalContainer } from \"dioma\";\n\nconst wild = globalContainer.childContainer(\"Wild\");\n\nconst zoo = wild.childContainer(\"Zoo\");\n\ninterface IAnimal {\n  speak(): void;\n}\n\nclass Dog implements IAnimal {\n  speak() {\n    console.log(\"Woof\");\n  }\n\n  static scope = Scopes.Container();\n}\n\nclass Cat implements IAnimal {\n  speak() {\n    console.log(\"Meow\");\n  }\n\n  static scope = Scopes.Container();\n}\n\nconst animalToken = new Token\u003cIAnimal\u003e(\"Animal\");\n\n// Register Dog class with the token\nwild.register({ token: animalToken, class: Dog });\n\n// Register Cat class with the token\nzoo.register({ token: animalToken, class: Cat });\n\n// Returns Dog instance\nconst wildAnimal = wild.inject(animalToken);\n\n// Returns Cat instance\nconst zooAnimal = zoo.inject(animalToken);\n```\n\n\u003c/details\u003e\n\nThe class token registration can also override the scope of the class:\n\n```typescript\nwild.register({ token: animalToken, class: Dog, scope: Scopes.Transient() });\n```\n\n### Value tokens\n\nValue tokens are useful to inject a constant value:\n\n```typescript\nimport { Token } from \"dioma\";\n\nconst token = new Token\u003cstring\u003e(\"Value token\");\n\ncontainer.register({ token, value: \"Value\" });\n\nconst value = container.inject(token);\n\nconsole.log(value); // Value\n```\n\n### Factory tokens\n\nFactory tokens are useful to inject a factory function.\nThe factory takes the current container as the first argument and returns a value:\n\n```typescript\nimport { Token } from \"dioma\";\n\nconst token = new Token\u003cstring\u003e(\"Factory token\");\n\ncontainer.register({ token, factory: (container) =\u003e \"Value\" });\n\nconst value = container.inject(token);\n\nconsole.log(value); // Value\n```\n\nFactory function can also take additional arguments:\n\n```typescript\nconst token = new Token\u003cstring\u003e(\"Factory token\");\n\ncontainer.register({\n  token,\n  factory: (container, a: string, b): string =\u003e a + b,\n});\n\nconst value = container.inject(token, \"Hello, \", \"world!\");\n\nconsole.log(value); // Hello, world!\n```\n\nAs a usual function, a factory can contain any additional logic, conditions, or dependencies.\n\n## Child containers\n\nYou can create child containers to isolate the scope of the classes.\nChild containers have a hierarchical structure, so Dioma searches instances top-down from the current container to the root container.\nIf the instance is not found, Dioma will create a new instance in the current container, or in the container where the class was registered.\n\nHere's an example:\n\n```typescript\nimport { Container, Scopes } from \"dioma\";\n\nconst container = new Container(null, \"Parent\");\n\nconst child = container.childContainer(\"Child\");\n\nclass ParentClass {\n  static scope = Scopes.Container();\n}\n\nclass ChildClass {\n  static scope = Scopes.Container();\n}\n\ncontainer.register({ class: ParentClass });\n\nchild.register({ class: ChildClass });\n\n// Returns ParentClass instance from the parent container\nconst parentInstance = child.inject(ParentClass);\n\n// Returns ChildClass instance from the child container\nconst childInstance = child.inject(ChildClass);\n```\n\n## Injection hooks\n\nWhen registering a class, you can provide hooks that will be called before the instance is created or injected:\n\n```typescript\ncontainer.register({\n  class: MyClass,\n  beforeInject: (container, descriptor, args) =\u003e {\n    console.log(\"Before inject\");\n  },\n  beforeCreate: (container, descriptor, args) =\u003e {\n    console.log(\"Before create\");\n  },\n});\n```\n\n## Async injection and circular dependencies\n\nWhen you have a circular dependency, there will be an error `Circular dependency detected`. To solve this problem, you can use async injection.\n\n\u003cdetails\u003e\n\n\u003csummary\u003e\u003cb\u003eHere is an example:\u003c/b\u003e\u003c/summary\u003e\n\n```typescript\nimport { inject, injectAsync, Scopes } from \"dioma\";\n\nclass A {\n  constructor(private instanceB = inject(B)) {}\n\n  doWork() {\n    console.log(\"doing work A\");\n    this.instanceB.help();\n  }\n\n  static scope = Scopes.Singleton();\n}\n\nclass B {\n  private declare instanceA: A;\n\n  // injectAsync returns a promise of the A instance\n  constructor(private promiseA = injectAsync(A)) {\n    this.promiseA.then((instance) =\u003e {\n      this.instanceA = instance;\n    });\n  }\n\n  help() {\n    console.log(\"helping with work\");\n  }\n\n  doAnotherWork() {\n    console.log(\"doing work B\");\n    this.instanceA.doWork();\n  }\n\n  static scope = Scopes.Singleton();\n}\n\nconst a = await injectAsync(A);\nconst b = await injectAsync(B);\n\n// Wait until all promises are resolved\nawait globalContainer.waitAsync();\n\na.doWork();\nb.doAnotherWork();\n```\n\n\u003c/details\u003e\n\nAsync injection has an undefined behavior when there is a loop with transient dependencies. It may return an instance with an unexpected loop, or throw the `Circular dependency detected in async resolution` error, so it's better to avoid such cases.\n\nAs defined in the code above, you need to use `container.waitAsync()` or **wait for the next tick** to get all instance promises resolved, even if you use `await injectAsync(...)`.\n\nGenerally, if you expect your dependency to have an async resolution, it's better to inject it with `injectAsync`, as in the example above. But, you can also use `inject` for async injection as long as you wait for it as above.\n\nTokens also can be used for async injection as well:\n\n```typescript\nimport { Token, Scopes } from \"dioma\";\n\nconst token = new Token\u003cA\u003e(\"A\");\n\nclass B {\n  private declare instanceA: A;\n\n  // token in used for async injection\n  constructor(private promiseA = injectAsync(token)) {\n    this.promiseA.then((instance) =\u003e {\n      this.instanceA = instance;\n    });\n  }\n}\n```\n\n## TypeScript\n\nDioma is written in TypeScript and provides type safety out of the box:\n\n```typescript\nimport { inject, Scopes, Injectable } from \"dioma\";\n\n// Injectable interface makes sure the static scope is defined\nclass Database implements Injectable\u003ctypeof Database\u003e {\n  constructor(private url: string) {}\n\n  connect() {\n    console.log(`Connected to ${this.url}`);\n  }\n\n  static scope = Scopes.Singleton();\n}\n\n// Error, scope is not specified\nclass Repository implements Injectable\u003ctypeof Repository\u003e {\n  constructor(private db = inject(Database)) {}\n}\n\ninject(Repository); // Also type error, scope is not specified\n```\n\nAlso, token and class injection infers the output types from the input types.\nIf available, arguments are also checked and inferred.\n\n## API Reference\n\n### `new Container(parent?, name?)`\n\nCreates a new container with the specified parent container and name.\n\n### `new Token\u003cT\u003e(name?)`\n\nCreates a new token with the specified type and name.\n\n### `container.inject(classOrToken, ...args)`\n\nInjects the instance of the class or token, and provides arguments to the constructor or factory function.\n\n### `container.injectAsync(classOrToken, ...args)`\n\nInjects the promise of the instance of the class or token, and provides arguments to the constructor or factory function.\n\n### `container.waitAsync()`\n\nReturns a promise that resolves when all current async injections are resolved.\n\n### `container.register({ class, token?, scope? })`\n\n### `container.register({ token, value })`\n\n### `container.register({ token, factory })`\n\nRegisters the class, value, or factory with the token in the container.\n\n### `container.unregister(classOrToken)`\n\nUnregister the class or token from the container.\n\n### `container.childContainer(name?)`\n\nCreates a new child container with the specified name.\n\n### Global exports\n\nGlobal container:\n\n- `globalContainer` - the global container that is used by default for the `inject` function.\n- `inject` - the function to inject the instance of the class or token.\n- `injectAsync` - the function to inject the promise of the instance of the class or token.\n\nErrors:\n\n- `DependencyCycleError` - thrown when a circular dependency is detected.\n- `AsyncDependencyCycleError` - thrown when a circular dependency is detected in async resolution.\n- `ArgumentsError` - thrown when the arguments are passed to unsupported scopes.\n- `TokenNotRegisteredError` - thrown when the token is not registered in the container.\n\n## Author\n\nEugene Daragan\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzheksoon%2Fdioma","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzheksoon%2Fdioma","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzheksoon%2Fdioma/lists"}