@zudojs/container 0.1.0 → 1.1.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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +221 -8
  3. package/dist/containerCore/containerCore.core.d.ts +112 -14
  4. package/dist/containerCore/containerCore.core.js +228 -46
  5. package/dist/containerCore/containerCore.scope.d.ts +66 -18
  6. package/dist/containerCore/containerCore.scope.js +120 -31
  7. package/dist/containerCore/containerCore.type.d.ts +22 -16
  8. package/dist/containerLifecycle/containerLifecycle.core.d.ts +59 -9
  9. package/dist/containerLifecycle/containerLifecycle.core.js +60 -15
  10. package/dist/containerOptions/containerOptions.type.d.ts +7 -17
  11. package/dist/containerOptions/containerOptions.type.js +0 -19
  12. package/dist/containerProvider/containerProvider.core.d.ts +11 -3
  13. package/dist/containerProvider/containerProvider.core.js +11 -5
  14. package/dist/containerRegistration/containerRegistration.core.d.ts +9 -3
  15. package/dist/containerRegistration/containerRegistration.core.js +0 -6
  16. package/dist/containerRegistry/containerRegistry.core.d.ts +23 -9
  17. package/dist/containerRegistry/containerRegistry.core.js +50 -47
  18. package/dist/containerRegistry/containerRegistry.type.d.ts +3 -1
  19. package/dist/containerRegistry/containerRegistry.type.js +2 -0
  20. package/dist/containerRegistry/index.d.ts +0 -1
  21. package/dist/containerRegistry/index.js +0 -1
  22. package/dist/containerResolution/containerResolution.core.d.ts +51 -10
  23. package/dist/containerResolution/containerResolution.core.js +255 -64
  24. package/dist/containerResolution/containerResolution.error.d.ts +68 -3
  25. package/dist/containerResolution/containerResolution.error.js +107 -3
  26. package/dist/containerResolution/containerResolution.type.d.ts +35 -3
  27. package/dist/containerResolution/index.d.ts +1 -0
  28. package/dist/containerResolution/index.js +1 -0
  29. package/dist/containerToken/containerToken.type.d.ts +26 -1
  30. package/dist/containerToken/containerToken.type.js +18 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.js +3 -0
  33. package/package.json +25 -12
  34. package/dist/.tsbuildinfo +0 -1
  35. package/dist/containerCore/containerCore.core.d.ts.map +0 -1
  36. package/dist/containerCore/containerCore.core.js.map +0 -1
  37. package/dist/containerCore/containerCore.scope.d.ts.map +0 -1
  38. package/dist/containerCore/containerCore.scope.js.map +0 -1
  39. package/dist/containerCore/containerCore.type.d.ts.map +0 -1
  40. package/dist/containerCore/containerCore.type.js.map +0 -1
  41. package/dist/containerCore/index.d.ts.map +0 -1
  42. package/dist/containerCore/index.js.map +0 -1
  43. package/dist/containerLifecycle/containerLifecycle.core.d.ts.map +0 -1
  44. package/dist/containerLifecycle/containerLifecycle.core.js.map +0 -1
  45. package/dist/containerLifecycle/index.d.ts.map +0 -1
  46. package/dist/containerLifecycle/index.js.map +0 -1
  47. package/dist/containerOptions/containerOptions.type.d.ts.map +0 -1
  48. package/dist/containerOptions/containerOptions.type.js.map +0 -1
  49. package/dist/containerOptions/index.d.ts.map +0 -1
  50. package/dist/containerOptions/index.js.map +0 -1
  51. package/dist/containerProvider/containerProvider.core.d.ts.map +0 -1
  52. package/dist/containerProvider/containerProvider.core.js.map +0 -1
  53. package/dist/containerProvider/index.d.ts.map +0 -1
  54. package/dist/containerProvider/index.js.map +0 -1
  55. package/dist/containerRegistration/containerRegistration.core.d.ts.map +0 -1
  56. package/dist/containerRegistration/containerRegistration.core.js.map +0 -1
  57. package/dist/containerRegistration/index.d.ts.map +0 -1
  58. package/dist/containerRegistration/index.js.map +0 -1
  59. package/dist/containerRegistry/containerRegistry.core.d.ts.map +0 -1
  60. package/dist/containerRegistry/containerRegistry.core.js.map +0 -1
  61. package/dist/containerRegistry/containerRegistry.error.d.ts +0 -12
  62. package/dist/containerRegistry/containerRegistry.error.d.ts.map +0 -1
  63. package/dist/containerRegistry/containerRegistry.error.js +0 -22
  64. package/dist/containerRegistry/containerRegistry.error.js.map +0 -1
  65. package/dist/containerRegistry/containerRegistry.type.d.ts.map +0 -1
  66. package/dist/containerRegistry/containerRegistry.type.js.map +0 -1
  67. package/dist/containerRegistry/index.d.ts.map +0 -1
  68. package/dist/containerRegistry/index.js.map +0 -1
  69. package/dist/containerResolution/containerResolution.core.d.ts.map +0 -1
  70. package/dist/containerResolution/containerResolution.core.js.map +0 -1
  71. package/dist/containerResolution/containerResolution.error.d.ts.map +0 -1
  72. package/dist/containerResolution/containerResolution.error.js.map +0 -1
  73. package/dist/containerResolution/containerResolution.type.d.ts.map +0 -1
  74. package/dist/containerResolution/containerResolution.type.js.map +0 -1
  75. package/dist/containerResolution/index.d.ts.map +0 -1
  76. package/dist/containerResolution/index.js.map +0 -1
  77. package/dist/containerScope/containerScope.type.d.ts.map +0 -1
  78. package/dist/containerScope/containerScope.type.js.map +0 -1
  79. package/dist/containerScope/index.d.ts.map +0 -1
  80. package/dist/containerScope/index.js.map +0 -1
  81. package/dist/containerToken/containerToken.type.d.ts.map +0 -1
  82. package/dist/containerToken/containerToken.type.js.map +0 -1
  83. package/dist/containerToken/index.d.ts.map +0 -1
  84. package/dist/containerToken/index.js.map +0 -1
  85. package/dist/index.d.ts.map +0 -1
  86. package/dist/index.js.map +0 -1
