@praxisjs/di 0.2.3 → 1.0.0
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/CHANGELOG.md +49 -0
- package/dist/__tests__/container.test.js +24 -3
- package/dist/__tests__/container.test.js.map +1 -1
- package/dist/__tests__/decorators.test.js +247 -100
- package/dist/__tests__/decorators.test.js.map +1 -1
- package/dist/container.d.ts +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +1 -3
- package/dist/container.js.map +1 -1
- package/dist/decorators.d.ts +16 -4
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +79 -45
- package/dist/decorators.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/__tests__/container.test.ts +20 -0
- package/src/__tests__/decorators.test.ts +153 -116
- package/src/container.ts +2 -1
- package/src/decorators.ts +94 -55
- package/src/index.ts +2 -3
package/src/decorators.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createFieldDecorator, type FieldBinding } from "@praxisjs/decorators";
|
|
2
|
+
|
|
3
|
+
import { container, type Container, Token, type Constructor, type InjectableOptions } from "./container";
|
|
4
|
+
|
|
5
|
+
// Maps instances to their scoped child containers (set by @Scope).
|
|
6
|
+
const scopedContainers = new WeakMap<object, Container>();
|
|
7
|
+
|
|
8
|
+
function resolveFrom(instance: object) {
|
|
9
|
+
return scopedContainers.get(instance) ?? container;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// ── @Injectable ───────────────────────────────────────────────────────────────
|
|
2
13
|
|
|
3
14
|
export function Injectable(options: InjectableOptions = {}) {
|
|
4
15
|
return function (value: Constructor, _context: ClassDecoratorContext): void {
|
|
@@ -6,67 +17,95 @@ export function Injectable(options: InjectableOptions = {}) {
|
|
|
6
17
|
};
|
|
7
18
|
}
|
|
8
19
|
|
|
20
|
+
// ── @Scope ────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a child DI container scoped to each instance of the decorated class.
|
|
24
|
+
* Services registered in the configure callback are available within the scope,
|
|
25
|
+
* and @Inject / @InjectContainer will resolve from it automatically.
|
|
26
|
+
*
|
|
27
|
+
* @Scope((c) => {
|
|
28
|
+
* c.register(UserRepository);
|
|
29
|
+
* c.registerValue(AuthToken, 'abc');
|
|
30
|
+
* })
|
|
31
|
+
* class UserModule extends StatefulComponent {
|
|
32
|
+
* @Inject(UserRepository) repo!: UserRepository;
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
export function Scope(configure?: (c: Container) => void) {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
return function(cls: new (...args: any[]) => any, _ctx: ClassDecoratorContext): any {
|
|
38
|
+
|
|
39
|
+
return class extends cls {
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
constructor(...args: any[]) {
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
43
|
+
super(...args);
|
|
44
|
+
const child = container.createChild();
|
|
45
|
+
configure?.(child);
|
|
46
|
+
scopedContainers.set(this as object, child);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── @Inject ───────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
9
54
|
export function Inject<T>(dep: Constructor<T> | Token<T>) {
|
|
10
|
-
|
|
11
|
-
const cache = new WeakMap<object, T>();
|
|
12
|
-
const propertyKey = String(context.name);
|
|
55
|
+
const cache = new WeakMap<object, T>();
|
|
13
56
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
57
|
+
return createFieldDecorator({
|
|
58
|
+
bind(_instance, name, _initialValue): FieldBinding {
|
|
59
|
+
return {
|
|
60
|
+
descriptor: {
|
|
61
|
+
get(this: object): T {
|
|
62
|
+
if (!cache.has(this)) {
|
|
63
|
+
const c = resolveFrom(this);
|
|
64
|
+
let resolved: T;
|
|
65
|
+
try {
|
|
66
|
+
resolved = c.resolve(dep as Constructor<T>);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`[Inject] Failed to resolve "${
|
|
70
|
+
dep instanceof Token
|
|
71
|
+
? dep.toString()
|
|
72
|
+
: (dep as Constructor).name
|
|
73
|
+
}" in "${(this as { constructor: { name: string } }).constructor.name}.${name}": ${(err as Error).message}`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
cache.set(this, resolved);
|
|
77
|
+
}
|
|
78
|
+
return cache.get(this) as T;
|
|
79
|
+
},
|
|
80
|
+
set(_value: unknown): void {
|
|
81
|
+
if (process.env.NODE_ENV !== "production") {
|
|
82
|
+
console.warn(
|
|
83
|
+
`[Inject] "${name}" is managed by the DI container and cannot be assigned directly.`,
|
|
28
84
|
);
|
|
29
85
|
}
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
return cache.get(this) as T;
|
|
86
|
+
},
|
|
33
87
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
enumerable: true,
|
|
44
|
-
configurable: true,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
};
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
// DI decorators work on any class, not just StatefulComponent
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
}) as unknown as (_value: undefined, context: ClassFieldDecoratorContext<any>) => void;
|
|
48
93
|
}
|
|
49
94
|
|
|
95
|
+
// ── @InjectContainer ──────────────────────────────────────────────────────────
|
|
96
|
+
|
|
50
97
|
export function InjectContainer() {
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
98
|
+
return createFieldDecorator({
|
|
99
|
+
bind(_instance, _name, _initialValue): FieldBinding {
|
|
100
|
+
return {
|
|
101
|
+
descriptor: {
|
|
102
|
+
get(this: object) {
|
|
103
|
+
return resolveFrom(this);
|
|
104
|
+
},
|
|
56
105
|
},
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function useService<T>(dep: Constructor<T> | Token<T>): T {
|
|
65
|
-
return container.resolve(dep as Constructor<T>);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function createScope(configure?: (c: Container) => void): Container {
|
|
69
|
-
const child = container.createChild();
|
|
70
|
-
configure?.(child);
|
|
71
|
-
return child;
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
// DI decorators work on any class, not just StatefulComponent
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
|
+
}) as unknown as (_value: undefined, context: ClassFieldDecoratorContext<any>) => void;
|
|
72
111
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,12 +4,11 @@ export {
|
|
|
4
4
|
Injectable,
|
|
5
5
|
Inject,
|
|
6
6
|
InjectContainer,
|
|
7
|
-
|
|
8
|
-
createScope,
|
|
7
|
+
Scope,
|
|
9
8
|
} from "./decorators";
|
|
10
9
|
export { Container, container, Token, token } from "./container";
|
|
11
10
|
export type {
|
|
12
|
-
Scope,
|
|
11
|
+
Scope as ScopeType,
|
|
13
12
|
InjectableOptions,
|
|
14
13
|
Constructor,
|
|
15
14
|
ServiceDescriptor,
|