import type { CreateEventParams, CreateEventRequest, Event, EventResult, HealthCheckPayload, ValidQueueName, WorkflowRun, World } from '#compiled/@workflow/world/index.js'; import { type PayloadKey } from '../serialization/encryption.js'; /** * Validates a workflow name and returns the corresponding queue name. * Ensures the workflow name only contains safe characters before * interpolating it into the queue name string. */ export declare function getWorkflowQueueName(workflowName: string, namespace?: string): ValidQueueName; /** * Result of a health check operation. */ export interface HealthCheckResult { healthy: boolean; /** Error message if health check failed */ error?: string; /** Latency if the health check was successful */ latencyMs?: number; /** Spec version of the responding deployment */ specVersion?: number; /** * `@workflow/core` version of the responding deployment, used for * capability detection (see `getRunCapabilities`). Omitted when the * responding deployment did not provide the field as a string — * for example, an older `@workflow/core` that predates this field, * or a non-JSON plain-text health response. */ workflowCoreVersion?: string; /** * The target run's X25519 public key (base64), returned only when the probe * carried a `runId` and the responding deployment has encryption enabled. * * Lets a cross-deployment `start()` seal the workflow arguments using a * response it was already waiting on, instead of making a separate * key-lookup request. */ encryptionPublicKey?: string; /** * The responding deployment's `HOOK_RESUME_INPUT_VERSION` — the protocol * version at which the *consumer* (queue-message target) re-ensures the * `hook_received` event from `hookInput` on replay. A cross-deployment * `start()` stamps the *target's* value (not the caller's) into the new * run's `executionContext.hookResumeInputVersion` so that `resumeHook()` * only takes the parallel path when the deployment that will actually * consume the queue message is known to honor `hookInput`. Omitted when the * responding deployment predates this field (an older consumer that ignores * `hookInput`), which fails the gate closed. */ hookResumeInputVersion?: number; } /** * Checks if the given message is a health check payload. * If so, returns the parsed payload. Otherwise returns undefined. */ export declare function parseHealthCheckPayload(message: unknown): HealthCheckPayload | undefined; /** * Handles a health check message by writing the result to the world's stream. * The caller can listen to this stream to get the health check response. * * @param healthCheck - The parsed health check payload */ export declare function handleHealthCheckMessage(healthCheck: HealthCheckPayload, worldSpecVersion?: number): Promise; export interface HealthCheckOptions { /** Timeout in milliseconds to wait for health check response. Default: 30000 (30s) */ timeout?: number; /** Deployment ID to send the health check to. Falls back to process.env.VERCEL_DEPLOYMENT_ID. */ deploymentId?: string; /** * The run id the caller is about to create. When set, the responding * deployment derives that run's public key locally and returns it as * `encryptionPublicKey`, letting a cross-deployment `start()` seal the * workflow arguments without a separate key lookup. */ runId?: string; /** * Queue namespace of the target deployment (e.g. `'eve'` for topics like * `__eve_wkf_workflow_*`). Falls back to `WORKFLOW_QUEUE_NAMESPACE` in the * calling process. Cross-context callers (e.g. the observability * dashboard) must pass the target deployment's namespace explicitly — * the env fallback resolves in the caller's process, and a message * published to a mismatched topic has no consumer, so the check would * always time out. */ namespace?: string; } export declare function healthCheck(world: World, options?: HealthCheckOptions): Promise; /** * Appends events whose IDs are not already present in `target`. * * Pass the IDs currently present in `target` when appending repeatedly to the * same array. The set is updated alongside `target`. * * Events are appended in the order the World returned them, and are not * re-sorted: a World's canonical order is its own, and the runtime cannot * reproduce it from event ids alone. `world-vercel` orders by event id, while * `world-local` orders by `(createdAt, eventId)` and deliberately re-mints keys * so that the two diverge. Every append source is already in canonical order * relative to the tail (a cursor-delimited page, or a write-response delta), so * receipt order is the order to keep. Nothing downstream may assume the tail is * the newest event — see {@link latestEventStateUpdatedAt}. */ export declare function appendUniqueEvents(target: Event[], events: readonly Event[], targetIds?: Set): void; /** * Inserts `event` into `target` at the position that keeps `target` ordered by * ascending `eventId`, or no-ops if an event with the same `eventId` is already * present (idempotent). * * `preloadedEvents` is loaded `sortOrder: 'asc'` and is never re-sorted * client-side, so a `hook_received` spliced in by the lazy-resume consumer must * land in `eventId` order — a plain `push` would place a late-committing * earlier event after events that sort before it, corrupting replay. Event IDs * are ULIDs, so lexicographic string order matches commit order. */ export declare function insertEventByEventId(target: Event[], event: Event): void; /** * Loads workflow run events by iterating through all pages of paginated * results. Events are returned in chronological (ascending) order for * deterministic workflow replay. * * @param runId - The workflow run ID. * @param afterCursor - If provided, only events after this cursor are * returned (incremental load). If omitted, all events are returned. * The returned cursor can be passed back in on a subsequent call for * incremental loading. */ export declare function loadWorkflowRunEvents(runId: string, afterCursor?: string): Promise<{ events: Event[]; cursor: string | null; }>; /** * The runtime's loaded event-log snapshot: the events replayed so far and the * cursor positioned after them. Handed to helpers that derive the precondition * snapshot from it; they do not mutate it. */ export interface LoadedEventLog { events: Event[]; cursor: string | null; } /** * Whether the optimistic-concurrency guard for event creation is enabled. * **On by default** where the runtime executes: replay-context creates send a * `stateUpdatedAt` snapshot (and can be rejected with 412 by a supporting * backend) unless `WORKFLOW_PRECONDITION_GUARD` is set to `0`. Backends without * guard support ignore the snapshot, so enabling by default is * backward-compatible. */ export declare function isPreconditionGuardEnabled(): boolean; /** * The `stateUpdatedAt` value to send with a replay-context event creation: the * *maximum* ULID time (epoch ms) over the events the runtime has loaded. Returns * `undefined` when there are no events or that id is not a decodable ULID. * * It is the maximum rather than the tail's because the loaded log is in the * World's canonical order, which is not necessarily event-id order (see * {@link appendUniqueEvents}). The maximum is what lets the count sent alongside * it be read as "events at or below this watermark": every loaded event is at or * below it, so the count is exactly `events.length`. Reading the tail instead * would understate the watermark on a World whose order is not id-ordered, which * is safe (it can only weaken detection) but needlessly imprecise. * * The maximum is found by lexicographic id comparison, decoding only once: the * 26-character Crockford ULID encodes its timestamp in the leading 10 * characters, so the greatest id also carries the greatest time. * * Granularity: snapshots are epoch-milliseconds, and the backend allows an * equal-timestamp snapshot (an up-to-date client must not be rejected). Two * out-of-band events landing in the same millisecond where only the first was * loaded therefore pass this half of the guard undetected — that is exactly * the hole `stateEventCount` closes, since the count of events at or below the * watermark differs even when the watermarks are equal. */ export declare function latestEventStateUpdatedAt(events: Event[]): number | undefined; /** * The precondition snapshot a replay-context event creation sends, describing * the event log the replay derived the event from. * * The three fields are one indivisible unit: the backend reads the count only * relative to the watermark, and returns its inline delta only relative to the * cursor. Passing them as a single object is what keeps them from drifting * apart at a call site. */ export interface PreconditionSnapshotParams { stateUpdatedAt?: number; stateEventCount?: number; stateCursor?: string; } /** * Build the precondition snapshot to attach to a replay-context event creation. * * Returns an empty object — no guard, backend behaves as before — when the * guard is disabled or the watermark is not derivable. All three fields fail * open together: a count without a watermark is meaningless to the backend, and * a cursor without either would invite a delta nobody asked for. * * `stateEventCount` is `events.length` because the watermark is the log's * *maximum* ULID time, so every loaded event is at or below it regardless of the * order the World returned them in. * * Both fields are therefore invariant under permutation of the log: a maximum is * order-independent, and the length is set cardinality once `appendUniqueEvents` * has deduped by event id. Two replays that consume the same events in different * orders send an identical snapshot, so this guard detects that a log is missing * an event and can never detect that a replay consumed one in a different order. */ export declare function preconditionSnapshotParams(events: Event[], cursor?: string | null): PreconditionSnapshotParams; /** * The events a rejecting World attached to a `PreconditionFailedError`, when it * returned the ones the client's snapshot was missing inline. * * Returns `null` for anything else — no details, a World that did not implement * this, or a payload that does not narrow cleanly. Callers fall back to * reloading the event log, which is always correct; this is untrusted-shaped * data on a failure path, so nothing here is repaired. * * `runId` is the caller's run. Every event must belong to it: the delta is * merged straight into the replay's log, and one foreign event there is a * corrupt log rather than a corrected one — the replay would consume a * correlation id for an event that does not exist on this run. */ export declare function preconditionEventDelta(error: unknown, runId: string): { events: Event[]; cursor: string | null; } | null; /** Creates one event on a bound run, carrying replay-recovery telemetry. */ export type EventCreator = (data: CreateEventRequest, params?: CreateEventParams) => Promise; /** * Wraps a request/response handler and adds a health check "mode" * based on the presence of a `__health` query parameter. */ export declare function withHealthCheck(handler: (req: Request) => Promise, worldSpecVersion?: number): (req: Request) => Promise; /** * Queues a message to the specified queue with tracing. */ export declare function queueMessage(world: World, ...args: Parameters): Promise; /** * Calculates the queue overhead time in milliseconds for a given message. */ export declare function getQueueOverhead(message: { requestedAt?: Date; }): { [k: string]: number; } | undefined; /** * Returns a memoized accessor for a run's full encryption capability. * * The first call resolves the run's key material via * `world.getEncryptionKeyForRun` (which may do HKDF derivation locally on * Vercel, or a network fetch from external contexts) and derives a * {@link PayloadKey} from it; subsequent calls await the same cached promise. * If the world doesn't support encryption or the run has no key configured, * the cached value is `undefined`. * * The resolved value is deliberately the *full* capability — the symmetric AES * key plus the run's X25519 keypair — not just a `CryptoKey`. A run reading * its own event log can encounter sealed (`encp`) payloads that another run * wrote to it (a cross-deployment hook resumption, say), and opening those * needs the keypair. Resolving only the symmetric key would leave those * payloads unopenable and wedge the run. * * Used by step / workflow handlers to defer the (potentially expensive) * key fetch until the first code path that actually needs it — typically * input hydration on the success path, or error dehydration on a failure * path. Both paths can race-call the accessor without triggering duplicate * fetches. * * Errors thrown by `getEncryptionKeyForRun` propagate to every caller * (the cached promise rejects). This is intentional: when encryption is * configured, we never want to silently fall back to plaintext * serialization. A propagated error in an event-emission path leaves the * outer try/catch to log and surface the issue; the queue's redelivery * semantics will retry the key fetch on the next attempt. */ export declare function memoizeEncryptionKey(world: World, runOrId: WorkflowRun | string): () => Promise; //# sourceMappingURL=helpers.d.ts.map