@codefast/di 0.3.14-canary.0 → 0.3.14-canary.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +54 -29
  3. package/dist/binding-scope.d.mts +11 -0
  4. package/dist/binding-scope.mjs +19 -0
  5. package/dist/binding-select.d.mts +8 -26
  6. package/dist/binding-select.mjs +56 -49
  7. package/dist/binding.d.mts +107 -327
  8. package/dist/binding.mjs +19 -324
  9. package/dist/constraints.d.mts +11 -29
  10. package/dist/constraints.mjs +32 -36
  11. package/dist/constructor-type.d.mts +17 -0
  12. package/dist/constructor-type.mjs +1 -0
  13. package/dist/container.d.mts +44 -128
  14. package/dist/container.mjs +664 -433
  15. package/dist/decorators/inject.d.mts +19 -50
  16. package/dist/decorators/inject.mjs +131 -93
  17. package/dist/decorators/injectable.d.mts +16 -37
  18. package/dist/decorators/injectable.mjs +47 -66
  19. package/dist/decorators/lifecycle-decorators.d.mts +2 -22
  20. package/dist/decorators/lifecycle-decorators.mjs +81 -37
  21. package/dist/dependency-graph.d.mts +24 -54
  22. package/dist/dependency-graph.mjs +51 -153
  23. package/dist/environment.d.mts +38 -12
  24. package/dist/environment.mjs +82 -16
  25. package/dist/errors.d.mts +69 -188
  26. package/dist/errors.mjs +92 -219
  27. package/dist/graph-adapters/cytoscape.d.mts +24 -0
  28. package/dist/graph-adapters/cytoscape.mjs +23 -0
  29. package/dist/graph-adapters/dot.d.mts +6 -0
  30. package/dist/graph-adapters/dot.mjs +17 -0
  31. package/dist/graph-adapters/reactflow.d.mts +29 -0
  32. package/dist/graph-adapters/reactflow.mjs +29 -0
  33. package/dist/graph-adapters/types.d.mts +2 -0
  34. package/dist/graph-adapters/types.mjs +1 -0
  35. package/dist/index.d.mts +16 -9
  36. package/dist/index.mjs +8 -5
  37. package/dist/inspector.d.mts +34 -95
  38. package/dist/inspector.mjs +57 -256
  39. package/dist/lifecycle.d.mts +20 -53
  40. package/dist/lifecycle.mjs +129 -99
  41. package/dist/metadata/metadata-keys.d.mts +9 -26
  42. package/dist/metadata/metadata-keys.mjs +7 -28
  43. package/dist/metadata/metadata-reader-token.d.mts +7 -0
  44. package/dist/metadata/metadata-reader-token.mjs +5 -0
  45. package/dist/metadata/metadata-types.d.mts +22 -71
  46. package/dist/metadata/symbol-metadata-reader.d.mts +10 -26
  47. package/dist/metadata/symbol-metadata-reader.mjs +32 -45
  48. package/dist/module.d.mts +30 -84
  49. package/dist/module.mjs +26 -72
  50. package/dist/registry.d.mts +32 -63
  51. package/dist/registry.mjs +131 -82
  52. package/dist/resolve-options.d.mts +18 -0
  53. package/dist/resolve-options.mjs +22 -0
  54. package/dist/resolver.d.mts +67 -190
  55. package/dist/resolver.mjs +715 -424
  56. package/dist/scope.d.mts +19 -102
  57. package/dist/scope.mjs +37 -192
  58. package/dist/token.d.mts +8 -22
  59. package/dist/token.mjs +9 -11
  60. package/dist/types.d.mts +48 -0
  61. package/dist/types.mjs +1 -0
  62. package/package.json +52 -14
  63. package/dist/metadata/param-registry.d.mts +0 -16
  64. package/dist/metadata/param-registry.mjs +0 -31
  65. package/dist/scope-validation.d.mts +0 -21
  66. package/dist/scope-validation.mjs +0 -35
