@husky-di/module 1.0.0 → 1.0.1

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.
@@ -1,7 +1,34 @@
1
1
  /**
2
+ * Factory for creating export guard middleware.
3
+ *
2
4
  * @overview
5
+ * This middleware enforces export restrictions for module containers. It ensures
6
+ * that only explicitly exported service identifiers can be accessed from outside
7
+ * the container. Internal access (within the same container) is always allowed.
8
+ *
9
+ * @remarks
10
+ * The middleware distinguishes between internal and external access by checking
11
+ * the resolution path. If the previous resolution step occurred in the same
12
+ * container, it's considered internal access. Otherwise, it's external access
13
+ * and must be in the exports list.
14
+ *
3
15
  * @author AEPKILL
4
16
  * @created 2025-08-18 22:01:34
5
17
  */
6
18
  import { type ResolveMiddleware, type ServiceIdentifier } from "@husky-di/core";
7
- export declare function createExportedGuardMiddlewareFactory(exports: ServiceIdentifier<unknown>[]): ResolveMiddleware<any, any>;
19
+ /**
20
+ * Creates a middleware factory that guards against accessing non-exported services.
21
+ *
22
+ * @param exports - Array of service identifiers that are allowed to be accessed from outside the container
23
+ * @returns A resolve middleware that enforces export restrictions
24
+ *
25
+ * @remarks
26
+ * The middleware works by:
27
+ * 1. Checking if the current resolution is from within the same container (internal access)
28
+ * 2. If external access is detected, verifying that the service identifier is in the exports list
29
+ * 3. Throwing a ResolveException if a non-exported service is accessed externally
30
+ *
31
+ * Note: Services not registered in the container are not considered external access,
32
+ * as they will be resolved from parent containers or fail with a different error.
33
+ */
34
+ export declare function createExportedGuardMiddlewareFactory(exports: ReadonlyArray<ServiceIdentifier<unknown>>): ResolveMiddleware<any, any>;
@@ -3,7 +3,7 @@
3
3
  * @author AEPKILL
4
4
  * @created 2025-08-09 14:51:09
5
5
  */
6
- import type { IContainer, IsRegisteredOptions, ResolveInstance, ResolveMiddleware, ResolveOptions, ServiceIdentifier } from "@husky-di/core";
6
+ import { type IContainer, type IsRegisteredOptions, type ResolveInstance, type ResolveMiddleware, type ResolveOptions, type ServiceIdentifier } from "@husky-di/core";
7
7
  import type { Alias, CreateModuleOptions, Declaration, IModule, ModuleWithAliases } from "../interfaces/module.interface";
