UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

81 lines 3.02 kB
/** * Idempotency hooks for @beignet/core/server */ import { type IdempotencyMeta, type IdempotencyPort, type IdempotencyScope } from "../../idempotency/index.js"; import { type ActivityActor, type ActivityTenant } from "../../ports/index.js"; import type { HttpRequestLike, ServerHook } from "../types.js"; /** * Ports required by idempotency hooks. */ export type IdempotencyPorts = { idempotency: IdempotencyPort; }; /** * Minimal context shape required for actor- and tenant-scoped idempotency. */ export type CtxWithIdempotency = { ports: IdempotencyPorts; actor?: ActivityActor; tenant?: ActivityTenant; }; /** * Options for `createIdempotencyHooks(...)`. */ export interface IdempotencyHooksOptions<Ctx> { /** * Build the idempotency namespace for a contract. * * Defaults to `http.<contract name>` so HTTP reservations never collide with * use-case `runIdempotently(...)` namespaces. */ namespace?: (args: { contract: { name: string; }; }) => string; /** * Build the idempotency scope after context exists. * * Defaults to a scope derived from `meta.scope`: omitted metadata scopes by * `ctx.actor?.id` and also `ctx.tenant?.id` when present, `"actor"` scopes by * the actor only, `"global"` stays global, `"tenant"` scopes by the tenant, * and `"actor-tenant"` scopes by both. */ scope?: (args: { ctx: Ctx; req: HttpRequestLike; meta: IdempotencyMeta; }) => IdempotencyScope; /** * Build the fingerprint input from the parsed request. * * Defaults to `{ path, query, body }`. */ fingerprintInput?: (args: { path: unknown; query: unknown; body: unknown; }) => unknown; } /** * Create metadata-driven idempotency hooks. * * The hook reads `contract.metadata.idempotency` and enforces it with * `ctx.ports.idempotency`. In `beforeHandle` it reserves the client key after * request parsing and route hook identity resolution, replays completed matching * responses with an `idempotency-replayed: true` header, and rejects in-progress * or conflicting keys with the framework * `IdempotencyInProgress`/`IdempotencyConflict` catalog errors. After the * response-validation phase, it stores final route-owned 2xx framework-neutral * responses for replay and releases the reservation for framework-owned * responses, errors, non-2xx responses, and native `Response` results, which * are not replayable. * * Use `runIdempotently(...)` from `@beignet/core/idempotency` for non-HTTP * workflows such as jobs, listeners, webhooks, and schedules. * * @param options - Optional namespace, scope, and fingerprint-input builders. * @returns A server hook backed by `ctx.ports.idempotency`. */ export declare function createIdempotencyHooks<Ctx extends CtxWithIdempotency>(options?: IdempotencyHooksOptions<Ctx>): ServerHook<Ctx, IdempotencyPorts>; //# sourceMappingURL=idempotency.d.ts.map