@devcraft-ts/diadem 0.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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/dist/auto-discovery-5IV22D5D.cjs +73 -0
- package/dist/auto-discovery-5IV22D5D.cjs.map +1 -0
- package/dist/auto-discovery-RPCKK3PB.js +68 -0
- package/dist/auto-discovery-RPCKK3PB.js.map +1 -0
- package/dist/chunk-72YY5X6T.cjs +683 -0
- package/dist/chunk-72YY5X6T.cjs.map +1 -0
- package/dist/chunk-FHQRDO5C.cjs +187 -0
- package/dist/chunk-FHQRDO5C.cjs.map +1 -0
- package/dist/chunk-RTX6B4YY.js +681 -0
- package/dist/chunk-RTX6B4YY.js.map +1 -0
- package/dist/chunk-W7NA3ZZF.js +169 -0
- package/dist/chunk-W7NA3ZZF.js.map +1 -0
- package/dist/cli.cjs +821 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +815 -0
- package/dist/cli.js.map +1 -0
- package/dist/container-C1FFn9_4.d.cts +249 -0
- package/dist/container-C1FFn9_4.d.ts +249 -0
- package/dist/index.cjs +145 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +304 -0
- package/dist/index.d.ts +304 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/setup/index.cjs +87 -0
- package/dist/setup/index.cjs.map +1 -0
- package/dist/setup/index.d.cts +81 -0
- package/dist/setup/index.d.ts +81 -0
- package/dist/setup/index.js +75 -0
- package/dist/setup/index.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
import { getLogger, loadManifest, getDIMetadata } from './chunk-W7NA3ZZF.js';
|
|
2
|
+
|
|
3
|
+
// src/core/dependency-resolver.ts
|
|
4
|
+
var EnhancedDependencyResolver = class {
|
|
5
|
+
registeredServices = /* @__PURE__ */ new Set();
|
|
6
|
+
serviceClassMap = /* @__PURE__ */ new Map();
|
|
7
|
+
serviceMetadataMap = /* @__PURE__ */ new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Register services using build-time dependency analysis
|
|
10
|
+
* Services are registered in the correct order based on their dependencies
|
|
11
|
+
*/
|
|
12
|
+
async registerServicesWithManifest(container, services, manifestEntries, environment) {
|
|
13
|
+
const warnings = [];
|
|
14
|
+
const registrationOrder = [];
|
|
15
|
+
let registeredCount = 0;
|
|
16
|
+
let skippedCount = 0;
|
|
17
|
+
const manifestMap = /* @__PURE__ */ new Map();
|
|
18
|
+
this.serviceClassMap.clear();
|
|
19
|
+
this.serviceMetadataMap.clear();
|
|
20
|
+
let servicesWithMetadata = 0;
|
|
21
|
+
const reverseMapping = /* @__PURE__ */ new Map();
|
|
22
|
+
try {
|
|
23
|
+
const { SERVICE_CLASSES } = await loadManifest();
|
|
24
|
+
Object.entries(SERVICE_CLASSES).forEach(([originalName, serviceClass]) => {
|
|
25
|
+
reverseMapping.set(serviceClass, originalName);
|
|
26
|
+
});
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
for (const serviceClass of services) {
|
|
30
|
+
const metadata = getDIMetadata(serviceClass);
|
|
31
|
+
if (metadata) {
|
|
32
|
+
const originalName = reverseMapping.get(serviceClass) || serviceClass.name;
|
|
33
|
+
this.serviceClassMap.set(originalName, serviceClass);
|
|
34
|
+
this.serviceMetadataMap.set(originalName, metadata);
|
|
35
|
+
servicesWithMetadata++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
getLogger().info(
|
|
39
|
+
`\u{1F4CB} Built service map: ${servicesWithMetadata}/${services.length} services with DI metadata`
|
|
40
|
+
);
|
|
41
|
+
if (servicesWithMetadata === 0) {
|
|
42
|
+
getLogger().error("\u274C CRITICAL: No services have DI metadata!");
|
|
43
|
+
getLogger().error(
|
|
44
|
+
"\u{1F50D} Service analysis:",
|
|
45
|
+
services.slice(0, 3).map((s) => ({
|
|
46
|
+
name: s.name,
|
|
47
|
+
hasDI: !!getDIMetadata(s)
|
|
48
|
+
}))
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
for (const entry of manifestEntries) {
|
|
52
|
+
if (environment && entry.environment && entry.environment !== environment) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
manifestMap.set(entry.className, entry);
|
|
56
|
+
}
|
|
57
|
+
getLogger().info(
|
|
58
|
+
`\u{1F4CB} Manifest map: ${manifestMap.size}/${manifestEntries.length} entries for ${environment || "any"}`
|
|
59
|
+
);
|
|
60
|
+
const sortedEntries = Array.from(manifestMap.values()).sort(
|
|
61
|
+
(a, b) => a.registrationOrder - b.registrationOrder
|
|
62
|
+
);
|
|
63
|
+
if (sortedEntries.length === 0) {
|
|
64
|
+
getLogger().error("\u274C CRITICAL: No services to register after filtering!");
|
|
65
|
+
getLogger().error("\u{1F50D} Debug analysis:", {
|
|
66
|
+
serviceClassMapSize: this.serviceClassMap.size,
|
|
67
|
+
manifestMapSize: manifestMap.size,
|
|
68
|
+
serviceNames: Array.from(this.serviceClassMap.keys()).slice(0, 5),
|
|
69
|
+
manifestNames: manifestEntries.slice(0, 5).map((e) => e.className),
|
|
70
|
+
environment
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
registeredServices: [],
|
|
74
|
+
registeredCount: 0,
|
|
75
|
+
skippedCount: 0,
|
|
76
|
+
dependencyStats: {
|
|
77
|
+
totalServices: 0,
|
|
78
|
+
servicesWithDependencies: 0,
|
|
79
|
+
totalDependencies: 0,
|
|
80
|
+
externalDependencies: 0,
|
|
81
|
+
maxDependencyDepth: 0
|
|
82
|
+
},
|
|
83
|
+
warnings: ["No services to register"],
|
|
84
|
+
registrationOrder: []
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
getLogger().info(`\u{1F517} Registering ${sortedEntries.length} services`);
|
|
88
|
+
for (const manifestEntry of sortedEntries) {
|
|
89
|
+
const serviceClass = this.serviceClassMap.get(manifestEntry.className);
|
|
90
|
+
if (!serviceClass) {
|
|
91
|
+
getLogger().error(`\u274C Service class not found: ${manifestEntry.className}`);
|
|
92
|
+
getLogger().error(
|
|
93
|
+
`\u{1F50D} Available: ${Array.from(this.serviceClassMap.keys()).slice(0, 5).join(", ")}...`
|
|
94
|
+
);
|
|
95
|
+
warnings.push(`Service class ${manifestEntry.className} not found`);
|
|
96
|
+
skippedCount++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
await this.registerSingleService(
|
|
101
|
+
container,
|
|
102
|
+
serviceClass,
|
|
103
|
+
manifestEntry,
|
|
104
|
+
warnings
|
|
105
|
+
);
|
|
106
|
+
registeredCount++;
|
|
107
|
+
registrationOrder.push(manifestEntry.className);
|
|
108
|
+
this.registeredServices.add(manifestEntry.className);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const err = error;
|
|
111
|
+
skippedCount++;
|
|
112
|
+
getLogger().error(
|
|
113
|
+
`\u274C Registration failed: ${manifestEntry.className} - ${err.message}`
|
|
114
|
+
);
|
|
115
|
+
warnings.push(
|
|
116
|
+
`Failed to register ${manifestEntry.className}: ${err.message}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
getLogger().info(
|
|
121
|
+
`\u{1F4CA} Registration complete: ${registeredCount} registered, ${skippedCount} skipped`
|
|
122
|
+
);
|
|
123
|
+
const dependencyStats = this.calculateDependencyStats(manifestEntries);
|
|
124
|
+
const result = {
|
|
125
|
+
registeredServices: Array.from(this.registeredServices),
|
|
126
|
+
registeredCount,
|
|
127
|
+
skippedCount,
|
|
128
|
+
dependencyStats,
|
|
129
|
+
warnings,
|
|
130
|
+
registrationOrder
|
|
131
|
+
};
|
|
132
|
+
if (process.env.DEBUG_DI) {
|
|
133
|
+
getLogger().info(`\u{1F4CA} Registration Summary:`);
|
|
134
|
+
getLogger().info(` \u2705 Registered: ${registeredCount}`);
|
|
135
|
+
getLogger().info(` \u26A0\uFE0F Skipped: ${skippedCount}`);
|
|
136
|
+
getLogger().info(
|
|
137
|
+
` \u{1F517} Dependencies resolved: ${dependencyStats.totalDependencies}`
|
|
138
|
+
);
|
|
139
|
+
if (warnings.length > 0) {
|
|
140
|
+
getLogger().warn(` \u26A0\uFE0F Warnings: ${warnings.length}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Register a single service with proper dependency injection
|
|
147
|
+
*/
|
|
148
|
+
async registerSingleService(container, ServiceClass, manifestEntry, warnings) {
|
|
149
|
+
const metadata = getDIMetadata(ServiceClass);
|
|
150
|
+
if (!metadata) {
|
|
151
|
+
getLogger().error(`\u274C No DI metadata: ${ServiceClass.name}`);
|
|
152
|
+
throw new Error(`No DI metadata found for ${ServiceClass.name}`);
|
|
153
|
+
}
|
|
154
|
+
if (typeof container.registerSingleton !== "function" || typeof container.registerFactory !== "function") {
|
|
155
|
+
getLogger().error(
|
|
156
|
+
`\u274C Container missing registration methods for ${ServiceClass.name}`
|
|
157
|
+
);
|
|
158
|
+
throw new Error(`Container missing registration methods`);
|
|
159
|
+
}
|
|
160
|
+
const createInstance = () => {
|
|
161
|
+
const resolvedArgs = [];
|
|
162
|
+
for (const dependency of manifestEntry.resolvedDependencies) {
|
|
163
|
+
try {
|
|
164
|
+
if (dependency.external) {
|
|
165
|
+
if (dependency.isOptional) {
|
|
166
|
+
resolvedArgs[dependency.paramIndex] = void 0;
|
|
167
|
+
} else {
|
|
168
|
+
const defaultValue = this.getDefaultValueForExternalDependency(dependency);
|
|
169
|
+
if (defaultValue !== void 0) {
|
|
170
|
+
resolvedArgs[dependency.paramIndex] = defaultValue;
|
|
171
|
+
} else {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`External dependency ${dependency.typeName} is required but no default available`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else if (dependency.implementingService) {
|
|
178
|
+
const depMetadata = this.serviceMetadataMap.get(
|
|
179
|
+
dependency.implementingService
|
|
180
|
+
);
|
|
181
|
+
if (!depMetadata) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`No DI metadata found for dependency ${dependency.implementingService}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const resolvedDep = container.resolve(depMetadata.token);
|
|
187
|
+
resolvedArgs[dependency.paramIndex] = resolvedDep;
|
|
188
|
+
} else {
|
|
189
|
+
if (dependency.isOptional) {
|
|
190
|
+
resolvedArgs[dependency.paramIndex] = void 0;
|
|
191
|
+
} else {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Unknown dependency type for ${dependency.typeName}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (dependency.isOptional) {
|
|
199
|
+
resolvedArgs[dependency.paramIndex] = void 0;
|
|
200
|
+
warnings.push(
|
|
201
|
+
`Optional dependency ${dependency.typeName} could not be resolved for ${ServiceClass.name}: ${error}`
|
|
202
|
+
);
|
|
203
|
+
} else {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Cannot resolve required dependency ${dependency.typeName} for service ${ServiceClass.name}: ${error}`,
|
|
206
|
+
{ cause: error }
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return new ServiceClass(...resolvedArgs);
|
|
212
|
+
};
|
|
213
|
+
getLogger().info(
|
|
214
|
+
`\u{1F527} Registering ${ServiceClass.name} (${manifestEntry.lifecycle})`
|
|
215
|
+
);
|
|
216
|
+
try {
|
|
217
|
+
switch (manifestEntry.lifecycle) {
|
|
218
|
+
case "singleton":
|
|
219
|
+
container.registerSingleton(metadata.token, createInstance);
|
|
220
|
+
break;
|
|
221
|
+
case "factory":
|
|
222
|
+
container.registerFactory(metadata.token, createInstance);
|
|
223
|
+
break;
|
|
224
|
+
case "lazy":
|
|
225
|
+
container.registerFactory(metadata.token, createInstance);
|
|
226
|
+
break;
|
|
227
|
+
case "lazySingleton": {
|
|
228
|
+
let lazySingletonInstance = null;
|
|
229
|
+
container.registerFactory(metadata.token, () => {
|
|
230
|
+
if (lazySingletonInstance === null) {
|
|
231
|
+
lazySingletonInstance = createInstance();
|
|
232
|
+
}
|
|
233
|
+
return lazySingletonInstance;
|
|
234
|
+
});
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
default:
|
|
238
|
+
container.registerSingleton(metadata.token, createInstance);
|
|
239
|
+
}
|
|
240
|
+
getLogger().info(`\u2705 Registered ${ServiceClass.name}`);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const err = error;
|
|
243
|
+
getLogger().error(
|
|
244
|
+
`\u274C Container registration failed for ${ServiceClass.name}: ${err.message}`
|
|
245
|
+
);
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get default value for external dependencies
|
|
251
|
+
*/
|
|
252
|
+
getDefaultValueForExternalDependency(dependency) {
|
|
253
|
+
switch (dependency.typeName) {
|
|
254
|
+
case "string":
|
|
255
|
+
return "";
|
|
256
|
+
case "number":
|
|
257
|
+
return 0;
|
|
258
|
+
case "boolean":
|
|
259
|
+
return false;
|
|
260
|
+
default:
|
|
261
|
+
return void 0;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Calculate dependency statistics from manifest entries
|
|
266
|
+
*/
|
|
267
|
+
calculateDependencyStats(manifestEntries) {
|
|
268
|
+
let servicesWithDependencies = 0;
|
|
269
|
+
let totalDependencies = 0;
|
|
270
|
+
let externalDependencies = 0;
|
|
271
|
+
let maxDependencyDepth = 0;
|
|
272
|
+
for (const entry of manifestEntries) {
|
|
273
|
+
if (entry.resolvedDependencies.length > 0) {
|
|
274
|
+
servicesWithDependencies++;
|
|
275
|
+
totalDependencies += entry.resolvedDependencies.length;
|
|
276
|
+
maxDependencyDepth = Math.max(
|
|
277
|
+
maxDependencyDepth,
|
|
278
|
+
entry.resolvedDependencies.length
|
|
279
|
+
);
|
|
280
|
+
externalDependencies += entry.resolvedDependencies.filter(
|
|
281
|
+
(dep) => dep.external
|
|
282
|
+
).length;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
totalServices: manifestEntries.length,
|
|
287
|
+
servicesWithDependencies,
|
|
288
|
+
totalDependencies,
|
|
289
|
+
externalDependencies,
|
|
290
|
+
maxDependencyDepth
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Validate that all required dependencies can be resolved
|
|
295
|
+
*/
|
|
296
|
+
validateDependencies(manifestEntries) {
|
|
297
|
+
const missingDependencies = [];
|
|
298
|
+
const availableServices = new Set(
|
|
299
|
+
manifestEntries.map((entry) => entry.className)
|
|
300
|
+
);
|
|
301
|
+
for (const entry of manifestEntries) {
|
|
302
|
+
for (const dep of entry.resolvedDependencies) {
|
|
303
|
+
if (!dep.external && !dep.isOptional && dep.implementingService) {
|
|
304
|
+
if (!availableServices.has(dep.implementingService)) {
|
|
305
|
+
missingDependencies.push(
|
|
306
|
+
`${entry.className} requires ${dep.implementingService} (${dep.typeName})`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const circularDependencies = this.detectCircularDependencies(manifestEntries);
|
|
313
|
+
return {
|
|
314
|
+
valid: missingDependencies.length === 0 && circularDependencies.length === 0,
|
|
315
|
+
missingDependencies,
|
|
316
|
+
circularDependencies
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Detect circular dependencies in manifest entries
|
|
321
|
+
*/
|
|
322
|
+
detectCircularDependencies(manifestEntries) {
|
|
323
|
+
const cycles = [];
|
|
324
|
+
const entryMap = new Map(
|
|
325
|
+
manifestEntries.map((entry) => [entry.className, entry])
|
|
326
|
+
);
|
|
327
|
+
const visited = /* @__PURE__ */ new Set();
|
|
328
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
329
|
+
const findCycles = (className, path) => {
|
|
330
|
+
if (visiting.has(className)) {
|
|
331
|
+
const cycleStart = path.indexOf(className);
|
|
332
|
+
const cycle = path.slice(cycleStart).join(" -> ") + ` -> ${className}`;
|
|
333
|
+
cycles.push(cycle);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (visited.has(className)) return;
|
|
337
|
+
const entry = entryMap.get(className);
|
|
338
|
+
if (!entry) return;
|
|
339
|
+
visited.add(className);
|
|
340
|
+
visiting.add(className);
|
|
341
|
+
for (const dep of entry.resolvedDependencies) {
|
|
342
|
+
if (dep.implementingService && !dep.external) {
|
|
343
|
+
findCycles(dep.implementingService, [...path, className]);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
visiting.delete(className);
|
|
347
|
+
};
|
|
348
|
+
for (const entry of manifestEntries) {
|
|
349
|
+
if (!visited.has(entry.className)) {
|
|
350
|
+
findCycles(entry.className, []);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return cycles;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
async function registerServicesWithManifestDependencies(container, services, manifestEntries, environment) {
|
|
357
|
+
const resolver = new EnhancedDependencyResolver();
|
|
358
|
+
const validation = resolver.validateDependencies(manifestEntries);
|
|
359
|
+
if (!validation.valid) {
|
|
360
|
+
if (process.env.NODE_ENV === "development" || process.env.DEBUG_DI) {
|
|
361
|
+
getLogger().warn("\u26A0\uFE0F Dependency validation warnings:");
|
|
362
|
+
validation.missingDependencies.forEach((dep) => {
|
|
363
|
+
getLogger().warn(` - ${dep}`);
|
|
364
|
+
});
|
|
365
|
+
validation.circularDependencies.forEach((cycle) => {
|
|
366
|
+
getLogger().warn(` - Circular: ${cycle}`);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return resolver.registerServicesWithManifest(
|
|
371
|
+
container,
|
|
372
|
+
services,
|
|
373
|
+
manifestEntries,
|
|
374
|
+
environment
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/core/container.ts
|
|
379
|
+
function isDisposable(value) {
|
|
380
|
+
return typeof value === "object" && value !== null && typeof value.dispose === "function";
|
|
381
|
+
}
|
|
382
|
+
var DiademContainer = class _DiademContainer {
|
|
383
|
+
dependencies = /* @__PURE__ */ new Map();
|
|
384
|
+
singletons = /* @__PURE__ */ new Map();
|
|
385
|
+
factories = /* @__PURE__ */ new Map();
|
|
386
|
+
asyncFactories = /* @__PURE__ */ new Map();
|
|
387
|
+
disposers = [];
|
|
388
|
+
ready = false;
|
|
389
|
+
disposed = false;
|
|
390
|
+
/**
|
|
391
|
+
* Register a dependency by value or class instance.
|
|
392
|
+
* @param token Dependency token
|
|
393
|
+
* @param implementation The value or instance to register
|
|
394
|
+
*/
|
|
395
|
+
register(token, implementation) {
|
|
396
|
+
this.dependencies.set(token, implementation);
|
|
397
|
+
this.trackDisposable(implementation);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Register a factory that returns a new instance on every resolve.
|
|
401
|
+
* Use for transient dependencies.
|
|
402
|
+
* @param token Dependency token
|
|
403
|
+
* @param factory Factory function
|
|
404
|
+
*/
|
|
405
|
+
registerFactory(token, factory) {
|
|
406
|
+
this.factories.set(token, factory);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Register a singleton (one instance per container).
|
|
410
|
+
*
|
|
411
|
+
* **Order matters:** the factory runs immediately, at registration time, so
|
|
412
|
+
* anything it resolves must already be registered. Register dependencies
|
|
413
|
+
* before their dependents — the build-time manifest is emitted in exactly this
|
|
414
|
+
* (topological) order. To defer construction until first resolve instead, use
|
|
415
|
+
* a `lazySingleton` lifecycle via the decorator/manifest.
|
|
416
|
+
*
|
|
417
|
+
* @param token Dependency token
|
|
418
|
+
* @param factory Factory function, called once, immediately
|
|
419
|
+
*/
|
|
420
|
+
registerSingleton(token, factory) {
|
|
421
|
+
if (!this.singletons.has(token)) {
|
|
422
|
+
const instance = factory();
|
|
423
|
+
this.singletons.set(token, instance);
|
|
424
|
+
this.trackDisposable(instance);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Register a factory that awaits its result on every {@link resolveAsync}.
|
|
429
|
+
* Use for transient dependencies that require asynchronous construction.
|
|
430
|
+
* @param token Dependency token
|
|
431
|
+
* @param factory Async factory function
|
|
432
|
+
*/
|
|
433
|
+
registerAsyncFactory(token, factory) {
|
|
434
|
+
this.asyncFactories.set(token, factory);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Register an async singleton: the factory runs at most once (on the first
|
|
438
|
+
* {@link resolveAsync}) and the awaited instance is cached. If the instance
|
|
439
|
+
* is {@link Disposable} it is torn down on {@link dispose}.
|
|
440
|
+
* @param token Dependency token
|
|
441
|
+
* @param factory Async factory function, called at most once
|
|
442
|
+
*/
|
|
443
|
+
registerAsyncSingleton(token, factory) {
|
|
444
|
+
let cached;
|
|
445
|
+
this.asyncFactories.set(token, () => {
|
|
446
|
+
cached ??= factory().then((instance) => {
|
|
447
|
+
this.trackDisposable(instance);
|
|
448
|
+
return instance;
|
|
449
|
+
});
|
|
450
|
+
return cached;
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/** Record a value's disposer if it implements {@link Disposable}. */
|
|
454
|
+
trackDisposable(value) {
|
|
455
|
+
if (isDisposable(value)) {
|
|
456
|
+
this.disposers.push(() => value.dispose());
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Resolve a dependency by token.
|
|
461
|
+
* @param token Dependency token
|
|
462
|
+
* @returns The resolved dependency
|
|
463
|
+
* @throws If the dependency is not found
|
|
464
|
+
*/
|
|
465
|
+
resolve(token) {
|
|
466
|
+
if (!token) {
|
|
467
|
+
throw new Error("Dependency token is undefined or null");
|
|
468
|
+
}
|
|
469
|
+
if (this.dependencies.has(token)) {
|
|
470
|
+
return this.dependencies.get(token);
|
|
471
|
+
}
|
|
472
|
+
if (this.singletons.has(token)) {
|
|
473
|
+
return this.singletons.get(token);
|
|
474
|
+
}
|
|
475
|
+
if (this.factories.has(token)) {
|
|
476
|
+
const factory = this.factories.get(token);
|
|
477
|
+
return factory?.();
|
|
478
|
+
}
|
|
479
|
+
if (this.asyncFactories.has(token)) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`Dependency ${token?.name || "Unknown"} is registered as an async factory; resolve it with resolveAsync() instead of resolve().`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
const tokenName = token?.name || "Unknown";
|
|
485
|
+
const registeredTokens = this.getRegisteredTokens();
|
|
486
|
+
const registeredNames = registeredTokens.map((t) => t?.name || "Unknown").join(", ");
|
|
487
|
+
throw new Error(
|
|
488
|
+
`Dependency not found: ${tokenName}. Available services: [${registeredNames}]. Total registered: ${registeredTokens.length}`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Resolve a dependency that may have been registered asynchronously.
|
|
493
|
+
* Synchronous registrations resolve immediately; async factories/singletons
|
|
494
|
+
* are awaited.
|
|
495
|
+
* @param token Dependency token
|
|
496
|
+
* @returns A promise of the resolved dependency
|
|
497
|
+
* @throws If the dependency is not found
|
|
498
|
+
*/
|
|
499
|
+
async resolveAsync(token) {
|
|
500
|
+
if (!token) {
|
|
501
|
+
throw new Error("Dependency token is undefined or null");
|
|
502
|
+
}
|
|
503
|
+
if (this.dependencies.has(token) || this.singletons.has(token) || this.factories.has(token)) {
|
|
504
|
+
return this.resolve(token);
|
|
505
|
+
}
|
|
506
|
+
const asyncFactory = this.asyncFactories.get(token);
|
|
507
|
+
if (asyncFactory) {
|
|
508
|
+
return asyncFactory();
|
|
509
|
+
}
|
|
510
|
+
return this.resolve(token);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Returns true if the container is fully set up.
|
|
514
|
+
*/
|
|
515
|
+
isReady() {
|
|
516
|
+
return this.ready;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Mark the container as ready (all dependencies registered).
|
|
520
|
+
*/
|
|
521
|
+
setReady() {
|
|
522
|
+
this.ready = true;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Clear all registrations (useful for testing or container reset).
|
|
526
|
+
*/
|
|
527
|
+
clear() {
|
|
528
|
+
this.dependencies.clear();
|
|
529
|
+
this.singletons.clear();
|
|
530
|
+
this.factories.clear();
|
|
531
|
+
this.asyncFactories.clear();
|
|
532
|
+
this.disposers.length = 0;
|
|
533
|
+
this.ready = false;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Register an arbitrary teardown callback, run on {@link dispose} in reverse
|
|
537
|
+
* order of registration.
|
|
538
|
+
*/
|
|
539
|
+
onDispose(callback) {
|
|
540
|
+
this.disposers.push(callback);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Dispose the container: run all teardown callbacks (and the `dispose()` of
|
|
544
|
+
* any registered {@link Disposable} singletons/values) in reverse order, then
|
|
545
|
+
* clear all registrations. Idempotent.
|
|
546
|
+
*
|
|
547
|
+
* Note: transient (`factory`) and lazy instances are owned by the caller, not
|
|
548
|
+
* the container, so they are not disposed here.
|
|
549
|
+
*/
|
|
550
|
+
async dispose() {
|
|
551
|
+
if (this.disposed) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
this.disposed = true;
|
|
555
|
+
for (const disposer of [...this.disposers].reverse()) {
|
|
556
|
+
try {
|
|
557
|
+
await disposer();
|
|
558
|
+
} catch (error) {
|
|
559
|
+
getLogger().error("Diadem: disposer threw during dispose()", error);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
this.clear();
|
|
563
|
+
}
|
|
564
|
+
/** Whether {@link dispose} has been called. */
|
|
565
|
+
isDisposed() {
|
|
566
|
+
return this.disposed;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Get diagnostic information about registered dependencies.
|
|
570
|
+
*/
|
|
571
|
+
getDiagnostics() {
|
|
572
|
+
return {
|
|
573
|
+
dependencies: this.dependencies.size,
|
|
574
|
+
singletons: this.singletons.size,
|
|
575
|
+
factories: this.factories.size,
|
|
576
|
+
asyncFactories: this.asyncFactories.size,
|
|
577
|
+
isReady: this.ready
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Check if a token is registered in the container
|
|
582
|
+
*/
|
|
583
|
+
has(token) {
|
|
584
|
+
if (!token) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
return this.dependencies.has(token) || this.singletons.has(token) || this.factories.has(token) || this.asyncFactories.has(token);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Get all registered tokens
|
|
591
|
+
*/
|
|
592
|
+
getRegisteredTokens() {
|
|
593
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
594
|
+
this.dependencies.forEach((_, token) => tokens.add(token));
|
|
595
|
+
this.singletons.forEach((_, token) => tokens.add(token));
|
|
596
|
+
this.factories.forEach((_, token) => tokens.add(token));
|
|
597
|
+
this.asyncFactories.forEach((_, token) => tokens.add(token));
|
|
598
|
+
return Array.from(tokens);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Create a child container that inherits this container's registrations and
|
|
602
|
+
* can override them locally. Useful for per-request or per-test scopes.
|
|
603
|
+
*
|
|
604
|
+
* The child shares the parent's already-created singleton instances by
|
|
605
|
+
* reference; it deliberately does NOT inherit the parent's disposers, so
|
|
606
|
+
* disposing the child never tears down shared parent-owned resources. Only
|
|
607
|
+
* resources the child registers itself are disposed with it.
|
|
608
|
+
*/
|
|
609
|
+
createChild() {
|
|
610
|
+
const child = new _DiademContainer();
|
|
611
|
+
this.dependencies.forEach((impl, token) => {
|
|
612
|
+
child.dependencies.set(token, impl);
|
|
613
|
+
});
|
|
614
|
+
this.singletons.forEach((impl, token) => {
|
|
615
|
+
child.singletons.set(token, impl);
|
|
616
|
+
});
|
|
617
|
+
this.factories.forEach((factory, token) => {
|
|
618
|
+
child.factories.set(token, factory);
|
|
619
|
+
});
|
|
620
|
+
this.asyncFactories.forEach((factory, token) => {
|
|
621
|
+
child.asyncFactories.set(token, factory);
|
|
622
|
+
});
|
|
623
|
+
return child;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Auto-discover and get information about available services.
|
|
627
|
+
* Does not register them - use autoRegisterDiscovered() for that.
|
|
628
|
+
*/
|
|
629
|
+
async autoDiscover(config) {
|
|
630
|
+
const { discoverServices } = await import('./auto-discovery-RPCKK3PB.js');
|
|
631
|
+
return discoverServices(config);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Auto-discover and register all DI-decorated services using build-time manifest.
|
|
635
|
+
* This is true autowiring that doesn't require hardcoded paths or explicit imports.
|
|
636
|
+
*
|
|
637
|
+
* @param environment Optional environment filter
|
|
638
|
+
* @example
|
|
639
|
+
* ```typescript
|
|
640
|
+
* const container = new DiademContainer()
|
|
641
|
+
* await container.autoRegisterDiscovered('production')
|
|
642
|
+
* container.setReady()
|
|
643
|
+
* ```
|
|
644
|
+
*/
|
|
645
|
+
async autoRegisterDiscovered(environment) {
|
|
646
|
+
try {
|
|
647
|
+
const { discoverServices } = await import('./auto-discovery-RPCKK3PB.js');
|
|
648
|
+
const discoveryResult = await discoverServices({ environment });
|
|
649
|
+
const services = discoveryResult.services;
|
|
650
|
+
if (!discoveryResult.manifestAvailable) {
|
|
651
|
+
getLogger().warn(
|
|
652
|
+
"Diadem: no manifest configured \u2014 nothing was auto-registered."
|
|
653
|
+
);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const { getServicesForEnvironment } = await loadManifest();
|
|
657
|
+
const manifestEntries = getServicesForEnvironment(environment);
|
|
658
|
+
const result = await registerServicesWithManifestDependencies(
|
|
659
|
+
this,
|
|
660
|
+
services,
|
|
661
|
+
manifestEntries,
|
|
662
|
+
environment
|
|
663
|
+
);
|
|
664
|
+
const log = getLogger();
|
|
665
|
+
log.debug(
|
|
666
|
+
`Diadem: registered ${result.registeredCount} services for "${environment ?? "all"}"`,
|
|
667
|
+
result.dependencyStats
|
|
668
|
+
);
|
|
669
|
+
if (result.warnings.length > 0) {
|
|
670
|
+
log.warn("Diadem: registration warnings", result.warnings);
|
|
671
|
+
}
|
|
672
|
+
} catch (error) {
|
|
673
|
+
getLogger().error("Diadem: auto-registration failed", error);
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
export { DiademContainer };
|
|
680
|
+
//# sourceMappingURL=chunk-RTX6B4YY.js.map
|
|
681
|
+
//# sourceMappingURL=chunk-RTX6B4YY.js.map
|