@nmtjs/core 0.7.3 → 0.7.5
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/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/container.js +56 -106
- package/dist/container.js.map +1 -1
- package/dist/injectables.js +171 -3
- package/dist/injectables.js.map +1 -1
- package/dist/registry.js +1 -1
- package/dist/registry.js.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +5 -0
- package/src/container.ts +96 -274
- package/src/injectables.ts +436 -3
- package/src/registry.ts +3 -3
package/src/container.ts
CHANGED
|
@@ -1,122 +1,27 @@
|
|
|
1
|
-
import { type Async, tryCaptureStackTrace } from '@nmtjs/common'
|
|
2
|
-
import {
|
|
3
|
-
kFactoryInjectable,
|
|
4
|
-
kInjectable,
|
|
5
|
-
kLazyInjectable,
|
|
6
|
-
kOptionalDependency,
|
|
7
|
-
kValueInjectable,
|
|
8
|
-
} from './constants.ts'
|
|
9
1
|
import { Scope } from './enums.ts'
|
|
2
|
+
import {
|
|
3
|
+
type AnyInjectable,
|
|
4
|
+
CoreInjectables,
|
|
5
|
+
compareScope,
|
|
6
|
+
createValueInjectable,
|
|
7
|
+
type Dependencies,
|
|
8
|
+
type DependencyContext,
|
|
9
|
+
getDepedencencyInjectable,
|
|
10
|
+
isClassInjectable,
|
|
11
|
+
isFactoryInjectable,
|
|
12
|
+
isInjectable,
|
|
13
|
+
isLazyInjectable,
|
|
14
|
+
isOptionalInjectable,
|
|
15
|
+
isValueInjectable,
|
|
16
|
+
type ResolveInjectableType,
|
|
17
|
+
} from './injectables.ts'
|
|
10
18
|
import type { Logger } from './logger.ts'
|
|
11
19
|
import type { Registry } from './registry.ts'
|
|
12
20
|
|
|
13
|
-
const ScopeStrictness = {
|
|
14
|
-
[Scope.Global]: 0,
|
|
15
|
-
[Scope.Connection]: 1,
|
|
16
|
-
[Scope.Call]: 2,
|
|
17
|
-
[Scope.Transient]: 3,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type DependencyOptional<T extends AnyInjectable = AnyInjectable> = {
|
|
21
|
-
[kOptionalDependency]: any
|
|
22
|
-
injectable: T
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type Depedency = DependencyOptional | AnyInjectable
|
|
26
|
-
|
|
27
|
-
export type Dependencies = Record<string, Depedency>
|
|
28
|
-
|
|
29
|
-
export type ResolveInjectableType<T extends AnyInjectable> =
|
|
30
|
-
T extends Injectable<infer Type, any, any> ? Type : never
|
|
31
|
-
|
|
32
|
-
export interface Dependant<Deps extends Dependencies = Dependencies> {
|
|
33
|
-
dependencies: Deps
|
|
34
|
-
label?: string
|
|
35
|
-
stack?: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type DependencyInjectable<T extends Depedency> = T extends AnyInjectable
|
|
39
|
-
? T
|
|
40
|
-
: T extends DependencyOptional
|
|
41
|
-
? T['injectable']
|
|
42
|
-
: never
|
|
43
|
-
|
|
44
|
-
export type DependencyContext<Deps extends Dependencies> = {
|
|
45
|
-
readonly [K in keyof Deps as Deps[K] extends AnyInjectable
|
|
46
|
-
? K
|
|
47
|
-
: never]: Deps[K] extends AnyInjectable
|
|
48
|
-
? ResolveInjectableType<Deps[K]>
|
|
49
|
-
: never
|
|
50
|
-
} & {
|
|
51
|
-
readonly [K in keyof Deps as Deps[K] extends DependencyOptional
|
|
52
|
-
? K
|
|
53
|
-
: never]?: Deps[K] extends DependencyOptional
|
|
54
|
-
? ResolveInjectableType<Deps[K]['injectable']>
|
|
55
|
-
: never
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export type InjectableFactoryType<
|
|
59
|
-
InjectableType,
|
|
60
|
-
InjectableDeps extends Dependencies,
|
|
61
|
-
> = (context: DependencyContext<InjectableDeps>) => Async<InjectableType>
|
|
62
|
-
|
|
63
|
-
export type InjectablePickType<Input, Output> = (injectable: Input) => Output
|
|
64
|
-
|
|
65
|
-
export type InjectableDisposeType<
|
|
66
|
-
InjectableType,
|
|
67
|
-
InjectableDeps extends Dependencies,
|
|
68
|
-
> = (
|
|
69
|
-
instance: InjectableType,
|
|
70
|
-
context: DependencyContext<InjectableDeps>,
|
|
71
|
-
) => any
|
|
72
|
-
|
|
73
|
-
export interface LazyInjectable<T, S extends Scope = Scope.Global>
|
|
74
|
-
extends Dependant<{}> {
|
|
75
|
-
scope: S
|
|
76
|
-
[kInjectable]: any
|
|
77
|
-
[kLazyInjectable]: T
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface ValueInjectable<T> extends Dependant<{}> {
|
|
81
|
-
scope: Scope.Global
|
|
82
|
-
value: T
|
|
83
|
-
[kInjectable]: any
|
|
84
|
-
[kValueInjectable]: any
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface FactoryInjectable<
|
|
88
|
-
T,
|
|
89
|
-
D extends Dependencies = {},
|
|
90
|
-
S extends Scope = Scope.Global,
|
|
91
|
-
P = T,
|
|
92
|
-
> extends Dependant<D> {
|
|
93
|
-
scope: S
|
|
94
|
-
factory: InjectableFactoryType<P, D>
|
|
95
|
-
pick: InjectablePickType<P, T>
|
|
96
|
-
dispose?: InjectableDisposeType<P, D>
|
|
97
|
-
[kInjectable]: any
|
|
98
|
-
[kFactoryInjectable]: any
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export type Injectable<
|
|
102
|
-
InjectableValue = any,
|
|
103
|
-
InjectableDeps extends Dependencies = {},
|
|
104
|
-
InjectableScope extends Scope = Scope,
|
|
105
|
-
> =
|
|
106
|
-
| LazyInjectable<InjectableValue, InjectableScope>
|
|
107
|
-
| ValueInjectable<InjectableValue>
|
|
108
|
-
| FactoryInjectable<InjectableValue, InjectableDeps, InjectableScope, any>
|
|
109
|
-
|
|
110
|
-
export type AnyInjectable<T = any, S extends Scope = Scope> = Injectable<
|
|
111
|
-
T,
|
|
112
|
-
any,
|
|
113
|
-
S
|
|
114
|
-
>
|
|
115
|
-
|
|
116
21
|
export class Container {
|
|
117
22
|
readonly instances = new Map<
|
|
118
23
|
AnyInjectable,
|
|
119
|
-
{ instance: any; picked
|
|
24
|
+
{ instance: any; picked?: any; context?: any }
|
|
120
25
|
>()
|
|
121
26
|
private readonly resolvers = new Map<AnyInjectable, Promise<any>>()
|
|
122
27
|
private readonly injectables = new Set<AnyInjectable>()
|
|
@@ -133,6 +38,7 @@ export class Container {
|
|
|
133
38
|
if ((scope as any) === Scope.Transient) {
|
|
134
39
|
throw new Error('Invalid scope')
|
|
135
40
|
}
|
|
41
|
+
this.provide(CoreInjectables.inject, createInjectFunction(this))
|
|
136
42
|
}
|
|
137
43
|
|
|
138
44
|
async load() {
|
|
@@ -267,7 +173,7 @@ export class Container {
|
|
|
267
173
|
throw new Error('Invalid scope: dependant is looser than injectable')
|
|
268
174
|
}
|
|
269
175
|
|
|
270
|
-
if (
|
|
176
|
+
if (isValueInjectable(injectable)) {
|
|
271
177
|
return Promise.resolve(injectable.value)
|
|
272
178
|
} else if (
|
|
273
179
|
this.parent?.contains(injectable) ||
|
|
@@ -291,52 +197,78 @@ export class Container {
|
|
|
291
197
|
} else if (this.resolvers.has(injectable)) {
|
|
292
198
|
return this.resolvers.get(injectable)!
|
|
293
199
|
} else {
|
|
294
|
-
const isLazy =
|
|
200
|
+
const isLazy = isLazyInjectable(injectable)
|
|
295
201
|
|
|
296
202
|
if (isLazy) {
|
|
297
|
-
const isOptional =
|
|
203
|
+
const isOptional = isOptionalInjectable(injectable)
|
|
298
204
|
if (isOptional) return Promise.resolve(undefined as any)
|
|
299
205
|
return Promise.reject(
|
|
300
206
|
new Error(
|
|
301
207
|
`No instance provided for ${label || 'an'} injectable:\n${stack}`,
|
|
302
208
|
),
|
|
303
209
|
)
|
|
304
|
-
} else if (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
210
|
+
} else if (isFactoryInjectable(injectable)) {
|
|
211
|
+
const resolution = this.createInjectableContext(
|
|
212
|
+
dependencies,
|
|
213
|
+
injectable,
|
|
214
|
+
)
|
|
215
|
+
.then((context) =>
|
|
216
|
+
Promise.resolve(injectable.factory(context)).then((instance) => ({
|
|
217
|
+
instance,
|
|
218
|
+
context,
|
|
219
|
+
})),
|
|
220
|
+
)
|
|
221
|
+
.then(({ instance, context }) => {
|
|
222
|
+
const picked = injectable.pick(instance)
|
|
223
|
+
if (compareScope(this.scope, '>=', scope))
|
|
224
|
+
this.instances.set(injectable, { instance, picked, context })
|
|
225
|
+
if (scope !== Scope.Transient) this.resolvers.delete(injectable)
|
|
226
|
+
return picked
|
|
227
|
+
})
|
|
228
|
+
if (scope !== Scope.Transient)
|
|
229
|
+
this.resolvers.set(injectable, resolution)
|
|
230
|
+
return resolution
|
|
231
|
+
} else if (isClassInjectable(injectable)) {
|
|
232
|
+
const resolution = this.createInjectableContext(
|
|
233
|
+
dependencies,
|
|
234
|
+
injectable,
|
|
317
235
|
)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
236
|
+
.then((context) => {
|
|
237
|
+
const instance = new injectable(context)
|
|
238
|
+
return instance.$onCreate().then(() => instance)
|
|
239
|
+
})
|
|
240
|
+
.then((instance) => {
|
|
241
|
+
// const picked = injectable.pick(instance)
|
|
242
|
+
if (compareScope(this.scope, '>=', scope))
|
|
243
|
+
this.instances.set(injectable, {
|
|
244
|
+
instance,
|
|
245
|
+
picked: undefined,
|
|
246
|
+
context: undefined,
|
|
247
|
+
})
|
|
248
|
+
if (scope !== Scope.Transient) this.resolvers.delete(injectable)
|
|
249
|
+
return instance
|
|
250
|
+
})
|
|
251
|
+
if (scope !== Scope.Transient)
|
|
252
|
+
this.resolvers.set(injectable, resolution)
|
|
253
|
+
return resolution
|
|
254
|
+
} else {
|
|
255
|
+
throw new Error('Invalid injectable type')
|
|
256
|
+
}
|
|
328
257
|
}
|
|
329
258
|
}
|
|
330
259
|
}
|
|
331
260
|
|
|
332
261
|
private async disposeInjectable(injectable: AnyInjectable) {
|
|
333
262
|
try {
|
|
334
|
-
if (
|
|
263
|
+
if (isFactoryInjectable(injectable)) {
|
|
335
264
|
const { dispose } = injectable
|
|
336
265
|
if (dispose) {
|
|
337
266
|
const { instance, context } = this.instances.get(injectable)!
|
|
338
267
|
await dispose(instance, context)
|
|
339
268
|
}
|
|
269
|
+
} else if (isClassInjectable(injectable)) {
|
|
270
|
+
const { instance } = this.instances.get(injectable)!
|
|
271
|
+
await instance.$onDispose()
|
|
340
272
|
}
|
|
341
273
|
} catch (cause) {
|
|
342
274
|
const error = new Error(
|
|
@@ -350,146 +282,36 @@ export class Container {
|
|
|
350
282
|
}
|
|
351
283
|
}
|
|
352
284
|
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return leftScope < rightScope
|
|
369
|
-
case '>=':
|
|
370
|
-
return leftScope >= rightScope
|
|
371
|
-
case '<=':
|
|
372
|
-
return leftScope <= rightScope
|
|
373
|
-
default:
|
|
374
|
-
throw new Error('Invalid operator')
|
|
375
|
-
}
|
|
376
|
-
}
|
|
285
|
+
export function createInjectFunction(container: Container) {
|
|
286
|
+
return <T extends AnyInjectable>(
|
|
287
|
+
injectable: T,
|
|
288
|
+
context: InlineInjectionDependencies<T>,
|
|
289
|
+
) => {
|
|
290
|
+
const dependencies: Dependencies = {}
|
|
291
|
+
|
|
292
|
+
for (const key in context) {
|
|
293
|
+
const dep = context[key]
|
|
294
|
+
if (isInjectable(dep) || isOptionalInjectable(dep)) {
|
|
295
|
+
dependencies[key] = dep
|
|
296
|
+
} else {
|
|
297
|
+
dependencies[key] = createValueInjectable(dep)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
377
300
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
injectable: any,
|
|
383
|
-
): injectable is FactoryInjectable<any> => kFactoryInjectable in injectable
|
|
384
|
-
export const checkIsValueInjectable = (
|
|
385
|
-
injectable: any,
|
|
386
|
-
): injectable is ValueInjectable<any> => kValueInjectable in injectable
|
|
387
|
-
export const checkIsInjectable = (
|
|
388
|
-
injectable: any,
|
|
389
|
-
): injectable is AnyInjectable<any> => kInjectable in injectable
|
|
390
|
-
export const checkIsOptional = (
|
|
391
|
-
injectable: any,
|
|
392
|
-
): injectable is DependencyOptional<any> => kOptionalDependency in injectable
|
|
393
|
-
|
|
394
|
-
export function getInjectableScope(injectable: AnyInjectable) {
|
|
395
|
-
let scope = injectable.scope
|
|
396
|
-
const deps = Object.values(injectable.dependencies as Dependencies)
|
|
397
|
-
for (const dependency of deps) {
|
|
398
|
-
const injectable = getDepedencencyInjectable(dependency)
|
|
399
|
-
const dependencyScope = getInjectableScope(injectable)
|
|
400
|
-
if (compareScope(dependencyScope, '>', scope)) {
|
|
401
|
-
scope = dependencyScope
|
|
301
|
+
const newInjectable = {
|
|
302
|
+
...injectable,
|
|
303
|
+
dependencies,
|
|
304
|
+
scope: Scope.Transient,
|
|
402
305
|
}
|
|
403
|
-
}
|
|
404
|
-
return scope
|
|
405
|
-
}
|
|
406
306
|
|
|
407
|
-
|
|
408
|
-
dependency: Depedency,
|
|
409
|
-
): AnyInjectable {
|
|
410
|
-
if (kOptionalDependency in dependency) {
|
|
411
|
-
return dependency.injectable
|
|
307
|
+
return container.resolve(newInjectable)
|
|
412
308
|
}
|
|
413
|
-
return dependency
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
export function createOptionalInjectable<T extends AnyInjectable>(
|
|
417
|
-
injectable: T,
|
|
418
|
-
) {
|
|
419
|
-
return {
|
|
420
|
-
[kOptionalDependency]: true,
|
|
421
|
-
injectable,
|
|
422
|
-
} as DependencyOptional<T>
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
export function createLazyInjectable<T, S extends Scope = Scope.Global>(
|
|
426
|
-
scope = Scope.Global as S,
|
|
427
|
-
label?: string,
|
|
428
|
-
): LazyInjectable<T, S> {
|
|
429
|
-
return Object.freeze({
|
|
430
|
-
scope,
|
|
431
|
-
dependencies: {},
|
|
432
|
-
label,
|
|
433
|
-
stack: tryCaptureStackTrace(),
|
|
434
|
-
[kInjectable]: true,
|
|
435
|
-
[kLazyInjectable]: true as unknown as T,
|
|
436
|
-
})
|
|
437
309
|
}
|
|
438
310
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return Object.freeze({
|
|
444
|
-
value,
|
|
445
|
-
scope: Scope.Global,
|
|
446
|
-
dependencies: {},
|
|
447
|
-
label,
|
|
448
|
-
stack: tryCaptureStackTrace(),
|
|
449
|
-
[kInjectable]: true,
|
|
450
|
-
[kValueInjectable]: true,
|
|
451
|
-
})
|
|
311
|
+
type InlineInjectionDependencies<T extends AnyInjectable> = {
|
|
312
|
+
[K in keyof T['dependencies']]:
|
|
313
|
+
| ResolveInjectableType<T['dependencies'][K]>
|
|
314
|
+
| AnyInjectable<ResolveInjectableType<T['dependencies'][K]>>
|
|
452
315
|
}
|
|
453
316
|
|
|
454
|
-
export
|
|
455
|
-
T,
|
|
456
|
-
D extends Dependencies = {},
|
|
457
|
-
S extends Scope = Scope.Global,
|
|
458
|
-
P = T,
|
|
459
|
-
>(
|
|
460
|
-
paramsOrFactory:
|
|
461
|
-
| {
|
|
462
|
-
dependencies?: D
|
|
463
|
-
scope?: S
|
|
464
|
-
pick?: InjectablePickType<P, T>
|
|
465
|
-
factory: InjectableFactoryType<P, D>
|
|
466
|
-
dispose?: InjectableDisposeType<P, D>
|
|
467
|
-
}
|
|
468
|
-
| InjectableFactoryType<P, D>,
|
|
469
|
-
label?: string,
|
|
470
|
-
): FactoryInjectable<null extends T ? P : T, D, S, P> {
|
|
471
|
-
const isFactory = typeof paramsOrFactory === 'function'
|
|
472
|
-
const params = isFactory ? { factory: paramsOrFactory } : paramsOrFactory
|
|
473
|
-
const injectable = {
|
|
474
|
-
dependencies: (params.dependencies ?? {}) as D,
|
|
475
|
-
scope: (params.scope ?? Scope.Global) as S,
|
|
476
|
-
factory: params.factory,
|
|
477
|
-
dispose: params.dispose,
|
|
478
|
-
pick: params.pick ?? ((instance: P) => instance as unknown as T),
|
|
479
|
-
label,
|
|
480
|
-
stack: tryCaptureStackTrace(),
|
|
481
|
-
[kInjectable]: true,
|
|
482
|
-
[kFactoryInjectable]: true,
|
|
483
|
-
}
|
|
484
|
-
const actualScope = getInjectableScope(injectable)
|
|
485
|
-
if (
|
|
486
|
-
!isFactory &&
|
|
487
|
-
params.scope &&
|
|
488
|
-
ScopeStrictness[actualScope] > ScopeStrictness[params.scope]
|
|
489
|
-
)
|
|
490
|
-
throw new Error(
|
|
491
|
-
`Invalid scope ${params.scope} for factory injectable: dependencies have stricter scope - ${actualScope}`,
|
|
492
|
-
)
|
|
493
|
-
injectable.scope = actualScope as unknown as S
|
|
494
|
-
return Object.freeze(injectable) as any
|
|
495
|
-
}
|
|
317
|
+
export type InjectFn = ReturnType<typeof createInjectFunction>
|