{"id":42030297,"url":"https://github.com/radzserg/rsdi","last_synced_at":"2026-01-26T04:34:45.135Z","repository":{"id":34995426,"uuid":"194827731","full_name":"radzserg/rsdi","owner":"radzserg","description":"Dependency Injection Container","archived":false,"fork":false,"pushed_at":"2023-07-18T21:37:32.000Z","size":109298,"stargazers_count":66,"open_issues_count":4,"forks_count":6,"subscribers_count":3,"default_branch":"main","last_synced_at":"2023-09-29T01:36:42.382Z","etag":null,"topics":["dependency-injection","javascript","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/radzserg.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}},"created_at":"2019-07-02T09:02:24.000Z","updated_at":"2023-09-13T08:27:30.000Z","dependencies_parsed_at":"2023-01-15T11:45:47.429Z","dependency_job_id":null,"html_url":"https://github.com/radzserg/rsdi","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"purl":"pkg:github/radzserg/rsdi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radzserg%2Frsdi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radzserg%2Frsdi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radzserg%2Frsdi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radzserg%2Frsdi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/radzserg","download_url":"https://codeload.github.com/radzserg/rsdi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radzserg%2Frsdi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28766887,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T03:54:34.369Z","status":"ssl_error","status_checked_at":"2026-01-26T03:54:33.031Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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","javascript","typescript"],"created_at":"2026-01-26T04:34:42.574Z","updated_at":"2026-01-26T04:34:45.123Z","avatar_url":"https://github.com/radzserg.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RSDI - Dependency Injection Container\n\nSimple and powerful dependency injection container for with strong type checking system.\n\n- [Motivation](#motivation)\n- [Features](#features)\n- [When to use](#when-to-use)\n- [Architecture](#architecture)\n- [Usage](#usage)\n  - [Raw values](#raw-values)\n  - [Object resolver](#object-resolver)\n  - [Function resolver](#function-resolver)\n  - [Factory resolver](#factory-resolver)\n- [Typescript type resolution](#typescript-type-resolution)\n- [Dependency declaration](#dependency-declaration)\n- Wiki\n  - [Async factory resolver](./docs/async_factory_resolver.md)\n  - [DI Container vs Context](./docs/context_vs_container.md)\n\n## Motivation\n\nPopular dependency injection libraries use `reflect-metadata` that allows to fetch argument types and based on\nthose types and do autowiring. Autowiring is a nice feature but the trade-off is decorators.\n\n```typescript\n@injectable()\nclass Foo {}\n```\n\nWhy component Foo should know that it's injectable?\n\nYour business logic depends on a specific framework that is not part of your domain model and can change.\n\nMore thoughts in a [dedicated article](https://radzserg.medium.com/https-medium-com-radzserg-dependency-injection-in-react-part-2-995e93b3327c)\n\n## Features\n\n- Simple but powerful\n- Does not requires decorators\n- Great types resolution\n- Works great with both javascript and typescript\n\n## When to use\n\n`RSDI` is most effective in complex applications. When the complexity of your application is high, it becomes necessary to\nbreak up huge components into smaller ones to control the complexity. You have components that use other components that\nuse other components. You have application layers and a layer hierarchy. There is a need to transfer dependencies from\nthe upper layers to the lower ones.\n\nYou like and respect and use Dependency Injection and TDD. You have to use Dependency Injection in order to have proper\nunit tests. Tests that test only one module - class, component, function, but not integration with nested dependencies.\n\n## Architecture\n\n`RSDI` expects (but does not require) that you build all your dependencies into a dependency tree. Let's take a typical\nweb application as an example. Given that your application is quite large and has many layers:\n\n- controllers (REST or graphql handlers)\n- domain model handlers (your domain models, various managers, use-cases etc)\n- DB repositories,\n- Low level services\n\n![architecture](https://github.com/radzserg/rsdi/raw/main/docs/RSDI_architecture.jpg \"RSDI Architecture\")\n\nAn application always has an entry point, whether it is a web application or a CLI application. This is the only place where you\nshould configure your dependency injection container. The top level components will then have the lower level components\ninjected.\n\n# How to use\n\nLet's take a simple web application as an example. We will cut into a small part of the application that registers a\nnew user. A real application will consist of dozens of components. The logic of the components will be much more\ncomplicated. This is just a demo. It's up to you to use classes or factory functions for the demonstration, and we'll\nuse both.\n\n```typescript\n// sample web application components\n\nexport function UserController(\n  userRegistrator: UserRegistrator,\n  userRepository: UserRepository\n) {\n  return {\n    async create(req: Request, res: Response) {\n      const user = await userRegistrator.register(req.body);\n      res.send(user);\n    },\n    async list(req: Request) {\n      const users = await userRepository.findAll(req.body);\n      res.send(users);\n    },\n  };\n}\n\nexport class UserRegistrator {\n  public constructor(public readonly userRepository: UserRepository) {}\n\n  public async register(userData: SignupData) {\n    // validate and send sign up email\n    return this.userRepository.saveNewUser(userData);\n  }\n}\n\nexport function MyDbProviderUserRepository(db: Knex): UserRepository {\n  return {\n    async saveNewUser(userAccountData: SignupData): Promise\u003cvoid\u003e {\n      await this.db(\"insert\").insert(userAccountData);\n    },\n  };\n}\n\nexport function buildDbConnection(): Knex {\n  return knex({\n    /* db credentials */\n  });\n}\n```\n\nNow we need to configure the dependency injection container before use. Dependencies are declared and not really initiated\nuntil the application really needs them. Your DI container initialization function - `configureDI` will include:\n\n```typescript\nimport DIContainer, { object, use, factory, func, IDIContainer } from \"rsdi\";\n\nexport default function configureDI() {\n  const container = new DIContainer();\n  container.add({\n    buildDbConnection: factory(() =\u003e {\n      buildDbConnection();\n    }),\n    [MyDbProviderUserRepository.name]: func(\n      MyDbProviderUserRepository,\n      use(buildDbConnection)\n    ),\n    [UserRegistrator.name]: object(UserRegistrator).construct(\n      use(MyDbProviderUserRepository.name)\n    ),\n    [UserController.name]: func(\n      UserController,\n      use(UserRegistrator.name),\n      use(MyDbProviderUserRepository.name)\n    ),\n  });\n  return container;\n}\n```\n\n**All resolvers are resolved only once and their result persists over the life of the container.**\n\nLet's map our web application routes to configured controllers\n\n```typescript\n// configure Express router\nexport default function configureRouter(\n  app: core.Express,\n  diContainer: IDIContainer\n) {\n  const usersController = diContainer.get(UsersController);\n  app\n    .route(\"/users\")\n    .get(usersController.list.bind(usersController))\n    .post(usersController.create.bind(usersController));\n}\n```\n\nAdd `configureDI` in the entry point of your application.\n\n```typescript\n// express.ts\nconst app = express();\n\nconst diContainer = configureDI();\nconfigureRouter(app, diContainer);\n\napp.listen(8000, () =\u003e {\n  console.log(`⚡️[server]: Server is running`);\n});\n```\n\nThe complete web application example can be found [here](https://radzserg.medium.com/dependency-injection-in-express-application-dd85295694ab)\n\n## Dependency Resolvers\n\n### Raw values resolver\n\nDependencies are set as raw values. Container keeps and return raw values.\n\n```typescript\nimport DIContainer from \"rsdi\";\n\nconst container = new DIContainer();\ncontainer.add({\n  ENV: \"PRODUCTION\",\n  HTTP_PORT: 3000,\n  storage: new CookieStorage(),\n});\nconst env: string = container.get(\"ENV\");\nconst port: number = container.get(\"HTTP_PORT\");\nconst authStorage: AuthStorage = container.get(AuthStorage); // instance of AuthStorage\n```\n\n### Object resolver\n\n`object(ClassName)` - constructs an instance of the given class. The simplest scenario it calls the class constructor `new ClassName()`.\nWhen you need to pass arguments to the constructor, you can use `construct` method. You can refer to the already defined\ndependencies via the `use` helper, or you can pass raw values.\n\nIf you need to call object method after initialization you can use `method` it will be called after constructor.\n\n```typescript\nclass ControllerContainer {\n  constructor(authStorage: AuthStorage, logger: Logger) {}\n\n  add(controller: Controller) {\n    this.controllers.push(controller);\n  }\n}\n\n// container\nconst container = new DIContainer();\ncontainer.add({\n  Storage: object(CookieStorage), // constructor without arguments\n  AuthStorage: object(AuthStorage).construct(\n    use(Storage) // refers to existing dependency\n  ),\n  UsersController: object(UserController),\n  PostsController: object(PostsController),\n  ControllerContainer: object(MainCliCommand)\n    .construct(use(AuthStorage), new Logger()) // use existing dependency, or pass raw values\n    .method(\"add\", use(UsersController)) // call class method after initialization\n    .method(\"add\", use(PostsController)),\n});\n```\n\n### Function resolver\n\nFunction resolver allows declaring lazy functions. Function will be called when it's actually needed.\n\n```typescript\nfunction UsersRepoFactory(knex: Knex): UsersRepo {\n  return {\n    async findById(id: number) {\n      await knex(\"users\").where({ id });\n    },\n  };\n}\n\nconst container = new DIContainer();\ncontainer.add({\n  DBConnection: knex(/* ... */),\n  UsersRepoFactory: func(UsersRepoFactory, use(\"DBConnection\")),\n});\n\nconst userRepo = container.get(UsersRepoFactory);\n```\n\n### Factory resolver\n\nFactory resolver is similar to a Function resolver. You can use factory resolver when you need more flexibility\nduring initialization. `container: IDIContainer` will be passed in as an argument to the factory method. You can\nresolve other dependencies inside the factory function and have conditions inside of it.\n\n```typescript\nconst container = new DIContainer();\ncontainer.add({\n  BrowserHistory: factory(configureHistory),\n});\n\nfunction configureHistory(container: IDIContainer): History {\n  const history = createBrowserHistory();\n  const env = container.get(\"ENV\");\n  if (env === \"production\") {\n    // do what you need\n  }\n  return history;\n}\nconst history = container.get(\"BrowserHistory\");\n```\n\n## Typescript type resolution\n\n`container.get` - return type based on declaration.\n\n```typescript\nconst container: DIContainer = new DIContainer();\ncontainer.add({\n  strVal: \"raw string value\",\n  numberVal: 123,\n  boolVal: true,\n  objectVal: object(Buzz), // resolvers to object of class Buzz\n  dateVal: func(function () {\n    return new Date(); // resolves to ReturnType of the function\n  }),\n});\nconst strVal = container.get(\"strVal\"); // strVal: string\nconst numberVal = container.get(\"numberVal\"); // numberVal: number\nconst boolVal = container.get(\"boolVal\"); // boolVal: boolean\nconst objectVal = container.get(\"objectVal\"); // boolVal: Buzz\nconst dateVal = container.get(\"dateVal\"); // dateVal: Date\n```\n\n`contrainer.use` - allows to reference declared dependency with respect to types.\n\n```typescript\nexport class Foo {\n  constructor(name: string, bar: Bar) {}\n}\n\nconst container: DIContainer = new DIContainer();\n\ncontainer.add({\n  bar: new ObjectResolver(Bar),\n  key1: new RawValueResolver(\"value1\"),\n\n  // `bar` dependency cannot be referenced in the same `add` call\n  // Argument of type 'string' is not assignable to parameter of type... '\n  // foo: new ObjectResolver(Foo).construct(\"foo\", container.use(\"bar\")),\n});\ncontainer.add({\n  foo: new ObjectResolver(Foo).construct(\"foo\", container.use(\"bar\")),\n});\n```\n\nTo support lazy loading `construct` method changes original Foo constructor to expect\n`(string | ReferenceResolver\u003cstring\u003e, Bar | ReferenceResolver\u003cBar\u003e)`.\n`container.use(\"bar\")` - will return object `ReferenceResolver\u003cBar\u003e` to respect safe types.\n\n`use` helper\n\n```typescript\nimport { use } from \"rsdi\";\n```\n\n`use` helper is less strict version of `container.use`. It allows to reference dependencies that **will be defined** relying\non convention over configuration rule.\n\n- if given name is a class - instance of the class\n- if given name is a function - return type of the function\n- if custom type is provided - return ReferenceResolver\u003cCustom\u003e\n- otherwise - any\n\n```typescript\nclass Foo {\n  constructor(private readonly bar: Bar) {}\n}\n\nfunction returnBoolean() {\n  return true;\n}\n\nconst container: DIContainer = new DIContainer();\ncontainer.add({\n  Bar: new Bar(),\n  Foo: object(Foo).construct(use(Bar)), // resolves Bar\n\n  expectBoolFunc: func(function (a: boolean) {\n    return null;\n  }, use(returnBoolean)), // resolves to ReturnType of returnBoolean function\n\n  expectDateFunc: func(function (a: Date) {\n    return null;\n  }, use\u003cDate\u003e(\"date\")), // resolves to Date\n\n  expectDateFunc2: func(function (a: Date) {\n    return null;\n  }, use(\"date\")), // resolves to any\n});\n```\n\n## Dependency declaration\n\n**Use string names**\n\n```typescript\nconst container: DIContainer = new DIContainer();\n\ncontainer.add({\n  bar: new ObjectResolver(Bar),\n  key1: new RawValueResolver(\"value1\"),\n});\n\ncontainer.add({\n  foo: new ObjectResolver(Foo).construct(\"foo\", container.use(\"bar\")),\n});\n```\n\nUse function and class names by declaring them as `[MyClass.name]`. In this case, it is safe to rename functions and\nclasses in the IDE. IDE will identify all usages and rename them in the container as well. You can declare all dependencies\nin a single `add` method and use `use` helper to reference other dependencies.\n\n```typescript\ncontainer.add({\n  [Foo.name]: new Foo(),\n  [MyFactory.name]: MyFactory(),\n  [Foo.name]: object(Foo).construct(use(Bar)),\n});\nconst foo = container.get(Foo);\nconst buzz = container.get(MyFactory);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradzserg%2Frsdi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fradzserg%2Frsdi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradzserg%2Frsdi/lists"}