/** * `RestBinding` — the REST-facing wrapper that references * a transport-neutral `OperationDefinition` by name. The binding owns * all HTTP-specific concerns that don't belong on the operation: * method, path, where each input field is extracted from (path / * query / header / body), and how the output is shaped into an HTTP * response. * * This module deliberately does NOT import `route-model.ts`: bindings * describe operation-backed HTTP routes, while that model describes the * remaining REST-only meta and discovery endpoints. */ import { WeftError } from '../core/weft-error.ts'; import type { OperationFault } from './operation-fault.ts'; import type { RestBodyReadOptions } from './rest-body.ts'; import type { HttpMethod } from './route-model.ts'; /** * Thrown by `bindingPathMatches` when a URL path parameter contains malformed * percent-encoding. The top-level handler catches it and returns a 400. */ export declare class MalformedRouteParameterError extends WeftError<'MalformedRouteParameterError'> { constructor(); } /** * Where a single top-level field of the operation's `inputSchema` * comes from in a REST request. The OpenAPI generator reads this to * emit accurate path / query / header / requestBody schema pieces. */ export type ParamSource = { readonly kind: 'path'; readonly pathParam: string; } | { readonly kind: 'query'; readonly queryParam: string; readonly repeating?: boolean; } | { readonly kind: 'header'; readonly headerName: string; } | { readonly kind: 'body'; readonly mediaType?: 'application/json' | 'application/octet-stream'; } | { readonly kind: 'body-field'; readonly bodyField: string; }; /** * How the operation's output is turned into an HTTP response. * - `json` — standard `Response.json(output, { status })`. * - `empty` — 204 No Content; output is ignored. * - `streaming` — the operation's output is an async iterable of * bytes/events; the transport adapter streams directly. */ export type ResponseShape = { readonly kind: 'json'; readonly status: number; } | { readonly kind: 'empty'; readonly status: number; } | { readonly kind: 'streaming'; readonly mediaType: 'text/event-stream' | 'application/octet-stream' | 'application/x-ndjson'; }; export type RestInputContext = RestBodyReadOptions; /** * Binds a REST mount point to an `OperationDefinition`. Generic over * `Input` / `Output` so `extractInput` returns a value the operation's * schema accepts, and `shapeSuccess` consumes the output without casts. */ export type RestBinding = { readonly method: HttpMethod; /** Express-style path with `:name` placeholders (e.g., `/v1/workflows/:id`). */ readonly path: string; /** Ordered list of path param names; must match the `:name` tokens in `path`. */ readonly pathParamNames: ReadonlyArray; /** Operation-catalog name this binding dispatches to. */ readonly operationName: string; /** * Long-lived transport specialization for structural catalog checks. * Omitted bindings are ordinary unary REST request/response operations. */ readonly transportKind?: 'unary' | 'sse' | 'websocket-subscription'; /** * Declarative map from top-level `Input` field names to their REST * source. Fields omitted from this map are not extracted from the * request by the OpenAPI generator's schema emission — `extractInput` * remains the authoritative runtime source. Keys are constrained to * string-typed keys of `Input` so a typo becomes a compile error * rather than a silent no-op at generator time. */ readonly inputSources: Partial, ParamSource>>; /** * Read the REST request and produce the operation's typed `Input`. * Called before `executeOperation`; the returned value is what the * pipeline's schema parse step sees. */ readonly extractInput: (request: Request, pathParams: Record, context: RestInputContext) => Promise; /** Canonical response shape — OpenAPI reads this, handler may override via `shapeSuccess`. */ readonly success: ResponseShape; /** * Optional override for response construction. When absent, transport * adapters default to `jsonResponse(output, success.status)`, which emits * `JSON.stringify(output)` with `Content-Type: application/json` (or * `new Response(null, { status })` for `empty`). Provide this * when the wire representation differs from `output` verbatim. * * Receives the original `Request` so REST-only response shaping — * `Accept` header negotiation (json vs msgpack), redirect URL * construction, etc. — can stay in the binding without leaking * HTTP-specific state into the operation's `Output` type. Other * transports (JSON-RPC HTTP/WS/stdio) never call `shapeSuccess`; * they emit `output` directly via the canonical envelope, so the * operation contract stays transport-neutral. */ readonly shapeSuccess?: (output: Output, request: Request) => Response; /** * Optional override for fault → HTTP response mapping. When absent, * the transport adapter falls back to `faultToHttpResponse`, which emits * the same flat audited `{ error, weftCode?, data? }` body as * `shapeRestFault`. This is distinct from the JSON-RPC fault object * (`faultToJsonRpcError`), which uses `{ code, message, data }` with a numeric * `code` and a broader data projection. REST and JSON-RPC deliberately differ * in fault shape, so each transport owns its own projection. * * REST operations provide this to shape faults the way a REST client * expects: most use `shapeRestFault`, which masks an `EngineFailure` to a * flat `{ error: "Internal server error" }` with status `500` (never * leaking internal detail over REST), adds audited safe context under a * `data` sibling for other faults, and maps the remaining fault codes to * their HTTP statuses. A few operations supply a bespoke shaper to special-case * a particular fault — typically to override its message or to handle one code * explicitly — while delegating the rest. The status often matches what the * shared map would already produce; the shaper exists for the operation-specific * detail (for example, `get-workflow-result` returns the custom message * `"Timeout waiting for workflow result"` on a `Timeout`, and `get-stream-chunks` * handles `InvalidParams` inline). This per-operation hook is the current * contract. */ readonly shapeFault?: (fault: OperationFault) => Response; }; /** * Match a binding's `:name` path pattern against a concrete URL path. * Returns a map of path-param names → URL-decoded values, or `null` * when the path does not match the pattern's structure. * * An empty segment (`/foo//bar`) is treated as a non-match: an empty * path param would propagate to the engine as a confusingly-shaped * "missing" identifier, and surfacing the shape error at the router * (404) produces a cleaner wire response than a downstream 400. * * WHATWG URL path normalization collapses `.` and `..` path segments (and * their percent-encoded forms) before `handleRequest()` ever sees * `url.pathname` — this function never observes those segments as literal * text, so a single trailing `:id`/`:operationId` segment can never match a * resource whose id is literally `.` or `..`, no matter how it is encoded. * This is no longer just a documented gap: `assertValidWorkflowId` (WFT-95) * and `isValidOperationId` reject those exact strings at admission, so a * caller-facing workflow or operation id can never legitimately be `.` or * `..` in the first place — this URL-normalization quirk is closed by * validation rather than left as something callers must work around. */ export declare function bindingPathMatches(pattern: string, actualPath: string): Record | null; /** * Runtime guard: a `RestBinding` is only dispatchable if it references * a live `OperationDefinition` that declares `transports.http === true` * AND the binding's `operationName` matches the operation's name. The * OpenAPI generator and the REST router consult this to exclude * bindings whose operation no longer mounts on HTTP. * * The binding and operation are typed independently here. Structural * compatibility between `Input`/`Output` is not checked at the type * level — that's the registry's job at registration, and enforcing it * here would require the caller to thread matching generics through * every call site. This runtime guard only asserts the two concrete * runtime properties the router depends on: name equality and HTTP * availability. */ export declare function isRestBindingCompatibleWithOperation(binding: { readonly operationName: string; }, operation: { readonly name: string; readonly transports: { readonly http: boolean; }; }): boolean;