@illuma/core 1.4.0 → 1.5.1

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 CHANGED
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 1.5.1 - 2026-01-11
11
+
12
+ ## 1.5.0 - 2026-01-10
13
+
10
14
  ## 1.4.0 - 2026-01-10
11
15
 
12
16
  ## 1.3.1 - 2026-01-10
package/README.md CHANGED
@@ -12,11 +12,11 @@ A lightweight, type-safe dependency injection container for TypeScript. Zero dep
12
12
  - 🎯 **Type-Safe** – Full TypeScript support with excellent type inference
13
13
  - 🪶 **Lightweight** – Zero dependencies, minimal bundle size
14
14
  - 🔄 **Flexible** – Classes, factories, values, and aliases
15
- - 🎨 **Decorators** – Optional Angular-style `@NodeInjectable()` decorator
15
+ - 🎨 **Optional decorators** – Angular-style `@NodeInjectable()` decorator
16
16
  - 🔗 **Multi-Tokens** – Built-in multi-provider support
17
- - 🔌 **Plugin System** – Extensible architecture with custom scanners and diagnostics
17
+ - 🔌 **Plugin System** – Extensible architecture with custom middlewares, scanners, and diagnostics
18
+ - 🧪 **Testkit** – Utilities for easy unit testing and mocking
18
19
  - 🌍 **Universal** – Node.js, Deno, browser, and Electron
19
-
20
20
  ## 📦 Installation
21
21
 
