@navios/di 0.4.2 → 0.5.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.
Files changed (120) hide show
  1. package/README.md +211 -1
  2. package/coverage/clover.xml +1912 -1277
  3. package/coverage/coverage-final.json +37 -28
  4. package/coverage/docs/examples/basic-usage.mts.html +1 -1
  5. package/coverage/docs/examples/factory-pattern.mts.html +1 -1
  6. package/coverage/docs/examples/index.html +1 -1
  7. package/coverage/docs/examples/injection-tokens.mts.html +1 -1
  8. package/coverage/docs/examples/request-scope-example.mts.html +1 -1
  9. package/coverage/docs/examples/service-lifecycle.mts.html +1 -1
  10. package/coverage/index.html +71 -41
  11. package/coverage/lib/_tsup-dts-rollup.d.mts.html +682 -43
  12. package/coverage/lib/index.d.mts.html +7 -4
  13. package/coverage/lib/index.html +5 -5
  14. package/coverage/lib/testing/index.d.mts.html +91 -0
  15. package/coverage/lib/testing/index.html +116 -0
  16. package/coverage/src/base-instance-holder-manager.mts.html +589 -0
  17. package/coverage/src/container.mts.html +257 -74
  18. package/coverage/src/decorators/factory.decorator.mts.html +1 -1
  19. package/coverage/src/decorators/index.html +1 -1
  20. package/coverage/src/decorators/index.mts.html +1 -1
  21. package/coverage/src/decorators/injectable.decorator.mts.html +20 -20
  22. package/coverage/src/enums/index.html +1 -1
  23. package/coverage/src/enums/index.mts.html +1 -1
  24. package/coverage/src/enums/injectable-scope.enum.mts.html +1 -1
  25. package/coverage/src/enums/injectable-type.enum.mts.html +1 -1
  26. package/coverage/src/errors/di-error.mts.html +292 -0
  27. package/coverage/src/errors/errors.enum.mts.html +30 -21
  28. package/coverage/src/errors/factory-not-found.mts.html +31 -22
  29. package/coverage/src/errors/factory-token-not-resolved.mts.html +29 -26
  30. package/coverage/src/errors/index.html +56 -41
  31. package/coverage/src/errors/index.mts.html +15 -9
  32. package/coverage/src/errors/instance-destroying.mts.html +31 -22
  33. package/coverage/src/errors/instance-expired.mts.html +31 -22
  34. package/coverage/src/errors/instance-not-found.mts.html +31 -22
  35. package/coverage/src/errors/unknown-error.mts.html +31 -43
  36. package/coverage/src/event-emitter.mts.html +14 -14
  37. package/coverage/src/factory-context.mts.html +1 -1
  38. package/coverage/src/index.html +121 -46
  39. package/coverage/src/index.mts.html +7 -4
  40. package/coverage/src/injection-token.mts.html +28 -28
  41. package/coverage/src/injector.mts.html +1 -1
  42. package/coverage/src/instance-resolver.mts.html +1762 -0
  43. package/coverage/src/interfaces/factory.interface.mts.html +1 -1
  44. package/coverage/src/interfaces/index.html +1 -1
  45. package/coverage/src/interfaces/index.mts.html +1 -1
  46. package/coverage/src/interfaces/on-service-destroy.interface.mts.html +1 -1
  47. package/coverage/src/interfaces/on-service-init.interface.mts.html +1 -1
  48. package/coverage/src/registry.mts.html +28 -28
  49. package/coverage/src/request-context-holder.mts.html +183 -102
  50. package/coverage/src/request-context-manager.mts.html +532 -0
  51. package/coverage/src/service-instantiator.mts.html +49 -49
  52. package/coverage/src/service-invalidator.mts.html +1372 -0
  53. package/coverage/src/service-locator-event-bus.mts.html +48 -48
  54. package/coverage/src/service-locator-instance-holder.mts.html +2 -14
  55. package/coverage/src/service-locator-manager.mts.html +71 -335
  56. package/coverage/src/service-locator.mts.html +240 -2328
  57. package/coverage/src/symbols/index.html +1 -1
  58. package/coverage/src/symbols/index.mts.html +1 -1
  59. package/coverage/src/symbols/injectable-token.mts.html +1 -1
  60. package/coverage/src/testing/index.html +131 -0
  61. package/coverage/src/testing/index.mts.html +88 -0
  62. package/coverage/src/testing/test-container.mts.html +445 -0
  63. package/coverage/src/token-processor.mts.html +607 -0
  64. package/coverage/src/utils/defer.mts.html +28 -214
  65. package/coverage/src/utils/get-injectable-token.mts.html +7 -7
  66. package/coverage/src/utils/get-injectors.mts.html +99 -99
  67. package/coverage/src/utils/index.html +15 -15
  68. package/coverage/src/utils/index.mts.html +4 -7
  69. package/coverage/src/utils/types.mts.html +1 -1
  70. package/docs/injectable.md +51 -11
  71. package/docs/scopes.md +63 -29
  72. package/lib/_tsup-dts-rollup.d.mts +376 -213
  73. package/lib/_tsup-dts-rollup.d.ts +376 -213
  74. package/lib/{chunk-3NLYPYBY.mjs → chunk-44F3LXW5.mjs} +1021 -605
  75. package/lib/chunk-44F3LXW5.mjs.map +1 -0
  76. package/lib/index.d.mts +6 -4
  77. package/lib/index.d.ts +6 -4
  78. package/lib/index.js +1192 -776
  79. package/lib/index.js.map +1 -1
  80. package/lib/index.mjs +2 -2
  81. package/lib/testing/index.js +1258 -840
  82. package/lib/testing/index.js.map +1 -1
  83. package/lib/testing/index.mjs +1 -1
  84. package/package.json +1 -1
  85. package/src/__tests__/container.spec.mts +47 -13
  86. package/src/__tests__/errors.spec.mts +53 -27
  87. package/src/__tests__/injectable.spec.mts +73 -0
  88. package/src/__tests__/request-scope.spec.mts +0 -2
  89. package/src/__tests__/service-locator-manager.spec.mts +12 -82
  90. package/src/__tests__/service-locator.spec.mts +1009 -1
  91. package/src/__type-tests__/inject.spec-d.mts +30 -7
  92. package/src/__type-tests__/injectable.spec-d.mts +76 -37
  93. package/src/base-instance-holder-manager.mts +2 -9
  94. package/src/container.mts +61 -9
  95. package/src/decorators/injectable.decorator.mts +29 -5
  96. package/src/errors/di-error.mts +69 -0
  97. package/src/errors/index.mts +9 -7
  98. package/src/injection-token.mts +1 -0
  99. package/src/injector.mts +2 -0
  100. package/src/instance-resolver.mts +559 -0
  101. package/src/request-context-holder.mts +0 -2
  102. package/src/request-context-manager.mts +149 -0
  103. package/src/service-invalidator.mts +429 -0
  104. package/src/service-locator-instance-holder.mts +0 -4
  105. package/src/service-locator-manager.mts +10 -40
  106. package/src/service-locator.mts +86 -782
  107. package/src/token-processor.mts +174 -0
  108. package/src/utils/get-injectors.mts +161 -24
  109. package/src/utils/index.mts +0 -1
  110. package/src/utils/types.mts +12 -8
  111. package/lib/chunk-3NLYPYBY.mjs.map +0 -1
  112. package/src/__tests__/defer.spec.mts +0 -166
  113. package/src/errors/errors.enum.mts +0 -8
  114. package/src/errors/factory-not-found.mts +0 -8
  115. package/src/errors/factory-token-not-resolved.mts +0 -10
  116. package/src/errors/instance-destroying.mts +0 -8
  117. package/src/errors/instance-expired.mts +0 -8
  118. package/src/errors/instance-not-found.mts +0 -8
  119. package/src/errors/unknown-error.mts +0 -15
  120. 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
