@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/injectables.ts
CHANGED
|
@@ -1,7 +1,440 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Async,
|
|
3
|
+
type ClassConstructor,
|
|
4
|
+
type ClassConstructorArgs,
|
|
5
|
+
type ClassInstance,
|
|
6
|
+
tryCaptureStackTrace,
|
|
7
|
+
} from '@nmtjs/common'
|
|
8
|
+
import {
|
|
9
|
+
kClassInjectable,
|
|
10
|
+
kFactoryInjectable,
|
|
11
|
+
kInjectable,
|
|
12
|
+
kLazyInjectable,
|
|
13
|
+
kOptionalDependency,
|
|
14
|
+
kValueInjectable,
|
|
15
|
+
} from './constants.ts'
|
|
16
|
+
import type { InjectFn } from './container.ts'
|
|
2
17
|
import { Scope } from './enums.ts'
|
|
3
18
|
import type { Logger } from './logger.ts'
|
|
4
19
|
|
|
5
|
-
const
|
|
20
|
+
const ScopeStrictness = {
|
|
21
|
+
[Scope.Global]: 0,
|
|
22
|
+
[Scope.Connection]: 1,
|
|
23
|
+
[Scope.Call]: 2,
|
|
24
|
+
[Scope.Transient]: 3,
|
|
25
|
+
}
|
|
6
26
|
|
|
7
|
-
export
|
|
27
|
+
export type DependencyOptional<T extends AnyInjectable = AnyInjectable> = {
|
|
28
|
+
[kOptionalDependency]: any
|
|
29
|
+
injectable: T
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type Depedency = DependencyOptional | AnyInjectable
|
|
33
|
+
|
|
34
|
+
export type Dependencies = Record<string, Depedency>
|
|
35
|
+
|
|
36
|
+
export type ResolveInjectableType<T extends AnyInjectable> =
|
|
37
|
+
T extends Injectable<infer Type, any, any> ? Type : never
|
|
38
|
+
|
|
39
|
+
export interface Dependant<Deps extends Dependencies = Dependencies> {
|
|
40
|
+
dependencies: Deps
|
|
41
|
+
label?: string
|
|
42
|
+
stack?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type DependencyInjectable<T extends Depedency> = T extends AnyInjectable
|
|
46
|
+
? T
|
|
47
|
+
: T extends DependencyOptional
|
|
48
|
+
? T['injectable']
|
|
49
|
+
: never
|
|
50
|
+
|
|
51
|
+
export type DependencyContext<Deps extends Dependencies> = {
|
|
52
|
+
readonly [K in keyof Deps as Deps[K] extends AnyInjectable
|
|
53
|
+
? K
|
|
54
|
+
: never]: Deps[K] extends AnyInjectable
|
|
55
|
+
? ResolveInjectableType<Deps[K]>
|
|
56
|
+
: never
|
|
57
|
+
} & {
|
|
58
|
+
readonly [K in keyof Deps as Deps[K] extends DependencyOptional
|
|
59
|
+
? K
|
|
60
|
+
: never]?: Deps[K] extends DependencyOptional
|
|
61
|
+
? ResolveInjectableType<Deps[K]['injectable']>
|
|
62
|
+
: never
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type InjectableFactoryType<
|
|
66
|
+
InjectableType,
|
|
67
|
+
InjectableDeps extends Dependencies,
|
|
68
|
+
> = (context: DependencyContext<InjectableDeps>) => Async<InjectableType>
|
|
69
|
+
|
|
70
|
+
export type InjectablePickType<Input, Output> = (injectable: Input) => Output
|
|
71
|
+
|
|
72
|
+
export type InjectableDisposeType<
|
|
73
|
+
InjectableType,
|
|
74
|
+
InjectableDeps extends Dependencies,
|
|
75
|
+
> = (
|
|
76
|
+
instance: InjectableType,
|
|
77
|
+
context: DependencyContext<InjectableDeps>,
|
|
78
|
+
) => any
|
|
79
|
+
|
|
80
|
+
export interface LazyInjectable<T, S extends Scope = Scope.Global>
|
|
81
|
+
extends Dependant<{}> {
|
|
82
|
+
scope: S
|
|
83
|
+
[kInjectable]: any
|
|
84
|
+
[kLazyInjectable]: T
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ValueInjectable<T> extends Dependant<{}> {
|
|
88
|
+
scope: Scope.Global
|
|
89
|
+
value: T
|
|
90
|
+
[kInjectable]: any
|
|
91
|
+
[kValueInjectable]: any
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface FactoryInjectable<
|
|
95
|
+
T,
|
|
96
|
+
D extends Dependencies = {},
|
|
97
|
+
S extends Scope = Scope.Global,
|
|
98
|
+
P = T,
|
|
99
|
+
> extends Dependant<D> {
|
|
100
|
+
scope: S
|
|
101
|
+
factory: InjectableFactoryType<P, D>
|
|
102
|
+
pick: InjectablePickType<P, T>
|
|
103
|
+
dispose?: InjectableDisposeType<P, D>
|
|
104
|
+
[kInjectable]: any
|
|
105
|
+
[kFactoryInjectable]: any
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface BaseClassInjectable<
|
|
109
|
+
T,
|
|
110
|
+
D extends Dependencies = {},
|
|
111
|
+
S extends Scope = Scope.Global,
|
|
112
|
+
> extends Dependant<D> {
|
|
113
|
+
new (...args: any[]): T
|
|
114
|
+
scope: S
|
|
115
|
+
[kInjectable]: any
|
|
116
|
+
[kClassInjectable]: any
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface ClassInjectable<
|
|
120
|
+
T,
|
|
121
|
+
D extends Dependencies = {},
|
|
122
|
+
S extends Scope = Scope.Global,
|
|
123
|
+
A extends any[] = [],
|
|
124
|
+
> extends Dependant<D> {
|
|
125
|
+
new (
|
|
126
|
+
$context: DependencyContext<D>,
|
|
127
|
+
...args: A
|
|
128
|
+
): T & {
|
|
129
|
+
$context: DependencyContext<D>
|
|
130
|
+
}
|
|
131
|
+
scope: S
|
|
132
|
+
[kInjectable]: any
|
|
133
|
+
[kClassInjectable]: any
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type Injectable<
|
|
137
|
+
V = any,
|
|
138
|
+
D extends Dependencies = {},
|
|
139
|
+
S extends Scope = Scope,
|
|
140
|
+
> =
|
|
141
|
+
| LazyInjectable<V, S>
|
|
142
|
+
| ValueInjectable<V>
|
|
143
|
+
| FactoryInjectable<V, D, S, any>
|
|
144
|
+
| BaseClassInjectable<V, D, S>
|
|
145
|
+
|
|
146
|
+
export type AnyInjectable<T = any, S extends Scope = Scope> = Injectable<
|
|
147
|
+
T,
|
|
148
|
+
any,
|
|
149
|
+
S
|
|
150
|
+
>
|
|
151
|
+
|
|
152
|
+
export const isLazyInjectable = (
|
|
153
|
+
injectable: any,
|
|
154
|
+
): injectable is LazyInjectable<any> => kLazyInjectable in injectable
|
|
155
|
+
export const isFactoryInjectable = (
|
|
156
|
+
injectable: any,
|
|
157
|
+
): injectable is FactoryInjectable<any> => kFactoryInjectable in injectable
|
|
158
|
+
export const isClassInjectable = (
|
|
159
|
+
injectable: any,
|
|
160
|
+
): injectable is ClassInjectable<any> => kClassInjectable in injectable
|
|
161
|
+
export const isValueInjectable = (
|
|
162
|
+
injectable: any,
|
|
163
|
+
): injectable is ValueInjectable<any> => kValueInjectable in injectable
|
|
164
|
+
export const isInjectable = (
|
|
165
|
+
injectable: any,
|
|
166
|
+
): injectable is AnyInjectable<any> => kInjectable in injectable
|
|
167
|
+
export const isOptionalInjectable = (
|
|
168
|
+
injectable: any,
|
|
169
|
+
): injectable is DependencyOptional<any> => kOptionalDependency in injectable
|
|
170
|
+
|
|
171
|
+
export function getInjectableScope(injectable: AnyInjectable) {
|
|
172
|
+
let scope = injectable.scope
|
|
173
|
+
const deps = Object.values(injectable.dependencies as Dependencies)
|
|
174
|
+
for (const dependency of deps) {
|
|
175
|
+
const injectable = getDepedencencyInjectable(dependency)
|
|
176
|
+
const dependencyScope = getInjectableScope(injectable)
|
|
177
|
+
if (compareScope(dependencyScope, '>', scope)) {
|
|
178
|
+
scope = dependencyScope
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return scope
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function getDepedencencyInjectable(
|
|
185
|
+
dependency: Depedency,
|
|
186
|
+
): AnyInjectable {
|
|
187
|
+
if (kOptionalDependency in dependency) {
|
|
188
|
+
return dependency.injectable
|
|
189
|
+
}
|
|
190
|
+
return dependency
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function createOptionalInjectable<T extends AnyInjectable>(
|
|
194
|
+
injectable: T,
|
|
195
|
+
) {
|
|
196
|
+
return {
|
|
197
|
+
[kOptionalDependency]: true,
|
|
198
|
+
injectable,
|
|
199
|
+
} as DependencyOptional<T>
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function createLazyInjectable<T, S extends Scope = Scope.Global>(
|
|
203
|
+
scope = Scope.Global as S,
|
|
204
|
+
label?: string,
|
|
205
|
+
stackTraceDepth = 0,
|
|
206
|
+
): LazyInjectable<T, S> {
|
|
207
|
+
return Object.freeze({
|
|
208
|
+
scope,
|
|
209
|
+
dependencies: {},
|
|
210
|
+
label,
|
|
211
|
+
stack: tryCaptureStackTrace(stackTraceDepth),
|
|
212
|
+
[kInjectable]: true,
|
|
213
|
+
[kLazyInjectable]: true as unknown as T,
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function createValueInjectable<T>(
|
|
218
|
+
value: T,
|
|
219
|
+
label?: string,
|
|
220
|
+
stackTraceDepth = 0,
|
|
221
|
+
): ValueInjectable<T> {
|
|
222
|
+
return Object.freeze({
|
|
223
|
+
value,
|
|
224
|
+
scope: Scope.Global,
|
|
225
|
+
dependencies: {},
|
|
226
|
+
label,
|
|
227
|
+
stack: tryCaptureStackTrace(stackTraceDepth),
|
|
228
|
+
[kInjectable]: true,
|
|
229
|
+
[kValueInjectable]: true,
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function createFactoryInjectable<
|
|
234
|
+
T,
|
|
235
|
+
D extends Dependencies = {},
|
|
236
|
+
S extends Scope = Scope.Global,
|
|
237
|
+
P = T,
|
|
238
|
+
>(
|
|
239
|
+
paramsOrFactory:
|
|
240
|
+
| {
|
|
241
|
+
dependencies?: D
|
|
242
|
+
scope?: S
|
|
243
|
+
pick?: InjectablePickType<P, T>
|
|
244
|
+
factory: InjectableFactoryType<P, D>
|
|
245
|
+
dispose?: InjectableDisposeType<P, D>
|
|
246
|
+
}
|
|
247
|
+
| InjectableFactoryType<P, D>,
|
|
248
|
+
label?: string,
|
|
249
|
+
stackTraceDepth = 0,
|
|
250
|
+
): FactoryInjectable<null extends T ? P : T, D, S, P> {
|
|
251
|
+
const isFactory = typeof paramsOrFactory === 'function'
|
|
252
|
+
const params = isFactory ? { factory: paramsOrFactory } : paramsOrFactory
|
|
253
|
+
const injectable = {
|
|
254
|
+
dependencies: (params.dependencies ?? {}) as D,
|
|
255
|
+
scope: (params.scope ?? Scope.Global) as S,
|
|
256
|
+
factory: params.factory,
|
|
257
|
+
dispose: params.dispose,
|
|
258
|
+
pick: params.pick ?? ((instance: P) => instance as unknown as T),
|
|
259
|
+
label,
|
|
260
|
+
stack: tryCaptureStackTrace(stackTraceDepth),
|
|
261
|
+
[kInjectable]: true,
|
|
262
|
+
[kFactoryInjectable]: true,
|
|
263
|
+
}
|
|
264
|
+
const actualScope = getInjectableScope(injectable)
|
|
265
|
+
if (
|
|
266
|
+
!isFactory &&
|
|
267
|
+
params.scope &&
|
|
268
|
+
ScopeStrictness[actualScope] > ScopeStrictness[params.scope]
|
|
269
|
+
)
|
|
270
|
+
throw new Error(
|
|
271
|
+
`Invalid scope ${params.scope} for factory injectable: dependencies have stricter scope - ${actualScope}`,
|
|
272
|
+
)
|
|
273
|
+
injectable.scope = actualScope as unknown as S
|
|
274
|
+
return Object.freeze(injectable) as any
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export const createClassInjectable = <
|
|
278
|
+
D extends Dependencies = {},
|
|
279
|
+
S extends Scope = Scope.Global,
|
|
280
|
+
>(
|
|
281
|
+
dependencies: D = {} as D,
|
|
282
|
+
scope: S = Scope.Global as S,
|
|
283
|
+
stackTraceDepth = 0,
|
|
284
|
+
): ClassInjectable<ClassInstance<typeof klass>, D, S> => {
|
|
285
|
+
const klass = class {
|
|
286
|
+
static dependencies = dependencies
|
|
287
|
+
static scope = scope
|
|
288
|
+
static stack = tryCaptureStackTrace(stackTraceDepth + 2)
|
|
289
|
+
static [kInjectable] = true
|
|
290
|
+
static [kClassInjectable] = true
|
|
291
|
+
|
|
292
|
+
static get label() {
|
|
293
|
+
// biome-ignore lint/complexity/noThisInStatic:
|
|
294
|
+
return this.name
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
constructor(public $context: DependencyContext<D>) {}
|
|
298
|
+
|
|
299
|
+
protected async $onCreate() {}
|
|
300
|
+
protected async $onDispose() {}
|
|
301
|
+
}
|
|
302
|
+
return klass
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function createExtendableClassInjectable<
|
|
306
|
+
B extends ClassConstructor<any>,
|
|
307
|
+
D extends Dependencies = {},
|
|
308
|
+
S extends Scope = Scope.Global,
|
|
309
|
+
>(
|
|
310
|
+
baseClass: B,
|
|
311
|
+
dependencies: D = {} as D,
|
|
312
|
+
scope: S = Scope.Global as S,
|
|
313
|
+
stackTraceDepth = 0,
|
|
314
|
+
): B extends ClassInjectable<any>
|
|
315
|
+
? ClassInjectable<ClassInstance<B>, D, S>
|
|
316
|
+
: ClassInjectable<ClassInstance<B>, D, S, ClassConstructorArgs<B, []>> {
|
|
317
|
+
if (isClassInjectable(baseClass)) {
|
|
318
|
+
dependencies = Object.assign({}, baseClass.dependencies, dependencies)
|
|
319
|
+
if (compareScope(scope, '<', baseClass.scope)) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
'Invalid scope for injectable: base class has stricter scope',
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// @ts-expect-error
|
|
326
|
+
return class extends baseClass {
|
|
327
|
+
static dependencies = dependencies
|
|
328
|
+
static scope = scope
|
|
329
|
+
static stack = tryCaptureStackTrace(stackTraceDepth)
|
|
330
|
+
static [kInjectable] = true
|
|
331
|
+
static [kClassInjectable] = true
|
|
332
|
+
|
|
333
|
+
static get label() {
|
|
334
|
+
// biome-ignore lint/complexity/noThisInStatic:
|
|
335
|
+
return this.name
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
$context!: DependencyContext<D>
|
|
339
|
+
|
|
340
|
+
constructor(...args: any[]) {
|
|
341
|
+
const [$context, ...baseClassArgs] = args
|
|
342
|
+
if (isClassInjectable(baseClass)) {
|
|
343
|
+
super($context)
|
|
344
|
+
} else {
|
|
345
|
+
super(...baseClassArgs)
|
|
346
|
+
this.$context = $context
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
protected async $onCreate() {
|
|
351
|
+
await super.$onCreate?.()
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
protected async $onDispose() {
|
|
355
|
+
await super.$onDispose?.()
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export type DependenciesSubstitution<T extends Dependencies> = {
|
|
361
|
+
[K in keyof T]?: T[K] extends AnyInjectable<infer Type>
|
|
362
|
+
? AnyInjectable<Type> | DependenciesSubstitution<T[K]['dependencies']>
|
|
363
|
+
: never
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function substitute<
|
|
367
|
+
T extends
|
|
368
|
+
| FactoryInjectable<any, any, Scope>
|
|
369
|
+
| BaseClassInjectable<any, any, Scope>,
|
|
370
|
+
>(
|
|
371
|
+
injectable: T,
|
|
372
|
+
substitution: DependenciesSubstitution<T['dependencies']>,
|
|
373
|
+
stackTraceDepth = 0,
|
|
374
|
+
): T {
|
|
375
|
+
const dependencies = { ...injectable.dependencies }
|
|
376
|
+
const depth = stackTraceDepth + 1
|
|
377
|
+
for (const key in substitution) {
|
|
378
|
+
const value = substitution[key]!
|
|
379
|
+
if (key in dependencies) {
|
|
380
|
+
const original = dependencies[key]
|
|
381
|
+
if (isInjectable(value)) {
|
|
382
|
+
dependencies[key] = value
|
|
383
|
+
} else if (isClassInjectable(original) || isFactoryInjectable(original)) {
|
|
384
|
+
dependencies[key] = substitute(original, value, depth)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (isClassInjectable(injectable)) {
|
|
390
|
+
// @ts-expect-error
|
|
391
|
+
return createExtendableClassInjectable(
|
|
392
|
+
injectable,
|
|
393
|
+
dependencies,
|
|
394
|
+
injectable.scope,
|
|
395
|
+
depth,
|
|
396
|
+
)
|
|
397
|
+
} else if (isFactoryInjectable(injectable)) {
|
|
398
|
+
// @ts-expect-error
|
|
399
|
+
return createFactoryInjectable(
|
|
400
|
+
{
|
|
401
|
+
...injectable,
|
|
402
|
+
dependencies,
|
|
403
|
+
},
|
|
404
|
+
injectable.label,
|
|
405
|
+
depth,
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
throw new Error('Invalid injectable type')
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function compareScope(
|
|
413
|
+
left: Scope,
|
|
414
|
+
operator: '>' | '<' | '>=' | '<=' | '=' | '!=',
|
|
415
|
+
right: Scope,
|
|
416
|
+
) {
|
|
417
|
+
const leftScope = ScopeStrictness[left]
|
|
418
|
+
const rightScope = ScopeStrictness[right]
|
|
419
|
+
switch (operator) {
|
|
420
|
+
case '=':
|
|
421
|
+
return leftScope === rightScope
|
|
422
|
+
case '!=':
|
|
423
|
+
return leftScope !== rightScope
|
|
424
|
+
case '>':
|
|
425
|
+
return leftScope > rightScope
|
|
426
|
+
case '<':
|
|
427
|
+
return leftScope < rightScope
|
|
428
|
+
case '>=':
|
|
429
|
+
return leftScope >= rightScope
|
|
430
|
+
case '<=':
|
|
431
|
+
return leftScope <= rightScope
|
|
432
|
+
default:
|
|
433
|
+
throw new Error('Invalid operator')
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export const CoreInjectables = {
|
|
438
|
+
logger: createLazyInjectable<Logger>(Scope.Global, 'Logger'),
|
|
439
|
+
inject: createLazyInjectable<InjectFn>(Scope.Global, 'Inject function'),
|
|
440
|
+
}
|
package/src/registry.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { type Hook, Scope } from './enums.ts'
|
|
2
|
+
import { Hooks, type HookType } from './hooks.ts'
|
|
1
3
|
import {
|
|
2
4
|
type AnyInjectable,
|
|
3
5
|
type Dependant,
|
|
4
6
|
getInjectableScope,
|
|
5
|
-
} from './
|
|
6
|
-
import { type Hook, Scope } from './enums.ts'
|
|
7
|
-
import { Hooks, type HookType } from './hooks.ts'
|
|
7
|
+
} from './injectables.ts'
|
|
8
8
|
import type { Logger } from './logger.ts'
|
|
9
9
|
|
|
10
10
|
export class Registry {
|