package/dist/module.d.mts CHANGED
@@ -1,92 +1,38 @@
1
+ import { Constructor } from "./constructor-type.mjs";
1
2
  import { Token } from "./token.mjs";
2
- import { BindingBuilder, Constructor } from "./binding.mjs";
3
+ import { BindToBuilder } from "./binding.mjs";
3
4
 
4
5
  //#region src/module.d.ts
5
- /**
6
- * Builder passed to the setup callback of a synchronous {@link Module}.
7
- * Use `import()` to declare module dependencies (sync modules only —
8
- * passing an {@link AsyncModule} throws {@link InternalError}) and `bind()` to register tokens.
9
- *
10
- * **Single slot (last-wins)** — `bind(key).to*(...)` with no `whenNamed` / `whenTagged` / `when`
11
- * *before* the `to*()` call replaces all prior bindings for `key` from this module pass.
12
- *
13
- * **Multi-binding** — put at least one disambiguator *before* `to*()` (e.g.
14
- * `bind(key).whenNamed("a").to*(...)`, `whenTagged` before `to*()`, or `when` before `to*()`).
15
- * Each such line **appends** another binding so {@link Container.resolveAll} can return every
16
- * implementation. Use this order in modules; chaining `.to*(...).whenNamed()` only updates that
17
- * binding in place and does not stack multiple registrations across lines.
18
- */
19
- type ModuleBuilder = {
20
- readonly import: (...modules: Module[]) => void;
21
- readonly bind: <Value>(key: Token<Value> | Constructor<Value>) => BindingBuilder<Value>;
22
- };
23
- /**
24
- * Builder passed to the setup callback of an {@link AsyncModule}.
25
- * Unlike {@link ModuleBuilder}, `import()` accepts both sync and async modules.
26
- * Async sub-imports are collected and awaited **after** the setup callback returns.
27
- */
28
- type AsyncModuleBuilder = {
29
- readonly import: (...modules: (Module | AsyncModule)[]) => void;
30
- readonly bind: <Value>(key: Token<Value> | Constructor<Value>) => BindingBuilder<Value>;
31
- };
32
- /**
33
- * Immutable description of a bundle of bindings. A {@link Module} holds no runtime state and the
34
- * same instance may be loaded into any number of containers independently (spec §7.3).
35
- *
36
- * The owning container is responsible for tracking which modules have been loaded and which
37
- * binding ids each module produced; the module itself never sees a container reference.
38
- */
39
- declare class Module {
40
- /**
41
- * Human-readable label used in error messages, graph output, and module-cycle diagnostics.
42
- */
6
+ declare const SYNC_MODULE_BRAND: unique symbol;
7
+ declare const ASYNC_MODULE_BRAND: unique symbol;
8
+ interface SyncModule {
43
9
  readonly name: string;
44
- /**
45
- * The user-supplied setup callback; invoked exactly once per `load()` call.
46
- */
47
- private readonly syncSetup;
48
- /**
49
- * @internal Use {@link Module.create} instead.
50
- */
51
- private constructor();
52
- /**
53
- * Defines a synchronous module.
54
- * @param name - Human-readable label used in error messages and debug output.
55
- * @param setup - Callback that registers bindings via the {@link ModuleBuilder}.
56
- */
57
- static create(name: string, setup: (builder: ModuleBuilder) => void): Module;
58
- /**
59
- * Defines an async module — use when setup requires awaiting (e.g. reading config, dynamic imports).
60
- * Load with {@link Container.loadAsync} or {@link Container.fromModulesAsync}.
61
- */
62
- static createAsync(name: string, setup: (builder: AsyncModuleBuilder) => Promise<void>): AsyncModule;
63
- /**
64
- * @internal Invoked by the container while loading this module.
65
- */
66
- runSyncSetup(builder: ModuleBuilder): void;
10
+ readonly [SYNC_MODULE_BRAND]: true;
11
+ readonly _setup: (builder: ModuleBuilder) => void;
67
12
  }
68
- /**
69
- * An async module whose setup callback may `await` before registering bindings.
70
- * Prefer {@link Module.createAsync} over constructing this class directly.
71
- *
72
- * Load via `Container.loadAsync()` or `Container.fromModulesAsync()`;
73
- * passing an `AsyncModule` to the synchronous `Container.load()` throws
74
- * {@link AsyncModuleLoadError}.
75
- */
76
- declare class AsyncModule {
77
- /**
78
- * Human-readable label used in error messages and graph output.
79
- */
13
+ interface AsyncModule {
80
14
  readonly name: string;
81
- /**
82
- * The user-supplied async setup callback; invoked exactly once per `loadAsync()` call.
83
- */
84
- private readonly asyncSetup;
85
- constructor(name: string, asyncSetup: (builder: AsyncModuleBuilder) => Promise<void>);
86
- /**
87
- * @internal Invoked by the container while loading this module.
88
- */
89
- runAsyncSetup(builder: AsyncModuleBuilder): Promise<void>;
15
+ readonly [ASYNC_MODULE_BRAND]: true;
16
+ readonly _setup: (builder: AsyncModuleBuilder) => Promise<void>;
17
+ }
18
+ interface ModuleBuilder {
19
+ bind<const Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value>;
20
+ import(...modules: SyncModule[]): void;
21
+ }
22
+ interface AsyncModuleBuilder {
23
+ bind<const Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value>;
24
+ import(...modules: Array<SyncModule | AsyncModule>): void;
90
25
  }
26
+ declare const SyncModule: {
27
+ create(name: string, setup: (builder: ModuleBuilder) => void): SyncModule;
28
+ };
29
+ declare const AsyncModule: {
30
+ create(name: string, setup: (builder: AsyncModuleBuilder) => Promise<void>): AsyncModule;
31
+ };
32
+ declare const Module: {
33
+ create(name: string, setup: (builder: ModuleBuilder) => void): SyncModule;
34
+ createAsync(name: string, setup: (builder: AsyncModuleBuilder) => Promise<void>): AsyncModule;
35
+ };
36
+ declare function isSyncModule(m: SyncModule | AsyncModule): m is SyncModule;
91
37
  //#endregion
92
- export { AsyncModule, AsyncModuleBuilder, Module, ModuleBuilder };
38
+ export { AsyncModule, AsyncModuleBuilder, Module, ModuleBuilder, SyncModule, isSyncModule };
package/dist/module.mjs CHANGED
@@ -1,76 +1,30 @@
1
1
  //#region src/module.ts
2
- /**
3
- * Immutable description of a bundle of bindings. A {@link Module} holds no runtime state and the
4
- * same instance may be loaded into any number of containers independently (spec §7.3).
5
- *
6
- * The owning container is responsible for tracking which modules have been loaded and which
7
- * binding ids each module produced; the module itself never sees a container reference.
8
- */
9
- var Module = class Module {
10
- /**
11
- * Human-readable label used in error messages, graph output, and module-cycle diagnostics.
12
- */
13
- name;
14
- /**
15
- * The user-supplied setup callback; invoked exactly once per `load()` call.
16
- */
17
- syncSetup;
18
- /**
19
- * @internal Use {@link Module.create} instead.
20
- */
21
- constructor(name, syncSetup) {
22
- this.name = name;
23
- this.syncSetup = syncSetup;
24
- }
25
- /**
26
- * Defines a synchronous module.
27
- * @param name - Human-readable label used in error messages and debug output.
28
- * @param setup - Callback that registers bindings via the {@link ModuleBuilder}.
29
- */
30
- static create(name, setup) {
31
- return new Module(name, setup);
32
- }
33
- /**
34
- * Defines an async module — use when setup requires awaiting (e.g. reading config, dynamic imports).
35
- * Load with {@link Container.loadAsync} or {@link Container.fromModulesAsync}.
36
- */
37
- static createAsync(name, setup) {
38
- return new AsyncModule(name, setup);
39
- }
40
- /**
41
- * @internal Invoked by the container while loading this module.
42
- */
43
- runSyncSetup(builder) {
44
- this.syncSetup(builder);
45
- }
46
- };
47
- /**
48
- * An async module whose setup callback may `await` before registering bindings.
49
- * Prefer {@link Module.createAsync} over constructing this class directly.
50
- *
51
- * Load via `Container.loadAsync()` or `Container.fromModulesAsync()`;
52
- * passing an `AsyncModule` to the synchronous `Container.load()` throws
53
- * {@link AsyncModuleLoadError}.
54
- */
55
- var AsyncModule = class {
56
- /**
57
- * Human-readable label used in error messages and graph output.
58
- */
59
- name;
60
- /**
61
- * The user-supplied async setup callback; invoked exactly once per `loadAsync()` call.
62
- */
63
- asyncSetup;
64
- constructor(name, asyncSetup) {
65
- this.name = name;
66
- this.asyncSetup = asyncSetup;
67
- }
68
- /**
69
- * @internal Invoked by the container while loading this module.
70
- */
71
- async runAsyncSetup(builder) {
72
- await this.asyncSetup(builder);
2
+ const SYNC_MODULE_BRAND = Symbol("di:sync-module");
3
+ const ASYNC_MODULE_BRAND = Symbol("di:async-module");
4
+ const SyncModule = { create(name, setup) {
5
+ return {
6
+ name,
7
+ [SYNC_MODULE_BRAND]: true,
8
+ _setup: setup
9
+ };
10
+ } };
11
+ const AsyncModule = { create(name, setup) {
12
+ return {
13
+ name,
14
+ [ASYNC_MODULE_BRAND]: true,
15
+ _setup: setup
16
+ };
17
+ } };
18
+ const Module = {
19
+ create(name, setup) {
20
+ return SyncModule.create(name, setup);
21
+ },
22
+ createAsync(name, setup) {
23
+ return AsyncModule.create(name, setup);
73
24
  }
74
25
  };
26
+ function isSyncModule(m) {
27
+ return m[SYNC_MODULE_BRAND] === true;
28
+ }
75
29
  //#endregion
76
- export { AsyncModule, Module };
30
+ export { AsyncModule, Module, SyncModule, isSyncModule };
@@ -1,69 +1,38 @@
1
+ import { Constructor } from "./constructor-type.mjs";
1
2
  import { Token } from "./token.mjs";
2
- import { Binding, BindingIdentifier, Constructor } from "./binding.mjs";
3
+ import { BindingIdentifier } from "./types.mjs";
4
+ import { Binding } from "./binding.mjs";
3
5
 
4
6
  //#region src/registry.d.ts
5
- /**
6
- * Key used to group {@link Binding} instances in the registry (reference equality for tokens).
7
- */
8
- type RegistryKey = Token<unknown> | Constructor<unknown>;
9
- /**
10
- * Flat, in-memory storage for {@link Binding} entries keyed by {@link RegistryKey}.
11
- * Each key maps to an ordered list of bindings (multi-binding support).
12
- *
13
- * The registry is a "dumb" store — it does not perform selection, scope caching, or
14
- * lifecycle management. Those concerns live in `DependencyResolver` and `ScopeManager`.
15
- *
16
- * Mutation styles:
17
- * - `add` — append-only; never removes existing entries.
18
- * - `replaceById` — swaps a single binding in place by ID; no removal callback.
19
- * - `remove` / `removeById` — delete entries **without** notifying callers; the caller
20
- * is responsible for draining the scope cache before calling these.
21
- * - `replaceKeyLastWins` — replaces all bindings for a key with a single new one **and**
22
- * invokes the `onReplaced` callback for each evicted binding, giving the caller
23
- * (typically the container or scope manager) a chance to run deactivation.
24
- */
25
7
  declare class BindingRegistry {
26
- /**
27
- * Primary index: registry key → ordered binding list.
28
- * Reference equality on the key (i.e. the same {@link Token} or {@link Constructor} object).
29
- */
30
- private readonly bindingsByKey;
31
- /**
32
- * Appends `binding` to the list for `key` (multi-binding: each call adds an entry).
33
- */
34
- add<Value>(key: Token<Value> | Constructor<Value>, binding: Binding<Value>): void;
35
- /**
36
- * Returns all bindings registered for `key`, or `undefined` if none exist.
37
- */
38
- get<Value>(key: Token<Value> | Constructor<Value>): readonly Binding<Value>[] | undefined;
39
- /**
40
- * Removes all bindings for `key`. Does **not** invoke any callback — the caller must
41
- * drain the scope cache (run deactivation) for the affected bindings before calling this.
42
- */
43
- remove(key: RegistryKey): void;
44
- /**
45
- * Returns owned registry rows (does not include parent containers).
46
- */
47
- listEntries(): readonly {
48
- key: RegistryKey;
49
- bindings: readonly Binding<unknown>[];
50
- }[];
51
- /**
52
- * Removes the single binding whose `id` matches, scanning all keys.
53
- * Like {@link remove}, does **not** invoke a removal callback.
54
- */
55
- removeById(id: BindingIdentifier): void;
56
- /**
57
- * Swaps the binding with the given `id` in place, preserving its position in the list.
58
- */
59
- replaceById(id: BindingIdentifier, next: Binding<unknown>): void;
60
- /**
61
- * Replaces all bindings for `key` with a single new binding (module "last-wins" semantics).
62
- * Unlike {@link remove} / {@link removeById}, this method invokes `onReplaced` for every
63
- * evicted binding **before** inserting the replacement, giving the caller (e.g. the
64
- * container's scope manager) a chance to release cached instances and run deactivation hooks.
65
- */
66
- replaceKeyLastWins<Value>(key: Token<Value> | Constructor<Value>, binding: Binding<Value>, onReplaced: (removed: Binding<unknown>) => void): void;
8
+ private readonly _bindings;
9
+ private readonly _byId;
10
+ private readonly _simpleNamed;
11
+ private readonly _fastDefault;
12
+ /** Add or replace binding using slot-aware last-wins. */
13
+ add(binding: Binding): void;
14
+ /** Remove all bindings for a token. Returns removed bindings. */
15
+ removeByToken(t: Token<unknown> | Constructor): Binding[];
16
+ /** Remove a specific binding by ID. Returns the removed binding or undefined. */
17
+ removeById(id: BindingIdentifier): Binding | undefined;
18
+ /** Get all bindings for a token. */
19
+ getAll(t: Token<unknown> | Constructor): readonly Binding[];
20
+ /** Get binding by ID. */
21
+ getById(id: BindingIdentifier): Binding | undefined;
22
+ /** Check if any binding exists for token. */
23
+ has(t: Token<unknown> | Constructor): boolean;
24
+ /** All bindings in the registry. */
25
+ allBindings(): readonly Binding[];
26
+ /** Remove all bindings. Returns all removed. */
27
+ clear(): readonly Binding[];
28
+ getSimpleNamed(token: Token<unknown> | Constructor, name: string): Binding | undefined;
29
+ getFastDefault(token: Token<unknown> | Constructor): Binding | undefined;
30
+ /** Summarize available slot strings for a token (for error messages). */
31
+ availableSlotStrings(t: Token<unknown> | Constructor): string[];
32
+ private _isPurePredicateBinding;
33
+ private _indexSimpleNamedBinding;
34
+ private _deindexSimpleNamedBinding;
35
+ private _refreshFastDefaultForToken;
67
36
  }
68
37
  //#endregion
69
- export { BindingRegistry, RegistryKey };
38
+ export { BindingRegistry };
package/dist/registry.mjs CHANGED
@@ -1,95 +1,144 @@
1
+ import { slotKeyEquals } from "./binding.mjs";
1
2
  //#region src/registry.ts
2
- /**
3
- * Flat, in-memory storage for {@link Binding} entries keyed by {@link RegistryKey}.
4
- * Each key maps to an ordered list of bindings (multi-binding support).
5
- *
6
- * The registry is a "dumb" store — it does not perform selection, scope caching, or
7
- * lifecycle management. Those concerns live in `DependencyResolver` and `ScopeManager`.
8
- *
9
- * Mutation styles:
10
- * - `add` — append-only; never removes existing entries.
11
- * - `replaceById` — swaps a single binding in place by ID; no removal callback.
12
- * - `remove` / `removeById` — delete entries **without** notifying callers; the caller
13
- * is responsible for draining the scope cache before calling these.
14
- * - `replaceKeyLastWins` — replaces all bindings for a key with a single new one **and**
15
- * invokes the `onReplaced` callback for each evicted binding, giving the caller
16
- * (typically the container or scope manager) a chance to run deactivation.
17
- */
18
3
  var BindingRegistry = class {
19
- /**
20
- * Primary index: registry key → ordered binding list.
21
- * Reference equality on the key (i.e. the same {@link Token} or {@link Constructor} object).
22
- */
23
- bindingsByKey = /* @__PURE__ */ new Map();
24
- /**
25
- * Appends `binding` to the list for `key` (multi-binding: each call adds an entry).
26
- */
27
- add(key, binding) {
28
- const registryKey = key;
29
- const nextBinding = binding;
30
- const existing = this.bindingsByKey.get(registryKey);
31
- const merged = existing === void 0 ? [nextBinding] : [...existing, nextBinding];
32
- this.bindingsByKey.set(registryKey, merged);
4
+ _bindings = /* @__PURE__ */ new Map();
5
+ _byId = /* @__PURE__ */ new Map();
6
+ _simpleNamed = /* @__PURE__ */ new Map();
7
+ _fastDefault = /* @__PURE__ */ new Map();
8
+ /** Add or replace binding using slot-aware last-wins. */
9
+ add(binding) {
10
+ const key = binding.token;
11
+ let list = this._bindings.get(key);
12
+ if (list === void 0) {
13
+ list = [];
14
+ this._bindings.set(key, list);
15
+ }
16
+ if (!this._isPurePredicateBinding(binding)) {
17
+ const existingIndex = list.findIndex((b) => !this._isPurePredicateBinding(b) && slotKeyEquals(b.slot, binding.slot));
18
+ if (existingIndex !== -1) {
19
+ const old = list[existingIndex];
20
+ this._byId.delete(old.id);
21
+ list.splice(existingIndex, 1);
22
+ }
23
+ }
24
+ list.push(binding);
25
+ this._byId.set(binding.id, binding);
26
+ this._indexSimpleNamedBinding(key, binding);
27
+ this._refreshFastDefaultForToken(key);
33
28
  }
34
- /**
35
- * Returns all bindings registered for `key`, or `undefined` if none exist.
36
- */
37
- get(key) {
38
- return this.bindingsByKey.get(key);
29
+ /** Remove all bindings for a token. Returns removed bindings. */
30
+ removeByToken(t) {
31
+ const key = t;
32
+ const list = this._bindings.get(key) ?? [];
33
+ this._bindings.delete(key);
34
+ this._simpleNamed.delete(key);
35
+ this._fastDefault.delete(key);
36
+ for (const b of list) this._byId.delete(b.id);
37
+ return list;
39
38
  }
40
- /**
41
- * Removes all bindings for `key`. Does **not** invoke any callback — the caller must
42
- * drain the scope cache (run deactivation) for the affected bindings before calling this.
43
- */
44
- remove(key) {
45
- this.bindingsByKey.delete(key);
39
+ /** Remove a specific binding by ID. Returns the removed binding or undefined. */
40
+ removeById(id) {
41
+ const binding = this._byId.get(id);
42
+ if (binding === void 0) return;
43
+ this._byId.delete(id);
44
+ const key = binding.token;
45
+ const list = this._bindings.get(key);
46
+ if (list !== void 0) {
47
+ const idx = list.findIndex((b) => b.id === id);
48
+ if (idx !== -1) list.splice(idx, 1);
49
+ this._deindexSimpleNamedBinding(key, binding);
50
+ if (list.length === 0) {
51
+ this._bindings.delete(key);
52
+ this._simpleNamed.delete(key);
53
+ this._fastDefault.delete(key);
54
+ } else this._refreshFastDefaultForToken(key);
55
+ }
56
+ return binding;
46
57
  }
47
- /**
48
- * Returns owned registry rows (does not include parent containers).
49
- */
50
- listEntries() {
51
- return [...this.bindingsByKey.entries()].map(([key, bindings]) => ({
52
- key,
53
- bindings
54
- }));
58
+ /** Get all bindings for a token. */
59
+ getAll(t) {
60
+ return this._bindings.get(t) ?? [];
55
61
  }
56
- /**
57
- * Removes the single binding whose `id` matches, scanning all keys.
58
- * Like {@link remove}, does **not** invoke a removal callback.
59
- */
60
- removeById(id) {
61
- for (const [registryKey, list] of [...this.bindingsByKey.entries()]) {
62
- const filtered = list.filter((binding) => binding.id !== id);
63
- if (filtered.length === list.length) continue;
64
- if (filtered.length === 0) this.bindingsByKey.delete(registryKey);
65
- else this.bindingsByKey.set(registryKey, filtered);
62
+ /** Get binding by ID. */
63
+ getById(id) {
64
+ return this._byId.get(id);
65
+ }
66
+ /** Check if any binding exists for token. */
67
+ has(t) {
68
+ const key = t;
69
+ const list = this._bindings.get(key);
70
+ return list !== void 0 && list.length > 0;
71
+ }
72
+ /** All bindings in the registry. */
73
+ allBindings() {
74
+ const result = [];
75
+ for (const list of this._bindings.values()) result.push(...list);
76
+ return result;
77
+ }
78
+ /** Remove all bindings. Returns all removed. */
79
+ clear() {
80
+ const all = this.allBindings();
81
+ this._bindings.clear();
82
+ this._byId.clear();
83
+ this._simpleNamed.clear();
84
+ this._fastDefault.clear();
85
+ return all;
86
+ }
87
+ getSimpleNamed(token, name) {
88
+ return this._simpleNamed.get(token)?.get(name);
89
+ }
90
+ getFastDefault(token) {
91
+ return this._fastDefault.get(token);
92
+ }
93
+ /** Summarize available slot strings for a token (for error messages). */
94
+ availableSlotStrings(t) {
95
+ return (this._bindings.get(t) ?? []).map((b) => {
96
+ const s = b.slot;
97
+ if (s.name === void 0 && s.tags.length === 0) return "default";
98
+ const parts = [];
99
+ if (s.name !== void 0) parts.push(`name:${s.name}`);
100
+ for (const [k, v] of s.tags) parts.push(`tag:${k}=${String(v)}`);
101
+ return parts.join(",");
102
+ });
103
+ }
104
+ _isPurePredicateBinding(binding) {
105
+ const slot = binding.slot;
106
+ const hasPredicate = binding.predicate !== void 0;
107
+ const hasConstraint = slot.name !== void 0 || slot.tags.length > 0;
108
+ return hasPredicate && !hasConstraint;
109
+ }
110
+ _indexSimpleNamedBinding(tokenKeyValue, binding) {
111
+ const slot = binding.slot;
112
+ if (slot.name === void 0 || slot.tags.length > 0) return;
113
+ let byName = this._simpleNamed.get(tokenKeyValue);
114
+ if (byName === void 0) {
115
+ byName = /* @__PURE__ */ new Map();
116
+ this._simpleNamed.set(tokenKeyValue, byName);
66
117
  }
118
+ byName.set(slot.name, binding);
67
119
  }
68
- /**
69
- * Swaps the binding with the given `id` in place, preserving its position in the list.
70
- */
71
- replaceById(id, next) {
72
- for (const [registryKey, list] of this.bindingsByKey.entries()) {
73
- const index = list.findIndex((binding) => binding.id === id);
74
- if (index === -1) continue;
75
- const updated = [...list];
76
- updated[index] = next;
77
- this.bindingsByKey.set(registryKey, updated);
78
- return;
120
+ _deindexSimpleNamedBinding(tokenKeyValue, binding) {
121
+ const slot = binding.slot;
122
+ if (slot.name === void 0 || slot.tags.length > 0) return;
123
+ const byName = this._simpleNamed.get(tokenKeyValue);
124
+ if (byName === void 0) return;
125
+ if (byName.get(slot.name)?.id === binding.id) {
126
+ byName.delete(slot.name);
127
+ if (byName.size === 0) this._simpleNamed.delete(tokenKeyValue);
79
128
  }
80
129
  }
81
- /**
82
- * Replaces all bindings for `key` with a single new binding (module "last-wins" semantics).
83
- * Unlike {@link remove} / {@link removeById}, this method invokes `onReplaced` for every
84
- * evicted binding **before** inserting the replacement, giving the caller (e.g. the
85
- * container's scope manager) a chance to release cached instances and run deactivation hooks.
86
- */
87
- replaceKeyLastWins(key, binding, onReplaced) {
88
- const registryKey = key;
89
- const nextBinding = binding;
90
- const existing = this.bindingsByKey.get(registryKey);
91
- if (existing !== void 0) for (const removed of existing) onReplaced(removed);
92
- this.bindingsByKey.set(registryKey, [nextBinding]);
130
+ _refreshFastDefaultForToken(tokenKeyValue) {
131
+ const list = this._bindings.get(tokenKeyValue);
132
+ if (list === void 0 || list.length !== 1) {
133
+ this._fastDefault.delete(tokenKeyValue);
134
+ return;
135
+ }
136
+ const onlyBinding = list[0];
137
+ if (!(onlyBinding.slot.name === void 0 && onlyBinding.slot.tags.length === 0) || onlyBinding.predicate !== void 0) {
138
+ this._fastDefault.delete(tokenKeyValue);
139
+ return;
140
+ }
141
+ this._fastDefault.set(tokenKeyValue, onlyBinding);
93
142
  }
94
143
  };
95
144
  //#endregion
@@ -0,0 +1,18 @@
1
+ import { ResolveOptions } from "./types.mjs";
2
+ import { SlotKey } from "./binding.mjs";
3
+
4
+ //#region src/resolve-options.d.ts
5
+ /**
6
+ * Builds a {@link ResolveOptions} safe for `exactOptionalPropertyTypes`:
7
+ * omits keys instead of assigning `undefined`.
8
+ */
9
+ declare function injectableSlotToResolveOptions(slot: {
10
+ readonly name?: string;
11
+ readonly tags?: ReadonlyArray<readonly [string, unknown]>;
12
+ }): ResolveOptions | undefined;
13
+ /**
14
+ * Hint from a binding {@link SlotKey} (tags may be empty; omits when nothing to match).
15
+ */
16
+ declare function slotKeyToResolveOptions(slot: SlotKey): ResolveOptions | undefined;
17
+ //#endregion
18
+ export { injectableSlotToResolveOptions, slotKeyToResolveOptions };
@@ -0,0 +1,22 @@
1
+ //#region src/resolve-options.ts
2
+ /**
3
+ * Builds a {@link ResolveOptions} safe for `exactOptionalPropertyTypes`:
4
+ * omits keys instead of assigning `undefined`.
5
+ */
6
+ function injectableSlotToResolveOptions(slot) {
7
+ const options = {};
8
+ if (slot.name !== void 0) options.name = slot.name;
9
+ if (slot.tags !== void 0) options.tags = slot.tags;
10
+ return options.name !== void 0 || options.tags !== void 0 ? options : void 0;
11
+ }
12
+ /**
13
+ * Hint from a binding {@link SlotKey} (tags may be empty; omits when nothing to match).
14
+ */
15
+ function slotKeyToResolveOptions(slot) {
16
+ const options = {};
17
+ if (slot.name !== void 0) options.name = slot.name;
18
+ if (slot.tags.length > 0) options.tags = slot.tags;
19
+ return options.name !== void 0 || options.tags !== void 0 ? options : void 0;
20
+ }
21
+ //#endregion
22
+ export { injectableSlotToResolveOptions, slotKeyToResolveOptions };