@rivetkit/core
Version:
1,641 lines (1,594 loc) • 78.8 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class; var _class2; var _class3; var _class4;
var _chunkOBXZ7YJ7cjs = require('./chunk-OBXZ7YJ7.cjs');
var _chunkON577KNDcjs = require('./chunk-ON577KND.cjs');
var _chunkHIB3AS73cjs = require('./chunk-HIB3AS73.cjs');
var _chunk53LWTTEXcjs = require('./chunk-53LWTTEX.cjs');
// src/actor/instance.ts
var _cborx = require('cbor-x'); var cbor = _interopRequireWildcard(_cborx); var cbor3 = _interopRequireWildcard(_cborx); var cbor2 = _interopRequireWildcard(_cborx);
var _invariant = require('invariant'); var _invariant2 = _interopRequireDefault(_invariant);
var _onchange = require('on-change'); var _onchange2 = _interopRequireDefault(_onchange);
// src/inspector/actor.ts
var _standardvalidator = require('@hono/standard-validator');
var _fastjsonpatch = require('@rivetkit/fast-json-patch'); var _fastjsonpatch2 = _interopRequireDefault(_fastjsonpatch);
var _hono = require('hono');
var _streaming = require('hono/streaming');
var _nanoevents = require('nanoevents');
var _v4 = require('zod/v4'); var _v42 = _interopRequireDefault(_v4);
function createActorInspectorRouter() {
return new (0, _hono.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",
_standardvalidator.sValidator.call(void 0,
"json",
_v42.default.object({ patch: _chunkOBXZ7YJ7cjs.PatchSchema }).or(_v42.default.object({ replace: _v42.default.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 } = _fastjsonpatch2.default.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 _streaming.streamSSE.call(void 0,
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 _streaming.streamSSE.call(void 0,
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 _streaming.streamSSE.call(void 0,
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 = _chunkOBXZ7YJ7cjs.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) => _chunkOBXZ7YJ7cjs.ColumnsSchema.parse(def));
const foreignKeysList = await Promise.all(
tables.map(
(table) => db.execute(`PRAGMA foreign_key_list(${table.name})`)
)
);
const foreignKeys = foreignKeysList.map(
(def) => _chunkOBXZ7YJ7cjs.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",
_standardvalidator.sValidator.call(void 0,
"json",
_v42.default.object({ query: _v42.default.string(), params: _v42.default.array(_v42.default.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);
}
}
);
}
var ActorInspector = (_class = class {
__init() {this.emitter = _nanoevents.createNanoEvents.call(void 0, )}
#lastRealtimeEvents = [];
get lastRealtimeEvents() {
return this.#lastRealtimeEvents;
}
constructor(accessors) {;_class.prototype.__init.call(this);
this.accessors = accessors();
this.emitter.on("eventFired", (event) => {
this.#lastRealtimeEvents.push({
id: crypto.randomUUID(),
timestamp: Date.now(),
...event
});
if (this.#lastRealtimeEvents.length > 100) {
this.#lastRealtimeEvents = this.#lastRealtimeEvents.slice(-100);
}
});
}
}, _class);
// src/actor/context.ts
var ActorContext = class {
#actor;
constructor(actor) {
this.#actor = actor;
}
/**
* Get the actor state
*/
get state() {
return this.#actor.state;
}
/**
* Get the actor variables
*/
get vars() {
return this.#actor.vars;
}
/**
* Broadcasts an event to all connected clients.
* @param name - The name of the event.
* @param args - The arguments to send with the event.
*/
broadcast(name, ...args) {
this.#actor._broadcast(name, ...args);
return;
}
/**
* Gets the logger instance.
*/
get log() {
return this.#actor.log;
}
/**
* Gets actor ID.
*/
get actorId() {
return this.#actor.id;
}
/**
* Gets the actor name.
*/
get name() {
return this.#actor.name;
}
/**
* Gets the actor key.
*/
get key() {
return this.#actor.key;
}
/**
* Gets the region.
*/
get region() {
return this.#actor.region;
}
/**
* Gets the scheduler.
*/
get schedule() {
return this.#actor.schedule;
}
/**
* Gets the map of connections.
*/
get conns() {
return this.#actor.conns;
}
/**
* Returns the client for the given registry.
*/
client() {
return this.#actor.inlineClient;
}
/**
* Gets the database.
* @experimental
* @throws {DatabaseNotEnabled} If the database is not enabled.
*/
get db() {
return this.#actor.db;
}
/**
* Forces the state to get saved.
*
* @param opts - Options for saving the state.
*/
async saveState(opts) {
return this.#actor.saveState(opts);
}
/**
* Runs a promise in the background.
*
* @param promise - The promise to run in the background.
*/
runInBackground(promise) {
this.#actor._runInBackground(promise);
return;
}
};
// src/actor/schedule.ts
var Schedule = class {
#actor;
constructor(actor) {
this.#actor = actor;
}
async after(duration, fn, ...args) {
await this.#actor.scheduleEvent(Date.now() + duration, fn, args);
}
async at(timestamp, fn, ...args) {
await this.#actor.scheduleEvent(timestamp, fn, args);
}
};
// src/actor/instance.ts
var ActorInstance = (_class2 = class {
// Shared actor context for this instance
__init2() {this.isStopping = false}
#persistChanged = false;
/**
* The proxied state that notifies of changes automatically.
*
* Any data that should be stored indefinitely should be held within this object.
*/
#persist;
/** Raw state without the proxy wrapper */
#persistRaw;
#writePersistLock = new (0, _chunkOBXZ7YJ7cjs.Lock)(void 0);
#lastSaveTime = 0;
#pendingSaveTimeout;
#vars;
#backgroundPromises = [];
#config;
#connectionDrivers;
#actorDriver;
#inlineClient;
#actorId;
#name;
#key;
#region;
#ready = false;
#connections = /* @__PURE__ */ new Map();
#subscriptionIndex = /* @__PURE__ */ new Map();
#schedule;
#db;
#inspector = new ActorInspector(() => {
return {
isDbEnabled: async () => {
return this.#db !== void 0;
},
getDb: async () => {
return this.db;
},
isStateEnabled: async () => {
return this.stateEnabled;
},
getState: async () => {
this.#validateStateEnabled();
return this.#persistRaw.s;
},
getRpcs: async () => {
return Object.keys(this.#config.actions);
},
getConnections: async () => {
return Array.from(this.#connections.entries()).map(([id, conn]) => ({
id,
stateEnabled: conn._stateEnabled,
params: conn.params,
state: conn._stateEnabled ? conn.state : void 0,
auth: conn.auth
}));
},
setState: async (state) => {
this.#validateStateEnabled();
this.#persist.s = { ...state };
await this.saveState({ immediate: true });
}
};
});
get id() {
return this.#actorId;
}
get inlineClient() {
return this.#inlineClient;
}
get inspector() {
return this.#inspector;
}
/**
* This constructor should never be used directly.
*
* Constructed in {@link ActorInstance.start}.
*
* @private
*/
constructor(config) {;_class2.prototype.__init2.call(this);
this.#config = config;
this.actorContext = new ActorContext(this);
}
async start(connectionDrivers, actorDriver, inlineClient, actorId, name, key, region) {
var _a, _b;
this.#connectionDrivers = connectionDrivers;
this.#actorDriver = actorDriver;
this.#inlineClient = inlineClient;
this.#actorId = actorId;
this.#name = name;
this.#key = key;
this.#region = region;
this.#schedule = new Schedule(this);
await this.#initialize();
if (this.#varsEnabled) {
let vars;
if ("createVars" in this.#config) {
const dataOrPromise = this.#config.createVars(
this.actorContext,
this.#actorDriver.getContext(this.#actorId)
);
if (dataOrPromise instanceof Promise) {
vars = await _chunkOBXZ7YJ7cjs.deadline.call(void 0,
dataOrPromise,
this.#config.options.lifecycle.createVarsTimeout
);
} else {
vars = dataOrPromise;
}
} else if ("vars" in this.#config) {
vars = structuredClone(this.#config.vars);
} else {
throw new Error("Could not variables from 'createVars' or 'vars'");
}
this.#vars = vars;
}
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).info("actor starting");
if (this.#config.onStart) {
const result = this.#config.onStart(this.actorContext);
if (result instanceof Promise) {
await result;
}
}
if ("db" in this.#config && this.#config.db) {
const client = await this.#config.db.createClient({
getDatabase: () => actorDriver.getDatabase(this.#actorId)
});
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).info("database migration starting");
await ((_b = (_a = this.#config.db).onMigrate) == null ? void 0 : _b.call(_a, client));
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).info("database migration complete");
this.#db = client;
}
if (this.#persist.e.length > 0) {
await this.#actorDriver.setAlarm(this, this.#persist.e[0].t);
}
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).info("actor ready");
this.#ready = true;
}
async scheduleEvent(timestamp, fn, args) {
const eventId = crypto.randomUUID();
const newEvent = {
e: eventId,
t: timestamp,
a: fn,
ar: args
};
this.actorContext.log.info("scheduling event", {
event: eventId,
timestamp,
action: fn
});
const insertIndex = this.#persist.e.findIndex((x) => x.t > newEvent.t);
if (insertIndex === -1) {
this.#persist.e.push(newEvent);
} else {
this.#persist.e.splice(insertIndex, 0, newEvent);
}
if (insertIndex === 0 || this.#persist.e.length === 1) {
this.actorContext.log.info("setting alarm", { timestamp });
await this.#actorDriver.setAlarm(this, newEvent.t);
}
}
async onAlarm() {
const now = Date.now();
this.actorContext.log.debug("alarm triggered", {
now,
events: this.#persist.e.length
});
const runIndex = this.#persist.e.findIndex((x) => x.t <= now);
if (runIndex === -1) {
this.actorContext.log.debug("no events to run", { now });
return;
}
const scheduleEvents = this.#persist.e.splice(0, runIndex + 1);
this.actorContext.log.debug("running events", {
count: scheduleEvents.length
});
if (this.#persist.e.length > 0) {
await this.#actorDriver.setAlarm(this, this.#persist.e[0].t);
}
for (const event of scheduleEvents) {
try {
this.actorContext.log.info("running action for event", {
event: event.e,
timestamp: event.t,
action: event.a,
args: event.ar
});
const fn = this.#config.actions[event.a];
if (!fn) throw new Error(`Missing action for alarm ${event.a}`);
if (typeof fn !== "function")
throw new Error(
`Alarm function lookup for ${event.a} returned ${typeof fn}`
);
try {
await fn.call(void 0, this.actorContext, ...event.ar);
} catch (error) {
this.actorContext.log.error("error while running event", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error),
event: event.e,
timestamp: event.t,
action: event.a,
args: event.ar
});
}
} catch (error) {
this.actorContext.log.error("internal error while running event", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error),
event: event.e,
timestamp: event.t,
action: event.a,
args: event.ar
});
}
}
}
get stateEnabled() {
return "createState" in this.#config || "state" in this.#config;
}
#validateStateEnabled() {
if (!this.stateEnabled) {
throw new (0, _chunk53LWTTEXcjs.StateNotEnabled)();
}
}
get #connStateEnabled() {
return "createConnState" in this.#config || "connState" in this.#config;
}
get #varsEnabled() {
return "createVars" in this.#config || "vars" in this.#config;
}
#validateVarsEnabled() {
if (!this.#varsEnabled) {
throw new (0, _chunk53LWTTEXcjs.VarsNotEnabled)();
}
}
/** Promise used to wait for a save to complete. This is required since you cannot await `#saveStateThrottled`. */
#onPersistSavedPromise;
/** Throttled save state method. Used to write to KV at a reasonable cadence. */
#savePersistThrottled() {
const now = Date.now();
const timeSinceLastSave = now - this.#lastSaveTime;
const saveInterval = this.#config.options.state.saveInterval;
if (timeSinceLastSave < saveInterval) {
if (this.#pendingSaveTimeout === void 0) {
this.#pendingSaveTimeout = setTimeout(() => {
this.#pendingSaveTimeout = void 0;
this.#savePersistInner();
}, saveInterval - timeSinceLastSave);
}
} else {
this.#savePersistInner();
}
}
/** Saves the state to KV. You probably want to use #saveStateThrottled instead except for a few edge cases. */
async #savePersistInner() {
var _a, _b;
try {
this.#lastSaveTime = Date.now();
if (this.#persistChanged) {
await this.#writePersistLock.lock(async () => {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("saving persist");
this.#persistChanged = false;
await this.#actorDriver.writePersistedData(
this.#actorId,
cbor.encode(this.#persistRaw)
);
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("persist saved");
});
}
(_a = this.#onPersistSavedPromise) == null ? void 0 : _a.resolve();
} catch (error) {
(_b = this.#onPersistSavedPromise) == null ? void 0 : _b.reject(error);
throw error;
}
}
/**
* Creates proxy for `#persist` that handles automatically flagging when state needs to be updated.
*/
#setPersist(target) {
this.#persistRaw = target;
if (target === null || typeof target !== "object") {
let invalidPath = "";
if (!_chunkHIB3AS73cjs.isCborSerializable.call(void 0,
target,
(path) => {
invalidPath = path;
},
""
)) {
throw new (0, _chunk53LWTTEXcjs.InvalidStateType)({ path: invalidPath });
}
return target;
}
if (this.#persist) {
_onchange2.default.unsubscribe(this.#persist);
}
this.#persist = _onchange2.default.call(void 0,
target,
// biome-ignore lint/suspicious/noExplicitAny: Don't know types in proxy
(path, value, _previousValue, _applyData) => {
let invalidPath = "";
if (!_chunkHIB3AS73cjs.isCborSerializable.call(void 0,
value,
(invalidPathPart) => {
invalidPath = invalidPathPart;
},
""
)) {
throw new (0, _chunk53LWTTEXcjs.InvalidStateType)({
path: path + (invalidPath ? `.${invalidPath}` : "")
});
}
this.#persistChanged = true;
this.inspector.emitter.emit("stateUpdated", this.#persist.s);
if (this.#config.onStateChange && this.#ready) {
try {
this.#config.onStateChange(this.actorContext, this.#persistRaw.s);
} catch (error) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("error in `_onStateChange`", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error)
});
}
}
},
{ ignoreDetached: true }
);
}
async #initialize() {
const persistDataBuffer = await this.#actorDriver.readPersistedData(
this.#actorId
);
_invariant2.default.call(void 0,
persistDataBuffer !== void 0,
"persist data has not been set, it should be set when initialized"
);
const persistData = cbor.decode(persistDataBuffer);
if (persistData.hi) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).info("actor restoring", {
connections: persistData.c.length
});
this.#setPersist(persistData);
for (const connPersist of this.#persist.c) {
const driver = this.__getConnDriver(connPersist.d);
const conn = new (0, _chunkOBXZ7YJ7cjs.Conn)(
this,
connPersist,
driver,
this.#connStateEnabled
);
this.#connections.set(conn.id, conn);
for (const sub of connPersist.su) {
this.#addSubscription(sub.n, conn, true);
}
}
} else {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).info("actor creating");
let stateData;
if (this.stateEnabled) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).info("actor state initializing");
if ("createState" in this.#config) {
this.#config.createState;
stateData = await this.#config.createState(
this.actorContext,
persistData.i
);
} else if ("state" in this.#config) {
stateData = structuredClone(this.#config.state);
} else {
throw new Error("Both 'createState' or 'state' were not defined");
}
} else {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("state not enabled");
}
persistData.s = stateData;
persistData.hi = true;
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("writing state");
await this.#actorDriver.writePersistedData(
this.#actorId,
cbor.encode(persistData)
);
this.#setPersist(persistData);
if (this.#config.onCreate) {
await this.#config.onCreate(this.actorContext, persistData.i);
}
}
}
__getConnForId(id) {
return this.#connections.get(id);
}
/**
* Removes a connection and cleans up its resources.
*/
__removeConn(conn) {
if (!conn) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).warn("`conn` does not exist");
return;
}
const connIdx = this.#persist.c.findIndex((c) => c.i === conn.id);
if (connIdx !== -1) {
this.#persist.c.splice(connIdx, 1);
this.saveState({ immediate: true });
} else {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).warn("could not find persisted connection to remove", {
connId: conn.id
});
}
this.#connections.delete(conn.id);
for (const eventName of [...conn.subscriptions.values()]) {
this.#removeSubscription(eventName, conn, true);
}
this.inspector.emitter.emit("connectionUpdated");
if (this.#config.onDisconnect) {
try {
const result = this.#config.onDisconnect(this.actorContext, conn);
if (result instanceof Promise) {
result.catch((error) => {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("error in `onDisconnect`", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error)
});
});
}
} catch (error) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("error in `onDisconnect`", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error)
});
}
}
}
async prepareConn(params, request) {
let connState;
const onBeforeConnectOpts = {
request
};
if (this.#config.onBeforeConnect) {
await this.#config.onBeforeConnect(
this.actorContext,
onBeforeConnectOpts,
params
);
}
if (this.#connStateEnabled) {
if ("createConnState" in this.#config) {
const dataOrPromise = this.#config.createConnState(
this.actorContext,
onBeforeConnectOpts,
params
);
if (dataOrPromise instanceof Promise) {
connState = await _chunkOBXZ7YJ7cjs.deadline.call(void 0,
dataOrPromise,
this.#config.options.lifecycle.createConnStateTimeout
);
} else {
connState = dataOrPromise;
}
} else if ("connState" in this.#config) {
connState = structuredClone(this.#config.connState);
} else {
throw new Error(
"Could not create connection state from 'createConnState' or 'connState'"
);
}
}
return connState;
}
__getConnDriver(driverId) {
const driver = this.#connectionDrivers[driverId];
if (!driver) throw new Error(`No connection driver: ${driverId}`);
return driver;
}
/**
* Called after establishing a connection handshake.
*/
async createConn(connectionId, connectionToken, params, state, driverId, driverState, authData) {
this.#assertReady();
if (this.#connections.has(connectionId)) {
throw new Error(`Connection already exists: ${connectionId}`);
}
const driver = this.__getConnDriver(driverId);
const persist = {
i: connectionId,
t: connectionToken,
d: driverId,
ds: driverState,
p: params,
s: state,
a: authData,
su: []
};
const conn = new (0, _chunkOBXZ7YJ7cjs.Conn)(
this,
persist,
driver,
this.#connStateEnabled
);
this.#connections.set(conn.id, conn);
this.#persist.c.push(persist);
this.saveState({ immediate: true });
if (this.#config.onConnect) {
try {
const result = this.#config.onConnect(this.actorContext, conn);
if (result instanceof Promise) {
_chunkOBXZ7YJ7cjs.deadline.call(void 0,
result,
this.#config.options.lifecycle.onConnectTimeout
).catch((error) => {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("error in `onConnect`, closing socket", {
error
});
conn == null ? void 0 : conn.disconnect("`onConnect` failed");
});
}
} catch (error) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("error in `onConnect`", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error)
});
conn == null ? void 0 : conn.disconnect("`onConnect` failed");
}
}
this.inspector.emitter.emit("connectionUpdated");
conn._sendMessage(
new (0, _chunkOBXZ7YJ7cjs.CachedSerializer)({
b: {
i: {
ai: this.id,
ci: conn.id,
ct: conn._token
}
}
})
);
return conn;
}
// MARK: Messages
async processMessage(message, conn) {
await _chunkOBXZ7YJ7cjs.processMessage.call(void 0, message, this, conn, {
onExecuteAction: async (ctx, name, args) => {
this.inspector.emitter.emit("eventFired", {
type: "action",
name,
args,
connId: conn.id
});
return await this.executeAction(ctx, name, args);
},
onSubscribe: async (eventName, conn2) => {
this.inspector.emitter.emit("eventFired", {
type: "subscribe",
eventName,
connId: conn2.id
});
this.#addSubscription(eventName, conn2, false);
},
onUnsubscribe: async (eventName, conn2) => {
this.inspector.emitter.emit("eventFired", {
type: "unsubscribe",
eventName,
connId: conn2.id
});
this.#removeSubscription(eventName, conn2, false);
}
});
}
// MARK: Events
#addSubscription(eventName, connection, fromPersist) {
if (connection.subscriptions.has(eventName)) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("connection already has subscription", { eventName });
return;
}
if (!fromPersist) {
connection.__persist.su.push({ n: eventName });
this.saveState({ immediate: true });
}
connection.subscriptions.add(eventName);
let subscribers = this.#subscriptionIndex.get(eventName);
if (!subscribers) {
subscribers = /* @__PURE__ */ new Set();
this.#subscriptionIndex.set(eventName, subscribers);
}
subscribers.add(connection);
}
#removeSubscription(eventName, connection, fromRemoveConn) {
if (!connection.subscriptions.has(eventName)) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).warn("connection does not have subscription", { eventName });
return;
}
if (!fromRemoveConn) {
connection.subscriptions.delete(eventName);
const subIdx = connection.__persist.su.findIndex(
(s) => s.n === eventName
);
if (subIdx !== -1) {
connection.__persist.su.splice(subIdx, 1);
} else {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).warn("subscription does not exist with name", { eventName });
}
this.saveState({ immediate: true });
}
const subscribers = this.#subscriptionIndex.get(eventName);
if (subscribers) {
subscribers.delete(connection);
if (subscribers.size === 0) {
this.#subscriptionIndex.delete(eventName);
}
}
}
#assertReady() {
if (!this.#ready) throw new (0, _chunk53LWTTEXcjs.InternalError)("Actor not ready");
}
/**
* Check if the actor is ready to handle requests.
*/
isReady() {
return this.#ready;
}
/**
* Execute an action call from a client.
*
* This method handles:
* 1. Validating the action name
* 2. Executing the action function
* 3. Processing the result through onBeforeActionResponse (if configured)
* 4. Handling timeouts and errors
* 5. Saving state changes
*
* @param ctx The action context
* @param actionName The name of the action being called
* @param args The arguments passed to the action
* @returns The result of the action call
* @throws {ActionNotFound} If the action doesn't exist
* @throws {ActionTimedOut} If the action times out
* @internal
*/
async executeAction(ctx, actionName, args) {
_invariant2.default.call(void 0, this.#ready, "exucuting action before ready");
if (!(actionName in this.#config.actions)) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).warn("action does not exist", { actionName });
throw new (0, _chunk53LWTTEXcjs.ActionNotFound)(actionName);
}
const actionFunction = this.#config.actions[actionName];
if (typeof actionFunction !== "function") {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).warn("action is not a function", {
actionName,
type: typeof actionFunction
});
throw new (0, _chunk53LWTTEXcjs.ActionNotFound)(actionName);
}
try {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("executing action", { actionName, args });
const outputOrPromise = actionFunction.call(void 0, ctx, ...args);
let output;
if (outputOrPromise instanceof Promise) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("awaiting async action", { actionName });
output = await _chunkOBXZ7YJ7cjs.deadline.call(void 0,
outputOrPromise,
this.#config.options.action.timeout
);
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("async action completed", { actionName });
} else {
output = outputOrPromise;
}
if (this.#config.onBeforeActionResponse) {
try {
const processedOutput = this.#config.onBeforeActionResponse(
this.actorContext,
actionName,
args,
output
);
if (processedOutput instanceof Promise) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("awaiting onBeforeActionResponse", {
actionName
});
output = await processedOutput;
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("onBeforeActionResponse completed", {
actionName
});
} else {
output = processedOutput;
}
} catch (error) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("error in `onBeforeActionResponse`", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error)
});
}
}
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("action completed", {
actionName,
outputType: typeof output,
isPromise: output instanceof Promise
});
return output;
} catch (error) {
if (error instanceof _chunkOBXZ7YJ7cjs.DeadlineError) {
throw new (0, _chunk53LWTTEXcjs.ActionTimedOut)();
}
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("action error", {
actionName,
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error)
});
throw error;
} finally {
this.#savePersistThrottled();
}
}
/**
* Returns a list of action methods available on this actor.
*/
get actions() {
return Object.keys(this.#config.actions);
}
/**
* Handles raw HTTP requests to the actor.
*/
async handleFetch(request, opts) {
this.#assertReady();
if (!this.#config.onFetch) {
throw new (0, _chunk53LWTTEXcjs.FetchHandlerNotDefined)();
}
try {
const response = await this.#config.onFetch(
this.actorContext,
request,
opts
);
if (!response) {
throw new (0, _chunk53LWTTEXcjs.InvalidFetchResponse)();
}
return response;
} catch (error) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("onFetch error", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error)
});
throw error;
} finally {
this.#savePersistThrottled();
}
}
/**
* Handles raw WebSocket connections to the actor.
*/
async handleWebSocket(websocket, opts) {
this.#assertReady();
if (!this.#config.onWebSocket) {
throw new (0, _chunk53LWTTEXcjs.InternalError)("onWebSocket handler not defined");
}
try {
const stateBeforeHandler = this.#persistChanged;
await this.#config.onWebSocket(this.actorContext, websocket, opts);
if (this.#persistChanged && !stateBeforeHandler) {
await this.saveState({ immediate: true });
}
} catch (error) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("onWebSocket error", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error)
});
throw error;
} finally {
this.#savePersistThrottled();
}
}
// MARK: Lifecycle hooks
// MARK: Exposed methods
/**
* Gets the logger instance.
*/
get log() {
return _chunkOBXZ7YJ7cjs.instanceLogger.call(void 0, );
}
/**
* Gets the name.
*/
get name() {
return this.#name;
}
/**
* Gets the key.
*/
get key() {
return this.#key;
}
/**
* Gets the region.
*/
get region() {
return this.#region;
}
/**
* Gets the scheduler.
*/
get schedule() {
return this.#schedule;
}
/**
* Gets the map of connections.
*/
get conns() {
return this.#connections;
}
/**
* Gets the current state.
*
* Changing properties of this value will automatically be persisted.
*/
get state() {
this.#validateStateEnabled();
return this.#persist.s;
}
/**
* Gets the database.
* @experimental
* @throws {DatabaseNotEnabled} If the database is not enabled.
*/
get db() {
if (!this.#db) {
throw new (0, _chunk53LWTTEXcjs.DatabaseNotEnabled)();
}
return this.#db;
}
/**
* Sets the current state.
*
* This property will automatically be persisted.
*/
set state(value) {
this.#validateStateEnabled();
this.#persist.s = value;
}
get vars() {
this.#validateVarsEnabled();
_invariant2.default.call(void 0, this.#vars !== void 0, "vars not enabled");
return this.#vars;
}
/**
* Broadcasts an event to all connected clients.
* @param name - The name of the event.
* @param args - The arguments to send with the event.
*/
_broadcast(name, ...args) {
this.#assertReady();
this.inspector.emitter.emit("eventFired", {
type: "broadcast",
eventName: name,
args
});
const subscriptions = this.#subscriptionIndex.get(name);
if (!subscriptions) return;
const toClientSerializer = new (0, _chunkOBXZ7YJ7cjs.CachedSerializer)({
b: {
ev: {
n: name,
a: args
}
}
});
for (const connection of subscriptions) {
connection._sendMessage(toClientSerializer);
}
}
/**
* Runs a promise in the background.
*
* This allows the actor runtime to ensure that a promise completes while
* returning from an action request early.
*
* @param promise - The promise to run in the background.
*/
_runInBackground(promise) {
this.#assertReady();
const nonfailablePromise = promise.then(() => {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("background promise complete");
}).catch((error) => {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).error("background promise failed", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error)
});
});
this.#backgroundPromises.push(nonfailablePromise);
}
/**
* Forces the state to get saved.
*
* This is helpful if running a long task that may fail later or when
* running a background job that updates the state.
*
* @param opts - Options for saving the state.
*/
async saveState(opts) {
this.#assertReady();
if (this.#persistChanged) {
if (opts.immediate) {
await this.#savePersistInner();
} else {
if (!this.#onPersistSavedPromise) {
this.#onPersistSavedPromise = Promise.withResolvers();
}
this.#savePersistThrottled();
await this.#onPersistSavedPromise.promise;
}
}
}
async stop() {
if (this.isStopping) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).warn("already stopping actor");
return;
}
this.isStopping = true;
await this.saveState({ immediate: true });
const promises = [];
for (const connection of this.#connections.values()) {
promises.push(connection.disconnect());
}
const res = Promise.race([
Promise.all(promises).then(() => false),
new Promise(
(res2) => globalThis.setTimeout(() => res2(true), 1500)
)
]);
if (await res) {
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).warn(
"timed out waiting for connections to close, shutting down anyway"
);
}
}
}, _class2);
// src/actor/definition.ts
var ActorDefinition = class {
#config;
constructor(config) {
this.#config = config;
}
get config() {
return this.#config;
}
instantiate() {
return new ActorInstance(this.#config);
}
};
function lookupInRegistry(registryConfig, name) {
const definition = registryConfig.use[name];
if (!definition) throw new Error(`no actor in registry for name ${name}`);
return definition;
}
// src/client/errors.ts
var ActorClientError = class extends Error {
};
var InternalError2 = class extends ActorClientError {
};
var ManagerError = class extends ActorClientError {
constructor(error, opts) {
super(`Manager error: ${error}`, opts);
}
};
var MalformedResponseMessage = class extends ActorClientError {
constructor(cause) {
super(`Malformed response message: ${cause}`, { cause });
}
};
var ActorError = (_class3 = class extends ActorClientError {
constructor(code, message, metadata) {
super(message);_class3.prototype.__init3.call(this);;
this.code = code;
this.metadata = metadata;
}
__init3() {this.__type = "ActorError"}
}, _class3);
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.");
}
};
// src/client/actor-conn.ts
var _pretry = require('p-retry'); var _pretry2 = _interopRequireDefault(_pretry);
// src/client/actor-handle.ts
// src/client/raw-utils.ts
async function rawHttpFetch(driver, actorQuery, params, input, init) {
let path;
let mergedInit = init || {};
if (typeof input === "string") {
path = input;
} else if (input instanceof URL) {
path = input.pathname + input.search;
} else if (input instanceof Request) {
const url = new URL(input.url);
path = 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,
path,
mergedInit,
void 0
);
}
async function rawWebSocket(driver, actorQuery, params, path, protocols) {
return await driver.rawWebSocket(
void 0,
actorQuery,
// Force JSON so it's readable by the user
"json",
params,
path || "",
protocols,
void 0
);
}
// src/client/actor-handle.ts
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() {
_chunkON577KNDcjs.logger.call(void 0, ).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(path, protocols) {
return rawWebSocket(
this.#driver,
this.#actorQuery,
this.#params,
path,
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.default.call(void 0, false, "actorQuery cannot be create");
} else {
_chunkOBXZ7YJ7cjs.assertUnreachable.call(void 0, this.#actorQuery);
}
}
};
// src/client/client.ts
var ACTOR_CONNS_SYMBOL = Symbol("actorConns");
var CREATE_ACTOR_CONN_PROXY = Symbol("createActorConnProxy");
var TRANSPORT_SYMBOL = Symbol("transport");
var ClientRaw = (_class4 = class {
#disposed = false;
__init4() {this[ACTOR_CONNS_SYMBOL] = /* @__PURE__ */ new Set()}
#driver;
#encodingKind;
/**
* 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) {;_class4.prototype.__init4.call(this);
this.#driver = driver;
this.#encodingKind = _nullishCoalesce((opts == null ? void 0 : opts.encoding), () => ( "cbor"));
this[TRANSPORT_SYMBOL] = _nullishCoalesce((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) {
_chunkON577KNDcjs.logger.call(void 0, ).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 || [];
_chunkON577KNDcjs.logger.call(void 0, ).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 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 {GetOptions} [opts] - Options for getting the actor.
* @returns {ActorHandle<AD>} - A handle to the actor.
*/
getOrCreate(name, key, opts) {
const keyArray = typeof key === "string" ? [key] : key || [];
_chunkON577KNDcjs.logger.call(void 0, ).debug("get or create handle to actor", {
name,
key: keyArray,
parameters: opts == null ? void 0 : opts.params,
createInRegion: opts == null ? void 0 : opts.createInRegion
});
const actorQuery = {
getOrCreateForKey: {
name,
key: keyArray,
input: opts == null ? void 0 : opts.createWithInput,
region: opts == null ? void 0 : opts.createInRegion
}
};
const handle = this