@navios/di 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/lib/browser/index.d.mts +15 -84
  3. package/lib/browser/index.d.mts.map +1 -1
  4. package/lib/browser/index.mjs +121 -484
  5. package/lib/browser/index.mjs.map +1 -1
  6. package/lib/{index-DW3K5sOX.d.cts → container-BuAutHGg.d.mts} +17 -62
  7. package/lib/container-BuAutHGg.d.mts.map +1 -0
  8. package/lib/{testing-DIaIRiJz.cjs → container-DnzgpfBe.cjs} +81 -459
  9. package/lib/container-DnzgpfBe.cjs.map +1 -0
  10. package/lib/{testing-BG_fa9TJ.mjs → container-Pb_Y4Z4x.mjs} +81 -446
  11. package/lib/container-Pb_Y4Z4x.mjs.map +1 -0
  12. package/lib/{index-7jfWsiG4.d.mts → container-oGTgX2iX.d.cts} +12 -67
  13. package/lib/container-oGTgX2iX.d.cts.map +1 -0
  14. package/lib/index.cjs +44 -52
  15. package/lib/index.cjs.map +1 -1
  16. package/lib/index.d.cts +6 -25
  17. package/lib/index.d.cts.map +1 -1
  18. package/lib/index.d.mts +6 -25
  19. package/lib/index.d.mts.map +1 -1
  20. package/lib/index.mjs +2 -2
  21. package/lib/testing/index.cjs +343 -4
  22. package/lib/testing/index.cjs.map +1 -0
  23. package/lib/testing/index.d.cts +65 -2
  24. package/lib/testing/index.d.cts.map +1 -0
  25. package/lib/testing/index.d.mts +65 -2
  26. package/lib/testing/index.d.mts.map +1 -0
  27. package/lib/testing/index.mjs +341 -2
  28. package/lib/testing/index.mjs.map +1 -0
  29. package/package.json +1 -1
  30. package/src/__tests__/async-local-storage.browser.spec.mts +18 -92
  31. package/src/__tests__/container.spec.mts +93 -0
  32. package/src/__tests__/e2e.browser.spec.mts +7 -15
  33. package/src/__tests__/library-findings.spec.mts +23 -21
  34. package/src/browser.mts +4 -9
  35. package/src/container/scoped-container.mts +14 -8
  36. package/src/index.mts +5 -8
  37. package/src/internal/context/async-local-storage.browser.mts +19 -0
  38. package/src/internal/context/async-local-storage.mts +46 -98
  39. package/src/internal/context/async-local-storage.types.mts +7 -0
  40. package/src/internal/context/resolution-context.mts +23 -5
  41. package/src/internal/context/sync-local-storage.mts +3 -1
  42. package/src/internal/core/instance-resolver.mts +8 -1
  43. package/src/internal/lifecycle/circular-detector.mts +15 -0
  44. package/src/token/registry.mts +21 -0
  45. package/tsdown.config.mts +12 -1
  46. package/vitest.config.mts +25 -3
  47. package/lib/index-7jfWsiG4.d.mts.map +0 -1
  48. package/lib/index-DW3K5sOX.d.cts.map +0 -1
  49. package/lib/testing-BG_fa9TJ.mjs.map +0 -1
  50. package/lib/testing-DIaIRiJz.cjs.map +0 -1
@@ -1,3 +1,342 @@
1
- import { n as _TestContainer, t as TestBindingBuilder } from "../testing-BG_fa9TJ.mjs";
1
+ import { A as Injectable, C as getInjectableToken, L as InjectableType, N as globalRegistry, R as InjectableScope, t as _Container$1 } from "../container-Pb_Y4Z4x.mjs";
2
2
 
