rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
136 lines (135 loc) • 8.53 kB
JavaScript
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)),
];
};