8
8
  export declare class Module implements IModule {
9
9
  get id(): string;
@@ -18,6 +18,8 @@ export declare class Module implements IModule {
18
18
  private _declarations?;
19
19
  private _imports?;
20
20
  private _exports?;
21
+ private readonly _visitedModules;
22
+ private readonly _visitStack;
21
23
  constructor(options: CreateModuleOptions);
22
24
  resolve<T, O extends ResolveOptions<T>>(serviceIdentifier: ServiceIdentifier<T>, options?: O): ResolveInstance<T, O>;
23
25
  isRegistered<T>(serviceIdentifier: ServiceIdentifier<T>, options?: IsRegisteredOptions): boolean;
@@ -25,4 +27,168 @@ export declare class Module implements IModule {
25
27
  use(middleware: ResolveMiddleware<any, any>): void;
26
28
  unused(middleware: ResolveMiddleware<any, any>): void;
27
29
  withAliases(aliases: Alias[]): ModuleWithAliases;
30
+ /**
31
+ * Builds the container for this module.
32
+ *
33
+ * @returns The built container
34
+ *
35
+ * @remarks
36
+ * This method is called after all validations have been performed.
37
+ * It creates the container and registers all declarations and imports.
38
+ */
39
+ private buildContainer;
40
+ /**
41
+ * Validates the module's imports.
42
+ *
43
+ * @throws Error if validation fails
44
+ *
45
+ * @remarks
46
+ * Validates the following rules:
47
+ * - Rule I1: Module uniqueness - no module imported multiple times
48
+ * - Rule I2: Circular dependencies - no circular import chains
49
+ * - Rule I3: Alias validation - aliases reference exported services and don't conflict
50
+ * - Rule I4: Import naming conflicts - no service name conflicts across imports
51
+ */
52
+ private validateImports;
53
+ /**
54
+ * Validates that no module is imported multiple times (Rule I1).
55
+ *
56
+ * @param imports - Array of imports to validate
57
+ * @throws Error if a module is imported more than once
58
+ *
59
+ * @remarks
60
+ * This prevents accidentally importing the same module multiple times,
61
+ * which would be redundant and potentially confusing.
62
+ */
63
+ private validateImportUniqueness;
64
+ /**
65
+ * Detects circular dependencies in the module import graph.
66
+ *
67
+ * @throws Error if a circular dependency is detected
68
+ */
69
+ private detectCircularDependencies;
70
+ /**
71
+ * Recursively visits a module to detect circular dependencies (Rule I2).
72
+ *
73
+ * @param module - The module to visit
74
+ * @throws Error if a circular dependency is detected
75
+ *
76
+ * @remarks
77
+ * Uses depth-first search with a visit stack to detect cycles.
78
+ * The visit stack tracks the current path being explored, while
79
+ * visitedModules tracks fully processed modules to avoid redundant work.
80
+ */
81
+ private visitModule;
82
+ /**
83
+ * Registers the module's local declarations in the container.
84
+ *
85
+ * @param container - The container to register declarations in
86
+ */
87
+ private registerDeclarations;
88
+ /**
89
+ * Registers the module's imports in the container.
90
+ *
91
+ * @param container - The container to register imports in
92
+ */
93
+ private registerImports;
94
+ /**
95
+ * Normalizes imports into a flat list of service identifiers with their aliases.
96
+ *
97
+ * @param imports - Array of imports to normalize
98
+ * @returns Normalized array of imports with alias mappings
99
+ *
100
+ * @remarks
101
+ * This method transforms the import declarations into a format suitable
102
+ * for container registration, applying any alias mappings in the process.
103
+ */
104
+ private normalizeImports;
105
+ /**
106
+ * Builds a map of service identifier aliases from an alias array.
107
+ *
108
+ * @param aliases - Array of alias mappings
109
+ * @returns Map from source service identifier to target service identifier
110
+ *
111
+ * @remarks
112
+ * This method is used throughout the validation and registration process
113
+ * to consistently apply alias transformations.
114
+ */
115
+ private buildAliasMap;
116
+ /**
117
+ * Validates declarations for this module.
118
+ *
119
+ * @throws Error if validation fails
120
+ *
121
+ * @remarks
122
+ * Validates that:
123
+ * 1. No serviceIdentifier is declared multiple times (Rule D1)
124
+ * 2. Each declaration has valid registration options (Rule D2)
125
+ */
126
+ private validateDeclarations;
127
+ /**
128
+ * Validates exports for this module.
129
+ *
130
+ * @throws Error if validation fails
131
+ *
132
+ * @remarks
133
+ * Validates that:
134
+ * 1. No serviceIdentifier is exported multiple times
135
+ * 2. Each exported serviceIdentifier is either declared locally or imported
136
+ * 3. Aliased imports must be exported using their alias name, not original name
137
+ */
138
+ private validateExports;
139
+ /**
140
+ * Collects all service identifiers that are available in this module.
141
+ *
142
+ * @returns Set of available service identifiers
143
+ *
144
+ * @remarks
145
+ * Available services include:
146
+ * 1. Locally declared services
147
+ * 2. Imported services (considering aliases)
148
+ *
149
+ * This is used by validateExports to ensure only available services are exported.
150
+ */
151
+ private collectAvailableServices;
152
+ /**
153
+ * Validates aliases for this module (called from withAliases).
154
+ *
155
+ * @param aliases - Array of alias mappings to validate
156
+ * @throws Error if validation fails
157
+ *
158
+ * @remarks
159
+ * Validates that:
160
+ * 1. Each aliased serviceIdentifier is exported by this module (Rule I3.1)
161
+ * 2. No serviceIdentifier is mapped multiple times (Rule I3.2)
162
+ *
163
+ * Note: Rule I3.3 (alias conflicts with declarations) is validated during
164
+ * module construction in validateAliasConflictsWithDeclarations()
165
+ */
166
+ private validateAliases;
167
+ /**
168
+ * Validates that aliases don't conflict with local declarations (Rule I3.3).
169
+ *
170
+ * @throws Error if an alias name conflicts with a local declaration
171
+ *
172
+ * @remarks
173
+ * This ensures that aliased imports don't shadow or conflict with
174
+ * services declared in the current module.
175
+ */
176
+ private validateAliasConflictsWithDeclarations;
177
+ /**
178
+ * Validates that imported services don't have naming conflicts (Rule I4).
179
+ *
180
+ * @throws Error if the same service identifier is exported by multiple modules
181
+ *
182
+ * @remarks
183
+ * This prevents ambiguity when multiple imported modules export services
184
+ * with the same name. Users should use aliases to resolve such conflicts.
185
+ */
186
+ private validateImportNamingConflicts;
187
+ /**
188
+ * Helper method to check if an import item is a ModuleWithAliases.
189
+ *
190
+ * @param item - The import item to check
191
+ * @returns True if the item is a ModuleWithAliases, false otherwise
192
+ */
193
+ private isModuleWithAliases;
28
194
  }
package/dist/index.cjs CHANGED
@@ -51,168 +51,6 @@ function findPreviousContainer(paths) {
51
51
  lastContainer = path.value.container;
52
52
  }
53
53
  }
54
- function isModuleWithAliases(moduleImport) {
55
- return moduleImport.module instanceof Module;
56
- }
57
- function getModuleByImport(moduleImport) {
58
- return isModuleWithAliases(moduleImport) ? moduleImport.module : moduleImport;
59
- }
60
- function build(module) {
61
- const builder = new ModuleBuilder(module);
62
- return builder.build();
63
- }
64
- class ModuleBuilder {
65
- module;
66
- serviceIdentifierMap = new Map();
67
- availableServiceIdentifiers = new Set();
68
- importAliasesCache = new Map();
69
- constructor(module){
70
- this.module = module;
71
- }
72
- build() {
73
- this.validateAndCollectInfo();
74
- if (this.module.container) return this.module.container;
75
- const container = (0, core_namespaceObject.createContainer)(this.module.name);
76
- if (this.module.exports?.length) container.use(createExportedGuardMiddlewareFactory(this.module.exports));
77
- this.registerDeclarations(container);
78
- this.registerImports(container);
79
- return container;
80
- }
81
- validateAndCollectInfo() {
82
- this.validateImportUniqueness();
83
- this.validateExportUniqueness();
84
- this.validateCircularDependencies();
85
- this.collectServiceInfoAndValidateConflicts();
86
- this.validateExportValidity();
87
- }
88
- validateImportUniqueness() {
89
- const { imports } = this.module;
90
- if (!imports?.length) return;
91
- const importModules = new Set();
92
- for (const importModule of imports)try {
93
- const importedModule = getModuleByImport(importModule);
94
- if (importModules.has(importedModule)) throw new Error(`Duplicate import module: "${importedModule.displayName}" in "${this.module.displayName}".`);
95
- importModules.add(importedModule);
96
- } catch (error) {
97
- throw new Error(`Invalid module import in "${this.module.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
98
- }
99
- }
100
- validateExportUniqueness() {
101
- const { exports: exports1 } = this.module;
102
- if (!exports1?.length) return;
103
- const existingExportServiceIdentifiers = new Set();
104
- for (const exported of exports1){
105
- if (existingExportServiceIdentifiers.has(exported)) throw new Error(`Duplicate export service identifier: "${(0, core_namespaceObject.getServiceIdentifierName)(exported)}" in "${this.module.displayName}".`);
106
- existingExportServiceIdentifiers.add(exported);
107
- }
108
- }
109
- validateCircularDependencies() {
110
- const visited = new Set();
111
- const visiting = new Set();
112
- const dependencyPath = [];
113
- this.detectCircularDependency(this.module, visited, visiting, dependencyPath);
114
- }
115
- detectCircularDependency(currentModule, visited, visiting, dependencyPath) {
116
- if (visited.has(currentModule)) return;
117
- if (visiting.has(currentModule)) {
118
- const cycleStartIndex = dependencyPath.findIndex((module)=>module === currentModule);
119
- const cyclePath = dependencyPath.slice(cycleStartIndex).concat(currentModule).map((module)=>module.displayName).join(" -> ");
120
- throw new Error(`Circular dependency detected: ${cyclePath}. Modules cannot have circular import relationships.`);
121
- }
122
- visiting.add(currentModule);
123
- dependencyPath.push(currentModule);
124
- const imports = currentModule.imports ?? [];
125
- for (const importModule of imports)try {
126
- const importedModule = getModuleByImport(importModule);
127
- this.detectCircularDependency(importedModule, visited, visiting, dependencyPath);
128
- } catch (error) {
129
- if (error instanceof Error && error.message.includes("Circular dependency detected")) throw error;
130
- throw new Error(`Failed to validate circular dependencies for "${currentModule.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
131
- }
132
- visiting.delete(currentModule);
133
- dependencyPath.pop();
134
- visited.add(currentModule);
135
- }
136
- collectServiceInfoAndValidateConflicts() {
137
- const { imports, declarations } = this.module;
138
- if (declarations?.length) for (const declaration of declarations){
139
- this.serviceIdentifierMap.set(declaration.serviceIdentifier, {
140
- type: "declaration",
141
- source: "declarations"
142
- });
143
- this.availableServiceIdentifiers.add(declaration.serviceIdentifier);
144
- }
145
- if (imports?.length) for (const importModule of imports)try {
146
- const importedModule = getModuleByImport(importModule);
147
- const exportedServices = importedModule.exports ?? [];
148
- const aliasesMap = this.buildAndCacheAliasesMap(importModule, importedModule);
149
- for (const exported of exportedServices){
150
- const existing = this.serviceIdentifierMap.get(exported);
151
- if (existing) {
152
- const conflictInfo = {
153
- serviceName: (0, core_namespaceObject.getServiceIdentifierName)(exported),
154
- currentModule: importedModule.displayName,
155
- existing,
156
- targetModule: this.module.displayName
157
- };
158
- throw new Error(this.buildConflictMessage(conflictInfo));
159
- }
160
- this.serviceIdentifierMap.set(exported, {
161
- type: "import",
162
- source: importedModule.displayName
163
- });
164
- this.availableServiceIdentifiers.add(exported);
165
- const alias = aliasesMap.get(exported);
166
- if (alias) this.availableServiceIdentifiers.add(alias);
167
- }
168
- } catch (error) {
169
- throw new Error(`Failed to validate imports in "${this.module.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
170
- }
171
- }
172
- validateExportValidity() {
173
- const { exports: exports1 } = this.module;
174
- if (!exports1?.length) return;
175
- for (const exported of exports1)if (!this.availableServiceIdentifiers.has(exported)) throw new Error(`Cannot export service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(exported)}" from "${this.module.displayName}": it is not declared in this module or imported from any imported module.`);
176
- }
177
- buildAndCacheAliasesMap(importModule, importedModule) {
178
- const cached = this.importAliasesCache.get(importedModule);
179
- if (cached) return cached;
180
- const aliasesMap = new Map();
181
- const moduleWithAliases = importModule;
182
- const aliases = moduleWithAliases.aliases ?? [];
183
- for (const alias of aliases)aliasesMap.set(alias.serviceIdentifier, alias.as);
184
- this.importAliasesCache.set(importedModule, aliasesMap);
185
- return aliasesMap;
186
- }
187
- registerDeclarations(container) {
188
- const { declarations } = this.module;
189
- if (!declarations?.length) return;
190
- for (const declaration of declarations){
191
- const { serviceIdentifier, ...rest } = declaration;
192
- container.register(serviceIdentifier, rest);
193
- }
194
- }
195
- registerImports(container) {
196
- const { imports } = this.module;
197
- if (!imports?.length) return;
198
- for (const importModule of imports){
199
- const importedModule = getModuleByImport(importModule);
200
- const aliasesMap = this.importAliasesCache.get(importedModule) ?? new Map();
201
- for (const exported of importedModule.exports ?? [])container.register(aliasesMap.get(exported) ?? exported, {
202
- useAlias: exported,
203
- getContainer () {
204
- return importedModule.container;
205
- }
206
- });
207
- }
208
- }
209
- buildConflictMessage(conflictInfo) {
210
- const { serviceName, currentModule, existing, targetModule } = conflictInfo;
211
- const conflictType = "declaration" === existing.type ? "declared in" : "exported by";
212
- const conflictSource = existing.source;
213
- return `Service identifier conflict: "${serviceName}" is exported by "${currentModule}" and ${conflictType} "${conflictSource}" in "${targetModule}".`;
214
- }
215
- }
216
54
  const createModuleId = (0, core_namespaceObject.incrementalIdFactory)("MODULE");
217
55
  class Module {
218
56
  get id() {
@@ -239,13 +77,19 @@ class Module {
239
77
  _declarations;
240
78
  _imports;
241
79
  _exports;
80
+ _visitedModules = new Set();
81
+ _visitStack = [];
242
82
  constructor(options){
243
83
  this._id = createModuleId();
244
84
  this._name = options.name;
245
85
  this._declarations = options.declarations;
246
86
  this._imports = options.imports;
247
87
  this._exports = options.exports;
248
- this.container = build(this);
88
+ this.validateDeclarations();
89
+ this.validateImports();
90
+ this.validateExports();
91
+ this.container = this.buildContainer();
92
+ this.container.use(createExportedGuardMiddlewareFactory(this.exports ?? []));
249
93
  }
250
94
  resolve(serviceIdentifier, options) {
251
95
  return this.container.resolve(serviceIdentifier, options);
@@ -263,11 +107,170 @@ class Module {
263
107
  this.container.unused(middleware);
264
108
  }
265
109
  withAliases(aliases) {
110
+ this.validateAliases(aliases);
266
111
  return {
267
112
  module: this,
268
113
  aliases
269
114
  };
270
115
  }
116
+ buildContainer() {
117
+ const container = (0, core_namespaceObject.createContainer)(this._name);
118
+ this.registerDeclarations(container);
119
+ this.registerImports(container);
120
+ return container;
121
+ }
122
+ validateImports() {
123
+ if (!this._imports || 0 === this._imports.length) return;
124
+ this.validateImportUniqueness(this._imports);
125
+ this.detectCircularDependencies();
126
+ this.validateAliasConflictsWithDeclarations();
127
+ this.validateImportNamingConflicts();
128
+ }
129
+ validateImportUniqueness(imports) {
130
+ const seenModules = new Set();
131
+ for (const item of imports){
132
+ const importedModule = this.isModuleWithAliases(item) ? item.module : item;
133
+ const moduleId = importedModule.id;
134
+ if (seenModules.has(moduleId)) throw new Error(`Duplicate import module: "${importedModule.displayName}" in "${this.displayName}".`);
135
+ seenModules.add(moduleId);
136
+ }
137
+ }
138
+ detectCircularDependencies() {
139
+ this._visitedModules.clear();
140
+ this._visitStack.length = 0;
141
+ this.visitModule(this);
142
+ }
143
+ visitModule(module) {
144
+ if (this._visitStack.includes(module)) {
145
+ const cycle = [
146
+ ...this._visitStack.slice(this._visitStack.indexOf(module)),
147
+ module
148
+ ];
149
+ const cyclePath = cycle.map((m)=>m.displayName).join(" \u2192 ");
150
+ throw new Error(`Circular dependency detected: ${cyclePath}`);
151
+ }
152
+ if (this._visitedModules.has(module.id)) return;
153
+ this._visitStack.push(module);
154
+ const imports = module.imports ?? [];
155
+ for (const item of imports){
156
+ const importedModule = this.isModuleWithAliases(item) ? item.module : item;
157
+ this.visitModule(importedModule);
158
+ }
159
+ this._visitedModules.add(module.id);
160
+ this._visitStack.pop();
161
+ }
162
+ registerDeclarations(container) {
163
+ if (!this._declarations || 0 === this._declarations.length) return;
164
+ for (const decl of this._declarations){
165
+ const { serviceIdentifier, ...options } = decl;
166
+ container.register(serviceIdentifier, options);
167
+ }
168
+ }
169
+ registerImports(container) {
170
+ if (!this._imports || 0 === this._imports.length) return;
171
+ const normalizedImports = this.normalizeImports(this._imports);
172
+ for (const { module: sourceModule, serviceIdentifier, as } of normalizedImports)if (serviceIdentifier !== as) container.register(as, {
173
+ useAlias: serviceIdentifier,
174
+ getContainer: ()=>sourceModule.container
175
+ });
176
+ else container.register(serviceIdentifier, {
177
+ useAlias: serviceIdentifier,
178
+ getContainer: ()=>sourceModule.container
179
+ });
180
+ }
181
+ normalizeImports(imports) {
182
+ return imports.flatMap((item)=>{
183
+ const module = this.isModuleWithAliases(item) ? item.module : item;
184
+ const aliases = this.isModuleWithAliases(item) ? item.aliases : void 0;
185
+ const aliasMap = this.buildAliasMap(aliases);
186
+ return (module.exports ?? []).map((serviceIdentifier)=>({
187
+ module,
188
+ serviceIdentifier,
189
+ as: aliasMap.get(serviceIdentifier) ?? serviceIdentifier
190
+ }));
191
+ });
192
+ }
193
+ buildAliasMap(aliases) {
194
+ return new Map((aliases ?? []).map((alias)=>[
195
+ alias.serviceIdentifier,
196
+ alias.as
197
+ ]));
198
+ }
199
+ validateDeclarations() {
200
+ if (!this._declarations || 0 === this._declarations.length) return;
201
+ const seen = new Set();
202
+ for (const decl of this._declarations){
203
+ const { serviceIdentifier } = decl;
204
+ if (seen.has(serviceIdentifier)) throw new Error(`Duplicate declaration of service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" in module "${this.displayName}".`);
205
+ seen.add(serviceIdentifier);
206
+ const hasValidOption = "useClass" in decl || "useFactory" in decl || "useValue" in decl || "useAlias" in decl;
207
+ if (!hasValidOption) throw new Error(`Invalid registration options for service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" in module "${this.displayName}": must specify useClass, useFactory, useValue, or useAlias.`);
208
+ }
209
+ }
210
+ validateExports() {
211
+ if (!this._exports || 0 === this._exports.length) return;
212
+ const seen = new Set();
213
+ for (const exportId of this._exports){
214
+ if (seen.has(exportId)) throw new Error(`Duplicate export of service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(exportId)}" in module "${this.displayName}".`);
215
+ seen.add(exportId);
216
+ }
217
+ const availableServices = this.collectAvailableServices();
218
+ for (const exportId of this._exports)if (!availableServices.has(exportId)) throw new Error(`Cannot export service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(exportId)}" from "${this.displayName}": it is not declared in this module or imported from any imported module.`);
219
+ }
220
+ collectAvailableServices() {
221
+ const localServices = (this._declarations ?? []).map((decl)=>decl.serviceIdentifier);
222
+ const importedServices = (this._imports ?? []).flatMap((item)=>{
223
+ const module = this.isModuleWithAliases(item) ? item.module : item;
224
+ const aliases = this.isModuleWithAliases(item) ? item.aliases : void 0;
225
+ const aliasMap = this.buildAliasMap(aliases);
226
+ return (module.exports ?? []).map((serviceId)=>aliasMap.get(serviceId) ?? serviceId);
227
+ });
228
+ return new Set([
229
+ ...localServices,
230
+ ...importedServices
231
+ ]);
232
+ }
233
+ validateAliases(aliases) {
234
+ if (!aliases || 0 === aliases.length) return;
235
+ const exportedSet = new Set(this._exports ?? []);
236
+ const mappedServices = new Set();
237
+ for (const alias of aliases){
238
+ const { serviceIdentifier } = alias;
239
+ if (!exportedSet.has(serviceIdentifier)) throw new Error(`Cannot alias service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" from module "${this.displayName}": it is not exported from that module.`);
240
+ if (mappedServices.has(serviceIdentifier)) throw new Error(`Duplicate alias mapping for service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" in module "${this.displayName}".`);
241
+ mappedServices.add(serviceIdentifier);
242
+ }
243
+ }
244
+ validateAliasConflictsWithDeclarations() {
245
+ if (!this._imports || !this._declarations) return;
246
+ const localDeclarations = new Set((this._declarations ?? []).map((decl)=>decl.serviceIdentifier));
247
+ const conflictingAlias = (this._imports ?? []).filter((item)=>this.isModuleWithAliases(item)).flatMap((item)=>item.aliases ?? []).find((alias)=>localDeclarations.has(alias.as));
248
+ if (conflictingAlias) throw new Error(`Alias "${(0, core_namespaceObject.getServiceIdentifierName)(conflictingAlias.as)}" conflicts with local declaration in module "${this.displayName}".`);
249
+ }
250
+ validateImportNamingConflicts() {
251
+ if (!this._imports || 0 === this._imports.length) return;
252
+ const serviceToModules = (this._imports ?? []).reduce((acc, item)=>{
253
+ const module = this.isModuleWithAliases(item) ? item.module : item;
254
+ const aliases = this.isModuleWithAliases(item) ? item.aliases : void 0;
255
+ const aliasMap = this.buildAliasMap(aliases);
256
+ for (const serviceId of module.exports ?? []){
257
+ const effectiveName = aliasMap.get(serviceId) ?? serviceId;
258
+ const modules = acc.get(effectiveName) ?? [];
259
+ modules.push(module);
260
+ acc.set(effectiveName, modules);
261
+ }
262
+ return acc;
263
+ }, new Map());
264
+ const conflicts = Array.from(serviceToModules.entries()).filter(([, modules])=>modules.length > 1);
265
+ if (conflicts.length > 0) {
266
+ const [serviceId, modules] = conflicts[0];
267
+ const moduleNames = modules.map((m)=>`"${m.displayName}"`).join(", ");
268
+ throw new Error(`Service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceId)}" is exported by multiple imported modules: ${moduleNames}. Consider using aliases to resolve the conflict.`);
269
+ }
270
+ }
271
+ isModuleWithAliases(item) {
272
+ return "module" in item;
273
+ }
271
274
  }
272
275
  function createModule(options) {
273
276
  return new Module(options);
package/dist/index.js CHANGED
@@ -23,168 +23,6 @@ function findPreviousContainer(paths) {
23
23
  lastContainer = path.value.container;
24
24
  }
25
25
  }
26
- function isModuleWithAliases(moduleImport) {
27
- return moduleImport.module instanceof Module;
28
- }
29
- function getModuleByImport(moduleImport) {
30
- return isModuleWithAliases(moduleImport) ? moduleImport.module : moduleImport;
31
- }
32
- function build(module) {
33
- const builder = new ModuleBuilder(module);
34
- return builder.build();
35
- }
36
- class ModuleBuilder {
37
- module;
38
- serviceIdentifierMap = new Map();
39
- availableServiceIdentifiers = new Set();
40
- importAliasesCache = new Map();
41
- constructor(module){
42
- this.module = module;
43
- }
44
- build() {
45
- this.validateAndCollectInfo();
46
- if (this.module.container) return this.module.container;
47
- const container = createContainer(this.module.name);
48
- if (this.module.exports?.length) container.use(createExportedGuardMiddlewareFactory(this.module.exports));
49
- this.registerDeclarations(container);
50
- this.registerImports(container);
51
- return container;
52
- }
53
- validateAndCollectInfo() {
54
- this.validateImportUniqueness();
55
- this.validateExportUniqueness();
56
- this.validateCircularDependencies();
57
- this.collectServiceInfoAndValidateConflicts();
58
- this.validateExportValidity();
59
- }
60
- validateImportUniqueness() {
61
- const { imports } = this.module;
62
- if (!imports?.length) return;
63
- const importModules = new Set();
64
- for (const importModule of imports)try {
65
- const importedModule = getModuleByImport(importModule);
66
- if (importModules.has(importedModule)) throw new Error(`Duplicate import module: "${importedModule.displayName}" in "${this.module.displayName}".`);
67
- importModules.add(importedModule);
68
- } catch (error) {
69
- throw new Error(`Invalid module import in "${this.module.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
70
- }
71
- }
72
- validateExportUniqueness() {
73
- const { exports } = this.module;
74
- if (!exports?.length) return;
75
- const existingExportServiceIdentifiers = new Set();
76
- for (const exported of exports){
77
- if (existingExportServiceIdentifiers.has(exported)) throw new Error(`Duplicate export service identifier: "${getServiceIdentifierName(exported)}" in "${this.module.displayName}".`);
78
- existingExportServiceIdentifiers.add(exported);
79
- }
80
- }
81
- validateCircularDependencies() {
82
- const visited = new Set();
83
- const visiting = new Set();
84
- const dependencyPath = [];
85
- this.detectCircularDependency(this.module, visited, visiting, dependencyPath);
86
- }
87
- detectCircularDependency(currentModule, visited, visiting, dependencyPath) {
88
- if (visited.has(currentModule)) return;
89
- if (visiting.has(currentModule)) {
90
- const cycleStartIndex = dependencyPath.findIndex((module)=>module === currentModule);
91
- const cyclePath = dependencyPath.slice(cycleStartIndex).concat(currentModule).map((module)=>module.displayName).join(" -> ");
92
- throw new Error(`Circular dependency detected: ${cyclePath}. Modules cannot have circular import relationships.`);
93
- }
94
- visiting.add(currentModule);
95
- dependencyPath.push(currentModule);
96
- const imports = currentModule.imports ?? [];
97
- for (const importModule of imports)try {
98
- const importedModule = getModuleByImport(importModule);
99
- this.detectCircularDependency(importedModule, visited, visiting, dependencyPath);
100
- } catch (error) {
101
- if (error instanceof Error && error.message.includes("Circular dependency detected")) throw error;
102
- throw new Error(`Failed to validate circular dependencies for "${currentModule.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
103
- }
104
- visiting.delete(currentModule);
105
- dependencyPath.pop();
106
- visited.add(currentModule);
107
- }
108
- collectServiceInfoAndValidateConflicts() {
109
- const { imports, declarations } = this.module;
110
- if (declarations?.length) for (const declaration of declarations){
111
- this.serviceIdentifierMap.set(declaration.serviceIdentifier, {
112
- type: "declaration",
113
- source: "declarations"
114
- });
115
- this.availableServiceIdentifiers.add(declaration.serviceIdentifier);
116
- }
117
- if (imports?.length) for (const importModule of imports)try {
118
- const importedModule = getModuleByImport(importModule);
119
- const exportedServices = importedModule.exports ?? [];
120
- const aliasesMap = this.buildAndCacheAliasesMap(importModule, importedModule);
121
- for (const exported of exportedServices){
122
- const existing = this.serviceIdentifierMap.get(exported);
123
- if (existing) {
124
- const conflictInfo = {
125
- serviceName: getServiceIdentifierName(exported),
126
- currentModule: importedModule.displayName,
127
- existing,
128
- targetModule: this.module.displayName
129
- };
130
- throw new Error(this.buildConflictMessage(conflictInfo));
131
- }
132
- this.serviceIdentifierMap.set(exported, {
133
- type: "import",
134
- source: importedModule.displayName
135
- });
136
- this.availableServiceIdentifiers.add(exported);
137
- const alias = aliasesMap.get(exported);
138
- if (alias) this.availableServiceIdentifiers.add(alias);
139
- }
140
- } catch (error) {
141
- throw new Error(`Failed to validate imports in "${this.module.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
142
- }
143
- }
144
- validateExportValidity() {
145
- const { exports } = this.module;
146
- if (!exports?.length) return;
147
- for (const exported of exports)if (!this.availableServiceIdentifiers.has(exported)) throw new Error(`Cannot export service identifier "${getServiceIdentifierName(exported)}" from "${this.module.displayName}": it is not declared in this module or imported from any imported module.`);
148
- }
149
- buildAndCacheAliasesMap(importModule, importedModule) {
150
- const cached = this.importAliasesCache.get(importedModule);
151
- if (cached) return cached;
152
- const aliasesMap = new Map();
153
- const moduleWithAliases = importModule;
154
- const aliases = moduleWithAliases.aliases ?? [];
155
- for (const alias of aliases)aliasesMap.set(alias.serviceIdentifier, alias.as);
156
- this.importAliasesCache.set(importedModule, aliasesMap);
157
- return aliasesMap;
158
- }
159
- registerDeclarations(container) {
160
- const { declarations } = this.module;
161
- if (!declarations?.length) return;
162
- for (const declaration of declarations){
163
- const { serviceIdentifier, ...rest } = declaration;
164
- container.register(serviceIdentifier, rest);
165
- }
166
- }
167
- registerImports(container) {
168
- const { imports } = this.module;
169
- if (!imports?.length) return;
170
- for (const importModule of imports){
171
- const importedModule = getModuleByImport(importModule);
172
- const aliasesMap = this.importAliasesCache.get(importedModule) ?? new Map();
173
- for (const exported of importedModule.exports ?? [])container.register(aliasesMap.get(exported) ?? exported, {
174
- useAlias: exported,
175
- getContainer () {
176
- return importedModule.container;
177
- }
178
- });
179
- }
180
- }
181
- buildConflictMessage(conflictInfo) {
182
- const { serviceName, currentModule, existing, targetModule } = conflictInfo;
183
- const conflictType = "declaration" === existing.type ? "declared in" : "exported by";
184
- const conflictSource = existing.source;
185
- return `Service identifier conflict: "${serviceName}" is exported by "${currentModule}" and ${conflictType} "${conflictSource}" in "${targetModule}".`;
186
- }
187
- }
188
26
  const createModuleId = incrementalIdFactory("MODULE");
189
27
  class Module {
190
28
  get id() {
@@ -211,13 +49,19 @@ class Module {
211
49
  _declarations;
212
50
  _imports;
213
51
  _exports;
52
+ _visitedModules = new Set();
53
+ _visitStack = [];
214
54
  constructor(options){
215
55
  this._id = createModuleId();
216
56
  this._name = options.name;
217
57
  this._declarations = options.declarations;
218
58
  this._imports = options.imports;
219
59
  this._exports = options.exports;
220
- this.container = build(this);
60
+ this.validateDeclarations();
61
+ this.validateImports();
62
+ this.validateExports();
63
+ this.container = this.buildContainer();
64
+ this.container.use(createExportedGuardMiddlewareFactory(this.exports ?? []));
221
65
  }
222
66
  resolve(serviceIdentifier, options) {
223
67
  return this.container.resolve(serviceIdentifier, options);
@@ -235,11 +79,170 @@ class Module {
235
79
  this.container.unused(middleware);
236
80
  }
237
81
  withAliases(aliases) {
82
+ this.validateAliases(aliases);
238
83
  return {
239
84
  module: this,
240
85
  aliases
241
86
  };
242
87
  }
88
+ buildContainer() {
89
+ const container = createContainer(this._name);
90
+ this.registerDeclarations(container);
91
+ this.registerImports(container);
92
+ return container;
93
+ }
94
+ validateImports() {
95
+ if (!this._imports || 0 === this._imports.length) return;
96
+ this.validateImportUniqueness(this._imports);
97
+ this.detectCircularDependencies();
98
+ this.validateAliasConflictsWithDeclarations();
99
+ this.validateImportNamingConflicts();
100
+ }
101
+ validateImportUniqueness(imports) {
102
+ const seenModules = new Set();
103
+ for (const item of imports){
104
+ const importedModule = this.isModuleWithAliases(item) ? item.module : item;
105
+ const moduleId = importedModule.id;
106
+ if (seenModules.has(moduleId)) throw new Error(`Duplicate import module: "${importedModule.displayName}" in "${this.displayName}".`);
107
+ seenModules.add(moduleId);
108
+ }
109
+ }
110
+ detectCircularDependencies() {
111
+ this._visitedModules.clear();
112
+ this._visitStack.length = 0;
113
+ this.visitModule(this);
114
+ }
115
+ visitModule(module) {
116
+ if (this._visitStack.includes(module)) {
117
+ const cycle = [
118
+ ...this._visitStack.slice(this._visitStack.indexOf(module)),
119
+ module
120
+ ];
121
+ const cyclePath = cycle.map((m)=>m.displayName).join(" \u2192 ");
122
+ throw new Error(`Circular dependency detected: ${cyclePath}`);
123
+ }
124
+ if (this._visitedModules.has(module.id)) return;
125
+ this._visitStack.push(module);
126
+ const imports = module.imports ?? [];
127
+ for (const item of imports){
128
+ const importedModule = this.isModuleWithAliases(item) ? item.module : item;
129
+ this.visitModule(importedModule);
130
+ }
131
+ this._visitedModules.add(module.id);
132
+ this._visitStack.pop();
133
+ }
134
+ registerDeclarations(container) {
135
+ if (!this._declarations || 0 === this._declarations.length) return;
136
+ for (const decl of this._declarations){
137
+ const { serviceIdentifier, ...options } = decl;
138
+ container.register(serviceIdentifier, options);
139
+ }
140
+ }
141
+ registerImports(container) {
142
+ if (!this._imports || 0 === this._imports.length) return;
143
+ const normalizedImports = this.normalizeImports(this._imports);
144
+ for (const { module: sourceModule, serviceIdentifier, as } of normalizedImports)if (serviceIdentifier !== as) container.register(as, {
145
+ useAlias: serviceIdentifier,
146
+ getContainer: ()=>sourceModule.container
147
+ });
148
+ else container.register(serviceIdentifier, {
149
+ useAlias: serviceIdentifier,
150
+ getContainer: ()=>sourceModule.container
151
+ });
152
+ }
153
+ normalizeImports(imports) {
154
+ return imports.flatMap((item)=>{
155
+ const module = this.isModuleWithAliases(item) ? item.module : item;
156
+ const aliases = this.isModuleWithAliases(item) ? item.aliases : void 0;
157
+ const aliasMap = this.buildAliasMap(aliases);
158
+ return (module.exports ?? []).map((serviceIdentifier)=>({
159
+ module,
160
+ serviceIdentifier,
161
+ as: aliasMap.get(serviceIdentifier) ?? serviceIdentifier
162
+ }));
163
+ });
164
+ }
165
+ buildAliasMap(aliases) {
166
+ return new Map((aliases ?? []).map((alias)=>[
167
+ alias.serviceIdentifier,
168
+ alias.as
169
+ ]));
170
+ }
171
+ validateDeclarations() {
172
+ if (!this._declarations || 0 === this._declarations.length) return;
173
+ const seen = new Set();
174
+ for (const decl of this._declarations){
175
+ const { serviceIdentifier } = decl;
176
+ if (seen.has(serviceIdentifier)) throw new Error(`Duplicate declaration of service identifier "${getServiceIdentifierName(serviceIdentifier)}" in module "${this.displayName}".`);
177
+ seen.add(serviceIdentifier);
178
+ const hasValidOption = "useClass" in decl || "useFactory" in decl || "useValue" in decl || "useAlias" in decl;
179
+ if (!hasValidOption) throw new Error(`Invalid registration options for service identifier "${getServiceIdentifierName(serviceIdentifier)}" in module "${this.displayName}": must specify useClass, useFactory, useValue, or useAlias.`);
180
+ }
181
+ }
182
+ validateExports() {
183
+ if (!this._exports || 0 === this._exports.length) return;
184
+ const seen = new Set();
185
+ for (const exportId of this._exports){
186
+ if (seen.has(exportId)) throw new Error(`Duplicate export of service identifier "${getServiceIdentifierName(exportId)}" in module "${this.displayName}".`);
187
+ seen.add(exportId);
188
+ }
189
+ const availableServices = this.collectAvailableServices();
190
+ for (const exportId of this._exports)if (!availableServices.has(exportId)) throw new Error(`Cannot export service identifier "${getServiceIdentifierName(exportId)}" from "${this.displayName}": it is not declared in this module or imported from any imported module.`);
191
+ }
192
+ collectAvailableServices() {
193
+ const localServices = (this._declarations ?? []).map((decl)=>decl.serviceIdentifier);
194
+ const importedServices = (this._imports ?? []).flatMap((item)=>{
195
+ const module = this.isModuleWithAliases(item) ? item.module : item;
196
+ const aliases = this.isModuleWithAliases(item) ? item.aliases : void 0;
197
+ const aliasMap = this.buildAliasMap(aliases);
198
+ return (module.exports ?? []).map((serviceId)=>aliasMap.get(serviceId) ?? serviceId);
199
+ });
200
+ return new Set([
201
+ ...localServices,
202
+ ...importedServices
203
+ ]);
204
+ }
205
+ validateAliases(aliases) {
206
+ if (!aliases || 0 === aliases.length) return;
207
+ const exportedSet = new Set(this._exports ?? []);
208
+ const mappedServices = new Set();
209
+ for (const alias of aliases){
210
+ const { serviceIdentifier } = alias;
211
+ if (!exportedSet.has(serviceIdentifier)) throw new Error(`Cannot alias service identifier "${getServiceIdentifierName(serviceIdentifier)}" from module "${this.displayName}": it is not exported from that module.`);
212
+ if (mappedServices.has(serviceIdentifier)) throw new Error(`Duplicate alias mapping for service identifier "${getServiceIdentifierName(serviceIdentifier)}" in module "${this.displayName}".`);
213
+ mappedServices.add(serviceIdentifier);
214
+ }
215
+ }
216
+ validateAliasConflictsWithDeclarations() {
217
+ if (!this._imports || !this._declarations) return;
218
+ const localDeclarations = new Set((this._declarations ?? []).map((decl)=>decl.serviceIdentifier));
219
+ const conflictingAlias = (this._imports ?? []).filter((item)=>this.isModuleWithAliases(item)).flatMap((item)=>item.aliases ?? []).find((alias)=>localDeclarations.has(alias.as));
220
+ if (conflictingAlias) throw new Error(`Alias "${getServiceIdentifierName(conflictingAlias.as)}" conflicts with local declaration in module "${this.displayName}".`);
221
+ }
222
+ validateImportNamingConflicts() {
223
+ if (!this._imports || 0 === this._imports.length) return;
224
+ const serviceToModules = (this._imports ?? []).reduce((acc, item)=>{
225
+ const module = this.isModuleWithAliases(item) ? item.module : item;
226
+ const aliases = this.isModuleWithAliases(item) ? item.aliases : void 0;
227
+ const aliasMap = this.buildAliasMap(aliases);
228
+ for (const serviceId of module.exports ?? []){
229
+ const effectiveName = aliasMap.get(serviceId) ?? serviceId;
230
+ const modules = acc.get(effectiveName) ?? [];
231
+ modules.push(module);
232
+ acc.set(effectiveName, modules);
233
+ }
234
+ return acc;
235
+ }, new Map());
236
+ const conflicts = Array.from(serviceToModules.entries()).filter(([, modules])=>modules.length > 1);
237
+ if (conflicts.length > 0) {
238
+ const [serviceId, modules] = conflicts[0];
239
+ const moduleNames = modules.map((m)=>`"${m.displayName}"`).join(", ");
240
+ throw new Error(`Service identifier "${getServiceIdentifierName(serviceId)}" is exported by multiple imported modules: ${moduleNames}. Consider using aliases to resolve the conflict.`);
241
+ }
242
+ }
243
+ isModuleWithAliases(item) {
244
+ return "module" in item;
245
+ }
243
246
  }
244
247
  function createModule(options) {
245
248
  return new Module(options);
@@ -5,11 +5,11 @@
5
5
  */
6
6
  import type { CreateRegistrationOptions, IContainer, IDisplayName, IUnique, ServiceIdentifier } from "@husky-di/core";
7
7
  export type Declaration<T> = CreateRegistrationOptions<T> & {
8
- serviceIdentifier: ServiceIdentifier<T> | string;
8
+ readonly serviceIdentifier: ServiceIdentifier<T>;
9
9
  };
10
10
  export type Alias = {
11
- serviceIdentifier: ServiceIdentifier<unknown>;
12
- as: ServiceIdentifier<unknown>;
11
+ readonly serviceIdentifier: ServiceIdentifier<unknown>;
12
+ readonly as: ServiceIdentifier<unknown>;
13
13
  };
14
14
  export type CreateModuleOptions = {
15
15
  readonly name: string;
@@ -18,14 +18,14 @@ export type CreateModuleOptions = {
18
18
  readonly exports?: ServiceIdentifier<unknown>[];
19
19
  };
20
20
  export type ModuleWithAliases = {
21
- module: IModule;
22
- aliases?: Alias[];
21
+ readonly module: IModule;
22
+ readonly aliases?: Alias[];
23
23
  };
24
24
  export interface IModule extends IUnique, IDisplayName, Pick<IContainer, "resolve" | "isRegistered" | "getServiceIdentifiers" | "use" | "unused"> {
25
25
  readonly name: string;
26
- readonly declarations?: Declaration<unknown>[];
27
- readonly imports?: Array<IModule | ModuleWithAliases>;
28
- readonly exports?: ServiceIdentifier<unknown>[];
26
+ readonly declarations?: ReadonlyArray<Declaration<unknown>>;
27
+ readonly imports?: ReadonlyArray<IModule | ModuleWithAliases>;
28
+ readonly exports?: ReadonlyArray<ServiceIdentifier<unknown>>;
29
29
  readonly container: IContainer;
30
30
  withAliases(aliases: Alias[]): ModuleWithAliases;
31
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@husky-di/module",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@husky-di/core": "1.0.0"
18
+ "@husky-di/core": "1.0.1"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@rslib/core": "^0.11.2",
@@ -23,6 +23,9 @@
23
23
  "typescript": "^5.9.2",
24
24
  "vitest": "^3.2.4"
25
25
  },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
26
29
  "scripts": {
27
30
  "build": "rslib build",
28
31
  "dev": "rslib build --watch",
@@ -1,126 +0,0 @@
1
- /**
2
- * @overview 模块工具函数,包含模块构建和验证逻辑
3
- * @author AEPKILL
4
- * @created 2025-08-12 19:57:50
5
- */
6
- import { type IContainer } from "@husky-di/core";
7
- import type { CreateModuleOptions, IModule, ModuleWithAliases } from "../interfaces/module.interface";
8
- /**
9
- * 类型守卫:检查模块导入是否包含别名映射
10
- *
11
- * @param moduleImport 模块导入对象
12
- * @returns 如果包含别名映射则返回 true,否则返回 false
13
- */
14
- export declare function isModuleWithAliases(moduleImport: NonNullable<CreateModuleOptions["imports"]>[number]): moduleImport is ModuleWithAliases;
15
- /**
16
- * 从模块导入中获取实际的模块对象
17
- *
18
- * @param moduleImport 模块导入(可能包含别名)
19
- * @returns 实际的模块对象
20
- */
21
- export declare function getModuleByImport(moduleImport: NonNullable<CreateModuleOptions["imports"]>[number]): IModule;
22
- /**
23
- * 构建模块容器的公共函数
24
- *
25
- * @param module 要构建的模块
26
- * @returns 构建好的容器
27
- */
28
- export declare function build(module: IModule): IContainer;
29
- /**
30
- * 模块构建器类,整合模块验证和构建逻辑
31
- *
32
- * 在验证过程中收集服务标识符信息,并在构建过程中复用这些信息
33
- */
34
- export declare class ModuleBuilder {
35
- /** 要构建的模块 */
36
- private readonly module;
37
- /** 服务标识符映射表(验证时构建,构建时复用) */
38
- private readonly serviceIdentifierMap;
39
- /** 可用服务标识符集合(验证时构建,构建时复用) */
40
- private readonly availableServiceIdentifiers;
41
- /** 导入模块的别名映射缓存 */
42
- private readonly importAliasesCache;
43
- constructor(module: IModule);
44
- /**
45
- * 构建模块容器
46
- *
47
- * @returns 构建好的容器
48
- * @throws {Error} 当模块配置无效时抛出错误
49
- */
50
- build(): IContainer;
51
- /**
52
- * 验证模块并收集信息
53
- *
54
- * @throws {Error} 当模块配置无效时抛出错误
55
- */
56
- validateAndCollectInfo(): void;
57
- /**
58
- * 验证导入模块的唯一性
59
- *
60
- * @throws {Error} 当存在重复导入时抛出错误
61
- */
62
- private validateImportUniqueness;
63
- /**
64
- * 验证导出服务标识符的唯一性
65
- *
66
- * @throws {Error} 当存在重复导出时抛出错误
67
- */
68
- private validateExportUniqueness;
69
- /**
70
- * 验证循环依赖
71
- *
72
- * 使用深度优先搜索(DFS)算法检测模块之间的循环依赖
73
- *
74
- * @throws {Error} 当检测到循环依赖时抛出错误
75
- */
76
- private validateCircularDependencies;
77
- /**
78
- * 递归检测循环依赖的核心方法
79
- *
80
- * @param currentModule 当前检查的模块
81
- * @param visited 已完全访问过的模块集合(白色节点)
82
- * @param visiting 正在访问中的模块集合(灰色节点)
83
- * @param dependencyPath 当前依赖路径,用于构建错误信息
84
- * @throws {Error} 当检测到循环依赖时抛出错误
85
- */
86
- private detectCircularDependency;
87
- /**
88
- * 收集服务标识符信息并验证冲突
89
- *
90
- * @throws {Error} 当存在服务标识符冲突时抛出错误
91
- */
92
- private collectServiceInfoAndValidateConflicts;
93
- /**
94
- * 验证导出服务标识符的有效性
95
- *
96
- * @throws {Error} 当导出的服务标识符不可用时抛出错误
97
- */
98
- private validateExportValidity;
99
- /**
100
- * 构建并缓存别名映射
101
- *
102
- * @param importModule 导入模块配置
103
- * @param importedModule 实际导入的模块
104
- * @returns 别名映射
105
- */
106
- private buildAndCacheAliasesMap;
107
- /**
108
- * 注册声明的服务
109
- *
110
- * @param container 目标容器
111
- */
112
- private registerDeclarations;
113
- /**
114
- * 注册导入的服务
115
- *
116
- * @param container 目标容器
117
- */
118
- private registerImports;
119
- /**
120
- * 构建服务标识符冲突的详细错误消息
121
- *
122
- * @param conflictInfo 包含冲突详细信息的对象
123
- * @returns 格式化的错误消息字符串
124
- */
125
- private buildConflictMessage;
126
- }