Crux
GuidesDurable ExecutionSignals

Provider recipes

Authentication, idempotency, retries, dead letters, replay, and operator deployment for Signal providers.

These recipes assume you already know the Signal provider guide. Copy the current public API only.

Authenticate and size-check at the edge

import { createHash } from "node:crypto";
import { webhook } from "@use-crux/core/signal/transport";
import { z } from "zod";

const MAX_BYTES = 64 * 1024;

const ProviderBodySchema = z.object({
  accountId: z.string().min(1),
  eventId: z.string().min(1),
  topic: z.string().min(1),
});

/** Reject oversized trusted Content-Length before any body read. */
function assertContentLengthBudget(request: Request, maxBytes: number): void {
  const header = request.headers.get("content-length");
  if (header === null) {
    return;
  }

  const length = Number(header);
  if (!Number.isFinite(length) || length < 0 || length > maxBytes) {
    throw new Error("payload too large");
  }
}

/**
 * Single bounded accumulation from the request stream.
 * Aborts as soon as total bytes would exceed `maxBytes`.
 */
async function readBoundedBody(
  request: Request,
  maxBytes: number,
): Promise<Uint8Array> {
  if (!request.body) {
    return new Uint8Array(0);
  }

  const reader = request.body.getReader();
  const chunks: Uint8Array[] = [];
  let total = 0;

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        break;
      }
      if (!value || value.byteLength === 0) {
        continue;
      }

      total += value.byteLength;
      if (total > maxBytes) {
        throw new Error("payload too large");
      }

      chunks.push(value);
    }
  } finally {
    reader.releaseLock();
  }

  const raw = new Uint8Array(total);
  let offset = 0;
  for (const chunk of chunks) {
    raw.set(chunk, offset);
    offset += chunk.byteLength;
  }
  return raw;
}

function sha256Hex(bytes: Uint8Array): string {
  return createHash("sha256").update(bytes).digest("hex");
}

export const ingress = webhook({
  async handle(request) {
    assertContentLengthBudget(request, MAX_BYTES);

    const raw = await readBoundedBody(request, MAX_BYTES);
    const signature = request.headers.get("x-signature");
    if (!verify(signature, raw)) {
      throw new Error("unauthenticated");
    }

    const body = ProviderBodySchema.parse(
      JSON.parse(new TextDecoder().decode(raw)),
    );

    return {
      accountId: body.accountId,
      eventId: body.eventId,
      authenticatedRouting: { source: "webhook", topic: body.topic },
      payload: {
        kind: "inline-base64url",
        value: Buffer.from(raw).toString("base64url"),
        byteLength: raw.byteLength,
        sha256: sha256Hex(raw),
      },
    };
  },
});

Throw or reject before returning whenever authentication or validation fails. The host must not call acceptTransportEnvelope() for failed handles.

Accept before acknowledge

import {
  acceptTransportEnvelope,
  TransportEnvelopeConflictError,
  type RuntimeAcceptedTransportEnvelope,
} from "@use-crux/core/runtime";

export async function postWebhook(request: Request, store: RuntimeStore) {
  const handled = await ingress.handle(request);
  const envelope: RuntimeAcceptedTransportEnvelope = {
    _tag: "RuntimeAcceptedTransportEnvelope",
    schemaVersion: 1,
    bindingId: "binding.orders",
    adapterId: "orders.webhook",
    provider: "orders",
    accountId: handled.accountId,
    eventId: handled.eventId,
    receivedAt: new Date().toISOString(),
    authenticatedRouting: handled.authenticatedRouting,
    payload: handled.payload,
    configRef: { id: "config.orders", revision: "1" },
    target: { kind: "signal", signalId: "order.submitted" },
  };

  try {
    // Accepted and duplicate both resolve with acknowledge: true.
    await acceptTransportEnvelope({
      store,
      namespace: "prod",
      envelope,
    });
    return new Response("ok", { status: 200 });
  } catch (error) {
    if (error instanceof TransportEnvelopeConflictError) {
      // Conflict throws; do not acknowledge as success.
      return new Response("conflict", { status: 409 });
    }

    throw error;
  }
}

