@martel/calyx 0.1.0 → 1.1.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.
@@ -6,6 +6,8 @@ import {
6
6
  ModuleMetadata,
7
7
  Type,
8
8
  resolveForwardRef,
9
+ Scope,
10
+ REQUEST,
9
11
  } from './metadata.ts';
10
12
  import { ModuleRef } from './module-ref.ts';
11
13
 
@@ -28,7 +30,7 @@ interface ModuleRecord {
28
30
  }
29
31
 
30
32
  class ContainerModuleRef extends ModuleRef {
31
- constructor(private container: calyxContainer, private moduleClass: any) {
33
+ constructor(private container: CalyxContainer, private moduleClass: any) {
32
34
  super();
33
35
  }
34
36
 
@@ -40,12 +42,28 @@ class ContainerModuleRef extends ModuleRef {
40
42
  return this.container.getGlobalOrAnyInstance(token);
41
43
  }
42
44
  }
45
+
46
+ async resolve<T>(token: InjectionToken, contextId?: any, options?: { strict: boolean }): Promise<T> {
47
+ const requestContext = contextId instanceof Map ? contextId : new Map<any, any>();
48
+ const strict = options?.strict ?? true;
49
+ if (strict) {
50
+ return this.container.resolveTokenInModuleContext(this.moduleClass, token, requestContext);
51
+ } else {
52
+ return this.container.resolveTokenGlobally(token, requestContext);
53
+ }
54
+ }
55
+
56
+ async create<T>(type: Type<T>): Promise<T> {
57
+ return this.container.instantiateClass(type, this.moduleClass);
58
+ }
43
59
  }
44
60
 
