@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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @codefast/di
2
2
 
3
+ ## 0.3.14-canary.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`4435cfb`](https://github.com/codefastlabs/codefast/commit/4435cfbf4883d018c29942aa571422bb95f73f97) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance binding key generation and simplify transient binding logic
8
+
9
+ - [`427bff6`](https://github.com/codefastlabs/codefast/commit/427bff64196ba62ccfcf1e893d93714875b7b42e) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): update predicates and improve documentation
10
+
11
+ - [`408a9ad`](https://github.com/codefastlabs/codefast/commit/408a9ad5903eb7f1f15ebc576017d2122a18722f) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): improve binding resolution logic in DefaultContainer and BindingRegistry
12
+
13
+ - [`4957e0e`](https://github.com/codefastlabs/codefast/commit/4957e0e8b4b2428447ef11380397b03deef0b092) Thanks [@thevuong](https://github.com/thevuong)! - fix(vitest): update test file patterns to use .test extension
14
+
15
+ - [`4601be2`](https://github.com/codefastlabs/codefast/commit/4601be25746e628c7d74cebaa9ee362e80301a19) Thanks [@thevuong](https://github.com/thevuong)! - feat(di): implement fast resolution mechanism in DefaultContainer
16
+
17
+ - [`568f370`](https://github.com/codefastlabs/codefast/commit/568f370143b5951fa018472ba6e882d8d599f5e7) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance transient binding handling in DependencyResolver
18
+
19
+ - [`ea57eab`](https://github.com/codefastlabs/codefast/commit/ea57eab321f4e57361520de3d6356abf900acbf8) Thanks [@thevuong](https://github.com/thevuong)! - refactor(benchmarks): enhance binding resolution and improve transient binding handling
20
+
21
+ ## 0.3.14-canary.1
22
+
23
+ ### Patch Changes
24
+
25
+ - [`2097bf6`](https://github.com/codefastlabs/codefast/commit/2097bf6c81639506c4c7f3f8a9a0f72bdb49ea49) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): update generateDependencyGraph method for improved clarity and functionality
26
+
27
+ - [`0720553`](https://github.com/codefastlabs/codefast/commit/0720553f01bfae88725c1688efaf608e2cf45493) Thanks [@thevuong](https://github.com/thevuong)! - feat(di): add graph adapters for visualization formats
28
+
29
+ - [`ab92dc7`](https://github.com/codefastlabs/codefast/commit/ab92dc7e5d864727bb5dcb1f2a3d08660c4112e8) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance type definitions and documentation for clarity
30
+
3
31
  ## 0.3.14-canary.0
4
32
 
5
33
  ### Patch Changes
package/README.md CHANGED
@@ -93,32 +93,42 @@ yarn add @codefast/di
93
93
  ## Quick Start
94
94
 
95
95
  ```typescript
96
- import { Container, inject, injectable, token } from "@codefast/di";
96
+ import { Container, injectable, token } from "@codefast/di";
97
97
 
98
98
  interface Logger {
99
- log(message: string): void;
99
+ info(message: string): void;
100
100
  }
101
101
 
102
102
  const LoggerToken = token<Logger>("Logger");
103
103
 
104
104
  @injectable([LoggerToken])
105
- class AppService {
105
+ class CheckoutService {
106
106
  constructor(private readonly logger: Logger) {}
107
107
 
108
- run(): void {
109
- this.logger.log("App started");
108
+ complete(orderId: string): void {
109
+ this.logger.info(`Order ${orderId} completed`);
110
110
  }
111
111
  }
112
112
 
113
113
  const container = Container.create();
114
114
 
115
- container.bind(LoggerToken).toConstantValue({ log: console.log });
116
- container.bind(AppService).toSelf().singleton();
115
+ // Production wiring
116
+ container.bind(LoggerToken).toConstantValue({
117
+ info: (message) => console.log(`[prod] ${message}`),
118
+ });
119
+ container.bind(CheckoutService).toSelf();
120
+
121
+ container.resolve(CheckoutService).complete("ORD-1001");
117
122
 
118
- container.resolve(AppService).run(); // "App started"
123
+ // Same service, test wiring
124
+ container.rebind(LoggerToken).toConstantValue({
125
+ info: (message) => console.log(`[test] ${message}`),
126
+ });
127
+
128
+ container.resolve(CheckoutService).complete("ORD-1002");
119
129
  ```
120
130
 
121
- The `@injectable([LoggerToken])` call lists constructor dependencies in order. Wrap an entry with `inject(token, options)` or `optional(token, options)` when you need a named/tagged hint or optional semantics.
131
+ `@injectable([...])` lists constructor dependencies in order. The key idea: business classes (`CheckoutService`) stay unchanged while behavior swaps by rebinding infrastructure (`LoggerToken`) per environment.
122
132
 
123
133
  ---
124
134
 
@@ -217,8 +227,8 @@ Multiple bindings can share the same token. A constraint picks the right one at
217
227
  **Named**
218
228
 
219
229
  ```typescript
220
- container.bind(LoggerToken).whenNamed("file").toConstantValue(fileLogger);
221
- container.bind(LoggerToken).whenNamed("console").toConstantValue(consoleLogger);
230
+ container.bind(LoggerToken).toConstantValue(fileLogger).whenNamed("file");
231
+ container.bind(LoggerToken).toConstantValue(consoleLogger).whenNamed("console");
222
232
 
223
233
  container.resolve(LoggerToken, { name: "file" });
224
234
  ```
@@ -226,8 +236,8 @@ container.resolve(LoggerToken, { name: "file" });
226
236
  **Tagged** — the hint is a tuple `[tag, value]`:
227
237
 
228
238
  ```typescript
229
- container.bind(StorageToken).whenTagged("provider", "s3").to(S3Storage);
230
- container.bind(StorageToken).whenTagged("provider", "local").to(LocalStorage);
239
+ container.bind(StorageToken).to(S3Storage).whenTagged("provider", "s3");
240
+ container.bind(StorageToken).to(LocalStorage).whenTagged("provider", "local");
231
241
 
232
242
  container.resolve(StorageToken, { tag: ["provider", "s3"] });
233
243
  ```
@@ -235,9 +245,9 @@ container.resolve(StorageToken, { tag: ["provider", "s3"] });
235
245
  **Predicate** — inspect the full resolution graph:
236
246
 
237
247
  ```typescript
238
- import { whenAnyAncestorIs, whenParentIs, whenTargetTagged } from "@codefast/di";
248
+ import { whenAnyAncestorIs, whenParentIs, whenParentTagged } from "@codefast/di/constraints";
239
249
 
240
- container.bind(LoggerToken).when(whenParentIs(DiagnosticsService)).toConstantValue(verboseLogger);
250
+ container.bind(LoggerToken).toConstantValue(verboseLogger).when(whenParentIs(DiagnosticsService));
241
251
  ```
242
252
 
243
253
  Built-in predicates:
@@ -246,7 +256,7 @@ Built-in predicates:
246
256
  | ------------------------------ | --------------------------------------------------------------------------- |
247
257
  | `whenParentIs(key)` | the direct parent binding was registered for `key`. |
248
258
  | `whenAnyAncestorIs(key)` | any ancestor binding on the materialization stack was registered for `key`. |
249
- | `whenTargetTagged(tag, value)` | the immediate parent binding carries `tag` with `value`. |
259
+ | `whenParentTagged(tag, value)` | the immediate parent binding carries `tag` with `value`. |
250
260
 
251
261
  For anything else, pass a custom `(ctx: ConstraintContext) => boolean` to `.when(predicate)`.
252
262
 
@@ -435,12 +445,21 @@ Control the environment heuristic via `NODE_ENV` — see `isDevelopmentOrTestEnv
435
445
  ### Introspection
436
446
 
437
447
  ```typescript
448
+ import { toCytoscapeGraph } from "@codefast/di/graph-adapters/cytoscape";
449
+ import { toDotGraph } from "@codefast/di/graph-adapters/dot";
450
+ import { toReactFlowGraph } from "@codefast/di/graph-adapters/reactflow";
451
+
438
452
  const snapshot = container.inspect();
439
- const dot = container.generateDependencyGraph({ hideInternals: true });
440
- const json = container.generateDependencyGraph({ format: "json" });
453
+ const json = container.generateDependencyGraph({ hideInternals: true });
454
+ const dot = toDotGraph(json);
455
+
456
+ // Adapters are pure converters from the canonical JSON graph.
457
+ const cytoscape = toCytoscapeGraph(json);
458
+ const reactflow = toReactFlowGraph(json);
441
459
  ```
442
460
 
443
- `generateDependencyGraph` returns a Graphviz DOT string by default, or a typed `ContainerGraphJson` when `format: "json"` is passed.
461
+ `generateDependencyGraph` always returns the canonical typed `ContainerGraphJson` (`nodes` + `edges`).
462
+ Keep visualization adapters (`toDotGraph`, `toCytoscapeGraph`, `toReactFlowGraph`, or your own converters) outside container/inspector core APIs and import them from direct subpaths under `@codefast/di/graph-adapters/*`.
444
463
 
445
464
  ### Disposal
446
465
 
@@ -464,22 +483,21 @@ Sync `using` is intentionally rejected: calling `Symbol.dispose` throws. Use `aw
464
483
 
465
484
  Modules bundle related bindings into reusable units. A module holds no runtime state and can be loaded into any number of containers.
466
485
 
467
- Inside module setup, binding semantics depend on whether a disambiguator is present before `to*()`:
486
+ Use the same fluent order everywhere (including inside modules): `bind(token).to*(…).when*(…).scope()…`.
468
487
 
469
- - `api.bind(Token).to*(...)` uses last-wins for that token.
470
- - `api.bind(Token).whenNamed(...)` / `.whenTagged(...)` / `.when(...)` **before** `to*()` appends another multi-binding entry.
471
- - `api.bind(Token).to*(...).whenNamed(...)` updates only that single built binding; it does not switch later lines into append mode.
488
+ - Register **multiple** implementations for one token with separate chains, e.g. `api.bind(T).to(A).whenNamed("a")` and `api.bind(T).to(B).whenNamed("b")`.
489
+ - **Last-wins** applies per slot (default vs named vs tag-set), as in the main container API.
472
490
 
473
491
  ```typescript
474
492
  import { Container, Module } from "@codefast/di";
475
493
 
476
- const InfraModule = Module.create("Infra", (api) => {
494
+ const InfrastructureModule = Module.create("Infra", (api) => {
477
495
  api.bind(LoggerToken).toConstantValue(console);
478
496
  api.bind(ConfigToken).toConstantValue(loadConfig());
479
497
  });
480
498
 
481
499
  const AppModule = Module.create("App", (api) => {
482
- api.import(InfraModule);
500
+ api.import(InfrastructureModule);
483
501
  api.bind(UserRepository).toSelf().singleton();
484
502
  api.bind(UserServiceToken).to(UserService).transient();
485
503
  });
@@ -501,7 +519,7 @@ const container = await Container.fromModulesAsync(DbModule, AppModule);
501
519
  Load and unload on an existing container:
502
520
 
503
521
  ```typescript
504
- container.load(InfraModule, AppModule);
522
+ container.load(InfrastructureModule, AppModule);
505
523
  await container.loadAsync(DbModule);
506
524
 
507
525
  container.unload(AppModule);
@@ -560,17 +578,24 @@ The root entry re-exports the full public API. Subpath exports are provided for
560
578
  | `@codefast/di/decorators/inject` | `inject`, `optional`, `injectAll`, `isInjectionDescriptor` |
561
579
  | `@codefast/di/decorators/injectable` | `@injectable`, `getAutoRegistered` |
562
580
  | `@codefast/di/decorators/lifecycle-decorators` | `@postConstruct`, `@preDestroy` |
563
- | `@codefast/di/constraints` | `whenParentIs`, `whenAnyAncestorIs`, `whenTargetTagged` |
581
+ | `@codefast/di/constraints` | `whenParentIs`, `whenAnyAncestorIs`, `whenParentTagged` |
564
582
  | `@codefast/di/registry` | `BindingRegistry` |
565
583
  | `@codefast/di/resolver` | `DependencyResolver` internals |
566
584
  | `@codefast/di/scope` | `ScopeManager` |
567
- | `@codefast/di/scope-validation` | `validateScopeRules` |
568
585
  | `@codefast/di/lifecycle` | Activation/deactivation runners |
569
586
  | `@codefast/di/dependency-graph` | Dependency-edge collection helpers |
587
+ | `@codefast/di/graph-adapters/cytoscape` | Cytoscape adapter for `ContainerGraphJson` |
588
+ | `@codefast/di/graph-adapters/dot` | DOT adapter for `ContainerGraphJson` |
589
+ | `@codefast/di/graph-adapters/reactflow` | React Flow adapter for `ContainerGraphJson` |
590
+ | `@codefast/di/graph-adapters/types` | Shared graph adapter type definitions |
570
591
  | `@codefast/di/inspector` | `ContainerInspector`, snapshot + graph types |
571
592
  | `@codefast/di/errors` | Full `DiError` hierarchy |
572
593
  | `@codefast/di/environment` | `isDevelopmentOrTestEnvironment`, `isProductionEnvironment` |
573
- | `@codefast/di/metadata/*` | Metadata keys, types, readers, parameter registry |
594
+ | `@codefast/di/metadata/metadata-keys` | Metadata symbol keys |
595
+ | `@codefast/di/metadata/metadata-types` | Metadata type definitions |
596
+ | `@codefast/di/metadata/param-registry` | Constructor parameter metadata registry |
597
+ | `@codefast/di/metadata/symbol-metadata-reader` | Symbol metadata reader utilities |
598
+ | `@codefast/di/package.json` | Package metadata |
574
599
 
575
600
  See `package.json → exports` for the authoritative list.
576
601
 
@@ -0,0 +1,11 @@
1
+ import { BindingScope } from "./types.mjs";
2
+ import { Binding } from "./binding.mjs";
3
+
4
+ //#region src/binding-scope.d.ts
5
+ /**
6
+ * Runtime scope used for validation and introspection. Alias bindings are treated as transient
7
+ * because they defer scope to the aliased target at resolve time.
8
+ */
9
+ declare function effectiveBindingScope(binding: Binding): BindingScope;
10
+ //#endregion
11
+ export { effectiveBindingScope };
@@ -0,0 +1,19 @@
1
+ //#region src/binding-scope.ts
2
+ /**
3
+ * Runtime scope used for validation and introspection. Alias bindings are treated as transient
4
+ * because they defer scope to the aliased target at resolve time.
5
+ */
6
+ function effectiveBindingScope(binding) {
7
+ switch (binding.kind) {
8
+ case "alias": return "transient";
9
+ case "class":
10
+ case "constant":
11
+ case "dynamic":
12
+ case "dynamic-async":
13
+ case "resolved":
14
+ case "resolved-async": return binding.scope;
15
+ default: return binding;
16
+ }
17
+ }
18
+ //#endregion
19
+ export { effectiveBindingScope };
@@ -1,33 +1,15 @@
1
- import { Token } from "./token.mjs";
2
- import { RegistryKey } from "./registry.mjs";
3
- import { Binding, ConstraintContext, Constructor, ResolveHint } from "./binding.mjs";
1
+ import { ConstraintContext, ResolveOptions } from "./types.mjs";
2
+ import { Binding } from "./binding.mjs";
4
3
 
5
4
  //#region src/binding-select.d.ts
6
5
  /**
7
- * Returns a human-readable label for a token or constructor (used in error messages and graph output).
6
+ * Select a single candidate from a list of bindings using slot matching + predicates.
7
+ * Returns undefined if no match, throws AmbiguousBindingError if multiple match.
8
8
  */
9
- declare function registryKeyLabel(key: Token<unknown> | Constructor<unknown>): string;
9
+ declare function selectBinding(bindings: readonly Binding[], hint: ResolveOptions | undefined, ctx: ConstraintContext, tName: string): Binding | undefined;
10
10
  /**
11
- * Narrows a binding list by applying name/tag hints and `when()` constraint predicates.
12
- *
13
- * Filtering order: name filter → tag filter → constraint predicate. Bindings without a
14
- * constraint predicate always pass the constraint stage. Returns the surviving candidates
15
- * (may be empty).
11
+ * Select all candidates matching hint + predicates.
16
12
  */
17
- declare function filterMatchingBindings(bindings: readonly Binding<unknown>[], hint: ResolveHint | undefined, constraintCtx: ConstraintContext | undefined): readonly Binding<unknown>[];
18
- /**
19
- * Selects exactly one binding from the provided list, applying hint and constraint filtering.
20
- *
21
- * @throws {@link TokenNotBoundError} — `bindings` list is empty, or no candidate survives
22
- * filtering without a name/tag hint.
23
- * @throws {@link NoMatchingBindingError} — a name/tag hint was provided but no candidate matched.
24
- * @throws {@link InternalError} — multiple candidates survive filtering (ambiguous binding).
25
- */
26
- declare function selectBindingForRegistry(bindings: readonly Binding<unknown>[], hint: ResolveHint | undefined, tokenLabel: string, pathLabels: readonly string[], constraintCtx: ConstraintContext | undefined): Binding<unknown>;
27
- /**
28
- * Convenience wrapper: looks up bindings for `key` and selects the default (no-hint,
29
- * no-constraint) binding. Throws {@link TokenNotBoundError} if the key is unregistered.
30
- */
31
- declare function selectDefaultBindingForKey(lookup: (key: RegistryKey) => readonly Binding<unknown>[] | undefined, key: RegistryKey, pathPrefix: readonly string[]): Binding<unknown>;
13
+ declare function selectAllBindings(bindings: readonly Binding[], hint: ResolveOptions | undefined, ctx: ConstraintContext): Binding[];
32
14
  //#endregion
33
- export { filterMatchingBindings, registryKeyLabel, selectBindingForRegistry, selectDefaultBindingForKey };
15
+ export { selectAllBindings, selectBinding };
@@ -1,61 +1,68 @@
1
- import { InternalError, NoMatchingBindingError, TokenNotBoundError } from "./errors.mjs";
1
+ import { AmbiguousBindingError } from "./errors.mjs";
2
2
  //#region src/binding-select.ts
3
3
  /**
4
- * Returns a human-readable label for a token or constructor (used in error messages and graph output).
4
+ * Select a single candidate from a list of bindings using slot matching + predicates.
5
+ * Returns undefined if no match, throws AmbiguousBindingError if multiple match.
5
6
  */
6
- function registryKeyLabel(key) {
7
- if (typeof key === "function") return key.name.length > 0 ? key.name : "(anonymous class)";
8
- return key.name.trim().length > 0 ? key.name : "(anonymous token)";
7
+ function selectBinding(bindings, hint, ctx, tName) {
8
+ const candidates = filterBindings(bindings, hint, ctx);
9
+ if (candidates.length === 0) return;
10
+ if (candidates.length === 1) return candidates[0];
11
+ throw new AmbiguousBindingError(tName, candidates.map((c) => c.id));
9
12
  }
10
13
  /**
11
- * Narrows a binding list by applying name/tag hints and `when()` constraint predicates.
12
- *
13
- * Filtering order: name filter → tag filter → constraint predicate. Bindings without a
14
- * constraint predicate always pass the constraint stage. Returns the surviving candidates
15
- * (may be empty).
14
+ * Select all candidates matching hint + predicates.
16
15
  */
17
- function filterMatchingBindings(bindings, hint, constraintCtx) {
18
- let candidates = bindings;
19
- if (hint?.name !== void 0) candidates = candidates.filter((binding) => binding.bindingName === hint.name);
20
- if (hint?.tag !== void 0) {
21
- const [tagKey, tagValue] = hint.tag;
22
- candidates = candidates.filter((binding) => Object.is(binding.tags.get(tagKey), tagValue));
23
- }
24
- if (constraintCtx !== void 0) candidates = candidates.filter((binding) => binding.constraint === void 0 || binding.constraint(constraintCtx));
25
- return candidates;
16
+ function selectAllBindings(bindings, hint, ctx) {
17
+ return filterBindings(bindings, hint, ctx, "all");
26
18
  }
27
- /**
28
- * Selects exactly one binding from the provided list, applying hint and constraint filtering.
29
- *
30
- * @throws {@link TokenNotBoundError} — `bindings` list is empty, or no candidate survives
31
- * filtering without a name/tag hint.
32
- * @throws {@link NoMatchingBindingError} — a name/tag hint was provided but no candidate matched.
33
- * @throws {@link InternalError} — multiple candidates survive filtering (ambiguous binding).
34
- */
35
- function selectBindingForRegistry(bindings, hint, tokenLabel, pathLabels, constraintCtx) {
36
- if (bindings.length === 0) throw new TokenNotBoundError(tokenLabel, [...pathLabels]);
37
- const candidates = filterMatchingBindings(bindings, hint, constraintCtx);
38
- if (candidates.length === 1) {
39
- const [only] = candidates;
40
- if (only === void 0) throw new InternalError(`Internal: expected binding candidate for "${tokenLabel}" (resolution path: ${pathLabels.join(" -> ")})`);
41
- return only;
19
+ function filterBindings(bindings, hint, ctx, mode = "single") {
20
+ if (hint === void 0) {
21
+ const resultWithoutHint = [];
22
+ if (mode === "all") {
23
+ for (const binding of bindings) if (matchesPredicate(binding, ctx)) resultWithoutHint.push(binding);
24
+ } else for (const binding of bindings) {
25
+ const slot = binding.slot;
26
+ if (slot.name === void 0 && slot.tags.length === 0 && matchesPredicate(binding, ctx)) resultWithoutHint.push(binding);
27
+ }
28
+ return resultWithoutHint;
42
29
  }
43
- if (candidates.length === 0) {
44
- if (hint !== void 0 && (hint.name !== void 0 || hint.tag !== void 0)) throw new NoMatchingBindingError(tokenLabel, hint, [...pathLabels]);
45
- throw new TokenNotBoundError(tokenLabel, [...pathLabels]);
30
+ const result = [];
31
+ for (const binding of bindings) if ((mode === "all" ? matchesSlotForResolveAll(binding, hint) : matchesSlot(binding, hint)) && matchesPredicate(binding, ctx)) result.push(binding);
32
+ return result;
33
+ }
34
+ function matchesSlotForResolveAll(binding, hint) {
35
+ if (!(hint !== void 0 && (hint.name !== void 0 || hint.tags !== void 0 && hint.tags.length > 0 || hint.tag !== void 0))) return true;
36
+ return matchesSlot(binding, hint);
37
+ }
38
+ function matchesSlot(binding, hint) {
39
+ const slot = binding.slot;
40
+ const hintName = hint?.name;
41
+ const hintTags = hint?.tags;
42
+ const singleHintTag = hint?.tag;
43
+ const hasHintTags = (hintTags?.length ?? 0) > 0 || singleHintTag !== void 0;
44
+ if (slot.name !== void 0) {
45
+ if (hintName === void 0) return false;
46
+ if (slot.name !== hintName) return false;
47
+ } else if (hintName !== void 0) return false;
48
+ if (slot.tags.length > 0) {
49
+ if (!hasHintTags) return false;
50
+ for (const [tagKey, tagValue] of slot.tags) if (!matchHintTag(tagKey, tagValue, hintTags, singleHintTag)) return false;
51
+ } else if (hasHintTags) return false;
52
+ return true;
53
+ }
54
+ function matchHintTag(tagKey, tagValue, hintTags, singleHintTag) {
55
+ if (singleHintTag !== void 0 && singleHintTag[0] === tagKey && Object.is(singleHintTag[1], tagValue)) return true;
56
+ if (hintTags === void 0 || hintTags.length === 0) return false;
57
+ for (let index = 0; index < hintTags.length; index += 1) {
58
+ const hintTag = hintTags[index];
59
+ if (hintTag[0] === tagKey && Object.is(hintTag[1], tagValue)) return true;
46
60
  }
47
- throw new InternalError(`Ambiguous binding for "${tokenLabel}": ${String(candidates.length)} candidates matched after applying ResolveHint (resolution path: ${pathLabels.join(" -> ")})`);
61
+ return false;
48
62
  }
49
- /**
50
- * Convenience wrapper: looks up bindings for `key` and selects the default (no-hint,
51
- * no-constraint) binding. Throws {@link TokenNotBoundError} if the key is unregistered.
52
- */
53
- function selectDefaultBindingForKey(lookup, key, pathPrefix) {
54
- const label = registryKeyLabel(key);
55
- const nextPath = [...pathPrefix, label];
56
- const bindings = lookup(key);
57
- if (bindings === void 0 || bindings.length === 0) throw new TokenNotBoundError(label, nextPath);
58
- return selectBindingForRegistry(bindings, void 0, label, nextPath, void 0);
63
+ function matchesPredicate(binding, ctx) {
64
+ if (binding.predicate === void 0) return true;
65
+ return binding.predicate(ctx);
59
66
  }
60
67
  //#endregion
61
- export { filterMatchingBindings, registryKeyLabel, selectBindingForRegistry, selectDefaultBindingForKey };
68
+ export { selectAllBindings, selectBinding };