katagami 2.3.0 → 3.0.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/README.md +82 -592
  2. package/dist/chunk-J2NYR3SH.js +6 -0
  3. package/dist/container/index.d.cts +108 -0
  4. package/dist/container/index.d.ts +5 -82
  5. package/dist/disposable/index.cjs +16 -25
  6. package/dist/disposable/index.d.cts +69 -0
  7. package/dist/disposable/index.d.ts +16 -8
  8. package/dist/disposable/index.js +1 -1
  9. package/dist/error/index.d.cts +11 -0
  10. package/dist/index.cjs +181 -86
  11. package/dist/index.d.cts +6 -0
  12. package/dist/index.d.ts +6 -6
  13. package/dist/index.js +177 -45
  14. package/dist/internal.d.cts +29 -0
  15. package/dist/internal.d.ts +3 -1
  16. package/dist/lazy/index.cjs +16 -25
  17. package/dist/lazy/index.d.cts +33 -0
  18. package/dist/lazy/index.d.ts +7 -9
  19. package/dist/lazy/index.js +1 -1
  20. package/dist/resolver/index.d.cts +93 -0
  21. package/dist/scope/index.d.cts +120 -0
  22. package/dist/scope/index.d.ts +4 -4
  23. package/docs/README.de.md +65 -0
  24. package/docs/README.es.md +65 -0
  25. package/docs/README.fr.md +65 -0
  26. package/docs/README.ja.md +66 -0
  27. package/docs/README.ko.md +65 -0
  28. package/docs/README.zh-CN.md +65 -0
  29. package/docs/README.zh-TW.md +65 -0
  30. package/docs/ai-coding-agents.md +78 -0
  31. package/docs/articles/ai-coding-agents.ja.md +83 -0
  32. package/docs/articles/ai-coding-agents.md +70 -0
  33. package/docs/articles/request-scope.md +48 -0
  34. package/docs/articles/without-decorators.md +54 -0
  35. package/docs/choosing-di.md +30 -0
  36. package/docs/growth/baseline-2026-09-11.json +68 -0
  37. package/docs/growth/github-metadata.json +13 -0
  38. package/docs/growth/rollout.md +77 -0
  39. package/docs/guide.md +186 -0
  40. package/docs/type-safety.md +126 -0
  41. package/examples/request-scope/README.md +37 -0
  42. package/examples/request-scope/app.ts +31 -0
  43. package/examples/request-scope/demo.ts +10 -0
  44. package/examples/request-scope/tsconfig.json +11 -0
  45. package/llms.txt +16 -0
  46. package/package.json +56 -28
  47. package/dist/index-g50fxds1.js +0 -34
  48. package/dist/index-jx8b52m0.js +0 -4
  49. package/dist/scope/index.cjs +0 -212
  50. package/dist/scope/index.js +0 -153
package/dist/index.cjs CHANGED
@@ -1,50 +1,107 @@
1
+ "use strict";
1
2
  var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __moduleCache = /* @__PURE__ */ new WeakMap;
6
- var __toCommonJS = (from) => {
7
- var entry = __moduleCache.get(from), desc;
8
- if (entry)
9
- return entry;
10
- entry = __defProp({}, "__esModule", { value: true });
11
- if (from && typeof from === "object" || typeof from === "function")
12
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
- get: () => from[key],
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- }));
16
- __moduleCache.set(from, entry);
17
- return entry;
18
- };
19
6
  var __export = (target, all) => {
20
7
  for (var name in all)
21
- __defProp(target, name, {
22
- get: all[name],
23
- enumerable: true,
24
- configurable: true,
25
- set: (newValue) => all[name] = () => newValue
26
- });
8
+ __defProp(target, name, { get: all[name], enumerable: true });
27
9
  };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
19
 
29
20
  // src/index.ts