@@ -1,5 +1,16 @@
1
1
  /**
2
2
  * Core dependency injection container for Zudojs.
3
+ *
4
+ * Lifetime and disposal model:
5
+ * - SINGLETON instances are cached in the container's root singleton cache
6
+ * and tracked by the container lifecycle — including singletons created
7
+ * transitively as dependencies of another resolution; they are disposed
8
+ * when the container is disposed (or when their registration is
9
+ * replaced/removed).
10
+ * - SCOPED instances only exist inside a scope created with `createScope()`;
11
+ * resolving a SCOPED token at the root throws.
12
+ * - TRANSIENT instances are never cached and never tracked — callers own
13
+ * their disposal.
3
14
  */
4
15
  import { existingProvider, factoryProvider, valueProvider, classProvider, } from "../containerProvider/containerProvider.core.js";
5
16
  import { ContainerScope as Scope } from "../containerScope/containerScope.type.js";
@@ -7,36 +18,64 @@ import { ContainerRegistry } from "../containerRegistry/containerRegistry.core.j
7
18
  import { ContainerResolver } from "../containerResolution/containerResolution.core.js";
8
19
  import { ContainerLifecycle, ContainerLifecycleOwner, } from "../containerLifecycle/containerLifecycle.core.js";
9
20
  import { resolveContainerOptions } from "../containerOptions/containerOptions.type.js";
21
+ import { unwrapToken } from "../containerToken/containerToken.type.js";
22
+ import { RegistrationNotFoundError } from "@zudojs/errors";
10
23
  import { ContainerScopeContext } from "./containerCore.scope.js";