3
- export { TestBindingBuilder, _TestContainer as TestContainer };
3
+ //#region src/testing/test-container.mts
4
+ function applyDecs2203RFactory() {
5
+ function createAddInitializerMethod(initializers, decoratorFinishedRef) {
6
+ return function addInitializer(initializer) {
7
+ assertNotFinished(decoratorFinishedRef, "addInitializer");
8
+ assertCallable(initializer, "An initializer");
9
+ initializers.push(initializer);
10
+ };
11
+ }
12
+ function memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, metadata, value) {
13
+ var kindStr;
14
+ switch (kind) {
15
+ case 1:
16
+ kindStr = "accessor";
17
+ break;
18
+ case 2:
19
+ kindStr = "method";
20
+ break;
21
+ case 3:
22
+ kindStr = "getter";
23
+ break;
24
+ case 4:
25
+ kindStr = "setter";
26
+ break;
27
+ default: kindStr = "field";
28
+ }
29
+ var ctx = {
30
+ kind: kindStr,
31
+ name: isPrivate ? "#" + name : name,
32
+ static: isStatic,
33
+ private: isPrivate,
34
+ metadata
35
+ };
36
+ var decoratorFinishedRef = { v: false };
37
+ ctx.addInitializer = createAddInitializerMethod(initializers, decoratorFinishedRef);
38
+ var get, set;
39
+ if (kind === 0) if (isPrivate) {
40
+ get = desc.get;
41
+ set = desc.set;
42
+ } else {
43
+ get = function() {
44
+ return this[name];
45
+ };
46
+ set = function(v) {
47
+ this[name] = v;
48
+ };
49
+ }
50
+ else if (kind === 2) get = function() {
51
+ return desc.value;
52
+ };
53
+ else {
54
+ if (kind === 1 || kind === 3) get = function() {
55
+ return desc.get.call(this);
56
+ };
57
+ if (kind === 1 || kind === 4) set = function(v) {
58
+ desc.set.call(this, v);
59
+ };
60
+ }
61
+ ctx.access = get && set ? {
62
+ get,
63
+ set
64
+ } : get ? { get } : { set };
65
+ try {
66
+ return dec(value, ctx);
67
+ } finally {
68
+ decoratorFinishedRef.v = true;
69
+ }
70
+ }
71
+ function assertNotFinished(decoratorFinishedRef, fnName) {
72
+ if (decoratorFinishedRef.v) throw new Error("attempted to call " + fnName + " after decoration was finished");
73
+ }
74
+ function assertCallable(fn, hint) {
75
+ if (typeof fn !== "function") throw new TypeError(hint + " must be a function");
76
+ }
77
+ function assertValidReturnValue(kind, value) {
78
+ var type = typeof value;
79
+ if (kind === 1) {
80
+ if (type !== "object" || value === null) throw new TypeError("accessor decorators must return an object with get, set, or init properties or void 0");
81
+ if (value.get !== void 0) assertCallable(value.get, "accessor.get");
82
+ if (value.set !== void 0) assertCallable(value.set, "accessor.set");
83
+ if (value.init !== void 0) assertCallable(value.init, "accessor.init");
84
+ } else if (type !== "function") {
85
+ var hint;
86
+ if (kind === 0) hint = "field";
87
+ else if (kind === 10) hint = "class";
88
+ else hint = "method";
89
+ throw new TypeError(hint + " decorators must return a function or void 0");
90
+ }
91
+ }
92
+ function applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers, metadata) {
93
+ var decs = decInfo[0];
94
+ var desc, init, value;
95
+ if (isPrivate) if (kind === 0 || kind === 1) desc = {
96
+ get: decInfo[3],
97
+ set: decInfo[4]
98
+ };
99
+ else if (kind === 3) desc = { get: decInfo[3] };
100
+ else if (kind === 4) desc = { set: decInfo[3] };
101
+ else desc = { value: decInfo[3] };
102
+ else if (kind !== 0) desc = Object.getOwnPropertyDescriptor(base, name);
103
+ if (kind === 1) value = {
104
+ get: desc.get,
105
+ set: desc.set
106
+ };
107
+ else if (kind === 2) value = desc.value;
108
+ else if (kind === 3) value = desc.get;
109
+ else if (kind === 4) value = desc.set;
110
+ var newValue, get, set;
111
+ if (typeof decs === "function") {
112
+ newValue = memberDec(decs, name, desc, initializers, kind, isStatic, isPrivate, metadata, value);
113
+ if (newValue !== void 0) {
114
+ assertValidReturnValue(kind, newValue);
115
+ if (kind === 0) init = newValue;
116
+ else if (kind === 1) {
117
+ init = newValue.init;
118
+ get = newValue.get || value.get;
119
+ set = newValue.set || value.set;
120
+ value = {
121
+ get,
122
+ set
123
+ };
124
+ } else value = newValue;
125
+ }
126
+ } else for (var i = decs.length - 1; i >= 0; i--) {
127
+ var dec = decs[i];
128
+ newValue = memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, metadata, value);
129
+ if (newValue !== void 0) {
130
+ assertValidReturnValue(kind, newValue);
131
+ var newInit;
132
+ if (kind === 0) newInit = newValue;
133
+ else if (kind === 1) {
134
+ newInit = newValue.init;
135
+ get = newValue.get || value.get;
136
+ set = newValue.set || value.set;
137
+ value = {
138
+ get,
139
+ set
140
+ };
141
+ } else value = newValue;
142
+ if (newInit !== void 0) if (init === void 0) init = newInit;
143
+ else if (typeof init === "function") init = [init, newInit];
144
+ else init.push(newInit);
145
+ }
146
+ }
147
+ if (kind === 0 || kind === 1) {
148
+ if (init === void 0) init = function(instance, init$1) {
149
+ return init$1;
150
+ };
151
+ else if (typeof init !== "function") {
152
+ var ownInitializers = init;
153
+ init = function(instance, init$1) {
154
+ var value$1 = init$1;
155
+ for (var i$1 = 0; i$1 < ownInitializers.length; i$1++) value$1 = ownInitializers[i$1].call(instance, value$1);
156
+ return value$1;
157
+ };
158
+ } else {
159
+ var originalInitializer = init;
160
+ init = function(instance, init$1) {
161
+ return originalInitializer.call(instance, init$1);
162
+ };
163
+ }
164
+ ret.push(init);
165
+ }
166
+ if (kind !== 0) {
167
+ if (kind === 1) {
168
+ desc.get = value.get;
169
+ desc.set = value.set;
170
+ } else if (kind === 2) desc.value = value;
171
+ else if (kind === 3) desc.get = value;
172
+ else if (kind === 4) desc.set = value;
173
+ if (isPrivate) if (kind === 1) {
174
+ ret.push(function(instance, args) {
175
+ return value.get.call(instance, args);
176
+ });
177
+ ret.push(function(instance, args) {
178
+ return value.set.call(instance, args);
179
+ });
180
+ } else if (kind === 2) ret.push(value);
181
+ else ret.push(function(instance, args) {
182
+ return value.call(instance, args);
183
+ });
184
+ else Object.defineProperty(base, name, desc);
185
+ }
186
+ }
187
+ function applyMemberDecs(Class, decInfos, metadata) {
188
+ var ret = [];
189
+ var protoInitializers;
190
+ var staticInitializers;
191
+ var existingProtoNonFields = /* @__PURE__ */ new Map();
192
+ var existingStaticNonFields = /* @__PURE__ */ new Map();
193
+ for (var i = 0; i < decInfos.length; i++) {
194
+ var decInfo = decInfos[i];
195
+ if (!Array.isArray(decInfo)) continue;
196
+ var kind = decInfo[1];
197
+ var name = decInfo[2];
198
+ var isPrivate = decInfo.length > 3;
199
+ var isStatic = kind >= 5;
200
+ var base;
201
+ var initializers;
202
+ if (isStatic) {
203
+ base = Class;
204
+ kind = kind - 5;
205
+ staticInitializers = staticInitializers || [];
206
+ initializers = staticInitializers;
207
+ } else {
208
+ base = Class.prototype;
209
+ protoInitializers = protoInitializers || [];
210
+ initializers = protoInitializers;
211
+ }
212
+ if (kind !== 0 && !isPrivate) {
213
+ var existingNonFields = isStatic ? existingStaticNonFields : existingProtoNonFields;
214
+ var existingKind = existingNonFields.get(name) || 0;
215
+ if (existingKind === true || existingKind === 3 && kind !== 4 || existingKind === 4 && kind !== 3) throw new Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: " + name);
216
+ else if (!existingKind && kind > 2) existingNonFields.set(name, kind);
217
+ else existingNonFields.set(name, true);
218
+ }
219
+ applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers, metadata);
220
+ }
221
+ pushInitializers(ret, protoInitializers);
222
+ pushInitializers(ret, staticInitializers);
223
+ return ret;
224
+ }
225
+ function pushInitializers(ret, initializers) {
226
+ if (initializers) ret.push(function(instance) {
227
+ for (var i = 0; i < initializers.length; i++) initializers[i].call(instance);
228
+ return instance;
229
+ });
230
+ }
231
+ function applyClassDecs(targetClass, classDecs, metadata) {
232
+ if (classDecs.length > 0) {
233
+ var initializers = [];
234
+ var newClass = targetClass;
235
+ var name = targetClass.name;
236
+ for (var i = classDecs.length - 1; i >= 0; i--) {
237
+ var decoratorFinishedRef = { v: false };
238
+ try {
239
+ var nextNewClass = classDecs[i](newClass, {
240
+ kind: "class",
241
+ name,
242
+ addInitializer: createAddInitializerMethod(initializers, decoratorFinishedRef),
243
+ metadata
244
+ });
245
+ } finally {
246
+ decoratorFinishedRef.v = true;
247
+ }
248
+ if (nextNewClass !== void 0) {
249
+ assertValidReturnValue(10, nextNewClass);
250
+ newClass = nextNewClass;
251
+ }
252
+ }
253
+ return [defineMetadata(newClass, metadata), function() {
254
+ for (var i$1 = 0; i$1 < initializers.length; i$1++) initializers[i$1].call(newClass);
255
+ }];
256
+ }
257
+ }
258
+ function defineMetadata(Class, metadata) {
259
+ return Object.defineProperty(Class, Symbol.metadata || Symbol.for("Symbol.metadata"), {
260
+ configurable: true,
261
+ enumerable: true,
262
+ value: metadata
263
+ });
264
+ }
265
+ return function applyDecs2203R(targetClass, memberDecs, classDecs, parentClass) {
266
+ if (parentClass !== void 0) var parentMetadata = parentClass[Symbol.metadata || Symbol.for("Symbol.metadata")];
267
+ var metadata = Object.create(parentMetadata === void 0 ? null : parentMetadata);
268
+ var e = applyMemberDecs(targetClass, memberDecs, metadata);
269
+ if (!classDecs.length) defineMetadata(targetClass, metadata);
270
+ return {
271
+ e,
272
+ get c() {
273
+ return applyClassDecs(targetClass, classDecs, metadata);
274
+ }
275
+ };
276
+ };
277
+ }
278
+ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
279
+ return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass);
280
+ }
281
+ var _dec, _initClass, _Container;
282
+ /**
283
+ * A binding builder for the TestContainer that allows chaining binding operations.
284
+ */ var TestBindingBuilder = class {
285
+ container;
286
+ token;
287
+ constructor(container, token) {
288
+ this.container = container;
289
+ this.token = token;
290
+ }
291
+ /**
292
+ * Binds the token to a specific value.
293
+ * This is useful for testing with mock values or constants.
294
+ * @param value The value to bind to the token
295
+ */ toValue(value) {
296
+ const instanceName = this.container.getServiceLocator().getInstanceIdentifier(this.token);
297
+ this.container.getServiceLocator().getManager().storeCreatedHolder(instanceName, value, InjectableType.Class, InjectableScope.Singleton);
298
+ return this.container;
299
+ }
300
+ /**
301
+ * Binds the token to a class constructor.
302
+ * @param target The class constructor to bind to
303
+ */ toClass(target) {
304
+ this.container["registry"].set(this.token, InjectableScope.Singleton, target, InjectableType.Class);
305
+ return this.container;
306
+ }
307
+ };
308
+ let _TestContainer;
309
+ _dec = Injectable();
310
+ var TestContainer = class extends (_Container = _Container$1) {
311
+ static {
312
+ ({c: [_TestContainer, _initClass]} = _apply_decs_2203_r(this, [], [_dec], _Container));
313
+ }
314
+ constructor(registry = globalRegistry, logger = null, injectors = void 0) {
315
+ super(registry, logger, injectors);
316
+ }
317
+ bind(token) {
318
+ let realToken = token;
319
+ if (typeof token === "function") realToken = getInjectableToken(token);
320
+ return new TestBindingBuilder(this, realToken);
321
+ }
322
+ bindValue(token, value) {
323
+ return this.bind(token).toValue(value);
324
+ }
325
+ bindClass(token, target) {
326
+ return this.bind(token).toClass(target);
327
+ }
328
+ /**
329
+ * Creates a new TestContainer instance with the same configuration.
330
+ * This is useful for creating isolated test containers.
331
+ * @returns A new TestContainer instance
332
+ */ createChild() {
333
+ return new _TestContainer(this.registry, this.logger, this.injectors);
334
+ }
335
+ static {
336
+ _initClass();
337
+ }
338
+ };
339
+
340
+ //#endregion
341
+ export { TestBindingBuilder, _TestContainer as TestContainer };
342
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["Container","Injectable","InjectableScope","InjectableType","globalRegistry","getInjectableToken","TestBindingBuilder","container","token","toValue","value","instanceName","getServiceLocator","getInstanceIdentifier","getManager","storeCreatedHolder","Class","Singleton","toClass","target","set","TestContainer","registry","logger","injectors","undefined","bind","realToken","bindValue","bindClass","createChild"],"sources":["../../src/testing/test-container.mts"],"sourcesContent":["import type { ClassType, InjectionToken } from '../token/injection-token.mjs'\nimport type { Registry } from '../token/registry.mjs'\nimport type { Injectors } from '../utils/index.mjs'\n\nimport { Container } from '../container/container.mjs'\nimport { Injectable } from '../decorators/injectable.decorator.mjs'\nimport { InjectableScope, InjectableType } from '../enums/index.mjs'\nimport { globalRegistry } from '../token/registry.mjs'\nimport { getInjectableToken } from '../utils/index.mjs'\n\n/**\n * A binding builder for the TestContainer that allows chaining binding operations.\n */\nexport class TestBindingBuilder<T> {\n constructor(\n private readonly container: TestContainer,\n private readonly token: InjectionToken<T, any>,\n ) {}\n\n /**\n * Binds the token to a specific value.\n * This is useful for testing with mock values or constants.\n * @param value The value to bind to the token\n */\n toValue(value: T): TestContainer {\n const instanceName = this.container\n .getServiceLocator()\n .getInstanceIdentifier(this.token)\n this.container\n .getServiceLocator()\n .getManager()\n .storeCreatedHolder(\n instanceName,\n value,\n InjectableType.Class,\n InjectableScope.Singleton,\n )\n return this.container\n }\n\n /**\n * Binds the token to a class constructor.\n * @param target The class constructor to bind to\n */\n toClass(target: ClassType): TestContainer {\n this.container['registry'].set(\n this.token,\n InjectableScope.Singleton,\n target,\n InjectableType.Class,\n )\n return this.container\n }\n}\n\n/**\n * TestContainer extends the base Container with additional methods useful for testing.\n * It provides a simplified API for binding values and classes during test setup.\n */\n@Injectable()\nexport class TestContainer extends Container {\n constructor(\n registry: Registry = globalRegistry,\n logger: Console | null = null,\n injectors: Injectors = undefined as any,\n ) {\n super(registry, logger, injectors)\n }\n\n /**\n * Creates a binding builder for the given token.\n * This allows chaining binding operations like bind(Token).toValue(value).\n * @param token The injection token to bind\n * @returns A TestBindingBuilder for chaining binding operations\n */\n bind<T>(token: ClassType): TestBindingBuilder<T>\n bind<T>(token: InjectionToken<T, any>): TestBindingBuilder<T>\n bind(token: any): TestBindingBuilder<any> {\n let realToken = token\n if (typeof token === 'function') {\n realToken = getInjectableToken(token)\n }\n return new TestBindingBuilder(this, realToken)\n }\n\n /**\n * Binds a value directly to a token.\n * This is a convenience method equivalent to bind(token).toValue(value).\n * @param token The injection token to bind\n * @param value The value to bind to the token\n * @returns The TestContainer instance for chaining\n */\n bindValue<T>(token: ClassType, value: T): TestContainer\n bindValue<T>(token: InjectionToken<T, any>, value: T): TestContainer\n bindValue(token: any, value: any): TestContainer {\n return this.bind(token).toValue(value)\n }\n\n /**\n * Binds a class to a token.\n * This is a convenience method equivalent to bind(token).toClass(target).\n * @param token The injection token to bind\n * @param target The class constructor to bind to\n * @returns The TestContainer instance for chaining\n */\n bindClass(token: ClassType, target: ClassType): TestContainer\n bindClass<T>(token: InjectionToken<T, any>, target: ClassType): TestContainer\n bindClass(token: any, target: any): TestContainer {\n return this.bind(token).toClass(target)\n }\n\n /**\n * Creates a new TestContainer instance with the same configuration.\n * This is useful for creating isolated test containers.\n * @returns A new TestContainer instance\n */\n createChild(): TestContainer {\n return new TestContainer(this.registry, this.logger, this.injectors)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA4DmCA;;;GA/CnC,IAAaM,qBAAb,MAAaA;;;CACX,YACE,WACA,OACA;OAFiBC,YAAAA;OACAC,QAAAA;;;;;;IAQnBC,QAAQC,OAAyB;EAC/B,MAAMC,eAAe,KAAKJ,UACvBK,mBAAiB,CACjBC,sBAAsB,KAAKL,MAAK;AACnC,OAAKD,UACFK,mBAAiB,CACjBE,YAAU,CACVC,mBACCJ,cACAD,OACAP,eAAea,OACfd,gBAAgBe,UAAS;AAE7B,SAAO,KAAKV;;;;;IAOdW,QAAQC,QAAkC;AACxC,OAAKZ,UAAU,YAAYa,IACzB,KAAKZ,OACLN,gBAAgBe,WAChBE,QACAhB,eAAea,MAAK;AAEtB,SAAO,KAAKT;;;;OAQfN,YAAAA;AACM,IAAMoB,gBAAN,eAA4BrB,aAAAA,cAAAA;;4EAAAA,WAAAA;;CACjC,YACEsB,WAAqBlB,gBACrBmB,SAAyB,MACzBC,YAAuBC,QACvB;AACA,QAAMH,UAAUC,QAAQC,UAAAA;;CAW1BE,KAAKlB,OAAqC;EACxC,IAAImB,YAAYnB;AAChB,MAAI,OAAOA,UAAU,WACnBmB,aAAYtB,mBAAmBG,MAAAA;AAEjC,SAAO,IAAIF,mBAAmB,MAAMqB,UAAAA;;CAYtCC,UAAUpB,OAAYE,OAA2B;AAC/C,SAAO,KAAKgB,KAAKlB,MAAAA,CAAOC,QAAQC,MAAAA;;CAYlCmB,UAAUrB,OAAYW,QAA4B;AAChD,SAAO,KAAKO,KAAKlB,MAAAA,CAAOU,QAAQC,OAAAA;;;;;;IAQlCW,cAA6B;AAC3B,SAAO,IAAIT,eAAc,KAAKC,UAAU,KAAKC,QAAQ,KAAKC,UAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navios/di",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "author": {
5
5
  "name": "Oleksandr Hanzha",
6
6
  "email": "alex@granted.name"
@@ -6,51 +6,47 @@
6
6
  * 2. All DI functionality works correctly with the polyfill
7
7
  * 3. The resolution context pattern works for synchronous operations
8
8
  *
9
- * Note: We use __testing__.forceSyncMode() to simulate browser behavior
10
- * since happy-dom still runs on Node.js and has process.versions.node.
9
+ * Note: We directly import the browser implementation to test it
10
+ * in isolation, simulating how it would work in a browser bundle.
11
11
  */
12
12
 
13
- import { afterAll, beforeAll, describe, expect, it } from 'vitest'
13
+ import { describe, expect, it } from 'vitest'
14
14
 
15
15
  import {
16
16
  createAsyncLocalStorage,
17
17
  isUsingNativeAsyncLocalStorage,
18
- __testing__,
19
- } from '../internal/context/async-local-storage.mjs'
18
+ } from '../internal/context/async-local-storage.browser.mjs'
20
19
  import {
21
20
  getCurrentResolutionContext,
22
21
  withResolutionContext,
23
- withoutResolutionContext,
24
22
  } from '../internal/context/resolution-context.mjs'
25
- import type { InstanceHolder } from '../internal/holder/instance-holder.mjs'
26
- import { InstanceStatus } from '../internal/holder/instance-holder.mjs'
27
- import { InjectableScope } from '../enums/index.mjs'
28
-
29
- // Force sync mode for all tests in this file to simulate browser behavior
30
- beforeAll(() => {
31
- __testing__.forceSyncMode()
32
- })
33
-
34
- afterAll(() => {
35
- __testing__.reset()
36
- })
23
+ import { SyncLocalStorage } from '../internal/context/sync-local-storage.mjs'
37
24
 
38
25
  // ============================================================================
39
26
  // SECTION 1: Environment Detection
40
27
  // ============================================================================
41
28
 
42
- describe('Browser Environment Detection (forced sync mode)', () => {
43
- it('should use sync polyfill when forced', () => {
44
- // After forcing sync mode, native AsyncLocalStorage should not be used
29
+ describe('Browser Environment Detection', () => {
30
+ it('should use sync polyfill in browser build', () => {
45
31
  expect(isUsingNativeAsyncLocalStorage()).toBe(false)
46
32
  })
47
33
 
34
+ it('should create a SyncLocalStorage instance', () => {
35
+ const storage = createAsyncLocalStorage<{ value: number }>()
36
+ expect(storage).toBeInstanceOf(SyncLocalStorage)
37
+ })
38
+
48
39
  it('should create a working storage instance', () => {
49
40
  const storage = createAsyncLocalStorage<{ value: number }>()
50
41
  expect(storage).toBeDefined()
51
42
  expect(typeof storage.run).toBe('function')
52
43
  expect(typeof storage.getStore).toBe('function')
53
44
  })
45
+
46
+ it('should create a resolution context instance', () => {
47
+ const resolutionContext = getCurrentResolutionContext()
48
+ expect(resolutionContext).toBeUndefined()
49
+ })
54
50
  })
55
51
 
56
52
  // ============================================================================
@@ -111,77 +107,7 @@ describe('SyncLocalStorage in Browser', () => {
111
107
  })
112
108
 
113
109
  // ============================================================================
114
- // SECTION 3: Resolution Context Integration
115
- // ============================================================================
116
-
117
- describe('Resolution Context in Browser', () => {
118
- function createMockHolder(name: string): InstanceHolder {
119
- return {
120
- status: InstanceStatus.Creating,
121
- name,
122
- instance: null,
123
- creationPromise: null,
124
- destroyPromise: null,
125
- type: class {} as unknown as InstanceHolder['type'],
126
- scope: InjectableScope.Singleton,
127
- deps: new Set(),
128
- destroyListeners: [],
129
- createdAt: Date.now(),
130
- waitingFor: new Set(),
131
- }
132
- }
133
-
134
- it('should track resolution context correctly', () => {
135
- const holderA = createMockHolder('ServiceA')
136
- const getHolder = () => undefined
137
-
138
- expect(getCurrentResolutionContext()).toBeUndefined()
139
-
140
- withResolutionContext(holderA, getHolder, () => {
141
- const ctx = getCurrentResolutionContext()
142
- expect(ctx).toBeDefined()
143
- expect(ctx?.waiterHolder).toBe(holderA)
144
- })
145
-
146
- expect(getCurrentResolutionContext()).toBeUndefined()
147
- })
148
-
149
- it('should handle nested resolution contexts', () => {
150
- const holderA = createMockHolder('ServiceA')
151
- const holderB = createMockHolder('ServiceB')
152
- const getHolder = () => undefined
153
-
154
- withResolutionContext(holderA, getHolder, () => {
155
- expect(getCurrentResolutionContext()?.waiterHolder.name).toBe('ServiceA')
156
-
157
- withResolutionContext(holderB, getHolder, () => {
158
- expect(getCurrentResolutionContext()?.waiterHolder.name).toBe(
159
- 'ServiceB',
160
- )
161
- })
162
-
163
- expect(getCurrentResolutionContext()?.waiterHolder.name).toBe('ServiceA')
164
- })
165
- })
166
-
167
- it('should clear context with withoutResolutionContext', () => {
168
- const holderA = createMockHolder('ServiceA')
169
- const getHolder = () => undefined
170
-
171
- withResolutionContext(holderA, getHolder, () => {
172
- expect(getCurrentResolutionContext()).toBeDefined()
173
-
174
- withoutResolutionContext(() => {
175
- expect(getCurrentResolutionContext()).toBeUndefined()
176
- })
177
-
178
- expect(getCurrentResolutionContext()).toBeDefined()
179
- })
180
- })
181
- })
182
-
183
- // ============================================================================
184
- // SECTION 4: Async Limitations in Browser
110
+ // SECTION 3: Async Limitations in Browser
185
111
  // ============================================================================
186
112
 
187
113
  describe('Async Limitations in Browser (expected behavior)', () => {
@@ -1317,6 +1317,99 @@ describe('Container', () => {
1317
1317
  await expect(container.get(ServiceWithUnregistered)).rejects.toThrow()
1318
1318
  })
1319
1319
  })
1320
+
1321
+ describe('failed service retry', () => {
1322
+ it('should allow retrying service creation after initialization failure', async () => {
1323
+ let callCount = 0
1324
+
1325
+ @Factory({ registry })
1326
+ class FailingFactory implements Factorable<TestService> {
1327
+ async create() {
1328
+ callCount++
1329
+ if (callCount === 1) {
1330
+ throw new Error('First attempt fails')
1331
+ }
1332
+ return new TestService()
1333
+ }
1334
+ }
1335
+
1336
+ @Injectable({ registry })
1337
+ class TestService {
1338
+ public value = 'success'
1339
+ }
1340
+
1341
+ // First attempt should fail
1342
+ await expect(container.get(FailingFactory)).rejects.toThrow(
1343
+ 'First attempt fails',
1344
+ )
1345
+
1346
+ // Second attempt should succeed (service should be removed from storage after failure)
1347
+ const instance = await container.get(FailingFactory)
1348
+ expect(instance).toBeInstanceOf(TestService)
1349
+ expect(instance.value).toBe('success')
1350
+ expect(callCount).toBe(2)
1351
+ })
1352
+
1353
+ it('should allow retrying injectable service creation after constructor throws', async () => {
1354
+ let callCount = 0
1355
+
1356
+ @Injectable({ registry })
1357
+ class FailingService {
1358
+ public value: string
1359
+
1360
+ constructor() {
1361
+ callCount++
1362
+ if (callCount === 1) {
1363
+ throw new Error('Constructor fails on first attempt')
1364
+ }
1365
+ this.value = 'success'
1366
+ }
1367
+ }
1368
+
1369
+ // First attempt should fail
1370
+ await expect(container.get(FailingService)).rejects.toThrow(
1371
+ 'Constructor fails on first attempt',
1372
+ )
1373
+
1374
+ // Second attempt should succeed
1375
+ const instance = await container.get(FailingService)
1376
+ expect(instance).toBeInstanceOf(FailingService)
1377
+ expect(instance.value).toBe('success')
1378
+ expect(callCount).toBe(2)
1379
+ })
1380
+
1381
+ it('should allow retrying request-scoped service creation after failure', async () => {
1382
+ let callCount = 0
1383
+
1384
+ @Injectable({ registry, scope: InjectableScope.Request })
1385
+ class FailingRequestService {
1386
+ public value: string
1387
+
1388
+ constructor() {
1389
+ callCount++
1390
+ if (callCount === 1) {
1391
+ throw new Error('Request service fails on first attempt')
1392
+ }
1393
+ this.value = 'success'
1394
+ }
1395
+ }
1396
+
1397
+ const scoped = container.beginRequest('retry-test-request')
1398
+
1399
+ // First attempt should fail
1400
+ await expect(scoped.get(FailingRequestService)).rejects.toThrow(
1401
+ 'Request service fails on first attempt',
1402
+ )
1403
+
1404
+ // Second attempt should succeed within the same request
1405
+ const instance = await scoped.get(FailingRequestService)
1406
+ expect(instance).toBeInstanceOf(FailingRequestService)
1407
+ expect(instance.value).toBe('success')
1408
+ expect(callCount).toBe(2)
1409
+
1410
+ await scoped.endRequest()
1411
+ })
1412
+ })
1320
1413
  })
1321
1414
  describe('custom injectors', () => {
1322
1415
  it('should work with custom injectors', async () => {
@@ -1,33 +1,25 @@
1
1
  /**
2
2
  * E2E tests for @navios/di running with SyncLocalStorage (browser mode).
3
3
  *
4
- * This file runs all the same E2E tests but with the sync polyfill forced,
5
- * simulating browser environment behavior.
4
+ * This file tests the browser implementation by directly importing from
5
+ * the browser-specific async-local-storage module.
6
6
  *
7
7
  * The key difference: In browser mode, AsyncLocalStorage context does NOT
8
8
  * propagate across async boundaries. This affects circular dependency detection
9
9
  * for async operations, but synchronous DI operations should work identically.
10
10
  */
11
11
 
12
- import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
12
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
13
+
14
+ import type { OnServiceDestroy } from '../interfaces/on-service-destroy.interface.mjs'
15
+ import type { OnServiceInit } from '../interfaces/on-service-init.interface.mjs'
13
16
 
14
17
  import { Container } from '../container/container.mjs'
15
18
  import { Injectable } from '../decorators/injectable.decorator.mjs'
16
19
  import { InjectableScope } from '../enums/index.mjs'
17
- import type { OnServiceDestroy } from '../interfaces/on-service-destroy.interface.mjs'
18
- import type { OnServiceInit } from '../interfaces/on-service-init.interface.mjs'
20
+ import { isUsingNativeAsyncLocalStorage } from '../internal/context/async-local-storage.browser.mjs'
19
21
  import { Registry } from '../token/registry.mjs'
20
22
  import { getInjectors } from '../utils/get-injectors.mjs'
21
- import { __testing__, isUsingNativeAsyncLocalStorage } from '../internal/context/async-local-storage.mjs'
22
-
23
- // Force sync mode for all tests in this file
24
- beforeAll(() => {
25
- __testing__.forceSyncMode()
26
- })
27
-
28
- afterAll(() => {
29
- __testing__.reset()
30
- })
31
23
 
32
24
  // ============================================================================
33
25
  // TEST UTILITIES