@noego/ioc 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.d.ts +16 -0
- package/dist/cjs/index.js +24 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/jest.config.d.ts +3 -0
- package/dist/cjs/jest.config.js +9 -0
- package/dist/cjs/jest.config.js.map +1 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/jest.config.d.ts +3 -0
- package/dist/esm/jest.config.js +7 -0
- package/dist/esm/jest.config.js.map +1 -0
- package/package.json +41 -0
- package/readme.md +566 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { createContainer } from "./framework/implementation/Container";
|
|
3
|
+
import { Container } from "./framework/implementation/Container";
|
|
4
|
+
import { LoadAs } from "./framework/implementation/LoadAs";
|
|
5
|
+
import { Parameter } from "./framework/implementation/Parameter";
|
|
6
|
+
import { IContainer } from "./framework/interface/Container";
|
|
7
|
+
import { NotAClassDefinitionError, ParameterNotFoundError, DependencyNotFoundError, InstanceNotCreatedError, CircularDependencyError } from "./framework/errors/ContainerErrors";
|
|
8
|
+
import { Component, Inject } from "./framework/decorators/index";
|
|
9
|
+
export { IContainer };
|
|
10
|
+
export { LoadAs };
|
|
11
|
+
export { Parameter };
|
|
12
|
+
export { Container };
|
|
13
|
+
export { NotAClassDefinitionError, ParameterNotFoundError, DependencyNotFoundError, InstanceNotCreatedError, CircularDependencyError };
|
|
14
|
+
export { Component, Inject };
|
|
15
|
+
export { createContainer } from "./framework/implementation/Container";
|
|
16
|
+
export default createContainer;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createContainer = exports.Inject = exports.Component = exports.CircularDependencyError = exports.InstanceNotCreatedError = exports.DependencyNotFoundError = exports.ParameterNotFoundError = exports.NotAClassDefinitionError = exports.Container = exports.Parameter = exports.LoadAs = void 0;
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
const Container_1 = require("./framework/implementation/Container");
|
|
6
|
+
const Container_2 = require("./framework/implementation/Container");
|
|
7
|
+
Object.defineProperty(exports, "Container", { enumerable: true, get: function () { return Container_2.Container; } });
|
|
8
|
+
const LoadAs_1 = require("./framework/implementation/LoadAs");
|
|
9
|
+
Object.defineProperty(exports, "LoadAs", { enumerable: true, get: function () { return LoadAs_1.LoadAs; } });
|
|
10
|
+
const Parameter_1 = require("./framework/implementation/Parameter");
|
|
11
|
+
Object.defineProperty(exports, "Parameter", { enumerable: true, get: function () { return Parameter_1.Parameter; } });
|
|
12
|
+
const ContainerErrors_1 = require("./framework/errors/ContainerErrors");
|
|
13
|
+
Object.defineProperty(exports, "NotAClassDefinitionError", { enumerable: true, get: function () { return ContainerErrors_1.NotAClassDefinitionError; } });
|
|
14
|
+
Object.defineProperty(exports, "ParameterNotFoundError", { enumerable: true, get: function () { return ContainerErrors_1.ParameterNotFoundError; } });
|
|
15
|
+
Object.defineProperty(exports, "DependencyNotFoundError", { enumerable: true, get: function () { return ContainerErrors_1.DependencyNotFoundError; } });
|
|
16
|
+
Object.defineProperty(exports, "InstanceNotCreatedError", { enumerable: true, get: function () { return ContainerErrors_1.InstanceNotCreatedError; } });
|
|
17
|
+
Object.defineProperty(exports, "CircularDependencyError", { enumerable: true, get: function () { return ContainerErrors_1.CircularDependencyError; } });
|
|
18
|
+
const index_1 = require("./framework/decorators/index");
|
|
19
|
+
Object.defineProperty(exports, "Component", { enumerable: true, get: function () { return index_1.Component; } });
|
|
20
|
+
Object.defineProperty(exports, "Inject", { enumerable: true, get: function () { return index_1.Inject; } });
|
|
21
|
+
var Container_3 = require("./framework/implementation/Container");
|
|
22
|
+
Object.defineProperty(exports, "createContainer", { enumerable: true, get: function () { return Container_3.createContainer; } });
|
|
23
|
+
exports.default = Container_1.createContainer;
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":";;;AAAA,4BAA0B;AAC1B,oEAAsE;AACtE,oEAAgE;AAUvD,0FAVA,qBAAS,OAUA;AATlB,8DAA0D;AAOjD,uFAPA,eAAM,OAOA;AANf,oEAAgE;AAOvD,0FAPA,qBAAS,OAOA;AALlB,wEAAgL;AAOvK,yGAPA,0CAAwB,OAOA;AAAE,uGAPA,wCAAsB,OAOA;AAAE,wGAPA,yCAAuB,OAOA;AAAE,wGAPA,yCAAuB,OAOA;AAAE,wGAPA,yCAAuB,OAOA;AANpI,wDAAgE;AAOvD,0FAPA,iBAAS,OAOA;AAAE,uFAPA,cAAM,OAOA;AAC1B,kEAAsE;AAA7D,4GAAA,eAAe,OAAA;AACxB,kBAAe,2BAAe,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jestConfig = {
|
|
4
|
+
preset: 'ts-jest',
|
|
5
|
+
testEnvironment: 'node',
|
|
6
|
+
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
|
|
7
|
+
};
|
|
8
|
+
exports.default = jestConfig;
|
|
9
|
+
//# sourceMappingURL=jest.config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jest.config.js","sourceRoot":"","sources":["../../jest.config.ts"],"names":[],"mappings":";;AAEA,MAAM,UAAU,GAAyB;IACvC,MAAM,EAAE,SAAS;IACjB,eAAe,EAAE,MAAM;IACvB,sBAAsB,EAAE,CAAC,gBAAgB,EAAE,QAAQ,CAAC;CACrD,CAAC;AAEF,kBAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { createContainer } from "./framework/implementation/Container";
|
|
3
|
+
import { Container } from "./framework/implementation/Container";
|
|
4
|
+
import { LoadAs } from "./framework/implementation/LoadAs";
|
|
5
|
+
import { Parameter } from "./framework/implementation/Parameter";
|
|
6
|
+
import { IContainer } from "./framework/interface/Container";
|
|
7
|
+
import { NotAClassDefinitionError, ParameterNotFoundError, DependencyNotFoundError, InstanceNotCreatedError, CircularDependencyError } from "./framework/errors/ContainerErrors";
|
|
8
|
+
import { Component, Inject } from "./framework/decorators/index";
|
|
9
|
+
export { IContainer };
|
|
10
|
+
export { LoadAs };
|
|
11
|
+
export { Parameter };
|
|
12
|
+
export { Container };
|
|
13
|
+
export { NotAClassDefinitionError, ParameterNotFoundError, DependencyNotFoundError, InstanceNotCreatedError, CircularDependencyError };
|
|
14
|
+
export { Component, Inject };
|
|
15
|
+
export { createContainer } from "./framework/implementation/Container";
|
|
16
|
+
export default createContainer;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { createContainer } from "./framework/implementation/Container";
|
|
3
|
+
import { Container } from "./framework/implementation/Container";
|
|
4
|
+
import { LoadAs } from "./framework/implementation/LoadAs";
|
|
5
|
+
import { Parameter } from "./framework/implementation/Parameter";
|
|
6
|
+
import { NotAClassDefinitionError, ParameterNotFoundError, DependencyNotFoundError, InstanceNotCreatedError, CircularDependencyError } from "./framework/errors/ContainerErrors";
|
|
7
|
+
import { Component, Inject } from "./framework/decorators/index";
|
|
8
|
+
export { LoadAs };
|
|
9
|
+
export { Parameter };
|
|
10
|
+
export { Container };
|
|
11
|
+
export { NotAClassDefinitionError, ParameterNotFoundError, DependencyNotFoundError, InstanceNotCreatedError, CircularDependencyError };
|
|
12
|
+
export { Component, Inject };
|
|
13
|
+
export { createContainer } from "./framework/implementation/Container";
|
|
14
|
+
export default createContainer;
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAA;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAA;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAA;AAEhE,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAA;AAChL,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAGhE,OAAO,EAAE,MAAM,EAAE,CAAA;AACjB,OAAO,EAAE,SAAS,EAAE,CAAA;AACpB,OAAO,EAAE,SAAS,EAAE,CAAA;AACpB,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,CAAA;AACtI,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAA;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAA;AACtE,eAAe,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jest.config.js","sourceRoot":"","sources":["../../jest.config.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAAyB;IACvC,MAAM,EAAE,SAAS;IACjB,eAAe,EAAE,MAAM;IACvB,sBAAsB,EAAE,CAAC,gBAAgB,EAAE,QAAQ,CAAC;CACrD,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noego/ioc",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "A self contained IoC container for Node.js",
|
|
5
|
+
"main": "dist/cjs/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "jest --silent",
|
|
9
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
10
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
|
11
|
+
"build": "npm run build:cjs && npm run build:esm",
|
|
12
|
+
"typecheck": "tsc -p tsconfig.cjs.json --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"exports":{
|
|
15
|
+
".": {
|
|
16
|
+
"require": "./dist/cjs/index.js",
|
|
17
|
+
"import": "./dist/esm/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json",
|
|
20
|
+
"./readme.md": "./readme.md"
|
|
21
|
+
},
|
|
22
|
+
"author": "Shavauhn Gabay",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@types/node": "^20.10.4",
|
|
29
|
+
"@types/randomstring": "^1.1.11",
|
|
30
|
+
"@types/reflect-metadata": "^0.0.5",
|
|
31
|
+
"randomstring": "^1.3.0",
|
|
32
|
+
"reflect-metadata": "^0.2.2"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/jest": "^29.5.14",
|
|
36
|
+
"jest": "^29.7.0",
|
|
37
|
+
"ts-jest": "^29.3.2",
|
|
38
|
+
"ts-node": "^10.9.2",
|
|
39
|
+
"typescript": "^5.8.3"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
# @noego/ioc
|
|
2
|
+
|
|
3
|
+
A lightweight, flexible Inversion of Control (IoC) container for Node.js and TypeScript applications, providing support for multiple dependency lifetime scopes, parameter injection, and both class and function registration with full type safety.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
**Dual Module Support**: Compatible with CommonJS and ES Modules
|
|
8
|
+
**TypeScript & Typings**: Built in TypeScript with bundled declaration files
|
|
9
|
+
|
|
10
|
+
- **Multiple Lifetime Scopes**: Support for Singleton, Transient, and Scoped dependencies
|
|
11
|
+
- **Class & Function Registration**: Register both classes and functions as dependencies
|
|
12
|
+
- **Parameter Injection**: Inject parameter values at resolution time
|
|
13
|
+
- **Container Extension**: Create child containers that inherit parent registrations
|
|
14
|
+
- **Decorator Support**: Use @Component and @Inject decorators for clean, declarative DI
|
|
15
|
+
- **TypeScript Support**: Built with full TypeScript support for type safety
|
|
16
|
+
- **Async Resolution**: Support for asynchronous dependency resolution
|
|
17
|
+
- **Lightweight**: Small footprint with minimal external dependencies
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @noego/ioc
|
|
23
|
+
# or
|
|
24
|
+
yarn add @noego/ioc
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If you want to use decorators, also install reflect-metadata:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install reflect-metadata
|
|
31
|
+
# or
|
|
32
|
+
yarn add reflect-metadata
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
And configure TypeScript for decorator support in tsconfig.json:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"compilerOptions": {
|
|
40
|
+
"experimentalDecorators": true,
|
|
41
|
+
"emitDecoratorMetadata": true,
|
|
42
|
+
"module": "ESNext",
|
|
43
|
+
"moduleResolution": "node",
|
|
44
|
+
"target": "ESNext"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Quick Start
|
|
50
|
+
Follow these minimal steps to get @noego/ioc working in a TypeScript project:
|
|
51
|
+
|
|
52
|
+
1. Install packages:
|
|
53
|
+
```bash
|
|
54
|
+
npm install @noego/ioc reflect-metadata
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
2. Configure `tsconfig.json`:
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"compilerOptions": {
|
|
61
|
+
"experimentalDecorators": true,
|
|
62
|
+
"emitDecoratorMetadata": true,
|
|
63
|
+
"module": "ESNext",
|
|
64
|
+
"moduleResolution": "node",
|
|
65
|
+
"target": "ESNext"
|
|
66
|
+
},
|
|
67
|
+
"include": ["src"]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
3. Create an entry file (`index.ts`):
|
|
72
|
+
```typescript
|
|
73
|
+
import 'reflect-metadata';
|
|
74
|
+
import createContainer, { Component } from '@noego/ioc';
|
|
75
|
+
|
|
76
|
+
@Component()
|
|
77
|
+
class ExampleService {}
|
|
78
|
+
|
|
79
|
+
async function bootstrap() {
|
|
80
|
+
const container = createContainer();
|
|
81
|
+
container.registerClass(ExampleService);
|
|
82
|
+
const svc = await container.instance(ExampleService);
|
|
83
|
+
console.log('Service instance:', svc);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
bootstrap();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
4. Run the entry file:
|
|
90
|
+
```bash
|
|
91
|
+
npx ts-node index.ts
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Getting Started
|
|
95
|
+
|
|
96
|
+
### Using manual registration:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import createContainer from "@noego/ioc";
|
|
100
|
+
|
|
101
|
+
// Create a container
|
|
102
|
+
const container = createContainer();
|
|
103
|
+
|
|
104
|
+
// Register your dependencies
|
|
105
|
+
container.registerClass(Database);
|
|
106
|
+
container.registerClass(UserRepository, { param: [Database] });
|
|
107
|
+
|
|
108
|
+
// Resolve and use
|
|
109
|
+
async function main() {
|
|
110
|
+
const repo = await container.instance(UserRepository);
|
|
111
|
+
const users = repo.getUsers();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Using decorators:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import createContainer, { Component, Inject } from "@noego/ioc";
|
|
121
|
+
import 'reflect-metadata'; // Required when using decorators
|
|
122
|
+
|
|
123
|
+
@Component({ scope: LoadAs.Singleton })
|
|
124
|
+
class Database {
|
|
125
|
+
connect() {
|
|
126
|
+
return "Connected to DB";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@Component()
|
|
131
|
+
class UserRepository {
|
|
132
|
+
constructor(private db: Database) {}
|
|
133
|
+
|
|
134
|
+
getUsers() {
|
|
135
|
+
this.db.connect();
|
|
136
|
+
return ["User1", "User2"];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create container and register classes
|
|
141
|
+
const container = createContainer();
|
|
142
|
+
container.registerClass(Database);
|
|
143
|
+
container.registerClass(UserRepository);
|
|
144
|
+
|
|
145
|
+
// Resolve and use
|
|
146
|
+
async function main() {
|
|
147
|
+
const repo = await container.instance(UserRepository);
|
|
148
|
+
const users = repo.getUsers();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Usage
|
|
155
|
+
|
|
156
|
+
### Basic Usage
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import createContainer from "@noego/ioc";
|
|
160
|
+
|
|
161
|
+
// Create a container
|
|
162
|
+
const container = createContainer();
|
|
163
|
+
|
|
164
|
+
// Define classes
|
|
165
|
+
class Database {
|
|
166
|
+
connect() {
|
|
167
|
+
return "Connected to DB";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
class UserRepository {
|
|
172
|
+
constructor(private db: Database) {}
|
|
173
|
+
|
|
174
|
+
getUsers() {
|
|
175
|
+
this.db.connect();
|
|
176
|
+
return ["User1", "User2"];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
class UserService {
|
|
181
|
+
constructor(private repo: UserRepository) {}
|
|
182
|
+
|
|
183
|
+
getAllUsers() {
|
|
184
|
+
return this.repo.getUsers();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Register dependencies
|
|
189
|
+
container.registerClass(Database);
|
|
190
|
+
container.registerClass(UserRepository, { param: [Database] });
|
|
191
|
+
container.registerClass(UserService, { param: [UserRepository] });
|
|
192
|
+
|
|
193
|
+
// Resolve dependencies
|
|
194
|
+
async function run() {
|
|
195
|
+
const userService = await container.instance(UserService);
|
|
196
|
+
const users = userService.getAllUsers();
|
|
197
|
+
console.log(users); // ["User1", "User2"]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
run();
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Lifetime Scopes
|
|
204
|
+
|
|
205
|
+
The container supports three different lifetime scopes:
|
|
206
|
+
|
|
207
|
+
1. **Transient** (default): A new instance is created every time the dependency is resolved
|
|
208
|
+
2. **Singleton**: Only one instance is created and reused throughout the application
|
|
209
|
+
3. **Scoped**: A single instance is created per container scope
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { LoadAs } from "@noego/ioc";
|
|
213
|
+
|
|
214
|
+
// Register a singleton
|
|
215
|
+
container.registerClass(Database, { loadAs: LoadAs.Singleton });
|
|
216
|
+
|
|
217
|
+
// Register a scoped dependency
|
|
218
|
+
container.registerClass(UserRepository, {
|
|
219
|
+
param: [Database],
|
|
220
|
+
loadAs: LoadAs.Scoped
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Parameter Injection
|
|
225
|
+
|
|
226
|
+
You can inject parameter values at resolution time:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { Parameter } from "@noego/ioc";
|
|
230
|
+
|
|
231
|
+
class User {
|
|
232
|
+
constructor(public id: number, public name: string) {}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Create parameters
|
|
236
|
+
const USER_ID = Parameter.create();
|
|
237
|
+
const USER_NAME = Parameter.create();
|
|
238
|
+
|
|
239
|
+
// Register with parameters
|
|
240
|
+
container.registerClass(User, { param: [USER_ID, USER_NAME] });
|
|
241
|
+
|
|
242
|
+
// Resolve with parameter values
|
|
243
|
+
async function createUser() {
|
|
244
|
+
const user = await container.instance(User, [
|
|
245
|
+
USER_ID.value(1),
|
|
246
|
+
USER_NAME.value("John")
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
console.log(user.id, user.name); // 1, "John"
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Function Registration
|
|
254
|
+
|
|
255
|
+
You can also register functions as dependencies:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
function createLogger(prefix: string) {
|
|
259
|
+
return {
|
|
260
|
+
log: (message: string) => console.log(`${prefix}: ${message}`)
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const PREFIX = Parameter.create();
|
|
265
|
+
|
|
266
|
+
// Register function
|
|
267
|
+
container.registerFunction("logger", createLogger, {
|
|
268
|
+
param: [PREFIX]
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Resolve function
|
|
272
|
+
async function useLogger() {
|
|
273
|
+
const logger = await container.get("logger", [PREFIX.value("APP")]);
|
|
274
|
+
logger.log("Application started"); // "APP: Application started"
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Using Decorators
|
|
279
|
+
|
|
280
|
+
The container supports decorator-based dependency injection using `@Component` and `@Inject`.
|
|
281
|
+
|
|
282
|
+
#### Setup
|
|
283
|
+
|
|
284
|
+
First, ensure TypeScript is configured to support decorators:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
// tsconfig.json
|
|
288
|
+
{
|
|
289
|
+
"compilerOptions": {
|
|
290
|
+
"experimentalDecorators": true,
|
|
291
|
+
"emitDecoratorMetadata": true,
|
|
292
|
+
// other options...
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Also, import `reflect-metadata` once at your application's entry point:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// index.ts or main.ts
|
|
301
|
+
import 'reflect-metadata';
|
|
302
|
+
// ... rest of your code
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### Component Decorator
|
|
306
|
+
|
|
307
|
+
Use `@Component` to mark a class as a component with an optional scope:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { Component, LoadAs } from '@noego/ioc';
|
|
311
|
+
|
|
312
|
+
@Component() // Default is Transient
|
|
313
|
+
class UserService {
|
|
314
|
+
// ...
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@Component({ scope: LoadAs.Singleton })
|
|
318
|
+
class DatabaseService {
|
|
319
|
+
// ...
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
@Component({ scope: LoadAs.Scoped })
|
|
323
|
+
class RequestContext {
|
|
324
|
+
// ...
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### Inject Decorator
|
|
329
|
+
|
|
330
|
+
Use `@Inject` to specify tokens for interface dependencies or to override constructor parameter types:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { Component, Inject } from '@noego/ioc';
|
|
334
|
+
|
|
335
|
+
// Define an interface
|
|
336
|
+
interface ILogger {
|
|
337
|
+
log(message: string): void;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Create a token for the interface
|
|
341
|
+
const LoggerToken = Symbol('ILogger');
|
|
342
|
+
|
|
343
|
+
// Implement the interface
|
|
344
|
+
@Component({ scope: LoadAs.Singleton })
|
|
345
|
+
class ConsoleLogger implements ILogger {
|
|
346
|
+
log(message: string) {
|
|
347
|
+
console.log(message);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Use @Inject to specify which implementation to use
|
|
352
|
+
@Component()
|
|
353
|
+
class UserService {
|
|
354
|
+
constructor(
|
|
355
|
+
// Use @Inject with a token
|
|
356
|
+
@Inject(LoggerToken) private logger: ILogger,
|
|
357
|
+
|
|
358
|
+
// Regular parameter - resolved by type
|
|
359
|
+
private database: DatabaseService
|
|
360
|
+
) {}
|
|
361
|
+
|
|
362
|
+
createUser() {
|
|
363
|
+
this.logger.log('Creating user...');
|
|
364
|
+
// ...
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Register
|
|
369
|
+
const container = createContainer();
|
|
370
|
+
container.registerClass(DatabaseService);
|
|
371
|
+
container.registerClass(ConsoleLogger);
|
|
372
|
+
container.registerFunction(LoggerToken, () => container.instance(ConsoleLogger));
|
|
373
|
+
container.registerClass(UserService);
|
|
374
|
+
|
|
375
|
+
// Resolve
|
|
376
|
+
const service = await container.instance(UserService);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### Override Priority
|
|
380
|
+
|
|
381
|
+
Manual registration options take precedence over decorators:
|
|
382
|
+
|
|
383
|
+
1. Manually defined parameters in `registerClass({ param: [...] })` override constructor parameter types and `@Inject` annotations.
|
|
384
|
+
2. Manually defined scope in `registerClass({ loadAs: ... })` overrides `@Component({ scope: ... })`.
|
|
385
|
+
|
|
386
|
+
This allows you to change behavior at registration time without modifying the decorated class.
|
|
387
|
+
|
|
388
|
+
### Extending Containers
|
|
389
|
+
|
|
390
|
+
You can create a child container that inherits all the registrations from the parent but allows overriding:
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// Create parent container
|
|
394
|
+
const parentContainer = createContainer();
|
|
395
|
+
parentContainer.registerClass(Database);
|
|
396
|
+
|
|
397
|
+
// Create child container
|
|
398
|
+
const childContainer = parentContainer.extend();
|
|
399
|
+
|
|
400
|
+
// Override in child container
|
|
401
|
+
childContainer.registerClass(Database, { /* different configuration */ });
|
|
402
|
+
|
|
403
|
+
// Parent container still uses the original registration
|
|
404
|
+
// Child container uses the new registration
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## API Reference
|
|
408
|
+
|
|
409
|
+
### Container
|
|
410
|
+
|
|
411
|
+
- `createContainer()`: Creates a new IoC container
|
|
412
|
+
- `registerClass<T>(classDefinition, options?)`: Register a class
|
|
413
|
+
- `registerFunction(label, function, options?)`: Register a function
|
|
414
|
+
- `instance<T>(classDefinition, params?)`: Resolve a class instance
|
|
415
|
+
- `get<T>(label, params?)`: Resolve a dependency by key
|
|
416
|
+
- `extend()`: Create a child container
|
|
417
|
+
|
|
418
|
+
### Decorators
|
|
419
|
+
|
|
420
|
+
- `@Component(options?)`: Mark a class as container-managed
|
|
421
|
+
- `@Inject(token)`: Specify a token for a constructor parameter
|
|
422
|
+
|
|
423
|
+
### Options
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
interface ContainerOptions {
|
|
427
|
+
param?: any[]; // Dependencies or parameters
|
|
428
|
+
loadAs?: LoadAs; // Lifetime scope
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### LoadAs Enum
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
enum LoadAs {
|
|
436
|
+
Singleton, // Single instance throughout application
|
|
437
|
+
Scoped, // Single instance per container scope
|
|
438
|
+
Transient // New instance each time
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Parameter
|
|
443
|
+
|
|
444
|
+
- `Parameter.create()`: Create a new parameter
|
|
445
|
+
- `parameter.value(value)`: Create a parameter value
|
|
446
|
+
|
|
447
|
+
### Component Options
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
interface ComponentOptions {
|
|
451
|
+
scope?: LoadAs; // Lifetime scope
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Real-World Use Cases
|
|
456
|
+
|
|
457
|
+
### Express Application
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
import express from 'express';
|
|
461
|
+
import createContainer, { LoadAs } from '@noego/ioc';
|
|
462
|
+
|
|
463
|
+
// Create services
|
|
464
|
+
class ConfigService {
|
|
465
|
+
getConfig() {
|
|
466
|
+
return { port: 3000 };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
class DatabaseService {
|
|
471
|
+
constructor(private config: ConfigService) {}
|
|
472
|
+
|
|
473
|
+
connect() {
|
|
474
|
+
console.log('Connected to database');
|
|
475
|
+
return {};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
class UserRepository {
|
|
480
|
+
constructor(private db: DatabaseService) {}
|
|
481
|
+
|
|
482
|
+
findAll() {
|
|
483
|
+
return [{ id: 1, name: 'User 1' }];
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
class UserController {
|
|
488
|
+
constructor(private repo: UserRepository) {}
|
|
489
|
+
|
|
490
|
+
getUsers(req, res) {
|
|
491
|
+
const users = this.repo.findAll();
|
|
492
|
+
res.json(users);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Setup container
|
|
497
|
+
const container = createContainer();
|
|
498
|
+
container.registerClass(ConfigService, { loadAs: LoadAs.Singleton });
|
|
499
|
+
container.registerClass(DatabaseService, { param: [ConfigService], loadAs: LoadAs.Singleton });
|
|
500
|
+
container.registerClass(UserRepository, { param: [DatabaseService] });
|
|
501
|
+
container.registerClass(UserController, { param: [UserRepository] });
|
|
502
|
+
|
|
503
|
+
// Create express app
|
|
504
|
+
const app = express();
|
|
505
|
+
|
|
506
|
+
// Setup routes using the container
|
|
507
|
+
app.get('/users', async (req, res) => {
|
|
508
|
+
const controller = await container.instance(UserController);
|
|
509
|
+
controller.getUsers(req, res);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Start server
|
|
513
|
+
async function bootstrap() {
|
|
514
|
+
const config = await container.instance(ConfigService);
|
|
515
|
+
app.listen(config.getConfig().port, () => {
|
|
516
|
+
console.log(`Server running on port ${config.getConfig().port}`);
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
bootstrap();
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Running Tests
|
|
524
|
+
|
|
525
|
+
The project uses Jest for testing. To run tests:
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
npm test
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## License
|
|
532
|
+
|
|
533
|
+
ISC
|
|
534
|
+
|
|
535
|
+
## Contributing
|
|
536
|
+
|
|
537
|
+
Contributions are welcome! Here's how you can contribute to this project:
|
|
538
|
+
|
|
539
|
+
1. Fork the repository
|
|
540
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
541
|
+
3. Install dependencies (`npm install`)
|
|
542
|
+
4. Make your changes
|
|
543
|
+
5. Run tests to ensure everything works (`npm test`)
|
|
544
|
+
6. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
545
|
+
7. Push to the branch (`git push origin feature/amazing-feature`)
|
|
546
|
+
8. Open a Pull Request
|
|
547
|
+
|
|
548
|
+
### Development Setup
|
|
549
|
+
|
|
550
|
+
1. Clone the repository:
|
|
551
|
+
```bash
|
|
552
|
+
git clone <repository-url>
|
|
553
|
+
cd ioc
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
2. Install dependencies:
|
|
557
|
+
```bash
|
|
558
|
+
npm install
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
3. Run tests:
|
|
562
|
+
```bash
|
|
563
|
+
npm test
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Please make sure to update tests as appropriate and follow the existing code style.
|