@praxisjs/di 0.2.2 → 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 +56 -0
- package/dist/__tests__/container.test.js +70 -1
- package/dist/__tests__/container.test.js.map +1 -1
- package/dist/__tests__/decorators.test.js +247 -85
- 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 +76 -0
- package/src/__tests__/decorators.test.ts +157 -102
- package/src/container.ts +2 -1
- package/src/decorators.ts +94 -55
- package/src/index.ts +2 -3
|
@@ -1,173 +1,228 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { describe, it, expect, vi } from "vitest";
|
|
3
3
|
|
|
4
|
-
import { Container, token } from "../container";
|
|
4
|
+
import { Container, container, token } from "../container";
|
|
5
5
|
import {
|
|
6
6
|
Injectable,
|
|
7
7
|
Inject,
|
|
8
8
|
InjectContainer,
|
|
9
|
-
|
|
10
|
-
createScope,
|
|
9
|
+
Scope,
|
|
11
10
|
} from "../decorators";
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
12
|
+
function makeFieldCtx(name: string) {
|
|
13
|
+
const initializers: Array<(this: unknown) => void> = [];
|
|
14
|
+
return {
|
|
15
|
+
ctx: {
|
|
16
|
+
name,
|
|
17
|
+
kind: "field" as const,
|
|
18
|
+
addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
|
|
19
|
+
} as ClassFieldDecoratorContext,
|
|
20
|
+
run(instance: unknown) { initializers.forEach((fn) => { fn.call(instance); }); },
|
|
21
|
+
};
|
|
16
22
|
}
|
|
17
23
|
|
|
18
|
-
// ── Injectable
|
|
24
|
+
// ── @Injectable ───────────────────────────────────────────────────────────────
|
|
19
25
|
|
|
20
26
|
describe("Injectable", () => {
|
|
21
27
|
it("registers the class in the global container", () => {
|
|
22
|
-
// We test via useService resolving without throwing
|
|
23
28
|
class MyService {
|
|
24
|
-
greet() {
|
|
25
|
-
return "hello";
|
|
26
|
-
}
|
|
29
|
+
greet() { return "hello"; }
|
|
27
30
|
}
|
|
28
31
|
Injectable()(MyService, {} as ClassDecoratorContext);
|
|
29
|
-
|
|
30
|
-
expect(
|
|
31
|
-
expect(instance.greet()).toBe("hello");
|
|
32
|
+
expect(container.resolve(MyService)).toBeInstanceOf(MyService);
|
|
33
|
+
expect(container.resolve(MyService).greet()).toBe("hello");
|
|
32
34
|
});
|
|
33
35
|
|
|
34
|
-
it("registers with transient scope", () => {
|
|
36
|
+
it("registers with transient scope — each resolve returns a new instance", () => {
|
|
35
37
|
class TmpService {}
|
|
36
38
|
Injectable({ scope: "transient" })(TmpService, {} as ClassDecoratorContext);
|
|
37
|
-
|
|
38
|
-
const b = useService(TmpService);
|
|
39
|
-
expect(a).not.toBe(b);
|
|
39
|
+
expect(container.resolve(TmpService)).not.toBe(container.resolve(TmpService));
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
// ── Inject
|
|
43
|
+
// ── @Inject ───────────────────────────────────────────────────────────────────
|
|
44
44
|
|
|
45
45
|
describe("Inject", () => {
|
|
46
46
|
it("injects a registered service via property", () => {
|
|
47
47
|
class Logger {
|
|
48
|
-
log(msg: string) {
|
|
49
|
-
return msg;
|
|
50
|
-
}
|
|
48
|
+
log(msg: string) { return msg; }
|
|
51
49
|
}
|
|
52
50
|
Injectable()(Logger, {} as ClassDecoratorContext);
|
|
53
51
|
|
|
54
|
-
const
|
|
55
|
-
const ctx = {
|
|
56
|
-
name: "logger",
|
|
57
|
-
kind: "field" as const,
|
|
58
|
-
addInitializer(fn: (this: unknown) => void) {
|
|
59
|
-
initializers.push(fn);
|
|
60
|
-
},
|
|
61
|
-
} as ClassFieldDecoratorContext;
|
|
62
|
-
|
|
52
|
+
const { ctx, run } = makeFieldCtx("logger");
|
|
63
53
|
Inject(Logger)(undefined, ctx);
|
|
64
|
-
|
|
65
54
|
const instance: Record<string, unknown> = {};
|
|
66
|
-
|
|
67
|
-
fn.call(instance);
|
|
68
|
-
});
|
|
69
|
-
|
|
55
|
+
run(instance);
|
|
70
56
|
expect(instance.logger).toBeInstanceOf(Logger);
|
|
57
|
+
// second access covers the cache-hit branch (line 62: !cache.has(this) === false)
|
|
58
|
+
expect(instance.logger).toBe(instance.logger);
|
|
71
59
|
});
|
|
72
60
|
|
|
73
61
|
it("throws a descriptive error when dep is not registered", () => {
|
|
74
62
|
class UnknownDep {}
|
|
75
|
-
|
|
76
|
-
const initializers: Array<(this: unknown) => void> = [];
|
|
77
|
-
const ctx = {
|
|
78
|
-
name: "dep",
|
|
79
|
-
kind: "field" as const,
|
|
80
|
-
addInitializer(fn: (this: unknown) => void) {
|
|
81
|
-
initializers.push(fn);
|
|
82
|
-
},
|
|
83
|
-
} as ClassFieldDecoratorContext;
|
|
84
|
-
|
|
63
|
+
const { ctx, run } = makeFieldCtx("dep");
|
|
85
64
|
Inject(UnknownDep)(undefined, ctx);
|
|
86
|
-
|
|
87
|
-
class MyClass {
|
|
88
|
-
constructor() {}
|
|
89
|
-
}
|
|
65
|
+
class MyClass {}
|
|
90
66
|
const instance = new MyClass() as Record<string, unknown>;
|
|
91
|
-
|
|
92
|
-
fn.call(instance);
|
|
93
|
-
});
|
|
94
|
-
|
|
67
|
+
run(instance);
|
|
95
68
|
expect(() => instance.dep).toThrow("[Inject]");
|
|
96
69
|
});
|
|
97
70
|
|
|
98
71
|
it("warns on direct assignment in non-production", () => {
|
|
99
72
|
class Dep2 {}
|
|
100
73
|
Injectable()(Dep2, {} as ClassDecoratorContext);
|
|
101
|
-
|
|
102
|
-
const initializers: Array<(this: unknown) => void> = [];
|
|
103
|
-
const ctx = {
|
|
104
|
-
name: "dep2",
|
|
105
|
-
kind: "field" as const,
|
|
106
|
-
addInitializer(fn: (this: unknown) => void) {
|
|
107
|
-
initializers.push(fn);
|
|
108
|
-
},
|
|
109
|
-
} as ClassFieldDecoratorContext;
|
|
110
|
-
|
|
74
|
+
const { ctx, run } = makeFieldCtx("dep2");
|
|
111
75
|
Inject(Dep2)(undefined, ctx);
|
|
112
76
|
const instance: Record<string, unknown> = {};
|
|
113
|
-
|
|
114
|
-
fn.call(instance);
|
|
115
|
-
});
|
|
116
|
-
|
|
77
|
+
run(instance);
|
|
117
78
|
const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
118
79
|
instance.dep2 = "override";
|
|
119
80
|
expect(spy).toHaveBeenCalledWith(expect.stringContaining("[Inject]"));
|
|
120
81
|
spy.mockRestore();
|
|
121
82
|
});
|
|
83
|
+
|
|
84
|
+
it("returns cached value on second access (cache hit)", () => {
|
|
85
|
+
class CachedService {
|
|
86
|
+
id = Math.random();
|
|
87
|
+
}
|
|
88
|
+
Injectable()(CachedService, {} as ClassDecoratorContext);
|
|
89
|
+
|
|
90
|
+
const { ctx, run } = makeFieldCtx("svc");
|
|
91
|
+
Inject(CachedService)(undefined, ctx);
|
|
92
|
+
const instance: Record<string, unknown> = {};
|
|
93
|
+
run(instance);
|
|
94
|
+
|
|
95
|
+
const first = instance.svc;
|
|
96
|
+
const second = instance.svc;
|
|
97
|
+
expect(first).toBe(second); // same cached instance
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("does not warn on direct assignment in production mode", () => {
|
|
101
|
+
class ProdDep {}
|
|
102
|
+
Injectable()(ProdDep, {} as ClassDecoratorContext);
|
|
103
|
+
const { ctx, run } = makeFieldCtx("dep");
|
|
104
|
+
Inject(ProdDep)(undefined, ctx);
|
|
105
|
+
const instance: Record<string, unknown> = {};
|
|
106
|
+
run(instance);
|
|
107
|
+
|
|
108
|
+
const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
109
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
110
|
+
instance.dep = "override";
|
|
111
|
+
expect(spy).not.toHaveBeenCalled();
|
|
112
|
+
|
|
113
|
+
vi.unstubAllEnvs();
|
|
114
|
+
spy.mockRestore();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("includes the Token description in the error when resolution fails", () => {
|
|
118
|
+
const MY_TOKEN = token<string>("MY_SERVICE");
|
|
119
|
+
const { ctx, run } = makeFieldCtx("svc");
|
|
120
|
+
Inject(MY_TOKEN)(undefined, ctx);
|
|
121
|
+
class Consumer {}
|
|
122
|
+
const instance = new Consumer() as Record<string, unknown>;
|
|
123
|
+
run(instance);
|
|
124
|
+
expect(() => instance.svc).toThrow("MY_SERVICE");
|
|
125
|
+
});
|
|
122
126
|
});
|
|
123
127
|
|
|
124
|
-
// ── InjectContainer
|
|
128
|
+
// ── @InjectContainer ──────────────────────────────────────────────────────────
|
|
125
129
|
|
|
126
130
|
describe("InjectContainer", () => {
|
|
127
|
-
it("injects the global container
|
|
128
|
-
const
|
|
129
|
-
const ctx = {
|
|
130
|
-
name: "container",
|
|
131
|
-
kind: "field" as const,
|
|
132
|
-
addInitializer(fn: (this: unknown) => void) {
|
|
133
|
-
initializers.push(fn);
|
|
134
|
-
},
|
|
135
|
-
} as ClassFieldDecoratorContext;
|
|
136
|
-
|
|
131
|
+
it("injects the global container on an unscoped class", () => {
|
|
132
|
+
const { ctx, run } = makeFieldCtx("c");
|
|
137
133
|
InjectContainer()(undefined, ctx);
|
|
138
134
|
const instance: Record<string, unknown> = {};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
expect(instance.container).toBeInstanceOf(Container);
|
|
135
|
+
run(instance);
|
|
136
|
+
expect(instance.c).toBeInstanceOf(Container);
|
|
137
|
+
expect(instance.c).toBe(container);
|
|
144
138
|
});
|
|
145
139
|
});
|
|
146
140
|
|
|
147
|
-
// ──
|
|
141
|
+
// ── @Scope ────────────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
describe("Scope", () => {
|
|
144
|
+
it("creates a child container per instance", () => {
|
|
145
|
+
@Scope()
|
|
146
|
+
class Widget {}
|
|
147
|
+
const { ctx, run } = makeFieldCtx("c");
|
|
148
|
+
InjectContainer()(undefined, ctx);
|
|
149
|
+
|
|
150
|
+
const a = new Widget() as Record<string, unknown>;
|
|
151
|
+
run(a);
|
|
152
|
+
const b = new Widget() as Record<string, unknown>;
|
|
153
|
+
run(b);
|
|
154
|
+
|
|
155
|
+
expect(a.c).toBeInstanceOf(Container);
|
|
156
|
+
expect(b.c).toBeInstanceOf(Container);
|
|
157
|
+
expect(a.c).not.toBe(b.c); // each instance gets its own scope
|
|
158
|
+
expect(a.c).not.toBe(container); // it's a child, not the root
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("services registered in configure are resolvable within the scope", () => {
|
|
162
|
+
const DB_URL = token<string>("DB_URL");
|
|
163
|
+
|
|
164
|
+
@Scope((c) => { c.registerValue(DB_URL, "postgres://localhost"); })
|
|
165
|
+
class DataModule {}
|
|
166
|
+
|
|
167
|
+
const { ctx, run } = makeFieldCtx("db");
|
|
168
|
+
Inject(DB_URL)(undefined, ctx);
|
|
169
|
+
|
|
170
|
+
const instance = new DataModule() as Record<string, unknown>;
|
|
171
|
+
run(instance);
|
|
148
172
|
|
|
149
|
-
|
|
150
|
-
|
|
173
|
+
expect(instance.db).toBe("postgres://localhost");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("@Inject resolves from the scoped container, not the global one", () => {
|
|
177
|
+
class ScopedRepo {}
|
|
178
|
+
// NOT registered in global container
|
|
151
179
|
const VERSION = token<string>("VERSION");
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
180
|
+
|
|
181
|
+
@Scope((c) => {
|
|
182
|
+
c.register(ScopedRepo);
|
|
183
|
+
c.registerValue(VERSION, "2.0");
|
|
184
|
+
})
|
|
185
|
+
class App {}
|
|
186
|
+
|
|
187
|
+
const { ctx: repoCtx, run: runRepo } = makeFieldCtx("repo");
|
|
188
|
+
Inject(ScopedRepo)(undefined, repoCtx);
|
|
189
|
+
const { ctx: verCtx, run: runVer } = makeFieldCtx("version");
|
|
190
|
+
Inject(VERSION)(undefined, verCtx);
|
|
191
|
+
|
|
192
|
+
const instance = new App() as Record<string, unknown>;
|
|
193
|
+
runRepo(instance);
|
|
194
|
+
runVer(instance);
|
|
195
|
+
|
|
196
|
+
expect(instance.repo).toBeInstanceOf(ScopedRepo);
|
|
197
|
+
expect(instance.version).toBe("2.0");
|
|
155
198
|
});
|
|
156
|
-
});
|
|
157
199
|
|
|
158
|
-
|
|
200
|
+
it("@InjectContainer returns the scoped child container", () => {
|
|
201
|
+
@Scope((c) => { c.registerValue(token<number>("X"), 42); })
|
|
202
|
+
class Service {}
|
|
159
203
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
204
|
+
const { ctx, run } = makeFieldCtx("container");
|
|
205
|
+
InjectContainer()(undefined, ctx);
|
|
206
|
+
|
|
207
|
+
const instance = new Service() as Record<string, unknown>;
|
|
208
|
+
run(instance);
|
|
209
|
+
|
|
210
|
+
expect(instance.container).toBeInstanceOf(Container);
|
|
211
|
+
expect(instance.container).not.toBe(container);
|
|
164
212
|
});
|
|
165
213
|
|
|
166
|
-
it("
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
|
|
214
|
+
it("child container inherits registrations from the parent", () => {
|
|
215
|
+
class GlobalService {}
|
|
216
|
+
Injectable()(GlobalService, {} as ClassDecoratorContext);
|
|
217
|
+
|
|
218
|
+
@Scope()
|
|
219
|
+
class Child {}
|
|
220
|
+
|
|
221
|
+
const { ctx, run } = makeFieldCtx("svc");
|
|
222
|
+
Inject(GlobalService)(undefined, ctx);
|
|
223
|
+
const instance = new Child() as Record<string, unknown>;
|
|
224
|
+
run(instance);
|
|
225
|
+
|
|
226
|
+
expect(instance.svc).toBeInstanceOf(GlobalService);
|
|
172
227
|
});
|
|
173
228
|
});
|
package/src/container.ts
CHANGED
|
@@ -10,7 +10,8 @@ export interface ServiceDescriptor {
|
|
|
10
10
|
instance?: unknown;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
export type Constructor<T = unknown> = new (...args: any[]) => T;
|
|
14
15
|
|
|
15
16
|
export class Token<_T> {
|
|
16
17
|
readonly description: string;
|
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,
|