@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.
- 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-44F3LXW5.mjs} +1021 -605
- 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 +1192 -776
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +2 -2
- package/lib/testing/index.js +1258 -840
- package/lib/testing/index.js.map +1 -1
- package/lib/testing/index.mjs +1 -1
- package/package.json +1 -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 +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-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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
3
|
+
|
|
4
|
+
import type { FactoryContext } from './factory-context.mjs'
|
|
5
|
+
import type {
|
|
6
|
+
AnyInjectableType,
|
|
7
|
+
InjectionTokenType,
|
|
8
|
+
} from './injection-token.mjs'
|
|
9
|
+
import type { RequestContextHolder } from './request-context-holder.mjs'
|
|
10
|
+
import type { ServiceLocator } from './service-locator.mjs'
|
|
11
|
+
|
|
12
|
+
import { DIError } from './errors/index.mjs'
|
|
13
|
+
import {
|
|
14
|
+
BoundInjectionToken,
|
|
15
|
+
FactoryInjectionToken,
|
|
16
|
+
InjectionToken,
|
|
17
|
+
} from './injection-token.mjs'
|
|
18
|
+
import { getInjectableToken } from './utils/index.mjs'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* TokenProcessor handles token validation, resolution, and instance name generation.
|
|
22
|
+
* Extracted from ServiceLocator to improve separation of concerns.
|
|
23
|
+
*/
|
|
24
|
+
export class TokenProcessor {
|
|
25
|
+
constructor(private readonly logger: Console | null = null) {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validates and resolves token arguments, handling factory token resolution and validation.
|
|
29
|
+
*/
|
|
30
|
+
validateAndResolveTokenArgs(
|
|
31
|
+
token: AnyInjectableType,
|
|
32
|
+
args?: any,
|
|
33
|
+
): [
|
|
34
|
+
DIError | undefined,
|
|
35
|
+
{ actualToken: InjectionTokenType; validatedArgs?: any },
|
|
36
|
+
] {
|
|
37
|
+
let actualToken = token as InjectionToken<any, any>
|
|
38
|
+
if (typeof token === 'function') {
|
|
39
|
+
actualToken = getInjectableToken(token)
|
|
40
|
+
}
|
|
41
|
+
let realArgs = args
|
|
42
|
+
if (actualToken instanceof BoundInjectionToken) {
|
|
43
|
+
realArgs = actualToken.value
|
|
44
|
+
} else if (actualToken instanceof FactoryInjectionToken) {
|
|
45
|
+
if (actualToken.resolved) {
|
|
46
|
+
realArgs = actualToken.value
|
|
47
|
+
} else {
|
|
48
|
+
return [DIError.factoryTokenNotResolved(token.name), { actualToken }]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!actualToken.schema) {
|
|
52
|
+
return [undefined, { actualToken, validatedArgs: realArgs }]
|
|
53
|
+
}
|
|
54
|
+
const validatedArgs = actualToken.schema?.safeParse(realArgs)
|
|
55
|
+
if (validatedArgs && !validatedArgs.success) {
|
|
56
|
+
this.logger?.error(
|
|
57
|
+
`[TokenProcessor]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`,
|
|
58
|
+
validatedArgs.error,
|
|
59
|
+
)
|
|
60
|
+
return [DIError.unknown(validatedArgs.error), { actualToken }]
|
|
61
|
+
}
|
|
62
|
+
return [undefined, { actualToken, validatedArgs: validatedArgs?.data }]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generates a unique instance name based on token and arguments.
|
|
67
|
+
*/
|
|
68
|
+
generateInstanceName(token: InjectionTokenType, args: any): string {
|
|
69
|
+
if (!args) {
|
|
70
|
+
return token.toString()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const formattedArgs = Object.entries(args)
|
|
74
|
+
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
|
|
75
|
+
.map(([key, value]) => `${key}=${this.formatArgValue(value)}`)
|
|
76
|
+
.join(',')
|
|
77
|
+
|
|
78
|
+
return `${token.toString()}:${formattedArgs.replaceAll(/"/g, '').replaceAll(/:/g, '=')}`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Formats a single argument value for instance name generation.
|
|
83
|
+
*/
|
|
84
|
+
formatArgValue(value: any): string {
|
|
85
|
+
if (typeof value === 'function') {
|
|
86
|
+
return `fn_${value.name}(${value.length})`
|
|
87
|
+
}
|
|
88
|
+
if (typeof value === 'symbol') {
|
|
89
|
+
return value.toString()
|
|
90
|
+
}
|
|
91
|
+
return JSON.stringify(value).slice(0, 40)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Creates a factory context for dependency injection during service instantiation.
|
|
96
|
+
* @param serviceLocator Reference to the service locator for dependency resolution
|
|
97
|
+
*/
|
|
98
|
+
createFactoryContext(
|
|
99
|
+
serviceLocator: ServiceLocator, // ServiceLocator reference for dependency resolution
|
|
100
|
+
): FactoryContext & {
|
|
101
|
+
getDestroyListeners: () => (() => void)[]
|
|
102
|
+
deps: Set<string>
|
|
103
|
+
} {
|
|
104
|
+
const destroyListeners = new Set<() => void>()
|
|
105
|
+
const deps = new Set<string>()
|
|
106
|
+
|
|
107
|
+
function addDestroyListener(listener: () => void) {
|
|
108
|
+
destroyListeners.add(listener)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getDestroyListeners() {
|
|
112
|
+
return Array.from(destroyListeners)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
// @ts-expect-error This is correct type
|
|
117
|
+
async inject(token, args) {
|
|
118
|
+
// Fall back to normal resolution
|
|
119
|
+
const [error, instance] = await serviceLocator.getInstance(
|
|
120
|
+
token,
|
|
121
|
+
args,
|
|
122
|
+
({ instanceName }: { instanceName: string }) => {
|
|
123
|
+
deps.add(instanceName)
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
if (error) {
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
return instance
|
|
130
|
+
},
|
|
131
|
+
addDestroyListener,
|
|
132
|
+
getDestroyListeners,
|
|
133
|
+
locator: serviceLocator,
|
|
134
|
+
deps,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Tries to get a pre-prepared instance from request contexts.
|
|
140
|
+
*/
|
|
141
|
+
tryGetPrePreparedInstance(
|
|
142
|
+
instanceName: string,
|
|
143
|
+
contextHolder: RequestContextHolder | undefined,
|
|
144
|
+
deps: Set<string>,
|
|
145
|
+
currentRequestContext: RequestContextHolder | null,
|
|
146
|
+
): any {
|
|
147
|
+
// Check provided context holder first (if has higher priority)
|
|
148
|
+
if (contextHolder && contextHolder.priority > 0) {
|
|
149
|
+
const prePreparedInstance = contextHolder.get(instanceName)?.instance
|
|
150
|
+
if (prePreparedInstance !== undefined) {
|
|
151
|
+
this.logger?.debug(
|
|
152
|
+
`[TokenProcessor] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`,
|
|
153
|
+
)
|
|
154
|
+
deps.add(instanceName)
|
|
155
|
+
return prePreparedInstance
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check current request context (if different from provided contextHolder)
|
|
160
|
+
if (currentRequestContext && currentRequestContext !== contextHolder) {
|
|
161
|
+
const prePreparedInstance =
|
|
162
|
+
currentRequestContext.get(instanceName)?.instance
|
|
163
|
+
if (prePreparedInstance !== undefined) {
|
|
164
|
+
this.logger?.debug(
|
|
165
|
+
`[TokenProcessor] Using pre-prepared instance ${instanceName} from current request context ${currentRequestContext.requestId}`,
|
|
166
|
+
)
|
|
167
|
+
deps.add(instanceName)
|
|
168
|
+
return prePreparedInstance
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return undefined
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -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
|
}
|