rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
184 lines (183 loc) • 11.9 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);
};
import { env } from "cloudflare:workers";
import { route } from "../runtime/entries/router";
import { runWithRequestInfo } from "../runtime/requestInfo/worker";
import { loadCapnweb } from "./capnweb-loader.mjs";
import { SyncedStateServer, } from "./SyncedStateServer.mjs";
import { DEFAULT_SYNCED_STATE_PATH } from "./constants.mjs";
export { SyncedStateServer };
const DEFAULT_SYNC_STATE_NAME = "syncedState";
let SyncedStateProxyClass = null;
async function getSyncedStateProxy() {
var _SyncedStateProxy_instances, _SyncedStateProxy_stub, _SyncedStateProxy_keyHandler, _SyncedStateProxy_requestInfo, _SyncedStateProxy_subscriptionClients, _SyncedStateProxy_transformKey, _SyncedStateProxy_callHandler, _a;
const { RpcTarget, newWorkersRpcResponse } = await loadCapnweb();
if (!SyncedStateProxyClass) {
SyncedStateProxyClass = (_a = 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);
// Map original RPC callbacks to the duplicated callbacks sent to the DO
// so unsubscribe uses the same identity that subscribe registered.
_SyncedStateProxy_subscriptionClients.set(this, new Map());
__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;
let clientsForKey = __classPrivateFieldGet(this, _SyncedStateProxy_subscriptionClients, "f").get(transformedKey);
if (!clientsForKey) {
clientsForKey = new Map();
__classPrivateFieldGet(this, _SyncedStateProxy_subscriptionClients, "f").set(transformedKey, clientsForKey);
}
clientsForKey.set(client, clientToPass);
try {
return await __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").subscribe(transformedKey, clientToPass);
}
catch (error) {
if (clientsForKey.get(client) === clientToPass) {
clientsForKey.delete(client);
if (clientsForKey.size === 0) {
__classPrivateFieldGet(this, _SyncedStateProxy_subscriptionClients, "f").delete(transformedKey);
}
}
throw error;
}
}
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"));
}
const clientsForKey = __classPrivateFieldGet(this, _SyncedStateProxy_subscriptionClients, "f").get(transformedKey);
const clientToPass = clientsForKey?.get(client) ?? client;
try {
await __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").unsubscribe(transformedKey, clientToPass);
}
catch (error) {
// Ignore errors during unsubscribe - handler has already been called
// This prevents RPC stub disposal errors from propagating
}
finally {
if (clientsForKey && clientsForKey.get(client) === clientToPass) {
clientsForKey.delete(client);
if (clientsForKey.size === 0) {
__classPrivateFieldGet(this, _SyncedStateProxy_subscriptionClients, "f").delete(transformedKey);
}
}
}
}
},
_SyncedStateProxy_stub = new WeakMap(),
_SyncedStateProxy_keyHandler = new WeakMap(),
_SyncedStateProxy_requestInfo = new WeakMap(),
_SyncedStateProxy_subscriptionClients = 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);
}
},
_a);
}
return {
SyncedStateProxy: SyncedStateProxyClass,
newWorkersRpcResponse,
};
}
/**
* 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 { SyncedStateProxy, newWorkersRpcResponse } = await getSyncedStateProxy();
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)),
];
};