@smounters/imperium 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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/core/app-tokens.d.ts +5 -0
- package/dist/core/app-tokens.js +4 -0
- package/dist/core/application.d.ts +34 -0
- package/dist/core/application.js +252 -0
- package/dist/core/config.d.ts +1 -0
- package/dist/core/config.js +1 -0
- package/dist/core/container.d.ts +76 -0
- package/dist/core/container.js +687 -0
- package/dist/core/errors.d.ts +31 -0
- package/dist/core/errors.js +169 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +4 -0
- package/dist/core/logger.d.ts +7 -0
- package/dist/core/logger.js +12 -0
- package/dist/core/reflector.d.ts +9 -0
- package/dist/core/reflector.js +39 -0
- package/dist/core/server.d.ts +10 -0
- package/dist/core/server.js +296 -0
- package/dist/core/types.d.ts +25 -0
- package/dist/core/types.js +1 -0
- package/dist/decorators/di.decorators.d.ts +10 -0
- package/dist/decorators/di.decorators.js +15 -0
- package/dist/decorators/filters.decorators.d.ts +6 -0
- package/dist/decorators/filters.decorators.js +16 -0
- package/dist/decorators/guards.decorators.d.ts +4 -0
- package/dist/decorators/guards.decorators.js +8 -0
- package/dist/decorators/http.decorators.d.ts +16 -0
- package/dist/decorators/http.decorators.js +60 -0
- package/dist/decorators/index.d.ts +8 -0
- package/dist/decorators/index.js +8 -0
- package/dist/decorators/interceptors.decorators.d.ts +4 -0
- package/dist/decorators/interceptors.decorators.js +8 -0
- package/dist/decorators/metadata.decorators.d.ts +4 -0
- package/dist/decorators/metadata.decorators.js +22 -0
- package/dist/decorators/pipes.decorators.d.ts +4 -0
- package/dist/decorators/pipes.decorators.js +8 -0
- package/dist/decorators/rpc.decorators.d.ts +11 -0
- package/dist/decorators/rpc.decorators.js +50 -0
- package/dist/http/adapter.d.ts +4 -0
- package/dist/http/adapter.js +199 -0
- package/dist/http/index.d.ts +3 -0
- package/dist/http/index.js +3 -0
- package/dist/http/router-builder.d.ts +4 -0
- package/dist/http/router-builder.js +30 -0
- package/dist/http/utils.d.ts +6 -0
- package/dist/http/utils.js +46 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/pipes/index.d.ts +1 -0
- package/dist/pipes/index.js +1 -0
- package/dist/pipes/zod.pipe.d.ts +7 -0
- package/dist/pipes/zod.pipe.js +8 -0
- package/dist/rpc/adapter.d.ts +7 -0
- package/dist/rpc/adapter.js +186 -0
- package/dist/rpc/index.d.ts +3 -0
- package/dist/rpc/index.js +3 -0
- package/dist/rpc/router-builder.d.ts +4 -0
- package/dist/rpc/router-builder.js +28 -0
- package/dist/rpc/utils.d.ts +6 -0
- package/dist/rpc/utils.js +46 -0
- package/dist/services/config.service.d.ts +10 -0
- package/dist/services/config.service.js +41 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +2 -0
- package/dist/services/logger.service.d.ts +30 -0
- package/dist/services/logger.service.js +52 -0
- package/dist/types.d.ts +167 -0
- package/dist/types.js +1 -0
- package/dist/validation/app-config.d.ts +30 -0
- package/dist/validation/app-config.js +25 -0
- package/dist/validation/common.d.ts +8 -0
- package/dist/validation/common.js +57 -0
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.js +2 -0
- package/package.json +98 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { container, Lifecycle } from "tsyringe";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { MODULE_KEY } from "../decorators/di.decorators";
|
|
5
|
+
import { ConfigService, LoggerService } from "../services";
|
|
6
|
+
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from "./app-tokens";
|
|
7
|
+
import { CONFIG_TOKEN } from "./config";
|
|
8
|
+
import { createLogger, LOGGER_TOKEN } from "./logger";
|
|
9
|
+
import { Reflector } from "./reflector";
|
|
10
|
+
function isDynamicModule(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && "module" in value && typeof value.module === "function";
|
|
12
|
+
}
|
|
13
|
+
function getProviderToken(provider) {
|
|
14
|
+
if (typeof provider === "function") {
|
|
15
|
+
return provider;
|
|
16
|
+
}
|
|
17
|
+
return provider.provide;
|
|
18
|
+
}
|
|
19
|
+
function tokenToString(token) {
|
|
20
|
+
if (typeof token === "string") {
|
|
21
|
+
return token;
|
|
22
|
+
}
|
|
23
|
+
if (typeof token === "symbol") {
|
|
24
|
+
return token.toString();
|
|
25
|
+
}
|
|
26
|
+
if (typeof token === "function") {
|
|
27
|
+
return token.name || "<anonymous constructor>";
|
|
28
|
+
}
|
|
29
|
+
return String(token);
|
|
30
|
+
}
|
|
31
|
+
function enhancerKey(value) {
|
|
32
|
+
if (typeof value === "function") {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
if (value && typeof value === "object") {
|
|
36
|
+
const maybeCtor = value.constructor;
|
|
37
|
+
if (maybeCtor && maybeCtor !== Object) {
|
|
38
|
+
return maybeCtor;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function pushUniqueEnhancer(values, value) {
|
|
44
|
+
const key = enhancerKey(value);
|
|
45
|
+
if (values.some((current) => enhancerKey(current) === key)) {
|
|
46
|
+
return values;
|
|
47
|
+
}
|
|
48
|
+
return [...values, value];
|
|
49
|
+
}
|
|
50
|
+
function isAppEnhancerToken(token) {
|
|
51
|
+
return token === APP_GUARD || token === APP_INTERCEPTOR || token === APP_PIPE || token === APP_FILTER;
|
|
52
|
+
}
|
|
53
|
+
function normalizeLifecycleError(error, phase) {
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
return new Error(`[${phase}] ${error.message}`, { cause: error });
|
|
56
|
+
}
|
|
57
|
+
return new Error(`[${phase}] ${String(error)}`);
|
|
58
|
+
}
|
|
59
|
+
function hasLifecycleHooks(target) {
|
|
60
|
+
const prototype = target.prototype;
|
|
61
|
+
return (typeof prototype.onModuleInit === "function" ||
|
|
62
|
+
typeof prototype.onApplicationBootstrap === "function" ||
|
|
63
|
+
typeof prototype.onModuleDestroy === "function" ||
|
|
64
|
+
typeof prototype.beforeApplicationShutdown === "function" ||
|
|
65
|
+
typeof prototype.onApplicationShutdown === "function");
|
|
66
|
+
}
|
|
67
|
+
function normalizeSignatureValue(value, seen = new WeakSet()) {
|
|
68
|
+
if (value === null || value === undefined) {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
const valueType = typeof value;
|
|
72
|
+
if (valueType === "string" || valueType === "number" || valueType === "boolean") {
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
if (valueType === "bigint") {
|
|
76
|
+
return `[bigint:${value.toString()}]`;
|
|
77
|
+
}
|
|
78
|
+
if (valueType === "symbol") {
|
|
79
|
+
return `[symbol:${value.toString()}]`;
|
|
80
|
+
}
|
|
81
|
+
if (valueType === "function") {
|
|
82
|
+
const fn = value;
|
|
83
|
+
return `[fn:${fn.name || "<anonymous>"}]`;
|
|
84
|
+
}
|
|
85
|
+
if (value instanceof Date) {
|
|
86
|
+
return `[date:${value.toISOString()}]`;
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
if (seen.has(value)) {
|
|
90
|
+
return "[circular]";
|
|
91
|
+
}
|
|
92
|
+
seen.add(value);
|
|
93
|
+
const normalized = value.map((item) => normalizeSignatureValue(item, seen));
|
|
94
|
+
seen.delete(value);
|
|
95
|
+
return normalized;
|
|
96
|
+
}
|
|
97
|
+
if (valueType === "object") {
|
|
98
|
+
const objectValue = value;
|
|
99
|
+
if (seen.has(objectValue)) {
|
|
100
|
+
return "[circular]";
|
|
101
|
+
}
|
|
102
|
+
seen.add(objectValue);
|
|
103
|
+
const normalized = {};
|
|
104
|
+
const entries = Object.entries(objectValue).sort(([left], [right]) => left.localeCompare(right));
|
|
105
|
+
for (const [key, currentValue] of entries) {
|
|
106
|
+
normalized[key] = normalizeSignatureValue(currentValue, seen);
|
|
107
|
+
}
|
|
108
|
+
seen.delete(objectValue);
|
|
109
|
+
return normalized;
|
|
110
|
+
}
|
|
111
|
+
return String(value);
|
|
112
|
+
}
|
|
113
|
+
function createDynamicModuleSignature(meta) {
|
|
114
|
+
return JSON.stringify(normalizeSignatureValue(meta));
|
|
115
|
+
}
|
|
116
|
+
export class AppContainer {
|
|
117
|
+
constructor() {
|
|
118
|
+
this.loadedModules = new Map();
|
|
119
|
+
this.dynamicModuleSignatures = new Map();
|
|
120
|
+
this.globalExportOwners = new Map();
|
|
121
|
+
this.loadingModules = new Set();
|
|
122
|
+
this.rpcControllerSet = new Set();
|
|
123
|
+
this.httpControllerSet = new Set();
|
|
124
|
+
this.rpcControllerModules = new Map();
|
|
125
|
+
this.httpControllerModules = new Map();
|
|
126
|
+
this.lifecycleTargets = [];
|
|
127
|
+
this.requestScopeOwners = new WeakMap();
|
|
128
|
+
this.requestScopeLinkedScopes = new WeakMap();
|
|
129
|
+
this.requestContextStorage = new AsyncLocalStorage();
|
|
130
|
+
this.rootModuleRef = null;
|
|
131
|
+
this.controllers = [];
|
|
132
|
+
this.httpControllers = [];
|
|
133
|
+
this.lifecycleInstances = null;
|
|
134
|
+
this.initialized = false;
|
|
135
|
+
this.closed = false;
|
|
136
|
+
this.exposeInternalErrors = false;
|
|
137
|
+
this.globalGuards = [];
|
|
138
|
+
this.globalInterceptors = [];
|
|
139
|
+
this.globalPipes = [];
|
|
140
|
+
this.globalFilters = [];
|
|
141
|
+
this.root = container.createChildContainer();
|
|
142
|
+
this.setConfig(process.env);
|
|
143
|
+
this.setLogger(createLogger());
|
|
144
|
+
this.root.registerSingleton(ConfigService, ConfigService);
|
|
145
|
+
this.root.registerSingleton(LoggerService, LoggerService);
|
|
146
|
+
this.root.registerSingleton(Reflector, Reflector);
|
|
147
|
+
}
|
|
148
|
+
addLifecycleTarget(containerRef, target) {
|
|
149
|
+
if (!hasLifecycleHooks(target)) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const exists = this.lifecycleTargets.some((item) => item.container === containerRef && item.target === target);
|
|
153
|
+
if (exists) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.lifecycleTargets.push({ container: containerRef, target });
|
|
157
|
+
this.lifecycleInstances = null;
|
|
158
|
+
}
|
|
159
|
+
getLifecycleInstances() {
|
|
160
|
+
if (this.lifecycleInstances) {
|
|
161
|
+
return this.lifecycleInstances;
|
|
162
|
+
}
|
|
163
|
+
const instances = this.lifecycleTargets.map(({ container, target }) => container.resolve(target));
|
|
164
|
+
this.lifecycleInstances = instances;
|
|
165
|
+
return instances;
|
|
166
|
+
}
|
|
167
|
+
normalizeModuleImport(moduleImport) {
|
|
168
|
+
if (isDynamicModule(moduleImport)) {
|
|
169
|
+
const { module, id, ...dynamicMeta } = moduleImport;
|
|
170
|
+
const normalizedId = typeof id === "string" && id.trim().length > 0 ? id.trim() : "__default__";
|
|
171
|
+
return {
|
|
172
|
+
key: `dynamic:${module.name || "<anonymous module>"}:${normalizedId}`,
|
|
173
|
+
moduleClass: module,
|
|
174
|
+
dynamicMeta,
|
|
175
|
+
dynamicSignature: createDynamicModuleSignature(dynamicMeta),
|
|
176
|
+
dynamicId: normalizedId,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
key: moduleImport,
|
|
181
|
+
moduleClass: moduleImport,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
readModuleMetadata(moduleClass) {
|
|
185
|
+
const meta = Reflect.getMetadata(MODULE_KEY, moduleClass);
|
|
186
|
+
if (!meta) {
|
|
187
|
+
throw new Error(`Invalid module: ${moduleClass.name || "<anonymous module>"} has no @Module() metadata`);
|
|
188
|
+
}
|
|
189
|
+
return meta;
|
|
190
|
+
}
|
|
191
|
+
mergeModuleMetadata(staticMeta, dynamicMeta) {
|
|
192
|
+
return {
|
|
193
|
+
providers: [...(staticMeta.providers ?? []), ...(dynamicMeta?.providers ?? [])],
|
|
194
|
+
controllers: [...(staticMeta.controllers ?? []), ...(dynamicMeta?.controllers ?? [])],
|
|
195
|
+
httpControllers: [...(staticMeta.httpControllers ?? []), ...(dynamicMeta?.httpControllers ?? [])],
|
|
196
|
+
imports: [...(staticMeta.imports ?? []), ...(dynamicMeta?.imports ?? [])],
|
|
197
|
+
exports: [...(staticMeta.exports ?? []), ...(dynamicMeta?.exports ?? [])],
|
|
198
|
+
global: dynamicMeta?.global ?? staticMeta.global ?? false,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
collectProviderTokens(providers) {
|
|
202
|
+
const tokens = new Set();
|
|
203
|
+
for (const provider of providers) {
|
|
204
|
+
tokens.add(getProviderToken(provider));
|
|
205
|
+
}
|
|
206
|
+
return tokens;
|
|
207
|
+
}
|
|
208
|
+
linkImportedExports(moduleRef) {
|
|
209
|
+
for (const importedRef of moduleRef.imports) {
|
|
210
|
+
for (const token of importedRef.exportedTokens) {
|
|
211
|
+
if (moduleRef.providers.has(token)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (moduleRef.container.isRegistered(token, false)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
moduleRef.container.register(token, {
|
|
218
|
+
useFactory: () => this.resolveTokenFromModuleScope(importedRef, token),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
resolveAppEnhancerInstance(moduleRef, provider) {
|
|
224
|
+
if ("useValue" in provider) {
|
|
225
|
+
return provider.useValue;
|
|
226
|
+
}
|
|
227
|
+
if ("useFactory" in provider) {
|
|
228
|
+
return provider.useFactory(moduleRef.container);
|
|
229
|
+
}
|
|
230
|
+
if ("useExisting" in provider) {
|
|
231
|
+
return moduleRef.container.resolve(provider.useExisting);
|
|
232
|
+
}
|
|
233
|
+
if (!moduleRef.container.isRegistered(provider.useClass, false)) {
|
|
234
|
+
moduleRef.container.register(provider.useClass, { useClass: provider.useClass }, provider.options);
|
|
235
|
+
this.addLifecycleTarget(moduleRef.container, provider.useClass);
|
|
236
|
+
}
|
|
237
|
+
return moduleRef.container.resolve(provider.useClass);
|
|
238
|
+
}
|
|
239
|
+
registerAppEnhancerProvider(moduleRef, provider) {
|
|
240
|
+
if (typeof provider === "function") {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (provider.provide === APP_GUARD) {
|
|
244
|
+
const guard = this.resolveAppEnhancerInstance(moduleRef, provider);
|
|
245
|
+
this.globalGuards = pushUniqueEnhancer(this.globalGuards, guard);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
if (provider.provide === APP_INTERCEPTOR) {
|
|
249
|
+
const interceptor = this.resolveAppEnhancerInstance(moduleRef, provider);
|
|
250
|
+
this.globalInterceptors = pushUniqueEnhancer(this.globalInterceptors, interceptor);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (provider.provide === APP_PIPE) {
|
|
254
|
+
const pipe = this.resolveAppEnhancerInstance(moduleRef, provider);
|
|
255
|
+
this.globalPipes = pushUniqueEnhancer(this.globalPipes, pipe);
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
if (provider.provide === APP_FILTER) {
|
|
259
|
+
const filter = this.resolveAppEnhancerInstance(moduleRef, provider);
|
|
260
|
+
this.globalFilters = pushUniqueEnhancer(this.globalFilters, filter);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
registerProvider(moduleRef, provider) {
|
|
266
|
+
if (this.registerAppEnhancerProvider(moduleRef, provider)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (typeof provider === "function") {
|
|
270
|
+
moduleRef.container.registerSingleton(provider, provider);
|
|
271
|
+
this.addLifecycleTarget(moduleRef.container, provider);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if ("useValue" in provider) {
|
|
275
|
+
moduleRef.container.registerInstance(provider.provide, provider.useValue);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if ("useFactory" in provider) {
|
|
279
|
+
moduleRef.container.register(provider.provide, {
|
|
280
|
+
useFactory: (dc) => provider.useFactory(dc),
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if ("useExisting" in provider) {
|
|
285
|
+
moduleRef.container.register(provider.provide, { useToken: provider.useExisting }, provider.options);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
moduleRef.container.register(provider.provide, { useClass: provider.useClass }, provider.options);
|
|
289
|
+
this.addLifecycleTarget(moduleRef.container, provider.useClass);
|
|
290
|
+
}
|
|
291
|
+
resolveModuleExports(moduleRef) {
|
|
292
|
+
const exportedTokens = new Set();
|
|
293
|
+
for (const token of moduleRef.meta.exports) {
|
|
294
|
+
const exportedBySelf = moduleRef.providers.has(token);
|
|
295
|
+
const exportedByImport = moduleRef.imports.some((imported) => imported.exportedTokens.has(token));
|
|
296
|
+
if (!exportedBySelf && !exportedByImport) {
|
|
297
|
+
const moduleName = moduleRef.moduleClass.name || "<anonymous module>";
|
|
298
|
+
throw new Error(`Module ${moduleName} exports unknown token: ${String(token)}`);
|
|
299
|
+
}
|
|
300
|
+
exportedTokens.add(token);
|
|
301
|
+
}
|
|
302
|
+
return exportedTokens;
|
|
303
|
+
}
|
|
304
|
+
registerGlobalExports(moduleRef) {
|
|
305
|
+
const moduleOwner = typeof moduleRef.key === "string" ? moduleRef.key : moduleRef.moduleClass.name || "<anonymous module>";
|
|
306
|
+
for (const token of moduleRef.exportedTokens) {
|
|
307
|
+
if (isAppEnhancerToken(token)) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const existingOwner = this.globalExportOwners.get(token);
|
|
311
|
+
if (existingOwner && existingOwner !== moduleOwner) {
|
|
312
|
+
throw new Error(`Global provider token collision: "${tokenToString(token)}" is exported by both ` +
|
|
313
|
+
`${existingOwner} and ${moduleOwner}.`);
|
|
314
|
+
}
|
|
315
|
+
if (!existingOwner) {
|
|
316
|
+
this.globalExportOwners.set(token, moduleOwner);
|
|
317
|
+
}
|
|
318
|
+
if (this.root.isRegistered(token, false)) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
this.root.register(token, {
|
|
322
|
+
useFactory: () => this.resolveTokenFromModuleScope(moduleRef, token),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
resolveControllerModule(controller) {
|
|
327
|
+
return this.httpControllerModules.get(controller) ?? this.rpcControllerModules.get(controller);
|
|
328
|
+
}
|
|
329
|
+
getOrCreateLinkedScopes(originScope) {
|
|
330
|
+
const existing = this.requestScopeLinkedScopes.get(originScope);
|
|
331
|
+
if (existing) {
|
|
332
|
+
return existing;
|
|
333
|
+
}
|
|
334
|
+
const created = new Set();
|
|
335
|
+
this.requestScopeLinkedScopes.set(originScope, created);
|
|
336
|
+
return created;
|
|
337
|
+
}
|
|
338
|
+
getModuleScopeForRequest(context, moduleRef) {
|
|
339
|
+
const existing = context.scopesByModule.get(moduleRef);
|
|
340
|
+
if (existing) {
|
|
341
|
+
return existing;
|
|
342
|
+
}
|
|
343
|
+
const scopedContainer = moduleRef.container.createChildContainer();
|
|
344
|
+
context.scopesByModule.set(moduleRef, scopedContainer);
|
|
345
|
+
this.getOrCreateLinkedScopes(context.originScope).add(scopedContainer);
|
|
346
|
+
return scopedContainer;
|
|
347
|
+
}
|
|
348
|
+
resolveTokenFromModuleScope(moduleRef, token) {
|
|
349
|
+
const context = this.requestContextStorage.getStore();
|
|
350
|
+
if (!context) {
|
|
351
|
+
return moduleRef.container.resolve(token);
|
|
352
|
+
}
|
|
353
|
+
const scope = this.getModuleScopeForRequest(context, moduleRef);
|
|
354
|
+
return scope.resolve(token);
|
|
355
|
+
}
|
|
356
|
+
assertRuntimeConfigMutable(settingName) {
|
|
357
|
+
if (this.closed) {
|
|
358
|
+
throw new Error(`Cannot configure ${settingName}: container is already closed`);
|
|
359
|
+
}
|
|
360
|
+
if (this.initialized) {
|
|
361
|
+
throw new Error(`Cannot configure ${settingName} after container initialization`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
loadModuleRef(moduleImport) {
|
|
365
|
+
const normalized = this.normalizeModuleImport(moduleImport);
|
|
366
|
+
const dynamicKey = typeof normalized.key === "string" ? normalized.key : undefined;
|
|
367
|
+
let signatureInserted = false;
|
|
368
|
+
if (dynamicKey && normalized.dynamicSignature) {
|
|
369
|
+
const existingSignature = this.dynamicModuleSignatures.get(dynamicKey);
|
|
370
|
+
if (existingSignature && existingSignature !== normalized.dynamicSignature) {
|
|
371
|
+
const moduleName = normalized.moduleClass.name || "<anonymous module>";
|
|
372
|
+
const dynamicId = normalized.dynamicId ?? "__default__";
|
|
373
|
+
throw new Error(`Dynamic module collision for ${moduleName} with id "${dynamicId}". ` +
|
|
374
|
+
"Use unique DynamicModule.id values for different module configurations.");
|
|
375
|
+
}
|
|
376
|
+
if (!existingSignature) {
|
|
377
|
+
this.dynamicModuleSignatures.set(dynamicKey, normalized.dynamicSignature);
|
|
378
|
+
signatureInserted = true;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const existing = this.loadedModules.get(normalized.key);
|
|
382
|
+
if (existing) {
|
|
383
|
+
return existing;
|
|
384
|
+
}
|
|
385
|
+
if (this.loadingModules.has(normalized.key)) {
|
|
386
|
+
const moduleName = normalized.moduleClass.name || "<anonymous module>";
|
|
387
|
+
throw new Error(`Circular module import detected: ${moduleName}`);
|
|
388
|
+
}
|
|
389
|
+
this.loadingModules.add(normalized.key);
|
|
390
|
+
const staticMeta = this.readModuleMetadata(normalized.moduleClass);
|
|
391
|
+
const meta = this.mergeModuleMetadata(staticMeta, normalized.dynamicMeta);
|
|
392
|
+
const moduleRef = {
|
|
393
|
+
key: normalized.key,
|
|
394
|
+
moduleClass: normalized.moduleClass,
|
|
395
|
+
container: this.root.createChildContainer(),
|
|
396
|
+
meta,
|
|
397
|
+
providers: this.collectProviderTokens(meta.providers),
|
|
398
|
+
exportedTokens: new Set(),
|
|
399
|
+
imports: [],
|
|
400
|
+
};
|
|
401
|
+
this.loadedModules.set(normalized.key, moduleRef);
|
|
402
|
+
try {
|
|
403
|
+
moduleRef.imports = meta.imports.map((imported) => this.loadModuleRef(imported));
|
|
404
|
+
this.linkImportedExports(moduleRef);
|
|
405
|
+
const appEnhancerProviders = [];
|
|
406
|
+
for (const provider of meta.providers) {
|
|
407
|
+
if (typeof provider !== "function" && isAppEnhancerToken(provider.provide)) {
|
|
408
|
+
appEnhancerProviders.push(provider);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
this.registerProvider(moduleRef, provider);
|
|
412
|
+
}
|
|
413
|
+
for (const provider of appEnhancerProviders) {
|
|
414
|
+
this.registerProvider(moduleRef, provider);
|
|
415
|
+
}
|
|
416
|
+
for (const ctrl of meta.controllers) {
|
|
417
|
+
if (this.rpcControllerSet.has(ctrl)) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
moduleRef.container.register(ctrl, { useClass: ctrl }, { lifecycle: Lifecycle.ContainerScoped });
|
|
421
|
+
this.addLifecycleTarget(moduleRef.container, ctrl);
|
|
422
|
+
this.rpcControllerSet.add(ctrl);
|
|
423
|
+
this.rpcControllerModules.set(ctrl, moduleRef);
|
|
424
|
+
this.controllers.push(ctrl);
|
|
425
|
+
}
|
|
426
|
+
for (const ctrl of meta.httpControllers) {
|
|
427
|
+
if (this.httpControllerSet.has(ctrl)) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
moduleRef.container.register(ctrl, { useClass: ctrl }, { lifecycle: Lifecycle.ContainerScoped });
|
|
431
|
+
this.addLifecycleTarget(moduleRef.container, ctrl);
|
|
432
|
+
this.httpControllerSet.add(ctrl);
|
|
433
|
+
this.httpControllerModules.set(ctrl, moduleRef);
|
|
434
|
+
this.httpControllers.push(ctrl);
|
|
435
|
+
}
|
|
436
|
+
moduleRef.exportedTokens = this.resolveModuleExports(moduleRef);
|
|
437
|
+
if (moduleRef.meta.global) {
|
|
438
|
+
this.registerGlobalExports(moduleRef);
|
|
439
|
+
}
|
|
440
|
+
return moduleRef;
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
this.loadedModules.delete(normalized.key);
|
|
444
|
+
if (signatureInserted && dynamicKey) {
|
|
445
|
+
this.dynamicModuleSignatures.delete(dynamicKey);
|
|
446
|
+
}
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
this.loadingModules.delete(normalized.key);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
loadModule(moduleImport) {
|
|
454
|
+
const moduleRef = this.loadModuleRef(moduleImport);
|
|
455
|
+
if (!this.rootModuleRef) {
|
|
456
|
+
this.rootModuleRef = moduleRef;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
resolve(token) {
|
|
460
|
+
if (this.root.isRegistered(token, false)) {
|
|
461
|
+
return this.root.resolve(token);
|
|
462
|
+
}
|
|
463
|
+
if (this.rootModuleRef?.container.isRegistered(token, true)) {
|
|
464
|
+
return this.rootModuleRef.container.resolve(token);
|
|
465
|
+
}
|
|
466
|
+
throw new Error(`Token "${tokenToString(token)}" is not available in the root module scope. ` +
|
|
467
|
+
"Export it from an imported module or register it as global.");
|
|
468
|
+
}
|
|
469
|
+
resolveAll(token) {
|
|
470
|
+
if (this.root.isRegistered(token, false)) {
|
|
471
|
+
return this.root.resolveAll(token);
|
|
472
|
+
}
|
|
473
|
+
if (this.rootModuleRef?.container.isRegistered(token, true)) {
|
|
474
|
+
return this.rootModuleRef.container.resolveAll(token);
|
|
475
|
+
}
|
|
476
|
+
throw new Error(`Token "${tokenToString(token)}" is not available in the root module scope. ` +
|
|
477
|
+
"Export it from an imported module or register it as global.");
|
|
478
|
+
}
|
|
479
|
+
setLogger(logger) {
|
|
480
|
+
this.assertRuntimeConfigMutable("logger");
|
|
481
|
+
this.root.registerInstance(LOGGER_TOKEN, logger);
|
|
482
|
+
}
|
|
483
|
+
configureLogger(options) {
|
|
484
|
+
const logger = createLogger(options);
|
|
485
|
+
this.setLogger(logger);
|
|
486
|
+
return logger;
|
|
487
|
+
}
|
|
488
|
+
getLogger() {
|
|
489
|
+
return this.resolve(LOGGER_TOKEN);
|
|
490
|
+
}
|
|
491
|
+
setExposeInternalErrors(value) {
|
|
492
|
+
this.exposeInternalErrors = value;
|
|
493
|
+
}
|
|
494
|
+
shouldExposeInternalErrors() {
|
|
495
|
+
return this.exposeInternalErrors;
|
|
496
|
+
}
|
|
497
|
+
setConfig(config) {
|
|
498
|
+
this.assertRuntimeConfigMutable("config");
|
|
499
|
+
this.root.registerInstance(CONFIG_TOKEN, config);
|
|
500
|
+
}
|
|
501
|
+
configureConfig(schema, source = process.env) {
|
|
502
|
+
const parsed = schema.safeParse(source);
|
|
503
|
+
if (!parsed.success) {
|
|
504
|
+
const details = parsed.error.issues
|
|
505
|
+
.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`)
|
|
506
|
+
.join("; ");
|
|
507
|
+
throw new Error(`Invalid configuration: ${details}`);
|
|
508
|
+
}
|
|
509
|
+
const data = parsed.data;
|
|
510
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
511
|
+
throw new Error("Config schema must produce an object");
|
|
512
|
+
}
|
|
513
|
+
this.setConfig(data);
|
|
514
|
+
return data;
|
|
515
|
+
}
|
|
516
|
+
getConfig() {
|
|
517
|
+
return this.resolve(CONFIG_TOKEN);
|
|
518
|
+
}
|
|
519
|
+
async init() {
|
|
520
|
+
if (this.initialized) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (this.closed) {
|
|
524
|
+
throw new Error("Container is already closed");
|
|
525
|
+
}
|
|
526
|
+
const instances = this.getLifecycleInstances();
|
|
527
|
+
for (const instance of instances) {
|
|
528
|
+
const hook = instance.onModuleInit;
|
|
529
|
+
if (typeof hook === "function") {
|
|
530
|
+
await hook.call(instance);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
for (const instance of instances) {
|
|
534
|
+
const hook = instance.onApplicationBootstrap;
|
|
535
|
+
if (typeof hook === "function") {
|
|
536
|
+
await hook.call(instance);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
this.initialized = true;
|
|
540
|
+
}
|
|
541
|
+
async close(signal) {
|
|
542
|
+
if (this.closed) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const instances = this.getLifecycleInstances();
|
|
546
|
+
const shutdownInstances = [...instances].reverse();
|
|
547
|
+
const shutdownErrors = [];
|
|
548
|
+
for (const instance of shutdownInstances) {
|
|
549
|
+
const hook = instance.beforeApplicationShutdown;
|
|
550
|
+
if (typeof hook === "function") {
|
|
551
|
+
try {
|
|
552
|
+
await hook.call(instance, signal);
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
shutdownErrors.push(normalizeLifecycleError(error, "beforeApplicationShutdown"));
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
for (const instance of shutdownInstances) {
|
|
560
|
+
const hook = instance.onModuleDestroy;
|
|
561
|
+
if (typeof hook === "function") {
|
|
562
|
+
try {
|
|
563
|
+
await hook.call(instance);
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
shutdownErrors.push(normalizeLifecycleError(error, "onModuleDestroy"));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
for (const instance of shutdownInstances) {
|
|
571
|
+
const hook = instance.onApplicationShutdown;
|
|
572
|
+
if (typeof hook === "function") {
|
|
573
|
+
try {
|
|
574
|
+
await hook.call(instance, signal);
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
shutdownErrors.push(normalizeLifecycleError(error, "onApplicationShutdown"));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const disposed = new Set();
|
|
582
|
+
for (const moduleRef of this.loadedModules.values()) {
|
|
583
|
+
if (disposed.has(moduleRef.container)) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
disposed.add(moduleRef.container);
|
|
587
|
+
try {
|
|
588
|
+
await moduleRef.container.dispose();
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
shutdownErrors.push(normalizeLifecycleError(error, "moduleContainer.dispose"));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
await this.root.dispose();
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
shutdownErrors.push(normalizeLifecycleError(error, "rootContainer.dispose"));
|
|
599
|
+
}
|
|
600
|
+
this.closed = true;
|
|
601
|
+
if (shutdownErrors.length > 0) {
|
|
602
|
+
throw new AggregateError(shutdownErrors, `Application shutdown completed with ${shutdownErrors.length} error(s)`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
async runInRequestScope(scope, execute) {
|
|
606
|
+
const originModuleRef = this.requestScopeOwners.get(scope);
|
|
607
|
+
const context = {
|
|
608
|
+
originScope: scope,
|
|
609
|
+
originModuleRef,
|
|
610
|
+
scopesByModule: new Map(),
|
|
611
|
+
};
|
|
612
|
+
if (originModuleRef) {
|
|
613
|
+
context.scopesByModule.set(originModuleRef, scope);
|
|
614
|
+
}
|
|
615
|
+
return this.requestContextStorage.run(context, async () => execute());
|
|
616
|
+
}
|
|
617
|
+
async disposeRequestScope(scope) {
|
|
618
|
+
const linkedScopes = this.requestScopeLinkedScopes.get(scope);
|
|
619
|
+
const errors = [];
|
|
620
|
+
if (linkedScopes && linkedScopes.size > 0) {
|
|
621
|
+
for (const linkedScope of linkedScopes) {
|
|
622
|
+
try {
|
|
623
|
+
await linkedScope.dispose();
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
errors.push(normalizeLifecycleError(error, "requestScope.linked.dispose"));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
this.requestScopeLinkedScopes.delete(scope);
|
|
631
|
+
try {
|
|
632
|
+
await scope.dispose();
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
errors.push(normalizeLifecycleError(error, "requestScope.dispose"));
|
|
636
|
+
}
|
|
637
|
+
if (errors.length > 0) {
|
|
638
|
+
throw new AggregateError(errors, `Request scope dispose completed with ${errors.length} error(s)`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
createRequestScope(controller) {
|
|
642
|
+
if (!controller) {
|
|
643
|
+
const scope = this.root.createChildContainer();
|
|
644
|
+
this.requestScopeOwners.set(scope, undefined);
|
|
645
|
+
return scope;
|
|
646
|
+
}
|
|
647
|
+
const moduleRef = this.resolveControllerModule(controller);
|
|
648
|
+
if (!moduleRef) {
|
|
649
|
+
const scope = this.root.createChildContainer();
|
|
650
|
+
this.requestScopeOwners.set(scope, undefined);
|
|
651
|
+
return scope;
|
|
652
|
+
}
|
|
653
|
+
const scope = moduleRef.container.createChildContainer();
|
|
654
|
+
this.requestScopeOwners.set(scope, moduleRef);
|
|
655
|
+
return scope;
|
|
656
|
+
}
|
|
657
|
+
getControllers() {
|
|
658
|
+
return [...this.controllers];
|
|
659
|
+
}
|
|
660
|
+
getHttpControllers() {
|
|
661
|
+
return [...this.httpControllers];
|
|
662
|
+
}
|
|
663
|
+
setGlobalGuards(guards) {
|
|
664
|
+
this.globalGuards = [...guards];
|
|
665
|
+
}
|
|
666
|
+
getGlobalGuards() {
|
|
667
|
+
return [...this.globalGuards];
|
|
668
|
+
}
|
|
669
|
+
setGlobalInterceptors(inters) {
|
|
670
|
+
this.globalInterceptors = [...inters];
|
|
671
|
+
}
|
|
672
|
+
getGlobalInterceptors() {
|
|
673
|
+
return [...this.globalInterceptors];
|
|
674
|
+
}
|
|
675
|
+
setGlobalPipes(pipes) {
|
|
676
|
+
this.globalPipes = [...pipes];
|
|
677
|
+
}
|
|
678
|
+
getGlobalPipes() {
|
|
679
|
+
return [...this.globalPipes];
|
|
680
|
+
}
|
|
681
|
+
setGlobalFilters(filters) {
|
|
682
|
+
this.globalFilters = [...filters];
|
|
683
|
+
}
|
|
684
|
+
getGlobalFilters() {
|
|
685
|
+
return [...this.globalFilters];
|
|
686
|
+
}
|
|
687
|
+
}
|