@navios/di 0.4.1 → 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 (128) 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 +447 -212
  73. package/lib/_tsup-dts-rollup.d.ts +447 -212
  74. package/lib/chunk-44F3LXW5.mjs +2043 -0
  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 +1199 -773
  79. package/lib/index.js.map +1 -1
  80. package/lib/index.mjs +4 -1599
  81. package/lib/index.mjs.map +1 -1
  82. package/lib/testing/index.d.mts +2 -0
  83. package/lib/testing/index.d.ts +2 -0
  84. package/lib/testing/index.js +2060 -0
  85. package/lib/testing/index.js.map +1 -0
  86. package/lib/testing/index.mjs +73 -0
  87. package/lib/testing/index.mjs.map +1 -0
  88. package/package.json +11 -1
  89. package/src/__tests__/container.spec.mts +47 -13
  90. package/src/__tests__/errors.spec.mts +53 -27
  91. package/src/__tests__/injectable.spec.mts +73 -0
  92. package/src/__tests__/request-scope.spec.mts +0 -2
  93. package/src/__tests__/service-locator-manager.spec.mts +12 -82
  94. package/src/__tests__/service-locator.spec.mts +1009 -1
  95. package/src/__type-tests__/inject.spec-d.mts +30 -7
  96. package/src/__type-tests__/injectable.spec-d.mts +76 -37
  97. package/src/base-instance-holder-manager.mts +2 -9
  98. package/src/container.mts +70 -10
  99. package/src/decorators/injectable.decorator.mts +29 -5
  100. package/src/errors/di-error.mts +69 -0
  101. package/src/errors/index.mts +9 -7
  102. package/src/injection-token.mts +1 -0
  103. package/src/injector.mts +2 -0
  104. package/src/instance-resolver.mts +559 -0
  105. package/src/request-context-holder.mts +0 -2
  106. package/src/request-context-manager.mts +149 -0
  107. package/src/service-invalidator.mts +429 -0
  108. package/src/service-locator-instance-holder.mts +0 -4
  109. package/src/service-locator-manager.mts +10 -40
  110. package/src/service-locator.mts +86 -782
  111. package/src/testing/README.md +80 -0
  112. package/src/testing/__tests__/test-container.spec.mts +173 -0
  113. package/src/testing/index.mts +1 -0
  114. package/src/testing/test-container.mts +120 -0
  115. package/src/token-processor.mts +174 -0
  116. package/src/utils/get-injectors.mts +161 -24
  117. package/src/utils/index.mts +0 -1
  118. package/src/utils/types.mts +12 -8
  119. package/tsup.config.mts +1 -1
  120. package/src/__tests__/defer.spec.mts +0 -166
  121. package/src/errors/errors.enum.mts +0 -8
  122. package/src/errors/factory-not-found.mts +0 -8
  123. package/src/errors/factory-token-not-resolved.mts +0 -10
  124. package/src/errors/instance-destroying.mts +0 -8
  125. package/src/errors/instance-expired.mts +0 -8
  126. package/src/errors/instance-not-found.mts +0 -8
  127. package/src/errors/unknown-error.mts +0 -15
  128. package/src/utils/defer.mts +0 -73
@@ -4,22 +4,45 @@ import type { FactoryContext } from '../factory-context.mjs'
4
4
  import type {
5
5
  BoundInjectionToken,
6
6
  ClassType,
7
+ ClassTypeWithArgument,
8
+ ClassTypeWithoutArguments,
7
9
  FactoryInjectionToken,
8
10
  InjectionToken,
9
11
  InjectionTokenSchemaType,
10
12
  } from '../injection-token.mjs'
11
- import type { Factorable } from '../interfaces/factory.interface.mjs'
12
- import type { InjectState, Join, UnionToArray } from './types.mjs'
13
+ import type {
14
+ Factorable,
15
+ FactorableWithArgs,
16
+ } from '../interfaces/factory.interface.mjs'
17
+ import type {
18
+ InjectRequest,
19
+ InjectState,
20
+ Join,
21
+ UnionToArray,
22
+ } from './types.mjs'
13
23
 
14
24
  import { InjectableTokenMeta } from '../symbols/index.mjs'
15
25
 
