@codefast/di 0.3.14-canary.1 → 0.3.14

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 (65) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +42 -26
  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 -335
  8. package/dist/binding.mjs +19 -332
  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 +43 -122
  14. package/dist/container.mjs +667 -482
  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 +70 -189
  26. package/dist/errors.mjs +92 -219
  27. package/dist/graph-adapters/cytoscape.d.mts +21 -7
  28. package/dist/graph-adapters/cytoscape.mjs +18 -35
  29. package/dist/graph-adapters/dot.d.mts +1 -4
  30. package/dist/graph-adapters/dot.mjs +6 -86
  31. package/dist/graph-adapters/reactflow.d.mts +26 -7
  32. package/dist/graph-adapters/reactflow.mjs +21 -72
  33. package/dist/graph-adapters/types.d.mts +2 -91
  34. package/dist/index.d.mts +16 -8
  35. package/dist/index.mjs +8 -5
  36. package/dist/inspector.d.mts +35 -74
  37. package/dist/inspector.mjs +61 -88
  38. package/dist/lifecycle.d.mts +20 -53
  39. package/dist/lifecycle.mjs +129 -99
  40. package/dist/metadata/metadata-keys.d.mts +9 -26
  41. package/dist/metadata/metadata-keys.mjs +7 -28
  42. package/dist/metadata/metadata-reader-token.d.mts +7 -0
  43. package/dist/metadata/metadata-reader-token.mjs +5 -0
  44. package/dist/metadata/metadata-types.d.mts +26 -75
  45. package/dist/metadata/symbol-metadata-reader.d.mts +10 -26
  46. package/dist/metadata/symbol-metadata-reader.mjs +32 -45
  47. package/dist/module.d.mts +30 -96
  48. package/dist/module.mjs +26 -72
  49. package/dist/registry.d.mts +32 -63
  50. package/dist/registry.mjs +131 -82
  51. package/dist/resolve-options.d.mts +18 -0
  52. package/dist/resolve-options.mjs +22 -0
  53. package/dist/resolver.d.mts +67 -190
  54. package/dist/resolver.mjs +715 -424
  55. package/dist/scope.d.mts +19 -106
  56. package/dist/scope.mjs +37 -196
  57. package/dist/token.d.mts +8 -22
  58. package/dist/token.mjs +9 -11
  59. package/dist/types.d.mts +48 -0
  60. package/dist/types.mjs +1 -0
  61. package/package.json +36 -14
  62. package/dist/metadata/param-registry.d.mts +0 -16
  63. package/dist/metadata/param-registry.mjs +0 -31
  64. package/dist/scope-validation.d.mts +0 -21
  65. package/dist/scope-validation.mjs +0 -35
package/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # @codefast/di
2
2
 
