import type { StandardSchemaV1 } from "@standard-schema/spec"; import { type EventPublishOptions } from "../events/index.js"; /** * Any Standard Schema compatible validator. */ export type StandardSchema = StandardSchemaV1; /** * Infer the parsed output type from a Standard Schema. */ export type InferOutput = StandardSchemaV1.InferOutput; /** * Infer the input type accepted by a Standard Schema. */ export type InferInput = StandardSchemaV1.InferInput; type SchemaOutput = T extends StandardSchemaV1 ? InferOutput : never; /** * Boundary phase that failed use-case schema validation. */ export type UseCaseValidationPhase = "input" | "output"; /** * Error thrown when a use case input or output fails schema validation. */ export declare class UseCaseValidationError extends Error { readonly name = "UseCaseValidationError"; readonly useCaseName: string; readonly phase: UseCaseValidationPhase; readonly issues: readonly StandardSchemaV1.Issue[]; constructor(args: { useCaseName: string; phase: UseCaseValidationPhase; issues: readonly StandardSchemaV1.Issue[]; }); } /** * Error thrown when a use case tries to emit an event it did not declare with * `.emits(...)`. */ export declare class UseCaseEventDeclarationError extends Error { readonly name = "UseCaseEventDeclarationError"; readonly useCaseName: string; readonly eventName: string; readonly declaredEventNames: readonly string[]; constructor(args: { useCaseName: string; eventName: string; declaredEventNames: readonly string[]; }); } /** * Error thrown when a use-case event helper fails to validate an event payload * before recording or publishing it. */ export declare class UseCaseEventValidationError extends Error { readonly name = "UseCaseEventValidationError"; readonly useCaseName: string; readonly eventName: string; readonly issues: readonly StandardSchemaV1.Issue[]; constructor(args: { useCaseName: string; eventName: string; issues: readonly StandardSchemaV1.Issue[]; }); } /** * Minimal domain event definition accepted by use-case event helpers. * * This structurally matches events from `@beignet/core/events` and compatible * app-owned definitions. */ export interface DomainEventLike { /** * Stable event name. */ name: string; /** * Standard Schema payload validator. */ payload: StandardSchema; } /** * Infer the output payload type from a use-case event definition. */ export type InferUseCaseEventPayload = E["payload"] extends StandardSchemaV1 ? Output : never; /** * Minimal recorder shape accepted by use-case event helpers. */ export interface UseCaseEventRecorderTarget { /** * Record a domain event payload. */ record(event: E, payload: InferUseCaseEventPayload, options?: EventPublishOptions): Promise | void; } /** * Minimal event-bus shape accepted by use-case event helpers. */ export interface UseCaseEventBusTarget { /** * Publish a domain event payload. */ publish(event: E, payload: InferUseCaseEventPayload, options?: EventPublishOptions): Promise | void; } /** * Event helper scoped to the events declared by a use case. */ export interface UseCaseEventHelpers { /** * The exact event definitions declared with `.emits(...)`. */ readonly declared: Emits; /** * Return whether an event is declared by this use case. */ isDeclared(event: DomainEventLike): boolean; /** * Throw if an event is not declared by this use case. */ assertDeclared(event: DomainEventLike): void; /** * Validate and record a declared event into a transaction-scoped recorder. */ record(recorder: UseCaseEventRecorderTarget, event: E, payload: InferUseCaseEventPayload): Promise; /** * Validate and publish a declared event directly through an event bus. */ publish(eventBus: UseCaseEventBusTarget, event: E, payload: InferUseCaseEventPayload): Promise; } /** * Use case kind - distinguishes commands (write/side-effect) from queries (read-only) */ export type UseCaseKind = "command" | "query"; /** * Symbol key for the trusted run path attached to finalized use cases. * * The server route binder calls this method instead of `run` when the route's * input was already validated by the exact same schema object at the HTTP * boundary. It behaves like `run` but skips the input parse; output * validation, instrumentation, events, and `onRun` are unchanged. * * The key uses `Symbol.for(...)` so the binder and the application builder * agree on the key even across separately bundled copies of the package. */ export declare const USE_CASE_TRUSTED_RUN: unique symbol; /** * Finalized use case definition. * * Use cases validate their input before `run(...)` executes and validate their * output before returning, unless validation is disabled on the builder. */ export interface UseCaseDef { /** * Stable use-case name, usually namespaced by feature. */ name: Name; /** * Whether this use case is a command or query. */ kind: Kind; /** Input schema, suitable for reuse in HTTP contracts and forms. */ inputSchema: InputSchema; /** Output schema, suitable for reuse in HTTP contracts and clients. */ outputSchema: OutputSchema; /** * Domain events this use case is allowed to record or publish through the * scoped `events` helper. */ emits: Emits; /** * Execute the use case with application context and typed input. */ run: (args: { ctx: Ctx; input: InferInput; }) => Promise>; } /** * Event passed to the `onRun` hook for instrumentation. */ export interface UseCaseRunEvent { /** * Use-case name. */ name: string; /** * Use-case kind. */ kind: UseCaseKind; /** * Execution phase being observed. */ phase: "start" | "end" | "error"; /** * Elapsed time for end/error events. */ durationMs?: number; /** * Error captured for error events. */ error?: unknown; /** * Application context used for the run. */ ctx: Ctx; } /** * Options for `createUseCase(...)`. */ export interface CreateUseCaseOptions { /** * Optional app-owned observer called on use case start, end, and error. * * Observers run in addition to the built-in instrumentation. */ onRun?: (event: UseCaseRunEvent) => void | Promise; /** * Built-in use-case instrumentation. * * By default every run records `usecase` lifecycle events (plus `error` * events for failed runs) into the provider instrumentation port resolved * from `ctx.ports` (`ports.instrumentation`, then `ports.devtools`). When no * port is installed, runs stay silent. Pass `false` to opt out. * * @default true */ instrumentation?: boolean; /** * Enable or disable schema validation for use case boundaries. * * Defaults to validating both input and output. Pass `false` to opt out, or * configure phases independently with `{ input: boolean, output: boolean }`. */ validate?: boolean | { input?: boolean; output?: boolean; }; } type ValidationOptions = { input: boolean; output: boolean; }; /** * Internal configuration for the use case builder */ interface UseCaseBuilderConfig { name: Name; kind: Kind; input?: InputSchema; output?: OutputSchema; emits: Emits; } /** * Fluent builder for creating use cases */ declare class UseCaseBuilder { private readonly config; private readonly onRun?; private readonly validation; private readonly instrumented; constructor(config: UseCaseBuilderConfig, onRun?: ((event: UseCaseRunEvent) => void | Promise) | undefined, validation?: ValidationOptions, instrumented?: boolean); /** * Define the input schema for this use case */ input(schema: I): UseCaseBuilder; /** * Define the output schema for this use case */ output(schema: O): UseCaseBuilder; /** * Define the domain events that this use case may emit. */ emits(events: E): UseCaseBuilder; /** * Define the run function and finalize the use case definition */ run(fn: InputSchema extends StandardSchemaV1 ? OutputSchema extends StandardSchemaV1 ? (args: { ctx: Ctx; input: SchemaOutput; events: UseCaseEventHelpers; }) => Promise> | SchemaOutput : never : never): InputSchema extends StandardSchemaV1 ? OutputSchema extends StandardSchemaV1 ? UseCaseDef : never : never; } /** * Root builder returned by createUseCase. */ export interface UseCaseBuilderRoot { /** * Create a command use case (write/side-effect path) */ command(name: Name): UseCaseBuilder; /** * Create a query use case (read-only path) */ query(name: Name): UseCaseBuilder; } /** * Infer the application context type from a finalized use case. */ export type UseCaseContext = TUseCase extends { run: (args: { ctx: infer Ctx; input: infer _Input; }) => Promise; } ? Ctx : never; /** * Infer the public input type accepted by a finalized use case. */ export type UseCaseInput = TUseCase extends { run: (args: { ctx: infer _Ctx; input: infer Input; }) => Promise; } ? Input : never; /** * Infer the public output type returned by a finalized use case. */ export type UseCaseOutput = TUseCase extends { run: (args: { ctx: infer _Ctx; input: infer _Input; }) => Promise; } ? Output : never; type MaybePromise = T | Promise; /** * Small test harness for running use cases with typed inputs. */ export interface UseCaseTester { /** * Create a fresh test context. */ ctx(): Promise; /** * Run a use case with a typed input and either a fresh or explicit context. */ run(useCase: { run(args: { ctx: Ctx; input: Input; }): Promise; }, input: Input, options?: { ctx?: Ctx; }): Promise; } /** * Create a small test harness for use cases. * * Pass a context factory when tests mutate ports or state. Pass a fixed context * for simple, immutable tests. */ export declare function createUseCaseTester(createContext: Ctx | (() => MaybePromise)): UseCaseTester; /** * Create a use case builder with a specific context type. * * Create this once in app code, usually in `lib/use-case.ts`, then import that * configured builder from feature use-case modules. * * @example * ```ts * export const useCase = createUseCase(); * * export const createTodo = useCase * .command("todos.create") * .input(CreateTodoInput) * .output(CreateTodoOutput) * .run(async ({ ctx, input }) => ctx.ports.todos.create(input)); * ``` * * @param options - Optional instrumentation and validation configuration. * @returns A root builder for command and query use cases. */ export declare function createUseCase(options?: CreateUseCaseOptions): UseCaseBuilderRoot; export {}; //# sourceMappingURL=index.d.ts.map