├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __test__ ├── AddService.test.ts ├── AppendService.test.ts ├── Context.test.ts ├── Destroy.test.ts ├── Inject.test.ts ├── Injectable.test.ts ├── InjectionToken.test.ts ├── ReplaceService.test.ts ├── ServiceProviderServiceExtensions.test.ts ├── TryAddService.test.ts └── utils.ts ├── docs.json ├── docs ├── Architecture │ ├── Activator.mdx │ ├── Exceptions.mdx │ ├── Overview.mdx │ └── ServiceCollection.mdx ├── CSharpDevelopers.mdx ├── Context.mdx ├── GettingStarted.mdx ├── InjectionToken.mdx ├── Recommendations.mdx ├── ServiceDisposal.mdx ├── ServiceExtensions.mdx ├── ServiceLifetime.mdx ├── ServiceRegistration.mdx ├── ServiceRegistration │ ├── AddService.mdx │ └── TryAddService.mdx ├── SyntaxComparison.mdx ├── Temp_InjectingContext.mdx ├── Testing.mdx ├── UsageWithDeno.mdx ├── UsingDependencyInjection.mdx ├── UsingDependencyInjection │ ├── Overview.mdx │ ├── Scoped.mdx │ ├── Singleton.mdx │ └── Transient.mdx ├── WithoutTypescript.mdx ├── configuration.mdx ├── contributing.mdx ├── examples │ ├── express.mdx │ ├── feathersjs.mdx │ ├── grpc.mdx │ ├── koa.mdx │ ├── typeorm.mdx │ └── using-request-object-beyond-controller.mdx └── index.mdx ├── esbuild.config.mjs ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── AbstractServiceCollection.ts ├── Activator.ts ├── Context.ts ├── ContextRegistry.ts ├── Exceptions │ ├── ActivationFailedException.ts │ ├── ArgumentException.ts │ ├── InvalidOperationException.ts │ ├── LifestyleMismatchException.ts │ ├── ResolutionFailedException.ts │ ├── ServiceExistException.ts │ ├── ServiceNotFoundException.ts │ └── index.ts ├── Extensions │ ├── AddScopedExtensions.ts │ ├── AddSingletonExtensions.ts │ ├── AddTransientExtensions.ts │ ├── AppendScopedExtensions.ts │ ├── AppendSingletonExtensions.ts │ ├── AppendTransientExtensions.ts │ ├── ContextExtensions.ts │ ├── ReplaceScopedExtensions.ts │ ├── ReplaceSingletonExtensions.ts │ ├── ReplaceTransientExtensions.ts │ ├── ServiceProviderServiceExtensions.ts │ ├── TryAddScopedExtensions.ts │ ├── TryAddSingletonExtensions.ts │ ├── TryAddTransientExtensions.ts │ └── index.ts ├── Helpers.ts ├── Inject.ts ├── Injectable.ts ├── InjectionToken.ts ├── Injector.ts ├── Options.ts ├── RootServiceCollection.ts ├── ServiceCollection.ts ├── ServiceDescriptor.ts ├── ServiceLifetime.ts ├── ServiceProvider.ts ├── Types.ts ├── Utils.ts └── index.ts ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.test.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.2.0](https://github.com/ezzabuzaid/tiny-injector/compare/0.1.2...0.2.0) (2024-10-01) 2 | 3 | 4 | ### Features 5 | 6 | * use esbuild to compile to esm ([dc7c273](https://github.com/ezzabuzaid/tiny-injector/commit/dc7c2733060ee0a3bf52f082b71a6d56deb25b10)) 7 | 8 | ### [0.1.2](https://github.com/ezzabuzaid/tiny-injector/compare/0.1.1...0.1.2) (2024-06-07) 9 | 10 | ### [0.1.1](https://github.com/ezzabuzaid/tiny-injector/compare/0.1.0...0.1.1) (2022-02-07) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **ServiceProviderServiceExtensions:** add context parameter to GetServices with injectionToken ([36b1c76](https://github.com/ezzabuzaid/tiny-injector/commit/36b1c7698594b9e8dea07b20af18193a619017ea)) 16 | 17 | # 0.1.0 (2022-01-27) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * **Context:** remove get method from context and only rely on Injector as service locator ([25164ea](https://github.com/ezzabuzaid/tiny-injector/commit/25164ea4b21d3e79ba0287d6305ef70bd33c3b8b)) 23 | * escape mdx variable ServiceLifetime ([4708b05](https://github.com/ezzabuzaid/tiny-injector/commit/4708b05f4a46e79cf0766441c9765f63f20be0a9)) 24 | * **Injectable:** use root as default providedIn ([02be573](https://github.com/ezzabuzaid/tiny-injector/commit/02be573364a858b21ef2c154114770747ff96651)) 25 | * **InjectionToken:** add private field to prevent collision with empty classes ([26f93d7](https://github.com/ezzabuzaid/tiny-injector/commit/26f93d7210947763314ddfd9b51051bc9d9da263)) 26 | * **Injector:** cache implementationFactory result based on ServiceLifetime ([6e13a08](https://github.com/ezzabuzaid/tiny-injector/commit/6e13a08e3bc132533e7ebb78053e19ea8029882b)) 27 | * **Injector:** move InjectionToken overrides after plain class orderrides ([be10cf0](https://github.com/ezzabuzaid/tiny-injector/commit/be10cf0568d6a95ae9d74a3f1572e2f608cbf081)) 28 | 29 | 30 | ### Features 31 | 32 | * add AbstractServiceCollection to make it replacable ([ead6608](https://github.com/ezzabuzaid/tiny-injector/commit/ead660869687bd22da494b3828993650b605a746)) 33 | * add support for abstract class ([0dbf3e7](https://github.com/ezzabuzaid/tiny-injector/commit/0dbf3e71794c4c94776050203e30238188994441)) 34 | * add TryAddService functionality ([870a698](https://github.com/ezzabuzaid/tiny-injector/commit/870a698b0730385b429598bad5465e32c1e0f9c0)) 35 | * add tryAddService option to Injectable ([0dc3799](https://github.com/ezzabuzaid/tiny-injector/commit/0dc37990afeec19a40dbc16eb192081ecc40e5f9)) 36 | * **AppendExtensions:** add support for InjectionToken ([e3a7df8](https://github.com/ezzabuzaid/tiny-injector/commit/e3a7df85f89464bec7e45ea516d7aa13c76e663b)) 37 | * create concrete class of AbstractServiceCollection ([e3fe2f3](https://github.com/ezzabuzaid/tiny-injector/commit/e3fe2f3f17b161912327b4c0af9360f0157e3759)) 38 | * export RootServiceCollection as public API ([0d48d32](https://github.com/ezzabuzaid/tiny-injector/commit/0d48d3248dd4b3550439bd3d406345d67a574fa8)) 39 | * implement abstract Extensions system with RootInjector ([e9f0f7f](https://github.com/ezzabuzaid/tiny-injector/commit/e9f0f7fc2d5413bd31119f8da3c163b3c8323c93)) 40 | * **Injector:** add replace service extensions ([75e0b51](https://github.com/ezzabuzaid/tiny-injector/commit/75e0b51ee70cb7cfc0145f7fc8493c6615500288)) 41 | * **Injector:** throw ServiceExistException from AddService if a service already exist ([078103a](https://github.com/ezzabuzaid/tiny-injector/commit/078103a58522912910f15c4152639ea0b9b6185e)) 42 | * **ServiceProviderServiceExtensions:** add support for InjectionToken ([da25744](https://github.com/ezzabuzaid/tiny-injector/commit/da25744e72fa68cf330a9da9225803da4573866b)) 43 | * **ServiceProvider:** throw ArgumentException if GetRequiredService or GetServices received null or undefined as service type ([e7227ed](https://github.com/ezzabuzaid/tiny-injector/commit/e7227ed2f73054aeb3492992a94f581f9556ff71)) 44 | * split context binding to seperate class (ContextRegistry) ([a69fe63](https://github.com/ezzabuzaid/tiny-injector/commit/a69fe63fdf4633b8fa3feb03468ed124f7572548)) 45 | * throw ServiceNotFoundException if service not added ([4a37bba](https://github.com/ezzabuzaid/tiny-injector/commit/4a37bba92fd55d1d23678771e9aa016c2b8c4f4f)) 46 | 47 | ### Changelog 48 | 49 | All notable changes to this project will be documented in this file. Dates are displayed in UTC. 50 | 51 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 52 | 53 | #### 0.0.1 54 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement via contacting the project team at https://github.com/ezzabuzaid/tiny-injector/issues. 63 | 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][mozilla coc]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][faq]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [mozilla coc]: https://github.com/mozilla/diversity 131 | [faq]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | We always welcome bug reports, pull requests, API proposals, and overall feedback. Here are a few tips on how you can make reporting your issue as effective as possible. 7 | 8 | Please note we have a code of conduct, please follow it in all your interactions with the project. 9 | 10 | ## Stability 11 | 12 | - The intentions is to follow .net DI API naming, therefore avoid changing APIs and core behaviors in general. 13 | - We will allow for API changes in cases where there is no other way to achieve a high priority bug fix or improvement. 14 | 15 | ## License 16 | 17 | By contributing, you agree that your contributions will be licensed under its MIT License. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ezzabuzaid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tiny Injector 2 | 3 | Tiny Injector is a tiny yet powerful and flexible Dependency Injection library for web projects that uses TypeScript. 4 | 5 | > It could be used on top of existing projects. 6 | 7 | The work heavily inspired by . NET Dependency Injection, Angular +2 and [This Answer](https://stackoverflow.com/a/48187842/10415423). 8 | 9 | Parts of documentation are taken from the Microsoft DI website 10 | 11 | Keep in mind that this still work in progress, espically the documentation part. the API interface won't have dramatical change since the essence is to provide .Net DI interface. 12 | 13 | ## Docs 14 | 15 | you can find them [here](https://docs.page/ezzabuzaid/tiny-injector). 16 | 17 | ## License 18 | 19 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 20 | 21 | - See [LICENSE](/LICENSE) 22 | 23 | Built and maintained with 💛 by [ezzabuzaid](https://github.com/ezzabuzaid) 24 | -------------------------------------------------------------------------------- /__test__/AddService.test.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "../src"; 2 | import { ArgumentException, LifestyleMismatchException, ServiceExistException } from "../src/Exceptions"; 3 | import { Injector } from "../src/Injector"; 4 | import { ServiceCollection } from "../src/ServiceCollection"; 5 | import { getSupportedImplementationTypeTypes, getSupportedServiceTypeTypes } from "./utils"; 6 | 7 | const services = Injector.Of(new ServiceCollection()); 8 | 9 | function setup() { 10 | @Injectable() 11 | class Service { 12 | id = Math.random() * Math.random(); 13 | } 14 | @Injectable() 15 | class Implementation implements Service { 16 | id = Math.random() * Math.random(); 17 | } 18 | return { Service, Implementation } 19 | } 20 | 21 | describe('AddSingleton_PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 22 | const { Implementation, Service } = setup(); 23 | const types = getSupportedServiceTypeTypes(); 24 | types.forEach((type) => { 25 | test(`${ type }`, () => { 26 | expect(() => services.AddSingleton(type as any, Implementation)) 27 | .toThrowError(ArgumentException); 28 | }); 29 | }); 30 | }); 31 | 32 | describe('AddScoped_PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 33 | const { Implementation, Service } = setup(); 34 | const types = getSupportedServiceTypeTypes(); 35 | types.forEach((type) => { 36 | test(`${ type }`, () => { 37 | expect(() => services.AddScoped(type as any, Implementation)) 38 | .toThrowError(ArgumentException); 39 | }) 40 | }); 41 | }); 42 | 43 | describe('AddTransient_PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 44 | const { Implementation, Service } = setup(); 45 | const types = getSupportedServiceTypeTypes(); 46 | types.forEach((type) => { 47 | test(`${ type }`, () => { 48 | expect(() => services.AddTransient(type as any, Implementation)) 49 | .toThrowError(ArgumentException); 50 | }) 51 | }); 52 | }); 53 | 54 | describe('AddSingleton_PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 55 | const { Implementation, Service } = setup(); 56 | const types = getSupportedImplementationTypeTypes(); 57 | types.forEach((type) => { 58 | test(`${ type }`, () => { 59 | expect(() => services.AddSingleton(Service, type as any)) 60 | .toThrowError(ArgumentException); 61 | }) 62 | }); 63 | }); 64 | 65 | describe('AddScoped_PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 66 | const { Implementation, Service } = setup(); 67 | const types = getSupportedImplementationTypeTypes(); 68 | types.forEach((type) => { 69 | test(`${ type }`, () => { 70 | expect(() => services.AddScoped(Service, type as any)) 71 | .toThrowError(ArgumentException); 72 | }) 73 | }); 74 | }); 75 | 76 | describe('AddTransient_PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 77 | const { Implementation, Service } = setup(); 78 | const types = getSupportedImplementationTypeTypes(); 79 | types.forEach((type) => { 80 | test(`${ type }`, () => { 81 | expect(() => services.AddTransient(Service, type as any)) 82 | .toThrowError(ArgumentException); 83 | }) 84 | }); 85 | }); 86 | 87 | test('AddSingleton_ServiceExist_ServiceExistExceptionThrown', () => { 88 | const { Implementation, Service } = setup(); 89 | expect(() => { 90 | services.AddSingleton(Service); 91 | services.AddSingleton(Service); 92 | }).toThrowErrorOfType(ServiceExistException); 93 | }); 94 | 95 | test('AddTransient_ServiceExist_ServiceExistExceptionThrown', () => { 96 | const { Implementation, Service } = setup(); 97 | expect(() => { 98 | services.AddTransient(Service); 99 | services.AddTransient(Service); 100 | }).toThrowErrorOfType(ServiceExistException); 101 | }); 102 | 103 | test('AddScoped_ServiceExist_ServiceExistExceptionThrown', () => { 104 | const { Implementation, Service } = setup(); 105 | expect(() => { 106 | services.AddScoped(Service); 107 | services.AddScoped(Service); 108 | }).toThrowErrorOfType(ServiceExistException); 109 | }); 110 | 111 | test('AddSingleton_ServiceExistWithTransientLifetime_ServiceExistExceptionThrown', () => { 112 | const { Implementation, Service } = setup(); 113 | expect(() => { 114 | services.AddSingleton(Service); 115 | services.AddTransient(Service); 116 | }).toThrowErrorOfType(ServiceExistException); 117 | }); 118 | 119 | test('AddSingleton_ServiceExistWithScopedLifetime_ServiceExistExceptionThrown', () => { 120 | const { Implementation, Service } = setup(); 121 | expect(() => { 122 | services.AddSingleton(Service); 123 | services.AddScoped(Service); 124 | }).toThrowErrorOfType(ServiceExistException); 125 | }); 126 | 127 | test('AddTransient_ServiceExistWithScopedLifetime_ServiceExistExceptionThrown', () => { 128 | const { Implementation, Service } = setup(); 129 | expect(() => { 130 | services.AddTransient(Service); 131 | services.AddScoped(Service); 132 | }).toThrowErrorOfType(ServiceExistException); 133 | }); 134 | 135 | test('AddTransient_ServiceExistWithSingletonLifetime_ServiceExistExceptionThrown', () => { 136 | const { Implementation, Service } = setup(); 137 | expect(() => { 138 | services.AddTransient(Service); 139 | services.AddSingleton(Service); 140 | }).toThrowErrorOfType(ServiceExistException); 141 | }); 142 | 143 | test('AddScoped_ServiceExistWithSingletonLifetime_ServiceExistExceptionThrown', () => { 144 | const { Implementation, Service } = setup(); 145 | expect(() => { 146 | services.AddScoped(Service); 147 | services.AddSingleton(Service); 148 | }).toThrowErrorOfType(ServiceExistException); 149 | }); 150 | 151 | test('AddScoped_ServiceExistWithTransientLifetime_ServiceExistExceptionThrown', () => { 152 | const { Implementation, Service } = setup(); 153 | expect(() => { 154 | services.AddScoped(Service); 155 | services.AddTransient(Service); 156 | }).toThrowErrorOfType(ServiceExistException); 157 | }); 158 | 159 | test('AddSingleton_ServiceTypeOnly_InstanceOfServiceTypeReturned', () => { 160 | const { Implementation, Service } = setup(); 161 | services.AddSingleton(Service); 162 | 163 | const instance = services.GetRequiredService(Service); 164 | 165 | expect(instance).toBeInstanceOf(Service); 166 | }); 167 | 168 | test('AddTransient_ServiceTypeOnly_InstanceOfServiceTypeReturned', () => { 169 | const { Implementation, Service } = setup(); 170 | services.AddTransient(Service); 171 | 172 | const instance = services.GetRequiredService(Service); 173 | 174 | expect(instance).toBeInstanceOf(Service); 175 | }); 176 | 177 | test('AddScoped_ServiceTypeOnly_InstanceOfServiceTypeReturned', () => { 178 | const { Implementation, Service } = setup(); 179 | services.AddScoped(Service); 180 | const context = services.Create(); 181 | 182 | const instance = services.GetRequiredService(Service, context); 183 | 184 | expect(instance).toBeInstanceOf(Service); 185 | }); 186 | 187 | test('AddSingleton_ServiceTypeAndImplementationType_InstanceOfImplementationTypeReturned', () => { 188 | const { Implementation, Service } = setup(); 189 | services.AddSingleton(Service, Implementation); 190 | 191 | const instance = services.GetRequiredService(Service); 192 | 193 | expect(instance).toBeInstanceOf(Implementation); 194 | }); 195 | 196 | test('AddTransient_ServiceTypeAndImplementationType_InstanceOfImplementationTypeReturned', () => { 197 | const { Implementation, Service } = setup(); 198 | services.AddTransient(Service, Implementation); 199 | 200 | const instance = services.GetRequiredService(Service); 201 | 202 | expect(instance).toBeInstanceOf(Implementation); 203 | }); 204 | test('AddScoped_ServiceTypeAndImplementationType_InstanceOfImplementationTypeReturned', () => { 205 | const { Implementation, Service } = setup(); 206 | services.AddScoped(Service, Implementation); 207 | const context = services.Create(); 208 | 209 | const instance = services.GetRequiredService(Service, context); 210 | 211 | expect(instance).toBeInstanceOf(Implementation); 212 | }); 213 | 214 | test('AddSingleton_ServiceTypeAndImplementationFactory_ReturnResultOfImplementationTypeReturned', () => { 215 | const { Implementation, Service } = setup(); 216 | const factoryResult = new Implementation(); 217 | services.AddSingleton(Service, () => factoryResult); 218 | 219 | const result = services.GetRequiredService(Service); 220 | 221 | expect(result).toBe(factoryResult); 222 | }); 223 | test('AddTransient_ServiceTypeAndImplementationFactory_ReturnResultOfImplementationTypeReturned', () => { 224 | const { Implementation, Service } = setup(); 225 | const factoryResult = new Implementation(); 226 | services.AddTransient(Service, () => factoryResult); 227 | 228 | const result = services.GetRequiredService(Service); 229 | 230 | expect(result).toBe(factoryResult); 231 | }); 232 | 233 | test('AddScoped_ServiceTypeAndImplementationFactory_ReturnResultOfImplementationTypeReturned', () => { 234 | const { Implementation, Service } = setup(); 235 | const factoryResult = new Implementation(); 236 | services.AddScoped(Service, () => factoryResult); 237 | const context = services.Create(); 238 | 239 | const result = services.GetRequiredService(Service, context); 240 | 241 | expect(result).toBe(factoryResult); 242 | }); 243 | 244 | test('AddSingleton_AddSingletonServiceThatInjectTransientService_LifestyleMismatchExceptionThrown', () => { 245 | @Injectable() 246 | class Service { 247 | id = Math.random() * Math.random(); 248 | } 249 | expect(() => { 250 | @Injectable() 251 | class InjectableService { 252 | constructor( 253 | private service: Service 254 | ) { } 255 | } 256 | services.AddSingleton(InjectableService); 257 | services.AddTransient(Service); 258 | }).toThrowErrorOfType(LifestyleMismatchException); 259 | }); 260 | 261 | test('AddSingleton_AddSingletonServiceThatInjectScopedService_LifestyleMismatchExceptionThrown', () => { 262 | @Injectable() 263 | class Service { 264 | id = Math.random() * Math.random(); 265 | } 266 | expect(() => { 267 | @Injectable() 268 | class InjectableService { 269 | constructor( 270 | private service: Service 271 | ) { } 272 | } 273 | services.AddScoped(Service); 274 | services.AddSingleton(InjectableService); 275 | }).toThrowErrorOfType(LifestyleMismatchException); 276 | }); 277 | 278 | test('AddService_InjectedServicesNotAddedYet_InjectedServicesToBeDelegated', () => { 279 | @Injectable() 280 | class ToBeInjectedService { 281 | constructor() { } 282 | } 283 | 284 | @Injectable() 285 | class ParentService { 286 | constructor( 287 | public toBeInjectedService: ToBeInjectedService 288 | ) { } 289 | } 290 | 291 | services.AddSingleton(ParentService); 292 | services.AddSingleton(ToBeInjectedService); 293 | const parentService = services.GetRequiredService(ParentService) 294 | expect(parentService).toBeInstanceOf(ParentService); 295 | expect(parentService.toBeInjectedService).toBeInstanceOf(ToBeInjectedService); 296 | }); 297 | 298 | test('AddSingleton_InjectScopedDelegatedServiceInSingleton_LifestyleMismatchExceptionThrown', () => { 299 | expect(() => { 300 | @Injectable() 301 | class ToBeInjectedService { 302 | constructor() { } 303 | } 304 | 305 | @Injectable() 306 | class ParentService { 307 | constructor( 308 | public toBeInjectedService: ToBeInjectedService 309 | ) { } 310 | } 311 | 312 | services.AddSingleton(ParentService); 313 | services.AddScoped(ToBeInjectedService); 314 | }).toThrowErrorOfType(LifestyleMismatchException); 315 | }); 316 | 317 | test('AddSingleton_InjectTransientDelegatedServiceInSingleton_LifestyleMismatchExceptionThrown', () => { 318 | expect(() => { 319 | @Injectable() 320 | class ToBeInjectedService { 321 | constructor() { } 322 | } 323 | 324 | @Injectable() 325 | class ParentService { 326 | constructor( 327 | public toBeInjectedService: ToBeInjectedService 328 | ) { } 329 | } 330 | 331 | services.AddSingleton(ParentService); 332 | services.AddTransient(ToBeInjectedService); 333 | }).toThrowErrorOfType(LifestyleMismatchException); 334 | }); 335 | -------------------------------------------------------------------------------- /__test__/AppendService.test.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentException, Injectable, Injector } from "../src"; 2 | import { getSupportedImplementationTypeTypes, getSupportedServiceTypeTypes } from "./utils"; 3 | 4 | describe('AppendSingleton', () => { 5 | describe('PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 6 | const types = getSupportedServiceTypeTypes(); 7 | types.forEach((type) => { 8 | test(`${type}`, () => { 9 | expect(() => Injector.AppendSingleton(type as any)) 10 | .toThrowError(ArgumentException); 11 | }); 12 | }); 13 | }); 14 | 15 | describe('PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 16 | @Injectable() 17 | class Service { } 18 | 19 | const types = getSupportedImplementationTypeTypes(); 20 | types.forEach((type) => { 21 | test(`${type}`, () => { 22 | expect(() => Injector.AppendSingleton(Service, type as any)) 23 | .toThrowError(ArgumentException); 24 | }) 25 | }); 26 | }); 27 | 28 | it('AddMultipleServices_ServicesWillBeAdded', () => { 29 | @Injectable() 30 | class Service { 31 | id = Math.random(); 32 | } 33 | 34 | @Injectable() 35 | class FirstService extends Service { 36 | aid = Math.random(); 37 | } 38 | @Injectable() 39 | class SecondService extends Service { 40 | aid = Math.random(); 41 | } 42 | 43 | Injector.AppendSingleton(Service); 44 | Injector.AppendSingleton(Service, FirstService); 45 | Injector.AppendSingleton(Service, SecondService); 46 | const services = Injector.GetServices(Service); 47 | expect(services[0]).toBeInstanceOf(Service); 48 | expect(services[1]).toBeInstanceOf(FirstService); 49 | expect(services[2]).toBeInstanceOf(SecondService); 50 | }); 51 | 52 | it('RegisterSameTypeMultipleTimes_RetrunsDifferentInstancesOfTheSameImplementationType', () => { 53 | @Injectable() 54 | class Service { 55 | id = Math.random(); 56 | } 57 | 58 | Injector.AppendSingleton(Service); 59 | Injector.AppendSingleton(Service); 60 | Injector.AppendSingleton(Service); 61 | const services = Injector.GetServices(Service); 62 | expect(services[0]).toBeInstanceOf(Service); 63 | expect(services[1]).toBeInstanceOf(Service); 64 | expect(services[2]).toBeInstanceOf(Service); 65 | }); 66 | }); 67 | 68 | describe('AppendTransient', () => { 69 | describe('PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 70 | const types = getSupportedServiceTypeTypes(); 71 | types.forEach((type) => { 72 | test(`${type}`, () => { 73 | expect(() => Injector.AppendTransient(type as any)) 74 | .toThrowError(ArgumentException); 75 | }); 76 | }); 77 | }); 78 | 79 | describe('PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 80 | @Injectable() 81 | class Service { } 82 | 83 | const types = getSupportedImplementationTypeTypes(); 84 | types.forEach((type) => { 85 | test(`${type}`, () => { 86 | expect(() => Injector.AppendTransient(Service, type as any)) 87 | .toThrowError(ArgumentException); 88 | }) 89 | }); 90 | }); 91 | 92 | it('AddMultipleServices_ServicesWillBeAdded', () => { 93 | @Injectable() 94 | class Service { 95 | id = Math.random(); 96 | } 97 | 98 | @Injectable() 99 | class FirstService extends Service { 100 | aid = Math.random(); 101 | } 102 | @Injectable() 103 | class SecondService extends Service { 104 | aid = Math.random(); 105 | } 106 | 107 | Injector.AppendTransient(Service); 108 | Injector.AppendTransient(Service, FirstService); 109 | Injector.AppendTransient(Service, SecondService); 110 | const services = Injector.GetServices(Service); 111 | expect(services[0]).toBeInstanceOf(Service); 112 | expect(services[1]).toBeInstanceOf(FirstService); 113 | expect(services[2]).toBeInstanceOf(SecondService); 114 | }); 115 | }); 116 | 117 | describe('AppendScoped', () => { 118 | describe('PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 119 | const types = getSupportedServiceTypeTypes(); 120 | types.forEach((type) => { 121 | test(`${type}`, () => { 122 | expect(() => Injector.AppendTransient(type as any)) 123 | .toThrowError(ArgumentException); 124 | }); 125 | }); 126 | }); 127 | 128 | describe('PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 129 | @Injectable() 130 | class Service { } 131 | 132 | const types = getSupportedImplementationTypeTypes(); 133 | types.forEach((type) => { 134 | test(`${type}`, () => { 135 | expect(() => Injector.AppendTransient(Service, type as any)) 136 | .toThrowError(ArgumentException); 137 | }) 138 | }); 139 | }); 140 | 141 | it('AddMultipleServices_ServicesWillBeAdded', () => { 142 | @Injectable() 143 | class Service { 144 | id = Math.random(); 145 | } 146 | 147 | @Injectable() 148 | class FirstService extends Service { 149 | aid = Math.random(); 150 | } 151 | @Injectable() 152 | class SecondService extends Service { 153 | aid = Math.random(); 154 | } 155 | 156 | const context = Injector.Create(); 157 | Injector.AppendScoped(Service); 158 | Injector.AppendScoped(Service, FirstService); 159 | Injector.AppendScoped(Service, SecondService); 160 | const services = Injector.GetServices(Service, context); 161 | expect(services[0]).toBeInstanceOf(Service); 162 | expect(services[1]).toBeInstanceOf(FirstService); 163 | expect(services[2]).toBeInstanceOf(SecondService); 164 | }); 165 | }); 166 | 167 | -------------------------------------------------------------------------------- /__test__/Context.test.ts: -------------------------------------------------------------------------------- 1 | import { Injector } from "../src/index"; 2 | 3 | describe('Context', () => { 4 | it('UpdateExitingExtras_ReturnsNewValue', () => { 5 | const context = Injector.Create(); 6 | const key = 'test'; 7 | let value = new Object(); 8 | 9 | context.setExtra(key, value); 10 | expect(context.getExtra(key)).toEqual(value); 11 | 12 | value = new Object(); 13 | context.setExtra(key, value); 14 | expect(context.getExtra(key)).toEqual(value); 15 | }); 16 | it('AddExtra_ReturnsTheAddedExtra', () => { 17 | const context = Injector.Create(); 18 | const key = 'test'; 19 | let value = new Object(); 20 | 21 | context.setExtra(key, value); 22 | expect(context.getExtra(key)).toEqual(value); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /__test__/Destroy.test.ts: -------------------------------------------------------------------------------- 1 | import { Context, ServiceLifetime, Injectable } from "../src"; 2 | import { InvalidOperationException, ArgumentException } from "../src/Exceptions"; 3 | import { Injector } from "../src/Injector"; 4 | 5 | 6 | 7 | describe('Destroy', () => { 8 | describe('PrimitiveTypeAsContext_ArgumentExceptionThrown', () => { 9 | const types = [null, undefined, {}, function () { }, () => { }, [], false, true, '', ' ']; 10 | types.forEach((type) => { 11 | test(`${ type }`, () => { 12 | expect(() => Injector.Destroy(type as any)) 13 | .toThrowError(ArgumentException); 14 | }) 15 | }); 16 | }); 17 | 18 | 19 | test('ContextNotFound_InvalidOperationExceptionThrown', () => { 20 | const context = new Context(); 21 | 22 | expect(() => { 23 | Injector.Destroy(context); 24 | }) 25 | .toThrowError(InvalidOperationException); 26 | }); 27 | 28 | test('ContextParameterOfTypeContextAndIsFound_ContextIsRemoved', () => { 29 | @Injectable() 30 | class Service { 31 | id = Math.random() * Math.random(); 32 | } 33 | 34 | const context = Injector.Create(); 35 | Injector.AddScoped(Service); 36 | 37 | Injector.Destroy(context); 38 | expect(() => { 39 | Injector.GetRequiredService(Service, context) 40 | }).toThrowError(InvalidOperationException); 41 | }); 42 | }) 43 | -------------------------------------------------------------------------------- /__test__/Inject.test.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, InjectionToken, ServiceLifetime, Injector } from "../src"; 2 | 3 | test('Inject_MultipleServicesWithArrayType_ReturnArrayOfServices', () => { 4 | @Injectable() 5 | class Service { } 6 | 7 | @Injectable() 8 | class Service1 extends Service { } 9 | @Injectable() 10 | class Service2 extends Service { } 11 | 12 | @Injectable() 13 | class Client { 14 | constructor( 15 | @Inject(Service) public services: Service[], 16 | @Inject(Service) public services1: Service[], 17 | ) { } 18 | } 19 | 20 | Injector.AppendSingleton(Service); 21 | Injector.AppendSingleton(Service, Service1); 22 | Injector.AppendSingleton(Service, Service2); 23 | Injector.AppendSingleton(Client); 24 | 25 | const client = Injector.GetRequiredService(Client); 26 | 27 | [Service, Service1, Service2].forEach((serviceType, index) => { 28 | expect(client.services[index]).toBeInstanceOf(serviceType); 29 | }); 30 | 31 | expect(client.services.every((instance, index) => client.services1[index] === instance)).toBeTruthy(); 32 | 33 | }); 34 | 35 | test('Inject_MultipleServicesWithNonArrayType_ReturnLastAddedService', () => { 36 | @Injectable() 37 | class Service { } 38 | 39 | @Injectable() 40 | class Service1 extends Service { } 41 | @Injectable() 42 | class ServiceToBeAddedLast extends Service { } 43 | 44 | @Injectable() 45 | class Client { 46 | constructor( 47 | @Inject(Service) public services: {}, 48 | ) { } 49 | } 50 | 51 | Injector.AppendSingleton(Service); 52 | Injector.AppendSingleton(Service, Service1); 53 | Injector.AppendSingleton(Service, ServiceToBeAddedLast); 54 | Injector.AppendSingleton(Client); 55 | 56 | const client = Injector.GetRequiredService(Client); 57 | 58 | expect(client.services).toBeInstanceOf(ServiceToBeAddedLast); 59 | 60 | }); 61 | 62 | test('Inject_InjectionToken_ReturnImplementationFactoryReturnedValue', () => { 63 | const tokenValue = Math.random(); 64 | const TOKEN_UNDER_TEST = new InjectionToken('UnderTest', { 65 | lifetime: ServiceLifetime.Transient, 66 | implementationFactory: () => tokenValue 67 | }); 68 | 69 | @Injectable() 70 | class Client { 71 | constructor( 72 | @Inject(TOKEN_UNDER_TEST) public tokenUnderTest: number, 73 | ) { } 74 | } 75 | 76 | Injector.AppendSingleton(Client); 77 | 78 | const client = Injector.GetRequiredService(Client); 79 | 80 | expect(client.tokenUnderTest).toEqual(tokenValue); 81 | 82 | }); 83 | -------------------------------------------------------------------------------- /__test__/Injectable.test.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentException, Injectable, Injector, ServiceLifetime } from "../src/"; 2 | import RootServiceCollection from "../src/RootServiceCollection"; 3 | import { ServiceCollection } from "../src/ServiceCollection"; 4 | import './utils'; 5 | 6 | describe('Injectable', () => { 7 | test.todo('PrimitiveTypeAsServiceType_ArgumentExceptionThrown'); 8 | test('NonRootStringAsProvidedIn_ArgumentExceptionThrown', () => { 9 | expect(() => { 10 | @Injectable({ 11 | provideIn: randomString() as any, 12 | lifetime: ServiceLifetime.Singleton 13 | }) 14 | class Service { } 15 | }).toThrowErrorOfType(ArgumentException) 16 | }); 17 | test('ProvidedInNotOfTypeAbstractServiceCollection_ArgumentExceptionThrown', () => { 18 | expect(() => { 19 | @Injectable({ 20 | provideIn: class { } as any, 21 | lifetime: ServiceLifetime.Singleton 22 | }) 23 | class Service { } 24 | }).toThrowErrorOfType(ArgumentException) 25 | }); 26 | test('RootAsProvidedIn_UseRootServiceCollection', () => { 27 | @Injectable({ 28 | provideIn: 'root', 29 | lifetime: ServiceLifetime.Singleton 30 | }) 31 | class Service { } 32 | 33 | const service = Injector.Of(RootServiceCollection).GetService(Service); 34 | 35 | expect(service).toBeInstanceOf(Service); 36 | }); 37 | test('CustomServiceCollectionProvidedIn_UseItInsteadOfRootServiceCollection', () => { 38 | const customServiceCollection = new ServiceCollection(); 39 | @Injectable({ 40 | provideIn: customServiceCollection, 41 | lifetime: ServiceLifetime.Singleton 42 | }) 43 | class Service { } 44 | 45 | const service = Injector.Of(customServiceCollection).GetService(Service); 46 | 47 | expect(service).toBeInstanceOf(Service); 48 | }); 49 | test('NotSupportedLifetime_ArgumentExceptionThrown', () => { 50 | expect(() => { 51 | @Injectable({ 52 | provideIn: 'root', 53 | lifetime: randomString() as any 54 | }) 55 | class Service { } 56 | }).toThrowErrorOfType(ArgumentException); 57 | }); 58 | test('ScopedLifetime_UseAddScoped', () => { 59 | const context = Injector.Create(); 60 | @Injectable({ 61 | provideIn: 'root', 62 | lifetime: ServiceLifetime.Scoped 63 | }) 64 | class Service { } 65 | 66 | const service = Injector.GetService(Service, context); 67 | 68 | expect(service).toBeInstanceOf(Service) 69 | }); 70 | test('SingletonLifetime_UseAddSingleton', () => { 71 | @Injectable({ 72 | provideIn: 'root', 73 | lifetime: ServiceLifetime.Singleton 74 | }) 75 | class Service { } 76 | 77 | const service = Injector.GetService(Service); 78 | 79 | expect(service).toBeInstanceOf(Service) 80 | }); 81 | test('TransientLifetime_UseAddTransient', () => { 82 | @Injectable({ 83 | provideIn: 'root', 84 | lifetime: ServiceLifetime.Transient 85 | }) 86 | class Service { } 87 | 88 | const service1 = Injector.GetService(Service); 89 | const service2 = Injector.GetService(Service); 90 | 91 | expect(service1).toBeInstanceOf(Service); 92 | expect(service2).toBeInstanceOf(Service); 93 | expect(service1 !== service2).toBeTruthy(); 94 | }); 95 | }); 96 | 97 | function randomString() { 98 | return Math.random().toString(36).substring(2); 99 | } 100 | -------------------------------------------------------------------------------- /__test__/InjectionToken.test.ts: -------------------------------------------------------------------------------- 1 | import { isConstructor } from "../src/Utils"; 2 | import { InjectionToken, ServiceLifetime, ArgumentException } from "../src"; 3 | import { Injector } from "../src/Injector"; 4 | 5 | test('InjectionToken_CreateNewInstance_ReturnReferenceToNewClass', () => { 6 | const TOKEN = new InjectionToken('UnderTest'); 7 | expect(isConstructor(TOKEN)).toBeTruthy(); 8 | }); 9 | 10 | test('InjectionToken_ImplictTokenAdd_TokenAddedSuccessfully', () => { 11 | const tokenValue = Math.random(); 12 | const TOKEN = new InjectionToken('UnderTest', { 13 | lifetime: ServiceLifetime.Singleton, 14 | implementationFactory: () => tokenValue 15 | }); 16 | 17 | expect(Injector.GetRequiredService(TOKEN)).toEqual(tokenValue); 18 | }); 19 | 20 | test('InjectionToken_ExplictTokenAdd_TokenAddedSuccessfully', () => { 21 | const tokenValue = Math.random(); 22 | const TOKEN = new InjectionToken('UnderTest'); 23 | 24 | Injector.AddSingleton(TOKEN, () => tokenValue); 25 | expect(Injector.GetRequiredService(TOKEN)).toEqual(tokenValue); 26 | }); 27 | 28 | test('InjectionToken_AddAsSingleton_ReturnSameResultOnSubsequentCalls', () => { 29 | const TOKEN = new InjectionToken('UnderTest'); 30 | 31 | Injector.AddSingleton(TOKEN, () => Math.random()); 32 | 33 | expect(Injector.GetRequiredService(TOKEN)).toBe(Injector.GetRequiredService(TOKEN)); 34 | }); 35 | 36 | test('InjectionToken_AddAsScoped_ReturnSameForTheSameContext', () => { 37 | const TOKEN = new InjectionToken('UnderTest'); 38 | const context = Injector.Create(); 39 | 40 | Injector.AddScoped(TOKEN, () => Math.random()); 41 | 42 | expect( 43 | Injector.GetRequiredService(TOKEN, context) 44 | === 45 | Injector.GetRequiredService(TOKEN, context) 46 | ).toBeTruthy(); 47 | }); 48 | 49 | test('InjectionToken_AddAsScoped_ReturnDifferentResultForDifferentContexts', () => { 50 | const TOKEN = new InjectionToken('UnderTest'); 51 | const context1 = Injector.Create(); 52 | const context2 = Injector.Create(); 53 | 54 | Injector.AddScoped(TOKEN, () => Math.random()); 55 | 56 | expect( 57 | Injector.GetRequiredService(TOKEN, context1) 58 | !== 59 | Injector.GetRequiredService(TOKEN, context2) 60 | ).toBeTruthy(); 61 | }); 62 | 63 | 64 | test('InjectionToken_Options.LifetimeIsNotSupported_ArgumentExceptionThrown', () => { 65 | expect(() => { 66 | new InjectionToken('UnderTest', { 67 | lifetime: 10, 68 | implementationFactory: () => 10 69 | }); 70 | }).toThrowError(ArgumentException); 71 | }); 72 | 73 | test('InjectionToken_ImplementationNotFactoryFunction_ArgumentExceptionThrown', () => { 74 | const types = [null, undefined, {}, function () { }, [], false, true, '', ' ']; 75 | types.forEach((type) => { 76 | expect(() => { 77 | new InjectionToken('UnderTest', { 78 | lifetime: ServiceLifetime.Scoped, 79 | implementationFactory: type as any 80 | }); 81 | }).toThrowError(ArgumentException); 82 | }); 83 | }); 84 | 85 | describe('InjectionToken_NameIsNotString_ArgumentExceptionThrown', () => { 86 | const types = [null, undefined, {}, function () { }, () => { }, [], false, true, '', ' ']; 87 | types.forEach((type) => { 88 | expect(() => { 89 | new InjectionToken(type as any, { 90 | lifetime: ServiceLifetime.Scoped, 91 | implementationFactory: {} as any 92 | }); 93 | }).toThrowError(ArgumentException); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /__test__/ReplaceService.test.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentException, Injectable, Injector, ServiceNotFoundException } from "../src"; 2 | import { getSupportedImplementationTypeTypes, getSupportedServiceTypeTypes } from "./utils"; 3 | 4 | describe('ReplaceSingleton', () => { 5 | describe('PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 6 | const types = getSupportedServiceTypeTypes(); 7 | types.forEach((type) => { 8 | test(`${ type }`, () => { 9 | expect(() => Injector.ReplaceSingleton(type as any)) 10 | .toThrowError(ArgumentException); 11 | }); 12 | }); 13 | }); 14 | 15 | describe('PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 16 | @Injectable() 17 | class Service { } 18 | 19 | const types = getSupportedImplementationTypeTypes(); 20 | types.forEach((type) => { 21 | test(`${ type }`, () => { 22 | expect(() => Injector.ReplaceSingleton(Service, type as any)) 23 | .toThrowError(ArgumentException); 24 | }) 25 | }); 26 | }); 27 | 28 | it('ServiceNotExist_ServiceNotFoundExceptionThrown', () => { 29 | @Injectable() 30 | class Service { } 31 | 32 | expect(() => { 33 | Injector.ReplaceSingleton(Service); 34 | }).toThrowErrorOfType(ServiceNotFoundException); 35 | }); 36 | 37 | it('ServiceExist_ServiceWillBeReplaced', () => { 38 | 39 | @Injectable() 40 | class Service { 41 | id = Math.random(); 42 | } 43 | @Injectable() 44 | class ToBeAddedService extends Service { 45 | aid = Math.random(); 46 | } 47 | @Injectable() 48 | class ToBeReplacedService extends Service { 49 | rid = Math.random(); 50 | } 51 | 52 | 53 | Injector.AddSingleton(Service, ToBeAddedService); 54 | const service = Injector.GetRequiredService(Service); 55 | expect(service).toBeInstanceOf(ToBeAddedService); 56 | 57 | Injector.ReplaceSingleton(Service, ToBeReplacedService); 58 | const newService = Injector.GetRequiredService(Service); 59 | expect(newService).toBeInstanceOf(ToBeReplacedService); 60 | }); 61 | }); 62 | 63 | describe('ReplaceScoped', () => { 64 | describe('PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 65 | const types = getSupportedServiceTypeTypes(); 66 | types.forEach((type) => { 67 | test(`${ type }`, () => { 68 | expect(() => Injector.ReplaceScoped(type as any)) 69 | .toThrowError(ArgumentException); 70 | }); 71 | }); 72 | }); 73 | 74 | describe('PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 75 | @Injectable() 76 | class Service { } 77 | 78 | const types = getSupportedImplementationTypeTypes(); 79 | types.forEach((type) => { 80 | test(`${ type }`, () => { 81 | expect(() => Injector.ReplaceScoped(Service, type as any)) 82 | .toThrowError(ArgumentException); 83 | }) 84 | }); 85 | }); 86 | 87 | it('ServiceNotExist_ServiceNotFoundExceptionThrown', () => { 88 | @Injectable() 89 | class Service { } 90 | 91 | expect(() => { 92 | Injector.ReplaceScoped(Service); 93 | }).toThrowErrorOfType(ServiceNotFoundException); 94 | }); 95 | 96 | it('ServiceExist_ServiceWillBeReplaced', () => { 97 | @Injectable() 98 | class Service { 99 | id = Math.random(); 100 | } 101 | @Injectable() 102 | class ToBeAddedService extends Service { 103 | aid = Math.random(); 104 | } 105 | @Injectable() 106 | class ToBeReplacedService extends Service { 107 | rid = Math.random(); 108 | } 109 | 110 | const context = Injector.Create(); 111 | 112 | Injector.AddScoped(Service, ToBeAddedService); 113 | const service = Injector.GetRequiredService(Service, context); 114 | expect(service).toBeInstanceOf(ToBeAddedService); 115 | 116 | Injector.ReplaceScoped(Service, ToBeReplacedService); 117 | const newService = Injector.GetRequiredService(Service, context); 118 | expect(newService).toBeInstanceOf(ToBeReplacedService); 119 | }); 120 | }); 121 | 122 | describe('ReplaceTransient', () => { 123 | describe('PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 124 | const types = getSupportedServiceTypeTypes(); 125 | types.forEach((type) => { 126 | test(`${ type }`, () => { 127 | expect(() => Injector.ReplaceTransient(type as any)) 128 | .toThrowError(ArgumentException); 129 | }); 130 | }); 131 | }); 132 | 133 | describe('PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 134 | @Injectable() 135 | class Service { } 136 | 137 | const types = getSupportedImplementationTypeTypes(); 138 | types.forEach((type) => { 139 | test(`${ type }`, () => { 140 | expect(() => Injector.ReplaceTransient(Service, type as any)) 141 | .toThrowError(ArgumentException); 142 | }) 143 | }); 144 | }); 145 | 146 | it('ServiceNotExist_ServiceNotFoundExceptionThrown', () => { 147 | @Injectable() 148 | class Service { } 149 | 150 | expect(() => { 151 | Injector.ReplaceTransient(Service); 152 | }).toThrowErrorOfType(ServiceNotFoundException); 153 | }); 154 | 155 | it('ServiceExist_ServiceWillBeReplaced', () => { 156 | 157 | @Injectable() 158 | class Service { 159 | id = Math.random(); 160 | } 161 | @Injectable() 162 | class ToBeAddedService extends Service { 163 | aid = Math.random(); 164 | } 165 | @Injectable() 166 | class ToBeReplacedService extends Service { 167 | rid = Math.random(); 168 | } 169 | 170 | 171 | Injector.AddTransient(Service, ToBeAddedService); 172 | const service = Injector.GetRequiredService(Service); 173 | expect(service).toBeInstanceOf(ToBeAddedService); 174 | 175 | Injector.ReplaceTransient(Service, ToBeReplacedService); 176 | const newService = Injector.GetRequiredService(Service); 177 | expect(newService).toBeInstanceOf(ToBeReplacedService); 178 | }); 179 | }); 180 | 181 | // xit('AddService throws error after calling ReplaceService'); -------------------------------------------------------------------------------- /__test__/ServiceProviderServiceExtensions.test.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "../src"; 2 | import { ArgumentException } from "../src/Exceptions/ArgumentException"; 3 | import { LifestyleMismatchException } from "../src/Exceptions/LifestyleMismatchException"; 4 | import { Injector } from "../src/Injector"; 5 | import { ServiceCollection } from "../src/ServiceCollection"; 6 | 7 | 8 | const services = Injector.Of(new ServiceCollection()) 9 | 10 | test('GetRequiredService_GetSingleton_ReturnSameInstanceOnSubsequentCalls', () => { 11 | @Injectable() 12 | class Service { 13 | id = Math.random() * Math.random(); 14 | } 15 | 16 | services.AddSingleton(Service); 17 | 18 | const instance1 = services.GetRequiredService(Service); 19 | const instance2 = services.GetRequiredService(Service); 20 | const instance3 = services.GetRequiredService(Service); 21 | 22 | expect(instance1).toBe(instance2); 23 | expect(instance2).toBe(instance3); 24 | expect(instance1).toBe(instance3); 25 | }); 26 | 27 | test('GetRequiredService_GetMultipleSingleton_ReturnSameInstanceOnSubsequentCalls', () => { 28 | @Injectable() 29 | class Service { 30 | id = Math.random() * Math.random(); 31 | } 32 | @Injectable() 33 | class Service1 extends Service { } 34 | @Injectable() 35 | class Service2 extends Service { } 36 | 37 | services.AddSingleton(Service); 38 | services.AppendSingleton(Service, Service1); 39 | services.AppendSingleton(Service, Service2); 40 | 41 | const instances = services.GetServices(Service); 42 | const instances1 = services.GetServices(Service); 43 | 44 | [Service, Service1, Service2].forEach((serviceType, index) => { 45 | expect(instances[index]).toBeInstanceOf(serviceType); 46 | }); 47 | 48 | expect(instances.every((instance, index) => instances1[index] === instance)).toBeTruthy(); 49 | }); 50 | 51 | test('GetRequiredService_GetTransient_ReturnNewInstanceOnSubsequentCalls', () => { 52 | @Injectable() 53 | class Service { 54 | id = Math.random() * Math.random(); 55 | } 56 | 57 | services.AddTransient(Service); 58 | 59 | const instance1 = services.GetRequiredService(Service); 60 | const instance2 = services.GetRequiredService(Service); 61 | 62 | expect(instance1 !== instance2); 63 | }); 64 | 65 | test('GetRequiredService_GetMultipleTransient_ReturnNewInstanceOnSubsequentCalls', () => { 66 | @Injectable() 67 | class Service { 68 | id = Math.random() * Math.random(); 69 | } 70 | 71 | @Injectable() 72 | class Service1 extends Service { } 73 | 74 | services.AddTransient(Service); 75 | services.AppendTransient(Service, Service1); 76 | 77 | const instances = services.GetServices(Service); 78 | const instances1 = services.GetServices(Service); 79 | 80 | [Service, Service1].forEach((serviceType, index) => { 81 | expect(instances[index]).toBeInstanceOf(serviceType); 82 | }); 83 | expect(instances.every((instance, index) => instances1[index] !== instance)).toBeTruthy(); 84 | expect(instances.slice(0).reverse().every((instance, index) => instances1[index] !== instance)).toBeTruthy(); 85 | 86 | }); 87 | 88 | test('GetRequiredService_GetScoped_ReturnSameInstanceForSameContextInSubsequentCalls', () => { 89 | @Injectable() 90 | class Service { 91 | id = Math.random() * Math.random(); 92 | } 93 | 94 | @Injectable() 95 | class Service1 extends Service { } 96 | 97 | services.AddScoped(Service); 98 | services.AddScoped(Service1); 99 | 100 | const context = services.Create(); 101 | 102 | const instance1 = services.GetRequiredService(Service, context); 103 | const instance2 = services.GetRequiredService(Service, context); 104 | const instance3 = services.GetRequiredService(Service, context); 105 | const instance11 = services.GetRequiredService(Service1, context); 106 | const instance12 = services.GetRequiredService(Service1, context); 107 | 108 | expect(instance1 === instance2).toBeTruthy(); 109 | expect(instance2 === instance1).toBeTruthy(); 110 | expect(instance2 === instance3).toBeTruthy(); 111 | 112 | expect(instance11 === instance12).toBeTruthy(); 113 | }); 114 | 115 | test('GetRequiredService_GetScoped_ReturnDifferentInstanceForDifferentContext', () => { 116 | @Injectable() 117 | class ScopedService { 118 | id = Math.random() * Math.random(); 119 | } 120 | 121 | services.AddScoped(ScopedService); 122 | 123 | const context1 = services.Create(); 124 | const context2 = services.Create(); 125 | 126 | const instanceContext1 = services.GetRequiredService(ScopedService, context1); 127 | const instanceContext2 = services.GetRequiredService(ScopedService, context2); 128 | expect(instanceContext1 !== instanceContext2).toBeTruthy(); 129 | }); 130 | 131 | test('GetRequiredService_GetMultipleScoped_ReturnSameInstanceForSameContextOnSubsequentCallsWith', () => { 132 | @Injectable() 133 | class Service { 134 | id = Math.random() * Math.random(); 135 | } 136 | @Injectable() 137 | class Service1 extends Service { } 138 | 139 | services.AddScoped(Service); 140 | services.AppendScoped(Service, Service1); 141 | 142 | const context = services.Create(); 143 | 144 | const instances = services.GetServices(Service, context); 145 | const instances1 = services.GetServices(Service, context); 146 | 147 | [Service, Service1].forEach((serviceType, index) => { 148 | expect(instances[index]).toBeInstanceOf(serviceType); 149 | }); 150 | expect(instances.every((instance, index) => instances1[index] === instance)).toBeTruthy(); 151 | }); 152 | 153 | 154 | test('GetRequiredService_GetScopedWithoutContext_ArgumentExceptionThrown', () => { 155 | @Injectable() 156 | class Service { 157 | id = Math.random() * Math.random(); 158 | } 159 | services.AddScoped(Service); 160 | 161 | expect(() => { 162 | services.GetRequiredService(Service); 163 | }) 164 | .toThrowError(ArgumentException); 165 | }); 166 | 167 | test('GetRequiredService_GetScopedInTransientServiceWithoutContext_LifestyleMismatchExceptionThrown', () => { 168 | @Injectable() 169 | class Service { 170 | id = Math.random() * Math.random(); 171 | } 172 | @Injectable() 173 | class InjectableService { 174 | constructor( 175 | private service: Service 176 | ) { } 177 | } 178 | 179 | services.AddScoped(Service); 180 | services.AddTransient(InjectableService); 181 | 182 | expect(() => { 183 | services.GetRequiredService(InjectableService); 184 | }) 185 | .toThrowError(LifestyleMismatchException); 186 | }); 187 | 188 | -------------------------------------------------------------------------------- /__test__/TryAddService.test.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentException, Context, Injectable, Injector, ServiceNotFoundException } from "../src"; 2 | import { getSupportedImplementationTypeTypes, getSupportedServiceTypeTypes } from "./utils"; 3 | 4 | describe('TryAddSingleton', () => { 5 | describe('PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 6 | const types = getSupportedServiceTypeTypes(); 7 | types.forEach((type) => { 8 | test(`${ type }`, () => { 9 | expect(() => Injector.TryAddSingleton(type as any)) 10 | .toThrowError(ArgumentException); 11 | }); 12 | }); 13 | }); 14 | 15 | describe('PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 16 | @Injectable() 17 | class Service { } 18 | 19 | const types = getSupportedImplementationTypeTypes(); 20 | types.forEach((type) => { 21 | test(`${ type }`, () => { 22 | expect(() => Injector.TryAddSingleton(Service, type as any)) 23 | .toThrowError(ArgumentException); 24 | }) 25 | }); 26 | }); 27 | 28 | it('ServiceNotExist_ServiceWillBeAdded', () => { 29 | @Injectable() 30 | class Service { 31 | id = Math.random(); 32 | } 33 | @Injectable() 34 | class ToBeAddedService extends Service { 35 | aid = Math.random(); 36 | } 37 | 38 | Injector.TryAddSingleton(Service, ToBeAddedService); 39 | const service = Injector.GetRequiredService(Service); 40 | expect(service).toBeInstanceOf(ToBeAddedService); 41 | }); 42 | 43 | it('ServiceExist_ServiceWillNotBeAdded', () => { 44 | @Injectable() 45 | class Service { 46 | id = Math.random(); 47 | } 48 | @Injectable() 49 | class ToBeAddedService extends Service { 50 | aid = Math.random(); 51 | } 52 | @Injectable() 53 | class ToBeIgnoredService extends Service { 54 | rid = Math.random(); 55 | } 56 | 57 | Injector.TryAddSingleton(Service, ToBeAddedService); 58 | const service = Injector.GetRequiredService(Service); 59 | expect(service).toBeInstanceOf(ToBeAddedService); 60 | 61 | Injector.TryAddSingleton(Service, ToBeIgnoredService); 62 | const newService = Injector.GetRequiredService(Service); 63 | expect(newService).toBeInstanceOf(ToBeAddedService); 64 | }); 65 | }); 66 | 67 | describe('TryAddScoped', () => { 68 | describe('PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 69 | const types = getSupportedServiceTypeTypes(); 70 | types.forEach((type) => { 71 | test(`${ type }`, () => { 72 | expect(() => Injector.TryAddScoped(type as any)) 73 | .toThrowError(ArgumentException); 74 | }); 75 | }); 76 | }); 77 | 78 | describe('PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 79 | @Injectable() 80 | class Service { } 81 | 82 | const types = getSupportedImplementationTypeTypes(); 83 | types.forEach((type) => { 84 | test(`${ type }`, () => { 85 | expect(() => Injector.TryAddScoped(Service, type as any)) 86 | .toThrowError(ArgumentException); 87 | }) 88 | }); 89 | }); 90 | 91 | it('ServiceNotExist_ServiceWillBeAdded', () => { 92 | @Injectable() 93 | class Service { 94 | id = Math.random(); 95 | } 96 | @Injectable() 97 | class ToBeAddedService extends Service { 98 | aid = Math.random(); 99 | } 100 | 101 | const context = Injector.Create() 102 | Injector.TryAddScoped(Service, ToBeAddedService); 103 | const service = Injector.GetRequiredService(Service, context); 104 | expect(service).toBeInstanceOf(ToBeAddedService); 105 | }); 106 | 107 | it('ServiceExist_ServiceWillNotBeAdded', () => { 108 | @Injectable() 109 | class Service { 110 | id = Math.random(); 111 | } 112 | @Injectable() 113 | class ToBeAddedService extends Service { 114 | aid = Math.random(); 115 | } 116 | @Injectable() 117 | class ToBeIgnoredService extends Service { 118 | rid = Math.random(); 119 | } 120 | 121 | const context = Injector.Create() 122 | Injector.TryAddScoped(Service, ToBeAddedService); 123 | const service = Injector.GetRequiredService(Service, context); 124 | expect(service).toBeInstanceOf(ToBeAddedService); 125 | 126 | Injector.TryAddScoped(Service, ToBeIgnoredService); 127 | const newService = Injector.GetRequiredService(Service, context); 128 | expect(newService).toBeInstanceOf(ToBeAddedService); 129 | }); 130 | }); 131 | 132 | describe('TryAddTransient', () => { 133 | describe('PrimitiveTypeAsServiceType_ArgumentExceptionThrown', () => { 134 | const types = getSupportedServiceTypeTypes(); 135 | types.forEach((type) => { 136 | test(`${ type }`, () => { 137 | expect(() => Injector.TryAddTransient(type as any)) 138 | .toThrowError(ArgumentException); 139 | }); 140 | }); 141 | }); 142 | 143 | describe('PrimitiveTypeAsImplementationType_ArgumentExceptionThrown', () => { 144 | @Injectable() 145 | class Service { } 146 | 147 | const types = getSupportedImplementationTypeTypes(); 148 | types.forEach((type) => { 149 | test(`${ type }`, () => { 150 | expect(() => Injector.TryAddTransient(Service, type as any)) 151 | .toThrowError(ArgumentException); 152 | }) 153 | }); 154 | }); 155 | 156 | it('ServiceNotExist_ServiceWillBeAdded', () => { 157 | @Injectable() 158 | class Service { 159 | id = Math.random(); 160 | } 161 | @Injectable() 162 | class ToBeAddedService extends Service { 163 | aid = Math.random(); 164 | } 165 | 166 | Injector.TryAddTransient(Service, ToBeAddedService); 167 | const service = Injector.GetRequiredService(Service); 168 | expect(service).toBeInstanceOf(ToBeAddedService); 169 | }); 170 | 171 | it('ServiceExist_ServiceWillNotBeAdded', () => { 172 | @Injectable() 173 | class Service { 174 | id = Math.random(); 175 | } 176 | @Injectable() 177 | class ToBeAddedService extends Service { 178 | aid = Math.random(); 179 | } 180 | @Injectable() 181 | class ToBeIgnoredService extends Service { 182 | rid = Math.random(); 183 | } 184 | 185 | Injector.TryAddTransient(Service, ToBeAddedService); 186 | const service = Injector.GetRequiredService(Service); 187 | expect(service).toBeInstanceOf(ToBeAddedService); 188 | 189 | Injector.TryAddTransient(Service, ToBeIgnoredService); 190 | const newService = Injector.GetRequiredService(Service); 191 | expect(newService).toBeInstanceOf(ToBeAddedService); 192 | }); 193 | }); 194 | -------------------------------------------------------------------------------- /__test__/utils.ts: -------------------------------------------------------------------------------- 1 | import { ClassType } from "../src/Types"; 2 | import { isTypeOf } from "../src/Utils"; 3 | 4 | export function getSupportedServiceTypeTypes() { 5 | return [null, undefined, {}, function () { }, () => { }, [], false, true, '', ' '] 6 | } 7 | 8 | export function getSupportedImplementationTypeTypes() { 9 | return [{}, function () { }, [], false, true, '', ' ']; 10 | } 11 | 12 | expect.extend({ 13 | toThrowErrorOfType: (received, actual) => { 14 | try { 15 | received(); 16 | return { pass: false, message: () => 'No error thrown' }; 17 | } catch (error) { 18 | const pass = isTypeOf(error, actual); 19 | return { pass: isTypeOf(error, actual), message: () => pass ? '' : `${( error).name} is not ${actual.name}` }; 20 | } 21 | } 22 | }); 23 | 24 | declare global { 25 | namespace jest { 26 | interface Matchers { 27 | toThrowErrorOfType(errorType: ClassType): R 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tiny Injector", 3 | "noindex": false, 4 | "twitter": "ezzabuzaid", 5 | "googleTagManager": "GTM-W4SVSKL", 6 | "sidebar": [ 7 | [ 8 | "Overview", 9 | "/" 10 | ], 11 | [ 12 | "Getting Started", 13 | "/GettingStarted" 14 | ], 15 | [ 16 | "Service Registration", 17 | "/ServiceRegistration" 18 | ], 19 | [ 20 | "Service Lifetime", 21 | "/ServiceLifetime" 22 | ], 23 | [ 24 | "Using Dependency Injection", 25 | "/UsingDependencyInjection" 26 | ], 27 | [ 28 | "Injection Token", 29 | "/InjectionToken" 30 | ], 31 | [ 32 | "Context", 33 | "/Context" 34 | ], 35 | [ 36 | "Service Disposal", 37 | "/ServiceDisposal" 38 | ], 39 | [ 40 | "Configuration", 41 | "/Configuration" 42 | ], 43 | [ 44 | "Usage With Deno", 45 | "/UsageWithDeno" 46 | ], 47 | [ 48 | "Architecture", 49 | [ 50 | [ 51 | "Overview", 52 | "/Architecture/Overview" 53 | ], 54 | [ 55 | "ServiceCollection", 56 | "/Architecture/ServiceCollection" 57 | ], 58 | [ 59 | "Activator", 60 | "/Architecture/Activator" 61 | ], 62 | [ 63 | "Exceptions", 64 | "/Exceptions" 65 | ] 66 | ] 67 | ], 68 | [ 69 | "Recommendations", 70 | "/Recommendations" 71 | ], 72 | [ 73 | "Testing", 74 | "/Testing" 75 | ], 76 | [ 77 | "For C# Developers", 78 | "/CSharpDevelopers" 79 | ], 80 | [ 81 | "Syntax Comparison", 82 | "/SyntaxComparison" 83 | ], 84 | [ 85 | "Examples", 86 | [ 87 | [ 88 | "Express", 89 | "/examples/express" 90 | ], 91 | [ 92 | "Using Request Object Beyond Controller", 93 | "/examples/using-request-object-beyond-controller" 94 | ] 95 | ] 96 | ], 97 | [ 98 | "Contributing", 99 | "/Contributing" 100 | ] 101 | ] 102 | } 103 | -------------------------------------------------------------------------------- /docs/Architecture/Activator.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/Architecture/Exceptions.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/Architecture/Overview.mdx: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | TBD 4 | 5 | ## Injector 6 | 7 | ## Context 8 | 9 | The context is a special object that binds a set of services altogether. It is primarly existed to limit the existance of a service or set of services. 10 | 11 | ## Service Collection 12 | 13 | ```typescript 14 | import { 15 | Injector, 16 | ServiceCollection, 17 | ServiceProvider, 18 | Injectable, 19 | } from "tiny-injector"; 20 | 21 | @Injectable() 22 | class ServiceType {} 23 | 24 | const serviceCollection = new ServiceCollection(); 25 | serviceCollection.AddTransient(ServiceType); 26 | ``` 27 | 28 | The `ServiceCollection` is the collection of the service descriptors. We can register our services in this collection with different lifetimes (Transient, Scoped, Singleton). 29 | ServiceProvider is the simple built-in container that is included and supports constructor injection by default. 30 | 31 | ## Service Provider 32 | 33 | The `ServiceProvider` is responsible for resolving instances of types at runtime, as required by the application. These instances can be injected into other services resolved from the same dependency injection container. The ServiceProvider ensures that resolved services live for the expected lifetime. 34 | 35 | A root service provider will be created implictly at application startup and its lifetime corresponds to the app's lifetime. 36 | The The root service provider is disposed when the app shuts down. 37 | 38 | Service porvider comes as part of `ServiceCollection`, 39 | 40 | ```typescript 41 | import { 42 | Injector, 43 | ServiceCollection, 44 | ServiceProvider, 45 | Injectable, 46 | } from "tiny-injector"; 47 | 48 | @Injectable() 49 | class ServiceType {} 50 | const serviceCollection = new ServiceCollection(); 51 | const serviceProvider = serviceCollection.BuildServiceProvider(); 52 | 53 | serviceCollection.AddTransient(ServiceType); 54 | const serviceType = serviceProvider.GetRequiredService(ServiceType); 55 | ``` 56 | 57 | ## Service Descriptor 58 | 59 | ## Activator 60 | -------------------------------------------------------------------------------- /docs/Architecture/ServiceCollection.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/CSharpDevelopers.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/Context.mdx: -------------------------------------------------------------------------------- 1 | # Context 2 | 3 | The context is a special object that binds a set of services altogether. It is primarly existed to limit the existance of a service or set of services. 4 | 5 | Imagine in an express app, each time the user requests the server you create a context to limit all service with _Scoped lifetime_ to it, and after responding to that request, you remove the context so all services are removed. 6 | for instance, there's an internal context used to group all singleton services, what made them singleton is that the context will live throughout the application lifetime. 7 | 8 | ## Create Context 9 | 10 | - A Context can be created using `Create()` method. 11 | 12 | ```typescript 13 | @Injectable({ 14 | lifetime: ServiceLifetime.Scoped, 15 | }) 16 | class Logger {} 17 | 18 | const context = Injector.Create(); 19 | const logger = Injector.GetRequiredService(Logger, context); 20 | 21 | // Remove the context and everything bound to it. 22 | Injector.Destroy(context); 23 | ``` 24 | 25 | The above sample creates a context and used it to return an instance of `Logger` type, 26 | when the context is no longer needed you have to destroy it to free up the memory. 27 | 28 | - Using `CreateScope()` 29 | 30 | The other way is to use `CreateScope()` which simplifies the consumption of Scoped services. 31 | 32 | `CreateScope` takes function that provide the context as argument. the function could be void or promise 33 | 34 | ```typescript 35 | @Injectable({ 36 | lifetime: ServiceLifetime.Scoped, 37 | }) 38 | class Logger {} 39 | 40 | Injector.CreateScope((context) => { 41 | const logger = Injector.GetRequiredService(Logger, context); 42 | }); 43 | ``` 44 | 45 | In this sample, the context created internally and provided as argument, the `Injector.GetRequiredService` method used to request a `Logger` instance. 46 | 47 | the `Destroy` method is no longer needed since it will be called internally immediately after the function execution is done. 48 | 49 | ```typescript 50 | const context = Injector.Create(); 51 | 52 | const logger = Injector.GetRequiredService(Logger, context); 53 | // equivalent to 54 | Injector.GetRequiredService(serviceType, context); 55 | ``` 56 | 57 | ## Context Extras 58 | 59 | A context can holds _extras_ which you could use to store additional data 60 | 61 | ```typescript 62 | const context = Injector.Create(); 63 | 64 | context.setExtra("data", {}); 65 | 66 | context.getExtra("data"); 67 | ``` 68 | 69 | it's most useful when you want to store some data at the creation of context and use it later on when requesting or registering a service. 70 | 71 | ## Summary 72 | 73 | 1. Think of **Context** as map that store service. 74 | 75 | 2. All singleton services are managed interally by a context. 76 | 77 | 3. There's two way you can create context, using `Create` or `CreateScope`. 78 | 79 | 4. If you created a context using `Create` make sure to destroy it. 80 | 81 | 5. The intention of Context Extras as to store data from and transfer it from scope to another and not to store any dependencies. 82 | -------------------------------------------------------------------------------- /docs/GettingStarted.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tiny Injector 3 | description: Getting started with Tiny Injector 4 | --- 5 | 6 | # Getting Started 7 | 8 | ## Installation 9 | 10 | Installation can be only done using a package manager and it can only be used in typescript codebase. 11 | 12 | - Using npm 13 | 14 | `npm install tiny-injector` 15 | 16 | - Using yarn 17 | 18 | `yarn add tiny-injector` 19 | 20 | ## Configuration 21 | 22 | include these options in tsconfig.json 23 | 24 | ```json 25 | "target": "ES2016" // or higer, 26 | "experimentalDecorators": true, 27 | "emitDecoratorMetadata": true, 28 | ``` 29 | 30 | ## Example 31 | 32 | Abstract class that defines simple method to log information and errors. 33 | 34 | ```typescript 35 | abstract class Logger { 36 | abstract debug(...objects: any[]): void; 37 | abstract error(error: Error): void; 38 | } 39 | ``` 40 | 41 | This abstract class is implemented by two concrete types 42 | 43 | ```typescript 44 | import { Injector, Injectable } from "tiny-injector"; 45 | 46 | @Injectable() 47 | class FileLogger extends Logger { 48 | debug(...objects: any[]) { 49 | // write to debug file 50 | } 51 | 52 | error(error: Error) { 53 | // write to error file 54 | } 55 | } 56 | 57 | @Injectable() 58 | class ConsoleLogger extends Logger { 59 | debug(...objects: any[]) { 60 | console.log("DEBUG", new Date().toString()); 61 | console.log(...objects); 62 | } 63 | 64 | error(error: Error) { 65 | console.error(error); 66 | } 67 | } 68 | ``` 69 | 70 | The sample code register the `abstract Logger` service with the concrete type `FileLogger` in production and `ConsoleLogger` otherwise. The AddSingleton method registers the service with a Singleton lifetime. 71 | 72 | ```typescript 73 | if (process.env.NODE_ENV === "production") { 74 | Injector.AddSingleton(Logger, FileLogger); 75 | } else { 76 | Injector.AddSingleton(Logger, ConsoleLogger); 77 | } 78 | ``` 79 | 80 | in this sample, the `abstract Logger` is injected in `DataProvider` constructor. 81 | the injector is registering the `DataProvider` first, then locating it to execute the fetchData method. 82 | 83 | ```typescript 84 | @Injectable() 85 | class DataProvider { 86 | constructor(private logger: Logger) { 87 | logger.debug("DataProvider"); 88 | } 89 | 90 | async fetchData() { 91 | try { 92 | return await fetch(".../data"); 93 | } catch (error) { 94 | this.logger.error(error); 95 | } 96 | } 97 | } 98 | 99 | Injector.AddSingleton(DataProvider); 100 | 101 | const dataProvider = Injector.GetRequiredService(DataProvider); 102 | dataProvider.fetchData(); 103 | ``` 104 | 105 | as you can see we provided two arguments when adding the `abstract Logger` service and only one argument when adding the `DataProvider` service, because `DataProvider` doesn't have `abstraction`. 106 | -------------------------------------------------------------------------------- /docs/InjectionToken.mdx: -------------------------------------------------------------------------------- 1 | # Injection Token 2 | 3 | Injection tokens allows injection of values that don't have a runtime representation. 4 | 5 | Use an InjectionToken whenever the type you are injecting is not reified (does not have a runtime representation) such as when injecting an interface, callable type, array or parameterized type. 6 | 7 | InjectionToken is parameterized on T which is the type of object which will be returned by the Injector. 8 | 9 | [Reference](https://github.com/angular/angular/blob/master/packages/core/src/di/injection_token.ts) 10 | 11 | ```typescript 12 | interface MyInterface { 13 | name: string; 14 | age: number; 15 | } 16 | const token = new InjectionToken('MyInterfaceToken'); 17 | Injector.AddSingleton(token, () => { 18 | return { 19 | name: "Jon", 20 | age: 20 21 | }; 22 | }) 23 | ``` 24 | 25 | Providing primitive values 26 | 27 | ```typescript 28 | const token = new InjectionToken('ProductionEnv'); 29 | 30 | Injector.AddSingleton(token, () => process.env.NODE_ENV === 'production'); 31 | 32 | const isProduction = Injector.GetRequiredService(token); 33 | 34 | // Or 35 | 36 | @Injectable({ 37 | lifetime: ServiceLifetime.Singleton 38 | }) 39 | class MyService { 40 | constructor(@Inject(token) isProduction: boolean) { 41 | } 42 | } 43 | ``` 44 | 45 | Or even better you can specify option argument 46 | 47 | ```typescript 48 | interface MyInterface { 49 | name: string; 50 | age: number; 51 | } 52 | const token = new InjectionToken('MyInterfaceToken', { 53 | lifetime: ServiceLifetime.Singleton, 54 | implementationFactory: () => { 55 | return { 56 | name: "Jon", 57 | age: 20 58 | }; 59 | } 60 | }); 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/Recommendations.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/ServiceDisposal.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/ServiceExtensions.mdx: -------------------------------------------------------------------------------- 1 | TBD -------------------------------------------------------------------------------- /docs/ServiceLifetime.mdx: -------------------------------------------------------------------------------- 1 | # Service lifetime 2 | 3 | Service lifetime is the total time for a service starting from the time it has been requested up till it is disposed of. 4 | 5 | The lifetime of a service can be either 6 | 7 | - Transient 8 | 9 | - Scoped 10 | 11 | - Singleton 12 | 13 | ## Singleton 14 | 15 | Singleton lifetime services are created The first time they're requested which means the same instance will be returned for each subsequent request. 16 | 17 | Singleton services are created only one time per container, aka `ServiceProvider`. for instance, a `RootServiceProvider` will be created implictly at app startup which will holds the singletons. 18 | Creating new `ServiceProvider` will lead to have different instances of a singleton service. 19 | 20 | The sample app demonstrates the difference between using the root service provider and custom one 21 | 22 | ```typescript 23 | import { Injector, ServiceCollection, ServiceProvider } from "tiny-injector"; 24 | 25 | // Service Type 26 | class Operation {} 27 | 28 | // Implictly it uses the root service provider 29 | Injector.AddSingleton(Operation); 30 | 31 | const dedicatedServiceCollection = new ServiceCollection(); 32 | dedicatedServiceCollection.AddSingleton(Operation); 33 | 34 | const dedicatedServiceProvider = 35 | dedicatedServiceCollection.BuildServiceProvider(); 36 | console.log( 37 | Injector.GetRequiredService(Operation) === 38 | dedicatedServiceProvider.GetRequiredService(Operation) 39 | // Will log false, because Operation have been requested from a different service provider 40 | ); 41 | 42 | // or you can user the Of method to build the service provider internally 43 | // worth mention that the same service provider will be used for the same serviceCollection instance 44 | console.log( 45 | Injector.GetRequiredService(Operation) === 46 | Injector.Of(dedicatedServiceCollection).GetRequiredService(Operation) 47 | ); 48 | ``` 49 | 50 | Check the _Architecture_ section to learn more. 51 | 52 | When scope validation is enabled you cannot inject either Transient or Scoped service. 53 | 54 | ## Transient 55 | 56 | Transient lifetime services are created each time they're requested. 57 | 58 | if a transient service used in scoped service, it will be disposed along with scoped service which implies that, the Transient will live as long as the Scoped service lives. 59 | 60 | ## Scoped 61 | 62 | Scoped services are created once per context. 63 | 64 | Scoped objects are the same for each context but different across each context. 65 | 66 | Context is special object that usually will be created with each web request when using routing framework such as `express.js` 67 | 68 | [Read More](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped) 69 | 70 | ## Scope validation 71 | 72 | 1. Using Scoped or Transient service in a Singleton service will eventually be singleton since singleton service will be available througot the application lifetime which leads to [**Captive Dependency**](https://blog.ploeh.dk/2014/06/02/captive-dependency/), when occured, `LifestyleMismatchException` will be thrown. 73 | 74 | 2. Injecting Scoped service in Transient service will require context to resolve the Transient service, if no context provided, `LifestyleMismatchException` will be thrown. 75 | 76 | Look at _Configuration_ section to disable this behaviour. 77 | 78 | ## [Reference](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) 79 | 80 | ## Example 81 | 82 | To demonstrate the difference between service lifetimes and their registration options, 83 | consider the following abstractions that represent an operation with an identifier, `operationId`. 84 | Depending on how the lifetime of an operation's service is configured for the following abstractions, the `Injector` provides 85 | either the same or different instances of the service when requested: 86 | 87 | ```typescript 88 | abstract class AbstractOperation { 89 | abstract operationId: number; 90 | } 91 | 92 | abstract class AbstractOperationTransient extends AbstractOperation {} 93 | abstract class AbstractOperationScoped extends AbstractOperation {} 94 | abstract class AbstractOperationSingleton extends AbstractOperation {} 95 | ``` 96 | 97 | The following Operation class implements `AbstractOperation` then generates a random number and stores in in the `operationId` property: 98 | 99 | ```typescript 100 | class Operation extends AbstractOperation { 101 | constructor() { 102 | super(); 103 | this.operationId = Math.random(); 104 | } 105 | } 106 | ``` 107 | 108 | The following code creates multiple registrations of the Operation class according to the named lifetimes: 109 | 110 | ```typescript 111 | Injector.AddTransient(AbstractOperationTransient, Operation); 112 | Injector.AddScoped(AbstractOperationScoped, Operation); 113 | Injector.AddSingleton(AbstractOperationSingleton, Operation); 114 | ``` 115 | 116 | The sample demonstrates transient lifetime when being requested multiple times. 117 | requesting `AbstractOperationTransient` multiple times will results in constructing new instance each time 118 | 119 | ```typescript 120 | const firstOperationTransient = Injector.GetRequiredService( 121 | AbstractOperationTransient 122 | ); 123 | const secondOperationTransient = Injector.GetRequiredService( 124 | AbstractOperationTransient 125 | ); 126 | 127 | console.log(firstOperationTransient.operationId); 128 | console.log(secondOperationTransient.operationId); 129 | // operationId from differnet instances won't be the same due to the transient lifetime. 130 | console.log( 131 | firstOperationTransient.operationId === secondOperationTransient.operationId 132 | // Will log "false" 133 | ); 134 | ``` 135 | 136 | The sample demonstrates scoped lifetime when being requested multiple times. 137 | requesting `AbstractOperationScoped` multiple times will results in returning the same instance for a giving context 138 | 139 | ```typescript 140 | const context = Injector.Create(); 141 | 142 | const firstOperationScoped = Injector.GetRequiredService( 143 | AbstractOperationScoped, 144 | context 145 | ); 146 | const secondOperationScoped = Injector.GetRequiredService( 147 | AbstractOperationScoped, 148 | context 149 | ); 150 | 151 | console.log(firstOperationScoped.operationId); 152 | console.log(secondOperationScoped.operationId); 153 | // operationId will equal since the same instance is returning, because we used the same context in each requeest 154 | console.log( 155 | firstOperationScoped.operationId === secondOperationScoped.operationId 156 | // Will log "true" 157 | ); 158 | ``` 159 | 160 | The sample demonstrates scoped lifetime when being requested multiple times but for different contexts. 161 | 162 | ```typescript 163 | const firstContext = Injector.Create(); 164 | const secondContext = Injector.Create(); 165 | 166 | const firstOperationScoped = Injector.GetRequiredService( 167 | AbstractOperationScoped, 168 | firstContext 169 | ); 170 | const secondOperationScoped = Injector.GetRequiredService( 171 | AbstractOperationScoped, 172 | secondContext 173 | ); 174 | 175 | console.log(firstOperationScoped.operationId); 176 | console.log(secondOperationScoped.operationId); 177 | // operationId will not equal since the different instance returned, because we used different context in each requeest 178 | console.log( 179 | firstOperationScoped.operationId === secondOperationScoped.operationId 180 | // Will log "false" 181 | ); 182 | ``` 183 | 184 | The sample demonstrates singleton lifetime when being requested multiple times. 185 | requesting `AbstractOperationSingleton` multiple times will results in returning the same instance 186 | 187 | ```typescript 188 | const firstOperationSingleton = Injector.GetRequiredService( 189 | AbstractOperationSingleton 190 | ); 191 | const secondOperationSingleton = Injector.GetRequiredService( 192 | AbstractOperationSingleton 193 | ); 194 | 195 | console.log(firstOperationSingleton.operationId); 196 | console.log(secondOperationSingleton.operationId); 197 | // operationId will be the same since it is the same instance. 198 | console.log( 199 | firstOperationSingleton.operationId === secondOperationSingleton.operationId 200 | // Will log "true" 201 | ); 202 | ``` 203 | 204 | ## Summary 205 | 206 | - **Transient** objects are always different. The transient `operationId` value is different in each instance. 207 | - **Scoped** objects are the same for a given context but differ across each new context. 208 | - **Singleton** objects are the same everytime. 209 | - The default container is `RootServiceProvider` 210 | - Do not resolve a scoped service from a singleton and be careful not to do so indirectly, for example, through a transient service. It may cause the service to have incorrect state when processing subsequent requests. It's fine to: 211 | 212 | - Resolve a singleton service from a scoped or transient service. 213 | - Resolve a scoped service from another scoped or transient service. 214 | -------------------------------------------------------------------------------- /docs/ServiceRegistration.mdx: -------------------------------------------------------------------------------- 1 | # Service registration 2 | 3 | Tiny Injector provide similar interface to .Net DI with almost identical behavior. 4 | There are several service registration methods that are useful in different scenarios. 5 | 6 | 1. Using `Injector` 7 | 2. Using `@Injectable` decorator. 8 | 9 | ## Using Lifetime methods 10 | 11 | ### Single registration 12 | 13 | | Syntax | Example | 14 | | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | 15 | | `Injector.Add{LIFETIME}({IMPLEMENTATION})` | `Injector.AddSingleton(ConcreteDep)` | 16 | | `Injector.Add{LIFETIME}({SERVICE}, {IMPLEMENTATION})` | `Injector.AddSingleton(AbstractDep, ConcreteDep)` | 17 | | `Injector.Add{LIFETIME}({SERVICE}, (context) => new {IMPLEMENTATION}`) | `Injector.AddSingleton(AbstractDep, (context) => new ConcreteDep())` | 18 | | `Injector.Add{LIFETIME}({InjectionToken}, (context) => {IMPLEMENTATION})` | `Injector.AddSingleton(new InjectionToken<{number}>('A Token'), (context) => 10)` | 19 | 20 | > Registering a service with only an implementation type is equivalent to registering that service with the same implementation and service type. 21 | 22 | ```typescript 23 | Injector.AddSingleton(ServiceType); 24 | Injector.AddSingleton(ServiceType, ServiceType); 25 | ``` 26 | 27 | > Calling `Add{Lifetime}` more than one time will result in throwing `ServiceExistException`, if your intentions to register multiple implementations, check out `Append{lifetime}` instead. 28 | 29 | ### Multiple registrations 30 | 31 | | Syntax | Example | 32 | | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | 33 | | `Injector.Append{LIFETIME}({IMPLEMENTATION})` | `Injector.AppendSingleton(ConcreteDep)` | 34 | | `Injector.Append{LIFETIME}({SERVICE}, {IMPLEMENTATION})` | `Injector.AppendSingleton(AbstractDep, ConcreteDep)` | 35 | | `Injector.Append{LIFETIME}({SERVICE}, (context) => new {IMPLEMENTATION})` | `Injector.AppendSingleton(AbstractDep, (context) => new ConcreteDep())` | 36 | | `Injector.Append{LIFETIME}({InjectionToken}, (context) => {IMPLEMENTATION})` | `Injector.AppendSingleton(new InjectionToken<{number}>('A Token'), (context) => 10)` | 37 | 38 | ```typescript 39 | Injector.AppendSingleton(ServiceType); 40 | Injector.AppendSingleton(ServiceType, ServiceType); 41 | Injector.AppendSingleton(ServiceType, ImplementationType); 42 | Injector.AppendSingleton(ServiceType, implementationFactory); 43 | ``` 44 | 45 | Example 46 | 47 | ```typescript 48 | abstract class Logger {} 49 | 50 | @Injectable() 51 | class InformativeLogger extends Logger {} 52 | 53 | @Injectable() 54 | class WarningLogger extends Logger {} 55 | 56 | Injector.AppendSingleton(Logger, InformativeLogger); 57 | Injector.AppendSingleton(Logger, WarningLogger); 58 | ``` 59 | 60 | You can use `AddSingleton` for the first add, but it is recommended to use `AppendSingleton` if you want array of implementations. 61 | 62 | > Multiple implementations of a service cannot be registered using the methods that don't take an explicit service type. These methods can register multiple instances of a service, but they will all have the same implementation type. 63 | 64 | ```typescript 65 | Injector.AppendSingleton(ServiceType); 66 | Injector.AppendSingleton(ServiceType); 67 | Injector.AppendSingleton(ServiceType); 68 | 69 | const services = Injector.GetServices(ServiceType); 70 | // services[0], services[1], and services[2] will have the same type as ServiceType 71 | // Basically, it is like creating 3 instances of ServiceType 72 | ``` 73 | 74 | > Calling `Injector.GetRequiredService(ServiceType)` will return the last registered implementation 75 | 76 | ### Safe registration 77 | 78 | | Syntax | Example | 79 | | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | 80 | | `Injector.TryAdd{LIFETIME}({IMPLEMENTATION})` | `Injector.TryAddSingleton(ConcreteDep)` | 81 | | `Injector.TryAdd{LIFETIME}({SERVICE}, {IMPLEMENTATION})` | `Injector.TryAddSingleton(AbstractDep, ConcreteDep)` | 82 | | `Injector.TryAdd{LIFETIME}({SERVICE}, (context) => new {IMPLEMENTATION})` | `Injector.TryAddSingleton(AbstractDep, (context) => new ConcreteDep())` | 83 | | `Injector.TryAdd{LIFETIME}({InjectionToken}, (context) => {IMPLEMENTATION})` | `Injector.TryAddSingleton(new InjectionToken<{number}>('A Token'), (context) => 10)` | 84 | 85 | > Register the service only if there isn't already an implementation registered. 86 | 87 | ```typescript 88 | Injector.TryAdd(ServiceType, FirstImplementationType); 89 | Injector.TryAdd(ServiceType, SecondImplementationType); 90 | ``` 91 | 92 | Only `FirstImplementationType` will be registered. 93 | 94 | ### Example 95 | 96 | ```typescript 97 | @Injectable() 98 | abstract class Logger {} 99 | 100 | @Injectable() 101 | class InformativeLogger extends Logger {} 102 | 103 | Injector.AddSingleton(Logger, InformativeLogger); 104 | ``` 105 | 106 | Using factory as implementation 107 | 108 | ```typescript 109 | Injector.AddSingleton(Logger, (context) => { 110 | // Some interesting logic 111 | return new InformativeLogger(); 112 | }); 113 | ``` 114 | 115 | > Make sure that ServiceType is always class syntax otherwise an error will be thrown. 116 | > @Injectable is must present on the service class otherwise it won't be resolved 117 | 118 | ## Using @Injectable 119 | 120 | The other approche is to use `@Injectable` decorator as it is the best choice for tree shaking purposes. 121 | with `@Injectable` you don't need to explict service registration using `Add{Lifetime}` methods. 122 | 123 | > In some cases you might need to use litetime methods such as, deffered registration or if you want to provide factoryFn as implementation 124 | 125 | ### Example 126 | 127 | Register `ServiceType` as Singleton, 128 | 129 | ```typescript 130 | @Injectable({ 131 | lifetime: ServiceLifetime.Singleton, 132 | }) 133 | class ServiceType {} 134 | ``` 135 | 136 | > Remember, registering a service with only an implementation type is equivalent to registering that service with the same implementation and service type. 137 | > so this would be registered as this behind the scene 138 | 139 | ```typescript 140 | @Injectable() 141 | class ServiceType {} 142 | 143 | Injector.AddSingleton(ServiceType, ServiceType); 144 | ``` 145 | 146 | Register `InformativeLogger` as Singleton against `Logger` 147 | 148 | ```typescript 149 | abstract class Logger {} 150 | 151 | @Injectable({ 152 | lifetime: ServiceLifetime.Singleton, 153 | serviceType: Logger, 154 | }) 155 | class InformativeLogger extends Logger {} 156 | ``` 157 | 158 | This is an alias for 159 | 160 | ```typescript 161 | Injector.AddSingleton(Logger, InformativeLogger); 162 | ``` 163 | 164 | > keep in mind that `@Injectable` is always needed in order to resolve the class constructor parameters type. 165 | 166 | ## Strict Type 167 | 168 | Thanks to TypeScript, the implementation will be constrained to the ServiceType. 169 | 170 | e.g 171 | 172 | ```typescript 173 | @Injectable() 174 | class Vehicle { 175 | start() {} 176 | } 177 | 178 | @Injectable() 179 | class Weapon { 180 | shoot() {} 181 | } 182 | 183 | Injector.AddSingleton(Vehicle, Weapon); 184 | 185 | Injector.AddSingleton(Vehicle, (context) => new Weapon()); 186 | ``` 187 | 188 | the above sample won't work since `Weapon` is not subtype of `Vehicle` 189 | 190 | ```typescript 191 | @Injectable() 192 | class Vehicle { 193 | start() {} 194 | } 195 | 196 | @Injectable() 197 | class Truck extends Vehicle { 198 | warmUp() {} 199 | } 200 | 201 | Injector.AddSingleton(Vehicle, Truck); 202 | 203 | Injector.AddSingleton(Vehicle, (context) => new Truck()); 204 | ``` 205 | 206 | now the implementation either factory or type will work just fine since the Truck is subtype of Vehicle. 207 | 208 | ### Type Equivalence 209 | 210 | Because of JavaScript nature, having different classes with the same properties and methods make them equivalent in regards to type, 211 | so the below code will work with no errors, although, this code can be handled at runtime using `instanceof` keyword 212 | 213 | ```typescript 214 | class Test { 215 | variable = 10; 216 | } 217 | 218 | class Service { 219 | variable = 10; 220 | } 221 | 222 | class Implementation extends Test {} 223 | 224 | Injector.AddSingleton(Service, Implementation); 225 | ``` 226 | -------------------------------------------------------------------------------- /docs/ServiceRegistration/AddService.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/ServiceRegistration/TryAddService.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/SyntaxComparison.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Comparison between Tiny Injector, Angular DI, and .Net DI 3 | --- 4 | 5 | # Comparison between Tiny Injector, Angular DI, and .Net DI 6 | 7 | ## Tiny Injector 8 | 9 | ```typescript 10 | Injector.AddSingleton(ServiceType); 11 | Injector.AddSingleton(ServiceType, ServiceType); 12 | Injector.AddSingleton(ServiceType, ImplementationType); 13 | Injector.AddSingleton(ServiceType, implementationFactory); 14 | ``` 15 | 16 | ## .Net 17 | 18 | ```c# 19 | app.Services.AddSingleton(); 20 | app.Services.AddSingleton(); 21 | app.Services.AddSingleton(); 22 | app.Services.AddSingleton(implementationFactory); 23 | ``` 24 | 25 | ## Angular 26 | 27 | ```typescript 28 | providers: [ 29 | ServiceType, 30 | { 31 | provide: ServiceType, 32 | useClass: ServiceType, 33 | }, 34 | { 35 | provide: ServiceType, 36 | useClass: ImplementationType, 37 | }, 38 | { 39 | provide: ServiceType, 40 | useFactory: implementationFactory, 41 | }, 42 | ]; 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/Temp_InjectingContext.mdx: -------------------------------------------------------------------------------- 1 | ## Injecting A Context 2 | 3 | Let's say we have this code 4 | 5 | ```typescript 6 | @Injectable({ 7 | lifetime: ServiceLifetime.Scoped, 8 | }) 9 | class Logger {} 10 | 11 | const context = Injector.Create(); 12 | const logger = Injector.GetRequiredService(Logger, context); 13 | 14 | Injector.Destroy(context); 15 | ``` 16 | 17 | In case you don't have the service added yet, you can inject the `Context` serviceType to later use it to locate the deferred service 18 | 19 | ```typescript 20 | @Injectable() 21 | class Test {} 22 | 23 | @Injectable() 24 | class Service { 25 | constructor(private context: Context) { 26 | // Calling `this.someMethod()` here will throw an error since Test serviceType is not added yet. 27 | 28 | // Supposedly, the Test service will be added before two seconds 29 | setTimeout(() => { 30 | Injector.AddSingleton(Test); 31 | this.someMethod(); 32 | }, 2000); 33 | } 34 | 35 | someMethod() { 36 | return context.get(Test); 37 | } 38 | } 39 | ``` 40 | 41 | Another case where you use service locator to locate a service 42 | -------------------------------------------------------------------------------- /docs/Testing.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/UsageWithDeno.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/UsingDependencyInjection.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using Dependency Injection In Tiny Injector 3 | --- 4 | 5 | # Using Dependency Injection 6 | 7 | If you recall from the [ServiceRegistration](./ServiceRegistration) page, you can have 8 | 9 | - One to one relation: one ServiceType to one ImplementationType 10 | - Many to one: one ServiceType to many ImplementationTypes 11 | 12 | In this page we're gonna learn how to be able to request them back! 13 | To request a `ServiceType` you can either use **class constructor** or `Service Locator Pattern` 14 | 15 | > Any class that is utilzing the DI should have the `@Injectable()` so it can be part of the play!. 16 | 17 | ## Constructor parameters 18 | 19 | ```typescript 20 | // Abstract Service 21 | @Injectable() 22 | abstract class Logger {} 23 | 24 | // Concrete Service 25 | @Injectable() 26 | class InformativeLogger extends Logger {} 27 | 28 | // Client 29 | @Injectable() 30 | class Application { 31 | constructor(private logger: Logger) {} 32 | } 33 | 34 | Injector.AddSingleton(Logger, InformativeLogger); 35 | Injector.AddSingleton(Application); 36 | ``` 37 | 38 | In the previous sample the `Application` class declared the abstract `Logger` as dependence. 39 | the `Logger` class have `InformativeLogger` as implementation type which implies that the `InformativeLogger` will 40 | be injected in the `Application` class. that's due the registration of `InformativeLogger` against `Logger` 41 | 42 | Same case with requesting multiple `ImplementationTypes` from the same `ServiceType` 43 | 44 | ```typescript 45 | abstract class Logger {} 46 | 47 | @Injectable() 48 | class InformativeLogger extends Logger {} 49 | 50 | @Injectable() 51 | class WarningLogger extends Logger {} 52 | 53 | @Injectable() 54 | class Application { 55 | constructor(@Inject(Logger) private loggers: Logger[]) {} 56 | } 57 | 58 | Injector.AppendSingleton(Logger, InformativeLogger); 59 | Injector.AppendSingleton(Logger, WarningLogger); 60 | Injector.AddSingleton(Application); 61 | ``` 62 | 63 | In the previous sample the `Application` class declared the abstract `Logger` as dependence but with array type instead of the abstraction 64 | and with `@Inject` decorator. 65 | 66 | > This is required to know that your intention is to have array of implementations. 67 | 68 | The `Logger` class have `InformativeLogger` and `WarningLogger` as implementations which implies that the both `InformativeLogger` and `WarningLogger` will 69 | be injected in the `Application` class as array type. 70 | that's due the registration of `InformativeLogger` and `WarningLogger` against `Logger` 71 | 72 | > The `@Inject` decorator is required to get array of implementations for a service type but also you have to make sure that the type of parameter is Array as `Logger[]` or `Array` 73 | 74 | ```typescript 75 | @Injectable() 76 | class Application { 77 | constructor(@Inject(Logger) private loggers: Logger) {} 78 | } 79 | ``` 80 | 81 | Important: **If the array type omitted the last registered implementation will be returned**. 82 | 83 | ## Service Locator Pattern 84 | 85 | Singleton and Scoped lifetimes are bound to a context. 86 | 87 | For Singleton there's a context that is used internally, but for Scoped service you have to create them yourself. 88 | 89 | ### Singleton 90 | 91 | ```typescript 92 | @Injectable() 93 | class SingletonService {} 94 | Injector.AddSingleton(SingletonService); 95 | 96 | const service = Injector.GetRequiredService(SingletonService); 97 | ``` 98 | 99 | ### Scoped 100 | 101 | Scoped is more like Singleton but for an explict context, so you need to create a context and provide it. 102 | 103 | ```typescript 104 | @Injectable() 105 | class ScopedService {} 106 | Injector.AddScoped(ScopedService); 107 | 108 | const context = Injector.Create(); 109 | const scopedService1 = Injector.GetRequiredService(ScopedService, context); 110 | const scopedService2 = Injector.GetRequiredService(ScopedService, context); // return the same instance as above, scopedService1 === scopedService2 111 | 112 | const secondContext = Injector.Create(); 113 | const scopedService3 = Injector.GetRequiredService( 114 | ScopedService, 115 | secondContext 116 | ); // will return new instance 117 | ``` 118 | 119 | Don't forgot to destroy the context when you done using it. 120 | 121 | ```typescript 122 | Injector.Destroy(context); 123 | ``` 124 | 125 | ### Transient 126 | 127 | ```typescript 128 | @Injectable({ 129 | lifetime: ServiceLifetime.Transient, 130 | }) 131 | class TransientService { 132 | constructor() {} 133 | } 134 | 135 | const transientService = Injector.GetRequiredService(TransientService); 136 | ``` 137 | 138 | In case you did inject a _Scoped_ service into a _Transient_ service, you'll have to provide context, otherwise `LifestyleMismatchException` will be thrown. 139 | 140 | this happens due to the fact that _Scoped_ service created once per context and _Transient_ service are created each time is requested, hence without context you'll have different instance for the _Scoped_ service within the _Transient_ service so providing a context will make sure that the same scoped service is returned regardless of how many times the _Transient_ service created. 141 | 142 | ```typescript 143 | @Injectable({ 144 | lifetime: ServiceLifetime.Scoped, 145 | }) 146 | class ScopedService {} 147 | 148 | @Injectable({ 149 | lifetime: ServiceLifetime.Transient, 150 | }) 151 | class TransientService { 152 | constructor(scopedService: ScopedService) {} 153 | } 154 | 155 | // Will throw an exception 156 | const transientService = Injector.GetRequiredService(TransientService); 157 | 158 | // works fine. 159 | const context = Injector.Create(); 160 | const logger1 = Injector.GetRequiredService(TransientService, context); 161 | const logger2 = Injector.GetRequiredService(TransientService, context); 162 | 163 | /** 164 | * Now, logger1 and logger2 are different instances but, 165 | * logger1.scopedService and logger2.scopedService are the same 166 | */ 167 | ``` 168 | 169 | It is always preferred to pass context with _Transient_ services to avoid an handled exceptions. 170 | 171 | ### Locating Array of the same service type 172 | 173 | ```typescript 174 | abstract class Logger {} 175 | 176 | @Injectable() 177 | class InformativeLogger extends Logger {} 178 | 179 | @Injectable() 180 | class WarningLogger extends Logger {} 181 | 182 | Injector.AppendSingleton(Logger, InformativeLogger); 183 | Injector.AppendSingleton(Logger, WarningLogger); 184 | 185 | const loggers = Injector.GetServices(Logger); 186 | ``` 187 | 188 | ## Lifetime and registration options 189 | 190 | ```typescript 191 | abstract class Operation { 192 | abstract operationId: string; 193 | } 194 | 195 | @Injectable({ 196 | lifetime: ServiceLifetime.Transient, 197 | }) 198 | class OperationTransient extends Operation { 199 | operationId = Math.random().toString(36); 200 | } 201 | 202 | @Injectable({ 203 | lifetime: ServiceLifetime.Scoped, 204 | }) 205 | class OperationScoped extends Operation { 206 | operationId = Math.random().toString(36); 207 | } 208 | 209 | @Injectable({ 210 | lifetime: ServiceLifetime.Singleton, 211 | }) 212 | class OperationSingleton extends Operation { 213 | operationId = Math.random().toString(36); 214 | } 215 | 216 | @Injectable() 217 | class Application { 218 | constructor( 219 | private transientOperation1: OperationTransient, 220 | private transientOperation2: OperationTransient, 221 | private scopedOperation1: OperationScoped, 222 | private scopedOperation2: OperationScoped, 223 | private singletonOperation: OperationSingleton 224 | ) {} 225 | } 226 | 227 | const context = Injector.Create(); 228 | const app = Injector.GetRequiredService(Application, context); 229 | 230 | console.log( 231 | app.transientOperation1.operationId === app.transientOperation2.operationId 232 | ); // false 233 | 234 | console.log( 235 | app.scopedOperation1.operationId === app.scopedOperation2.operationId 236 | ); // true 237 | ``` 238 | 239 | - Transient objects are always different. transientOperation2 is not the same as transientOperation1. 240 | 241 | - Scoped objects are the same for each context but different across each context. 242 | 243 | - Singleton objects are the same for every context. 244 | 245 | ## Summary 246 | 247 | - `@Injectable` is required on top of each service even if you don't use it to register a service. 248 | 249 | - Once you add service you cannot override it and an error will be throw if you tried to do. 250 | 251 | - If you want to override a service you have to use `Injector.Replace{Lifetime}` instead. 252 | 253 | - A service will only be created when requested with respect to a service lifetime. 254 | 255 | - You would use `Append{Lifetime}` to have multiple implementation for the same service type. 256 | 257 | - Use `@Inject` to resolve multiple implementations with array type, `@Inject(Logger) private loggers: Logger[]`. 258 | 259 | - `@Inject` is optional to resolve single implementation. 260 | 261 | ```typescript 262 | @Injectable() 263 | class Application { 264 | constructor( 265 | private logger: Logger, 266 | // Shorthand for 267 | @Inject(Logger) private logger: Logger 268 | ) {} 269 | } 270 | ``` 271 | 272 | - Last implementation will always returned if you don't use array type. 273 | 274 | - Inject `Context` to resolve deferred dependencies. 275 | -------------------------------------------------------------------------------- /docs/UsingDependencyInjection/Overview.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/UsingDependencyInjection/Scoped.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/UsingDependencyInjection/Singleton.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/UsingDependencyInjection/Transient.mdx: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /docs/WithoutTypescript.mdx: -------------------------------------------------------------------------------- 1 | # Without TypeScript 2 | 3 | While this library built completely using TypeScript and for TypeScript codebase, still you can use it with vanilla JavaScript, but you won't able to use constructor injection, you'll always use service locator. 4 | 5 | Todo 6 | 7 | 1. provide the context argument as constructor parameter. 8 | -------------------------------------------------------------------------------- /docs/configuration.mdx: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | TBD 4 | -------------------------------------------------------------------------------- /docs/contributing.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contributing 3 | description: We actively welcome your pull requests. 4 | --- 5 | 6 | # Contributing 7 | 8 | When contributing to this repository, please first discuss the change you wish to make via issue, 9 | email, or any other method with the owners of this repository before making a change. 10 | 11 | We always welcome bug reports, pull requests, API proposals, and overall feedback. Here are a few tips on how you can make reporting your issue as effective as possible. 12 | 13 | Please note we have a code of conduct, please follow it in all your interactions with the project. 14 | 15 | ## Pull Request Process 16 | 17 | 1. Fork the repo and create your branch from `main`. 18 | 2. If you've added code that should be tested, add tests and ensure the test suite passes. 19 | 3. Ensure the docs are up to date with details of changes. 20 | 21 | ## Running Locally 22 | 23 | 1. Open the directory where the source code lives using an editor. 24 | 2. In the terminal, run `npm install` command to install dependencies. 25 | 3. In the terminal, run `npm run build` command to build the project or use `npm run build:watch` to build the project on files content changes. 26 | 4. In the terminal, run `npm start` to start the project. 27 | 5. Use `npm link` to be able to install/link the project else where in your device as dependency in order to check the result correctness beside runnning tests. 28 | 29 | ## Stability 30 | 31 | - The intentions is to follow .net DI API naming, therefore avoid changing APIs and core behaviors in general. 32 | - We will allow for API changes in cases where there is no other way to achieve a high priority bug fix or improvement. 33 | 34 | ## License 35 | 36 | By contributing, you agree that your contributions will be licensed under its MIT License. 37 | -------------------------------------------------------------------------------- /docs/examples/express.mdx: -------------------------------------------------------------------------------- 1 | # Express 2 | 3 | In this tutorial, we'll talk about using dependency injection with Express to build a simple web API. 4 | 5 | ### Setup HTTP server 6 | 7 | ```tsx 8 | import express from "express"; 9 | import { Injector, ServiceLifetime } from "tiny-injector"; 10 | 11 | const application = express(); 12 | 13 | application.listen("8080", () => { 14 | console.log(`${new Date()}: Server running at http://localhost:8080`); 15 | }); 16 | ``` 17 | 18 | ### Create top level express middleware that will create a new context at the beginning of a request and dispose at the end of it 19 | 20 | ```tsx 21 | application.use((req, res, next) => { 22 | const context = Injector.Create(); 23 | const dispose = () => Injector.Destroy(context); 24 | // At the end the current request, everything related to that context should be garbage collected 25 | // so you need to make sure that you let that happen by calling dispose function 26 | 27 | ["error", "end"].forEach((eventName) => { 28 | req.on(eventName, dispose); 29 | }); 30 | 31 | // Helper function to be able to retrieve services easily 32 | req.locate = (serviceType) => context.get(serviceType); 33 | // Or 34 | req.locate = (serviceType) => 35 | Injector.GetRequiredService(serviceType, context); 36 | next(); 37 | }); 38 | ``` 39 | 40 | ### The Product Model 41 | 42 | ```tsx 43 | export class Product { 44 | constructor(public id: number, public price: number, public name: string) {} 45 | } 46 | ``` 47 | 48 | ### Product Service 49 | 50 | In this snippet, the CRUD operations will be performed over the products list. 51 | 52 | The `ProductService` is registered as **Singleton** since we need only one instance of it. 53 | 54 | ```tsx 55 | import { Product } from "./product"; 56 | import { Injectable, ServiceLifetime } from "tiny-injector"; 57 | 58 | @Injectable({ 59 | lifetime: ServiceLifetime.Singleton, 60 | }) 61 | export class ProductService { 62 | /** 63 | Seed Product 64 | */ 65 | private products = [ 66 | new Product(0, 10, "iPhone"), 67 | new Product(1, 20, "MacBook"), 68 | new Product(2, 30, "MacPro"), 69 | ]; 70 | 71 | getProducts() { 72 | return this.products; 73 | } 74 | 75 | addProduct(product: Product) { 76 | product.id = this.products.length; 77 | this.products.push(product); 78 | } 79 | 80 | updateProduct(product: Product) { 81 | const productIndex = this.products.findIndex((it) => it.id === product.id); 82 | if (productIndex < 0) { 83 | throw new Error(`Cannot find product with id ${product.id}`); 84 | } 85 | this.products.splice(productIndex, 1, product); 86 | } 87 | } 88 | ``` 89 | 90 | ### Product Controller 91 | 92 | The `ProductController` is registered as **Scoped** because whenever a request is made new instance should be instantiated, so each request creates a fresh instance of your controller and that means a fresh instance of your variables within it. 93 | 94 | _In this example registering the controller as **Transient** won't make difference unless you're using scoped service as a dependency._ 95 | 96 | ```tsx 97 | import { Injectable, ServiceLifetime } from "tiny-injector"; 98 | import { Product } from "./product"; 99 | import { ProductService } from "./product_service"; 100 | 101 | @Injectable({ 102 | lifetime: ServiceLifetime.Scoped, 103 | }) 104 | export class ProductController { 105 | constructor(private productService: ProductService) {} 106 | 107 | getProducts() { 108 | return this.productService.getProducts(); 109 | } 110 | 111 | addProduct(product: Product) { 112 | return this.productService.addProduct(product); 113 | } 114 | 115 | updateProduct(product: Product) { 116 | return this.productService.updateProduct(product); 117 | } 118 | } 119 | ``` 120 | 121 | ### The Route Layer 122 | 123 | The final stage where you connect the controller to a route 124 | 125 | _Take a look at the start snippet where the setup middleware added to know how 126 | `req.locate` works_ 127 | 128 | ```tsx 129 | application 130 | .all("/products") 131 | .post("/", (req, res) => { 132 | const productController = req.locate(ProductController); 133 | res.json(productController.addProduct(req.body)); 134 | }) 135 | .put("/:id", (req, res) => { 136 | const productController = req.locate(ProductController); 137 | res.json(productController.updateProduct(req.body)); 138 | }) 139 | .get("/", (req, res) => { 140 | const productController = req.locate(ProductController); 141 | res.json(productController.getProducts()); 142 | }); 143 | ``` 144 | 145 | ## Key Takeaways 146 | 147 | - We assumed that the "end" and "error" events occured when the request is fullfilled therefore we disposed the associated context. 148 | - Context disposal at the end of each request is important to free up the memory. 149 | - If you were intending to use the request context in an express middleware, make sure it is added after the custom set up middleware. 150 | - It is recommended to register the controller as **Scoped** so it won't instantiate new instance from it when you ask for it. 151 | 152 | Source Code: [Express-Tiny-Injector](https://github.com/ezzabuzaid/tiny-injector-express) 153 | -------------------------------------------------------------------------------- /docs/examples/feathersjs.mdx: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/examples/grpc.mdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezzabuzaid/tiny-injector/5560fcbf362ff717fda72f1df3a21f2f9f5d973c/docs/examples/grpc.mdx -------------------------------------------------------------------------------- /docs/examples/koa.mdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezzabuzaid/tiny-injector/5560fcbf362ff717fda72f1df3a21f2f9f5d973c/docs/examples/koa.mdx -------------------------------------------------------------------------------- /docs/examples/typeorm.mdx: -------------------------------------------------------------------------------- 1 | # TypeORM 2 | 3 | ## Connection Injection Token 4 | 5 | ## Simple Audit Trail 6 | 7 | reference the issue related to how to get the request into subscriber 8 | -------------------------------------------------------------------------------- /docs/examples/using-request-object-beyond-controller.mdx: -------------------------------------------------------------------------------- 1 | ## Add request injection token 2 | 3 | ### Set reference to request object 4 | 5 | ```typescript 6 | application.use((req, res, next) => { 7 | // Create context ... 8 | 9 | // attach the request to the context so you can refernce it later on 10 | context.setExtra("request", req); 11 | 12 | // rest of code 13 | }); 14 | ``` 15 | 16 | ### Create _InjectionToken_ 17 | 18 | `request_token.ts` file 19 | 20 | ```typescript 21 | import { Request } from "express"; 22 | export const REQUEST_TOKEN = new InjectionToken("TOKEN_REQUEST", { 23 | lifetime: ServiceLifetime.Scoped, 24 | implementationFactory: (context) => context.getExtra("request"), 25 | }); 26 | ``` 27 | 28 | ### Use `@Inject` to reference `REQUEST_TOKEN` 29 | 30 | ```typescript 31 | @Injectable({ 32 | lifetime: ServiceLifetime.Scoped, 33 | }) 34 | export class DataService { 35 | constructor(@Inject(REQUEST_TOKEN) private request: Request) { 36 | const userAgent = request.headers["user-agent"]; 37 | } 38 | } 39 | ``` 40 | 41 | --- 42 | 43 | Make sure that the service that utilize the `REQUEST_TOKEN` is 44 | 45 | 1. Either `Transient` or `Scoped`. 46 | 2. Descendant of `Scoped` controller 47 | -------------------------------------------------------------------------------- /docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tiny Injector 3 | description: Tiny Injector is a tiny yet powerful and flexible Dependency Injection library 4 | --- 5 | 6 | # Tiny Injector 7 | 8 | Tiny Injector is a tiny yet powerful and flexible Dependency Injection library for projects that uses TypeScript. 9 | It could be used on top of existing projects althought it is recommended to be used as first-class citizen 10 | 11 | The work heavily inspired by . NET Dependency Injection, Angular +2 and [This Answer](https://stackoverflow.com/a/48187842/10415423). 12 | 13 | Parts of documentation are taken from the Microsoft DI website 14 | 15 | [![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/ezzabuzaid/tiny-injector) 16 | 17 | ## What Is Dependency Injection 18 | 19 | In software engineering, dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. 20 | 21 | In the typical "using" relationship the receiving object is called a client and the passed (that is, "injected") object is called a service. 22 | 23 | [Reference](https://en.wikipedia.org/wiki/Dependency_injection) 24 | [You might check here](https://stackoverflow.com/questions/130794/what-is-dependency-injection/140655#140655) 25 | [And here as well]() 26 | 27 | _in which an object receives other objects that it depends on._ 28 | Let's say you have two classes `ObjectA` and `ObjectB` , and `ObjectB` depends on `ObjectA` , in this case we call `ObjectA` dependency on `ObjectB` . 29 | 30 | _In the typical "using" relationship the receiving object is called a client and the passed (that is, "injected") object is called a service._ 31 | hence, `ObjectB` is client and `ObjectA` is "injected" service. 32 | 33 | ```typescript 34 | class ObjectA {} 35 | 36 | class ObjectB { 37 | constructor(objectA: ObjectA); 38 | } 39 | ``` 40 | 41 | which leads us to second defention 42 | 43 | _Dependency Injection is a software design concept that allows a service to be used/injected in a way that is completely independent of any client consumption._ 44 | 45 | so `ObjectB` will receive the service `ObjectA` without any knowledge of how the service constructed. 46 | 47 | _Dependency injection separates the creation of a client's dependencies from the client's behavior, which allows program designs to be loosely coupled._ 48 | 49 | [Reference](https://www.codementor.io/@olotintemitope/dependency-injection-explained-in-plain-english-b24hippx7) 50 | 51 | The goal of the dependency injection technique is to remove this dependency by separating the usage from the creation of the object. This reduces the amount of required boilerplate code and improves flexibility. 52 | 53 | With dependency injection you won't care how the service "injected object" is created or what dependencies it needs, you care only about specifing either the concrete implementation or the abstractions. 54 | 55 | - Dependency Inversion Principle 56 | 57 | _Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions._ 58 | 59 | so to truly have loosely coupled application you should start thinking abstractions instead of using concretions directly. 60 | 61 | ```typescript 62 | class SqlConnection { 63 | connect() {} 64 | } 65 | 66 | class MySqlConnection { 67 | connect() {} 68 | } 69 | 70 | class DataRepository { 71 | constructor(sqlConnection: SqlConnection) {} 72 | 73 | async fetch() { 74 | const connection = await this.sqlConnection.connect(); 75 | return connection.find(); 76 | } 77 | } 78 | 79 | const connection = new SqlConnection(); 80 | const repository = new DataRepository(connection); 81 | ``` 82 | 83 | this sample violates the **Dependency Inversion Principle** because if you thought about changing the `SqlConnection` to `MySqlConnection` you would have to change `DataRepository` as well since it depends on `SqlConnection` . 84 | 85 | to overcome this issue you need to create abstract class for database connection 86 | 87 | ```typescript 88 | abstract class DatabaseConnection { 89 | abstract connect(): Promise; 90 | } 91 | 92 | class SqlConnection extends DatabaseConnection { 93 | connect() {} 94 | } 95 | 96 | class MySqlConnection extends DatabaseConnection { 97 | connect() {} 98 | } 99 | 100 | class DataRepository { 101 | constructor(databaseConnection: DatabaseConnection) {} 102 | 103 | async fetch() { 104 | const connection = await this.databaseConnection.connect(); 105 | return connection.find(); 106 | } 107 | } 108 | 109 | const sqlConnection = new SqlConnection(); 110 | const mySqlConnection = new mySqlConnection(); 111 | const repository = new DataRepository(mySqlConnection); 112 | ``` 113 | 114 | now, whether you choose `SqlConnection` or `MySqlConnection` you won't change the `DataRepository` since it depends on the abstraction and not on any of the concretions. 115 | 116 | ## Features 117 | 118 | Included 119 | 120 | - Supports Node.js and anything uses TypeScript. 121 | - Singleton, Scoped and Transient service lifetime. 122 | - Auto scope validation. 123 | - Uses class constructor to identify and inject its dependencies 124 | 125 | Not Included 126 | 127 | - Property injection 128 | - Injection based on name 129 | - Custom lifetime management 130 | 131 | TODO: 132 | 133 | 1. Service disposal. 134 | 2. Asynchronous registration. 135 | 3. Provide user-defined options. 136 | 4. ~~Replace service functionalit.~~ 137 | 5. ~~Error less service registration via `Try{ServiceLifetime}` methods.~~ 138 | 6. User defined extensions. 139 | 7. Injector hierarchy resolver. 140 | 141 | ## Other third-party libraries 142 | 143 | Worth to mention other great libraries. 144 | 145 | 1. [InversifyJS](https://www.npmjs.com/package/inversify) 146 | 2. [Awilix](https://www.npmjs.com/package/awilix) 147 | 3. [BottleJS](https://www.npmjs.com/package/bottlejs) 148 | 4. [injection-js](https://www.npmjs.com/package/injection-js) 149 | 150 | ## License 151 | 152 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 153 | 154 | - See [LICENSE](/LICENSE) 155 | 156 | Built and maintained with ❤️ by [ezzabuzaid](https://github.com/ezzabuzaid) 157 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from 'esbuild'; 2 | import { nodeExternalsPlugin } from 'esbuild-node-externals'; 3 | import { join } from 'path'; 4 | 5 | esbuild.build({ 6 | entryPoints: [join( 7 | process.cwd(), 8 | 'src', 9 | 'index.ts' 10 | )], 11 | outfile: join( 12 | process.cwd(), 13 | 'dist', 14 | 'index.js' 15 | ), 16 | format: 'esm', 17 | platform: 'neutral', 18 | treeShaking: true, 19 | minify: false, 20 | keepNames: true, 21 | minifyIdentifiers: false, 22 | minifySyntax: false, 23 | minifyWhitespace: false, 24 | bundle: true, 25 | tsconfig: join( 26 | process.cwd(), 27 | 'tsconfig.app.json' 28 | ), 29 | plugins: [ 30 | nodeExternalsPlugin({ 31 | packagePath: [join(process.cwd(), 'package.json')], 32 | }), 33 | ], 34 | }) -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "roots": [ 3 | "/__test__" 4 | ], 5 | "testMatch": [ 6 | "**/__tests__/**/*.+(ts|tsx|js)", 7 | "**/?(*.)+(spec|test).+(ts|tsx|js)" 8 | ], 9 | "transform": { 10 | "^.+\\.(ts|tsx)$": "ts-jest" 11 | }, 12 | "clearMocks": true, 13 | "resetMocks": true, 14 | globals: { 15 | 'ts-jest': { 16 | tsconfig: 'tsconfig.test.json' 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-injector", 3 | "version": "0.2.0", 4 | "description": "Lightweight dependency injection library for TypeScript applications", 5 | "main": "index.js", 6 | "module": "index.js", 7 | "type": "module", 8 | "scripts": { 9 | "test": "jest --runInBand --detectOpenHandles --no-cache", 10 | "test:majestic": "majestic", 11 | "start": "ts-node src/index.ts", 12 | "build:watch": "tsc --project tsconfig.app.json --watch", 13 | "build": "node ./esbuild.config.mjs && tsc --project tsconfig.app.json", 14 | "prebuild": "del-cli ./dist/", 15 | "postbuild": "cpy 'README.md' 'package*.json' ./dist/ --no-overwrite --parents", 16 | "release": "release-it" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/ezzabuzaid/tiny-injector.git" 21 | }, 22 | "husky": { 23 | "hooks": { 24 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 25 | } 26 | }, 27 | "author": "ezzabuzaid", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/ezzabuzaid/tiny-injector/issues" 31 | }, 32 | "homepage": "https://github.com/ezzabuzaid/tiny-injector#readme", 33 | "keywords": [ 34 | "dependency injection", 35 | "inversion of control", 36 | "containers", 37 | "ioc", 38 | "di", 39 | "decorators", 40 | "ezzabuzaid" 41 | ], 42 | "private": false, 43 | "release-it": { 44 | "plugins": { 45 | "@release-it/conventional-changelog": { 46 | "preset": "conventionalcommits", 47 | "infile": "CHANGELOG.md" 48 | } 49 | }, 50 | "git": { 51 | "requireBranch": "main", 52 | "requireCleanWorkingDir": true, 53 | "requireCommits": true, 54 | "requireUpstream": true, 55 | "tag": true 56 | }, 57 | "github": { 58 | "release": true 59 | }, 60 | "npm": { 61 | "publish": true, 62 | "publishPath": "./dist" 63 | }, 64 | "hooks": { 65 | "after:bump": "npm run build", 66 | "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}." 67 | } 68 | }, 69 | "dependencies": { 70 | "esbuild": "^0.14.54", 71 | "esbuild-node-externals": "^1.15.0", 72 | "reflect-metadata": "^0.1.13", 73 | "tslib": "^2.3.1", 74 | "typescript": "^5.6.2" 75 | }, 76 | "devDependencies": { 77 | "@release-it/conventional-changelog": "^4.1.0", 78 | "@types/jest": "^27.4.0", 79 | "cpy-cli": "^1.0.1", 80 | "del-cli": "^4.0.1", 81 | "jest": "^27.4.7", 82 | "majestic": "^1.8.1", 83 | "release-it": "^14.12.1", 84 | "ts-jest": "^27.1.2", 85 | "ts-node": "^10.4.0" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/AbstractServiceCollection.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "./Context"; 2 | import { AddScopedExtensions, AddSingletonExtensions, AddTransientExtensions, AppendScopedExtensions, AppendSingletonExtensions, AppendTransientExtensions, ReplaceScopedExtensions, ReplaceSingletonExtensions, ReplaceTransientExtensions } from "./Extensions"; 3 | import { InjectionToken, InjectionTokenGenericParam } from "./InjectionToken"; 4 | import { ServiceDescriptor } from "./ServiceDescriptor"; 5 | import { ServiceProvider } from "./ServiceProvider"; 6 | import { ClassType, ServiceType, TypeOf } from "./Types"; 7 | 8 | export abstract class AbstractServiceCollection implements 9 | AddScopedExtensions, 10 | AddSingletonExtensions, 11 | AddTransientExtensions, 12 | AppendScopedExtensions, 13 | AppendSingletonExtensions, 14 | AppendTransientExtensions, 15 | ReplaceSingletonExtensions, 16 | ReplaceScopedExtensions, 17 | ReplaceTransientExtensions { 18 | 19 | public abstract AddScoped>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 20 | public abstract AddScoped>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 21 | public abstract AddScoped, I extends ClassType>>(serviceType: T, implementationType: I): void; 22 | public abstract AddScoped>(serviceType: T): void; 23 | 24 | public abstract AddSingleton>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 25 | public abstract AddSingleton>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 26 | public abstract AddSingleton, I extends ClassType>>(serviceType: T, implementationType: I): void; 27 | public abstract AddSingleton>(serviceType: T): void; 28 | 29 | public abstract AddTransient>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 30 | public abstract AddTransient>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 31 | public abstract AddTransient, I extends ClassType>>(serviceType: T, implementationType: I): void; 32 | public abstract AddTransient>(serviceType: T): void; 33 | 34 | public abstract TryAddScoped>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 35 | public abstract TryAddScoped>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 36 | public abstract TryAddScoped, I extends ClassType>>(serviceType: T, implementationType: I): void; 37 | public abstract TryAddScoped>(serviceType: T): void; 38 | 39 | public abstract TryAddSingleton>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 40 | public abstract TryAddSingleton>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 41 | public abstract TryAddSingleton, I extends ClassType>>(serviceType: T, implementationType: I): void; 42 | public abstract TryAddSingleton>(serviceType: T): void; 43 | 44 | public abstract TryAddTransient>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 45 | public abstract TryAddTransient>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 46 | public abstract TryAddTransient, I extends ClassType>>(serviceType: T, implementationType: I): void; 47 | public abstract TryAddTransient>(serviceType: T): void; 48 | 49 | public abstract ReplaceSingleton>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 50 | public abstract ReplaceSingleton>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 51 | public abstract ReplaceSingleton, I extends ClassType>>(serviceType: T, implementationType: I): void; 52 | public abstract ReplaceSingleton>(serviceType: T): void; 53 | 54 | public abstract ReplaceScoped>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 55 | public abstract ReplaceScoped>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 56 | public abstract ReplaceScoped, I extends ClassType>>(serviceType: T, implementationType: I): void; 57 | public abstract ReplaceScoped>(serviceType: T): void; 58 | 59 | public abstract ReplaceTransient>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 60 | public abstract ReplaceTransient>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 61 | public abstract ReplaceTransient, I extends ClassType>>(serviceType: T, implementationType: I): void; 62 | public abstract ReplaceTransient>(serviceType: T): void; 63 | 64 | public abstract AppendTransient>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 65 | public abstract AppendTransient, I extends ClassType>>(serviceType: T, implementationType: T): void; 66 | public abstract AppendTransient>(serviceType: T): void; 67 | 68 | public abstract AppendScoped>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 69 | public abstract AppendScoped, I extends ClassType>>(serviceType: T, implementationType: T): void; 70 | public abstract AppendScoped>(serviceType: T): void; 71 | 72 | public abstract AppendSingleton>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 73 | public abstract AppendSingleton, I extends ClassType>>(serviceType: T, implementationType: T): void; 74 | public abstract AppendSingleton>(serviceType: T): void; 75 | 76 | public abstract BuildServiceProvider(): ServiceProvider; 77 | 78 | public abstract GetServiceDescriptors(serviceType: ServiceType | InjectionToken): ServiceDescriptor[]; 79 | 80 | public abstract Remove(serviceType: ServiceType): void; 81 | } 82 | -------------------------------------------------------------------------------- /src/Activator.ts: -------------------------------------------------------------------------------- 1 | class Activator { } 2 | -------------------------------------------------------------------------------- /src/Context.ts: -------------------------------------------------------------------------------- 1 | 2 | export class Context { 3 | #extras = new Map(); 4 | constructor() { 5 | Object.freeze(this); 6 | Object.seal(this); 7 | } 8 | 9 | setExtra(name: string, value: any) { 10 | this.#extras.set(name, value); 11 | } 12 | 13 | getExtra(name: string): T { 14 | return this.#extras.get(name); 15 | } 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/ContextRegistry.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "./Context"; 2 | import { ServiceDescriptor } from "./ServiceDescriptor"; 3 | import { TypeOf } from "./Types"; 4 | 5 | export class ContextRegistry { 6 | #registry = new WeakMap>>(); 7 | 8 | private constructor() { } 9 | 10 | Has(context: Context) { 11 | return this.#registry.has(context); 12 | } 13 | 14 | Add(context: Context) { 15 | this.#registry.set(context, new WeakMap()); 16 | } 17 | 18 | Delete(context: Context) { 19 | this.#registry.delete(context); 20 | } 21 | 22 | GetContainer(context: Context) { 23 | return this.#registry.get(context); 24 | } 25 | 26 | static GetInstance() { 27 | return this.instance ?? (this.instance = new ContextRegistry()); 28 | } 29 | private static instance: ContextRegistry; 30 | } 31 | -------------------------------------------------------------------------------- /src/Exceptions/ActivationFailedException.ts: -------------------------------------------------------------------------------- 1 | import { ServiceType } from "../Types"; 2 | import { InvalidOperationException } from "./InvalidOperationException"; 3 | 4 | export class ActivationFailedException extends InvalidOperationException { 5 | constructor( 6 | serviceType: ServiceType, 7 | injectedServiceType: ServiceType, 8 | ) { 9 | super( 10 | `Unable to resolve service for type '${ injectedServiceType.name }' while attempting to activate '${ serviceType.name }'.` 11 | ) 12 | } 13 | } -------------------------------------------------------------------------------- /src/Exceptions/ArgumentException.ts: -------------------------------------------------------------------------------- 1 | 2 | export class ArgumentException extends Error { 3 | constructor(message: string, paramName?: string) { 4 | super(`${ message }${ paramName ? `\nParameter name: ${ paramName }` : '' }`); 5 | } 6 | } -------------------------------------------------------------------------------- /src/Exceptions/InvalidOperationException.ts: -------------------------------------------------------------------------------- 1 | 2 | export class InvalidOperationException extends Error { } -------------------------------------------------------------------------------- /src/Exceptions/LifestyleMismatchException.ts: -------------------------------------------------------------------------------- 1 | import { ServiceType } from "../Types"; 2 | import { InvalidOperationException } from "./InvalidOperationException"; 3 | 4 | export class LifestyleMismatchException extends InvalidOperationException { 5 | constructor(details: { 6 | serviceType: ServiceType, 7 | injectedServiceType: ServiceType, 8 | serviceTypeLifetimeType: string, 9 | injectedServiceLifetimeType: string, 10 | needContext?: boolean 11 | } 12 | ) { 13 | super( 14 | `Cannot consume ${ details.injectedServiceLifetimeType } service ${ details.injectedServiceType.name } from ${ details.serviceTypeLifetimeType } service ${ details.serviceType.name } ${ details.needContext ? 'without context' : '' }.` 15 | ) 16 | } 17 | } -------------------------------------------------------------------------------- /src/Exceptions/ResolutionFailedException.ts: -------------------------------------------------------------------------------- 1 | import { InvalidOperationException } from "./InvalidOperationException"; 2 | 3 | export class ResolutionFailedException extends InvalidOperationException { 4 | constructor( 5 | serviceTypeName: string 6 | ) { 7 | super( 8 | `Could not resolve type ${ serviceTypeName }.` 9 | ); 10 | } 11 | } -------------------------------------------------------------------------------- /src/Exceptions/ServiceExistException.ts: -------------------------------------------------------------------------------- 1 | import { InvalidOperationException } from "./InvalidOperationException"; 2 | 3 | export class ServiceExistException extends InvalidOperationException { 4 | constructor( 5 | serviceTypeName: string, 6 | serviceTypeLifetime: string, 7 | ) { 8 | super( 9 | `You cannot override a registered type. the ${ serviceTypeName } is already registered as ${ serviceTypeLifetime }` 10 | ); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Exceptions/ServiceNotFoundException.ts: -------------------------------------------------------------------------------- 1 | import { InvalidOperationException } from "./InvalidOperationException"; 2 | 3 | export class ServiceNotFoundException extends InvalidOperationException { 4 | constructor( 5 | serviceTypeName: string, 6 | ) { 7 | super(`There is no service of type ${ serviceTypeName }`); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Exceptions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ActivationFailedException'; 2 | export * from './ArgumentException'; 3 | export * from './InvalidOperationException'; 4 | export * from './LifestyleMismatchException'; 5 | export * from './ServiceExistException'; 6 | export * from './ServiceNotFoundException'; 7 | export * from './ResolutionFailedException'; 8 | -------------------------------------------------------------------------------- /src/Extensions/AddScopedExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | export abstract class AddScopedExtensions { 6 | 7 | /** 8 | * Add a scoped service of the type specified in serviceType with a factory specified in implementationFactory. 9 | * 10 | * @description 11 | * Scoped services are created once per context. 12 | * 13 | * @see {CreateScope} 14 | * @see {Create} 15 | * 16 | * @param serviceType The type of the service to add. 17 | * @param implementationFactory the factory that creates the service. 18 | */ 19 | abstract AddScoped>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 20 | /** 21 | * Add a scoped service of the injection token with a factory specified in implementationFactory. 22 | * 23 | * @description 24 | * Scoped services are created once per context. 25 | * 26 | * @see {CreateScope} 27 | * @see {Create} 28 | * 29 | * @param injectionToken The token to add. 30 | * @param implementationFactory the factory that creates the service. 31 | * 32 | */ 33 | abstract AddScoped>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 34 | /** 35 | * Add a scoped service of the type specified in serviceType with an implementation of the type specified in implementationType. 36 | * 37 | * @description 38 | * Scoped services are created once per context. 39 | * 40 | * @see {CreateScope} 41 | * @see {Create} 42 | * 43 | * @param serviceType The type of the service to add. 44 | * @param implementationType The type of the implementation to use. 45 | */ 46 | abstract AddScoped, I extends ClassType>>(serviceType: T, implementationType: I): void; 47 | /** 48 | * Add a scoped service of the type specified in serviceType. 49 | * 50 | * @description 51 | * Scoped services are created once per context. 52 | * 53 | * @see {CreateScope} 54 | * @see {Create} 55 | * 56 | * @param serviceType The type of the service to add. 57 | */ 58 | abstract AddScoped>(serviceType: T): void; 59 | } -------------------------------------------------------------------------------- /src/Extensions/AddSingletonExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | 6 | export abstract class AddSingletonExtensions { 7 | 8 | /** 9 | * Add a singleton service of the type specified in serviceType with a factory specified in implementationFactory. 10 | * 11 | * @description 12 | * Singleton services are created The first time they're requested. 13 | * the same instance will be resolved for each subsequent request. 14 | * 15 | * @param serviceType The type of the service to add. 16 | * @param implementationFactory the factory that creates the service. 17 | * 18 | */ 19 | abstract AddSingleton>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 20 | /** 21 | * Add a singleton service of the injection token with a factory specified in implementationFactory. 22 | * 23 | * @description 24 | * Singleton services are created The first time they're requested. 25 | * the same result will be returned for each subsequent request. 26 | * 27 | * @param injectionToken The token to add. 28 | * @param implementationFactory the factory that creates the service. 29 | * 30 | */ 31 | abstract AddSingleton>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 32 | /** 33 | * Add a singleton service of the type specified in serviceType with an implementation of the type specified in implementationType. 34 | * 35 | * @description 36 | * Singleton services are created The first time they're requested. 37 | * the same instance will be resolved for each subsequent request. 38 | * 39 | * @param serviceType The type of the service to add. 40 | * @param implementationType The type of the implementation to use. 41 | */ 42 | abstract AddSingleton, I extends ClassType>>(serviceType: T, implementationType: I): void; 43 | /** 44 | * Add a singleton service of the type specified in serviceType. 45 | * 46 | * @description 47 | * Singleton services are created The first time they're requested. 48 | * the same instance will be resolved for each subsequent request. 49 | * 50 | * @param serviceType The type of the service to add. 51 | * 52 | */ 53 | abstract AddSingleton>(serviceType: T): void; 54 | 55 | } -------------------------------------------------------------------------------- /src/Extensions/AddTransientExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | 6 | 7 | export abstract class AddTransientExtensions { 8 | 9 | /** 10 | * Add a transient service of the type specified in serviceType with a factory specified in implementationFactory. 11 | * 12 | * @description 13 | * Transient services are created each time they're requested, Unlike Singleton and Scoped, the reference for the implementation won't be stored. 14 | * 15 | * @param serviceType - The type of the service to add. 16 | * @param implementationFactory - the factory that creates the service. 17 | */ 18 | abstract AddTransient>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 19 | /** 20 | * Add a transient service of the injection token with a factory specified in implementationFactory. 21 | * 22 | * @description 23 | * Transient services are created each time they're requested, Unlike Singleton and Scoped, the reference for the implementation won't be stored. 24 | * 25 | * @param injectionToken The token to add. 26 | * @param implementationFactory the factory that creates the service. 27 | * 28 | */ 29 | abstract AddTransient>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 30 | /** 31 | * Add a transient service of the type specified in serviceType with an implementation of the type specified in implementationType. 32 | * 33 | * @description 34 | * Transient services are created each time they're requested, Unlike Singleton and Scoped, the reference for the implementation won't be stored 35 | * 36 | * @param serviceType - The type of the service to add. 37 | * @param implementationType - The type of the implementation to use. 38 | */ 39 | abstract AddTransient, I extends ClassType>>(serviceType: T, implementationType: I): void; 40 | /** 41 | * Add a transient service of the type specified in serviceType. 42 | * 43 | * @description 44 | * Transient services are created each time they're requested, Unlike Singleton and Scoped, the reference for the implementation won't be stored 45 | * 46 | * @param serviceType - The type of the service to add. 47 | */ 48 | abstract AddTransient>(serviceType: T): void; 49 | } 50 | -------------------------------------------------------------------------------- /src/Extensions/AppendScopedExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | export abstract class AppendScopedExtensions { 6 | 7 | /** 8 | * Append a scoped service of the type specified in serviceType with a factory specified in implementationFactory. 9 | * 10 | * @description 11 | * Use this method when you want to have different implementations for the same serviceType 12 | * 13 | * This method introduced because AddScoped will prevent the same serviceType from being added twice 14 | * 15 | * You'll need to use {@Inject} decorator to resolve array of implementations. 16 | * @Inject(Interceptor) interceptors: Interceptor[] 17 | * 18 | * @see AddScoped 19 | * 20 | * @param serviceType The type of the service to add. 21 | * @param implementationFactory the factory that creates the service. 22 | * 23 | */ 24 | abstract AppendScoped>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 25 | /** 26 | * Append a scoped service of the injection token with a factory specified in implementationFactory 27 | * 28 | * @description 29 | * This method introduced because AddScoped will prevent the same injection token from being added twice 30 | * 31 | * You'll need to use {@Inject} decorator to resolve array of implementations. 32 | * @Inject(TOKEN) interceptors: TokenInterface[] 33 | * 34 | * @param injectionToken The token to add. 35 | * @param implementationFactory the factory that creates the service. 36 | * 37 | */ 38 | abstract AppendScoped>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 39 | /** 40 | * Append a Scoped service of the type specified in serviceType with an implementation of the type specified in implementationType. 41 | * 42 | * @description 43 | * Use this method when you want to have different implementations for the same serviceType 44 | * 45 | * This method introduced because AddScoped will prevent the same serviceType from being added twice 46 | * 47 | * You'll need to use {@Inject} decorator to resolve array of implementations. 48 | * @Inject(Interceptor) interceptors: Interceptor[] 49 | * 50 | * @see AddScoped 51 | * 52 | * @param serviceType The type of the service to add. 53 | * @param implementationType The type of the implementation to use. 54 | */ 55 | abstract AppendScoped, I extends ClassType>>(serviceType: T, implementationType: T): void; 56 | /** 57 | * Append a scoped service of the type specified in serviceType. 58 | * 59 | * @description 60 | * Use this method when you want to have different implementations for the same serviceType 61 | * 62 | * This method introduced because AddScoped will prevent the same serviceType from being added twice 63 | * 64 | * You'll need to use {@Inject} decorator to resolve array of implementations. 65 | * @Inject(Interceptor) interceptors: Interceptor[] 66 | * 67 | * @see AddScoped 68 | * 69 | * @param serviceType The type of the service to add. 70 | * 71 | */ 72 | abstract AppendScoped>(serviceType: T): void; 73 | } 74 | -------------------------------------------------------------------------------- /src/Extensions/AppendSingletonExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | 6 | export abstract class AppendSingletonExtensions { 7 | 8 | /** 9 | * Append a singleton service of the type specified in serviceType with a factory specified in implementationFactory. 10 | * 11 | * @description 12 | * Use this method when you want to have different implementations for the same serviceType 13 | * 14 | * This method introduced because AddSingleton will prevent the same serviceType from being added twice 15 | * 16 | * You'll need to use {@Inject} decorator to resolve array of implementations. 17 | * "@Inject(Interceptor) interceptors: Interceptor[]"" 18 | * 19 | * @see AddSingleton 20 | * 21 | * @param serviceType The type of the service to add. 22 | * @param implementationFactory the factory that creates the service. 23 | * 24 | */ 25 | abstract AppendSingleton>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 26 | /** 27 | * Append a singleton service of the injection token with a factory specified in implementationFactory 28 | * 29 | * @description 30 | * This method introduced because AddSingleton will prevent the same injection token from being added twice 31 | * 32 | * You'll need to use {@Inject} decorator to resolve array of implementations. 33 | * @Inject(TOKEN) interceptors: TokenInterface[] 34 | * 35 | * @param injectionToken The token to add. 36 | * @param implementationFactory the factory that creates the service. 37 | * 38 | */ 39 | abstract AppendSingleton>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 40 | /** 41 | * Append a singleton service of the type specified in serviceType with an implementation of the type specified in implementationType. 42 | * 43 | * @description 44 | * Use this method when you want to have different implementations for the same serviceType 45 | * 46 | * This method introduced because AddSingleton will prevent the same serviceType from being added twice 47 | * 48 | * You'll need to use {@Inject} decorator to resolve array of implementations. 49 | * @Inject(Interceptor) interceptors: Interceptor[] 50 | * 51 | * @see AddSingleton 52 | * 53 | * @param serviceType The type of the service to add. 54 | * @param implementationType The type of the implementation to use. 55 | */ 56 | abstract AppendSingleton, I extends ClassType>>(serviceType: T, implementationType: T): void; 57 | /** 58 | * Append a singleton service of the type specified in serviceType. 59 | * 60 | * @description 61 | * Use this method when you want to have different implementations for the same serviceType 62 | * 63 | * This method introduced because AddSingleton will prevent the same serviceType from being added twice 64 | * 65 | * You'll need to use {@Inject} decorator to resolve array of implementations. 66 | * @Inject(Interceptor) interceptors: Interceptor[] 67 | * 68 | * @see AddSingleton 69 | * 70 | * @param serviceType The type of the service to add. 71 | * 72 | */ 73 | abstract AppendSingleton>(serviceType: T): void; 74 | } 75 | -------------------------------------------------------------------------------- /src/Extensions/AppendTransientExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | 6 | export abstract class AppendTransientExtensions { 7 | /** 8 | * Append a transient service of the type specified in serviceType with a factory specified in implementationFactory. 9 | * 10 | * @description 11 | * Use this method when you want to have different implementations for the same serviceType 12 | * 13 | * This method introduced because AddTransient will prevent the same serviceType from being added twice 14 | * 15 | * You'll need to use {@Inject} decorator to resolve array of implementations. 16 | * @Inject(Interceptor) interceptors: Interceptor[] 17 | * 18 | * @see AddTransient 19 | * 20 | * @param serviceType The type of the service to add. 21 | * @param implementationFactory the factory that creates the service. 22 | * 23 | */ 24 | abstract AppendTransient>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 25 | /** 26 | * Append a transient service of the injection token with a factory specified in implementationFactory 27 | * 28 | * @description 29 | * This method introduced because AddTransient will prevent the same injection token from being added twice 30 | * 31 | * You'll need to use {@Inject} decorator to resolve array of implementations. 32 | * @Inject(TOKEN) interceptors: TokenInterface[] 33 | * 34 | * @param injectionToken The token to add. 35 | * @param implementationFactory the factory that creates the service. 36 | * 37 | */ 38 | abstract AppendTransient>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 39 | 40 | /** 41 | * Append a Transient service of the type specified in serviceType with an implementation of the type specified in implementationType. 42 | * 43 | * @description 44 | * Use this method when you want to have different implementations for the same serviceType 45 | * 46 | * This method introduced because AddTransient will prevent the same serviceType from being added twice 47 | * 48 | * You'll need to use {@Inject} decorator to resolve array of implementations. 49 | * @Inject(Interceptor) interceptors: Interceptor[] 50 | * 51 | * @see AddTransient 52 | * 53 | * @param serviceType The type of the service to add. 54 | * @param implementationType The type of the implementation to use. 55 | */ 56 | abstract AppendTransient, I extends ClassType>>(serviceType: T, implementationType: T): void; 57 | /** 58 | * Append a transient service of the type specified in serviceType. 59 | * 60 | * @description 61 | * Use this method when you want to have different implementations for the same serviceType 62 | * 63 | * This method introduced because AddTransient will prevent the same serviceType from being added twice 64 | * 65 | * You'll need to use {@Inject} decorator to resolve array of implementations. 66 | * @Inject(Interceptor) interceptors: Interceptor[] 67 | * 68 | * @see AddTransient 69 | * 70 | * @param serviceType The type of the service to add. 71 | * 72 | */ 73 | abstract AppendTransient>(serviceType: T): void; 74 | } 75 | -------------------------------------------------------------------------------- /src/Extensions/ContextExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | export abstract class ContextExtensions { 3 | 4 | /** 5 | * Create a context and immediately destroy it after computation is done. 6 | * 7 | * @returns return value of `computation` 8 | * 9 | * @param computation 10 | */ 11 | abstract CreateScope(computation: (context: Context) => Promise | T): Promise; 12 | 13 | /** 14 | * Create context 15 | */ 16 | abstract Create(): Context; 17 | 18 | abstract Destroy(context: Context): void; 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/Extensions/ReplaceScopedExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | 6 | export abstract class ReplaceScopedExtensions { 7 | 8 | /** 9 | * Replace a scoped service of the type specified in serviceType with an implementation of the type specified in implementationType. 10 | * 11 | * @description 12 | * Replace removes previous registerd service. 13 | * 14 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 15 | * 16 | * @param serviceType The type of the service to replace. 17 | * @param implementationType The type of the implementation to use. 18 | */ 19 | abstract ReplaceScoped, I extends ClassType>>(serviceType: T, implementationType: I): void; 20 | 21 | /** 22 | * Replace scoped service of the type specified in serviceType with a factory specified in implementationFactory. 23 | * 24 | * @description 25 | * Replace removes previous registerd service. 26 | * 27 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 28 | * 29 | * @param serviceType The type of the service to replace. 30 | * @param implementationFactory the factory that creates the service. 31 | * 32 | */ 33 | abstract ReplaceScoped>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 34 | 35 | /** 36 | * Replace a scoped service of the injection token with a factory specified in implementationFactory. 37 | * 38 | * @description 39 | * Replace removes previous registerd service. 40 | * 41 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 42 | * 43 | * @param injectionToken The token to replace. 44 | * @param implementationFactory the factory that creates the service. 45 | * 46 | */ 47 | abstract ReplaceScoped>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 48 | 49 | /** 50 | * Replace a scoped service of the type specified in serviceType. 51 | * 52 | * @description 53 | * Replace removes previous registerd service. 54 | * 55 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 56 | * 57 | * @param serviceType The type of the service to replace. 58 | * 59 | */ 60 | abstract ReplaceScoped>(serviceType: T): void; 61 | 62 | } -------------------------------------------------------------------------------- /src/Extensions/ReplaceSingletonExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | 6 | export abstract class ReplaceSingletonExtensions { 7 | /** 8 | * Replace a singleton service of the type specified in serviceType with an implementation of the type specified in implementationType. 9 | * 10 | * @description 11 | * Replace removes previous registerd service. 12 | * 13 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 14 | * 15 | * @param serviceType The type of the service to replace. 16 | * @param implementationType The type of the implementation to use. 17 | */ 18 | abstract ReplaceSingleton, I extends ClassType>>(serviceType: T, implementationType: I): void; 19 | 20 | /** 21 | * Replace singleton service of the type specified in serviceType with a factory specified in implementationFactory. 22 | * 23 | * @description 24 | * Replace removes previous registerd service. 25 | * 26 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 27 | * 28 | * @param serviceType The type of the service to replace. 29 | * @param implementationFactory the factory that creates the service. 30 | * 31 | */ 32 | abstract ReplaceSingleton>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 33 | 34 | /** 35 | * Replace a singleton service of the injection token with a factory specified in implementationFactory. 36 | * 37 | * @description 38 | * Replace removes previous registerd service. 39 | * 40 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 41 | * 42 | * @param injectionToken The token to replace. 43 | * @param implementationFactory the factory that creates the service. 44 | * 45 | */ 46 | abstract ReplaceSingleton>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 47 | 48 | /** 49 | * Replace a singleton service of the type specified in serviceType. 50 | * 51 | * @description 52 | * Replace removes previous registerd service. 53 | * 54 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 55 | * 56 | * @param serviceType The type of the service to replace. 57 | * 58 | */ 59 | abstract ReplaceSingleton>(serviceType: T): void; 60 | 61 | } -------------------------------------------------------------------------------- /src/Extensions/ReplaceTransientExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | 6 | export abstract class ReplaceTransientExtensions { 7 | 8 | /** 9 | * Replace a singleton service of the type specified in serviceType with an implementation of the type specified in implementationType. 10 | * 11 | * @description 12 | * Replace removes previous registerd service. 13 | * 14 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 15 | * 16 | * @param serviceType The type of the service to replace. 17 | * @param implementationType The type of the implementation to use. 18 | */ 19 | abstract ReplaceTransient, I extends ClassType>>(serviceType: T, implementationType: I): void; 20 | 21 | /** 22 | * Replace singleton service of the type specified in serviceType with a factory specified in implementationFactory. 23 | * 24 | * @description 25 | * Replace removes previous registerd service. 26 | * 27 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 28 | * 29 | * @param serviceType The type of the service to replace. 30 | * @param implementationFactory the factory that creates the service. 31 | * 32 | */ 33 | abstract ReplaceTransient>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 34 | 35 | /** 36 | * Replace a singleton service of the injection token with a factory specified in implementationFactory. 37 | * 38 | * @description 39 | * Replace removes previous registerd service. 40 | * 41 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 42 | * 43 | * @param injectionToken The token to replace. 44 | * @param implementationFactory the factory that creates the service. 45 | * 46 | */ 47 | abstract ReplaceTransient>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 48 | 49 | /** 50 | * Replace a singleton service of the type specified in serviceType. 51 | * 52 | * @description 53 | * Replace removes previous registerd service. 54 | * 55 | * @throws {ServiceNotFoundException} in case serviceType is not registered before. 56 | * 57 | * @param serviceType The type of the service to replace. 58 | * 59 | */ 60 | abstract ReplaceTransient>(serviceType: T): void; 61 | 62 | } -------------------------------------------------------------------------------- /src/Extensions/ServiceProviderServiceExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ServiceType } from "../Types"; 4 | 5 | export abstract class ServiceProviderServiceExtensions { 6 | 7 | /** 8 | * Get service of type serviceType. 9 | * 10 | * @param serviceType The type of the service to get. 11 | * 12 | * @throws {ServiceNotFoundException} in case no service of type serviceType is registered. 13 | * 14 | * @returns A service of type serviceType. 15 | */ 16 | abstract GetRequiredService(serviceType: ServiceType): T; 17 | 18 | /** 19 | * Get return value of `InjectionToken` implementationFactory. 20 | * 21 | * @param injectionToken Instance of InjectionToken. 22 | * 23 | * @throws {ServiceNotFoundException} in case injectionToken is not registered. 24 | * 25 | * @returns Return value of implementationFactory. 26 | */ 27 | abstract GetRequiredService(injectionToken: InjectionToken): T; 28 | 29 | /** 30 | * Get service of type serviceType that is bound to that context. 31 | * 32 | * Use this overload to get scoped or transient service. 33 | * 34 | * @param serviceType The type of the service to get. 35 | * @param context A context to bind the service to. 36 | * 37 | * @throws {ServiceNotFoundException} in case no service of type serviceType is registered. 38 | * 39 | * @returns A scoped service of type serviceType. 40 | */ 41 | abstract GetRequiredService(serviceType: ServiceType, context: Context): T; 42 | 43 | /** 44 | * Get return value of `InjectionToken` implementationFactory. 45 | * 46 | * Use this overload to resolve scoped or transient InjectionToken implementationFactory. 47 | * 48 | * @param injectionToken Instance of InjectionToken. 49 | * @param context A context to bind `injectionToken` to. 50 | * 51 | * @throws {ServiceNotFoundException} in case injectionToken is not registered. 52 | * 53 | * @returns Return value of implementationFactory. 54 | */ 55 | abstract GetRequiredService(injectionToken: InjectionToken, context: Context): T; 56 | 57 | /** 58 | * Get service of type serviceType. 59 | * 60 | * @param serviceType The type of the service to get. 61 | * 62 | * @returns A service of type serviceType or null if there is no such service. 63 | */ 64 | abstract GetService(serviceType: ServiceType): T | null; 65 | 66 | /** 67 | * Get return value of `InjectionToken` implementationFactory. 68 | * 69 | * @param injectionToken Instance of InjectionToken. 70 | * 71 | * @returns Return value of implementationFactory or null if there is no such service. 72 | */ 73 | abstract GetService(injectionToken: InjectionToken): T | null; 74 | 75 | /** 76 | * Get service of type serviceType that is bound to that context. 77 | * 78 | * Use this overload to get scoped or transient service. 79 | * 80 | * @param serviceType The type of the service to get. 81 | * @param context A context to bind the service to. 82 | * 83 | * @returns A scoped service of type serviceType or null if there is no such service. 84 | */ 85 | abstract GetService(serviceType: ServiceType, context: Context): T | null; 86 | 87 | /** 88 | * Get the value of `InjectionToken` implementationFactory. 89 | * 90 | * Use this overload to resolve scoped or transient InjectionToken implementationFactory. 91 | * 92 | * @param injectionToken Instance of InjectionToken. 93 | * @param context A context to bind `injectionToken` to. 94 | * 95 | * @returns Return value of implementationFactory or null if there is no such service. 96 | */ 97 | abstract GetService(injectionToken: InjectionToken, context: Context): T | null; 98 | 99 | /** 100 | * Get array of services of type serviceType. 101 | * 102 | * @param serviceType The type of the service to get. 103 | * 104 | * @throws {ServiceNotFoundException} in case no service of type serviceType is registered. 105 | * 106 | * @returns An array of services of type serviceType 107 | */ 108 | abstract GetServices(serviceType: ServiceType): T[]; 109 | /** 110 | * Get array of registered `InjectionToken` implementationFactory values. 111 | * 112 | * @param injectionToken Instance of InjectionToken. 113 | * 114 | * @throws {ServiceNotFoundException} in case no service of type serviceType is registered. 115 | * 116 | * @returns An array of registered implementationFactory values 117 | */ 118 | abstract GetServices>(injectionToken: T): InjectionTokenGenericParam[]; 119 | /** 120 | * Get array of services of type serviceType that is bound to that context. 121 | * 122 | * Use this overload to get list of scoped or transient service. 123 | * 124 | * @param serviceType The type of the service to get. 125 | * @param context A context to bind the service to. 126 | * 127 | * @throws {ServiceNotFoundException} in case no service of type serviceType is registered. 128 | * 129 | * @returns An array of services of type serviceType 130 | */ 131 | abstract GetServices(serviceType: ServiceType, context: Context): T[]; 132 | /** 133 | * Get array of registered `InjectionToken` implementationFactory values. 134 | * 135 | * @param injectionToken Instance of InjectionToken. 136 | * @param context A context to bind `injectionToken` to. 137 | * 138 | * @throws {ServiceNotFoundException} in case no service of type serviceType is registered. 139 | * 140 | * @returns An array of registered implementationFactory values 141 | */ 142 | abstract GetServices>(injectionToken: T, context: Context): InjectionTokenGenericParam[]; 143 | } 144 | -------------------------------------------------------------------------------- /src/Extensions/TryAddScopedExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | export abstract class TryAddScopedExtensions { 6 | 7 | /** 8 | * Add a scoped service of the type specified in serviceType with a factory specified in implementationFactory if the service type hasn't already been registered. 9 | * 10 | * @description 11 | * Scoped services are created once per context. 12 | * 13 | * @see {CreateScope} 14 | * @see {Create} 15 | * 16 | * @param serviceType The type of the service to add. 17 | * @param implementationFactory the factory that creates the service. 18 | */ 19 | abstract TryAddScoped>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 20 | /** 21 | * Add a scoped service of the injection token with a factory specified in implementationFactory if the service type hasn't already been registered. 22 | * 23 | * @description 24 | * Scoped services are created once per context. 25 | * 26 | * @see {CreateScope} 27 | * @see {Create} 28 | * 29 | * @param injectionToken The token to add. 30 | * @param implementationFactory the factory that creates the service. 31 | * 32 | */ 33 | abstract TryAddScoped>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 34 | /** 35 | * Add a scoped service of the type specified in serviceType with an implementation of the type specified in implementationType if the service type hasn't already been registered. 36 | * 37 | * @description 38 | * Scoped services are created once per context. 39 | * 40 | * @see {CreateScope} 41 | * @see {Create} 42 | * 43 | * @param serviceType The type of the service to add. 44 | * @param implementationType The type of the implementation to use. 45 | */ 46 | abstract TryAddScoped, I extends ClassType>>(serviceType: T, implementationType: I): void; 47 | /** 48 | * Add a scoped service of the type specified in serviceType if the service type hasn't already been registered.. 49 | * 50 | * @description 51 | * Scoped services are created once per context. 52 | * 53 | * @see {CreateScope} 54 | * @see {Create} 55 | * 56 | * @param serviceType The type of the service to add. 57 | */ 58 | abstract TryAddScoped>(serviceType: T): void; 59 | } 60 | -------------------------------------------------------------------------------- /src/Extensions/TryAddSingletonExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | 6 | export abstract class TryAddSingletonExtensions { 7 | 8 | /** 9 | * Add a singleton service of the type specified in serviceType with a factory specified in implementationFactory if the service type hasn't already been registered. 10 | * 11 | * @description 12 | * Singleton services are created The first time they're requested. 13 | * the same instance will be resolved for each subsequent request. 14 | * 15 | * @param serviceType The type of the service to add. 16 | * @param implementationFactory the factory that creates the service. 17 | * 18 | */ 19 | abstract TryAddSingleton>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 20 | /** 21 | * Add a singleton service of the injection token with a factory specified in implementationFactory if the service type hasn't already been registered. 22 | * 23 | * @description 24 | * Singleton services are created The first time they're requested. 25 | * the same result will be returned for each subsequent request. 26 | * 27 | * @param injectionToken The token to add. 28 | * @param implementationFactory the factory that creates the service. 29 | * 30 | */ 31 | abstract TryAddSingleton>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 32 | /** 33 | * Add a singleton service of the type specified in serviceType with an implementation of the type specified in implementationType if the service type hasn't already been registered. 34 | * 35 | * @description 36 | * Singleton services are created The first time they're requested. 37 | * the same instance will be resolved for each subsequent request. 38 | * 39 | * @param serviceType The type of the service to add. 40 | * @param implementationType The type of the implementation to use. 41 | */ 42 | abstract TryAddSingleton, I extends ClassType>>(serviceType: T, implementationType: I): void; 43 | /** 44 | * Add a singleton service of the type specified in serviceType if the service type hasn't already been registered. 45 | * 46 | * @description 47 | * Singleton services are created The first time they're requested. 48 | * the same instance will be resolved for each subsequent request. 49 | * 50 | * @param serviceType The type of the service to add. 51 | * 52 | */ 53 | abstract TryAddSingleton>(serviceType: T): void; 54 | 55 | } -------------------------------------------------------------------------------- /src/Extensions/TryAddTransientExtensions.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "../Context"; 2 | import { InjectionToken, InjectionTokenGenericParam } from "../InjectionToken"; 3 | import { ClassType, ServiceType, TypeOf } from "../Types"; 4 | 5 | export abstract class TryAddTransientExtensions { 6 | 7 | /** 8 | * Add a transient service of the type specified in serviceType with a factory specified in implementationFactory if the service type hasn't already been registered. 9 | * 10 | * @description 11 | * Transient services are created each time they're requested, Unlike Singleton and Scoped, the reference for the implementation won't be stored. 12 | * 13 | * @param serviceType - The type of the service to add. 14 | * @param implementationFactory - the factory that creates the service. 15 | */ 16 | abstract TryAddTransient>(serviceType: T, implementationFactory: (context: Context) => TypeOf): void; 17 | /** 18 | * Add a transient service of the injection token with a factory specified in implementationFactory if the service type hasn't already been registered. 19 | * 20 | * @description 21 | * Transient services are created each time they're requested, Unlike Singleton and Scoped, the reference for the implementation won't be stored. 22 | * 23 | * @param injectionToken The token to add. 24 | * @param implementationFactory the factory that creates the service. 25 | * 26 | */ 27 | abstract TryAddTransient>(injectionToken: T, implementationFactory: (context: Context) => InjectionTokenGenericParam): void; 28 | /** 29 | * Add a transient service of the type specified in serviceType with an implementation of the type specified in implementationType if the service type hasn't already been registered. 30 | * 31 | * @description 32 | * Transient services are created each time they're requested, Unlike Singleton and Scoped, the reference for the implementation won't be stored 33 | * 34 | * @param serviceType - The type of the service to add. 35 | * @param implementationType - The type of the implementation to use. 36 | */ 37 | abstract TryAddTransient, I extends ClassType>>(serviceType: T, implementationType: I): void; 38 | /** 39 | * Add a transient service of the type specified in serviceType if the service type hasn't already been registered. 40 | * 41 | * @description 42 | * Transient services are created each time they're requested, Unlike Singleton and Scoped, the reference for the implementation won't be stored 43 | * 44 | * @param serviceType - The type of the service to add. 45 | */ 46 | abstract TryAddTransient>(serviceType: T): void; 47 | } 48 | -------------------------------------------------------------------------------- /src/Extensions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AddScopedExtensions'; 2 | export * from './AddSingletonExtensions'; 3 | export * from './AddTransientExtensions'; 4 | export * from './AppendScopedExtensions'; 5 | export * from './AppendSingletonExtensions'; 6 | export * from './AppendTransientExtensions'; 7 | export * from './ContextExtensions'; 8 | export * from './ReplaceScopedExtensions'; 9 | export * from './ReplaceSingletonExtensions'; 10 | export * from './ReplaceTransientExtensions'; 11 | export * from './ServiceProviderServiceExtensions'; 12 | export * from './TryAddScopedExtensions'; 13 | export * from './TryAddSingletonExtensions'; 14 | export * from './TryAddTransientExtensions'; 15 | 16 | -------------------------------------------------------------------------------- /src/Helpers.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "./Injectable"; 2 | import { ServiceLifetime } from "./ServiceLifetime"; 3 | import { ServiceType } from "./Types"; 4 | 5 | interface Options { 6 | serviceType?: ServiceType; 7 | } 8 | 9 | /** 10 | * A class level decorator used to registere the class as Singleton 11 | */ 12 | export function Singleton(options?: Options): ClassDecorator { 13 | return Injectable({ 14 | lifetime: ServiceLifetime.Singleton, 15 | serviceType: options?.serviceType 16 | }); 17 | } 18 | 19 | /** 20 | * A class level decorator used to registere the class as Transient 21 | */ 22 | export function Transient(options?: Options): ClassDecorator { 23 | return Injectable({ 24 | lifetime: ServiceLifetime.Transient, 25 | serviceType: options?.serviceType 26 | }); 27 | } 28 | 29 | /** 30 | * A class level decorator used to registere the class as Scoped 31 | */ 32 | export function Scoped(options?: Options): ClassDecorator { 33 | return Injectable({ 34 | lifetime: ServiceLifetime.Scoped, 35 | serviceType: options?.serviceType 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/Inject.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from "./InjectionToken"; 2 | import { InjectMetadata, ServiceType } from "./Types"; 3 | 4 | export function Inject | InjectionToken)>(serviceType: T): ParameterDecorator { 5 | return function (target, propertyKey, parameterIndex) { 6 | Reflect.defineMetadata(`DI:Inject:${ parameterIndex }`, { 7 | serviceType, 8 | parameterIndex, 9 | propertyKey 10 | } as InjectMetadata, target); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Injectable.ts: -------------------------------------------------------------------------------- 1 | import { AbstractServiceCollection } from "./AbstractServiceCollection"; 2 | import { ArgumentException } from "./Exceptions"; 3 | import RootServiceCollection from "./RootServiceCollection"; 4 | import { ServiceLifetime } from "./ServiceLifetime"; 5 | import { ServiceType } from "./Types"; 6 | import { isNullOrUndefined } from "./Utils"; 7 | 8 | interface InjectableOptions { 9 | /** 10 | * ``` 11 | * class Test {} 12 | * @Injectable({ 13 | * serviceType: Test, 14 | * lifetime: ServiceLifetime.Singleton 15 | * }) 16 | * class MyService {} 17 | * ``` 18 | * 19 | * Alias to 20 | * ``` 21 | * Injector.AddSingleton(Test, MyService) 22 | * ``` 23 | * 24 | * if not specifed, the serviceType will be the implementationType 25 | * ``` 26 | * Injector.AddSingleton(MyService, MyService) 27 | * ``` 28 | */ 29 | serviceType?: ServiceType; 30 | /** 31 | * Lifetime of the service 32 | */ 33 | lifetime: ServiceLifetime; 34 | 35 | /** 36 | * The service collection to contain the service. default to RootServiceCollection if not specifed 37 | */ 38 | provideIn?: 'root' | AbstractServiceCollection; 39 | 40 | /** 41 | * Use `TryAdd{lifetime}` instead of `Add{lifetime}` with respect to serviceType option. 42 | * 43 | * Injector.TryAdd{lifetime} 44 | */ 45 | tryAddService?: boolean; 46 | } 47 | 48 | /** 49 | * Decorator that makes a service available to be injected as a dependency. 50 | * 51 | * it takes option parameter that used to add the class to the injector directly 52 | * ``` 53 | * "@Injectable({ 54 | * lifetime: ServiceLifetime.Singleton 55 | * }) 56 | * class MySingletonService { }" 57 | * ``` 58 | * @param options 59 | */ 60 | export function Injectable(options?: InjectableOptions): ClassDecorator { 61 | return function (target: Function) { 62 | // TODO: validate options.serviceType 63 | 64 | // TODO: add option to specify if the service should be replaced if exist 65 | // the option should be function callback style that returns boolean 66 | // if false is returned an error should be thrown in case of existance 67 | // if true is returned the service should be replaced 68 | // replaceOnExist: () => boolean 69 | 70 | // TODO: add option to skip adding the service if exist 71 | // tryAddService: boolean; 72 | 73 | if (isNullOrUndefined(options)) { 74 | return; 75 | } 76 | 77 | const injectableOptions = Object.assign({}, options); 78 | injectableOptions.provideIn ??= 'root'; 79 | const serviceCollection = injectableOptions.provideIn === 'root' ? RootServiceCollection : injectableOptions.provideIn; 80 | if (!(serviceCollection instanceof AbstractServiceCollection)) { 81 | throw new ArgumentException('@Injectable providedIn accepts only "root" or instance of AbstractServiceCollection', 'options.providedIn') 82 | } 83 | 84 | switch (injectableOptions.lifetime) { 85 | case ServiceLifetime.Scoped: 86 | if (options.tryAddService) { 87 | serviceCollection.TryAddScoped(injectableOptions.serviceType ?? target, target as any) 88 | } else { 89 | serviceCollection.AddScoped(injectableOptions.serviceType ?? target, target as any) 90 | } 91 | break; 92 | case ServiceLifetime.Singleton: 93 | if (options.tryAddService) { 94 | serviceCollection.TryAddSingleton(injectableOptions.serviceType ?? target, target as any) 95 | } else { 96 | serviceCollection.AddSingleton(injectableOptions.serviceType ?? target, target as any) 97 | } 98 | break; 99 | case ServiceLifetime.Transient: 100 | if (options.tryAddService) { 101 | serviceCollection.TryAddTransient(injectableOptions.serviceType ?? target, target as any) 102 | } else { 103 | serviceCollection.AddTransient(injectableOptions.serviceType ?? target, target as any) 104 | } 105 | break; 106 | default: 107 | throw new ArgumentException('@Injectable lifetime is unknown', 'options.lifetime') 108 | } 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /src/InjectionToken.ts: -------------------------------------------------------------------------------- 1 | import RootServiceCollection from "./RootServiceCollection"; 2 | import { AbstractServiceCollection } from "./AbstractServiceCollection"; 3 | import { ArgumentException } from "./Exceptions"; 4 | import { ServiceLifetime } from "./ServiceLifetime"; 5 | import { ImplementationFactory } from "./Types"; 6 | import { isArrowFn, isNullOrUndefined, notNullOrUndefined } from "./Utils"; 7 | export type InjectionTokenGenericParam> = C extends InjectionToken ? T : unknown; 8 | 9 | type FactoryType any) 11 | | 12 | string 13 | | 14 | Array 15 | | 16 | Object 17 | > = T extends new (...args: any) => infer R ? R : T; 18 | interface Options any) 20 | | 21 | string 22 | | 23 | Array 24 | | 25 | Object> { 26 | /** 27 | * Lifetime of the service 28 | */ 29 | lifetime: ServiceLifetime; 30 | 31 | /** 32 | * The factory that creates the service. 33 | */ 34 | implementationFactory: ImplementationFactory>; 35 | 36 | provideIn?: 'root' | AbstractServiceCollection; 37 | } 38 | 39 | /** 40 | * Injection tokens allows injection of values that don't have a runtime representation. 41 | * 42 | * Use an InjectionToken whenever the type you are injecting is not reified (does not have a runtime representation) such as when injecting an interface, callable type, array or parameterized type. 43 | * 44 | * InjectionToken is parameterized on T which is the type of object which will be returned by the Injector . 45 | * 46 | * @link https://github.com/angular/angular/blob/master/packages/core/src/di/injection_token.ts 47 | */ 48 | export class InjectionToken { 49 | #InjectionTokenDifferentiator = null; 50 | constructor(_name: string, options?: Options) { 51 | class serviceType extends InjectionToken { }; 52 | 53 | if (typeof _name !== 'string' || _name.trim() === '') { 54 | throw new ArgumentException('InjectionToken name must be valid non string', 'name') 55 | } 56 | 57 | if (notNullOrUndefined(options) && !isArrowFn(options.implementationFactory)) { 58 | throw new ArgumentException('InjectionToken implementationFactory can only be arrow function', 'options.implementationFactory') 59 | } 60 | 61 | if (notNullOrUndefined(options) && isNullOrUndefined(ServiceLifetime[options.lifetime])) { 62 | throw new ArgumentException('InjectionToken lifetime is unknown', 'options.lifetime') 63 | } 64 | 65 | Object.defineProperty(serviceType, 'name', { value: _name }); 66 | 67 | if (notNullOrUndefined(options)) { 68 | options.provideIn ??= 'root'; 69 | const serviceCollection = options.provideIn === 'root' ? RootServiceCollection : options.provideIn; 70 | switch (options.lifetime) { 71 | case ServiceLifetime.Scoped: 72 | serviceCollection.AddScoped(serviceType, options.implementationFactory as any) 73 | break; 74 | case ServiceLifetime.Singleton: 75 | serviceCollection.AddSingleton(serviceType, options.implementationFactory as any) 76 | break; 77 | case ServiceLifetime.Transient: 78 | serviceCollection.AddTransient(serviceType, options.implementationFactory as any) 79 | break; 80 | default: 81 | throw new ArgumentException('Injectable lifetime is unknown', 'options.lifetime') 82 | } 83 | } 84 | return serviceType as any; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Injector.ts: -------------------------------------------------------------------------------- 1 | import { AbstractServiceCollection } from "./AbstractServiceCollection"; 2 | import { Context } from "./Context"; 3 | import { 4 | AddScopedExtensions, 5 | AddSingletonExtensions, 6 | AddTransientExtensions, 7 | AppendScopedExtensions, 8 | AppendSingletonExtensions, 9 | AppendTransientExtensions, 10 | ContextExtensions, 11 | ReplaceScopedExtensions, 12 | ReplaceSingletonExtensions, 13 | ReplaceTransientExtensions, 14 | ServiceProviderServiceExtensions, 15 | TryAddScopedExtensions, 16 | TryAddSingletonExtensions, 17 | TryAddTransientExtensions 18 | } from "./Extensions"; 19 | import RootServiceCollection from "./RootServiceCollection"; 20 | 21 | 22 | type Extensions = 23 | AddScopedExtensions & 24 | AddSingletonExtensions & 25 | AddTransientExtensions & 26 | AppendScopedExtensions & 27 | AppendSingletonExtensions & 28 | AppendTransientExtensions & 29 | ReplaceSingletonExtensions & 30 | ReplaceTransientExtensions & 31 | ReplaceScopedExtensions & 32 | TryAddScopedExtensions & 33 | TryAddSingletonExtensions & 34 | TryAddTransientExtensions & 35 | ServiceProviderServiceExtensions & 36 | ContextExtensions 37 | 38 | type Of = { 39 | Of(serviceCollection: AbstractServiceCollection): Extensions; 40 | } 41 | 42 | class _Injector implements Extensions { 43 | private readonly serviceProvider ; 44 | constructor( 45 | private serviceCollection: AbstractServiceCollection 46 | ) { 47 | this.serviceProvider =this.serviceCollection.BuildServiceProvider() 48 | } 49 | 50 | ReplaceTransient(serviceType: any, implementationType?: any): void { 51 | this.serviceCollection.ReplaceTransient(serviceType, implementationType); 52 | } 53 | 54 | ReplaceScoped(serviceType: any, implementationType?: any): void { 55 | this.serviceCollection.ReplaceScoped(serviceType, implementationType); 56 | } 57 | 58 | ReplaceSingleton(serviceType: any, implementationType?: any): void { 59 | this.serviceCollection.ReplaceSingleton(serviceType, implementationType); 60 | } 61 | 62 | AppendTransient(serviceType: any, implementation?: any) { 63 | this.serviceCollection.AppendTransient(serviceType, implementation); 64 | } 65 | 66 | AppendSingleton(serviceType: any, implementation?: any) { 67 | this.serviceCollection.AppendSingleton(serviceType, implementation); 68 | } 69 | 70 | AppendScoped(serviceType: any, implementation?: any) { 71 | this.serviceCollection.AppendScoped(serviceType, implementation); 72 | } 73 | 74 | AddScoped(serviceType: any, implementation?: any) { 75 | this.serviceCollection.AddScoped(serviceType, implementation); 76 | } 77 | 78 | AddSingleton(serviceType: any, implementation?: any) { 79 | this.serviceCollection.AddSingleton(serviceType, implementation); 80 | } 81 | 82 | AddTransient(serviceType: any, implementation?: any) { 83 | this.serviceCollection.AddTransient(serviceType, implementation); 84 | } 85 | 86 | TryAddScoped(serviceType: any, implementation?: any) { 87 | this.serviceCollection.TryAddScoped(serviceType, implementation); 88 | } 89 | 90 | TryAddSingleton(serviceType: any, implementation?: any) { 91 | this.serviceCollection.TryAddSingleton(serviceType, implementation); 92 | } 93 | 94 | TryAddTransient(serviceType: any, implementation?: any) { 95 | this.serviceCollection.TryAddTransient(serviceType, implementation); 96 | } 97 | 98 | GetRequiredService(injectionToken: any, context?: any): T { 99 | return this.serviceProvider.GetRequiredService(injectionToken, context); 100 | } 101 | 102 | GetService(injectionToken: any, context?: any): T | null { 103 | return this.serviceProvider.GetService(injectionToken, context); 104 | } 105 | 106 | GetServices(serviceType: any, context?: any): T[] { 107 | return this.serviceProvider.GetServices(serviceType, context); 108 | } 109 | 110 | CreateScope(computation: (context: Context) => T | Promise): Promise { 111 | return this.serviceProvider.CreateScope(computation); 112 | } 113 | 114 | Destroy(context: Context): void { 115 | return this.serviceProvider.Destroy(context); 116 | } 117 | 118 | Create(): Context { 119 | return this.serviceProvider.Create(); 120 | } 121 | } 122 | export const Injector: Extensions & Of = new _Injector(RootServiceCollection) as any; 123 | 124 | 125 | Injector.Of = (serviceCollection: AbstractServiceCollection) => { 126 | return new _Injector(serviceCollection); 127 | } 128 | -------------------------------------------------------------------------------- /src/Options.ts: -------------------------------------------------------------------------------- 1 | export class ServiceProviderOptions { 2 | constructor( 3 | public disableSingletonLifetimeValidation: boolean, 4 | public disableTransientLifetimeValidation: boolean, 5 | public disableStrictTypeValidation: boolean, 6 | /** 7 | * 8 | */ 9 | public allowFunctionAsServiceType: boolean 10 | ) { } 11 | } 12 | -------------------------------------------------------------------------------- /src/RootServiceCollection.ts: -------------------------------------------------------------------------------- 1 | import { AbstractServiceCollection } from "./AbstractServiceCollection"; 2 | import { ServiceCollection } from "./ServiceCollection" 3 | 4 | const collection: AbstractServiceCollection = new ServiceCollection(); 5 | export default collection; -------------------------------------------------------------------------------- /src/ServiceCollection.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { AbstractServiceCollection } from "./AbstractServiceCollection"; 3 | import { Context } from "./Context"; 4 | import { ContextRegistry } from "./ContextRegistry"; 5 | import { 6 | ActivationFailedException, 7 | ArgumentException, 8 | InvalidOperationException, 9 | LifestyleMismatchException, 10 | ResolutionFailedException, 11 | ServiceExistException, 12 | ServiceNotFoundException 13 | } from "./Exceptions"; 14 | import { ServiceDescriptor } from "./ServiceDescriptor"; 15 | import { ServiceLifetime } from "./ServiceLifetime"; 16 | import { ServiceProvider } from "./ServiceProvider"; 17 | import { ClassType, ImplementationFactory, InjectMetadata, ServiceType } from "./Types"; 18 | import { isArrowFn, isConstructor, isNullOrUndefined, isTypeOf, lastElement, notNullOrUndefined } from './Utils'; 19 | 20 | export class ServiceCollection extends AbstractServiceCollection { 21 | 22 | #serviceTypeToServiceDescriptor = new Map, ServiceDescriptor[]>(); 23 | #toBeCreatedServices = new Map, ServiceType>(); 24 | #serviceProvider!: ServiceProvider; 25 | 26 | public ReplaceScoped(serviceType: any, implementationType?: any): void { 27 | this.ReplaceService(serviceType, implementationType, ServiceLifetime.Scoped); 28 | } 29 | 30 | public ReplaceTransient(serviceType: any, implementationType?: any): void { 31 | this.ReplaceService(serviceType, implementationType, ServiceLifetime.Transient); 32 | } 33 | 34 | public ReplaceSingleton(serviceType: any, implementationType?: any): void { 35 | this.ReplaceService(serviceType, implementationType, ServiceLifetime.Singleton); 36 | } 37 | 38 | public AppendTransient(serviceType: any, implementation?: any) { 39 | this.AppendService(serviceType, implementation, ServiceLifetime.Transient); 40 | } 41 | 42 | public AppendSingleton(serviceType: any, implementation?: any) { 43 | this.AppendService(serviceType, implementation, ServiceLifetime.Singleton); 44 | } 45 | 46 | public AppendScoped(serviceType: any, implementation?: any) { 47 | this.AppendService(serviceType, implementation, ServiceLifetime.Scoped); 48 | } 49 | 50 | public AddScoped(serviceType: any, implementation?: any) { 51 | this.AddService(serviceType, implementation, ServiceLifetime.Scoped); 52 | } 53 | 54 | public AddSingleton(serviceType: any, implementation?: any) { 55 | this.AddService(serviceType, implementation, ServiceLifetime.Singleton); 56 | } 57 | 58 | public AddTransient(serviceType: any, implementation?: any) { 59 | this.AddService(serviceType, implementation, ServiceLifetime.Transient); 60 | } 61 | 62 | public TryAddScoped(serviceType: any, implementation?: any) { 63 | this.TryAddService(serviceType, implementation, ServiceLifetime.Scoped); 64 | } 65 | 66 | public TryAddSingleton(serviceType: any, implementation?: any) { 67 | this.TryAddService(serviceType, implementation, ServiceLifetime.Singleton); 68 | } 69 | 70 | public TryAddTransient(serviceType: any, implementation?: any) { 71 | this.TryAddService(serviceType, implementation, ServiceLifetime.Transient); 72 | } 73 | /** 74 | * @internal 75 | */ 76 | private AddService(serviceType: ServiceType, implementation: ClassType | ImplementationFactory, lifetime: ServiceLifetime): void { 77 | this.ValidateService(serviceType, implementation); 78 | 79 | const currentDescriptors = this.#serviceTypeToServiceDescriptor.get(serviceType); 80 | if (currentDescriptors) { 81 | const descriptor = currentDescriptors[0] as ServiceDescriptor; 82 | throw new ServiceExistException( 83 | serviceType.name, 84 | descriptor.lifetimeType 85 | ); 86 | } 87 | const descriptor = this.MakeServiceDescriptor(serviceType, implementation, lifetime); 88 | 89 | const descriptors = this.#serviceTypeToServiceDescriptor.get(serviceType) ?? []; 90 | descriptors.push(descriptor); 91 | this.#serviceTypeToServiceDescriptor.set(serviceType, descriptors); 92 | 93 | const parentServiceType = this.#toBeCreatedServices.get(serviceType); 94 | if (parentServiceType) { 95 | this.ValidateSingletonLifetime(parentServiceType, this.GetServiceDependencies(parentServiceType)); 96 | this.#toBeCreatedServices.delete(serviceType); 97 | } 98 | 99 | if (lifetime === ServiceLifetime.Singleton) { 100 | this.ValidateSingletonLifetime(serviceType, this.GetServiceDependencies(serviceType)); 101 | } 102 | 103 | } 104 | 105 | /** 106 | * @internal 107 | */ 108 | private TryAddService(serviceType: ServiceType, implementation: ClassType | ImplementationFactory, lifetime: ServiceLifetime): void { 109 | try { 110 | this.AddService(serviceType, implementation, lifetime); 111 | } catch (error) { 112 | if (!isTypeOf(error, ServiceExistException)) { 113 | throw error; 114 | } 115 | } 116 | } 117 | 118 | private AppendService(serviceType: ServiceType, implementation: ClassType | ImplementationFactory, lifetime: ServiceLifetime): void { 119 | this.ValidateService(serviceType, implementation); 120 | 121 | const descriptor = this.MakeServiceDescriptor(serviceType, implementation, lifetime); 122 | 123 | const descriptors = this.#serviceTypeToServiceDescriptor.get(serviceType) ?? []; 124 | descriptors.push(descriptor); 125 | this.#serviceTypeToServiceDescriptor.set(serviceType, descriptors); 126 | } 127 | 128 | private ReplaceService(serviceType: ServiceType, implementation: ClassType | ImplementationFactory, lifetime: ServiceLifetime): void { 129 | this.ValidateService(serviceType, implementation); 130 | 131 | const descriptors = this.#serviceTypeToServiceDescriptor.get(serviceType); 132 | if (isNullOrUndefined(descriptors)) { 133 | throw new ServiceNotFoundException(serviceType.name); 134 | } 135 | 136 | this.Remove(serviceType); 137 | 138 | this.AddService(serviceType, implementation, lifetime); 139 | } 140 | 141 | public Remove(serviceType: ServiceType) { 142 | this.#serviceTypeToServiceDescriptor.delete(serviceType); 143 | } 144 | 145 | public GetServiceDescriptors(serviceType: ServiceType) { 146 | const descriptor = this.#serviceTypeToServiceDescriptor.get(serviceType); 147 | return descriptor ?? []; 148 | } 149 | 150 | public BuildServiceProvider() { 151 | this.#serviceProvider ??= new ServiceProvider(this) 152 | return this.#serviceProvider; 153 | } 154 | 155 | private GetServiceDependencies(serviceType: ServiceType): ServiceType[] { 156 | return Reflect.getMetadata('design:paramtypes', serviceType) ?? []; 157 | } 158 | 159 | private GetServiceInjectMeta(serviceType: ServiceType, index: number): InjectMetadata { 160 | return Reflect.getMetadata(`DI:Inject:${ index }`, serviceType); 161 | } 162 | 163 | private ValidateService(serviceType: any, implementation: any) { 164 | if (!isConstructor(serviceType)) { 165 | throw new ArgumentException(`the serviceType ${ serviceType?.name ?? '' } cannot be added. it must be class syntax`, 'serviceType'); 166 | } 167 | 168 | if ( 169 | notNullOrUndefined(implementation) 170 | && !isConstructor(implementation) 171 | && !isArrowFn(implementation) 172 | ) { 173 | throw new ArgumentException('implementation can be either class or factory function (arrow function syntax)', 'implementation'); 174 | } 175 | } 176 | 177 | private ValidateSingletonLifetime(serviceType: ServiceType, tokens: ServiceType[]) { 178 | tokens.forEach((token, index) => { 179 | const injectMetadata = this.GetServiceInjectMeta(serviceType, index); 180 | const argumentType = injectMetadata?.serviceType ?? token 181 | const descriptor = lastElement(this.GetServiceDescriptors(argumentType))!; 182 | if (isNullOrUndefined(descriptor)) { 183 | this.#toBeCreatedServices.set(argumentType, serviceType); 184 | // throw new ActivationFailedException(argumentType, serviceType); 185 | return; 186 | } 187 | 188 | // Only other Singleton services can be injected in Singleton service 189 | if (descriptor.lifetime !== ServiceLifetime.Singleton) { 190 | const serviceTypeDescriptor = lastElement(this.GetServiceDescriptors(serviceType))!; 191 | 192 | if (isNullOrUndefined(serviceTypeDescriptor)) { 193 | throw new ServiceNotFoundException(serviceType.name); 194 | } 195 | 196 | throw new LifestyleMismatchException({ 197 | serviceType: serviceType, 198 | injectedServiceType: argumentType, 199 | serviceTypeLifetimeType: serviceTypeDescriptor.lifetimeType, 200 | injectedServiceLifetimeType: descriptor.lifetimeType 201 | }); 202 | } 203 | 204 | }); 205 | } 206 | 207 | private MakeServiceDescriptor(parentServiceType: any, implementation: any, lifetime: ServiceLifetime) { 208 | const resolver = (serviceTypeOrFactory: ClassType | ImplementationFactory) => { 209 | if (lifetime === ServiceLifetime.Transient) { 210 | return (descriptor: ServiceDescriptor, context?: Context) => { 211 | if (isArrowFn(serviceTypeOrFactory)) { 212 | return serviceTypeOrFactory(context); 213 | } 214 | return this.Resolve(serviceTypeOrFactory, lifetime)(context); 215 | }; 216 | } 217 | if (isArrowFn(serviceTypeOrFactory)) { 218 | return this.Cache(parentServiceType, serviceTypeOrFactory); 219 | } 220 | return this.Cache(parentServiceType, this.Resolve(serviceTypeOrFactory, lifetime)); 221 | } 222 | 223 | return new ServiceDescriptor(lifetime, resolver(implementation ?? parentServiceType)); 224 | } 225 | 226 | private Resolve(serviceType: ClassType, lifetime: ServiceLifetime): ImplementationFactory { 227 | // TODO: add option to disable SingletonLifetime validation 228 | return (context?: Context) => { 229 | const tokens = this.GetServiceDependencies(serviceType); 230 | 231 | return new serviceType(...tokens.map((token, index) => { 232 | const injectMetadata = this.GetServiceInjectMeta(serviceType, index); 233 | const argumentType = injectMetadata?.serviceType ?? token 234 | 235 | // TODO: add option to disable TransientLifetime validation 236 | if (lifetime === ServiceLifetime.Transient && !(context instanceof Context)) { 237 | this.ValidateTransientLifetime(serviceType, argumentType); 238 | } 239 | if (token === Array) { 240 | return this.#serviceProvider.GetServices(argumentType, context); 241 | } else { 242 | return this.#serviceProvider.GetRequiredService(argumentType, context); 243 | } 244 | })); 245 | } 246 | } 247 | 248 | private Cache>(serviceType: T, fn: Function) { 249 | return (descriptor: ServiceDescriptor, context?: Context) => { 250 | if (!(context instanceof Context)) { 251 | throw new InvalidOperationException(`Wrong context used to retrieve the dependency ${ serviceType.name }, make sure to use the same context that was used before with {Injector.Create}`); 252 | } 253 | const container = ContextRegistry.GetInstance().GetContainer(context); 254 | if (!(container instanceof WeakMap)) { 255 | throw new InvalidOperationException(`Context are not registered, use {Injector.Create} to register the context.`); 256 | } 257 | if (!container.has(descriptor)) { 258 | container.set(descriptor, fn(context)) 259 | } 260 | const result = container.get(descriptor); 261 | if (isNullOrUndefined(result)) { 262 | throw new ResolutionFailedException(serviceType.name); 263 | } 264 | return result; 265 | }; 266 | } 267 | 268 | private ValidateTransientLifetime(serviceType: ServiceType, argumentType: ServiceType) { 269 | const descriptor = lastElement(this.GetServiceDescriptors(argumentType)); 270 | if (isNullOrUndefined(descriptor)) { 271 | throw new ActivationFailedException(argumentType, serviceType); 272 | } 273 | 274 | if (descriptor.lifetime === ServiceLifetime.Scoped) { 275 | const serviceTypeDescriptor = lastElement(this.GetServiceDescriptors(serviceType))!; 276 | if (isNullOrUndefined(serviceTypeDescriptor)) { 277 | throw new ServiceNotFoundException(serviceType.name); 278 | } 279 | 280 | throw new LifestyleMismatchException({ 281 | serviceType: serviceType, 282 | injectedServiceType: argumentType, 283 | serviceTypeLifetimeType: serviceTypeDescriptor.lifetimeType, 284 | injectedServiceLifetimeType: descriptor.lifetimeType, 285 | needContext: true 286 | }); 287 | } 288 | 289 | } 290 | 291 | } 292 | 293 | 294 | -------------------------------------------------------------------------------- /src/ServiceDescriptor.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "./Context"; 2 | import { ServiceLifetime } from "./ServiceLifetime"; 3 | export class ServiceDescriptor { 4 | constructor( 5 | public readonly lifetime: ServiceLifetime, 6 | public readonly implementation: (self: ServiceDescriptor, context?: Context | undefined) => any 7 | ) { 8 | Object.seal(this); 9 | Object.freeze(this); 10 | } 11 | 12 | get lifetimeType() { 13 | return ServiceLifetime[this.lifetime]!; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/ServiceLifetime.ts: -------------------------------------------------------------------------------- 1 | export enum ServiceLifetime { 2 | /** 3 | * Specifies that a new instance of the service will be created for each scope. 4 | * 5 | * Scope can be created by supplying arbitary object 6 | */ 7 | Scoped = 1, 8 | /** 9 | * Specifies that a new instance of the service will be created every time it is requested. 10 | */ 11 | Transient, 12 | /** 13 | * Specifies that a single instance of the service will be created. 14 | */ 15 | Singleton 16 | } 17 | -------------------------------------------------------------------------------- /src/ServiceProvider.ts: -------------------------------------------------------------------------------- 1 | import { AbstractServiceCollection } from "./AbstractServiceCollection"; 2 | import { Context } from "./Context"; 3 | import { ContextRegistry } from "./ContextRegistry"; 4 | import { ArgumentException, InvalidOperationException, ServiceNotFoundException } from "./Exceptions"; 5 | import { ContextExtensions, ServiceProviderServiceExtensions } from "./Extensions"; 6 | import { ServiceDescriptor } from "./ServiceDescriptor"; 7 | import { ServiceLifetime } from "./ServiceLifetime"; 8 | import { isNullOrUndefined, isTypeOf, lastElement } from "./Utils"; 9 | 10 | export class ServiceProvider implements ServiceProviderServiceExtensions, ContextExtensions { 11 | #contextRegistry = ContextRegistry.GetInstance(); 12 | #singletonContext?: Context; 13 | 14 | private get singletonContext() { 15 | return this.#singletonContext ?? (this.#singletonContext = this.Create()); 16 | } 17 | 18 | constructor(private serviceCollection: AbstractServiceCollection) { 19 | } 20 | 21 | GetService(serviceTypeOrInjectionToken: any, context?: Context): T | null { 22 | try { 23 | return this.GetRequiredService(serviceTypeOrInjectionToken, context); 24 | } catch (error) { 25 | if (!isTypeOf(error, ServiceNotFoundException)) { 26 | throw error; 27 | } 28 | return null; 29 | } 30 | } 31 | 32 | GetServices(serviceTypeOrInjectionToken: any, context?: any): T[] { 33 | if (isNullOrUndefined(serviceTypeOrInjectionToken)) { 34 | throw new ArgumentException('Must provide service type', 'serviceType'); 35 | } 36 | const descriptors = this.serviceCollection.GetServiceDescriptors(serviceTypeOrInjectionToken); 37 | return descriptors.map(descriptor => this.GetImplementation(descriptor, context)); 38 | } 39 | 40 | 41 | GetRequiredService(serviceTypeOrInjectionToken: any, context?: Context): T { 42 | if (isNullOrUndefined(serviceTypeOrInjectionToken)) { 43 | throw new ArgumentException('Must provide service type', 'serviceType'); 44 | } 45 | const descriptor = lastElement(this.serviceCollection.GetServiceDescriptors(serviceTypeOrInjectionToken)); 46 | if (isNullOrUndefined(descriptor)) { 47 | throw new ServiceNotFoundException(serviceTypeOrInjectionToken.name); 48 | } 49 | return this.GetImplementation(descriptor, context); 50 | } 51 | 52 | public Create() { 53 | const context = new Context(); 54 | 55 | if (!(context instanceof Context)) { 56 | throw new ArgumentException(`${context} should be of type Context`, 'context'); 57 | } 58 | 59 | if (this.#contextRegistry.Has(context)) { 60 | throw new InvalidOperationException("Context already in use."); 61 | } 62 | 63 | this.#contextRegistry.Add(context) 64 | 65 | return context; 66 | } 67 | 68 | public async CreateScope(computation: (context: Context) => T | Promise): Promise { 69 | const context = this.Create(); 70 | const result = await computation(context); 71 | this.Destroy(context); 72 | return result; 73 | } 74 | 75 | public Destroy(context: Context) { 76 | if (!(context instanceof Context)) { 77 | throw new ArgumentException(`${context} should be of type Context`, 'context'); 78 | } 79 | 80 | if (!this.#contextRegistry.Has(context)) { 81 | throw new InvalidOperationException("Cannot find context"); 82 | } 83 | 84 | this.#contextRegistry.Delete(context) 85 | } 86 | 87 | private GetImplementation(descriptor: ServiceDescriptor, context: Context | undefined) { 88 | switch (descriptor.lifetime) { 89 | case ServiceLifetime.Singleton: 90 | return descriptor.implementation(descriptor, this.singletonContext); 91 | case ServiceLifetime.Transient: 92 | return descriptor.implementation(descriptor, context); 93 | case ServiceLifetime.Scoped: 94 | if (!(context instanceof Context)) { 95 | throw new ArgumentException(`${context} should be of type Context`, 'context'); 96 | } 97 | return descriptor.implementation(descriptor, context); 98 | default: 99 | throw new InvalidOperationException( 100 | `Lifetime ${ServiceLifetime[descriptor.lifetime]} is not supported. 101 | it looks like problem with the library it self, please create an issue in Github 102 | so it could be fixed. 103 | ` 104 | ); 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/Types.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "./Context"; 2 | 3 | 4 | export type ClassType = new (...args: any[]) => T; 5 | 6 | export declare interface AbstractClassType extends Function { 7 | prototype: T; 8 | } 9 | 10 | export type ServiceType = ClassType | AbstractClassType; 11 | 12 | 13 | export type TypeOf> = T extends new (...args: any) => infer R 14 | ? R : T extends { prototype: infer R } ? R : any; 15 | 16 | export type ImplementationFactory = (context?: Context) => T; 17 | 18 | export interface InjectMetadata { 19 | serviceType: ServiceType; 20 | parameterIndex: number; 21 | propertyKey: string | symbol; 22 | } -------------------------------------------------------------------------------- /src/Utils.ts: -------------------------------------------------------------------------------- 1 | import { ClassType, ServiceType } from "./Types"; 2 | 3 | 4 | export function isConstructor(value: any): value is ServiceType { 5 | if (isNullOrUndefined(value)) { 6 | return false; 7 | } 8 | 9 | if (value.toString().startsWith('function')) { 10 | return false; 11 | } 12 | 13 | return !!value.prototype && !!value.prototype.constructor.name; 14 | } 15 | 16 | export function isArrowFn(fn: any): fn is (...args: any[]) => any { 17 | return (typeof fn === 'function') && /^[^{]+?=>/.test(fn.toString()); 18 | }; 19 | 20 | export function isNullOrUndefined(value: any): value is undefined | null { 21 | return value === undefined || value === null; 22 | } 23 | 24 | export function notNullOrUndefined(value: T): value is Exclude { 25 | return !isNullOrUndefined(value); 26 | } 27 | 28 | export function lastElement(list: T[]): T | undefined { 29 | return list[list.length - 1]; 30 | } 31 | /** 32 | * Check if error is instanceOf type but not any of it's parent 33 | */ 34 | export function isTypeOf>(error: any, type: T): error is T { 35 | return Object.getPrototypeOf(error).constructor === type; 36 | } 37 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import RootServiceCollection from './RootServiceCollection'; 2 | 3 | export * from './AbstractServiceCollection'; 4 | export * from './Context'; 5 | export * from './Exceptions'; 6 | export * from './Helpers'; 7 | export * from './Inject'; 8 | export * from './Injectable'; 9 | export * from './InjectionToken'; 10 | export * from './Injector'; 11 | export * from './ServiceCollection'; 12 | export * from './ServiceLifetime'; 13 | export * from './Types'; 14 | export { ServiceType } from './Types'; 15 | export { RootServiceCollection }; 16 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist", 6 | "__test__" 7 | ] 8 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "incremental": true, 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "declaration": true, 8 | "declarationMap": false, 9 | "emitDeclarationOnly": true, 10 | "inlineSourceMap": true, 11 | "inlineSources": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./src", 14 | "removeComments": false, 15 | "noEmit": false, 16 | "importHelpers": true, 17 | "strict": true, 18 | "noImplicitAny": true, 19 | "strictNullChecks": true, 20 | "strictFunctionTypes": true, 21 | "strictBindCallApply": true, 22 | "strictPropertyInitialization": true, 23 | "noImplicitThis": true, 24 | "alwaysStrict": true, 25 | "noUnusedLocals": false, 26 | "noUnusedParameters": false, 27 | "noImplicitReturns": true, 28 | "noFallthroughCasesInSwitch": true, 29 | "noUncheckedIndexedAccess": true, 30 | "moduleResolution": "Bundler", 31 | "esModuleInterop": true, 32 | "experimentalDecorators": true, 33 | "emitDecoratorMetadata": true, 34 | "skipLibCheck": true, 35 | "forceConsistentCasingInFileNames": true 36 | } 37 | } -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist", 6 | ] 7 | } --------------------------------------------------------------------------------