30
- var exports_src = {};
31
- __export(exports_src, {
32
- createContainer: () => createContainer,
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Container: () => Container,
33
24
  ContainerError: () => ContainerError,
34
- Container: () => Container
25
+ Scope: () => Scope,
26
+ createContainer: () => createContainer,
27
+ createScope: () => createScope
35
28
  });
36
- module.exports = __toCommonJS(exports_src);
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/internal.ts
32
+ var INTERNALS = /* @__PURE__ */ Symbol.for("katagami.internals.v3");
33
+
34
+ // src/container/index.ts
35
+ function createContainer() {
36
+ return new Container();
37
+ }
38
+ var Container = class {
39
+ registrations;
40
+ singletonCache;
41
+ disposed = false;
42
+ /**
43
+ * Internal state accessor for extension modules (scope, disposable).
44
+ *
45
+ * @internal
46
+ */
47
+ [INTERNALS];
48
+ constructor() {
49
+ this.registrations = /* @__PURE__ */ new Map();
50
+ this.singletonCache = /* @__PURE__ */ new Map();
51
+ this[INTERNALS] = {
52
+ isDisposed: () => this.disposed,
53
+ markDisposed: () => {
54
+ this.disposed = true;
55
+ },
56
+ ownCache: this.singletonCache,
57
+ registrations: this.registrations,
58
+ singletonCache: this.singletonCache
59
+ };
60
+ }
61
+ registerSingleton(token, factory) {
62
+ return this.addRegistration(token, factory, "singleton");
63
+ }
64
+ registerTransient(token, factory) {
65
+ return this.addRegistration(token, factory, "transient");
66
+ }
67
+ registerScoped(token, factory) {
68
+ return this.addRegistration(token, factory, "scoped");
69
+ }
70
+ use(source) {
71
+ for (const [token, registrations] of source[INTERNALS].registrations) {
72
+ this.registrations.set(token, [...registrations]);
73
+ }
74
+ return this;
75
+ }
76
+ /**
77
+ * Add a registration entry. Accumulates registrations for the same token.
78
+ *
79
+ * @param token Token
80
+ * @param factory Factory function
81
+ * @param lifetime Lifetime of the registration
82
+ * @returns The container for method chaining
83
+ */
84
+ addRegistration(token, factory, lifetime) {
85
+ const existing = this.registrations.get(token);
86
+ if (existing !== void 0) {
87
+ existing.push({ factory, lifetime });
88
+ } else {
89
+ this.registrations.set(token, [{ factory, lifetime }]);
90
+ }
91
+ return this;
92
+ }
93
+ };
37
94
 
38
95
  // src/error/index.ts
39
- class ContainerError extends Error {
96
+ var ContainerError = class extends Error {
97
+ /**
98
+ * @param message Error message
99
+ */
40
100
  constructor(message) {
41
101
  super(message);
42
102
  this.name = "ContainerError";
43
103
  }
44
- }
45
-
46
- // src/internal.ts
47
- var INTERNALS = Symbol("katagami.internals");
104
+ };
48
105
 
49
106
  // src/resolver/index.ts