- test('#1 Classes', async () => {
61
- @Injectable()
62
- class Foo {
63
- makeFoo() {
64
- return 'foo'
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
- assertType<Foo>(await asyncInject(Foo))
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
- // #2 it's required in token but optional in class allowed
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
- // #2 optional value but class accepts it
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
- // #2 optional value and class accepts it
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
- // #2 compatible schemas
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
- // #2 compatible schemas
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
- // #2 typed token and required argument
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
- // #2 typed token and required argument
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
- // #2 should fail if not compatible
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
- // #2 should fail if not compatible
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
- // #2 typed token without schema
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
- // #2 typed token without schema fail if not compatible
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
- // #1 Injectable w/o decorators enabled in project
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
- // #2 it's required in token but optional in class allowed
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
- // #2 optional value but class accepts it
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
- // #2 optional value and class accepts it
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
- // #2 compatible schemas
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
- // #2 compatible schemas
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
- // #2 typed token and required argument
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
- // #2 typed token and required argument
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
- // #2 should fail if not compatible
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
- // #2 should fail if not compatible
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
- // #2 typed token without schema
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
- // #2 typed token without schema fail if not compatible
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 createDeferred<[undefined, Instance]>>,
94
+ ReturnType<typeof Promise.withResolvers<[undefined, Instance]>>,
98
95
  ServiceLocatorInstanceHolder<Instance>,
99
96
  ] {
100
- const deferred = createDeferred<[undefined, Instance]>()
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 holderMap = this.serviceLocator
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.getManager().clear()
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 ClassType>(
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 ClassType>(target: T, context?: ClassDecoratorContext) => T
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 ClassType>(target: T, context?: ClassDecoratorContext) => T
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
+ }
@@ -1,7 +1,9 @@
1
- export * from './errors.enum.mjs'
2
- export * from './factory-not-found.mjs'
3
- export * from './factory-token-not-resolved.mjs'
4
- export * from './instance-destroying.mjs'
5
- export * from './instance-expired.mjs'
6
- export * from './instance-not-found.mjs'
7
- export * from './unknown-error.mjs'
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'
@@ -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