22
22
  ```bash
package/dist/index.cjs CHANGED
@@ -4,6 +4,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
6
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
7
10
  var __export = (target, all) => {
8
11
  for (var name in all)
9
12
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -18,6 +21,153 @@ var __copyProps = (to, from, except, desc) => {
18
21
  };
19
22
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
23
 
24
+ // src/lib/plugins/core/plugin-container.ts
25
+ var Illuma;
26
+ var init_plugin_container = __esm({
27
+ "src/lib/plugins/core/plugin-container.ts"() {
28
+ "use strict";
29
+ Illuma = class _Illuma {
30
+ static {
31
+ __name(this, "Illuma");
32
+ }
33
+ static _diagnostics = /* @__PURE__ */ new Set();
34
+ static _scanners = [];
35
+ static _middlewares = [];
36
+ /** @internal */
37
+ static get contextScanners() {
38
+ return _Illuma._scanners;
39
+ }
40
+ /**
41
+ * Extends the diagnostics with a new diagnostics module.
42
+ * These will be run on diagnostics reports after container bootstrap.
43
+ *
44
+ * @param m - The diagnostics module instance to add
45
+ */
46
+ static extendDiagnostics(m) {
47
+ _Illuma._diagnostics.add(m);
48
+ }
49
+ /**
50
+ * Extends the context scanners with a new context scanner.
51
+ * These will be run in injection context scans to detect additional injections (alongside `nodeInject` calls).
52
+ *
53
+ * @param scanner - The context scanner instance to add
54
+ */
55
+ static extendContextScanner(scanner) {
56
+ _Illuma._scanners.push(scanner);
57
+ }
58
+ /**
59
+ * Registers a global middleware to be applied during instance creation.
60
+ * Typically used for cross-cutting concerns like logging, profiling, or custom instantiation logic.
61
+ * Function should accept instantiation parameters and a `next` function to proceed with the next middleware or actual instantiation.
62
+ *
63
+ * @param m - The middleware function to register
64
+ */
65
+ static registerGlobalMiddleware(m) {
66
+ _Illuma._middlewares.push(m);
67
+ }
68
+ middlewares = [];
69
+ registerMiddleware(m) {
70
+ this.middlewares.push(m);
71
+ }
72
+ static onReport(report) {
73
+ for (const diag of _Illuma._diagnostics) diag.onReport(report);
74
+ }
75
+ /**
76
+ * @internal
77
+ * Check if diagnostics modules are registered
78
+ */
79
+ static hasDiagnostics() {
80
+ return _Illuma._diagnostics.size > 0;
81
+ }
82
+ /**
83
+ * @internal
84
+ * Reset all plugin registrations
85
+ */
86
+ static __resetPlugins() {
87
+ _Illuma._diagnostics.clear();
88
+ _Illuma._scanners.length = 0;
89
+ _Illuma._middlewares.length = 0;
90
+ }
91
+ };
92
+ }
93
+ });
94
+
95
+ // src/lib/plugins/core/index.ts
96
+ var init_core = __esm({
97
+ "src/lib/plugins/core/index.ts"() {
98
+ "use strict";
99
+ init_plugin_container();
100
+ }
101
+ });
102
+
103
+ // src/lib/plugins/middlewares/diagnostics.middleware.ts
104
+ var performanceDiagnostics;
105
+ var init_diagnostics_middleware = __esm({
106
+ "src/lib/plugins/middlewares/diagnostics.middleware.ts"() {
107
+ "use strict";
108
+ performanceDiagnostics = /* @__PURE__ */ __name((params, next) => {
109
+ if (!params.deps.size) {
110
+ return next(params);
111
+ }
112
+ const start = performance.now();
113
+ const instance = next(params);
114
+ const end = performance.now();
115
+ const duration = end - start;
116
+ console.log(`Instantiated ${params.token.name} in ${duration.toFixed(2)} ms`);
117
+ return instance;
118
+ }, "performanceDiagnostics");
119
+ }
120
+ });
121
+
122
+ // src/lib/plugins/diagnostics/default-impl.ts
123
+ var DiagnosticsDefaultReporter;
124
+ var init_default_impl = __esm({
125
+ "src/lib/plugins/diagnostics/default-impl.ts"() {
126
+ "use strict";
127
+ DiagnosticsDefaultReporter = class {
128
+ static {
129
+ __name(this, "DiagnosticsDefaultReporter");
130
+ }
131
+ onReport(report) {
132
+ console.log("[Illuma] \u{1F9F9} Diagnostics:");
133
+ console.log(` Total: ${report.totalNodes} node(s)`);
134
+ console.log(` ${report.unusedNodes.length} were not used while bootstrap:`);
135
+ for (const node of report.unusedNodes) console.log(` - ${node.toString()}`);
136
+ }
137
+ };
138
+ }
139
+ });
140
+
141
+ // src/lib/plugins/diagnostics/built-in.ts
142
+ var built_in_exports = {};
143
+ __export(built_in_exports, {
144
+ __resetDiagnosticsState: () => __resetDiagnosticsState,
145
+ enableIllumaDiagnostics: () => enableIllumaDiagnostics
146
+ });
147
+ function enableIllumaDiagnostics() {
148
+ if (state.enabled) return;
149
+ state.enabled = true;
150
+ Illuma.extendDiagnostics(new DiagnosticsDefaultReporter());
151
+ Illuma.registerGlobalMiddleware(performanceDiagnostics);
152
+ }
153
+ function __resetDiagnosticsState() {
154
+ state.enabled = false;
155
+ }
156
+ var state;
157
+ var init_built_in = __esm({
158
+ "src/lib/plugins/diagnostics/built-in.ts"() {
159
+ "use strict";
160
+ init_core();
161
+ init_diagnostics_middleware();
162
+ init_default_impl();
163
+ state = {
164
+ enabled: false
165
+ };
166
+ __name(enableIllumaDiagnostics, "enableIllumaDiagnostics");
167
+ __name(__resetDiagnosticsState, "__resetDiagnosticsState");
168
+ }
169
+ });
170
+
21
171
  // src/index.ts
22
172
  var index_exports = {};
23
173
  __export(index_exports, {
@@ -267,56 +417,8 @@ function registerClassAsInjectable(ctor, token) {
267
417
  }
268
418
  __name(registerClassAsInjectable, "registerClassAsInjectable");
269
419
 
270
- // src/lib/plugins/core/plugin-container.ts
271
- var Illuma = class _Illuma {
272
- static {
273
- __name(this, "Illuma");
274
- }
275
- static _diagnostics = [];
276
- static _scanners = [];
277
- static _middlewares = [];
278
- /** @internal */
279
- static get contextScanners() {
280
- return _Illuma._scanners;
281
- }
282
- /**
283
- * Extends the diagnostics with a new diagnostics module.
284
- * These will be run on diagnostics reports after container bootstrap.
285
- *
286
- * @param m - The diagnostics module instance to add
287
- */
288
- static extendDiagnostics(m) {
289
- _Illuma._diagnostics.push(m);
290
- }
291
- /**
292
- * Extends the context scanners with a new context scanner.
293
- * These will be run in injection context scans to detect additional injections (alongside `nodeInject` calls).
294
- *
295
- * @param scanner - The context scanner instance to add
296
- */
297
- static extendContextScanner(scanner) {
298
- _Illuma._scanners.push(scanner);
299
- }
300
- /**
301
- * Registers a global middleware to be applied during instance creation.
302
- * Typically used for cross-cutting concerns like logging, profiling, or custom instantiation logic.
303
- * Function should accept instantiation parameters and a `next` function to proceed with the next middleware or actual instantiation.
304
- *
305
- * @param m - The middleware function to register
306
- */
307
- static registerGlobalMiddleware(m) {
308
- _Illuma._middlewares.push(m);
309
- }
310
- middlewares = [];
311
- registerMiddleware(m) {
312
- this.middlewares.push(m);
313
- }
314
- static onReport(report) {
315
- for (const diag of _Illuma._diagnostics) diag.onReport(report);
316
- }
317
- };
318
-
319
420
  // src/lib/context/context.ts
421
+ init_plugin_container();
320
422
  var InjectionContext = class _InjectionContext {
321
423
  static {
322
424
  __name(this, "InjectionContext");
@@ -416,18 +518,19 @@ function nodeInject(provider, options) {
416
518
  }
417
519
  __name(nodeInject, "nodeInject");
418
520
 
419
- // src/lib/plugins/diagnostics/default-impl.ts
420
- var DiagnosticsDefaultReporter = class {
421
- static {
422
- __name(this, "DiagnosticsDefaultReporter");
423
- }
424
- onReport(report) {
425
- console.log("[Illuma] \u{1F9F9} Diagnostics:");
426
- console.log(` Total: ${report.totalNodes} node(s)`);
427
- console.log(` ${report.unusedNodes.length} were not used while bootstrap:`);
428
- for (const node of report.unusedNodes) console.log(` - ${node.toString()}`);
429
- }
430
- };
521
+ // src/lib/container/container.ts
522
+ init_plugin_container();
523
+
524
+ // src/lib/plugins/middlewares/runner.ts
525
+ function runMiddlewares(middlewares, params) {
526
+ const ms = middlewares;
527
+ const next = /* @__PURE__ */ __name((i, current) => {
528
+ if (i >= ms.length) return current.factory();
529
+ return ms[i](current, (nextParams) => next(i + 1, nextParams));
530
+ }, "next");
531
+ return next(0, params);
532
+ }
533
+ __name(runMiddlewares, "runMiddlewares");
431
534
 
432
535
  // src/lib/provider/extractor.ts
433
536
  function extractProvider(provider) {
@@ -534,24 +637,15 @@ var InjectorImpl = class {
534
637
  var Injector = new NodeToken("Injector");
535
638
 
536
639
  // src/lib/provider/tree-node.ts
537
- function runMiddlewares(middlewares, params) {
538
- const ms = middlewares;
539
- const next = /* @__PURE__ */ __name((i, current) => {
540
- if (i >= ms.length) return current.factory();
541
- return ms[i](current, (nextParams) => next(i + 1, nextParams));
542
- }, "next");
543
- return next(0, params);
544
- }
545
- __name(runMiddlewares, "runMiddlewares");
546
640
  function retrieverFactory(node, deps, transparentDeps) {
547
641
  return (token, optional) => {
548
642
  const depNode = deps.get(token);
549
643
  if (!depNode && !optional) {
550
- const transparent = Array.from(transparentDeps).find((n) => "proto" in n && n.proto.parent.token === token);
644
+ const transparent = transparentDeps.get(token);
551
645
  if (transparent) return transparent.instance;
552
646
  throw InjectionError.untracked(token, node);
553
647
  }
554
- return depNode?.instance ?? null;
648
+ return depNode ? depNode.instance : null;
555
649
  };
556
650
  }
557
651
  __name(retrieverFactory, "retrieverFactory");
@@ -562,7 +656,7 @@ var TreeRootNode = class {
562
656
  instant;
563
657
  middlewares;
564
658
  _deps = /* @__PURE__ */ new Set();
565
- _treePool = /* @__PURE__ */ new WeakMap();
659
+ _treePool = /* @__PURE__ */ new Map();
566
660
  constructor(instant = true, middlewares = []) {
567
661
  this.instant = instant;
568
662
  this.middlewares = middlewares;
@@ -627,13 +721,21 @@ var TreeNodeSingle = class {
627
721
  if (this._resolved) return;
628
722
  for (const node of this._deps.values()) node.instantiate(pool, middlewares);
629
723
  for (const dep of this._transparent) dep.instantiate(pool, middlewares);
630
- const retriever = retrieverFactory(this.proto.token, this._deps, this._transparent);
724
+ const transparentMap = /* @__PURE__ */ new Map();
725
+ for (const tNode of this._transparent) {
726
+ transparentMap.set(tNode.proto.parent.token, tNode);
727
+ }
728
+ const retriever = retrieverFactory(this.proto.token, this._deps, transparentMap);
631
729
  const factory = this.proto.factory ?? this.proto.token.opts?.factory;
632
730
  if (!factory) throw InjectionError.notFound(this.proto.token);
633
731
  const contextFactory = /* @__PURE__ */ __name(() => InjectionContext.instantiate(factory, retriever), "contextFactory");
634
732
  this._instance = runMiddlewares(middlewares, {
635
733
  token: this.proto.token,
636
- factory: contextFactory
734
+ factory: contextFactory,
735
+ deps: /* @__PURE__ */ new Set([
736
+ ...this._deps.keys(),
737
+ ...Array.from(this._transparent).map((n) => n.proto.parent.token)
738
+ ])
637
739
  });
638
740
  this._resolved = true;
639
741
  if (pool) pool.set(this.proto.token, this);
@@ -672,11 +774,19 @@ var TreeNodeTransparent = class _TreeNodeTransparent {
672
774
  if (this._resolved) return;
673
775
  for (const dep of this._transparent) dep.instantiate(pool, middlewares);
674
776
  for (const node of this._deps.values()) node.instantiate(pool, middlewares);
675
- const retriever = retrieverFactory(this.proto.parent.token, this._deps, this._transparent);
777
+ const transparentMap = /* @__PURE__ */ new Map();
778
+ for (const tNode of this._transparent) {
779
+ transparentMap.set(tNode.proto.parent.token, tNode);
780
+ }
781
+ const retriever = retrieverFactory(this.proto.parent.token, this._deps, transparentMap);
676
782
  const refFactory = /* @__PURE__ */ __name(() => InjectionContext.instantiate(this.proto.factory, retriever), "refFactory");
677
783
  this._instance = runMiddlewares(middlewares, {
678
784
  token: this.proto.parent.token,
679
- factory: refFactory
785
+ factory: refFactory,
786
+ deps: /* @__PURE__ */ new Set([
787
+ ...this._deps.keys(),
788
+ ...Array.from(this._transparent).map((n) => n.proto.parent.token)
789
+ ])
680
790
  });
681
791
  this._resolved = true;
682
792
  }
@@ -967,10 +1077,13 @@ var NodeContainer = class extends Illuma {
967
1077
  _multiProtoNodes = /* @__PURE__ */ new Map();
968
1078
  constructor(_opts) {
969
1079
  super(), this._opts = _opts;
970
- this._parent = _opts?.parent;
971
1080
  if (_opts?.diagnostics) {
972
- Illuma.extendDiagnostics(new DiagnosticsDefaultReporter());
1081
+ console.warn("[Illuma] Deprecation Warning: The 'diagnostics' option in iContainerOptions is deprecated and will be removed in future versions. Please use the `enableIllumaDiagnostics` from '@illuma/core/plugins` instead.");
1082
+ const m = (init_built_in(), __toCommonJS(built_in_exports));
1083
+ if (m.enabled) return;
1084
+ m.enableIllumaDiagnostics();
973
1085
  }
