@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/src/decorators.ts CHANGED
@@ -1,4 +1,15 @@
1
- import { container, type Container, Token, type Constructor, type InjectableOptions } from "./container";
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
- return function (_value: undefined, context: ClassFieldDecoratorContext): void {
11
- const cache = new WeakMap<object, T>();
12
- const propertyKey = String(context.name);
55
+ const cache = new WeakMap<object, T>();
13
56
 
14
- context.addInitializer(function (this: unknown) {
15
- Object.defineProperty(this, context.name, {
16
- get(this: object): T {
17
- if (!cache.has(this)) {
18
- let resolved: T;
19
- try {
20
- resolved = container.resolve(dep as Constructor<T>);
21
- } catch (err) {
22
- throw new Error(
23
- `[Inject] Failed to resolve "${
24
- dep instanceof Token
25
- ? dep.toString()
26
- : (dep as Constructor).name
27
- }" in "${(this as { constructor: { name: string } }).constructor.name}.${propertyKey}": ${(err as Error).message}`,
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
- cache.set(this, resolved);
31
- }
32
- return cache.get(this) as T;
86
+ },
33
87
  },
34
-
35
- set(_value: unknown): void {
36
- if (process.env.NODE_ENV !== "production") {
37
- console.warn(
38
- `[Inject] "${propertyKey}" is managed by the DI container and cannot be assigned directly.`,
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 function (_value: undefined, context: ClassFieldDecoratorContext): void {
52
- context.addInitializer(function (this: unknown) {
53
- Object.defineProperty(this, context.name, {
54
- get() {
55
- return container;
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
- enumerable: true,
58
- configurable: true,
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
- useService,
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,