@illuma/core 1.3.1 → 1.5.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 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.0 - 2026-01-10
11
+
12
+ ## 1.4.0 - 2026-01-10
13
+
10
14
  ## 1.3.1 - 2026-01-10
11
15
 
12
16
  ## 1.3.0 - 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,41 +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
- /** @internal */
278
- static get contextScanners() {
279
- return _Illuma._scanners;
280
- }
281
- /**
282
- * Extends the diagnostics with a new diagnostics module.
283
- * These will be run on diagnostics reports after container bootstrap.
284
- *
285
- * @param m - The diagnostics module instance to add
286
- */
287
- static extendDiagnostics(m) {
288
- _Illuma._diagnostics.push(m);
289
- }
290
- /**
291
- * Extends the context scanners with a new context scanner.
292
- * These will be run in injection context scans to detect additional injections (alongside `nodeInject` calls).
293
- *
294
- * @param scanner - The context scanner instance to add
295
- */
296
- static extendContextScanner(scanner) {
297
- _Illuma._scanners.push(scanner);
298
- }
299
- static onReport(report) {
300
- for (const diag of _Illuma._diagnostics) diag.onReport(report);
301
- }
302
- };
303
-
304
420
  // src/lib/context/context.ts
421
+ init_plugin_container();
305
422
  var InjectionContext = class _InjectionContext {
306
423
  static {
307
424
  __name(this, "InjectionContext");
@@ -401,18 +518,8 @@ function nodeInject(provider, options) {
401
518
  }
402
519
  __name(nodeInject, "nodeInject");
403
520
 
404
- // src/lib/plugins/diagnostics/default-impl.ts
405
- var DiagnosticsDefaultReporter = class {
406
- static {
407
- __name(this, "DiagnosticsDefaultReporter");
408
- }
409
- onReport(report) {
410
- console.log("[Illuma] \u{1F9F9} Diagnostics:");
411
- console.log(` Total: ${report.totalNodes} node(s)`);
412
- console.log(` ${report.unusedNodes.length} were not used while bootstrap:`);
413
- for (const node of report.unusedNodes) console.log(` - ${node.toString()}`);
414
- }
415
- };
521
+ // src/lib/container/container.ts
522
+ init_plugin_container();
416
523
 
417
524
  // src/lib/provider/extractor.ts
418
525
  function extractProvider(provider) {
@@ -500,7 +607,34 @@ function isNotTransparentProto(proto) {
500
607
  }
501
608
  __name(isNotTransparentProto, "isNotTransparentProto");
502
609
 
610
+ // src/lib/utils/injector.ts
611
+ var InjectorImpl = class {
612
+ static {
613
+ __name(this, "InjectorImpl");
614
+ }
615
+ container;
616
+ constructor(container) {
617
+ this.container = container;
618
+ }
619
+ get(token) {
620
+ return this.container.get(token);
621
+ }
622
+ produce(fn) {
623
+ return this.container.produce(fn);
624
+ }
625
+ };
626
+ var Injector = new NodeToken("Injector");
627
+
503
628
  // src/lib/provider/tree-node.ts
629
+ function runMiddlewares(middlewares, params) {
630
+ const ms = middlewares;
631
+ const next = /* @__PURE__ */ __name((i, current) => {
632
+ if (i >= ms.length) return current.factory();
633
+ return ms[i](current, (nextParams) => next(i + 1, nextParams));
634
+ }, "next");
635
+ return next(0, params);
636
+ }
637
+ __name(runMiddlewares, "runMiddlewares");
504
638
  function retrieverFactory(node, deps, transparentDeps) {
505
639
  return (token, optional) => {
506
640
  const depNode = deps.get(token);
@@ -518,10 +652,12 @@ var TreeRootNode = class {
518
652
  __name(this, "TreeRootNode");
519
653
  }
520
654
  instant;
655
+ middlewares;
521
656
  _deps = /* @__PURE__ */ new Set();
522
657
  _treePool = /* @__PURE__ */ new WeakMap();
523
- constructor(instant = true) {
658
+ constructor(instant = true, middlewares = []) {
524
659
  this.instant = instant;
660
+ this.middlewares = middlewares;
525
661
  }
526
662
  get dependencies() {
527
663
  return this._deps;
@@ -532,14 +668,14 @@ var TreeRootNode = class {
532
668
  build() {
533
669
  for (const dep of this._deps) {
534
670
  if ("token" in dep.proto) this._treePool.set(dep.proto.token, dep);
535
- if (this.instant) dep.instantiate(this._treePool);
671
+ if (this.instant) dep.instantiate(this._treePool, this.middlewares);
536
672
  else dep.collectPool(this._treePool);
537
673
  }
538
674
  }
539
675
  find(token) {
540
676
  const node = this._treePool.get(token);
541
677
  if (!node) return null;
542
- if (!this.instant) node.instantiate(this._treePool);
678
+ if (!this.instant) node.instantiate(this._treePool, this.middlewares);
543
679
  return node;
544
680
  }
545
681
  toString() {
@@ -564,6 +700,10 @@ var TreeNodeSingle = class {
564
700
  }
565
701
  constructor(proto) {
566
702
  this.proto = proto;
703
+ if (proto.token === Injector) {
704
+ this._instance = proto.factory();
705
+ this._resolved = true;
706
+ }
567
707
  }
568
708
  addDependency(node) {
569
709
  if (node instanceof TreeNodeTransparent) this._transparent.add(node);
@@ -575,14 +715,22 @@ var TreeNodeSingle = class {
575
715
  for (const dep of this._transparent) dep.collectPool(pool);
576
716
  pool.set(this.proto.token, this);
577
717
  }
578
- instantiate(pool) {
718
+ instantiate(pool, middlewares = []) {
579
719
  if (this._resolved) return;
580
- for (const node of this._deps.values()) node.instantiate(pool);
581
- for (const dep of this._transparent) dep.instantiate(pool);
720
+ for (const node of this._deps.values()) node.instantiate(pool, middlewares);
721
+ for (const dep of this._transparent) dep.instantiate(pool, middlewares);
582
722
  const retriever = retrieverFactory(this.proto.token, this._deps, this._transparent);
583
723
  const factory = this.proto.factory ?? this.proto.token.opts?.factory;
584
724
  if (!factory) throw InjectionError.notFound(this.proto.token);
585
- this._instance = InjectionContext.instantiate(factory, retriever);
725
+ const contextFactory = /* @__PURE__ */ __name(() => InjectionContext.instantiate(factory, retriever), "contextFactory");
726
+ this._instance = runMiddlewares(middlewares, {
727
+ token: this.proto.token,
728
+ factory: contextFactory,
729
+ deps: /* @__PURE__ */ new Set([
730
+ ...this._deps.keys(),
731
+ ...Array.from(this._transparent).map((n) => n.proto.parent.token)
732
+ ])
733
+ });
586
734
  this._resolved = true;
587
735
  if (pool) pool.set(this.proto.token, this);
588
736
  }
@@ -616,12 +764,20 @@ var TreeNodeTransparent = class _TreeNodeTransparent {
616
764
  for (const node of this._deps.values()) node.collectPool(pool);
617
765
  for (const dep of this._transparent) dep.collectPool(pool);
618
766
  }
619
- instantiate(pool) {
767
+ instantiate(pool, middlewares = []) {
620
768
  if (this._resolved) return;
621
- for (const dep of this._transparent) dep.instantiate(pool);
622
- for (const node of this._deps.values()) node.instantiate(pool);
769
+ for (const dep of this._transparent) dep.instantiate(pool, middlewares);
770
+ for (const node of this._deps.values()) node.instantiate(pool, middlewares);
623
771
  const retriever = retrieverFactory(this.proto.parent.token, this._deps, this._transparent);
624
- this._instance = InjectionContext.instantiate(this.proto.factory, retriever);
772
+ const refFactory = /* @__PURE__ */ __name(() => InjectionContext.instantiate(this.proto.factory, retriever), "refFactory");
773
+ this._instance = runMiddlewares(middlewares, {
774
+ token: this.proto.parent.token,
775
+ factory: refFactory,
776
+ deps: /* @__PURE__ */ new Set([
777
+ ...this._deps.keys(),
778
+ ...Array.from(this._transparent).map((n) => n.proto.parent.token)
779
+ ])
780
+ });
625
781
  this._resolved = true;
626
782
  }
627
783
  toString() {
@@ -644,10 +800,10 @@ var TreeNodeMulti = class _TreeNodeMulti {
644
800
  for (const dep of this._deps) dep.collectPool(pool);
645
801
  pool.set(this.proto.token, this);
646
802
  }
647
- instantiate(pool) {
803
+ instantiate(pool, middlewares = []) {
648
804
  if (this._resolved) return;
649
805
  for (const dep of this._deps) {
650
- dep.instantiate(pool);
806
+ dep.instantiate(pool, middlewares);
651
807
  if (dep instanceof TreeNodeSingle) {
652
808
  this.instance.push(dep.instance);
653
809
  } else if (dep instanceof _TreeNodeMulti) {
@@ -802,24 +958,6 @@ function resolveTreeNode(rootProto, cache, singleNodes, multiNodes, upstreamGett
802
958
  }
803
959
  __name(resolveTreeNode, "resolveTreeNode");
804
960
 
805
- // src/lib/utils/injector.ts
806
- var InjectorImpl = class {
807
- static {
808
- __name(this, "InjectorImpl");
809
- }
810
- container;
811
- constructor(container) {
812
- this.container = container;
813
- }
814
- get(token) {
815
- return this.container.get(token);
816
- }
817
- produce(fn) {
818
- return this.container.produce(fn);
819
- }
820
- };
821
- var Injector = new NodeToken("Injector");
822
-
823
961
  // src/lib/utils/defer.ts
824
962
  function injectDefer(provider, options) {
825
963
  const injector = nodeInject(Injector);
@@ -929,10 +1067,13 @@ var NodeContainer = class extends Illuma {
929
1067
  _multiProtoNodes = /* @__PURE__ */ new Map();
930
1068
  constructor(_opts) {
931
1069
  super(), this._opts = _opts;
932
- this._parent = _opts?.parent;
933
1070
  if (_opts?.diagnostics) {
934
- Illuma.extendDiagnostics(new DiagnosticsDefaultReporter());
1071
+ 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.");
1072
+ const m = (init_built_in(), __toCommonJS(built_in_exports));
1073
+ if (m.enabled) return;
1074
+ m.enableIllumaDiagnostics();
935
1075
  }
1076
+ this._parent = _opts?.parent;
936
1077
  }
937
1078
  /**
938
1079
  * Registers a provider in the container.
@@ -1045,7 +1186,11 @@ var NodeContainer = class extends Illuma {
1045
1186
  return parentNode.findNode(token);
1046
1187
  }
1047
1188
  _buildInjectionTree() {
1048
- const root = new TreeRootNode(this._opts?.instant);
1189
+ const middlewares = [
1190
+ ...Illuma._middlewares,
1191
+ ...this.collectMiddlewares()
1192
+ ];
1193
+ const root = new TreeRootNode(this._opts?.instant, middlewares);
1049
1194
  const cache = /* @__PURE__ */ new Map();
1050
1195
  const nodes = [
1051
1196
  ...this._protoNodes.values(),
@@ -1062,6 +1207,12 @@ var NodeContainer = class extends Illuma {
1062
1207
  this._multiProtoNodes.clear();
1063
1208
  return root;
1064
1209
  }
1210
+ collectMiddlewares() {
1211
+ return [
1212
+ ...this._parent && "collectMiddlewares" in this._parent && typeof this._parent.collectMiddlewares === "function" ? this._parent.collectMiddlewares() : [],
1213
+ ...this.middlewares
1214
+ ];
1215
+ }
1065
1216
  /**
1066
1217
  * Bootstraps the container by resolving the dependency trees and instantiating all providers.
1067
1218
  * This must be called after all providers are registered and before calling {@link get}.
@@ -1097,7 +1248,7 @@ var NodeContainer = class extends Illuma {
1097
1248
  if (this._opts?.measurePerformance) {
1098
1249
  console.log(`[Illuma] \u{1F680} Bootstrapped in ${duration.toFixed(2)} ms`);
1099
1250
  }
1100
- if (this._opts?.diagnostics) {
1251
+ if (this._opts?.diagnostics || Illuma.hasDiagnostics()) {
1101
1252
  const allNodes = this._rootNode.dependencies.size;
1102
1253
  const unusedNodes = Array.from(this._rootNode.dependencies).filter((node) => node.allocations === 0).filter((node) => {
1103
1254
  if (!(node.proto instanceof ProtoNodeSingle)) return true;