16
26
  export interface Injectors {
17
27
  // #1 Simple class
18
- asyncInject<T extends ClassType>(
28
+ asyncInject<T extends ClassTypeWithoutArguments>(
19
29
  token: T,
20
30
  ): InstanceType<T> extends Factorable<infer R>
21
31
  ? Promise<R>
22
32
  : Promise<InstanceType<T>>
33
+ asyncInject<Args, T extends ClassTypeWithArgument<Args>>(
34
+ token: T,
35
+ args: Args,
36
+ ): Promise<InstanceType<T>>
37
+ asyncInject<
38
+ Schema extends InjectionTokenSchemaType,
39
+ R,
40
+ T extends FactorableWithArgs<R, Schema>,
41
+ >(
42
+ token: T,
43
+ args: z.input<Schema>,
44
+ ): Promise<R>
45
+
23
46
  // #2 Token with required Schema
24
47
  asyncInject<T, S extends InjectionTokenSchemaType>(
25
48
  token: InjectionToken<T, S>,
@@ -41,9 +64,22 @@ export interface Injectors {
41
64
  asyncInject<T>(token: BoundInjectionToken<T, any>): Promise<T>
42
65
  asyncInject<T>(token: FactoryInjectionToken<T, any>): Promise<T>
43
66
 
44
- inject<T extends ClassType>(
67
+ inject<T extends ClassTypeWithoutArguments>(
45
68
  token: T,
46
69
  ): InstanceType<T> extends Factorable<infer R> ? R : InstanceType<T>
70
+ inject<Args, T extends ClassTypeWithArgument<Args>>(
71
+ token: T,
72
+ args: Args,
73
+ ): InstanceType<T>
74
+ inject<
75
+ Schema extends InjectionTokenSchemaType,
76
+ R,
77
+ T extends FactorableWithArgs<R, Schema>,
78
+ >(
79
+ token: T,
80
+ args: z.input<Schema>,
81
+ ): R
82
+
47
83
  inject<T, S extends InjectionTokenSchemaType>(
48
84
  token: InjectionToken<T, S>,
49
85
  args: z.input<S>,
@@ -63,6 +99,45 @@ export interface Injectors {
63
99
  inject<T>(token: BoundInjectionToken<T, any>): T
64
100
  inject<T>(token: FactoryInjectionToken<T, any>): T
65
101
 
102
+ /**
103
+ * Optional injection that returns null if the service fails to initialize
104
+ * or is not available. This is useful when you want to inject a service
105
+ * that may not be configured or may fail gracefully.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * class MyService {
110
+ * constructor() {
111
+ * const optionalService = optional(OptionalServiceToken)
112
+ * // optionalService will be null if initialization fails
113
+ * if (optionalService) {
114
+ * optionalService.doSomething()
115
+ * }
116
+ * }
117
+ * }
118
+ * ```
119
+ */
120
+ optional<T extends ClassType>(
121
+ token: T,
122
+ ): (InstanceType<T> extends Factorable<infer R> ? R : InstanceType<T>) | null
123
+ optional<T, S extends InjectionTokenSchemaType>(
124
+ token: InjectionToken<T, S>,
125
+ args: z.input<S>,
126
+ ): T | null
127
+ optional<T, S extends InjectionTokenSchemaType, R extends boolean>(
128
+ token: InjectionToken<T, S, R>,
129
+ ): R extends false
130
+ ? T | null
131
+ : S extends ZodType<infer Type>
132
+ ? `Error: Your token requires args: ${Join<
133
+ UnionToArray<keyof Type>,
134
+ ', '
135
+ >}`
136
+ : 'Error: Your token requires args'
137
+ optional<T>(token: InjectionToken<T, undefined>): T | null
138
+ optional<T>(token: BoundInjectionToken<T, any>): T | null
139
+ optional<T>(token: FactoryInjectionToken<T, any>): T | null
140
+
66
141
  wrapSyncInit(
67
142
  cb: () => any,
68
143
  ): (injectState?: InjectState) => [any, Promise<any>[], InjectState]
@@ -92,17 +167,10 @@ export function getInjectors() {
92
167
  let promiseCollector: null | ((promise: Promise<any>) => void) = null
93
168
  let injectState: InjectState | null = null
94
169
 
95
- function asyncInject(
96
- token:
97
- | ClassType
98
- | InjectionToken<any>
99
- | BoundInjectionToken<any, any>
100
- | FactoryInjectionToken<any, any>,
101
- args?: unknown,
102
- ) {
170
+ function getRequest(token: InjectionToken<any>, args?: unknown) {
103
171
  if (!injectState) {
104
172
  throw new Error(
105
- '[Injector] Trying to access inject outside of a injectable context',
173
+ '[Injector] Trying to make a request outside of a injectable context',
106
174
  )
107
175
  }
108
176
  if (injectState.isFrozen) {
@@ -113,17 +181,59 @@ export function getInjectors() {
113
181
  `[Injector] Wrong token order. Expected ${request.token.toString()} but got ${token.toString()}`,
114
182
  )
115
183
  }
116
- return request.promise
184
+ return request
117
185
  }
118
-
119
- const promise = getFactoryContext().inject(token as any, args as any)
120
- injectState.requests.push({
186
+ let result: any = null
187
+ let error: Error | null = null
188
+ const promise = getFactoryContext()
189
+ .inject(token as any, args as any)
190
+ .then((r) => {
191
+ result = r
192
+ return r
193
+ })
194
+ .catch((e) => {
195
+ // We don't throw here because we have a mechanism to handle errors
196
+ error = e
197
+ })
198
+ const request: InjectRequest = {
121
199
  token,
122
200
  promise,
123
- })
201
+ get result() {
202
+ return result
203
+ },
204
+ get error() {
205
+ return error
206
+ },
207
+ }
208
+ injectState.requests.push(request)
124
209
  injectState.currentIndex++
125
210
 
126
- return promise
211
+ return request
212
+ }
213
+
214
+ function asyncInject(
215
+ token:
216
+ | ClassType
217
+ | InjectionToken<any>
218
+ | BoundInjectionToken<any, any>
219
+ | FactoryInjectionToken<any, any>,
220
+ args?: unknown,
221
+ ) {
222
+ if (!injectState) {
223
+ throw new Error(
224
+ '[Injector] Trying to access inject outside of a injectable context',
225
+ )
226
+ }
227
+ // @ts-expect-error In case we have a class
228
+ const realToken = token[InjectableTokenMeta] ?? token
229
+ const request = getRequest(realToken, args)
230
+ return request.promise.then((result) => {
231
+ if (request.error) {
232
+ // We throw here because we want to fail the asyncInject call if the dependency fails to initialize
233
+ throw request.error
234
+ }
235
+ return result
236
+ })
127
237
  }
128
238
 
129
239
  function wrapSyncInit(cb: () => any) {
@@ -166,23 +276,33 @@ export function getInjectors() {
166
276
  // @ts-expect-error In case we have a class
167
277
  const realToken = token[InjectableTokenMeta] ?? token
168
278
 
279
+ if (!injectState) {
280
+ throw new Error(
281
+ '[Injector] Trying to access inject outside of a injectable context',
282
+ )
283
+ }
284
+
169
285
  const instance = getFactoryContext().locator.getSyncInstance(
170
286
  realToken,
171
287
  args,
172
288
  )
173
289
  if (!instance) {
290
+ const request = getRequest(realToken, args)
291
+ if (request.error) {
292
+ throw request.error
293
+ } else if (request.result) {
294
+ return request.result
295
+ }
174
296
  if (promiseCollector) {
175
- const promise = getFactoryContext().inject(realToken, args)
176
- promiseCollector(promise)
177
- } else {
178
- throw new Error(`[Injector] Cannot initiate ${realToken.toString()}`)
297
+ promiseCollector(request.promise)
179
298
  }
299
+ // Return a dynamic proxy that looks up the instance when accessed
180
300
  return new Proxy(
181
301
  {},
182
302
  {
183
303
  get() {
184
304
  throw new Error(
185
- `[Injector] Trying to access ${realToken.toString()} before it's initialized, please use asyncInject() instead of inject() or do not use the value outside of class methods`,
305
+ `[Injector] Trying to access ${realToken.toString()} before it's initialized, please move the code to a onServiceInit method`,
186
306
  )
187
307
  },
188
308
  },
@@ -191,9 +311,26 @@ export function getInjectors() {
191
311
  return instance as unknown as T
192
312
  }
193
313
 
314
+ function optional<
315
+ T,
316
+ Token extends
317
+ | InjectionToken<T>
318
+ | BoundInjectionToken<T, any>
319
+ | FactoryInjectionToken<T, any>,
320
+ S extends ZodObject | unknown = Token['schema'],
321
+ >(token: Token, args?: S extends ZodObject ? z.input<S> : never): T | null {
322
+ try {
323
+ return inject(token, args)
324
+ } catch {
325
+ // If injection fails, return null instead of throwing
326
+ return null
327
+ }
328
+ }
329
+
194
330
  const injectors: Injectors = {
195
331
  asyncInject,
196
332
  inject,
333
+ optional,
197
334
  wrapSyncInit,
198
335
  provideFactoryContext,
199
336
  } as Injectors
@@ -1,4 +1,3 @@
1
1
  export * from './get-injectors.mjs'
2
2
  export * from './get-injectable-token.mjs'
3
- export * from './defer.mjs'
4
3
  export * from './types.mjs'
@@ -37,16 +37,20 @@ export type UnionToArray<T, A extends unknown[] = []> =
37
37
  ? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
38
38
  : [T, ...A]
39
39
 
40
+ export type InjectRequest = {
41
+ token:
42
+ | InjectionToken<any>
43
+ | BoundInjectionToken<any, any>
44
+ | FactoryInjectionToken<any, any>
45
+ | ClassType
46
+ promise: Promise<any>
47
+ readonly result: any
48
+ readonly error: Error | null
49
+ }
50
+
40
51
  // InjectState interface for managing injection state
41
52
  export interface InjectState {
42
53
  currentIndex: number
43
54
  isFrozen: boolean
44
- requests: {
45
- token:
46
- | InjectionToken<any>
47
- | BoundInjectionToken<any, any>
48
- | FactoryInjectionToken<any, any>
49
- | ClassType
50
- promise: Promise<any>
51
- }[]
55
+ requests: InjectRequest[]
52
56
  }
package/tsup.config.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineConfig } from 'tsup'
2
2
 
3
3
  export default defineConfig({
4
- entry: ['src/index.mts'],
4
+ entry: ['src/index.mts', 'src/testing/index.mts'],
5
5
  outDir: 'lib',
6
6
  format: ['esm', 'cjs'],
7
7
  clean: true,
@@ -1,166 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { createDeferred, Deferred } from '../utils/defer.mjs'
4
-
5
- describe('Deferred', () => {
6
- describe('constructor', () => {
7
- it('should create a deferred promise', () => {
8
- const deferred = new Deferred<string>()
9
-
10
- expect(deferred.promise).toBeInstanceOf(Promise)
11
- expect(deferred.isResolved).toBe(false)
12
- expect(deferred.isRejected).toBe(false)
13
- expect(deferred.isSettled).toBe(false)
14
- })
15
- })
16
-
17
- describe('resolve', () => {
18
- it('should resolve the promise with given value', async () => {
19
- const deferred = new Deferred<string>()
20
- const testValue = 'test value'
21
-
22
- deferred.resolve(testValue)
23
- const result = await deferred.promise
24
-
25
- expect(result).toBe(testValue)
26
- expect(deferred.isResolved).toBe(true)
27
- expect(deferred.isRejected).toBe(false)
28
- expect(deferred.isSettled).toBe(true)
29
- })
30
-
31
- it('should throw error if already resolved', () => {
32
- const deferred = new Deferred<string>()
33
-
34
- deferred.resolve('test')
35
-
36
- expect(() => {
37
- deferred.resolve('test2')
38
- }).toThrow('Deferred promise has already been resolved or rejected')
39
- })
40
-
41
- it('should throw error if already rejected', async () => {
42
- const deferred = new Deferred<string>()
43
-
44
- deferred.reject(new Error('test error'))
45
-
46
- // Catch the rejection to avoid unhandled promise rejection
47
- try {
48
- await deferred.promise
49
- } catch (error) {
50
- // Expected to fail
51
- }
52
-
53
- expect(() => {
54
- deferred.resolve('test')
55
- }).toThrow('Deferred promise has already been resolved or rejected')
56
- })
57
- })
58
-
59
- describe('reject', () => {
60
- it('should reject the promise with given reason', async () => {
61
- const deferred = new Deferred<string>()
62
- const testError = new Error('test error')
63
-
64
- deferred.reject(testError)
65
-
66
- await expect(deferred.promise).rejects.toThrow('test error')
67
- expect(deferred.isResolved).toBe(false)
68
- expect(deferred.isRejected).toBe(true)
69
- expect(deferred.isSettled).toBe(true)
70
- })
71
-
72
- it('should reject with string reason', async () => {
73
- const deferred = new Deferred<string>()
74
-
75
- deferred.reject('string error')
76
-
77
- try {
78
- await deferred.promise
79
- expect.fail('Should have rejected')
80
- } catch (error) {
81
- expect(error).toBe('string error')
82
- }
83
- })
84
-
85
- it('should throw error if already resolved', () => {
86
- const deferred = new Deferred<string>()
87
-
88
- deferred.resolve('test')
89
-
90
- expect(() => {
91
- deferred.reject(new Error('test error'))
92
- }).toThrow('Deferred promise has already been resolved or rejected')
93
- })
94
-
95
- it('should throw error if already rejected', async () => {
96
- const deferred = new Deferred<string>()
97
-
98
- deferred.reject(new Error('first error'))
99
-
100
- // Catch the rejection to avoid unhandled promise rejection
101
- try {
102
- await deferred.promise
103
- } catch (error) {
104
- // Expected to fail
105
- }
106
-
107
- expect(() => {
108
- deferred.reject(new Error('second error'))
109
- }).toThrow('Deferred promise has already been resolved or rejected')
110
- })
111
- })
112
-
113
- describe('status getters', () => {
114
- it('should return correct status for unresolved promise', () => {
115
- const deferred = new Deferred<string>()
116
-
117
- expect(deferred.isResolved).toBe(false)
118
- expect(deferred.isRejected).toBe(false)
119
- expect(deferred.isSettled).toBe(false)
120
- })
121
-
122
- it('should return correct status after resolve', () => {
123
- const deferred = new Deferred<string>()
124
-
125
- deferred.resolve('test')
126
-
127
- expect(deferred.isResolved).toBe(true)
128
- expect(deferred.isRejected).toBe(false)
129
- expect(deferred.isSettled).toBe(true)
130
- })
131
-
132
- it('should return correct status after reject', async () => {
133
- const deferred = new Deferred<string>()
134
-
135
- deferred.reject(new Error('test'))
136
-
137
- // Catch the rejection to avoid unhandled promise rejection
138
- try {
139
- await deferred.promise
140
- } catch (error) {
141
- // Expected to fail
142
- }
143
-
144
- expect(deferred.isResolved).toBe(false)
145
- expect(deferred.isRejected).toBe(true)
146
- expect(deferred.isSettled).toBe(true)
147
- })
148
- })
149
- })
150
-
151
- describe('createDeferred', () => {
152
- it('should create a new Deferred instance', () => {
153
- const deferred = createDeferred<string>()
154
-
155
- expect(deferred).toBeInstanceOf(Deferred)
156
- expect(deferred.promise).toBeInstanceOf(Promise)
157
- })
158
-
159
- it('should create independent instances', () => {
160
- const deferred1 = createDeferred<string>()
161
- const deferred2 = createDeferred<string>()
162
-
163
- expect(deferred1).not.toBe(deferred2)
164
- expect(deferred1.promise).not.toBe(deferred2.promise)
165
- })
166
- })
@@ -1,8 +0,0 @@
1
- export enum ErrorsEnum {
2
- InstanceExpired = 'InstanceExpired',
3
- InstanceNotFound = 'InstanceNotFound',
4
- InstanceDestroying = 'InstanceDestroying',
5
- UnknownError = 'UnknownError',
6
- FactoryNotFound = 'FactoryNotFound',
7
- FactoryTokenNotResolved = 'FactoryTokenNotResolved',
8
- }
@@ -1,8 +0,0 @@
1
- import { ErrorsEnum } from './errors.enum.mjs'
2
-
3
- export class FactoryNotFound extends Error {
4
- code = ErrorsEnum.FactoryNotFound
5
- constructor(public name: string) {
6
- super(`Factory ${name} not found`)
7
- }
8
- }
@@ -1,10 +0,0 @@
1
- import type { ClassType } from '../injection-token.mjs'
2
-
3
- import { ErrorsEnum } from './errors.enum.mjs'
4
-
5
- export class FactoryTokenNotResolved extends Error {
6
- code = ErrorsEnum.FactoryTokenNotResolved
7
- constructor(name: string | symbol | ClassType) {
8
- super(`Factory token not resolved: ${name.toString()}`)
9
- }
10
- }
@@ -1,8 +0,0 @@
1
- import { ErrorsEnum } from './errors.enum.mjs'
2
-
3
- export class InstanceDestroying extends Error {
4
- code = ErrorsEnum.InstanceDestroying
5
- constructor(public name: string) {
6
- super(`Instance ${name} destroying`)
7
- }
8
- }
@@ -1,8 +0,0 @@
1
- import { ErrorsEnum } from './errors.enum.mjs'
2
-
3
- export class InstanceExpired extends Error {
4
- code = ErrorsEnum.InstanceExpired
5
- constructor(public name: string) {
6
- super(`Instance ${name} expired`)
7
- }
8
- }
@@ -1,8 +0,0 @@
1
- import { ErrorsEnum } from './errors.enum.mjs'
2
-
3
- export class InstanceNotFound extends Error {
4
- code = ErrorsEnum.InstanceNotFound
5
- constructor(public name: string) {
6
- super(`Instance ${name} not found`)
7
- }
8
- }
@@ -1,15 +0,0 @@
1
- import { ErrorsEnum } from './errors.enum.mjs'
2
-
3
- export class UnknownError extends Error {
4
- code = ErrorsEnum.UnknownError
5
- parent?: Error
6
-
7
- constructor(message: string | Error) {
8
- if (message instanceof Error) {
9
- super(message.message)
10
- this.parent = message
11
- return
12
- }
13
- super(message)
14
- }
15
- }
@@ -1,73 +0,0 @@
1
- /**
2
- * Creates a deferred promise that can be resolved or rejected externally.
3
- * This is useful for creating stub holders that can be fulfilled later.
4
- */
5
- export class Deferred<T> {
6
- public readonly promise: Promise<T>
7
- private _resolve!: (value: T) => void
8
- private _reject!: (reason?: any) => void
9
- private _isResolved = false
10
- private _isRejected = false
11
-
12
- constructor() {
13
- this.promise = new Promise<T>((resolve, reject) => {
14
- this._resolve = resolve
15
- this._reject = reject
16
- })
17
- }
18
-
19
- /**
20
- * Resolves the deferred promise with the given value.
21
- * @param value The value to resolve with
22
- * @throws Error if the promise has already been resolved or rejected
23
- */
24
- resolve(value: T): void {
25
- if (this._isResolved || this._isRejected) {
26
- throw new Error('Deferred promise has already been resolved or rejected')
27
- }
28
- this._isResolved = true
29
- this._resolve(value)
30
- }
31
-
32
- /**
33
- * Rejects the deferred promise with the given reason.
34
- * @param reason The reason for rejection
35
- * @throws Error if the promise has already been resolved or rejected
36
- */
37
- reject(reason?: any): void {
38
- if (this._isResolved || this._isRejected) {
39
- throw new Error('Deferred promise has already been resolved or rejected')
40
- }
41
- this._isRejected = true
42
- this._reject(reason)
43
- }
44
-
45
- /**
46
- * Returns true if the promise has been resolved.
47
- */
48
- get isResolved(): boolean {
49
- return this._isResolved
50
- }
51
-
52
- /**
53
- * Returns true if the promise has been rejected.
54
- */
55
- get isRejected(): boolean {
56
- return this._isRejected
57
- }
58
-
59
- /**
60
- * Returns true if the promise has been settled (resolved or rejected).
61
- */
62
- get isSettled(): boolean {
63
- return this._isResolved || this._isRejected
64
- }
65
- }
66
-
67
- /**
68
- * Creates a new deferred promise.
69
- * @returns A new Deferred instance
70
- */
71
- export function createDeferred<T>(): Deferred<T> {
72
- return new Deferred<T>()
73
- }