@navios/di 0.4.2 → 0.5.1
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 +211 -1
- package/coverage/clover.xml +1912 -1277
- package/coverage/coverage-final.json +37 -28
- package/coverage/docs/examples/basic-usage.mts.html +1 -1
- package/coverage/docs/examples/factory-pattern.mts.html +1 -1
- package/coverage/docs/examples/index.html +1 -1
- package/coverage/docs/examples/injection-tokens.mts.html +1 -1
- package/coverage/docs/examples/request-scope-example.mts.html +1 -1
- package/coverage/docs/examples/service-lifecycle.mts.html +1 -1
- package/coverage/index.html +71 -41
- package/coverage/lib/_tsup-dts-rollup.d.mts.html +682 -43
- package/coverage/lib/index.d.mts.html +7 -4
- package/coverage/lib/index.html +5 -5
- package/coverage/lib/testing/index.d.mts.html +91 -0
- package/coverage/lib/testing/index.html +116 -0
- package/coverage/src/base-instance-holder-manager.mts.html +589 -0
- package/coverage/src/container.mts.html +257 -74
- package/coverage/src/decorators/factory.decorator.mts.html +1 -1
- package/coverage/src/decorators/index.html +1 -1
- package/coverage/src/decorators/index.mts.html +1 -1
- package/coverage/src/decorators/injectable.decorator.mts.html +20 -20
- package/coverage/src/enums/index.html +1 -1
- package/coverage/src/enums/index.mts.html +1 -1
- package/coverage/src/enums/injectable-scope.enum.mts.html +1 -1
- package/coverage/src/enums/injectable-type.enum.mts.html +1 -1
- package/coverage/src/errors/di-error.mts.html +292 -0
- package/coverage/src/errors/errors.enum.mts.html +30 -21
- package/coverage/src/errors/factory-not-found.mts.html +31 -22
- package/coverage/src/errors/factory-token-not-resolved.mts.html +29 -26
- package/coverage/src/errors/index.html +56 -41
- package/coverage/src/errors/index.mts.html +15 -9
- package/coverage/src/errors/instance-destroying.mts.html +31 -22
- package/coverage/src/errors/instance-expired.mts.html +31 -22
- package/coverage/src/errors/instance-not-found.mts.html +31 -22
- package/coverage/src/errors/unknown-error.mts.html +31 -43
- package/coverage/src/event-emitter.mts.html +14 -14
- package/coverage/src/factory-context.mts.html +1 -1
- package/coverage/src/index.html +121 -46
- package/coverage/src/index.mts.html +7 -4
- package/coverage/src/injection-token.mts.html +28 -28
- package/coverage/src/injector.mts.html +1 -1
- package/coverage/src/instance-resolver.mts.html +1762 -0
- package/coverage/src/interfaces/factory.interface.mts.html +1 -1
- package/coverage/src/interfaces/index.html +1 -1
- package/coverage/src/interfaces/index.mts.html +1 -1
- package/coverage/src/interfaces/on-service-destroy.interface.mts.html +1 -1
- package/coverage/src/interfaces/on-service-init.interface.mts.html +1 -1
- package/coverage/src/registry.mts.html +28 -28
- package/coverage/src/request-context-holder.mts.html +183 -102
- package/coverage/src/request-context-manager.mts.html +532 -0
- package/coverage/src/service-instantiator.mts.html +49 -49
- package/coverage/src/service-invalidator.mts.html +1372 -0
- package/coverage/src/service-locator-event-bus.mts.html +48 -48
- package/coverage/src/service-locator-instance-holder.mts.html +2 -14
- package/coverage/src/service-locator-manager.mts.html +71 -335
- package/coverage/src/service-locator.mts.html +240 -2328
- package/coverage/src/symbols/index.html +1 -1
- package/coverage/src/symbols/index.mts.html +1 -1
- package/coverage/src/symbols/injectable-token.mts.html +1 -1
- package/coverage/src/testing/index.html +131 -0
- package/coverage/src/testing/index.mts.html +88 -0
- package/coverage/src/testing/test-container.mts.html +445 -0
- package/coverage/src/token-processor.mts.html +607 -0
- package/coverage/src/utils/defer.mts.html +28 -214
- package/coverage/src/utils/get-injectable-token.mts.html +7 -7
- package/coverage/src/utils/get-injectors.mts.html +99 -99
- package/coverage/src/utils/index.html +15 -15
- package/coverage/src/utils/index.mts.html +4 -7
- package/coverage/src/utils/types.mts.html +1 -1
- package/docs/injectable.md +51 -11
- package/docs/scopes.md +63 -29
- package/lib/_tsup-dts-rollup.d.mts +376 -213
- package/lib/_tsup-dts-rollup.d.ts +376 -213
- package/lib/{chunk-3NLYPYBY.mjs → chunk-2M576LCC.mjs} +1024 -608
- package/lib/chunk-2M576LCC.mjs.map +1 -0
- package/lib/index.d.mts +6 -4
- package/lib/index.d.ts +6 -4
- package/lib/index.js +1189 -773
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +2 -2
- package/lib/testing/index.js +1261 -843
- package/lib/testing/index.js.map +1 -1
- package/lib/testing/index.mjs +1 -1
- package/package.json +4 -4
- package/src/__tests__/container.spec.mts +47 -13
- package/src/__tests__/errors.spec.mts +53 -27
- package/src/__tests__/injectable.spec.mts +73 -0
- package/src/__tests__/request-scope.spec.mts +0 -2
- package/src/__tests__/service-instantiator.spec.mts +1 -0
- package/src/__tests__/service-locator-manager.spec.mts +12 -82
- package/src/__tests__/service-locator.spec.mts +1009 -1
- package/src/__type-tests__/inject.spec-d.mts +30 -7
- package/src/__type-tests__/injectable.spec-d.mts +76 -37
- package/src/base-instance-holder-manager.mts +2 -9
- package/src/container.mts +61 -9
- package/src/decorators/injectable.decorator.mts +29 -5
- package/src/errors/di-error.mts +69 -0
- package/src/errors/index.mts +9 -7
- package/src/injection-token.mts +1 -0
- package/src/injector.mts +2 -0
- package/src/instance-resolver.mts +559 -0
- package/src/request-context-holder.mts +0 -2
- package/src/request-context-manager.mts +149 -0
- package/src/service-invalidator.mts +429 -0
- package/src/service-locator-event-bus.mts +1 -1
- package/src/service-locator-instance-holder.mts +0 -4
- package/src/service-locator-manager.mts +10 -40
- package/src/service-locator.mts +86 -782
- package/src/token-processor.mts +174 -0
- package/src/utils/get-injectors.mts +161 -24
- package/src/utils/index.mts +0 -1
- package/src/utils/types.mts +12 -8
- package/lib/chunk-3NLYPYBY.mjs.map +0 -1
- package/src/__tests__/defer.spec.mts +0 -166
- package/src/errors/errors.enum.mts +0 -8
- package/src/errors/factory-not-found.mts +0 -8
- package/src/errors/factory-token-not-resolved.mts +0 -10
- package/src/errors/instance-destroying.mts +0 -8
- package/src/errors/instance-expired.mts +0 -8
- package/src/errors/instance-not-found.mts +0 -8
- package/src/errors/unknown-error.mts +0 -15
- package/src/utils/defer.mts +0 -73
|
@@ -57,15 +57,38 @@ const typedOptionalObjectToken = InjectionToken.create<
|
|
|
57
57
|
const typedToken = InjectionToken.create<FooService>(Symbol.for('Typed token'))
|
|
58
58
|
|
|
59
59
|
describe('inject', () => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
describe('#1 Classes', () => {
|
|
61
|
+
test('simple class', async () => {
|
|
62
|
+
@Injectable()
|
|
63
|
+
class Foo {
|
|
64
|
+
makeFoo() {
|
|
65
|
+
return 'foo'
|
|
66
|
+
}
|
|
65
67
|
}
|
|
66
|
-
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
assertType<Foo>(await asyncInject(Foo))
|
|
70
|
+
})
|
|
71
|
+
test('class with required argument', async () => {
|
|
72
|
+
@Injectable({
|
|
73
|
+
schema: simpleObjectSchema,
|
|
74
|
+
})
|
|
75
|
+
class Foo {
|
|
76
|
+
constructor(public arg: z.infer<typeof simpleObjectSchema>) {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
assertType<Foo>(await asyncInject(Foo, { foo: 'bar' }))
|
|
80
|
+
})
|
|
81
|
+
test('should fail if not compatible', async () => {
|
|
82
|
+
@Injectable({
|
|
83
|
+
schema: simpleObjectSchema,
|
|
84
|
+
})
|
|
85
|
+
class Foo {
|
|
86
|
+
constructor(public arg: z.infer<typeof simpleObjectSchema>) {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// @ts-expect-error Should fail if not compatible
|
|
90
|
+
await asyncInject(Foo, { test: 'bar' })
|
|
91
|
+
})
|
|
69
92
|
})
|
|
70
93
|
test('#2 Token with required Schema', async () => {
|
|
71
94
|
const result = await asyncInject(typelessObjectToken, { foo: 'bar' })
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// oxlint-disable no-unused-vars
|
|
1
2
|
import { expectTypeOf, test } from 'vitest'
|
|
2
3
|
import { z } from 'zod/v4'
|
|
3
4
|
|
|
@@ -16,6 +17,14 @@ const simpleOptionalObjectSchema = z
|
|
|
16
17
|
foo: z.string(),
|
|
17
18
|
})
|
|
18
19
|
.optional()
|
|
20
|
+
const otherObjectSchema = z.object({
|
|
21
|
+
bar: z.string(),
|
|
22
|
+
})
|
|
23
|
+
const otherOptionalObjectSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
bar: z.string(),
|
|
26
|
+
})
|
|
27
|
+
.optional()
|
|
19
28
|
// const simpleRecordSchema = z.record(z.string(), z.string())
|
|
20
29
|
// const simpleOptionalRecordSchema = z.record(z.string(), z.string()).optional()
|
|
21
30
|
|
|
@@ -61,8 +70,52 @@ test('Injectable types', () => {
|
|
|
61
70
|
@Injectable()
|
|
62
71
|
class {},
|
|
63
72
|
).toBeConstructibleWith()
|
|
73
|
+
// #1 Injectable w/o decorators enabled in project
|
|
74
|
+
expectTypeOf(
|
|
75
|
+
Injectable({
|
|
76
|
+
token: typedObjectToken,
|
|
77
|
+
})(
|
|
78
|
+
class {
|
|
79
|
+
constructor() {}
|
|
80
|
+
makeFoo() {
|
|
81
|
+
return 'foo'
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
),
|
|
85
|
+
).toBeConstructibleWith()
|
|
64
86
|
|
|
65
87
|
// #2 required argument
|
|
88
|
+
expectTypeOf(
|
|
89
|
+
@Injectable({
|
|
90
|
+
schema: simpleObjectSchema,
|
|
91
|
+
})
|
|
92
|
+
class {
|
|
93
|
+
constructor(public arg: z.infer<typeof simpleObjectSchema>) {}
|
|
94
|
+
},
|
|
95
|
+
).toBeConstructibleWith({
|
|
96
|
+
foo: 'something',
|
|
97
|
+
})
|
|
98
|
+
// #2 it's required in schema but optional in class allowed
|
|
99
|
+
expectTypeOf(
|
|
100
|
+
@Injectable({
|
|
101
|
+
schema: simpleObjectSchema,
|
|
102
|
+
})
|
|
103
|
+
class {
|
|
104
|
+
constructor(public arg?: z.infer<typeof simpleObjectSchema>) {}
|
|
105
|
+
},
|
|
106
|
+
).toBeConstructibleWith({
|
|
107
|
+
foo: 'something',
|
|
108
|
+
})
|
|
109
|
+
// #2 should fail if not compatible
|
|
110
|
+
// @ts-expect-error Should fail if not compatible
|
|
111
|
+
@Injectable({
|
|
112
|
+
schema: simpleObjectSchema,
|
|
113
|
+
})
|
|
114
|
+
class FailWithOtherSchema {
|
|
115
|
+
constructor(public arg: z.infer<typeof otherObjectSchema>) {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// #3 required argument
|
|
66
119
|
expectTypeOf(
|
|
67
120
|
@Injectable({
|
|
68
121
|
token: typelessObjectToken,
|
|
@@ -73,7 +126,7 @@ test('Injectable types', () => {
|
|
|
73
126
|
).toBeConstructibleWith({
|
|
74
127
|
foo: 'something',
|
|
75
128
|
})
|
|
76
|
-
// #
|
|
129
|
+
// #3 it's required in token but optional in class allowed
|
|
77
130
|
expectTypeOf(
|
|
78
131
|
@Injectable({
|
|
79
132
|
token: typelessObjectToken,
|
|
@@ -84,7 +137,7 @@ test('Injectable types', () => {
|
|
|
84
137
|
).toBeConstructibleWith({
|
|
85
138
|
foo: 'something',
|
|
86
139
|
})
|
|
87
|
-
// #
|
|
140
|
+
// #3 optional value but class accepts it
|
|
88
141
|
expectTypeOf(
|
|
89
142
|
@Injectable({
|
|
90
143
|
token: typelessOptionalObjectToken,
|
|
@@ -95,7 +148,7 @@ test('Injectable types', () => {
|
|
|
95
148
|
).toBeConstructibleWith({
|
|
96
149
|
foo: 'something',
|
|
97
150
|
})
|
|
98
|
-
// #
|
|
151
|
+
// #3 optional value and class accepts it
|
|
99
152
|
expectTypeOf(
|
|
100
153
|
@Injectable({
|
|
101
154
|
token: typelessOptionalObjectToken,
|
|
@@ -104,7 +157,7 @@ test('Injectable types', () => {
|
|
|
104
157
|
constructor(public arg: z.infer<typeof simpleOptionalObjectSchema>) {}
|
|
105
158
|
},
|
|
106
159
|
).toBeConstructibleWith(undefined)
|
|
107
|
-
// #
|
|
160
|
+
// #3 compatible schemas
|
|
108
161
|
expectTypeOf(
|
|
109
162
|
@Injectable({
|
|
110
163
|
token: typelessOptionalObjectToken,
|
|
@@ -113,7 +166,7 @@ test('Injectable types', () => {
|
|
|
113
166
|
constructor(public arg?: z.infer<typeof simpleObjectSchema>) {}
|
|
114
167
|
},
|
|
115
168
|
).toBeConstructibleWith(undefined)
|
|
116
|
-
// #
|
|
169
|
+
// #3 compatible schemas
|
|
117
170
|
expectTypeOf(
|
|
118
171
|
// @ts-expect-error token has optional schema, but Class has required, should fail
|
|
119
172
|
@Injectable({
|
|
@@ -126,7 +179,7 @@ test('Injectable types', () => {
|
|
|
126
179
|
foo: 'something',
|
|
127
180
|
})
|
|
128
181
|
|
|
129
|
-
// #
|
|
182
|
+
// #3 typed token and required argument
|
|
130
183
|
expectTypeOf(
|
|
131
184
|
@Injectable({
|
|
132
185
|
token: typedObjectToken,
|
|
@@ -141,7 +194,7 @@ test('Injectable types', () => {
|
|
|
141
194
|
).toBeConstructibleWith({
|
|
142
195
|
foo: 'something',
|
|
143
196
|
})
|
|
144
|
-
// #
|
|
197
|
+
// #3 typed token and required argument
|
|
145
198
|
expectTypeOf(
|
|
146
199
|
@Injectable({
|
|
147
200
|
token: typedOptionalObjectToken,
|
|
@@ -156,7 +209,7 @@ test('Injectable types', () => {
|
|
|
156
209
|
).toBeConstructibleWith({
|
|
157
210
|
foo: 'something',
|
|
158
211
|
})
|
|
159
|
-
// #
|
|
212
|
+
// #3 should fail if not compatible
|
|
160
213
|
expectTypeOf(
|
|
161
214
|
// @ts-expect-error class doesn't implement the token type
|
|
162
215
|
@Injectable({
|
|
@@ -168,7 +221,7 @@ test('Injectable types', () => {
|
|
|
168
221
|
).toBeConstructibleWith({
|
|
169
222
|
foo: 'something',
|
|
170
223
|
})
|
|
171
|
-
// #
|
|
224
|
+
// #3 should fail if not compatible
|
|
172
225
|
expectTypeOf(
|
|
173
226
|
// @ts-expect-error class doesn't implement the token type
|
|
174
227
|
@Injectable({
|
|
@@ -184,7 +237,7 @@ test('Injectable types', () => {
|
|
|
184
237
|
).toBeConstructibleWith({
|
|
185
238
|
foo: 'something',
|
|
186
239
|
})
|
|
187
|
-
// #
|
|
240
|
+
// #3 typed token without schema
|
|
188
241
|
expectTypeOf(
|
|
189
242
|
@Injectable({
|
|
190
243
|
token: typedToken,
|
|
@@ -196,7 +249,7 @@ test('Injectable types', () => {
|
|
|
196
249
|
}
|
|
197
250
|
},
|
|
198
251
|
).toBeConstructibleWith()
|
|
199
|
-
// #
|
|
252
|
+
// #3 typed token without schema fail if not compatible
|
|
200
253
|
expectTypeOf(
|
|
201
254
|
// @ts-expect-error class doesn't implement the token type
|
|
202
255
|
@Injectable({
|
|
@@ -207,21 +260,7 @@ test('Injectable types', () => {
|
|
|
207
260
|
},
|
|
208
261
|
).toBeConstructibleWith()
|
|
209
262
|
|
|
210
|
-
// #
|
|
211
|
-
expectTypeOf(
|
|
212
|
-
Injectable({
|
|
213
|
-
token: typedObjectToken,
|
|
214
|
-
})(
|
|
215
|
-
class {
|
|
216
|
-
constructor() {}
|
|
217
|
-
makeFoo() {
|
|
218
|
-
return 'foo'
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
),
|
|
222
|
-
).toBeConstructibleWith()
|
|
223
|
-
|
|
224
|
-
// #2 required argument
|
|
263
|
+
// #3 required argument
|
|
225
264
|
expectTypeOf(
|
|
226
265
|
Injectable({
|
|
227
266
|
token: typelessObjectToken,
|
|
@@ -233,7 +272,7 @@ test('Injectable types', () => {
|
|
|
233
272
|
).toBeConstructibleWith({
|
|
234
273
|
foo: 'something',
|
|
235
274
|
})
|
|
236
|
-
// #
|
|
275
|
+
// #3 it's required in token but optional in class allowed
|
|
237
276
|
expectTypeOf(
|
|
238
277
|
Injectable({
|
|
239
278
|
token: typelessObjectToken,
|
|
@@ -245,7 +284,7 @@ test('Injectable types', () => {
|
|
|
245
284
|
).toBeConstructibleWith({
|
|
246
285
|
foo: 'something',
|
|
247
286
|
})
|
|
248
|
-
// #
|
|
287
|
+
// #3 optional value but class accepts it
|
|
249
288
|
expectTypeOf(
|
|
250
289
|
Injectable({
|
|
251
290
|
token: typelessOptionalObjectToken,
|
|
@@ -257,7 +296,7 @@ test('Injectable types', () => {
|
|
|
257
296
|
).toBeConstructibleWith({
|
|
258
297
|
foo: 'something',
|
|
259
298
|
})
|
|
260
|
-
// #
|
|
299
|
+
// #3 optional value and class accepts it
|
|
261
300
|
expectTypeOf(
|
|
262
301
|
Injectable({
|
|
263
302
|
token: typelessOptionalObjectToken,
|
|
@@ -267,7 +306,7 @@ test('Injectable types', () => {
|
|
|
267
306
|
},
|
|
268
307
|
),
|
|
269
308
|
).toBeConstructibleWith(undefined)
|
|
270
|
-
// #
|
|
309
|
+
// #3 compatible schemas
|
|
271
310
|
expectTypeOf(
|
|
272
311
|
Injectable({
|
|
273
312
|
token: typelessOptionalObjectToken,
|
|
@@ -277,7 +316,7 @@ test('Injectable types', () => {
|
|
|
277
316
|
},
|
|
278
317
|
),
|
|
279
318
|
).toBeConstructibleWith(undefined)
|
|
280
|
-
// #
|
|
319
|
+
// #3 compatible schemas
|
|
281
320
|
expectTypeOf(
|
|
282
321
|
Injectable({
|
|
283
322
|
token: typelessOptionalObjectToken,
|
|
@@ -291,7 +330,7 @@ test('Injectable types', () => {
|
|
|
291
330
|
foo: 'something',
|
|
292
331
|
})
|
|
293
332
|
|
|
294
|
-
// #
|
|
333
|
+
// #3 typed token and required argument
|
|
295
334
|
expectTypeOf(
|
|
296
335
|
Injectable({
|
|
297
336
|
token: typedObjectToken,
|
|
@@ -307,7 +346,7 @@ test('Injectable types', () => {
|
|
|
307
346
|
).toBeConstructibleWith({
|
|
308
347
|
foo: 'something',
|
|
309
348
|
})
|
|
310
|
-
// #
|
|
349
|
+
// #3 typed token and required argument
|
|
311
350
|
expectTypeOf(
|
|
312
351
|
Injectable({
|
|
313
352
|
token: typedOptionalObjectToken,
|
|
@@ -323,7 +362,7 @@ test('Injectable types', () => {
|
|
|
323
362
|
).toBeConstructibleWith({
|
|
324
363
|
foo: 'something',
|
|
325
364
|
})
|
|
326
|
-
// #
|
|
365
|
+
// #3 should fail if not compatible
|
|
327
366
|
expectTypeOf(
|
|
328
367
|
Injectable({
|
|
329
368
|
token: typedOptionalObjectToken,
|
|
@@ -336,7 +375,7 @@ test('Injectable types', () => {
|
|
|
336
375
|
).toBeConstructibleWith({
|
|
337
376
|
foo: 'something',
|
|
338
377
|
})
|
|
339
|
-
// #
|
|
378
|
+
// #3 should fail if not compatible
|
|
340
379
|
expectTypeOf(
|
|
341
380
|
Injectable({
|
|
342
381
|
token: typedOptionalObjectToken,
|
|
@@ -353,7 +392,7 @@ test('Injectable types', () => {
|
|
|
353
392
|
).toBeConstructibleWith({
|
|
354
393
|
foo: 'something',
|
|
355
394
|
})
|
|
356
|
-
// #
|
|
395
|
+
// #3 typed token without schema
|
|
357
396
|
expectTypeOf(
|
|
358
397
|
Injectable({
|
|
359
398
|
token: typedToken,
|
|
@@ -366,7 +405,7 @@ test('Injectable types', () => {
|
|
|
366
405
|
},
|
|
367
406
|
),
|
|
368
407
|
).toBeConstructibleWith()
|
|
369
|
-
// #
|
|
408
|
+
// #3 typed token without schema fail if not compatible
|
|
370
409
|
expectTypeOf(
|
|
371
410
|
Injectable({
|
|
372
411
|
token: typedToken,
|
|
@@ -2,7 +2,6 @@ import type { ServiceLocatorInstanceHolder } from './service-locator-instance-ho
|
|
|
2
2
|
|
|
3
3
|
import { InjectableScope, InjectableType } from './enums/index.mjs'
|
|
4
4
|
import { ServiceLocatorInstanceHolderStatus } from './service-locator-instance-holder.mjs'
|
|
5
|
-
import { createDeferred } from './utils/defer.mjs'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Abstract base class that provides common functionality for managing ServiceLocatorInstanceHolder objects.
|
|
@@ -84,7 +83,6 @@ export abstract class BaseInstanceHolderManager {
|
|
|
84
83
|
* @param type The injectable type
|
|
85
84
|
* @param scope The injectable scope
|
|
86
85
|
* @param deps Optional set of dependencies
|
|
87
|
-
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
88
86
|
* @returns A tuple containing the deferred promise and the holder
|
|
89
87
|
*/
|
|
90
88
|
createCreatingHolder<Instance>(
|
|
@@ -92,12 +90,11 @@ export abstract class BaseInstanceHolderManager {
|
|
|
92
90
|
type: InjectableType,
|
|
93
91
|
scope: InjectableScope,
|
|
94
92
|
deps: Set<string> = new Set(),
|
|
95
|
-
ttl: number = Infinity,
|
|
96
93
|
): [
|
|
97
|
-
ReturnType<typeof
|
|
94
|
+
ReturnType<typeof Promise.withResolvers<[undefined, Instance]>>,
|
|
98
95
|
ServiceLocatorInstanceHolder<Instance>,
|
|
99
96
|
] {
|
|
100
|
-
const deferred =
|
|
97
|
+
const deferred = Promise.withResolvers<[undefined, Instance]>()
|
|
101
98
|
|
|
102
99
|
const holder: ServiceLocatorInstanceHolder<Instance> = {
|
|
103
100
|
status: ServiceLocatorInstanceHolderStatus.Creating,
|
|
@@ -110,7 +107,6 @@ export abstract class BaseInstanceHolderManager {
|
|
|
110
107
|
deps,
|
|
111
108
|
destroyListeners: [],
|
|
112
109
|
createdAt: Date.now(),
|
|
113
|
-
ttl,
|
|
114
110
|
}
|
|
115
111
|
|
|
116
112
|
return [deferred, holder]
|
|
@@ -124,7 +120,6 @@ export abstract class BaseInstanceHolderManager {
|
|
|
124
120
|
* @param type The injectable type
|
|
125
121
|
* @param scope The injectable scope
|
|
126
122
|
* @param deps Optional set of dependencies
|
|
127
|
-
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
128
123
|
* @returns The created holder
|
|
129
124
|
*/
|
|
130
125
|
protected createCreatedHolder<Instance>(
|
|
@@ -133,7 +128,6 @@ export abstract class BaseInstanceHolderManager {
|
|
|
133
128
|
type: InjectableType,
|
|
134
129
|
scope: InjectableScope,
|
|
135
130
|
deps: Set<string> = new Set(),
|
|
136
|
-
ttl: number = Infinity,
|
|
137
131
|
): ServiceLocatorInstanceHolder<Instance> {
|
|
138
132
|
const holder: ServiceLocatorInstanceHolder<Instance> = {
|
|
139
133
|
status: ServiceLocatorInstanceHolderStatus.Created,
|
|
@@ -146,7 +140,6 @@ export abstract class BaseInstanceHolderManager {
|
|
|
146
140
|
deps,
|
|
147
141
|
destroyListeners: [],
|
|
148
142
|
createdAt: Date.now(),
|
|
149
|
-
ttl,
|
|
150
143
|
}
|
|
151
144
|
|
|
152
145
|
return holder
|
package/src/container.mts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
import type { Factorable } from './interfaces/factory.interface.mjs'
|
|
11
11
|
import type { Registry } from './registry.mjs'
|
|
12
12
|
import type { RequestContextHolder } from './request-context-holder.mjs'
|
|
13
|
+
import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
|
|
13
14
|
import type { Injectors } from './utils/index.mjs'
|
|
14
15
|
import type { Join, UnionToArray } from './utils/types.mjs'
|
|
15
16
|
|
|
@@ -103,16 +104,67 @@ export class Container {
|
|
|
103
104
|
* Invalidates a service and its dependencies
|
|
104
105
|
*/
|
|
105
106
|
async invalidate(service: unknown): Promise<void> {
|
|
106
|
-
const
|
|
107
|
-
.getManager()
|
|
108
|
-
.filter((holder) => holder.instance === service)
|
|
109
|
-
if (holderMap.size === 0) {
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
const holder = holderMap.values().next().value
|
|
107
|
+
const holder = this.getHolderByInstance(service)
|
|
113
108
|
if (holder) {
|
|
114
109
|
await this.serviceLocator.invalidate(holder.name)
|
|
110
|
+
} else {
|
|
111
|
+
const requestHolder = this.getRequestHolderByInstance(service)
|
|
112
|
+
if (requestHolder) {
|
|
113
|
+
await this.serviceLocator.invalidate(requestHolder.name)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Gets a service holder by instance (reverse lookup)
|
|
120
|
+
*/
|
|
121
|
+
private getHolderByInstance(
|
|
122
|
+
instance: unknown,
|
|
123
|
+
): ServiceLocatorInstanceHolder | null {
|
|
124
|
+
const holderMap = Array.from(
|
|
125
|
+
this.serviceLocator
|
|
126
|
+
.getManager()
|
|
127
|
+
.filter((holder) => holder.instance === instance)
|
|
128
|
+
.values(),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return holderMap.length > 0 ? holderMap[0] : null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private getRequestHolderByInstance(
|
|
135
|
+
instance: unknown,
|
|
136
|
+
): ServiceLocatorInstanceHolder | null {
|
|
137
|
+
const requestContexts = this.serviceLocator
|
|
138
|
+
.getRequestContextManager()
|
|
139
|
+
.getRequestContexts()
|
|
140
|
+
if (requestContexts) {
|
|
141
|
+
for (const requestContext of requestContexts.values()) {
|
|
142
|
+
for (const holder of requestContext.holders.values()) {
|
|
143
|
+
if (holder.instance === instance) {
|
|
144
|
+
return holder
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
115
148
|
}
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Checks if a service is registered in the container
|
|
154
|
+
*/
|
|
155
|
+
isRegistered(token: any): boolean {
|
|
156
|
+
try {
|
|
157
|
+
return this.serviceLocator.getInstanceIdentifier(token) !== null
|
|
158
|
+
} catch {
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Disposes the container and cleans up all resources
|
|
165
|
+
*/
|
|
166
|
+
async dispose(): Promise<void> {
|
|
167
|
+
await this.serviceLocator.clearAll()
|
|
116
168
|
}
|
|
117
169
|
|
|
118
170
|
/**
|
|
@@ -169,7 +221,7 @@ export class Container {
|
|
|
169
221
|
* Clears all instances and bindings from the container.
|
|
170
222
|
* This is useful for testing or resetting the container state.
|
|
171
223
|
*/
|
|
172
|
-
clear(): void {
|
|
173
|
-
this.serviceLocator.
|
|
224
|
+
clear(): Promise<void> {
|
|
225
|
+
return this.serviceLocator.clearAll()
|
|
174
226
|
}
|
|
175
227
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from 'zod/v4'
|
|
1
|
+
import { z, ZodObject } from 'zod/v4'
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
BaseInjectionTokenSchemaType,
|
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
ClassTypeWithInstanceAndArgument,
|
|
9
9
|
ClassTypeWithInstanceAndOptionalArgument,
|
|
10
10
|
ClassTypeWithOptionalArgument,
|
|
11
|
+
ClassTypeWithoutArguments,
|
|
12
|
+
InjectionTokenSchemaType,
|
|
11
13
|
OptionalInjectionTokenSchemaType,
|
|
12
14
|
} from '../injection-token.mjs'
|
|
13
15
|
import type { Registry } from '../registry.mjs'
|
|
@@ -20,20 +22,36 @@ import { InjectableTokenMeta } from '../symbols/index.mjs'
|
|
|
20
22
|
export interface InjectableOptions {
|
|
21
23
|
scope?: InjectableScope
|
|
22
24
|
token?: InjectionToken<any, any>
|
|
25
|
+
schema?: InjectionTokenSchemaType
|
|
23
26
|
registry?: Registry
|
|
24
27
|
}
|
|
25
28
|
// #1 Simple constructorless class
|
|
26
|
-
export function Injectable(): <T extends
|
|
29
|
+
export function Injectable(): <T extends ClassTypeWithoutArguments>(
|
|
27
30
|
target: T,
|
|
28
31
|
context?: ClassDecoratorContext,
|
|
29
32
|
) => T
|
|
30
33
|
export function Injectable(options: {
|
|
31
34
|
scope?: InjectableScope
|
|
32
35
|
registry: Registry
|
|
33
|
-
}): <T extends
|
|
36
|
+
}): <T extends ClassTypeWithoutArguments>(
|
|
37
|
+
target: T,
|
|
38
|
+
context?: ClassDecoratorContext,
|
|
39
|
+
) => T
|
|
34
40
|
export function Injectable(options: {
|
|
35
41
|
scope: InjectableScope
|
|
36
|
-
}): <T extends
|
|
42
|
+
}): <T extends ClassTypeWithoutArguments>(
|
|
43
|
+
target: T,
|
|
44
|
+
context?: ClassDecoratorContext,
|
|
45
|
+
) => T
|
|
46
|
+
// #2 Class with schema
|
|
47
|
+
export function Injectable<Schema extends InjectionTokenSchemaType>(options: {
|
|
48
|
+
scope?: InjectableScope
|
|
49
|
+
schema: Schema
|
|
50
|
+
registry?: Registry
|
|
51
|
+
}): <T extends ClassTypeWithArgument<z.output<Schema>>>(
|
|
52
|
+
target: T,
|
|
53
|
+
context?: ClassDecoratorContext,
|
|
54
|
+
) => T
|
|
37
55
|
|
|
38
56
|
// #3 Class with typeless token and schema
|
|
39
57
|
export function Injectable<Type, Schema>(options: {
|
|
@@ -76,6 +94,7 @@ export function Injectable<Type, Schema>(options: {
|
|
|
76
94
|
export function Injectable({
|
|
77
95
|
scope = InjectableScope.Singleton,
|
|
78
96
|
token,
|
|
97
|
+
schema,
|
|
79
98
|
registry = globalRegistry,
|
|
80
99
|
}: InjectableOptions = {}) {
|
|
81
100
|
return <T extends ClassType>(
|
|
@@ -90,8 +109,13 @@ export function Injectable({
|
|
|
90
109
|
'[ServiceLocator] @Injectable decorator can only be used on classes.',
|
|
91
110
|
)
|
|
92
111
|
}
|
|
112
|
+
if (schema && token) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
'[ServiceLocator] @Injectable decorator cannot have both a token and a schema',
|
|
115
|
+
)
|
|
116
|
+
}
|
|
93
117
|
let injectableToken: InjectionToken<any, any> =
|
|
94
|
-
token ?? InjectionToken.create(target)
|
|
118
|
+
token ?? InjectionToken.create(target, schema as InjectionTokenSchemaType)
|
|
95
119
|
|
|
96
120
|
registry.set(injectableToken, scope, target, InjectableType.Class)
|
|
97
121
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export enum DIErrorCode {
|
|
2
|
+
FactoryNotFound = 'FactoryNotFound',
|
|
3
|
+
FactoryTokenNotResolved = 'FactoryTokenNotResolved',
|
|
4
|
+
InstanceNotFound = 'InstanceNotFound',
|
|
5
|
+
InstanceDestroying = 'InstanceDestroying',
|
|
6
|
+
UnknownError = 'UnknownError',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class DIError extends Error {
|
|
10
|
+
public readonly code: DIErrorCode
|
|
11
|
+
public readonly context?: Record<string, unknown>
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
code: DIErrorCode,
|
|
15
|
+
message: string,
|
|
16
|
+
context?: Record<string, unknown>,
|
|
17
|
+
) {
|
|
18
|
+
super(message)
|
|
19
|
+
this.name = 'DIError'
|
|
20
|
+
this.code = code
|
|
21
|
+
this.context = context
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Static factory methods for common error types
|
|
25
|
+
static factoryNotFound(name: string): DIError {
|
|
26
|
+
return new DIError(
|
|
27
|
+
DIErrorCode.FactoryNotFound,
|
|
28
|
+
`Factory ${name} not found`,
|
|
29
|
+
{ name },
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static factoryTokenNotResolved(token: string | symbol | unknown): DIError {
|
|
34
|
+
return new DIError(
|
|
35
|
+
DIErrorCode.FactoryTokenNotResolved,
|
|
36
|
+
`Factory token not resolved: ${token?.toString() ?? 'unknown'}`,
|
|
37
|
+
{ token },
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static instanceNotFound(name: string): DIError {
|
|
42
|
+
return new DIError(
|
|
43
|
+
DIErrorCode.InstanceNotFound,
|
|
44
|
+
`Instance ${name} not found`,
|
|
45
|
+
{ name },
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static instanceDestroying(name: string): DIError {
|
|
50
|
+
return new DIError(
|
|
51
|
+
DIErrorCode.InstanceDestroying,
|
|
52
|
+
`Instance ${name} destroying`,
|
|
53
|
+
{ name },
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static unknown(
|
|
58
|
+
message: string | Error,
|
|
59
|
+
context?: Record<string, unknown>,
|
|
60
|
+
): DIError {
|
|
61
|
+
if (message instanceof Error) {
|
|
62
|
+
return new DIError(DIErrorCode.UnknownError, message.message, {
|
|
63
|
+
...context,
|
|
64
|
+
parent: message,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
return new DIError(DIErrorCode.UnknownError, message, context)
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/errors/index.mts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
1
|
+
export * from './di-error.mjs'
|
|
2
|
+
|
|
3
|
+
// Legacy exports for backward compatibility (deprecated)
|
|
4
|
+
export { DIError as FactoryNotFound } from './di-error.mjs'
|
|
5
|
+
export { DIError as FactoryTokenNotResolved } from './di-error.mjs'
|
|
6
|
+
export { DIError as InstanceDestroying } from './di-error.mjs'
|
|
7
|
+
export { DIError as InstanceNotFound } from './di-error.mjs'
|
|
8
|
+
export { DIError as UnknownError } from './di-error.mjs'
|
|
9
|
+
export { DIErrorCode as ErrorsEnum } from './di-error.mjs'
|
package/src/injection-token.mts
CHANGED
|
@@ -3,6 +3,7 @@ import type { z, ZodObject, ZodOptional, ZodRecord } from 'zod/v4'
|
|
|
3
3
|
import type { FactoryContext } from './factory-context.mjs'
|
|
4
4
|
|
|
5
5
|
export type ClassType = new (...args: any[]) => any
|
|
6
|
+
export type ClassTypeWithoutArguments = new () => any
|
|
6
7
|
export type ClassTypeWithArgument<Arg> = new (arg: Arg) => any
|
|
7
8
|
export type ClassTypeWithOptionalArgument<Arg> = new (arg?: Arg) => any
|
|
8
9
|
|
package/src/injector.mts
CHANGED
|
@@ -9,6 +9,8 @@ export const asyncInject: Injectors['asyncInject'] =
|
|
|
9
9
|
|
|
10
10
|
export const inject: Injectors['inject'] = defaultInjectors.inject
|
|
11
11
|
|
|
12
|
+
export const optional: Injectors['optional'] = defaultInjectors.optional
|
|
13
|
+
|
|
12
14
|
export const wrapSyncInit: Injectors['wrapSyncInit'] =
|
|
13
15
|
defaultInjectors.wrapSyncInit
|
|
14
16
|
|