@zudojs/container 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Token-based dependency injection container for managing application dependencies and service lifetimes.
4
4
 
5
+ <!-- zudo-docs:start -->
6
+
7
+ **Documentation:** [zudojs.oyinlola.site/docs/packages-container](https://zudojs.oyinlola.site/docs/packages-container) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-container.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
8
+
9
+ <!-- zudo-docs:end -->
10
+
5
11
  ## Installation
6
12
 
7
13
  ```bash
@@ -71,7 +77,10 @@ not capture a shorter-lived dependency.
71
77
 
72
78
  `registerValue()` always registers as `SINGLETON`, overriding any
73
79
  `options.scope` you pass — a value provider can only ever return the one
74
- instance you gave it.
80
+ instance you gave it. The container did not create that value, so it
81
+ never disposes it: a pool or logger shared between containers, or owned
82
+ by the host, stays open when a container is disposed. Register a factory
83
+ instead when the container should own the instance.
75
84
 
76
85
  A `useExisting` alias never becomes a second owner of its target's instance:
77
86
  a `SINGLETON` or `SCOPED` target is disposed exactly once by whoever created
@@ -148,7 +157,12 @@ intentionally swap a registration.
148
157
 
149
158
  Replacing or removing a registration **evicts and disposes** its cached
150
159
  singleton — and evicts any cached `useExisting` alias of it — so the next
151
- `resolve()` of either token uses the new provider.
160
+ `resolve()` of either token uses the new provider. Eviction cascades: every
161
+ cached singleton that was built on the replaced token (directly or through
162
+ a `TRANSIENT` in between) is evicted and disposed too, so consumers are
163
+ rebuilt against the new instance instead of keeping the old, disposed one.
164
+ Live scopes drop (and dispose) their cached `SCOPED` copies of the replaced
165
+ token and of its consumers.
152
166
 
153
167
  `DuplicateRegistrationError`, `CircularDependencyError`,
154
168
  `RegistrationNotFoundError` and `ProviderResolutionError` are defined in
@@ -208,6 +222,7 @@ await container.dispose();
208
222
  have a `dispose()` method or implement `Symbol.dispose` /
209
223
  `Symbol.asyncDispose`.
210
224
  - `TRANSIENT` instances are never tracked; dispose them yourself.
225
+ - `registerValue()` values are never disposed by the container.
211
226
  - With `autoDispose: false`, `dispose()` releases tracked references
212
227
  **without disposing them** — disposal becomes your responsibility.
213
228
  - Disposal is terminal: the container is marked disposed even if some
@@ -77,7 +77,10 @@ export declare class Container implements ContainerLike {
77
77
  getRegistration<T>(token: RegistrationToken<T>): ContainerRegistration<T> | undefined;
78
78
  replace<T>(token: RegistrationToken<T>, provider: ContainerProvider<T>, options?: CreateRegistrationOptions): ContainerRegistration<T>;
79
79
  remove<T>(token: RegistrationToken<T>): boolean;
80
- /** Removes every registration (evicting and disposing cached singletons). */
80
+ /**
81
+ * Removes every registration, evicting and disposing cached instances:
82
+ * container-owned singletons and every live scope's SCOPED copies alike.
83
+ */
81
84
  clearRegistrations(): void;
82
85
  createScope(options?: ContainerScopeOptions): ContainerScopeContext;
83
86
  getRegistrations(): readonly ContainerRegistration[];
@@ -91,8 +94,10 @@ export declare class Container implements ContainerLike {
91
94
  snapshot(): readonly ContainerRegistration[];
92
95
  /**
93
96
  * Wholesale-replaces the registration set with a previous snapshot.
94
- * Entries are validated, cached singletons for the old set are evicted and
95
- * disposed, and the operation is refused when registrations are frozen.
97
+ * Entries are validated, cached instances built from the old set are
98
+ * evicted and disposed — container-owned singletons and every live
99
+ * scope's SCOPED copies alike — and the operation is refused when
100
+ * registrations are frozen.
96
101
  */
97
102
  restoreSnapshot(registrations: readonly ContainerRegistration[]): void;
98
103
  isStarted(): boolean;
@@ -19,7 +19,7 @@ import { ContainerResolver } from "../containerResolution/containerResolution.co
19
19
  import { ContainerLifecycle, ContainerLifecycleOwner, } from "../containerLifecycle/containerLifecycle.core.js";
20
20
  import { resolveContainerOptions } from "../containerOptions/containerOptions.type.js";
21
21
  import { unwrapToken } from "../containerToken/containerToken.type.js";
22
- import { RegistrationNotFoundError } from "@zudojs/errors";
22
+ import { ContainerError, ContainerLifecycleError, RegistrationNotFoundError, } from "@zudojs/errors";
23
23
  import { ContainerScopeContext } from "./containerCore.scope.js";
24
24
  export class Container {
25
25
  name;
@@ -44,6 +44,11 @@ export class Container {
44
44
  void this.#lifecycle.disposeInstance(token).catch(() => {
45
45
  /* see clearSingletons()/dispose() for error-surfacing disposal */
46
46
  });
47
+ }, (token) => {
48
+ // Live scopes would otherwise keep serving a replaced SCOPED
49
+ // instance, or one built on a replaced dependency.
50
+ for (const scope of this.#liveScopes)
51
+ scope.evictCached(token);
47
52
  });
48
53
  }
49
54
  /**
@@ -142,7 +147,10 @@ export class Container {
142
147
  this.ensureMutable();
143
148
  return this.#registry.remove(token);
144
149
  }
145
- /** Removes every registration (evicting and disposing cached singletons). */
150
+ /**
151
+ * Removes every registration, evicting and disposing cached instances:
152
+ * container-owned singletons and every live scope's SCOPED copies alike.
153
+ */
146
154
  clearRegistrations() {
147
155
  this.ensureMutable();
148
156
  this.#registry.clear();
@@ -150,7 +158,7 @@ export class Container {
150
158
  createScope(options = {}) {
151
159
  this.ensureActive();
152
160
  if (!this.options.allowScopes)
153
- throw new Error(`Container scopes are disabled for "${this.name}".`);
161
+ throw new ContainerError(`Container scopes are disabled for "${this.name}".`);
154
162
  const scope = new ContainerScopeContext(this, options);
155
163
  this.#liveScopes.add(scope);
156
164
  return scope;
@@ -177,8 +185,10 @@ export class Container {
177
185
  }
178
186
  /**
179
187
  * 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.
188
+ * Entries are validated, cached instances built from the old set are
189
+ * evicted and disposed — container-owned singletons and every live
190
+ * scope's SCOPED copies alike — and the operation is refused when
191
+ * registrations are frozen.
182
192
  */
183
193
  restoreSnapshot(registrations) {
184
194
  this.ensureMutable();
@@ -313,14 +323,14 @@ export class Container {
313
323
  }
314
324
  ensureNotDisposed() {
315
325
  if (this.#disposed)
316
- throw new Error(`Container "${this.name}" has already been disposed.`);
326
+ throw new ContainerLifecycleError("dispose", `Container "${this.name}" has already been disposed.`);
317
327
  }
318
328
  ensureMutable() {
319
329
  this.ensureNotDisposed();
320
330
  if (!this.options.freezeRegistrations)
321
331
  return;
322
332
  if (this.#started)
323
- throw new Error(`Registrations for container "${this.name}" are frozen.`);
333
+ throw new ContainerError(`Registrations for container "${this.name}" are frozen.`);
324
334
  }
325
335
  }
326
336
  export function createContainer(options = {}) {
@@ -17,6 +17,7 @@
17
17
  * ancestor (scope or container) is disposed.
18
18
  */
19
19
  import type { RegistrationToken, ResolvedTokens } from "../containerRegistration/containerRegistration.core.js";
20
+ import type { Token } from "../containerToken/containerToken.type.js";
20
21
  import type { ContainerScopeOptions, ContainerLike } from "./containerCore.type.js";
21
22
  export declare class ContainerScopeContext {
22
23
  private disposed;
@@ -89,6 +90,12 @@ export declare class ContainerScopeContext {
89
90
  * Returns the container that owns the whole scope tree.
90
91
  */
91
92
  getContainer(): ContainerLike;
93
+ /**
94
+ * @internal Drops a cached SCOPED instance whose registration (or a
95
+ * dependency of it) was replaced or removed, disposing it best-effort,
96
+ * in this scope and every child scope.
97
+ */
98
+ evictCached(token: Token): void;
92
99
  /** @internal Detaches a disposed child scope. */
93
100
  releaseChild(scope: ContainerScopeContext): void;
94
101
  /**
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { ContainerLifecycle, ContainerLifecycleOwner, } from "../containerLifecycle/containerLifecycle.core.js";
20
20
  import { ContainerScope } from "../containerScope/containerScope.type.js";
21
+ import { ContainerLifecycleError } from "@zudojs/errors";
21
22
  export class ContainerScopeContext {
22
23
  disposed = false;
23
24
  disposing;
@@ -156,6 +157,19 @@ export class ContainerScopeContext {
156
157
  getContainer() {
157
158
  return this.container;
158
159
  }
160
+ /**
161
+ * @internal Drops a cached SCOPED instance whose registration (or a
162
+ * dependency of it) was replaced or removed, disposing it best-effort,
163
+ * in this scope and every child scope.
164
+ */
165
+ evictCached(token) {
166
+ if (this.disposed)
167
+ return;
168
+ this.cache.delete?.(token);
169
+ void this.lifecycle.disposeInstance(token).catch(() => undefined);
170
+ for (const child of this.children)
171
+ child.evictCached(token);
172
+ }
159
173
  /** @internal Detaches a disposed child scope. */
160
174
  releaseChild(scope) {
161
175
  this.children.delete(scope);
@@ -176,13 +190,13 @@ export class ContainerScopeContext {
176
190
  */
177
191
  ensureActive() {
178
192
  if (this.disposed) {
179
- throw new Error(`Container scope "${this.name}" has already been disposed.`);
193
+ throw new ContainerLifecycleError("dispose", `Container scope "${this.name}" has already been disposed.`);
180
194
  }
181
195
  if (this.parentScope?.isDisposed()) {
182
- throw new Error(`Parent scope "${this.parentScope.name}" of scope "${this.name}" has been disposed.`);
196
+ throw new ContainerLifecycleError("dispose", `Parent scope "${this.parentScope.name}" of scope "${this.name}" has been disposed.`);
183
197
  }
184
198
  if (this.container.isDisposed()) {
185
- throw new Error(`Container "${this.container.name}" owning scope "${this.name}" has been disposed.`);
199
+ throw new ContainerLifecycleError("dispose", `Container "${this.container.name}" owning scope "${this.name}" has been disposed.`);
186
200
  }
187
201
  }
188
202
  }
@@ -22,7 +22,10 @@
22
22
  * The resolver subscribes to registry change events: REPLACE/REMOVE evict the
23
23
  * affected token's cached singleton, CLEAR/RESTORE evict all cached
24
24
  * singletons. Each eviction is reported through the `onSingletonEvicted`
25
- * callback so the owning container can dispose the instance.
25
+ * callback so the owning container can dispose the instance, and every
26
+ * invalidated token — for CLEAR/RESTORE that is every cached token, SCOPED
27
+ * ones included — through `onTokenInvalidated` so live scopes drop and
28
+ * dispose their own copies.
26
29
  */
27
30
  import type { RegistrationToken } from "../containerRegistration/containerRegistration.core.js";
28
31
  import type { ContainerRegistry } from "../containerRegistry/containerRegistry.core.js";
@@ -32,7 +35,28 @@ export declare class ContainerResolver {
32
35
  private readonly registry;
33
36
  private readonly singletonCache;
34
37
  private readonly onSingletonEvicted;
35
- constructor(registry: ContainerRegistry, onSingletonEvicted?: (token: Token<unknown>) => void);
38
+ private readonly onTokenInvalidated;
39
+ private readonly dependents;
40
+ /**
41
+ * Tokens for which a SCOPED instance has ever been cached in a scope.
42
+ *
43
+ * The singleton cache only knows about SINGLETON tokens and the
44
+ * dependent index only records tokens consumed by another cached
45
+ * instance, so neither can name a SCOPED token that a scope resolved
46
+ * directly. A wholesale CLEAR/RESTORE must still tell live scopes to
47
+ * drop those instances.
48
+ */
49
+ private readonly scopedTokens;
50
+ /**
51
+ * @param onSingletonEvicted Called for each evicted cached singleton so
52
+ * the owner can dispose it.
53
+ * @param onTokenInvalidated Called for every token invalidated by a
54
+ * registry change — for `replace()`/`remove()` the token itself and
55
+ * each cached consumer, for `clear()`/`restore()` every token that
56
+ * was cached at all — so owners of scope caches can drop and dispose
57
+ * their SCOPED copies.
58
+ */
59
+ constructor(registry: ContainerRegistry, onSingletonEvicted?: (token: Token<unknown>) => void, onTokenInvalidated?: (token: Token<unknown>) => void);
36
60
  resolve<T>(token: RegistrationToken<T>, options?: ResolutionOptions): T;
37
61
  resolveDetailed<T>(token: RegistrationToken<T>, options?: ResolutionOptions): ResolutionResult<T>;
38
62
  private resolveInternal;
@@ -22,16 +22,20 @@
22
22
  * The resolver subscribes to registry change events: REPLACE/REMOVE evict the
23
23
  * affected token's cached singleton, CLEAR/RESTORE evict all cached
24
24
  * singletons. Each eviction is reported through the `onSingletonEvicted`
25
- * callback so the owning container can dispose the instance.
25
+ * callback so the owning container can dispose the instance, and every
26
+ * invalidated token — for CLEAR/RESTORE that is every cached token, SCOPED
27
+ * ones included — through `onTokenInvalidated` so live scopes drop and
28
+ * dispose their own copies.
26
29
  */
27
30
  import { isClassProvider, isExistingProvider, isFactoryProvider, isValueProvider, normalizeProvider, } from "../containerProvider/containerProvider.core.js";
28
31
  import { ContainerScope as Scope } from "../containerScope/containerScope.type.js";
29
32
  import { defineRegistration, getRegistrationToken, } from "../containerRegistration/containerRegistration.core.js";
30
33
  import { RegistryOperation } from "../containerRegistry/containerRegistry.type.js";
31
34
  import { unwrapToken } from "../containerToken/containerToken.type.js";
32
- import { CircularDependencyError, ProviderResolutionError, RegistrationNotFoundError, } from "@zudojs/errors";
35
+ import { CircularDependencyError, ContainerError, ProviderResolutionError, RegistrationNotFoundError, } from "@zudojs/errors";
33
36
  import { AsyncProviderError, CaptiveDependencyError, DependencyResolutionError, MaxResolutionDepthError, ScopedResolutionError, } from "./containerResolution.error.js";
34
37
  import { describeToken } from "../containerToken/containerToken.type.js";
38
+ import { DependentIndex } from "./containerResolution.dependents.js";
35
39
  /**
36
40
  * Scope cache that falls back to its parent scope's cache for lookups while
37
41
  * writing only to its own map. Nested scopes therefore see SCOPED instances
@@ -55,6 +59,9 @@ class ChainedResolutionCache {
55
59
  set(token, value) {
56
60
  this.#own.set(token, value);
57
61
  }
62
+ delete(token) {
63
+ return this.#own.delete(token);
64
+ }
58
65
  clear() {
59
66
  this.#own.clear();
60
67
  }
@@ -63,9 +70,31 @@ export class ContainerResolver {
63
70
  registry;
64
71
  singletonCache = new Map();
65
72
  onSingletonEvicted;
66
- constructor(registry, onSingletonEvicted) {
73
+ onTokenInvalidated;
74
+ dependents = new DependentIndex();
75
+ /**
76
+ * Tokens for which a SCOPED instance has ever been cached in a scope.
77
+ *
78
+ * The singleton cache only knows about SINGLETON tokens and the
79
+ * dependent index only records tokens consumed by another cached
80
+ * instance, so neither can name a SCOPED token that a scope resolved
81
+ * directly. A wholesale CLEAR/RESTORE must still tell live scopes to
82
+ * drop those instances.
83
+ */
84
+ scopedTokens = new Set();
85
+ /**
86
+ * @param onSingletonEvicted Called for each evicted cached singleton so
87
+ * the owner can dispose it.
88
+ * @param onTokenInvalidated Called for every token invalidated by a
89
+ * registry change — for `replace()`/`remove()` the token itself and
90
+ * each cached consumer, for `clear()`/`restore()` every token that
91
+ * was cached at all — so owners of scope caches can drop and dispose
92
+ * their SCOPED copies.
93
+ */
94
+ constructor(registry, onSingletonEvicted, onTokenInvalidated) {
67
95
  this.registry = registry;
68
96
  this.onSingletonEvicted = onSingletonEvicted;
97
+ this.onTokenInvalidated = onTokenInvalidated;
69
98
  registry.subscribe((event) => this.handleRegistryChange(event));
70
99
  }
71
100
  resolve(token, options = {}) {
@@ -108,6 +137,7 @@ export class ContainerResolver {
108
137
  }
109
138
  }
110
139
  const currentPath = [...state.path, token];
140
+ this.dependents.record(token, state.path, (t) => this.registry.get(t)?.scope);
111
141
  if (registration.scope === Scope.SINGLETON) {
112
142
  if (this.singletonCache.has(token)) {
113
143
  return {
@@ -150,8 +180,11 @@ export class ContainerResolver {
150
180
  }
151
181
  if (registration.scope === Scope.SINGLETON)
152
182
  this.singletonCache.set(token, value);
153
- else if (registration.scope === Scope.SCOPED)
183
+ else if (registration.scope === Scope.SCOPED) {
154
184
  state.scopeCache?.set(token, value);
185
+ if (state.scopeCache)
186
+ this.scopedTokens.add(token);
187
+ }
155
188
  const result = {
156
189
  value,
157
190
  token,
@@ -173,14 +206,17 @@ export class ContainerResolver {
173
206
  const provider = normalizeProvider(registration.provider);
174
207
  const token = getRegistrationToken(registration);
175
208
  try {
209
+ // A pre-built value belongs to whoever built it (often shared
210
+ // between containers); the container did not create it, so it
211
+ // must not dispose it.
176
212
  if (isValueProvider(provider))
177
- return { value: provider.useValue, owned: true };
213
+ return { value: provider.useValue, owned: false };
178
214
  if (isExistingProvider(provider)) {
179
215
  const target = unwrapToken(provider.useExisting);
180
216
  if (!this.registry.has(target) &&
181
217
  !(state.autoRegisterClasses && typeof target === "function")) {
182
- throw new Error(`useExisting target "${describeToken(target)}" for token ` +
183
- `"${describeToken(token)}" is not registered.`);
218
+ throw new ContainerError(`useExisting target "${describeToken(target)}" for token ` +
219
+ `"${describeToken(token)}" is not registered.`, { token: describeToken(token) });
184
220
  }
185
221
  const resolved = this.resolveInternal(target, state, singletonAncestor);
186
222
  // Only a TRANSIENT target has no owner of its own; a cached alias
@@ -206,7 +242,9 @@ export class ContainerResolver {
206
242
  const ctor = provider.useClass;
207
243
  return { value: new ctor(...args), owned: true };
208
244
  }
209
- throw new Error("Unsupported container provider.");
245
+ throw new ContainerError("Unsupported container provider.", {
246
+ token: describeToken(token),
247
+ });
210
248
  }
211
249
  catch (error) {
212
250
  // Resolution errors created deeper in the chain already carry the full
@@ -252,14 +290,33 @@ export class ContainerResolver {
252
290
  case RegistryOperation.REPLACE:
253
291
  case RegistryOperation.REMOVE: {
254
292
  const token = unwrapToken(event.token);
293
+ // Consumers first, so each is disposed before what it consumed.
294
+ for (const dependent of this.dependents.take(token)) {
295
+ this.evictSingleton(dependent);
296
+ this.onTokenInvalidated?.(dependent);
297
+ }
255
298
  this.evictSingleton(token);
256
299
  this.evictAliasesOf(token);
300
+ this.onTokenInvalidated?.(token);
257
301
  break;
258
302
  }
259
303
  case RegistryOperation.CLEAR:
260
304
  case RegistryOperation.RESTORE: {
305
+ // Every cached token is discarded by a wholesale change, so every
306
+ // one of them must be reported as invalidated — not just the
307
+ // singletons. Only REPLACE/REMOVE used to notify, so a live scope
308
+ // went on serving (and never disposed) the SCOPED instance built
309
+ // from a registration that clear()/restore() had thrown away.
310
+ const invalidated = new Set([
311
+ ...this.singletonCache.keys(),
312
+ ...this.scopedTokens,
313
+ ]);
261
314
  for (const t of [...this.singletonCache.keys()])
262
315
  this.evictSingleton(t);
316
+ this.dependents.clear();
317
+ this.scopedTokens.clear();
318
+ for (const t of invalidated)
319
+ this.onTokenInvalidated?.(t);
263
320
  break;
264
321
  }
265
322
  default:
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Reverse dependency index for cached container instances.
3
+ *
4
+ * `replace()`/`remove()` used to evict only the changed token, so a
5
+ * singleton that had already captured it kept serving the old — by then
6
+ * disposed — instance. Recording which cached instance consumed which
7
+ * token lets eviction cascade to every consumer.
8
+ */
9
+ import { ContainerScope as Scope } from "../containerScope/containerScope.type.js";
10
+ import type { Token } from "../containerToken/containerToken.type.js";
11
+ /** Looks up the lifetime of a registered token. */
12
+ export type LifetimeLookup = (token: Token<unknown>) => Scope | undefined;
13
+ /**
14
+ * Maps a token to the cached (SINGLETON or SCOPED) instances whose
15
+ * construction resolved it.
16
+ */
17
+ export declare class DependentIndex {
18
+ #private;
19
+ /**
20
+ * Records that the nearest cached ancestor on `path` depends on
21
+ * `token`. Transient ancestors are skipped: they are rebuilt on every
22
+ * resolution, so the cached instance above them is the real consumer.
23
+ */
24
+ record(token: Token<unknown>, path: readonly Token<unknown>[], lifetimeOf: LifetimeLookup): void;
25
+ /**
26
+ * Returns every token that transitively depends on `token`, nearest
27
+ * consumers last, and forgets them.
28
+ */
29
+ take(token: Token<unknown>): readonly Token<unknown>[];
30
+ /** Forgets every recorded dependency. */
31
+ clear(): void;
32
+ }
33
+ //# sourceMappingURL=containerResolution.dependents.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Reverse dependency index for cached container instances.
3
+ *
4
+ * `replace()`/`remove()` used to evict only the changed token, so a
5
+ * singleton that had already captured it kept serving the old — by then
6
+ * disposed — instance. Recording which cached instance consumed which
7
+ * token lets eviction cascade to every consumer.
8
+ */
9
+ import { ContainerScope as Scope } from "../containerScope/containerScope.type.js";
10
+ /**
11
+ * Maps a token to the cached (SINGLETON or SCOPED) instances whose
12
+ * construction resolved it.
13
+ */
14
+ export class DependentIndex {
15
+ #dependents = new Map();
16
+ /**
17
+ * Records that the nearest cached ancestor on `path` depends on
18
+ * `token`. Transient ancestors are skipped: they are rebuilt on every
19
+ * resolution, so the cached instance above them is the real consumer.
20
+ */
21
+ record(token, path, lifetimeOf) {
22
+ for (let i = path.length - 1; i >= 0; i--) {
23
+ const owner = path[i];
24
+ const lifetime = lifetimeOf(owner);
25
+ if (lifetime === Scope.SINGLETON || lifetime === Scope.SCOPED) {
26
+ if (owner === token)
27
+ return;
28
+ let set = this.#dependents.get(token);
29
+ if (set === undefined) {
30
+ set = new Set();
31
+ this.#dependents.set(token, set);
32
+ }
33
+ set.add(owner);
34
+ return;
35
+ }
36
+ }
37
+ }
38
+ /**
39
+ * Returns every token that transitively depends on `token`, nearest
40
+ * consumers last, and forgets them.
41
+ */
42
+ take(token) {
43
+ const ordered = [];
44
+ const visited = new Set([token]);
45
+ const visit = (current) => {
46
+ const direct = this.#dependents.get(current);
47
+ this.#dependents.delete(current);
48
+ if (direct === undefined)
49
+ return;
50
+ for (const dependent of direct) {
51
+ if (visited.has(dependent))
52
+ continue;
53
+ visited.add(dependent);
54
+ visit(dependent);
55
+ ordered.push(dependent);
56
+ }
57
+ };
58
+ visit(token);
59
+ return ordered;
60
+ }
61
+ /** Forgets every recorded dependency. */
62
+ clear() {
63
+ this.#dependents.clear();
64
+ }
65
+ }
66
+ //# sourceMappingURL=containerResolution.dependents.js.map
@@ -15,6 +15,8 @@ export interface ResolutionCache {
15
15
  has(token: Token<unknown>): boolean;
16
16
  get(token: Token<unknown>): unknown;
17
17
  set(token: Token<unknown>, value: unknown): void;
18
+ /** Drops one entry from this cache (not its parents). Optional. */
19
+ delete?(token: Token<unknown>): boolean;
18
20
  clear(): void;
19
21
  }
20
22
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/container",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Token-based dependency injection container for managing application dependencies and service lifetimes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,7 +18,7 @@
18
18
  "!dist/.tsbuildinfo"
19
19
  ],
20
20
  "dependencies": {
21
- "@zudojs/errors": "1.0.1"
21
+ "@zudojs/errors": "1.2.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "typescript": "7.0.2",