import { $t as IDuplexAcceptorCarrier, Gt as IChannelServer, Ht as IChannelHostAdapter, It as IFetchHandler, Jt as IServeConnectionStateOptions, Lt as IForwardContext, Ut as TServeHostOptions, d as ActionRuntime, dn as TActionRuntimeHandler, pn as IActionChannel, r as IAcceptorFrameProtocol, sn as ConnectionStateStore, tn as IInboundFrameLimits, u as ActionDomain, vn as TCombinedAcceptorDomains, zt as IForwardToOptions } from "../../ChannelAcceptor-Cmu6jyhn.mjs"; import { ESecurityLevel, IClientVerifyKeyResolver, RuntimeCoordinate } from "@nice-code/wire"; import { ClientCryptoKeyLink, StorageAdapter, TCreateDurableObjectStorageOptions, TCreateKVStorageOptions } from "@nice-code/util"; import { ISqlStorage, ISqlStorageCursor, cloudflareReliableLog, createSqlReliableLogStore } from "@nice-code/wire/platform/cloudflare"; //#region src/platform/cloudflare/index.d.ts /** * Cloudflare-specific helpers for `@nice-code/action`, imported from `@nice-code/action/platform/cloudflare`. * They collapse the Durable Object boilerplate (the `WebSocketPair` upgrade, hibernation attachment wiring, * and DO-storage adapter) into one-liners you hand to `serveChannel`. The core library stays * platform-agnostic — nothing here is reachable from the main entry. * * The Workers runtime surface this module needs is declared *structurally* (and the two globals it * constructs are declared module-locally below) rather than pulled from `@cloudflare/workers-types`, so the * library's own DOM-lib build never clashes with that package's global `Response`/`WebSocket` redefinitions. * A real `DurableObjectState` and its hibernatable `WebSocket`s satisfy these shapes. */ type TDurableObjectStorage = TCreateDurableObjectStorageOptions["durableObjectStorage"]; /** The slice of a Durable Object's hibernatable WebSocket these helpers touch. */ interface IDurableObjectWebSocket { send(data: string | ArrayBuffer | Uint8Array): void; serializeAttachment(value: unknown): void; deserializeAttachment(): any; } /** * The authenticated client coordinate persisted on a hibernatable socket's attachment (review * A.5): the supported way to answer "who is this socket?" — live and at DO wake — instead of * reaching into the attachment's raw shape (`attachment?.binding?.client?.…`), which is the * library's private persistence format. `undefined` for a socket with no readable binding (e.g. * still mid-handshake, or persisted before the versioned binding schema). */ declare function clientCoordinateFromConnection(ws: IDurableObjectWebSocket): RuntimeCoordinate | undefined; /** The slice of a Durable Object's `state` (its `ctx`) these helpers touch. */ interface IDurableObjectContext { /** DO storage; `sql` is present on a SQLite-backed class and backs the persisted reliability tier. */ storage: TDurableObjectStorage & { sql?: ISqlStorage; }; getWebSockets(): IDurableObjectWebSocket[]; acceptWebSocket(ws: IDurableObjectWebSocket): void; /** Register a runtime-answered keepalive so pings never wake the DO. */ setWebSocketAutoResponse(pair: IWebSocketRequestResponsePair): void; } /** An `IChannelServer` whose connections are a Durable Object's hibernatable WebSockets — the type to * store the result of `serveChannel(...)` in when serving over {@link durableObjectWsCarrier}. `TApp` is the * per-connection app-state type when `connectionState` is used (defaults to `unknown` otherwise). */ type TDurableObjectChannelServer = IChannelServer; /** The keepalive pair shape — just the fields the Workers runtime's `WebSocketRequestResponsePair` exposes. */ interface IWebSocketRequestResponsePair { readonly request: string; readonly response: string; } interface IDurableObjectWsCarrierOptions { /** * Whether each socket runs the secure handshake (default `true`). Pass `false` for a plain WS endpoint — * then `serveChannel` needs no `storage` for this carrier. */ secure?: boolean; /** * Sockets this carrier must ignore when it enumerates the DO's connections at wake. A Durable Object * may hold sockets that are not client links — a devtools dev socket, say — and a channel server * must never try to rehydrate a binding for one. */ excludeConnection?: (connection: IDurableObjectWebSocket) => boolean; /** Carrier-level frame/rate enforcement before secure handshake and action decoding. */ inboundLimits?: IInboundFrameLimits; } /** * The devtools handle a DO can hand to {@link serveDurableObject} — structurally the wire devtools * host (`createNiceDurableObjectDevtools(ctx, …)` from `@nice-code/devtools/server`). Typed * structurally so this package gains no runtime dependency on the devtools packages: a production * Worker that never imports devtools ships none of it. Devtools rides the wire mux as its own * (token-gated, plain-admitted) **protocol** — no separate dev route, no socket bookkeeping: * passing the handle registers the protocol beside the app's `protocols` and defaults the server's * `wireTap` to the handle's traffic core. */ interface IDurableObjectDevtoolsHandle { /** * Whether this handle is live. * * A *disabled* handle — production, or no dev token — is inert but not absent: * `createNiceDurableObjectDevtools` hands one back rather than making every DO write the same * `token ? devtools(…) : undefined` conditional. Its `wireTap` is a no-op (wiring it anyway * would make the wire size every frame to feed a sink) and it carries no protocol. Absent reads * as enabled. */ readonly enabled?: boolean; readonly wireTap: import("@nice-code/wire").TWireTapFn; /** The devtools wire acceptor protocol — registered alongside the app's `protocols`. */ readonly protocol?: IAcceptorFrameProtocol; } /** * Build a hibernatable-WebSocket acceptor carrier for a Durable Object in one call — the `send`, the * `WebSocketPair` upgrade, and the hibernation attachment hooks all derived from the DO's `ctx`. Hand it * straight to `serveChannel`'s `carriers`, and forward the DO's socket events to the returned handle: * ```ts * const ws = durableObjectWsCarrier(this.ctx); * const server = serveChannel(runtime, channel, { * clientEnv, * storage: durableObjectStorage(this.ctx, { keyPrefix: "ws:" }), * handlers: [localHandler], * carriers: [ws, httpAcceptorCarrier()], * }); * // webSocketMessage(c, m) => ws.receive(c, m); * // webSocketClose/Error(c) => ws.drop(c); * ``` * * The carrier exposes the DO's socket attachment to `serveChannel`, which persists the routing binding * there and replays it on wake — and, when `connectionState` is requested, co-stores per-connection app * state in the *same* attachment, so both survive eviction without the DO wiring any of it by hand. */ declare function durableObjectWsCarrier(ctx: IDurableObjectContext, options?: IDurableObjectWsCarrierOptions): IDuplexAcceptorCarrier; interface IDurableObjectStorageOptions { /** Namespace prefix for every key (e.g. `"demo-ws:"`), so several adapters can share one DO storage. */ keyPrefix?: string; } /** * Wrap a Durable Object's storage as a {@link StorageAdapter} for `serveChannel`'s `storage` — sugar over * `createDurableObjectStorageAdapter({ durableObjectStorage: ctx.storage, … })` so a DO needs one import. */ declare function durableObjectStorage(ctx: IDurableObjectContext, options?: IDurableObjectStorageOptions): StorageAdapter; /** * Wrap a Cloudflare KV namespace as a {@link StorageAdapter} for an action endpoint's `storage` — sugar * over `@nice-code/util`'s `createKVStorageAdapter`, re-exported here so a Worker integrating * `@nice-code/action` needs only one import. Back a stateless {@link serveWorker} endpoint's crypto * identity + TOFU pins with it. */ declare function kvStorageAdapter(options: TCreateKVStorageOptions): StorageAdapter; /** {@link serveWorker}'s options — the stateless-Worker counterpart of {@link serveDurableObject}. */ interface IServeWorkerOptions[]> { /** * Factory for this endpoint's runtime — a factory, not an instance, because the Workers runtime forbids * generating random ids / doing I/O at module scope. `serveWorker` builds it lazily on the first request * and memoizes it for the isolate's life. */ runtime: () => ActionRuntime; /** Coordinate of the connecting clients (the offline-return scoring fallback; see `serveChannel`). */ clientEnv?: RuntimeCoordinate; /** * Backing store for the crypto identity + TOFU pins — a generic {@link StorageAdapter} the developer * supplies (e.g. {@link kvStorageAdapter} over a KV namespace). Required unless `secure: false`. */ storage?: StorageAdapter; /** * Factory for your execution handlers (e.g. the local handler holding the action cases) — a factory, not * an array, for the same reason as {@link runtime}: constructing a handler generates a random id, which * the Workers runtime forbids at module scope. `serveWorker` calls it lazily on the first request. */ handlers?: () => TActionRuntimeHandler[]; /** Accepted level(s); defaults to negotiating any of none/authenticated/encrypted. */ securityLevel?: ESecurityLevel | readonly ESecurityLevel[]; /** * Trust policy for a client's verify key. Defaults to **in-memory TOFU** — the right default for a public * endpoint hit by fresh per-page client identities (persisting their pins would only accumulate, and each * key is signature-verified). Pass a storage-backed resolver for cross-isolate pinning. */ verifyKeyResolver?: IClientVerifyKeyResolver; /** Whether the exchange runs the secure handshake (default `true`). `false` = a plain endpoint, no storage. */ secure?: boolean; /** CORS for the endpoint (default permissive `*`; `false` attaches none). */ cors?: Record | false; /** * Crypto-identity provisioning. `"required"` (default) builds an `identityMode: "required"` link and * provisions it once on the first request — fork-safe on an *eventually-consistent* store (Cloudflare KV), * where a transient read miss could otherwise fork a second identity that pinned clients then reject. * `"lazy"` defers to the store's own consistency (fine for a strongly-consistent store). Ignored when * `link` is passed (then you own provisioning out-of-band). */ identityMode?: "required" | "lazy"; /** Pre-built crypto identity; overrides the storage-derived link (you then `provisionIdentity()` it yourself). */ link?: ClientCryptoKeyLink; /** Default per-action timeout for server-initiated actions awaiting a client response. */ defaultTimeout?: number; /** * The individual channels this endpoint serves, for **subset selection** — set by {@link serveWorkers} so a * client connecting any subset (advertised as `hello.channels` tags) gets the matching composed dictionary * version. Omit for a single channel (then `channel` is used as-is). When two or more are given, a * connection with no advertised tags falls back to the combined `channel`. */ channels?: readonly IActionChannel[]; /** Internal: the carrier channel this endpoint serves (always one HTTP exchange carrier). */ _channelDomains?: TO_ACCEPTOR; } /** The handle {@link serveWorker} returns — forward the Worker's `fetch` to it. */ interface IWorkerChannelServer { /** Forward the Worker's incoming request here. Awaits one-time identity provisioning on the first call. */ fetch(request: Request): Promise; /** * Provision the crypto identity out-of-band (idempotent). Call from a one-time deploy step for a * multi-region deploy; otherwise the first `fetch` provisions lazily for you. */ provision(): Promise; } /** * Serve a secure channel from a **stateless Worker** in one call — the stateless counterpart of * {@link serveDurableObject}. It folds in everything a hand-rolled stateless endpoint repeats: the crypto * identity link (with one-time provisioning on an eventually-consistent store), the in-memory TOFU default, * the single HTTP-exchange carrier, and the lazy memoization the Workers global scope forces. The whole * thing builds on the first request and is reused across the isolate's life: * ```ts * const serveCreate = serveWorker(bridgeCreateChannel, { * runtime: () => new ActionRuntime(bridgeCreatorCoord), * clientEnv: frontendCoord, * storage: kvStorageAdapter({ kvNamespace: env.KV, keyPrefix: "bridge-create-identity:" }), * handlers: () => [bridgeCreateHandler()], * }); * // route it: honoApi.on(["POST", "OPTIONS"], "/create/secure", (c) => serveCreate.fetch(c.req.raw)); * // or drop into a router: actionRouter().route("/create/*", serveCreate) * ``` * * A stateless Worker realistically serves the **HTTP-exchange** path only (no durable sockets) — WebSocket * / stateful channels live in a Durable Object (`serveDurableObject`), reached through {@link forwardToDurableObject}. * * To serve **several channels** on one stateless endpoint, use {@link serveWorkers} (the multi-channel form * — the stateless dual of `serveChannels`): it composes the matching dictionary version per connection from * the client's advertised subset, so a client connecting just one channel via `connectChannel` is accepted. */ declare function serveWorker[] = readonly ActionDomain[], TO_CONNECTOR extends readonly ActionDomain[] = readonly ActionDomain[]>(channel: IActionChannel, options: IServeWorkerOptions): IWorkerChannelServer; /** * Serve a **set** of channels from one stateless Worker endpoint — the stateless dual of `serveChannels` * and the multi-channel form of {@link serveWorker}. The channels are combined into one (their domains * unioned in list order, see {@link combineChannels}) and served over a single HTTP-exchange carrier + one * crypto identity; the runtime routes each inbound action to its handler by domain, exactly as for a single * channel. Crucially the individual channels are passed through as the **subset registry**, so a client * connecting just one of them via `connectChannel` advertises its tag and gets the matching composed * dictionary version — without this, a combined endpoint would reject every single-channel client with a * dictionary-version mismatch (the trap that forces a per-channel endpoint otherwise). * ```ts * const serveStatelessApi = serveWorkers([bridgeCreateChannel, walletRegisterChannel], { * runtime: () => new ActionRuntime(backendCoord), * storage: kvStorageAdapter({ kvNamespace: env.KV, keyPrefix: "stateless-api-identity:" }), * securityLevel: ESecurityLevel.encrypted, * handlers: () => [bridgeCreateHandler(), walletRegisterHandler()], * }); * // route it: actionRouter().route("/api/action/*", serveStatelessApi) * ``` * Both ends must list the **same channels in the same order** (the `combineChannels` contract) — though each * client only connects the subset it uses (one channel via `connectChannel`, several via `connectChannels`). * A multi-role endpoint serving clients of several envs should omit `clientEnv` (see `serveChannel`). */ declare function serveWorkers[]>(channels: CHANNELS, options: IServeWorkerOptions>): IWorkerChannelServer; interface ICloudflareDurableObjectHostOptions { /** Namespace prefix for the DO-storage crypto identity keys (e.g. `"lobby-ws:"`). */ keyPrefix?: string; /** * The HTTP fallback that sits beside the WebSocket: `"plain"` (default — POSTs the raw action wire, the * usual fallback for a public client), `"secure"` (the full handshake-protected exchange, sharing the WS * identity), or `false` (WebSocket only). */ httpFallback?: "plain" | "secure" | false; /** Whether the WebSocket runs the secure handshake (default `true`). `false` = a plain WS endpoint. */ secure?: boolean; /** * Prebuilt identity/TOFU storage. Defaults to a tracked adapter over `ctx.storage`; pass an * untracked/prefixed adapter when whole-object deletion owns reclamation or when sharing identity. */ storage?: StorageAdapter; /** Carrier-level frame/rate enforcement before secure handshake and action decoding. */ inboundLimits?: IInboundFrameLimits; } /** * Build the {@link IChannelHostAdapter} for a Durable Object in one call — the entire repeated transport * stack a DO would otherwise assemble by hand: a hibernatable secure WebSocket carrier, an HTTP fallback, * the DO-storage-backed crypto identity, and a runtime-answered `ping`/`pong` keepalive (so pings never * wake the DO). Hand it to {@link serveHost}, or use {@link serveDurableObject} which composes both. */ declare function cloudflareDurableObjectHost(ctx: IDurableObjectContext, options?: ICloudflareDurableObjectHostOptions): IChannelHostAdapter; /** {@link serveDurableObject}'s options: the `serveHost` surface + the DO runtime + the host knobs. */ type TServeDurableObjectOptions[], TApp = unknown> = TServeHostOptions & ICloudflareDurableObjectHostOptions & { /** This DO's runtime (e.g. `new ActionRuntime(coord.withPersistentId(ctx.id.toString()))`). */runtime: ActionRuntime; /** * A devtools handle from `createNiceDurableObjectDevtools(ctx, …)`. Passing it wires the whole * observation path: the server's `wireTap` feeds the devtools traffic core (unless the app * brought its own tap), and the handle's devtools **protocol** — token-gated, plain-admitted — * is registered beside the app's `protocols`, so a devtools window dials this DO's ordinary * WebSocket endpoint. Omit it (or pass a disabled handle) and a DO ships no devtools protocol * and no cost — the default. */ devtools?: IDurableObjectDevtoolsHandle; }; /** * Serve a channel from a Durable Object **with devtools attached**: * ```ts * const devtools = createNiceDurableObjectDevtools(this.ctx, { * runtime, stage: "development", token: env.DEVTOOLS_TOKEN, * realms: { match: () => this.engine }, * }); * this.server = serveDurableObject(this.ctx, channel, { runtime, devtools }); * ``` * The DO's forwards (`fetch` / `receive` / `drop`) stay exactly as they are — devtools rides the * wire mux as its own token-gated protocol on the same sockets. See `createWireDevtoolsHost` for * the safety model and the hibernation caveat. */ /** * Serve a secure channel from a Durable Object in one call — the whole transport stack * ({@link cloudflareDurableObjectHost}: hibernatable secure WebSocket + HTTP fallback + DO-storage crypto * identity + keepalive) folded in, leaving the DO to forward its four socket lifecycle methods to the * returned server's `fetch` / `receive` / `drop`: * ```ts * const server = serveDurableObject(this.ctx, lobbyChannel, { * runtime, clientEnv, * connectionState: { schema: vs_player }, // optional, survives hibernation * channelCases: { join: (action, conn) => { conn.setState(action.input); conn.broadcast(…); } }, * }); * // fetch(req) => server.fetch(req) * // webSocketMessage(ws, m) => server.receive(ws, m) * // webSocketClose/Error(ws)=> server.drop(ws) * ``` * Passing `connectionState` narrows the return so `server.connections` is non-optional. * * To serve **several channels** on one DO endpoint, pass a channel *array* — the DO becomes a multi-channel * acceptor and a client connecting any subset (via `connectChannels` / `connectChannel`) gets the matching * codec composed per connection: * ```ts * const server = serveDurableObject(this.ctx, [coreChannel, lobbyChannel], { runtime, clientEnv, handlers }); * ``` */ declare function serveDurableObject[], TO_CONNECTOR extends readonly ActionDomain[], TApp>(ctx: IDurableObjectContext, channel: IActionChannel, options: TServeDurableObjectOptions & { connectionState: IServeConnectionStateOptions; }): TDurableObjectChannelServer & { connections: ConnectionStateStore; }; declare function serveDurableObject[] = readonly ActionDomain[], TO_CONNECTOR extends readonly ActionDomain[] = readonly ActionDomain[], TApp = unknown>(ctx: IDurableObjectContext, channel: IActionChannel, options: TServeDurableObjectOptions): TDurableObjectChannelServer; declare function serveDurableObject[], TApp>(ctx: IDurableObjectContext, channels: CHANNELS, options: TServeDurableObjectOptions, TApp> & { connectionState: IServeConnectionStateOptions; }): TDurableObjectChannelServer & { connections: ConnectionStateStore; }; declare function serveDurableObject[], TApp = unknown>(ctx: IDurableObjectContext, channels: CHANNELS, options: TServeDurableObjectOptions, TApp>): TDurableObjectChannelServer; /** The slice of a Durable Object stub {@link forwardToDurableObject} calls — `env.NS.get(id)`. */ interface IDurableObjectStub { fetch(request: Request): Promise; } /** * Build an opaque forwarder that fans a Worker's incoming action request out to a *per-id* (or singleton) * Durable Object which serves the exchange itself — the CF-specific sugar over the generic {@link forwardTo}. * * A secure exchange body is opaque to the Worker (handshake / encrypted frames), so the DO it belongs to is * chosen from the **URL**, not the body; security stays end-to-end between the origin client and the DO. * The CORS `OPTIONS` preflight is answered *at the edge* (default) so a per-id DO is never woken (or billed) * just to reply to a preflight. Returns an {@link IFetchHandler}, so it drops into {@link actionRouter} or * any framework: * ```ts * // wrangler: a Durable Object namespace `BRIDGE`, each instance one bridge serving `bridgeChannel`. * const router = actionRouter() * .route("/bridge/:id/*", forwardToDurableObject(({ params }) => * env.BRIDGE.get(env.BRIDGE.idFromString(params.id)))) // per-id, E2E client ↔ DO * .route("/app/*", forwardToDurableObject(() => * env.APP.get(env.APP.idFromName("main")))); // singleton * export default { fetch: (request: Request) => router.fetch(request) }; * * // …and in the Durable Object, serve the secure exchange (+ a WS upgrade) as usual: * // fetch(request) { return this.server.fetch(request); } * // where this.server = serveDurableObject(this.ctx, bridgeChannel, { runtime, httpFallback: "secure" }); * ``` * `pickStub` may be async (e.g. to look an id up first); it receives `{ request, url, params }` (matched * path params when forwarded through {@link actionRouter}). */ declare function forwardToDurableObject(pickStub: (ctx: IForwardContext) => IDurableObjectStub | Promise, options?: IForwardToOptions): IFetchHandler; //#endregion export { ICloudflareDurableObjectHostOptions, IDurableObjectContext, IDurableObjectDevtoolsHandle, IDurableObjectStorageOptions, IDurableObjectStub, IDurableObjectWebSocket, IDurableObjectWsCarrierOptions, IServeWorkerOptions, type ISqlStorage, type ISqlStorageCursor, IWorkerChannelServer, TDurableObjectChannelServer, TServeDurableObjectOptions, clientCoordinateFromConnection, cloudflareDurableObjectHost, cloudflareReliableLog, createSqlReliableLogStore, durableObjectStorage, durableObjectWsCarrier, forwardToDurableObject, kvStorageAdapter, serveDurableObject, serveWorker, serveWorkers }; //# sourceMappingURL=index.d.mts.map