Idempotent provider event IDs

Use the provider's stable event ID as the envelope eventId. On redelivery:

  • matching digest → kind: "duplicate", acknowledge: true
  • different authenticated payload → conflict error, do not ack as success

Inside onEvent, prefer the accepted event identity for Signal publish idempotency. When idempotencyKey is omitted, the transport runner defaults to the provider/account/event identity so crash recovery cannot create a second logical delivery for the same envelope.

Retries, dead letters, and explicit replay

import {
  createTransportNormalizationRunner,
  replayTransportEnvelope,
  transportBindingHealth,
  transportStatistics,
  projectTransportEnvelope,
} from "@use-crux/core/runtime";

const runner = createTransportNormalizationRunner({
  store,
  namespace: "prod",
  providers: [orders],
});

// Runtime maintenance / worker cadence normally calls this.
await runner.runOnce();

const stats = await transportStatistics({ store, namespace: "prod" });
console.log(stats.total.retried, stats.total.deadLettered);

// Restart-safe binding health: identity, status, last owner, cursor age, counts.
// `program` is the immutable RuntimeProgram that declares managed bindings.
const health = await transportBindingHealth({
  store,
  namespace: "prod",
  program, // createRuntimeProgram({ providers, transports })
});
console.log(health.bindings[0]?.status, health.bindings[0]?.cursor.ageMs);

// Operators requeue dead letters explicitly — workers never invent replay.
const revived = await replayTransportEnvelope({
  store,
  namespace: "prod",
  provider: "orders",
  accountId: "acct_1",
  eventId: "evt_poison",
});

const view = projectTransportEnvelope(revived);
// view.lineage is occurrence identities only; no payload bytes.

Bounded retry metadata lives on the envelope record. After maxAttempts, the record becomes dead-letter and stays until retention or explicit replay.

Deploy with inert bindings and one worker

  1. Export signalProvider(...) definitions and managedTransportBinding(...) projections from application modules.
  2. Generate or hand-write a Runtime program that includes those providers and inert transports.
  3. Run one configured Runtime worker. It drains accepted envelopes on the existing maintenance cadence; do not start a second transport daemon.
  4. Configure retention with transportEnvelopes (default 7d) when terminal envelope rows should expire.

Inert bindings hold only stable ids, config refs, and Signal targets. They must never capture credentials, Request objects, live clients, poll, open, or onEvent closures.

Supervise a managed stream connection

import { stream } from "@use-crux/core/signal/transport";
import { ManagedStreamTerminalError } from "@use-crux/core/runtime";
import {
  managedTransportBinding,
  signalProvider,
} from "@use-crux/core/signal/provider";

export const marketplaceStream = signalProvider({
  id: "marketplace.stream",
  transport: stream({
    async *open({ cursor, signal, configRef }) {
      const session = await openMarketplaceSession({
        resumeFrom: cursor,
        revision: configRef.revision,
        signal,
      });

      try {
        for await (const frame of session.frames) {
          if (signal.aborted) {
            break;
          }

          if (frame.kind === "heartbeat") {
            // Cursor-only progress: Runtime may checkpoint immediately.
            yield { kind: "cursor", cursor: frame.cursor };
            continue;
          }

          if (frame.kind === "auth_revoked") {
            // Terminal: durable faulted status, no automatic reconnect.
            throw new ManagedStreamTerminalError(
              "MARKETPLACE_AUTH_REVOKED",
              "provider revoked credentials",
            );
          }

          yield {
            kind: "envelope",
            accountId: frame.accountId,
            eventId: frame.eventId,
            authenticatedRouting: { source: "stream", topic: frame.topic },
            payload: encodeInlinePayload(frame.body),
            // Checkpoint only after this envelope is durably accepted.
            cursor: frame.cursor,
          };
        }
      } finally {
        await session.close();
      }
    },
  }),
  signals: { orderSubmitted },
  async onEvent(envelope, { signals }) {
    await signals.orderSubmitted.publish({
      orderId: decodeOrderId(envelope.payload),
    });
  },
});