11
24
  export class Container {
12
25
  name;
13
26
  options;
14
- registry;
15
- resolver;
16
- lifecycle;
17
- started = false;
18
- disposed = false;
27
+ #registry;
28
+ #resolver;
29
+ #lifecycle;
30
+ #liveScopes = new Set();
31
+ #started = false;
32
+ #disposed = false;
33
+ #disposing;
19
34
  constructor(options = {}) {
20
35
  this.options = resolveContainerOptions(options);
21
36
  this.name = this.options.name;
22
- this.registry = new ContainerRegistry(this.options.registry);
23
- this.resolver = new ContainerResolver(this.registry);
24
- this.lifecycle = new ContainerLifecycle(this.options.lifecycle);
37
+ this.#registry = new ContainerRegistry(this.options.registry);
38
+ this.#lifecycle = new ContainerLifecycle(this.options.lifecycle);
39
+ this.#resolver = new ContainerResolver(this.#registry, (token) => {
40
+ // A cached singleton was evicted because its registration changed;
41
+ // dispose the tracked instance. Eviction disposal is best-effort:
42
+ // failures cannot propagate through the synchronous registry mutation
43
+ // that triggered them.
44
+ void this.#lifecycle.disposeInstance(token).catch(() => {
45
+ /* see clearSingletons()/dispose() for error-surfacing disposal */
46
+ });
47
+ });
25
48
  }
