@illuma/core 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.
@@ -0,0 +1,995 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/testkit.ts
22
+ var testkit_exports = {};
23
+ __export(testkit_exports, {
24
+ createTestFactory: () => createTestFactory
25
+ });
26
+ module.exports = __toCommonJS(testkit_exports);
27
+
28
+ // src/lib/errors.ts
29
+ var InjectionError = class _InjectionError extends Error {
30
+ static {
31
+ __name(this, "InjectionError");
32
+ }
33
+ code;
34
+ constructor(code, message) {
35
+ super(`[i${code}] ${message}`), this.code = code;
36
+ this.name = "InjectionError";
37
+ }
38
+ // Provider errors
39
+ static duplicate(token) {
40
+ return new _InjectionError(100, `Duplicate provider for token "${token.toString()}" detected.`);
41
+ }
42
+ static duplicateFactory(token) {
43
+ return new _InjectionError(101, `Tried to re-provide factory for token "${token.toString()}" detected.`);
44
+ }
45
+ static invalidCtor(ctor) {
46
+ return new _InjectionError(102, `Cannot use constructor for token "${ctor.name}". Please make sure to use @nodeInjectable() decorator`);
47
+ }
48
+ static invalidProvider(provider) {
49
+ return new _InjectionError(103, `Cannot use provider as it is neither a NodeToken nor MultiNodeToken nor a valid constructor.:
50
+ ${provider}`);
51
+ }
52
+ // Alias errors
53
+ static invalidAlias(alias) {
54
+ const aliasStr = typeof alias === "function" ? alias.name || "Unknown" : String(alias);
55
+ return new _InjectionError(200, `Invalid alias target "${aliasStr}". Alias must be a NodeToken, MultiNodeToken, or a class decorated with @NodeInjectable().`);
56
+ }
57
+ static loopAlias(alias) {
58
+ return new _InjectionError(201, `Token "${alias.toString()}" cannot alias itself in a loop.`);
59
+ }
60
+ // Bootstrap errors
61
+ static notBootstrapped() {
62
+ return new _InjectionError(300, "Cannot retrieve providers before the container has been bootstrapped.");
63
+ }
64
+ static bootstrapped() {
65
+ return new _InjectionError(301, "Cannot modify providers after the container has been bootstrapped.");
66
+ }
67
+ static doubleBootstrap() {
68
+ return new _InjectionError(302, "Container has already been bootstrapped and cannot be bootstrapped again.");
69
+ }
70
+ // Retrieval errors
71
+ static notFound(token) {
72
+ return new _InjectionError(400, `No provider found for "${token.toString()}".`);
73
+ }
74
+ static circularDependency(provider, path) {
75
+ const providerStr = provider instanceof NodeBase ? provider.toString() : provider.name;
76
+ const pathStr = path.map((p) => p instanceof NodeBase ? p.toString() : p.name).join(" -> ");
77
+ return new _InjectionError(401, `Circular dependency detected while resolving "${providerStr}":
78
+ ${pathStr}`);
79
+ }
80
+ // Instantiation errors
81
+ static untracked(token, parent) {
82
+ const tokenStr = token instanceof NodeBase ? token.toString() : token.name;
83
+ const parentStr = parent instanceof NodeBase ? parent.toString() : parent.name;
84
+ return new _InjectionError(500, `Cannot instantiate ${parentStr} because it depends on untracked injection ${tokenStr}. Please make sure all injections are properly tracked.`);
85
+ }
86
+ static outsideContext(token) {
87
+ const tokenStr = token instanceof NodeBase ? token.toString() : token.name;
88
+ return new _InjectionError(501, `Cannot inject "${tokenStr}" outside of an injection context.`);
89
+ }
90
+ static calledUtilsOutsideContext() {
91
+ return new _InjectionError(502, "Cannot call injection utilities outside of an injection context.");
92
+ }
93
+ static instanceAccessFailed(token) {
94
+ return new _InjectionError(503, `Failed to access instance for token "${token.toString()}". It was not properly instantiated.`);
95
+ }
96
+ static accessFailed() {
97
+ return new _InjectionError(504, "Failed to access the requested instance due to an unknown error.");
98
+ }
99
+ };
100
+
101
+ // src/lib/api/token.ts
102
+ var NodeBase = class {
103
+ static {
104
+ __name(this, "NodeBase");
105
+ }
106
+ name;
107
+ opts;
108
+ constructor(name, opts) {
109
+ this.name = name;
110
+ this.opts = opts;
111
+ }
112
+ /** Provides this token with a value */
113
+ withValue(value) {
114
+ return {
115
+ provide: this,
116
+ value
117
+ };
118
+ }
119
+ /** Provides this token using a factory function */
120
+ withFactory(factory) {
121
+ return {
122
+ provide: this,
123
+ factory
124
+ };
125
+ }
126
+ /** Provides this token using a class constructor */
127
+ withClass(ctor) {
128
+ return {
129
+ provide: this,
130
+ useClass: ctor
131
+ };
132
+ }
133
+ /** Creates an alias to another token */
134
+ withAlias(alias) {
135
+ return {
136
+ provide: this,
137
+ alias
138
+ };
139
+ }
140
+ toString() {
141
+ return `Token[${this.name}]`;
142
+ }
143
+ };
144
+ var NodeToken = class extends NodeBase {
145
+ static {
146
+ __name(this, "NodeToken");
147
+ }
148
+ multi = false;
149
+ toString() {
150
+ return `NodeToken[${this.name}]`;
151
+ }
152
+ };
153
+ var MultiNodeToken = class extends NodeBase {
154
+ static {
155
+ __name(this, "MultiNodeToken");
156
+ }
157
+ multi = true;
158
+ toString() {
159
+ return `MultiNodeToken[${this.name}]`;
160
+ }
161
+ };
162
+ function isNodeBase(specimen) {
163
+ return specimen instanceof NodeToken || specimen instanceof MultiNodeToken;
164
+ }
165
+ __name(isNodeBase, "isNodeBase");
166
+ function extractToken(provider, isAlias = false) {
167
+ let token = null;
168
+ if (isInjectable(provider)) {
169
+ token = getInjectableToken(provider);
170
+ } else if (isNodeBase(provider)) token = provider;
171
+ if (!token || !isNodeBase(token)) {
172
+ if (isAlias) throw InjectionError.invalidAlias(provider);
173
+ throw InjectionError.invalidProvider(JSON.stringify(provider));
174
+ }
175
+ return token;
176
+ }
177
+ __name(extractToken, "extractToken");
178
+
179
+ // src/lib/api/decorator.ts
180
+ var INJECTION_SYMBOL = Symbol("Injectable");
181
+ function isInjectable(ctor) {
182
+ return typeof ctor === "function" && isConstructor(ctor) && INJECTION_SYMBOL in ctor && ctor[INJECTION_SYMBOL] instanceof NodeToken;
183
+ }
184
+ __name(isInjectable, "isInjectable");
185
+ function getInjectableToken(ctor) {
186
+ return ctor[INJECTION_SYMBOL];
187
+ }
188
+ __name(getInjectableToken, "getInjectableToken");
189
+ function isConstructor(fn) {
190
+ return typeof fn === "function" && fn.prototype && fn.prototype.constructor === fn;
191
+ }
192
+ __name(isConstructor, "isConstructor");
193
+
194
+ // src/lib/plugins/core/plugin-container.ts
195
+ var Illuma = class _Illuma {
196
+ static {
197
+ __name(this, "Illuma");
198
+ }
199
+ static _diagnostics = [];
200
+ static _scanners = [];
201
+ /** @internal */
202
+ static get contextScanners() {
203
+ return _Illuma._scanners;
204
+ }
205
+ /**
206
+ * Extends the diagnostics with a new diagnostics module.
207
+ * These will be run on diagnostics reports after container bootstrap.
208
+ *
209
+ * @param m - The diagnostics module instance to add
210
+ */
211
+ static extendDiagnostics(m) {
212
+ _Illuma._diagnostics.push(m);
213
+ }
214
+ /**
215
+ * Extends the context scanners with a new context scanner.
216
+ * These will be run in injection context scans to detect additional injections (alongside `nodeInject` calls).
217
+ *
218
+ * @param scanner - The context scanner instance to add
219
+ */
220
+ static extendContextScanner(scanner) {
221
+ _Illuma._scanners.push(scanner);
222
+ }
223
+ static onReport(report) {
224
+ for (const diag of _Illuma._diagnostics) diag.onReport(report);
225
+ }
226
+ };
227
+
228
+ // src/lib/context/context.ts
229
+ var InjectionContext = class _InjectionContext {
230
+ static {
231
+ __name(this, "InjectionContext");
232
+ }
233
+ static contextOpen = false;
234
+ static _calls = /* @__PURE__ */ new Set();
235
+ static injector = null;
236
+ static _scanners = Illuma.contextScanners;
237
+ /**
238
+ * Adds a dependency to the current injection context.
239
+ * Called by `nodeInject` when a dependency is requested.
240
+ *
241
+ * @param node - The injection node representing the dependency
242
+ * @throws {InjectionError} If called outside of an active injection context
243
+ */
244
+ static addDep(node) {
245
+ if (!_InjectionContext.contextOpen) {
246
+ throw InjectionError.calledUtilsOutsideContext();
247
+ }
248
+ _InjectionContext._calls.add(node);
249
+ }
250
+ /**
251
+ * Opens a new injection context.
252
+ * Resets the calls set and sets the injector if provided.
253
+ *
254
+ * @param injector - Optional injector function to use for resolving dependencies
255
+ */
256
+ /**
257
+ * Scans a factory function for dependencies.
258
+ * Executes the factory in a dry-run mode to capture `nodeInject` calls.
259
+ * Also runs registered context scanners.
260
+ *
261
+ * @param factory - The factory function to scan
262
+ * @returns A set of detected injection nodes
263
+ */
264
+ static open(injector) {
265
+ _InjectionContext._calls.clear();
266
+ _InjectionContext.contextOpen = true;
267
+ _InjectionContext.injector = injector || null;
268
+ }
269
+ static scan(factory) {
270
+ if (typeof factory !== "function") return /* @__PURE__ */ new Set();
271
+ _InjectionContext.open();
272
+ try {
273
+ factory();
274
+ } catch {
275
+ }
276
+ const scanners = _InjectionContext._scanners;
277
+ for (const scanner of scanners) {
278
+ const scanned = scanner.scan(factory);
279
+ for (const node of scanned) _InjectionContext._calls.add(node);
280
+ }
281
+ const injections = _InjectionContext.closeAndReport();
282
+ return injections;
283
+ }
284
+ static instantiate(factory, injector) {
285
+ _InjectionContext.open(injector);
286
+ try {
287
+ return factory();
288
+ } finally {
289
+ _InjectionContext.closeAndReport();
290
+ }
291
+ }
292
+ static closeAndReport() {
293
+ const calls = new Set(_InjectionContext._calls);
294
+ _InjectionContext.contextOpen = false;
295
+ _InjectionContext._calls.clear();
296
+ _InjectionContext.injector = null;
297
+ return calls;
298
+ }
299
+ };
300
+
301
+ // src/lib/api/injection.ts
302
+ function nodeInject(provider, options) {
303
+ let token = provider;
304
+ if (isInjectable(provider)) token = getInjectableToken(provider);
305
+ if (!InjectionContext.contextOpen) {
306
+ throw InjectionError.outsideContext(token);
307
+ }
308
+ if (!isNodeBase(token)) throw InjectionError.invalidProvider(String(token));
309
+ const injection = {
310
+ token,
311
+ optional: options?.optional ?? false
312
+ };
313
+ InjectionContext.addDep(injection);
314
+ if (InjectionContext.injector) {
315
+ return InjectionContext.injector(token, options?.optional);
316
+ }
317
+ return injection;
318
+ }
319
+ __name(nodeInject, "nodeInject");
320
+
321
+ // src/lib/plugins/diagnostics/default-impl.ts
322
+ var DiagnosticsDefaultReporter = class {
323
+ static {
324
+ __name(this, "DiagnosticsDefaultReporter");
325
+ }
326
+ onReport(report) {
327
+ console.log("[Illuma] \u{1F9F9} Diagnostics:");
328
+ console.log(` Total: ${report.totalNodes} node(s)`);
329
+ console.log(` ${report.unusedNodes.length} were not used while bootstrap:`);
330
+ for (const node of report.unusedNodes) console.log(` - ${node.toString()}`);
331
+ }
332
+ };
333
+
334
+ // src/lib/provider/extractor.ts
335
+ function extractProvider(provider) {
336
+ if ("value" in provider) return () => provider.value;
337
+ if ("factory" in provider) return provider.factory;
338
+ if ("useClass" in provider) return () => new provider.useClass();
339
+ if ("alias" in provider) return extractToken(provider.alias, true);
340
+ throw InjectionError.invalidProvider(JSON.stringify(provider));
341
+ }
342
+ __name(extractProvider, "extractProvider");
343
+
344
+ // src/lib/provider/proto.ts
345
+ var ProtoNodeSingle = class {
346
+ static {
347
+ __name(this, "ProtoNodeSingle");
348
+ }
349
+ // Metadata
350
+ token;
351
+ injections;
352
+ // Instantiation
353
+ factory = null;
354
+ constructor(token, factory) {
355
+ this.token = token;
356
+ this.factory = factory ?? null;
357
+ this.injections = factory ? InjectionContext.scan(factory) : /* @__PURE__ */ new Set();
358
+ }
359
+ hasFactory() {
360
+ return typeof this.factory === "function";
361
+ }
362
+ setFactory(factory) {
363
+ if (this.factory) throw InjectionError.duplicateFactory(this.token);
364
+ this.factory = factory;
365
+ const scanned = InjectionContext.scan(factory);
366
+ for (const injection of scanned) {
367
+ this.injections.add(injection);
368
+ }
369
+ }
370
+ toString() {
371
+ return `ProtoNodeSingle<${this.token.toString()}>`;
372
+ }
373
+ };
374
+ var ProtoNodeTransparent = class {
375
+ static {
376
+ __name(this, "ProtoNodeTransparent");
377
+ }
378
+ parent;
379
+ factory;
380
+ injections;
381
+ constructor(parent, factory) {
382
+ this.parent = parent;
383
+ this.factory = factory;
384
+ this.injections = InjectionContext.scan(factory);
385
+ }
386
+ toString() {
387
+ return `ProtoNodeTransparent<${this.factory.name || "anonymous"}>`;
388
+ }
389
+ };
390
+ var ProtoNodeMulti = class {
391
+ static {
392
+ __name(this, "ProtoNodeMulti");
393
+ }
394
+ token;
395
+ singleNodes = /* @__PURE__ */ new Set();
396
+ multiNodes = /* @__PURE__ */ new Set();
397
+ transparentNodes = /* @__PURE__ */ new Set();
398
+ constructor(token) {
399
+ this.token = token;
400
+ }
401
+ addProvider(retriever) {
402
+ if (retriever instanceof NodeToken) {
403
+ this.singleNodes.add(retriever);
404
+ } else if (retriever instanceof MultiNodeToken) {
405
+ this.multiNodes.add(retriever);
406
+ } else if (typeof retriever === "function") {
407
+ const transparentProto = new ProtoNodeTransparent(this, retriever);
408
+ this.transparentNodes.add(transparentProto);
409
+ }
410
+ }
411
+ toString() {
412
+ return `ProtoNodeMulti<${this.token.toString()}>`;
413
+ }
414
+ };
415
+ function isNotTransparentProto(proto) {
416
+ return !(proto instanceof ProtoNodeTransparent);
417
+ }
418
+ __name(isNotTransparentProto, "isNotTransparentProto");
419
+
420
+ // src/lib/provider/tree-node.ts
421
+ function retrieverFactory(node, deps, transparentDeps) {
422
+ return (token, optional) => {
423
+ const depNode = deps.get(token);
424
+ if (!depNode && !optional) {
425
+ const transparent = Array.from(transparentDeps).find((n) => "proto" in n && n.proto.parent.token === token);
426
+ if (transparent) return transparent.instance;
427
+ throw InjectionError.untracked(token, node);
428
+ }
429
+ return depNode?.instance ?? null;
430
+ };
431
+ }
432
+ __name(retrieverFactory, "retrieverFactory");
433
+ var TreeRootNode = class {
434
+ static {
435
+ __name(this, "TreeRootNode");
436
+ }
437
+ _deps = /* @__PURE__ */ new Set();
438
+ _treePool = /* @__PURE__ */ new Map();
439
+ get dependencies() {
440
+ return this._deps;
441
+ }
442
+ addDependency(node) {
443
+ this._deps.add(node);
444
+ }
445
+ instantiate() {
446
+ for (const dep of this._deps) {
447
+ dep.instantiate(this._treePool);
448
+ if ("token" in dep.proto) this._treePool.set(dep.proto.token, dep);
449
+ }
450
+ }
451
+ find(token) {
452
+ const node = this._treePool.get(token);
453
+ if (!node) return null;
454
+ return node;
455
+ }
456
+ toString() {
457
+ return "TreeRootNode";
458
+ }
459
+ };
460
+ var TreeNodeSingle = class {
461
+ static {
462
+ __name(this, "TreeNodeSingle");
463
+ }
464
+ proto;
465
+ _transparent = /* @__PURE__ */ new Set();
466
+ _deps = /* @__PURE__ */ new Map();
467
+ _instance = null;
468
+ _resolved = false;
469
+ allocations = 0;
470
+ get instance() {
471
+ if (!this._resolved) {
472
+ throw InjectionError.instanceAccessFailed(this.proto.token);
473
+ }
474
+ return this._instance;
475
+ }
476
+ constructor(proto) {
477
+ this.proto = proto;
478
+ }
479
+ addDependency(node) {
480
+ if (node instanceof TreeNodeTransparent) this._transparent.add(node);
481
+ else this._deps.set(node.proto.token, node);
482
+ node.allocations++;
483
+ }
484
+ instantiate(pool) {
485
+ if (this._resolved) return;
486
+ for (const node of this._deps.values()) node.instantiate(pool);
487
+ for (const dep of this._transparent) dep.instantiate(pool);
488
+ const retriever = retrieverFactory(this.proto.token, this._deps, this._transparent);
489
+ const factory = this.proto.factory ?? this.proto.token.opts?.factory;
490
+ if (!factory) throw InjectionError.notFound(this.proto.token);
491
+ this._instance = InjectionContext.instantiate(factory, retriever);
492
+ this._resolved = true;
493
+ if (pool) pool.set(this.proto.token, this);
494
+ }
495
+ toString() {
496
+ return `TreeNodeSingle<${this.proto.token.toString()}>`;
497
+ }
498
+ };
499
+ var TreeNodeTransparent = class _TreeNodeTransparent {
500
+ static {
501
+ __name(this, "TreeNodeTransparent");
502
+ }
503
+ proto;
504
+ _transparent = /* @__PURE__ */ new Set();
505
+ _deps = /* @__PURE__ */ new Map();
506
+ _instance = null;
507
+ _resolved = false;
508
+ allocations = 0;
509
+ get instance() {
510
+ if (!this._resolved) throw InjectionError.accessFailed();
511
+ return this._instance;
512
+ }
513
+ constructor(proto) {
514
+ this.proto = proto;
515
+ }
516
+ addDependency(node) {
517
+ if (node instanceof _TreeNodeTransparent) this._transparent.add(node);
518
+ else this._deps.set(node.proto.token, node);
519
+ node.allocations++;
520
+ }
521
+ instantiate(pool) {
522
+ if (this._resolved) return;
523
+ for (const dep of this._transparent) dep.instantiate(pool);
524
+ for (const node of this._deps.values()) node.instantiate(pool);
525
+ const retriever = retrieverFactory(this.proto.parent.token, this._deps, this._transparent);
526
+ this._instance = InjectionContext.instantiate(this.proto.factory, retriever);
527
+ this._resolved = true;
528
+ }
529
+ toString() {
530
+ return `TreeNodeTransparent<${this.proto.parent.token.toString()}>`;
531
+ }
532
+ };
533
+ var TreeNodeMulti = class _TreeNodeMulti {
534
+ static {
535
+ __name(this, "TreeNodeMulti");
536
+ }
537
+ proto;
538
+ _deps = /* @__PURE__ */ new Set();
539
+ instance = [];
540
+ _resolved = false;
541
+ allocations = 0;
542
+ constructor(proto) {
543
+ this.proto = proto;
544
+ }
545
+ instantiate(pool) {
546
+ if (this._resolved) return;
547
+ for (const dep of this._deps) {
548
+ dep.instantiate(pool);
549
+ if (dep instanceof TreeNodeSingle) {
550
+ this.instance.push(dep.instance);
551
+ } else if (dep instanceof _TreeNodeMulti) {
552
+ this.instance.push(...dep.instance);
553
+ } else if (dep instanceof TreeNodeTransparent) {
554
+ this.instance.push(dep.instance);
555
+ }
556
+ }
557
+ this._resolved = true;
558
+ if (pool) pool.set(this.proto.token, this);
559
+ }
560
+ addDependency(...nodes) {
561
+ for (const node of nodes) {
562
+ this._deps.add(node);
563
+ node.allocations++;
564
+ }
565
+ }
566
+ toString() {
567
+ return `TreeNodeMulti<${this.proto.token.toString()}>`;
568
+ }
569
+ };
570
+
571
+ // src/lib/provider/resolver.ts
572
+ function createTreeNode(p) {
573
+ if (p instanceof ProtoNodeSingle) return new TreeNodeSingle(p);
574
+ if (p instanceof ProtoNodeMulti) return new TreeNodeMulti(p);
575
+ if (p instanceof ProtoNodeTransparent) return new TreeNodeTransparent(p);
576
+ throw new Error("Unknown ProtoNode type");
577
+ }
578
+ __name(createTreeNode, "createTreeNode");
579
+ function resolveTreeNode(rootProto, cache, singleNodes, multiNodes, upstreamGetter) {
580
+ const inCache = cache.get(rootProto);
581
+ if (inCache) return inCache;
582
+ const rootNode = createTreeNode(rootProto);
583
+ const stack = [
584
+ {
585
+ proto: rootProto,
586
+ node: rootNode,
587
+ processed: false
588
+ }
589
+ ];
590
+ const visiting = /* @__PURE__ */ new Set();
591
+ while (stack.length > 0) {
592
+ let addDependency2 = function(token, optional = false) {
593
+ if (token instanceof NodeToken) {
594
+ const p = singleNodes.get(token);
595
+ if (p) {
596
+ deps.push(p);
597
+ return;
598
+ }
599
+ } else if (token instanceof MultiNodeToken) {
600
+ const p = multiNodes.get(token);
601
+ if (p) {
602
+ deps.push(p);
603
+ return;
604
+ }
605
+ }
606
+ const upstream = upstreamGetter?.(token);
607
+ if (upstream) {
608
+ deps.push(upstream);
609
+ return;
610
+ }
611
+ if (!optional) {
612
+ if (isInjectable(token)) {
613
+ const nodeToken = getInjectableToken(token);
614
+ throw InjectionError.notFound(nodeToken);
615
+ }
616
+ throw InjectionError.notFound(token);
617
+ }
618
+ };
619
+ var addDependency = addDependency2;
620
+ __name(addDependency2, "addDependency");
621
+ const frame = stack[stack.length - 1];
622
+ const { proto, node } = frame;
623
+ if (frame.processed) {
624
+ stack.pop();
625
+ visiting.delete(proto);
626
+ cache.set(proto, node);
627
+ continue;
628
+ }
629
+ if (visiting.has(proto) && isNotTransparentProto(proto)) {
630
+ const path = stack.map((f) => f.proto).filter((p) => isNotTransparentProto(p));
631
+ const index = path.indexOf(proto);
632
+ const cycle = path.slice(index);
633
+ const cycleTokens = cycle.map((p) => p.token);
634
+ throw InjectionError.circularDependency(proto.token, cycleTokens);
635
+ }
636
+ visiting.add(proto);
637
+ frame.processed = true;
638
+ const deps = [];
639
+ if (proto instanceof ProtoNodeSingle || proto instanceof ProtoNodeTransparent) {
640
+ for (const injection of proto.injections) {
641
+ addDependency2(injection.token, injection.optional);
642
+ }
643
+ }
644
+ if (proto instanceof ProtoNodeMulti) {
645
+ const parentNodes = upstreamGetter?.(proto.token);
646
+ if (parentNodes instanceof TreeNodeMulti) {
647
+ node.addDependency(parentNodes);
648
+ }
649
+ for (const single of proto.singleNodes) {
650
+ let p = singleNodes.get(single);
651
+ if (!p) {
652
+ p = new ProtoNodeSingle(single);
653
+ singleNodes.set(single, p);
654
+ }
655
+ deps.push(p);
656
+ }
657
+ for (const multi of proto.multiNodes) {
658
+ let p = multiNodes.get(multi);
659
+ if (!p) {
660
+ p = new ProtoNodeMulti(multi);
661
+ multiNodes.set(multi, p);
662
+ }
663
+ deps.push(p);
664
+ }
665
+ for (const transparent of proto.transparentNodes) {
666
+ deps.push(transparent);
667
+ }
668
+ }
669
+ for (const dep of deps) {
670
+ if (dep instanceof TreeNodeSingle || dep instanceof TreeNodeMulti || dep instanceof TreeNodeTransparent) {
671
+ node.addDependency(dep);
672
+ continue;
673
+ }
674
+ const depProto = dep;
675
+ const cached = cache.get(depProto);
676
+ if (cached) {
677
+ node.addDependency(cached);
678
+ continue;
679
+ }
680
+ if (visiting.has(depProto) && isNotTransparentProto(depProto)) {
681
+ const path = stack.map((f) => f.proto).filter((p) => isNotTransparentProto(p));
682
+ const index = path.indexOf(depProto);
683
+ const cycle = [
684
+ ...path.slice(index),
685
+ depProto
686
+ ];
687
+ const cycleTokens = cycle.map((p) => p.token);
688
+ throw InjectionError.circularDependency(depProto.token, cycleTokens);
689
+ }
690
+ const childNode = createTreeNode(depProto);
691
+ node.addDependency(childNode);
692
+ stack.push({
693
+ proto: depProto,
694
+ node: childNode,
695
+ processed: false
696
+ });
697
+ }
698
+ }
699
+ return rootNode;
700
+ }
701
+ __name(resolveTreeNode, "resolveTreeNode");
702
+
703
+ // src/lib/utils/injector.ts
704
+ var InjectorImpl = class {
705
+ static {
706
+ __name(this, "InjectorImpl");
707
+ }
708
+ container;
709
+ constructor(container) {
710
+ this.container = container;
711
+ }
712
+ get(token) {
713
+ return this.container.get(token);
714
+ }
715
+ produce(fn) {
716
+ return this.container.produce(fn);
717
+ }
718
+ };
719
+ var Injector = new NodeToken("Injector");
720
+
721
+ // src/lib/container/container.ts
722
+ var NodeContainer = class extends Illuma {
723
+ static {
724
+ __name(this, "NodeContainer");
725
+ }
726
+ _opts;
727
+ _bootstrapped = false;
728
+ _rootNode;
729
+ _parent;
730
+ _protoNodes = /* @__PURE__ */ new Map();
731
+ _multiProtoNodes = /* @__PURE__ */ new Map();
732
+ constructor(_opts) {
733
+ super(), this._opts = _opts;
734
+ this._parent = _opts?.parent;
735
+ if (_opts?.diagnostics) {
736
+ Illuma.extendDiagnostics(new DiagnosticsDefaultReporter());
737
+ }
738
+ }
739
+ /**
740
+ * Registers a provider in the container.
741
+ * Must be called before {@link bootstrap}.
742
+ *
743
+ * @template T - The type of value being provided
744
+ * @param provider - The provider configuration (token, class, or provider object)
745
+ * @throws {InjectionError} If called after bootstrap or if a duplicate provider is detected
746
+ *
747
+ * @example
748
+ * ```typescript
749
+ * // Provide a value
750
+ * container.provide({ provide: CONFIG_TOKEN, value: { apiKey: '123' } });
751
+ *
752
+ * // Provide a factory
753
+ * container.provide({ provide: LOGGER_TOKEN, factory: () => new ConsoleLogger() });
754
+ *
755
+ * // Provide an injectable class directly
756
+ * container.provide(UserService);
757
+ *
758
+ * // Provide a class override
759
+ * container.provide({ provide: ServiceClass, useClass: ServiceOverride });
760
+ * ```
761
+ */
762
+ provide(provider) {
763
+ if (this._bootstrapped) {
764
+ throw InjectionError.bootstrapped();
765
+ }
766
+ if (Array.isArray(provider)) {
767
+ for (const item of provider) this.provide(item);
768
+ return;
769
+ }
770
+ if (provider instanceof MultiNodeToken) {
771
+ if (this._multiProtoNodes.has(provider)) {
772
+ throw InjectionError.duplicate(provider);
773
+ }
774
+ const newProto = new ProtoNodeMulti(provider);
775
+ this._multiProtoNodes.set(provider, newProto);
776
+ return;
777
+ }
778
+ if (provider instanceof NodeToken) {
779
+ if (this._protoNodes.has(provider)) {
780
+ throw InjectionError.duplicate(provider);
781
+ }
782
+ const proto = new ProtoNodeSingle(provider);
783
+ this._protoNodes.set(provider, proto);
784
+ return;
785
+ }
786
+ if (typeof provider === "function") {
787
+ if (!isInjectable(provider)) throw InjectionError.invalidCtor(provider);
788
+ const token2 = getInjectableToken(provider);
789
+ if (!(token2 instanceof NodeToken)) throw InjectionError.invalidCtor(provider);
790
+ const existing = this._protoNodes.get(token2);
791
+ if (existing?.hasFactory()) throw InjectionError.duplicate(token2);
792
+ const factory = token2.opts?.factory ?? (() => new provider());
793
+ if (existing) {
794
+ existing.setFactory(factory);
795
+ return;
796
+ }
797
+ const proto = new ProtoNodeSingle(token2, factory);
798
+ this._protoNodes.set(token2, proto);
799
+ return;
800
+ }
801
+ const obj = provider;
802
+ const token = extractToken(obj.provide);
803
+ const retriever = extractProvider(obj);
804
+ if (token instanceof MultiNodeToken) {
805
+ const multiProto = this._multiProtoNodes.get(token);
806
+ if (multiProto) {
807
+ multiProto.addProvider(retriever);
808
+ return;
809
+ }
810
+ const newProto = new ProtoNodeMulti(token);
811
+ this._multiProtoNodes.set(token, newProto);
812
+ newProto.addProvider(retriever);
813
+ return;
814
+ }
815
+ if (token instanceof NodeToken) {
816
+ const existing = this._protoNodes.get(token);
817
+ if (existing?.hasFactory()) throw InjectionError.duplicate(token);
818
+ let factory;
819
+ if (typeof retriever === "function") factory = retriever;
820
+ if (isNodeBase(retriever)) {
821
+ if (retriever === token) throw InjectionError.loopAlias(token);
822
+ factory = /* @__PURE__ */ __name(() => nodeInject(retriever), "factory");
823
+ }
824
+ if (existing && factory) {
825
+ existing.setFactory(factory);
826
+ return;
827
+ }
828
+ const proto = new ProtoNodeSingle(token, factory);
829
+ this._protoNodes.set(token, proto);
830
+ return;
831
+ }
832
+ throw InjectionError.invalidProvider(JSON.stringify(provider));
833
+ }
834
+ findNode(token) {
835
+ if (!this._rootNode) return null;
836
+ if (!this._bootstrapped) return null;
837
+ if (isInjectable(token)) {
838
+ const node = getInjectableToken(token);
839
+ return this._rootNode.find(node);
840
+ }
841
+ const treeNode = this._rootNode.find(token);
842
+ return treeNode;
843
+ }
844
+ _getFromParent(token) {
845
+ if (!this._parent) return null;
846
+ const parentNode = this._parent;
847
+ return parentNode.findNode(token);
848
+ }
849
+ _buildInjectionTree() {
850
+ const root = new TreeRootNode();
851
+ const cache = /* @__PURE__ */ new Map();
852
+ const nodes = [
853
+ ...this._protoNodes.values(),
854
+ ...this._multiProtoNodes.values()
855
+ ];
856
+ const upstreamGetter = this._getFromParent.bind(this);
857
+ for (const node of nodes) {
858
+ if (cache.has(node)) continue;
859
+ const treeNode = resolveTreeNode(node, cache, this._protoNodes, this._multiProtoNodes, upstreamGetter);
860
+ root.addDependency(treeNode);
861
+ }
862
+ cache.clear();
863
+ this._protoNodes.clear();
864
+ this._multiProtoNodes.clear();
865
+ return root;
866
+ }
867
+ /**
868
+ * Bootstraps the container by resolving the dependency trees and instantiating all providers.
869
+ * This must be called after all providers are registered and before calling {@link get}.
870
+ *
871
+ * The bootstrap process:
872
+ * 1. Validates all provider registrations
873
+ * 2. Builds dependency injection trees
874
+ * 3. Detects circular dependencies in each tree
875
+ * 4. Instantiates all dependencies in the correct order
876
+ *
877
+ * @throws {InjectionError} If the container is already bootstrapped or if circular dependencies are detected
878
+ *
879
+ * @example
880
+ * ```typescript
881
+ * const container = new NodeContainer();
882
+ * container.provide(UserService);
883
+ * container.provide(LoggerService);
884
+ * container.bootstrap(); // Resolves and instantiates all dependencies
885
+ * ```
886
+ */
887
+ bootstrap() {
888
+ if (this._bootstrapped) throw InjectionError.doubleBootstrap();
889
+ const start = performance.now();
890
+ this.provide({
891
+ provide: Injector,
892
+ value: new InjectorImpl(this)
893
+ });
894
+ this._rootNode = this._buildInjectionTree();
895
+ this._rootNode.instantiate();
896
+ this._bootstrapped = true;
897
+ const end = performance.now();
898
+ const duration = end - start;
899
+ if (this._opts?.measurePerformance) {
900
+ console.log(`[Illuma] \u{1F680} Bootstrapped in ${duration.toFixed(2)} ms`);
901
+ }
902
+ if (this._opts?.diagnostics) {
903
+ const allNodes = this._rootNode.dependencies.size;
904
+ const unusedNodes = Array.from(this._rootNode.dependencies).filter((node) => node.allocations === 0).filter((node) => {
905
+ if (!(node.proto instanceof ProtoNodeSingle)) return true;
906
+ return node.proto.token !== Injector;
907
+ });
908
+ Illuma.onReport({
909
+ totalNodes: allNodes,
910
+ unusedNodes,
911
+ bootstrapDuration: duration
912
+ });
913
+ }
914
+ }
915
+ get(provider) {
916
+ if (!this._bootstrapped || !this._rootNode) {
917
+ throw InjectionError.notBootstrapped();
918
+ }
919
+ let token = null;
920
+ if (typeof provider === "function") {
921
+ if (!isInjectable(provider)) throw InjectionError.invalidCtor(provider);
922
+ token = getInjectableToken(provider);
923
+ }
924
+ if (isNodeBase(provider)) token = provider;
925
+ if (!token) {
926
+ throw InjectionError.invalidProvider(JSON.stringify(provider));
927
+ }
928
+ const treeNode = this._rootNode.find(token);
929
+ if (!treeNode) {
930
+ const upstream = this._getFromParent(token);
931
+ if (upstream) return upstream.instance;
932
+ if (token instanceof MultiNodeToken) return [];
933
+ throw InjectionError.notFound(token);
934
+ }
935
+ return treeNode.instance;
936
+ }
937
+ /**
938
+ * Instantiates a class outside injection context. Primarily used to create instances via Injector.
939
+ * Class does not get registered in the container and cannot be retrieved via {@link get} or {@link nodeInject}.
940
+ * Must be called after {@link bootstrap}.
941
+ *
942
+ * @template T - The type of the class being instantiated
943
+ * @param factory - Factory or class constructor to instantiate
944
+ * @returns A new instance of the class with dependencies injected
945
+ * @throws {InjectionError} If called before bootstrap or if the constructor is invalid
946
+ */
947
+ produce(fn) {
948
+ if (!this._bootstrapped || !this._rootNode) {
949
+ throw InjectionError.notBootstrapped();
950
+ }
951
+ if (typeof fn !== "function") throw InjectionError.invalidCtor(fn);
952
+ if (isConstructor(fn) && !isInjectable(fn)) {
953
+ throw InjectionError.invalidCtor(fn);
954
+ }
955
+ const factory = isInjectable(fn) ? () => new fn() : fn;
956
+ return InjectionContext.instantiate(factory, (t, optional) => {
957
+ if (!this._rootNode) throw InjectionError.notBootstrapped();
958
+ const node = this._rootNode.find(t);
959
+ if (!node && !optional) throw InjectionError.notFound(t);
960
+ return node ? node.instance : null;
961
+ });
962
+ }
963
+ };
964
+
965
+ // src/lib/testkit/helpers.ts
966
+ function createTestFactory(cfg) {
967
+ const factorySelf = /* @__PURE__ */ __name(() => {
968
+ const container = new NodeContainer();
969
+ if (cfg.provide) container.provide(cfg.provide);
970
+ container.provide(cfg.target);
971
+ container.bootstrap();
972
+ const instance = container.get(cfg.target);
973
+ const nodeInject2 = /* @__PURE__ */ __name((token, opts) => {
974
+ try {
975
+ return container.get(token);
976
+ } catch (e) {
977
+ if (e instanceof InjectionError && e.code === 400 && opts?.optional) {
978
+ return null;
979
+ }
980
+ throw e;
981
+ }
982
+ }, "nodeInject");
983
+ return {
984
+ instance,
985
+ nodeInject: nodeInject2
986
+ };
987
+ }, "factorySelf");
988
+ return factorySelf;
989
+ }
990
+ __name(createTestFactory, "createTestFactory");
991
+ // Annotate the CommonJS export names for ESM import in node:
992
+ 0 && (module.exports = {
993
+ createTestFactory
994
+ });
995
+ //# sourceMappingURL=testkit.cjs.map