@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/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: any; context: any }
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 (checkIsValueInjectable(injectable)) {
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 = checkIsLazyInjectable(injectable)
200
+ const isLazy = isLazyInjectable(injectable)
295
201
 
296
202
  if (isLazy) {
297
- const isOptional = checkIsOptional(injectable)
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 (!checkIsFactoryInjectable(injectable)) {
305
- throw new Error('Invalid injectable')
306
- }
307
-
308
- const resolution = this.createInjectableContext(
309
- dependencies,
310
- injectable,
311
- )
312
- .then((context) =>
313
- Promise.resolve(injectable.factory(context)).then((instance) => ({
314
- instance,
315
- context,
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
- .then(({ instance, context }) => {
319
- const picked = injectable.pick(instance)
320
- if (compareScope(this.scope, '>=', scope))
321
- this.instances.set(injectable, { instance, picked, context })
322
- if (scope !== Scope.Transient) this.resolvers.delete(injectable)
323
- return picked
324
- })
325
- if (scope !== Scope.Transient)
326
- this.resolvers.set(injectable, resolution)
327
- return resolution
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 (kFactoryInjectable in injectable) {
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 compareScope(
354
- left: Scope,
355
- operator: '>' | '<' | '>=' | '<=' | '=' | '!=',
356
- right: Scope,
357
- ) {
358
- const leftScope = ScopeStrictness[left]
359
- const rightScope = ScopeStrictness[right]
360
- switch (operator) {
361
- case '=':
362
- return leftScope === rightScope
363
- case '!=':
364
- return leftScope !== rightScope
365
- case '>':
366
- return leftScope > rightScope
367
- case '<':
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
- export const checkIsLazyInjectable = (
379
- injectable: any,
380
- ): injectable is LazyInjectable<any> => kLazyInjectable in injectable
381
- export const checkIsFactoryInjectable = (
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
- export function getDepedencencyInjectable(
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
- export function createValueInjectable<T>(
440
- value: T,
441
- label?: string,
442
- ): ValueInjectable<T> {
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 function createFactoryInjectable<
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>