@codefast/di 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @codefast/di
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#847](https://github.com/codefastlabs/codefast/pull/847) [`d0b794c`](https://github.com/codefastlabs/codefast/commit/d0b794c047344c4040b5641202c259d72a0ea48c) Thanks [@thevuong](https://github.com/thevuong)! - `Token` gains a second type parameter, `Names extends string = string`, declaring the slot names its bindings may use:
8
+ `token<Logger, "console" | "file">("Logger")`. `whenNamed`, and `name` in `ResolveOptions` and `InjectOptions`, narrow
9
+ to it, so a misspelt name is a compile error and the IDE completes the declared names at every bind and request site.
10
+ `Names` is a covariant phantom that defaults to `string`, so existing tokens, class keys and internal `Token<unknown>`
11
+ lanes are unchanged; a new `SlotNamesOf<Key>` type reads the set back.
12
+
13
+ **Breaking:** `whenParentNamed` and `whenAnyAncestorNamed` now take the parent token first —
14
+ `whenParentNamed(Database, "primary")` — and match only when that frame resolves that token at that slot. A slot name is
15
+ a label on one token's bindings, so the token is part of the question and is what types the name; a label shared across
16
+ tokens is what a tag key is for. `validate()` checks the name on that token's bindings and `UnreachableConstraintError`
17
+ carries the new `requiredTokenName`. The reserved criterion handed to a `…Tagged` helper
18
+ (`whenParentTagged(slotName.of("x"))`) is now validated too, as it is the same bare string.
19
+
20
+ Display names follow one rule everywhere the package speaks — spelled like the TS symbol they stand for, under the
21
+ owner's namespace: `token<Logger>("app:Logger")`, `Module.create("app:Infra", …)`, `tag("app:cacheTier")`. The package's
22
+ own `MetadataReaderToken` now prints as `di:MetadataReader`, beside the reserved `di:name` key. SPEC gains a normative
23
+ "Display names" section stating the rule and its enforcement.
24
+
25
+ ### Patch Changes
26
+
27
+ - [#827](https://github.com/codefastlabs/codefast/pull/827) [`0984174`](https://github.com/codefastlabs/codefast/commit/0984174df148a7cffcd09b837bdde1922f38f24e) Thanks [@thevuong](https://github.com/thevuong)! - `package.json` now carries `homepage` and `bugs`, so npm links the package README and the issue tracker the way the
28
+ other `@codefast/*` packages already do.
29
+
3
30
  ## 0.8.1
4
31
 
5
32
  ### Patch Changes
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 CodeFast Labs
3
+ Copyright (c) 2024 Codefast Labs
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -48,7 +48,7 @@ interface Logger {
48
48
  info(message: string): void;
49
49
  }
50
50
 
51
- const LoggerToken = token<Logger>("Logger");
51
+ const LoggerToken = token<Logger>("app:Logger");
52
52
 
53
53
  @injectable([LoggerToken])
54
54
  class CheckoutService {
@@ -86,7 +86,7 @@ and resolve against. Tokens compare by reference, so declare each one once and r
86
86
  ```ts
87
87
  import { token } from "@codefast/di";
88
88
 
89
- const DbToken = token<Database>("Database");
89
+ const DbToken = token<Database>("app:Database");
90
90
  ```
91
91
 
92
92
  A class constructor works as a key too: `container.bind(UserService).toSelf()`, then `container.resolve(UserService)`.
@@ -170,6 +170,15 @@ container.bind(LoggerToken).toConstantValue(fileLogger).whenNamed("file");
170
170
  container.resolve(LoggerToken, { name: "file" }); // → fileLogger
171
171
  ```
172
172
 
173
+ Declare the names on the token and they become checked, completable literals at every bind and request site:
174
+
175
+ ```ts
176
+ const LoggerToken = token<Logger, "console" | "file">("app:Logger");
177
+
178
+ container.bind(LoggerToken).toConstantValue(fileLogger).whenNamed("file");
179
+ container.resolve(LoggerToken, { name: "file" }); // { name: "fiel" } is a compile error
180
+ ```
181
+
173
182
  **Tagged — for typed, collision-proof keys.** A criterion is a `[key, value]` pair. Declare the key once with
174
183
  `tag<Value>(name)`, then mint a criterion with `key.of(value)`. The bind site and the resolve site share the same typed
175
184
  key: a key declared `tag<"s3" | "gcs">` refuses any other value, so the two sites can't drift apart.
@@ -177,7 +186,7 @@ key: a key declared `tag<"s3" | "gcs">` refuses any other value, so the two site
177
186
  ```ts
178
187
  import { tag } from "@codefast/di";
179
188
 
180
- const Provider = tag<"s3" | "gcs">("provider");
189
+ const Provider = tag<"s3" | "gcs">("app:provider");
181
190
 
182
191
  container.bind(StorageToken).to(S3Storage).whenTagged(Provider.of("s3"));
183
192
  container.resolve(StorageToken, { tag: Provider.of("s3") }); // → S3Storage
@@ -202,18 +211,18 @@ container.resolve(StorageToken, { tag: Provider.of("s3") }); // → S3Storage
202
211
  resolving — pass a predicate to `.when(ctx => boolean)`. It runs at resolve time, after slot matching. These ready-made
203
212
  predicates ship from the root entry:
204
213
 
205
- | Predicate | Matches when |
206
- | ------------------------------------ | ------------------------------------------------------ |
207
- | `whenParentIs(token)` | the direct parent resolves `token` |
208
- | `whenNoParentIs(token)` | there is no parent, or it resolves a different token |
209
- | `whenAnyAncestorIs(token)` | some ancestor resolves `token` |
210
- | `whenNoAncestorIs(token)` | no ancestor resolves `token` |
211
- | `whenParentNamed(name)` | the parent's slot carries that name |
212
- | `whenAnyAncestorNamed(name)` | some ancestor's slot carries that name |
213
- | `whenParentTagged(criterion)` | the parent's slot carries that criterion |
214
- | `whenAnyAncestorTagged(criterion)` | some ancestor's slot carries that criterion |
215
- | `whenParentTaggedAll(criteria)` | the parent's slot carries all criteria in the array |
216
- | `whenAnyAncestorTaggedAll(criteria)` | some ancestor's slot carries all criteria in the array |
214
+ | Predicate | Matches when |
215
+ | ------------------------------------ | ------------------------------------------------------- |
216
+ | `whenParentIs(token)` | the direct parent resolves `token` |
217
+ | `whenNoParentIs(token)` | there is no parent, or it resolves a different token |
218
+ | `whenAnyAncestorIs(token)` | some ancestor resolves `token` |
219
+ | `whenNoAncestorIs(token)` | no ancestor resolves `token` |
220
+ | `whenParentNamed(token, name)` | the parent resolves `token` at the slot named `name` |
221
+ | `whenAnyAncestorNamed(token, name)` | some ancestor resolves `token` at the slot named `name` |
222
+ | `whenParentTagged(criterion)` | the parent's slot carries that criterion |
223
+ | `whenAnyAncestorTagged(criterion)` | some ancestor's slot carries that criterion |
224
+ | `whenParentTaggedAll(criteria)` | the parent's slot carries all criteria in the array |
225
+ | `whenAnyAncestorTaggedAll(criteria)` | some ancestor's slot carries all criteria in the array |
217
226
 
218
227
  For the exact matching and most-specific-wins rules, see [`SPEC.md` → Slots and last-wins](./SPEC.md#slot-matching).
219
228
 
@@ -358,11 +367,11 @@ A module is a reusable, stateless bundle of related bindings. Group them once, t
358
367
  ```ts
359
368
  import { Container, Module } from "@codefast/di";
360
369
 
361
- const InfrastructureModule = Module.create("Infra", (api) => {
370
+ const InfrastructureModule = Module.create("app:Infra", (api) => {
362
371
  api.bind(LoggerToken).toConstantValue(console);
363
372
  });
364
373
 
365
- const AppModule = Module.create("App", (api) => {
374
+ const AppModule = Module.create("app:Root", (api) => {
366
375
  api.import(InfrastructureModule);
367
376
  api.bind(UserRepository).toSelf().singleton();
368
377
  });
@@ -31,23 +31,23 @@ export interface BindingRegistration {
31
31
  *
32
32
  * @since 0.5.0-canary.8
33
33
  */
34
- export declare class BindingChain<Value> implements AliasBindingBuilder, BindingBuilder<Value>, BindToBuilder<Value>, ConstantBindingBuilder<Value>, ScopedBindingBuilder<Value>, SingletonBindingBuilder<Value>, SingletonLifecycleBuilder<Value>, TransientBindingBuilder<Value> {
34
+ export declare class BindingChain<Value, Names extends string = string> implements AliasBindingBuilder<Names>, BindingBuilder<Value, Names>, BindToBuilder<Value, Names>, ConstantBindingBuilder<Value, Names>, ScopedBindingBuilder<Value>, SingletonBindingBuilder<Value>, SingletonLifecycleBuilder<Value>, TransientBindingBuilder<Value> {
35
35
  #private;
36
- constructor(token: Token<Value> | Constructor<Value>, registration: BindingRegistration);
37
- to(type: Constructor<Value>): BindingBuilder<Value>;
38
- toSelf(): BindingBuilder<Value>;
39
- toConstantValue(value: Value): ConstantBindingBuilder<Value>;
40
- toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value>;
41
- toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value>;
36
+ constructor(token: Token<Value, Names> | Constructor<Value>, registration: BindingRegistration);
37
+ to(type: Constructor<Value>): BindingBuilder<Value, Names>;
38
+ toSelf(): BindingBuilder<Value, Names>;
39
+ toConstantValue(value: Value): ConstantBindingBuilder<Value, Names>;
40
+ toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value, Names>;
41
+ toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value, Names>;
42
42
  toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
43
43
  [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
44
- }) => Value, deps: Deps): BindingBuilder<Value>;
44
+ }) => Value, deps: Deps): BindingBuilder<Value, Names>;
45
45
  toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
46
46
  [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
47
- }) => Promise<Value>, deps: Deps): BindingBuilder<Value>;
48
- toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder;
47
+ }) => Promise<Value>, deps: Deps): BindingBuilder<Value, Names>;
48
+ toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder<Names>;
49
49
  when(predicate: BindingConstraint): this;
50
- whenNamed(name: string): this;
50
+ whenNamed(name: Names): this;
51
51
  whenTagged(criterion: BindingTag): this;
52
52
  whenDefault(): this;
53
53
  singleton(): SingletonBindingBuilder<Value>;
@@ -13,12 +13,12 @@ import type { MetadataReader } from "#/metadata/metadata-types";
13
13
  */
14
14
  export interface Container {
15
15
  readonly isDisposed: boolean;
16
- bind<Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value>;
16
+ bind<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>): BindToBuilder<Value, Names>;
17
17
  unbind(tokenOrId: Token<unknown> | Constructor | BindingIdentifier): void;
18
18
  unbindAsync(tokenOrId: Token<unknown> | Constructor | BindingIdentifier): Promise<void>;
19
19
  unbindAll(): void;
20
20
  unbindAllAsync(): Promise<void>;
21
- rebind<Value>(token: Token<Value> | Constructor<Value>): BindToBuilder<Value>;
21
+ rebind<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>): BindToBuilder<Value, Names>;
22
22
  load(...modules: Array<SyncModule>): void;
23
23
  loadAsync(...modules: Array<SyncModule | AsyncModule>): Promise<void>;
24
24
  unload(...modules: Array<SyncModule>): void;
@@ -26,20 +26,20 @@ export interface Container {
26
26
  loadAutoRegistered(registry: AutoRegisterRegistry): number;
27
27
  onActivation<Value>(token: Token<Value> | Constructor<Value>, handler: ActivationHandler<Value>): void;
28
28
  onDeactivation<Value>(token: Token<Value> | Constructor<Value>, handler: DeactivationHandler<Value>): void;
29
- resolve<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value;
30
- resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value>;
31
- resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined;
32
- resolveOptionalAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value | undefined>;
33
- resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value>;
34
- resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Array<Value>>;
29
+ resolve<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Value;
30
+ resolveAsync<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Promise<Value>;
31
+ resolveOptional<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Value | undefined;
32
+ resolveOptionalAsync<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Promise<Value | undefined>;
33
+ resolveAll<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Array<Value>;
34
+ resolveAllAsync<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<ResolveOptions<Names>>): Promise<Array<Value>>;
35
35
  createChild(): Container;
36
36
  dispose(): Promise<void>;
37
37
  [Symbol.asyncDispose](): Promise<void>;
38
38
  [Symbol.dispose](): never;
39
39
  initializeAsync(): Promise<void>;
40
40
  validate(): void;
41
- has(token: Token<unknown> | Constructor, options?: ResolveOptions): boolean;
42
- hasOwn(token: Token<unknown> | Constructor, options?: ResolveOptions): boolean;
41
+ has<Names extends string = string>(token: Token<unknown, Names> | Constructor, options?: NoInfer<ResolveOptions<Names>>): boolean;
42
+ hasOwn<Names extends string = string>(token: Token<unknown, Names> | Constructor, options?: NoInfer<ResolveOptions<Names>>): boolean;
43
43
  lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot>;
44
44
  inspect(): ContainerSnapshot;
45
45
  generateDependencyGraph(options?: GraphOptions): ContainerGraphJson;
@@ -2,7 +2,7 @@ import { BindingChain } from "#/container/binding-builders";
2
2
  import { NO_INSTANCE } from "#/core/binding";
3
3
  import { effectiveBindingScope } from "#/core/binding-scope";
4
4
  import { constraintRequirementsOf } from "#/core/constraint-requirement";
5
- import { getOrInsert } from "#/core/map-upsert";
5
+ import { getOrInsert, getOrInsertComputed } from "#/core/map-upsert";
6
6
  import { isSyncModule, MODULE_SETUP } from "#/core/module";
7
7
  import { BindingRegistry } from "#/core/registry";
8
8
  import { tokenName } from "#/core/token";
@@ -18,6 +18,20 @@ import { defaultMetadataReader } from "#/metadata/symbol-metadata-reader";
18
18
  import { verifyingMetadataReader } from "#/metadata/verifying-metadata-reader";
19
19
  import { ROOT_BRANCH } from "#/resolution/path/resolution-path";
20
20
  import { DependencyResolver } from "#/resolution/resolver";
21
+ /** Whether a requirement's name is declared — on its token when it names one, on any token otherwise. */
22
+ function isSlotNameDeclared(declared, requirement) {
23
+ if (requirement.tokenName !== undefined) {
24
+ return declared.get(requirement.tokenName)?.has(requirement.name) ?? false;
25
+ }
26
+ for (const names of declared.values()) {
27
+ if (names.has(requirement.name)) {
28
+ return true;
29
+ }
30
+ }
31
+ return false;
32
+ }
33
+ /** Hoisted so the slot-name index's `getOrInsertComputed` allocates only on a miss, no closure per call. */
34
+ const newSlotNameSet = () => new Set();
21
35
  // A Record rather than an if-chain, so a new `BindingScope` is a compile error here instead of
22
36
  // silently landing in whichever branch happened to be last.
23
37
  const APPLY_BINDING_SCOPE = {
@@ -552,18 +566,19 @@ class DefaultContainer {
552
566
  }
553
567
  declaredSlotNames ??= this.#slotNamesInChain();
554
568
  for (const requirement of requirements) {
555
- if (!declaredSlotNames.has(requirement.name)) {
556
- throw new UnreachableConstraintError(tokenName(binding.token), requirement.name, requirement.helperName);
569
+ if (!isSlotNameDeclared(declaredSlotNames, requirement)) {
570
+ throw new UnreachableConstraintError(tokenName(binding.token), requirement);
557
571
  }
558
572
  }
559
573
  }
560
574
  }
561
- /** Every slot name declared anywhere a resolve through this container could reach. */
575
+ /** Every slot name declared anywhere a resolve through this container could reach, by declaring token. */
562
576
  #slotNamesInChain() {
563
- const names = this.#parent === undefined ? new Set() : this.#parent.#slotNamesInChain();
577
+ const names = this.#parent === undefined ? new Map() : this.#parent.#slotNamesInChain();
564
578
  for (const binding of this.#registry.allBindings()) {
565
- if (binding.slot.name !== undefined) {
566
- names.add(binding.slot.name);
579
+ const name = binding.slot.name;
580
+ if (name !== undefined) {
581
+ getOrInsertComputed(names, tokenName(binding.token), newSlotNameSet).add(name);
567
582
  }
568
583
  }
569
584
  return names;
@@ -248,11 +248,16 @@ export declare function clearBindingFrame<Value>(binding: Binding<Value>): void;
248
248
  *
249
249
  * @since 0.3.16-canary.0
250
250
  */
251
- export interface SlotConstrainedBuilder {
251
+ export interface SlotConstrainedBuilder<Names extends string = string> {
252
+ /** Narrows the binding to requests the predicate accepts, evaluated on every resolve. */
252
253
  when(predicate: BindingConstraint): this;
253
- whenNamed(name: string): this;
254
+ /** Declares the binding's slot name, one of the names the token declares. */
255
+ whenNamed(name: Names): this;
256
+ /** Declares one criterion of the binding's slot, replacing any earlier criterion of the same key. */
254
257
  whenTagged(criterion: BindingTag): this;
258
+ /** Keeps the binding on the default slot, the one an unconstrained request selects. */
255
259
  whenDefault(): this;
260
+ /** The identifier this binding is registered under. */
256
261
  id(): BindingIdentifier;
257
262
  }
258
263
  /**
@@ -260,26 +265,26 @@ export interface SlotConstrainedBuilder {
260
265
  *
261
266
  * @since 0.3.16-canary.0
262
267
  */
263
- export interface BindToBuilder<Value> {
264
- to(type: Constructor<Value>): BindingBuilder<Value>;
265
- toSelf(): BindingBuilder<Value>;
266
- toConstantValue(value: Value): ConstantBindingBuilder<Value>;
267
- toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value>;
268
- toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value>;
268
+ export interface BindToBuilder<Value, Names extends string = string> {
269
+ to(type: Constructor<Value>): BindingBuilder<Value, Names>;
270
+ toSelf(): BindingBuilder<Value, Names>;
271
+ toConstantValue(value: Value): ConstantBindingBuilder<Value, Names>;
272
+ toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value, Names>;
273
+ toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value, Names>;
269
274
  toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
270
275
  [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
271
- }) => Value, deps: Deps): BindingBuilder<Value>;
276
+ }) => Value, deps: Deps): BindingBuilder<Value, Names>;
272
277
  toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
273
278
  [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
274
- }) => Promise<Value>, deps: Deps): BindingBuilder<Value>;
275
- toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder;
279
+ }) => Promise<Value>, deps: Deps): BindingBuilder<Value, Names>;
280
+ toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder<Names>;
276
281
  }
277
282
  /**
278
283
  * The scope-selection step of the fluent chain.
279
284
  *
280
285
  * @since 0.3.16-canary.0
281
286
  */
282
- export interface BindingBuilder<Value> extends SlotConstrainedBuilder {
287
+ export interface BindingBuilder<Value, Names extends string = string> extends SlotConstrainedBuilder<Names> {
283
288
  singleton(): SingletonBindingBuilder<Value>;
284
289
  transient(): TransientBindingBuilder<Value>;
285
290
  scoped(): ScopedBindingBuilder<Value>;
@@ -289,7 +294,7 @@ export interface BindingBuilder<Value> extends SlotConstrainedBuilder {
289
294
  *
290
295
  * @since 0.3.16-canary.0
291
296
  */
292
- export interface ConstantBindingBuilder<Value> extends SlotConstrainedBuilder {
297
+ export interface ConstantBindingBuilder<Value, Names extends string = string> extends SlotConstrainedBuilder<Names> {
293
298
  onActivation(fn: ActivationHandler<Value>): SingletonLifecycleBuilder<Value>;
294
299
  onDeactivation(fn: DeactivationHandler<Value>): SingletonLifecycleBuilder<Value>;
295
300
  }
@@ -298,7 +303,7 @@ export interface ConstantBindingBuilder<Value> extends SlotConstrainedBuilder {
298
303
  *
299
304
  * @since 0.3.16-canary.0
300
305
  */
301
- export interface AliasBindingBuilder extends SlotConstrainedBuilder {
306
+ export interface AliasBindingBuilder<Names extends string = string> extends SlotConstrainedBuilder<Names> {
302
307
  }
303
308
  /**
304
309
  * The fluent chain after `singleton()`, where both lifecycle hooks stay available.
@@ -12,23 +12,38 @@ export declare const CONSTRAINT_REQUIREMENT: unique symbol;
12
12
  /**
13
13
  * The slot name a constraint waits for on an ancestor.
14
14
  *
15
- * @remarks Only names are described. A tag criterion is interned, so a typo cannot produce one that
16
- * looks valid, while a name is a bare string that nothing checks.
15
+ * @remarks Only names are described, however spelled: a tag criterion of any other key is minted from
16
+ * a typed key, so a typo cannot produce one that looks valid, while a name is a bare string that
17
+ * nothing checks — through `whenNamed`'s reserved key included.
17
18
  *
18
19
  * @since 0.6.0
19
20
  */
20
21
  export interface ConstraintRequirement {
21
22
  readonly requires: "ancestorSlotName";
23
+ /** The token whose slot must carry the name, or `undefined` when the spelling named no token. */
24
+ readonly tokenName: string | undefined;
22
25
  readonly name: string;
23
26
  /** The helper that built the predicate, so a report can name what the caller wrote. */
24
27
  readonly helperName: string;
25
28
  }
26
29
  /**
27
- * Records what a predicate waits for. Called once, where the predicate is built.
30
+ * What a helper records about one waited-for slot name, before the discriminant is stamped on.
31
+ *
32
+ * @since 0.9.0
33
+ */
34
+ export type SlotNameRequirement = Omit<ConstraintRequirement, "requires">;
35
+ /**
36
+ * Records the one slot name a predicate waits for. Called once, where the predicate is built.
28
37
  *
29
38
  * @since 0.6.0
30
39
  */
31
- export declare function requiringAncestorSlotName(predicate: BindingConstraint, name: string, helperName: string): BindingConstraint;
40
+ export declare function requiringAncestorSlotName(predicate: BindingConstraint, requirement: SlotNameRequirement): BindingConstraint;
41
+ /**
42
+ * Records every slot name a predicate waits for — one per reserved criterion in a `…TaggedAll` list.
43
+ *
44
+ * @since 0.9.0
45
+ */
46
+ export declare function requiringAncestorSlotNames(predicate: BindingConstraint, requirements: ReadonlyArray<SlotNameRequirement>): BindingConstraint;
32
47
  /**
33
48
  * The requirement a predicate carries, if it was built by a helper that records one.
34
49
  *
@@ -8,13 +8,27 @@
8
8
  */
9
9
  export const CONSTRAINT_REQUIREMENT = Symbol("di:constraint-requirement");
10
10
  /**
11
- * Records what a predicate waits for. Called once, where the predicate is built.
11
+ * Records the one slot name a predicate waits for. Called once, where the predicate is built.
12
12
  *
13
13
  * @since 0.6.0
14
14
  */
15
- export function requiringAncestorSlotName(predicate, name, helperName) {
16
- const requirement = { requires: "ancestorSlotName", name, helperName };
17
- Object.defineProperty(predicate, CONSTRAINT_REQUIREMENT, { value: requirement, enumerable: false });
15
+ export function requiringAncestorSlotName(predicate, requirement) {
16
+ return requiringAncestorSlotNames(predicate, [requirement]);
17
+ }
18
+ /**
19
+ * Records every slot name a predicate waits for — one per reserved criterion in a `…TaggedAll` list.
20
+ *
21
+ * @since 0.9.0
22
+ */
23
+ export function requiringAncestorSlotNames(predicate, requirements) {
24
+ return attachRequirements(predicate, requirements.map((requirement) => ({ requires: "ancestorSlotName", ...requirement })));
25
+ }
26
+ /** One non-enumerable write per predicate: a second define on the same key would throw. */
27
+ function attachRequirements(predicate, requirements) {
28
+ Object.defineProperty(predicate, CONSTRAINT_REQUIREMENT, {
29
+ value: requirements.length === 1 ? requirements[0] : requirements,
30
+ enumerable: false,
31
+ });
18
32
  return predicate;
19
33
  }
20
34
  /**
@@ -53,9 +67,5 @@ export function mergingConstraintRequirements(composite, left, right) {
53
67
  if (merged.length === 0) {
54
68
  return composite;
55
69
  }
56
- Object.defineProperty(composite, CONSTRAINT_REQUIREMENT, {
57
- value: merged.length === 1 ? merged[0] : merged,
58
- enumerable: false,
59
- });
60
- return composite;
70
+ return attachRequirements(composite, merged);
61
71
  }
@@ -59,12 +59,12 @@ export interface TagKey<Value = unknown> {
59
59
  /**
60
60
  * Declares a tag key, whose `of()` builds the criteria a `whenTagged` and a resolve both take.
61
61
  *
62
- * @remarks The value type is checked at both ends: a key declared `tag<Region>("region")` refuses a
62
+ * @remarks The value type is checked at both ends: a key declared `tag<Region>("di:region")` refuses a
63
63
  * value that is not a `Region`, so a bind site and a resolve site cannot drift apart silently.
64
64
  *
65
65
  * @example
66
66
  * ```ts
67
- * const Region = tag<"eu" | "us">("region");
67
+ * const Region = tag<"eu" | "us">("di:region");
68
68
  * container.bind(Storage).to(S3).whenTagged(Region.of("eu"));
69
69
  * container.resolve(Storage, { tag: Region.of("eu") });
70
70
  * ```
package/dist/core/tag.js CHANGED
@@ -27,12 +27,12 @@ function internKeyFor(value) {
27
27
  /**
28
28
  * Declares a tag key, whose `of()` builds the criteria a `whenTagged` and a resolve both take.
29
29
  *
30
- * @remarks The value type is checked at both ends: a key declared `tag<Region>("region")` refuses a
30
+ * @remarks The value type is checked at both ends: a key declared `tag<Region>("di:region")` refuses a
31
31
  * value that is not a `Region`, so a bind site and a resolve site cannot drift apart silently.
32
32
  *
33
33
  * @example
34
34
  * ```ts
35
- * const Region = tag<"eu" | "us">("region");
35
+ * const Region = tag<"eu" | "us">("di:region");
36
36
  * container.bind(Storage).to(S3).whenTagged(Region.of("eu"));
37
37
  * container.resolve(Storage, { tag: Region.of("eu") });
38
38
  * ```
@@ -1,20 +1,40 @@
1
1
  import type { Constructor } from "#/core/constructor-type";
2
2
  declare const TOKEN_BRAND: unique symbol;
3
+ declare const TOKEN_NAMES_BRAND: unique symbol;
3
4
  /**
4
- * A branded identifier carrying the value type its bindings resolve to.
5
+ * A branded identifier carrying the value type its bindings resolve to, and the slot names they may declare.
6
+ *
7
+ * @remarks `Names` exists at the type level only: `whenNamed`, and `name` in `ResolveOptions` and
8
+ * `InjectOptions`, narrow to it, so a bind site and a request site cannot drift apart silently. It
9
+ * is covariant, so a token declaring names is still a `Token<unknown>` wherever the engine erases
10
+ * the value type; the default `string` leaves a token that declares none unconstrained.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const Logger = token<Logger, "console" | "file">("di:Logger");
15
+ * container.bind(Logger).to(FileLogger).whenNamed("file");
16
+ * container.resolve(Logger, { name: "file" });
17
+ * ```
5
18
  *
6
19
  * @since 0.3.16-canary.0
7
20
  */
8
- export interface Token<out Value> {
21
+ export interface Token<out Value, out Names extends string = string> {
9
22
  readonly name: string;
10
23
  readonly [TOKEN_BRAND]: Value;
24
+ readonly [TOKEN_NAMES_BRAND]?: Names;
11
25
  }
12
26
  /**
13
- * Creates a named `Token` for the given value type.
27
+ * The slot names a dependency key declares — `string` for a class, or a token that declares none.
28
+ *
29
+ * @since 0.9.0
30
+ */
31
+ export type SlotNamesOf<Key> = Key extends Token<unknown, infer Names extends string> ? Names : string;
32
+ /**
33
+ * Creates a named `Token` for the given value type, optionally declaring the slot names its bindings may use.
14
34
  *
15
35
  * @since 0.3.16-canary.0
16
36
  */
17
- export declare function token<Value>(name: string): Token<Value>;
37
+ export declare function token<Value, Names extends string = string>(name: string): Token<Value, Names>;
18
38
  /**
19
39
  * Returns the display name of a token or class used as a dependency key.
20
40
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Creates a named `Token` for the given value type.
2
+ * Creates a named `Token` for the given value type, optionally declaring the slot names its bindings may use.
3
3
  *
4
4
  * @since 0.3.16-canary.0
5
5
  */
@@ -47,8 +47,14 @@ export type DeactivationHandler<Value> = (instance: Value) => void | Promise<voi
47
47
  *
48
48
  * @since 0.3.16-canary.0
49
49
  */
50
- export interface ResolveOptions {
51
- name?: string | undefined;
50
+ export interface ResolveOptions<Names extends string = string> {
51
+ /**
52
+ * The slot name a binding declared with `whenNamed`.
53
+ *
54
+ * @remarks Narrowed to the names the token declares, so a request cannot ask for a name no
55
+ * binding could carry; a token declaring none takes any string.
56
+ */
57
+ name?: Names | undefined;
52
58
  /**
53
59
  * Single-tag shorthand, equivalent to listing the one pair in `tags`.
54
60
  *
@@ -8,5 +8,5 @@ type ClassAccessorDecorator<This, Value> = (target: ClassAccessorDecoratorTarget
8
8
  *
9
9
  * @since 0.3.16-canary.0
10
10
  */
11
- export declare function inject<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value> & ClassAccessorDecorator<unknown, Value>;
11
+ export declare function inject<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<InjectOptions<Names>>): InjectionDescriptor<Value> & ClassAccessorDecorator<unknown, Value>;
12
12
  export {};
@@ -30,6 +30,8 @@ export function inject(token, options) {
30
30
  // Derived from the descriptor, not from `options`: the descriptor is where the tag shorthand has
31
31
  // already been folded. Built once here rather than per constructed instance.
32
32
  const resolveOptions = injectionSlotToResolveOptions(descriptor);
33
+ // The names are checked above; the container lane only needs the value type.
34
+ const resolveKey = token;
33
35
  const decoratorFn = (_target, context) => {
34
36
  if (context.static) {
35
37
  throw new StaticMemberDecoratorError("inject", String(context.name));
@@ -50,8 +52,8 @@ export function inject(token, options) {
50
52
  const ambient = getAmbientResolution();
51
53
  if (ambient !== undefined) {
52
54
  const value = descriptor.optional
53
- ? ambient.resolveOptional(token, resolveOptions)
54
- : ambient.resolve(token, resolveOptions);
55
+ ? ambient.resolveOptional(resolveKey, resolveOptions)
56
+ : ambient.resolve(resolveKey, resolveOptions);
55
57
  context.access.set(this, value);
56
58
  return;
57
59
  }
@@ -60,8 +62,8 @@ export function inject(token, options) {
60
62
  throw new MissingContainerContextError(classNameOf(this), context.name);
61
63
  }
62
64
  const value = descriptor.optional
63
- ? container.resolveOptional(token, resolveOptions)
64
- : container.resolve(token, resolveOptions);
65
+ ? container.resolveOptional(resolveKey, resolveOptions)
66
+ : container.resolve(resolveKey, resolveOptions);
65
67
  context.access.set(this, value);
66
68
  });
67
69
  return {};
@@ -1,3 +1,4 @@
1
+ import type { ConstraintRequirement } from "#/core/constraint-requirement";
1
2
  import type { BindingIdentifier, BindingScope, ResolveOptions } from "#/core/types";
2
3
  /**
3
4
  * Base class for every error the library throws, each carrying a machine-readable `code`.
@@ -132,8 +133,10 @@ export declare class UnreachableConstraintError extends DiError {
132
133
  readonly code = "UNREACHABLE_CONSTRAINT";
133
134
  readonly tokenName: string;
134
135
  readonly requiredName: string;
136
+ /** The token the name was required on, or `undefined` when the constraint named no token. */
137
+ readonly requiredTokenName: string | undefined;
135
138
  readonly helperName: string;
136
- constructor(tokenName: string, requiredName: string, helperName: string);
139
+ constructor(tokenName: string, requirement: ConstraintRequirement);
137
140
  }
138
141
  /**
139
142
  * A container-level lifecycle hook whose token nothing is bound to, so it can never run.
@@ -191,11 +191,17 @@ export class UnreachableConstraintError extends DiError {
191
191
  code = "UNREACHABLE_CONSTRAINT";
192
192
  tokenName;
193
193
  requiredName;
194
+ /** The token the name was required on, or `undefined` when the constraint named no token. */
195
+ requiredTokenName;
194
196
  helperName;
195
- constructor(tokenName, requiredName, helperName) {
196
- super(`The binding for '${tokenName}' is constrained by ${helperName}('${requiredName}'), but no binding in this container or its ancestors declares the slot name '${requiredName}', so the constraint can never hold. Name the slot with .whenNamed('${requiredName}') on the binding it should match, or correct the name here.`);
197
+ constructor(tokenName, requirement) {
198
+ const { name, helperName, tokenName: requiredTokenName } = requirement;
199
+ const scope = requiredTokenName === undefined ? "no binding" : `no binding for '${requiredTokenName}'`;
200
+ const target = requiredTokenName === undefined ? "the binding it should match" : `a '${requiredTokenName}' binding`;
201
+ super(`The binding for '${tokenName}' is constrained by ${helperName} waiting on the slot name '${name}', but ${scope} in this container or its ancestors declares it, so the constraint can never hold. Name the slot with .whenNamed('${name}') on ${target}, or correct the name here.`);
197
202
  this.tokenName = tokenName;
198
- this.requiredName = requiredName;
203
+ this.requiredName = name;
204
+ this.requiredTokenName = requiredTokenName;
199
205
  this.helperName = helperName;
200
206
  }
201
207
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export type { ActivationHandler, BindingConstraint, BindingIdentifier, BindingKind, BindingScope, BindingTag, ConstraintContext, Constructor, DependencyKey, DeactivationHandler, ResolutionFrame, ResolveOptions, ResolutionContext, TokenValue, } from "#/core/types";
2
2
  export { token, tokenName } from "#/core/token";
3
- export type { Token } from "#/core/token";
3
+ export type { SlotNamesOf, Token } from "#/core/token";
4
4
  export { coversTagKeys, NO_TAG_KEYS, slotName, tag, tagKeyMaskOf } from "#/core/tag";
5
5
  export type { TagKey, TagKeyMask } from "#/core/tag";
6
6
  export type { AliasBindingBuilder, BindToBuilder, BindingBuilder, ConstantBindingBuilder, ScopedBindingBuilder, SingletonBindingBuilder, SingletonLifecycleBuilder, SlotConstrainedBuilder, TransientBindingBuilder, } from "#/core/binding";
@@ -7,8 +7,14 @@ import type { DependencySlot } from "#/injection/resolve-options";
7
7
  *
8
8
  * @since 0.3.16-canary.0
9
9
  */
10
- export interface InjectOptions {
11
- name?: string | undefined;
10
+ export interface InjectOptions<Names extends string = string> {
11
+ /**
12
+ * The slot name a binding declared with `whenNamed`.
13
+ *
14
+ * @remarks Narrowed to the names the token declares, so a dependency cannot ask for a name no
15
+ * binding could carry; a token declaring none takes any string.
16
+ */
17
+ name?: Names | undefined;
12
18
  /**
13
19
  * Single-tag shorthand, equivalent to listing the one pair in `tags`.
14
20
  *
@@ -74,17 +80,17 @@ export declare function normalizeToDescriptor(dependency: InjectableDependency):
74
80
  *
75
81
  * @since 0.6.0
76
82
  */
77
- export declare function buildInjectionDescriptor<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value>;
83
+ export declare function buildInjectionDescriptor<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<InjectOptions<Names>>): InjectionDescriptor<Value>;
78
84
  /**
79
85
  * Creates a descriptor that resolves to `undefined` instead of throwing when no binding matches.
80
86
  *
81
87
  * @since 0.3.16-canary.0
82
88
  */
83
- export declare function optional<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value | undefined>;
89
+ export declare function optional<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<InjectOptions<Names>>): InjectionDescriptor<Value | undefined>;
84
90
  /**
85
91
  * Creates a descriptor that resolves every matching binding for the token into an array.
86
92
  *
87
93
  * @since 0.3.16-canary.0
88
94
  */
89
- export declare function injectAll<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Array<Value>>;
95
+ export declare function injectAll<Value, Names extends string = string>(token: Token<Value, Names> | Constructor<Value>, options?: NoInfer<InjectOptions<Names>>): InjectionDescriptor<Array<Value>>;
90
96
  export {};
@@ -4,4 +4,4 @@ import { token } from "#/core/token";
4
4
  *
5
5
  * @since 0.3.16-canary.0
6
6
  */
7
- export const MetadataReaderToken = token("MetadataReader");
7
+ export const MetadataReaderToken = token("di:MetadataReader");
@@ -26,17 +26,20 @@ export declare function whenAnyAncestorIs(token: Token<unknown> | Constructor):
26
26
  */
27
27
  export declare function whenNoAncestorIs(token: Token<unknown> | Constructor): BindingConstraint;
28
28
  /**
29
- * Matches when the direct parent slot carries the given name.
29
+ * Matches when the direct parent resolves the given token at the slot carrying the given name.
30
+ *
31
+ * @remarks A slot name is a label on one token's bindings, so the token is part of the question — and
32
+ * what types `name` to the names that token declares.
30
33
  *
31
34
  * @since 0.3.16-canary.0
32
35
  */
33
- export declare function whenParentNamed(name: string): BindingConstraint;
36
+ export declare function whenParentNamed<Names extends string>(token: Token<unknown, Names> | Constructor, name: NoInfer<Names>): BindingConstraint;
34
37
  /**
35
- * Matches when at least one ancestor slot carries the given name.
38
+ * Matches when at least one ancestor resolves the given token at the slot carrying the given name.
36
39
  *
37
40
  * @since 0.3.16-canary.0
38
41
  */
39
- export declare function whenAnyAncestorNamed(name: string): BindingConstraint;
42
+ export declare function whenAnyAncestorNamed<Names extends string>(token: Token<unknown, Names> | Constructor, name: NoInfer<Names>): BindingConstraint;
40
43
  /**
41
44
  * Matches when the direct parent slot carries the given tag pair.
42
45
  *
@@ -1,5 +1,5 @@
1
- import { requiringAncestorSlotName } from "#/core/constraint-requirement";
2
- import { coversTagKeys, tagKeyMaskOf } from "#/core/tag";
1
+ import { requiringAncestorSlotName, requiringAncestorSlotNames } from "#/core/constraint-requirement";
2
+ import { coversTagKeys, slotName, tagKeyMaskOf } from "#/core/tag";
3
3
  import { tokenName } from "#/core/token";
4
4
  import { EmptyTagCriteriaError } from "#/errors/errors";
5
5
  /**
@@ -39,20 +39,27 @@ export function whenNoAncestorIs(token) {
39
39
  return (constraintContext) => constraintContext.ancestors.every((ancestorFrame) => ancestorFrame.tokenName !== tokenDisplayName);
40
40
  }
41
41
  /**
42
- * Matches when the direct parent slot carries the given name.
42
+ * Matches when the direct parent resolves the given token at the slot carrying the given name.
43
+ *
44
+ * @remarks A slot name is a label on one token's bindings, so the token is part of the question — and
45
+ * what types `name` to the names that token declares.
43
46
  *
44
47
  * @since 0.3.16-canary.0
45
48
  */
46
- export function whenParentNamed(name) {
47
- return requiringAncestorSlotName((constraintContext) => constraintContext.parent !== undefined && constraintContext.parent.slot.name === name, name, "whenParentNamed");
49
+ export function whenParentNamed(token, name) {
50
+ const parentTokenName = tokenName(token);
51
+ return requiringAncestorSlotName((constraintContext) => constraintContext.parent !== undefined &&
52
+ constraintContext.parent.tokenName === parentTokenName &&
53
+ constraintContext.parent.slot.name === name, { tokenName: parentTokenName, name, helperName: "whenParentNamed" });
48
54
  }
49
55
  /**
50
- * Matches when at least one ancestor slot carries the given name.
56
+ * Matches when at least one ancestor resolves the given token at the slot carrying the given name.
51
57
  *
52
58
  * @since 0.3.16-canary.0
53
59
  */
54
- export function whenAnyAncestorNamed(name) {
55
- return requiringAncestorSlotName((constraintContext) => constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.slot.name === name), name, "whenAnyAncestorNamed");
60
+ export function whenAnyAncestorNamed(token, name) {
61
+ const ancestorTokenName = tokenName(token);
62
+ return requiringAncestorSlotName((constraintContext) => constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.tokenName === ancestorTokenName && ancestorFrame.slot.name === name), { tokenName: ancestorTokenName, name, helperName: "whenAnyAncestorNamed" });
56
63
  }
57
64
  /**
58
65
  * Matches when the direct parent slot carries the given tag pair.
@@ -60,7 +67,7 @@ export function whenAnyAncestorNamed(name) {
60
67
  * @since 0.3.16-canary.0
61
68
  */
62
69
  export function whenParentTagged(criterion) {
63
- return (constraintContext) => constraintContext.parent !== undefined && constraintContext.parent.slot.tags.includes(criterion);
70
+ return requiringReservedNamesAmong((constraintContext) => constraintContext.parent !== undefined && constraintContext.parent.slot.tags.includes(criterion), [criterion], "whenParentTagged");
64
71
  }
65
72
  /**
66
73
  * Matches when at least one ancestor slot carries the given tag pair.
@@ -68,7 +75,7 @@ export function whenParentTagged(criterion) {
68
75
  * @since 0.3.16-canary.0
69
76
  */
70
77
  export function whenAnyAncestorTagged(criterion) {
71
- return (constraintContext) => constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.slot.tags.includes(criterion));
78
+ return requiringReservedNamesAmong((constraintContext) => constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.slot.tags.includes(criterion)), [criterion], "whenAnyAncestorTagged");
72
79
  }
73
80
  /**
74
81
  * Matches when the direct parent slot carries **all** of the given tag pairs.
@@ -80,13 +87,13 @@ export function whenAnyAncestorTagged(criterion) {
80
87
  export function whenParentTaggedAll(tags) {
81
88
  assertHasCriteria(tags, "whenParentTaggedAll");
82
89
  const wanted = tagKeyMaskOf(tags);
83
- return (constraintContext) => {
90
+ return requiringReservedNamesAmong((constraintContext) => {
84
91
  const { parent } = constraintContext;
85
92
  if (parent === undefined || !coversTagKeys(parent.slot.keyMask, wanted)) {
86
93
  return false;
87
94
  }
88
95
  return tags.every((criterion) => parent.slot.tags.includes(criterion));
89
- };
96
+ }, tags, "whenParentTaggedAll");
90
97
  }
91
98
  /**
92
99
  * Matches when at least one ancestor slot carries **all** of the given tag pairs.
@@ -98,7 +105,21 @@ export function whenParentTaggedAll(tags) {
98
105
  export function whenAnyAncestorTaggedAll(tags) {
99
106
  assertHasCriteria(tags, "whenAnyAncestorTaggedAll");
100
107
  const wanted = tagKeyMaskOf(tags);
101
- return (constraintContext) => constraintContext.ancestors.some((frame) => coversTagKeys(frame.slot.keyMask, wanted) && tags.every((criterion) => frame.slot.tags.includes(criterion)));
108
+ return requiringReservedNamesAmong((constraintContext) => constraintContext.ancestors.some((frame) => coversTagKeys(frame.slot.keyMask, wanted) && tags.every((criterion) => frame.slot.tags.includes(criterion))), tags, "whenAnyAncestorTaggedAll");
109
+ }
110
+ /**
111
+ * Records a requirement for every reserved-key criterion in a list, so a name spelled through the tag
112
+ * lane is validated exactly as one spelled through `whenNamed`.
113
+ */
114
+ function requiringReservedNamesAmong(predicate, tags, helperName) {
115
+ let requirements;
116
+ for (const criterion of tags) {
117
+ if (criterion.key === slotName) {
118
+ requirements ??= [];
119
+ requirements.push({ tokenName: undefined, name: criterion.value, helperName });
120
+ }
121
+ }
122
+ return requirements === undefined ? predicate : requiringAncestorSlotNames(predicate, requirements);
102
123
  }
103
124
  /**
104
125
  * Refuses a criteria list with nothing in it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codefast/di",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "Lightweight dependency injection primitives for Codefast",
5
5
  "keywords": [
6
6
  "codefast",
@@ -9,8 +9,12 @@
9
9
  "inversion-of-control",
10
10
  "typescript"
11
11
  ],
12
+ "homepage": "https://github.com/codefastlabs/codefast/tree/main/packages/di#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/codefastlabs/codefast/issues"
15
+ },
12
16
  "license": "MIT",
13
- "author": "Vuong Phan <mr.thevuong@gmail.com>",
17
+ "author": "Vuong Phan <mr.thevuong@gmail.com> (https://github.com/thevuong)",
14
18
  "repository": {
15
19
  "type": "git",
16
20
  "url": "git+https://github.com/codefastlabs/codefast.git",
@@ -28,20 +32,6 @@
28
32
  "module": "./dist/index.js",
29
33
  "types": "./dist/index.d.ts",
30
34
  "imports": {
31
- "#/tests/*": [
32
- "./tests/*",
33
- "./tests/*.ts",
34
- "./tests/*.tsx",
35
- "./tests/*/index.ts",
36
- "./tests/*/index.tsx"
37
- ],
38
- "#/examples/*": [
39
- "./examples/*",
40
- "./examples/*.ts",
41
- "./examples/*.tsx",
42
- "./examples/*/index.ts",
43
- "./examples/*/index.tsx"
44
- ],
45
35
  "#/*": {
46
36
  "types": "./dist/*.d.ts",
47
37
  "default": "./dist/*.js"
@@ -225,31 +215,7 @@
225
215
  "publishConfig": {
226
216
  "access": "public"
227
217
  },
228
- "devDependencies": {
229
- "@babel/core": "^8.0.1",
230
- "@babel/plugin-proposal-decorators": "8.0.2",
231
- "@rolldown/plugin-babel": "^0.2.3",
232
- "@types/node": "^26.4.1",
233
- "@vitest/coverage-v8": "^5.0.0",
234
- "tsx": "^4.23.13",
235
- "typescript": "^7.0.2",
236
- "vitest": "^5.0.0",
237
- "@codefast/typescript-config": "0.9.0"
238
- },
239
218
  "engines": {
240
219
  "node": ">=24.0.0"
241
- },
242
- "scripts": {
243
- "build": "rm -rf dist && tsc -p tsconfig.build.json",
244
- "check-types": "tsc --noEmit",
245
- "clean": "rm -rf dist",
246
- "examples": "node examples/run.mjs",
247
- "test": "vitest run",
248
- "test:coverage": "vitest run --coverage",
249
- "test:e2e": "vitest run tests/e2e",
250
- "test:integration": "vitest run tests/integration",
251
- "test:type": "vitest run tests/types",
252
- "test:unit": "vitest run tests/unit",
253
- "test:watch": "vitest"
254
220
  }
255
221
  }