45
- export class calyxContainer {
61
+ export class CalyxContainer {
46
62
  private modules = new Map<any, ModuleRecord>();
47
63
  private globalModules = new Set<any>();
48
64
  private resolvingStack: { moduleClass: any; token: InjectionToken }[] = [];
65
+ private providerScopes = new Map<InjectionToken, Scope>();
66
+ private controllerScopes = new Map<any, Scope>();
49
67
 
50
68
  getModuleRecord(moduleClass: any): ModuleRecord | undefined {
51
69
  return this.modules.get(resolveForwardRef(moduleClass));
@@ -55,6 +73,19 @@ export class calyxContainer {
55
73
  return this.modules;
56
74
  }
57
75
 
76
+ getProviderAndControllerInstances(): any[] {
77
+ const instances = new Set<any>();
78
+ for (const record of this.modules.values()) {
79
+ for (const [token, instance] of record.instances.entries()) {
80
+ if (token === ModuleRef) continue;
81
+ if (instance && typeof instance === 'object') {
82
+ instances.add(instance);
83
+ }
84
+ }
85
+ }
86
+ return Array.from(instances);
87
+ }
88
+
58
89
  addModule(moduleClass: any) {
59
90
  const resolvedClass = resolveForwardRef(moduleClass);
60
91
  if (this.modules.has(resolvedClass)) return;
@@ -146,7 +177,7 @@ export class calyxContainer {
146
177
  return provider.provide;
147
178
  }
148
179
 
149
- resolveTokenInModuleContext<T>(moduleClass: any, token: InjectionToken): T {
180
+ resolveTokenInModuleContext<T>(moduleClass: any, token: InjectionToken, requestContext?: Map<any, any>): T {
150
181
  // 1. Check circular dependency
151
182
  const isResolving = this.resolvingStack.some(
152
183
  (item) => item.moduleClass === moduleClass && item.token === token
@@ -167,6 +198,13 @@ export class calyxContainer {
167
198
  }
168
199
 
169
200
  // Special resolution cases
201
+ if (token === REQUEST) {
202
+ if (!requestContext || !requestContext.has(REQUEST)) {
203
+ throw new Error(`calyx DI: REQUEST token resolved outside of a request context`);
204
+ }
205
+ return requestContext.get(REQUEST);
206
+ }
207
+
170
208
  if (token === ModuleRef) {
171
209
  const instance = record.instances.get(ModuleRef);
172
210
  if (instance) return instance;
@@ -178,7 +216,7 @@ export class calyxContainer {
178
216
  if (token === moduleClass) {
179
217
  const instance = record.instances.get(moduleClass);
180
218
  if (instance) return instance;
181
- const instantiatedModule = this.instantiateClass(moduleClass, moduleClass);
219
+ const instantiatedModule = this.instantiateClass(moduleClass, moduleClass, requestContext);
182
220
  record.instances.set(moduleClass, instantiatedModule);
183
221
  return instantiatedModule;
184
222
  }
@@ -215,26 +253,30 @@ export class calyxContainer {
215
253
  const { targetModuleClass, provider } = resolution;
216
254
  const targetRecord = this.modules.get(targetModuleClass)!;
217
255
 
218
- // Check if instance already exists in target module
219
- if (targetRecord.instances.has(token)) {
220
- return targetRecord.instances.get(token);
256
+ const scope = this.providerScopes.get(token) ?? Scope.DEFAULT;
257
+
258
+ if (scope === Scope.REQUEST) {
259
+ if (!requestContext) {
260
+ throw new Error(`calyx DI: Cannot resolve request-scoped provider "${String(token.name ?? token)}" without request context.`);
261
+ }
262
+ if (requestContext.has(token)) {
263
+ return requestContext.get(token);
264
+ }
265
+ const instance = this.createInstanceFromProvider(provider, targetModuleClass, requestContext);
266
+ requestContext.set(token, instance);
267
+ return instance;
268
+ }
269
+
270
+ if (scope === Scope.TRANSIENT) {
271
+ return this.createInstanceFromProvider(provider, targetModuleClass, requestContext);
221
272
  }
222
273
 
223
- // Instantiate based on provider definition
224
- let instance: any;
225
- if (typeof provider !== 'function' && 'useValue' in provider) {
226
- instance = provider.useValue;
227
- } else if (typeof provider !== 'function' && 'useClass' in provider) {
228
- instance = this.instantiateClass(provider.useClass, targetModuleClass);
229
- } else if (typeof provider !== 'function' && 'useFactory' in provider) {
230
- const injectTokens = provider.inject || [];
231
- const args = injectTokens.map((t) => this.resolveTokenInModuleContext(targetModuleClass, resolveForwardRef(t)));
232
- instance = provider.useFactory(...args);
233
- } else {
234
- // Raw class provider
235
- instance = this.instantiateClass(provider as Type<any>, targetModuleClass);
274
+ // Check if instance already exists in target module (Singleton)
275
+ if (targetRecord.instances.has(token)) {
276
+ return targetRecord.instances.get(token);
236
277
  }
237
278
 
279
+ const instance = this.createInstanceFromProvider(provider, targetModuleClass, requestContext);
238
280
  targetRecord.instances.set(token, instance);
239
281
  return instance;
240
282
  } finally {
@@ -242,6 +284,20 @@ export class calyxContainer {
242
284
  }
243
285
  }
244
286
 
287
+ private createInstanceFromProvider(provider: Provider, targetModuleClass: any, requestContext?: Map<any, any>): any {
288
+ if (typeof provider !== 'function' && 'useValue' in provider) {
289
+ return provider.useValue;
290
+ } else if (typeof provider !== 'function' && 'useClass' in provider) {
291
+ return this.instantiateClass(provider.useClass, targetModuleClass, requestContext);
292
+ } else if (typeof provider !== 'function' && 'useFactory' in provider) {
293
+ const injectTokens = provider.inject || [];
294
+ const args = injectTokens.map((t) => this.resolveTokenInModuleContext(targetModuleClass, resolveForwardRef(t), requestContext));
295
+ return provider.useFactory(...args);
296
+ } else {
297
+ return this.instantiateClass(provider as Type<any>, targetModuleClass, requestContext);
298
+ }
299
+ }
300
+
245
301
  private isTokenExportedByModule(moduleClass: any, token: InjectionToken, visited = new Set<any>()): boolean {
246
302
  if (visited.has(moduleClass)) return false;
247
303
  visited.add(moduleClass);
@@ -310,7 +366,7 @@ export class calyxContainer {
310
366
  return null;
311
367
  }
312
368
 
313
- private instantiateClass(Class: Type<any>, moduleClass: any): any {
369
+ public instantiateClass(Class: Type<any>, moduleClass: any, requestContext?: Map<any, any>): any {
314
370
  if (
315
371
  !Reflect.hasMetadata(METADATA_KEYS.INJECTABLE, Class) &&
316
372
  !Reflect.hasMetadata(METADATA_KEYS.CONTROLLER, Class) &&
@@ -328,7 +384,7 @@ export class calyxContainer {
328
384
  const resolvedToken = resolveForwardRef(token);
329
385
 
330
386
  try {
331
- return this.resolveTokenInModuleContext(moduleClass, resolvedToken);
387
+ return this.resolveTokenInModuleContext(moduleClass, resolvedToken, requestContext);
332
388
  } catch (err) {
333
389
  if (optionalParams.has(i)) {
334
390
  return undefined;
@@ -337,7 +393,16 @@ export class calyxContainer {
337
393
  }
338
394
  });
339
395
 
340
- return new Class(...args);
396
+ const instance = new Class(...args);
397
+
398
+ // Resolve property-level injections
399
+ const propertyInjects: Map<string | symbol, InjectionToken> =
400
+ Reflect.getOwnMetadata(METADATA_KEYS.PROPERTY_INJECTS, Class) || new Map();
401
+ for (const [propertyKey, token] of propertyInjects.entries()) {
402
+ instance[propertyKey] = this.resolveTokenInModuleContext(moduleClass, token, requestContext);
403
+ }
404
+
405
+ return instance;
341
406
  }
342
407
 
343
408
  getGlobalOrAnyInstance(token: InjectionToken): any {
@@ -352,17 +417,26 @@ export class calyxContainer {
352
417
  bootstrap(rootModule: any) {
353
418
  this.addModule(rootModule);
354
419
 
355
- // Instantiate all providers
420
+ // Compute scopes of all providers and controllers (including bubble-up)
421
+ this.resolveProviderAndControllerScopes();
422
+
423
+ // Instantiate all singleton providers
356
424
  for (const [moduleClass, record] of this.modules.entries()) {
357
425
  for (const token of record.providers.keys()) {
358
- this.resolveTokenInModuleContext(moduleClass, token);
426
+ const scope = this.providerScopes.get(token) ?? Scope.DEFAULT;
427
+ if (scope === Scope.DEFAULT) {
428
+ this.resolveTokenInModuleContext(moduleClass, token);
429
+ }
359
430
  }
360
431
  }
361
432
 
362
- // Instantiate all controllers
433
+ // Instantiate all singleton controllers
363
434
  for (const [moduleClass, record] of this.modules.entries()) {
364
435
  for (const controllerClass of record.controllers) {
365
- this.resolveController(moduleClass, controllerClass);
436
+ const scope = this.controllerScopes.get(controllerClass) ?? Scope.DEFAULT;
437
+ if (scope === Scope.DEFAULT) {
438
+ this.resolveController(moduleClass, controllerClass);
439
+ }
366
440
  }
367
441
  }
368
442
 
@@ -384,4 +458,155 @@ export class calyxContainer {
384
458
  record.instances.set(controllerClass, instance);
385
459
  return instance;
386
460
  }
461
+
462
+ resolveTokenGlobally<T>(token: InjectionToken, requestContext?: Map<any, any>): T {
463
+ const scope = this.providerScopes.get(token) ?? Scope.DEFAULT;
464
+ if (scope === Scope.REQUEST && requestContext?.has(token)) {
465
+ return requestContext.get(token);
466
+ }
467
+ for (const [moduleClass, record] of this.modules.entries()) {
468
+ if (record.providers.has(token)) {
469
+ return this.resolveTokenInModuleContext(moduleClass, token, requestContext);
470
+ }
471
+ }
472
+ return this.getGlobalOrAnyInstance(token);
473
+ }
474
+
475
+ resolveControllerInRequestContext(moduleClass: any, controllerClass: any, requestContext: Map<any, any>): any {
476
+ const scope = this.controllerScopes.get(controllerClass) ?? Scope.DEFAULT;
477
+ if (scope === Scope.REQUEST) {
478
+ if (requestContext.has(controllerClass)) {
479
+ return requestContext.get(controllerClass);
480
+ }
481
+ const instance = this.instantiateClass(controllerClass, moduleClass, requestContext);
482
+ requestContext.set(controllerClass, instance);
483
+ return instance;
484
+ }
485
+ const record = this.modules.get(moduleClass)!;
486
+ return record.instances.get(controllerClass);
487
+ }
488
+
489
+ getProviderScope(token: InjectionToken): Scope {
490
+ return this.providerScopes.get(token) ?? Scope.DEFAULT;
491
+ }
492
+
493
+ getControllerScope(controllerClass: any): Scope {
494
+ return this.controllerScopes.get(controllerClass) ?? Scope.DEFAULT;
495
+ }
496
+
497
+ private resolveProviderAndControllerScopes() {
498
+ // 1. Resolve direct scopes for providers
499
+ for (const record of this.modules.values()) {
500
+ for (const [token, provider] of record.providers.entries()) {
501
+ let scope = Scope.DEFAULT;
502
+ if (typeof provider === 'function') {
503
+ scope = Reflect.getMetadata(METADATA_KEYS.SCOPE, provider) ?? Scope.DEFAULT;
504
+ } else if (provider && typeof provider === 'object') {
505
+ scope = provider.scope ?? Scope.DEFAULT;
506
+ if (scope === Scope.DEFAULT && 'useClass' in provider) {
507
+ scope = Reflect.getMetadata(METADATA_KEYS.SCOPE, provider.useClass) ?? Scope.DEFAULT;
508
+ }
509
+ }
510
+ this.providerScopes.set(token, scope);
511
+ }
512
+ }
513
+
514
+ // 2. Bubble up Request Scope through provider dependencies
515
+ let changed = true;
516
+ while (changed) {
517
+ changed = false;
518
+ for (const record of this.modules.values()) {
519
+ for (const [token, provider] of record.providers.entries()) {
520
+ const currentScope = this.providerScopes.get(token) ?? Scope.DEFAULT;
521
+ if (currentScope === Scope.REQUEST) continue;
522
+
523
+ let dependsOnRequestScope = false;
524
+ const Class = typeof provider === 'function'
525
+ ? provider
526
+ : ('useClass' in provider ? provider.useClass : null);
527
+
528
+ if (Class) {
529
+ const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', Class) || [];
530
+ const injectTokens: Map<number, InjectionToken> = Reflect.getMetadata(METADATA_KEYS.INJECT_TOKENS, Class) || new Map();
531
+ for (let i = 0; i < paramTypes.length; i++) {
532
+ const depToken = injectTokens.get(i) ?? paramTypes[i];
533
+ const resolvedDepToken = resolveForwardRef(depToken);
534
+ const depScope = this.providerScopes.get(resolvedDepToken) ?? Scope.DEFAULT;
535
+ if (depScope === Scope.REQUEST) {
536
+ dependsOnRequestScope = true;
537
+ break;
538
+ }
539
+ }
540
+
541
+ if (!dependsOnRequestScope) {
542
+ const propertyInjects: Map<string | symbol, InjectionToken> =
543
+ Reflect.getOwnMetadata(METADATA_KEYS.PROPERTY_INJECTS, Class) || new Map();
544
+ for (const depToken of propertyInjects.values()) {
545
+ const resolvedDepToken = resolveForwardRef(depToken);
546
+ const depScope = this.providerScopes.get(resolvedDepToken) ?? Scope.DEFAULT;
547
+ if (depScope === Scope.REQUEST) {
548
+ dependsOnRequestScope = true;
549
+ break;
550
+ }
551
+ }
552
+ }
553
+ } else if (provider && typeof provider === 'object' && 'useFactory' in provider) {
554
+ const injectTokens = provider.inject || [];
555
+ for (const depToken of injectTokens) {
556
+ const resolvedDepToken = resolveForwardRef(depToken);
557
+ const depScope = this.providerScopes.get(resolvedDepToken) ?? Scope.DEFAULT;
558
+ if (depScope === Scope.REQUEST) {
559
+ dependsOnRequestScope = true;
560
+ break;
561
+ }
562
+ }
563
+ }
564
+
565
+ if (dependsOnRequestScope) {
566
+ this.providerScopes.set(token, Scope.REQUEST);
567
+ changed = true;
568
+ }
569
+ }
570
+ }
571
+ }
572
+
573
+ // 3. Resolve scopes for controllers
574
+ for (const record of this.modules.values()) {
575
+ for (const controllerClass of record.controllers) {
576
+ let scope = Reflect.getMetadata(METADATA_KEYS.SCOPE, controllerClass) ?? Scope.DEFAULT;
577
+ if (scope !== Scope.REQUEST) {
578
+ const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', controllerClass) || [];
579
+ const injectTokens: Map<number, InjectionToken> = Reflect.getMetadata(METADATA_KEYS.INJECT_TOKENS, controllerClass) || new Map();
580
+ let dependsOnRequestScope = false;
581
+ for (let i = 0; i < paramTypes.length; i++) {
582
+ const depToken = injectTokens.get(i) ?? paramTypes[i];
583
+ const resolvedDepToken = resolveForwardRef(depToken);
584
+ const depScope = this.providerScopes.get(resolvedDepToken) ?? Scope.DEFAULT;
585
+ if (depScope === Scope.REQUEST) {
586
+ dependsOnRequestScope = true;
587
+ break;
588
+ }
589
+ }
590
+
591
+ if (!dependsOnRequestScope) {
592
+ const propertyInjects: Map<string | symbol, InjectionToken> =
593
+ Reflect.getOwnMetadata(METADATA_KEYS.PROPERTY_INJECTS, controllerClass) || new Map();
594
+ for (const depToken of propertyInjects.values()) {
595
+ const resolvedDepToken = resolveForwardRef(depToken);
596
+ const depScope = this.providerScopes.get(resolvedDepToken) ?? Scope.DEFAULT;
597
+ if (depScope === Scope.REQUEST) {
598
+ dependsOnRequestScope = true;
599
+ break;
600
+ }
601
+ }
602
+ }
603
+
604
+ if (dependsOnRequestScope) {
605
+ scope = Scope.REQUEST;
606
+ }
607
+ }
608
+ this.controllerScopes.set(controllerClass, scope);
609
+ }
610
+ }
611
+ }
387
612
  }
@@ -1,5 +1,5 @@
1
1
  import 'reflect-metadata';
2
- import { METADATA_KEYS, ModuleMetadata, InjectionToken, ForwardReference } from './metadata.ts';
2
+ import { METADATA_KEYS, ModuleMetadata, InjectionToken, ForwardReference, Scope } from './metadata.ts';
3
3
 
4
4
  export function Module(metadata: ModuleMetadata): ClassDecorator {
5
5
  return (target) => {
@@ -7,20 +7,44 @@ export function Module(metadata: ModuleMetadata): ClassDecorator {
7
7
  };
8
8
  }
9
9
 
10
- export function Injectable(): ClassDecorator {
10
+ export interface InjectableOptions {
11
+ scope?: Scope;
12
+ }
13
+
14
+ export function Injectable(options?: InjectableOptions): ClassDecorator {
11
15
  return (target) => {
12
16
  Reflect.defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
17
+ if (options?.scope !== undefined) {
18
+ Reflect.defineMetadata(METADATA_KEYS.SCOPE, options.scope, target);
19
+ }
13
20
  };
14
21
  }
15
22
 
16
- export function Inject(token: InjectionToken): ParameterDecorator {
17
- return (target, propertyKey, parameterIndex) => {
18
- // For constructor parameters, propertyKey is undefined
19
- const classTarget = propertyKey === undefined ? target : target.constructor;
20
- const existingInjectTokens: Map<number, InjectionToken> =
21
- Reflect.getOwnMetadata(METADATA_KEYS.INJECT_TOKENS, classTarget) || new Map();
22
- existingInjectTokens.set(parameterIndex, token);
23
- Reflect.defineMetadata(METADATA_KEYS.INJECT_TOKENS, existingInjectTokens, classTarget);
23
+ export function Inject(token?: InjectionToken): ParameterDecorator & PropertyDecorator {
24
+ return (target: any, propertyKey?: string | symbol, parameterIndex?: number) => {
25
+ if (propertyKey !== undefined && parameterIndex === undefined) {
26
+ // Property decorator
27
+ const classTarget = target.constructor;
28
+ const resolvedToken = token ?? Reflect.getMetadata('design:type', target, propertyKey);
29
+ if (!resolvedToken) {
30
+ throw new Error(`Cannot resolve injection token for property "${String(propertyKey)}" of class ${classTarget.name}. Make sure reflect-metadata is imported and emitDecoratorMetadata is enabled, or pass the token explicitly to @Inject().`);
31
+ }
32
+ const existingPropertyInjects: Map<string | symbol, InjectionToken> =
33
+ Reflect.getOwnMetadata(METADATA_KEYS.PROPERTY_INJECTS, classTarget) || new Map();
34
+ existingPropertyInjects.set(propertyKey, resolvedToken);
35
+ Reflect.defineMetadata(METADATA_KEYS.PROPERTY_INJECTS, existingPropertyInjects, classTarget);
36
+ } else {
37
+ // Parameter decorator
38
+ const classTarget = propertyKey === undefined ? target : target.constructor;
39
+ const resolvedToken = token ?? (Reflect.getMetadata('design:paramtypes', classTarget) || [])[parameterIndex!];
40
+ if (!resolvedToken) {
41
+ throw new Error(`Cannot resolve injection token for constructor parameter at index ${parameterIndex} of class ${classTarget.name}. Make sure reflect-metadata is imported and emitDecoratorMetadata is enabled, or pass the token explicitly to @Inject().`);
42
+ }
43
+ const existingInjectTokens: Map<number, InjectionToken> =
44
+ Reflect.getOwnMetadata(METADATA_KEYS.INJECT_TOKENS, classTarget) || new Map();
45
+ existingInjectTokens.set(parameterIndex!, resolvedToken);
46
+ Reflect.defineMetadata(METADATA_KEYS.INJECT_TOKENS, existingInjectTokens, classTarget);
47
+ }
24
48
  };
25
49
  }
26
50
 
@@ -43,3 +67,33 @@ export function Global(): ClassDecorator {
43
67
  export function forwardRef(fn: () => any): ForwardReference {
44
68
  return { forwardRef: fn };
45
69
  }
70
+
71
+ export interface CustomDecorator<TKey = any> {
72
+ (target: object, key?: string | symbol, descriptor?: any): any;
73
+ KEY: TKey;
74
+ }
75
+
76
+ export function SetMetadata<K = any, V = any>(metadataKey: K, metadataValue: V): CustomDecorator<K> {
77
+ const decoratorFn = (target: any, key?: string | symbol, descriptor?: any) => {
78
+ if (descriptor) {
79
+ Reflect.defineMetadata(metadataKey, metadataValue, descriptor.value);
80
+ return descriptor;
81
+ }
82
+ Reflect.defineMetadata(metadataKey, metadataValue, target);
83
+ return target;
84
+ };
85
+ decoratorFn.KEY = metadataKey;
86
+ return decoratorFn;
87
+ }
88
+
89
+ export function applyDecorators(...decorators: any[]) {
90
+ return (target: any, propertyKey?: string | symbol, descriptor?: any) => {
91
+ for (const decorator of decorators) {
92
+ if (target instanceof Function && !propertyKey) {
93
+ decorator(target);
94
+ } else {
95
+ decorator(target, propertyKey, descriptor);
96
+ }
97
+ }
98
+ };
99
+ }
package/src/core/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './metadata.ts';
2
2
  export * from './decorators.ts';
3
3
  export * from './module-ref.ts';
4
4
  export * from './container.ts';
5
+ export * from './reflector.ts';
@@ -15,6 +15,8 @@ export const METADATA_KEYS = {
15
15
  INTERCEPTORS: 'calyx:interceptors',
16
16
  PIPES: 'calyx:pipes',
17
17
  FILTERS: 'calyx:filters',
18
+ PROPERTY_INJECTS: 'calyx:property_injects',
19
+ SCOPE: 'calyx:scope',
18
20
  };
19
21
 
20
22
  export interface Type<T = any> extends Function {
@@ -26,17 +28,20 @@ export type InjectionToken = string | symbol | Type<any>;
26
28
  export interface ValueProvider {
27
29
  provide: InjectionToken;
28
30
  useValue: any;
31
+ scope?: Scope;
29
32
  }
30
33
 
31
34
  export interface ClassProvider {
32
35
  provide: InjectionToken;
33
36
  useClass: Type<any>;
37
+ scope?: Scope;
34
38
  }
35
39
 
36
40
  export interface FactoryProvider {
37
41
  provide: InjectionToken;
38
42
  useFactory: (...args: any[]) => any;
39
43
  inject?: InjectionToken[];
44
+ scope?: Scope;
40
45
  }
41
46
 
42
47
  export type Provider = Type<any> | ValueProvider | ClassProvider | FactoryProvider;
@@ -59,3 +64,11 @@ export function isForwardReference(obj: any): obj is ForwardReference {
59
64
  export function resolveForwardRef(typeOrFunc: any): any {
60
65
  return isForwardReference(typeOrFunc) ? typeOrFunc.forwardRef() : typeOrFunc;
61
66
  }
67
+
68
+ export enum Scope {
69
+ DEFAULT = 0,
70
+ TRANSIENT = 1,
71
+ REQUEST = 2,
72
+ }
73
+
74
+ export const REQUEST = Symbol('REQUEST');
@@ -1,5 +1,7 @@
1
- import { InjectionToken } from './metadata.ts';
1
+ import { InjectionToken, Type } from './metadata.ts';
2
2
 
3
3
  export abstract class ModuleRef {
4
4
  abstract get<T>(token: InjectionToken, options?: { strict: boolean }): T;
5
+ abstract resolve<T>(token: InjectionToken, contextId?: any, options?: { strict: boolean }): Promise<T>;
6
+ abstract create<T>(type: Type<T>): Promise<T>;
5
7
  }
@@ -0,0 +1,32 @@
1
+ import { Injectable } from './decorators.ts';
2
+ import { Type } from './metadata.ts';
3
+
4
+ @Injectable()
5
+ export class Reflector {
6
+ get<T = any>(metadataKey: any, target: Function | Type<any>): T {
7
+ return Reflect.getMetadata(metadataKey, target);
8
+ }
9
+
10
+ getAllAndOverride<T = any>(metadataKey: any, targets: (Function | Type<any>)[]): T {
11
+ for (const target of targets) {
12
+ if (!target) continue;
13
+ const val = Reflect.getMetadata(metadataKey, target);
14
+ if (val !== undefined) return val;
15
+ }
16
+ return undefined as any;
17
+ }
18
+
19
+ getAllAndMerge<T = any>(metadataKey: any, targets: (Function | Type<any>)[]): T {
20
+ const result: any[] = [];
21
+ for (const target of targets) {
22
+ if (!target) continue;
23
+ const val = Reflect.getMetadata(metadataKey, target);
24
+ if (Array.isArray(val)) {
25
+ result.push(...val);
26
+ } else if (val !== undefined) {
27
+ result.push(val);
28
+ }
29
+ }
30
+ return result as any as T;
31
+ }
32
+ }