import type { ReadonlyDeep } from "type-fest"; import type { CacheSpec } from "../types/00_CacheSpec.js"; import type { RequestPairedProducerResult } from "../types/05_RequestPairedProducer.js"; import type { AnyParams, AnyValidators, ConsumerRequest, RequestPairedProducer } from "../types/index.js"; /** * A single branch of a {@link producerByIdType} producer: pairs a runtime * type guard for a subset of the spec's ids with a handler whose return type * is narrowed to that subset's content. * * `NarrowedId` distributes over the spec's id types, so providing a guard * that only matches one variant gives a handler whose `req.id` is the * variant's id (literal or template-literal) and whose return type is * required to be content for *that* variant. */ export type ProducerBranch = NarrowedId extends Spec["id"] ? { readonly matches: (id: Spec["id"]) => id is NarrowedId; readonly handle: (req: ReadonlyDeep>, options?: { signal?: AbortSignal; }) => Promise>; } : never; /** * The fluent builder returned by {@link producerByIdType}. Use `.when(...)` * to add per-id-type branches; each call infers its own `NarrowedId` from * the type guard, so the handler's `req.id` is concrete and TypeScript can * fully verify the (id, content) correlation in the body. End the chain * with `.build()` to produce the final {@link RequestPairedProducer}. * * The phantom `Covered` parameter accumulates the union of `NarrowedId`s * supplied to each `.when(...)` call so that `.build()` can statically * verify the chain is exhaustive for `Spec["id"]`. When the chain is * non-exhaustive, `.build()` returns a {@link _NonExhaustiveBuildError} * whose tuple shape names the missing ids and is not assignable to * `RequestPairedProducer`; the resulting error surfaces at the call site * that consumes the build result (typically `wrapProducer`). */ export type ProducerByIdTypeBuilder = { readonly when: (matches: (id: Spec["id"]) => id is NarrowedId, handle: (req: ReadonlyDeep>, options?: { signal?: AbortSignal; }) => Promise>) => ProducerByIdTypeBuilder; readonly build: () => _BuildResult; }; /** * Error type produced by `.build()` when the builder hasn't covered every * id type in `Spec["id"]`. Surfaces as a TS error wherever the build result * is used (e.g., the `wrapProducer(...)` call site), naming the missing * ids in the message. */ type _NonExhaustiveBuildError = readonly [ "producerByIdType: builder is non-exhaustive; missing `.when(...)` branches for these ids:", Missing ]; type _BuildResult = [Exclude] extends [never] ? RequestPairedProducer : _NonExhaustiveBuildError>; /** * Builds a {@link RequestPairedProducer} for a multi-id-type cache from a * sequence of per-id-pattern branches. * * This is the recommended way to write per-id-typed producers. Each branch's * `handle` function is non-generic over `Id`, so the request's id is a * concrete (template-literal or branded) type and TypeScript can fully * verify the (id, content) correlation within its body. The unsafe * `Id`-bridging cast that a hand-written multi-id producer would require * lives once, inside this helper, and is justified by the type guards * provided to each `.when(...)`. * * Branches are tried in declaration order. If no branch matches a request's * id at runtime, the returned producer rejects with a descriptive error. * (Use exhaustive specs and matching guards to avoid this in production * code paths.) * * @example * type StoriesSpec = * | CacheSpec<`story:${string}`, Story> * | CacheSpec<`collection:${string}`, Story[]>; * * const fetcher = wrapProducer(cache, options, * producerByIdType() * .when(idStartsWith("story:"), async (req) => ({ * // req.id: `story:${string}` ⇒ content must be Story * content: { id: req.id, title: `Story ${req.id}` }, * directives: { freshUntilAge: 1 }, * })) * .when(idStartsWith("collection:"), async (req) => ({ * // req.id: `collection:${string}` ⇒ content must be Story[] * content: [{ id: "1", title: "a" }], * directives: { freshUntilAge: 1 }, * })) * .build(), * ); */ export declare function producerByIdType(): ProducerByIdTypeBuilder; /** * Type guard helper: matches ids whose runtime value starts with a prefix. * Useful with template-literal-keyed specs (e.g., `\`story:${string}\``) * when used with {@link producerByIdType}. * * The returned guard is generic in the input id, so passing it to a * `producerByIdType` branch correctly narrows the spec's id union down to * its `${Prefix}${string}` constituents. */ export declare function idStartsWith(prefix: Prefix): (id: Id) => id is Extract; export {}; //# sourceMappingURL=producerByIdType.d.ts.map