@navios/di 0.7.1 → 0.9.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 +110 -0
- package/README.md +117 -17
- package/lib/browser/container/abstract-container.d.mts +112 -0
- package/lib/browser/container/abstract-container.d.mts.map +1 -0
- package/lib/browser/container/abstract-container.mjs +100 -0
- package/lib/browser/container/abstract-container.mjs.map +1 -0
- package/lib/browser/container/container.d.mts +100 -0
- package/lib/browser/container/container.d.mts.map +1 -0
- package/lib/browser/container/container.mjs +424 -0
- package/lib/browser/container/container.mjs.map +1 -0
- package/lib/browser/container/scoped-container.d.mts +93 -0
- package/lib/browser/container/scoped-container.d.mts.map +1 -0
- package/lib/browser/container/scoped-container.mjs +119 -0
- package/lib/browser/container/scoped-container.mjs.map +1 -0
- package/lib/browser/decorators/factory.decorator.d.mts +26 -0
- package/lib/browser/decorators/factory.decorator.d.mts.map +1 -0
- package/lib/browser/decorators/factory.decorator.mjs +20 -0
- package/lib/browser/decorators/factory.decorator.mjs.map +1 -0
- package/lib/browser/decorators/injectable.decorator.d.mts +38 -0
- package/lib/browser/decorators/injectable.decorator.d.mts.map +1 -0
- package/lib/browser/decorators/injectable.decorator.mjs +21 -0
- package/lib/browser/decorators/injectable.decorator.mjs.map +1 -0
- package/lib/browser/enums/injectable-scope.enum.d.mts +18 -0
- package/lib/browser/enums/injectable-scope.enum.d.mts.map +1 -0
- package/lib/browser/enums/injectable-scope.enum.mjs +20 -0
- package/lib/browser/enums/injectable-scope.enum.mjs.map +1 -0
- package/lib/browser/enums/injectable-type.enum.d.mts +8 -0
- package/lib/browser/enums/injectable-type.enum.d.mts.map +1 -0
- package/lib/browser/enums/injectable-type.enum.mjs +10 -0
- package/lib/browser/enums/injectable-type.enum.mjs.map +1 -0
- package/lib/browser/errors/di-error.d.mts +43 -0
- package/lib/browser/errors/di-error.d.mts.map +1 -0
- package/lib/browser/errors/di-error.mjs +98 -0
- package/lib/browser/errors/di-error.mjs.map +1 -0
- package/lib/browser/event-emitter.d.mts +16 -0
- package/lib/browser/event-emitter.d.mts.map +1 -0
- package/lib/browser/event-emitter.mjs +320 -0
- package/lib/browser/event-emitter.mjs.map +1 -0
- package/lib/browser/index.d.mts +37 -1508
- package/lib/browser/index.mjs +29 -2650
- package/lib/browser/interfaces/container.interface.d.mts +59 -0
- package/lib/browser/interfaces/container.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/factory.interface.d.mts +14 -0
- package/lib/browser/interfaces/factory.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/on-service-destroy.interface.d.mts +7 -0
- package/lib/browser/interfaces/on-service-destroy.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/on-service-init.interface.d.mts +7 -0
- package/lib/browser/interfaces/on-service-init.interface.d.mts.map +1 -0
- package/lib/browser/internal/context/async-local-storage.browser.mjs +20 -0
- package/lib/browser/internal/context/async-local-storage.browser.mjs.map +1 -0
- package/lib/browser/internal/context/async-local-storage.d.mts +9 -0
- package/lib/browser/internal/context/async-local-storage.d.mts.map +1 -0
- package/lib/browser/internal/context/async-local-storage.types.d.mts +11 -0
- package/lib/browser/internal/context/async-local-storage.types.d.mts.map +1 -0
- package/lib/browser/internal/context/factory-context.d.mts +23 -0
- package/lib/browser/internal/context/factory-context.d.mts.map +1 -0
- package/lib/browser/internal/context/resolution-context.d.mts +43 -0
- package/lib/browser/internal/context/resolution-context.d.mts.map +1 -0
- package/lib/browser/internal/context/resolution-context.mjs +56 -0
- package/lib/browser/internal/context/resolution-context.mjs.map +1 -0
- package/lib/browser/internal/context/service-initialization-context.d.mts +48 -0
- package/lib/browser/internal/context/service-initialization-context.d.mts.map +1 -0
- package/lib/browser/internal/context/sync-local-storage.mjs +53 -0
- package/lib/browser/internal/context/sync-local-storage.mjs.map +1 -0
- package/lib/browser/internal/core/instance-resolver.d.mts +119 -0
- package/lib/browser/internal/core/instance-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/instance-resolver.mjs +306 -0
- package/lib/browser/internal/core/instance-resolver.mjs.map +1 -0
- package/lib/browser/internal/core/name-resolver.d.mts +52 -0
- package/lib/browser/internal/core/name-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/name-resolver.mjs +118 -0
- package/lib/browser/internal/core/name-resolver.mjs.map +1 -0
- package/lib/browser/internal/core/scope-tracker.d.mts +65 -0
- package/lib/browser/internal/core/scope-tracker.d.mts.map +1 -0
- package/lib/browser/internal/core/scope-tracker.mjs +120 -0
- package/lib/browser/internal/core/scope-tracker.mjs.map +1 -0
- package/lib/browser/internal/core/service-initializer.d.mts +44 -0
- package/lib/browser/internal/core/service-initializer.d.mts.map +1 -0
- package/lib/browser/internal/core/service-initializer.mjs +109 -0
- package/lib/browser/internal/core/service-initializer.mjs.map +1 -0
- package/lib/browser/internal/core/service-invalidator.d.mts +81 -0
- package/lib/browser/internal/core/service-invalidator.d.mts.map +1 -0
- package/lib/browser/internal/core/service-invalidator.mjs +142 -0
- package/lib/browser/internal/core/service-invalidator.mjs.map +1 -0
- package/lib/browser/internal/core/token-resolver.d.mts +54 -0
- package/lib/browser/internal/core/token-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/token-resolver.mjs +77 -0
- package/lib/browser/internal/core/token-resolver.mjs.map +1 -0
- package/lib/browser/internal/holder/holder-storage.interface.d.mts +99 -0
- package/lib/browser/internal/holder/holder-storage.interface.d.mts.map +1 -0
- package/lib/browser/internal/holder/instance-holder.d.mts +101 -0
- package/lib/browser/internal/holder/instance-holder.d.mts.map +1 -0
- package/lib/browser/internal/holder/instance-holder.mjs +19 -0
- package/lib/browser/internal/holder/instance-holder.mjs.map +1 -0
- package/lib/browser/internal/holder/unified-storage.d.mts +53 -0
- package/lib/browser/internal/holder/unified-storage.d.mts.map +1 -0
- package/lib/browser/internal/holder/unified-storage.mjs +144 -0
- package/lib/browser/internal/holder/unified-storage.mjs.map +1 -0
- package/lib/browser/internal/lifecycle/circular-detector.d.mts +39 -0
- package/lib/browser/internal/lifecycle/circular-detector.d.mts.map +1 -0
- package/lib/browser/internal/lifecycle/circular-detector.mjs +55 -0
- package/lib/browser/internal/lifecycle/circular-detector.mjs.map +1 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts +18 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts.map +1 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs +43 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs.map +1 -0
- package/lib/browser/internal/stub-factory-class.d.mts +14 -0
- package/lib/browser/internal/stub-factory-class.d.mts.map +1 -0
- package/lib/browser/internal/stub-factory-class.mjs +18 -0
- package/lib/browser/internal/stub-factory-class.mjs.map +1 -0
- package/lib/browser/symbols/injectable-token.d.mts +5 -0
- package/lib/browser/symbols/injectable-token.d.mts.map +1 -0
- package/lib/browser/symbols/injectable-token.mjs +6 -0
- package/lib/browser/symbols/injectable-token.mjs.map +1 -0
- package/lib/browser/token/injection-token.d.mts +55 -0
- package/lib/browser/token/injection-token.d.mts.map +1 -0
- package/lib/browser/token/injection-token.mjs +100 -0
- package/lib/browser/token/injection-token.mjs.map +1 -0
- package/lib/browser/token/registry.d.mts +37 -0
- package/lib/browser/token/registry.d.mts.map +1 -0
- package/lib/browser/token/registry.mjs +86 -0
- package/lib/browser/token/registry.mjs.map +1 -0
- package/lib/browser/utils/default-injectors.d.mts +12 -0
- package/lib/browser/utils/default-injectors.d.mts.map +1 -0
- package/lib/browser/utils/default-injectors.mjs +13 -0
- package/lib/browser/utils/default-injectors.mjs.map +1 -0
- package/lib/browser/utils/get-injectable-token.d.mts +9 -0
- package/lib/browser/utils/get-injectable-token.d.mts.map +1 -0
- package/lib/browser/utils/get-injectable-token.mjs +13 -0
- package/lib/browser/utils/get-injectable-token.mjs.map +1 -0
- package/lib/browser/utils/get-injectors.d.mts +55 -0
- package/lib/browser/utils/get-injectors.d.mts.map +1 -0
- package/lib/browser/utils/get-injectors.mjs +121 -0
- package/lib/browser/utils/get-injectors.mjs.map +1 -0
- package/lib/browser/utils/types.d.mts +23 -0
- package/lib/browser/utils/types.d.mts.map +1 -0
- package/lib/{container-Pb_Y4Z4x.mjs → container-8-z89TyQ.mjs} +1269 -1305
- package/lib/container-8-z89TyQ.mjs.map +1 -0
- package/lib/{container-BuAutHGg.d.mts → container-CNiqesCL.d.mts} +600 -569
- package/lib/container-CNiqesCL.d.mts.map +1 -0
- package/lib/{container-DnzgpfBe.cjs → container-CaY2fDuk.cjs} +1287 -1329
- package/lib/container-CaY2fDuk.cjs.map +1 -0
- package/lib/{container-oGTgX2iX.d.cts → container-D-0Ho3qL.d.cts} +601 -565
- package/lib/container-D-0Ho3qL.d.cts.map +1 -0
- package/lib/index.cjs +13 -15
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +58 -223
- package/lib/index.d.cts.map +1 -1
- package/lib/index.d.mts +62 -222
- package/lib/index.d.mts.map +1 -1
- package/lib/index.mjs +5 -6
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.cjs +569 -311
- package/lib/testing/index.cjs.map +1 -1
- package/lib/testing/index.d.cts +370 -41
- package/lib/testing/index.d.cts.map +1 -1
- package/lib/testing/index.d.mts +370 -41
- package/lib/testing/index.d.mts.map +1 -1
- package/lib/testing/index.mjs +568 -305
- package/lib/testing/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/circular-detector.spec.mts +193 -0
- package/src/__tests__/concurrent.spec.mts +368 -0
- package/src/__tests__/container.spec.mts +32 -30
- package/src/__tests__/di-error.spec.mts +351 -0
- package/src/__tests__/e2e.browser.spec.mts +0 -4
- package/src/__tests__/e2e.spec.mts +10 -19
- package/src/__tests__/event-emitter.spec.mts +232 -109
- package/src/__tests__/get-injectors.spec.mts +250 -39
- package/src/__tests__/injection-token.spec.mts +293 -349
- package/src/__tests__/library-findings.spec.mts +8 -8
- package/src/__tests__/registry.spec.mts +358 -210
- package/src/__tests__/resolution-context.spec.mts +255 -0
- package/src/__tests__/scope-tracker.spec.mts +598 -0
- package/src/__tests__/scope-upgrade.spec.mts +808 -0
- package/src/__tests__/scoped-container.spec.mts +595 -0
- package/src/__tests__/test-container.spec.mts +293 -0
- package/src/__tests__/token-resolver.spec.mts +207 -0
- package/src/__tests__/unified-storage.spec.mts +535 -0
- package/src/__tests__/unit-test-container.spec.mts +405 -0
- package/src/__type-tests__/container.spec-d.mts +180 -0
- package/src/__type-tests__/factory.spec-d.mts +15 -3
- package/src/__type-tests__/inject.spec-d.mts +115 -20
- package/src/__type-tests__/injectable.spec-d.mts +69 -52
- package/src/__type-tests__/injection-token.spec-d.mts +176 -0
- package/src/__type-tests__/scoped-container.spec-d.mts +212 -0
- package/src/container/abstract-container.mts +327 -0
- package/src/container/container.mts +142 -170
- package/src/container/scoped-container.mts +126 -208
- package/src/decorators/factory.decorator.mts +16 -11
- package/src/decorators/injectable.decorator.mts +20 -16
- package/src/enums/index.mts +2 -2
- package/src/enums/injectable-scope.enum.mts +1 -0
- package/src/enums/injectable-type.enum.mts +1 -0
- package/src/errors/di-error.mts +96 -0
- package/src/event-emitter.mts +3 -27
- package/src/index.mts +6 -153
- package/src/interfaces/container.interface.mts +13 -0
- package/src/interfaces/factory.interface.mts +1 -1
- package/src/interfaces/index.mts +1 -1
- package/src/internal/context/async-local-storage.mts +3 -2
- package/src/internal/context/async-local-storage.types.mts +1 -0
- package/src/internal/context/factory-context.mts +1 -0
- package/src/internal/context/index.mts +3 -1
- package/src/internal/context/resolution-context.mts +1 -0
- package/src/internal/context/service-initialization-context.mts +43 -0
- package/src/internal/core/index.mts +5 -4
- package/src/internal/core/instance-resolver.mts +461 -292
- package/src/internal/core/name-resolver.mts +196 -0
- package/src/internal/core/scope-tracker.mts +242 -0
- package/src/internal/core/{instantiator.mts → service-initializer.mts} +51 -29
- package/src/internal/core/service-invalidator.mts +290 -0
- package/src/internal/core/{token-processor.mts → token-resolver.mts} +17 -88
- package/src/internal/holder/holder-storage.interface.mts +11 -5
- package/src/internal/holder/index.mts +2 -5
- package/src/internal/holder/instance-holder.mts +1 -3
- package/src/internal/holder/unified-storage.mts +245 -0
- package/src/internal/index.mts +2 -1
- package/src/internal/lifecycle/circular-detector.mts +1 -0
- package/src/internal/lifecycle/index.mts +1 -1
- package/src/internal/lifecycle/lifecycle-event-bus.mts +1 -0
- package/src/internal/stub-factory-class.mts +16 -0
- package/src/symbols/injectable-token.mts +3 -1
- package/src/testing/index.mts +2 -0
- package/src/testing/test-container.mts +546 -85
- package/src/testing/types.mts +117 -0
- package/src/testing/unit-test-container.mts +509 -0
- package/src/token/injection-token.mts +41 -4
- package/src/token/registry.mts +75 -9
- package/src/utils/default-injectors.mts +16 -0
- package/src/utils/get-injectable-token.mts +2 -3
- package/src/utils/get-injectors.mts +26 -15
- package/src/utils/index.mts +3 -1
- package/src/utils/types.mts +1 -0
- package/tsdown.config.mts +11 -1
- package/lib/browser/index.d.mts.map +0 -1
- package/lib/browser/index.mjs.map +0 -1
- package/lib/container-BuAutHGg.d.mts.map +0 -1
- package/lib/container-DnzgpfBe.cjs.map +0 -1
- package/lib/container-Pb_Y4Z4x.mjs.map +0 -1
- package/lib/container-oGTgX2iX.d.cts.map +0 -1
- package/src/__tests__/async-local-storage.browser.spec.mts +0 -166
- package/src/__tests__/async-local-storage.spec.mts +0 -333
- package/src/__tests__/errors.spec.mts +0 -87
- package/src/__tests__/factory.spec.mts +0 -137
- package/src/__tests__/injectable.spec.mts +0 -246
- package/src/__tests__/request-scope.spec.mts +0 -416
- package/src/__tests__/service-instantiator.spec.mts +0 -410
- package/src/__tests__/service-locator-event-bus.spec.mts +0 -242
- package/src/__tests__/service-locator-manager.spec.mts +0 -300
- package/src/__tests__/service-locator.spec.mts +0 -966
- package/src/__tests__/unified-api.spec.mts +0 -130
- package/src/browser.mts +0 -11
- package/src/injectors.mts +0 -18
- package/src/internal/context/request-context.mts +0 -214
- package/src/internal/core/invalidator.mts +0 -437
- package/src/internal/core/service-locator.mts +0 -202
- package/src/internal/holder/base-holder-manager.mts +0 -238
- package/src/internal/holder/holder-manager.mts +0 -85
- package/src/internal/holder/request-storage.mts +0 -134
- package/src/internal/holder/singleton-storage.mts +0 -105
- package/src/testing/README.md +0 -80
- package/src/testing/__tests__/test-container.spec.mts +0 -173
|
@@ -25,19 +25,45 @@ let InjectableType = /* @__PURE__ */ function(InjectableType$1) {
|
|
|
25
25
|
return InjectableType$1;
|
|
26
26
|
}({});
|
|
27
27
|
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/symbols/injectable-token.mts
|
|
30
|
+
const InjectableTokenMeta = /* @__PURE__ */ Symbol.for("InjectableTokenMeta");
|
|
31
|
+
|
|
28
32
|
//#endregion
|
|
29
33
|
//#region src/token/injection-token.mts
|
|
34
|
+
/**
|
|
35
|
+
* Simple hash function for deterministic ID generation
|
|
36
|
+
*/ function simpleHash(str) {
|
|
37
|
+
let hash = 0;
|
|
38
|
+
for (let i = 0; i < str.length; i++) {
|
|
39
|
+
const char = str.charCodeAt(i);
|
|
40
|
+
hash = (hash << 5) - hash + char;
|
|
41
|
+
hash = hash & hash;
|
|
42
|
+
}
|
|
43
|
+
return Math.abs(hash).toString(36);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generate deterministic ID from token name
|
|
47
|
+
*/ function generateTokenId(name, customId) {
|
|
48
|
+
if (customId) return customId;
|
|
49
|
+
let base;
|
|
50
|
+
if (typeof name === "function") base = `${name.name}_${name.toString()}`;
|
|
51
|
+
else if (typeof name === "symbol") base = `symbol_${name.toString()}`;
|
|
52
|
+
else base = `token_${name}`;
|
|
53
|
+
return `${base.split("_")[0]}_${simpleHash(base)}`;
|
|
54
|
+
}
|
|
30
55
|
var InjectionToken = class InjectionToken {
|
|
31
56
|
name;
|
|
32
57
|
schema;
|
|
33
|
-
id
|
|
58
|
+
id;
|
|
34
59
|
formattedName = null;
|
|
35
|
-
constructor(name, schema) {
|
|
60
|
+
constructor(name, schema, customId) {
|
|
36
61
|
this.name = name;
|
|
37
62
|
this.schema = schema;
|
|
63
|
+
this.id = generateTokenId(name, customId);
|
|
38
64
|
}
|
|
39
|
-
static create(name, schema) {
|
|
40
|
-
return new InjectionToken(name, schema);
|
|
65
|
+
static create(name, schema, customId) {
|
|
66
|
+
return new InjectionToken(name, schema, customId);
|
|
41
67
|
}
|
|
42
68
|
static bound(token, value) {
|
|
43
69
|
return new BoundInjectionToken(token, value);
|
|
@@ -106,6 +132,7 @@ var FactoryInjectionToken = class {
|
|
|
106
132
|
var Registry = class {
|
|
107
133
|
parent;
|
|
108
134
|
factories = /* @__PURE__ */ new Map();
|
|
135
|
+
highestPriority = /* @__PURE__ */ new Map();
|
|
109
136
|
constructor(parent) {
|
|
110
137
|
this.parent = parent;
|
|
111
138
|
}
|
|
@@ -115,23 +142,50 @@ var Registry = class {
|
|
|
115
142
|
return false;
|
|
116
143
|
}
|
|
117
144
|
get(token) {
|
|
118
|
-
const factory = this.
|
|
145
|
+
const factory = this.highestPriority.get(token.id);
|
|
119
146
|
if (!factory) {
|
|
120
147
|
if (this.parent) return this.parent.get(token);
|
|
121
148
|
throw new Error(`[Registry] No factory found for ${token.toString()}`);
|
|
122
149
|
}
|
|
123
150
|
return factory;
|
|
124
151
|
}
|
|
125
|
-
|
|
126
|
-
this.factories.
|
|
152
|
+
getAll(token) {
|
|
153
|
+
const records = this.factories.get(token.id);
|
|
154
|
+
if (!records || records.length === 0) {
|
|
155
|
+
if (this.parent) return this.parent.getAll(token);
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
return [...records].sort((a, b) => b.priority - a.priority);
|
|
159
|
+
}
|
|
160
|
+
set(token, scope, target, type, priority = 0) {
|
|
161
|
+
const record = {
|
|
127
162
|
scope,
|
|
128
163
|
originalToken: token,
|
|
129
164
|
target,
|
|
130
|
-
type
|
|
131
|
-
|
|
165
|
+
type,
|
|
166
|
+
priority
|
|
167
|
+
};
|
|
168
|
+
const existing = this.factories.get(token.id) || [];
|
|
169
|
+
existing.push(record);
|
|
170
|
+
this.factories.set(token.id, existing);
|
|
171
|
+
const currentHighest = this.highestPriority.get(token.id);
|
|
172
|
+
if (!currentHighest || priority > currentHighest.priority) this.highestPriority.set(token.id, record);
|
|
132
173
|
}
|
|
133
174
|
delete(token) {
|
|
134
|
-
this.factories.
|
|
175
|
+
const records = this.factories.get(token.id);
|
|
176
|
+
if (records) {
|
|
177
|
+
const deletedHighest = this.highestPriority.get(token.id);
|
|
178
|
+
this.factories.delete(token.id);
|
|
179
|
+
this.highestPriority.delete(token.id);
|
|
180
|
+
if (deletedHighest && records.length > 1) {
|
|
181
|
+
const remaining = records.filter((r) => r.originalToken.id !== deletedHighest.originalToken.id || r.priority !== deletedHighest.priority);
|
|
182
|
+
if (remaining.length > 0) {
|
|
183
|
+
const newHighest = remaining.reduce((max, current) => current.priority > max.priority ? current : max);
|
|
184
|
+
this.highestPriority.set(token.id, newHighest);
|
|
185
|
+
this.factories.set(token.id, remaining);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
135
189
|
}
|
|
136
190
|
/**
|
|
137
191
|
* Updates the scope of an already registered factory.
|
|
@@ -142,29 +196,29 @@ var Registry = class {
|
|
|
142
196
|
* @param scope The new scope to set
|
|
143
197
|
* @returns true if the scope was updated, false if the token was not found
|
|
144
198
|
*/ updateScope(token, scope) {
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
|
|
199
|
+
const records = this.factories.get(token.id);
|
|
200
|
+
if (records && records.length > 0) {
|
|
201
|
+
records.forEach((record) => {
|
|
202
|
+
record.scope = scope;
|
|
203
|
+
});
|
|
204
|
+
const highest = this.highestPriority.get(token.id);
|
|
205
|
+
if (highest) highest.scope = scope;
|
|
148
206
|
return true;
|
|
149
207
|
}
|
|
150
208
|
if (this.parent) return this.parent.updateScope(token, scope);
|
|
151
209
|
return false;
|
|
152
210
|
}
|
|
153
211
|
};
|
|
154
|
-
const globalRegistry = new Registry();
|
|
155
|
-
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/symbols/injectable-token.mts
|
|
158
|
-
const InjectableTokenMeta = Symbol.for("InjectableTokenMeta");
|
|
212
|
+
const globalRegistry = /* @__PURE__ */ new Registry();
|
|
159
213
|
|
|
160
214
|
//#endregion
|
|
161
215
|
//#region src/decorators/injectable.decorator.mts
|
|
162
|
-
function Injectable({ scope = InjectableScope.Singleton, token, schema, registry = globalRegistry } = {}) {
|
|
216
|
+
function Injectable({ scope = InjectableScope.Singleton, token, schema, registry = globalRegistry, priority = 0 } = {}) {
|
|
163
217
|
return (target, context) => {
|
|
164
|
-
if (context && context.kind !== "class" || target instanceof Function && !context) throw new Error("[
|
|
165
|
-
if (schema && token) throw new Error("[
|
|
218
|
+
if (context && context.kind !== "class" || target instanceof Function && !context) throw new Error("[DI] @Injectable decorator can only be used on classes.");
|
|
219
|
+
if (schema && token) throw new Error("[DI] @Injectable decorator cannot have both a token and a schema");
|
|
166
220
|
let injectableToken = token ?? InjectionToken.create(target, schema);
|
|
167
|
-
registry.set(injectableToken, scope, target, InjectableType.Class);
|
|
221
|
+
registry.set(injectableToken, scope, target, InjectableType.Class, priority);
|
|
168
222
|
target[InjectableTokenMeta] = injectableToken;
|
|
169
223
|
return target;
|
|
170
224
|
};
|
|
@@ -172,45 +226,97 @@ function Injectable({ scope = InjectableScope.Singleton, token, schema, registry
|
|
|
172
226
|
|
|
173
227
|
//#endregion
|
|
174
228
|
//#region src/errors/di-error.mts
|
|
175
|
-
|
|
229
|
+
var DIErrorCode = /* @__PURE__ */ function(DIErrorCode$1) {
|
|
176
230
|
DIErrorCode$1["FactoryNotFound"] = "FactoryNotFound";
|
|
177
231
|
DIErrorCode$1["FactoryTokenNotResolved"] = "FactoryTokenNotResolved";
|
|
178
232
|
DIErrorCode$1["InstanceNotFound"] = "InstanceNotFound";
|
|
179
233
|
DIErrorCode$1["InstanceDestroying"] = "InstanceDestroying";
|
|
180
234
|
DIErrorCode$1["CircularDependency"] = "CircularDependency";
|
|
235
|
+
DIErrorCode$1["TokenValidationError"] = "TokenValidationError";
|
|
236
|
+
DIErrorCode$1["TokenSchemaRequiredError"] = "TokenSchemaRequiredError";
|
|
237
|
+
DIErrorCode$1["ClassNotInjectable"] = "ClassNotInjectable";
|
|
238
|
+
DIErrorCode$1["ScopeMismatchError"] = "ScopeMismatchError";
|
|
239
|
+
DIErrorCode$1["PriorityConflictError"] = "PriorityConflictError";
|
|
240
|
+
DIErrorCode$1["StorageError"] = "StorageError";
|
|
241
|
+
DIErrorCode$1["InitializationError"] = "InitializationError";
|
|
242
|
+
DIErrorCode$1["DependencyResolutionError"] = "DependencyResolutionError";
|
|
181
243
|
DIErrorCode$1["UnknownError"] = "UnknownError";
|
|
182
244
|
return DIErrorCode$1;
|
|
183
245
|
}({});
|
|
184
246
|
var DIError = class DIError extends Error {
|
|
247
|
+
code;
|
|
248
|
+
message;
|
|
185
249
|
context;
|
|
186
250
|
constructor(code, message, context) {
|
|
187
|
-
super(message);
|
|
188
|
-
this.code = code;
|
|
189
|
-
this.message = message;
|
|
251
|
+
super(message), this.code = code, this.message = message;
|
|
190
252
|
this.context = context;
|
|
253
|
+
this.name = "DIError";
|
|
191
254
|
}
|
|
192
255
|
static factoryNotFound(name) {
|
|
193
|
-
return new DIError(
|
|
256
|
+
return new DIError("FactoryNotFound", `Factory ${name} not found`, { name });
|
|
194
257
|
}
|
|
195
258
|
static factoryTokenNotResolved(token) {
|
|
196
|
-
return new DIError(
|
|
259
|
+
return new DIError("FactoryTokenNotResolved", `Factory token not resolved: ${token?.toString() ?? "unknown"}`, { token });
|
|
197
260
|
}
|
|
198
261
|
static instanceNotFound(name) {
|
|
199
|
-
return new DIError(
|
|
262
|
+
return new DIError("InstanceNotFound", `Instance ${name} not found`, { name });
|
|
200
263
|
}
|
|
201
264
|
static instanceDestroying(name) {
|
|
202
|
-
return new DIError(
|
|
265
|
+
return new DIError("InstanceDestroying", `Instance ${name} destroying`, { name });
|
|
203
266
|
}
|
|
204
267
|
static unknown(message, context) {
|
|
205
|
-
if (message instanceof Error) return new DIError(
|
|
268
|
+
if (message instanceof Error) return new DIError("UnknownError", message.message, {
|
|
206
269
|
...context,
|
|
207
270
|
parent: message
|
|
208
271
|
});
|
|
209
|
-
return new DIError(
|
|
272
|
+
return new DIError("UnknownError", message, context);
|
|
210
273
|
}
|
|
211
274
|
static circularDependency(cycle) {
|
|
212
|
-
|
|
213
|
-
|
|
275
|
+
return new DIError("CircularDependency", `Circular dependency detected: ${cycle.join(" -> ")}`, { cycle });
|
|
276
|
+
}
|
|
277
|
+
static tokenValidationError(message, schema, value) {
|
|
278
|
+
return new DIError("TokenValidationError", message, {
|
|
279
|
+
schema,
|
|
280
|
+
value
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
static tokenSchemaRequiredError(token) {
|
|
284
|
+
return new DIError("TokenSchemaRequiredError", `Token ${token?.toString() ?? "unknown"} requires schema arguments and cannot be used with addInstance. Use BoundInjectionToken or provide arguments when resolving.`, { token });
|
|
285
|
+
}
|
|
286
|
+
static classNotInjectable(className) {
|
|
287
|
+
return new DIError("ClassNotInjectable", `Class ${className} is not decorated with @Injectable.`, { className });
|
|
288
|
+
}
|
|
289
|
+
static scopeMismatchError(token, expectedScope, actualScope) {
|
|
290
|
+
return new DIError("ScopeMismatchError", `Scope mismatch for ${token?.toString() ?? "unknown"}: expected ${expectedScope}, got ${actualScope}`, {
|
|
291
|
+
token,
|
|
292
|
+
expectedScope,
|
|
293
|
+
actualScope
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
static priorityConflictError(token, records) {
|
|
297
|
+
return new DIError("PriorityConflictError", `Priority conflict for ${token?.toString() ?? "unknown"}: multiple bindings with same priority`, {
|
|
298
|
+
token,
|
|
299
|
+
records
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
static storageError(message, operation, instanceName) {
|
|
303
|
+
return new DIError("StorageError", `Storage error: ${message}`, {
|
|
304
|
+
operation,
|
|
305
|
+
instanceName
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
static initializationError(serviceName, error) {
|
|
309
|
+
return new DIError("InitializationError", `Service ${serviceName} initialization failed: ${error instanceof Error ? error.message : error}`, {
|
|
310
|
+
serviceName,
|
|
311
|
+
error
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
static dependencyResolutionError(serviceName, dependencyName, error) {
|
|
315
|
+
return new DIError("DependencyResolutionError", `Failed to resolve dependency ${dependencyName} for service ${serviceName}: ${error instanceof Error ? error.message : error}`, {
|
|
316
|
+
serviceName,
|
|
317
|
+
dependencyName,
|
|
318
|
+
error
|
|
319
|
+
});
|
|
214
320
|
}
|
|
215
321
|
};
|
|
216
322
|
|
|
@@ -240,6 +346,9 @@ function getModule() {
|
|
|
240
346
|
function createAsyncLocalStorage() {
|
|
241
347
|
return getModule().createAsyncLocalStorage();
|
|
242
348
|
}
|
|
349
|
+
function isUsingNativeAsyncLocalStorage() {
|
|
350
|
+
return getModule().isUsingNativeAsyncLocalStorage();
|
|
351
|
+
}
|
|
243
352
|
|
|
244
353
|
//#endregion
|
|
245
354
|
//#region src/internal/context/resolution-context.mts
|
|
@@ -294,135 +403,21 @@ function getResolutionContext() {
|
|
|
294
403
|
}
|
|
295
404
|
|
|
296
405
|
//#endregion
|
|
297
|
-
//#region src/
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (!injectState) throw new Error("[Injector] Trying to make a request outside of a injectable context");
|
|
313
|
-
if (injectState.isFrozen) {
|
|
314
|
-
const idx = injectState.currentIndex++;
|
|
315
|
-
const request$1 = injectState.requests[idx];
|
|
316
|
-
if (request$1.token !== token) throw new Error(`[Injector] Wrong token order. Expected ${request$1.token.toString()} but got ${token.toString()}`);
|
|
317
|
-
return request$1;
|
|
318
|
-
}
|
|
319
|
-
let result = null;
|
|
320
|
-
let error = null;
|
|
321
|
-
const doInject = () => getFactoryContext().inject(token, args).then((r) => {
|
|
322
|
-
result = r;
|
|
323
|
-
return r;
|
|
324
|
-
}).catch((e) => {
|
|
325
|
-
error = e;
|
|
326
|
-
});
|
|
327
|
-
const request = {
|
|
328
|
-
token,
|
|
329
|
-
promise: skipCycleTracking ? withoutResolutionContext(doInject) : doInject(),
|
|
330
|
-
get result() {
|
|
331
|
-
return result;
|
|
332
|
-
},
|
|
333
|
-
get error() {
|
|
334
|
-
return error;
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
injectState.requests.push(request);
|
|
338
|
-
injectState.currentIndex++;
|
|
339
|
-
return request;
|
|
340
|
-
}
|
|
341
|
-
function asyncInject$1(token, args) {
|
|
342
|
-
if (!injectState) throw new Error("[Injector] Trying to access inject outside of a injectable context");
|
|
343
|
-
const request = getRequest(token[InjectableTokenMeta] ?? token, args, true);
|
|
344
|
-
return request.promise.then((result) => {
|
|
345
|
-
if (request.error) throw request.error;
|
|
346
|
-
return result;
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
function wrapSyncInit$1(cb) {
|
|
350
|
-
return (previousState) => {
|
|
351
|
-
const promises = [];
|
|
352
|
-
const originalPromiseCollector = promiseCollector;
|
|
353
|
-
const originalInjectState = injectState;
|
|
354
|
-
injectState = previousState ? {
|
|
355
|
-
...previousState,
|
|
356
|
-
currentIndex: 0
|
|
357
|
-
} : {
|
|
358
|
-
currentIndex: 0,
|
|
359
|
-
isFrozen: false,
|
|
360
|
-
requests: []
|
|
361
|
-
};
|
|
362
|
-
promiseCollector = (promise) => {
|
|
363
|
-
promises.push(promise);
|
|
364
|
-
};
|
|
365
|
-
const result = cb();
|
|
366
|
-
promiseCollector = originalPromiseCollector;
|
|
367
|
-
const newInjectState = {
|
|
368
|
-
...injectState,
|
|
369
|
-
isFrozen: true
|
|
370
|
-
};
|
|
371
|
-
injectState = originalInjectState;
|
|
372
|
-
return [
|
|
373
|
-
result,
|
|
374
|
-
promises,
|
|
375
|
-
newInjectState
|
|
376
|
-
];
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
function inject$1(token, args) {
|
|
380
|
-
const realToken = token[InjectableTokenMeta] ?? token;
|
|
381
|
-
if (!injectState) throw new Error("[Injector] Trying to access inject outside of a injectable context");
|
|
382
|
-
const instance = getFactoryContext().container.tryGetSync(realToken, args);
|
|
383
|
-
if (!instance) {
|
|
384
|
-
const request = getRequest(realToken, args);
|
|
385
|
-
if (request.error) throw request.error;
|
|
386
|
-
else if (request.result) return request.result;
|
|
387
|
-
if (promiseCollector) promiseCollector(request.promise);
|
|
388
|
-
return new Proxy({}, { get() {
|
|
389
|
-
throw new Error(`[Injector] Trying to access ${realToken.toString()} before it's initialized, please move the code to a onServiceInit method`);
|
|
390
|
-
} });
|
|
391
|
-
}
|
|
392
|
-
return instance;
|
|
393
|
-
}
|
|
394
|
-
function optional$1(token, args) {
|
|
395
|
-
try {
|
|
396
|
-
return inject$1(token, args);
|
|
397
|
-
} catch {
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return {
|
|
402
|
-
asyncInject: asyncInject$1,
|
|
403
|
-
inject: inject$1,
|
|
404
|
-
optional: optional$1,
|
|
405
|
-
wrapSyncInit: wrapSyncInit$1,
|
|
406
|
-
provideFactoryContext: provideFactoryContext$1
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
//#endregion
|
|
411
|
-
//#region src/utils/get-injectable-token.mts
|
|
412
|
-
function getInjectableToken(target) {
|
|
413
|
-
const token = target[InjectableTokenMeta];
|
|
414
|
-
if (!token) throw new Error(`[ServiceLocator] Class ${target.name} is not decorated with @Injectable.`);
|
|
415
|
-
return token;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
//#endregion
|
|
419
|
-
//#region src/injectors.mts
|
|
420
|
-
const defaultInjectors = getInjectors();
|
|
421
|
-
const asyncInject = defaultInjectors.asyncInject;
|
|
422
|
-
const inject = defaultInjectors.inject;
|
|
423
|
-
const optional = defaultInjectors.optional;
|
|
424
|
-
const wrapSyncInit = defaultInjectors.wrapSyncInit;
|
|
425
|
-
const provideFactoryContext = defaultInjectors.provideFactoryContext;
|
|
406
|
+
//#region src/internal/holder/instance-holder.mts
|
|
407
|
+
/**
|
|
408
|
+
* Represents the lifecycle status of an instance holder.
|
|
409
|
+
*/
|
|
410
|
+
let InstanceStatus = /* @__PURE__ */ function(InstanceStatus$1) {
|
|
411
|
+
/** Instance has been successfully created and is ready for use */
|
|
412
|
+
InstanceStatus$1["Created"] = "created";
|
|
413
|
+
/** Instance is currently being created (async initialization in progress) */
|
|
414
|
+
InstanceStatus$1["Creating"] = "creating";
|
|
415
|
+
/** Instance is being destroyed (cleanup in progress) */
|
|
416
|
+
InstanceStatus$1["Destroying"] = "destroying";
|
|
417
|
+
/** Instance creation failed with an error */
|
|
418
|
+
InstanceStatus$1["Error"] = "error";
|
|
419
|
+
return InstanceStatus$1;
|
|
420
|
+
}({});
|
|
426
421
|
|
|
427
422
|
//#endregion
|
|
428
423
|
//#region src/internal/lifecycle/circular-detector.mts
|
|
@@ -483,784 +478,559 @@ const provideFactoryContext = defaultInjectors.provideFactoryContext;
|
|
|
483
478
|
};
|
|
484
479
|
|
|
485
480
|
//#endregion
|
|
486
|
-
//#region src/internal/
|
|
487
|
-
/**
|
|
488
|
-
* Represents the lifecycle status of an instance holder.
|
|
489
|
-
*/
|
|
490
|
-
let InstanceStatus = /* @__PURE__ */ function(InstanceStatus$1) {
|
|
491
|
-
/** Instance has been successfully created and is ready for use */
|
|
492
|
-
InstanceStatus$1["Created"] = "created";
|
|
493
|
-
/** Instance is currently being created (async initialization in progress) */
|
|
494
|
-
InstanceStatus$1["Creating"] = "creating";
|
|
495
|
-
/** Instance is being destroyed (cleanup in progress) */
|
|
496
|
-
InstanceStatus$1["Destroying"] = "destroying";
|
|
497
|
-
/** Instance creation failed with an error */
|
|
498
|
-
InstanceStatus$1["Error"] = "error";
|
|
499
|
-
return InstanceStatus$1;
|
|
500
|
-
}({});
|
|
501
|
-
|
|
502
|
-
//#endregion
|
|
503
|
-
//#region src/internal/holder/base-holder-manager.mts
|
|
481
|
+
//#region src/internal/core/instance-resolver.mts
|
|
504
482
|
/**
|
|
505
|
-
*
|
|
483
|
+
* Resolves instances from tokens, handling caching, creation, and scope rules.
|
|
506
484
|
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
|
|
485
|
+
* Uses unified storage for both singleton and request-scoped services.
|
|
486
|
+
* Coordinates with ServiceInitializer for actual service creation.
|
|
487
|
+
* Integrates ScopeTracker for automatic scope upgrades.
|
|
488
|
+
*/ var InstanceResolver = class {
|
|
489
|
+
registry;
|
|
490
|
+
storage;
|
|
491
|
+
serviceInitializer;
|
|
492
|
+
tokenResolver;
|
|
493
|
+
nameResolver;
|
|
494
|
+
scopeTracker;
|
|
495
|
+
serviceInvalidator;
|
|
496
|
+
eventBus;
|
|
510
497
|
logger;
|
|
511
|
-
|
|
512
|
-
|
|
498
|
+
constructor(registry, storage, serviceInitializer, tokenResolver, nameResolver, scopeTracker, serviceInvalidator, eventBus, logger = null) {
|
|
499
|
+
this.registry = registry;
|
|
500
|
+
this.storage = storage;
|
|
501
|
+
this.serviceInitializer = serviceInitializer;
|
|
502
|
+
this.tokenResolver = tokenResolver;
|
|
503
|
+
this.nameResolver = nameResolver;
|
|
504
|
+
this.scopeTracker = scopeTracker;
|
|
505
|
+
this.serviceInvalidator = serviceInvalidator;
|
|
506
|
+
this.eventBus = eventBus;
|
|
513
507
|
this.logger = logger;
|
|
514
|
-
this._holders = /* @__PURE__ */ new Map();
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Protected getter for accessing the holders map from subclasses.
|
|
518
|
-
*/ get holders() {
|
|
519
|
-
return this._holders;
|
|
520
508
|
}
|
|
521
509
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
|
|
526
|
-
|
|
510
|
+
* Resolves an instance for the given token and arguments.
|
|
511
|
+
* This method is used for singleton and transient services.
|
|
512
|
+
*
|
|
513
|
+
* @param token The injection token
|
|
514
|
+
* @param args Optional arguments
|
|
515
|
+
* @param contextContainer The container to use for creating context
|
|
516
|
+
* @param requestStorage Optional request storage (for scope upgrades)
|
|
517
|
+
* @param requestId Optional request ID (for scope upgrades)
|
|
518
|
+
*/ async resolveInstance(token, args, contextContainer, requestStorage, requestId) {
|
|
519
|
+
return this.resolveWithStorage(token, args, contextContainer, this.storage, void 0, requestStorage, requestId);
|
|
527
520
|
}
|
|
528
521
|
/**
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
|
|
533
|
-
|
|
522
|
+
* Resolves a request-scoped instance for a ScopedContainer.
|
|
523
|
+
* The service will be stored in the ScopedContainer's request storage.
|
|
524
|
+
*
|
|
525
|
+
* @param token The injection token
|
|
526
|
+
* @param args Optional arguments
|
|
527
|
+
* @param scopedContainer The ScopedContainer that owns the request context
|
|
528
|
+
*/ async resolveRequestScopedInstance(token, args, scopedContainer) {
|
|
529
|
+
return this.resolveWithStorage(token, args, scopedContainer.getParent(), scopedContainer.getParent().getStorage(), scopedContainer, scopedContainer.getStorage(), scopedContainer.getRequestId());
|
|
534
530
|
}
|
|
535
531
|
/**
|
|
536
|
-
*
|
|
537
|
-
|
|
538
|
-
|
|
532
|
+
* Unified resolution method that works with any IHolderStorage.
|
|
533
|
+
* This eliminates duplication between singleton and request-scoped resolution.
|
|
534
|
+
*
|
|
535
|
+
* IMPORTANT: The check-and-store logic is carefully designed to avoid race conditions.
|
|
536
|
+
* The storage check and holder creation must happen synchronously (no awaits between).
|
|
537
|
+
*
|
|
538
|
+
* @param token The injection token
|
|
539
|
+
* @param args Optional arguments
|
|
540
|
+
* @param contextContainer The container for context
|
|
541
|
+
* @param storage The storage strategy to use
|
|
542
|
+
* @param scopedContainer Optional scoped container for request-scoped services
|
|
543
|
+
* @param requestStorage Optional request storage (for scope upgrades)
|
|
544
|
+
* @param requestId Optional request ID (for scope upgrades)
|
|
545
|
+
*/ async resolveWithStorage(token, args, contextContainer, storage, scopedContainer, requestStorage, requestId) {
|
|
546
|
+
const [err, data] = await this.resolveTokenAndPrepareInstanceName(token, args, contextContainer, requestId, scopedContainer);
|
|
547
|
+
if (err) return [err];
|
|
548
|
+
const { instanceName, validatedArgs, realToken, scope } = data;
|
|
549
|
+
const getResult = storage.get(instanceName) ?? requestStorage?.get(instanceName) ?? null;
|
|
550
|
+
const getHolder = (name) => {
|
|
551
|
+
const result = storage.get(name);
|
|
552
|
+
if (result && result[0] === void 0 && result[1]) return result[1];
|
|
553
|
+
if (requestStorage) {
|
|
554
|
+
const reqResult = requestStorage.get(name);
|
|
555
|
+
if (reqResult && reqResult[0] === void 0 && reqResult[1]) return reqResult[1];
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
if (getResult !== null) {
|
|
559
|
+
const [error, holder$1] = getResult;
|
|
560
|
+
if (!error && holder$1) {
|
|
561
|
+
const waiterHolder = getCurrentResolutionContext()?.waiterHolder;
|
|
562
|
+
const readyResult = await this.waitForInstanceReady(holder$1, waiterHolder, getHolder);
|
|
563
|
+
if (readyResult[0]) return [readyResult[0]];
|
|
564
|
+
return [void 0, readyResult[1].instance];
|
|
565
|
+
}
|
|
566
|
+
if (error) {
|
|
567
|
+
const handledResult = await this.handleStorageError(instanceName, error, holder$1, storage);
|
|
568
|
+
if (handledResult) return handledResult;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const [createError, holder] = await this.createAndStoreInstance(instanceName, realToken, validatedArgs, contextContainer, storage, scopedContainer, requestStorage, requestId, scope);
|
|
572
|
+
if (createError) return [createError];
|
|
573
|
+
return [void 0, holder.instance];
|
|
539
574
|
}
|
|
540
575
|
/**
|
|
541
|
-
*
|
|
542
|
-
|
|
543
|
-
|
|
576
|
+
* Internal method to resolve token args and create instance name.
|
|
577
|
+
* Handles factory token resolution and validation.
|
|
578
|
+
*/ async resolveTokenAndPrepareInstanceName(token, args, contextContainer, requestId, scopedContainer) {
|
|
579
|
+
const [err, { actualToken, validatedArgs }] = this.tokenResolver.validateAndResolveTokenArgs(token, args);
|
|
580
|
+
if (err instanceof DIError && err.code === DIErrorCode.TokenValidationError) return [err];
|
|
581
|
+
else if (err instanceof DIError && err.code === DIErrorCode.FactoryTokenNotResolved && actualToken instanceof FactoryInjectionToken) {
|
|
582
|
+
this.logger?.log(`[InstanceResolver]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`);
|
|
583
|
+
const factoryCtx = {
|
|
584
|
+
inject: async (t, a) => (scopedContainer ?? contextContainer).get(t, a),
|
|
585
|
+
container: scopedContainer ?? contextContainer,
|
|
586
|
+
addDestroyListener: () => {}
|
|
587
|
+
};
|
|
588
|
+
await actualToken.resolve(factoryCtx);
|
|
589
|
+
return this.resolveTokenAndPrepareInstanceName(token, void 0, contextContainer, requestId, scopedContainer);
|
|
590
|
+
}
|
|
591
|
+
const realToken = this.tokenResolver.getRealToken(actualToken);
|
|
592
|
+
const scope = this.registry.get(realToken).scope;
|
|
593
|
+
return [void 0, {
|
|
594
|
+
instanceName: this.nameResolver.generateInstanceName(actualToken, validatedArgs, requestId, scope),
|
|
595
|
+
validatedArgs,
|
|
596
|
+
actualToken,
|
|
597
|
+
realToken,
|
|
598
|
+
scope
|
|
599
|
+
}];
|
|
544
600
|
}
|
|
545
601
|
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
602
|
+
* Handles storage error states (destroying, error, etc.).
|
|
603
|
+
* Returns a result if handled, null if should proceed with creation.
|
|
604
|
+
*/ async handleStorageError(instanceName, error, holder, storage) {
|
|
605
|
+
switch (error.code) {
|
|
606
|
+
case DIErrorCode.InstanceDestroying:
|
|
607
|
+
this.logger?.log(`[InstanceResolver] Instance ${instanceName} is being destroyed, waiting...`);
|
|
608
|
+
if (holder?.destroyPromise) await holder.destroyPromise;
|
|
609
|
+
const newResult = storage.get(instanceName);
|
|
610
|
+
if (newResult !== null && !newResult[0]) {
|
|
611
|
+
const getHolder = (name) => {
|
|
612
|
+
const result = storage.get(name);
|
|
613
|
+
return result && result[0] === void 0 && result[1] ? result[1] : void 0;
|
|
614
|
+
};
|
|
615
|
+
const readyResult = await this.waitForInstanceReady(newResult[1], void 0, getHolder);
|
|
616
|
+
if (readyResult[0]) return [readyResult[0]];
|
|
617
|
+
return [void 0, readyResult[1].instance];
|
|
618
|
+
}
|
|
619
|
+
return null;
|
|
620
|
+
default:
|
|
621
|
+
if (holder) {
|
|
622
|
+
this.logger?.log(`[InstanceResolver] Removing failed instance ${instanceName} from storage to allow retry`);
|
|
623
|
+
storage.delete(instanceName);
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
568
627
|
}
|
|
569
628
|
/**
|
|
570
|
-
* Creates a new
|
|
571
|
-
* This
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
629
|
+
* Creates a new instance and stores it using the provided storage strategy.
|
|
630
|
+
* This unified method replaces instantiateServiceFromRegistry and createRequestScopedInstance.
|
|
631
|
+
*
|
|
632
|
+
* For transient services, the instance is created but not stored (no caching).
|
|
633
|
+
*/ async createAndStoreInstance(instanceName, realToken, args, contextContainer, storage, scopedContainer, requestStorage, requestId, scope) {
|
|
634
|
+
this.logger?.log(`[InstanceResolver]#createAndStoreInstance() Creating instance for ${instanceName}`);
|
|
635
|
+
if (!this.registry.has(realToken)) return [DIError.factoryNotFound(realToken.name.toString())];
|
|
636
|
+
const record = this.registry.get(realToken);
|
|
637
|
+
const { type, scope: recordScope } = record;
|
|
638
|
+
const serviceScope = scope || recordScope;
|
|
639
|
+
if (serviceScope === InjectableScope.Transient) return this.createTransientInstance(instanceName, record, args, contextContainer, scopedContainer, requestStorage, requestId);
|
|
640
|
+
if (serviceScope === InjectableScope.Request && !requestStorage) return [DIError.initializationError(`Request storage is required for request-scoped services`, instanceName)];
|
|
641
|
+
let storageToUse;
|
|
642
|
+
if (serviceScope === InjectableScope.Request) storageToUse = requestStorage;
|
|
643
|
+
else storageToUse = storage;
|
|
644
|
+
const [deferred, holder] = storageToUse.createHolder(instanceName, type, /* @__PURE__ */ new Set());
|
|
645
|
+
storageToUse.set(instanceName, holder);
|
|
646
|
+
const ctx = this.createServiceInitializationContext(scopedContainer ?? contextContainer, instanceName, serviceScope, holder.deps, realToken, requestStorage, requestId);
|
|
647
|
+
holder.destroyListeners = ctx.getDestroyListeners();
|
|
648
|
+
const getHolder = (name) => {
|
|
649
|
+
const result = storage.get(name);
|
|
650
|
+
if (result && result[0] === void 0 && result[1]) return result[1];
|
|
651
|
+
if (requestStorage) {
|
|
652
|
+
const reqResult = requestStorage.get(name);
|
|
653
|
+
if (reqResult && reqResult[0] === void 0 && reqResult[1]) return reqResult[1];
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
withResolutionContext(holder, getHolder, () => {
|
|
657
|
+
this.serviceInitializer.instantiateService(ctx, record, args).then(async (result) => {
|
|
658
|
+
const [error, instance] = result.length === 2 ? result : [result[0], void 0];
|
|
659
|
+
const newScope = record.scope;
|
|
660
|
+
const newName = this.nameResolver.generateInstanceName(realToken, args, requestId, newScope);
|
|
661
|
+
await this.handleInstantiationResult(newName, holder, ctx, deferred, newScope, error, instance, scopedContainer, requestStorage, requestId);
|
|
662
|
+
}).catch(async (error) => {
|
|
663
|
+
const newScope = record.scope;
|
|
664
|
+
const newName = this.nameResolver.generateInstanceName(realToken, args, requestId, newScope);
|
|
665
|
+
await this.handleInstantiationError(newName, holder, deferred, newScope, error);
|
|
666
|
+
}).catch(() => {});
|
|
667
|
+
});
|
|
668
|
+
const waiterHolder = getCurrentResolutionContext()?.waiterHolder;
|
|
669
|
+
return this.waitForInstanceReady(holder, waiterHolder, getHolder);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Creates a transient instance without storage or locking.
|
|
673
|
+
* Each call creates a new instance.
|
|
674
|
+
*/ async createTransientInstance(instanceName, record, args, contextContainer, scopedContainer, requestStorage, requestId) {
|
|
675
|
+
this.logger?.log(`[InstanceResolver]#createTransientInstance() Creating transient instance for ${instanceName}`);
|
|
676
|
+
const ctx = this.createServiceInitializationContext(scopedContainer ?? contextContainer, instanceName, InjectableScope.Transient, /* @__PURE__ */ new Set(), record.originalToken, requestStorage, requestId);
|
|
677
|
+
const [error, instance] = await this.serviceInitializer.instantiateService(ctx, record, args);
|
|
678
|
+
if (error) return [error];
|
|
679
|
+
return [void 0, {
|
|
580
680
|
status: InstanceStatus.Created,
|
|
581
|
-
name,
|
|
681
|
+
name: instanceName,
|
|
582
682
|
instance,
|
|
583
683
|
creationPromise: null,
|
|
584
684
|
destroyPromise: null,
|
|
585
|
-
type,
|
|
586
|
-
scope,
|
|
587
|
-
deps,
|
|
588
|
-
destroyListeners:
|
|
685
|
+
type: record.type,
|
|
686
|
+
scope: InjectableScope.Transient,
|
|
687
|
+
deps: ctx.dependencies,
|
|
688
|
+
destroyListeners: ctx.getDestroyListeners(),
|
|
589
689
|
createdAt: Date.now(),
|
|
590
690
|
waitingFor: /* @__PURE__ */ new Set()
|
|
591
|
-
};
|
|
691
|
+
}];
|
|
592
692
|
}
|
|
593
693
|
/**
|
|
594
|
-
*
|
|
595
|
-
*/
|
|
596
|
-
|
|
694
|
+
* Handles successful service instantiation.
|
|
695
|
+
*/ async handleInstantiationSuccess(instanceName, holder, ctx, deferred, instance, _scopedContainer, requestStorage, _requestId) {
|
|
696
|
+
holder.instance = instance;
|
|
697
|
+
holder.status = InstanceStatus.Created;
|
|
698
|
+
const storageForSubscriptions = requestStorage || this.storage;
|
|
699
|
+
if (ctx.dependencies.size > 0) this.serviceInvalidator.setupDependencySubscriptions(instanceName, ctx.dependencies, storageForSubscriptions, holder);
|
|
700
|
+
this.logger?.log(`[InstanceResolver] Instance ${instanceName} created successfully`);
|
|
701
|
+
deferred.resolve([void 0, instance]);
|
|
597
702
|
}
|
|
598
703
|
/**
|
|
599
|
-
*
|
|
600
|
-
*/
|
|
601
|
-
|
|
704
|
+
* Handles service instantiation errors.
|
|
705
|
+
*/ async handleInstantiationError(instanceName, holder, deferred, scope, error) {
|
|
706
|
+
holder.status = InstanceStatus.Error;
|
|
707
|
+
holder.instance = error instanceof DIError ? error : DIError.unknown(error);
|
|
708
|
+
this.logger?.error(`[InstanceResolver] Instance ${instanceName} creation failed:`, error);
|
|
709
|
+
deferred.reject(error instanceof DIError ? error : DIError.unknown(error));
|
|
602
710
|
}
|
|
603
711
|
/**
|
|
604
|
-
*
|
|
605
|
-
*/
|
|
606
|
-
|
|
712
|
+
* Handles instantiation result (success or error).
|
|
713
|
+
*/ async handleInstantiationResult(instanceName, holder, ctx, deferred, scope, error, instance, scopedContainer, requestStorage, requestId) {
|
|
714
|
+
if (error) await this.handleInstantiationError(instanceName, holder, deferred, scope, error);
|
|
715
|
+
else await this.handleInstantiationSuccess(instanceName, holder, ctx, deferred, instance, scopedContainer, requestStorage, requestId);
|
|
607
716
|
}
|
|
608
717
|
/**
|
|
609
|
-
* Waits for
|
|
610
|
-
* This is a shared utility used by both singleton and request-scoped resolution.
|
|
718
|
+
* Waits for an instance holder to be ready and returns the appropriate result.
|
|
611
719
|
*
|
|
612
720
|
* @param holder The holder to wait for
|
|
613
721
|
* @param waiterHolder Optional holder that is doing the waiting (for circular dependency detection)
|
|
614
722
|
* @param getHolder Optional function to retrieve holders by name (required if waiterHolder is provided)
|
|
615
|
-
|
|
616
|
-
*/ static async waitForHolderReady(holder, waiterHolder, getHolder) {
|
|
723
|
+
*/ async waitForInstanceReady(holder, waiterHolder, getHolder) {
|
|
617
724
|
switch (holder.status) {
|
|
618
725
|
case InstanceStatus.Creating:
|
|
619
726
|
if (waiterHolder && getHolder) {
|
|
620
727
|
const cycle = CircularDetector.detectCycle(waiterHolder.name, holder.name, getHolder);
|
|
621
728
|
if (cycle) return [DIError.circularDependency(cycle)];
|
|
622
|
-
waiterHolder.waitingFor.add(holder.name);
|
|
729
|
+
if (process.env.NODE_ENV !== "production") waiterHolder.waitingFor.add(holder.name);
|
|
623
730
|
}
|
|
624
731
|
try {
|
|
625
732
|
await holder.creationPromise;
|
|
626
733
|
} finally {
|
|
627
|
-
if (
|
|
734
|
+
if (process.env.NODE_ENV !== "production") {
|
|
735
|
+
if (waiterHolder) waiterHolder.waitingFor.delete(holder.name);
|
|
736
|
+
}
|
|
628
737
|
}
|
|
629
|
-
return
|
|
738
|
+
return this.waitForInstanceReady(holder, waiterHolder, getHolder);
|
|
630
739
|
case InstanceStatus.Destroying: return [DIError.instanceDestroying(holder.name)];
|
|
631
740
|
case InstanceStatus.Error: return [holder.instance];
|
|
632
741
|
case InstanceStatus.Created: return [void 0, holder];
|
|
633
|
-
default: return [DIError.instanceNotFound("unknown")];
|
|
742
|
+
default: return [DIError.instanceNotFound(holder?.name ?? "unknown")];
|
|
634
743
|
}
|
|
635
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* Creates a ServiceInitializationContext for service instantiation.
|
|
747
|
+
*/ createServiceInitializationContext(container, serviceName, scope, deps, serviceToken, requestStorage, requestId) {
|
|
748
|
+
const destroyListeners = [];
|
|
749
|
+
return {
|
|
750
|
+
inject: async (token, args) => {
|
|
751
|
+
const actualToken = typeof token === "function" ? this.tokenResolver.normalizeToken(token) : token;
|
|
752
|
+
const realToken = this.tokenResolver.getRealToken(actualToken);
|
|
753
|
+
const depScope = this.registry.get(realToken).scope;
|
|
754
|
+
const dependencyRequestId = depScope === InjectableScope.Request ? requestId : void 0;
|
|
755
|
+
const finalDepName = this.nameResolver.generateInstanceName(actualToken, args, dependencyRequestId, depScope);
|
|
756
|
+
if (scope === InjectableScope.Singleton && depScope === InjectableScope.Request && requestStorage && requestId) {
|
|
757
|
+
const [needsUpgrade, newServiceName] = this.scopeTracker.checkAndUpgradeScope(serviceName, scope, finalDepName, depScope, serviceToken, this.storage, requestStorage, requestId);
|
|
758
|
+
if (needsUpgrade && newServiceName) {}
|
|
759
|
+
}
|
|
760
|
+
deps.add(finalDepName);
|
|
761
|
+
return container.get(token, args);
|
|
762
|
+
},
|
|
763
|
+
container,
|
|
764
|
+
addDestroyListener: (listener) => {
|
|
765
|
+
destroyListeners.push(listener);
|
|
766
|
+
},
|
|
767
|
+
getDestroyListeners: () => destroyListeners,
|
|
768
|
+
serviceName,
|
|
769
|
+
dependencies: deps,
|
|
770
|
+
scope,
|
|
771
|
+
trackDependency: (name, depScope) => {
|
|
772
|
+
deps.add(name);
|
|
773
|
+
if (scope === InjectableScope.Singleton && depScope === InjectableScope.Request && requestStorage && requestId) this.scopeTracker.checkAndUpgradeScope(serviceName, scope, name, depScope, serviceToken, this.storage, requestStorage, requestId);
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
636
777
|
};
|
|
637
778
|
|
|
638
779
|
//#endregion
|
|
639
|
-
//#region src/internal/
|
|
780
|
+
//#region src/internal/core/name-resolver.mts
|
|
640
781
|
/**
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
this.
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
/**
|
|
657
|
-
* Public getter for holders to maintain interface compatibility.
|
|
658
|
-
*/ get holders() {
|
|
659
|
-
return this._holders;
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Gets a holder by name. For RequestContext, this is a simple lookup.
|
|
663
|
-
*/ get(name) {
|
|
664
|
-
return this._holders.get(name);
|
|
665
|
-
}
|
|
666
|
-
/**
|
|
667
|
-
* Sets a holder by name.
|
|
668
|
-
*/ set(name, holder) {
|
|
669
|
-
this._holders.set(name, holder);
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Checks if a holder exists by name.
|
|
673
|
-
*/ has(name) {
|
|
674
|
-
return this._holders.has(name);
|
|
782
|
+
* Simple LRU cache for instance name generation.
|
|
783
|
+
* Uses a Map which maintains insertion order for efficient LRU eviction.
|
|
784
|
+
*/ var InstanceNameCache = class {
|
|
785
|
+
cache = /* @__PURE__ */ new Map();
|
|
786
|
+
maxSize;
|
|
787
|
+
constructor(maxSize = 1e3) {
|
|
788
|
+
this.maxSize = maxSize;
|
|
789
|
+
}
|
|
790
|
+
get(key) {
|
|
791
|
+
const value = this.cache.get(key);
|
|
792
|
+
if (value !== void 0) {
|
|
793
|
+
this.cache.delete(key);
|
|
794
|
+
this.cache.set(key, value);
|
|
795
|
+
}
|
|
796
|
+
return value;
|
|
675
797
|
}
|
|
676
|
-
|
|
677
|
-
if (
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
this.
|
|
681
|
-
} else {
|
|
682
|
-
if (!holder) throw new Error("Holder is required when adding an instance by name");
|
|
683
|
-
this._holders.set(instanceName, holder);
|
|
798
|
+
set(key, value) {
|
|
799
|
+
if (this.cache.has(key)) this.cache.delete(key);
|
|
800
|
+
else if (this.cache.size >= this.maxSize) {
|
|
801
|
+
const firstKey = this.cache.keys().next().value;
|
|
802
|
+
if (firstKey !== void 0) this.cache.delete(firstKey);
|
|
684
803
|
}
|
|
804
|
+
this.cache.set(key, value);
|
|
685
805
|
}
|
|
686
806
|
clear() {
|
|
687
|
-
|
|
688
|
-
this.metadata.clear();
|
|
689
|
-
}
|
|
690
|
-
getMetadata(key) {
|
|
691
|
-
return this.metadata.get(key);
|
|
692
|
-
}
|
|
693
|
-
setMetadata(key, value) {
|
|
694
|
-
this.metadata.set(key, value);
|
|
807
|
+
this.cache.clear();
|
|
695
808
|
}
|
|
696
809
|
};
|
|
697
810
|
/**
|
|
698
|
-
*
|
|
699
|
-
*/ function
|
|
700
|
-
|
|
811
|
+
* Simple hash function for deterministic hashing of arguments
|
|
812
|
+
*/ function hashArgs(args) {
|
|
813
|
+
const str = JSON.stringify(args, Object.keys(args || {}).sort());
|
|
814
|
+
let hash = 0;
|
|
815
|
+
for (let i = 0; i < str.length; i++) {
|
|
816
|
+
const char = str.charCodeAt(i);
|
|
817
|
+
hash = (hash << 5) - hash + char;
|
|
818
|
+
hash = hash & hash;
|
|
819
|
+
}
|
|
820
|
+
return Math.abs(hash).toString(36);
|
|
701
821
|
}
|
|
702
|
-
|
|
703
|
-
//#endregion
|
|
704
|
-
//#region src/internal/holder/request-storage.mts
|
|
705
822
|
/**
|
|
706
|
-
*
|
|
823
|
+
* Handles instance name generation with support for requestId and scope.
|
|
707
824
|
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
this.contextHolder = contextHolder;
|
|
716
|
-
this.holderManager = holderManager;
|
|
825
|
+
* Generates unique instance identifiers based on token, arguments, and scope.
|
|
826
|
+
* Request-scoped services MUST include requestId in their name for proper isolation.
|
|
827
|
+
*/ var NameResolver = class {
|
|
828
|
+
logger;
|
|
829
|
+
instanceNameCache = new InstanceNameCache();
|
|
830
|
+
constructor(logger = null) {
|
|
831
|
+
this.logger = logger;
|
|
717
832
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
833
|
+
/**
|
|
834
|
+
* Generates a unique instance name based on token, arguments, requestId, and scope.
|
|
835
|
+
*
|
|
836
|
+
* Name formats:
|
|
837
|
+
* - Singleton/Transient without args: `${tokenId}`
|
|
838
|
+
* - Singleton/Transient with args: `${tokenId}:${argsHash}`
|
|
839
|
+
* - Request without args: `${tokenId}:requestId=${requestId}`
|
|
840
|
+
* - Request with args: `${tokenId}:requestId=${requestId}:${argsHash}`
|
|
841
|
+
*
|
|
842
|
+
* @param token The injection token
|
|
843
|
+
* @param args Optional arguments
|
|
844
|
+
* @param requestId Optional request ID (required for request-scoped services)
|
|
845
|
+
* @param scope Optional scope (used to determine if requestId should be included)
|
|
846
|
+
* @returns The generated instance name
|
|
847
|
+
*/ generateInstanceName(token, args, requestId, scope) {
|
|
848
|
+
const tokenStr = token.toString();
|
|
849
|
+
const isRequest = scope === InjectableScope.Request;
|
|
850
|
+
if (isRequest && !requestId) throw new Error(`[NameResolver] requestId is required for request-scoped services`);
|
|
851
|
+
const cacheKey = `${tokenStr}:${scope}:${requestId || ""}:${args ? JSON.stringify(args) : ""}`;
|
|
852
|
+
const cached = this.instanceNameCache.get(cacheKey);
|
|
853
|
+
if (cached !== void 0) return cached;
|
|
854
|
+
let result = tokenStr;
|
|
855
|
+
if (isRequest && requestId) result = `${result}:requestId=${requestId}`;
|
|
856
|
+
if (args) {
|
|
857
|
+
const argsHash = hashArgs(args);
|
|
858
|
+
result = `${result}:${argsHash}`;
|
|
727
859
|
}
|
|
860
|
+
this.instanceNameCache.set(cacheKey, result);
|
|
861
|
+
return result;
|
|
728
862
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
return
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
findByInstance(instance) {
|
|
750
|
-
for (const holder of this.contextHolder.holders.values()) if (holder.instance === instance) return holder;
|
|
751
|
-
return null;
|
|
863
|
+
/**
|
|
864
|
+
* Upgrades an existing instance name to include requestId.
|
|
865
|
+
* Preserves any args hash that might already be in the name.
|
|
866
|
+
*
|
|
867
|
+
* Examples:
|
|
868
|
+
* - `TokenName` → `TokenName:requestId=req-123`
|
|
869
|
+
* - `TokenName:abc123` → `TokenName:requestId=req-123:abc123`
|
|
870
|
+
*
|
|
871
|
+
* @param existingName The existing instance name (without requestId)
|
|
872
|
+
* @param requestId The request ID to add
|
|
873
|
+
* @returns The upgraded instance name with requestId
|
|
874
|
+
*/ upgradeInstanceNameToRequest(existingName, requestId) {
|
|
875
|
+
if (existingName.includes(`:requestId=${requestId}`)) return existingName;
|
|
876
|
+
if (/:requestId=/.test(existingName)) return existingName;
|
|
877
|
+
const colonIndex = existingName.indexOf(":");
|
|
878
|
+
if (colonIndex === -1) return `${existingName}:requestId=${requestId}`;
|
|
879
|
+
const tokenPart = existingName.substring(0, colonIndex);
|
|
880
|
+
const argsPart = existingName.substring(colonIndex + 1);
|
|
881
|
+
if (argsPart.startsWith("requestId=")) return existingName;
|
|
882
|
+
return `${tokenPart}:requestId=${requestId}:${argsPart}`;
|
|
752
883
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
return
|
|
884
|
+
/**
|
|
885
|
+
* Formats a single argument value for instance name generation.
|
|
886
|
+
*/ formatArgValue(value) {
|
|
887
|
+
if (typeof value === "function") return `fn_${value.name}(${value.length})`;
|
|
888
|
+
if (typeof value === "symbol") return value.toString();
|
|
889
|
+
return JSON.stringify(value).slice(0, 40);
|
|
758
890
|
}
|
|
759
891
|
};
|
|
760
892
|
|
|
761
893
|
//#endregion
|
|
762
|
-
//#region src/
|
|
894
|
+
//#region src/internal/core/scope-tracker.mts
|
|
763
895
|
/**
|
|
764
|
-
*
|
|
896
|
+
* Component for tracking and handling scope upgrades.
|
|
765
897
|
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
|
|
769
|
-
* when multiple requests are processed concurrently.
|
|
770
|
-
*/ var ScopedContainer = class {
|
|
771
|
-
parent;
|
|
898
|
+
* Detects when a Singleton service needs to be upgraded to Request scope
|
|
899
|
+
* and coordinates the scope upgrade process atomically.
|
|
900
|
+
*/ var ScopeTracker = class {
|
|
772
901
|
registry;
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
disposed = false;
|
|
777
|
-
constructor(parent, registry, requestId, metadata, priority = 100) {
|
|
778
|
-
this.parent = parent;
|
|
902
|
+
nameResolver;
|
|
903
|
+
logger;
|
|
904
|
+
constructor(registry, nameResolver, logger = null) {
|
|
779
905
|
this.registry = registry;
|
|
780
|
-
this.
|
|
781
|
-
this.
|
|
782
|
-
this.holderStorage = new RequestStorage(this.requestContextHolder, this.parent.getServiceLocator().getManager());
|
|
783
|
-
}
|
|
784
|
-
/**
|
|
785
|
-
* Gets the request context holder for this scoped container.
|
|
786
|
-
*/ getRequestContextHolder() {
|
|
787
|
-
return this.requestContextHolder;
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Gets the holder storage for this scoped container.
|
|
791
|
-
* Used by InstanceResolver for request-scoped resolution.
|
|
792
|
-
*/ getHolderStorage() {
|
|
793
|
-
return this.holderStorage;
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* Gets the request ID for this scoped container.
|
|
797
|
-
*/ getRequestId() {
|
|
798
|
-
return this.requestId;
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* Gets the parent container.
|
|
802
|
-
*/ getParent() {
|
|
803
|
-
return this.parent;
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Gets metadata from the request context.
|
|
807
|
-
*/ getMetadata(key) {
|
|
808
|
-
return this.requestContextHolder.getMetadata(key);
|
|
809
|
-
}
|
|
810
|
-
/**
|
|
811
|
-
* Sets metadata on the request context.
|
|
812
|
-
*/ setMetadata(key, value) {
|
|
813
|
-
this.requestContextHolder.setMetadata(key, value);
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* Adds a pre-prepared instance to the request context.
|
|
817
|
-
*/ addInstance(token, instance) {
|
|
818
|
-
this.requestContextHolder.addInstance(token, instance);
|
|
819
|
-
}
|
|
820
|
-
async get(token, args) {
|
|
821
|
-
if (this.disposed) throw DIError.unknown(`ScopedContainer for request ${this.requestId} has been disposed`);
|
|
822
|
-
const actualToken = this.parent.getServiceLocator().getTokenProcessor().normalizeToken(token);
|
|
823
|
-
if (this.isRequestScoped(actualToken)) return this.resolveRequestScoped(actualToken, args);
|
|
824
|
-
return this.parent.getWithContext(token, args, this);
|
|
906
|
+
this.nameResolver = nameResolver;
|
|
907
|
+
this.logger = logger;
|
|
825
908
|
}
|
|
826
909
|
/**
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
910
|
+
* Checks if a dependency requires scope upgrade and performs it if needed.
|
|
911
|
+
* Called during service resolution when a dependency is resolved.
|
|
912
|
+
*
|
|
913
|
+
* @param currentServiceName - Name of the service being created
|
|
914
|
+
* @param currentServiceScope - Current scope of the service being created
|
|
915
|
+
* @param dependencyName - Name of the dependency being resolved
|
|
916
|
+
* @param dependencyScope - Scope of the dependency
|
|
917
|
+
* @param dependencyToken - Token of the dependency
|
|
918
|
+
* @param singletonStorage - Singleton storage instance
|
|
919
|
+
* @param requestStorage - Request storage instance (if in request context)
|
|
920
|
+
* @param requestId - Request ID (if in request context)
|
|
921
|
+
* @returns [needsUpgrade: boolean, newName?: string] - whether upgrade occurred and new name
|
|
922
|
+
*/ checkAndUpgradeScope(currentServiceName, currentServiceScope, dependencyName, dependencyScope, dependencyToken, singletonStorage, requestStorage, requestId) {
|
|
923
|
+
if (currentServiceScope !== InjectableScope.Singleton || dependencyScope !== InjectableScope.Request) return [false];
|
|
924
|
+
if (!requestStorage || !requestId) {
|
|
925
|
+
this.logger?.warn(`[ScopeTracker] Cannot upgrade scope for ${currentServiceName}: missing requestStorage or requestId`);
|
|
926
|
+
return [false];
|
|
834
927
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
* Disposes this scoped container and cleans up all request-scoped instances.
|
|
844
|
-
* This is an alias for endRequest() for IContainer compatibility.
|
|
845
|
-
*/ async dispose() {
|
|
846
|
-
await this.endRequest();
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* Ends the request and cleans up all request-scoped instances.
|
|
850
|
-
* Uses the invalidation system to properly cascade to dependent singletons.
|
|
851
|
-
*/ async endRequest() {
|
|
852
|
-
if (this.disposed) return;
|
|
853
|
-
this.disposed = true;
|
|
854
|
-
await this.parent.getServiceLocator().getInvalidator().clearAllWithStorage(this.holderStorage, {
|
|
855
|
-
waitForSettlement: true,
|
|
856
|
-
maxRounds: 10
|
|
857
|
-
});
|
|
858
|
-
this.requestContextHolder.clear();
|
|
859
|
-
this.parent.removeActiveRequest(this.requestId);
|
|
860
|
-
}
|
|
861
|
-
/**
|
|
862
|
-
* Waits for all pending operations to complete.
|
|
863
|
-
*/ async ready() {
|
|
864
|
-
await this.parent.ready();
|
|
928
|
+
this.logger?.log(`[ScopeTracker] Upgrading ${currentServiceName} from Singleton to Request scope`);
|
|
929
|
+
try {
|
|
930
|
+
const [success, newName] = this.upgradeScopeToRequestSync(currentServiceName, dependencyToken, singletonStorage, requestStorage, requestId);
|
|
931
|
+
if (success && newName) return [true, newName];
|
|
932
|
+
} catch (error) {
|
|
933
|
+
this.logger?.error(`[ScopeTracker] Error upgrading scope for ${currentServiceName}:`, error);
|
|
934
|
+
}
|
|
935
|
+
return [false];
|
|
865
936
|
}
|
|
866
937
|
/**
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
* For request-scoped services, checks this container's context first.
|
|
870
|
-
* For other services, delegates to the parent container.
|
|
938
|
+
* Performs the actual scope upgrade from Singleton to Request.
|
|
939
|
+
* This is the core migration logic.
|
|
871
940
|
*
|
|
872
|
-
*
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
941
|
+
* @param serviceName - Current service name (without requestId)
|
|
942
|
+
* @param token - Service injection token
|
|
943
|
+
* @param singletonStorage - Source storage
|
|
944
|
+
* @param requestStorage - Target storage
|
|
945
|
+
* @param requestId - Request ID to include in new name
|
|
946
|
+
* @returns [success: boolean, newName?: string, error?: DIError]
|
|
947
|
+
*/ async upgradeScopeToRequest(serviceName, token, singletonStorage, requestStorage, requestId) {
|
|
948
|
+
try {
|
|
949
|
+
const [success, newName] = this.upgradeScopeToRequestSync(serviceName, token, singletonStorage, requestStorage, requestId);
|
|
950
|
+
if (success && newName) return [true, newName];
|
|
951
|
+
return [
|
|
952
|
+
false,
|
|
953
|
+
void 0,
|
|
954
|
+
DIError.storageError("Scope upgrade failed", "upgradeScopeToRequest", serviceName)
|
|
955
|
+
];
|
|
956
|
+
} catch (error) {
|
|
957
|
+
return [
|
|
958
|
+
false,
|
|
959
|
+
void 0,
|
|
960
|
+
error instanceof DIError ? error : DIError.unknown(error)
|
|
961
|
+
];
|
|
880
962
|
}
|
|
881
|
-
return this.parent.tryGetSync(token, args);
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* Checks if a token is for a request-scoped service.
|
|
885
|
-
*/ isRequestScoped(token) {
|
|
886
|
-
const realToken = this.parent.getServiceLocator().getTokenProcessor().getRealToken(token);
|
|
887
|
-
if (!this.registry.has(realToken)) return false;
|
|
888
|
-
return this.registry.get(realToken).scope === InjectableScope.Request;
|
|
889
963
|
}
|
|
890
964
|
/**
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
*/
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
const [error, readyHolder] = await BaseHolderManager.waitForHolderReady(existingHolder);
|
|
899
|
-
if (error) throw error;
|
|
900
|
-
return readyHolder.instance;
|
|
965
|
+
* Synchronous part of scope upgrade - handles immediate updates.
|
|
966
|
+
* Async operations (like waiting for holder creation) should be done separately.
|
|
967
|
+
*/ upgradeScopeToRequestSync(serviceName, token, singletonStorage, requestStorage, requestId) {
|
|
968
|
+
const newName = this.nameResolver.upgradeInstanceNameToRequest(serviceName, requestId);
|
|
969
|
+
if (!this.registry.updateScope(token, InjectableScope.Request)) {
|
|
970
|
+
this.logger?.warn(`[ScopeTracker] Could not update scope in registry for ${serviceName}`);
|
|
971
|
+
return [false];
|
|
901
972
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
973
|
+
const holderResult = singletonStorage.get(serviceName);
|
|
974
|
+
if (holderResult === null) return [true, newName];
|
|
975
|
+
const [error, holder] = holderResult;
|
|
976
|
+
if (error) {
|
|
977
|
+
this.logger?.warn(`[ScopeTracker] Holder for ${serviceName} is in error state: ${error.message}`);
|
|
978
|
+
return [false];
|
|
979
|
+
}
|
|
980
|
+
if (!holder) return [false];
|
|
981
|
+
if (holder.status === InstanceStatus.Creating) {
|
|
982
|
+
holder.name = newName;
|
|
983
|
+
requestStorage.set(newName, holder);
|
|
984
|
+
singletonStorage.delete(serviceName);
|
|
985
|
+
this.updateParentDependencies(serviceName, newName, singletonStorage, requestStorage);
|
|
986
|
+
return [true, newName];
|
|
987
|
+
}
|
|
988
|
+
holder.name = newName;
|
|
989
|
+
requestStorage.set(newName, holder);
|
|
990
|
+
singletonStorage.delete(serviceName);
|
|
991
|
+
this.updateParentDependencies(serviceName, newName, singletonStorage, requestStorage);
|
|
992
|
+
return [true, newName];
|
|
915
993
|
}
|
|
916
994
|
/**
|
|
917
|
-
*
|
|
918
|
-
*
|
|
919
|
-
|
|
920
|
-
|
|
995
|
+
* Updates all parent dependencies to reference the new service name.
|
|
996
|
+
*
|
|
997
|
+
* @param oldName - Original service name
|
|
998
|
+
* @param newName - New service name with requestId
|
|
999
|
+
* @param singletonStorage - Singleton storage to check
|
|
1000
|
+
* @param requestStorage - Request storage to check
|
|
1001
|
+
*/ updateParentDependencies(oldName, newName, singletonStorage, requestStorage) {
|
|
1002
|
+
singletonStorage.updateDependencyReference(oldName, newName);
|
|
1003
|
+
if (requestStorage) requestStorage.updateDependencyReference(oldName, newName);
|
|
921
1004
|
}
|
|
922
1005
|
};
|
|
923
1006
|
|
|
924
1007
|
//#endregion
|
|
925
|
-
//#region src/internal/
|
|
1008
|
+
//#region src/internal/core/service-initializer.mts
|
|
926
1009
|
/**
|
|
927
|
-
*
|
|
1010
|
+
* Creates service instances from registry records.
|
|
928
1011
|
*
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
constructor(manager) {
|
|
936
|
-
this.manager = manager;
|
|
1012
|
+
* Handles both class-based (@Injectable) and factory-based (@Factory) services,
|
|
1013
|
+
* managing the instantiation lifecycle including lifecycle hook invocation.
|
|
1014
|
+
*/ var ServiceInitializer = class {
|
|
1015
|
+
injectors;
|
|
1016
|
+
constructor(injectors) {
|
|
1017
|
+
this.injectors = injectors;
|
|
937
1018
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
createHolder(instanceName, type, deps) {
|
|
954
|
-
return this.manager.createCreatingHolder(instanceName, type, this.scope, deps);
|
|
955
|
-
}
|
|
956
|
-
handles(scope) {
|
|
957
|
-
return scope === InjectableScope.Singleton;
|
|
958
|
-
}
|
|
959
|
-
getAllNames() {
|
|
960
|
-
return this.manager.getAllNames();
|
|
961
|
-
}
|
|
962
|
-
forEach(callback) {
|
|
963
|
-
for (const [name, holder] of this.manager.filter(() => true)) callback(name, holder);
|
|
964
|
-
}
|
|
965
|
-
findByInstance(instance) {
|
|
966
|
-
for (const [, holder] of this.manager.filter((h) => h.instance === instance)) return holder;
|
|
967
|
-
return null;
|
|
968
|
-
}
|
|
969
|
-
findDependents(instanceName) {
|
|
970
|
-
const dependents = [];
|
|
971
|
-
for (const [name, holder] of this.manager.filter(() => true)) if (holder.deps.has(instanceName)) dependents.push(name);
|
|
972
|
-
return dependents;
|
|
973
|
-
}
|
|
974
|
-
};
|
|
975
|
-
|
|
976
|
-
//#endregion
|
|
977
|
-
//#region src/internal/core/instance-resolver.mts
|
|
978
|
-
/**
|
|
979
|
-
* Resolves instances from tokens, handling caching, creation, and scope rules.
|
|
980
|
-
*
|
|
981
|
-
* Uses the Storage Strategy pattern for unified singleton/request-scoped handling.
|
|
982
|
-
* Coordinates with Instantiator for actual service creation.
|
|
983
|
-
*/ var InstanceResolver = class {
|
|
984
|
-
registry;
|
|
985
|
-
manager;
|
|
986
|
-
instantiator;
|
|
987
|
-
tokenProcessor;
|
|
988
|
-
logger;
|
|
989
|
-
serviceLocator;
|
|
990
|
-
singletonStorage;
|
|
991
|
-
constructor(registry, manager, instantiator, tokenProcessor, logger = null, serviceLocator) {
|
|
992
|
-
this.registry = registry;
|
|
993
|
-
this.manager = manager;
|
|
994
|
-
this.instantiator = instantiator;
|
|
995
|
-
this.tokenProcessor = tokenProcessor;
|
|
996
|
-
this.logger = logger;
|
|
997
|
-
this.serviceLocator = serviceLocator;
|
|
998
|
-
this.singletonStorage = new SingletonStorage(manager);
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Resolves an instance for the given token and arguments.
|
|
1002
|
-
* This method is used for singleton and transient services.
|
|
1003
|
-
*
|
|
1004
|
-
* @param token The injection token
|
|
1005
|
-
* @param args Optional arguments
|
|
1006
|
-
* @param contextContainer The container to use for creating FactoryContext
|
|
1007
|
-
*/ async resolveInstance(token, args, contextContainer) {
|
|
1008
|
-
return this.resolveWithStorage(token, args, contextContainer, this.singletonStorage);
|
|
1009
|
-
}
|
|
1010
|
-
/**
|
|
1011
|
-
* Resolves a request-scoped instance for a ScopedContainer.
|
|
1012
|
-
* The service will be stored in the ScopedContainer's request context.
|
|
1013
|
-
*
|
|
1014
|
-
* @param token The injection token
|
|
1015
|
-
* @param args Optional arguments
|
|
1016
|
-
* @param scopedContainer The ScopedContainer that owns the request context
|
|
1017
|
-
*/ async resolveRequestScopedInstance(token, args, scopedContainer) {
|
|
1018
|
-
return this.resolveWithStorage(token, args, scopedContainer, scopedContainer.getHolderStorage(), scopedContainer);
|
|
1019
|
-
}
|
|
1020
|
-
/**
|
|
1021
|
-
* Unified resolution method that works with any IHolderStorage.
|
|
1022
|
-
* This eliminates duplication between singleton and request-scoped resolution.
|
|
1023
|
-
*
|
|
1024
|
-
* IMPORTANT: The check-and-store logic is carefully designed to avoid race conditions.
|
|
1025
|
-
* The storage check and holder creation must happen synchronously (no awaits between).
|
|
1026
|
-
*
|
|
1027
|
-
* @param token The injection token
|
|
1028
|
-
* @param args Optional arguments
|
|
1029
|
-
* @param contextContainer The container for FactoryContext
|
|
1030
|
-
* @param storage The storage strategy to use
|
|
1031
|
-
* @param scopedContainer Optional scoped container for request-scoped services
|
|
1032
|
-
*/ async resolveWithStorage(token, args, contextContainer, storage, scopedContainer) {
|
|
1033
|
-
const [err, data] = await this.resolveTokenAndPrepareInstanceName(token, args, contextContainer);
|
|
1034
|
-
if (err) return [err];
|
|
1035
|
-
const { instanceName, validatedArgs, realToken } = data;
|
|
1036
|
-
const getResult = storage.get(instanceName);
|
|
1037
|
-
if (getResult !== null) {
|
|
1038
|
-
const [error, holder$1] = getResult;
|
|
1039
|
-
if (!error && holder$1) {
|
|
1040
|
-
const readyResult = await this.waitForInstanceReady(holder$1);
|
|
1041
|
-
if (readyResult[0]) return [readyResult[0]];
|
|
1042
|
-
return [void 0, readyResult[1].instance];
|
|
1043
|
-
}
|
|
1044
|
-
if (error) {
|
|
1045
|
-
const handledResult = await this.handleStorageError(instanceName, error, holder$1, storage);
|
|
1046
|
-
if (handledResult) return handledResult;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
const [createError, holder] = await this.createAndStoreInstance(instanceName, realToken, validatedArgs, contextContainer, storage, scopedContainer);
|
|
1050
|
-
if (createError) return [createError];
|
|
1051
|
-
return [void 0, holder.instance];
|
|
1052
|
-
}
|
|
1053
|
-
/**
|
|
1054
|
-
* Handles storage error states (destroying, error, etc.).
|
|
1055
|
-
* Returns a result if handled, null if should proceed with creation.
|
|
1056
|
-
*/ async handleStorageError(instanceName, error, holder, storage) {
|
|
1057
|
-
switch (error.code) {
|
|
1058
|
-
case DIErrorCode.InstanceDestroying:
|
|
1059
|
-
this.logger?.log(`[InstanceResolver] Instance ${instanceName} is being destroyed, waiting...`);
|
|
1060
|
-
if (holder?.destroyPromise) await holder.destroyPromise;
|
|
1061
|
-
const newResult = storage.get(instanceName);
|
|
1062
|
-
if (newResult !== null && !newResult[0]) {
|
|
1063
|
-
const readyResult = await this.waitForInstanceReady(newResult[1]);
|
|
1064
|
-
if (readyResult[0]) return [readyResult[0]];
|
|
1065
|
-
return [void 0, readyResult[1].instance];
|
|
1066
|
-
}
|
|
1067
|
-
return null;
|
|
1068
|
-
default:
|
|
1069
|
-
if (holder) {
|
|
1070
|
-
this.logger?.log(`[InstanceResolver] Removing failed instance ${instanceName} from storage to allow retry`);
|
|
1071
|
-
storage.delete(instanceName);
|
|
1072
|
-
}
|
|
1073
|
-
return null;
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Creates a new instance and stores it using the provided storage strategy.
|
|
1078
|
-
* This unified method replaces instantiateServiceFromRegistry and createRequestScopedInstance.
|
|
1079
|
-
*
|
|
1080
|
-
* For transient services, the instance is created but not stored (no caching).
|
|
1081
|
-
*/ async createAndStoreInstance(instanceName, realToken, args, contextContainer, storage, scopedContainer) {
|
|
1082
|
-
this.logger?.log(`[InstanceResolver]#createAndStoreInstance() Creating instance for ${instanceName}`);
|
|
1083
|
-
if (!this.registry.has(realToken)) return [DIError.factoryNotFound(realToken.name.toString())];
|
|
1084
|
-
const ctx = this.createFactoryContext(contextContainer);
|
|
1085
|
-
const record = this.registry.get(realToken);
|
|
1086
|
-
const { scope, type } = record;
|
|
1087
|
-
if (scope === InjectableScope.Transient) return this.createTransientInstance(instanceName, record, args, ctx);
|
|
1088
|
-
const [deferred, holder] = this.manager.createCreatingHolder(instanceName, type, scope, ctx.deps);
|
|
1089
|
-
storage.set(instanceName, holder);
|
|
1090
|
-
const getHolder = (name) => {
|
|
1091
|
-
const storageResult = storage.get(name);
|
|
1092
|
-
if (storageResult !== null) {
|
|
1093
|
-
const [, storageHolder] = storageResult;
|
|
1094
|
-
if (storageHolder) return storageHolder;
|
|
1095
|
-
}
|
|
1096
|
-
const [, managerHolder] = this.manager.get(name);
|
|
1097
|
-
return managerHolder;
|
|
1098
|
-
};
|
|
1099
|
-
withResolutionContext(holder, getHolder, () => {
|
|
1100
|
-
this.instantiator.instantiateService(ctx, record, args).then(async (result) => {
|
|
1101
|
-
const [error, instance] = result.length === 2 ? result : [result[0], void 0];
|
|
1102
|
-
await this.handleInstantiationResult(instanceName, holder, ctx, deferred, scope, error, instance, scopedContainer);
|
|
1103
|
-
}).catch(async (error) => {
|
|
1104
|
-
await this.handleInstantiationError(instanceName, holder, deferred, scope, error);
|
|
1105
|
-
}).catch(() => {});
|
|
1106
|
-
});
|
|
1107
|
-
return this.waitForInstanceReady(holder);
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Creates a transient instance without storage or locking.
|
|
1111
|
-
* Each call creates a new instance.
|
|
1112
|
-
*/ async createTransientInstance(instanceName, record, args, ctx) {
|
|
1113
|
-
this.logger?.log(`[InstanceResolver]#createTransientInstance() Creating transient instance for ${instanceName}`);
|
|
1114
|
-
const tempHolder = {
|
|
1115
|
-
status: InstanceStatus.Creating,
|
|
1116
|
-
name: instanceName,
|
|
1117
|
-
instance: null,
|
|
1118
|
-
creationPromise: null,
|
|
1119
|
-
destroyPromise: null,
|
|
1120
|
-
type: record.type,
|
|
1121
|
-
scope: InjectableScope.Transient,
|
|
1122
|
-
deps: ctx.deps,
|
|
1123
|
-
destroyListeners: [],
|
|
1124
|
-
createdAt: Date.now(),
|
|
1125
|
-
waitingFor: /* @__PURE__ */ new Set()
|
|
1126
|
-
};
|
|
1127
|
-
const getHolder = (name) => {
|
|
1128
|
-
const [, managerHolder] = this.manager.get(name);
|
|
1129
|
-
return managerHolder;
|
|
1130
|
-
};
|
|
1131
|
-
const [error, instance] = await withResolutionContext(tempHolder, getHolder, () => this.instantiator.instantiateService(ctx, record, args));
|
|
1132
|
-
if (error) return [error];
|
|
1133
|
-
return [void 0, {
|
|
1134
|
-
status: InstanceStatus.Created,
|
|
1135
|
-
name: instanceName,
|
|
1136
|
-
instance,
|
|
1137
|
-
creationPromise: null,
|
|
1138
|
-
destroyPromise: null,
|
|
1139
|
-
type: record.type,
|
|
1140
|
-
scope: InjectableScope.Transient,
|
|
1141
|
-
deps: ctx.deps,
|
|
1142
|
-
destroyListeners: ctx.getDestroyListeners(),
|
|
1143
|
-
createdAt: Date.now(),
|
|
1144
|
-
waitingFor: /* @__PURE__ */ new Set()
|
|
1145
|
-
}];
|
|
1146
|
-
}
|
|
1147
|
-
/**
|
|
1148
|
-
* Gets a synchronous instance (for sync operations).
|
|
1149
|
-
*/ getSyncInstance(token, args, contextContainer) {
|
|
1150
|
-
const [err, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
1151
|
-
if (err) return null;
|
|
1152
|
-
const instanceName = this.tokenProcessor.generateInstanceName(actualToken, validatedArgs);
|
|
1153
|
-
if ("getRequestInstance" in contextContainer) {
|
|
1154
|
-
const requestHolder = contextContainer.getRequestInstance(instanceName);
|
|
1155
|
-
if (requestHolder) return requestHolder.instance;
|
|
1156
|
-
}
|
|
1157
|
-
const [error, holder] = this.manager.get(instanceName);
|
|
1158
|
-
if (error) return null;
|
|
1159
|
-
return holder.instance;
|
|
1160
|
-
}
|
|
1161
|
-
/**
|
|
1162
|
-
* Internal method to resolve token args and create instance name.
|
|
1163
|
-
* Handles factory token resolution and validation.
|
|
1164
|
-
*/ async resolveTokenAndPrepareInstanceName(token, args, contextContainer) {
|
|
1165
|
-
const [err, { actualToken, validatedArgs }] = this.tokenProcessor.validateAndResolveTokenArgs(token, args);
|
|
1166
|
-
if (err instanceof DIError && err.code === DIErrorCode.UnknownError) return [err];
|
|
1167
|
-
else if (err instanceof DIError && err.code === DIErrorCode.FactoryTokenNotResolved && actualToken instanceof FactoryInjectionToken) {
|
|
1168
|
-
this.logger?.log(`[InstanceResolver]#resolveTokenAndPrepareInstanceName() Factory token not resolved, resolving it`);
|
|
1169
|
-
await actualToken.resolve(this.createFactoryContext(contextContainer));
|
|
1170
|
-
return this.resolveTokenAndPrepareInstanceName(token, void 0, contextContainer);
|
|
1171
|
-
}
|
|
1172
|
-
return [void 0, {
|
|
1173
|
-
instanceName: this.tokenProcessor.generateInstanceName(actualToken, validatedArgs),
|
|
1174
|
-
validatedArgs,
|
|
1175
|
-
actualToken,
|
|
1176
|
-
realToken: actualToken instanceof BoundInjectionToken || actualToken instanceof FactoryInjectionToken ? actualToken.token : actualToken
|
|
1177
|
-
}];
|
|
1178
|
-
}
|
|
1179
|
-
/**
|
|
1180
|
-
* Waits for an instance holder to be ready and returns the appropriate result.
|
|
1181
|
-
* Uses the shared utility from BaseHolderManager.
|
|
1182
|
-
* Passes the current resolution context for circular dependency detection.
|
|
1183
|
-
*/ waitForInstanceReady(holder) {
|
|
1184
|
-
const ctx = getCurrentResolutionContext();
|
|
1185
|
-
return BaseHolderManager.waitForHolderReady(holder, ctx?.waiterHolder, ctx?.getHolder);
|
|
1186
|
-
}
|
|
1187
|
-
/**
|
|
1188
|
-
* Handles the result of service instantiation.
|
|
1189
|
-
*/ async handleInstantiationResult(instanceName, holder, ctx, deferred, scope, error, instance, scopedContainer) {
|
|
1190
|
-
holder.destroyListeners = ctx.getDestroyListeners();
|
|
1191
|
-
holder.creationPromise = null;
|
|
1192
|
-
if (error) await this.handleInstantiationError(instanceName, holder, deferred, scope, error);
|
|
1193
|
-
else await this.handleInstantiationSuccess(instanceName, holder, ctx, deferred, instance, scopedContainer);
|
|
1194
|
-
}
|
|
1195
|
-
/**
|
|
1196
|
-
* Handles successful service instantiation.
|
|
1197
|
-
*/ async handleInstantiationSuccess(instanceName, holder, ctx, deferred, instance, scopedContainer) {
|
|
1198
|
-
holder.instance = instance;
|
|
1199
|
-
holder.status = InstanceStatus.Created;
|
|
1200
|
-
if (ctx.deps.size > 0) ctx.deps.forEach((dependency) => {
|
|
1201
|
-
holder.destroyListeners.push(this.serviceLocator.getEventBus().on(dependency, "destroy", () => {
|
|
1202
|
-
this.logger?.log(`[InstanceResolver] Dependency ${dependency} destroyed, invalidating ${instanceName}`);
|
|
1203
|
-
this.serviceLocator.getInvalidator().invalidate(instanceName);
|
|
1204
|
-
}));
|
|
1205
|
-
if (scopedContainer) {
|
|
1206
|
-
const prefixedDependency = scopedContainer.getPrefixedEventName(dependency);
|
|
1207
|
-
holder.destroyListeners.push(this.serviceLocator.getEventBus().on(prefixedDependency, "destroy", () => {
|
|
1208
|
-
this.logger?.log(`[InstanceResolver] Request-scoped dependency ${dependency} destroyed, invalidating ${instanceName}`);
|
|
1209
|
-
scopedContainer.invalidate(instance);
|
|
1210
|
-
}));
|
|
1211
|
-
}
|
|
1212
|
-
});
|
|
1213
|
-
this.logger?.log(`[InstanceResolver] Instance ${instanceName} created successfully`);
|
|
1214
|
-
deferred.resolve([void 0, instance]);
|
|
1215
|
-
}
|
|
1216
|
-
/**
|
|
1217
|
-
* Handles service instantiation errors.
|
|
1218
|
-
*/ async handleInstantiationError(instanceName, holder, deferred, scope, error) {
|
|
1219
|
-
this.logger?.error(`[InstanceResolver] Error creating instance for ${instanceName}`, error);
|
|
1220
|
-
holder.status = InstanceStatus.Error;
|
|
1221
|
-
holder.instance = error;
|
|
1222
|
-
holder.creationPromise = null;
|
|
1223
|
-
if (scope === InjectableScope.Singleton) {
|
|
1224
|
-
this.logger?.log(`[InstanceResolver] Singleton ${instanceName} failed, will be invalidated`);
|
|
1225
|
-
this.serviceLocator.getInvalidator().invalidate(instanceName).catch(() => {});
|
|
1226
|
-
}
|
|
1227
|
-
deferred.reject(error);
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Creates a factory context for dependency injection during service instantiation.
|
|
1231
|
-
*/ createFactoryContext(contextContainer) {
|
|
1232
|
-
return this.tokenProcessor.createFactoryContext(contextContainer);
|
|
1233
|
-
}
|
|
1234
|
-
};
|
|
1235
|
-
|
|
1236
|
-
//#endregion
|
|
1237
|
-
//#region src/internal/core/instantiator.mts
|
|
1238
|
-
/**
|
|
1239
|
-
* Creates service instances from registry records.
|
|
1240
|
-
*
|
|
1241
|
-
* Handles both class-based (@Injectable) and factory-based (@Factory) services,
|
|
1242
|
-
* managing the instantiation lifecycle including sync initialization retries
|
|
1243
|
-
* and lifecycle hook invocation (onServiceInit, onServiceDestroy).
|
|
1244
|
-
*/ var Instantiator = class {
|
|
1245
|
-
injectors;
|
|
1246
|
-
constructor(injectors) {
|
|
1247
|
-
this.injectors = injectors;
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Instantiates a service based on its registry record.
|
|
1251
|
-
* @param ctx The factory context for dependency injection
|
|
1252
|
-
* @param record The factory record from the registry
|
|
1253
|
-
* @param args Optional arguments for the service
|
|
1254
|
-
* @returns Promise resolving to [undefined, instance] or [error]
|
|
1255
|
-
*/ async instantiateService(ctx, record, args = void 0) {
|
|
1256
|
-
try {
|
|
1257
|
-
switch (record.type) {
|
|
1258
|
-
case InjectableType.Class: return this.instantiateClass(ctx, record, args);
|
|
1259
|
-
case InjectableType.Factory: return this.instantiateFactory(ctx, record, args);
|
|
1260
|
-
default: throw DIError.unknown(`[Instantiator] Unknown service type: ${record.type}`);
|
|
1261
|
-
}
|
|
1262
|
-
} catch (error) {
|
|
1263
|
-
return [error instanceof DIError ? error : DIError.unknown(String(error))];
|
|
1019
|
+
/**
|
|
1020
|
+
* Instantiates a service based on its registry record.
|
|
1021
|
+
* @param ctx The factory context for dependency injection
|
|
1022
|
+
* @param record The factory record from the registry
|
|
1023
|
+
* @param args Optional arguments for the service
|
|
1024
|
+
* @returns Promise resolving to [undefined, instance] or [error]
|
|
1025
|
+
*/ async instantiateService(ctx, record, args = void 0) {
|
|
1026
|
+
try {
|
|
1027
|
+
switch (record.type) {
|
|
1028
|
+
case InjectableType.Class: return this.instantiateClass(ctx, record, args);
|
|
1029
|
+
case InjectableType.Factory: return this.instantiateFactory(ctx, record, args);
|
|
1030
|
+
default: throw DIError.unknown(`[ServiceInitializer] Unknown service type: ${record.type}`);
|
|
1031
|
+
}
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
return [error instanceof DIError ? error : DIError.initializationError(record.target.name, error)];
|
|
1264
1034
|
}
|
|
1265
1035
|
}
|
|
1266
1036
|
/**
|
|
@@ -1279,18 +1049,18 @@ var SingletonStorage = class {
|
|
|
1279
1049
|
});
|
|
1280
1050
|
let [instance, promises, injectState] = tryLoad();
|
|
1281
1051
|
if (promises.length > 0) {
|
|
1282
|
-
if ((await Promise.allSettled(promises)).some((result) => result.status === "rejected")) throw DIError.
|
|
1052
|
+
if ((await Promise.allSettled(promises)).some((result) => result.status === "rejected")) throw DIError.initializationError(record.target.name, /* @__PURE__ */ new Error("Service cannot be instantiated"));
|
|
1283
1053
|
const newRes = tryLoad(injectState);
|
|
1284
1054
|
instance = newRes[0];
|
|
1285
1055
|
promises = newRes[1];
|
|
1286
1056
|
}
|
|
1287
1057
|
if (promises.length > 0) {
|
|
1288
|
-
console.error(`[
|
|
1058
|
+
console.error(`[ServiceInitializer] ${record.target.name} has problem with it's definition.
|
|
1289
1059
|
|
|
1290
|
-
One or more of the dependencies are registered as a InjectableScope.
|
|
1060
|
+
One or more of the dependencies are registered as a InjectableScope.Transient and are used with inject.
|
|
1291
1061
|
|
|
1292
|
-
Please use
|
|
1293
|
-
throw DIError.
|
|
1062
|
+
Please use asyncInject instead of inject to load those dependencies.`);
|
|
1063
|
+
throw DIError.initializationError(record.target.name, /* @__PURE__ */ new Error("Service cannot be instantiated"));
|
|
1294
1064
|
}
|
|
1295
1065
|
if ("onServiceInit" in instance) await instance.onServiceInit();
|
|
1296
1066
|
if ("onServiceDestroy" in instance) ctx.addDestroyListener(async () => {
|
|
@@ -1298,7 +1068,7 @@ var SingletonStorage = class {
|
|
|
1298
1068
|
});
|
|
1299
1069
|
return [void 0, instance];
|
|
1300
1070
|
} catch (error) {
|
|
1301
|
-
return [error instanceof DIError ? error : DIError.
|
|
1071
|
+
return [error instanceof DIError ? error : DIError.initializationError(record.target.name, error)];
|
|
1302
1072
|
}
|
|
1303
1073
|
}
|
|
1304
1074
|
/**
|
|
@@ -1317,108 +1087,96 @@ var SingletonStorage = class {
|
|
|
1317
1087
|
});
|
|
1318
1088
|
let [builder, promises, injectState] = tryLoad();
|
|
1319
1089
|
if (promises.length > 0) {
|
|
1320
|
-
if ((await Promise.allSettled(promises)).some((result) => result.status === "rejected")) throw DIError.
|
|
1090
|
+
if ((await Promise.allSettled(promises)).some((result) => result.status === "rejected")) throw DIError.initializationError(record.target.name, /* @__PURE__ */ new Error("Service cannot be instantiated"));
|
|
1321
1091
|
const newRes = tryLoad(injectState);
|
|
1322
1092
|
builder = newRes[0];
|
|
1323
1093
|
promises = newRes[1];
|
|
1324
1094
|
}
|
|
1325
1095
|
if (promises.length > 0) {
|
|
1326
|
-
console.error(`[
|
|
1096
|
+
console.error(`[ServiceInitializer] ${record.target.name} has problem with it's definition.
|
|
1327
1097
|
|
|
1328
|
-
One or more of the dependencies are registered as a InjectableScope.
|
|
1098
|
+
One or more of the dependencies are registered as a InjectableScope.Transient and are used with inject.
|
|
1329
1099
|
|
|
1330
1100
|
Please use asyncInject instead of inject to load those dependencies.`);
|
|
1331
|
-
throw DIError.
|
|
1101
|
+
throw DIError.initializationError(record.target.name, /* @__PURE__ */ new Error("Service cannot be instantiated"));
|
|
1332
1102
|
}
|
|
1333
|
-
if (typeof builder.create !== "function") throw DIError.
|
|
1103
|
+
if (typeof builder.create !== "function") throw DIError.initializationError(record.target.name, /* @__PURE__ */ new Error("Factory does not implement the create method"));
|
|
1334
1104
|
return [void 0, await builder.create(ctx, args)];
|
|
1335
1105
|
} catch (error) {
|
|
1336
|
-
return [error instanceof DIError ? error : DIError.
|
|
1106
|
+
return [error instanceof DIError ? error : DIError.initializationError(record.target.name, error)];
|
|
1337
1107
|
}
|
|
1338
1108
|
}
|
|
1339
1109
|
};
|
|
1340
1110
|
|
|
1341
1111
|
//#endregion
|
|
1342
|
-
//#region src/internal/core/invalidator.mts
|
|
1112
|
+
//#region src/internal/core/service-invalidator.mts
|
|
1343
1113
|
/**
|
|
1344
|
-
* Manages graceful service cleanup with
|
|
1114
|
+
* Manages graceful service cleanup with event-based invalidation.
|
|
1345
1115
|
*
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
*/ var
|
|
1116
|
+
* Uses event subscriptions instead of manual dependent finding.
|
|
1117
|
+
* When a service is created, it subscribes to destroy events of its dependencies.
|
|
1118
|
+
* When a dependency is destroyed, the event automatically invalidates dependents.
|
|
1119
|
+
*/ var ServiceInvalidator = class {
|
|
1350
1120
|
eventBus;
|
|
1351
1121
|
logger;
|
|
1352
|
-
|
|
1353
|
-
constructor(manager, eventBus, logger = null) {
|
|
1122
|
+
constructor(eventBus, logger = null) {
|
|
1354
1123
|
this.eventBus = eventBus;
|
|
1355
1124
|
this.logger = logger;
|
|
1356
|
-
this.storage = new SingletonStorage(manager);
|
|
1357
|
-
}
|
|
1358
|
-
/**
|
|
1359
|
-
* Invalidates a service and all its dependencies.
|
|
1360
|
-
* Works with the configured storage (singleton by default).
|
|
1361
|
-
*/ invalidate(service, round = 1) {
|
|
1362
|
-
return this.invalidateWithStorage(service, this.storage, round);
|
|
1363
1125
|
}
|
|
1364
1126
|
/**
|
|
1365
1127
|
* Invalidates a service using a specific storage.
|
|
1366
|
-
*
|
|
1128
|
+
* Event-based invalidation means dependents are automatically invalidated
|
|
1129
|
+
* via destroy event subscriptions - no need to manually find dependents.
|
|
1367
1130
|
*
|
|
1368
1131
|
* @param service The instance name to invalidate
|
|
1369
1132
|
* @param storage The storage to use for this invalidation
|
|
1370
|
-
* @param round Current invalidation round (for recursion limiting)
|
|
1371
1133
|
* @param options Additional options for invalidation behavior
|
|
1372
|
-
*/ async invalidateWithStorage(service, storage,
|
|
1373
|
-
const {
|
|
1374
|
-
|
|
1375
|
-
this.logger?.log(`[Invalidator] Skipping ${service} - already being invalidated in this chain`);
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
|
-
this.logger?.log(`[Invalidator] Starting invalidation process for ${service}`);
|
|
1134
|
+
*/ async invalidateWithStorage(service, storage, options = {}) {
|
|
1135
|
+
const { emitEvents = true, onInvalidated } = options;
|
|
1136
|
+
this.logger?.log(`[ServiceInvalidator] Starting invalidation process for ${service}`);
|
|
1379
1137
|
const result = storage.get(service);
|
|
1380
1138
|
if (result === null) return;
|
|
1381
|
-
_invalidating.add(service);
|
|
1382
|
-
const optionsWithTracking = {
|
|
1383
|
-
...options,
|
|
1384
|
-
_invalidating
|
|
1385
|
-
};
|
|
1386
|
-
if (cascade) {
|
|
1387
|
-
const dependents = storage.findDependents(service);
|
|
1388
|
-
for (const dependentName of dependents) await this.invalidateWithStorage(dependentName, storage, round, optionsWithTracking);
|
|
1389
|
-
}
|
|
1390
1139
|
const [, holder] = result;
|
|
1391
|
-
if (holder) await this.invalidateHolderWithStorage(service, holder, storage,
|
|
1140
|
+
if (holder) await this.invalidateHolderWithStorage(service, holder, storage, emitEvents, onInvalidated);
|
|
1392
1141
|
}
|
|
1393
1142
|
/**
|
|
1394
|
-
*
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
1397
|
-
|
|
1398
|
-
|
|
1143
|
+
* Sets up destroy event subscriptions for a service's dependencies.
|
|
1144
|
+
* Called when a service is successfully instantiated.
|
|
1145
|
+
*
|
|
1146
|
+
* @param serviceName The name of the service
|
|
1147
|
+
* @param dependencies The set of dependency names
|
|
1148
|
+
* @param storage The storage to use for invalidation
|
|
1149
|
+
* @param holder The holder for the service (to add unsubscribe to destroy listeners)
|
|
1150
|
+
*/ setupDependencySubscriptions(serviceName, dependencies, storage, holder) {
|
|
1151
|
+
if (!this.eventBus) return;
|
|
1152
|
+
for (const dependencyName of dependencies) {
|
|
1153
|
+
const unsubscribe = this.eventBus.on(dependencyName, "destroy", () => {
|
|
1154
|
+
this.logger?.log(`[ServiceInvalidator] Dependency ${dependencyName} destroyed, invalidating ${serviceName}`);
|
|
1155
|
+
this.invalidateWithStorage(serviceName, storage).catch((error) => {
|
|
1156
|
+
this.logger?.error(`[ServiceInvalidator] Error invalidating ${serviceName} after dependency ${dependencyName} destroyed:`, error);
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1159
|
+
holder.destroyListeners.push(unsubscribe);
|
|
1160
|
+
}
|
|
1399
1161
|
}
|
|
1400
1162
|
/**
|
|
1401
1163
|
* Gracefully clears all services in a specific storage.
|
|
1402
1164
|
* This allows clearing request-scoped services using a RequestStorage.
|
|
1403
1165
|
*/ async clearAllWithStorage(storage, options = {}) {
|
|
1404
|
-
const {
|
|
1405
|
-
this.logger?.log("[
|
|
1166
|
+
const { waitForSettlement = true } = options;
|
|
1167
|
+
this.logger?.log("[ServiceInvalidator] Starting graceful clearing of all services");
|
|
1406
1168
|
if (waitForSettlement) {
|
|
1407
|
-
this.logger?.log("[
|
|
1169
|
+
this.logger?.log("[ServiceInvalidator] Waiting for all services to settle...");
|
|
1408
1170
|
await this.readyWithStorage(storage);
|
|
1409
1171
|
}
|
|
1410
1172
|
const allServiceNames = storage.getAllNames();
|
|
1411
|
-
if (allServiceNames.length === 0) this.logger?.log("[
|
|
1173
|
+
if (allServiceNames.length === 0) this.logger?.log("[ServiceInvalidator] No services to clear");
|
|
1412
1174
|
else {
|
|
1413
|
-
this.logger?.log(`[
|
|
1414
|
-
|
|
1175
|
+
this.logger?.log(`[ServiceInvalidator] Found ${allServiceNames.length} services to clear: ${allServiceNames.join(", ")}`);
|
|
1176
|
+
const clearPromises = allServiceNames.map((serviceName) => this.invalidateWithStorage(serviceName, storage));
|
|
1177
|
+
await Promise.all(clearPromises);
|
|
1415
1178
|
}
|
|
1416
|
-
this.logger?.log("[
|
|
1417
|
-
}
|
|
1418
|
-
/**
|
|
1419
|
-
* Waits for all services to settle (either created, destroyed, or error state).
|
|
1420
|
-
*/ async ready() {
|
|
1421
|
-
return this.readyWithStorage(this.storage);
|
|
1179
|
+
this.logger?.log("[ServiceInvalidator] Graceful clearing completed");
|
|
1422
1180
|
}
|
|
1423
1181
|
/**
|
|
1424
1182
|
* Waits for all services in a specific storage to settle.
|
|
@@ -1429,29 +1187,22 @@ var SingletonStorage = class {
|
|
|
1429
1187
|
}
|
|
1430
1188
|
/**
|
|
1431
1189
|
* Invalidates a single holder using a specific storage.
|
|
1432
|
-
*/ async invalidateHolderWithStorage(key, holder, storage,
|
|
1433
|
-
|
|
1434
|
-
await this.invalidateHolderByStatus(holder, round, {
|
|
1190
|
+
*/ async invalidateHolderWithStorage(key, holder, storage, emitEvents, onInvalidated) {
|
|
1191
|
+
await this.invalidateHolderByStatus(holder, {
|
|
1435
1192
|
context: key,
|
|
1436
|
-
onCreationError: () => this.logger?.error(`[Invalidator] ${key} creation triggered too many invalidation rounds`),
|
|
1437
|
-
onRecursiveInvalidate: () => this.invalidateWithStorage(key, storage, round + 1, options),
|
|
1438
1193
|
onDestroy: () => this.destroyHolderWithStorage(key, holder, storage, emitEvents, onInvalidated)
|
|
1439
1194
|
});
|
|
1440
1195
|
}
|
|
1441
1196
|
/**
|
|
1442
1197
|
* Common invalidation logic for holders based on their status.
|
|
1443
|
-
*/ async invalidateHolderByStatus(holder,
|
|
1198
|
+
*/ async invalidateHolderByStatus(holder, options) {
|
|
1444
1199
|
switch (holder.status) {
|
|
1445
1200
|
case InstanceStatus.Destroying:
|
|
1446
1201
|
await holder.destroyPromise;
|
|
1447
1202
|
break;
|
|
1448
1203
|
case InstanceStatus.Creating:
|
|
1449
1204
|
await holder.creationPromise;
|
|
1450
|
-
|
|
1451
|
-
options.onCreationError();
|
|
1452
|
-
return;
|
|
1453
|
-
}
|
|
1454
|
-
await options.onRecursiveInvalidate();
|
|
1205
|
+
await options.onDestroy();
|
|
1455
1206
|
break;
|
|
1456
1207
|
default:
|
|
1457
1208
|
await options.onDestroy();
|
|
@@ -1462,7 +1213,7 @@ var SingletonStorage = class {
|
|
|
1462
1213
|
* Destroys a holder using a specific storage.
|
|
1463
1214
|
*/ async destroyHolderWithStorage(key, holder, storage, emitEvents, onInvalidated) {
|
|
1464
1215
|
holder.status = InstanceStatus.Destroying;
|
|
1465
|
-
this.logger?.log(`[
|
|
1216
|
+
this.logger?.log(`[ServiceInvalidator] Invalidating ${key} and notifying listeners`);
|
|
1466
1217
|
holder.destroyPromise = Promise.all(holder.destroyListeners.map((listener) => listener())).then(async () => {
|
|
1467
1218
|
holder.destroyListeners = [];
|
|
1468
1219
|
holder.deps.clear();
|
|
@@ -1470,89 +1221,369 @@ var SingletonStorage = class {
|
|
|
1470
1221
|
if (emitEvents && this.eventBus) await this.emitInstanceEvent(key, "destroy");
|
|
1471
1222
|
if (onInvalidated) await onInvalidated(key);
|
|
1472
1223
|
});
|
|
1473
|
-
await holder.destroyPromise;
|
|
1224
|
+
await holder.destroyPromise;
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Waits for a holder to settle (either created, destroyed, or error state).
|
|
1228
|
+
*/ async waitForHolderToSettle(holder) {
|
|
1229
|
+
switch (holder.status) {
|
|
1230
|
+
case InstanceStatus.Creating:
|
|
1231
|
+
await holder.creationPromise;
|
|
1232
|
+
break;
|
|
1233
|
+
case InstanceStatus.Destroying:
|
|
1234
|
+
await holder.destroyPromise;
|
|
1235
|
+
break;
|
|
1236
|
+
case InstanceStatus.Created:
|
|
1237
|
+
case InstanceStatus.Error: break;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Emits events to listeners for instance lifecycle events.
|
|
1242
|
+
*/ emitInstanceEvent(name, event = "create") {
|
|
1243
|
+
if (!this.eventBus) return Promise.resolve();
|
|
1244
|
+
this.logger?.log(`[ServiceInvalidator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`);
|
|
1245
|
+
return this.eventBus.emit(name, event);
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
//#endregion
|
|
1250
|
+
//#region src/utils/get-injectors.mts
|
|
1251
|
+
function getInjectors() {
|
|
1252
|
+
let currentFactoryContext = null;
|
|
1253
|
+
function provideFactoryContext$1(context) {
|
|
1254
|
+
const original = currentFactoryContext;
|
|
1255
|
+
currentFactoryContext = context;
|
|
1256
|
+
return original;
|
|
1257
|
+
}
|
|
1258
|
+
function getFactoryContext() {
|
|
1259
|
+
if (!currentFactoryContext) throw new Error("[Injector] Trying to access injection context outside of a injectable context");
|
|
1260
|
+
return currentFactoryContext;
|
|
1261
|
+
}
|
|
1262
|
+
let promiseCollector = null;
|
|
1263
|
+
let injectState = null;
|
|
1264
|
+
function getRequest(token, args, skipCycleTracking = false) {
|
|
1265
|
+
if (!injectState) throw new Error("[Injector] Trying to make a request outside of a injectable context");
|
|
1266
|
+
if (injectState.isFrozen) {
|
|
1267
|
+
const idx = injectState.currentIndex++;
|
|
1268
|
+
const request$1 = injectState.requests[idx];
|
|
1269
|
+
if (request$1.token !== token) throw new Error(`[Injector] Wrong token order. Expected ${request$1.token.toString()} but got ${token.toString()}`);
|
|
1270
|
+
return request$1;
|
|
1271
|
+
}
|
|
1272
|
+
let result = null;
|
|
1273
|
+
let error = null;
|
|
1274
|
+
const doInject = () => getFactoryContext().inject(token, args).then((r) => {
|
|
1275
|
+
result = r;
|
|
1276
|
+
return r;
|
|
1277
|
+
}).catch((e) => {
|
|
1278
|
+
error = e;
|
|
1279
|
+
});
|
|
1280
|
+
const request = {
|
|
1281
|
+
token,
|
|
1282
|
+
promise: skipCycleTracking ? withoutResolutionContext(doInject) : doInject(),
|
|
1283
|
+
get result() {
|
|
1284
|
+
return result;
|
|
1285
|
+
},
|
|
1286
|
+
get error() {
|
|
1287
|
+
return error;
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
injectState.requests.push(request);
|
|
1291
|
+
injectState.currentIndex++;
|
|
1292
|
+
return request;
|
|
1293
|
+
}
|
|
1294
|
+
function asyncInject$1(token, args) {
|
|
1295
|
+
if (!injectState) throw new Error("[Injector] Trying to access inject outside of a injectable context");
|
|
1296
|
+
const request = getRequest(token[InjectableTokenMeta] ?? token, args, true);
|
|
1297
|
+
return request.promise.then((result) => {
|
|
1298
|
+
if (request.error) throw request.error;
|
|
1299
|
+
return result;
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
function wrapSyncInit$1(cb) {
|
|
1303
|
+
return (previousState) => {
|
|
1304
|
+
const promises = [];
|
|
1305
|
+
const originalPromiseCollector = promiseCollector;
|
|
1306
|
+
const originalInjectState = injectState;
|
|
1307
|
+
injectState = previousState ? {
|
|
1308
|
+
...previousState,
|
|
1309
|
+
currentIndex: 0
|
|
1310
|
+
} : {
|
|
1311
|
+
currentIndex: 0,
|
|
1312
|
+
isFrozen: false,
|
|
1313
|
+
requests: []
|
|
1314
|
+
};
|
|
1315
|
+
promiseCollector = (promise) => {
|
|
1316
|
+
promises.push(promise);
|
|
1317
|
+
};
|
|
1318
|
+
const result = cb();
|
|
1319
|
+
promiseCollector = originalPromiseCollector;
|
|
1320
|
+
const newInjectState = {
|
|
1321
|
+
...injectState,
|
|
1322
|
+
isFrozen: true
|
|
1323
|
+
};
|
|
1324
|
+
injectState = originalInjectState;
|
|
1325
|
+
return [
|
|
1326
|
+
result,
|
|
1327
|
+
promises,
|
|
1328
|
+
newInjectState
|
|
1329
|
+
];
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
function inject$1(token, args) {
|
|
1333
|
+
const realToken = token[InjectableTokenMeta] ?? token;
|
|
1334
|
+
if (!injectState) throw new Error("[Injector] Trying to access inject outside of a injectable context");
|
|
1335
|
+
const ctx = getFactoryContext();
|
|
1336
|
+
const instance = ctx.container.tryGetSync(realToken, args);
|
|
1337
|
+
if (!instance) {
|
|
1338
|
+
const request = getRequest(realToken, args);
|
|
1339
|
+
if (request.error) throw request.error;
|
|
1340
|
+
else if (request.result) return request.result;
|
|
1341
|
+
if (promiseCollector) promiseCollector(request.promise);
|
|
1342
|
+
return new Proxy({}, { get() {
|
|
1343
|
+
throw new Error(`[Injector] Trying to access ${realToken.toString()} before it's initialized, please move the code to a onServiceInit method`);
|
|
1344
|
+
} });
|
|
1345
|
+
}
|
|
1346
|
+
ctx.inject(realToken, args).catch(() => {});
|
|
1347
|
+
return instance;
|
|
1348
|
+
}
|
|
1349
|
+
function optional$1(token, args) {
|
|
1350
|
+
try {
|
|
1351
|
+
return inject$1(token, args);
|
|
1352
|
+
} catch {
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return {
|
|
1357
|
+
asyncInject: asyncInject$1,
|
|
1358
|
+
inject: inject$1,
|
|
1359
|
+
optional: optional$1,
|
|
1360
|
+
wrapSyncInit: wrapSyncInit$1,
|
|
1361
|
+
provideFactoryContext: provideFactoryContext$1
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region src/utils/default-injectors.mts
|
|
1367
|
+
const defaultInjectors = /* @__PURE__ */ getInjectors();
|
|
1368
|
+
const inject = defaultInjectors.inject;
|
|
1369
|
+
const optional = defaultInjectors.optional;
|
|
1370
|
+
const asyncInject = defaultInjectors.asyncInject;
|
|
1371
|
+
const wrapSyncInit = defaultInjectors.wrapSyncInit;
|
|
1372
|
+
const provideFactoryContext = defaultInjectors.provideFactoryContext;
|
|
1373
|
+
|
|
1374
|
+
//#endregion
|
|
1375
|
+
//#region src/utils/get-injectable-token.mts
|
|
1376
|
+
function getInjectableToken(target) {
|
|
1377
|
+
const token = target[InjectableTokenMeta];
|
|
1378
|
+
if (!token) throw DIError.classNotInjectable(target.name);
|
|
1379
|
+
return token;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region src/internal/core/token-resolver.mts
|
|
1384
|
+
/**
|
|
1385
|
+
* Handles token validation and resolution.
|
|
1386
|
+
*
|
|
1387
|
+
* Focuses on token validation, normalization, and argument validation.
|
|
1388
|
+
* Name generation is handled by NameResolver.
|
|
1389
|
+
*/ var TokenResolver = class {
|
|
1390
|
+
logger;
|
|
1391
|
+
constructor(logger = null) {
|
|
1392
|
+
this.logger = logger;
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Normalizes a token to an InjectionToken.
|
|
1396
|
+
* Handles class constructors by getting their injectable token.
|
|
1397
|
+
*
|
|
1398
|
+
* @param token A class constructor, InjectionToken, BoundInjectionToken, or FactoryInjectionToken
|
|
1399
|
+
* @returns The normalized InjectionTokenType
|
|
1400
|
+
*/ normalizeToken(token) {
|
|
1401
|
+
if (typeof token === "function") return getInjectableToken(token);
|
|
1402
|
+
return token;
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Gets the underlying "real" token from wrapped tokens.
|
|
1406
|
+
* For BoundInjectionToken and FactoryInjectionToken, returns the wrapped token.
|
|
1407
|
+
* For other tokens, returns the token itself.
|
|
1408
|
+
*
|
|
1409
|
+
* @param token The token to unwrap
|
|
1410
|
+
* @returns The underlying InjectionToken
|
|
1411
|
+
*/ getRealToken(token) {
|
|
1412
|
+
if (token instanceof BoundInjectionToken || token instanceof FactoryInjectionToken) return token.token;
|
|
1413
|
+
return token;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Convenience method that normalizes a token and then gets the real token.
|
|
1417
|
+
* Useful for checking registry entries where you need the actual registered token.
|
|
1418
|
+
*
|
|
1419
|
+
* @param token Any injectable type
|
|
1420
|
+
* @returns The underlying InjectionToken
|
|
1421
|
+
*/ getRegistryToken(token) {
|
|
1422
|
+
return this.getRealToken(this.normalizeToken(token));
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Validates and resolves token arguments, handling factory token resolution and validation.
|
|
1426
|
+
*
|
|
1427
|
+
* @param token The token to validate
|
|
1428
|
+
* @param args Optional arguments
|
|
1429
|
+
* @returns [error, { actualToken, validatedArgs }]
|
|
1430
|
+
*/ validateAndResolveTokenArgs(token, args) {
|
|
1431
|
+
let actualToken = token;
|
|
1432
|
+
if (typeof token === "function") actualToken = getInjectableToken(token);
|
|
1433
|
+
let realArgs = args;
|
|
1434
|
+
if (actualToken instanceof BoundInjectionToken) realArgs = actualToken.value;
|
|
1435
|
+
else if (actualToken instanceof FactoryInjectionToken) if (actualToken.resolved) realArgs = actualToken.value;
|
|
1436
|
+
else return [DIError.factoryTokenNotResolved(token.name), { actualToken }];
|
|
1437
|
+
if (!actualToken.schema) return [void 0, {
|
|
1438
|
+
actualToken,
|
|
1439
|
+
validatedArgs: realArgs
|
|
1440
|
+
}];
|
|
1441
|
+
const validatedArgs = actualToken.schema?.safeParse(realArgs);
|
|
1442
|
+
if (validatedArgs && !validatedArgs.success) {
|
|
1443
|
+
this.logger?.error(`[TokenResolver]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`, validatedArgs.error);
|
|
1444
|
+
return [DIError.tokenValidationError(`Validation failed for ${actualToken.name.toString()}`, actualToken.schema, realArgs), { actualToken }];
|
|
1445
|
+
}
|
|
1446
|
+
return [void 0, {
|
|
1447
|
+
actualToken,
|
|
1448
|
+
validatedArgs: validatedArgs?.data
|
|
1449
|
+
}];
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
//#endregion
|
|
1454
|
+
//#region src/internal/holder/unified-storage.mts
|
|
1455
|
+
/**
|
|
1456
|
+
* Unified storage implementation that works the same way regardless of scope.
|
|
1457
|
+
* Replaces RequestContext, HolderManager, SingletonStorage, RequestStorage.
|
|
1458
|
+
*
|
|
1459
|
+
* Scope is just metadata - storage operations are identical for all scopes.
|
|
1460
|
+
* Different storage instances are just isolated storage spaces.
|
|
1461
|
+
*/ var UnifiedStorage = class {
|
|
1462
|
+
scope;
|
|
1463
|
+
holders = /* @__PURE__ */ new Map();
|
|
1464
|
+
/**
|
|
1465
|
+
* Reverse dependency index: maps a dependency name to the set of holder names that depend on it.
|
|
1466
|
+
* This allows O(1) lookup of dependents instead of O(n) iteration.
|
|
1467
|
+
*/ dependents = /* @__PURE__ */ new Map();
|
|
1468
|
+
constructor(scope = InjectableScope.Singleton) {
|
|
1469
|
+
this.scope = scope;
|
|
1470
|
+
}
|
|
1471
|
+
get(instanceName) {
|
|
1472
|
+
const holder = this.holders.get(instanceName);
|
|
1473
|
+
if (!holder) return null;
|
|
1474
|
+
switch (holder.status) {
|
|
1475
|
+
case InstanceStatus.Destroying: return [DIError.instanceDestroying(instanceName), holder];
|
|
1476
|
+
case InstanceStatus.Error: return [holder.instance, holder];
|
|
1477
|
+
case InstanceStatus.Creating:
|
|
1478
|
+
case InstanceStatus.Created: return [void 0, holder];
|
|
1479
|
+
default: return null;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
set(instanceName, holder) {
|
|
1483
|
+
this.holders.set(instanceName, holder);
|
|
1484
|
+
if (holder.deps.size > 0) this.registerDependencies(instanceName, holder.deps);
|
|
1485
|
+
}
|
|
1486
|
+
delete(instanceName) {
|
|
1487
|
+
const holder = this.holders.get(instanceName);
|
|
1488
|
+
if (holder) this.removeFromDependentsIndex(instanceName, holder.deps);
|
|
1489
|
+
return this.holders.delete(instanceName);
|
|
1490
|
+
}
|
|
1491
|
+
createHolder(instanceName, type, deps) {
|
|
1492
|
+
const deferred = Promise.withResolvers();
|
|
1493
|
+
return [deferred, {
|
|
1494
|
+
status: InstanceStatus.Creating,
|
|
1495
|
+
name: instanceName,
|
|
1496
|
+
instance: null,
|
|
1497
|
+
creationPromise: deferred.promise,
|
|
1498
|
+
destroyPromise: null,
|
|
1499
|
+
type,
|
|
1500
|
+
scope: this.scope,
|
|
1501
|
+
deps,
|
|
1502
|
+
destroyListeners: [],
|
|
1503
|
+
createdAt: Date.now(),
|
|
1504
|
+
waitingFor: /* @__PURE__ */ new Set()
|
|
1505
|
+
}];
|
|
1506
|
+
}
|
|
1507
|
+
storeInstance(instanceName, instance) {
|
|
1508
|
+
if (this.holders.get(instanceName)) throw DIError.storageError("Instance already stored", "storeInstance", instanceName);
|
|
1509
|
+
this.set(instanceName, {
|
|
1510
|
+
status: InstanceStatus.Created,
|
|
1511
|
+
name: instanceName,
|
|
1512
|
+
instance,
|
|
1513
|
+
creationPromise: null,
|
|
1514
|
+
destroyPromise: null,
|
|
1515
|
+
type: InjectableType.Class,
|
|
1516
|
+
scope: this.scope,
|
|
1517
|
+
deps: /* @__PURE__ */ new Set(),
|
|
1518
|
+
destroyListeners: typeof instance === "object" && instance !== null && "onServiceDestroy" in instance ? [instance.onServiceDestroy] : [],
|
|
1519
|
+
createdAt: Date.now(),
|
|
1520
|
+
waitingFor: /* @__PURE__ */ new Set()
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
handles(scope) {
|
|
1524
|
+
return scope === this.scope;
|
|
1525
|
+
}
|
|
1526
|
+
getAllNames() {
|
|
1527
|
+
return Array.from(this.holders.keys());
|
|
1528
|
+
}
|
|
1529
|
+
forEach(callback) {
|
|
1530
|
+
for (const [name, holder] of this.holders) callback(name, holder);
|
|
1531
|
+
}
|
|
1532
|
+
findByInstance(instance) {
|
|
1533
|
+
for (const holder of this.holders.values()) if (holder.instance === instance) return holder;
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
findDependents(instanceName) {
|
|
1537
|
+
const dependents = this.dependents.get(instanceName);
|
|
1538
|
+
return dependents ? Array.from(dependents) : [];
|
|
1474
1539
|
}
|
|
1475
1540
|
/**
|
|
1476
|
-
*
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1541
|
+
* Updates dependency references when instance names change.
|
|
1542
|
+
* Used during scope upgrades when instance names are regenerated with requestId.
|
|
1543
|
+
*
|
|
1544
|
+
* @param oldName The old instance name
|
|
1545
|
+
* @param newName The new instance name
|
|
1546
|
+
*/ updateDependencyReference(oldName, newName) {
|
|
1547
|
+
for (const holder of this.holders.values()) if (holder.deps.has(oldName)) {
|
|
1548
|
+
holder.deps.delete(oldName);
|
|
1549
|
+
holder.deps.add(newName);
|
|
1550
|
+
}
|
|
1551
|
+
const oldDependents = this.dependents.get(oldName);
|
|
1552
|
+
if (oldDependents) {
|
|
1553
|
+
const newDependents = this.dependents.get(newName) || /* @__PURE__ */ new Set();
|
|
1554
|
+
for (const dependent of oldDependents) newDependents.add(dependent);
|
|
1555
|
+
this.dependents.set(newName, newDependents);
|
|
1556
|
+
this.dependents.delete(oldName);
|
|
1557
|
+
}
|
|
1558
|
+
for (const [depName, dependents] of this.dependents.entries()) if (depName === oldName) {
|
|
1559
|
+
const newDependents = this.dependents.get(newName) || /* @__PURE__ */ new Set();
|
|
1560
|
+
for (const dependent of dependents) newDependents.add(dependent);
|
|
1561
|
+
this.dependents.set(newName, newDependents);
|
|
1562
|
+
this.dependents.delete(oldName);
|
|
1487
1563
|
}
|
|
1488
1564
|
}
|
|
1489
1565
|
/**
|
|
1490
|
-
*
|
|
1491
|
-
*/
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
if (servicesToClearThisRound.length === 0) {
|
|
1498
|
-
const remainingServices = serviceNames.filter((name) => !clearedServices.has(name));
|
|
1499
|
-
if (remainingServices.length > 0) {
|
|
1500
|
-
this.logger?.warn(`[Invalidator] No services ready for clearing, forcing cleanup of remaining: ${remainingServices.join(", ")}`);
|
|
1501
|
-
await this.forceClearServicesInStorage(remainingServices, storage);
|
|
1502
|
-
remainingServices.forEach((name) => clearedServices.add(name));
|
|
1503
|
-
}
|
|
1504
|
-
break;
|
|
1566
|
+
* Registers a holder's dependencies in the reverse index.
|
|
1567
|
+
*/ registerDependencies(holderName, deps) {
|
|
1568
|
+
for (const dep of deps) {
|
|
1569
|
+
let dependents = this.dependents.get(dep);
|
|
1570
|
+
if (!dependents) {
|
|
1571
|
+
dependents = /* @__PURE__ */ new Set();
|
|
1572
|
+
this.dependents.set(dep, dependents);
|
|
1505
1573
|
}
|
|
1506
|
-
|
|
1507
|
-
try {
|
|
1508
|
-
await this.invalidateWithStorage(serviceName, storage, round);
|
|
1509
|
-
clearedServices.add(serviceName);
|
|
1510
|
-
this.logger?.log(`[Invalidator] Successfully cleared service: ${serviceName}`);
|
|
1511
|
-
} catch (error) {
|
|
1512
|
-
this.logger?.error(`[Invalidator] Error clearing service ${serviceName}:`, error);
|
|
1513
|
-
clearedServices.add(serviceName);
|
|
1514
|
-
}
|
|
1515
|
-
});
|
|
1516
|
-
await Promise.all(clearPromises);
|
|
1517
|
-
round++;
|
|
1574
|
+
dependents.add(holderName);
|
|
1518
1575
|
}
|
|
1519
|
-
if (clearedServices.size < serviceNames.length) this.logger?.warn(`[Invalidator] Clearing completed after ${maxRounds} rounds, but ${serviceNames.length - clearedServices.size} services may not have been properly cleared`);
|
|
1520
|
-
}
|
|
1521
|
-
/**
|
|
1522
|
-
* Finds services that are ready to be cleared in the current round.
|
|
1523
|
-
* A service is ready if all its dependencies have already been cleared.
|
|
1524
|
-
*/ findServicesReadyForClearingInStorage(allServiceNames, clearedServices, storage) {
|
|
1525
|
-
return allServiceNames.filter((serviceName) => {
|
|
1526
|
-
if (clearedServices.has(serviceName)) return false;
|
|
1527
|
-
const result = storage.get(serviceName);
|
|
1528
|
-
if (result === null || result[0]) return true;
|
|
1529
|
-
const [, holder] = result;
|
|
1530
|
-
return !Array.from(holder.deps).some((dep) => !clearedServices.has(dep));
|
|
1531
|
-
});
|
|
1532
1576
|
}
|
|
1533
1577
|
/**
|
|
1534
|
-
*
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
if (
|
|
1541
|
-
const [, holder] = result;
|
|
1542
|
-
await this.destroyHolderWithStorage(serviceName, holder, storage, true);
|
|
1543
|
-
}
|
|
1544
|
-
} catch (error) {
|
|
1545
|
-
this.logger?.error(`[Invalidator] Error force clearing service ${serviceName}:`, error);
|
|
1578
|
+
* Removes a holder from the reverse dependency index.
|
|
1579
|
+
*/ removeFromDependentsIndex(holderName, deps) {
|
|
1580
|
+
for (const dep of deps) {
|
|
1581
|
+
const dependents = this.dependents.get(dep);
|
|
1582
|
+
if (dependents) {
|
|
1583
|
+
dependents.delete(holderName);
|
|
1584
|
+
if (dependents.size === 0) this.dependents.delete(dep);
|
|
1546
1585
|
}
|
|
1547
|
-
}
|
|
1548
|
-
await Promise.all(promises);
|
|
1549
|
-
}
|
|
1550
|
-
/**
|
|
1551
|
-
* Emits events to listeners for instance lifecycle events.
|
|
1552
|
-
*/ emitInstanceEvent(name, event = "create") {
|
|
1553
|
-
if (!this.eventBus) return Promise.resolve();
|
|
1554
|
-
this.logger?.log(`[Invalidator]#emitInstanceEvent() Notifying listeners for ${name} with event ${event}`);
|
|
1555
|
-
return this.eventBus.emit(name, event);
|
|
1586
|
+
}
|
|
1556
1587
|
}
|
|
1557
1588
|
};
|
|
1558
1589
|
|
|
@@ -1598,270 +1629,220 @@ var SingletonStorage = class {
|
|
|
1598
1629
|
};
|
|
1599
1630
|
|
|
1600
1631
|
//#endregion
|
|
1601
|
-
//#region src/internal/
|
|
1632
|
+
//#region src/internal/stub-factory-class.mts
|
|
1602
1633
|
/**
|
|
1603
|
-
*
|
|
1634
|
+
* Stub factory class used when registering InjectionTokens without a real class implementation.
|
|
1635
|
+
* This is used in ScopedContainer.addInstance() to allow registering tokens that don't have
|
|
1636
|
+
* a corresponding class, while preventing accidental instantiation.
|
|
1604
1637
|
*
|
|
1605
|
-
*
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
super(logger);
|
|
1610
|
-
}
|
|
1611
|
-
get(name) {
|
|
1612
|
-
const holder = this._holders.get(name);
|
|
1613
|
-
if (holder) {
|
|
1614
|
-
if (holder.status === InstanceStatus.Destroying) {
|
|
1615
|
-
this.logger?.log(`[HolderManager]#get() Instance ${holder.name} is destroying`);
|
|
1616
|
-
return [DIError.instanceDestroying(holder.name), holder];
|
|
1617
|
-
} else if (holder.status === InstanceStatus.Error) {
|
|
1618
|
-
this.logger?.log(`[HolderManager]#get() Instance ${holder.name} is in error state`);
|
|
1619
|
-
return [holder.instance, holder];
|
|
1620
|
-
}
|
|
1621
|
-
return [void 0, holder];
|
|
1622
|
-
} else {
|
|
1623
|
-
this.logger?.log(`[HolderManager]#get() Instance ${name} not found`);
|
|
1624
|
-
return [DIError.instanceNotFound(name)];
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
set(name, holder) {
|
|
1628
|
-
this._holders.set(name, holder);
|
|
1629
|
-
}
|
|
1630
|
-
has(name) {
|
|
1631
|
-
const [error, holder] = this.get(name);
|
|
1632
|
-
if (!error) return [void 0, true];
|
|
1633
|
-
if (error.code === DIErrorCode.InstanceDestroying) return [error];
|
|
1634
|
-
return [void 0, !!holder];
|
|
1635
|
-
}
|
|
1636
|
-
/**
|
|
1637
|
-
* Creates a new holder with Created status and stores it.
|
|
1638
|
-
* This is useful for creating holders that already have their instance ready.
|
|
1639
|
-
* @param name The name of the instance
|
|
1640
|
-
* @param instance The actual instance to store
|
|
1641
|
-
* @param type The injectable type
|
|
1642
|
-
* @param scope The injectable scope
|
|
1643
|
-
* @param deps Optional set of dependencies
|
|
1644
|
-
* @returns The created holder
|
|
1645
|
-
*/ storeCreatedHolder(name, instance, type, scope, deps = /* @__PURE__ */ new Set()) {
|
|
1646
|
-
const holder = this.createCreatedHolder(name, instance, type, scope, deps);
|
|
1647
|
-
this._holders.set(name, holder);
|
|
1648
|
-
return holder;
|
|
1638
|
+
* @internal
|
|
1639
|
+
*/ var StubFactoryClass = class {
|
|
1640
|
+
constructor() {
|
|
1641
|
+
throw DIError.factoryNotFound("Trying to get instance of factory without real implementation");
|
|
1649
1642
|
}
|
|
1650
1643
|
};
|
|
1651
1644
|
|
|
1652
1645
|
//#endregion
|
|
1653
|
-
//#region src/
|
|
1646
|
+
//#region src/container/abstract-container.mts
|
|
1654
1647
|
/**
|
|
1655
|
-
*
|
|
1648
|
+
* Abstract base class for dependency injection containers.
|
|
1656
1649
|
*
|
|
1657
|
-
* Provides
|
|
1658
|
-
*
|
|
1659
|
-
*/ var
|
|
1660
|
-
logger;
|
|
1661
|
-
constructor(logger = null) {
|
|
1662
|
-
this.logger = logger;
|
|
1663
|
-
}
|
|
1650
|
+
* Provides shared implementation for common container operations.
|
|
1651
|
+
* Both Container and ScopedContainer extend this class.
|
|
1652
|
+
*/ var AbstractContainer = class {
|
|
1664
1653
|
/**
|
|
1665
|
-
*
|
|
1666
|
-
* Handles class constructors by getting their injectable token.
|
|
1654
|
+
* Calculates the instance name for a given token and optional arguments.
|
|
1667
1655
|
*
|
|
1668
|
-
* @
|
|
1669
|
-
* @
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1656
|
+
* @internal
|
|
1657
|
+
* @param token The class type, InjectionToken, BoundInjectionToken, or FactoryInjectionToken
|
|
1658
|
+
* @param args Optional arguments (ignored for BoundInjectionToken which uses its bound value)
|
|
1659
|
+
* @returns The calculated instance name string, or null if the token is a FactoryInjectionToken that is not yet resolved
|
|
1660
|
+
*/ calculateInstanceName(token, args) {
|
|
1661
|
+
const [err, { actualToken, validatedArgs }] = this.getTokenResolver().validateAndResolveTokenArgs(token, args);
|
|
1662
|
+
if (err) {
|
|
1663
|
+
if (err instanceof DIError && err.code === DIErrorCode.FactoryTokenNotResolved) return null;
|
|
1664
|
+
if (err instanceof DIError && err.code === DIErrorCode.TokenValidationError) return null;
|
|
1665
|
+
}
|
|
1666
|
+
const realToken = this.getTokenResolver().getRealToken(actualToken);
|
|
1667
|
+
const registry = this.getRegistry();
|
|
1668
|
+
const scope = registry.has(realToken) ? registry.get(realToken).scope : this.defaultScope;
|
|
1669
|
+
return this.getNameResolver().generateInstanceName(actualToken, validatedArgs, scope === InjectableScope.Request ? this.requestId : void 0, scope);
|
|
1673
1670
|
}
|
|
1674
1671
|
/**
|
|
1675
|
-
*
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
* @param token The token to unwrap
|
|
1680
|
-
* @returns The underlying InjectionToken
|
|
1681
|
-
*/ getRealToken(token) {
|
|
1682
|
-
if (token instanceof BoundInjectionToken || token instanceof FactoryInjectionToken) return token.token;
|
|
1683
|
-
return token;
|
|
1672
|
+
* Checks if a service is registered in the container.
|
|
1673
|
+
*/ isRegistered(token) {
|
|
1674
|
+
const realToken = this.getTokenResolver().getRegistryToken(token);
|
|
1675
|
+
return this.getRegistry().has(realToken);
|
|
1684
1676
|
}
|
|
1685
1677
|
/**
|
|
1686
|
-
*
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
* @param token Any injectable type
|
|
1690
|
-
* @returns The underlying InjectionToken
|
|
1691
|
-
*/ getRegistryToken(token) {
|
|
1692
|
-
return this.getRealToken(this.normalizeToken(token));
|
|
1678
|
+
* Waits for all pending operations to complete.
|
|
1679
|
+
*/ async ready() {
|
|
1680
|
+
await this.getServiceInvalidator().readyWithStorage(this.getStorage());
|
|
1693
1681
|
}
|
|
1694
1682
|
/**
|
|
1695
|
-
*
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
let realArgs = args;
|
|
1700
|
-
if (actualToken instanceof BoundInjectionToken) realArgs = actualToken.value;
|
|
1701
|
-
else if (actualToken instanceof FactoryInjectionToken) if (actualToken.resolved) realArgs = actualToken.value;
|
|
1702
|
-
else return [DIError.factoryTokenNotResolved(token.name), { actualToken }];
|
|
1703
|
-
if (!actualToken.schema) return [void 0, {
|
|
1704
|
-
actualToken,
|
|
1705
|
-
validatedArgs: realArgs
|
|
1706
|
-
}];
|
|
1707
|
-
const validatedArgs = actualToken.schema?.safeParse(realArgs);
|
|
1708
|
-
if (validatedArgs && !validatedArgs.success) {
|
|
1709
|
-
this.logger?.error(`[TokenProcessor]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`, validatedArgs.error);
|
|
1710
|
-
return [DIError.unknown(validatedArgs.error), { actualToken }];
|
|
1711
|
-
}
|
|
1712
|
-
return [void 0, {
|
|
1713
|
-
actualToken,
|
|
1714
|
-
validatedArgs: validatedArgs?.data
|
|
1715
|
-
}];
|
|
1683
|
+
* @internal
|
|
1684
|
+
* Attempts to get an instance synchronously if it already exists.
|
|
1685
|
+
*/ tryGetSync(token, args) {
|
|
1686
|
+
return this.tryGetSyncFromStorage(token, args, this.getStorage(), this.requestId);
|
|
1716
1687
|
}
|
|
1717
1688
|
/**
|
|
1718
|
-
*
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
const
|
|
1722
|
-
|
|
1689
|
+
* @internal
|
|
1690
|
+
* Internal method for getting instances synchronously with configurable storage.
|
|
1691
|
+
*/ tryGetSyncFromStorage(token, args, storage, requestId) {
|
|
1692
|
+
const tokenResolver = this.getTokenResolver();
|
|
1693
|
+
const realToken = tokenResolver.getRegistryToken(token);
|
|
1694
|
+
const registry = this.getRegistry();
|
|
1695
|
+
const scope = registry.has(realToken) ? registry.get(realToken).scope : InjectableScope.Singleton;
|
|
1696
|
+
try {
|
|
1697
|
+
const instanceName = this.getNameResolver().generateInstanceName(tokenResolver.normalizeToken(token), args, requestId, scope);
|
|
1698
|
+
const result = storage.get(instanceName);
|
|
1699
|
+
if (result && result[0] === void 0 && result[1]) {
|
|
1700
|
+
const holder = result[1];
|
|
1701
|
+
if (holder.status === InstanceStatus.Created) return holder.instance;
|
|
1702
|
+
}
|
|
1703
|
+
} catch {}
|
|
1704
|
+
return null;
|
|
1723
1705
|
}
|
|
1724
1706
|
/**
|
|
1725
|
-
*
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1707
|
+
* Adds an instance to the container.
|
|
1708
|
+
* Accepts class types, InjectionTokens, and BoundInjectionTokens.
|
|
1709
|
+
* Rejects InjectionTokens with required schemas (use BoundInjectionToken instead).
|
|
1710
|
+
*
|
|
1711
|
+
* @param token The class type, InjectionToken, or BoundInjectionToken to register the instance for
|
|
1712
|
+
* @param instance The instance to store
|
|
1713
|
+
*/ addInstance(token, instance) {
|
|
1714
|
+
this.addInstanceToStorage(token, instance, this.getStorage(), this.defaultScope, this.requestId);
|
|
1730
1715
|
}
|
|
1731
1716
|
/**
|
|
1732
|
-
*
|
|
1733
|
-
*
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
destroyListeners.add(listener);
|
|
1740
|
-
}
|
|
1741
|
-
function getDestroyListeners() {
|
|
1742
|
-
return Array.from(destroyListeners);
|
|
1717
|
+
* @internal
|
|
1718
|
+
* Internal method for adding instances with configurable scope and storage.
|
|
1719
|
+
*/ addInstanceToStorage(token, instance, storage, scope, requestId) {
|
|
1720
|
+
if (token instanceof InjectionToken) {
|
|
1721
|
+
if (token.schema) {
|
|
1722
|
+
if (token.schema?.def?.type !== "optional") throw DIError.tokenSchemaRequiredError(token.name);
|
|
1723
|
+
}
|
|
1743
1724
|
}
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
},
|
|
1753
|
-
addDestroyListener,
|
|
1754
|
-
getDestroyListeners,
|
|
1755
|
-
container,
|
|
1756
|
-
deps
|
|
1757
|
-
};
|
|
1725
|
+
const tokenResolver = this.getTokenResolver();
|
|
1726
|
+
const registry = this.getRegistry();
|
|
1727
|
+
const normalizedToken = tokenResolver.normalizeToken(token);
|
|
1728
|
+
const realToken = tokenResolver.getRegistryToken(token);
|
|
1729
|
+
if (typeof token === "function" && !registry.has(realToken)) registry.set(realToken, scope, token, InjectableType.Class);
|
|
1730
|
+
else if (!registry.has(realToken)) registry.set(realToken, scope, StubFactoryClass, InjectableType.Class, -1);
|
|
1731
|
+
const instanceName = this.getNameResolver().generateInstanceName(normalizedToken, normalizedToken instanceof BoundInjectionToken ? normalizedToken.value : void 0, requestId, scope);
|
|
1732
|
+
storage.storeInstance(instanceName, instance);
|
|
1758
1733
|
}
|
|
1759
1734
|
};
|
|
1760
1735
|
|
|
1761
1736
|
//#endregion
|
|
1762
|
-
//#region src/
|
|
1737
|
+
//#region src/container/scoped-container.mts
|
|
1763
1738
|
/**
|
|
1764
|
-
*
|
|
1739
|
+
* Request-scoped dependency injection container.
|
|
1765
1740
|
*
|
|
1766
|
-
*
|
|
1767
|
-
* delegating
|
|
1768
|
-
*
|
|
1769
|
-
|
|
1741
|
+
* Wraps a parent Container and provides isolated request-scoped instances
|
|
1742
|
+
* while delegating singleton and transient resolution to the parent.
|
|
1743
|
+
* This design eliminates race conditions that can occur with async operations
|
|
1744
|
+
* when multiple requests are processed concurrently.
|
|
1745
|
+
*/ var ScopedContainer = class extends AbstractContainer {
|
|
1746
|
+
parent;
|
|
1770
1747
|
registry;
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
this.registry = registry;
|
|
1781
|
-
this.logger = logger;
|
|
1782
|
-
this.injectors = injectors;
|
|
1783
|
-
this.eventBus = new LifecycleEventBus(logger);
|
|
1784
|
-
this.manager = new HolderManager(logger);
|
|
1785
|
-
this.instantiator = new Instantiator(injectors);
|
|
1786
|
-
this.tokenProcessor = new TokenProcessor(logger);
|
|
1787
|
-
this.invalidator = new Invalidator(this.manager, this.eventBus, logger);
|
|
1788
|
-
this.instanceResolver = new InstanceResolver(this.registry, this.manager, this.instantiator, this.tokenProcessor, logger, this);
|
|
1748
|
+
requestId;
|
|
1749
|
+
defaultScope = InjectableScope.Request;
|
|
1750
|
+
storage;
|
|
1751
|
+
disposed = false;
|
|
1752
|
+
metadata;
|
|
1753
|
+
constructor(parent, registry, requestId, metadata) {
|
|
1754
|
+
super(), this.parent = parent, this.registry = registry, this.requestId = requestId;
|
|
1755
|
+
this.storage = new UnifiedStorage(InjectableScope.Request);
|
|
1756
|
+
this.metadata = metadata || {};
|
|
1789
1757
|
}
|
|
1790
|
-
|
|
1791
|
-
return this.
|
|
1758
|
+
getStorage() {
|
|
1759
|
+
return this.storage;
|
|
1792
1760
|
}
|
|
1793
|
-
|
|
1794
|
-
return this.
|
|
1761
|
+
getRegistry() {
|
|
1762
|
+
return this.registry;
|
|
1795
1763
|
}
|
|
1796
|
-
|
|
1797
|
-
return this.
|
|
1764
|
+
getTokenResolver() {
|
|
1765
|
+
return this.parent.getTokenResolver();
|
|
1798
1766
|
}
|
|
1799
|
-
|
|
1800
|
-
return this.
|
|
1767
|
+
getNameResolver() {
|
|
1768
|
+
return this.parent.getNameResolver();
|
|
1801
1769
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
if (err) throw err;
|
|
1805
|
-
return this.tokenProcessor.generateInstanceName(actualToken, validatedArgs);
|
|
1770
|
+
getServiceInvalidator() {
|
|
1771
|
+
return this.parent.getServiceInvalidator();
|
|
1806
1772
|
}
|
|
1807
1773
|
/**
|
|
1808
|
-
* Gets
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
* @param contextContainer The container to use for creating FactoryContext
|
|
1812
|
-
*/ async getInstance(token, args, contextContainer) {
|
|
1813
|
-
const [err, data] = await this.instanceResolver.resolveInstance(token, args, contextContainer);
|
|
1814
|
-
if (err) return [err];
|
|
1815
|
-
return [void 0, data];
|
|
1774
|
+
* Gets the request ID for this scoped container.
|
|
1775
|
+
*/ getRequestId() {
|
|
1776
|
+
return this.requestId;
|
|
1816
1777
|
}
|
|
1817
1778
|
/**
|
|
1818
|
-
* Gets
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1779
|
+
* Gets the parent container.
|
|
1780
|
+
*/ getParent() {
|
|
1781
|
+
return this.parent;
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Gets metadata from the request context.
|
|
1785
|
+
*/ getMetadata(key) {
|
|
1786
|
+
return this.metadata[key];
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Sets metadata on the request context.
|
|
1790
|
+
*/ setMetadata(key, value) {
|
|
1791
|
+
this.metadata[key] = value;
|
|
1792
|
+
}
|
|
1793
|
+
async get(token, args) {
|
|
1794
|
+
if (this.disposed) throw new Error("ScopedContainer has been disposed");
|
|
1795
|
+
const realToken = this.getTokenResolver().getRegistryToken(token);
|
|
1796
|
+
if (this.registry.has(realToken)) {
|
|
1797
|
+
if (this.registry.get(realToken).scope === InjectableScope.Request) {
|
|
1798
|
+
const [error$1, instance$1] = await this.parent.getInstanceResolver().resolveRequestScopedInstance(token, args, this);
|
|
1799
|
+
if (error$1) throw error$1;
|
|
1800
|
+
return instance$1;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
const [error, instance] = await this.parent.getInstanceResolver().resolveInstance(token, args, this, this.storage, this.requestId);
|
|
1824
1804
|
if (error) throw error;
|
|
1825
1805
|
return instance;
|
|
1826
1806
|
}
|
|
1827
1807
|
/**
|
|
1828
|
-
*
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
* @param scopedContainer The ScopedContainer that owns the request context
|
|
1834
|
-
*/ async resolveRequestScoped(token, args, scopedContainer) {
|
|
1835
|
-
const [err, data] = await this.instanceResolver.resolveRequestScopedInstance(token, args, scopedContainer);
|
|
1836
|
-
if (err) throw err;
|
|
1837
|
-
return data;
|
|
1838
|
-
}
|
|
1839
|
-
getSyncInstance(token, args, contextContainer) {
|
|
1840
|
-
return this.instanceResolver.getSyncInstance(token, args, contextContainer);
|
|
1808
|
+
* Invalidates a service and its dependencies.
|
|
1809
|
+
*/ async invalidate(service) {
|
|
1810
|
+
const holder = this.storage.findByInstance(service);
|
|
1811
|
+
if (!holder) return this.parent.invalidate(service);
|
|
1812
|
+
await this.getServiceInvalidator().invalidateWithStorage(holder.name, this.storage);
|
|
1841
1813
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1814
|
+
/**
|
|
1815
|
+
* Disposes the container and cleans up all resources.
|
|
1816
|
+
* Alias for endRequest().
|
|
1817
|
+
*/ async dispose() {
|
|
1818
|
+
return this.endRequest();
|
|
1844
1819
|
}
|
|
1845
1820
|
/**
|
|
1846
|
-
*
|
|
1847
|
-
*
|
|
1848
|
-
*
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1821
|
+
* @internal
|
|
1822
|
+
* Attempts to get an instance synchronously if it already exists.
|
|
1823
|
+
* Checks request storage first, then delegates to parent.
|
|
1824
|
+
*/ tryGetSync(token, args) {
|
|
1825
|
+
const realToken = this.getTokenResolver().getRegistryToken(token);
|
|
1826
|
+
if ((this.registry.has(realToken) ? this.registry.get(realToken).scope : InjectableScope.Singleton) === InjectableScope.Request) {
|
|
1827
|
+
const result = this.tryGetSyncFromStorage(token, args, this.storage, this.requestId);
|
|
1828
|
+
if (result !== null) return result;
|
|
1829
|
+
}
|
|
1830
|
+
return this.parent.tryGetSync(token, args, this.requestId);
|
|
1854
1831
|
}
|
|
1855
1832
|
/**
|
|
1856
|
-
*
|
|
1857
|
-
|
|
1858
|
-
|
|
1833
|
+
* Adds an instance to the container.
|
|
1834
|
+
* Overrides base class to check disposed state.
|
|
1835
|
+
*/ addInstance(token, instance) {
|
|
1836
|
+
if (this.disposed) throw new Error("ScopedContainer has been disposed");
|
|
1837
|
+
super.addInstance(token, instance);
|
|
1859
1838
|
}
|
|
1860
1839
|
/**
|
|
1861
|
-
*
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1840
|
+
* Ends the request and cleans up all request-scoped services.
|
|
1841
|
+
*/ async endRequest() {
|
|
1842
|
+
if (this.disposed) return;
|
|
1843
|
+
this.disposed = true;
|
|
1844
|
+
await this.getServiceInvalidator().clearAllWithStorage(this.storage);
|
|
1845
|
+
this.parent.removeRequestId(this.requestId);
|
|
1865
1846
|
}
|
|
1866
1847
|
};
|
|
1867
1848
|
|
|
@@ -2144,142 +2125,125 @@ function applyDecs2203RFactory() {
|
|
|
2144
2125
|
function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
|
|
2145
2126
|
return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass);
|
|
2146
2127
|
}
|
|
2147
|
-
var _dec, _initClass;
|
|
2128
|
+
var _dec, _initClass, _AbstractContainer;
|
|
2148
2129
|
let _Container;
|
|
2149
2130
|
_dec = Injectable();
|
|
2150
|
-
var Container = class {
|
|
2131
|
+
var Container = class extends (_AbstractContainer = AbstractContainer) {
|
|
2151
2132
|
registry;
|
|
2152
2133
|
logger;
|
|
2153
2134
|
injectors;
|
|
2154
2135
|
static {
|
|
2155
|
-
({c: [_Container, _initClass]} = _apply_decs_2203_r(this, [], [_dec]));
|
|
2136
|
+
({c: [_Container, _initClass]} = _apply_decs_2203_r(this, [], [_dec], _AbstractContainer));
|
|
2156
2137
|
}
|
|
2157
2138
|
constructor(registry = globalRegistry, logger = null, injectors = defaultInjectors) {
|
|
2158
|
-
this.registry = registry;
|
|
2159
|
-
this.
|
|
2160
|
-
this.
|
|
2161
|
-
this.
|
|
2139
|
+
super(), this.registry = registry, this.logger = logger, this.injectors = injectors;
|
|
2140
|
+
this.storage = new UnifiedStorage(InjectableScope.Singleton);
|
|
2141
|
+
this.eventBus = new LifecycleEventBus(logger);
|
|
2142
|
+
this.nameResolver = new NameResolver(logger);
|
|
2143
|
+
this.tokenResolver = new TokenResolver(logger);
|
|
2144
|
+
this.scopeTracker = new ScopeTracker(registry, this.nameResolver, logger);
|
|
2145
|
+
this.serviceInitializer = new ServiceInitializer(injectors);
|
|
2146
|
+
this.serviceInvalidator = new ServiceInvalidator(this.eventBus, logger);
|
|
2147
|
+
this.instanceResolver = new InstanceResolver(registry, this.storage, this.serviceInitializer, this.tokenResolver, this.nameResolver, this.scopeTracker, this.serviceInvalidator, this.eventBus, logger);
|
|
2162
2148
|
this.registerSelf();
|
|
2163
2149
|
}
|
|
2164
|
-
|
|
2150
|
+
defaultScope = InjectableScope.Singleton;
|
|
2151
|
+
requestId = void 0;
|
|
2152
|
+
storage;
|
|
2153
|
+
serviceInitializer;
|
|
2154
|
+
serviceInvalidator;
|
|
2155
|
+
tokenResolver;
|
|
2156
|
+
nameResolver;
|
|
2157
|
+
scopeTracker;
|
|
2158
|
+
eventBus;
|
|
2159
|
+
instanceResolver;
|
|
2165
2160
|
activeRequestIds = /* @__PURE__ */ new Set();
|
|
2166
2161
|
registerSelf() {
|
|
2167
2162
|
const token = getInjectableToken(_Container);
|
|
2168
|
-
|
|
2169
|
-
this.
|
|
2163
|
+
this.registry.set(token, InjectableScope.Singleton, _Container, InjectableType.Class);
|
|
2164
|
+
const instanceName = this.nameResolver.generateInstanceName(token, void 0, void 0, InjectableScope.Singleton);
|
|
2165
|
+
this.storage.storeInstance(instanceName, this);
|
|
2170
2166
|
}
|
|
2171
2167
|
async get(token, args) {
|
|
2172
|
-
const realToken = this.
|
|
2168
|
+
const realToken = this.tokenResolver.getRegistryToken(token);
|
|
2173
2169
|
if (this.registry.has(realToken)) {
|
|
2174
|
-
if (this.registry.get(realToken).scope === InjectableScope.Request) throw DIError.
|
|
2170
|
+
if (this.registry.get(realToken).scope === InjectableScope.Request) throw DIError.scopeMismatchError(realToken.name, "ScopedContainer", "Container");
|
|
2175
2171
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
* Gets an instance with a specific container context.
|
|
2180
|
-
* Used by ScopedContainer to delegate singleton/transient resolution
|
|
2181
|
-
* while maintaining the correct container context for nested inject() calls.
|
|
2182
|
-
*
|
|
2183
|
-
* @internal
|
|
2184
|
-
*/ async getWithContext(token, args, contextContainer) {
|
|
2185
|
-
return this.serviceLocator.getOrThrowInstance(token, args, contextContainer);
|
|
2186
|
-
}
|
|
2187
|
-
/**
|
|
2188
|
-
* Resolves a request-scoped service for a ScopedContainer.
|
|
2189
|
-
* The service will be stored in the ScopedContainer's request context.
|
|
2190
|
-
*
|
|
2191
|
-
* @internal
|
|
2192
|
-
*/ async resolveForRequest(token, args, scopedContainer) {
|
|
2193
|
-
return this.serviceLocator.resolveRequestScoped(token, args, scopedContainer);
|
|
2194
|
-
}
|
|
2195
|
-
/**
|
|
2196
|
-
* Gets the underlying ServiceLocator instance for advanced usage
|
|
2197
|
-
*/ getServiceLocator() {
|
|
2198
|
-
return this.serviceLocator;
|
|
2199
|
-
}
|
|
2200
|
-
/**
|
|
2201
|
-
* Gets the registry
|
|
2202
|
-
*/ getRegistry() {
|
|
2203
|
-
return this.registry;
|
|
2172
|
+
const [error, instance] = await this.instanceResolver.resolveInstance(token, args, this);
|
|
2173
|
+
if (error) throw error;
|
|
2174
|
+
return instance;
|
|
2204
2175
|
}
|
|
2205
2176
|
/**
|
|
2206
|
-
* Invalidates a service and its dependencies
|
|
2177
|
+
* Invalidates a service and its dependencies.
|
|
2207
2178
|
*/ async invalidate(service) {
|
|
2208
|
-
const holder = this.
|
|
2209
|
-
if (holder)
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
* Gets a service holder by instance (reverse lookup)
|
|
2213
|
-
*/ getHolderByInstance(instance) {
|
|
2214
|
-
const holderMap = Array.from(this.serviceLocator.getManager().filter((holder) => holder.instance === instance).values());
|
|
2215
|
-
return holderMap.length > 0 ? holderMap[0] : null;
|
|
2216
|
-
}
|
|
2217
|
-
/**
|
|
2218
|
-
* Checks if a service is registered in the container
|
|
2219
|
-
*/ isRegistered(token) {
|
|
2220
|
-
try {
|
|
2221
|
-
return this.serviceLocator.getInstanceIdentifier(token) !== null;
|
|
2222
|
-
} catch {
|
|
2223
|
-
return false;
|
|
2179
|
+
const holder = this.storage.findByInstance(service);
|
|
2180
|
+
if (!holder) {
|
|
2181
|
+
this.logger?.warn(`[Container] Service instance not found for invalidation`);
|
|
2182
|
+
return;
|
|
2224
2183
|
}
|
|
2184
|
+
await this.serviceInvalidator.invalidateWithStorage(holder.name, this.storage);
|
|
2225
2185
|
}
|
|
2226
2186
|
/**
|
|
2227
|
-
* Disposes the container and cleans up all resources
|
|
2187
|
+
* Disposes the container and cleans up all resources.
|
|
2228
2188
|
*/ async dispose() {
|
|
2229
|
-
await this.
|
|
2230
|
-
}
|
|
2231
|
-
/**
|
|
2232
|
-
* Waits for all pending operations to complete
|
|
2233
|
-
*/ async ready() {
|
|
2234
|
-
await this.serviceLocator.ready();
|
|
2189
|
+
await this.serviceInvalidator.clearAllWithStorage(this.storage);
|
|
2235
2190
|
}
|
|
2236
2191
|
/**
|
|
2237
2192
|
* @internal
|
|
2238
2193
|
* Attempts to get an instance synchronously if it already exists.
|
|
2239
|
-
*
|
|
2240
|
-
*/ tryGetSync(token, args) {
|
|
2241
|
-
return this.
|
|
2194
|
+
* Overrides base class to support requestId parameter for ScopedContainer compatibility.
|
|
2195
|
+
*/ tryGetSync(token, args, requestId) {
|
|
2196
|
+
return this.tryGetSyncFromStorage(token, args, this.storage, requestId ?? this.requestId);
|
|
2242
2197
|
}
|
|
2243
2198
|
/**
|
|
2244
2199
|
* Begins a new request context and returns a ScopedContainer.
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
* while delegating singleton and transient services to this Container.
|
|
2248
|
-
*
|
|
2249
|
-
* @param requestId Unique identifier for this request
|
|
2250
|
-
* @param metadata Optional metadata for the request
|
|
2251
|
-
* @param priority Priority for resolution (higher = more priority)
|
|
2252
|
-
* @returns A ScopedContainer for this request
|
|
2253
|
-
*/ beginRequest(requestId, metadata, priority = 100) {
|
|
2254
|
-
if (this.activeRequestIds.has(requestId)) throw DIError.unknown(`Request context "${requestId}" already exists. Use a unique request ID.`);
|
|
2200
|
+
*/ beginRequest(requestId, metadata) {
|
|
2201
|
+
if (this.activeRequestIds.has(requestId)) throw new Error(`Request with ID ${requestId} already exists`);
|
|
2255
2202
|
this.activeRequestIds.add(requestId);
|
|
2256
|
-
|
|
2257
|
-
return new ScopedContainer(this, this.registry, requestId, metadata, priority);
|
|
2258
|
-
}
|
|
2259
|
-
/**
|
|
2260
|
-
* Removes a request ID from the active set.
|
|
2261
|
-
* Called by ScopedContainer when the request ends.
|
|
2262
|
-
*
|
|
2263
|
-
* @internal
|
|
2264
|
-
*/ removeActiveRequest(requestId) {
|
|
2265
|
-
this.activeRequestIds.delete(requestId);
|
|
2266
|
-
this.logger?.log(`[Container] Ended request context: ${requestId}`);
|
|
2203
|
+
return new ScopedContainer(this, this.registry, requestId, metadata);
|
|
2267
2204
|
}
|
|
2268
2205
|
/**
|
|
2269
|
-
* Gets
|
|
2206
|
+
* Gets all active request IDs.
|
|
2270
2207
|
*/ getActiveRequestIds() {
|
|
2271
2208
|
return this.activeRequestIds;
|
|
2272
2209
|
}
|
|
2273
2210
|
/**
|
|
2274
|
-
* Checks if a request
|
|
2211
|
+
* Checks if a request is active.
|
|
2275
2212
|
*/ hasActiveRequest(requestId) {
|
|
2276
2213
|
return this.activeRequestIds.has(requestId);
|
|
2277
2214
|
}
|
|
2278
2215
|
/**
|
|
2279
|
-
*
|
|
2280
|
-
*
|
|
2281
|
-
*/
|
|
2282
|
-
|
|
2216
|
+
* Removes a request ID from active requests.
|
|
2217
|
+
* Called by ScopedContainer when request ends.
|
|
2218
|
+
*/ removeRequestId(requestId) {
|
|
2219
|
+
this.activeRequestIds.delete(requestId);
|
|
2220
|
+
}
|
|
2221
|
+
getStorage() {
|
|
2222
|
+
return this.storage;
|
|
2223
|
+
}
|
|
2224
|
+
getServiceInitializer() {
|
|
2225
|
+
return this.serviceInitializer;
|
|
2226
|
+
}
|
|
2227
|
+
getServiceInvalidator() {
|
|
2228
|
+
return this.serviceInvalidator;
|
|
2229
|
+
}
|
|
2230
|
+
getTokenResolver() {
|
|
2231
|
+
return this.tokenResolver;
|
|
2232
|
+
}
|
|
2233
|
+
getNameResolver() {
|
|
2234
|
+
return this.nameResolver;
|
|
2235
|
+
}
|
|
2236
|
+
getScopeTracker() {
|
|
2237
|
+
return this.scopeTracker;
|
|
2238
|
+
}
|
|
2239
|
+
getEventBus() {
|
|
2240
|
+
return this.eventBus;
|
|
2241
|
+
}
|
|
2242
|
+
getRegistry() {
|
|
2243
|
+
return this.registry;
|
|
2244
|
+
}
|
|
2245
|
+
getInstanceResolver() {
|
|
2246
|
+
return this.instanceResolver;
|
|
2283
2247
|
}
|
|
2284
2248
|
static {
|
|
2285
2249
|
_initClass();
|
|
@@ -2287,5 +2251,5 @@ var Container = class {
|
|
|
2287
2251
|
};
|
|
2288
2252
|
|
|
2289
2253
|
//#endregion
|
|
2290
|
-
export {
|
|
2291
|
-
//# sourceMappingURL=container-
|
|
2254
|
+
export { Registry as A, withResolutionContext as C, DIError as D, isUsingNativeAsyncLocalStorage as E, InjectableTokenMeta as F, InjectableType as I, InjectableScope as L, BoundInjectionToken as M, FactoryInjectionToken as N, DIErrorCode as O, InjectionToken as P, getCurrentResolutionContext as S, createAsyncLocalStorage as T, ScopeTracker as _, UnifiedStorage as a, CircularDetector as b, asyncInject as c, optional as d, provideFactoryContext as f, ServiceInitializer as g, ServiceInvalidator as h, LifecycleEventBus as i, globalRegistry as j, Injectable as k, defaultInjectors as l, getInjectors as m, ScopedContainer as n, TokenResolver as o, wrapSyncInit as p, StubFactoryClass as r, getInjectableToken as s, _Container as t, inject as u, NameResolver as v, withoutResolutionContext as w, InstanceStatus as x, InstanceResolver as y };
|
|
2255
|
+
//# sourceMappingURL=container-8-z89TyQ.mjs.map
|