UNPKG

@rivetkit/redis

Version:

_Lightweight Libraries for Backends_

1,767 lines (1,764 loc) 132 kB
import { ActorAlreadyExists, ActorNotFound, ConnNotFound, IncorrectConnToken, InternalError, InvalidActionRequest, InvalidEncoding, InvalidParams, InvalidRequest, KEYS, MalformedMessage, MessageTooLong, Unreachable, UserError, assertUnreachable, deconstructError, getEnvUniversal, getLogger, httpUserAgent, logger, noopNext, stringifyError } from "./chunk-K6L53HR4.js"; // ../../core/dist/chunk-IVHL2KCJ.js import * as cbor from "cbor-x"; import { z } from "zod"; import { streamSSE } from "hono/streaming"; import { z as z2 } from "zod"; import { z as z4 } from "zod"; import { z as z3 } from "zod"; import z6 from "zod/v4"; import { z as z5 } from "zod"; var RUNTIME_LOGGER_NAME = "actor-runtime"; function logger2() { return getLogger(RUNTIME_LOGGER_NAME); } function assertUnreachable2(x) { logger2().error("unreachable", { value: `${x}`, stack: new Error().stack }); throw new Unreachable(x); } function generateSecureToken(length = 32) { const array = new Uint8Array(length); crypto.getRandomValues(array); return btoa(String.fromCharCode(...array)); } function generateRandomString(length = 32) { const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; for (let i = 0; i < length; i++) { const randomIndex = Math.floor(Math.random() * characters.length); result += characters[randomIndex]; } return result; } var EncodingSchema = z.enum(["json", "cbor"]); function serialize(value, encoding) { if (encoding === "json") { return JSON.stringify(value); } else if (encoding === "cbor") { const cleanValue = JSON.parse(JSON.stringify(value)); return cbor.encode(cleanValue); } else { assertUnreachable2(encoding); } } async function deserialize(data, encoding) { if (encoding === "json") { if (typeof data !== "string") { logger2().warn("received non-string for json parse"); throw new MalformedMessage(); } else { return JSON.parse(data); } } else if (encoding === "cbor") { if (data instanceof Blob) { const arrayBuffer = await data.arrayBuffer(); return cbor.decode(new Uint8Array(arrayBuffer)); } else if (data instanceof Uint8Array) { return cbor.decode(data); } else if (data instanceof ArrayBuffer || data instanceof SharedArrayBuffer) { return cbor.decode(new Uint8Array(data)); } else { logger2().warn("received non-binary type for cbor parse"); throw new MalformedMessage(); } } else { assertUnreachable2(encoding); } } function base64EncodeUint8Array(uint8Array) { let binary = ""; const len = uint8Array.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(uint8Array[i]); } return btoa(binary); } function base64EncodeArrayBuffer(arrayBuffer) { const uint8Array = new Uint8Array(arrayBuffer); return base64EncodeUint8Array(uint8Array); } function encodeDataToString(message) { if (typeof message === "string") { return message; } else if (message instanceof ArrayBuffer) { return base64EncodeArrayBuffer(message); } else if (message instanceof Uint8Array) { return base64EncodeUint8Array(message); } else { assertUnreachable2(message); } } function generateConnId() { return crypto.randomUUID(); } function generateConnToken() { return generateSecureToken(32); } var GenericConnGlobalState = class { websockets = /* @__PURE__ */ new Map(); sseStreams = /* @__PURE__ */ new Map(); }; function createGenericConnDrivers(globalState) { return { [CONN_DRIVER_GENERIC_WEBSOCKET]: createGenericWebSocketDriver(globalState), [CONN_DRIVER_GENERIC_SSE]: createGenericSseDriver(globalState), [CONN_DRIVER_GENERIC_HTTP]: createGeneircHttpDriver() }; } var CONN_DRIVER_GENERIC_WEBSOCKET = "genericWebSocket"; function createGenericWebSocketDriver(globalState) { return { sendMessage: (actor, conn, state, message) => { const ws = globalState.websockets.get(conn.id); if (!ws) { logger2().warn("missing ws for sendMessage", { actorId: actor.id, connId: conn.id, totalCount: globalState.websockets.size }); return; } const serialized = message.serialize(state.encoding); logger2().debug("sending websocket message", { encoding: state.encoding, dataType: typeof serialized, isUint8Array: serialized instanceof Uint8Array, isArrayBuffer: serialized instanceof ArrayBuffer, dataLength: serialized.byteLength || serialized.length }); if (serialized instanceof Uint8Array) { const buffer = serialized.buffer.slice( serialized.byteOffset, serialized.byteOffset + serialized.byteLength ); if (buffer instanceof SharedArrayBuffer) { const arrayBuffer = new ArrayBuffer(buffer.byteLength); new Uint8Array(arrayBuffer).set(new Uint8Array(buffer)); logger2().debug("converted SharedArrayBuffer to ArrayBuffer", { byteLength: arrayBuffer.byteLength }); ws.send(arrayBuffer); } else { logger2().debug("sending ArrayBuffer", { byteLength: buffer.byteLength }); ws.send(buffer); } } else { logger2().debug("sending string data", { length: serialized.length }); ws.send(serialized); } }, disconnect: async (actor, conn, _state, reason) => { const ws = globalState.websockets.get(conn.id); if (!ws) { logger2().warn("missing ws for disconnect", { actorId: actor.id, connId: conn.id, totalCount: globalState.websockets.size }); return; } const raw = ws.raw; if (!raw) { logger2().warn("ws.raw does not exist"); return; } const { promise, resolve } = Promise.withResolvers(); raw.addEventListener("close", () => resolve()); ws.close(1e3, reason); await promise; } }; } var CONN_DRIVER_GENERIC_SSE = "genericSse"; function createGenericSseDriver(globalState) { return { sendMessage: (_actor, conn, state, message) => { const stream = globalState.sseStreams.get(conn.id); if (!stream) { logger2().warn("missing sse stream for sendMessage", { connId: conn.id }); return; } stream.writeSSE({ data: encodeDataToString(message.serialize(state.encoding)) }); }, disconnect: async (_actor, conn, _state, _reason) => { const stream = globalState.sseStreams.get(conn.id); if (!stream) { logger2().warn("missing sse stream for disconnect", { connId: conn.id }); return; } stream.close(); } }; } var CONN_DRIVER_GENERIC_HTTP = "genericHttp"; function createGeneircHttpDriver() { return { disconnect: async () => { } }; } var ActionContext = class { /** * Should not be called directly. * * @param actorContext - The actor context * @param conn - The connection associated with the action */ constructor(actorContext, conn) { this.conn = conn; this.#actorContext = actorContext; } #actorContext; /** * Get the actor state */ get state() { return this.#actorContext.state; } /** * Get the actor variables */ get vars() { return this.#actorContext.vars; } /** * Broadcasts an event to all connected clients. */ broadcast(name, ...args) { this.#actorContext.broadcast(name, ...args); } /** * Gets the logger instance. */ get log() { return this.#actorContext.log; } /** * Gets actor ID. */ get actorId() { return this.#actorContext.actorId; } /** * Gets the actor name. */ get name() { return this.#actorContext.name; } /** * Gets the actor key. */ get key() { return this.#actorContext.key; } /** * Gets the region. */ get region() { return this.#actorContext.region; } /** * Gets the scheduler. */ get schedule() { return this.#actorContext.schedule; } /** * Gets the map of connections. */ get conns() { return this.#actorContext.conns; } /** * Returns the client for the given registry. */ client() { return this.#actorContext.client(); } /** * @experimental */ get db() { return this.#actorContext.db; } /** * Forces the state to get saved. */ async saveState(opts) { return this.#actorContext.saveState(opts); } /** * Runs a promise in the background. */ runInBackground(promise) { this.#actorContext.runInBackground(promise); } }; var ActionRequestSchema = z2.object({ // Args a: z2.array(z2.unknown()) }); var ActionResponseSchema = z2.object({ // Output o: z2.unknown() }); var ActionRequestSchema2 = z3.object({ // ID i: z3.number().int(), // Name n: z3.string(), // Args a: z3.array(z3.unknown()) }); var SubscriptionRequestSchema = z3.object({ // Event name e: z3.string(), // Subscribe s: z3.boolean() }); var ToServerSchema = z3.object({ // Body b: z3.union([ z3.object({ ar: ActionRequestSchema2 }), z3.object({ sr: SubscriptionRequestSchema }) ]) }); var TransportSchema = z4.enum(["websocket", "sse"]); function getValueLength(value) { if (typeof value === "string") { return value.length; } else if (value instanceof Blob) { return value.size; } else if (value instanceof ArrayBuffer || value instanceof SharedArrayBuffer || value instanceof Uint8Array) { return value.byteLength; } else { assertUnreachable2(value); } } async function parseMessage(value, opts) { const length = getValueLength(value); if (length > opts.maxIncomingMessageSize) { throw new MessageTooLong(); } const deserializedValue = await deserialize(value, opts.encoding); const { data: message, success, error } = ToServerSchema.safeParse(deserializedValue); if (!success) { throw new MalformedMessage(error); } return message; } var LOGGER_NAME = "actor-manager"; function logger22() { return getLogger(LOGGER_NAME); } var HonoWebSocketAdapter = class { // WebSocket readyState values CONNECTING = 0; OPEN = 1; CLOSING = 2; CLOSED = 3; #ws; #readyState = 1; // Start as OPEN since WSContext is already connected #eventListeners = /* @__PURE__ */ new Map(); #closeCode; #closeReason; constructor(ws) { this.#ws = ws; this.#readyState = this.OPEN; setTimeout(() => { this.#fireEvent("open", { type: "open", target: this }); }, 0); } get readyState() { return this.#readyState; } get binaryType() { return "arraybuffer"; } set binaryType(value) { } get bufferedAmount() { return 0; } get extensions() { return ""; } get protocol() { return ""; } get url() { return ""; } send(data) { if (this.readyState !== this.OPEN) { throw new Error("WebSocket is not open"); } try { logger22().debug("bridge sending data", { dataType: typeof data, isString: typeof data === "string", isArrayBuffer: data instanceof ArrayBuffer, dataStr: typeof data === "string" ? data.substring(0, 100) : "<non-string>" }); if (typeof data === "string") { this.#ws.send(data); } else if (data instanceof ArrayBuffer) { this.#ws.send(data); } else if (ArrayBuffer.isView(data)) { const buffer = data.buffer.slice( data.byteOffset, data.byteOffset + data.byteLength ); if (buffer instanceof SharedArrayBuffer) { const arrayBuffer = new ArrayBuffer(buffer.byteLength); new Uint8Array(arrayBuffer).set(new Uint8Array(buffer)); this.#ws.send(arrayBuffer); } else { this.#ws.send(buffer); } } else if (data instanceof Blob) { data.arrayBuffer().then((buffer) => { this.#ws.send(buffer); }).catch((error) => { logger22().error("failed to convert blob to arraybuffer", { error }); this.#fireEvent("error", { type: "error", target: this, error }); }); } else { logger22().warn("unsupported data type, converting to string", { dataType: typeof data, data }); this.#ws.send(String(data)); } } catch (error) { logger22().error("error sending websocket data", { error }); this.#fireEvent("error", { type: "error", target: this, error }); throw error; } } close(code = 1e3, reason = "") { if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) { return; } this.#readyState = this.CLOSING; this.#closeCode = code; this.#closeReason = reason; try { this.#ws.close(code, reason); this.#readyState = this.CLOSED; this.#fireEvent("close", { type: "close", target: this, code, reason, wasClean: code === 1e3 }); } catch (error) { logger22().error("error closing websocket", { error }); this.#readyState = this.CLOSED; this.#fireEvent("close", { type: "close", target: this, code: 1006, reason: "Abnormal closure", wasClean: false }); } } addEventListener(type, listener) { if (!this.#eventListeners.has(type)) { this.#eventListeners.set(type, /* @__PURE__ */ new Set()); } this.#eventListeners.get(type).add(listener); } removeEventListener(type, listener) { const listeners = this.#eventListeners.get(type); if (listeners) { listeners.delete(listener); } } dispatchEvent(event) { const listeners = this.#eventListeners.get(event.type); if (listeners) { for (const listener of listeners) { try { listener(event); } catch (error) { logger22().error(`error in ${event.type} event listener`, { error }); } } } return true; } // Internal method to handle incoming messages from WSContext _handleMessage(data) { let messageData; if (typeof data === "string") { messageData = data; } else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { messageData = data; } else if (data && typeof data === "object" && "data" in data) { messageData = data.data; } else { messageData = String(data); } logger22().debug("bridge handling message", { dataType: typeof messageData, isArrayBuffer: messageData instanceof ArrayBuffer, dataStr: typeof messageData === "string" ? messageData : "<binary>" }); this.#fireEvent("message", { type: "message", target: this, data: messageData }); } // Internal method to handle close from WSContext _handleClose(code, reason) { this.#ws.close(1e3, "hack_force_close"); if (this.readyState === this.CLOSED) return; this.#readyState = this.CLOSED; this.#closeCode = code; this.#closeReason = reason; this.#fireEvent("close", { type: "close", target: this, code, reason, wasClean: code === 1e3 }); } // Internal method to handle errors from WSContext _handleError(error) { this.#fireEvent("error", { type: "error", target: this, error }); } #fireEvent(type, event) { const listeners = this.#eventListeners.get(type); if (listeners) { for (const listener of listeners) { try { listener(event); } catch (error) { logger22().error(`error in ${type} event listener`, { error }); } } } switch (type) { case "open": if (this.#onopen) { try { this.#onopen(event); } catch (error) { logger22().error("error in onopen handler", { error }); } } break; case "close": if (this.#onclose) { try { this.#onclose(event); } catch (error) { logger22().error("error in onclose handler", { error }); } } break; case "error": if (this.#onerror) { try { this.#onerror(event); } catch (error) { logger22().error("error in onerror handler", { error }); } } break; case "message": if (this.#onmessage) { try { this.#onmessage(event); } catch (error) { logger22().error("error in onmessage handler", { error }); } } break; } } // Event handler properties with getters/setters #onopen = null; #onclose = null; #onerror = null; #onmessage = null; get onopen() { return this.#onopen; } set onopen(handler) { this.#onopen = handler; } get onclose() { return this.#onclose; } set onclose(handler) { this.#onclose = handler; } get onerror() { return this.#onerror; } set onerror(handler) { this.#onerror = handler; } get onmessage() { return this.#onmessage; } set onmessage(handler) { this.#onmessage = handler; } }; async function handleWebSocketConnect(c, runConfig, actorDriver, actorId, encoding, parameters, authData) { const exposeInternalError = c ? getRequestExposeInternalError(c.req) : false; const { promise: handlersPromise, resolve: handlersResolve, reject: handlersReject } = Promise.withResolvers(); let actor; try { actor = await actorDriver.loadActor(actorId); } catch (error) { return { onOpen: (_evt, ws) => { const { code } = deconstructError( error, logger2(), { wsEvent: "open" }, exposeInternalError ); ws.close(1011, code); }, onMessage: (_evt, ws) => { ws.close(1011, "Actor not loaded"); }, onClose: (_event, _ws) => { }, onError: (_error) => { } }; } return { onOpen: (_evt, ws) => { logger2().debug("websocket open"); (async () => { try { const connId = generateConnId(); const connToken = generateConnToken(); const connState = await actor.prepareConn(parameters, c == null ? void 0 : c.req.raw); const connGlobalState = actorDriver.getGenericConnGlobalState(actorId); connGlobalState.websockets.set(connId, ws); logger2().debug("registered websocket for conn", { actorId, totalCount: connGlobalState.websockets.size }); const conn = await actor.createConn( connId, connToken, parameters, connState, CONN_DRIVER_GENERIC_WEBSOCKET, { encoding }, authData ); handlersResolve({ conn, actor, connId }); } catch (error) { handlersReject(error); const { code } = deconstructError( error, logger2(), { wsEvent: "open" }, exposeInternalError ); ws.close(1011, code); } })(); }, onMessage: (evt, ws) => { handlersPromise.then(({ conn, actor: actor2 }) => { logger2().debug("received message"); const value = evt.data.valueOf(); parseMessage(value, { encoding, maxIncomingMessageSize: runConfig.maxIncomingMessageSize }).then((message) => { actor2.processMessage(message, conn).catch((error) => { const { code } = deconstructError( error, logger2(), { wsEvent: "message" }, exposeInternalError ); ws.close(1011, code); }); }).catch((error) => { const { code } = deconstructError( error, logger2(), { wsEvent: "message" }, exposeInternalError ); ws.close(1011, code); }); }).catch((error) => { const { code } = deconstructError( error, logger2(), { wsEvent: "message" }, exposeInternalError ); ws.close(1011, code); }); }, onClose: (event, ws) => { if (event.wasClean) { logger2().info("websocket closed", { code: event.code, reason: event.reason, wasClean: event.wasClean }); } else { logger2().warn("websocket closed", { code: event.code, reason: event.reason, wasClean: event.wasClean }); } ws.close(1e3, "hack_force_close"); handlersPromise.then(({ conn, actor: actor2, connId }) => { const connGlobalState = actorDriver.getGenericConnGlobalState(actorId); const didDelete = connGlobalState.websockets.delete(connId); if (didDelete) { logger2().info("removing websocket for conn", { totalCount: connGlobalState.websockets.size }); } else { logger2().warn("websocket does not exist for conn", { actorId, totalCount: connGlobalState.websockets.size }); } actor2.__removeConn(conn); }).catch((error) => { deconstructError( error, logger2(), { wsEvent: "close" }, exposeInternalError ); }); }, onError: (_error) => { try { logger2().warn("websocket error"); } catch (error) { deconstructError( error, logger2(), { wsEvent: "error" }, exposeInternalError ); } } }; } async function handleSseConnect(c, runConfig, actorDriver, actorId, authData) { const encoding = getRequestEncoding(c.req); const parameters = getRequestConnParams(c.req); return streamSSE(c, async (stream) => { let actor; let connId; let connToken; let connState; let conn; try { actor = await actorDriver.loadActor(actorId); connId = generateConnId(); connToken = generateConnToken(); connState = await actor.prepareConn(parameters, c.req.raw); logger2().debug("sse open"); actorDriver.getGenericConnGlobalState(actorId).sseStreams.set(connId, stream); conn = await actor.createConn( connId, connToken, parameters, connState, CONN_DRIVER_GENERIC_SSE, { encoding }, authData ); stream.onAbort(() => { }); const abortResolver = Promise.withResolvers(); c.req.raw.signal.addEventListener("abort", async () => { try { logger2().debug("sse shutting down"); if (connId) { actorDriver.getGenericConnGlobalState(actorId).sseStreams.delete(connId); } if (conn && actor) { actor.__removeConn(conn); } abortResolver.resolve(void 0); } catch (error) { logger2().error("error closing sse connection", { error }); } }); try { c.executionCtx.waitUntil(abortResolver.promise); } catch { } await abortResolver.promise; } catch (error) { logger2().error("error in sse connection", { error }); if (connId !== void 0) { actorDriver.getGenericConnGlobalState(actorId).sseStreams.delete(connId); } if (conn && actor !== void 0) { actor.__removeConn(conn); } stream.close(); } }); } async function handleAction(c, runConfig, actorDriver, actionName, actorId, authData) { const encoding = getRequestEncoding(c.req); const parameters = getRequestConnParams(c.req); logger2().debug("handling action", { actionName, encoding }); let body; if (encoding === "json") { try { body = await c.req.json(); } catch (err) { if (err instanceof InvalidActionRequest) { throw err; } throw new InvalidActionRequest( `Invalid JSON: ${stringifyError(err)}` ); } } else if (encoding === "cbor") { try { const value = await c.req.arrayBuffer(); const uint8Array = new Uint8Array(value); body = await deserialize(uint8Array, encoding); } catch (err) { throw new InvalidActionRequest( `Invalid binary format: ${stringifyError(err)}` ); } } else { return assertUnreachable2(encoding); } let actionArgs; try { const result = ActionRequestSchema.safeParse(body); if (!result.success) { throw new InvalidActionRequest("Invalid action request format"); } actionArgs = result.data.a; } catch (err) { throw new InvalidActionRequest( `Invalid schema: ${stringifyError(err)}` ); } let actor; let conn; let output; try { actor = await actorDriver.loadActor(actorId); const connState = await actor.prepareConn(parameters, c.req.raw); conn = await actor.createConn( generateConnId(), generateConnToken(), parameters, connState, CONN_DRIVER_GENERIC_HTTP, {}, authData ); const ctx = new ActionContext(actor.actorContext, conn); output = await actor.executeAction(ctx, actionName, actionArgs); } finally { if (conn) { actor == null ? void 0 : actor.__removeConn(conn); } } if (encoding === "json") { const responseData = { o: output // Use the format expected by ResponseOkSchema }; return c.json(responseData); } else if (encoding === "cbor") { const responseData = { o: output // Use the format expected by ResponseOkSchema }; const serialized = serialize(responseData, encoding); return c.body(serialized, 200, { "Content-Type": "application/octet-stream" }); } else { return assertUnreachable2(encoding); } } async function handleConnectionMessage(c, runConfig, actorDriver, connId, connToken, actorId) { const encoding = getRequestEncoding(c.req); let message; if (encoding === "json") { try { message = await c.req.json(); } catch (_err) { throw new InvalidRequest("Invalid JSON"); } } else if (encoding === "cbor") { try { const value = await c.req.arrayBuffer(); const uint8Array = new Uint8Array(value); message = await parseMessage(uint8Array, { encoding, maxIncomingMessageSize: runConfig.maxIncomingMessageSize }); } catch (err) { throw new InvalidRequest( `Invalid binary format: ${stringifyError(err)}` ); } } else { return assertUnreachable2(encoding); } const actor = await actorDriver.loadActor(actorId); const conn = actor.conns.get(connId); if (!conn) { throw new ConnNotFound(connId); } if (conn._token !== connToken) { throw new IncorrectConnToken(); } await actor.processMessage(message, conn); return c.json({}); } async function handleRawWebSocketHandler(c, path3, actorDriver, actorId, authData) { const actor = await actorDriver.loadActor(actorId); return { onOpen: (_evt, ws) => { const adapter = new HonoWebSocketAdapter(ws); ws.__adapter = adapter; const url = new URL(path3, "http://actor"); const pathname = url.pathname.replace(/^\/raw\/websocket/, "") || "/"; const normalizedPath = pathname + url.search; let newRequest; if (c) { newRequest = new Request(`http://actor${normalizedPath}`, c.req.raw); } else { newRequest = new Request(`http://actor${normalizedPath}`, { method: "GET" }); } logger2().debug("rewriting websocket url", { from: path3, to: newRequest.url }); actor.handleWebSocket(adapter, { request: newRequest, auth: authData }); }, onMessage: (event, ws) => { const adapter = ws.__adapter; if (adapter) { adapter._handleMessage(event); } }, onClose: (evt, ws) => { const adapter = ws.__adapter; if (adapter) { adapter._handleClose((evt == null ? void 0 : evt.code) || 1006, (evt == null ? void 0 : evt.reason) || ""); } }, onError: (error, ws) => { const adapter = ws.__adapter; if (adapter) { adapter._handleError(error); } } }; } function getRequestEncoding(req) { const encodingParam = req.header(HEADER_ENCODING); if (!encodingParam) { throw new InvalidEncoding("undefined"); } const result = EncodingSchema.safeParse(encodingParam); if (!result.success) { throw new InvalidEncoding(encodingParam); } return result.data; } function getRequestExposeInternalError(req) { const param = req.header(HEADER_EXPOSE_INTERNAL_ERROR); if (!param) { return false; } return param === "true"; } var HEADER_ACTOR_QUERY = "X-RivetKit-Query"; var HEADER_ENCODING = "X-RivetKit-Encoding"; var HEADER_EXPOSE_INTERNAL_ERROR = "X-RivetKit-Expose-Internal-Error"; var HEADER_CONN_PARAMS = "X-RivetKit-Conn-Params"; var HEADER_AUTH_DATA = "X-RivetKit-Auth-Data"; var HEADER_ACTOR_ID = "X-RivetKit-Actor"; var HEADER_CONN_ID = "X-RivetKit-Conn"; var HEADER_CONN_TOKEN = "X-RivetKit-Conn-Token"; function getRequestConnParams(req) { const paramsParam = req.header(HEADER_CONN_PARAMS); if (!paramsParam) { return null; } try { return JSON.parse(paramsParam); } catch (err) { throw new InvalidParams( `Invalid params JSON: ${stringifyError(err)}` ); } } var MAX_ACTOR_KEY_SIZE = 128; var ActorKeySchema = z5.array(z5.string().max(MAX_ACTOR_KEY_SIZE)); var CreateRequestSchema = z5.object({ name: z5.string(), key: ActorKeySchema, input: z5.unknown().optional(), region: z5.string().optional() }); var GetForKeyRequestSchema = z5.object({ name: z5.string(), key: ActorKeySchema }); var GetOrCreateRequestSchema = z5.object({ name: z5.string(), key: ActorKeySchema, input: z5.unknown().optional(), region: z5.string().optional() }); var ActorQuerySchema = z5.union([ z5.object({ getForId: z5.object({ actorId: z5.string() }) }), z5.object({ getForKey: GetForKeyRequestSchema }), z5.object({ getOrCreateForKey: GetOrCreateRequestSchema }), z5.object({ create: CreateRequestSchema }) ]); var ConnectRequestSchema = z5.object({ query: ActorQuerySchema.describe(HEADER_ACTOR_QUERY), encoding: EncodingSchema.describe(HEADER_ENCODING), connParams: z5.string().optional().describe(HEADER_CONN_PARAMS) }); var ConnectWebSocketRequestSchema = z5.object({ query: ActorQuerySchema.describe("query"), encoding: EncodingSchema.describe("encoding"), connParams: z5.unknown().optional().describe("conn_params") }); var ConnMessageRequestSchema = z5.object({ actorId: z5.string().describe(HEADER_ACTOR_ID), connId: z5.string().describe(HEADER_CONN_ID), encoding: EncodingSchema.describe(HEADER_ENCODING), connToken: z5.string().describe(HEADER_CONN_TOKEN) }); var ResolveRequestSchema = z5.object({ query: ActorQuerySchema.describe(HEADER_ACTOR_QUERY), connParams: z5.string().optional().describe(HEADER_CONN_PARAMS) }); var ActorId = z6.string().brand("ActorId"); var ActorFeature = /* @__PURE__ */ ((ActorFeature2) => { ActorFeature2["Logs"] = "logs"; ActorFeature2["Config"] = "config"; ActorFeature2["Connections"] = "connections"; ActorFeature2["State"] = "state"; ActorFeature2["Console"] = "console"; ActorFeature2["Runtime"] = "runtime"; ActorFeature2["Metrics"] = "metrics"; ActorFeature2["EventsMonitoring"] = "events-monitoring"; ActorFeature2["Database"] = "database"; return ActorFeature2; })(ActorFeature || {}); var ActorLogEntry = z6.object({ level: z6.string(), message: z6.string(), timestamp: z6.string(), metadata: z6.record(z6.string(), z6.any()).optional() }); var ActorSchema = z6.object({ id: ActorId, name: z6.string(), key: z6.array(z6.string()), tags: z6.record(z6.string(), z6.string()).optional(), region: z6.string().optional(), createdAt: z6.string().optional(), startedAt: z6.string().optional(), destroyedAt: z6.string().optional(), features: z6.array(z6.enum(ActorFeature)).optional() }); var OperationSchema = z6.discriminatedUnion("op", [ z6.object({ op: z6.literal("remove"), path: z6.string() }), z6.object({ op: z6.literal("add"), path: z6.string(), value: z6.unknown() }), z6.object({ op: z6.literal("replace"), path: z6.string(), value: z6.unknown() }), z6.object({ op: z6.literal("move"), path: z6.string(), from: z6.string() }), z6.object({ op: z6.literal("copy"), path: z6.string(), from: z6.string() }), z6.object({ op: z6.literal("test"), path: z6.string(), value: z6.unknown() }) ]); var PatchSchema = z6.array(OperationSchema); var ConnectionSchema = z6.object({ params: z6.record(z6.string(), z6.any()).optional(), id: z6.string(), stateEnabled: z6.boolean().optional(), state: z6.any().optional(), auth: z6.record(z6.string(), z6.any()).optional() }); var RealtimeEventSchema = z6.discriminatedUnion("type", [ z6.object({ type: z6.literal("action"), name: z6.string(), args: z6.array(z6.any()), connId: z6.string() }), z6.object({ type: z6.literal("broadcast"), eventName: z6.string(), args: z6.array(z6.any()) }), z6.object({ type: z6.literal("subscribe"), eventName: z6.string(), connId: z6.string() }), z6.object({ type: z6.literal("unsubscribe"), eventName: z6.string(), connId: z6.string() }), z6.object({ type: z6.literal("event"), eventName: z6.string(), args: z6.array(z6.any()), connId: z6.string() }) ]); var RecordedRealtimeEventSchema = RealtimeEventSchema.and( z6.object({ id: z6.string(), timestamp: z6.number() }) ); var DatabaseQuerySchema = z6.object({ sql: z6.string(), args: z6.array(z6.string().or(z6.number())) }); var TableSchema = z6.object({ schema: z6.string(), name: z6.string(), type: z6.enum(["table", "view"]) }); var TablesSchema = z6.array(TableSchema); var ColumnSchema = z6.object({ cid: z6.number(), name: z6.string(), type: z6.string().toLowerCase().transform((val) => { return z6.enum(["integer", "text", "real", "blob", "numeric", "serial"]).parse(val); }), notnull: z6.coerce.boolean(), dflt_value: z6.string().nullable(), pk: z6.coerce.boolean().nullable() }); var ColumnsSchema = z6.array(ColumnSchema); var ForeignKeySchema = z6.object({ id: z6.number(), table: z6.string(), from: z6.string(), to: z6.string() }); var ForeignKeysSchema = z6.array(ForeignKeySchema); var BuildSchema = z6.object({ name: z6.string(), createdAt: z6.string().optional(), tags: z6.record(z6.string(), z6.string()).optional() }); var BuildsSchema = z6.array(BuildSchema); var CreateActorSchema = z6.object({ name: z6.string(), // FIXME: Replace with ActorKeySchema when ready key: z6.array(z6.string().max(MAX_ACTOR_KEY_SIZE)), input: z6.any() }); // ../../core/dist/chunk-EKNB2QDI.js var LOGGER_NAME2 = "actor-client"; function logger3() { return getLogger(LOGGER_NAME2); } // ../../core/dist/chunk-KFDEBHI5.js import * as cbor2 from "cbor-x"; import invariant from "invariant"; import onChange from "on-change"; import { sValidator } from "@hono/standard-validator"; import jsonPatch from "@rivetkit/fast-json-patch"; import { Hono } from "hono"; import { streamSSE as streamSSE2 } from "hono/streaming"; import { createNanoEvents } from "nanoevents"; import z7 from "zod/v4"; import * as cbor3 from "cbor-x"; import pRetry from "p-retry"; import invariant2 from "invariant"; import * as cbor22 from "cbor-x"; function createActorInspectorRouter() { return new Hono().get("/ping", (c) => { return c.json({ message: "pong" }, 200); }).get("/state", async (c) => { if (await c.var.inspector.accessors.isStateEnabled()) { return c.json( { enabled: true, state: await c.var.inspector.accessors.getState() }, 200 ); } return c.json({ enabled: false, state: null }, 200); }).patch( "/state", sValidator( "json", z7.object({ patch: PatchSchema }).or(z7.object({ replace: z7.any() })) ), async (c) => { if (!await c.var.inspector.accessors.isStateEnabled()) { return c.json({ enabled: false }, 200); } const body = c.req.valid("json"); if ("replace" in body) { await c.var.inspector.accessors.setState(body.replace); return c.json( { enabled: true, state: await c.var.inspector.accessors.getState() }, 200 ); } const state = await c.var.inspector.accessors.getState(); const { newDocument: newState } = jsonPatch.applyPatch( state, body.patch ); await c.var.inspector.accessors.setState(newState); return c.json( { enabled: true, state: await c.var.inspector.accessors.getState() }, 200 ); } ).get("/state/stream", async (c) => { if (!await c.var.inspector.accessors.isStateEnabled()) { return c.json({ enabled: false }, 200); } let id = 0; let unsub; return streamSSE2( c, async (stream) => { unsub = c.var.inspector.emitter.on("stateUpdated", async (state) => { stream.writeSSE({ data: JSON.stringify(state) || "", event: "state-update", id: String(id++) }); }); const { promise } = Promise.withResolvers(); return promise; }, async () => { unsub == null ? void 0 : unsub(); } ); }).get("/connections", async (c) => { const connections = await c.var.inspector.accessors.getConnections(); return c.json({ connections }, 200); }).get("/connections/stream", async (c) => { let id = 0; let unsub; return streamSSE2( c, async (stream) => { unsub = c.var.inspector.emitter.on("connectionUpdated", async () => { stream.writeSSE({ data: JSON.stringify( await c.var.inspector.accessors.getConnections() ), event: "connection-update", id: String(id++) }); }); const { promise } = Promise.withResolvers(); return promise; }, async () => { unsub == null ? void 0 : unsub(); } ); }).get("/events", async (c) => { const events = c.var.inspector.lastRealtimeEvents; return c.json({ events }, 200); }).post("/events/clear", async (c) => { c.var.inspector.lastRealtimeEvents.length = 0; return c.json({ message: "Events cleared" }, 200); }).get("/events/stream", async (c) => { let id = 0; let unsub; return streamSSE2( c, async (stream) => { unsub = c.var.inspector.emitter.on("eventFired", () => { stream.writeSSE({ data: JSON.stringify(c.var.inspector.lastRealtimeEvents), event: "realtime-event", id: String(id++) }); }); const { promise } = Promise.withResolvers(); return promise; }, async () => { unsub == null ? void 0 : unsub(); } ); }).get("/rpcs", async (c) => { const rpcs = await c.var.inspector.accessors.getRpcs(); return c.json({ rpcs }, 200); }).get("/db", async (c) => { if (!await c.var.inspector.accessors.isDbEnabled()) { return c.json({ enabled: false, db: null }, 200); } const db = await c.var.inspector.accessors.getDb(); const rows = await db.execute(`PRAGMA table_list`); const tables = TablesSchema.parse(rows).filter( (table) => table.schema !== "temp" && !table.name.startsWith("sqlite_") ); const tablesInfo = await Promise.all( tables.map((table) => db.execute(`PRAGMA table_info(${table.name})`)) ); const columns = tablesInfo.map((def) => ColumnsSchema.parse(def)); const foreignKeysList = await Promise.all( tables.map( (table) => db.execute(`PRAGMA foreign_key_list(${table.name})`) ) ); const foreignKeys = foreignKeysList.map( (def) => ForeignKeysSchema.parse(def) ); const countInfo = await Promise.all( tables.map( (table) => db.execute(`SELECT COUNT(*) as count FROM ${table.name}`) ) ); const counts = countInfo.map((def) => { return def[0].count || 0; }); return c.json( { enabled: true, db: tablesInfo.map((_, index) => { return { table: tables[index], columns: columns[index], foreignKeys: foreignKeys[index], records: counts[index] }; }) }, 200 ); }).post( "/db", sValidator( "json", z7.object({ query: z7.string(), params: z7.array(z7.any()).optional() }) ), async (c) => { if (!await c.var.inspector.accessors.isDbEnabled()) { return c.json({ enabled: false }, 200); } const db = await c.var.inspector.accessors.getDb(); try { const result = await db.execute( c.req.valid("json").query, ...c.req.valid("json").params || [] ); return c.json({ result }, 200); } catch (error) { c; return c.json({ error: error.message }, 500); } } ); } function lookupInRegistry(registryConfig, name) { const definition = registryConfig.use[name]; if (!definition) throw new Error(`no actor in registry for name ${name}`); return definition; } var ActorClientError = class extends Error { }; var InternalError2 = class extends ActorClientError { }; var ActorError = class extends ActorClientError { constructor(code, message, metadata) { super(message); this.code = code; this.metadata = metadata; } __type = "ActorError"; }; var HttpRequestError = class extends ActorClientError { constructor(message, opts) { super(`HTTP request error: ${message}`, { cause: opts == null ? void 0 : opts.cause }); } }; var ActorConnDisposed = class extends ActorClientError { constructor() { super("Attempting to interact with a disposed actor connection."); } }; async function rawHttpFetch(driver, actorQuery, params, input, init) { let path3; let mergedInit = init || {}; if (typeof input === "string") { path3 = input; } else if (input instanceof URL) { path3 = input.pathname + input.search; } else if (input instanceof Request) { const url = new URL(input.url); path3 = url.pathname + url.search; const requestHeaders = new Headers(input.headers); const initHeaders = new Headers((init == null ? void 0 : init.headers) || {}); const mergedHeaders = new Headers(requestHeaders); for (const [key, value] of initHeaders) { mergedHeaders.set(key, value); } mergedInit = { method: input.method, body: input.body, mode: input.mode, credentials: input.credentials, redirect: input.redirect, referrer: input.referrer, referrerPolicy: input.referrerPolicy, integrity: input.integrity, keepalive: input.keepalive, signal: input.signal, ...mergedInit, // init overrides Request properties headers: mergedHeaders // headers must be set after spread to ensure proper merge }; if (mergedInit.body) { mergedInit.duplex = "half"; } } else { throw new TypeError("Invalid input type for fetch"); } return await driver.rawHttpRequest( void 0, actorQuery, // Force JSON so it's readable by the user "json", params, path3, mergedInit, void 0 ); } async function rawWebSocket(driver, actorQuery, params, path3, protocols) { return await driver.rawWebSocket( void 0, actorQuery, // Force JSON so it's readable by the user "json", params, path3 || "", protocols, void 0 ); } var ActorHandleRaw = class { #client; #driver; #encodingKind; #actorQuery; #params; /** * Do not call this directly. * * Creates an instance of ActorHandleRaw. * * @protected */ constructor(client, driver, params, encodingKind, actorQuery) { this.#client = client; this.#driver = driver; this.#encodingKind = encodingKind; this.#actorQuery = actorQuery; this.#params = params; } /** * Call a raw action. This method sends an HTTP request to invoke the named action. * * @see {@link ActorHandle} * @template Args - The type of arguments to pass to the action function. * @template Response - The type of the response returned by the action function. */ async action(opts) { return await this.#driver.action( void 0, this.#actorQuery, this.#encodingKind, this.#params, opts.name, opts.args, { signal: opts.signal } ); } /** * Establishes a persistent connection to the actor. * * @template AD The actor class that this connection is for. * @returns {ActorConn<AD>} A connection to the actor. */ connect() { logger3().debug("establishing connection from handle", { query: this.#actorQuery }); const conn = new ActorConnRaw( this.#client, this.#driver, this.#params, this.#encodingKind, this.#actorQuery ); return this.#client[CREATE_ACTOR_CONN_PROXY]( conn ); } /** * Makes a raw HTTP request to the actor. * * @param input - The URL, path, or Request object * @param init - Standard fetch RequestInit options * @returns Promise<Response> - The raw HTTP response */ async fetch(input, init) { return rawHttpFetch( this.#driver, this.#actorQuery, this.#params, input, init ); } /** * Creates a raw WebSocket connection to the actor. * * @param path - The path for the WebSocket connection (e.g., "stream") * @param protocols - Optional WebSocket subprotocols * @returns WebSocket - A raw WebSocket connection */ async websocket(path3, protocols) { return rawWebSocket( this.#driver, this.#actorQuery, this.#params, path3, protocols ); } /** * Resolves the actor to get its unique actor ID * * @returns {Promise<string>} - A promise that resolves to the actor's ID */ async resolve({ signal } = {}) { if ("getForKey" in this.#actorQuery || "getOrCreateForKey" in this.#actorQuery) { const actorId = await this.#driver.resolveActorId( void 0, this.#actorQuery, this.#encodingKind, this.#params, signal ? { signal } : void 0 ); this.#actorQuery = { getForId: { actorId } }; return actorId; } else if ("getForId" in this.#actorQuery) { return this.#actorQuery.getForId.actorId; } else if ("create" in this.#actorQuery) { invariant2(false, "actorQuery cannot be create"); } else { assertUnreachable2(this.#actorQuery); } } }; var ACTOR_CONNS_SYMBOL = Symbol("actorConns"); var CREATE_ACTOR_CONN_PROXY = Symbol("createActorConnProxy"); var TRANSPORT_SYMBOL = Symbol("transport"); var ClientRaw = class { #disposed = false; [ACTOR_CONNS_SYMBOL] = /* @__PURE__ */ new Set(); #driver; #encodingKind; [TRANSPORT_SYMBOL]; /** * Creates an instance of Client. * * @param {string} managerEndpoint - The manager endpoint. See {@link https://rivet.gg/docs/setup|Initial Setup} for instructions on getting the manager endpoint. * @param {ClientOptions} [opts] - Options for configuring the client. * @see {@link https://rivet.gg/docs/setup|Initial Setup} */ constructor(driver, opts) { this.#driver = driver; this.#encodingKind = (opts == null ? void 0 : opts.encoding) ?? "cbor"; this[TRANSPORT_SYMBOL] = (opts == null ? void 0 : opts.transport) ?? "websocket"; } /** * Gets a stateless handle to a actor by its ID. * * @template AD The actor class that this handle is for. * @param {string} name - The name of the actor. * @param {string} actorId - The ID of the actor. * @param {GetWithIdOptions} [opts] - Options for getting the actor. * @returns {ActorHandle<AD>} - A handle to the actor. */ getForId(name, actorId, opts) { logger3().debug("get handle to actor with id", { name, actorId, params: opts == null ? void 0 : opts.params }); const actorQuery = { getForId: { actorId } }; const handle = this.#createHandle(opts == null ? void 0 : opts.params, actorQuery); return createActorProxy(handle); } /** * Gets a stateless handle to a actor by its key, but does not create the actor if it doesn't exist. * * @template AD The actor class that this handle is for. * @param {string} name - The name of the actor. * @param {string | string[]} [key=[]] - The key to identify the actor. Can be a single string or an array of strings. * @param {GetWithIdOptions} [opts] - Options for getting the actor. * @returns {ActorHandle<AD>} - A handle to the actor. */ get(name, key, opts) { const keyArray = typeof key === "string" ? [key] : key || []; logger3().debug("get handle to actor", { name, key: keyArray, parameters: opts == null ? void 0 : opts.params }); const actorQuery = { getForKey: { name, key: keyArray } }; const handle = this.#createHandle(opts == null ? void 0 : opts.params, actorQuery); return createActorProxy(handle); } /** * Gets a stateless handle to a actor by its key, creating it if necessary. * * @template AD The