@rivetkit/redis
Version:
_Lightweight Libraries for Backends_
1 lines • 58.3 kB
Source Map (JSON)
{"version":3,"sources":["/Users/nathan/rivetkit/packages/drivers/redis/dist/chunk-E7IBW7FC.cjs","../../../core/src/actor/protocol/serde.ts","../../../core/src/actor/router-endpoints.ts","../../../core/src/actor/protocol/http/action.ts","../../../core/src/actor/protocol/message/mod.ts","../../../core/src/actor/protocol/message/to-server.ts","../../../core/src/actor/log.ts","../../../core/src/actor/utils.ts","../../../core/src/actor/connection.ts","../../../core/src/registry/run-config.ts","../../../core/src/inspector/config.ts","../../../core/src/driver-helpers/utils.ts","../src/manager.ts","../src/utils.ts"],"names":["logger"],"mappings":"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","file":"/Users/nathan/rivetkit/packages/drivers/redis/dist/chunk-E7IBW7FC.cjs","sourcesContent":[null,"import * as cbor from \"cbor-x\";\nimport { z } from \"zod\";\nimport * as errors from \"@/actor/errors\";\nimport { logger } from \"../log\";\nimport { assertUnreachable } from \"../utils\";\n\n/** Data that can be deserialized. */\nexport type InputData = string | Buffer | Blob | ArrayBufferLike | Uint8Array;\n\n/** Data that's been serialized. */\nexport type OutputData = string | Uint8Array;\n\nexport const EncodingSchema = z.enum([\"json\", \"cbor\"]);\n\n/**\n * Encoding used to communicate between the client & actor.\n */\nexport type Encoding = z.infer<typeof EncodingSchema>;\n\n/**\n * Helper class that helps serialize data without re-serializing for the same encoding.\n */\nexport class CachedSerializer<T> {\n\t#data: T;\n\t#cache = new Map<Encoding, OutputData>();\n\n\tconstructor(data: T) {\n\t\tthis.#data = data;\n\t}\n\n\tpublic get rawData(): T {\n\t\treturn this.#data;\n\t}\n\n\tpublic serialize(encoding: Encoding): OutputData {\n\t\tconst cached = this.#cache.get(encoding);\n\t\tif (cached) {\n\t\t\treturn cached;\n\t\t} else {\n\t\t\tconst serialized = serialize(this.#data, encoding);\n\t\t\tthis.#cache.set(encoding, serialized);\n\t\t\treturn serialized;\n\t\t}\n\t}\n}\n\n/**\n * Use `CachedSerializer` if serializing the same data repeatedly.\n */\nexport function serialize<T>(value: T, encoding: Encoding): OutputData {\n\tif (encoding === \"json\") {\n\t\treturn JSON.stringify(value);\n\t} else if (encoding === \"cbor\") {\n\t\t// TODO: Remove this hack, but cbor-x can't handle anything extra in data structures\n\t\tconst cleanValue = JSON.parse(JSON.stringify(value));\n\t\treturn cbor.encode(cleanValue);\n\t} else {\n\t\tassertUnreachable(encoding);\n\t}\n}\n\nexport async function deserialize(data: InputData, encoding: Encoding) {\n\tif (encoding === \"json\") {\n\t\tif (typeof data !== \"string\") {\n\t\t\tlogger().warn(\"received non-string for json parse\");\n\t\t\tthrow new errors.MalformedMessage();\n\t\t} else {\n\t\t\treturn JSON.parse(data);\n\t\t}\n\t} else if (encoding === \"cbor\") {\n\t\tif (data instanceof Blob) {\n\t\t\tconst arrayBuffer = await data.arrayBuffer();\n\t\t\treturn cbor.decode(new Uint8Array(arrayBuffer));\n\t\t} else if (data instanceof Uint8Array) {\n\t\t\treturn cbor.decode(data);\n\t\t} else if (\n\t\t\tdata instanceof ArrayBuffer ||\n\t\t\tdata instanceof SharedArrayBuffer\n\t\t) {\n\t\t\treturn cbor.decode(new Uint8Array(data));\n\t\t} else {\n\t\t\tlogger().warn(\"received non-binary type for cbor parse\");\n\t\t\tthrow new errors.MalformedMessage();\n\t\t}\n\t} else {\n\t\tassertUnreachable(encoding);\n\t}\n}\n\n// TODO: Encode base 128\nfunction base64EncodeUint8Array(uint8Array: Uint8Array): string {\n\tlet binary = \"\";\n\tconst len = uint8Array.byteLength;\n\tfor (let i = 0; i < len; i++) {\n\t\tbinary += String.fromCharCode(uint8Array[i]);\n\t}\n\treturn btoa(binary);\n}\n\nfunction base64EncodeArrayBuffer(arrayBuffer: ArrayBuffer): string {\n\tconst uint8Array = new Uint8Array(arrayBuffer);\n\treturn base64EncodeUint8Array(uint8Array);\n}\n\n/** Converts data that was encoded to a string. Some formats (like SSE) don't support raw binary data. */\nexport function encodeDataToString(message: OutputData): string {\n\tif (typeof message === \"string\") {\n\t\treturn message;\n\t} else if (message instanceof ArrayBuffer) {\n\t\treturn base64EncodeArrayBuffer(message);\n\t} else if (message instanceof Uint8Array) {\n\t\treturn base64EncodeUint8Array(message);\n\t} else {\n\t\tassertUnreachable(message);\n\t}\n}\n","import type { Context as HonoContext, HonoRequest } from \"hono\";\nimport { type SSEStreamingApi, streamSSE } from \"hono/streaming\";\nimport type { WSContext } from \"hono/ws\";\nimport { ActionContext } from \"@/actor/action\";\nimport type { AnyConn } from \"@/actor/connection\";\nimport {\n\tCONNECTION_DRIVER_HTTP,\n\tCONNECTION_DRIVER_SSE,\n\tCONNECTION_DRIVER_WEBSOCKET,\n\tgenerateConnId,\n\tgenerateConnToken,\n} from \"@/actor/connection\";\nimport * as errors from \"@/actor/errors\";\nimport type { AnyActorInstance } from \"@/actor/instance\";\nimport * as protoHttpAction from \"@/actor/protocol/http/action\";\nimport { parseMessage } from \"@/actor/protocol/message/mod\";\nimport type * as messageToServer from \"@/actor/protocol/message/to-server\";\nimport type { InputData } from \"@/actor/protocol/serde\";\nimport {\n\tdeserialize,\n\ttype Encoding,\n\tEncodingSchema,\n\tserialize,\n} from \"@/actor/protocol/serde\";\nimport type { UpgradeWebSocketArgs } from \"@/common/inline-websocket-adapter2\";\nimport { deconstructError, stringifyError } from \"@/common/utils\";\nimport type { UniversalWebSocket } from \"@/common/websocket-interface\";\nimport { HonoWebSocketAdapter } from \"@/manager/hono-websocket-adapter\";\nimport type { RunConfig } from \"@/registry/run-config\";\nimport type { ActorDriver } from \"./driver\";\nimport type {\n\tGenericHttpDriverState,\n\tGenericSseDriverState,\n\tGenericWebSocketDriverState,\n} from \"./generic-conn-driver\";\nimport { logger } from \"./log\";\nimport { assertUnreachable } from \"./utils\";\n\nexport interface ConnectWebSocketOpts {\n\treq?: HonoRequest;\n\tencoding: Encoding;\n\tactorId: string;\n\tparams: unknown;\n\tauthData: unknown;\n}\n\nexport interface ConnectWebSocketOutput {\n\tonOpen: (ws: WSContext) => void;\n\tonMessage: (message: messageToServer.ToServer) => void;\n\tonClose: () => void;\n}\n\nexport interface ConnectSseOpts {\n\treq?: HonoRequest;\n\tencoding: Encoding;\n\tparams: unknown;\n\tactorId: string;\n\tauthData: unknown;\n}\n\nexport interface ConnectSseOutput {\n\tonOpen: (stream: SSEStreamingApi) => void;\n\tonClose: () => Promise<void>;\n}\n\nexport interface ActionOpts {\n\treq?: HonoRequest;\n\tparams: unknown;\n\tactionName: string;\n\tactionArgs: unknown[];\n\tactorId: string;\n\tauthData: unknown;\n}\n\nexport interface ActionOutput {\n\toutput: unknown;\n}\n\nexport interface ConnsMessageOpts {\n\treq?: HonoRequest;\n\tconnId: string;\n\tconnToken: string;\n\tmessage: messageToServer.ToServer;\n\tactorId: string;\n}\n\nexport interface FetchOpts {\n\trequest: Request;\n\tactorId: string;\n\tauthData: unknown;\n}\n\nexport interface WebSocketOpts {\n\trequest: Request;\n\twebsocket: UniversalWebSocket;\n\tactorId: string;\n\tauthData: unknown;\n}\n\n/**\n * Creates a WebSocket connection handler\n */\nexport async function handleWebSocketConnect(\n\treq: Request | undefined,\n\trunConfig: RunConfig,\n\tactorDriver: ActorDriver,\n\tactorId: string,\n\tencoding: Encoding,\n\tparameters: unknown,\n\tauthData: unknown,\n): Promise<UpgradeWebSocketArgs> {\n\tconst exposeInternalError = req ? getRequestExposeInternalError(req) : false;\n\n\t// Setup promise for the init handlers since all other behavior depends on this\n\tconst {\n\t\tpromise: handlersPromise,\n\t\tresolve: handlersResolve,\n\t\treject: handlersReject,\n\t} = Promise.withResolvers<{\n\t\tconn: AnyConn;\n\t\tactor: AnyActorInstance;\n\t\tconnId: string;\n\t}>();\n\n\t// Pre-load the actor to catch errors early\n\tlet actor: AnyActorInstance;\n\ttry {\n\t\tactor = await actorDriver.loadActor(actorId);\n\t} catch (error) {\n\t\t// Return handler that immediately closes with error\n\t\treturn {\n\t\t\tonOpen: (_evt: any, ws: WSContext) => {\n\t\t\t\tconst { code } = deconstructError(\n\t\t\t\t\terror,\n\t\t\t\t\tlogger(),\n\t\t\t\t\t{\n\t\t\t\t\t\twsEvent: \"open\",\n\t\t\t\t\t},\n\t\t\t\t\texposeInternalError,\n\t\t\t\t);\n\t\t\t\tws.close(1011, code);\n\t\t\t},\n\t\t\tonMessage: (_evt: { data: any }, ws: WSContext) => {\n\t\t\t\tws.close(1011, \"Actor not loaded\");\n\t\t\t},\n\t\t\tonClose: (_event: any, _ws: WSContext) => {},\n\t\t\tonError: (_error: unknown) => {},\n\t\t};\n\t}\n\n\treturn {\n\t\tonOpen: (_evt: any, ws: WSContext) => {\n\t\t\tlogger().debug(\"websocket open\");\n\n\t\t\t// Run async operations in background\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst connId = generateConnId();\n\t\t\t\t\tconst connToken = generateConnToken();\n\t\t\t\t\tconst connState = await actor.prepareConn(parameters, req);\n\n\t\t\t\t\t// Save socket\n\t\t\t\t\tconst connGlobalState =\n\t\t\t\t\t\tactorDriver.getGenericConnGlobalState(actorId);\n\t\t\t\t\tconnGlobalState.websockets.set(connId, ws);\n\t\t\t\t\tlogger().debug(\"registered websocket for conn\", {\n\t\t\t\t\t\tactorId,\n\t\t\t\t\t\ttotalCount: connGlobalState.websockets.size,\n\t\t\t\t\t});\n\n\t\t\t\t\t// Create connection\n\t\t\t\t\tconst conn = await actor.createConn(\n\t\t\t\t\t\tconnId,\n\t\t\t\t\t\tconnToken,\n\t\t\t\t\t\tparameters,\n\t\t\t\t\t\tconnState,\n\t\t\t\t\t\tCONNECTION_DRIVER_WEBSOCKET,\n\t\t\t\t\t\t{ encoding } satisfies GenericWebSocketDriverState,\n\t\t\t\t\t\tauthData,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Unblock other handlers\n\t\t\t\t\thandlersResolve({ conn, actor, connId });\n\t\t\t\t} catch (error) {\n\t\t\t\t\thandlersReject(error);\n\n\t\t\t\t\tconst { code } = deconstructError(\n\t\t\t\t\t\terror,\n\t\t\t\t\t\tlogger(),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\twsEvent: \"open\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\texposeInternalError,\n\t\t\t\t\t);\n\t\t\t\t\tws.close(1011, code);\n\t\t\t\t}\n\t\t\t})();\n\t\t},\n\t\tonMessage: (evt: { data: any }, ws: WSContext) => {\n\t\t\t// Handle message asynchronously\n\t\t\thandlersPromise\n\t\t\t\t.then(({ conn, actor }) => {\n\t\t\t\t\tlogger().debug(\"received message\");\n\n\t\t\t\t\tconst value = evt.data.valueOf() as InputData;\n\t\t\t\t\tparseMessage(value, {\n\t\t\t\t\t\tencoding: encoding,\n\t\t\t\t\t\tmaxIncomingMessageSize: runConfig.maxIncomingMessageSize,\n\t\t\t\t\t})\n\t\t\t\t\t\t.then((message) => {\n\t\t\t\t\t\t\tactor.processMessage(message, conn).catch((error) => {\n\t\t\t\t\t\t\t\tconst { code } = deconstructError(\n\t\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t\t\tlogger(),\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twsEvent: \"message\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\texposeInternalError,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tws.close(1011, code);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\t\tconst { code } = deconstructError(\n\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t\tlogger(),\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\twsEvent: \"message\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\texposeInternalError,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tws.close(1011, code);\n\t\t\t\t\t\t});\n\t\t\t\t})\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconst { code } = deconstructError(\n\t\t\t\t\t\terror,\n\t\t\t\t\t\tlogger(),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\twsEvent: \"message\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\texposeInternalError,\n\t\t\t\t\t);\n\t\t\t\t\tws.close(1011, code);\n\t\t\t\t});\n\t\t},\n\t\tonClose: (\n\t\t\tevent: {\n\t\t\t\twasClean: boolean;\n\t\t\t\tcode: number;\n\t\t\t\treason: string;\n\t\t\t},\n\t\t\tws: WSContext,\n\t\t) => {\n\t\t\tif (event.wasClean) {\n\t\t\t\tlogger().info(\"websocket closed\", {\n\t\t\t\t\tcode: event.code,\n\t\t\t\t\treason: event.reason,\n\t\t\t\t\twasClean: event.wasClean,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tlogger().warn(\"websocket closed\", {\n\t\t\t\t\tcode: event.code,\n\t\t\t\t\treason: event.reason,\n\t\t\t\t\twasClean: event.wasClean,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// HACK: Close socket in order to fix bug with Cloudflare leaving WS in closing state\n\t\t\t// https://github.com/cloudflare/workerd/issues/2569\n\t\t\tws.close(1000, \"hack_force_close\");\n\n\t\t\t// Handle cleanup asynchronously\n\t\t\thandlersPromise\n\t\t\t\t.then(({ conn, actor, connId }) => {\n\t\t\t\t\tconst connGlobalState =\n\t\t\t\t\t\tactorDriver.getGenericConnGlobalState(actorId);\n\t\t\t\t\tconst didDelete = connGlobalState.websockets.delete(connId);\n\t\t\t\t\tif (didDelete) {\n\t\t\t\t\t\tlogger().info(\"removing websocket for conn\", {\n\t\t\t\t\t\t\ttotalCount: connGlobalState.websockets.size,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger().warn(\"websocket does not exist for conn\", {\n\t\t\t\t\t\t\tactorId,\n\t\t\t\t\t\t\ttotalCount: connGlobalState.websockets.size,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tactor.__removeConn(conn);\n\t\t\t\t})\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tdeconstructError(\n\t\t\t\t\t\terror,\n\t\t\t\t\t\tlogger(),\n\t\t\t\t\t\t{ wsEvent: \"close\" },\n\t\t\t\t\t\texposeInternalError,\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t},\n\t\tonError: (_error: unknown) => {\n\t\t\ttry {\n\t\t\t\t// Actors don't need to know about this, since it's abstracted away\n\t\t\t\tlogger().warn(\"websocket error\");\n\t\t\t} catch (error) {\n\t\t\t\tdeconstructError(\n\t\t\t\t\terror,\n\t\t\t\t\tlogger(),\n\t\t\t\t\t{ wsEvent: \"error\" },\n\t\t\t\t\texposeInternalError,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Creates an SSE connection handler\n */\nexport async function handleSseConnect(\n\tc: HonoContext,\n\trunConfig: RunConfig,\n\tactorDriver: ActorDriver,\n\tactorId: string,\n\tauthData: unknown,\n) {\n\tconst encoding = getRequestEncoding(c.req);\n\tconst parameters = getRequestConnParams(c.req);\n\n\t// Return the main handler with all async work inside\n\treturn streamSSE(c, async (stream) => {\n\t\tlet actor: AnyActorInstance | undefined;\n\t\tlet connId: string | undefined;\n\t\tlet connToken: string | undefined;\n\t\tlet connState: unknown;\n\t\tlet conn: AnyConn | undefined;\n\n\t\ttry {\n\t\t\t// Do all async work inside the handler\n\t\t\tactor = await actorDriver.loadActor(actorId);\n\t\t\tconnId = generateConnId();\n\t\t\tconnToken = generateConnToken();\n\t\t\tconnState = await actor.prepareConn(parameters, c.req.raw);\n\n\t\t\tlogger().debug(\"sse open\");\n\n\t\t\t// Save stream\n\t\t\tactorDriver\n\t\t\t\t.getGenericConnGlobalState(actorId)\n\t\t\t\t.sseStreams.set(connId, stream);\n\n\t\t\t// Create connection\n\t\t\tconn = await actor.createConn(\n\t\t\t\tconnId,\n\t\t\t\tconnToken,\n\t\t\t\tparameters,\n\t\t\t\tconnState,\n\t\t\t\tCONNECTION_DRIVER_SSE,\n\t\t\t\t{ encoding } satisfies GenericSseDriverState,\n\t\t\t\tauthData,\n\t\t\t);\n\n\t\t\t// HACK: This is required so the abort handler below works\n\t\t\t//\n\t\t\t// See https://github.com/honojs/hono/issues/1770#issuecomment-2461966225\n\t\t\tstream.onAbort(() => {});\n\n\t\t\t// Wait for close\n\t\t\tconst abortResolver = Promise.withResolvers();\n\t\t\tc.req.raw.signal.addEventListener(\"abort\", async () => {\n\t\t\t\ttry {\n\t\t\t\t\tlogger().debug(\"sse shutting down\");\n\n\t\t\t\t\t// Cleanup\n\t\t\t\t\tif (connId) {\n\t\t\t\t\t\tactorDriver\n\t\t\t\t\t\t\t.getGenericConnGlobalState(actorId)\n\t\t\t\t\t\t\t.sseStreams.delete(connId);\n\t\t\t\t\t}\n\t\t\t\t\tif (conn && actor) {\n\t\t\t\t\t\tactor.__removeConn(conn);\n\t\t\t\t\t}\n\n\t\t\t\t\tabortResolver.resolve(undefined);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger().error(\"error closing sse connection\", { error });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// HACK: Will throw if not configured\n\t\t\ttry {\n\t\t\t\tc.executionCtx.waitUntil(abortResolver.promise);\n\t\t\t} catch {}\n\n\t\t\t// Wait until connection aborted\n\t\t\tawait abortResolver.promise;\n\t\t} catch (error) {\n\t\t\tlogger().error(\"error in sse connection\", { error });\n\n\t\t\t// Cleanup on error\n\t\t\tif (connId !== undefined) {\n\t\t\t\tactorDriver\n\t\t\t\t\t.getGenericConnGlobalState(actorId)\n\t\t\t\t\t.sseStreams.delete(connId);\n\t\t\t}\n\t\t\tif (conn && actor !== undefined) {\n\t\t\t\tactor.__removeConn(conn);\n\t\t\t}\n\n\t\t\t// Close the stream on error\n\t\t\tstream.close();\n\t\t}\n\t});\n}\n\n/**\n * Creates an action handler\n */\nexport async function handleAction(\n\tc: HonoContext,\n\trunConfig: RunConfig,\n\tactorDriver: ActorDriver,\n\tactionName: string,\n\tactorId: string,\n\tauthData: unknown,\n) {\n\tconst encoding = getRequestEncoding(c.req);\n\tconst parameters = getRequestConnParams(c.req);\n\n\tlogger().debug(\"handling action\", { actionName, encoding });\n\n\t// Validate incoming request\n\tlet body: unknown;\n\tif (encoding === \"json\") {\n\t\ttry {\n\t\t\tbody = await c.req.json();\n\t\t} catch (err) {\n\t\t\tif (err instanceof errors.InvalidActionRequest) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tthrow new errors.InvalidActionRequest(\n\t\t\t\t`Invalid JSON: ${stringifyError(err)}`,\n\t\t\t);\n\t\t}\n\t} else if (encoding === \"cbor\") {\n\t\ttry {\n\t\t\tconst value = await c.req.arrayBuffer();\n\t\t\tconst uint8Array = new Uint8Array(value);\n\t\t\tbody = await deserialize(uint8Array as unknown as InputData, encoding);\n\t\t} catch (err) {\n\t\t\tthrow new errors.InvalidActionRequest(\n\t\t\t\t`Invalid binary format: ${stringifyError(err)}`,\n\t\t\t);\n\t\t}\n\t} else {\n\t\treturn assertUnreachable(encoding);\n\t}\n\n\t// Validate using the action schema\n\tlet actionArgs: unknown[];\n\ttry {\n\t\tconst result = protoHttpAction.ActionRequestSchema.safeParse(body);\n\t\tif (!result.success) {\n\t\t\tthrow new errors.InvalidActionRequest(\"Invalid action request format\");\n\t\t}\n\n\t\tactionArgs = result.data.a;\n\t} catch (err) {\n\t\tthrow new errors.InvalidActionRequest(\n\t\t\t`Invalid schema: ${stringifyError(err)}`,\n\t\t);\n\t}\n\n\t// Invoke the action\n\tlet actor: AnyActorInstance | undefined;\n\tlet conn: AnyConn | undefined;\n\tlet output: unknown | undefined;\n\ttry {\n\t\tactor = await actorDriver.loadActor(actorId);\n\n\t\t// Create conn\n\t\tconst connState = await actor.prepareConn(parameters, c.req.raw);\n\t\tconn = await actor.createConn(\n\t\t\tgenerateConnId(),\n\t\t\tgenerateConnToken(),\n\t\t\tparameters,\n\t\t\tconnState,\n\t\t\tCONNECTION_DRIVER_HTTP,\n\t\t\t{} satisfies GenericHttpDriverState,\n\t\t\tauthData,\n\t\t);\n\n\t\t// Call action\n\t\tconst ctx = new ActionContext(actor.actorContext!, conn!);\n\t\toutput = await actor.executeAction(ctx, actionName, actionArgs);\n\t} finally {\n\t\tif (conn) {\n\t\t\tactor?.__removeConn(conn);\n\t\t}\n\t}\n\n\t// Encode the response\n\tif (encoding === \"json\") {\n\t\tconst responseData = {\n\t\t\to: output, // Use the format expected by ResponseOkSchema\n\t\t};\n\t\treturn c.json(responseData);\n\t} else if (encoding === \"cbor\") {\n\t\t// Use serialize from serde.ts instead of custom encoder\n\t\tconst responseData = {\n\t\t\to: output, // Use the format expected by ResponseOkSchema\n\t\t};\n\t\tconst serialized = serialize(responseData, encoding);\n\n\t\treturn c.body(serialized as Uint8Array, 200, {\n\t\t\t\"Content-Type\": \"application/octet-stream\",\n\t\t});\n\t} else {\n\t\treturn assertUnreachable(encoding);\n\t}\n}\n\n/**\n * Create a connection message handler\n */\nexport async function handleConnectionMessage(\n\tc: HonoContext,\n\trunConfig: RunConfig,\n\tactorDriver: ActorDriver,\n\tconnId: string,\n\tconnToken: string,\n\tactorId: string,\n) {\n\tconst encoding = getRequestEncoding(c.req);\n\n\t// Validate incoming request\n\tlet message: messageToServer.ToServer;\n\tif (encoding === \"json\") {\n\t\ttry {\n\t\t\tmessage = await c.req.json();\n\t\t} catch (_err) {\n\t\t\tthrow new errors.InvalidRequest(\"Invalid JSON\");\n\t\t}\n\t} else if (encoding === \"cbor\") {\n\t\ttry {\n\t\t\tconst value = await c.req.arrayBuffer();\n\t\t\tconst uint8Array = new Uint8Array(value);\n\t\t\tmessage = await parseMessage(uint8Array as unknown as InputData, {\n\t\t\t\tencoding,\n\t\t\t\tmaxIncomingMessageSize: runConfig.maxIncomingMessageSize,\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tthrow new errors.InvalidRequest(\n\t\t\t\t`Invalid binary format: ${stringifyError(err)}`,\n\t\t\t);\n\t\t}\n\t} else {\n\t\treturn assertUnreachable(encoding);\n\t}\n\n\tconst actor = await actorDriver.loadActor(actorId);\n\n\t// Find connection\n\tconst conn = actor.conns.get(connId);\n\tif (!conn) {\n\t\tthrow new errors.ConnNotFound(connId);\n\t}\n\n\t// Authenticate connection\n\tif (conn._token !== connToken) {\n\t\tthrow new errors.IncorrectConnToken();\n\t}\n\n\t// Process message\n\tawait actor.processMessage(message, conn);\n\n\treturn c.json({});\n}\n\nexport async function handleRawWebSocketHandler(\n\treq: Request | undefined,\n\tpath: string,\n\tactorDriver: ActorDriver,\n\tactorId: string,\n\tauthData: unknown,\n): Promise<UpgradeWebSocketArgs> {\n\tconst actor = await actorDriver.loadActor(actorId);\n\n\t// Return WebSocket event handlers\n\treturn {\n\t\tonOpen: (_evt: any, ws: any) => {\n\t\t\t// Wrap the Hono WebSocket in our adapter\n\t\t\tconst adapter = new HonoWebSocketAdapter(ws);\n\n\t\t\t// Store adapter reference on the WebSocket for event handlers\n\t\t\t(ws as any).__adapter = adapter;\n\n\t\t\t// Extract the path after prefix and preserve query parameters\n\t\t\t// Use URL API for cleaner parsing\n\t\t\tconst url = new URL(path, \"http://actor\");\n\t\t\tconst pathname = url.pathname.replace(/^\\/raw\\/websocket/, \"\") || \"/\";\n\t\t\tconst normalizedPath = pathname + url.search;\n\n\t\t\tlet newRequest: Request;\n\t\t\tif (req) {\n\t\t\t\tnewRequest = new Request(`http://actor${normalizedPath}`, req);\n\t\t\t} else {\n\t\t\t\tnewRequest = new Request(`http://actor${normalizedPath}`, {\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tlogger().debug(\"rewriting websocket url\", {\n\t\t\t\tfrom: path,\n\t\t\t\tto: newRequest.url,\n\t\t\t});\n\n\t\t\t// Call the actor's onWebSocket handler with the adapted WebSocket\n\t\t\tactor.handleWebSocket(adapter, {\n\t\t\t\trequest: newRequest,\n\t\t\t\tauth: authData,\n\t\t\t});\n\t\t},\n\t\tonMessage: (event: any, ws: any) => {\n\t\t\t// Find the adapter for this WebSocket\n\t\t\tconst adapter = (ws as any).__adapter;\n\t\t\tif (adapter) {\n\t\t\t\tadapter._handleMessage(event);\n\t\t\t}\n\t\t},\n\t\tonClose: (evt: any, ws: any) => {\n\t\t\t// Find the adapter for this WebSocket\n\t\t\tconst adapter = (ws as any).__adapter;\n\t\t\tif (adapter) {\n\t\t\t\tadapter._handleClose(evt?.code || 1006, evt?.reason || \"\");\n\t\t\t}\n\t\t},\n\t\tonError: (error: any, ws: any) => {\n\t\t\t// Find the adapter for this WebSocket\n\t\t\tconst adapter = (ws as any).__adapter;\n\t\t\tif (adapter) {\n\t\t\t\tadapter._handleError(error);\n\t\t\t}\n\t\t},\n\t};\n}\n\n// Helper to get the connection encoding from a request\nexport function getRequestEncoding(req: HonoRequest): Encoding {\n\tconst encodingParam = req.header(HEADER_ENCODING);\n\tif (!encodingParam) {\n\t\tthrow new errors.InvalidEncoding(\"undefined\");\n\t}\n\n\tconst result = EncodingSchema.safeParse(encodingParam);\n\tif (!result.success) {\n\t\tthrow new errors.InvalidEncoding(encodingParam as string);\n\t}\n\n\treturn result.data;\n}\n\nexport function getRequestExposeInternalError(req: Request): boolean {\n\tconst param = req.headers.get(HEADER_EXPOSE_INTERNAL_ERROR);\n\tif (!param) {\n\t\treturn false;\n\t}\n\n\treturn param === \"true\";\n}\n\nexport function getRequestQuery(c: HonoContext): unknown {\n\t// Get query parameters for actor lookup\n\tconst queryParam = c.req.header(HEADER_ACTOR_QUERY);\n\tif (!queryParam) {\n\t\tlogger().error(\"missing query parameter\");\n\t\tthrow new errors.InvalidRequest(\"missing query\");\n\t}\n\n\t// Parse the query JSON and validate with schema\n\ttry {\n\t\tconst parsed = JSON.parse(queryParam);\n\t\treturn parsed;\n\t} catch (error) {\n\t\tlogger().error(\"invalid query json\", { error });\n\t\tthrow new errors.InvalidQueryJSON(error);\n\t}\n}\n\nexport const HEADER_ACTOR_QUERY = \"X-RivetKit-Query\";\n\nexport const HEADER_ENCODING = \"X-RivetKit-Encoding\";\n\n// Internal header\nexport const HEADER_EXPOSE_INTERNAL_ERROR = \"X-RivetKit-Expose-Internal-Error\";\n\n// IMPORTANT: Params must be in headers or in an E2EE part of the request (i.e. NOT the URL or query string) in order to ensure that tokens can be securely passed in params.\nexport const HEADER_CONN_PARAMS = \"X-RivetKit-Conn-Params\";\n\n// Internal header\nexport const HEADER_AUTH_DATA = \"X-RivetKit-Auth-Data\";\n\nexport const HEADER_ACTOR_ID = \"X-RivetKit-Actor\";\n\nexport const HEADER_CONN_ID = \"X-RivetKit-Conn\";\n\nexport const HEADER_CONN_TOKEN = \"X-RivetKit-Conn-Token\";\n\n/**\n * Headers that publics can send from public clients.\n *\n * Used for CORS.\n **/\nexport const ALLOWED_PUBLIC_HEADERS = [\n\t\"Content-Type\",\n\t\"User-Agent\",\n\tHEADER_ACTOR_QUERY,\n\tHEADER_ENCODING,\n\tHEADER_CONN_PARAMS,\n\tHEADER_ACTOR_ID,\n\tHEADER_CONN_ID,\n\tHEADER_CONN_TOKEN,\n];\n\n// Helper to get connection parameters for the request\nexport function getRequestConnParams(req: HonoRequest): unknown {\n\tconst paramsParam = req.header(HEADER_CONN_PARAMS);\n\tif (!paramsParam) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\treturn JSON.parse(paramsParam);\n\t} catch (err) {\n\t\tthrow new errors.InvalidParams(\n\t\t\t`Invalid params JSON: ${stringifyError(err)}`,\n\t\t);\n\t}\n}\n","import { z } from \"zod\";\n\nexport const ActionRequestSchema = z.object({\n\t// Args\n\ta: z.array(z.unknown()),\n});\n\nexport const ActionResponseSchema = z.object({\n\t// Output\n\to: z.unknown(),\n});\n\nexport type ActionRequest = z.infer<typeof ActionRequestSchema>;\nexport type ActionResponse = z.infer<typeof ActionResponseSchema>;\n","import { z } from \"zod\";\nimport type { AnyDatabaseProvider } from \"@/actor/database\";\nimport type * as wsToClient from \"@/actor/protocol/message/to-client\";\nimport * as wsToServer from \"@/actor/protocol/message/to-server\";\nimport {\n\tCachedSerializer,\n\tdeserialize,\n\ttype Encoding,\n\ttype InputData,\n} from \"@/actor/protocol/serde\";\nimport { deconstructError } from \"@/common/utils\";\nimport { ActionContext } from \"../../action\";\nimport type { Conn } from \"../../connection\";\nimport * as errors from \"../../errors\";\nimport type { ActorInstance } from \"../../instance\";\nimport { logger } from \"../../log\";\nimport { assertUnreachable } from \"../../utils\";\n\nexport const TransportSchema = z.enum([\"websocket\", \"sse\"]);\n\n/**\n * Transport mechanism used to communicate between client & actor.\n */\nexport type Transport = z.infer<typeof TransportSchema>;\n\ninterface MessageEventOpts {\n\tencoding: Encoding;\n\tmaxIncomingMessageSize: number;\n}\n\nfunction getValueLength(value: InputData): number {\n\tif (typeof value === \"string\") {\n\t\treturn value.length;\n\t} else if (value instanceof Blob) {\n\t\treturn value.size;\n\t} else if (\n\t\tvalue instanceof ArrayBuffer ||\n\t\tvalue instanceof SharedArrayBuffer ||\n\t\tvalue instanceof Uint8Array\n\t) {\n\t\treturn value.byteLength;\n\t} else {\n\t\tassertUnreachable(value);\n\t}\n}\n\nexport async function parseMessage(\n\tvalue: InputData,\n\topts: MessageEventOpts,\n): Promise<wsToServer.ToServer> {\n\t// Validate value length\n\tconst length = getValueLength(value);\n\tif (length > opts.maxIncomingMessageSize) {\n\t\tthrow new errors.MessageTooLong();\n\t}\n\n\t// Parse & validate message\n\tconst deserializedValue = await deserialize(value, opts.encoding);\n\tconst {\n\t\tdata: message,\n\t\tsuccess,\n\t\terror,\n\t} = wsToServer.ToServerSchema.safeParse(deserializedValue);\n\tif (!success) {\n\t\tthrow new errors.MalformedMessage(error);\n\t}\n\n\treturn message;\n}\n\nexport interface ProcessMessageHandler<\n\tS,\n\tCP,\n\tCS,\n\tV,\n\tI,\n\tAD,\n\tDB extends AnyDatabaseProvider,\n> {\n\tonExecuteAction?: (\n\t\tctx: ActionContext<S, CP, CS, V, I, AD, DB>,\n\t\tname: string,\n\t\targs: unknown[],\n\t) => Promise<unknown>;\n\tonSubscribe?: (\n\t\teventName: string,\n\t\tconn: Conn<S, CP, CS, V, I, AD, DB>,\n\t) => Promise<void>;\n\tonUnsubscribe?: (\n\t\teventName: string,\n\t\tconn: Conn<S, CP, CS, V, I, AD, DB>,\n\t) => Promise<void>;\n}\n\nexport async function processMessage<\n\tS,\n\tCP,\n\tCS,\n\tV,\n\tI,\n\tAD,\n\tDB extends AnyDatabaseProvider,\n>(\n\tmessage: wsToServer.ToServer,\n\tactor: ActorInstance<S, CP, CS, V, I, AD, DB>,\n\tconn: Conn<S, CP, CS, V, I, AD, DB>,\n\thandler: ProcessMessageHandler<S, CP, CS, V, I, AD, DB>,\n) {\n\tlet actionId: number | undefined;\n\tlet actionName: string | undefined;\n\n\ttry {\n\t\tif (\"ar\" in message.b) {\n\t\t\t// Action request\n\n\t\t\tif (handler.onExecuteAction === undefined) {\n\t\t\t\tthrow new errors.Unsupported(\"Action\");\n\t\t\t}\n\n\t\t\tconst { i: id, n: name, a: args = [] } = message.b.ar;\n\n\t\t\tactionId = id;\n\t\t\tactionName = name;\n\n\t\t\tlogger().debug(\"processing action request\", {\n\t\t\t\tid,\n\t\t\t\tname,\n\t\t\t\targsCount: args.length,\n\t\t\t});\n\n\t\t\tconst ctx = new ActionContext<S, CP, CS, V, I, AD, DB>(\n\t\t\t\tactor.actorContext,\n\t\t\t\tconn,\n\t\t\t);\n\n\t\t\t// Process the action request and wait for the result\n\t\t\t// This will wait for async actions to complete\n\t\t\tconst output = await handler.onExecuteAction(ctx, name, args);\n\n\t\t\tlogger().debug(\"sending action response\", {\n\t\t\t\tid,\n\t\t\t\tname,\n\t\t\t\toutputType: typeof output,\n\t\t\t\tisPromise: output instanceof Promise,\n\t\t\t});\n\n\t\t\t// Send the response back to the client\n\t\t\tconn._sendMessage(\n\t\t\t\tnew CachedSerializer<wsToClient.ToClient>({\n\t\t\t\t\tb: {\n\t\t\t\t\t\tar: {\n\t\t\t\t\t\t\ti: id,\n\t\t\t\t\t\t\to: output,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tlogger().debug(\"action response sent\", { id, name });\n\t\t} else if (\"sr\" in message.b) {\n\t\t\t// Subscription request\n\n\t\t\tif (\n\t\t\t\thandler.onSubscribe === undefined ||\n\t\t\t\thandler.onUnsubscribe === undefined\n\t\t\t) {\n\t\t\t\tthrow new errors.Unsupported(\"Subscriptions\");\n\t\t\t}\n\n\t\t\tconst { e: eventName, s: subscribe } = message.b.sr;\n\t\t\tlogger().debug(\"processing subscription request\", {\n\t\t\t\teventName,\n\t\t\t\tsubscribe,\n\t\t\t});\n\n\t\t\tif (subscribe) {\n\t\t\t\tawait handler.onSubscribe(eventName, conn);\n\t\t\t} else {\n\t\t\t\tawait handler.onUnsubscribe(eventName, conn);\n\t\t\t}\n\n\t\t\tlogger().debug(\"subscription request completed\", {\n\t\t\t\teventName,\n\t\t\t\tsubscribe,\n\t\t\t});\n\t\t} else {\n\t\t\tassertUnreachable(message.b);\n\t\t}\n\t} catch (error) {\n\t\tconst { code, message, metadata } = deconstructError(error, logger(), {\n\t\t\tconnectionId: conn.id,\n\t\t\tactionId,\n\t\t\tactionName,\n\t\t});\n\n\t\tlogger().debug(\"sending error response\", {\n\t\t\tactionId,\n\t\t\tactionName,\n\t\t\tcode,\n\t\t\tmessage,\n\t\t});\n\n\t\t// Build response\n\t\tconn._sendMessage(\n\t\t\tnew CachedSerializer<wsToClient.ToClient>({\n\t\t\t\tb: {\n\t\t\t\t\te: {\n\t\t\t\t\t\tc: code,\n\t\t\t\t\t\tm: message,\n\t\t\t\t\t\tmd: metadata,\n\t\t\t\t\t\tai: actionId,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\n\t\tlogger().debug(\"error response sent\", { actionId, actionName });\n\t}\n}\n","import { z } from \"zod\";\n\nconst ActionRequestSchema = z.object({\n\t// ID\n\ti: z.number().int(),\n\t// Name\n\tn: z.string(),\n\t// Args\n\ta: z.array(z.unknown()),\n});\n\nconst SubscriptionRequestSchema = z.object({\n\t// Event name\n\te: z.string(),\n\t// Subscribe\n\ts: z.boolean(),\n});\n\nexport const ToServerSchema = z.object({\n\t// Body\n\tb: z.union([\n\t\tz.object({ ar: ActionRequestSchema }),\n\t\tz.object({ sr: SubscriptionRequestSchema }),\n\t]),\n});\n\nexport type ToServer = z.infer<typeof ToServerSchema>;\nexport type ActionRequest = z.infer<typeof ActionRequestSchema>;\nexport type SubscriptionRequest = z.infer<typeof SubscriptionRequestSchema>;\n","import { getLogger } from \"@/common//log\";\n\n/** Logger for this library. */\nexport const RUNTIME_LOGGER_NAME = \"actor-runtime\";\n\n/** Logger used for logs from the actor instance itself. */\nexport const ACTOR_LOGGER_NAME = \"actor\";\n\nexport function logger() {\n\treturn getLogger(RUNTIME_LOGGER_NAME);\n}\n\nexport function instanceLogger() {\n\treturn getLogger(ACTOR_LOGGER_NAME);\n}\n","import * as errors from \"./errors\";\nimport { logger } from \"./log\";\n\nexport function assertUnreachable(x: never): never {\n\tlogger().error(\"unreachable\", { value: `${x}`, stack: new Error().stack });\n\tthrow new errors.Unreachable(x);\n}\n\nexport const throttle = <\n\t// biome-ignore lint/suspicious/noExplicitAny: we want to allow any function\n\tFn extends (...args: any) => any,\n>(\n\tfn: Fn,\n\tdelay: number,\n) => {\n\tlet lastRan = false;\n\tlet lastArgs: Parameters<Fn> | null = null;\n\n\treturn (...args: Parameters<Fn>) => {\n\t\tif (!lastRan) {\n\t\t\tfn.apply(this, args);\n\t\t\tlastRan = true;\n\t\t\tconst timer = () =>\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tlastRan = false;\n\t\t\t\t\tif (lastArgs) {\n\t\t\t\t\t\tfn.apply(this, lastArgs);\n\t\t\t\t\t\tlastRan = true;\n\t\t\t\t\t\tlastArgs = null;\n\t\t\t\t\t\ttimer();\n\t\t\t\t\t}\n\t\t\t\t}, delay);\n\t\t\ttimer();\n\t\t} else lastArgs = args;\n\t};\n};\n\nexport class DeadlineError extends Error {\n\tconstructor() {\n\t\tsuper(\"Promise did not complete before deadline.\");\n\t}\n}\n\nexport function deadline<T>(promise: Promise<T>, timeout: number): Promise<T> {\n\tconst controller = new AbortController();\n\tconst signal = controller.signal;\n\n\t// Set a timeout to abort the operation\n\tconst timeoutId = setTimeout(() => controller.abort(), timeout);\n\n\treturn Promise.race<T>([\n\t\tpromise,\n\t\tnew Promise<T>((_, reject) => {\n\t\t\tsignal.addEventListener(\"abort\", () => reject(new DeadlineError()));\n\t\t}),\n\t]).finally(() => {\n\t\tclearTimeout(timeoutId);\n\t});\n}\n\nexport class Lock<T> {\n\tprivate _locked = false;\n\tprivate _waiting: Array<() => void> = [];\n\n\tconstructor(private _value: T) {}\n\n\tasync lock(fn: (value: T) => Promise<void>): Promise<void> {\n\t\tif (this._locked) {\n\t\t\tawait new Promise<void>((resolve) => this._waiting.push(resolve));\n\t\t}\n\t\tthis._locked = true;\n\n\t\ttry {\n\t\t\tawait fn(this._value);\n\t\t} finally {\n\t\t\tthis._locked = false;\n\t\t\tconst next = this._waiting.shift();\n\t\t\tif (next) next();\n\t\t}\n\t}\n}\n\nexport function generateSecureToken(length = 32) {\n\tconst array = new Uint8Array(length);\n\tcrypto.getRandomValues(array);\n\treturn btoa(String.fromCharCode(...array));\n}\n\nexport function generateRandomString(length = 32) {\n\tconst characters =\n\t\t\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n\tlet result = \"\";\n\tfor (let i = 0; i < length; i++) {\n\t\tconst randomIndex = Math.floor(Math.random() * characters.length);\n\t\tresult += characters[randomIndex];\n\t}\n\treturn result;\n}\n","import type * as messageToClient from \"@/actor/protocol/message/to-client\";\nimport type * as wsToClient from \"@/actor/protocol/message/to-client\";\nimport type { AnyDatabaseProvider } from \"./database\";\nimport { type ConnDriver, ConnectionReadyState } from \"./driver\";\nimport * as errors from \"./errors\";\nimport type { ActorInstance } from \"./instance\";\nimport { logger } from \"./log\";\nimport type { PersistedConn } from \"./persisted\";\nimport { CachedSerializer } from \"./protocol/serde\";\nimport { generateSecureToken } from \"./utils\";\n\nexport function generateConnId(): string {\n\treturn crypto.randomUUID();\n}\n\nexport function generateConnToken(): string {\n\treturn generateSecureToken(32);\n}\n\nexport type ConnId = string;\n\nexport type AnyConn = Conn<any, any, any, any, any, any, any>;\n\nexport const CONNECTION_DRIVER_WEBSOCKET = \"webSocket\";\nexport const CONNECTION_DRIVER_SSE = \"sse\";\nexport const CONNECTION_DRIVER_HTTP = \"http\";\n\nexport type ConnectionDriver =\n\t| typeof CONNECTION_DRIVER_WEBSOCKET\n\t| typeof CONNECTION_DRIVER_SSE\n\t| typeof CONNECTION_DRIVER_HTTP;\n\nexport type ConnectionStatus = \"connected\" | \"reconnecting\";\n\nexport const CONNECTION_CHECK_LIVENESS_SYMBOL = Symbol(\"checkLiveness\");\n\n/**\n * Represents a client connection to a actor.\n *\n * Manages connection-specific data and controls the connection lifecycle.\n *\n * @see {@link https://rivet.gg/docs/connections|Connection Documentation}\n */\nexport class Conn<S, CP, CS, V, I, AD, DB extends AnyDatabaseProvider> {\n\tsubscriptions: Set<string> = new Set<string>();\n\n\t#stateEnabled: boolean;\n\n\t// TODO: Remove this cyclical reference\n\t#actor: ActorInstance<S, CP, CS, V, I, AD, DB>;\n\n\t#status: ConnectionStatus = \"connected\";\n\n\t/**\n\t * The proxied state that notifies of changes automatically.\n\t *\n\t * Any data that should be stored indefinitely should be held within this object.\n\t */\n\t__persist: PersistedConn<CP, CS>;\n\n\t/**\n\t * Driver used to manage realtime connection communication.\n\t *\n\t * @protected\n\t */\n\t#driver: ConnDriver;\n\n\tpublic get params(): CP {\n\t\treturn this.__persist.p;\n\t}\n\n\tpublic get auth(): AD {\n\t\treturn this.__persist.a as AD;\n\t}\n\n\tpublic get driver(): ConnectionDriver {\n\t\treturn this.__persist.d as ConnectionDriver;\n\t}\n\n\tpublic get _stateEnabled() {\n\t\treturn this.#stateEnabled;\n\t}\n\n\t/**\n\t * Gets the current state of the connection.\n\t *\n\t * Throws an error if the state is not enabled.\n\t */\n\tpublic get state(): CS {\n\t\tthis.#validateStateEnabled();\n\t\tif (!this.__persist.s) throw new Error(\"state should exists\");\n\t\treturn this.__persist.s;\n\t}\n\n\t/**\n\t * Sets the state of the connection.\n\t *\n\t * Throws an error if the state is not enabled.\n\t */\n\tpublic set state(value: CS) {\n\t\tthis.#validateStateEnabled();\n\t\tthis.__persist.s = value;\n\t}\n\n\t/**\n\t * Unique identifier for the connection.\n\t */\n\tpublic get id(): ConnId {\n\t\treturn this.__persist.i;\n\t}\n\n\t/**\n\t * Token used to authenticate this request.\n\t */\n\tpublic get _token(): string {\n\t\treturn this.__persist.t;\n\t}\n\n\t/**\n\t * Status of the connection.\n\t */\n\tpublic get status(): ConnectionStatus {\n\t\treturn this.#status;\n\t}\n\n\t/**\n\t * Timestamp of the last time the connection was seen, i.e. the last time the connection was active and checked for liveness.\n\t */\n\tpublic get lastSeen(): number {\n\t\treturn this.__persist.l;\n\t}\n\n\t/**\n\t * Initializes a new instance of the Connection class.\n\t *\n\t * This should only be constructed by {@link Actor}.\n\t *\n\t * @protected\n\t */\n\tpublic constructor(\n\t\tactor: ActorInstance<S, CP, CS, V, I, AD, DB>,\n\t\tpersist: PersistedConn<CP, CS>,\n\t\tdriver: ConnDriver,\n\t\tstateEnabled: boolean,\n\t) {\n\t\tthis.#actor = actor;\n\t\tthis.__persist = persist;\n\t\tthis.#driver = driver;\n\t\tthis.#stateEnabled = stateEnabled;\n\t}\n\n\t#validateStateEnabled() {\n\t\tif (!this.#stateEnabled) {\n\t\t\tthrow new errors.ConnStateNotEnabled();\n\t\t}\n\t}\n\n\t/**\n\t * Sends a WebSocket message to the client.\n\t *\n\t * @param message - The message to send.\n\t *\n\t * @protected\n\t */\n\tpublic _sendMessage(message: CachedSerializer<messageToClient.ToClient>) {\n\t\tthis.#driver.sendMessage?.(this.#actor, this, this.__persist.ds, message);\n\t}\n\n\t/**\n\t * Sends an event with arguments to the client.\n\t *\n\t * @param eventName - The name of the event.\n\t * @param args - The arguments for the event.\n\t * @see {@link https://rivet.gg/docs/events|Events Documentation}\n\t */\n\tpublic send(eventName: string, ...args: unknown[]) {\n\t\tthis.#actor.inspector.emitter.emit(\"eventFired\", {\n\t\t\ttype: \"event\",\n\t\t\teventName,\n\t\t\targs,\n\t\t\tconnId: this.id,\n\t\t});\n\t\tthis._sendMessage(\n\t\t\tnew CachedSerializer<wsToClient.ToClient>({\n\t\t\t\tb: {\n\t\t\t\t\tev: {\n\t\t\t\t\t\tn: eventName,\n\t\t\t\t\t\ta: args,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\t}\n\n\t/**\n\t * Disconnects the client with an optional reason.\n\t *\n\t * @param reason - The reason for disconnection.\n\t */\n\tpublic async disconnect(reason?: string) {\n\t\tthis.#status = \"reconnecting\";\n\t\tawait this.#driver.disconnect(this.#actor, this, this.__persist.ds, reason);\n\t}\n\n\t/**\n\t * This method checks the connection's liveness by querying the driver for its ready state.\n\t * If the connection is not closed, it updates the last liveness timestamp and returns `true`.\n\t * Otherwise, it returns `false`.\n\t * @internal\n\t */\n\t[CONNECTION_CHECK_LIVENESS_SYMBOL]() {\n\t\tconst readyState = this.#driver.getConnectionReadyState?.(\n\t\t\tthis.#actor,\n\t\t\tthis,\n\t\t);\n\n\t\tconst isConnectionClosed =\n\t\t\treadyState === ConnectionReadyState.CLOSED ||\n\t\t\treadyState === ConnectionReadyState.CLOSING ||\n\t\t\treadyState === undefined;\n\n\t\tconst newLastSeen = Date.now();\n\t\tconst newStatus = isConnectionClosed ? \"reconnecting\" : \"connected\";\n\n\t\tlogger().debug(\"liveness probe for connection\", {\n\t\t\tconnId: this.id,\n\t\t\tactorId: this.#actor.id,\n\t\t\treadyState,\n\n\t\t\tstatus: this.#status,\n\t\t\tnewStatus,\n\n\t\t\tlastSeen: this.__persist.l,\n\t\t\tcurrentTs: newLastSeen,\n\t\t});\n\n\t\tif (!isConnectionClosed) {\n\t\t\tthis.__persist.l = newLastSeen;\n\t\t}\n\n\t\tthis.#status = newStatus;\n\t\treturn {\n\t\t\tstatus: this.#status,\n\t\t\tlastSeen: this.__persist.l,\n\t\t};\n\t}\n}\n","import type { cors } from \"hono/cors\";\nimport { z } from \"zod\";\nimport type { ActorDriverBuilder } from \"@/actor/driver\";\nimport { InspectorConfigSchema } from \"@/inspector/config\";\nimport type { ManagerDriverBuilder } from \"@/manager/driver\";\nimport type { UpgradeWebSocket } from \"@/utils\";\n\ntype CorsOptions = NonNullable<Parameters<typeof cors>[0]>;\n\nexport type GetUpgradeWebSocket = () => UpgradeWebSocket;\n\nexport const DriverConfigSchema = z.object({\n\t/** Machine-readable name to identify this driver by. */\n\tname: z.string(),\n\tmanager: z.custom<ManagerDriverBuilder>(),\n\tactor: z.custom<ActorDriverBuilder>(),\n});\n\nexport type DriverConfig = z.infer<typeof DriverConfigSchema>;\n\n/** Base config used for the actor config across all platforms. */\nexport const RunConfigSchema = z\n\t.object({\n\t\tdriver: DriverConfigSchema.optional(),\n\n\t\t/** Endpoint to connect to the Rivet engine. Can be configured via RIVET_ENGINE env var. */\n\t\tengine: z.string().optional(),\n\n\t\t// This is a function to allow for lazy configuration of upgradeWebSocket on the\n\t\t// fly. This is required since the dependencies that profie upgradeWebSocket\n\t\t// (specifically Node.js) can sometimes only be specified after the router is\n\t\t// created or must be imported async using `await import(...)`\n\t\tgetUpgradeWebSocket: z.custom<GetUpgradeWebSocket>().optional(),\n\n\t\trole: z.enum([\"all\", \"server\", \"runner\"]).optional().default(\"all\"),\n\n\t\t/** CORS configuration for the router. Uses Hono's CORS middleware options. */\n\t\tcors: z.custom<CorsOptions>().optional(),\n\n\t\tmaxIncomingMessageSize: z.number().optional().default(65_536),\n\n\t\tinspector: InspectorConfigSchema,\n\n\t\t/**\n\t\t * Base path for the router. This is used to prefix all routes.\n\t\t * For example, if the base path is `/api`, then the route `/actors` will be\n\t\t * available at `/api/actors`.\n\t\t */\n\t\tbasePath: z.string().optional().default(\"/\"),\n\t})\n\t.default({});\n\nexport type RunConfig = z.infer<typeof RunConfigSchema>;\nexport type RunConfigInput = z.input<typeof RunConfigSchema>;\n","import type { cors } from \"hono/cors\";\nimport { z } from \"zod\";\nimport { HEADER_ACTOR_QUERY } from \"@/driver-helpers/mod\";\nimport { getEnvUniversal } from \"@/utils\";\n\ntype CorsOptions = NonNullable<Parameters<typeof cors>[0]>;\n\nconst defaultTokenFn = () => {\n\tconst envToken = getEnvUniversal(\"RIVETKIT_INSPECTOR_TOKEN\");\n\n\tif (envToken) {\n\t\treturn envToken;\n\t}\n\n\treturn \"\";\n};\n\nconst defaultEnabled = () => {\n\treturn (\n\t\tgetEnvUniversal(\"NODE_ENV\") !== \"production\" ||\n\t\t!getEnvUniversal(\"RIVETKIT_INSPECTOR_DISABLE\")\n\t);\n};\n\nconst defaultInspectorOrigins = [\n\t\"http://localhost:43708\",\n\t\"https://studio.rivet.gg\",\n];\n\nconst defaultCors: CorsOptions = {\n\torigin: (origin) => {\n\t\tif (\n\t\t\tdefaultInspectorOrigins.includes(origin) ||\n\t\t\t(origin.startsWith(\"https://\") && origin.endsWith(\"rivet-gg.vercel.app\"))\n\t\t) {\n\t\t\treturn origin;\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t},\n\tallowMethods: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"],\n\tallowHeaders: [\n\t\t\"Content-Type\",\n\t\t\"Authorization\",\n\t\tHEADER_ACTOR_QUERY,\n\t\t\"last-event-id\",\n\t],\n\tmaxAge: 3600,\n\tcredentials: true,\n};\n\nexport const InspectorConfigSchema = z\n\t.object({\n\t\tenabled: z.boolean().optional().default(defaultEnabled),\n\t\t/** CORS configuration for the router. Uses Hono's CORS middleware options. */\n\t\tcors: z\n\t\t\t.custom<CorsOptions>()\n\t\t\t.optional()\n\t\t\t.default(() => defaultCors),\n\n\t\t/**\n\t\t * Token used to access the Inspector.\n\t\t */\n\t\ttoken: z\n\t\t\t.