@husky-di/module 1.0.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/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # Rslib project
2
+
3
+ ## Setup
4
+
5
+ Install the dependencies:
6
+
7
+ ```bash
8
+ pnpm install
9
+ ```
10
+
11
+ ## Get started
12
+
13
+ Build the library:
14
+
15
+ ```bash
16
+ pnpm build
17
+ ```
18
+
19
+ Build the library in watch mode:
20
+
21
+ ```bash
22
+ pnpm dev
23
+ ```
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @overview
3
+ * @author AEPKILL
4
+ * @created 2025-08-18 22:01:34
5
+ */
6
+ import { type ResolveMiddleware, type ServiceIdentifier } from "@husky-di/core";
7
+ export declare function createExportedGuardMiddlewareFactory(exports: ServiceIdentifier<unknown>[]): ResolveMiddleware<any, any>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @overview
3
+ * @author AEPKILL
4
+ * @created 2025-08-09 21:57:05
5
+ */
6
+ import type { CreateModuleOptions, IModule } from "../interfaces/module.interface";
7
+ export declare function createModule(options: CreateModuleOptions): IModule;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @overview
3
+ * @author AEPKILL
4
+ * @created 2025-08-09 14:51:09
5
+ */
6
+ import type { IContainer, IsRegisteredOptions, ResolveInstance, ResolveMiddleware, ResolveOptions, 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
+ constructor(options: CreateModuleOptions);
22
+ resolve<T, O extends ResolveOptions<T>>(serviceIdentifier: ServiceIdentifier<T>, options?: O): ResolveInstance<T, O>;
23
+ isRegistered<T>(serviceIdentifier: ServiceIdentifier<T>, options?: IsRegisteredOptions): boolean;
24
+ getServiceIdentifiers(): ServiceIdentifier<unknown>[];
25
+ use(middleware: ResolveMiddleware<any, any>): void;
26
+ unused(middleware: ResolveMiddleware<any, any>): void;
27
+ withAliases(aliases: Alias[]): ModuleWithAliases;
28
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,281 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ createModule: ()=>createModule
28
+ });
29
+ const core_namespaceObject = require("@husky-di/core");
30
+ function createExportedGuardMiddlewareFactory(exports1) {
31
+ const exportedSet = new Set(exports1);
32
+ return {
33
+ name: "ExportGuard",
34
+ executor (params, next) {
35
+ const { serviceIdentifier, container, resolveRecord } = params;
36
+ const previousContainer = findPreviousContainer(resolveRecord.getPaths());
37
+ if (previousContainer === container) return next(params);
38
+ if (!exportedSet.has(serviceIdentifier)) {
39
+ if (container.isRegistered(serviceIdentifier, {
40
+ recursive: true
41
+ })) throw new core_namespaceObject.ResolveException(`Service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(serviceIdentifier)}" is not exported from ${container.displayName}.`, resolveRecord);
42
+ }
43
+ return next(params);
44
+ }
45
+ };
46
+ }
47
+ function findPreviousContainer(paths) {
48
+ let lastContainer;
49
+ for (const path of paths)if ((0, core_namespaceObject.isResolveServiceIdentifierRecord)(path.value)) {
50
+ if (lastContainer) return path.value.container;
51
+ lastContainer = path.value.container;
52
+ }
53
+ }
54
+ function isModuleWithAliases(moduleImport) {
55
+ return moduleImport.module instanceof Module;
56
+ }
57
+ function getModuleByImport(moduleImport) {
58
+ return isModuleWithAliases(moduleImport) ? moduleImport.module : moduleImport;
59
+ }
60
+ function build(module) {
61
+ const builder = new ModuleBuilder(module);
62
+ return builder.build();
63
+ }
64
+ class ModuleBuilder {
65
+ module;
66
+ serviceIdentifierMap = new Map();
67
+ availableServiceIdentifiers = new Set();
68
+ importAliasesCache = new Map();
69
+ constructor(module){
70
+ this.module = module;
71
+ }
72
+ build() {
73
+ this.validateAndCollectInfo();
74
+ if (this.module.container) return this.module.container;
75
+ const container = (0, core_namespaceObject.createContainer)(this.module.name);
76
+ if (this.module.exports?.length) container.use(createExportedGuardMiddlewareFactory(this.module.exports));
77
+ this.registerDeclarations(container);
78
+ this.registerImports(container);
79
+ return container;
80
+ }
81
+ validateAndCollectInfo() {
82
+ this.validateImportUniqueness();
83
+ this.validateExportUniqueness();
84
+ this.validateCircularDependencies();
85
+ this.collectServiceInfoAndValidateConflicts();
86
+ this.validateExportValidity();
87
+ }
88
+ validateImportUniqueness() {
89
+ const { imports } = this.module;
90
+ if (!imports?.length) return;
91
+ const importModules = new Set();
92
+ for (const importModule of imports)try {
93
+ const importedModule = getModuleByImport(importModule);
94
+ if (importModules.has(importedModule)) throw new Error(`Duplicate import module: "${importedModule.displayName}" in "${this.module.displayName}".`);
95
+ importModules.add(importedModule);
96
+ } catch (error) {
97
+ throw new Error(`Invalid module import in "${this.module.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
98
+ }
99
+ }
100
+ validateExportUniqueness() {
101
+ const { exports: exports1 } = this.module;
102
+ if (!exports1?.length) return;
103
+ const existingExportServiceIdentifiers = new Set();
104
+ for (const exported of exports1){
105
+ if (existingExportServiceIdentifiers.has(exported)) throw new Error(`Duplicate export service identifier: "${(0, core_namespaceObject.getServiceIdentifierName)(exported)}" in "${this.module.displayName}".`);
106
+ existingExportServiceIdentifiers.add(exported);
107
+ }
108
+ }
109
+ validateCircularDependencies() {
110
+ const visited = new Set();
111
+ const visiting = new Set();
112
+ const dependencyPath = [];
113
+ this.detectCircularDependency(this.module, visited, visiting, dependencyPath);
114
+ }
115
+ detectCircularDependency(currentModule, visited, visiting, dependencyPath) {
116
+ if (visited.has(currentModule)) return;
117
+ if (visiting.has(currentModule)) {
118
+ const cycleStartIndex = dependencyPath.findIndex((module)=>module === currentModule);
119
+ const cyclePath = dependencyPath.slice(cycleStartIndex).concat(currentModule).map((module)=>module.displayName).join(" -> ");
120
+ throw new Error(`Circular dependency detected: ${cyclePath}. Modules cannot have circular import relationships.`);
121
+ }
122
+ visiting.add(currentModule);
123
+ dependencyPath.push(currentModule);
124
+ const imports = currentModule.imports ?? [];
125
+ for (const importModule of imports)try {
126
+ const importedModule = getModuleByImport(importModule);
127
+ this.detectCircularDependency(importedModule, visited, visiting, dependencyPath);
128
+ } catch (error) {
129
+ if (error instanceof Error && error.message.includes("Circular dependency detected")) throw error;
130
+ throw new Error(`Failed to validate circular dependencies for "${currentModule.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
131
+ }
132
+ visiting.delete(currentModule);
133
+ dependencyPath.pop();
134
+ visited.add(currentModule);
135
+ }
136
+ collectServiceInfoAndValidateConflicts() {
137
+ const { imports, declarations } = this.module;
138
+ if (declarations?.length) for (const declaration of declarations){
139
+ this.serviceIdentifierMap.set(declaration.serviceIdentifier, {
140
+ type: "declaration",
141
+ source: "declarations"
142
+ });
143
+ this.availableServiceIdentifiers.add(declaration.serviceIdentifier);
144
+ }
145
+ if (imports?.length) for (const importModule of imports)try {
146
+ const importedModule = getModuleByImport(importModule);
147
+ const exportedServices = importedModule.exports ?? [];
148
+ const aliasesMap = this.buildAndCacheAliasesMap(importModule, importedModule);
149
+ for (const exported of exportedServices){
150
+ const existing = this.serviceIdentifierMap.get(exported);
151
+ if (existing) {
152
+ const conflictInfo = {
153
+ serviceName: (0, core_namespaceObject.getServiceIdentifierName)(exported),
154
+ currentModule: importedModule.displayName,
155
+ existing,
156
+ targetModule: this.module.displayName
157
+ };
158
+ throw new Error(this.buildConflictMessage(conflictInfo));
159
+ }
160
+ this.serviceIdentifierMap.set(exported, {
161
+ type: "import",
162
+ source: importedModule.displayName
163
+ });
164
+ this.availableServiceIdentifiers.add(exported);
165
+ const alias = aliasesMap.get(exported);
166
+ if (alias) this.availableServiceIdentifiers.add(alias);
167
+ }
168
+ } catch (error) {
169
+ throw new Error(`Failed to validate imports in "${this.module.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
170
+ }
171
+ }
172
+ validateExportValidity() {
173
+ const { exports: exports1 } = this.module;
174
+ if (!exports1?.length) return;
175
+ for (const exported of exports1)if (!this.availableServiceIdentifiers.has(exported)) throw new Error(`Cannot export service identifier "${(0, core_namespaceObject.getServiceIdentifierName)(exported)}" from "${this.module.displayName}": it is not declared in this module or imported from any imported module.`);
176
+ }
177
+ buildAndCacheAliasesMap(importModule, importedModule) {
178
+ const cached = this.importAliasesCache.get(importedModule);
179
+ if (cached) return cached;
180
+ const aliasesMap = new Map();
181
+ const moduleWithAliases = importModule;
182
+ const aliases = moduleWithAliases.aliases ?? [];
183
+ for (const alias of aliases)aliasesMap.set(alias.serviceIdentifier, alias.as);
184
+ this.importAliasesCache.set(importedModule, aliasesMap);
185
+ return aliasesMap;
186
+ }
187
+ registerDeclarations(container) {
188
+ const { declarations } = this.module;
189
+ if (!declarations?.length) return;
190
+ for (const declaration of declarations){
191
+ const { serviceIdentifier, ...rest } = declaration;
192
+ container.register(serviceIdentifier, rest);
193
+ }
194
+ }
195
+ registerImports(container) {
196
+ const { imports } = this.module;
197
+ if (!imports?.length) return;
198
+ for (const importModule of imports){
199
+ const importedModule = getModuleByImport(importModule);
200
+ const aliasesMap = this.importAliasesCache.get(importedModule) ?? new Map();
201
+ for (const exported of importedModule.exports ?? [])container.register(aliasesMap.get(exported) ?? exported, {
202
+ useAlias: exported,
203
+ getContainer () {
204
+ return importedModule.container;
205
+ }
206
+ });
207
+ }
208
+ }
209
+ buildConflictMessage(conflictInfo) {
210
+ const { serviceName, currentModule, existing, targetModule } = conflictInfo;
211
+ const conflictType = "declaration" === existing.type ? "declared in" : "exported by";
212
+ const conflictSource = existing.source;
213
+ return `Service identifier conflict: "${serviceName}" is exported by "${currentModule}" and ${conflictType} "${conflictSource}" in "${targetModule}".`;
214
+ }
215
+ }
216
+ const createModuleId = (0, core_namespaceObject.incrementalIdFactory)("MODULE");
217
+ class Module {
218
+ get id() {
219
+ return this._id;
220
+ }
221
+ get name() {
222
+ return this._name;
223
+ }
224
+ get declarations() {
225
+ return this._declarations;
226
+ }
227
+ get exports() {
228
+ return this._exports;
229
+ }
230
+ get imports() {
231
+ return this._imports;
232
+ }
233
+ get displayName() {
234
+ return `${String(this._name)}#${this._id}`;
235
+ }
236
+ container;
237
+ _id;
238
+ _name;
239
+ _declarations;
240
+ _imports;
241
+ _exports;
242
+ constructor(options){
243
+ this._id = createModuleId();
244
+ this._name = options.name;
245
+ this._declarations = options.declarations;
246
+ this._imports = options.imports;
247
+ this._exports = options.exports;
248
+ this.container = build(this);
249
+ }
250
+ resolve(serviceIdentifier, options) {
251
+ return this.container.resolve(serviceIdentifier, options);
252
+ }
253
+ isRegistered(serviceIdentifier, options) {
254
+ return this.container.isRegistered(serviceIdentifier, options);
255
+ }
256
+ getServiceIdentifiers() {
257
+ return this.container.getServiceIdentifiers();
258
+ }
259
+ use(middleware) {
260
+ this.container.use(middleware);
261
+ }
262
+ unused(middleware) {
263
+ this.container.unused(middleware);
264
+ }
265
+ withAliases(aliases) {
266
+ return {
267
+ module: this,
268
+ aliases
269
+ };
270
+ }
271
+ }
272
+ function createModule(options) {
273
+ return new Module(options);
274
+ }
275
+ exports.createModule = __webpack_exports__.createModule;
276
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
277
+ "createModule"
278
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
279
+ Object.defineProperty(exports, '__esModule', {
280
+ value: true
281
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @overview
3
+ * @author AEPKILL
4
+ * @created 2025-08-12 22:53:27
5
+ */
6
+ export { createModule } from "./factories/module.factory";
7
+ export type { CreateModuleOptions, IModule, } from "./interfaces/module.interface";
package/dist/index.js ADDED
@@ -0,0 +1,247 @@
1
+ import { ResolveException, createContainer, getServiceIdentifierName, incrementalIdFactory, isResolveServiceIdentifierRecord } from "@husky-di/core";
2
+ function createExportedGuardMiddlewareFactory(exports) {
3
+ const exportedSet = new Set(exports);
4
+ return {
5
+ name: "ExportGuard",
6
+ executor (params, next) {
7
+ const { serviceIdentifier, container, resolveRecord } = params;
8
+ const previousContainer = findPreviousContainer(resolveRecord.getPaths());
9
+ if (previousContainer === container) return next(params);
10
+ if (!exportedSet.has(serviceIdentifier)) {
11
+ if (container.isRegistered(serviceIdentifier, {
12
+ recursive: true
13
+ })) throw new ResolveException(`Service identifier "${getServiceIdentifierName(serviceIdentifier)}" is not exported from ${container.displayName}.`, resolveRecord);
14
+ }
15
+ return next(params);
16
+ }
17
+ };
18
+ }
19
+ function findPreviousContainer(paths) {
20
+ let lastContainer;
21
+ for (const path of paths)if (isResolveServiceIdentifierRecord(path.value)) {
22
+ if (lastContainer) return path.value.container;
23
+ lastContainer = path.value.container;
24
+ }
25
+ }
26
+ function isModuleWithAliases(moduleImport) {
27
+ return moduleImport.module instanceof Module;
28
+ }
29
+ function getModuleByImport(moduleImport) {
30
+ return isModuleWithAliases(moduleImport) ? moduleImport.module : moduleImport;
31
+ }
32
+ function build(module) {
33
+ const builder = new ModuleBuilder(module);
34
+ return builder.build();
35
+ }
36
+ class ModuleBuilder {
37
+ module;
38
+ serviceIdentifierMap = new Map();
39
+ availableServiceIdentifiers = new Set();
40
+ importAliasesCache = new Map();
41
+ constructor(module){
42
+ this.module = module;
43
+ }
44
+ build() {
45
+ this.validateAndCollectInfo();
46
+ if (this.module.container) return this.module.container;
47
+ const container = createContainer(this.module.name);
48
+ if (this.module.exports?.length) container.use(createExportedGuardMiddlewareFactory(this.module.exports));
49
+ this.registerDeclarations(container);
50
+ this.registerImports(container);
51
+ return container;
52
+ }
53
+ validateAndCollectInfo() {
54
+ this.validateImportUniqueness();
55
+ this.validateExportUniqueness();
56
+ this.validateCircularDependencies();
57
+ this.collectServiceInfoAndValidateConflicts();
58
+ this.validateExportValidity();
59
+ }
60
+ validateImportUniqueness() {
61
+ const { imports } = this.module;
62
+ if (!imports?.length) return;
63
+ const importModules = new Set();
64
+ for (const importModule of imports)try {
65
+ const importedModule = getModuleByImport(importModule);
66
+ if (importModules.has(importedModule)) throw new Error(`Duplicate import module: "${importedModule.displayName}" in "${this.module.displayName}".`);
67
+ importModules.add(importedModule);
68
+ } catch (error) {
69
+ throw new Error(`Invalid module import in "${this.module.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
70
+ }
71
+ }
72
+ validateExportUniqueness() {
73
+ const { exports } = this.module;
74
+ if (!exports?.length) return;
75
+ const existingExportServiceIdentifiers = new Set();
76
+ for (const exported of exports){
77
+ if (existingExportServiceIdentifiers.has(exported)) throw new Error(`Duplicate export service identifier: "${getServiceIdentifierName(exported)}" in "${this.module.displayName}".`);
78
+ existingExportServiceIdentifiers.add(exported);
79
+ }
80
+ }
81
+ validateCircularDependencies() {
82
+ const visited = new Set();
83
+ const visiting = new Set();
84
+ const dependencyPath = [];
85
+ this.detectCircularDependency(this.module, visited, visiting, dependencyPath);
86
+ }
87
+ detectCircularDependency(currentModule, visited, visiting, dependencyPath) {
88
+ if (visited.has(currentModule)) return;
89
+ if (visiting.has(currentModule)) {
90
+ const cycleStartIndex = dependencyPath.findIndex((module)=>module === currentModule);
91
+ const cyclePath = dependencyPath.slice(cycleStartIndex).concat(currentModule).map((module)=>module.displayName).join(" -> ");
92
+ throw new Error(`Circular dependency detected: ${cyclePath}. Modules cannot have circular import relationships.`);
93
+ }
94
+ visiting.add(currentModule);
95
+ dependencyPath.push(currentModule);
96
+ const imports = currentModule.imports ?? [];
97
+ for (const importModule of imports)try {
98
+ const importedModule = getModuleByImport(importModule);
99
+ this.detectCircularDependency(importedModule, visited, visiting, dependencyPath);
100
+ } catch (error) {
101
+ if (error instanceof Error && error.message.includes("Circular dependency detected")) throw error;
102
+ throw new Error(`Failed to validate circular dependencies for "${currentModule.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
103
+ }
104
+ visiting.delete(currentModule);
105
+ dependencyPath.pop();
106
+ visited.add(currentModule);
107
+ }
108
+ collectServiceInfoAndValidateConflicts() {
109
+ const { imports, declarations } = this.module;
110
+ if (declarations?.length) for (const declaration of declarations){
111
+ this.serviceIdentifierMap.set(declaration.serviceIdentifier, {
112
+ type: "declaration",
113
+ source: "declarations"
114
+ });
115
+ this.availableServiceIdentifiers.add(declaration.serviceIdentifier);
116
+ }
117
+ if (imports?.length) for (const importModule of imports)try {
118
+ const importedModule = getModuleByImport(importModule);
119
+ const exportedServices = importedModule.exports ?? [];
120
+ const aliasesMap = this.buildAndCacheAliasesMap(importModule, importedModule);
121
+ for (const exported of exportedServices){
122
+ const existing = this.serviceIdentifierMap.get(exported);
123
+ if (existing) {
124
+ const conflictInfo = {
125
+ serviceName: getServiceIdentifierName(exported),
126
+ currentModule: importedModule.displayName,
127
+ existing,
128
+ targetModule: this.module.displayName
129
+ };
130
+ throw new Error(this.buildConflictMessage(conflictInfo));
131
+ }
132
+ this.serviceIdentifierMap.set(exported, {
133
+ type: "import",
134
+ source: importedModule.displayName
135
+ });
136
+ this.availableServiceIdentifiers.add(exported);
137
+ const alias = aliasesMap.get(exported);
138
+ if (alias) this.availableServiceIdentifiers.add(alias);
139
+ }
140
+ } catch (error) {
141
+ throw new Error(`Failed to validate imports in "${this.module.displayName}": ${error instanceof Error ? error.message : "Unknown error"}`);
142
+ }
143
+ }
144
+ validateExportValidity() {
145
+ const { exports } = this.module;
146
+ if (!exports?.length) return;
147
+ for (const exported of exports)if (!this.availableServiceIdentifiers.has(exported)) throw new Error(`Cannot export service identifier "${getServiceIdentifierName(exported)}" from "${this.module.displayName}": it is not declared in this module or imported from any imported module.`);
148
+ }
149
+ buildAndCacheAliasesMap(importModule, importedModule) {
150
+ const cached = this.importAliasesCache.get(importedModule);
151
+ if (cached) return cached;
152
+ const aliasesMap = new Map();
153
+ const moduleWithAliases = importModule;
154
+ const aliases = moduleWithAliases.aliases ?? [];
155
+ for (const alias of aliases)aliasesMap.set(alias.serviceIdentifier, alias.as);
156
+ this.importAliasesCache.set(importedModule, aliasesMap);
157
+ return aliasesMap;
158
+ }
159
+ registerDeclarations(container) {
160
+ const { declarations } = this.module;
161
+ if (!declarations?.length) return;
162
+ for (const declaration of declarations){
163
+ const { serviceIdentifier, ...rest } = declaration;
164
+ container.register(serviceIdentifier, rest);
165
+ }
166
+ }
167
+ registerImports(container) {
168
+ const { imports } = this.module;
169
+ if (!imports?.length) return;
170
+ for (const importModule of imports){
171
+ const importedModule = getModuleByImport(importModule);
172
+ const aliasesMap = this.importAliasesCache.get(importedModule) ?? new Map();
173
+ for (const exported of importedModule.exports ?? [])container.register(aliasesMap.get(exported) ?? exported, {
174
+ useAlias: exported,
175
+ getContainer () {
176
+ return importedModule.container;
177
+ }
178
+ });
179
+ }
180
+ }
181
+ buildConflictMessage(conflictInfo) {
182
+ const { serviceName, currentModule, existing, targetModule } = conflictInfo;
183
+ const conflictType = "declaration" === existing.type ? "declared in" : "exported by";
184
+ const conflictSource = existing.source;
185
+ return `Service identifier conflict: "${serviceName}" is exported by "${currentModule}" and ${conflictType} "${conflictSource}" in "${targetModule}".`;
186
+ }
187
+ }
188
+ const createModuleId = incrementalIdFactory("MODULE");
189
+ class Module {
190
+ get id() {
191
+ return this._id;
192
+ }
193
+ get name() {
194
+ return this._name;
195
+ }
196
+ get declarations() {
197
+ return this._declarations;
198
+ }
199
+ get exports() {
200
+ return this._exports;
201
+ }
202
+ get imports() {
203
+ return this._imports;
204
+ }
205
+ get displayName() {
206
+ return `${String(this._name)}#${this._id}`;
207
+ }
208
+ container;
209
+ _id;
210
+ _name;
211
+ _declarations;
212
+ _imports;
213
+ _exports;
214
+ constructor(options){
215
+ this._id = createModuleId();
216
+ this._name = options.name;
217
+ this._declarations = options.declarations;
218
+ this._imports = options.imports;
219
+ this._exports = options.exports;
220
+ this.container = build(this);
221
+ }
222
+ resolve(serviceIdentifier, options) {
223
+ return this.container.resolve(serviceIdentifier, options);
224
+ }
225
+ isRegistered(serviceIdentifier, options) {
226
+ return this.container.isRegistered(serviceIdentifier, options);
227
+ }
228
+ getServiceIdentifiers() {
229
+ return this.container.getServiceIdentifiers();
230
+ }
231
+ use(middleware) {
232
+ this.container.use(middleware);
233
+ }
234
+ unused(middleware) {
235
+ this.container.unused(middleware);
236
+ }
237
+ withAliases(aliases) {
238
+ return {
239
+ module: this,
240
+ aliases
241
+ };
242
+ }
243
+ }
244
+ function createModule(options) {
245
+ return new Module(options);
246
+ }
247
+ export { createModule };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @overview
3
+ * @author AEPKILL
4
+ * @created 2025-08-09 14:42:25
5
+ */
6
+ import type { CreateRegistrationOptions, IContainer, IDisplayName, IUnique, ServiceIdentifier } from "@husky-di/core";
7
+ export type Declaration<T> = CreateRegistrationOptions<T> & {
8
+ serviceIdentifier: ServiceIdentifier<T> | string;
9
+ };
10
+ export type Alias = {
11
+ serviceIdentifier: ServiceIdentifier<unknown>;
12
+ as: ServiceIdentifier<unknown>;
13
+ };
14
+ export type CreateModuleOptions = {
15
+ readonly name: string;
16
+ readonly declarations?: Declaration<unknown>[];
17
+ readonly imports?: Array<IModule | ModuleWithAliases>;
18
+ readonly exports?: ServiceIdentifier<unknown>[];
19
+ };
20
+ export type ModuleWithAliases = {
21
+ module: IModule;
22
+ aliases?: Alias[];
23
+ };
24
+ export interface IModule extends IUnique, IDisplayName, Pick<IContainer, "resolve" | "isRegistered" | "getServiceIdentifiers" | "use" | "unused"> {
25
+ readonly name: string;
26
+ readonly declarations?: Declaration<unknown>[];
27
+ readonly imports?: Array<IModule | ModuleWithAliases>;
28
+ readonly exports?: ServiceIdentifier<unknown>[];
29
+ readonly container: IContainer;
30
+ withAliases(aliases: Alias[]): ModuleWithAliases;
31
+ }
@@ -0,0 +1,126 @@
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
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @overview
3
+ * @author AEPKILL
4
+ * @created 2025-08-09 14:55:21
5
+ */
6
+ export declare const createModuleId: import("@husky-di/core").IdGenerator;
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@husky-di/module",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js",
9
+ "require": "./dist/index.cjs"
10
+ }
11
+ },
12
+ "main": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@husky-di/core": "1.0.0"
19
+ },
20
+ "devDependencies": {
21
+ "@rslib/core": "^0.11.2",
22
+ "@types/node": "^22.17.0",
23
+ "typescript": "^5.9.2",
24
+ "vitest": "^3.2.4"
25
+ },
26
+ "scripts": {
27
+ "build": "rslib build",
28
+ "dev": "rslib build --watch",
29
+ "test": "vitest run"
30
+ }
31
+ }