decorator-dependency-injection 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -1,11 +1,15 @@
1
1
  # Decorator Dependency Injection
2
- [![npm version](https://badge.fury.io/js/decorator-dependency-injection.svg)](http://badge.fury.io/js/decorator-dependency-injection)
3
- [![Build Status](https://github.com/mallocator/decorator-dependency-injection/actions/workflows/node.js.yml/badge.svg)](https://github.com/mallocator/decorator-dependency-injection/actions/workflows/node.js.yml)
4
2
 
3
+ [![npm version](https://badge.fury.io/js/decorator-dependency-injection.svg)](http://badge.fury.io/js/decorator-dependency-injection)
4
+ [![Build Status](https://github.com/mallocator/decorator-dependency-injection/actions/workflows/release.yml/badge.svg)](https://github.com/mallocator/decorator-dependency-injection/actions/workflows/release.yml)
5
+ [![Coverage](https://img.shields.io/badge/coverage-93%25-brightgreen)](https://github.com/mallocator/decorator-dependency-injection)
5
6
 
6
7
  ## Description
7
8
 
8
- With [TC39](https://github.com/tc39/proposal-decorators) reaching stage 3 on the decorators proposal, it's time to start thinking about how we can use them in our projects. One of the most common patterns in JavaScript is dependency injection. This pattern is used to make our code more testable and maintainable. This library provides simple decorators to help you inject dependencies into your classes and mock them for testing.
9
+ With the [TC39 proposal-decorators](https://github.com/tc39/proposal-decorators) reaching stage 3, it's time to start
10
+ thinking about how we can use them in our projects. One of the most common patterns in JavaScript is dependency
11
+ injection. This pattern is used to make our code more testable and maintainable. This library provides simple decorators
12
+ to help you inject dependencies into your classes and mock them for testing.
9
13
 
10
14
  ## Installation
11
15
 
@@ -13,21 +17,26 @@ With [TC39](https://github.com/tc39/proposal-decorators) reaching stage 3 on the
13
17
  npm install decorator-dependency-injection
14
18
  ```
15
19
 
16
- Until we reach stage 4, you will need to enable the decorators proposal in your project. You can do this by adding the following babel transpiler options to your `.babelrc` file.
20
+ Until we reach stage 4, you will need to enable the decorators proposal in your project. You can do this by adding the
21
+ following babel transpiler options to your `.babelrc` file.
17
22
 
18
23
  ```json
19
24
  {
20
- "plugins": ["@babel/plugin-proposal-decorators"]
25
+ "plugins": [
26
+ "@babel/plugin-proposal-decorators"
27
+ ]
21
28
  }
22
29
  ```
23
30
 
24
- To run your project with decorators enabled you will need to use the babel transpiler. You can do this by running the following command in your project root.
31
+ To run your project with decorators enabled, you will need to use the babel transpiler. You can do this by running the
32
+ following command in your project root.
25
33
 
26
34
  ```bash
27
35
  npx babel-node index.js
28
36
  ```
29
37
 
30
- Finally, for running tests with decorators enabled you will need to use the babel-jest package. You can do this by adding the following configuration to your `package.json` file.
38
+ Finally, for running tests with decorators enabled, you will need to use the babel-jest package. You can do this by
39
+ adding the following configuration to your `package.json` file.
31
40
 
32
41
  ```json
33
42
  {
@@ -43,20 +52,21 @@ Other testing frameworks may require a different configuration.
43
52
 
44
53
  For a full example of how to set up a project with decorators, see this project's ```package.json``` file.
45
54
 
46
-
47
55
  ## Usage
48
56
 
49
- There are 2 ways of specifying injectable dependencies: ```@Singleton``` and ```@Factory```:
57
+ There are two ways of specifying injectable dependencies: ```@Singleton``` and ```@Factory```:
50
58
 
51
59
  ### Singleton
52
60
 
53
- The ```@Singleton``` decorator is used to inject a single instance of a dependency into a class. This is useful when you want to share the same instance of a class across multiple classes.
61
+ The ```@Singleton``` decorator is used to inject a single instance of a dependency into a class. This is useful when you
62
+ want to share the same instance of a class across multiple classes.
54
63
 
55
64
  ```javascript
56
- import { Singleton } from 'decorator-dependency-injection';
65
+ import {Singleton, Inject} from 'decorator-dependency-injection';
57
66
 
58
- @Singleton
59
- class Dependency {}
67
+ @Singleton()
68
+ class Dependency {
69
+ }
60
70
 
61
71
  class Consumer {
62
72
  @Inject(Dependency) dependency // creates an instance only once
@@ -65,25 +75,48 @@ class Consumer {
65
75
 
66
76
  ### Factory
67
77
 
68
- The ```@Factory``` decorator is used to inject a new instance of a dependency into a class each time it is requested. This is useful when you want to create a new instance of a class each time it is injected.
78
+ The ```@Factory``` decorator is used to inject a new instance of a dependency into a class each time it is requested.
79
+ This is useful when you want to create a new instance of a class each time it is injected.
69
80
 
70
81
  ```javascript
71
- import { Factory } from 'decorator-dependency-injection';
82
+ import {Factory, Inject} from 'decorator-dependency-injection';
72
83
 
73
- @Factory
74
- class Dependency {}
84
+ @Factory()
85
+ class Dependency {
86
+ }
75
87
 
76
88
  class Consumer {
77
89
  @Inject(Dependency) dependency // creates a new instance each time a new Consumer is created
78
90
  }
79
91
  ```
80
92
 
93
+ ### InjectLazy
94
+
95
+ ```@Inject``` annotated properties are evaluated during instance initialization. That means that all properties should
96
+ be accessible in the constructor. That also means that we're creating an instance no matter if you access the property
97
+ or not. If you want to only create an instance when you access the property, you can use the ```@InjectLazy```
98
+ decorator. This will create the instance only when the property is accessed for the first time. Note that this also
99
+ works from the constructor, same as the regular ```@Inject```.
100
+
101
+ ```javascript
102
+ import {Singleton, InjectLazy} from 'decorator-dependency-injection';
103
+
104
+ @Singleton()
105
+ class Dependency {
106
+ }
107
+
108
+ class Consumer {
109
+ @InjectLazy(Dependency) dependency // creates an instance only when the property is accessed
110
+ }
111
+ ```
112
+
81
113
  ## Passing parameters to a dependency
82
114
 
83
- You can pass parameters to a dependency by using the ```@Inject``` decorator with a function that returns the dependency.
115
+ You can pass parameters to a dependency by using the ```@Inject``` decorator with a function that returns the
116
+ dependency.
84
117
 
85
118
  ```javascript
86
- import { Factory, Inject } from 'decorator-dependency-injection';
119
+ import {Factory, Inject} from 'decorator-dependency-injection';
87
120
 
88
121
  @Factory
89
122
  class Dependency {
@@ -98,16 +131,17 @@ class Consumer {
98
131
  }
99
132
  ```
100
133
 
101
- While this is most useful for Factory dependencies, it can also be used with Singleton dependencies. However, parameters will only be passed to the dependency the first time it is created.
134
+ While this is most useful for Factory dependencies, it can also be used with Singleton dependencies. However, parameters
135
+ will only be passed to the dependency the first time it is created.
102
136
 
103
137
  ## Mocking dependencies for testing
104
138
 
105
139
  You can mock dependencies by using the ```@Mock``` decorator with a function that returns the mock dependency.
106
140
 
107
141
  ```javascript
108
- import { Factory, Inject, Mock } from 'decorator-dependency-injection'
142
+ import {Factory, Inject, Mock, resetMock} from 'decorator-dependency-injection'
109
143
 
110
- @Factory
144
+ @Factory()
111
145
  class Dependency {
112
146
  method() {
113
147
  return 'real'
@@ -138,12 +172,35 @@ resetMock(Dependency)
138
172
  const consumer = new Consumer() // prints 'real'
139
173
  ```
140
174
 
141
- You can also use the ```@Mock``` decorator as a proxy instead of a full mock. Any method calls not implemented in the mock will be passed to the real dependency.
175
+ ### Resetting Mocks
176
+
177
+ The `resetMock` utility function allows you to remove any active mock for a dependency and restore the original
178
+ implementation. This is useful for cleaning up after tests or switching between real and mock dependencies.
142
179
 
143
180
  ```javascript
144
- import { Factory, Inject, Mock } from 'decorator-dependency-injection'
181
+ import {resetMock, resetMocks} from 'decorator-dependency-injection';
145
182
 
146
- @Factory
183
+ resetMock(Dependency); // Restores the original Dependency implementation
184
+ resetMocks(); // Restores all mocked dependencies
185
+ ```
186
+
187
+ ### Clearing the Container
188
+
189
+ For complete test isolation, you can clear all registered instances from the container:
190
+
191
+ ```javascript
192
+ import {clearContainer} from 'decorator-dependency-injection';
193
+
194
+ clearContainer(); // Removes all registered singletons, factories, and mocks
195
+ ```
196
+
197
+ You can also use the ```@Mock``` decorator as a proxy instead of a full mock. Any method calls not implemented in the
198
+ mock will be passed to the real dependency.
199
+
200
+ ```javascript
201
+ import {Factory, Inject, Mock, resetMock} from 'decorator-dependency-injection'
202
+
203
+ @Factory()
147
204
  class Dependency {
148
205
  method() {
149
206
  return 'real'
@@ -180,6 +237,45 @@ const consumer = new Consumer() // prints 'real other'
180
237
 
181
238
  For more examples, see the tests in the ```test``` directory.
182
239
 
240
+ ## Advanced Usage
241
+
242
+ ### Using Isolated Containers
243
+
244
+ For advanced scenarios like parallel test execution or module isolation, you can create separate containers:
245
+
246
+ ```javascript
247
+ import {Container} from 'decorator-dependency-injection';
248
+
249
+ const container1 = new Container();
250
+ const container2 = new Container();
251
+
252
+ class MyService {}
253
+
254
+ // Register the same class in different containers
255
+ container1.registerSingleton(MyService);
256
+ container2.registerSingleton(MyService);
257
+
258
+ // Each container maintains its own singleton instance
259
+ const ctx1 = container1.getContext(MyService);
260
+ const ctx2 = container2.getContext(MyService);
261
+
262
+ const instance1 = container1.getInstance(ctx1, []);
263
+ const instance2 = container2.getInstance(ctx2, []);
264
+
265
+ console.log(instance1 === instance2); // false - different containers
266
+ ```
267
+
268
+ ### Accessing the Default Container
269
+
270
+ You can access the default global container for programmatic registration:
271
+
272
+ ```javascript
273
+ import {getContainer} from 'decorator-dependency-injection';
274
+
275
+ const container = getContainer();
276
+ console.log(container.has(MyService)); // Check if a class is registered
277
+ ```
278
+
183
279
  ## Running the tests
184
280
 
185
281
  To run the tests, run the following command in the project root.
@@ -192,4 +288,6 @@ npm test
192
288
 
193
289
  - 1.0.0 - Initial release
194
290
  - 1.0.1 - Automated release with GitHub Actions
195
- - 1.0.2 - Added proxy option to @Mock decorator
291
+ - 1.0.2 - Added proxy option to @Mock decorator
292
+ - 1.0.3 - Added @InjectLazy decorator
293
+ - 1.0.4 - Added Container abstraction, clearContainer(), TypeScript definitions, improved proxy support
@@ -0,0 +1,73 @@
1
+ import { defineConfig } from "eslint/config";
2
+ import js from "@eslint/js";
3
+ import babelParser from "@babel/eslint-parser";
4
+
5
+ export default defineConfig([
6
+ // Recommended base config
7
+ js.configs.recommended,
8
+
9
+ // Global ignores
10
+ {
11
+ ignores: ["node_modules/**", "coverage/**", "docs/**", ".history/**"]
12
+ },
13
+
14
+ // Source files configuration
15
+ {
16
+ name: "source-files",
17
+ files: ["index.js", "src/**/*.js"],
18
+ languageOptions: {
19
+ ecmaVersion: 2022,
20
+ sourceType: "module",
21
+ parser: babelParser,
22
+ parserOptions: {
23
+ requireConfigFile: true,
24
+ babelOptions: {
25
+ configFile: "./babel.config.json"
26
+ }
27
+ },
28
+ globals: {
29
+ console: "readonly",
30
+ process: "readonly"
31
+ }
32
+ },
33
+ rules: {
34
+ "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
35
+ "prefer-const": "error",
36
+ "no-var": "error",
37
+ "eqeqeq": ["error", "always"],
38
+ "no-throw-literal": "error"
39
+ }
40
+ },
41
+
42
+ // Test files with relaxed rules and Jest globals
43
+ {
44
+ name: "test-files",
45
+ files: ["test/**/*.js"],
46
+ languageOptions: {
47
+ ecmaVersion: 2022,
48
+ sourceType: "module",
49
+ parser: babelParser,
50
+ parserOptions: {
51
+ requireConfigFile: true,
52
+ babelOptions: {
53
+ configFile: "./babel.config.json"
54
+ }
55
+ },
56
+ globals: {
57
+ console: "readonly",
58
+ process: "readonly",
59
+ describe: "readonly",
60
+ it: "readonly",
61
+ expect: "readonly",
62
+ beforeEach: "readonly",
63
+ afterEach: "readonly",
64
+ jest: "readonly",
65
+ fail: "readonly"
66
+ }
67
+ },
68
+ rules: {
69
+ // In tests, decorated classes are used by the decorator system, not directly
70
+ "no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }]
71
+ }
72
+ }
73
+ ]);
package/index.d.ts ADDED
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Type definitions for decorator-dependency-injection
3
+ */
4
+
5
+ /**
6
+ * Context for registered instances in the container
7
+ */
8
+ export interface InstanceContext {
9
+ /** The type of registration */
10
+ type: 'singleton' | 'factory'
11
+ /** The current class constructor (may be a mock) */
12
+ clazz: new (...args: any[]) => any
13
+ /** The original class constructor if mocked */
14
+ originalClazz?: new (...args: any[]) => any
15
+ /** The cached singleton instance */
16
+ instance?: any
17
+ /** Whether to use proxy mocking */
18
+ proxy?: boolean
19
+ }
20
+
21
+ /**
22
+ * A dependency injection container that manages singleton and factory instances.
23
+ */
24
+ export declare class Container {
25
+ /**
26
+ * Register a class as a singleton.
27
+ */
28
+ registerSingleton(clazz: new (...args: any[]) => any, name?: string): void
29
+
30
+ /**
31
+ * Register a class as a factory.
32
+ */
33
+ registerFactory(clazz: new (...args: any[]) => any, name?: string): void
34
+
35
+ /**
36
+ * Get the context for a given class or name.
37
+ */
38
+ getContext(clazzOrName: string | (new (...args: any[]) => any)): InstanceContext
39
+
40
+ /**
41
+ * Check if a class or name is registered.
42
+ */
43
+ has(clazzOrName: string | (new (...args: any[]) => any)): boolean
44
+
45
+ /**
46
+ * Get or create an instance based on the context.
47
+ */
48
+ getInstance(instanceContext: InstanceContext, params: any[]): any
49
+
50
+ /**
51
+ * Register a mock for an existing class.
52
+ */
53
+ registerMock(
54
+ targetClazzOrName: string | (new (...args: any[]) => any),
55
+ mockClazz: new (...args: any[]) => any,
56
+ useProxy?: boolean
57
+ ): void
58
+
59
+ /**
60
+ * Reset a specific mock to its original class.
61
+ */
62
+ resetMock(clazzOrName: string | (new (...args: any[]) => any)): void
63
+
64
+ /**
65
+ * Reset all mocks to their original classes.
66
+ */
67
+ resetAllMocks(): void
68
+
69
+ /**
70
+ * Clear all registered instances and mocks.
71
+ */
72
+ clear(): void
73
+ }
74
+
75
+ /**
76
+ * Register a class as a singleton.
77
+ * @param name Optional name to register the singleton under
78
+ */
79
+ export declare function Singleton(name?: string): ClassDecorator
80
+
81
+ /**
82
+ * Register a class as a factory.
83
+ * @param name Optional name to register the factory under
84
+ */
85
+ export declare function Factory(name?: string): ClassDecorator
86
+
87
+ /**
88
+ * Inject a singleton or factory instance into a class field.
89
+ * @param clazzOrName The class or name to inject
90
+ * @param params Optional parameters to pass to the constructor
91
+ */
92
+ export declare function Inject<T>(
93
+ clazzOrName: string | (new (...args: any[]) => T),
94
+ ...params: any[]
95
+ ): PropertyDecorator
96
+
97
+ /**
98
+ * Inject a singleton or factory instance lazily into a class field.
99
+ * The instance is created on first access.
100
+ * @param clazzOrName The class or name to inject
101
+ * @param params Optional parameters to pass to the constructor
102
+ */
103
+ export declare function InjectLazy<T>(
104
+ clazzOrName: string | (new (...args: any[]) => T),
105
+ ...params: any[]
106
+ ): PropertyDecorator
107
+
108
+ /**
109
+ * Mark a class as a mock for another class.
110
+ * @param mockedClazzOrName The class or name to mock
111
+ * @param proxy If true, unmocked methods delegate to the original
112
+ */
113
+ export declare function Mock(
114
+ mockedClazzOrName: string | (new (...args: any[]) => any),
115
+ proxy?: boolean
116
+ ): ClassDecorator
117
+
118
+ /**
119
+ * Reset all mocks to their original classes.
120
+ */
121
+ export declare function resetMocks(): void
122
+
123
+ /**
124
+ * Reset a specific mock to its original class.
125
+ * @param clazzOrName The class or name to reset
126
+ */
127
+ export declare function resetMock(clazzOrName: string | (new (...args: any[]) => any)): void
128
+
129
+ /**
130
+ * Clear all registered instances and mocks from the container.
131
+ */
132
+ export declare function clearContainer(): void
133
+
134
+ /**
135
+ * Get the default container instance.
136
+ */
137
+ export declare function getContainer(): Container
138
+
139
+ /**
140
+ * Create a proxy that delegates to the mock first, then falls back to the original.
141
+ * This is an internal utility but exported for advanced use cases.
142
+ *
143
+ * @param mock The mock instance
144
+ * @param original The original instance to fall back to
145
+ */
146
+ export declare function createProxy<T extends object>(mock: T, original: T): T