@husky-di/module 1.0.1 → 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.
- package/dist/enums/module-error-code.enum.d.ts +57 -0
- package/dist/exceptions/module.exception.d.ts +12 -0
- package/dist/factories/exported-guard-middleware.factory.d.ts +5 -1
- package/dist/factories/import-scope.factory.d.ts +12 -0
- package/dist/impls/ModuleImpl.d.ts +35 -0
- package/dist/index.cjs +181 -155
- package/dist/index.d.ts +3 -0
- package/dist/index.js +174 -154
- package/dist/types/import-scope.type.d.ts +22 -0
- package/dist/types/module-error-code.type.d.ts +7 -0
- package/dist/utils/module-import.utils.d.ts +11 -0
- package/dist/utils/module-validator.utils.d.ts +18 -0
- package/package.json +6 -6
- package/dist/impls/module.d.ts +0 -194
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @overview Module error code enum.
|
|
3
|
+
* @author AEPKILL
|
|
4
|
+
* @created 2026-05-09 00:00:00
|
|
5
|
+
*/
|
|
6
|
+
export declare enum ModuleErrorCodeEnum {
|
|
7
|
+
/**
|
|
8
|
+
* Module contains multiple declarations with the same ServiceIdentifier.
|
|
9
|
+
* @see SPECIFICATION.md Section 4.1 D1
|
|
10
|
+
*/
|
|
11
|
+
E_DUPLICATE_DECLARATION = "E_DUPLICATE_DECLARATION",
|
|
12
|
+
/**
|
|
13
|
+
* Declaration lacks valid registration strategy (useClass, useFactory, useValue, or useAlias).
|
|
14
|
+
* @see SPECIFICATION.md Section 4.1 D2
|
|
15
|
+
*/
|
|
16
|
+
E_INVALID_REGISTRATION = "E_INVALID_REGISTRATION",
|
|
17
|
+
/**
|
|
18
|
+
* Same module instance imported multiple times in a single import list.
|
|
19
|
+
* @see SPECIFICATION.md Section 4.2 I1
|
|
20
|
+
*/
|
|
21
|
+
E_DUPLICATE_IMPORT_MODULE = "E_DUPLICATE_IMPORT_MODULE",
|
|
22
|
+
/**
|
|
23
|
+
* Circular dependency detected in module import graph.
|
|
24
|
+
* @see SPECIFICATION.md Section 4.2 I2
|
|
25
|
+
*/
|
|
26
|
+
E_CIRCULAR_DEPENDENCY = "E_CIRCULAR_DEPENDENCY",
|
|
27
|
+
/**
|
|
28
|
+
* Multiple imported modules export the same ServiceIdentifier without alias resolution.
|
|
29
|
+
* @see SPECIFICATION.md Section 4.2 I3
|
|
30
|
+
*/
|
|
31
|
+
E_IMPORT_COLLISION = "E_IMPORT_COLLISION",
|
|
32
|
+
/**
|
|
33
|
+
* Imported ServiceIdentifier conflicts with a local declaration in the importing module.
|
|
34
|
+
* @see SPECIFICATION.md Section 4.2 I4
|
|
35
|
+
*/
|
|
36
|
+
E_IMPORT_CONFLICT_LOCAL = "E_IMPORT_CONFLICT_LOCAL",
|
|
37
|
+
/**
|
|
38
|
+
* Alias source ServiceIdentifier is not exported by the source module.
|
|
39
|
+
* @see SPECIFICATION.md Section 5.1 Resolution Logic
|
|
40
|
+
*/
|
|
41
|
+
E_ALIAS_SOURCE_NOT_EXPORTED = "E_ALIAS_SOURCE_NOT_EXPORTED",
|
|
42
|
+
/**
|
|
43
|
+
* Same source ServiceIdentifier mapped multiple times within a single alias list.
|
|
44
|
+
* @see SPECIFICATION.md Section 5.1 Resolution Logic
|
|
45
|
+
*/
|
|
46
|
+
E_DUPLICATE_ALIAS_MAP = "E_DUPLICATE_ALIAS_MAP",
|
|
47
|
+
/**
|
|
48
|
+
* Export references a ServiceIdentifier that is neither declared locally nor imported.
|
|
49
|
+
* @see SPECIFICATION.md Section 4.3 E1
|
|
50
|
+
*/
|
|
51
|
+
E_EXPORT_NOT_FOUND = "E_EXPORT_NOT_FOUND",
|
|
52
|
+
/**
|
|
53
|
+
* Same ServiceIdentifier exported multiple times from a single module.
|
|
54
|
+
* @see SPECIFICATION.md Section 4.3 E2
|
|
55
|
+
*/
|
|
56
|
+
E_DUPLICATE_EXPORT = "E_DUPLICATE_EXPORT"
|
|
57
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @overview Module package exception.
|
|
3
|
+
* @author AEPKILL
|
|
4
|
+
* @created 2026-05-09 00:00:00
|
|
5
|
+
*/
|
|
6
|
+
import { CodedException } from "@husky-di/core";
|
|
7
|
+
import type { ModuleErrorCode } from "../types/module-error-code.type";
|
|
8
|
+
export declare class ModuleException extends CodedException<ModuleErrorCode> {
|
|
9
|
+
/** Internal marker to identify ModuleException instances across frames. */
|
|
10
|
+
private __isModuleException__;
|
|
11
|
+
static isModuleException(error: unknown): error is ModuleException;
|
|
12
|
+
}
|
|
@@ -30,5 +30,9 @@ import { type ResolveMiddleware, type ServiceIdentifier } from "@husky-di/core";
|
|
|
30
30
|
*
|
|
31
31
|
* Note: Services not registered in the container are not considered external access,
|
|
32
32
|
* as they will be resolved from parent containers or fail with a different error.
|
|
33
|
+
*
|
|
34
|
+
* @important Do not remove this middleware from the container once registered.
|
|
35
|
+
* Removing this middleware will disable export protection and allow external access
|
|
36
|
+
* to non-exported services.
|
|
33
37
|
*/
|
|
34
|
-
export declare function
|
|
38
|
+
export declare function createExportedGuardMiddleware(exports: ReadonlyArray<ServiceIdentifier<unknown>>): ResolveMiddleware<any, any>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import scope factory.
|
|
3
|
+
*
|
|
4
|
+
* @overview
|
|
5
|
+
* Creates the normalized import visibility model for module imports.
|
|
6
|
+
*
|
|
7
|
+
* @author AEPKILL
|
|
8
|
+
* @created 2026-05-09 00:00:00
|
|
9
|
+
*/
|
|
10
|
+
import type { IModule, ModuleWithAliases } from "../interfaces/module.interface";
|
|
11
|
+
import type { ImportScope } from "../types/import-scope.type";
|
|
12
|
+
export declare function createImportScope(imports?: ReadonlyArray<IModule | ModuleWithAliases>): ImportScope;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @overview Module implementation.
|
|
3
|
+
* @author AEPKILL
|
|
4
|
+
* @created 2025-08-09 14:51:09
|
|
5
|
+
*/
|
|
6
|
+
import { type Cleanup, type IContainer, type IsRegisteredOptions, type ResolveInstance, type ResolveMiddleware, type ResolveOptions, type ServiceIdentifier } from "@husky-di/core";
|
|
7
|
+
import type { Alias, CreateModuleOptions, Declaration, IModule, ModuleWithAliases } from "../interfaces/module.interface";
|
|
8
|
+
export declare class ModuleImpl implements IModule {
|
|
9
|
+
get id(): string;
|
|
10
|
+
get name(): string;
|
|
11
|
+
get declarations(): Declaration<unknown>[] | undefined;
|
|
12
|
+
get exports(): ServiceIdentifier<unknown>[] | undefined;
|
|
13
|
+
get imports(): (IModule | ModuleWithAliases)[] | undefined;
|
|
14
|
+
get displayName(): string;
|
|
15
|
+
readonly container: IContainer;
|
|
16
|
+
private readonly _id;
|
|
17
|
+
private readonly _name;
|
|
18
|
+
private readonly _declarations?;
|
|
19
|
+
private readonly _imports?;
|
|
20
|
+
private readonly _exports?;
|
|
21
|
+
private readonly _importScope;
|
|
22
|
+
constructor(options: CreateModuleOptions);
|
|
23
|
+
resolve<T, O extends ResolveOptions<T>>(serviceIdentifier: ServiceIdentifier<T>, options?: O): ResolveInstance<T, O>;
|
|
24
|
+
isRegistered<T>(serviceIdentifier: ServiceIdentifier<T>, options?: IsRegisteredOptions): boolean;
|
|
25
|
+
getServiceIdentifiers(): ServiceIdentifier<unknown>[];
|
|
26
|
+
use(...middleware: ResolveMiddleware<any, any>[]): Cleanup;
|
|
27
|
+
unused(...middleware: ResolveMiddleware<any, any>[]): void;
|
|
28
|
+
withAliases(aliases: Alias[]): ModuleWithAliases;
|
|
29
|
+
private buildContainer;
|
|
30
|
+
private validateConfiguration;
|
|
31
|
+
private validateImports;
|
|
32
|
+
private registerDeclarations;
|
|
33
|
+
private registerImports;
|
|
34
|
+
private validateExports;
|
|
35
|
+
}
|
package/dist/index.cjs
CHANGED
|
@@ -13,7 +13,7 @@ var __webpack_require__ = {};
|
|
|
13
13
|
})();
|
|
14
14
|
(()=>{
|
|
15
15
|
__webpack_require__.r = (exports1)=>{
|
|
16
|
-
if (
|
|
16
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
17
|
value: 'Module'
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports1, '__esModule', {
|
|
@@ -24,10 +24,31 @@ var __webpack_require__ = {};
|
|
|
24
24
|
var __webpack_exports__ = {};
|
|
25
25
|
__webpack_require__.r(__webpack_exports__);
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
ModuleErrorCodeEnum: ()=>module_error_code_enum_ModuleErrorCodeEnum,
|
|
28
|
+
ModuleException: ()=>ModuleException,
|
|
27
29
|
createModule: ()=>createModule
|
|
28
30
|
});
|
|
31
|
+
var module_error_code_enum_ModuleErrorCodeEnum = /*#__PURE__*/ function(ModuleErrorCodeEnum) {
|
|
32
|
+
ModuleErrorCodeEnum["E_DUPLICATE_DECLARATION"] = "E_DUPLICATE_DECLARATION";
|
|
33
|
+
ModuleErrorCodeEnum["E_INVALID_REGISTRATION"] = "E_INVALID_REGISTRATION";
|
|
34
|
+
ModuleErrorCodeEnum["E_DUPLICATE_IMPORT_MODULE"] = "E_DUPLICATE_IMPORT_MODULE";
|
|
35
|
+
ModuleErrorCodeEnum["E_CIRCULAR_DEPENDENCY"] = "E_CIRCULAR_DEPENDENCY";
|
|
36
|
+
ModuleErrorCodeEnum["E_IMPORT_COLLISION"] = "E_IMPORT_COLLISION";
|
|
37
|
+
ModuleErrorCodeEnum["E_IMPORT_CONFLICT_LOCAL"] = "E_IMPORT_CONFLICT_LOCAL";
|
|
38
|
+
ModuleErrorCodeEnum["E_ALIAS_SOURCE_NOT_EXPORTED"] = "E_ALIAS_SOURCE_NOT_EXPORTED";
|
|
39
|
+
ModuleErrorCodeEnum["E_DUPLICATE_ALIAS_MAP"] = "E_DUPLICATE_ALIAS_MAP";
|
|
40
|
+
ModuleErrorCodeEnum["E_EXPORT_NOT_FOUND"] = "E_EXPORT_NOT_FOUND";
|
|
41
|
+
ModuleErrorCodeEnum["E_DUPLICATE_EXPORT"] = "E_DUPLICATE_EXPORT";
|
|
42
|
+
return ModuleErrorCodeEnum;
|
|
43
|
+
}({});
|
|
29
44
|
const core_namespaceObject = require("@husky-di/core");
|
|
30
|
-
|
|
45
|
+
class ModuleException extends core_namespaceObject.CodedException {
|
|
46
|
+
__isModuleException__ = true;
|
|
47
|
+
static isModuleException(error) {
|
|
48
|
+
return error?.__isModuleException__ === true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function createExportedGuardMiddleware(exports1) {
|
|
31
52
|
const exportedSet = new Set(exports1);
|
|
32
53
|
return {
|
|
33
54
|
name: "ExportGuard",
|
|
@@ -38,7 +59,7 @@ function createExportedGuardMiddlewareFactory(exports1) {
|
|
|
38
59
|
if (!exportedSet.has(serviceIdentifier)) {
|
|
39
60
|
if (container.isRegistered(serviceIdentifier, {
|
|
40
61
|
recursive: true
|
|
41
|
-
})) throw new core_namespaceObject.ResolveException(`Service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" is not exported from ${container.displayName}.`, resolveRecord);
|
|
62
|
+
})) throw new core_namespaceObject.ResolveException(module_error_code_enum_ModuleErrorCodeEnum.E_EXPORT_NOT_FOUND, `Service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" is not exported from ${container.displayName}.`, resolveRecord);
|
|
42
63
|
}
|
|
43
64
|
return next(params);
|
|
44
65
|
}
|
|
@@ -51,8 +72,131 @@ function findPreviousContainer(paths) {
|
|
|
51
72
|
lastContainer = path.value.container;
|
|
52
73
|
}
|
|
53
74
|
}
|
|
75
|
+
function isModuleWithAliases(item) {
|
|
76
|
+
return "module" in item;
|
|
77
|
+
}
|
|
78
|
+
function createImportScope(imports) {
|
|
79
|
+
const bindings = (imports ?? []).flatMap((item)=>{
|
|
80
|
+
const sourceModule = isModuleWithAliases(item) ? item.module : item;
|
|
81
|
+
const aliases = isModuleWithAliases(item) ? item.aliases ?? [] : [];
|
|
82
|
+
const aliasMap = createAliasMap(aliases);
|
|
83
|
+
return (sourceModule.exports ?? []).map((sourceServiceIdentifier)=>{
|
|
84
|
+
const localServiceIdentifier = aliasMap.get(sourceServiceIdentifier) ?? sourceServiceIdentifier;
|
|
85
|
+
return {
|
|
86
|
+
sourceModule,
|
|
87
|
+
sourceServiceIdentifier,
|
|
88
|
+
localServiceIdentifier,
|
|
89
|
+
isAliased: aliasMap.has(sourceServiceIdentifier)
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
bindings,
|
|
95
|
+
visibleServiceIdentifiers: new Set(bindings.map((binding)=>binding.localServiceIdentifier))
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createAliasMap(aliases) {
|
|
99
|
+
return new Map(aliases.map((alias)=>[
|
|
100
|
+
alias.serviceIdentifier,
|
|
101
|
+
alias.as
|
|
102
|
+
]));
|
|
103
|
+
}
|
|
104
|
+
function validateDeclarations(moduleName, declarations) {
|
|
105
|
+
if (!declarations || 0 === declarations.length) return;
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const decl of declarations){
|
|
108
|
+
const { serviceIdentifier } = decl;
|
|
109
|
+
if (seen.has(serviceIdentifier)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_DUPLICATE_DECLARATION, `Duplicate declaration of service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" in module "${moduleName}".`);
|
|
110
|
+
seen.add(serviceIdentifier);
|
|
111
|
+
const hasValidOption = "useClass" in decl || "useFactory" in decl || "useValue" in decl || "useAlias" in decl;
|
|
112
|
+
if (!hasValidOption) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_INVALID_REGISTRATION, `Invalid registration options for service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" in module "${moduleName}": must specify useClass, useFactory, useValue, or useAlias.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function validateImportUniqueness(moduleName, imports) {
|
|
116
|
+
const seenModules = new Set();
|
|
117
|
+
for (const item of imports){
|
|
118
|
+
const importedModule = isModuleWithAliases(item) ? item.module : item;
|
|
119
|
+
const moduleId = importedModule.id;
|
|
120
|
+
if (seenModules.has(moduleId)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_DUPLICATE_IMPORT_MODULE, `Duplicate import module: "${importedModule.displayName}" in "${moduleName}".`);
|
|
121
|
+
seenModules.add(moduleId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function validateImportAliases(imports) {
|
|
125
|
+
for (const item of imports)if (isModuleWithAliases(item)) validateAliases(item.module.displayName, item.aliases ?? [], item.module.exports);
|
|
126
|
+
}
|
|
127
|
+
function detectCircularDependencies(module, visitedModules = new Set(), visitStack = []) {
|
|
128
|
+
if (visitStack.includes(module)) {
|
|
129
|
+
const cycle = [
|
|
130
|
+
...visitStack.slice(visitStack.indexOf(module)),
|
|
131
|
+
module
|
|
132
|
+
];
|
|
133
|
+
const cyclePath = cycle.map((m)=>m.displayName).join(" → ");
|
|
134
|
+
throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_CIRCULAR_DEPENDENCY, `Circular dependency detected: ${cyclePath}`);
|
|
135
|
+
}
|
|
136
|
+
if (visitedModules.has(module.id)) return;
|
|
137
|
+
visitStack.push(module);
|
|
138
|
+
const imports = module.imports ?? [];
|
|
139
|
+
for (const item of imports){
|
|
140
|
+
const importedModule = isModuleWithAliases(item) ? item.module : item;
|
|
141
|
+
detectCircularDependencies(importedModule, visitedModules, visitStack);
|
|
142
|
+
}
|
|
143
|
+
visitedModules.add(module.id);
|
|
144
|
+
visitStack.pop();
|
|
145
|
+
}
|
|
146
|
+
function validateAliases(moduleName, aliases, exports1) {
|
|
147
|
+
if (!aliases || 0 === aliases.length) return;
|
|
148
|
+
const exportedSet = new Set(exports1 ?? []);
|
|
149
|
+
const mappedServices = new Set();
|
|
150
|
+
for (const alias of aliases){
|
|
151
|
+
const { serviceIdentifier } = alias;
|
|
152
|
+
if (!exportedSet.has(serviceIdentifier)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_ALIAS_SOURCE_NOT_EXPORTED, `Cannot alias service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" from module "${moduleName}": it is not exported from that module.`);
|
|
153
|
+
if (mappedServices.has(serviceIdentifier)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_DUPLICATE_ALIAS_MAP, `Duplicate alias mapping for service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" in module "${moduleName}".`);
|
|
154
|
+
mappedServices.add(serviceIdentifier);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function validateImportConflictsWithDeclarations(moduleName, importScope, declarations) {
|
|
158
|
+
if (!declarations) return;
|
|
159
|
+
const localDeclarations = new Set((declarations ?? []).map((decl)=>decl.serviceIdentifier));
|
|
160
|
+
const conflictingImport = importScope.bindings.find((binding)=>localDeclarations.has(binding.localServiceIdentifier));
|
|
161
|
+
if (conflictingImport) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_IMPORT_CONFLICT_LOCAL, `Imported service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(conflictingImport.localServiceIdentifier)}" conflicts with local declaration in module "${moduleName}". Use an alias to resolve the conflict.`);
|
|
162
|
+
}
|
|
163
|
+
function validateImportNamingConflicts(_moduleName, importScope) {
|
|
164
|
+
if (0 === importScope.bindings.length) return;
|
|
165
|
+
const serviceToModules = importScope.bindings.reduce((acc, binding)=>{
|
|
166
|
+
const modules = acc.get(binding.localServiceIdentifier) ?? [];
|
|
167
|
+
modules.push(binding.sourceModule);
|
|
168
|
+
acc.set(binding.localServiceIdentifier, modules);
|
|
169
|
+
return acc;
|
|
170
|
+
}, new Map());
|
|
171
|
+
const conflicts = Array.from(serviceToModules.entries()).filter(([, modules])=>modules.length > 1);
|
|
172
|
+
if (conflicts.length > 0) {
|
|
173
|
+
const [serviceId, modules] = conflicts[0];
|
|
174
|
+
const moduleNames = modules.map((m)=>`"${m.displayName}"`).join(", ");
|
|
175
|
+
throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_IMPORT_COLLISION, `Service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceId)}" is exported by multiple imported modules: ${moduleNames}. Consider using aliases to resolve the conflict.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function validateExportUniqueness(moduleName, exports1) {
|
|
179
|
+
if (!exports1 || 0 === exports1.length) return;
|
|
180
|
+
const seen = new Set();
|
|
181
|
+
for (const exportId of exports1){
|
|
182
|
+
if (seen.has(exportId)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_DUPLICATE_EXPORT, `Duplicate export of service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(exportId)}" in module "${moduleName}".`);
|
|
183
|
+
seen.add(exportId);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function collectAvailableServices(declarations, importScope) {
|
|
187
|
+
const localServices = (declarations ?? []).map((decl)=>decl.serviceIdentifier);
|
|
188
|
+
return new Set([
|
|
189
|
+
...localServices,
|
|
190
|
+
...importScope?.visibleServiceIdentifiers ?? []
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
function validateExportAvailability(moduleName, exports1, declarations, importScope) {
|
|
194
|
+
if (!exports1 || 0 === exports1.length) return;
|
|
195
|
+
const availableServices = collectAvailableServices(declarations, importScope);
|
|
196
|
+
for (const exportId of exports1)if (!availableServices.has(exportId)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_EXPORT_NOT_FOUND, `Cannot export service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(exportId)}" from "${moduleName}": it is not declared in this module or imported from any imported module.`);
|
|
197
|
+
}
|
|
54
198
|
const createModuleId = (0, core_namespaceObject.incrementalIdFactory)("MODULE");
|
|
55
|
-
class
|
|
199
|
+
class ModuleImpl {
|
|
56
200
|
get id() {
|
|
57
201
|
return this._id;
|
|
58
202
|
}
|
|
@@ -69,7 +213,7 @@ class Module {
|
|
|
69
213
|
return this._imports;
|
|
70
214
|
}
|
|
71
215
|
get displayName() {
|
|
72
|
-
return `${String(this._name)}
|
|
216
|
+
return `${String(this._name)}/${this._id}`;
|
|
73
217
|
}
|
|
74
218
|
container;
|
|
75
219
|
_id;
|
|
@@ -77,19 +221,17 @@ class Module {
|
|
|
77
221
|
_declarations;
|
|
78
222
|
_imports;
|
|
79
223
|
_exports;
|
|
80
|
-
|
|
81
|
-
_visitStack = [];
|
|
224
|
+
_importScope;
|
|
82
225
|
constructor(options){
|
|
83
226
|
this._id = createModuleId();
|
|
84
227
|
this._name = options.name;
|
|
85
228
|
this._declarations = options.declarations;
|
|
86
229
|
this._imports = options.imports;
|
|
87
230
|
this._exports = options.exports;
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
90
|
-
this.validateExports();
|
|
231
|
+
this._importScope = createImportScope(this._imports);
|
|
232
|
+
this.validateConfiguration();
|
|
91
233
|
this.container = this.buildContainer();
|
|
92
|
-
this.container.use(
|
|
234
|
+
this.container.use(createExportedGuardMiddleware(this.exports ?? []));
|
|
93
235
|
}
|
|
94
236
|
resolve(serviceIdentifier, options) {
|
|
95
237
|
return this.container.resolve(serviceIdentifier, options);
|
|
@@ -100,14 +242,14 @@ class Module {
|
|
|
100
242
|
getServiceIdentifiers() {
|
|
101
243
|
return this.container.getServiceIdentifiers();
|
|
102
244
|
}
|
|
103
|
-
use(middleware) {
|
|
104
|
-
this.container.use(middleware);
|
|
245
|
+
use(...middleware) {
|
|
246
|
+
return this.container.use(...middleware);
|
|
105
247
|
}
|
|
106
|
-
unused(middleware) {
|
|
107
|
-
this.container.unused(middleware);
|
|
248
|
+
unused(...middleware) {
|
|
249
|
+
this.container.unused(...middleware);
|
|
108
250
|
}
|
|
109
251
|
withAliases(aliases) {
|
|
110
|
-
this.
|
|
252
|
+
validateAliases(this.displayName, aliases, this._exports);
|
|
111
253
|
return {
|
|
112
254
|
module: this,
|
|
113
255
|
aliases
|
|
@@ -119,45 +261,18 @@ class Module {
|
|
|
119
261
|
this.registerImports(container);
|
|
120
262
|
return container;
|
|
121
263
|
}
|
|
264
|
+
validateConfiguration() {
|
|
265
|
+
validateDeclarations(this.displayName, this._declarations);
|
|
266
|
+
this.validateImports();
|
|
267
|
+
this.validateExports();
|
|
268
|
+
}
|
|
122
269
|
validateImports() {
|
|
123
270
|
if (!this._imports || 0 === this._imports.length) return;
|
|
124
|
-
|
|
125
|
-
this.
|
|
126
|
-
this
|
|
127
|
-
this.
|
|
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();
|
|
271
|
+
validateImportUniqueness(this.displayName, this._imports);
|
|
272
|
+
validateImportAliases(this._imports);
|
|
273
|
+
detectCircularDependencies(this);
|
|
274
|
+
validateImportConflictsWithDeclarations(this.displayName, this._importScope, this._declarations);
|
|
275
|
+
validateImportNamingConflicts(this.displayName, this._importScope);
|
|
161
276
|
}
|
|
162
277
|
registerDeclarations(container) {
|
|
163
278
|
if (!this._declarations || 0 === this._declarations.length) return;
|
|
@@ -167,118 +282,29 @@ class Module {
|
|
|
167
282
|
}
|
|
168
283
|
}
|
|
169
284
|
registerImports(container) {
|
|
170
|
-
if (
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
getContainer: ()=>sourceModule.container
|
|
285
|
+
if (0 === this._importScope.bindings.length) return;
|
|
286
|
+
for (const binding of this._importScope.bindings)container.register(binding.localServiceIdentifier, {
|
|
287
|
+
useAlias: binding.sourceServiceIdentifier,
|
|
288
|
+
getContainer: ()=>binding.sourceModule.container
|
|
175
289
|
});
|
|
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
290
|
}
|
|
210
291
|
validateExports() {
|
|
211
292
|
if (!this._exports || 0 === this._exports.length) return;
|
|
212
|
-
|
|
213
|
-
|
|
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;
|
|
293
|
+
validateExportUniqueness(this.displayName, this._exports);
|
|
294
|
+
validateExportAvailability(this.displayName, this._exports, this._declarations, this._importScope);
|
|
273
295
|
}
|
|
274
296
|
}
|
|
275
297
|
function createModule(options) {
|
|
276
|
-
return new
|
|
298
|
+
return new ModuleImpl(options);
|
|
277
299
|
}
|
|
300
|
+
exports.ModuleErrorCodeEnum = __webpack_exports__.ModuleErrorCodeEnum;
|
|
301
|
+
exports.ModuleException = __webpack_exports__.ModuleException;
|
|
278
302
|
exports.createModule = __webpack_exports__.createModule;
|
|
279
|
-
for(var
|
|
303
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
304
|
+
"ModuleErrorCodeEnum",
|
|
305
|
+
"ModuleException",
|
|
280
306
|
"createModule"
|
|
281
|
-
].indexOf(
|
|
307
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
282
308
|
Object.defineProperty(exports, '__esModule', {
|
|
283
309
|
value: true
|
|
284
310
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,8 @@
|
|
|
3
3
|
* @author AEPKILL
|
|
4
4
|
* @created 2025-08-12 22:53:27
|
|
5
5
|
*/
|
|
6
|
+
export { ModuleErrorCodeEnum } from "./enums/module-error-code.enum";
|
|
7
|
+
export { ModuleException } from "./exceptions/module.exception";
|
|
6
8
|
export { createModule } from "./factories/module.factory";
|
|
7
9
|
export type { CreateModuleOptions, IModule, } from "./interfaces/module.interface";
|
|
10
|
+
export type { ModuleErrorCode } from "./types/module-error-code.type";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
|
-
import { ResolveException, createContainer, getServiceIdentifierName, incrementalIdFactory, isResolveServiceIdentifierRecord } from "@husky-di/core";
|
|
2
|
-
function
|
|
1
|
+
import { CodedException, ResolveException, createContainer, getServiceIdentifierName, incrementalIdFactory, isResolveServiceIdentifierRecord } from "@husky-di/core";
|
|
2
|
+
var module_error_code_enum_ModuleErrorCodeEnum = /*#__PURE__*/ function(ModuleErrorCodeEnum) {
|
|
3
|
+
ModuleErrorCodeEnum["E_DUPLICATE_DECLARATION"] = "E_DUPLICATE_DECLARATION";
|
|
4
|
+
ModuleErrorCodeEnum["E_INVALID_REGISTRATION"] = "E_INVALID_REGISTRATION";
|
|
5
|
+
ModuleErrorCodeEnum["E_DUPLICATE_IMPORT_MODULE"] = "E_DUPLICATE_IMPORT_MODULE";
|
|
6
|
+
ModuleErrorCodeEnum["E_CIRCULAR_DEPENDENCY"] = "E_CIRCULAR_DEPENDENCY";
|
|
7
|
+
ModuleErrorCodeEnum["E_IMPORT_COLLISION"] = "E_IMPORT_COLLISION";
|
|
8
|
+
ModuleErrorCodeEnum["E_IMPORT_CONFLICT_LOCAL"] = "E_IMPORT_CONFLICT_LOCAL";
|
|
9
|
+
ModuleErrorCodeEnum["E_ALIAS_SOURCE_NOT_EXPORTED"] = "E_ALIAS_SOURCE_NOT_EXPORTED";
|
|
10
|
+
ModuleErrorCodeEnum["E_DUPLICATE_ALIAS_MAP"] = "E_DUPLICATE_ALIAS_MAP";
|
|
11
|
+
ModuleErrorCodeEnum["E_EXPORT_NOT_FOUND"] = "E_EXPORT_NOT_FOUND";
|
|
12
|
+
ModuleErrorCodeEnum["E_DUPLICATE_EXPORT"] = "E_DUPLICATE_EXPORT";
|
|
13
|
+
return ModuleErrorCodeEnum;
|
|
14
|
+
}({});
|
|
15
|
+
class ModuleException extends CodedException {
|
|
16
|
+
__isModuleException__ = true;
|
|
17
|
+
static isModuleException(error) {
|
|
18
|
+
return error?.__isModuleException__ === true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function createExportedGuardMiddleware(exports) {
|
|
3
22
|
const exportedSet = new Set(exports);
|
|
4
23
|
return {
|
|
5
24
|
name: "ExportGuard",
|
|
@@ -10,7 +29,7 @@ function createExportedGuardMiddlewareFactory(exports) {
|
|
|
10
29
|
if (!exportedSet.has(serviceIdentifier)) {
|
|
11
30
|
if (container.isRegistered(serviceIdentifier, {
|
|
12
31
|
recursive: true
|
|
13
|
-
})) throw new ResolveException(`Service identifier "${getServiceIdentifierName(serviceIdentifier)}" is not exported from ${container.displayName}.`, resolveRecord);
|
|
32
|
+
})) throw new ResolveException(module_error_code_enum_ModuleErrorCodeEnum.E_EXPORT_NOT_FOUND, `Service identifier "${getServiceIdentifierName(serviceIdentifier)}" is not exported from ${container.displayName}.`, resolveRecord);
|
|
14
33
|
}
|
|
15
34
|
return next(params);
|
|
16
35
|
}
|
|
@@ -23,8 +42,131 @@ function findPreviousContainer(paths) {
|
|
|
23
42
|
lastContainer = path.value.container;
|
|
24
43
|
}
|
|
25
44
|
}
|
|
45
|
+
function isModuleWithAliases(item) {
|
|
46
|
+
return "module" in item;
|
|
47
|
+
}
|
|
48
|
+
function createImportScope(imports) {
|
|
49
|
+
const bindings = (imports ?? []).flatMap((item)=>{
|
|
50
|
+
const sourceModule = isModuleWithAliases(item) ? item.module : item;
|
|
51
|
+
const aliases = isModuleWithAliases(item) ? item.aliases ?? [] : [];
|
|
52
|
+
const aliasMap = createAliasMap(aliases);
|
|
53
|
+
return (sourceModule.exports ?? []).map((sourceServiceIdentifier)=>{
|
|
54
|
+
const localServiceIdentifier = aliasMap.get(sourceServiceIdentifier) ?? sourceServiceIdentifier;
|
|
55
|
+
return {
|
|
56
|
+
sourceModule,
|
|
57
|
+
sourceServiceIdentifier,
|
|
58
|
+
localServiceIdentifier,
|
|
59
|
+
isAliased: aliasMap.has(sourceServiceIdentifier)
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
bindings,
|
|
65
|
+
visibleServiceIdentifiers: new Set(bindings.map((binding)=>binding.localServiceIdentifier))
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function createAliasMap(aliases) {
|
|
69
|
+
return new Map(aliases.map((alias)=>[
|
|
70
|
+
alias.serviceIdentifier,
|
|
71
|
+
alias.as
|
|
72
|
+
]));
|
|
73
|
+
}
|
|
74
|
+
function validateDeclarations(moduleName, declarations) {
|
|
75
|
+
if (!declarations || 0 === declarations.length) return;
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
for (const decl of declarations){
|
|
78
|
+
const { serviceIdentifier } = decl;
|
|
79
|
+
if (seen.has(serviceIdentifier)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_DUPLICATE_DECLARATION, `Duplicate declaration of service identifier "${getServiceIdentifierName(serviceIdentifier)}" in module "${moduleName}".`);
|
|
80
|
+
seen.add(serviceIdentifier);
|
|
81
|
+
const hasValidOption = "useClass" in decl || "useFactory" in decl || "useValue" in decl || "useAlias" in decl;
|
|
82
|
+
if (!hasValidOption) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_INVALID_REGISTRATION, `Invalid registration options for service identifier "${getServiceIdentifierName(serviceIdentifier)}" in module "${moduleName}": must specify useClass, useFactory, useValue, or useAlias.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function validateImportUniqueness(moduleName, imports) {
|
|
86
|
+
const seenModules = new Set();
|
|
87
|
+
for (const item of imports){
|
|
88
|
+
const importedModule = isModuleWithAliases(item) ? item.module : item;
|
|
89
|
+
const moduleId = importedModule.id;
|
|
90
|
+
if (seenModules.has(moduleId)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_DUPLICATE_IMPORT_MODULE, `Duplicate import module: "${importedModule.displayName}" in "${moduleName}".`);
|
|
91
|
+
seenModules.add(moduleId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function validateImportAliases(imports) {
|
|
95
|
+
for (const item of imports)if (isModuleWithAliases(item)) validateAliases(item.module.displayName, item.aliases ?? [], item.module.exports);
|
|
96
|
+
}
|
|
97
|
+
function detectCircularDependencies(module, visitedModules = new Set(), visitStack = []) {
|
|
98
|
+
if (visitStack.includes(module)) {
|
|
99
|
+
const cycle = [
|
|
100
|
+
...visitStack.slice(visitStack.indexOf(module)),
|
|
101
|
+
module
|
|
102
|
+
];
|
|
103
|
+
const cyclePath = cycle.map((m)=>m.displayName).join(" → ");
|
|
104
|
+
throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_CIRCULAR_DEPENDENCY, `Circular dependency detected: ${cyclePath}`);
|
|
105
|
+
}
|
|
106
|
+
if (visitedModules.has(module.id)) return;
|
|
107
|
+
visitStack.push(module);
|
|
108
|
+
const imports = module.imports ?? [];
|
|
109
|
+
for (const item of imports){
|
|
110
|
+
const importedModule = isModuleWithAliases(item) ? item.module : item;
|
|
111
|
+
detectCircularDependencies(importedModule, visitedModules, visitStack);
|
|
112
|
+
}
|
|
113
|
+
visitedModules.add(module.id);
|
|
114
|
+
visitStack.pop();
|
|
115
|
+
}
|
|
116
|
+
function validateAliases(moduleName, aliases, exports) {
|
|
117
|
+
if (!aliases || 0 === aliases.length) return;
|
|
118
|
+
const exportedSet = new Set(exports ?? []);
|
|
119
|
+
const mappedServices = new Set();
|
|
120
|
+
for (const alias of aliases){
|
|
121
|
+
const { serviceIdentifier } = alias;
|
|
122
|
+
if (!exportedSet.has(serviceIdentifier)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_ALIAS_SOURCE_NOT_EXPORTED, `Cannot alias service identifier "${getServiceIdentifierName(serviceIdentifier)}" from module "${moduleName}": it is not exported from that module.`);
|
|
123
|
+
if (mappedServices.has(serviceIdentifier)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_DUPLICATE_ALIAS_MAP, `Duplicate alias mapping for service identifier "${getServiceIdentifierName(serviceIdentifier)}" in module "${moduleName}".`);
|
|
124
|
+
mappedServices.add(serviceIdentifier);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function validateImportConflictsWithDeclarations(moduleName, importScope, declarations) {
|
|
128
|
+
if (!declarations) return;
|
|
129
|
+
const localDeclarations = new Set((declarations ?? []).map((decl)=>decl.serviceIdentifier));
|
|
130
|
+
const conflictingImport = importScope.bindings.find((binding)=>localDeclarations.has(binding.localServiceIdentifier));
|
|
131
|
+
if (conflictingImport) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_IMPORT_CONFLICT_LOCAL, `Imported service identifier "${getServiceIdentifierName(conflictingImport.localServiceIdentifier)}" conflicts with local declaration in module "${moduleName}". Use an alias to resolve the conflict.`);
|
|
132
|
+
}
|
|
133
|
+
function validateImportNamingConflicts(_moduleName, importScope) {
|
|
134
|
+
if (0 === importScope.bindings.length) return;
|
|
135
|
+
const serviceToModules = importScope.bindings.reduce((acc, binding)=>{
|
|
136
|
+
const modules = acc.get(binding.localServiceIdentifier) ?? [];
|
|
137
|
+
modules.push(binding.sourceModule);
|
|
138
|
+
acc.set(binding.localServiceIdentifier, modules);
|
|
139
|
+
return acc;
|
|
140
|
+
}, new Map());
|
|
141
|
+
const conflicts = Array.from(serviceToModules.entries()).filter(([, modules])=>modules.length > 1);
|
|
142
|
+
if (conflicts.length > 0) {
|
|
143
|
+
const [serviceId, modules] = conflicts[0];
|
|
144
|
+
const moduleNames = modules.map((m)=>`"${m.displayName}"`).join(", ");
|
|
145
|
+
throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_IMPORT_COLLISION, `Service identifier "${getServiceIdentifierName(serviceId)}" is exported by multiple imported modules: ${moduleNames}. Consider using aliases to resolve the conflict.`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function validateExportUniqueness(moduleName, exports) {
|
|
149
|
+
if (!exports || 0 === exports.length) return;
|
|
150
|
+
const seen = new Set();
|
|
151
|
+
for (const exportId of exports){
|
|
152
|
+
if (seen.has(exportId)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_DUPLICATE_EXPORT, `Duplicate export of service identifier "${getServiceIdentifierName(exportId)}" in module "${moduleName}".`);
|
|
153
|
+
seen.add(exportId);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function collectAvailableServices(declarations, importScope) {
|
|
157
|
+
const localServices = (declarations ?? []).map((decl)=>decl.serviceIdentifier);
|
|
158
|
+
return new Set([
|
|
159
|
+
...localServices,
|
|
160
|
+
...importScope?.visibleServiceIdentifiers ?? []
|
|
161
|
+
]);
|
|
162
|
+
}
|
|
163
|
+
function validateExportAvailability(moduleName, exports, declarations, importScope) {
|
|
164
|
+
if (!exports || 0 === exports.length) return;
|
|
165
|
+
const availableServices = collectAvailableServices(declarations, importScope);
|
|
166
|
+
for (const exportId of exports)if (!availableServices.has(exportId)) throw new ModuleException(module_error_code_enum_ModuleErrorCodeEnum.E_EXPORT_NOT_FOUND, `Cannot export service identifier "${getServiceIdentifierName(exportId)}" from "${moduleName}": it is not declared in this module or imported from any imported module.`);
|
|
167
|
+
}
|
|
26
168
|
const createModuleId = incrementalIdFactory("MODULE");
|
|
27
|
-
class
|
|
169
|
+
class ModuleImpl {
|
|
28
170
|
get id() {
|
|
29
171
|
return this._id;
|
|
30
172
|
}
|
|
@@ -41,7 +183,7 @@ class Module {
|
|
|
41
183
|
return this._imports;
|
|
42
184
|
}
|
|
43
185
|
get displayName() {
|
|
44
|
-
return `${String(this._name)}
|
|
186
|
+
return `${String(this._name)}/${this._id}`;
|
|
45
187
|
}
|
|
46
188
|
container;
|
|
47
189
|
_id;
|
|
@@ -49,19 +191,17 @@ class Module {
|
|
|
49
191
|
_declarations;
|
|
50
192
|
_imports;
|
|
51
193
|
_exports;
|
|
52
|
-
|
|
53
|
-
_visitStack = [];
|
|
194
|
+
_importScope;
|
|
54
195
|
constructor(options){
|
|
55
196
|
this._id = createModuleId();
|
|
56
197
|
this._name = options.name;
|
|
57
198
|
this._declarations = options.declarations;
|
|
58
199
|
this._imports = options.imports;
|
|
59
200
|
this._exports = options.exports;
|
|
60
|
-
this.
|
|
61
|
-
this.
|
|
62
|
-
this.validateExports();
|
|
201
|
+
this._importScope = createImportScope(this._imports);
|
|
202
|
+
this.validateConfiguration();
|
|
63
203
|
this.container = this.buildContainer();
|
|
64
|
-
this.container.use(
|
|
204
|
+
this.container.use(createExportedGuardMiddleware(this.exports ?? []));
|
|
65
205
|
}
|
|
66
206
|
resolve(serviceIdentifier, options) {
|
|
67
207
|
return this.container.resolve(serviceIdentifier, options);
|
|
@@ -72,14 +212,14 @@ class Module {
|
|
|
72
212
|
getServiceIdentifiers() {
|
|
73
213
|
return this.container.getServiceIdentifiers();
|
|
74
214
|
}
|
|
75
|
-
use(middleware) {
|
|
76
|
-
this.container.use(middleware);
|
|
215
|
+
use(...middleware) {
|
|
216
|
+
return this.container.use(...middleware);
|
|
77
217
|
}
|
|
78
|
-
unused(middleware) {
|
|
79
|
-
this.container.unused(middleware);
|
|
218
|
+
unused(...middleware) {
|
|
219
|
+
this.container.unused(...middleware);
|
|
80
220
|
}
|
|
81
221
|
withAliases(aliases) {
|
|
82
|
-
this.
|
|
222
|
+
validateAliases(this.displayName, aliases, this._exports);
|
|
83
223
|
return {
|
|
84
224
|
module: this,
|
|
85
225
|
aliases
|
|
@@ -91,45 +231,18 @@ class Module {
|
|
|
91
231
|
this.registerImports(container);
|
|
92
232
|
return container;
|
|
93
233
|
}
|
|
234
|
+
validateConfiguration() {
|
|
235
|
+
validateDeclarations(this.displayName, this._declarations);
|
|
236
|
+
this.validateImports();
|
|
237
|
+
this.validateExports();
|
|
238
|
+
}
|
|
94
239
|
validateImports() {
|
|
95
240
|
if (!this._imports || 0 === this._imports.length) return;
|
|
96
|
-
|
|
97
|
-
this.
|
|
98
|
-
this
|
|
99
|
-
this.
|
|
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();
|
|
241
|
+
validateImportUniqueness(this.displayName, this._imports);
|
|
242
|
+
validateImportAliases(this._imports);
|
|
243
|
+
detectCircularDependencies(this);
|
|
244
|
+
validateImportConflictsWithDeclarations(this.displayName, this._importScope, this._declarations);
|
|
245
|
+
validateImportNamingConflicts(this.displayName, this._importScope);
|
|
133
246
|
}
|
|
134
247
|
registerDeclarations(container) {
|
|
135
248
|
if (!this._declarations || 0 === this._declarations.length) return;
|
|
@@ -139,112 +252,19 @@ class Module {
|
|
|
139
252
|
}
|
|
140
253
|
}
|
|
141
254
|
registerImports(container) {
|
|
142
|
-
if (
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
getContainer: ()=>sourceModule.container
|
|
147
|
-
});
|
|
148
|
-
else container.register(serviceIdentifier, {
|
|
149
|
-
useAlias: serviceIdentifier,
|
|
150
|
-
getContainer: ()=>sourceModule.container
|
|
255
|
+
if (0 === this._importScope.bindings.length) return;
|
|
256
|
+
for (const binding of this._importScope.bindings)container.register(binding.localServiceIdentifier, {
|
|
257
|
+
useAlias: binding.sourceServiceIdentifier,
|
|
258
|
+
getContainer: ()=>binding.sourceModule.container
|
|
151
259
|
});
|
|
152
260
|
}
|
|
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
261
|
validateExports() {
|
|
183
262
|
if (!this._exports || 0 === this._exports.length) return;
|
|
184
|
-
|
|
185
|
-
|
|
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;
|
|
263
|
+
validateExportUniqueness(this.displayName, this._exports);
|
|
264
|
+
validateExportAvailability(this.displayName, this._exports, this._declarations, this._importScope);
|
|
245
265
|
}
|
|
246
266
|
}
|
|
247
267
|
function createModule(options) {
|
|
248
|
-
return new
|
|
268
|
+
return new ModuleImpl(options);
|
|
249
269
|
}
|
|
250
|
-
export { createModule };
|
|
270
|
+
export { ModuleException, createModule, module_error_code_enum_ModuleErrorCodeEnum as ModuleErrorCodeEnum };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import scope type definitions.
|
|
3
|
+
*
|
|
4
|
+
* @overview
|
|
5
|
+
* Defines the normalized import visibility model used by module validation and
|
|
6
|
+
* container assembly.
|
|
7
|
+
*
|
|
8
|
+
* @author AEPKILL
|
|
9
|
+
* @created 2026-05-09 00:00:00
|
|
10
|
+
*/
|
|
11
|
+
import type { ServiceIdentifier } from "@husky-di/core";
|
|
12
|
+
import type { IModule } from "../interfaces/module.interface";
|
|
13
|
+
export type ImportBinding = {
|
|
14
|
+
readonly sourceModule: IModule;
|
|
15
|
+
readonly sourceServiceIdentifier: ServiceIdentifier<unknown>;
|
|
16
|
+
readonly localServiceIdentifier: ServiceIdentifier<unknown>;
|
|
17
|
+
readonly isAliased: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type ImportScope = {
|
|
20
|
+
readonly bindings: ReadonlyArray<ImportBinding>;
|
|
21
|
+
readonly visibleServiceIdentifiers: ReadonlySet<ServiceIdentifier<unknown>>;
|
|
22
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module import utilities.
|
|
3
|
+
*
|
|
4
|
+
* @overview
|
|
5
|
+
* Provides helpers for working with module import descriptors.
|
|
6
|
+
*
|
|
7
|
+
* @author AEPKILL
|
|
8
|
+
* @created 2026-05-09 00:00:00
|
|
9
|
+
*/
|
|
10
|
+
import type { IModule, ModuleWithAliases } from "../interfaces/module.interface";
|
|
11
|
+
export declare function isModuleWithAliases(item: IModule | ModuleWithAliases): item is ModuleWithAliases;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @overview Module validation utilities.
|
|
3
|
+
* @author AEPKILL
|
|
4
|
+
* @created 2025-03-30
|
|
5
|
+
*/
|
|
6
|
+
import { type ServiceIdentifier } from "@husky-di/core";
|
|
7
|
+
import type { Alias, Declaration, IModule, ModuleWithAliases } from "../interfaces/module.interface";
|
|
8
|
+
import type { ImportScope } from "../types/import-scope.type";
|
|
9
|
+
export declare function validateDeclarations(moduleName: string, declarations?: Declaration<unknown>[]): void;
|
|
10
|
+
export declare function validateImportUniqueness(moduleName: string, imports: ReadonlyArray<IModule | ModuleWithAliases>): void;
|
|
11
|
+
export declare function validateImportAliases(imports: ReadonlyArray<IModule | ModuleWithAliases>): void;
|
|
12
|
+
export declare function detectCircularDependencies(module: IModule, visitedModules?: Set<string | symbol>, visitStack?: IModule[]): void;
|
|
13
|
+
export declare function validateAliases(moduleName: string, aliases: ReadonlyArray<Alias>, exports?: ReadonlyArray<ServiceIdentifier<unknown>>): void;
|
|
14
|
+
export declare function validateImportConflictsWithDeclarations(moduleName: string, importScope: ImportScope, declarations?: Declaration<unknown>[]): void;
|
|
15
|
+
export declare function validateImportNamingConflicts(_moduleName: string, importScope: ImportScope): void;
|
|
16
|
+
export declare function validateExportUniqueness(moduleName: string, exports?: ServiceIdentifier<unknown>[]): void;
|
|
17
|
+
export declare function collectAvailableServices(declarations?: Declaration<unknown>[], importScope?: ImportScope): Set<ServiceIdentifier<unknown>>;
|
|
18
|
+
export declare function validateExportAvailability(moduleName: string, exports?: ServiceIdentifier<unknown>[], declarations?: Declaration<unknown>[], importScope?: ImportScope): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@husky-di/module",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@husky-di/core": "1.0
|
|
18
|
+
"@husky-di/core": "1.1.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@rslib/core": "^0.
|
|
22
|
-
"@types/node": "^
|
|
23
|
-
"typescript": "^
|
|
24
|
-
"vitest": "^
|
|
21
|
+
"@rslib/core": "^0.20.1",
|
|
22
|
+
"@types/node": "^25.5.0",
|
|
23
|
+
"typescript": "^6.0.3",
|
|
24
|
+
"vitest": "^4.1.2"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|
package/dist/impls/module.d.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @overview
|
|
3
|
-
* @author AEPKILL
|
|
4
|
-
* @created 2025-08-09 14:51:09
|
|
5
|
-
*/
|
|
6
|
-
import { type IContainer, type IsRegisteredOptions, type ResolveInstance, type ResolveMiddleware, type ResolveOptions, type ServiceIdentifier } from "@husky-di/core";
|
|
7
|
-
import type { Alias, CreateModuleOptions, Declaration, IModule, ModuleWithAliases } from "../interfaces/module.interface";
|
|
8
|
-
export declare class Module implements IModule {
|
|
9
|
-
get id(): string;
|
|
10
|
-
get name(): string;
|
|
11
|
-
get declarations(): Declaration<unknown>[] | undefined;
|
|
12
|
-
get exports(): ServiceIdentifier<unknown>[] | undefined;
|
|
13
|
-
get imports(): (IModule | ModuleWithAliases)[] | undefined;
|
|
14
|
-
get displayName(): string;
|
|
15
|
-
readonly container: IContainer;
|
|
16
|
-
private _id;
|
|
17
|
-
private _name;
|
|
18
|
-
private _declarations?;
|
|
19
|
-
private _imports?;
|
|
20
|
-
private _exports?;
|
|
21
|
-
private readonly _visitedModules;
|
|
22
|
-
private readonly _visitStack;
|
|
23
|
-
constructor(options: CreateModuleOptions);
|
|
24
|
-
resolve<T, O extends ResolveOptions<T>>(serviceIdentifier: ServiceIdentifier<T>, options?: O): ResolveInstance<T, O>;
|
|
25
|
-
isRegistered<T>(serviceIdentifier: ServiceIdentifier<T>, options?: IsRegisteredOptions): boolean;
|
|
26
|
-
getServiceIdentifiers(): ServiceIdentifier<unknown>[];
|
|
27
|
-
use(middleware: ResolveMiddleware<any, any>): void;
|
|
28
|
-
unused(middleware: ResolveMiddleware<any, any>): void;
|
|
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;
|
|
194
|
-
}
|