@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.
- 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 +447 -212
- package/lib/_tsup-dts-rollup.d.ts +447 -212
- package/lib/chunk-44F3LXW5.mjs +2043 -0
- package/lib/chunk-44F3LXW5.mjs.map +1 -0
- package/lib/index.d.mts +6 -4
- package/lib/index.d.ts +6 -4
- package/lib/index.js +1199 -773
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +4 -1599
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.d.mts +2 -0
- package/lib/testing/index.d.ts +2 -0
- package/lib/testing/index.js +2060 -0
- package/lib/testing/index.js.map +1 -0
- package/lib/testing/index.mjs +73 -0
- package/lib/testing/index.mjs.map +1 -0
- package/package.json +11 -1
- 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-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 +70 -10
- 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-instance-holder.mts +0 -4
- package/src/service-locator-manager.mts +10 -40
- package/src/service-locator.mts +86 -782
- package/src/testing/README.md +80 -0
- package/src/testing/__tests__/test-container.spec.mts +173 -0
- package/src/testing/index.mts +1 -0
- package/src/testing/test-container.mts +120 -0
- 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/tsup.config.mts +1 -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
|
@@ -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 {
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
184
|
+
return request
|
|
117
185
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
package/src/utils/index.mts
CHANGED
package/src/utils/types.mts
CHANGED
|
@@ -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,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,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,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
|
-
}
|
package/src/utils/defer.mts
DELETED
|
@@ -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
|
-
}
|