export const marketplaceStreamBinding = managedTransportBinding(
  marketplaceStream,
  {
    id: "binding.marketplace.stream",
    configRef: { id: "config.marketplace.stream", revision: "1" },
    signalId: "order.submitted",
  },
);

Operator notes:

  • Deploy the live provider + inert binding on one Runtime worker with a store that implements binding checkpoints (Memory or PostgreSQL).
  • Bump configRef.revision when connection credentials or resume semantics change so Runtime invalidates the prior cursor.
  • Faulted bindings stay faulted across restart until config identity changes or an operator clears status — they do not silently reopen.
  • Prefer sse({ open }) when the external system is SSE; it lowers onto this same stream fiber. Prefer websocket({ open }) for WebSocket ingress (optional post-accept ack + bounded push buffer).

Supervise a managed SSE connection

import {
  sse,
  classifySseHttpStatus,
  sseHttpStatusErrorCode,
} from "@use-crux/core/signal/transport";
import { ManagedStreamTerminalError } from "@use-crux/core/runtime";
import {
  managedTransportBinding,
  signalProvider,
} from "@use-crux/core/signal/provider";

export const marketplaceSse = signalProvider({
  id: "marketplace.sse",
  transport: sse({
    async *open({ cursor, signal, configRef }) {
      // Map the durable Runtime cursor to the HTTP Last-Event-ID header.
      const response = await connectMarketplaceSse({
        lastEventId: cursor,
        revision: configRef.revision,
        signal,
      });

      if (!response.ok) {
        // Release an unused body when present; null bodies are a no-op.
        await response.body?.cancel?.();
        if (classifySseHttpStatus(response.status) === "terminal") {
          throw new ManagedStreamTerminalError(
            sseHttpStatusErrorCode(response.status),
            `SSE connect rejected with HTTP ${response.status}`,
          );
        }
        throw new Error(`SSE connect transient HTTP ${response.status}`);
      }

      try {
        for await (const frame of parseSse(response.body, signal)) {
          if (signal.aborted) {
            break;
          }

          if (frame.kind === "comment" && frame.id) {
            // Cursor-only: genuine new Last-Event-ID without an envelope.
            yield { kind: "cursor", lastEventId: frame.id };
            continue;
          }

          if (frame.kind === "event") {
            yield {
              kind: "envelope",
              accountId: frame.accountId,
              eventId: frame.eventId,
              authenticatedRouting: {
                source: "sse",
                eventType: frame.eventType ?? "message",
              },
              payload: encodeInlinePayload(frame.body),
              // Progress through this event inclusive (wire id:).
              lastEventId: frame.id ?? undefined,
            };
          }
        }
      } finally {
        await response.body?.cancel?.();
      }
    },
  }),
  signals: { orderSubmitted },
  async onEvent(envelope, { signals }) {
    await signals.orderSubmitted.publish({
      orderId: decodeOrderId(envelope.payload),
    });
  },
});

export const marketplaceSseBinding = managedTransportBinding(marketplaceSse, {
  id: "binding.marketplace.sse",
  configRef: { id: "config.marketplace.sse", revision: "1" },
  signalId: "order.submitted",
});

Provider-ingress SSE is not React browser SSE

SurfacePackageDirectionReconnect owner
Managed SSE sse({ open })@use-crux/core/signal/transportInbound third-party SSE → durable envelopes → SignalsRuntime worker stream fiber
Managed WebSocket websocket({ open })@use-crux/core/signal/transportInbound third-party WebSocket → durable envelopes → SignalsRuntime worker stream fiber
React SSE createSSETransport / cruxSSEHandler@use-crux/reactOutbound Crux RecordStore/state → browser hooksBrowser EventSource helper

Never share types, checkpoints, or supervision between managed provider ingress and React browser egress.

Operator notes for SSE:

  • Core does not own fetch, EventSource, wire-frame parsing, or credentials. Those stay in the adapter closure (and on inert bindings as secret-free configRef only).
  • Durable checkpoints still store a generic cursor; lastEventId is the authoring vocabulary. Cursor-only items advance progress without a new envelope. No Runtime ack after accept for SSE.
  • EOF, transient throw, terminal fault, abort, and pull backpressure reuse the managed stream recipe above.