49
+ /**
50
+ * Marks the container as started.
51
+ *
52
+ * Calling `start()` explicitly is optional: `resolve()` (and
53
+ * `createScope()`) auto-start the container on first use. Note that with
54
+ * `freezeRegistrations: true`, starting — including the implicit start
55
+ * performed by the first `resolve()` — freezes the registration set.
56
+ */
26
57
  start() {
27
58
  this.ensureNotDisposed();
28
- if (this.started)
59
+ if (this.#started)
29
60
  return this;
30
- this.started = true;
61
+ this.#started = true;
31
62
  return this;
32
63
  }
33
64
  register(token, provider, options = {}) {
34
65
  this.ensureMutable();
35
- return this.registry.register(token, provider, options);
66
+ return this.#registry.register(token, provider, options);
36
67
  }
37
68
  registerClass(token, ctor, options = {}) {
38
- return this.register(token, classProvider(ctor), options);
69
+ const { inject, ...rest } = options;
70
+ return this.register(token, classProvider(ctor, inject ?? []), rest);
39
71
  }
72
+ /**
73
+ * Registers a pre-built value.
74
+ *
75
+ * Note: the scope is forcibly set to SINGLETON (overriding any
76
+ * `options.scope`) — a value provider always returns the same instance, so
77
+ * any other lifetime would be misleading.
78
+ */
40
79
  registerValue(token, value, options = {}) {
41
80
  return this.register(token, valueProvider(value), {
42
81
  ...options,
@@ -49,73 +88,182 @@ export class Container {
49
88
  registerExisting(token, existing, options = {}) {
50
89
  return this.register(token, existingProvider(existing), options);
51
90
  }
91
+ /**
92
+ * Resolves a dependency at the container root.
93
+ *
94
+ * Auto-starts the container on first use. SCOPED registrations cannot be
95
+ * resolved here — create a scope with {@link createScope} instead.
96
+ */
52
97
  resolve(token) {
53
98
  this.ensureActive();
54
- const result = this.resolver.resolveDetailed(token, this.resolutionOptions);
55
- this.lifecycle.track(result.token, result.value, ContainerLifecycleOwner.CONTAINER);
56
- return result.value;
99
+ return this.#resolver.resolveDetailed(token, this.buildResolutionOptions())
100
+ .value;
57
101
  }
102
+ /**
103
+ * Resolves several tokens at once. The result is a tuple typed per token,
104
+ * so heterogeneous token lists keep their individual types.
105
+ */
58
106
  resolveMany(tokens) {
59
107
  return tokens.map((t) => this.resolve(t));
60
108
  }
109
+ /**
110
+ * Like {@link resolve} but returns `undefined` when the token has no
111
+ * registration (and cannot be auto-registered). Other resolution failures
112
+ * — broken factories, missing dependencies, captive dependencies — still
113
+ * throw.
114
+ */
61
115
  resolveOptional(token) {
62
- if (!this.canResolve(token))
63
- return undefined;
64
- return this.resolve(token);
116
+ try {
117
+ return this.resolve(token);
118
+ }
119
+ catch (error) {
120
+ if (error instanceof RegistrationNotFoundError)
121
+ return undefined;
122
+ throw error;
123
+ }
65
124
  }
66
125
  canResolve(token) {
67
126
  this.ensureNotDisposed();
68
- return this.resolver.canResolve(token);
127
+ return this.#resolver.canResolve(token, this.resolutionOptions.autoRegisterClasses);
69
128
  }
70
129
  has(token) {
71
130
  this.ensureNotDisposed();
72
- return this.registry.has(token);
131
+ return this.#registry.has(token);
73
132
  }
74
133
  getRegistration(token) {
75
134
  this.ensureNotDisposed();
76
- return this.registry.get(token);
135
+ return this.#registry.get(token);
136
+ }
137
+ replace(token, provider, options = {}) {
138
+ this.ensureMutable();
139
+ return this.#registry.replace(token, provider, options);
77
140
  }
78
141
  remove(token) {
79
142
  this.ensureMutable();
80
- return this.registry.remove(token);
143
+ return this.#registry.remove(token);
144
+ }
145
+ /** Removes every registration (evicting and disposing cached singletons). */
146
+ clearRegistrations() {
147
+ this.ensureMutable();
148
+ this.#registry.clear();
81
149
  }
82
150
  createScope(options = {}) {
83
151
  this.ensureActive();
84
152
  if (!this.options.allowScopes)
85
- throw new Error("Container scopes are disabled.");
86
- return new ContainerScopeContext(this, options);
153
+ throw new Error(`Container scopes are disabled for "${this.name}".`);
154
+ const scope = new ContainerScopeContext(this, options);
155
+ this.#liveScopes.add(scope);
156
+ return scope;
87
157
  }
88
158
  getRegistrations() {
89
159
  this.ensureNotDisposed();
90
- return this.registry.getAll();
160
+ return this.#registry.getAll();
161
+ }
162
+ getTokens() {
163
+ this.ensureNotDisposed();
164
+ return this.#registry.getTokens();
91
165
  }
92
166
  get registrationCount() {
93
- return this.registry.size;
167
+ return this.#registry.size;
168
+ }
169
+ /**
170
+ * Returns an immutable snapshot of the current registrations, suitable for
171
+ * later {@link restoreSnapshot}. Cached instances are NOT part of the
172
+ * snapshot — only the registrations.
173
+ */
174
+ snapshot() {
175
+ this.ensureNotDisposed();
176
+ return this.#registry.snapshot();
177
+ }
178
+ /**
179
+ * Wholesale-replaces the registration set with a previous snapshot.
180
+ * Entries are validated, cached singletons for the old set are evicted and
181
+ * disposed, and the operation is refused when registrations are frozen.
182
+ */
183
+ restoreSnapshot(registrations) {
184
+ this.ensureMutable();
185
+ this.#registry.restore(registrations);
94
186
  }
95
187
  isStarted() {
96
- return this.started;
188
+ return this.#started;
97
189
  }
98
190
  isDisposed() {
99
- return this.disposed;
191
+ return this.#disposed;
100
192
  }
101
- clearSingletons() {
102
- this.ensureActive();
103
- this.resolver.clearSingletons();
193
+ /**
194
+ * Evicts all cached singleton instances and disposes the tracked ones.
195
+ * Fails with an AggregateError listing every disposal failure.
196
+ */
197
+ async clearSingletons() {
198
+ this.ensureNotDisposed();
199
+ const tokens = this.#resolver.getCachedSingletonTokens();
200
+ this.#resolver.clearSingletonCache();
201
+ const failures = [];
202
+ for (const token of tokens) {
203
+ try {
204
+ await this.#lifecycle.disposeInstance(token);
205
+ }
206
+ catch (error) {
207
+ failures.push(error);
208
+ }
209
+ }
210
+ if (failures.length > 0)
211
+ throw new AggregateError(failures, `Failed to dispose ${failures.length} singleton instance(s) of container "${this.name}".`);
104
212
  }
105
- async dispose() {
106
- if (this.disposed)
107
- return;
108
- try {
109
- if (this.options.autoDispose)
110
- await this.lifecycle.disposeContainer();
111
- else
112
- this.lifecycle.reset();
213
+ /**
214
+ * Disposes the container: all live scopes first, then (with
215
+ * `autoDispose: true`, the default) every tracked singleton in reverse
216
+ * creation order.
217
+ *
218
+ * With `autoDispose: false` tracked instances are NOT disposed — their
219
+ * references are simply released and disposal becomes the caller's
220
+ * responsibility.
221
+ *
222
+ * Disposal is terminal: the container is marked disposed even when some
223
+ * instances fail to dispose; every failure is reported in the thrown
224
+ * AggregateError. Idempotent.
225
+ *
226
+ * The container is marked disposed *before* any cleanup runs, so a
227
+ * `resolve()` racing the disposal throws instead of creating a singleton
228
+ * that would be tracked after the disposal snapshot and then silently
229
+ * dropped. Concurrent `dispose()` calls share the in-flight disposal and
230
+ * settle only when it has finished.
231
+ */
232
+ dispose() {
233
+ if (this.#disposing)
234
+ return this.#disposing;
235
+ if (this.#disposed)
236
+ return Promise.resolve();
237
+ this.#disposed = true;
238
+ this.#started = false;
239
+ this.#disposing = this.runDispose().finally(() => {
240
+ this.#disposing = undefined;
241
+ });
242
+ return this.#disposing;
243
+ }
244
+ async runDispose() {
245
+ const failures = [];
246
+ for (const scope of [...this.#liveScopes]) {
247
+ try {
248
+ await scope.dispose();
249
+ }
250
+ catch (error) {
251
+ failures.push(error);
252
+ }
113
253
  }
114
- finally {
115
- this.resolver.clearSingletons();
116
- this.disposed = true;
117
- this.started = false;
254
+ this.#liveScopes.clear();
255
+ if (this.options.autoDispose) {
256
+ try {
257
+ await this.#lifecycle.dispose();
258
+ }
259
+ catch (error) {
260
+ failures.push(error);
261
+ }
118
262
  }
263
+ this.#lifecycle.shutdown();
264
+ this.#resolver.clearSingletonCache();
265
+ if (failures.length > 0)
266
+ throw new AggregateError(failures, `Container "${this.name}" was disposed, but ${failures.length} cleanup step(s) failed.`);
119
267
  }
120
268
  get resolutionOptions() {
121
269
  return {
@@ -124,20 +272,54 @@ export class Container {
124
272
  maxResolutionDepth: this.options.resolution.maxResolutionDepth ?? 100,
125
273
  };
126
274
  }
275
+ /** @internal Creates a fresh scoped-instance cache for a scope. */
276
+ createScopeCache(parent) {
277
+ return this.#resolver.createScope(parent);
278
+ }
279
+ /** @internal Resolves on behalf of a scope, tracking singleton results. */
280
+ resolveInScope(token, cache, onInstanceCreated) {
281
+ this.ensureActive();
282
+ return this.#resolver.resolveDetailed(token, this.buildResolutionOptions(cache, onInstanceCreated));
283
+ }
284
+ /** @internal Unregisters a top-level scope from the live-scope set. */
285
+ releaseScope(scope) {
286
+ this.#liveScopes.delete(scope);
287
+ }
288
+ buildResolutionOptions(cache, onInstanceCreated) {
289
+ return {
290
+ ...this.resolutionOptions,
291
+ allowRegistration: !this.options.freezeRegistrations,
292
+ cache,
293
+ onInstanceCreated: (result) => {
294
+ this.trackCreated(result);
295
+ onInstanceCreated?.(result);
296
+ },
297
+ };
298
+ }
299
+ /**
300
+ * Tracks every freshly created SINGLETON instance — top-level results and
301
+ * transitively created dependencies alike — in the container lifecycle.
302
+ * SCOPED instances are owned by scopes and TRANSIENT ones by callers.
303
+ */
304
+ trackCreated(result) {
305
+ if (result.scope !== Scope.SINGLETON)
306
+ return;
307
+ this.#lifecycle.track(unwrapToken(result.token), result.value, ContainerLifecycleOwner.CONTAINER);
308
+ }
127
309
  ensureActive() {
128
310
  this.ensureNotDisposed();
129
- if (!this.started)
311
+ if (!this.#started)
130
312
  this.start();
131
313
  }
132
314
  ensureNotDisposed() {
133
- if (this.disposed)
315
+ if (this.#disposed)
134
316
  throw new Error(`Container "${this.name}" has already been disposed.`);
135
317
  }
136
318
  ensureMutable() {
137
319
  this.ensureNotDisposed();
138
320
  if (!this.options.freezeRegistrations)
139
321
  return;
140
- if (this.started)
322
+ if (this.#started)
141
323
  throw new Error(`Registrations for container "${this.name}" are frozen.`);
142
324
  }
143
325
  }
@@ -1,31 +1,54 @@
1
1
  /**
2
- * ContainerScopeContext — child dependency scope with
3
- * its own scoped-instance cache.
4
- */
5
- import type { RegistrationToken } from "../containerRegistration/containerRegistration.core.js";
6
- import type { ContainerScopeOptions, ContainerLike } from "./containerCore.type.js";
7
- /**
8
- * Represents a child dependency scope.
2
+ * ContainerScopeContext — a dependency scope with its own scoped-instance
3
+ * cache and its own lifecycle for SCOPED instances.
9
4
  *
10
- * A scope has its own scoped-instance cache while sharing
11
- * registrations and singleton instances with its parent.
5
+ * Ownership model:
6
+ * - SCOPED instances created through this scope — including those created
7
+ * transitively as dependencies — are owned by the scope and disposed when
8
+ * the scope is disposed.
9
+ * - SINGLETON instances resolved through a scope stay owned by the parent
10
+ * container — a scope's disposal never touches them.
11
+ * - TRANSIENT instances are not tracked anywhere; callers own their disposal.
12
+ *
13
+ * Nesting: `scope.createScope()` creates a true child scope. A child sees
14
+ * SCOPED instances already created by its ancestors (lookups chain upward),
15
+ * while instances it creates itself are private to the child. Disposing a
16
+ * scope disposes its children first, and a child refuses to resolve once any
17
+ * ancestor (scope or container) is disposed.
12
18
  */
19
+ import type { RegistrationToken, ResolvedTokens } from "../containerRegistration/containerRegistration.core.js";
20
+ import type { ContainerScopeOptions, ContainerLike } from "./containerCore.type.js";
13
21
  export declare class ContainerScopeContext {
14
- private readonly parent;
15
22
  private disposed;
23
+ private disposing;
16
24
  private readonly cache;
17
25
  private readonly lifecycle;
26
+ private readonly container;
27
+ private readonly parentScope;
28
+ private readonly children;
18
29
  readonly name: string;
19
30
  readonly metadata: Readonly<Record<string, unknown>>;
20
- constructor(parent: ContainerLike, options?: ContainerScopeOptions);
31
+ /**
32
+ * @param container The container that owns the scope tree.
33
+ * @param options Scope name / metadata.
34
+ * @param parentScope When given, the new scope is a nested child of it.
35
+ * Application code should use `container.createScope()` /
36
+ * `scope.createScope()` rather than constructing scopes directly.
37
+ */
38
+ constructor(container: ContainerLike, options?: ContainerScopeOptions, parentScope?: ContainerScopeContext);
21
39
  /**
22
40
  * Resolves a dependency within this scope.
41
+ *
42
+ * SCOPED instances are cached per scope (visible to child scopes) and
43
+ * tracked for disposal with the scope that created them. SINGLETON
44
+ * instances come from (and are tracked by) the container. TRANSIENT
45
+ * instances are created fresh and never tracked.
23
46
  */
24
47
  resolve<T>(token: RegistrationToken<T>): T;
25
48
  /**
26
- * Resolves multiple dependencies within this scope.
49
+ * Resolves multiple dependencies within this scope, typed per token.
27
50
  */
28
- resolveMany<T>(tokens: readonly RegistrationToken<T>[]): T[];
51
+ resolveMany<const Tokens extends readonly RegistrationToken[]>(tokens: Tokens): ResolvedTokens<Tokens>;
29
52
  /**
30
53
  * Checks whether a dependency can be resolved.
31
54
  */
@@ -35,23 +58,48 @@ export declare class ContainerScopeContext {
35
58
  */
36
59
  has<T>(token: RegistrationToken<T>): boolean;
37
60
  /**
38
- * Creates another nested scope.
61
+ * Creates a nested child scope.
62
+ *
63
+ * The child inherits this scope's cached SCOPED instances for lookups,
64
+ * caches the SCOPED instances it creates itself, and is disposed
65
+ * automatically when this scope is disposed.
39
66
  */
40
67
  createScope(options?: ContainerScopeOptions): ContainerScopeContext;
41
68
  /**
42
- * Disposes all instances belonging to this scope.
69
+ * Disposes child scopes (most recent first), then all SCOPED instances
70
+ * belonging to this scope in reverse creation order, and detaches the scope
71
+ * from its parent. Container-owned singletons are not touched. Idempotent.
72
+ *
73
+ * Every failure is collected; the scope is marked disposed regardless and
74
+ * an AggregateError listing the failures is thrown afterwards. Concurrent
75
+ * callers share the in-flight disposal rather than returning early.
43
76
  */
44
77
  dispose(): Promise<void>;
78
+ private runDispose;
45
79
  /**
46
80
  * Returns whether the scope has been disposed.
47
81
  */
48
82
  isDisposed(): boolean;
49
83
  /**
50
- * Returns the parent container.
84
+ * Returns the direct parent: the container for a top-level scope, or the
85
+ * parent scope for a nested one.
86
+ */
87
+ getParent(): ContainerLike | ContainerScopeContext;
88
+ /**
89
+ * Returns the container that owns the whole scope tree.
90
+ */
91
+ getContainer(): ContainerLike;
92
+ /** @internal Detaches a disposed child scope. */
93
+ releaseChild(scope: ContainerScopeContext): void;
94
+ /**
95
+ * Tracks SCOPED instances created during a resolution through this scope
96
+ * (top-level result or transitively created dependency). Instances served
97
+ * from an ancestor's cache are cache hits and never reach here.
51
98
  */
52
- getParent(): ContainerLike;
99
+ private trackCreated;
53
100
  /**
54
- * Throws when the scope is no longer active.
101
+ * Throws when the scope — or any ancestor scope, or the container that
102
+ * owns it — is no longer active.
55
103
  */
56
104
  private ensureActive;
57
105
  }