50
107
  function tokenToString(token) {
@@ -71,46 +128,42 @@ function buildCircularPath(resolvingTokens, token) {
71
128
  return path.join(" -> ");
72
129
  }
73
130
 
74
- // src/container/index.ts
75
- function createContainer() {
76
- return new Container;
131
+ // src/scope/index.ts
132
+ function createScope(source) {
133
+ const internals = source[INTERNALS];
134
+ if (internals.isDisposed()) {
135
+ throw new ContainerError("Cannot create a scope from a disposed container.");
136
+ }
137
+ return new Scope(internals.registrations, internals.singletonCache);
77
138
  }
78
-
79
- class Container {
139
+ var Scope = class {
80
140
  registrations;
81
141
  singletonCache;
142
+ scopedCache;
82
143
  resolvingTokens;
144
+ singletonDepth = 0;
83
145
  disposed = false;
146
+ /**
147
+ * Internal state accessor for extension modules (scope, disposable).
148
+ *
149
+ * @internal
150
+ */
84
151
  [INTERNALS];
85
- constructor() {
86
- this.registrations = new Map;
87
- this.singletonCache = new Map;
88
- this.resolvingTokens = new Set;
152
+ constructor(registrations, singletonCache) {
153
+ this.registrations = registrations;
154
+ this.singletonCache = singletonCache;
155
+ this.scopedCache = /* @__PURE__ */ new Map();
156
+ this.resolvingTokens = /* @__PURE__ */ new Set();
89
157
  this[INTERNALS] = {
90
158
  isDisposed: () => this.disposed,
91
159
  markDisposed: () => {
92
160
  this.disposed = true;
93
161
  },
94
- ownCache: this.singletonCache,
162
+ ownCache: this.scopedCache,
95
163
  registrations: this.registrations,
96
164
  singletonCache: this.singletonCache
97
165
  };
98
166
  }
99
- registerSingleton(token, factory) {
100
- return this.addRegistration(token, factory, "singleton");
101
- }
102
- registerTransient(token, factory) {
103
- return this.addRegistration(token, factory, "transient");
104
- }
105
- registerScoped(token, factory) {
106
- return this.addRegistration(token, factory, "scoped");
107
- }
108
- use(source) {
109
- for (const [token, registrations] of source[INTERNALS].registrations) {
110
- this.registrations.set(token, [...registrations]);
111
- }
112
- return this;
113
- }
114
167
  resolve(token) {
115
168
  return this.resolveToken(token, true);
116
169
  }
@@ -123,49 +176,81 @@ class Container {
123
176
  tryResolveAll(token) {
124
177
  return this.resolveAllTokens(token, false);
125
178
  }
179
+ /**
180
+ * Internal resolution logic shared by resolve and tryResolve.
181
+ * Resolves the last registered factory for the token.
182
+ *
183
+ * @param token Token to resolve
184
+ * @param required If true, throws when the token is not registered. If false, returns undefined.
185
+ * @returns The resolved instance, or undefined if not registered and required is false
186
+ */
126
187
  resolveToken(token, required) {
127
188
  if (this.disposed) {
128
- throw new ContainerError("Cannot resolve from a disposed container.");
189
+ throw new ContainerError("Cannot resolve from a disposed scope.");
129
190
  }
130
191
  const registrations = this.registrations.get(token);
131
- if (registrations === undefined || registrations.length === 0) {
192
+ if (registrations === void 0 || registrations.length === 0) {
132
193
  if (required) {
133
194
  throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
134
195
  }
135
- return;
196
+ return void 0;
136
197
  }
137
198
  const registration = registrations[registrations.length - 1];
138
- const cached = this.singletonCache.get(registration);
139
- if (cached !== undefined) {
140
- return cached;
199
+ const singletonCached = this.singletonCache.get(registration);
200
+ if (singletonCached !== void 0) {
201
+ return singletonCached;
202
+ }
203
+ if (registration.lifetime === "scoped" && this.singletonDepth > 0) {
204
+ throw new ContainerError(
205
+ `Captive dependency detected: scoped token "${tokenToString(token)}" cannot be resolved inside a singleton factory. Scoped instances must not be captured by singletons.`
206
+ );
141
207
  }
142
- if (registration.lifetime === "scoped") {
143
- throw new ContainerError(`Cannot resolve scoped token "${tokenToString(token)}" from the root container. Use createScope() to create a scope first.`);
208
+ const scopedCached = this.scopedCache.get(registration);
209
+ if (scopedCached !== void 0) {
210
+ return scopedCached;
144
211
  }
145
212
  if (this.resolvingTokens.has(token)) {
146
213
  throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
147
214
  }
148
215
  this.resolvingTokens.add(token);
216
+ if (registration.lifetime === "singleton") {
217
+ this.singletonDepth++;
218
+ }
149
219
  try {
150
- const instance = registration.factory(this);
220
+ const instance = registration.factory(
221
+ this
222
+ );
151
223
  if (registration.lifetime === "singleton") {
152
224
  this.singletonCache.set(registration, instance);
225
+ } else if (registration.lifetime === "scoped") {
226
+ this.scopedCache.set(registration, instance);
153
227
  }
154
228
  return instance;
155
229
  } finally {
230
+ if (registration.lifetime === "singleton") {
231
+ this.singletonDepth--;
232
+ }
156
233
  this.resolvingTokens.delete(token);
157
234
  }
158
235
  }
236
+ /**
237
+ * Internal resolution logic shared by resolveAll and tryResolveAll.
238
+ * Resolves all registered factories for the token.
239
+ *
240
+ * @param token Token to resolve
241
+ * @param required If true, throws when the token is not registered. If false, returns undefined.
242
+ * @returns An array of resolved instances, or undefined if not registered and required is false
243
+ */
159
244
  resolveAllTokens(token, required) {
160
245
  if (this.disposed) {
161
- throw new ContainerError("Cannot resolve from a disposed container.");
246
+ throw new ContainerError("Cannot resolve from a disposed scope.");
162
247
  }
163
248
  const registrations = this.registrations.get(token);
164
- if (registrations === undefined || registrations.length === 0) {
249
+ if (registrations === void 0 || registrations.length === 0) {
165
250
  if (required) {
166
251
  throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
167
252
  }
168
- return;
253
+ return void 0;
169
254
  }
170
255
  if (this.resolvingTokens.has(token)) {
171
256
  throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
@@ -174,30 +259,40 @@ class Container {
174
259
  try {
175
260
  return registrations.map((registration) => {
176
261
  const reg = registration;
177
- const cached = this.singletonCache.get(registration);
178
- if (cached !== undefined) {
179
- return cached;
262
+ const singletonCached = this.singletonCache.get(registration);
263
+ if (singletonCached !== void 0) {
264
+ return singletonCached;
265
+ }
266
+ if (reg.lifetime === "scoped" && this.singletonDepth > 0) {
267
+ throw new ContainerError(
268
+ `Captive dependency detected: scoped token "${tokenToString(token)}" cannot be resolved inside a singleton factory. Scoped instances must not be captured by singletons.`
269
+ );
180
270
  }
181
- if (reg.lifetime === "scoped") {
182
- throw new ContainerError(`Cannot resolve scoped token "${tokenToString(token)}" from the root container. Use createScope() to create a scope first.`);
271
+ const scopedCached = this.scopedCache.get(registration);
272
+ if (scopedCached !== void 0) {
273
+ return scopedCached;
183
274
  }
184
- const instance = reg.factory(this);
185
275
  if (reg.lifetime === "singleton") {
186
- this.singletonCache.set(registration, instance);
276
+ this.singletonDepth++;
277
+ }
278
+ try {
279
+ const instance = reg.factory(
280
+ this
281
+ );
282
+ if (reg.lifetime === "singleton") {
283
+ this.singletonCache.set(registration, instance);
284
+ } else if (reg.lifetime === "scoped") {
285
+ this.scopedCache.set(registration, instance);
286
+ }
287
+ return instance;
288
+ } finally {
289
+ if (reg.lifetime === "singleton") {
290
+ this.singletonDepth--;
291
+ }
187
292
  }
188
- return instance;
189
293
  });
190
294
  } finally {
191
295
  this.resolvingTokens.delete(token);
192
296
  }
193
297
  }
194
- addRegistration(token, factory, lifetime) {
195
- const existing = this.registrations.get(token);
196
- if (existing !== undefined) {
197
- existing.push({ factory, lifetime });
198
- } else {
199
- this.registrations.set(token, [{ factory, lifetime }]);
200
- }
201
- return this;
202
- }
203
- }
298
+ };
@@ -0,0 +1,6 @@
1
+ export { Container, createContainer } from './container/index.cjs';
2
+ export type { DisposableContainer, DisposableScope, disposable } from './disposable/index.cjs';
3
+ export { ContainerError } from './error/index.cjs';
4
+ export type { lazy } from './lazy/index.cjs';
5
+ export type { Resolver } from './resolver/index.cjs';
6
+ export { createScope, Scope } from './scope/index.cjs';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { Container, createContainer } from './container';
2
- export type { DisposableContainer, DisposableScope, disposable } from './disposable';
3
- export { ContainerError } from './error';
4
- export type { lazy } from './lazy';
5
- export type { Resolver } from './resolver';
6
- export type { createScope, Scope } from './scope';
1
+ export { Container, createContainer } from './container/index.js';
2
+ export type { DisposableContainer, DisposableScope, disposable } from './disposable/index.js';
3
+ export { ContainerError } from './error/index.js';
4
+ export type { lazy } from './lazy/index.js';
5
+ export type { Resolver } from './resolver/index.js';
6
+ export { createScope, Scope } from './scope/index.js';
package/dist/index.js CHANGED
@@ -1,27 +1,24 @@
1
- import {
2
- ContainerError,
3
- buildCircularPath,
4
- tokenToString
5
- } from "./index-g50fxds1.js";
6
1
  import {
7
2
  INTERNALS
8
- } from "./index-jx8b52m0.js";
3
+ } from "./chunk-J2NYR3SH.js";
9
4
 
10
5
  // src/container/index.ts
11
6
  function createContainer() {
12
- return new Container;
7
+ return new Container();
13
8
  }
14
-
15
- class Container {
9
+ var Container = class {
16
10
  registrations;
17
11
  singletonCache;
18
- resolvingTokens;
19
12
  disposed = false;
13
+ /**
14
+ * Internal state accessor for extension modules (scope, disposable).
15
+ *
16
+ * @internal
17
+ */
20
18
  [INTERNALS];
21
19
  constructor() {
22
- this.registrations = new Map;
23
- this.singletonCache = new Map;
24
- this.resolvingTokens = new Set;
20
+ this.registrations = /* @__PURE__ */ new Map();
21
+ this.singletonCache = /* @__PURE__ */ new Map();
25
22
  this[INTERNALS] = {
26
23
  isDisposed: () => this.disposed,
27
24
  markDisposed: () => {
@@ -47,6 +44,97 @@ class Container {
47
44
  }
48
45
  return this;
49
46
  }
47
+ /**
48
+ * Add a registration entry. Accumulates registrations for the same token.
49
+ *
50
+ * @param token Token
51
+ * @param factory Factory function
52
+ * @param lifetime Lifetime of the registration
53
+ * @returns The container for method chaining
54
+ */
55
+ addRegistration(token, factory, lifetime) {
56
+ const existing = this.registrations.get(token);
57
+ if (existing !== void 0) {
58
+ existing.push({ factory, lifetime });
59
+ } else {
60
+ this.registrations.set(token, [{ factory, lifetime }]);
61
+ }
62
+ return this;
63
+ }
64
+ };
65
+
66
+ // src/error/index.ts
67
+ var ContainerError = class extends Error {
68
+ /**
69
+ * @param message Error message
70
+ */
71
+ constructor(message) {
72
+ super(message);
73
+ this.name = "ContainerError";
74
+ }
75
+ };
76
+
77
+ // src/resolver/index.ts
78
+ function tokenToString(token) {
79
+ if (typeof token === "function") {
80
+ return token.name || "anonymous function";
81
+ }
82
+ if (typeof token === "symbol") {
83
+ return token.toString();
84
+ }
85
+ return String(token);
86
+ }
87
+ function buildCircularPath(resolvingTokens, token) {
88
+ const path = [];
89
+ let found = false;
90
+ for (const t of resolvingTokens) {
91
+ if (t === token) {
92
+ found = true;
93
+ }
94
+ if (found) {
95
+ path.push(tokenToString(t));
96
+ }
97
+ }
98
+ path.push(tokenToString(token));
99
+ return path.join(" -> ");
100
+ }
101
+
102
+ // src/scope/index.ts
103
+ function createScope(source) {
104
+ const internals = source[INTERNALS];
105
+ if (internals.isDisposed()) {
106
+ throw new ContainerError("Cannot create a scope from a disposed container.");
107
+ }
108
+ return new Scope(internals.registrations, internals.singletonCache);
109
+ }
110
+ var Scope = class {
111
+ registrations;
112
+ singletonCache;
113
+ scopedCache;
114
+ resolvingTokens;
115
+ singletonDepth = 0;
116
+ disposed = false;
117
+ /**
118
+ * Internal state accessor for extension modules (scope, disposable).
119
+ *
120
+ * @internal
121
+ */
122
+ [INTERNALS];
123
+ constructor(registrations, singletonCache) {
124
+ this.registrations = registrations;
125
+ this.singletonCache = singletonCache;
126
+ this.scopedCache = /* @__PURE__ */ new Map();
127
+ this.resolvingTokens = /* @__PURE__ */ new Set();
128
+ this[INTERNALS] = {
129
+ isDisposed: () => this.disposed,
130
+ markDisposed: () => {
131
+ this.disposed = true;
132
+ },
133
+ ownCache: this.scopedCache,
134
+ registrations: this.registrations,
135
+ singletonCache: this.singletonCache
136
+ };
137
+ }
50
138
  resolve(token) {
51
139
  return this.resolveToken(token, true);
52
140
  }
@@ -59,49 +147,81 @@ class Container {
59
147
  tryResolveAll(token) {
60
148
  return this.resolveAllTokens(token, false);
61
149
  }
150
+ /**
151
+ * Internal resolution logic shared by resolve and tryResolve.
152
+ * Resolves the last registered factory for the token.
153
+ *
154
+ * @param token Token to resolve
155
+ * @param required If true, throws when the token is not registered. If false, returns undefined.
156
+ * @returns The resolved instance, or undefined if not registered and required is false
157
+ */
62
158
  resolveToken(token, required) {
63
159
  if (this.disposed) {
64
- throw new ContainerError("Cannot resolve from a disposed container.");
160
+ throw new ContainerError("Cannot resolve from a disposed scope.");
65
161
  }
66
162
  const registrations = this.registrations.get(token);
67
- if (registrations === undefined || registrations.length === 0) {
163
+ if (registrations === void 0 || registrations.length === 0) {
68
164
  if (required) {
69
165
  throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
70
166
  }
71
- return;
167
+ return void 0;
72
168
  }
73
169
  const registration = registrations[registrations.length - 1];
74
- const cached = this.singletonCache.get(registration);
75
- if (cached !== undefined) {
76
- return cached;
170
+ const singletonCached = this.singletonCache.get(registration);
171
+ if (singletonCached !== void 0) {
172
+ return singletonCached;
173
+ }
174
+ if (registration.lifetime === "scoped" && this.singletonDepth > 0) {
175
+ throw new ContainerError(
176
+ `Captive dependency detected: scoped token "${tokenToString(token)}" cannot be resolved inside a singleton factory. Scoped instances must not be captured by singletons.`
177
+ );
77
178
  }
78
- if (registration.lifetime === "scoped") {
79
- throw new ContainerError(`Cannot resolve scoped token "${tokenToString(token)}" from the root container. Use createScope() to create a scope first.`);
179
+ const scopedCached = this.scopedCache.get(registration);
180
+ if (scopedCached !== void 0) {
181
+ return scopedCached;
80
182
  }
81
183
  if (this.resolvingTokens.has(token)) {
82
184
  throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
83
185
  }
84
186
  this.resolvingTokens.add(token);
187
+ if (registration.lifetime === "singleton") {
188
+ this.singletonDepth++;
189
+ }
85
190
  try {
86
- const instance = registration.factory(this);
191
+ const instance = registration.factory(
192
+ this
193
+ );
87
194
  if (registration.lifetime === "singleton") {
88
195
  this.singletonCache.set(registration, instance);
196
+ } else if (registration.lifetime === "scoped") {
197
+ this.scopedCache.set(registration, instance);
89
198
  }
90
199
  return instance;
91
200
  } finally {
201
+ if (registration.lifetime === "singleton") {
202
+ this.singletonDepth--;
203
+ }
92
204
  this.resolvingTokens.delete(token);
93
205
  }
94
206
  }
207
+ /**
208
+ * Internal resolution logic shared by resolveAll and tryResolveAll.
209
+ * Resolves all registered factories for the token.
210
+ *
211
+ * @param token Token to resolve
212
+ * @param required If true, throws when the token is not registered. If false, returns undefined.
213
+ * @returns An array of resolved instances, or undefined if not registered and required is false
214
+ */
95
215
  resolveAllTokens(token, required) {
96
216
  if (this.disposed) {
97
- throw new ContainerError("Cannot resolve from a disposed container.");
217
+ throw new ContainerError("Cannot resolve from a disposed scope.");
98
218
  }
99
219
  const registrations = this.registrations.get(token);
100
- if (registrations === undefined || registrations.length === 0) {
220
+ if (registrations === void 0 || registrations.length === 0) {
101
221
  if (required) {
102
222
  throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
103
223
  }
104
- return;
224
+ return void 0;
105
225
  }
106
226
  if (this.resolvingTokens.has(token)) {
107
227
  throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
@@ -110,35 +230,47 @@ class Container {
110
230
  try {
111
231
  return registrations.map((registration) => {
112
232
  const reg = registration;
113
- const cached = this.singletonCache.get(registration);
114
- if (cached !== undefined) {
115
- return cached;
233
+ const singletonCached = this.singletonCache.get(registration);
234
+ if (singletonCached !== void 0) {
235
+ return singletonCached;
116
236
  }
117
- if (reg.lifetime === "scoped") {
118
- throw new ContainerError(`Cannot resolve scoped token "${tokenToString(token)}" from the root container. Use createScope() to create a scope first.`);
237
+ if (reg.lifetime === "scoped" && this.singletonDepth > 0) {
238
+ throw new ContainerError(
239
+ `Captive dependency detected: scoped token "${tokenToString(token)}" cannot be resolved inside a singleton factory. Scoped instances must not be captured by singletons.`
240
+ );
241
+ }
242
+ const scopedCached = this.scopedCache.get(registration);
243
+ if (scopedCached !== void 0) {
244
+ return scopedCached;
119
245
  }
120
- const instance = reg.factory(this);
121
246
  if (reg.lifetime === "singleton") {
122
- this.singletonCache.set(registration, instance);
247
+ this.singletonDepth++;
248
+ }
249
+ try {
250
+ const instance = reg.factory(
251
+ this
252
+ );
253
+ if (reg.lifetime === "singleton") {
254
+ this.singletonCache.set(registration, instance);
255
+ } else if (reg.lifetime === "scoped") {
256
+ this.scopedCache.set(registration, instance);
257
+ }
258
+ return instance;
259
+ } finally {
260
+ if (reg.lifetime === "singleton") {
261
+ this.singletonDepth--;
262
+ }
123
263
  }
124
- return instance;
125
264
  });
126
265
  } finally {
127
266
  this.resolvingTokens.delete(token);
128
267
  }
129
268
  }
130
- addRegistration(token, factory, lifetime) {
131
- const existing = this.registrations.get(token);
132
- if (existing !== undefined) {
133
- existing.push({ factory, lifetime });
134
- } else {
135
- this.registrations.set(token, [{ factory, lifetime }]);
136
- }
137
- return this;
138
- }
139
- }
269
+ };
140
270
  export {
141
- createContainer,
271
+ Container,
142
272
  ContainerError,
143
- Container
273
+ Scope,
274
+ createContainer,
275
+ createScope
144
276
  };