Supervise a managed WebSocket connection

import {
  websocket,
  createBoundedPushBuffer,
  classifyWebSocketCloseCode,
  webSocketCloseErrorCode,
} from "@use-crux/core/signal/transport";
import { ManagedStreamTerminalError } from "@use-crux/core/runtime";
import {
  managedTransportBinding,
  signalProvider,
} from "@use-crux/core/signal/provider";

export const marketplaceWs = signalProvider({
  id: "marketplace.ws",
  transport: websocket({
    async *open({ cursor, signal, configRef }) {
      const socket = await connectMarketplaceWs({
        resume: cursor,
        revision: configRef.revision,
        signal,
      });
      const buffer = createBoundedPushBuffer({ capacity: 64, signal });

      let finalized = false;
      const finalize = () => {
        if (finalized) {
          return;
        }
        finalized = true;
        signal.removeEventListener("abort", onAbort);
        socket.close();
      };
      const onAbort = () => {
        finalize();
      };

      socket.onmessage = (event) => {
        try {
          const message = mapMarketplaceFrame(event);
          if (message.kind === "heartbeat") {
            buffer.push({ kind: "cursor", cursor: message.cursor });
            return;
          }

          buffer.push({
            kind: "envelope",
            accountId: message.accountId,
            eventId: message.eventId,
            authenticatedRouting: { source: "websocket" },
            payload: encodeInlinePayload(message.body),
            cursor: message.cursor,
            // Optional: Runtime calls this only after durable accept + checkpoint.
            acknowledge: () => socket.sendAck(message.wireId),
          });
        } catch (error) {
          // Fail first so a later clean onclose cannot mask parse/overflow faults.
          buffer.fail(error);
          finalize();
        }
      };

      socket.onclose = (closeEvent) => {
        const kind = classifyWebSocketCloseCode(closeEvent.code);
        if (kind === "normal") {
          // close() is a no-op after fail(), so prior failure stays authoritative.
          buffer.close();
          return;
        }
        if (kind === "terminal") {
          buffer.fail(
            new ManagedStreamTerminalError(
              webSocketCloseErrorCode(closeEvent.code),
              `WebSocket closed with code ${closeEvent.code}`,
            ),
          );
          return;
        }
        buffer.fail(new Error(`WebSocket closed with code ${closeEvent.code}`));
      };

      signal.addEventListener("abort", onAbort);
      try {
        yield* buffer.items;
      } finally {
        // Early return, clean EOF, and throw all close the socket and detach abort.
        finalize();
      }
    },
  }),
  signals: { orderSubmitted },
  async onEvent(envelope, { signals }) {
    await signals.orderSubmitted.publish({
      orderId: decodeOrderId(envelope.payload),
    });
  },
});

export const marketplaceWsBinding = managedTransportBinding(marketplaceWs, {
  id: "binding.marketplace.ws",
  configRef: { id: "config.marketplace.ws", revision: "1" },
  signalId: "order.submitted",
});
SurfacePackageDirectionReconnect owner
Managed WebSocket websocket({ open })@use-crux/core/signal/transportInbound third-party WS → durable envelopes → SignalsRuntime worker stream fiber

Operator notes for WebSocket:

  • Core does not own browser/Node WebSocket, ping/pong timers, or wire codecs.
  • Receive-only ingress omits acknowledge. When the provider needs an application ack after durable progress, attach acknowledge on the envelope item. Ack failure is observable and reconnects from the durable cursor; it never rolls back acceptance.
  • Use createBoundedPushBuffer (or an equivalent bound). Overflow must fail the connection — never silently drop messages.
  • EOF, transient throw, terminal fault, abort, config invalidation, and accept-before-checkpoint reuse the managed stream recipe above.

Out of scope

First-party WebSocket authoring helpers and Channel exclusive conversation ownership remain follow-on work (#340 WebSocket child, #302 Channels). Webhook edge accept, polling, managed stream, and managed SSE share one Runtime worker and envelope kernel.

On this page