@stitchem/core 0.0.3
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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +815 -0
- package/dist/container/container.d.ts +79 -0
- package/dist/container/container.js +156 -0
- package/dist/container/module.map.d.ts +22 -0
- package/dist/container/module.map.js +40 -0
- package/dist/context/context.d.ts +181 -0
- package/dist/context/context.js +395 -0
- package/dist/context/scope.d.ts +30 -0
- package/dist/context/scope.js +42 -0
- package/dist/core/core.lifecycle.d.ts +41 -0
- package/dist/core/core.lifecycle.js +37 -0
- package/dist/core/core.lifetime.d.ts +21 -0
- package/dist/core/core.lifetime.js +22 -0
- package/dist/core/core.types.d.ts +2 -0
- package/dist/core/core.types.js +2 -0
- package/dist/core/core.utils.d.ts +8 -0
- package/dist/core/core.utils.js +13 -0
- package/dist/decorator/inject.decorator.d.ts +50 -0
- package/dist/decorator/inject.decorator.js +78 -0
- package/dist/decorator/injectable.decorator.d.ts +45 -0
- package/dist/decorator/injectable.decorator.js +46 -0
- package/dist/errors/core.error.d.ts +24 -0
- package/dist/errors/core.error.js +59 -0
- package/dist/errors/error.codes.d.ts +17 -0
- package/dist/errors/error.codes.js +21 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +23 -0
- package/dist/injector/injector.d.ts +78 -0
- package/dist/injector/injector.js +295 -0
- package/dist/instance-wrapper/instance-wrapper.d.ts +61 -0
- package/dist/instance-wrapper/instance-wrapper.js +142 -0
- package/dist/instance-wrapper/instance-wrapper.types.d.ts +18 -0
- package/dist/instance-wrapper/instance-wrapper.types.js +2 -0
- package/dist/logger/console.logger.d.ts +52 -0
- package/dist/logger/console.logger.js +90 -0
- package/dist/logger/logger.token.d.ts +23 -0
- package/dist/logger/logger.token.js +23 -0
- package/dist/logger/logger.types.d.ts +38 -0
- package/dist/logger/logger.types.js +12 -0
- package/dist/module/module.d.ts +104 -0
- package/dist/module/module.decorator.d.ts +28 -0
- package/dist/module/module.decorator.js +42 -0
- package/dist/module/module.graph.d.ts +52 -0
- package/dist/module/module.graph.js +263 -0
- package/dist/module/module.js +181 -0
- package/dist/module/module.ref.d.ts +81 -0
- package/dist/module/module.ref.js +123 -0
- package/dist/module/module.types.d.ts +80 -0
- package/dist/module/module.types.js +10 -0
- package/dist/provider/provider.guards.d.ts +46 -0
- package/dist/provider/provider.guards.js +62 -0
- package/dist/provider/provider.interface.d.ts +39 -0
- package/dist/provider/provider.interface.js +2 -0
- package/dist/test/test.d.ts +22 -0
- package/dist/test/test.js +23 -0
- package/dist/test/test.module-builder.d.ts +136 -0
- package/dist/test/test.module-builder.js +377 -0
- package/dist/test/test.module.d.ts +71 -0
- package/dist/test/test.module.js +151 -0
- package/dist/token/lazy.token.d.ts +44 -0
- package/dist/token/lazy.token.js +42 -0
- package/dist/token/token.types.d.ts +8 -0
- package/dist/token/token.types.js +2 -0
- package/dist/token/token.utils.d.ts +9 -0
- package/dist/token/token.utils.js +19 -0
- package/package.json +62 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injection token for the logger.
|
|
3
|
+
*
|
|
4
|
+
* Use this token to inject or replace the logger via DI.
|
|
5
|
+
*
|
|
6
|
+
* @example Inject
|
|
7
|
+
* ```ts
|
|
8
|
+
* @injectable()
|
|
9
|
+
* class UserService {
|
|
10
|
+
* static inject = [LOGGER] as const;
|
|
11
|
+
* constructor(readonly logger: Logger) {}
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @example Replace globally
|
|
16
|
+
* ```ts
|
|
17
|
+
* await using ctx = await Context.create(AppModule, {
|
|
18
|
+
* logging: { logger: pino() },
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const LOGGER = Symbol.for('stitchem:logger');
|
|
23
|
+
//# sourceMappingURL=logger.token.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log levels in order of verbosity (lowest to highest).
|
|
3
|
+
*/
|
|
4
|
+
export declare enum LogLevel {
|
|
5
|
+
DEBUG = 0,
|
|
6
|
+
INFO = 1,
|
|
7
|
+
WARN = 2,
|
|
8
|
+
ERROR = 3,
|
|
9
|
+
SILENT = 4
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Minimal logger interface.
|
|
13
|
+
*
|
|
14
|
+
* Compatible with `console`, `pino()`, and `winston.createLogger()` out of the box.
|
|
15
|
+
*
|
|
16
|
+
* @example DI injection
|
|
17
|
+
* ```ts
|
|
18
|
+
* @injectable()
|
|
19
|
+
* class UserService {
|
|
20
|
+
* static inject = [LOGGER] as const;
|
|
21
|
+
* constructor(readonly logger: Logger) {}
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example Replace with pino
|
|
26
|
+
* ```ts
|
|
27
|
+
* await using ctx = await Context.create(AppModule, {
|
|
28
|
+
* logging: { logger: pino() },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export interface Logger {
|
|
33
|
+
info(message: string, ...args: unknown[]): void;
|
|
34
|
+
warn(message: string, ...args: unknown[]): void;
|
|
35
|
+
error(message: string, ...args: unknown[]): void;
|
|
36
|
+
debug(message: string, ...args: unknown[]): void;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=logger.types.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log levels in order of verbosity (lowest to highest).
|
|
3
|
+
*/
|
|
4
|
+
export var LogLevel;
|
|
5
|
+
(function (LogLevel) {
|
|
6
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
7
|
+
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
|
8
|
+
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
|
9
|
+
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
|
10
|
+
LogLevel[LogLevel["SILENT"] = 4] = "SILENT";
|
|
11
|
+
})(LogLevel || (LogLevel = {}));
|
|
12
|
+
//# sourceMappingURL=logger.types.js.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Token } from '../token/token.types.js';
|
|
2
|
+
import type { constructor } from '../core/core.types.js';
|
|
3
|
+
import type { Provider } from '../provider/provider.interface.js';
|
|
4
|
+
import type { ModuleMetadata } from './module.types.js';
|
|
5
|
+
import { Scope } from '../context/scope.js';
|
|
6
|
+
import { InstanceWrapper } from '../instance-wrapper/instance-wrapper.js';
|
|
7
|
+
/**
|
|
8
|
+
* Options for creating a Module.
|
|
9
|
+
*/
|
|
10
|
+
export interface ModuleOptions {
|
|
11
|
+
/** Unique identifier for this module instance. */
|
|
12
|
+
id: string;
|
|
13
|
+
/** The module class (decorated with @module). */
|
|
14
|
+
classRef: constructor;
|
|
15
|
+
/** Module metadata (providers, exports, imports). */
|
|
16
|
+
metadata: ModuleMetadata;
|
|
17
|
+
/** Whether this is a global module. */
|
|
18
|
+
isGlobal?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Represents a module instance with its own providers.
|
|
22
|
+
* Each module maintains its own provider registry and visibility rules.
|
|
23
|
+
*/
|
|
24
|
+
export declare class Module {
|
|
25
|
+
/** Unique identifier for this module instance. */
|
|
26
|
+
readonly id: string;
|
|
27
|
+
/** The module class. */
|
|
28
|
+
readonly classRef: constructor;
|
|
29
|
+
/** Whether this is a global module. */
|
|
30
|
+
readonly isGlobal: boolean;
|
|
31
|
+
/** Original metadata from @module decorator + dynamic config. */
|
|
32
|
+
readonly metadata: ModuleMetadata;
|
|
33
|
+
/** Provider storage (token -> InstanceWrapper). */
|
|
34
|
+
private readonly _providers;
|
|
35
|
+
/** Exported tokens. */
|
|
36
|
+
private readonly _exports;
|
|
37
|
+
/** Imported modules. */
|
|
38
|
+
private readonly _imports;
|
|
39
|
+
/** Registration order for disposal. */
|
|
40
|
+
private readonly _registrationOrder;
|
|
41
|
+
/** Component instances keyed by metadata key (e.g. 'routers'). */
|
|
42
|
+
private readonly _components;
|
|
43
|
+
/** Injectable instances (guards, filters, middleware, etc.) keyed by class ref. */
|
|
44
|
+
private readonly _injectables;
|
|
45
|
+
/** Insertion order for injectable disposal. */
|
|
46
|
+
private readonly _injectableOrder;
|
|
47
|
+
/** Module instance (the instantiated module class). */
|
|
48
|
+
private _instance?;
|
|
49
|
+
constructor(options: ModuleOptions);
|
|
50
|
+
/** All providers in this module. */
|
|
51
|
+
get providers(): ReadonlyMap<Token, InstanceWrapper>;
|
|
52
|
+
/** All exported tokens. */
|
|
53
|
+
get exports(): ReadonlySet<Token>;
|
|
54
|
+
/** All imported modules. */
|
|
55
|
+
get imports(): ReadonlySet<Module>;
|
|
56
|
+
/** The module class instance. */
|
|
57
|
+
get instance(): unknown;
|
|
58
|
+
set instance(value: unknown);
|
|
59
|
+
/**
|
|
60
|
+
* Adds a provider to this module.
|
|
61
|
+
* Validates that class-based providers have @injectable().
|
|
62
|
+
*
|
|
63
|
+
* @throws CoreError (NOT_INJECTABLE) if a class provider lacks @injectable()
|
|
64
|
+
*/
|
|
65
|
+
addProvider<T>(provider: Provider<T>): InstanceWrapper<T>;
|
|
66
|
+
/** Gets a provider by token. */
|
|
67
|
+
getProvider<T>(token: Token<T>): InstanceWrapper<T> | undefined;
|
|
68
|
+
/** Checks if a provider exists in this module. */
|
|
69
|
+
hasProvider(token: Token): boolean;
|
|
70
|
+
/** Adds an export token. */
|
|
71
|
+
addExport(token: Token): void;
|
|
72
|
+
/** Adds an imported module. */
|
|
73
|
+
addImport(mod: Module): void;
|
|
74
|
+
/** Registers a component instance under a metadata key. */
|
|
75
|
+
addComponent(key: string, instance: unknown, classRef: constructor): void;
|
|
76
|
+
/** Returns component instances for a metadata key. */
|
|
77
|
+
getComponents(key: string): readonly {
|
|
78
|
+
instance: unknown;
|
|
79
|
+
classRef: constructor;
|
|
80
|
+
}[];
|
|
81
|
+
/** Returns a cached injectable instance, or undefined if not resolved yet. */
|
|
82
|
+
getInjectable<T>(ctor: constructor<T>): T | undefined;
|
|
83
|
+
/** Caches an injectable instance and tracks insertion order. */
|
|
84
|
+
setInjectable<T>(ctor: constructor<T>, instance: T): void;
|
|
85
|
+
/** All cached injectables as [classRef, instance] pairs. */
|
|
86
|
+
get injectables(): ReadonlyMap<constructor, unknown>;
|
|
87
|
+
/** Disposes all injectable instances in reverse insertion order. */
|
|
88
|
+
disposeInjectables(): Promise<void>;
|
|
89
|
+
/** Disposes all component instances in reverse order. */
|
|
90
|
+
disposeComponents(): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Disposes provider instances in reverse registration order.
|
|
93
|
+
*
|
|
94
|
+
* - With scope: only dispose SCOPED instances for that scope
|
|
95
|
+
* - Without scope: dispose all SINGLETON instances (full shutdown)
|
|
96
|
+
*/
|
|
97
|
+
dispose(scope?: Scope): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Validates a provider configuration.
|
|
100
|
+
* @throws CoreError (NOT_INJECTABLE) if a class provider lacks @injectable()
|
|
101
|
+
*/
|
|
102
|
+
private validateProvider;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { constructor } from '../core/core.types.js';
|
|
2
|
+
import type { ModuleMetadata } from './module.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Marks a class as a module.
|
|
5
|
+
*
|
|
6
|
+
* Modules are the building blocks of a stitchem application.
|
|
7
|
+
* They group related providers and define dependencies on other modules.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* @module({
|
|
12
|
+
* imports: [DatabaseModule],
|
|
13
|
+
* providers: [UserService, UserRepository],
|
|
14
|
+
* exports: [UserService],
|
|
15
|
+
* })
|
|
16
|
+
* class UserModule {}
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function module(metadata?: ModuleMetadata): (target: constructor, _context: ClassDecoratorContext) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Checks if a class is decorated with @module().
|
|
22
|
+
*/
|
|
23
|
+
export declare function isModule(target: constructor): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Gets the module metadata from a class, or undefined if not decorated.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getModuleMetadata(target: constructor): ModuleMetadata | undefined;
|
|
28
|
+
//# sourceMappingURL=module.decorator.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { flushPendingInjectTokens } from '../decorator/inject.decorator.js';
|
|
2
|
+
// WeakMap-based metadata storage.
|
|
3
|
+
// TODO: Replace with `context.metadata[key]` once transpilers ship decorator metadata support.
|
|
4
|
+
// That change is internal-only — the public API stays identical.
|
|
5
|
+
const registry = new WeakMap();
|
|
6
|
+
/**
|
|
7
|
+
* Marks a class as a module.
|
|
8
|
+
*
|
|
9
|
+
* Modules are the building blocks of a stitchem application.
|
|
10
|
+
* They group related providers and define dependencies on other modules.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* @module({
|
|
15
|
+
* imports: [DatabaseModule],
|
|
16
|
+
* providers: [UserService, UserRepository],
|
|
17
|
+
* exports: [UserService],
|
|
18
|
+
* })
|
|
19
|
+
* class UserModule {}
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function module(metadata = {}) {
|
|
23
|
+
return function (target, _context) {
|
|
24
|
+
registry.set(target, metadata);
|
|
25
|
+
// Flush any @inject() accessor tokens accumulated during class definition.
|
|
26
|
+
// TC39 order: accessor decorators run first, class decorator runs last.
|
|
27
|
+
flushPendingInjectTokens(target);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Checks if a class is decorated with @module().
|
|
32
|
+
*/
|
|
33
|
+
export function isModule(target) {
|
|
34
|
+
return registry.has(target);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Gets the module metadata from a class, or undefined if not decorated.
|
|
38
|
+
*/
|
|
39
|
+
export function getModuleMetadata(target) {
|
|
40
|
+
return registry.get(target);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=module.decorator.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { constructor } from '../core/core.types.js';
|
|
2
|
+
import type { DynamicModule } from './module.types.js';
|
|
3
|
+
import { Container } from '../container/container.js';
|
|
4
|
+
/**
|
|
5
|
+
* Analyzes and builds the module dependency graph.
|
|
6
|
+
* Creates Module instances and sets up their relationships.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ModuleGraph {
|
|
9
|
+
private readonly container;
|
|
10
|
+
/** Module IDs in topological order (dependencies first). */
|
|
11
|
+
readonly sortedIds: readonly string[];
|
|
12
|
+
/** Counter for generating unique DynamicModule instance IDs. */
|
|
13
|
+
private dynamicModuleCounter;
|
|
14
|
+
/** Tracks DynamicModule objects to their assigned IDs. */
|
|
15
|
+
private readonly dynamicModuleIds;
|
|
16
|
+
/** Internal nodes during graph building. */
|
|
17
|
+
private readonly nodes;
|
|
18
|
+
/** Tracks IDs currently being collected (prevents infinite recursion on cycles). */
|
|
19
|
+
private readonly visiting;
|
|
20
|
+
/**
|
|
21
|
+
* Builds the module graph and populates the container.
|
|
22
|
+
*/
|
|
23
|
+
constructor(rootModule: constructor | DynamicModule, container: Container);
|
|
24
|
+
/** The root module ID (last in topological order). */
|
|
25
|
+
get rootModuleId(): string;
|
|
26
|
+
/** Generates a unique ID for a module import. */
|
|
27
|
+
private getModuleId;
|
|
28
|
+
/** Collects all modules recursively. */
|
|
29
|
+
private collectModules;
|
|
30
|
+
/** Unwraps a ModuleImport to get the class and optional dynamic config. */
|
|
31
|
+
private unwrapModule;
|
|
32
|
+
/**
|
|
33
|
+
* Gets and validates module metadata.
|
|
34
|
+
* @throws CoreError (INVALID_MODULE) if the class is not decorated with @module()
|
|
35
|
+
*/
|
|
36
|
+
private getValidatedMetadata;
|
|
37
|
+
/**
|
|
38
|
+
* Merges base metadata with dynamic module config.
|
|
39
|
+
* Well-known keys are merged type-safely; augmented component keys
|
|
40
|
+
* (e.g. `routers: constructor[]`) are concatenated.
|
|
41
|
+
*/
|
|
42
|
+
private mergeMetadata;
|
|
43
|
+
/** Topological sort with global modules first. */
|
|
44
|
+
private toposort;
|
|
45
|
+
/** Collects all transitive import IDs reachable from the given root IDs. */
|
|
46
|
+
private collectTransitiveImports;
|
|
47
|
+
/** Creates Module instances and sets up relationships. */
|
|
48
|
+
private buildModules;
|
|
49
|
+
/** Checks if a constructor is the classRef of any imported module. */
|
|
50
|
+
private isImportedModule;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=module.graph.d.ts.map
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import toposort from 'toposort';
|
|
2
|
+
import { isDynamicModule } from './module.types.js';
|
|
3
|
+
import { isModule, getModuleMetadata } from './module.decorator.js';
|
|
4
|
+
import { Module } from './module.js';
|
|
5
|
+
import { CoreError } from '../errors/core.error.js';
|
|
6
|
+
/**
|
|
7
|
+
* Analyzes and builds the module dependency graph.
|
|
8
|
+
* Creates Module instances and sets up their relationships.
|
|
9
|
+
*/
|
|
10
|
+
export class ModuleGraph {
|
|
11
|
+
container;
|
|
12
|
+
/** Module IDs in topological order (dependencies first). */
|
|
13
|
+
sortedIds;
|
|
14
|
+
/** Counter for generating unique DynamicModule instance IDs. */
|
|
15
|
+
dynamicModuleCounter = 0;
|
|
16
|
+
/** Tracks DynamicModule objects to their assigned IDs. */
|
|
17
|
+
dynamicModuleIds = new WeakMap();
|
|
18
|
+
/** Internal nodes during graph building. */
|
|
19
|
+
nodes = new Map();
|
|
20
|
+
/** Tracks IDs currently being collected (prevents infinite recursion on cycles). */
|
|
21
|
+
visiting = new Set();
|
|
22
|
+
/**
|
|
23
|
+
* Builds the module graph and populates the container.
|
|
24
|
+
*/
|
|
25
|
+
constructor(rootModule, container) {
|
|
26
|
+
this.container = container;
|
|
27
|
+
const edges = [];
|
|
28
|
+
const globalIds = new Set();
|
|
29
|
+
this.collectModules(rootModule, globalIds, edges);
|
|
30
|
+
this.sortedIds = this.toposort(edges, globalIds);
|
|
31
|
+
this.buildModules();
|
|
32
|
+
this.container.buildGlobalExports();
|
|
33
|
+
}
|
|
34
|
+
/** The root module ID (last in topological order). */
|
|
35
|
+
get rootModuleId() {
|
|
36
|
+
return this.sortedIds[this.sortedIds.length - 1];
|
|
37
|
+
}
|
|
38
|
+
/** Generates a unique ID for a module import. */
|
|
39
|
+
getModuleId(moduleImport) {
|
|
40
|
+
if (isDynamicModule(moduleImport)) {
|
|
41
|
+
const existingId = this.dynamicModuleIds.get(moduleImport);
|
|
42
|
+
if (existingId)
|
|
43
|
+
return existingId;
|
|
44
|
+
const id = `${moduleImport.module.name}$${++this.dynamicModuleCounter}`;
|
|
45
|
+
this.dynamicModuleIds.set(moduleImport, id);
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
return moduleImport.name;
|
|
49
|
+
}
|
|
50
|
+
/** Collects all modules recursively. */
|
|
51
|
+
collectModules(moduleImport, globalIds, edges) {
|
|
52
|
+
const id = this.getModuleId(moduleImport);
|
|
53
|
+
if (this.nodes.has(id))
|
|
54
|
+
return id;
|
|
55
|
+
// Cycle guard: if we're already collecting this module's imports,
|
|
56
|
+
// return the ID without recursing (the node will be built when the
|
|
57
|
+
// original call unwinds). This allows circular module imports when
|
|
58
|
+
// lazy() is used to break the provider-level cycle.
|
|
59
|
+
if (this.visiting.has(id))
|
|
60
|
+
return id;
|
|
61
|
+
this.visiting.add(id);
|
|
62
|
+
const { moduleClass, dynamicConfig } = this.unwrapModule(moduleImport);
|
|
63
|
+
const baseMeta = this.getValidatedMetadata(moduleClass);
|
|
64
|
+
const mergedMeta = dynamicConfig
|
|
65
|
+
? this.mergeMetadata(baseMeta, dynamicConfig)
|
|
66
|
+
: { ...baseMeta };
|
|
67
|
+
const importIds = [];
|
|
68
|
+
for (const imported of mergedMeta.imports ?? []) {
|
|
69
|
+
const importedId = this.collectModules(imported, globalIds, edges);
|
|
70
|
+
importIds.push(importedId);
|
|
71
|
+
edges.push([id, importedId]);
|
|
72
|
+
}
|
|
73
|
+
const isGlobalModule = dynamicConfig?.global ?? baseMeta.global ?? false;
|
|
74
|
+
const node = {
|
|
75
|
+
id,
|
|
76
|
+
moduleClass,
|
|
77
|
+
metadata: mergedMeta,
|
|
78
|
+
isGlobal: isGlobalModule,
|
|
79
|
+
importIds,
|
|
80
|
+
};
|
|
81
|
+
this.nodes.set(id, node);
|
|
82
|
+
if (isGlobalModule)
|
|
83
|
+
globalIds.add(id);
|
|
84
|
+
return id;
|
|
85
|
+
}
|
|
86
|
+
/** Unwraps a ModuleImport to get the class and optional dynamic config. */
|
|
87
|
+
unwrapModule(moduleImport) {
|
|
88
|
+
if (isDynamicModule(moduleImport)) {
|
|
89
|
+
return { moduleClass: moduleImport.module, dynamicConfig: moduleImport };
|
|
90
|
+
}
|
|
91
|
+
return { moduleClass: moduleImport, dynamicConfig: null };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Gets and validates module metadata.
|
|
95
|
+
* @throws CoreError (INVALID_MODULE) if the class is not decorated with @module()
|
|
96
|
+
*/
|
|
97
|
+
getValidatedMetadata(moduleClass) {
|
|
98
|
+
const meta = getModuleMetadata(moduleClass);
|
|
99
|
+
if (!meta)
|
|
100
|
+
throw CoreError.invalidModule(moduleClass);
|
|
101
|
+
return meta;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Merges base metadata with dynamic module config.
|
|
105
|
+
* Well-known keys are merged type-safely; augmented component keys
|
|
106
|
+
* (e.g. `routers: constructor[]`) are concatenated.
|
|
107
|
+
*/
|
|
108
|
+
mergeMetadata(base, dynamic) {
|
|
109
|
+
const WELL_KNOWN = new Set([
|
|
110
|
+
'module',
|
|
111
|
+
'global',
|
|
112
|
+
'imports',
|
|
113
|
+
'providers',
|
|
114
|
+
'exports',
|
|
115
|
+
]);
|
|
116
|
+
const result = {
|
|
117
|
+
global: dynamic.global ?? base.global,
|
|
118
|
+
imports: [...(base.imports ?? []), ...(dynamic.imports ?? [])],
|
|
119
|
+
providers: [...(base.providers ?? []), ...(dynamic.providers ?? [])],
|
|
120
|
+
exports: [...(base.exports ?? []), ...(dynamic.exports ?? [])],
|
|
121
|
+
};
|
|
122
|
+
const baseExtras = base;
|
|
123
|
+
const dynExtras = dynamic;
|
|
124
|
+
const resultExtras = result;
|
|
125
|
+
for (const key of Object.keys(base)) {
|
|
126
|
+
if (!WELL_KNOWN.has(key)) {
|
|
127
|
+
resultExtras[key] = baseExtras[key];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const key of Object.keys(dynamic)) {
|
|
131
|
+
if (WELL_KNOWN.has(key))
|
|
132
|
+
continue;
|
|
133
|
+
const dynArr = dynExtras[key];
|
|
134
|
+
if (!dynArr)
|
|
135
|
+
continue;
|
|
136
|
+
const baseArr = resultExtras[key];
|
|
137
|
+
resultExtras[key] = baseArr ? [...baseArr, ...dynArr] : [...dynArr];
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
/** Topological sort with global modules first. */
|
|
142
|
+
toposort(edges, globalIds) {
|
|
143
|
+
// Add edges: non-global modules depend on global modules.
|
|
144
|
+
// Skip transitive imports of global modules to avoid creating cycles.
|
|
145
|
+
const globalImports = this.collectTransitiveImports(globalIds);
|
|
146
|
+
for (const [id, node] of this.nodes) {
|
|
147
|
+
if (!node.isGlobal && !globalImports.has(id)) {
|
|
148
|
+
for (const globalId of globalIds) {
|
|
149
|
+
edges.push([id, globalId]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
let sortedIds;
|
|
154
|
+
try {
|
|
155
|
+
sortedIds = toposort(edges).reverse();
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (error instanceof Error &&
|
|
159
|
+
error.message.toLowerCase().includes('cycl')) {
|
|
160
|
+
// Circular module imports are allowed when lazy() breaks the
|
|
161
|
+
// provider-level cycle. Fall back to globals-first insertion order.
|
|
162
|
+
const globals = [];
|
|
163
|
+
const rest = [];
|
|
164
|
+
for (const id of this.nodes.keys()) {
|
|
165
|
+
if (globalIds.has(id))
|
|
166
|
+
globals.push(id);
|
|
167
|
+
else
|
|
168
|
+
rest.push(id);
|
|
169
|
+
}
|
|
170
|
+
sortedIds = [...globals, ...rest];
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Handle modules with no dependencies (not in any edge).
|
|
177
|
+
const allIds = new Set(this.nodes.keys());
|
|
178
|
+
const idsInEdges = new Set(sortedIds);
|
|
179
|
+
for (const id of allIds) {
|
|
180
|
+
if (!idsInEdges.has(id))
|
|
181
|
+
sortedIds.unshift(id);
|
|
182
|
+
}
|
|
183
|
+
return sortedIds.filter((id) => this.nodes.has(id));
|
|
184
|
+
}
|
|
185
|
+
/** Collects all transitive import IDs reachable from the given root IDs. */
|
|
186
|
+
collectTransitiveImports(rootIds) {
|
|
187
|
+
const visited = new Set();
|
|
188
|
+
const stack = [...rootIds];
|
|
189
|
+
while (stack.length > 0) {
|
|
190
|
+
const id = stack.pop();
|
|
191
|
+
const node = this.nodes.get(id);
|
|
192
|
+
if (!node)
|
|
193
|
+
continue;
|
|
194
|
+
for (const importId of node.importIds) {
|
|
195
|
+
if (!visited.has(importId)) {
|
|
196
|
+
visited.add(importId);
|
|
197
|
+
stack.push(importId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return visited;
|
|
202
|
+
}
|
|
203
|
+
/** Creates Module instances and sets up relationships. */
|
|
204
|
+
buildModules() {
|
|
205
|
+
// First pass: create all Module instances.
|
|
206
|
+
for (const id of this.sortedIds) {
|
|
207
|
+
const node = this.nodes.get(id);
|
|
208
|
+
const mod = new Module({
|
|
209
|
+
id: node.id,
|
|
210
|
+
classRef: node.moduleClass,
|
|
211
|
+
metadata: node.metadata,
|
|
212
|
+
isGlobal: node.isGlobal,
|
|
213
|
+
});
|
|
214
|
+
for (const provider of node.metadata.providers ?? []) {
|
|
215
|
+
mod.addProvider(provider);
|
|
216
|
+
}
|
|
217
|
+
this.container.addModule(mod);
|
|
218
|
+
}
|
|
219
|
+
// Second pass: set up imports (now all modules exist).
|
|
220
|
+
for (const id of this.sortedIds) {
|
|
221
|
+
const node = this.nodes.get(id);
|
|
222
|
+
const mod = this.container.getModule(id);
|
|
223
|
+
for (const importedId of node.importIds) {
|
|
224
|
+
const importedModule = this.container.getModule(importedId);
|
|
225
|
+
if (importedModule)
|
|
226
|
+
mod.addImport(importedModule);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Third pass: register exports (imports must be wired first for module re-exports).
|
|
230
|
+
for (const id of this.sortedIds) {
|
|
231
|
+
const node = this.nodes.get(id);
|
|
232
|
+
const mod = this.container.getModule(id);
|
|
233
|
+
for (const token of node.metadata.exports ?? []) {
|
|
234
|
+
if (typeof token === 'function' && this.isImportedModule(mod, token)) {
|
|
235
|
+
// Module re-export: copy all exported tokens from matching imported modules.
|
|
236
|
+
for (const importedModule of mod.imports) {
|
|
237
|
+
if (importedModule.classRef === token) {
|
|
238
|
+
for (const exportedToken of importedModule.exports) {
|
|
239
|
+
mod.addExport(exportedToken);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else if (typeof token === 'function' && isModule(token)) {
|
|
245
|
+
// Module class in exports but not imported — error.
|
|
246
|
+
throw CoreError.moduleNotExported(token, mod.id);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
mod.addExport(token);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** Checks if a constructor is the classRef of any imported module. */
|
|
255
|
+
isImportedModule(mod, token) {
|
|
256
|
+
for (const importedModule of mod.imports) {
|
|
257
|
+
if (importedModule.classRef === token)
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=module.graph.js.map
|