UNPKG

partyserver

Version:

Build real-time applications powered by [Durable Objects](https://developers.cloudflare.com/durable-objects/), inspired by [PartyKit](https://www.partykit.io/).

392 lines (390 loc) 14.2 kB
import { DurableObject } from "cloudflare:workers"; //#region src/types.d.ts type ImmutablePrimitive = undefined | null | boolean | string | number; type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>; type ImmutableArray<T> = ReadonlyArray<Immutable<T>>; type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>; type ImmutableSet<T> = ReadonlySet<Immutable<T>>; type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> }; type ConnectionState<T> = ImmutableObject<T> | null; type ConnectionSetStateFn<T> = (prevState: ConnectionState<T>) => T; type ConnectionContext = { request: Request; }; /** A WebSocket connected to the Server */ type Connection<TState = unknown> = WebSocket & { /** Connection identifier */ id: string; /** * The URL of the original WebSocket upgrade request. * Persisted in the WebSocket attachment so it survives hibernation. */ uri: string | null; /** * Arbitrary state associated with this connection. * Read-only — use {@link Connection.setState} to update. * * This property is configurable, meaning it can be redefined via * `Object.defineProperty` by downstream consumers (e.g. the Cloudflare * Agents SDK) to namespace or wrap internal state storage. */ state: ConnectionState<TState>; /** * Update the state associated with this connection. * * Accepts either a new state value or an updater function that receives * the previous state and returns the next state. * * This property is configurable, meaning it can be redefined via * `Object.defineProperty` by downstream consumers. If you redefine * `state` and `setState`, you are responsible for calling * `serializeAttachment` / `deserializeAttachment` yourself if you need * the state to survive hibernation. */ setState( state: TState | ConnectionSetStateFn<TState> | null ): ConnectionState<TState>; /** * @deprecated use {@link Connection.setState} instead. * * Low-level method to persist data in the connection's attachment storage. * This property is configurable and can be redefined by downstream * consumers that need to wrap or namespace the underlying storage. */ serializeAttachment<T = unknown>(attachment: T): void; /** * @deprecated use {@link Connection.state} instead. * * Low-level method to read data from the connection's attachment storage. * This property is configurable and can be redefined by downstream * consumers that need to wrap or namespace the underlying storage. */ deserializeAttachment<T = unknown>(): T | null; /** * Tags assigned to this connection via {@link Server.getConnectionTags}. * Always includes the connection id as the first tag. */ tags: readonly string[]; /** * @deprecated Use `this.name` on the Server instead. * The server name. Populated from `Server.name` after initialization. */ server: string; }; //#endregion //#region src/index.d.ts type WSMessage = ArrayBuffer | ArrayBufferView | string; interface RoutingRetryEvent { error: unknown; attempt: number; maxAttempts: number; delayMs: number; name: string; className?: string; } interface RoutingRetryOptions { /** Max number of attempts, including the first. Default: 3 */ maxAttempts?: number; /** Base delay in ms for exponential backoff. Default: 100 */ baseDelayMs?: number; /** Max delay cap in ms. Default: 800 */ maxDelayMs?: number; /** Optional callback invoked before each retry delay. */ onRetry?: (event: RoutingRetryEvent) => void | Promise<void>; } /** * For a given server namespace, create a server with a name. * * Makes an RPC that awaits the DO's `onStart()` before returning, so callers * can invoke user-defined RPC methods on the returned stub and trust that * `onStart()` has completed. (User-defined RPC methods don't * otherwise pass through `Server.fetch()`, which is where initialization * would normally be triggered.) * * `this.name` inside the DO is always populated from `ctx.id.name`, so * the RPC no longer needs to carry the name for bookkeeping; it exists * purely to synchronize `onStart()` and to deliver `props`. */ declare function getServerByName< Env extends Cloudflare.Env = Cloudflare.Env, T extends Server<Env> = Server<Env>, Props extends Record<string, unknown> = Record<string, unknown> >( serverNamespace: DurableObjectNamespace<T>, name: string, options?: { jurisdiction?: DurableObjectJurisdiction; locationHint?: DurableObjectLocationHint; props?: Props; routingRetry?: false | RoutingRetryOptions; } ): Promise<DurableObjectStub<T>>; interface Lobby<Env = Cloudflare.Env> { /** * The kebab-case namespace from the URL path (e.g. `"my-agent"`). * @deprecated Use `className` instead, which returns the Durable Object class name. * In the next major version, `party` will return the class name instead of the kebab-case namespace. */ party: string; /** The Durable Object class name / env binding name (e.g. `"MyAgent"`). */ className: Extract<keyof Env, string>; /** The room / instance name extracted from the URL. */ name: string; } interface PartyServerOptions< Env = Cloudflare.Env, Props = Record<string, unknown> > { prefix?: string; jurisdiction?: DurableObjectJurisdiction; locationHint?: DurableObjectLocationHint; props?: Props; /** * Whether to enable CORS for matched routes. * * When `true`, uses default permissive CORS headers: * - Access-Control-Allow-Origin: * * - Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS * - Access-Control-Allow-Headers: * * - Access-Control-Max-Age: 86400 * * For credentialed requests, pass explicit headers with a specific origin: * ```ts * cors: { * "Access-Control-Allow-Origin": "https://myapp.com", * "Access-Control-Allow-Credentials": "true", * "Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS", * "Access-Control-Allow-Headers": "Content-Type, Authorization" * } * ``` * * When set to a `HeadersInit` value, uses those as the CORS headers instead. * CORS preflight (OPTIONS) requests are handled automatically for matched routes. * Non-WebSocket responses on matched routes will also have the CORS headers appended. */ cors?: boolean | HeadersInit; /** * Retry transient Durable Object infrastructure errors thrown while routing * to the target DO. Enabled by default; pass `false` to disable. * * Only errors marked `retryable === true` are retried, and overloaded * errors (`overloaded === true`) are never retried. */ routingRetry?: false | RoutingRetryOptions; onBeforeConnect?: ( req: Request, lobby: Lobby<Env> ) => Response | Request | void | Promise<Response | Request | void>; onBeforeRequest?: ( req: Request, lobby: Lobby<Env> ) => | Response | Request | void | Promise<Response | Request | undefined | void>; } declare function routePartykitRequest< Env extends Cloudflare.Env = Cloudflare.Env, T extends Server<Env> = Server<Env>, Props extends Record<string, unknown> = Record<string, unknown> >( req: Request, env?: Env, options?: PartyServerOptions<Env, Props> ): Promise<Response | null>; declare class Server< Env extends Cloudflare.Env = Cloudflare.Env, Props extends Record<string, unknown> = Record<string, unknown> > extends DurableObject<Env> { #private; static options: { hibernate?: boolean; }; /** * Execute SQL queries against the Server's database * @template T Type of the returned rows * @param strings SQL query template strings * @param values Values to be inserted into the query * @returns Array of query results */ sql<T = Record<string, string | number | boolean | null>>( strings: TemplateStringsArray, ...values: (string | number | boolean | null)[] ): T[]; constructor(ctx: DurableObjectState, env: Env); /** * Handle incoming requests to the server. */ fetch(request: Request): Promise<Response>; webSocketMessage(ws: WebSocket, message: WSMessage): Promise<void>; webSocketClose( ws: WebSocket, code: number, reason: string, wasClean: boolean ): Promise<void>; webSocketError(ws: WebSocket, error: unknown): Promise<void>; /** * @internal — Do not use directly. This is an escape hatch for frameworks * (like Agents) that receive calls via native DO RPC, bypassing the * standard fetch/alarm/webSocket entry points where initialization * normally happens. Calling this from application code is unsupported * and may break without notice. */ __unsafe_ensureInitialized(): Promise<void>; /** * The name for this server. * * Resolves from `this.ctx.id.name` — the native DO id name, populated * whenever the stub was created via `idFromName()` or `getByName()`. * This is available inside every entry point (including the constructor, * alarms, and hibernating websocket handlers). * * For alarm handlers firing on stale on-disk alarm records from * older workerd versions that didn't persist `name` into the alarm * record, the name is recovered from a storage fallback record. * * Throws if neither source is available — typically this means the DO * was addressed via `idFromString()` or `newUniqueId()`, which is not * supported by PartyServer. */ get name(): string; /** * Establish this server's name and trigger `onStart()`. * * Use cases: * * 1. **Framework-level bootstrap of DOs where `ctx.id.name` is * undefined** — e.g. DOs addressed via `idFromString()` / * `newUniqueId()`. `setName()` stashes the name in memory and * persists it under `__ps_name` so cold-wake invocations * recover it via `#ensureInitialized()`'s legacy fallback. * 2. **Delivering initial `props` to `onStart()`** via the * optional second argument. * * For DOs addressed via `idFromName()` / `getByName()`, calling * `setName()` is redundant — `this.name` is available automatically * from `ctx.id.name`. The normal initialization path also persists * a fallback record so old-compat alarm handlers can recover the name. * Throws if `name` does not match `ctx.id.name`. * * **Not appropriate for facets.** Cloudflare Agents and any other * framework using `ctx.facets.get(...)` should pass an explicit * `id` in `FacetStartupOptions` so the facet has its own * `ctx.id.name`: * * ```ts * const stub = ctx.facets.get(facetKey, () => ({ * class: ChildClass, * id: ctx.exports.SomeBoundDOClass.idFromName(facetName), * })); * ``` * * Without an explicit `id`, the facet inherits the parent DO's * `ctx.id` (including `ctx.id.name`), and `setName()` will throw * the ctx.id.name-mismatch error because the facet's intended * name differs from the parent's. See * https://developers.cloudflare.com/dynamic-workers/usage/durable-object-facets/ * for the `FacetStartupOptions.id` semantics. * * @deprecated for callers that address DOs via `idFromName()` / * `getByName()`. Still the supported API for framework-level * bootstrap of header/`newUniqueId`-addressed DOs and for * delivering initial `props` to `onStart()`. */ setName(name: string, props?: Props): Promise<void>; /** * @internal * @deprecated Retained for backward compatibility with older callers. * `routePartykitRequest` no longer uses this method; it sends props via * the `x-partykit-props` header on the underlying `fetch()` request. */ _initAndFetch( name: string, props: Props | undefined, request: Request ): Promise<Response>; /** Send a message to all connected clients, except connection ids listed in `without` */ broadcast( msg: string | ArrayBuffer | ArrayBufferView, without?: string[] | undefined ): void; /** Get a connection by connection id */ getConnection<TState = unknown>(id: string): Connection<TState> | undefined; /** * Get all connections. Optionally, you can provide a tag to filter returned connections. * Use `Server#getConnectionTags` to tag the connection on connect. */ getConnections<TState = unknown>(tag?: string): Iterable<Connection<TState>>; /** * You can tag a connection to filter them in Server#getConnections. * Each connection supports up to 9 tags, each tag max length is 256 characters. */ getConnectionTags( connection: Connection, context: ConnectionContext ): string[] | Promise<string[]>; /** * Called when the server is started for the first time. */ onStart(props?: Props): void | Promise<void>; /** * Called when a new connection is made to the server. */ onConnect( connection: Connection, ctx: ConnectionContext ): void | Promise<void>; /** * Called when a message is received from a connection. */ onMessage(connection: Connection, message: WSMessage): void | Promise<void>; /** * Called when a connection is closed. */ onClose( connection: Connection, code: number, reason: string, wasClean: boolean ): void | Promise<void>; /** * Called when an error occurs on a connection. */ onError(connection: Connection, error: unknown): void | Promise<void>; /** * Called when a request is made to the server. */ onRequest(request: Request): Response | Promise<Response>; /** * Called when an exception occurs. * @param error - The error that occurred. */ onException(error: unknown): void | Promise<void>; onAlarm(): void | Promise<void>; alarm(): Promise<void>; } //#endregion export { Connection, ConnectionContext, ConnectionSetStateFn, ConnectionState, Lobby, PartyServerOptions, RoutingRetryEvent, RoutingRetryOptions, Server, WSMessage, getServerByName, routePartykitRequest }; //# sourceMappingURL=index.d.ts.map