1086
+ this._parent = _opts?.parent;
974
1087
  }
975
1088
  /**
976
1089
  * Registers a provider in the container.
@@ -1145,7 +1258,7 @@ var NodeContainer = class extends Illuma {
1145
1258
  if (this._opts?.measurePerformance) {
1146
1259
  console.log(`[Illuma] \u{1F680} Bootstrapped in ${duration.toFixed(2)} ms`);
1147
1260
  }
1148
- if (this._opts?.diagnostics) {
1261
+ if (this._opts?.diagnostics || Illuma.hasDiagnostics()) {
1149
1262
  const allNodes = this._rootNode.dependencies.size;
1150
1263
  const unusedNodes = Array.from(this._rootNode.dependencies).filter((node) => node.allocations === 0).filter((node) => {
1151
1264
  if (!(node.proto instanceof ProtoNodeSingle)) return true;
@@ -1198,12 +1311,33 @@ var NodeContainer = class extends Illuma {
1198
1311
  if (isConstructor(fn) && !isInjectable(fn)) {
1199
1312
  throw InjectionError.invalidCtor(fn);
1200
1313
  }
1201
- const factory = isInjectable(fn) ? () => new fn() : fn;
1202
- return InjectionContext.instantiate(factory, (t, optional) => {
1203
- if (!this._rootNode) throw InjectionError.notBootstrapped();
1204
- const node = this._rootNode.find(t);
1205
- if (!node && !optional) throw InjectionError.notFound(t);
1314
+ let factory;
1315
+ if (isInjectable(fn)) {
1316
+ const f = getInjectableToken(fn).opts?.factory;
1317
+ if (!f) factory = /* @__PURE__ */ __name(() => new fn(), "factory");
1318
+ else factory = /* @__PURE__ */ __name(() => getInjectableToken(fn).opts?.factory?.(), "factory");
1319
+ } else {
1320
+ factory = fn;
1321
+ }
1322
+ const rootNode = this._rootNode;
1323
+ if (!rootNode) throw InjectionError.notBootstrapped();
1324
+ const retriever = /* @__PURE__ */ __name((token, optional) => {
1325
+ const node = rootNode.find(token);
1326
+ if (!node && !optional) throw InjectionError.notFound(token);
1206
1327
  return node ? node.instance : null;
1328
+ }, "retriever");
1329
+ const deps = InjectionContext.scan(factory);
1330
+ const middlewares = [
1331
+ ...Illuma._middlewares,
1332
+ ...this.collectMiddlewares()
1333
+ ];
1334
+ const contextFactory = /* @__PURE__ */ __name(() => InjectionContext.instantiate(factory, retriever), "contextFactory");
1335
+ return runMiddlewares(middlewares, {
1336
+ token: new NodeToken("ProducedNode"),
1337
+ deps: new Set([
1338
+ ...deps.values()
1339
+ ].map((d) => d.token)),
1340
+ factory: contextFactory
1207
1341
  });
1208
1342
  }
1209
1343
  };