3
+ ## 0.3.14
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
+ - [`5d39880`](https://github.com/codefastlabs/codefast/commit/5d398804b061eac5102730ae4121d0f6f0197590) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): update injectable decorator usage for consistency
10
+
11
+ - [`427bff6`](https://github.com/codefastlabs/codefast/commit/427bff64196ba62ccfcf1e893d93714875b7b42e) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): update predicates and improve documentation
12
+
13
+ - [`2097bf6`](https://github.com/codefastlabs/codefast/commit/2097bf6c81639506c4c7f3f8a9a0f72bdb49ea49) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): update generateDependencyGraph method for improved clarity and functionality
14
+
15
+ - [`787a8fc`](https://github.com/codefastlabs/codefast/commit/787a8fc818295bb7f7e8455ff0a7f993d7e0aab5) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): improve type safety and readability in binding selection and error handling
16
+
17
+ - [`408a9ad`](https://github.com/codefastlabs/codefast/commit/408a9ad5903eb7f1f15ebc576017d2122a18722f) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): improve binding resolution logic in DefaultContainer and BindingRegistry
18
+
19
+ - [`0720553`](https://github.com/codefastlabs/codefast/commit/0720553f01bfae88725c1688efaf608e2cf45493) Thanks [@thevuong](https://github.com/thevuong)! - feat(di): add graph adapters for visualization formats
20
+
21
+ - [`3457958`](https://github.com/codefastlabs/codefast/commit/3457958f3a90c62149b14161db92b9d763e90fd2) Thanks [@thevuong](https://github.com/thevuong)! - docs(di): add TypeDoc configuration and enhance comments for clarity
22
+
23
+ - [`4957e0e`](https://github.com/codefastlabs/codefast/commit/4957e0e8b4b2428447ef11380397b03deef0b092) Thanks [@thevuong](https://github.com/thevuong)! - fix(vitest): update test file patterns to use .test extension
24
+
25
+ - [`a78ae1d`](https://github.com/codefastlabs/codefast/commit/a78ae1d57f4fea0c7d9cfeb345e67d9fc040b0e0) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance clarity and consistency in SPEC.md and examples
26
+
27
+ - [`4601be2`](https://github.com/codefastlabs/codefast/commit/4601be25746e628c7d74cebaa9ee362e80301a19) Thanks [@thevuong](https://github.com/thevuong)! - feat(di): implement fast resolution mechanism in DefaultContainer
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
+
31
+ - [`680b28d`](https://github.com/codefastlabs/codefast/commit/680b28d6fca0c5ec754967b02a994231dca0fa9f) Thanks [@thevuong](https://github.com/thevuong)! - docs(di): update README.md for clarity and structure
32
+
33
+ - [`b44597c`](https://github.com/codefastlabs/codefast/commit/b44597c9ec3a121a707aa269d7f68550ae1da72a) Thanks [@thevuong](https://github.com/thevuong)! - docs: enhance type annotations and comments for clarity across multiple files
34
+
35
+ - [`8109f4e`](https://github.com/codefastlabs/codefast/commit/8109f4e1f8186b91c296d25d640594b43493cdef) Thanks [@thevuong](https://github.com/thevuong)! - docs: update README.md files across packages for consistency and clarity
36
+
37
+ - [`568f370`](https://github.com/codefastlabs/codefast/commit/568f370143b5951fa018472ba6e882d8d599f5e7) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance transient binding handling in DependencyResolver
38
+
39
+ - [`0803cd0`](https://github.com/codefastlabs/codefast/commit/0803cd04c5f12848061451d664b74fd5552eb2fb) Thanks [@thevuong](https://github.com/thevuong)! - feat(di): introduce injectAll for multi-binding resolution
40
+
41
+ - [`ea57eab`](https://github.com/codefastlabs/codefast/commit/ea57eab321f4e57361520de3d6356abf900acbf8) Thanks [@thevuong](https://github.com/thevuong)! - refactor(benchmarks): enhance binding resolution and improve transient binding handling
42
+
43
+ ## 0.3.14-canary.2
44
+
45
+ ### Patch Changes
46
+
47
+ - [`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
48
+
49
+ - [`427bff6`](https://github.com/codefastlabs/codefast/commit/427bff64196ba62ccfcf1e893d93714875b7b42e) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): update predicates and improve documentation
50
+
51
+ - [`408a9ad`](https://github.com/codefastlabs/codefast/commit/408a9ad5903eb7f1f15ebc576017d2122a18722f) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): improve binding resolution logic in DefaultContainer and BindingRegistry
52
+
53
+ - [`4957e0e`](https://github.com/codefastlabs/codefast/commit/4957e0e8b4b2428447ef11380397b03deef0b092) Thanks [@thevuong](https://github.com/thevuong)! - fix(vitest): update test file patterns to use .test extension
54
+
55
+ - [`4601be2`](https://github.com/codefastlabs/codefast/commit/4601be25746e628c7d74cebaa9ee362e80301a19) Thanks [@thevuong](https://github.com/thevuong)! - feat(di): implement fast resolution mechanism in DefaultContainer
56
+
57
+ - [`568f370`](https://github.com/codefastlabs/codefast/commit/568f370143b5951fa018472ba6e882d8d599f5e7) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance transient binding handling in DependencyResolver
58
+
59
+ - [`ea57eab`](https://github.com/codefastlabs/codefast/commit/ea57eab321f4e57361520de3d6356abf900acbf8) Thanks [@thevuong](https://github.com/thevuong)! - refactor(benchmarks): enhance binding resolution and improve transient binding handling
60
+
3
61
  ## 0.3.14-canary.1
4
62
 
5
63
  ### 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");
122
+
123
+ // Same service, test wiring
124
+ container.rebind(LoggerToken).toConstantValue({
125
+ info: (message) => console.log(`[test] ${message}`),
126
+ });
117
127
 
118
- container.resolve(AppService).run(); // "App started"
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
 
@@ -473,22 +483,21 @@ Sync `using` is intentionally rejected: calling `Symbol.dispose` throws. Use `aw
473
483
 
474
484
  Modules bundle related bindings into reusable units. A module holds no runtime state and can be loaded into any number of containers.
475
485
 
476
- 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()…`.
477
487
 
478
- - `api.bind(Token).to*(...)` uses last-wins for that token.
479
- - `api.bind(Token).whenNamed(...)` / `.whenTagged(...)` / `.when(...)` **before** `to*()` appends another multi-binding entry.
480
- - `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.
481
490
 
482
491
  ```typescript
483
492
  import { Container, Module } from "@codefast/di";
484
493
 
485
- const InfraModule = Module.create("Infra", (api) => {
494
+ const InfrastructureModule = Module.create("Infra", (api) => {
486
495
  api.bind(LoggerToken).toConstantValue(console);
487
496
  api.bind(ConfigToken).toConstantValue(loadConfig());
488
497
  });
489
498
 
490
499
  const AppModule = Module.create("App", (api) => {
491
- api.import(InfraModule);
500
+ api.import(InfrastructureModule);
492
501
  api.bind(UserRepository).toSelf().singleton();
493
502
  api.bind(UserServiceToken).to(UserService).transient();
494
503
  });
@@ -510,7 +519,7 @@ const container = await Container.fromModulesAsync(DbModule, AppModule);
510
519
  Load and unload on an existing container:
511
520
 
512
521
  ```typescript
513
- container.load(InfraModule, AppModule);
522
+ container.load(InfrastructureModule, AppModule);
514
523
  await container.loadAsync(DbModule);
515
524
 
516
525
  container.unload(AppModule);
@@ -569,17 +578,24 @@ The root entry re-exports the full public API. Subpath exports are provided for
569
578
  | `@codefast/di/decorators/inject` | `inject`, `optional`, `injectAll`, `isInjectionDescriptor` |
570
579
  | `@codefast/di/decorators/injectable` | `@injectable`, `getAutoRegistered` |
571
580
  | `@codefast/di/decorators/lifecycle-decorators` | `@postConstruct`, `@preDestroy` |
572
- | `@codefast/di/constraints` | `whenParentIs`, `whenAnyAncestorIs`, `whenTargetTagged` |
581
+ | `@codefast/di/constraints` | `whenParentIs`, `whenAnyAncestorIs`, `whenParentTagged` |
573
582
  | `@codefast/di/registry` | `BindingRegistry` |
574
583
  | `@codefast/di/resolver` | `DependencyResolver` internals |
575
584
  | `@codefast/di/scope` | `ScopeManager` |
576
- | `@codefast/di/scope-validation` | `validateScopeRules` |
577
585
  | `@codefast/di/lifecycle` | Activation/deactivation runners |
578
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 |
579
591
  | `@codefast/di/inspector` | `ContainerInspector`, snapshot + graph types |
580
592
  | `@codefast/di/errors` | Full `DiError` hierarchy |
581
593
  | `@codefast/di/environment` | `isDevelopmentOrTestEnvironment`, `isProductionEnvironment` |
582
- | `@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 |
583
599
 
584
600
  See `package.json → exports` for the authoritative list.
585
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 };