import ts from 'typescript'; type Func = (...args: Args) => Return; interface Ctor { new(...args: Args): Instance; prototype: Instance; } /** * ONE overload's own non-call properties, carried across the peeling recursion so * a callable-with-statics keeps them. `Pick` is `{}` for a bare * function type and the static side for a constructor type. */ type OverloadProps = Pick; /** * Peel an intersection of call signatures (an overloaded function type) into a * UNION of its individual signatures. The technique (Vojtěch Mašek / type-fest): * `infer` matches the LAST signature and emits it, then recurses with the * accumulator intersected back in so the next match resolves to the PRECEDING * overload. Bounded — each step strips one signature, terminating once the * accumulator already subsumes the whole overload set (`TAccumulator extends * TOverload`). */ type OverloadUnionRecursive = TOverload extends (...args: infer TArgs) => infer TReturn ? TAccumulator extends TOverload ? never : OverloadUnionRecursive TReturn) & OverloadProps> | ((...args: TArgs) => TReturn) : never; /** * The UNION of a function type's individual call-signature overloads. Seeds the * recursion with a `() => never` overload hoisted to the FRONT of the * intersection (required for the bounded recursion to fire), then excludes that * sentinel from the result unless `T` genuinely is `() => never`. */ type OverloadUnion any> = Exclude never) & T>, T extends () => never ? never : () => never>; /** * Every overload's parameter tuple for a function type `T`, as a union — the * overload-faithful analog of the builtin `Parameters`. For a `T` with * signatures `(a: A)` and `(a: B, b: C)` this is `[a: A] | [a: B, b: C]`; a * single-overload function yields its one tuple. */ type OverloadedParameters any> = Parameters>; /** The construct-signature counterpart of {@link OverloadProps} — the static side. */ type ConstructorOverloadProps = Pick; /** * The construct-signature counterpart of {@link OverloadUnionRecursive}: peels an * intersection of CONSTRUCT signatures (an overloaded constructor type) into a * union of its individual signatures. A concrete `new` is used, NOT `abstract * new`: intersecting an abstract construct signature with a concrete class's * `new` signatures derails overload inference (it collapses to `any`), and the * sole consumer — a factory that does `new C(...args)` — needs a concrete * constructor anyway. */ type ConstructorOverloadUnionRecursive = TOverload extends new (...args: infer TArgs) => infer TReturn ? TAccumulator extends TOverload ? never : ConstructorOverloadUnionRecursive TReturn) & ConstructorOverloadProps> | (new (...args: TArgs) => TReturn) : never; /** The construct-signature counterpart of {@link OverloadUnion}. */ type ConstructorOverloadUnion any> = Exclude never) & T>, T extends new () => never ? never : new () => never>; /** * Every construct-overload's parameter tuple for a constructor type `T`, as a * union — the overload-faithful analog of the builtin `ConstructorParameters`. * For a `C` with constructors `(a: A)` and `(a: B, b: C)`, * `OverloadedConstructorParameters` is `[a: A] | [a: B, b: C]`; a * single-overload ctor yields its one tuple and a zero-arg ctor yields `[]`. * Constrained to a concrete (`new`-able) constructor — an abstract class has no * constructible instance, and the factory that consumes this must `new` its * argument. */ type OverloadedConstructorParameters any> = ConstructorParameters>; declare module "@fnioc/di" { interface ServiceManifestClass { /** * Type-driven class authoring — lowers to `add("token", C)`. The ctor is * typed `Ctor` (a plain construct signature, so an abstract class * is rejected). Never runs post-transform. * * A GENERIC impl is authored as an instantiation expression — * `add>>(SqlRepository<$<1>>)` (open template) or * `add>(SqlRepository)` (closed) — and lowers to * `add("token", C, signatures)` with its dep signatures carried on the * registration (type args stripped from the emitted ctor). */ add(ctor: Ctor): AddBuilder; /** * Registration-time override form — a sparse positional override array for a * class whose ctor you can't edit (third-party / generic). Each element * overrides the transformer-derived token at that position; `undefined` (or * an array hole) keeps the derived token. Lowers to * `add("token", C, [[...merged...]])`. Never runs post-transform. * * add(RedisCache, ["pkg:IRedisClient", undefined, "pkg:ILogger"]) */ add(ctor: Ctor, overrides: readonly (string | undefined)[]): AddBuilder; /** * Type-driven factory authoring — lowers to `addFactory("token", fn)` (the * transformer knows the arg is a function). Never runs post-transform. */ add(factory: Func): AddBuilder; /** * Type-driven factory authoring, EXPLICIT form — `addFactory(fn)` lowers to * `addFactory("token", fn)`. Mirrors `add(factory)`; the explicit method * name documents intent at the call site (a factory, never a class). It * coexists with di's runtime `addFactory(token, factory, signatures?)` overload * — arity disambiguates (one value arg here vs. the runtime form's leading * string token). Never runs post-transform. */ addFactory(factory: Func): AddBuilder; /** * Type-driven value authoring — lowers to `addValue("token", v)`. Never runs * post-transform. */ addValue(value: I): void; } interface ScopeAddAuthoring { /** * Authored class form — `addRequest(C)` lowers to `add("token", C).as("request")`. * Mirrors `add(ctor)`, with the scope baked into the method name. */ (ctor: Ctor): void; /** * Authored factory form — `addRequest(fn)` lowers to * `addFactory("token", fn).as("request")`. Mirrors `add(factory)`. */ (factory: Func): void; } interface AddBuilder { /** * The AUTHORED lifetime form — `.as<"singleton">()`. The scope name is a * TYPE argument; the `S extends Scopes` bound is the compile-time * captive-misconfiguration guard. The transformer rewrites it to the * value-arg `.as("singleton")` before it runs. */ as(): void; } interface Resolver { /** * Tokenless authored resolve — `resolve()`. The transformer lowers it * to an explicit-token `resolve("token")` (or `resolveFactory` for a * function-typed arg) before runtime. */ resolve(): T; /** * Tokenless authored factory resolve — `resolve<(a: A, b: B) => T>()`. The * transformer lowers it to `resolveFactory("T-token", ["A-token", "B-token"])`. * Zero-param form `resolve<() => T>()` lowers to `resolveFactory("T-token")`. * Never runs post-transform. */ resolve any>(): ReturnType; } interface ServiceProvider { resolve(): T; resolve any>(): ReturnType; } } /** A diagnostic the transformer raises. Alias kept for call-site clarity. */ type Diagnostic = ts.Diagnostic; /** The sink the transformer writes diagnostics to (ts-patch supplies this). */ interface DiagnosticSink { addDiagnostic(diagnostic: Diagnostic): number; } /** * Stable numeric codes for transformer-emitted diagnostics. The high offset * keeps them clear of TypeScript's own code space. These are part of the * transformer's observable surface — tests assert on them. */ declare enum DiagnosticCode { /** A factory param's call signature doesn't match the target ctor's holes. */ FactorySignatureMismatch = 990003, /** * A constructor / factory parameter whose type has no derivable token and * carries no `Inject` brand — a hard compile error. */ UnderivableToken = 990006, /** * A type reaches token derivation while still referencing an UNBOUND type * parameter — a bare generic class registered without an instantiation * expression (`add>>(Foo)` instead of `Foo<$<1>>` / `Foo`), * or a type parameter leaking into a token position. Hard compile error. */ UnboundTypeParameter = 990007, /** * An open SERVICE token mixes concrete args and holes (`IFoo<$<1>,string>`, * `IFoo>>`). v1 requires every type arg of an open service token to * be a bare hole (`IFoo<$<1>,$<2>>`; repeats like `IFoo<$<1>,$<1>>` are allowed). */ MixedServiceTokenArgs = 990008, /** * An open template token on an `addValue` / factory registration. Open * registrations are class registrations only — a value or factory has no * per-closing construction the container could substitute into. */ OpenTokenOnValueOrFactory = 990009, /** * A dependency slot references a hole (`$N`) that the service template does * not bind — substitution at close time would have no argument for it. */ DepHoleNotInServiceTemplate = 990010, /** * A registration-time override array element (`add(C, [...])`) is neither a * string-literal token nor an `undefined`/elision gap — an object literal, a * variable, or a call the transformer cannot resolve statically. The element is * ignored and the derived token is kept; use a string-literal token override. */ UnresolvableOverrideElement = 990011 } /** Build a warning diagnostic anchored at `node` in `file`. */ declare function warning(file: ts.SourceFile, node: ts.Node, code: DiagnosticCode, messageText: string): Diagnostic; /** Build an error diagnostic anchored at `node` in `file`. */ declare function error(file: ts.SourceFile, node: ts.Node, code: DiagnosticCode, messageText: string): Diagnostic; /** * Create the `ts.TransformerFactory` that rewrites a SourceFile. Exposed so the * test harness can run the transformer against an in-memory Program without * ts-patch. */ declare function createTransformerFactory(program: ts.Program, sink: DiagnosticSink, options?: { readFile?: Func<[string], string | undefined>; }): ts.TransformerFactory; /** * Extras shape ts-patch passes to a program transformer. We only need * `addDiagnostic`; `ts` is the originating TypeScript instance. */ interface ProgramTransformerExtras { readonly ts: typeof ts; addDiagnostic(diagnostic: ts.Diagnostic): number; } /** * The ts-patch PROGRAM transformer entry point. Configure in `tsconfig.json`: * * ```jsonc * { * "compilerOptions": { * "plugins": [{ "transform": "@fnioc/transformer", "import": "transform" }] * } * } * ``` * * It does NOT alter the Program (it returns the same instance); the rewrite * runs via the returned `before` transformer factory during emit. Returning a * `TransformerBasePlugin` (with `before`) keeps TypeChecker access while letting * tsc drive the emit pipeline. */ declare function transform(program: ts.Program, _config: unknown, extras: ProgramTransformerExtras): { before: ts.TransformerFactory; }; /** * Compile-time token for a type. Rewritten by the @fnioc transformer to a * string literal; the runtime body only runs when the transformer is absent. * * @example * ```ts * const key = nameof(); // → "pkg/contracts:IUserRepo" at compile time * ``` */ declare function nameof(): string; interface TokenContext { readonly checker: ts.TypeChecker; /** * Project root used ONLY for the rootless best-effort token (a declaration * with no owning `package.json` up-tree). App-internal tokens are rendered * relative to their owning *package* root, not this directory. */ readonly projectRoot: string; /** * Reads a file's text for `package.json` discovery, or `undefined` if absent. * Defaults to `ts.sys.readFile` in production; the test harness injects a * reader that sees its virtual filesystem. */ readonly readFile?: Func<[string], string | undefined>; /** * Look up a program source file by its EXTENSION-STRIPPED absolute path (its * "stem"). Turns a package export entry's on-disk target (e.g. * `.../contracts/index.js`) into the declaration file the program actually * loaded (`.../contracts/index.d.ts`) so its module exports can be read for * package-public detection. Wired inside `createTransformerFactory`; when * absent (a hand-built context), package-public detection is skipped and the * type falls through to the app-internal / rootless branch. */ readonly sourceFileAtStem?: Func<[string], ts.SourceFile | undefined>; /** * True when a source file is a TypeScript default lib (`lib.es*.d.ts`). * A type declared there tokenizes as its BARE symbol name (`Promise`, `Map`) * — the lib path is machine-dependent and carries no identity. Wired to * `program.isSourceFileDefaultLibrary` in production; when absent, default-lib * symbols fall through to the (nondeterministic) path-based derivation. */ readonly isDefaultLib?: Func<[ts.SourceFile], boolean>; } /** * Classification of a parameter type for dep extraction. Only `resolvable` is * now representable — when no token can be derived the caller is responsible for * emitting a hard diagnostic (UnderivableToken). The `hole` variant has been * removed; there is no silent fallback. */ type TokenResult = { readonly kind: "resolvable"; readonly token: string; }; /** * Classify a constructor-parameter type into a token result. * * Returns `{ kind: "resolvable", token }` when a token can be derived, or * `undefined` only for an ANONYMOUS inline structure with no name (a `__type` * symbol, or a nameless non-intrinsic type). Every NAMED type tokenizes (Rule * 1): each intrinsic — `string`, `number`, `boolean`, `symbol`, `bigint`, * `any`, `unknown`, `void`, `never` — yields its keyword as a token, and a * literal yields its quoted/rendered token. An unregistered token simply misses * at runtime (UnregisteredTokenError); it is NOT a compile error. The caller * emits the `UnderivableToken` hard diagnostic only when `undefined` is returned * (anonymous structure) and no `Inject` brand is present. */ declare function tokenForType(type: ts.Type, ctx: TokenContext, failure?: DeriveFailure): TokenResult | undefined; /** * Inspect whether `type` carries the `Inject` brand and, if so, return * the literal string token `K`. Returns `undefined` when the type is not * branded. * * Detection strategy: `brandLiteralFor` walks the type's properties for one * that is a unique-symbol keyed optional property whose value type is a * string literal. That is exactly the shape of `declare const TOK: unique * symbol; T & { readonly [TOK]?: K }`. * * Union awareness: for a type like `(T & { [TOK]?: K }) | undefined` (which * arises from `x?: Inject` or `x: Inject | undefined`), * `getPropertiesOfType` returns only properties common to ALL union members — * `undefined` contributes none, so the brand is invisible. We handle this by * iterating union constituents, skipping nullish members, and checking each * non-nullish member individually. The first branded token found wins. */ declare function injectTokenFor(type: ts.Type, checker: ts.TypeChecker): string | undefined; /** * If `type` carries the `Hole` brand (an open-generic placeholder), * return the hole number `N`. Returns `undefined` when the type is not a hole. * * Detection mirrors `injectTokenFor` exactly via `brandLiteralFor`: walk the * type's properties (the checker flattens intersections, so the constrained * form `Hole<2, Entity>` — `Entity & { [HOLE]?: 2 }` — works) for one declared * as a computed-symbol property named `HOLE`, then extract the number literal * from its type. The brand property is optional, so its type is `N | * undefined` — the literal is pulled from the union. Works for the anonymous * unconstrained form `Hole<1>` (a `__type` with no aliasSymbol) and for * aliased/constrained forms alike. */ declare function holeNumberFor(type: ts.Type, checker: ts.TypeChecker): number | undefined; /** * Failure channel for `deriveToken` — an `undefined` return alone means * "underivable" (990006); when the failure was specifically an UNBOUND type * parameter reaching derivation (990007 territory), the field below is set so * the caller can emit the sharper diagnostic. Callers that don't care simply * omit the argument. */ interface DeriveFailure { unboundTypeParameter?: ts.Type; } /** * Derive the token string for a (already Promise-unwrapped) type. Returns * `undefined` for an anonymous structural type with no name (a `__type` * symbol or a nameless non-intrinsic) — the caller treats that as the * underivable hard-error case — and for an unbound type parameter (reported * through `failure` when supplied). Intrinsics tokenize by name (Rule 1); * literals by value; a `Hole`-branded type yields `$N`; a GENERIC type * reference recurses into its checker-resolved type arguments and renders the * canonical closed form `base`. */ declare function deriveToken(type: ts.Type, ctx: TokenContext, failure?: DeriveFailure): string | undefined; /** The value payload of a singular (Rule-2) type — may itself be `undefined`/`null`. */ type LiteralValue = string | number | boolean | bigint | undefined | null; /** * A factory slot in an extracted signature — the transformer's in-memory mirror * of the runtime `FactoryRef` shape. Emitted as `{ type: "" }` (or * `{ type: "", params: [...] }` when params are present) in the inline * signature array (the registration's third argument). */ interface FactorySlot { readonly type: string; readonly params?: readonly string[]; } /** * A scope slot — the transformer's in-memory mirror of the runtime `ScopeRef`. * Emitted as a `{ scope: true }` object literal. Produced for a parameter whose * type is `ResolveScope`: the engine fills it with the live resolution scope. */ interface ScopeSlot { readonly scope: true; } /** * A union slot — the transformer's in-memory mirror of the runtime `Union` shape. * Produced when a parameter's type annotation is an inline union type node * (`A | B`), NOT a named type alias referencing a union. Emitted as * `{ union: [slotA, slotB, ...] }` in the inline signature array. * Detection is purely syntactic (the annotation node shape). */ interface UnionSlot { readonly union: readonly Slot[]; } /** * A literal slot — the transformer's in-memory mirror of the runtime * `LiteralRef`. Produced for a SINGULAR (Rule-2) parameter: a literal (`"dev"`, * `42`, `true`, `1n`) OR a whole-type `void`/`undefined`/`null`. The value is * supplied directly, no container lookup. Emitted as `{ value: ... }` in the * inline signature array. A literal/nullish UNION (`"a" | "b"`, * `Foo | undefined`) is NOT a literal slot. `value` may itself be `undefined`, * so the slot is identified by the PRESENCE of the `value` key. */ interface LiteralSlot { readonly value: LiteralValue; } /** * A type-arg slot — the transformer's in-memory mirror of the runtime * `TypeArgRef`. Produced for a parameter typed `Typeof` where `T` is * bound to a Hole: the parameter receives the TOKEN STRING of the * registration's `typeArg`th type argument (1-based). Emitted as * `{ typeArg: N }` in the registration-carried signature array; substitution * closes it into a literal value slot per closing. A CONCRETE binding emits a * `LiteralSlot` with the derived token directly instead. */ interface TypeArgSlot { readonly typeArg: number; } /** * One positional slot: a token string, a factory ref, a scope ref, a union of * alternatives, a literal value, or a type-arg ref. There is no `null` / hole * sentinel — an unresolvable (anonymous-structure) type causes a hard compile * error (`UnderivableToken`). */ type Slot = string | FactorySlot | ScopeSlot | UnionSlot | LiteralSlot | TypeArgSlot; /** One emitted signature: positional slots (token / factory / scope / union / literal). */ type Signature = readonly Slot[]; /** True when a slot is a factory ref rather than a plain token / scope / union. */ declare function isFactorySlot(slot: Slot): slot is FactorySlot; /** True when a slot is a scope ref (`{ scope: true }`). */ declare function isScopeSlot(slot: Slot): slot is ScopeSlot; /** True when a slot is a union of alternatives (`{ union: [...] }`). */ declare function isUnionSlot(slot: Slot): slot is UnionSlot; /** * True when a slot is a type-arg ref (`{ typeArg: N }`). Key-disjoint from * every other slot kind, so the numeric check is sufficient. */ declare function isTypeArgSlot(slot: Slot): slot is TypeArgSlot; /** * Structural equality for slots. Two slots are equal when: * - both are the same string token * - both are factory refs with the same type and params * - both are scope refs * - both are union slots with element-wise equal members (recursive) * - both are literal slots with strictly-equal values * - both are type-arg refs with the same hole number */ declare function slotsEqual(a: Slot, b: Slot): boolean; interface ConstructorExtraction { /** The class symbol the constructor belongs to. */ readonly classSymbol: ts.Symbol; /** * The extracted signatures: one per DECLARED ctor overload, or a single * signature from the implementation when no overloads are declared (optional * params become union-with-`undefined`-fallback slots, not extra signatures). */ readonly signatures: Signature[]; } /** * Context required by dep-extraction helpers that emit diagnostics. * Extends TokenContext with the diagnostic sink and anchor source file. */ interface DepContext extends TokenContext { readonly sink: DiagnosticSink; readonly sourceFile: ts.SourceFile; } /** * Resolve the class a registration's concrete-argument expression refers to and * extract its constructor signature. Returns `undefined` when the expression * does not statically resolve to a class with a declaration (a dynamic * registration — the caller emits no dep array and warns). */ declare function extractFromExpression(expr: ts.Expression, ctx: DepContext): ConstructorExtraction | undefined; /** * Extract the constructor signatures from a class declaration. * * - DECLARED overloads (bodyless ctor declarations preceding the * implementation) are honored AS-IS: one emitted signature per declared * overload, in declaration order, with the implementation signature ignored * entirely (TS hides the impl from callers — so do we). Each overload's * params run the normal per-param rules (incl. the optional-union fallback). * - No declared overloads → the implementation signature drives extraction, * yielding exactly ONE signature (union-unification, no overload expansion). * - No explicit constructor (or a zero-param one) → a single empty signature. * * Parameter properties / modifiers are irrelevant — only param TYPES drive * token derivation. */ declare function extractSignatureFromClass(classDecl: ts.ClassDeclaration, ctx: DepContext): Signature[]; /** * Extract the constructor signature(s) of an INSTANTIATION EXPRESSION * registration arg (`add>>(SqlRepository<$<1>>)` — an * `ExpressionWithTypeArguments` in value position). The checker's construct * signatures on the EWTA's type are already INSTANTIATED (holes and concrete * args substituted for the class's type parameters), so the "inverted mapping" * (`Foo<$<2>,$<1>>`) falls out for free. Each param pairs its DECLARATION node * (syntactic classification: optional / FunctionTypeNode / UnionTypeNode) with * the instantiated type from `checker.getTypeOfSymbol` — the declaration node * alone would yield the unsubstituted type parameters. * * Returns `undefined` when the expression is not constructable or a parameter * cannot be read positionally (no declaration / rest param), so the caller * falls back to its non-EWTA handling. */ declare function extractInstantiatedSignature(ewta: ts.ExpressionWithTypeArguments, ctx: DepContext): Signature[] | undefined; interface CheckContext extends TokenContext { readonly sink: DiagnosticSink; readonly sourceFile: ts.SourceFile; } export { DiagnosticCode, createTransformerFactory, deriveToken, error, extractFromExpression, extractInstantiatedSignature, extractSignatureFromClass, holeNumberFor, injectTokenFor, isFactorySlot, isScopeSlot, isTypeArgSlot, isUnionSlot, nameof, slotsEqual, tokenForType, transform, transform as transformer, warning }; export type { CheckContext, ConstructorExtraction, DepContext, DeriveFailure, Diagnostic, DiagnosticSink, FactorySlot, OverloadedConstructorParameters, OverloadedParameters, ScopeSlot, Signature, Slot, TokenContext, TokenResult, TypeArgSlot, UnionSlot };