@rivetkit/redis
Version:
_Lightweight Libraries for Backends_
1,767 lines (1,764 loc) • 132 kB
JavaScript
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