@rhombus-std/di.core 0.0.0-alpha.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.
@@ -0,0 +1,775 @@
1
+ type Func<in Args extends readonly any[] = any[], out Return = any> = (...args: Args) => Return;
2
+
3
+ interface Ctor<in Args extends readonly any[] = any[], out Instance = any> {
4
+ new(...args: Args): Instance;
5
+ prototype: Instance;
6
+ }
7
+
8
+ /**
9
+ * Anything a dependency signature can describe: a class constructor (its deps
10
+ * are the ctor parameters) or a factory function (its deps are the call
11
+ * parameters). The `never[]` rest keeps any concrete function assignable here
12
+ * regardless of its own parameter list.
13
+ */
14
+ type DepTarget = Ctor | Func<never[], unknown>;
15
+ /**
16
+ * A stable string identifying an interface — the DI key.
17
+ *
18
+ * No branding, no literal types. Generated by the transformer from a TypeScript
19
+ * type at compile time; referenced by hand when using the manual authoring surfaces.
20
+ */
21
+ type Token = string;
22
+ /**
23
+ * Marks a constructor parameter to be injected as a *factory* producing the
24
+ * registered type token, rather than a resolved instance. The factory's own
25
+ * call signature is determined by the caller-supplied `params` list.
26
+ *
27
+ * `type` is the token of the produced type T (replaces the former `.factory` field).
28
+ * `params` is the complete, authored-order list of caller-supplied parameter tokens;
29
+ * when present it pins the factory shape so it no longer drifts with registration state.
30
+ */
31
+ interface FactoryRef {
32
+ readonly type: Token;
33
+ readonly params?: readonly Token[];
34
+ }
35
+ /**
36
+ * A set of alternative dependency slots tried in declaration order (first
37
+ * resolvable member wins). If no member is resolvable, resolution throws.
38
+ * Each member is itself a `DepSlot` — nesting is allowed.
39
+ */
40
+ interface Union {
41
+ readonly union: readonly DepSlot[];
42
+ }
43
+ /**
44
+ * A SINGULAR (non-union) type that supplies its value directly — no container
45
+ * lookup. Emitted for:
46
+ * - a non-union literal param (`"dev"`, `42`, `true`, `1n`) → its value, and
47
+ * - a whole-type `void` / `undefined` → `undefined`; a whole-type `null` →
48
+ * `null` (a singleton type has exactly one inhabitant, so it is supplied
49
+ * directly, NOT tokenized — Rule 2).
50
+ * The engine injects `value` verbatim. A LITERAL/typed UNION (`"a" | "b"`,
51
+ * `Foo | undefined`) is NOT a `LiteralRef`: a literal union stays a resolved
52
+ * token, and a nullish union is stripped by the optional/overload path. Always
53
+ * satisfiable — the value is self-supplying.
54
+ *
55
+ * NOTE: `value` may legitimately be `undefined` (the `void`/`undefined` case),
56
+ * so a `LiteralRef` is identified by the PRESENCE of the `value` key, never by
57
+ * `value !== undefined`. See `isLiteralRef`.
58
+ */
59
+ interface LiteralRef {
60
+ readonly value: string | number | boolean | bigint | undefined | null;
61
+ }
62
+ /**
63
+ * Marks a parameter to be injected with the TOKEN STRING of one of the
64
+ * registration's type arguments — the `typeof(T)` analog for open-generic
65
+ * templates. `typeArg` is the 1-based hole number (`{ typeArg: 1 }` names the
66
+ * argument bound to `$1`). At close time, substitution replaces the slot with
67
+ * a `LiteralRef` carrying the substituted argument's token string; a raw
68
+ * (unsubstituted) `TypeArgRef` reaching resolution is an error.
69
+ */
70
+ interface TypeArgRef {
71
+ readonly typeArg: number;
72
+ }
73
+ /**
74
+ * One positional slot in a constructor / factory signature:
75
+ * - a `Token` string — a container-resolved dependency (a plain `Resolver`
76
+ * token resolves to the live provider view — see `RESOLVER_TOKEN`),
77
+ * - a `FactoryRef` — a factory-injected parameter (see `FactoryRef`),
78
+ * - a `Union` — member-level alternatives tried in order,
79
+ * - a `LiteralRef` — a singular literal supplying its value directly, or
80
+ * - a `TypeArgRef` — the token string of a type argument (see `TypeArgRef`).
81
+ */
82
+ type DepSlot = Token | FactoryRef | Union | LiteralRef | TypeArgRef;
83
+ /**
84
+ * Per-constructor dependency metadata carried on a registration.
85
+ *
86
+ * `signatures` is an array of arrays: each element is one constructor signature
87
+ * (for overload support). `signatures[i][j]` is the `DepSlot` — a token, a
88
+ * `FactoryRef`, a `Union`, or a `LiteralRef` — for constructor parameter `j` of
89
+ * overload `i`.
90
+ */
91
+ interface DepRecord {
92
+ readonly signatures: readonly (readonly DepSlot[])[];
93
+ }
94
+ /**
95
+ * The result of parsing a closed-generic token `base<arg1,arg2>` into its base
96
+ * and top-level args. A pure data shape (the parse routine that produces it is a
97
+ * runtime helper that lives in `@rhombus-std/di`); kept here so the type surface a
98
+ * consumer references stays in the types-only substrate.
99
+ */
100
+ interface ParsedToken {
101
+ readonly base: Token;
102
+ readonly args: readonly Token[];
103
+ }
104
+
105
+ /**
106
+ * Compile-time phantom brand that pins a specific token for one constructor or
107
+ * factory parameter, overriding the token the transformer would normally derive.
108
+ *
109
+ * The value type stays `T` — a plain `T` is assignable because the brand
110
+ * property is optional. Zero runtime footprint.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * class Handler {
115
+ * constructor(
116
+ * cache: Inject<ICache, "pkg:redis-cache">, // pinned token
117
+ * log: ILogger, // derived normally
118
+ * ) {}
119
+ * }
120
+ * ```
121
+ */
122
+ declare const TOK: unique symbol;
123
+ type Inject<T, K extends Token> = T & {
124
+ readonly [TOK]?: K;
125
+ };
126
+ /**
127
+ * Compile-time skolem standing in for the `N`th type argument of an open
128
+ * template (1-based). Writing `add<IRepository<$<1>>>(SqlRepository<$<1>>)` binds
129
+ * the hole; the transformer derives `$N` wherever a Hole-branded type appears.
130
+ *
131
+ * `C` is the constraint carrier: `Hole<1, Entity>` IS an `Entity` (the brand
132
+ * property is optional, so the intersection stays assignable to `C`), which
133
+ * lets a constrained implementation `class Repo<T extends Entity>` accept a
134
+ * hole as its type argument. Zero runtime footprint.
135
+ */
136
+ declare const HOLE: unique symbol;
137
+ type Hole<N extends number, C = unknown> = C & {
138
+ readonly [HOLE]?: N;
139
+ };
140
+ /**
141
+ * Unbounded sugar for the common unconstrained hole: `$<1>`, `$<2>`, … `$<N>`.
142
+ * `$<N>` is exactly `Hole<N>`; reach for `Hole<N, C>` when the impl's type
143
+ * parameter carries a constraint the skolem must satisfy.
144
+ */
145
+ type $<N extends number> = Hole<N>;
146
+ /**
147
+ * Compile-time phantom brand marking a constructor parameter that receives the
148
+ * TOKEN STRING of type argument `T` — the `typeof(T)` analog (hence the name).
149
+ * The value type stays `Token` (a plain string is assignable; the brand
150
+ * property is optional).
151
+ *
152
+ * `Typeof<T>` is type-driven: the transformer infers the hole from `T`. The
153
+ * manual counterpart `typeArg(n)` is positional — a plugin-less author names
154
+ * the hole by number.
155
+ *
156
+ * When `T` is a Hole, the transformer emits an open `{ typeArg: N }` slot that
157
+ * substitution closes per registration; when `T` is concrete, it emits the
158
+ * derived token directly as a literal value slot. Zero runtime footprint.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * class SqlRepository<T> {
163
+ * constructor(readonly entityToken: Typeof<T>) {}
164
+ * }
165
+ * ```
166
+ */
167
+ declare const ARG: unique symbol;
168
+ type Typeof<T> = Token & {
169
+ readonly [ARG]?: T;
170
+ };
171
+
172
+ /**
173
+ * ONE overload's own non-call properties, carried across the peeling recursion so
174
+ * a callable-with-statics keeps them. `Pick<T, keyof T>` is `{}` for a bare
175
+ * function type and the static side for a constructor type.
176
+ */
177
+ type OverloadProps<T> = Pick<T, keyof T>;
178
+ /**
179
+ * Peel an intersection of call signatures (an overloaded function type) into a
180
+ * UNION of its individual signatures. The technique (Vojtěch Mašek / type-fest):
181
+ * `infer` matches the LAST signature and emits it, then recurses with the
182
+ * accumulator intersected back in so the next match resolves to the PRECEDING
183
+ * overload. Bounded — each step strips one signature, terminating once the
184
+ * accumulator already subsumes the whole overload set (`TAccumulator extends
185
+ * TOverload`).
186
+ */
187
+ type OverloadUnionRecursive<TOverload, TAccumulator = unknown> = TOverload extends (...args: infer TArgs) => infer TReturn ? TAccumulator extends TOverload ? never : OverloadUnionRecursive<TAccumulator & TOverload, TAccumulator & ((...args: TArgs) => TReturn) & OverloadProps<TOverload>> | ((...args: TArgs) => TReturn) : never;
188
+ /**
189
+ * The UNION of a function type's individual call-signature overloads. Seeds the
190
+ * recursion with a `() => never` overload hoisted to the FRONT of the
191
+ * intersection (required for the bounded recursion to fire), then excludes that
192
+ * sentinel from the result unless `T` genuinely is `() => never`.
193
+ */
194
+ type OverloadUnion<T extends (...args: any[]) => any> = Exclude<OverloadUnionRecursive<(() => never) & T>, T extends () => never ? never : () => never>;
195
+ /**
196
+ * Every overload's parameter tuple for a function type `T`, as a union — the
197
+ * overload-faithful analog of the builtin `Parameters<T>`. For a `T` with
198
+ * signatures `(a: A)` and `(a: B, b: C)` this is `[a: A] | [a: B, b: C]`; a
199
+ * single-overload function yields its one tuple.
200
+ */
201
+ type OverloadedParameters<T extends (...args: any[]) => any> = Parameters<OverloadUnion<T>>;
202
+ /** The construct-signature counterpart of {@link OverloadProps} — the static side. */
203
+ type ConstructorOverloadProps<T> = Pick<T, keyof T>;
204
+ /**
205
+ * The construct-signature counterpart of {@link OverloadUnionRecursive}: peels an
206
+ * intersection of CONSTRUCT signatures (an overloaded constructor type) into a
207
+ * union of its individual signatures. A concrete `new` is used, NOT `abstract
208
+ * new`: intersecting an abstract construct signature with a concrete class's
209
+ * `new` signatures derails overload inference (it collapses to `any`), and the
210
+ * sole consumer — a factory that does `new C(...args)` — needs a concrete
211
+ * constructor anyway.
212
+ */
213
+ type ConstructorOverloadUnionRecursive<TOverload, TAccumulator = unknown> = TOverload extends new (...args: infer TArgs) => infer TReturn ? TAccumulator extends TOverload ? never : ConstructorOverloadUnionRecursive<TAccumulator & TOverload, TAccumulator & (new (...args: TArgs) => TReturn) & ConstructorOverloadProps<TOverload>> | (new (...args: TArgs) => TReturn) : never;
214
+ /** The construct-signature counterpart of {@link OverloadUnion}. */
215
+ type ConstructorOverloadUnion<T extends new (...args: any[]) => any> = Exclude<ConstructorOverloadUnionRecursive<(new () => never) & T>, T extends new () => never ? never : new () => never>;
216
+ /**
217
+ * Every construct-overload's parameter tuple for a constructor type `T`, as a
218
+ * union — the overload-faithful analog of the builtin `ConstructorParameters<T>`.
219
+ * For a `C` with constructors `(a: A)` and `(a: B, b: C)`,
220
+ * `OverloadedConstructorParameters<typeof C>` is `[a: A] | [a: B, b: C]`; a
221
+ * single-overload ctor yields its one tuple and a zero-arg ctor yields `[]`.
222
+ * Constrained to a concrete (`new`-able) constructor — an abstract class has no
223
+ * constructible instance, and the factory that consumes this must `new` its
224
+ * argument.
225
+ */
226
+ type OverloadedConstructorParameters<T extends new (...args: any[]) => any> = ConstructorParameters<ConstructorOverloadUnion<T>>;
227
+
228
+ /**
229
+ * The continuation returned by a class `ServiceManifest.add`. Carries the just-added
230
+ * registration so `.as()` can attach its lifetime in place. An `.add()` with no
231
+ * trailing `.as()` leaves the registration scopeless ⇒ transient.
232
+ *
233
+ * `Scopes` is threaded so `.as()` only accepts a declared scope name —
234
+ * compile-time guard at the registration site. The authored type-arg form
235
+ * `.as<"scope">()` is DECLARATION-MERGED onto this interface by the
236
+ * `@rhombus-std/di.transformer` augmentation — a pure typing that surfaces only
237
+ * when the transformer is in the program.
238
+ */
239
+ interface AddBuilder<Scopes extends string> {
240
+ /**
241
+ * Attaches the lifetime — the RUNTIME (lowered) form. Must name a declared
242
+ * scope.
243
+ *
244
+ * `.as("singleton")` is what the engine executes: the transformer rewrites the
245
+ * authored type-arg form (`.as<"singleton">()`) to this value-arg form before
246
+ * runtime, and a plugin-less caller writes it directly. The AUTHORED type-arg
247
+ * form (`.as<S extends Scopes>(): void`) is a PURE TYPING contributed by the
248
+ * `@rhombus-std/di.transformer` augmentation — it is not part of di's published surface,
249
+ * so it only type-checks when the transformer's types are in the program.
250
+ */
251
+ as(scope: Scopes): void;
252
+ }
253
+ /**
254
+ * The AUTHORING INTERFACE for the registration collection — the base surface a
255
+ * lib author types a setup function against, and the interface the concrete
256
+ * `ServiceManifestClass` (in this same package) implements. It names the three
257
+ * runtime registration methods (`add` / `addFactory` / `addValue`) plus `build`.
258
+ *
259
+ * It is also the interface-first public surface a di consumer holds: di's public
260
+ * `ServiceManifest` type is `ServiceManifestBase<S, ServiceProvider<S>>` (not the
261
+ * impl class), so the type-driven authoring forms (`add<I>(C)`, `addFactory<I>(fn)`,
262
+ * `addValue<I>(v)`) the `@rhombus-std/di.transformer` DECLARATION-MERGES onto this
263
+ * interface surface on a consumer's `services.add<I>(...)`. An interface picks up
264
+ * those merged overloads; the impl class would not — the same reason the provider
265
+ * surface is an interface.
266
+ *
267
+ * `Provider` is the type `build()` returns. A core-only lib author never calls
268
+ * `build()` (the application does), so it defaults to `unknown`; `@rhombus-std/di`
269
+ * binds it to the concrete `ServiceProvider<Scopes>` when its class implements
270
+ * this interface. Keeping it generic is what lets this interface live in the
271
+ * types-only substrate without referencing di's runtime provider type.
272
+ */
273
+ interface ServiceManifestBase<Scopes extends string = "singleton", Provider = unknown> {
274
+ /**
275
+ * Class registration — a string token bound to a concrete constructor. The
276
+ * optional third `signatures` arg carries the positional dep signatures ON the
277
+ * registration (a lib author authors them as plain `DepSlot` data literals).
278
+ */
279
+ add(token: Token, ctor: Ctor, signatures?: readonly (readonly DepSlot[])[]): AddBuilder<Scopes>;
280
+ /**
281
+ * Factory registration — a string token bound to a factory function, its call
282
+ * parameters injected by the optional third `signatures` arg.
283
+ */
284
+ addFactory(token: Token, factory: Func<any[], unknown>, signatures?: readonly (readonly DepSlot[])[]): AddBuilder<Scopes>;
285
+ /** Value registration — an already-built instance, no deps and no lifetime. */
286
+ addValue(token: Token, value: unknown): void;
287
+ /** Seals the collection and returns the built provider. */
288
+ build(): Provider;
289
+ }
290
+
291
+ /**
292
+ * The minimal resolution surface — resolve tokens and get factories. A factory
293
+ * (or ctor) parameter typed `Resolver` is injected with the live provider view:
294
+ * the type derives the intrinsic provider token (`RESOLVER_TOKEN`), which the
295
+ * engine resolves to the view relative to the resolving frame — "I want the
296
+ * provider" is plain DI, no dedicated slot kind.
297
+ *
298
+ * `resolve` has two published shapes here; the tokenless authoring form
299
+ * `resolve<T>()` (and the factory form `resolve<F>()`) is a PURE TYPING the
300
+ * `@rhombus-std/di.transformer` DECLARATION-MERGES onto THIS interface (via
301
+ * `declare module "@rhombus-std/di.core"`), so it lights up only when the
302
+ * transformer is in the TypeScript program. Merging onto the interface (rather
303
+ * than a separate carrier) is what lets both a factory parameter typed `Resolver`
304
+ * AND the `ServiceProvider` interface a consumer holds pick up the authored form
305
+ * — an interface inherits a base interface's merged overloads; a class would not,
306
+ * which is exactly why the public provider surface is this interface, not the
307
+ * impl class.
308
+ * - `resolve<T>(token)` — explicit token, typed return.
309
+ * - `resolve(token)` — explicit token, `unknown` return (dynamic).
310
+ */
311
+ interface Resolver {
312
+ resolve<T>(token: Token): T;
313
+ resolve(token: Token): unknown;
314
+ /**
315
+ * Resolves asynchronously — the only path that may satisfy `T` via a
316
+ * `Promise<T>` registration. Always returns a Promise; a lookup miss whose
317
+ * honest `Promise<T>` registration exists is awaited and delivers `T`.
318
+ */
319
+ resolveAsync<T>(token: Token): Promise<T>;
320
+ resolveAsync(token: Token): Promise<unknown>;
321
+ /**
322
+ * Non-throwing resolve — returns the resolved instance, or `undefined` when
323
+ * `token` is UNREGISTERED. Mirrors the reference DI's nullable `GetService<T>`
324
+ * against `resolve`'s throwing `GetRequiredService` (#25). A bare nullable, not
325
+ * a tuple: a resolved service is always a truthy instance, so `undefined`
326
+ * unambiguously means "not registered".
327
+ *
328
+ * Only an unregistered TOKEN yields `undefined`. A registered token whose
329
+ * construction fails for another reason (a missing dependency, a cycle, an
330
+ * async-only construction) throws exactly as `resolve` would — `tryResolve`
331
+ * softens the "is it registered?" miss, nothing else.
332
+ *
333
+ * The tokenless authoring form `tryResolve<T>()` is the pure typing the
334
+ * `@rhombus-std/di.transformer` DECLARATION-MERGES onto this interface.
335
+ * - `tryResolve<T>(token)` — explicit token, typed nullable return.
336
+ * - `tryResolve(token)` — explicit token, `unknown` return (dynamic).
337
+ */
338
+ tryResolve<T>(token: Token): T | undefined;
339
+ tryResolve(token: Token): unknown;
340
+ /**
341
+ * A token-based registration predicate — `true` when `token` would resolve
342
+ * (a registration exists, directly or via an open-generic closing), `false`
343
+ * otherwise. Mirrors the reference DI's `IServiceProviderIsService.IsService`
344
+ * (#23); being token-based, it also covers the keyed case in one method. Does
345
+ * NOT attempt construction — a registered token whose dependencies are missing
346
+ * still reports `true` (it IS a service; building it is a separate concern).
347
+ *
348
+ * The tokenless authoring form `isService<T>()` is the pure typing the
349
+ * `@rhombus-std/di.transformer` DECLARATION-MERGES onto this interface.
350
+ */
351
+ isService(token: Token): boolean;
352
+ /**
353
+ * Returns a FACTORY for `type` rather than an instance. When `params` is
354
+ * absent or empty, returns a strict zero-arg `() => T` — every ctor slot must
355
+ * resolve from the container. When `params` is present, it is the complete
356
+ * authored-order list of caller-supplied parameter tokens; the returned factory
357
+ * has shape `(...params) => T`. The authored `resolve<(a: A) => T>()` lowers
358
+ * to `resolveFactory("pkg:T", ["pkg:A"])`.
359
+ */
360
+ resolveFactory(type: Token, params?: readonly Token[]): unknown;
361
+ }
362
+ /**
363
+ * The scope-creation surface. Injected into factory parameters typed
364
+ * `ScopeFactory`, and implemented by the provider impl. `createScope` returns
365
+ * the `ServiceProvider` INTERFACE (the abstractions seam), never the impl class.
366
+ */
367
+ interface ScopeFactory<S extends string = string> {
368
+ createScope(...args: "scoped" extends S ? [name?: S] : [name: S]): ServiceProvider<S>;
369
+ }
370
+ /**
371
+ * The PUBLIC container surface a consumer holds — the abstractions seam mirroring
372
+ * MEDI's `IServiceProvider`. Composes the resolution surface (`Resolver`, which
373
+ * carries the tokenless authoring forms via `ResolverAuthoring`), scope creation
374
+ * (`ScopeFactory`), and native `Disposable` / `AsyncDisposable`. The concrete
375
+ * `ServiceProviderClass` in `@rhombus-std/di` implements this; `build()` and
376
+ * `createScope()` return it rather than the class so consumers program against
377
+ * the interface.
378
+ *
379
+ * `S` is the user-declared scope-name union.
380
+ */
381
+ interface ServiceProvider<S extends string = string> extends Resolver, ScopeFactory<S>, Disposable, AsyncDisposable {
382
+ /**
383
+ * The name of this provider's open scope frame. Throws if the provider is
384
+ * frameless (no scope open — e.g. the provider straight from `build()`).
385
+ */
386
+ readonly name: S;
387
+ /**
388
+ * Closes this provider synchronously, disposing the instances its scope frame
389
+ * owns in reverse construction order. Throws `AsyncDisposalRequiredError` if an
390
+ * owned instance is a pending Promise. Idempotent.
391
+ */
392
+ dispose(): void;
393
+ /**
394
+ * Closes this provider asynchronously, awaiting owned Promise-valued instances
395
+ * before disposing them in reverse construction order. Idempotent.
396
+ */
397
+ disposeAsync(): Promise<void>;
398
+ }
399
+ /**
400
+ * @deprecated Use `Resolver` instead. Kept for backwards compatibility.
401
+ *
402
+ * The resolution surface a factory receives when it declares a provider-typed
403
+ * parameter. Like `Resolver`, its token is intrinsic — the engine fills the
404
+ * parameter with the live provider view — with `createScope` added.
405
+ */
406
+ interface ResolveScope extends Resolver {
407
+ createScope(name: string): ServiceProvider;
408
+ }
409
+ /**
410
+ * The named lifetime tag for a registration. `"singleton"` and `"transient"`
411
+ * are the built-in names; `U` is the user-declared scope-name union (defaults
412
+ * to `"scoped"`). Transient is represented by the ABSENCE of a lifetime tag
413
+ * (`undefined` on the registration), not by the string `"transient"`.
414
+ */
415
+ type Lifetime<U extends string = "scoped"> = "singleton" | "transient" | U;
416
+
417
+ /**
418
+ * A registration-level factory function. Its parameters are filled by the
419
+ * engine at resolve time, the same way a class constructor's are: each parameter
420
+ * is resolved by its slot (token → resolved instance, provider token → the live
421
+ * provider view, hole → caller-supplied). A factory with no signatures runs with
422
+ * no injected args — it declares the deps it wants, nothing is auto-supplied.
423
+ *
424
+ * May be async — it can return a `Promise<T>`. The container never awaits; the
425
+ * Promise flows through the sync resolution channel as a value (§"Async as
426
+ * values"). A consumer that depends on it declares `Promise<T>` and awaits.
427
+ */
428
+ type Factory = Func<any[], unknown>;
429
+ /**
430
+ * Builds an instance from its resolved positional args. The single normalized
431
+ * form the three authoring kinds collapse into at registration time:
432
+ * - class → `(...a) => new Ctor(...a)`
433
+ * - value → `() => value`
434
+ * - factory → the factory function itself
435
+ */
436
+ type Producer = Func<any[], unknown>;
437
+ /**
438
+ * A single normalized registration — ONE "producer" shape for all three
439
+ * authoring kinds (class / value / factory). The builder wraps each into a
440
+ * `produce` closure at registration time (see `Producer`); the engine dispatches
441
+ * on this one shape, calling `produce(...args)` uniformly rather than switching
442
+ * on a `kind` discriminant.
443
+ */
444
+ interface Registration {
445
+ /** Builds the instance from the resolved positional args (see `Producer`). */
446
+ readonly produce: Producer;
447
+ /**
448
+ * The lifetime — the scope name that owns and caches the instance. `undefined`
449
+ * means transient (never cached; produced fresh per resolve). A value is
450
+ * always transient: a value IS its instance, so ownership/caching is moot and
451
+ * a value that is itself a `Promise` is returned raw, never awaited.
452
+ */
453
+ readonly scope: string | undefined;
454
+ /**
455
+ * Registration-carried dep signatures — the positional slots that feed
456
+ * `produce`, and the sole signature channel now that the global metadata store
457
+ * is retired. Emitted inline by the transformer (`add`/`addFactory` third arg)
458
+ * and hand-fed by a plugin-less caller. Absent or empty means `produce` takes
459
+ * no injected args (a zero-arg ctor, a value, or a signature-less factory).
460
+ */
461
+ readonly signatures?: readonly (readonly DepSlot[])[];
462
+ /**
463
+ * The producer's diagnostic name — the ctor / factory name, carried EXPLICITLY
464
+ * because a wrapper closure (`(...a) => new Ctor(...a)`) reports `""` for its
465
+ * own `.name`. Empty string for a value. Feeds the `MissingMetadataError` /
466
+ * `NoSatisfiableSignatureError` diagnostics.
467
+ */
468
+ readonly name: string;
469
+ /**
470
+ * The original constructor arity (`Ctor.length`), carried EXPLICITLY because a
471
+ * rest-param wrapper reports `0` for its own `.length`. Drives the
472
+ * missing-metadata signal: a signature-less producer whose `arity` is nonzero
473
+ * (a class ctor that needs args) throws `MissingMetadataError`. `0` for a value
474
+ * or a factory — a signature-less factory simply runs with no injected args.
475
+ */
476
+ readonly arity: number;
477
+ }
478
+ /**
479
+ * An OPEN registration — a class bound to an open template token whose type
480
+ * arguments are all holes (`pkg:IRepo<$1>`). It never resolves directly;
481
+ * resolving a closed token that misses the exact map matches against these
482
+ * (base + arity + repeated-hole equality, last registered wins), substitutes
483
+ * the closing's arg tokens through the carried signatures, and synthesizes an
484
+ * ordinary class `Registration` (a ctor-wrapping producer) memoized per closed
485
+ * token.
486
+ */
487
+ interface OpenRegistration {
488
+ /** The full template token as registered (`pkg:IRepo<$1>`). */
489
+ readonly template: Token;
490
+ /** The template's base (`pkg:IRepo`) — the open-table key. */
491
+ readonly base: Token;
492
+ /**
493
+ * The parsed top-level args of the template — each exactly a hole (`$N`).
494
+ * Length is the arity; repeated holes (`["$1","$1"]`) constrain a match to
495
+ * equal arg tokens.
496
+ */
497
+ readonly pattern: readonly Token[];
498
+ readonly ctor: Ctor;
499
+ /** The lifetime tag, applied per closing. `undefined` means transient. */
500
+ readonly scope: string | undefined;
501
+ /**
502
+ * The template dep signatures (holes and `TypeArgRef`s still open) —
503
+ * substituted per closing. When absent, the closing has no template to
504
+ * substitute (a zero-arg ctor closes to a bare `new Ctor()`).
505
+ */
506
+ readonly signatures?: readonly (readonly DepSlot[])[];
507
+ }
508
+ /**
509
+ * The sealed, immutable snapshot a `ServiceManifestClass` hands to the engine.
510
+ * `ServiceManifestClass.seal()` deep-freezes its registration tables into this
511
+ * shape; `@rhombus-std/di`'s `build()` extension reads it to construct the
512
+ * provider (the engine-constructing half stays in the runtime package). This is
513
+ * the seam that lets the collection live in di.core while provider construction
514
+ * lives in di.
515
+ */
516
+ interface SealedManifest {
517
+ readonly registrations: ReadonlyMap<Token, readonly Registration[]>;
518
+ readonly openRegistrations: ReadonlyMap<Token, readonly OpenRegistration[]>;
519
+ }
520
+
521
+ /**
522
+ * The registration builder.
523
+ *
524
+ * `Scopes` is the union of declarable scope names — the tags `.as()` and
525
+ * `.createScope()` accept (default `"singleton"`). There is no root: scopes are
526
+ * uniform tags, and `"singleton"` is just a tag you happen to open once at the
527
+ * top. `"transient"` is NOT a member — transient is the absence of a scope, not
528
+ * a scope. A registration whose tagged scope is not open at resolution time
529
+ * resolves transiently (fresh instance, no cache).
530
+ *
531
+ * @example
532
+ * ```ts
533
+ * const services = new ServiceManifest<"singleton" | "request">();
534
+ * services.add("pkg:ILogger", ConsoleLogger).as("singleton"); // lowered form
535
+ * const provider = services.build(); // no frame pre-opened
536
+ * const app = provider.createScope("singleton"); // open the singleton frame
537
+ * const logger = app.resolve<ILogger>("pkg:ILogger");
538
+ * const req = app.createScope("request"); // nested child scope
539
+ * ```
540
+ *
541
+ * NOTE: this is the IMPLEMENTATION class. The public `ServiceManifest` TYPE
542
+ * (below) is the interface consumers hold; the public `ServiceManifest` VALUE
543
+ * (`new ServiceManifest<S>()`) lives in `@rhombus-std/di`, which also patches
544
+ * `build()` onto this prototype. The class is exported so cross-package fluent
545
+ * augmentations can prototype-patch it (their authored typings merge onto the
546
+ * di.core interfaces, never onto this class directly).
547
+ */
548
+ declare class ServiceManifestClass<Scopes extends string = "singleton"> implements ServiceManifestBase<Scopes, ServiceProvider<Scopes>> {
549
+ #private;
550
+ constructor();
551
+ /**
552
+ * Class registration — a string token bound to a concrete constructor. The
553
+ * runtime form: what the transformer emits for a class, and what a
554
+ * plugin-less caller writes directly. Returns the `.as(scope?)` continuation.
555
+ *
556
+ * The optional third `signatures` param carries the dep signatures ON the
557
+ * registration record — the sole signature channel now that the global
558
+ * metadata store is retired. The transformer emits it inline for every
559
+ * constructed class (`add(token, ctor, [[...]])`); a plugin-less caller
560
+ * hand-feeds it directly. Keying signatures on the registration (not on the
561
+ * ctor object) is what lets one JS class close differently per registration —
562
+ * an open template and its closings never collide.
563
+ *
564
+ * An OPEN template token (`pkg:IRepo<$1>` — every type arg a hole) routes
565
+ * into the open-registration table instead of the exact map; resolution
566
+ * closes it per requested token. Mixing concrete args and holes in the
567
+ * service token throws (v1 all-holes rule).
568
+ */
569
+ add(token: Token, ctor: Ctor, signatures?: readonly (readonly DepSlot[])[]): AddBuilder<Scopes>;
570
+ /**
571
+ * Factory registration — a string token bound to a factory function. The
572
+ * runtime form the transformer emits for an authored `add<I>(fn)` /
573
+ * `addFactory<I>(fn)`, and what a plugin-less caller writes directly.
574
+ *
575
+ * Parameter injection follows the metadata rule (see `ServiceProvider`): each
576
+ * parameter is injected by its slot from the registration-carried signatures
577
+ * (the optional third arg, emitted inline by the transformer). A factory that
578
+ * wants the live provider declares it as an ordinary parameter (a provider-typed
579
+ * slot); a signature-less factory simply runs with no injected args — nothing is
580
+ * auto-supplied. Returns the `.as(scope?)` continuation so a factory caches at a
581
+ * named scope exactly like a class.
582
+ *
583
+ * The implementation signature admits the single-arg authoring form
584
+ * (`addFactory<I>(fn)`) so the `@rhombus-std/di.transformer` overload merges onto it —
585
+ * that form never runs post-transform, and the runtime guard below fails a
586
+ * plugin-less call loud rather than registering junk (mirrors `add`).
587
+ */
588
+ addFactory(token: Token, factory: Factory, signatures?: readonly (readonly DepSlot[])[]): AddBuilder<Scopes>;
589
+ /**
590
+ * Value registration — an already-built instance, no deps and no lifetime.
591
+ * Separate from `add` because a value may itself be a function (a callable
592
+ * service), which is structurally indistinguishable from a factory inside one
593
+ * overload. The authoring form `addValue<I>(v)` (which lowers to
594
+ * `addValue("token", v)`) is a PURE TYPING contributed by the
595
+ * `@rhombus-std/di.transformer` augmentation, not part of di's published surface.
596
+ */
597
+ addValue(token: Token, value: unknown): void;
598
+ /**
599
+ * Seals the collection into an immutable snapshot — the SEALING half of
600
+ * `build()`. Deep-freezing the maps and each per-token list ensures that any
601
+ * `.add()` call on the builder after sealing cannot mutate what the provider
602
+ * and its descendants see — the container's view is fixed at build time.
603
+ *
604
+ * This is the collection's own concern, so it lives here in di.core. The
605
+ * ENGINE-CONSTRUCTING half — turning this snapshot into a `ServiceProvider` —
606
+ * is a `@rhombus-std/di` extension (`build()` below), because it needs the
607
+ * runtime resolution engine di.core deliberately does not depend on.
608
+ */
609
+ seal(): SealedManifest;
610
+ /**
611
+ * Seals the collection and returns the built `ServiceProvider`.
612
+ *
613
+ * The IMPLEMENTATION lives in `@rhombus-std/di`, not here — mirroring the
614
+ * reference DI split where the collection ships in the abstractions package
615
+ * but the provider-building entry is a runtime-package extension. Importing
616
+ * `@rhombus-std/di` PROTOTYPE-PATCHES this method onto `ServiceManifestClass`
617
+ * at load time (`services.seal()` → `new ServiceProviderClass(...)`), exactly
618
+ * how a cross-package fluent-authoring augmentation patches the concrete
619
+ * builder. The stub below is what runs if the runtime was never imported.
620
+ *
621
+ * NO frame is pre-opened: the returned provider is frameless. There is no
622
+ * root scope — resolving a tagged registration with no matching frame open
623
+ * yields a transient instance, and an untagged registration is transient as
624
+ * always. Open a scope explicitly with `createScope(name)` when you want a
625
+ * tagged registration to cache.
626
+ */
627
+ build(): ServiceProvider<Scopes>;
628
+ }
629
+ /**
630
+ * The public registration-builder INTERFACE a di consumer holds — the
631
+ * `ServiceManifestBase` interface bound to the concrete provider `build()`
632
+ * returns (the ME `IServiceCollection` analog). Interface-first (not the impl
633
+ * class) so the `@rhombus-std/di.transformer` augmentation — which merges the
634
+ * authored `add<I>()` / `.as<"scope">()` forms onto `ServiceManifestBase` —
635
+ * surfaces on a consumer typing against `ServiceManifest<S>`. A class would not
636
+ * inherit those augmented overloads; the interface does.
637
+ *
638
+ * The constructor side (`ServiceManifestCtor`) and the constructible
639
+ * `ServiceManifest` VALUE live in `@rhombus-std/di`, alongside the `build()`
640
+ * prototype-patch that makes `new ServiceManifest().build()` produce a provider.
641
+ */
642
+ type ServiceManifest<S extends string = "singleton"> = ServiceManifestBase<S, ServiceProvider<S>>;
643
+
644
+ /** True when `slot` is a `FactoryRef` (carries a `.type` token). */
645
+ declare function isFactoryRef(slot: DepSlot): slot is FactoryRef;
646
+ /** True when `slot` is a `Union` (carries a `.union` array of member slots). */
647
+ declare function isUnionSlot(slot: DepSlot): slot is Union;
648
+ /**
649
+ * True when `slot` is a `LiteralRef` — an object slot carrying a `value` key.
650
+ * The value supplies a singular literal directly (`"dev"`, `42`, `true`, `1n`)
651
+ * OR the lone inhabitant of `void` / `undefined` / `null`.
652
+ *
653
+ * Identified by the PRESENCE of the `value` key (`"value" in slot`), never by
654
+ * `value !== undefined` — `value` is legitimately `undefined` for the
655
+ * `void`/`undefined` case. No other slot kind (FactoryRef `.type`, Union
656
+ * `.union`) carries a `value` key, so this is unambiguous.
657
+ */
658
+ declare function isLiteralRef(slot: DepSlot): slot is LiteralRef;
659
+ /**
660
+ * True when `slot` is a `TypeArgRef` — an object slot carrying a numeric
661
+ * `typeArg` key (the 1-based hole number). Key-disjoint from every other slot
662
+ * kind (FactoryRef `.type`, Union `.union`, LiteralRef `.value`), so the check
663
+ * is unambiguous.
664
+ */
665
+ declare function isTypeArgRef(slot: DepSlot): slot is TypeArgRef;
666
+
667
+ /**
668
+ * Constructs a `Union` slot — a set of alternative dependency slots tried in
669
+ * declaration order. The first resolvable member wins; if none is resolvable,
670
+ * resolution throws.
671
+ *
672
+ * @example
673
+ * ```ts
674
+ * services.add("pkg:IHandler", Handler, [[
675
+ * union("pkg:IRedis", "pkg:IMemoryCache"),
676
+ * "pkg:ILogger",
677
+ * ]]);
678
+ * ```
679
+ */
680
+ declare function union(...slots: DepSlot[]): Union;
681
+ /**
682
+ * Constructs a `TypeArgRef` slot — a parameter that receives the TOKEN STRING
683
+ * of the registration's `n`th type argument (1-based, matching `$n`). Used on
684
+ * the manual authoring surface for hole-template signatures; substitution
685
+ * closes it into a literal value slot per closing.
686
+ *
687
+ * @example
688
+ * ```ts
689
+ * services.add("app/IRepo<$1>", SqlRepository, [[typeArg(1), "app/IDb"]]);
690
+ * ```
691
+ */
692
+ declare function typeArg(n: number): TypeArgRef;
693
+
694
+ /**
695
+ * Renders the canonical closed-generic form `base<arg1,arg2>`. With no args,
696
+ * returns `base` unchanged. Args may themselves be closed-generic tokens
697
+ * (nesting) or holes (`$N` — producing an open template).
698
+ */
699
+ declare function closeToken(base: Token, ...args: Token[]): Token;
700
+ /**
701
+ * Parses a closed-generic token into its base and top-level args.
702
+ *
703
+ * Returns `undefined` for non-generic tokens (no top-level `<`) AND for
704
+ * malformed input (empty base, unbalanced brackets, empty arg, trailing text
705
+ * after the closing `>`, unterminated quote) — callers fall through to their
706
+ * exact-match / unregistered-token handling either way.
707
+ *
708
+ * The scan is depth-tracked over `<` / `>` and quote-aware for double quotes
709
+ * (backslash escapes honored), so literal-type args like `"a,b" | "<c>"` split
710
+ * correctly.
711
+ */
712
+ declare function parseToken(token: Token): ParsedToken | undefined;
713
+ /**
714
+ * True when `token` contains a hole (`$N`) at any depth — i.e. it is an open
715
+ * template rather than a resolvable token. Grammar-aware: a `$N` inside a
716
+ * quoted literal arg is NOT a hole.
717
+ */
718
+ declare function isOpenToken(token: Token): boolean;
719
+ /**
720
+ * Substitutes hole nodes in an open template with the supplied argument tokens
721
+ * (1-based: `$1` → `args[0]`). Grammar-aware and recursive — a node that is
722
+ * exactly `$N` is replaced by the arg token (which may itself be
723
+ * closed-generic); this is NOT a naive string replace, so a `$N` inside a
724
+ * quoted literal arg survives untouched.
725
+ *
726
+ * Throws `RangeError` when the template references a hole beyond the supplied
727
+ * args — callers match arity before substituting.
728
+ */
729
+ declare function substituteToken(template: Token, args: readonly Token[]): Token;
730
+ /**
731
+ * Substitutes the supplied argument tokens through every slot of every
732
+ * signature, producing the closed signatures for one closing of an open
733
+ * registration:
734
+ * - a string token → `substituteToken`,
735
+ * - a `FactoryRef` → `type` and each `params` token substituted,
736
+ * - a `Union` → members substituted recursively,
737
+ * - a `TypeArgRef` → a `LiteralRef` carrying `args[typeArg - 1]` (the
738
+ * substituted argument's token string),
739
+ * - a `LiteralRef` → unchanged.
740
+ */
741
+ declare function substituteSignatures(signatures: readonly (readonly DepSlot[])[], args: readonly Token[]): readonly (readonly DepSlot[])[];
742
+
743
+ /**
744
+ * The token a `Resolver`-typed parameter derives to. The engine resolves it to
745
+ * the live provider view (the scope-generic-free `Resolver` surface, per #24)
746
+ * relative to the resolving frame, rather than to a registration. Exported so a
747
+ * plugin-less author can hand-feed it in a signature (`[[RESOLVER_TOKEN]]`)
748
+ * without spelling the package-qualified string by hand.
749
+ */
750
+ declare const RESOLVER_TOKEN: Token;
751
+ /**
752
+ * True when `token` is an intrinsic provider token — one the engine resolves to
753
+ * the live provider view instead of a registration. Always satisfiable during
754
+ * signature selection, and reported as a service by `isService`.
755
+ */
756
+ declare function isProviderToken(token: Token): boolean;
757
+
758
+ /** Base class for every error the container raises. */
759
+ declare class DiError extends Error {
760
+ constructor(message: string);
761
+ }
762
+ /**
763
+ * An open template token was passed to a registration method that cannot
764
+ * accept one: `addValue`/`addFactory` (open registrations are class-only), or
765
+ * `add` with a template whose type arguments are not ALL holes (v1 forbids
766
+ * mixing concrete args and holes in the service token).
767
+ */
768
+ declare class OpenTokenRegistrationError extends DiError {
769
+ readonly token: Token;
770
+ readonly method: "add" | "addFactory" | "addValue";
771
+ constructor(token: Token, method: "add" | "addFactory" | "addValue");
772
+ }
773
+
774
+ export { DiError, OpenTokenRegistrationError, RESOLVER_TOKEN, ServiceManifestClass, closeToken, isFactoryRef, isLiteralRef, isOpenToken, isProviderToken, isTypeArgRef, isUnionSlot, parseToken, substituteSignatures, substituteToken, typeArg, union };
775
+ export type { $, AddBuilder, Ctor, DepRecord, DepSlot, DepTarget, Factory, FactoryRef, Hole, Inject, Lifetime, LiteralRef, OpenRegistration, OverloadedConstructorParameters, OverloadedParameters, ParsedToken, Producer, Registration, ResolveScope, Resolver, ScopeFactory, SealedManifest, ServiceManifest, ServiceManifestBase, ServiceProvider, Token, TypeArgRef, Typeof, Union };