@husky-di/module 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +32 -1
- package/dist/factories/import-scope.factory.d.ts +12 -0
- package/dist/impls/{module.d.ts → ModuleImpl.d.ts} +17 -10
- package/dist/index.cjs +193 -164
- package/dist/index.d.ts +3 -0
- package/dist/index.js +186 -163
- package/dist/interfaces/module.interface.d.ts +8 -8
- 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 +9 -6
- package/dist/utils/module.utils.d.ts +0 -126
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,38 @@
|
|
|
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
|
-
|
|
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
|
+
* @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.
|
|
37
|
+
*/
|
|
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;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @overview
|
|
2
|
+
* @overview Module implementation.
|
|
3
3
|
* @author AEPKILL
|
|
4
4
|
* @created 2025-08-09 14:51:09
|
|
5
5
|
*/
|
|
6
|
-
import type
|
|
6
|
+
import { type Cleanup, 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
|
-
export declare class
|
|
8
|
+
export declare class ModuleImpl implements IModule {
|
|
9
9
|
get id(): string;
|
|
10
10
|
get name(): string;
|
|
11
11
|
get declarations(): Declaration<unknown>[] | undefined;
|
|
@@ -13,16 +13,23 @@ export declare class Module implements IModule {
|
|
|
13
13
|
get imports(): (IModule | ModuleWithAliases)[] | undefined;
|
|
14
14
|
get displayName(): string;
|
|
15
15
|
readonly container: IContainer;
|
|
16
|
-
private _id;
|
|
17
|
-
private _name;
|
|
18
|
-
private _declarations?;
|
|
19
|
-
private _imports?;
|
|
20
|
-
private _exports?;
|
|
16
|
+
private readonly _id;
|
|
17
|
+
private readonly _name;
|
|
18
|
+
private readonly _declarations?;
|
|
19
|
+
private readonly _imports?;
|
|
20
|
+
private readonly _exports?;
|
|
21
|
+
private readonly _importScope;
|
|
21
22
|
constructor(options: CreateModuleOptions);
|
|
22
23
|
resolve<T, O extends ResolveOptions<T>>(serviceIdentifier: ServiceIdentifier<T>, options?: O): ResolveInstance<T, O>;
|
|
23
24
|
isRegistered<T>(serviceIdentifier: ServiceIdentifier<T>, options?: IsRegisteredOptions): boolean;
|
|
24
25
|
getServiceIdentifiers(): ServiceIdentifier<unknown>[];
|
|
25
|
-
use(middleware: ResolveMiddleware<any, any>):
|
|
26
|
-
unused(middleware: ResolveMiddleware<any, any>): void;
|
|
26
|
+
use(...middleware: ResolveMiddleware<any, any>[]): Cleanup;
|
|
27
|
+
unused(...middleware: ResolveMiddleware<any, any>[]): void;
|
|
27
28
|
withAliases(aliases: Alias[]): ModuleWithAliases;
|
|
29
|
+
private buildContainer;
|
|
30
|
+
private validateConfiguration;
|
|
31
|
+
private validateImports;
|
|
32
|
+
private registerDeclarations;
|
|
33
|
+
private registerImports;
|
|
34
|
+
private validateExports;
|
|
28
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,170 +72,131 @@ function findPreviousContainer(paths) {
|
|
|
51
72
|
lastContainer = path.value.container;
|
|
52
73
|
}
|
|
53
74
|
}
|
|
54
|
-
function isModuleWithAliases(
|
|
55
|
-
return
|
|
75
|
+
function isModuleWithAliases(item) {
|
|
76
|
+
return "module" in item;
|
|
56
77
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
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
|
+
};
|
|
59
97
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
98
|
+
function createAliasMap(aliases) {
|
|
99
|
+
return new Map(aliases.map((alias)=>[
|
|
100
|
+
alias.serviceIdentifier,
|
|
101
|
+
alias.as
|
|
102
|
+
]));
|
|
63
103
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
}
|
|
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.`);
|
|
99
113
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
}
|
|
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);
|
|
171
122
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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}`);
|
|
176
135
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
for (const alias of aliases)aliasesMap.set(alias.serviceIdentifier, alias.as);
|
|
184
|
-
this.importAliasesCache.set(importedModule, aliasesMap);
|
|
185
|
-
return aliasesMap;
|
|
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);
|
|
186
142
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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);
|
|
194
155
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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.`);
|
|
208
176
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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);
|
|
214
184
|
}
|
|
215
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
|
+
}
|
|
216
198
|
const createModuleId = (0, core_namespaceObject.incrementalIdFactory)("MODULE");
|
|
217
|
-
class
|
|
199
|
+
class ModuleImpl {
|
|
218
200
|
get id() {
|
|
219
201
|
return this._id;
|
|
220
202
|
}
|
|
@@ -231,7 +213,7 @@ class Module {
|
|
|
231
213
|
return this._imports;
|
|
232
214
|
}
|
|
233
215
|
get displayName() {
|
|
234
|
-
return `${String(this._name)}
|
|
216
|
+
return `${String(this._name)}/${this._id}`;
|
|
235
217
|
}
|
|
236
218
|
container;
|
|
237
219
|
_id;
|
|
@@ -239,13 +221,17 @@ class Module {
|
|
|
239
221
|
_declarations;
|
|
240
222
|
_imports;
|
|
241
223
|
_exports;
|
|
224
|
+
_importScope;
|
|
242
225
|
constructor(options){
|
|
243
226
|
this._id = createModuleId();
|
|
244
227
|
this._name = options.name;
|
|
245
228
|
this._declarations = options.declarations;
|
|
246
229
|
this._imports = options.imports;
|
|
247
230
|
this._exports = options.exports;
|
|
248
|
-
this.
|
|
231
|
+
this._importScope = createImportScope(this._imports);
|
|
232
|
+
this.validateConfiguration();
|
|
233
|
+
this.container = this.buildContainer();
|
|
234
|
+
this.container.use(createExportedGuardMiddleware(this.exports ?? []));
|
|
249
235
|
}
|
|
250
236
|
resolve(serviceIdentifier, options) {
|
|
251
237
|
return this.container.resolve(serviceIdentifier, options);
|
|
@@ -256,26 +242,69 @@ class Module {
|
|
|
256
242
|
getServiceIdentifiers() {
|
|
257
243
|
return this.container.getServiceIdentifiers();
|
|
258
244
|
}
|
|
259
|
-
use(middleware) {
|
|
260
|
-
this.container.use(middleware);
|
|
245
|
+
use(...middleware) {
|
|
246
|
+
return this.container.use(...middleware);
|
|
261
247
|
}
|
|
262
|
-
unused(middleware) {
|
|
263
|
-
this.container.unused(middleware);
|
|
248
|
+
unused(...middleware) {
|
|
249
|
+
this.container.unused(...middleware);
|
|
264
250
|
}
|
|
265
251
|
withAliases(aliases) {
|
|
252
|
+
validateAliases(this.displayName, aliases, this._exports);
|
|
266
253
|
return {
|
|
267
254
|
module: this,
|
|
268
255
|
aliases
|
|
269
256
|
};
|
|
270
257
|
}
|
|
258
|
+
buildContainer() {
|
|
259
|
+
const container = (0, core_namespaceObject.createContainer)(this._name);
|
|
260
|
+
this.registerDeclarations(container);
|
|
261
|
+
this.registerImports(container);
|
|
262
|
+
return container;
|
|
263
|
+
}
|
|
264
|
+
validateConfiguration() {
|
|
265
|
+
validateDeclarations(this.displayName, this._declarations);
|
|
266
|
+
this.validateImports();
|
|
267
|
+
this.validateExports();
|
|
268
|
+
}
|
|
269
|
+
validateImports() {
|
|
270
|
+
if (!this._imports || 0 === this._imports.length) return;
|
|
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);
|
|
276
|
+
}
|
|
277
|
+
registerDeclarations(container) {
|
|
278
|
+
if (!this._declarations || 0 === this._declarations.length) return;
|
|
279
|
+
for (const decl of this._declarations){
|
|
280
|
+
const { serviceIdentifier, ...options } = decl;
|
|
281
|
+
container.register(serviceIdentifier, options);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
registerImports(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
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
validateExports() {
|
|
292
|
+
if (!this._exports || 0 === this._exports.length) return;
|
|
293
|
+
validateExportUniqueness(this.displayName, this._exports);
|
|
294
|
+
validateExportAvailability(this.displayName, this._exports, this._declarations, this._importScope);
|
|
295
|
+
}
|
|
271
296
|
}
|
|
272
297
|
function createModule(options) {
|
|
273
|
-
return new
|
|
298
|
+
return new ModuleImpl(options);
|
|
274
299
|
}
|
|
300
|
+
exports.ModuleErrorCodeEnum = __webpack_exports__.ModuleErrorCodeEnum;
|
|
301
|
+
exports.ModuleException = __webpack_exports__.ModuleException;
|
|
275
302
|
exports.createModule = __webpack_exports__.createModule;
|
|
276
|
-
for(var
|
|
303
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
304
|
+
"ModuleErrorCodeEnum",
|
|
305
|
+
"ModuleException",
|
|
277
306
|
"createModule"
|
|
278
|
-
].indexOf(
|
|
307
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
279
308
|
Object.defineProperty(exports, '__esModule', {
|
|
280
309
|
value: true
|
|
281
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,170 +42,131 @@ function findPreviousContainer(paths) {
|
|
|
23
42
|
lastContainer = path.value.container;
|
|
24
43
|
}
|
|
25
44
|
}
|
|
26
|
-
function isModuleWithAliases(
|
|
27
|
-
return
|
|
45
|
+
function isModuleWithAliases(item) {
|
|
46
|
+
return "module" in item;
|
|
28
47
|
}
|
|
29
|
-
function
|
|
30
|
-
|
|
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
|
+
};
|
|
31
67
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
68
|
+
function createAliasMap(aliases) {
|
|
69
|
+
return new Map(aliases.map((alias)=>[
|
|
70
|
+
alias.serviceIdentifier,
|
|
71
|
+
alias.as
|
|
72
|
+
]));
|
|
35
73
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.`);
|
|
43
83
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
}
|
|
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);
|
|
80
92
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
}
|
|
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}`);
|
|
143
105
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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;
|
|
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);
|
|
158
112
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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);
|
|
166
125
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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.`);
|
|
180
146
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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);
|
|
186
154
|
}
|
|
187
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
|
+
}
|
|
188
168
|
const createModuleId = incrementalIdFactory("MODULE");
|
|
189
|
-
class
|
|
169
|
+
class ModuleImpl {
|
|
190
170
|
get id() {
|
|
191
171
|
return this._id;
|
|
192
172
|
}
|
|
@@ -203,7 +183,7 @@ class Module {
|
|
|
203
183
|
return this._imports;
|
|
204
184
|
}
|
|
205
185
|
get displayName() {
|
|
206
|
-
return `${String(this._name)}
|
|
186
|
+
return `${String(this._name)}/${this._id}`;
|
|
207
187
|
}
|
|
208
188
|
container;
|
|
209
189
|
_id;
|
|
@@ -211,13 +191,17 @@ class Module {
|
|
|
211
191
|
_declarations;
|
|
212
192
|
_imports;
|
|
213
193
|
_exports;
|
|
194
|
+
_importScope;
|
|
214
195
|
constructor(options){
|
|
215
196
|
this._id = createModuleId();
|
|
216
197
|
this._name = options.name;
|
|
217
198
|
this._declarations = options.declarations;
|
|
218
199
|
this._imports = options.imports;
|
|
219
200
|
this._exports = options.exports;
|
|
220
|
-
this.
|
|
201
|
+
this._importScope = createImportScope(this._imports);
|
|
202
|
+
this.validateConfiguration();
|
|
203
|
+
this.container = this.buildContainer();
|
|
204
|
+
this.container.use(createExportedGuardMiddleware(this.exports ?? []));
|
|
221
205
|
}
|
|
222
206
|
resolve(serviceIdentifier, options) {
|
|
223
207
|
return this.container.resolve(serviceIdentifier, options);
|
|
@@ -228,20 +212,59 @@ class Module {
|
|
|
228
212
|
getServiceIdentifiers() {
|
|
229
213
|
return this.container.getServiceIdentifiers();
|
|
230
214
|
}
|
|
231
|
-
use(middleware) {
|
|
232
|
-
this.container.use(middleware);
|
|
215
|
+
use(...middleware) {
|
|
216
|
+
return this.container.use(...middleware);
|
|
233
217
|
}
|
|
234
|
-
unused(middleware) {
|
|
235
|
-
this.container.unused(middleware);
|
|
218
|
+
unused(...middleware) {
|
|
219
|
+
this.container.unused(...middleware);
|
|
236
220
|
}
|
|
237
221
|
withAliases(aliases) {
|
|
222
|
+
validateAliases(this.displayName, aliases, this._exports);
|
|
238
223
|
return {
|
|
239
224
|
module: this,
|
|
240
225
|
aliases
|
|
241
226
|
};
|
|
242
227
|
}
|
|
228
|
+
buildContainer() {
|
|
229
|
+
const container = createContainer(this._name);
|
|
230
|
+
this.registerDeclarations(container);
|
|
231
|
+
this.registerImports(container);
|
|
232
|
+
return container;
|
|
233
|
+
}
|
|
234
|
+
validateConfiguration() {
|
|
235
|
+
validateDeclarations(this.displayName, this._declarations);
|
|
236
|
+
this.validateImports();
|
|
237
|
+
this.validateExports();
|
|
238
|
+
}
|
|
239
|
+
validateImports() {
|
|
240
|
+
if (!this._imports || 0 === this._imports.length) return;
|
|
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);
|
|
246
|
+
}
|
|
247
|
+
registerDeclarations(container) {
|
|
248
|
+
if (!this._declarations || 0 === this._declarations.length) return;
|
|
249
|
+
for (const decl of this._declarations){
|
|
250
|
+
const { serviceIdentifier, ...options } = decl;
|
|
251
|
+
container.register(serviceIdentifier, options);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
registerImports(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
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
validateExports() {
|
|
262
|
+
if (!this._exports || 0 === this._exports.length) return;
|
|
263
|
+
validateExportUniqueness(this.displayName, this._exports);
|
|
264
|
+
validateExportAvailability(this.displayName, this._exports, this._declarations, this._importScope);
|
|
265
|
+
}
|
|
243
266
|
}
|
|
244
267
|
function createModule(options) {
|
|
245
|
-
return new
|
|
268
|
+
return new ModuleImpl(options);
|
|
246
269
|
}
|
|
247
|
-
export { createModule };
|
|
270
|
+
export { ModuleException, createModule, module_error_code_enum_ModuleErrorCodeEnum as ModuleErrorCodeEnum };
|
|
@@ -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
|
|
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?:
|
|
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
|
}
|
|
@@ -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.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -15,13 +15,16 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@husky-di/core": "1.
|
|
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
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
25
28
|
},
|
|
26
29
|
"scripts": {
|
|
27
30
|
"build": "rslib build",
|
|
@@ -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
|
-
}
|