@rivetkit/core
Version:
1,664 lines (1,645 loc) • 44.1 kB
JavaScript
import {
getLogger
} from "./chunk-CLOGFICZ.js";
import {
deconstructError,
stringifyError
} from "./chunk-WDSX6QCH.js";
import {
ConnNotFound,
ConnStateNotEnabled,
IncorrectConnToken,
InvalidActionRequest,
InvalidEncoding,
InvalidParams,
InvalidQueryJSON,
InvalidRequest,
MalformedMessage,
MessageTooLong,
Unreachable,
Unsupported
} from "./chunk-INRCNZ4I.js";
// src/actor/protocol/serde.ts
import * as cbor from "cbor-x";
import { z } from "zod";
// src/actor/log.ts
var RUNTIME_LOGGER_NAME = "actor-runtime";
var ACTOR_LOGGER_NAME = "actor";
function logger() {
return getLogger(RUNTIME_LOGGER_NAME);
}
function instanceLogger() {
return getLogger(ACTOR_LOGGER_NAME);
}
// src/actor/utils.ts
function assertUnreachable(x) {
logger().error("unreachable", { value: `${x}`, stack: new Error().stack });
throw new Unreachable(x);
}
var DeadlineError = class extends Error {
constructor() {
super("Promise did not complete before deadline.");
}
};
function deadline(promise, timeout) {
const controller = new AbortController();
const signal = controller.signal;
const timeoutId = setTimeout(() => controller.abort(), timeout);
return Promise.race([
promise,
new Promise((_, reject) => {
signal.addEventListener("abort", () => reject(new DeadlineError()));
})
]).finally(() => {
clearTimeout(timeoutId);
});
}
var Lock = class {
constructor(_value) {
this._value = _value;
}
_locked = false;
_waiting = [];
async lock(fn) {
if (this._locked) {
await new Promise((resolve) => this._waiting.push(resolve));
}
this._locked = true;
try {
await fn(this._value);
} finally {
this._locked = false;
const next = this._waiting.shift();
if (next) next();
}
}
};
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;
}
// src/actor/protocol/serde.ts
var EncodingSchema = z.enum(["json", "cbor"]);
var CachedSerializer = class {
#data;
#cache = /* @__PURE__ */ new Map();
constructor(data) {
this.#data = data;
}
get rawData() {
return this.#data;
}
serialize(encoding) {
const cached = this.#cache.get(encoding);
if (cached) {
return cached;
} else {
const serialized = serialize(this.#data, encoding);
this.#cache.set(encoding, serialized);
return serialized;
}
}
};
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 {
assertUnreachable(encoding);
}
}
async function deserialize(data, encoding) {
if (encoding === "json") {
if (typeof data !== "string") {
logger().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 {
logger().warn("received non-binary type for cbor parse");
throw new MalformedMessage();
}
} else {
assertUnreachable(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 {
assertUnreachable(message);
}
}
// src/actor/connection.ts
function generateConnId() {
return crypto.randomUUID();
}
function generateConnToken() {
return generateSecureToken(32);
}
var Conn = class {
subscriptions = /* @__PURE__ */ new Set();
#stateEnabled;
// TODO: Remove this cyclical reference
#actor;
/**
* The proxied state that notifies of changes automatically.
*
* Any data that should be stored indefinitely should be held within this object.
*/
__persist;
/**
* Driver used to manage realtime connection communication.
*
* @protected
*/
#driver;
get params() {
return this.__persist.p;
}
get auth() {
return this.__persist.a;
}
get _stateEnabled() {
return this.#stateEnabled;
}
/**
* Gets the current state of the connection.
*
* Throws an error if the state is not enabled.
*/
get state() {
this.#validateStateEnabled();
if (!this.__persist.s) throw new Error("state should exists");
return this.__persist.s;
}
/**
* Sets the state of the connection.
*
* Throws an error if the state is not enabled.
*/
set state(value) {
this.#validateStateEnabled();
this.__persist.s = value;
}
/**
* Unique identifier for the connection.
*/
get id() {
return this.__persist.i;
}
/**
* Token used to authenticate this request.
*/
get _token() {
return this.__persist.t;
}
/**
* Initializes a new instance of the Connection class.
*
* This should only be constructed by {@link Actor}.
*
* @protected
*/
constructor(actor, persist, driver, stateEnabled) {
this.#actor = actor;
this.__persist = persist;
this.#driver = driver;
this.#stateEnabled = stateEnabled;
}
#validateStateEnabled() {
if (!this.#stateEnabled) {
throw new ConnStateNotEnabled();
}
}
/**
* Sends a WebSocket message to the client.
*
* @param message - The message to send.
*
* @protected
*/
_sendMessage(message) {
var _a, _b;
(_b = (_a = this.#driver).sendMessage) == null ? void 0 : _b.call(_a, this.#actor, this, this.__persist.ds, message);
}
/**
* Sends an event with arguments to the client.
*
* @param eventName - The name of the event.
* @param args - The arguments for the event.
* @see {@link https://rivet.gg/docs/events|Events Documentation}
*/
send(eventName, ...args) {
this.#actor.inspector.emitter.emit("eventFired", {
type: "event",
eventName,
args,
connId: this.id
});
this._sendMessage(
new CachedSerializer({
b: {
ev: {
n: eventName,
a: args
}
}
})
);
}
/**
* Disconnects the client with an optional reason.
*
* @param reason - The reason for disconnection.
*/
async disconnect(reason) {
await this.#driver.disconnect(this.#actor, this, this.__persist.ds, reason);
}
};
// src/actor/generic-conn-driver.ts
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) {
logger().warn("missing ws for sendMessage", {
actorId: actor.id,
connId: conn.id,
totalCount: globalState.websockets.size
});
return;
}
const serialized = message.serialize(state.encoding);
logger().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));
logger().debug("converted SharedArrayBuffer to ArrayBuffer", {
byteLength: arrayBuffer.byteLength
});
ws.send(arrayBuffer);
} else {
logger().debug("sending ArrayBuffer", {
byteLength: buffer.byteLength
});
ws.send(buffer);
}
} else {
logger().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) {
logger().warn("missing ws for disconnect", {
actorId: actor.id,
connId: conn.id,
totalCount: globalState.websockets.size
});
return;
}
const raw = ws.raw;
if (!raw) {
logger().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) {
logger().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) {
logger().warn("missing sse stream for disconnect", { connId: conn.id });
return;
}
stream.close();
}
};
}
var CONN_DRIVER_GENERIC_HTTP = "genericHttp";
function createGeneircHttpDriver() {
return {
disconnect: async () => {
}
};
}
// src/actor/router-endpoints.ts
import { streamSSE } from "hono/streaming";
// src/actor/action.ts
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);
}
};
// src/actor/protocol/http/action.ts
import { z as z2 } from "zod";
var ActionRequestSchema = z2.object({
// Args
a: z2.array(z2.unknown())
});
var ActionResponseSchema = z2.object({
// Output
o: z2.unknown()
});
// src/actor/protocol/message/mod.ts
import { z as z4 } from "zod";
// src/actor/protocol/message/to-server.ts
import { z as z3 } from "zod";
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 })
])
});
// src/actor/protocol/message/mod.ts
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 {
assertUnreachable(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;
}
async function processMessage(message, actor, conn, handler) {
let actionId;
let actionName;
try {
if ("ar" in message.b) {
if (handler.onExecuteAction === void 0) {
throw new Unsupported("Action");
}
const { i: id, n: name, a: args = [] } = message.b.ar;
actionId = id;
actionName = name;
logger().debug("processing action request", {
id,
name,
argsCount: args.length
});
const ctx = new ActionContext(
actor.actorContext,
conn
);
const output = await handler.onExecuteAction(ctx, name, args);
logger().debug("sending action response", {
id,
name,
outputType: typeof output,
isPromise: output instanceof Promise
});
conn._sendMessage(
new CachedSerializer({
b: {
ar: {
i: id,
o: output
}
}
})
);
logger().debug("action response sent", { id, name });
} else if ("sr" in message.b) {
if (handler.onSubscribe === void 0 || handler.onUnsubscribe === void 0) {
throw new Unsupported("Subscriptions");
}
const { e: eventName, s: subscribe } = message.b.sr;
logger().debug("processing subscription request", {
eventName,
subscribe
});
if (subscribe) {
await handler.onSubscribe(eventName, conn);
} else {
await handler.onUnsubscribe(eventName, conn);
}
logger().debug("subscription request completed", {
eventName,
subscribe
});
} else {
assertUnreachable(message.b);
}
} catch (error) {
const { code, message: message2, metadata } = deconstructError(error, logger(), {
connectionId: conn.id,
actionId,
actionName
});
logger().debug("sending error response", {
actionId,
actionName,
code,
message: message2
});
conn._sendMessage(
new CachedSerializer({
b: {
e: {
c: code,
m: message2,
md: metadata,
ai: actionId
}
}
})
);
logger().debug("error response sent", { actionId, actionName });
}
}
// src/manager/log.ts
var LOGGER_NAME = "actor-manager";
function logger2() {
return getLogger(LOGGER_NAME);
}
// src/manager/hono-websocket-adapter.ts
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 {
logger2().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) => {
logger2().error("failed to convert blob to arraybuffer", { error });
this.#fireEvent("error", { type: "error", target: this, error });
});
} else {
logger2().warn("unsupported data type, converting to string", {
dataType: typeof data,
data
});
this.#ws.send(String(data));
}
} catch (error) {
logger2().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) {
logger2().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) {
logger2().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);
}
logger2().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) {
logger2().error(`error in ${type} event listener`, { error });
}
}
}
switch (type) {
case "open":
if (this.#onopen) {
try {
this.#onopen(event);
} catch (error) {
logger2().error("error in onopen handler", { error });
}
}
break;
case "close":
if (this.#onclose) {
try {
this.#onclose(event);
} catch (error) {
logger2().error("error in onclose handler", { error });
}
}
break;
case "error":
if (this.#onerror) {
try {
this.#onerror(event);
} catch (error) {
logger2().error("error in onerror handler", { error });
}
}
break;
case "message":
if (this.#onmessage) {
try {
this.#onmessage(event);
} catch (error) {
logger2().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;
}
};
// src/actor/router-endpoints.ts
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,
logger(),
{
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) => {
logger().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);
logger().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,
logger(),
{
wsEvent: "open"
},
exposeInternalError
);
ws.close(1011, code);
}
})();
},
onMessage: (evt, ws) => {
handlersPromise.then(({ conn, actor: actor2 }) => {
logger().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,
logger(),
{
wsEvent: "message"
},
exposeInternalError
);
ws.close(1011, code);
});
}).catch((error) => {
const { code } = deconstructError(
error,
logger(),
{
wsEvent: "message"
},
exposeInternalError
);
ws.close(1011, code);
});
}).catch((error) => {
const { code } = deconstructError(
error,
logger(),
{
wsEvent: "message"
},
exposeInternalError
);
ws.close(1011, code);
});
},
onClose: (event, ws) => {
if (event.wasClean) {
logger().info("websocket closed", {
code: event.code,
reason: event.reason,
wasClean: event.wasClean
});
} else {
logger().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) {
logger().info("removing websocket for conn", {
totalCount: connGlobalState.websockets.size
});
} else {
logger().warn("websocket does not exist for conn", {
actorId,
totalCount: connGlobalState.websockets.size
});
}
actor2.__removeConn(conn);
}).catch((error) => {
deconstructError(
error,
logger(),
{ wsEvent: "close" },
exposeInternalError
);
});
},
onError: (_error) => {
try {
logger().warn("websocket error");
} catch (error) {
deconstructError(
error,
logger(),
{ 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);
logger().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 {
logger().debug("sse shutting down");
if (connId) {
actorDriver.getGenericConnGlobalState(actorId).sseStreams.delete(connId);
}
if (conn && actor) {
actor.__removeConn(conn);
}
abortResolver.resolve(void 0);
} catch (error) {
logger().error("error closing sse connection", { error });
}
});
try {
c.executionCtx.waitUntil(abortResolver.promise);
} catch {
}
await abortResolver.promise;
} catch (error) {
logger().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);
logger().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 assertUnreachable(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 assertUnreachable(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 assertUnreachable(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, path, 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(path, "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"
});
}
logger().debug("rewriting websocket url", {
from: path,
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";
}
function getRequestQuery(c) {
const queryParam = c.req.header(HEADER_ACTOR_QUERY);
if (!queryParam) {
logger().error("missing query parameter");
throw new InvalidRequest("missing query");
}
try {
const parsed = JSON.parse(queryParam);
return parsed;
} catch (error) {
logger().error("invalid query json", { error });
throw new InvalidQueryJSON(error);
}
}
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";
var ALLOWED_PUBLIC_HEADERS = [
"Content-Type",
"User-Agent",
HEADER_ACTOR_QUERY,
HEADER_ENCODING,
HEADER_CONN_PARAMS,
HEADER_ACTOR_ID,
HEADER_CONN_ID,
HEADER_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)}`
);
}
}
// src/inspector/protocol/common.ts
import z6 from "zod/v4";
// src/manager/protocol/query.ts
import { z as z5 } from "zod";
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)
});
// src/inspector/protocol/common.ts
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()
});
export {
logger,
instanceLogger,
assertUnreachable,
DeadlineError,
deadline,
Lock,
generateRandomString,
EncodingSchema,
CachedSerializer,
serialize,
generateConnId,
generateConnToken,
Conn,
processMessage,
logger2,
GenericConnGlobalState,
createGenericConnDrivers,
handleWebSocketConnect,
handleSseConnect,
handleAction,
handleConnectionMessage,
handleRawWebSocketHandler,
getRequestEncoding,
getRequestExposeInternalError,
getRequestQuery,
HEADER_ACTOR_QUERY,
HEADER_ENCODING,
HEADER_EXPOSE_INTERNAL_ERROR,
HEADER_CONN_PARAMS,
HEADER_AUTH_DATA,
HEADER_ACTOR_ID,
HEADER_CONN_ID,
HEADER_CONN_TOKEN,
ALLOWED_PUBLIC_HEADERS,
ActorQuerySchema,
ConnectRequestSchema,
ConnectWebSocketRequestSchema,
ConnMessageRequestSchema,
ResolveRequestSchema,
ActorId,
ActorFeature,
ActorLogEntry,
ActorSchema,
OperationSchema,
PatchSchema,
ConnectionSchema,
RealtimeEventSchema,
RecordedRealtimeEventSchema,
DatabaseQuerySchema,
TableSchema,
TablesSchema,
ColumnSchema,
ColumnsSchema,
ForeignKeySchema,
ForeignKeysSchema,
BuildSchema,
BuildsSchema,
CreateActorSchema
};
//# sourceMappingURL=chunk-IVHL2KCJ.js.map