UNPKG

rwsdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

136 lines (135 loc) 8.53 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _SyncedStateProxy_instances, _SyncedStateProxy_stub, _SyncedStateProxy_keyHandler, _SyncedStateProxy_requestInfo, _SyncedStateProxy_transformKey, _SyncedStateProxy_callHandler; import { RpcTarget, newWorkersRpcResponse } from "capnweb"; import { env } from "cloudflare:workers"; import { route } from "../runtime/entries/router"; import { runWithRequestInfo } from "../runtime/requestInfo/worker"; import { SyncedStateServer, } from "./SyncedStateServer.mjs"; import { DEFAULT_SYNCED_STATE_PATH } from "./constants.mjs"; export { SyncedStateServer }; const DEFAULT_SYNC_STATE_NAME = "syncedState"; class SyncedStateProxy extends RpcTarget { constructor(stub, keyHandler, requestInfo) { super(); _SyncedStateProxy_instances.add(this); _SyncedStateProxy_stub.set(this, void 0); _SyncedStateProxy_keyHandler.set(this, void 0); _SyncedStateProxy_requestInfo.set(this, void 0); __classPrivateFieldSet(this, _SyncedStateProxy_stub, stub, "f"); __classPrivateFieldSet(this, _SyncedStateProxy_keyHandler, keyHandler, "f"); __classPrivateFieldSet(this, _SyncedStateProxy_requestInfo, requestInfo, "f"); // Set stub in DO instance so handlers can access it if (stub && typeof stub._setStub === "function") { void stub._setStub(stub); } } async getState(key) { const transformedKey = await __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_transformKey).call(this, key); return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").getState(transformedKey); } async setState(value, key) { const transformedKey = await __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_transformKey).call(this, key); return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").setState(value, transformedKey); } async subscribe(key, client) { const transformedKey = await __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_transformKey).call(this, key); const subscribeHandler = SyncedStateServer.getSubscribeHandler(); if (subscribeHandler) { __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_callHandler).call(this, subscribeHandler, transformedKey, __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f")); } // dup the client if it is a function; otherwise, pass it as is; // this is because the client is a WebSocketRpcSession, and we need to pass a new instance of the client to the DO; const clientToPass = typeof client.dup === "function" ? client.dup() : client; return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").subscribe(transformedKey, clientToPass); } async unsubscribe(key, client) { const transformedKey = await __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_transformKey).call(this, key); // Call unsubscribe handler before unsubscribe, similar to subscribe handler // This ensures the handler is called even if the unsubscribe doesn't find a match // or if the RPC call fails const unsubscribeHandler = SyncedStateServer.getUnsubscribeHandler(); if (unsubscribeHandler) { __classPrivateFieldGet(this, _SyncedStateProxy_instances, "m", _SyncedStateProxy_callHandler).call(this, unsubscribeHandler, transformedKey, __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f")); } try { await __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").unsubscribe(transformedKey, client); } catch (error) { // Ignore errors during unsubscribe - handler has already been called // This prevents RPC stub disposal errors from propagating } } } _SyncedStateProxy_stub = new WeakMap(), _SyncedStateProxy_keyHandler = new WeakMap(), _SyncedStateProxy_requestInfo = new WeakMap(), _SyncedStateProxy_instances = new WeakSet(), _SyncedStateProxy_transformKey = /** * Transforms a key using the keyHandler, preserving async context so requestInfo.ctx is available. */ async function _SyncedStateProxy_transformKey(key) { if (!__classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f")) { return key; } if (__classPrivateFieldGet(this, _SyncedStateProxy_requestInfo, "f")) { // Preserve async context when calling keyHandler so requestInfo.ctx is available return await runWithRequestInfo(__classPrivateFieldGet(this, _SyncedStateProxy_requestInfo, "f"), async () => await __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f")(key, __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f"))); } return await __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f").call(this, key, __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f")); }, _SyncedStateProxy_callHandler = function _SyncedStateProxy_callHandler(handler, key, stub) { if (__classPrivateFieldGet(this, _SyncedStateProxy_requestInfo, "f")) { // Preserve async context when calling handler so requestInfo.ctx is available runWithRequestInfo(__classPrivateFieldGet(this, _SyncedStateProxy_requestInfo, "f"), () => { handler(key, stub); }); } else { handler(key, stub); } }; /** * Registers routes that forward sync state requests to the configured Durable Object namespace. * @param getNamespace Function that returns the Durable Object namespace from the Worker env. * @param options Optional overrides for base path and object name. * @returns Router entries for the sync state API. */ export const syncedStateRoutes = (getNamespace, options = {}) => { const basePath = options.basePath ?? DEFAULT_SYNCED_STATE_PATH; const durableObjectName = options.durableObjectName ?? DEFAULT_SYNC_STATE_NAME; const forwardRequest = async (request, requestInfo) => { const namespace = getNamespace(env); // Register the namespace and DO name so handlers can access it SyncedStateServer.registerNamespace(namespace, durableObjectName); const keyHandler = SyncedStateServer.getKeyHandler(); const roomHandler = SyncedStateServer.getRoomHandler(); // Get the room ID from the URL parameter, or undefined if not present const idParam = requestInfo.params?.id; // Resolve the room name using the roomHandler if present, otherwise use the param or default let resolvedRoomName; if (roomHandler) { resolvedRoomName = await runWithRequestInfo(requestInfo, async () => await roomHandler(idParam, requestInfo)); } else { resolvedRoomName = idParam ?? durableObjectName; } if (!keyHandler) { const id = namespace.idFromName(resolvedRoomName); return namespace.get(id).fetch(request); } const id = namespace.idFromName(resolvedRoomName); const coordinator = namespace.get(id); const proxy = new SyncedStateProxy(coordinator, keyHandler, requestInfo); return newWorkersRpcResponse(request, proxy); }; return [ route(basePath, (requestInfo) => forwardRequest(requestInfo.request, requestInfo)), route(basePath + "/:id", (requestInfo) => forwardRequest(requestInfo.request, requestInfo)), ]; };