{"_id":"@alxcube/di-container","_rev":"1-f68c151387a9494b579da7ec093e64f7","name":"@alxcube/di-container","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@alxcube/di-container","version":"1.0.0","type":"module","main":"./dist/di-container.umd.cjs","module":"./dist/di-container.js","types":"./dist/di-container.d.ts","exports":{".":{"import":"./dist/di-container.js","require":"./dist/di-container.umd.cjs"}},"scripts":{"build":"tsc && vite build","test":"vitest --run","lint":"eslint ./src ./spec --ext .ts && npm run prettier","prettier":"prettier --write 'src/**/*.ts' && prettier --write 'spec/**/*.ts'"},"author":{"name":"Alexander Alexandrov","email":"alxcube@gmail.com"},"description":"Simple but flexible type-safe dependency injection container for TypeScript applications.","keywords":["dependency injection","inversion of control","service container","ioc","di"],"repository":{"type":"git","url":"git+https://github.com/alxcube/di-container.git"},"license":"MIT","devDependencies":{"@rollup/plugin-terser":"^0.4.4","@types/node":"^20.12.7","@typescript-eslint/eslint-plugin":"^7.7.1","eslint":"^8.57.0","eslint-config-prettier":"^9.1.0","eslint-plugin-import":"^2.29.1","eslint-plugin-prettier":"^5.1.3","prettier":"^3.2.5","typescript":"^5.2.2","vite":"^5.2.0","vite-plugin-banner":"^0.7.1","vite-plugin-dts":"^3.9.0","vitest":"^1.5.2"},"_id":"@alxcube/di-container@1.0.0","gitHead":"a1a175baa788292bf5ed476893b0ba8954694c40","bugs":{"url":"https://github.com/alxcube/di-container/issues"},"homepage":"https://github.com/alxcube/di-container#readme","_nodeVersion":"18.18.2","_npmVersion":"9.8.1","dist":{"integrity":"sha512-1TsP5rA2pJDPt0oLvw1yIBE5SqBSQZuzMa2w34g9i3q1qxH92Nfcwt71Icj0CUMfW3Ww4xI8oekf5X7xdyfdDg==","shasum":"7375695d129eda852776b8a1aa958683d7c95118","tarball":"https://registry.npmjs.org/@alxcube/di-container/-/di-container-1.0.0.tgz","fileCount":8,"unpackedSize":192109,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD1CcZNX9XyeHFfWRhEArgzalTLcgbTLmsaIFXOLTV8qQIhANDFyCNhR+Sw9fIcPx0BtJqE3r4PDuQj7VtHzYfImgmE"}]},"_npmUser":{"name":"alxcube","email":"alxcube@gmail.com"},"directories":{},"maintainers":[{"name":"alxcube","email":"alxcube@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/di-container_1.0.0_1715006097161_0.1629795046476863"},"_hasShrinkwrap":false},"1.0.1":{"name":"@alxcube/di-container","version":"1.0.1","type":"module","main":"./dist/di-container.umd.cjs","module":"./dist/di-container.js","types":"./dist/di-container.d.ts","exports":{".":{"import":"./dist/di-container.js","require":"./dist/di-container.umd.cjs"}},"scripts":{"build":"tsc && vite build","test":"vitest --run","lint":"eslint ./src ./spec --ext .ts && npm run prettier","prettier":"prettier --write 'src/**/*.ts' && prettier --write 'spec/**/*.ts'"},"author":{"name":"Alexander Alexandrov","email":"alxcube@gmail.com"},"description":"Simple but flexible type-safe dependency injection container for TypeScript applications.","keywords":["dependency injection","inversion of control","service container","ioc","di"],"repository":{"type":"git","url":"git+https://github.com/alxcube/di-container.git"},"license":"MIT","devDependencies":{"@rollup/plugin-terser":"^0.4.4","@types/node":"^20.12.7","@typescript-eslint/eslint-plugin":"^7.7.1","eslint":"^8.57.0","eslint-config-prettier":"^9.1.0","eslint-plugin-import":"^2.29.1","eslint-plugin-prettier":"^5.1.3","prettier":"^3.2.5","typescript":"^5.2.2","vite":"^5.2.0","vite-plugin-banner":"^0.7.1","vite-plugin-dts":"^3.9.0","vitest":"^1.5.2"},"_id":"@alxcube/di-container@1.0.1","gitHead":"9b2cde1d5ac8308fea731c0d69b894149d634098","bugs":{"url":"https://github.com/alxcube/di-container/issues"},"homepage":"https://github.com/alxcube/di-container#readme","_nodeVersion":"18.18.2","_npmVersion":"9.8.1","dist":{"integrity":"sha512-OsdvH5Kj2IaJTG0vUr6441n6ocxIufxLkq9NUFYPdV75S/0cWRQWmr8E0wU8NECGRjEtpbSs14NMY0dFxYRzUQ==","shasum":"3b13a01b6420d0583cc8dc6c22e00ba0834ea0ff","tarball":"https://registry.npmjs.org/@alxcube/di-container/-/di-container-1.0.1.tgz","fileCount":8,"unpackedSize":192141,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFZcxr5Lzq9IoJwOf0qnktAoSUFOcjYK70DzxFF8GyFkAiEA5K78JZiIziOjgymThe8r2ubdp5hhD3OyMVvpyczsb6Y="}]},"_npmUser":{"name":"alxcube","email":"alxcube@gmail.com"},"directories":{},"maintainers":[{"name":"alxcube","email":"alxcube@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/di-container_1.0.1_1717645665994_0.7299582660001127"},"_hasShrinkwrap":false}},"time":{"created":"2024-05-06T14:34:57.016Z","1.0.0":"2024-05-06T14:34:57.338Z","modified":"2024-06-06T03:47:46.372Z","1.0.1":"2024-06-06T03:47:46.182Z"},"maintainers":[{"name":"alxcube","email":"alxcube@gmail.com"}],"description":"Simple but flexible type-safe dependency injection container for TypeScript applications.","homepage":"https://github.com/alxcube/di-container#readme","keywords":["dependency injection","inversion of control","service container","ioc","di"],"repository":{"type":"git","url":"git+https://github.com/alxcube/di-container.git"},"author":{"name":"Alexander Alexandrov","email":"alxcube@gmail.com"},"bugs":{"url":"https://github.com/alxcube/di-container/issues"},"license":"MIT","readme":"# @alxcube/di-container\n\nSimple but flexible type-safe dependency injection container for TypeScript\napplications.\n\n## Key Features\n\n* **Flexible Service Creation**: Utilize factories to create services with injected\ndependencies and customizable configurations, enhancing flexibility in service instantiation.\n* **Interface-Implementation Binding**: Seamlessly link interfaces with their respective\nimplementations.\n* **Lifecycle Management**: Control the lifecycle of services with support for *transient*,\n*singleton*, and *request* scopes.\n* **Circular Dependency Support**: Handle circular dependencies gracefully.\n* **Contextual Resolution**: Dynamically resolve dependencies based on the context,\nenhancing adaptability.\n* **Type Safety**: Ensure type safety throughout the dependency injection process,\nenhancing code robustness and reliability.\n* **Error Handling**: Detect and manage errors during service resolution with clear and \ninformative error messages, facilitating debugging.\n* **Testing Support**: Simplify unit testing by instantiating classes with injected\ndependencies using the container's features. Additionally, backup and restore container\nstate for seamless testing.\n\n## Installation\n\n```shell\nnpm i @alxcube/di-container\n```\n\n## Glossary\n\nThis section explains the terms used in this documentation.\n\n### Service Container\n\nImplementation of Dependency Injection Container pattern.\n\n### Service Resolution Context\n\nService resolution context is a special object available in\n[service factories](#service-factory).\nThis object provides methods for retrieving services that are dependencies of the service\nbeing created by the factory. It exists within the scope of a single root service request.\nAdditionally, it provides methods to determine whether the current service is being\nresolved as a dependency for another service, as well as the current stack of service\nresolution.\n\nSee [ServiceResolutionContext interface](./src/ServiceResolutionContext.ts).\n\n### Service\n\nService - an object or value obtained from the container. In general, services are\nimplementations of your application's interfaces, but the container allows storing\nvalues of any type.\n\n### Service Factory\n\nService factory is a callback function that will be invoked when a service is requested\nfrom the container. The sole argument of the function is the\n[Service Resolution Context](#service-resolution-context) object, which allows obtaining\ndependencies of the constructed service. This function should return a value of the\ncorresponding type.\n\n### Service Map\n\nService map is an auxiliary interface that represents the mapping between service keys and\nservice types. It enables TypeScript to leverage type inference, making calls to container\nmethods type-safe, and assists you in working with code hints from your IDE.\n\nWhile any strings can be used as property names in this interface, it is recommended to\nname them according to the names of your application's interfaces.\n\nIndex signature (`[key: string]: any;`) should not be used, since it breaks type inference.\n\n```ts\ninterface ServicesMap {}\n```\n\n### Service Key\n\nService key is either a key of the [service map](#service-map) interface or a class\nconstructor. Services are registered and retrieved from the container using this key.\n\n```ts\ntype ServiceKey<TServicesMap extends ServicesMap> =\n  | keyof TServicesMap\n  | Constructor<object>\n```\n\n### Named Service Key\n\nObject with two properties: `service` - the [service key](#service-key), and `name` -\nthe service name.\n\n```ts\ninterface NamedServiceKey<TServicesMap extends ServicesMap> {\n  service: ServiceKey<TServicesMap>;\n  name: string;\n}\n```\n\n### Service Token\n\nType alias for union of service key and named service key.\n\n```ts\ntype ServiceToken<TServicesMap extends ServicesMap> =\n  | ServiceKey<TServicesMap>\n  | NamedServiceKey<TServicesMap>;\n```\n\n### Constant Token\n\nConstant token is an object with a single property `'constant'`, which can hold a value\nof any type. It is used to declare dependencies of a class that are not retrieved from\nthe container and are passed directly to the class constructor in methods\n`registerClassConfig()`, `implement()` and `instantiate()`.\n\n### Dependencies Tuple\n\nDependencies tuple is a special type of tuple whose members are\n[service tokens](#service-token), or [constant tokens](#constant-token), from which\nvalues of the corresponding type are resolved. It is used for declaratively specifying the\ndependencies of a class constructor in the methods `registerClassConfig()`, `implement()`,\nand `instantiate()`.\n\n## Usage\n\n### Create Service Map\n\nFirst of all, you need to create a [service map](#service-map) and specify in it the\ntypes of services that will be available in the container.\n\n```ts\n// TypesMap.ts\nimport type { ServicesMap } from \"@alxcube/di-container\";\nimport type { HttpClient } from \"./HttpClient\";\nimport type { BackendApiClient } from \"./BackendApiClient\";\n\n// Extending of ServicesMap is not required, but recommended for clarity.\nexport interface TypesMap extends ServicesMap {\n  HttpClient: HttpClient;\n  \"HttpClient[]\": HttpClient[];\n  BackendApiClient: BackendApiClient;\n  applicationKey: string;\n}\n```\n\n### Creating Container\n\nAfter declaring service map, you are now ready to create container instance:\n\n```ts\n// container.ts\nimport { Container } from \"@alxcube/di-container\";\nimport type { TypesMap } from \"./TypesMap\";\n\nexport const container = new Container<TypesMap>();\n```\n\n### Resolving Services\n\nThere are several methods for resolving services from container.\n\n#### Resolving Single Service\n\nTo retrieve a service from the container, you use the `resolve()` method. It takes the\n[service key](#service-key) as an argument and returns a value of the corresponding type.\nIf the key passed is key of [service map](#service-map), the method returns the\ncorresponding type according to the map. If a constructor is passed as the key, an instance\nof the provided class will be returned. However, remember to register the appropriate\n[service factory](#service-factory) for the class; the container does not inherently know\nhow to instantiate class instances.\n\nThe second argument of the method, `name`, allows you to retrieve a service with the\ncorresponding name. If this parameter is omitted, the name \"default\" is implicitly used\n(the same applies to registration in the container).\n\nIf there is no service registered in the container with the given key or name, a\n`RangeError` will be thrown.\n\n```ts\nconst httpClient: HttpClient = container.resolve(\"HttpClient\");\n\n// There is no need to declare types, since type inference works using types map\nconst paymentsApiHttpClient = container.resolve(\"HttpClient\", \"payment\");\n```\n\n#### Resolving Array Of Services\n\nThe resolveAll() method takes a service key and returns an array of services of the\ncorresponding type registered under different names. If there are no registrations for\nthis service in the container, an empty array will be returned.\n\n```ts\n// The inferred type is HttpClient[]\nconst httpClients = container.resolveAll(\"HttpClient\");\n```\n\n#### Resolving Tuple Of Services\n\nTo obtain a tuple of services, you use the `resolveTuple()` method. It takes a tuple as\nan argument, whose members are [service tokens](#service-token). The return value is a\ntuple of the corresponding services.\n\nThis method can be useful when you need to retrieve multiple services from the container\nwithin the same context, when the services have a `'request'` lifecycle. Using this method for\nservices with other lifecycles does not make sense. The method is also available on\nthe [service resolution context](#service-resolution-context) object (in\n[service factories](#service-factory)), but using it there does not make sense either, as\ncalls to `resolve()` on the `ServiceResolutionContext` object will already return services\nwithin the context of the same request.\n\n```ts\nconst [httpClient, backendApiClient] = container.resolveTuple([\n  {\n    service: \"HttpClient\",\n    name: \"backend\"\n  }, // Named services can be resolved, using NamedServiceKey interface\n  \"BackendApiClient\"\n] as const); // Don't forget \"as const\" to make type inference work\n```\n\n#### Retrieving Service Names\n\nUsing the `getServiceNames()` method, you can obtain an array of all names under which a\nservice with the given [service key](#service-key) has been registered. If the service\nwas registered without explicitly specifying a name, this array will include the name\n`\"default\"`. If the service was not registered, an empty array will be returned.\n\n```ts\ncontainer.registerConstant(\"HttpClient\", new ConcreteHttpClient());\ncontainer.registerConstant(\"HttpClient\", new AnotherHttpClient(), { name: \"another\" });\n\nconsole.log(container.getServiceNames(\"HttpClient\")); // [\"default\", \"another\"]\n```\n\n### Registering Services\n\nThere are several ways to register services.\n\n#### Registering Constant\n\nTo register a constant value, you use the `registerConstant()` method. It takes a\n[service key](#service-key) and the constant value as arguments. Values of any type are\nsupported, except for `undefined`. For example, you can register a primitive value or a\nsingleton object created outside the container using this method.\n\nThe third optional argument is an options object. The following options are available:\n* `name` - Allows registering multiple services of the same type under the same service key.\nIt serves to differentiate between services of the same type. If this option is not\nspecified, the name `\"default\"` will be used implicitly.\n* `replace` - When set to `true`, it replaces the service (taking into account the service\nname) that was previously registered. If the option is set to `false` or omitted, and a\nservice with the given key (and name) is already registered, a `TypeError` will be thrown.\n\n```ts\n// Register string constant\ncontainer.registerConstant(\"applicationKey\", \"my_app_key\");\n\n// Register interface implementation as singleton\ncontainer.registerConstant(\"HttpClient\", new ConcreteHttpClient(), { name: \"payments\" });\n\n// Replace registered services\ncontainer.registerConstant(\"applicationKey\", \"other_app_key\", { replace: true });\ncontainer.registerConstant(\"HttpClient\", new ConcreteHttpClient(), { name: \"payments\", replace: true });\n```\n\n#### Registering Service Factory\n\nService factories are the most flexible and versatile way to register a service. To register\na factory, you use the `registerFactory()` method. It takes three parameters: the\n[service key](#service-key) as the first parameter, the [factory function](#service-factory)\nas the second parameter, and an options object as the third parameter.\n\nThis factory function accepts the [context object](#service-resolution-context) as an\nargument and should return the corresponding service. Dependencies of the constructed\nservice can be obtained from the context object using the `resolve()`, `resolveAll()`, or\n`resolveTuple()` methods.\n\nThis factory function will be invoked when the service is requested from the container or\nas a dependency of another service in another factory function.\n\nThe lifecycle of the created service is regulated by the `lifecycle` option, which can\nhave one of three values:\n\n* `\"transient\"` (default) - for each request of this service, the factory function will be\ncalled, generally returning a new instance of the service.\n* `\"singleton\"` - the factory function will be called once, after which the created\ninstance of the service will be stored in the container. For all subsequent requests,\nthe same instance of the service will be returned throughout the application's lifetime\n(assuming the service registration is not updated).\n* `\"request\"` - operates similarly to `\"singleton\"`, but only within the scope of a single\nroot request. A root request is considered to be a call to one of the `resolve()`,\n`resolveAll()`, or `resolveTuple()` methods on the container instance. Thus, when\nresolving a service of one root request, all services that depend on the service with the\n`\"request\"` lifecycle will receive the same instance of it, but in the next root request,\nthis instance will be different.\n\nIn addition to `lifecycle`, the options of the `registerFactory()` method also include\n`name` and `replace`, the meaning and action of which are identical to the similarly named\noptions of the [`registerConstant()`](#registering-constant) method.\n\n```ts\n// Register interface implementation as singleton\ncontainer.registerFactory(\n  \"HttpClient\",\n  () => new ConcreteHttpClient(),\n  { lifecycle: \"singleton\" }\n);\n\n// Register interface implementation with dependencies\ncontainer.registerFactory(\n  \"BackendApiClient\",\n  (context) => new ConcreteBackendClient(context.resolve(\"HttpClient\"))\n);\n\n// Register service factory, using constructor as key\ncontainer.registerFactory(TextEncoder, () => new TextEncoder());\n```\n\n#### Registering Class Configuration\n\nIn general, when only dependency injections through a class constructor are used, your\nclass factories may look quite similar:\n\n```ts\ncontainer.registerFactory(\n  MyClass,\n  (context) => new MyClass(context.resolve(\"Dep1\"), context.resolve(\"Dep2\"))\n)\n```\n\nTo free you from routine and make class factory registration more declarative, the\n`registerClassConfig()` method is designed. It takes the class constructor as the first\nargument, and as the second argument, it accepts a\n[tuple of dependencies](#dependencies-tuple), the corresponding members of which will be\nused to extract the constructor dependencies in the respective order.\n\nThe third argument is an options object. In addition to options of\n[`registerFactory()`](#registering-service-factory) method, there are one more option:\n* `circular` - this should be set to true, when class has circular dependencies. See\ndetails below in corresponding section.\n\n\nPlease note that you can pass dependencies that are not directly extracted from the\ncontainer by using a [constant token](#constant-token). Typically, this applies to\nprimitive data types that do not make much sense to store in the container.\n\nYou can use the `constant()` helper for convenience in creating constant tokens.\n\n```ts \nimport { constant } from \"@alxcube/di-container\";\n\n// Register different configurations with constant token\ncontainer.registerClassConfig(\n  TextDecoder,\n  [{ constant: \"utf-8\" }],\n  { name: \"utf8\" }\n);\ncontainer.registerClassConfig(\n  TextDecoder,\n  [constant(\"koi8-r\")], // use `constant()` helper\n  { name: \"koi8\" }\n);\n\n// Resolving\nconst utf8Decoder = container.resolve(TextDecoder, \"utf8\");\nconst koi8Decoder = container.resolve(TextDecoder, \"koi8\");\n\n// Registering class config with container dependencies\ncontainer.registerClassConfig(\n  PaymentsApiClient,\n  [\n    { service: \"HttpClient\", name: \"payment\" }, // Dependency on service with specific name\n    \"XmlParser\", // Dependency on default service\n    TextDecoder, // Dependency on class\n  ]\n);\n```\n\nYou also can use `classNames` Map for binding constructors to their names. This helps\nto keep meaningful class names in error messages after your code gets minified.\n\n```ts\nimport { classNames } from \"@alxcube/di-container\";\n\nclassNames.set(ConcreteHttpClient, \"ConcreteHttpClient\");\n```\n\n#### Registering Interface Implementation\n\nSimilarly, the `implement()` method works like the `registerClassConfig()` method,\nallowing you to declaratively bind an interface to its implementing class. The first\nargument of the method takes the string name of the interface, which is key of the\n[service map](#service-map). The second argument is the constructor of the class\nimplementing this interface. The third argument is a\n[tuple of class dependencies](#dependencies-tuple), just like in\n[`registerClassConfig()`](#registering-class-configuration).\nThe fourth argument is options, which are the same as in\n[`registerClassConfig()`](#registering-class-configuration).\n\n```ts\n// Register implementaion with no dependencies\ncontainer.implement(\"HttpClient\", ConcreteHttpClient, []);\n\n// Register implementation with dependencies\ncontainer.implement(\"BackendApiClient\", ConcreteBackendClient, [\"HttpClient\"]);\n```\n\n#### Generating Array Resolvers\n\nSome of your classes may depend on an array of homogeneous interfaces from the container.\nTypically, such a dependency is resolved using the `resolveAll()` method inside the\nservice factory:\n\n```ts\ncontainer.registerFactory(\n  \"HttpClientsPool\",\n  (context) => new ConcreteHttpClientsPool(\n    context.resolveAll(\"HttpClient\")\n  )\n);\n```\n\nTo be able to leverage the benefits of declarative dependency specification in methods like\n`registerClassConfig()` or `implement()`, you can use the `createArrayResolver()` method.\n\nFirst, add a separate type for the array of the selected service to your service map.\nFor example, it might look like this:\n\n```ts\ninterface AppServiceMap {\n    HttpClient: HttpClient;\n    \"HttpClient[]\": HttpClient[];\n}\n```\n\nThen pass the keys of the single type and the corresponding array type to the\n`createArrayResolver()` method:\n\n```ts\ncontainer.createArrayResolver(\"HttpClient\", \"HttpClient[]\");\n```\n\nThis will be equivalent to the following code:\n\n```ts\ncontainer.registerFactory(\"HttpClient[]\", (context) => context.resolveAll(\"HttpClient\"));\n```\n\nNow you can declaratively use the dependency on the array:\n\n```ts\ncontainer.implement(\"HttpClientPool\", ConcreteHttpClientsPool, [\"HttpClient[]\"]);\n```\n\nThe `createArrayResolver()` method also accepts options similar to the options of the\n[`registerFactory()`](#registering-service-factory) method.\n\n### Child Containers\n\nThe `createChild()` method creates an empty child container. It is \"empty\" in the sense\nthat it initially does not have its own service registrations, but all services registered\nin the parent container are also accessible in the child container.\n\nWhen registering services in the child container, they override registrations with the same\nname from the parent container.\n\nWhen removing a service from the child container, the existing registration from the\nparent container (if it exists) is not removed unless the `cascade` parameter of the\n`unregister()` method is set to `true`.\n\nTo obtain the parent container, you can use the `getParent()` method. It returns the\nparent container or `undefined` if the container has no parent.\n\nIt is important to note that when requesting a service from the child container, a process\nof merging all registrations across the container hierarchy occurs, which may lead to\nperformance degradation when there are a large number of registrations.\n\n```ts\n// Register interface implementation as singleton\ncontainer.implement(\"HttpClient\", ConcreteHttpClient, [], { lifecycle: \"singleton\" });\n\n// Create child container\nconst childContainer = container.createChild();\n\n// Resolve http client from child container\nconst httpClient1 = childContainer.resolve(\"HttpClient\");\n\n// Override interface implementation in child container. (No need to pass `true` as\n// `replace` option value, since child container hasn't own registration of HttpClient\nchildContainer.implement(\n  \"HttpClient\",\n  AxiosHttpClient,\n  [\"AxiosInstanceFactory\"],\n  { lifecycle: \"singleton\" }\n);\n\n// Resolve new implementation of http client from child container\nconst httpClient2 = childContainer.resolve(\"HttpClient\");\n\n// Now there are different implementations in parent and child containers.\nconsole.log(httpClient1 === httpClient2); // false\n\n// Parent container keeps old registration\nconsole.log(httpClient1 === container.resolve(\"HttpClient\")); // true\n```\n\n### Checking Service Existence\n\nThe `has()` method takes a [service key](#service-key) and an optional service name as\narguments and returns `true` if such a service is registered in the container, and `false`\notherwise. If no name is provided, the method returns `true` if there is at least one\nregistration for the service with that key. If a name is provided, it checks for the\nexistence of a registration with that specific name.\n\nThe `hasOwn()` method works similarly, with the only difference being that, unlike the\n`has()` method, which checks for registrations in parent containers as well, the `hasOwn()`\nmethod only checks for service registration in the container on which it was called.\n\n```ts\n// assume that HttpClient is registered in parent container, and not registered in child\nconsole.log(childContainer.has(\"HttpClient\")); // true\nconsole.log(childContainer.hasOwn(\"HttpClient\")); // false\nconsole.log(container.has(\"HttpClient\")); // true\nconsole.log(container.hasOwn(\"HttpClient\")); // true\n\n// Check with name\nconsole.log(container.has(\"HttpClient\", \"default\")); // true\nconsole.log(container.has(\"HttpClient\", \"not-registered-name\")); // false\n```\n\n### Unregistering Service\n\nTo remove a service registration, the `unregister()` method is used. The only mandatory\nargument is the [service key](#service-key). The second argument is the service name.\nThe third argument, `cascade`, indicates whether the service should also be removed from\nall parent containers.\n\nIf the service name is not specified (or is `undefined`), all service registrations with\nthat key will be removed. If a name is provided, only the registration with the\ncorresponding name will be removed.\n\n```ts\n// Unregister HttpClient from child container.\nchildContainer.unregister(\"HttpClient\");\n\n// Unregistering services that are not registered does nothing\nchildContainer.unregister(\"HttpClient\");\nchildContainer.unregister(\"HttpClient\", \"default\");\n\n// Unregister service from whole container hierarchy\nconsole.log(parentContainer.has(\"HttpClient\")); // true\nchildContainer.unregister(\"HttpClient\", undefined, true);\nconsole.log(parentContainer.has(\"HttpClient\")); // false\n```\n\n### Circular dependencies\n\n\nIf your classes have circular dependencies, and for some reason you cannot refactor to\neliminate them, there are 2 ways to register classes with circular dependencies.\n\n#### Using `circular()` Helper\n\nThe first way is to wrap your [service factory](#service-factory) using the `circular()`\nhelper:\n\n```ts\nimport { circular } from \"@alxcube/di-container\";\n\nclass CircularA {\n  constructor(private readonly circularB: CircularB) {}\n}\n\nclass CircularB {\n  constructor(private readonly circularA: CircularA) {}\n}\n\ncontainer.registerFactory(\n  CircularA,\n  circular(\n    (context) => new CircularA(\n      context.resolve(CircularB)\n    )\n  )\n);\ncontainer.registerFactory(\n  CircularB,\n  circular(\n    (context) => new CircularB(\n      context.resolve(CircularA)\n    )\n  )\n);\n```\n\nThis function will return a service factory that creates a JavaScript\n[Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy),\nreplacing the requested class, and only when this object is first accessed, your factory\nwill be called, which will create an instance of the class.\n\nIf you register classes with circular dependencies using the `registerClassConfig()` or\n`implement()` methods, set the `circular` option to `true`. Under the hood, this will\nwrap the generated service factory using the `circular()` helper.\n\n#### Using Delayed Dependencies Injection\n\nThe second approach is delayed dependency injection. This method is less convenient and\nnot as universal, but it does not use proxies, so it may be useful if proxies are not\navailable in your application environment, although this is unlikely.\n\nTo implement this approach, your class dependencies must be injected through public\nproperties or must have setter methods. Additionally, the `\"singleton\"` or `\"request\"`\nlifecycle is a mandatory requirement for such circular dependencies.\n\nYou can pass a callback to the `delay()` method of the [context object](#service-resolution-context)\nin the [service factory](#service-factory), in which you can resolve and set the necessary\ndependencies. This callback will be invoked after resolving the dependency stack of the\ncurrent service.\n\n```ts\nclass CircularA {\n  constructor(public circularB: CircularB = undefined) {}\n}\n\nclass CircularB {\n  constructor(public circularA: CircularA = undefined) {}\n}\n\ncontainer.registerFactory(\n  CircularA,\n  (context) => {\n    // Create instance without dependency\n    const instance = new CircularA();\n    // Delay resolution and injection of dependency\n    context.delay(() => instance.circularB = context.resolve(CircularB));\n    // Return instance\n    return instance;\n  },\n  {\n    // Lifecycle must be either \"request\" or \"singleton\"\n    lifecycle: \"request\"\n  }\n);\n\n// Same for other circular dependency\ncontainer.registerFactory(\n  CircularB,\n  (context) => {\n    const instance = new CircularB();\n    context.delay(() => instance.circularA = context.resolve(CircularA));\n    return instance;\n  },\n  { lifecycle: \"request\" }\n);\n```\n\n### Service Modules\n\nTo categorize registrations in the container and separate code, you can use service modules.\nThese are simple objects with a single `register()` method, which takes the container\nobject as its sole parameter.\n\nTo activate a module, pass it to the `loadModule()` method of the container.\n\n```ts\n// module.ts\nimport type { ServiceModule } from \"@alcube/di-container\";\nimport type { AppServiceMap } from \"./AppServiceMap\";\nexport const module: ServiceModule<AppServiceMap> = {\n  register(container) {\n    container.registerConstant(\"applicationKey\", \"some-app-key\");\n  }\n}\n\n// container.ts\nimport { Container } from \"@alxcube/di-container\";\nimport type { AppServiceMap } from \"./AppServiceMap\";\nimport { module } from \"./module\";\n\nconst container = new Container<AppServiceMap>();\ncontainer.loadModule(module);\n```\n\n### Testing\n\nThe container also provides some methods that are useful for testing purposes.\n\n#### Container Snapshots\n\nUsing the `backup()` method, you can create snapshots of the container's state, and with\nthe `restore()` method, you can roll back the container's state to a previous snapshot.\nSnapshots work on a stack principle, and their number is unlimited. Typically, you would\nuse the `backup()` and `restore()` methods, respectively, in the `beforeEach()` and\n`afterEach()` hooks of your testing framework.\n\nCalling the `backup()` method without parameters creates a snapshot of the container on\nwhich it was called. However, if the optional parameter `cascade` is set to `true`, this\nmethod will also be called on all parent containers, causing them to create snapshots of\ntheir own state. The `restore()` method works similarly. Be careful when using cascading\nsnapshots and remember to set the `cascade` parameter to `true` for the corresponding\n`restore()` calls, otherwise, you may encounter hard-to-track container state violations.\n\n```ts\nlet httpClientSpy: HttpClientSpy;\n\nbeforeEach(() => {\n  container.backup();\n  httpClientSpy = new HttpClientSpy();\n  container.registerConstant(\"HttpClient\", httpClientSpy, { replace: true });\n});\n\nafterEach(() => {\n  container.restore();\n})\n```\n\n#### Creating Class Instances\n\nThe `instantiate()` method exists for conveniently creating instances of a class with\ndependency injection through the constructor, using the container. This method takes the\nclass constructor as the first argument and a\n[tuple of dependencies](#dependencies-tuple) as the second argument, and returns an\ninstance of the provided class. This is convenient for use in unit testing specific\nclasses.\n\n```ts\nlet backendClient: ConcreteBackendClient;\n\nbeforeEach(() => {\n  backendClient = container.instantiate(ConcreteBackendClient, [\"HttpClient\"]);\n})\n```\n\n### Contextual Dependencies Resolving\n\nTo contextually resolve dependencies, you can use the methods `isResolvingFor()` and\n`isDirectlyResolvingFor()` of the [context object](#service-resolution-context) inside\n[service factories](#service-factory). Both methods take a [service key](#service-key) and\nan optional service name as parameters.\n\nThe first method returns `true` if the current\nservice (returned by the factory) is resolved as a dependency at any level for the\ncorresponding service. This means, for example, that the current service can be a\ndependency of a dependency of the service whose key is passed to the method.\n\nThe second method is similar to the first one but checks if the current service is a\ndirect dependency of the corresponding service.\n\nIf the `name` argument is not provided, only the service key is considered, and the\nname is ignored. To check if the current service is resolved specifically for the\ndefault registration of another service, pass `\"default\"` as the second argument\nexplicitly.\n\nYou can also get the entire current dependency resolution stack by calling the `getStack()`\nmethod of the context object. The stack is an array of\n[named service keys](#named-service-key), where the first element is the service key\nrequested from the container, and the last element is the key of the current service\n(in whose factory the check is performed).\n\nNote that for this factory to work correctly, it must have a `'transient'` lifecycle.\n\n```ts\n// Register class configs for implementations (this may be singletons)\ncontainer.registerClassConfig(LocalFileSystemDriver, [], { lifecycle: \"singleton\" });\ncontainer.registerClassConfig(CloudFileSystemDriver, [\"HttpClient\"]);\n\n// Register contextual service factory (this must be transient)\ncontainer.registerFactory(\"FileSystem\", (context) => {\n  if (context.isResolvingFor(\"UserpicRepository\")) {\n    return context.resolve(LocalFileSystemDriver);\n  }\n  return context.resolve(CloudFileSystemDriver);\n});\n```\n\n### Service Resolution Error\n\nWhen an error occurs during the resolution of a service, a `ServiceResolutionError` will\nbe thrown.\n\nThis error class contains a `stack` property representing the service resolution stack.\nThe stack is an array of [named service keys](#named-service-key), where the first element\nis the service key requested from the container, and the last element is the key of the\nservice in whose factory the error occurred.\n\nThe `cause` property of the `ServiceResolutionError` object contains the caught value that\ncaused the failure.\n\nThe `message` property contains a string that includes the string representation of the\ncaught value, as well as the textual representation of the resolution stack in reverse\norder: the first line of the stack represents the key and name of the service in whose\nfactory the error occurred.","readmeFilename":"README.md"}