@rivetkit/core
Version:
1,567 lines (1,469 loc) • 107 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 _chunkDSGTB57Jcjs = require('./chunk-DSGTB57J.cjs');
var _chunkOBXZ7YJ7cjs = require('./chunk-OBXZ7YJ7.cjs');
var _chunk4KRNEW7Dcjs = require('./chunk-4KRNEW7D.cjs');
var _chunkHIB3AS73cjs = require('./chunk-HIB3AS73.cjs');
var _chunk53LWTTEXcjs = require('./chunk-53LWTTEX.cjs');
// src/registry/run-config.ts
var _zod = require('zod');
// src/drivers/file-system/actor.ts
var FileSystemActorDriver = class {
#registryConfig;
#runConfig;
#managerDriver;
#inlineClient;
#state;
constructor(registryConfig, runConfig, managerDriver, inlineClient, state) {
this.#registryConfig = registryConfig;
this.#runConfig = runConfig;
this.#managerDriver = managerDriver;
this.#inlineClient = inlineClient;
this.#state = state;
}
async loadActor(actorId) {
return this.#state.startActor(
this.#registryConfig,
this.#runConfig,
this.#inlineClient,
this,
actorId
);
}
getGenericConnGlobalState(actorId) {
return this.#state.getActorOrError(actorId).genericConnGlobalState;
}
/**
* Get the current storage directory path
*/
get storagePath() {
return this.#state.storagePath;
}
getContext(_actorId) {
return {};
}
async readPersistedData(actorId) {
return (await this.#state.loadActorStateOrError(actorId)).persistedData;
}
async writePersistedData(actorId, data) {
const entry = await this.#state.loadActorStateOrError(actorId);
entry.persistedData = data;
await this.#state.writeActor(actorId);
}
async setAlarm(actor2, timestamp) {
const delay = Math.max(0, timestamp - Date.now());
setTimeout(() => {
actor2.onAlarm();
}, delay);
}
getDatabase(actorId) {
return this.#state.createDatabase(actorId);
}
};
// src/drivers/file-system/global-state.ts
var _crypto = require('crypto'); var crypto2 = _interopRequireWildcard(_crypto); var crypto = _interopRequireWildcard(_crypto);
var _fs = require('fs'); var fsSync2 = _interopRequireWildcard(_fs); var fsSync = _interopRequireWildcard(_fs);
var _promises = require('fs/promises'); var fs2 = _interopRequireWildcard(_promises); var fs = _interopRequireWildcard(_promises);
var _path = require('path'); var path2 = _interopRequireWildcard(_path); var path = _interopRequireWildcard(_path);
var _cborx = require('cbor-x'); var cbor = _interopRequireWildcard(_cborx); var cbor2 = _interopRequireWildcard(_cborx); var cbor3 = _interopRequireWildcard(_cborx);
var _invariant = require('invariant'); var _invariant2 = _interopRequireDefault(_invariant);
// src/drivers/file-system/log.ts
var LOGGER_NAME = "driver-fs";
function logger3() {
return _chunk4KRNEW7Dcjs.getLogger.call(void 0, LOGGER_NAME);
}
// src/drivers/file-system/utils.ts
var _os = require('os'); var os = _interopRequireWildcard(_os);
function generateActorId(name, key) {
const jsonString = JSON.stringify([name, key]);
const hash = crypto.createHash("sha256").update(jsonString).digest("hex").substring(0, 16);
return hash;
}
function createHashForPath(dirPath) {
const normalizedPath = path.normalize(dirPath);
const lastComponent = path.basename(normalizedPath);
const hash = crypto.createHash("sha256").update(normalizedPath).digest("hex").substring(0, 8);
return `${lastComponent}-${hash}`;
}
function getStoragePath(customPath) {
const dataPath = getDataPath("rivetkit");
const pathToHash = customPath || process.cwd();
const dirHash = createHashForPath(pathToHash);
return path.join(dataPath, dirHash);
}
async function pathExists(path3) {
try {
await fs.access(path3);
return true;
} catch (e2) {
return false;
}
}
async function ensureDirectoryExists(directoryPath) {
if (!await pathExists(directoryPath)) {
await fs.mkdir(directoryPath, { recursive: true });
}
}
function ensureDirectoryExistsSync(directoryPath) {
if (!fsSync.existsSync(directoryPath)) {
fsSync.mkdirSync(directoryPath, { recursive: true });
}
}
function getDataPath(appName) {
const platform = process.platform;
const homeDir = os.homedir();
switch (platform) {
case "win32":
return path.join(
process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"),
appName
);
case "darwin":
return path.join(homeDir, "Library", "Application Support", appName);
default:
return path.join(
process.env.XDG_DATA_HOME || path.join(homeDir, ".local", "share"),
appName
);
}
}
// src/drivers/file-system/global-state.ts
var FileSystemGlobalState = class {
#storagePath;
#stateDir;
#dbsDir;
#persist;
#actors = /* @__PURE__ */ new Map();
#actorCountOnStartup = 0;
get storagePath() {
return this.#storagePath;
}
get actorCountOnStartup() {
return this.#actorCountOnStartup;
}
constructor(persist = true, customPath) {
this.#persist = persist;
this.#storagePath = persist ? getStoragePath(customPath) : "/tmp";
this.#stateDir = path2.join(this.#storagePath, "state");
this.#dbsDir = path2.join(this.#storagePath, "databases");
if (this.#persist) {
ensureDirectoryExistsSync(this.#stateDir);
ensureDirectoryExistsSync(this.#dbsDir);
try {
const actorIds = fsSync2.readdirSync(this.#stateDir);
this.#actorCountOnStartup = actorIds.length;
} catch (error) {
logger3().error("failed to count actors", { error });
}
logger3().debug("file system driver ready", {
dir: this.#storagePath,
actorCount: this.#actorCountOnStartup
});
try {
this.#cleanupTempFilesSync();
} catch (err) {
logger3().error("failed to cleanup temp files", { error: err });
}
} else {
logger3().debug("memory driver ready");
}
}
getActorStatePath(actorId) {
return path2.join(this.#stateDir, actorId);
}
getActorDbPath(actorId) {
return path2.join(this.#dbsDir, `${actorId}.db`);
}
async *getActorsIterator(params) {
const actorIds = fsSync2.readdirSync(this.#stateDir).filter((id) => !id.includes(".tmp")).sort();
const startIndex = params.cursor ? actorIds.indexOf(params.cursor) + 1 : 0;
for (let i = startIndex; i < actorIds.length; i++) {
const actorId = actorIds[i];
if (!actorId) {
continue;
}
try {
const state = await this.loadActorStateOrError(actorId);
yield state;
} catch (error) {
logger3().error("failed to load actor state", { actorId, error });
}
}
}
/**
* Ensures an entry exists for this actor.
*
* Used for #createActor and #loadActor.
*/
#upsertEntry(actorId) {
let entry = this.#actors.get(actorId);
if (entry) {
return entry;
}
entry = {
id: actorId,
genericConnGlobalState: new (0, _chunkOBXZ7YJ7cjs.GenericConnGlobalState)()
};
this.#actors.set(actorId, entry);
return entry;
}
/**
* Creates a new actor and writes to file system.
*/
async createActor(actorId, name, key, input) {
if (this.#actors.has(actorId)) {
throw new (0, _chunk53LWTTEXcjs.ActorAlreadyExists)(name, key);
}
const entry = this.#upsertEntry(actorId);
entry.state = {
id: actorId,
name,
key,
persistedData: serializeEmptyPersistData(input)
};
await this.writeActor(actorId);
return entry;
}
/**
* Loads the actor from disk or returns the existing actor entry. This will return an entry even if the actor does not actually exist.
*/
async loadActor(actorId) {
const entry = this.#upsertEntry(actorId);
if (entry.state) {
return entry;
}
if (!this.#persist) {
return entry;
}
if (entry.loadPromise) {
await entry.loadPromise;
return entry;
}
entry.loadPromise = this.loadActorState(entry);
return entry.loadPromise;
}
async loadActorState(entry) {
const stateFilePath = this.getActorStatePath(entry.id);
try {
const stateData = await fs2.readFile(stateFilePath);
const state = cbor.decode(stateData);
const stats = await fs2.stat(stateFilePath);
state.createdAt = stats.birthtime;
entry.state = state;
return entry;
} catch (innerError) {
if (innerError.code === "ENOENT") {
entry.loadPromise = void 0;
return entry;
}
const error = new Error(`Failed to load actor state: ${innerError}`);
throw error;
}
}
async loadOrCreateActor(actorId, name, key, input) {
const entry = await this.loadActor(actorId);
if (!entry.state) {
entry.state = {
id: actorId,
name,
key,
persistedData: serializeEmptyPersistData(input)
};
await this.writeActor(actorId);
}
return entry;
}
/**
* Save actor state to disk
*/
async writeActor(actorId) {
if (!this.#persist) {
return;
}
const entry = this.#actors.get(actorId);
_invariant2.default.call(void 0, entry == null ? void 0 : entry.state, "missing actor state");
const state = entry.state;
const currentWrite = entry.writePromise || Promise.resolve();
const newWrite = currentWrite.then(() => this.#performWrite(actorId, state)).catch((err) => {
logger3().error("write failed", { actorId, error: err });
throw err;
});
entry.writePromise = newWrite;
try {
await newWrite;
} finally {
if (entry.writePromise === newWrite) {
entry.writePromise = void 0;
}
}
}
/**
* Perform the actual write operation with atomic writes
*/
async #performWrite(actorId, state) {
const dataPath = this.getActorStatePath(actorId);
const tempPath = `${dataPath}.tmp.${crypto2.randomUUID()}`;
try {
await ensureDirectoryExists(path2.dirname(dataPath));
const serializedState = cbor.encode(state);
await fs2.writeFile(tempPath, serializedState);
await fs2.rename(tempPath, dataPath);
} catch (error) {
try {
await fs2.unlink(tempPath);
} catch (e3) {
}
logger3().error("failed to save actor state", { actorId, error });
throw new Error(`Failed to save actor state: ${error}`);
}
}
async startActor(registryConfig, runConfig, inlineClient, actorDriver, actorId) {
var _a;
const entry = await this.loadActor(actorId);
if (!entry.state) {
throw new Error(`Actor does exist and cannot be started: ${actorId}`);
}
if (entry.startPromise) {
await entry.startPromise.promise;
_invariant2.default.call(void 0, entry.actor, "actor should have loaded");
return entry.actor;
}
if (entry.actor) {
return entry.actor;
}
entry.startPromise = Promise.withResolvers();
try {
const definition = _chunkDSGTB57Jcjs.lookupInRegistry.call(void 0, registryConfig, entry.state.name);
entry.actor = definition.instantiate();
const connDrivers = _chunkOBXZ7YJ7cjs.createGenericConnDrivers.call(void 0,
entry.genericConnGlobalState
);
await entry.actor.start(
connDrivers,
actorDriver,
inlineClient,
actorId,
entry.state.name,
entry.state.key,
"unknown"
);
entry.startPromise.resolve();
entry.startPromise = void 0;
return entry.actor;
} catch (innerError) {
const error = new Error(
`Failed to start actor ${actorId}: ${innerError}`
);
(_a = entry.startPromise) == null ? void 0 : _a.reject(error);
entry.startPromise = void 0;
throw error;
}
}
async loadActorStateOrError(actorId) {
const state = (await this.loadActor(actorId)).state;
if (!state) throw new Error(`Actor does not exist: ${actorId}`);
return state;
}
getActorOrError(actorId) {
const entry = this.#actors.get(actorId);
if (!entry) throw new Error(`No entry for actor: ${actorId}`);
return entry;
}
async createDatabase(actorId) {
return this.getActorDbPath(actorId);
}
getOrCreateInspectorAccessToken() {
const tokenPath = path2.join(this.#storagePath, "inspector-token");
if (fsSync2.existsSync(tokenPath)) {
return fsSync2.readFileSync(tokenPath, "utf-8");
}
const newToken = _chunkOBXZ7YJ7cjs.generateRandomString.call(void 0, );
fsSync2.writeFileSync(tokenPath, newToken);
return newToken;
}
/**
* Cleanup stale temp files on startup (synchronous)
*/
#cleanupTempFilesSync() {
try {
const files = fsSync2.readdirSync(this.#stateDir);
const tempFiles = files.filter((f) => f.includes(".tmp."));
const oneHourAgo = Date.now() - 36e5;
for (const tempFile of tempFiles) {
try {
const fullPath = path2.join(this.#stateDir, tempFile);
const stat2 = fsSync2.statSync(fullPath);
if (stat2.mtimeMs < oneHourAgo) {
fsSync2.unlinkSync(fullPath);
logger3().info("cleaned up stale temp file", { file: tempFile });
}
} catch (err) {
logger3().debug("failed to cleanup temp file", {
file: tempFile,
error: err
});
}
}
} catch (err) {
logger3().error("failed to read actors directory for cleanup", {
error: err
});
}
}
};
// src/drivers/file-system/manager.ts
// src/actor/router.ts
var _hono = require('hono');
// src/common/router.ts
function logger4() {
return _chunk4KRNEW7Dcjs.getLogger.call(void 0, "router");
}
function loggerMiddleware(logger8) {
return async (c, next) => {
const method = c.req.method;
const path3 = c.req.path;
const startTime = Date.now();
await next();
const duration = Date.now() - startTime;
logger8.debug("http request", {
method,
path: path3,
status: c.res.status,
dt: `${duration}ms`,
reqSize: c.req.header("content-length"),
resSize: c.res.headers.get("content-length"),
userAgent: c.req.header("user-agent")
});
};
}
function handleRouteNotFound(c) {
return c.text("Not Found (RivetKit)", 404);
}
function handleRouteError(opts, error, c) {
const exposeInternalError = opts.enableExposeInternalError && _chunkOBXZ7YJ7cjs.getRequestExposeInternalError.call(void 0, c.req);
const { statusCode, code, message, metadata } = _chunkHIB3AS73cjs.deconstructError.call(void 0,
error,
logger4(),
{
method: c.req.method,
path: c.req.path
},
exposeInternalError
);
let encoding;
try {
encoding = _chunkOBXZ7YJ7cjs.getRequestEncoding.call(void 0, c.req);
} catch (err) {
logger4().debug("failed to extract encoding", {
error: _chunkHIB3AS73cjs.stringifyError.call(void 0, err)
});
encoding = "json";
}
const output = _chunkOBXZ7YJ7cjs.serialize.call(void 0,
{
c: code,
m: message,
md: metadata
},
encoding
);
return c.body(output, { status: statusCode });
}
// src/inspector/utils.ts
var _factory = require('hono/factory');
// src/inspector/log.ts
function inspectorLogger() {
return _chunk4KRNEW7Dcjs.getLogger.call(void 0, "inspector");
}
// src/inspector/utils.ts
function compareSecrets(providedSecret, validSecret) {
if (providedSecret.length !== validSecret.length) {
return false;
}
const encoder = new TextEncoder();
const a = encoder.encode(providedSecret);
const b = encoder.encode(validSecret);
if (a.byteLength !== b.byteLength) {
return false;
}
if (!crypto2.default.timingSafeEqual(a, b)) {
return false;
}
return true;
}
var secureInspector = (runConfig) => _factory.createMiddleware.call(void 0, async (c, next) => {
var _a, _b, _c;
if (!runConfig.studio.enabled) {
return c.text("Inspector is not enabled", 503);
}
const userToken = (_a = c.req.header("Authorization")) == null ? void 0 : _a.replace("Bearer ", "");
if (!userToken) {
return c.text("Unauthorized", 401);
}
const inspectorToken = (_c = (_b = runConfig.studio).token) == null ? void 0 : _c.call(_b);
if (!inspectorToken) {
return c.text("Unauthorized", 401);
}
const isValid = compareSecrets(userToken, inspectorToken);
if (!isValid) {
return c.text("Unauthorized", 401);
}
await next();
});
function getStudioUrl(runConfig) {
var _a, _b, _c, _d;
if (!((_a = runConfig == null ? void 0 : runConfig.studio) == null ? void 0 : _a.enabled)) {
return "disabled";
}
const accessToken = (_c = (_b = runConfig == null ? void 0 : runConfig.studio) == null ? void 0 : _b.token) == null ? void 0 : _c.call(_b);
if (!accessToken) {
inspectorLogger().warn(
"Studio Token is not set, but Studio is enabled. Please set it in the run configuration `inspector.token` or via `RIVETKIT_STUDIO_TOKEN` environment variable. Studio will not be accessible."
);
return "disabled";
}
const url = new URL("https://studio.rivet.gg");
url.searchParams.set("t", accessToken);
if ((_d = runConfig == null ? void 0 : runConfig.studio) == null ? void 0 : _d.defaultEndpoint) {
url.searchParams.set("u", runConfig.studio.defaultEndpoint);
}
return url.href;
}
// src/actor/router.ts
var PATH_CONNECT_WEBSOCKET = "/connect/websocket";
var PATH_RAW_WEBSOCKET_PREFIX = "/raw/websocket/";
function createActorRouter(runConfig, actorDriver) {
const router = new (0, _hono.Hono)({ strict: false });
router.use("*", loggerMiddleware(_chunkOBXZ7YJ7cjs.logger.call(void 0, )));
router.get("/", (c) => {
return c.text(
"This is an RivetKit actor.\n\nLearn more at https://rivetkit.org"
);
});
router.get("/health", (c) => {
return c.text("ok");
});
router.get(PATH_CONNECT_WEBSOCKET, async (c) => {
var _a;
const upgradeWebSocket = (_a = runConfig.getUpgradeWebSocket) == null ? void 0 : _a.call(runConfig);
if (upgradeWebSocket) {
return upgradeWebSocket(async (c2) => {
const encodingRaw = c2.req.header(_chunkOBXZ7YJ7cjs.HEADER_ENCODING);
const connParamsRaw = c2.req.header(_chunkOBXZ7YJ7cjs.HEADER_CONN_PARAMS);
const authDataRaw = c2.req.header(_chunkOBXZ7YJ7cjs.HEADER_AUTH_DATA);
const encoding = _chunkOBXZ7YJ7cjs.EncodingSchema.parse(encodingRaw);
const connParams = connParamsRaw ? JSON.parse(connParamsRaw) : void 0;
const authData = authDataRaw ? JSON.parse(authDataRaw) : void 0;
return await _chunkOBXZ7YJ7cjs.handleWebSocketConnect.call(void 0,
c2,
runConfig,
actorDriver,
c2.env.actorId,
encoding,
connParams,
authData
);
})(c, _chunkHIB3AS73cjs.noopNext.call(void 0, ));
} else {
return c.text(
"WebSockets are not enabled for this driver. Use SSE instead.",
400
);
}
});
router.get("/connect/sse", async (c) => {
const authDataRaw = c.req.header(_chunkOBXZ7YJ7cjs.HEADER_AUTH_DATA);
let authData;
if (authDataRaw) {
authData = JSON.parse(authDataRaw);
}
return _chunkOBXZ7YJ7cjs.handleSseConnect.call(void 0, c, runConfig, actorDriver, c.env.actorId, authData);
});
router.post("/action/:action", async (c) => {
const actionName = c.req.param("action");
const authDataRaw = c.req.header(_chunkOBXZ7YJ7cjs.HEADER_AUTH_DATA);
let authData;
if (authDataRaw) {
authData = JSON.parse(authDataRaw);
}
return _chunkOBXZ7YJ7cjs.handleAction.call(void 0,
c,
runConfig,
actorDriver,
actionName,
c.env.actorId,
authData
);
});
router.post("/connections/message", async (c) => {
const connId = c.req.header(_chunkOBXZ7YJ7cjs.HEADER_CONN_ID);
const connToken = c.req.header(_chunkOBXZ7YJ7cjs.HEADER_CONN_TOKEN);
if (!connId || !connToken) {
throw new Error("Missing required parameters");
}
return _chunkOBXZ7YJ7cjs.handleConnectionMessage.call(void 0,
c,
runConfig,
actorDriver,
connId,
connToken,
c.env.actorId
);
});
router.all("/raw/http/*", async (c) => {
const authDataRaw = c.req.header(_chunkOBXZ7YJ7cjs.HEADER_AUTH_DATA);
let authData;
if (authDataRaw) {
authData = JSON.parse(authDataRaw);
}
const actor2 = await actorDriver.loadActor(c.env.actorId);
const url = new URL(c.req.url);
const originalPath = url.pathname.replace(/^\/raw\/http/, "") || "/";
const correctedUrl = new URL(originalPath + url.search, url.origin);
const correctedRequest = new Request(correctedUrl, {
method: c.req.method,
headers: c.req.raw.headers,
body: c.req.raw.body
});
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("rewriting http url", {
from: c.req.url,
to: correctedRequest.url
});
const response = await actor2.handleFetch(correctedRequest, {
auth: authData
});
if (!response) {
throw new (0, _chunk53LWTTEXcjs.InternalError)("handleFetch returned void unexpectedly");
}
return response;
});
router.get(`${PATH_RAW_WEBSOCKET_PREFIX}*`, async (c) => {
var _a;
const upgradeWebSocket = (_a = runConfig.getUpgradeWebSocket) == null ? void 0 : _a.call(runConfig);
if (upgradeWebSocket) {
return upgradeWebSocket(async (c2) => {
const encodingRaw = c2.req.header(_chunkOBXZ7YJ7cjs.HEADER_ENCODING);
const connParamsRaw = c2.req.header(_chunkOBXZ7YJ7cjs.HEADER_CONN_PARAMS);
const authDataRaw = c2.req.header(_chunkOBXZ7YJ7cjs.HEADER_AUTH_DATA);
const encoding = _chunkOBXZ7YJ7cjs.EncodingSchema.parse(encodingRaw);
const connParams = connParamsRaw ? JSON.parse(connParamsRaw) : void 0;
const authData = authDataRaw ? JSON.parse(authDataRaw) : void 0;
const url = new URL(c2.req.url);
const pathWithQuery = c2.req.path + url.search;
_chunkOBXZ7YJ7cjs.logger.call(void 0, ).debug("actor router raw websocket", {
path: c2.req.path,
url: c2.req.url,
search: url.search,
pathWithQuery
});
return await _chunkOBXZ7YJ7cjs.handleRawWebSocketHandler.call(void 0,
c2,
pathWithQuery,
actorDriver,
c2.env.actorId,
authData
);
})(c, _chunkHIB3AS73cjs.noopNext.call(void 0, ));
} else {
return c.text(
"WebSockets are not enabled for this driver. Use SSE instead.",
400
);
}
});
if (runConfig.studio.enabled) {
router.route(
"/inspect",
new (0, _hono.Hono)().use(secureInspector(runConfig), async (c, next) => {
const inspector = (await actorDriver.loadActor(c.env.actorId)).inspector;
_invariant2.default.call(void 0, inspector, "inspector not supported on this platform");
c.set("inspector", inspector);
await next();
}).route("/", _chunkDSGTB57Jcjs.createActorInspectorRouter.call(void 0, ))
);
}
router.notFound(handleRouteNotFound);
router.onError(
handleRouteError.bind(void 0, {
// All headers to this endpoint are considered secure, so we can enable the expose internal error header for requests from the internal client
enableExposeInternalError: true
})
);
return router;
}
// src/common/inline-websocket-adapter2.ts
var _ws = require('hono/ws');
var LOGGER_NAME2 = "fake-event-source2";
function logger5() {
return _chunk4KRNEW7Dcjs.getLogger.call(void 0, LOGGER_NAME2);
}
var InlineWebSocketAdapter2 = (_class = class {
// WebSocket readyState values
__init() {this.CONNECTING = 0}
__init2() {this.OPEN = 1}
__init3() {this.CLOSING = 2}
__init4() {this.CLOSED = 3}
// Private properties
#handler;
#wsContext;
#readyState = 0;
// Start in CONNECTING state
#queuedMessages = [];
// Event buffering is needed since events can be fired
// before JavaScript has a chance to add event listeners (e.g. within the same tick)
#bufferedEvents = [];
// Event listeners with buffering
#eventListeners = /* @__PURE__ */ new Map();
constructor(handler) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);
this.#handler = handler;
this.#wsContext = new (0, _ws.WSContext)({
send: (data) => {
logger5().debug("WSContext.send called");
this.#handleMessage(data);
},
close: (code, reason) => {
logger5().debug("WSContext.close called", { code, reason });
this.#handleClose(code || 1e3, reason || "");
},
// Set readyState to 1 (OPEN) since handlers expect an open connection
readyState: 1
});
this.#initialize();
}
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) {
logger5().debug("send called", { readyState: this.readyState });
if (this.readyState !== this.OPEN) {
const error = new Error("WebSocket is not open");
logger5().warn("cannot send message, websocket not open", {
readyState: this.readyState,
dataType: typeof data,
dataLength: typeof data === "string" ? data.length : "binary",
error
});
this.#fireError(error);
return;
}
this.#handler.onMessage({ data }, this.#wsContext);
}
/**
* Closes the connection
*/
close(code = 1e3, reason = "") {
if (this.readyState === this.CLOSED || this.readyState === this.CLOSING) {
return;
}
logger5().debug("closing fake websocket", { code, reason });
this.#readyState = this.CLOSING;
try {
this.#handler.onClose({ code, reason, wasClean: true }, this.#wsContext);
} catch (err) {
logger5().error("error closing websocket", { error: err });
} finally {
this.#readyState = this.CLOSED;
const closeEvent = {
type: "close",
wasClean: code === 1e3,
code,
reason,
target: this,
currentTarget: this
};
this.#fireClose(closeEvent);
}
}
/**
* Initialize the connection with the handler
*/
async #initialize() {
try {
logger5().debug("fake websocket initializing");
logger5().debug("calling handler.onOpen with WSContext");
this.#handler.onOpen(void 0, this.#wsContext);
this.#readyState = this.OPEN;
logger5().debug("fake websocket initialized and now OPEN");
this.#fireOpen();
if (this.#queuedMessages.length > 0) {
if (this.readyState !== this.OPEN) {
logger5().warn("socket no longer open, dropping queued messages");
return;
}
logger5().debug(
`now processing ${this.#queuedMessages.length} queued messages`
);
const messagesToProcess = [...this.#queuedMessages];
this.#queuedMessages = [];
for (const message of messagesToProcess) {
logger5().debug("processing queued message");
this.#handleMessage(message);
}
}
} catch (err) {
logger5().error("error opening fake websocket", {
error: err,
errorMessage: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : void 0
});
this.#fireError(err);
this.close(1011, "Internal error during initialization");
}
}
/**
* Handle messages received from the server via the WSContext
*/
#handleMessage(data) {
if (this.readyState !== this.OPEN) {
logger5().debug("message received before socket is OPEN, queuing", {
readyState: this.readyState,
dataType: typeof data,
dataLength: typeof data === "string" ? data.length : data instanceof ArrayBuffer ? data.byteLength : data instanceof Uint8Array ? data.byteLength : "unknown"
});
this.#queuedMessages.push(data);
return;
}
logger5().debug("fake websocket received message from server", {
dataType: typeof data,
dataLength: typeof data === "string" ? data.length : data instanceof ArrayBuffer ? data.byteLength : data instanceof Uint8Array ? data.byteLength : "unknown"
});
const event = {
type: "message",
data,
target: this,
currentTarget: this
};
this.#dispatchEvent("message", event);
}
#handleClose(code, reason) {
if (this.readyState === this.CLOSED) return;
this.#readyState = this.CLOSED;
const event = {
type: "close",
code,
reason,
wasClean: code === 1e3,
target: this,
currentTarget: this
};
this.#dispatchEvent("close", event);
}
addEventListener(type, listener) {
if (!this.#eventListeners.has(type)) {
this.#eventListeners.set(type, []);
}
this.#eventListeners.get(type).push(listener);
this.#flushBufferedEvents(type);
}
removeEventListener(type, listener) {
const listeners = this.#eventListeners.get(type);
if (listeners) {
const index = listeners.indexOf(listener);
if (index !== -1) {
listeners.splice(index, 1);
}
}
}
#dispatchEvent(type, event) {
const listeners = this.#eventListeners.get(type);
if (listeners && listeners.length > 0) {
logger5().debug(
`dispatching ${type} event to ${listeners.length} listeners`
);
for (const listener of listeners) {
try {
listener(event);
} catch (err) {
logger5().error(`error in ${type} event listener`, { error: err });
}
}
} else {
logger5().debug(`no ${type} listeners registered, buffering event`);
this.#bufferedEvents.push({ type, event });
}
switch (type) {
case "open":
if (this.#onopen) {
try {
this.#onopen(event);
} catch (error) {
logger5().error("error in onopen handler", { error });
}
}
break;
case "close":
if (this.#onclose) {
try {
this.#onclose(event);
} catch (error) {
logger5().error("error in onclose handler", { error });
}
}
break;
case "error":
if (this.#onerror) {
try {
this.#onerror(event);
} catch (error) {
logger5().error("error in onerror handler", { error });
}
}
break;
case "message":
if (this.#onmessage) {
try {
this.#onmessage(event);
} catch (error) {
logger5().error("error in onmessage handler", { error });
}
}
break;
}
}
dispatchEvent(event) {
this.#dispatchEvent(event.type, event);
return true;
}
#flushBufferedEvents(type) {
const eventsToFlush = this.#bufferedEvents.filter(
(buffered) => buffered.type === type
);
this.#bufferedEvents = this.#bufferedEvents.filter(
(buffered) => buffered.type !== type
);
for (const { event } of eventsToFlush) {
this.#dispatchEvent(type, event);
}
}
#fireOpen() {
try {
const event = {
type: "open",
target: this,
currentTarget: this
};
this.#dispatchEvent("open", event);
} catch (err) {
logger5().error("error in open event", { error: err });
}
}
#fireClose(event) {
try {
this.#dispatchEvent("close", event);
} catch (err) {
logger5().error("error in close event", { error: err });
}
}
#fireError(error) {
try {
const event = {
type: "error",
target: this,
currentTarget: this,
error,
message: error instanceof Error ? error.message : String(error)
};
this.#dispatchEvent("error", event);
} catch (err) {
logger5().error("error in error event", { error: err });
}
logger5().error("websocket error", { error });
}
// 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;
}
}, _class);
// src/inline-client-driver/mod.ts
var _onchange = require('on-change'); var _onchange2 = _interopRequireDefault(_onchange);
// src/inline-client-driver/log.ts
var LOGGER_NAME3 = "inline-client-driver";
function logger6() {
return _chunk4KRNEW7Dcjs.getLogger.call(void 0, LOGGER_NAME3);
}
// src/inline-client-driver/mod.ts
function createInlineClientDriver(managerDriver) {
const driver = {
action: async (c, actorQuery, encoding, params, actionName, args, opts) => {
try {
const { actorId } = await queryActor(c, actorQuery, managerDriver);
logger6().debug("found actor for action", { actorId });
_invariant2.default.call(void 0, actorId, "Missing actor ID");
logger6().debug("handling action", { actionName, encoding });
const responseData = await _chunkDSGTB57Jcjs.sendHttpRequest.call(void 0, {
url: `http://actor/action/${encodeURIComponent(actionName)}`,
method: "POST",
headers: {
[_chunkOBXZ7YJ7cjs.HEADER_ENCODING]: encoding,
...params !== void 0 ? { [_chunkOBXZ7YJ7cjs.HEADER_CONN_PARAMS]: JSON.stringify(params) } : {},
[_chunkOBXZ7YJ7cjs.HEADER_EXPOSE_INTERNAL_ERROR]: "true"
},
body: { a: args },
encoding,
customFetch: managerDriver.sendRequest.bind(managerDriver, actorId),
signal: opts == null ? void 0 : opts.signal
});
return responseData.o;
} catch (err) {
const { code, message, metadata } = _chunkHIB3AS73cjs.deconstructError.call(void 0,
err,
logger6(),
{},
true
);
const x = new (0, _chunkDSGTB57Jcjs.ActorError)(code, message, metadata);
throw new (0, _chunkDSGTB57Jcjs.ActorError)(code, message, metadata);
}
},
resolveActorId: async (c, actorQuery, _encodingKind) => {
const { actorId } = await queryActor(c, actorQuery, managerDriver);
logger6().debug("resolved actor", { actorId });
_invariant2.default.call(void 0, actorId, "missing actor ID");
return actorId;
},
connectWebSocket: async (c, actorQuery, encodingKind, params) => {
const { actorId } = await queryActor(c, actorQuery, managerDriver);
logger6().debug("found actor for action", { actorId });
_invariant2.default.call(void 0, actorId, "Missing actor ID");
logger6().debug("opening websocket", { actorId, encoding: encodingKind });
const ws = await managerDriver.openWebSocket(
PATH_CONNECT_WEBSOCKET,
actorId,
encodingKind,
params
);
return ws;
},
connectSse: async (c, actorQuery, encodingKind, params) => {
const { actorId } = await queryActor(c, actorQuery, managerDriver);
logger6().debug("found actor for sse connection", { actorId });
_invariant2.default.call(void 0, actorId, "Missing actor ID");
logger6().debug("opening sse connection", {
actorId,
encoding: encodingKind
});
const EventSourceClass = await _chunkDSGTB57Jcjs.importEventSource.call(void 0, );
const eventSource = new EventSourceClass("http://actor/connect/sse", {
fetch: (input, init) => {
return fetch(input, {
...init,
headers: {
...init == null ? void 0 : init.headers,
"User-Agent": _chunkHIB3AS73cjs.httpUserAgent.call(void 0, ),
[_chunkOBXZ7YJ7cjs.HEADER_ENCODING]: encodingKind,
...params !== void 0 ? { [_chunkOBXZ7YJ7cjs.HEADER_CONN_PARAMS]: JSON.stringify(params) } : {},
[_chunkOBXZ7YJ7cjs.HEADER_EXPOSE_INTERNAL_ERROR]: "true"
}
});
}
});
return eventSource;
},
sendHttpMessage: async (c, actorId, encoding, connectionId, connectionToken, message) => {
logger6().debug("sending http message", { actorId, connectionId });
return _chunkDSGTB57Jcjs.sendHttpRequest.call(void 0, {
url: "http://actor/connections/message",
method: "POST",
headers: {
[_chunkOBXZ7YJ7cjs.HEADER_ENCODING]: encoding,
[_chunkOBXZ7YJ7cjs.HEADER_CONN_ID]: connectionId,
[_chunkOBXZ7YJ7cjs.HEADER_CONN_TOKEN]: connectionToken,
[_chunkOBXZ7YJ7cjs.HEADER_EXPOSE_INTERNAL_ERROR]: "true"
},
body: message,
encoding,
skipParseResponse: true,
customFetch: managerDriver.sendRequest.bind(managerDriver, actorId)
});
},
rawHttpRequest: async (c, actorQuery, encoding, params, path3, init) => {
try {
const { actorId } = await queryActor(c, actorQuery, managerDriver);
logger6().debug("found actor for raw http", { actorId });
_invariant2.default.call(void 0, actorId, "Missing actor ID");
const normalizedPath = path3.startsWith("/") ? path3.slice(1) : path3;
const url = new URL(`http://actor/raw/http/${normalizedPath}`);
const proxyRequest = new Request(url, init);
if (params) {
proxyRequest.headers.set(_chunkOBXZ7YJ7cjs.HEADER_CONN_PARAMS, JSON.stringify(params));
}
return await managerDriver.sendRequest(actorId, proxyRequest);
} catch (err) {
const { code, message, metadata } = _chunkHIB3AS73cjs.deconstructError.call(void 0,
err,
logger6(),
{},
true
);
throw new (0, _chunkDSGTB57Jcjs.ActorError)(code, message, metadata);
}
},
rawWebSocket: async (c, actorQuery, encoding, params, path3, protocols) => {
const { actorId } = await queryActor(c, actorQuery, managerDriver);
logger6().debug("found actor for action", { actorId });
_invariant2.default.call(void 0, actorId, "Missing actor ID");
const normalizedPath = path3.startsWith("/") ? path3.slice(1) : path3;
logger6().debug("opening websocket", {
actorId,
encoding,
path: normalizedPath
});
const ws = await managerDriver.openWebSocket(
`${PATH_RAW_WEBSOCKET_PREFIX}${normalizedPath}`,
actorId,
encoding,
params
);
return ws;
}
};
return driver;
}
async function queryActor(c, query, driver) {
logger6().debug("querying actor", { query });
let actorOutput;
if ("getForId" in query) {
const output = await driver.getForId({
c,
actorId: query.getForId.actorId
});
if (!output) throw new (0, _chunk53LWTTEXcjs.ActorNotFound)(query.getForId.actorId);
actorOutput = output;
} else if ("getForKey" in query) {
const existingActor = await driver.getWithKey({
c,
name: query.getForKey.name,
key: query.getForKey.key
});
if (!existingActor) {
throw new (0, _chunk53LWTTEXcjs.ActorNotFound)(
`${query.getForKey.name}:${JSON.stringify(query.getForKey.key)}`
);
}
actorOutput = existingActor;
} else if ("getOrCreateForKey" in query) {
const getOrCreateOutput = await driver.getOrCreateWithKey({
c,
name: query.getOrCreateForKey.name,
key: query.getOrCreateForKey.key,
input: query.getOrCreateForKey.input,
region: query.getOrCreateForKey.region
});
actorOutput = {
actorId: getOrCreateOutput.actorId
};
} else if ("create" in query) {
const createOutput = await driver.createActor({
c,
name: query.create.name,
key: query.create.key,
input: query.create.input,
region: query.create.region
});
actorOutput = {
actorId: createOutput.actorId
};
} else {
throw new (0, _chunk53LWTTEXcjs.InvalidRequest)("Invalid query format");
}
logger6().debug("actor query result", {
actorId: actorOutput.actorId
});
return { actorId: actorOutput.actorId };
}
// src/inspector/manager.ts
var _standardvalidator = require('@hono/standard-validator');
function createManagerInspectorRouter() {
return new (0, _hono.Hono)().get("/ping", (c) => {
return c.json({ message: "pong" }, 200);
}).get("/actors", async (c) => {
const limit = Number.parseInt(_nullishCoalesce(c.req.query("limit"), () => ( ""))) || void 0;
const cursor = c.req.query("cursor") || void 0;
_invariant2.default.call(void 0, limit && limit > 0, "Limit must be a positive integer");
try {
const actors = await c.var.inspector.accessors.getAllActors({
limit,
cursor
});
return c.json(actors, 200);
} catch (error) {
inspectorLogger().error("Failed to fetch actors", error);
return c.json("Failed to fetch actors", 500);
}
}).post("/actors", _standardvalidator.sValidator.call(void 0, "json", _chunkOBXZ7YJ7cjs.CreateActorSchema), async (c) => {
const actor2 = await c.var.inspector.accessors.createActor(
c.req.valid("json")
);
return c.json(actor2, 201);
}).get("/builds", async (c) => {
const builds = await c.var.inspector.accessors.getBuilds();
return c.json(builds, 200);
}).get("/actor/:id", async (c) => {
const id = c.req.param("id");
const actor2 = await c.var.inspector.accessors.getActorById(id);
if (!actor2) {
return c.json({ error: "Actor not found" }, 404);
}
return c.json(actor2, 200);
}).get("/bootstrap", async (c) => {
const actors = await c.var.inspector.accessors.getAllActors({
limit: 10
});
return c.json({ actors }, 200);
});
}
var ManagerInspector = class {
constructor(accessors) {
this.accessors = accessors();
inspectorLogger().debug("Manager Inspector enabled and ready");
}
};
// src/actor/config.ts
var ActorConfigSchema = _zod.z.object({
onAuth: _zod.z.function().optional(),
onCreate: _zod.z.function().optional(),
onStart: _zod.z.function().optional(),
onStateChange: _zod.z.function().optional(),
onBeforeConnect: _zod.z.function().optional(),
onConnect: _zod.z.function().optional(),
onDisconnect: _zod.z.function().optional(),
onBeforeActionResponse: _zod.z.function().optional(),
onFetch: _zod.z.function().optional(),
onWebSocket: _zod.z.function().optional(),
actions: _zod.z.record(_zod.z.function()).default({}),
state: _zod.z.any().optional(),
createState: _zod.z.function().optional(),
connState: _zod.z.any().optional(),
createConnState: _zod.z.function().optional(),
vars: _zod.z.any().optional(),
db: _zod.z.any().optional(),
createVars: _zod.z.function().optional(),
options: _zod.z.object({
lifecycle: _zod.z.object({
createVarsTimeout: _zod.z.number().positive().default(5e3),
createConnStateTimeout: _zod.z.number().positive().default(5e3),
onConnectTimeout: _zod.z.number().positive().default(5e3)
}).strict().default({}),
state: _zod.z.object({
saveInterval: _zod.z.number().positive().default(1e4)
}).strict().default({}),
action: _zod.z.object({
timeout: _zod.z.number().positive().default(6e4)
}).strict().default({})
}).strict().default({})
}).strict().refine(
(data) => !(data.state !== void 0 && data.createState !== void 0),
{
message: "Cannot define both 'state' and 'createState'",
path: ["state"]
}
).refine(
(data) => !(data.connState !== void 0 && data.createConnState !== void 0),
{
message: "Cannot define both 'connState' and 'createConnState'",
path: ["connState"]
}
).refine(
(data) => !(data.vars !== void 0 && data.createVars !== void 0),
{
message: "Cannot define both 'vars' and 'createVars'",
path: ["vars"]
}
);
// src/actor/mod.ts
function actor(input) {
const config = ActorConfigSchema.parse(input);
return new (0, _chunkDSGTB57Jcjs.ActorDefinition)(config);
}
// src/manager/router.ts
var _zodopenapi = require('@hono/zod-openapi');
var _cors = require('hono/cors');
var _streaming = require('hono/streaming');
// src/manager/auth.ts
function getIntentsFromQuery(query) {
const intents = /* @__PURE__ */ new Set();
if ("getForId" in query) {
intents.add("get");
} else if ("getForKey" in query) {
intents.add("get");
} else if ("getOrCreateForKey" in query) {
intents.add("get");
intents.add("create");
} else if ("create" in query) {
intents.add("create");
}
return intents;
}
async function getActorNameFromQuery(c, driver, query) {
if ("getForId" in query) {
const output = await driver.getForId({
c,
actorId: query.getForId.actorId
});
if (!output) throw new (0, _chunk53LWTTEXcjs.ActorNotFound)(query.getForId.actorId);
return output.name;
} else if ("getForKey" in query) {
return query.getForKey.name;
} else if ("getOrCreateForKey" in query) {
return query.getOrCreateForKey.name;
} else if ("create" in query) {
return query.create.name;
} else {
throw new (0, _chunk53LWTTEXcjs.InvalidRequest)("Invalid query format");
}
}
async function authenticateRequest(c, actorDefinition, intents, params) {
if (!("onAuth" in actorDefinition.config)) {
throw new (0, _chunk53LWTTEXcjs.Forbidden)(
"Actor requires authentication but no onAuth handler is defined (https://rivet.gg/docs/actors/authentication/). Provide an empty handler to disable auth: `onAuth: () => {}`"
);
}
try {
const dataOrPromise = actorDefinition.config.onAuth(
{
request: c.req.raw,
intents
},
params
);
if (dataOrPromise instanceof Promise) {
return await dataOrPromise;
} else {
return dataOrPromise;
}
} catch (error) {
_chunkOBXZ7YJ7cjs.logger2.call(void 0, ).info("authentication error", { error: _chunkHIB3AS73cjs.stringifyError.call(void 0, error) });
throw error;
}
}
async function authenticateEndpoint(c, driver, registryConfig, query, additionalIntents, params) {
const intents = getIntentsFromQuery(query);
for (const intent of additionalIntents) {
intents.add(intent);
}
const actorName = await getActorNameFromQuery(c, driver, query);
const actorDefinition = registryConfig.use[actorName];
if (!actorDefinition) {
throw new (0, _chunk53LWTTEXcjs.ActorNotFound)(actorName);
}
return await authenticateRequest(c, actorDefinition, intents, params);
}
// src/manager/router.ts
function parseWebSocketProtocols(protocols) {
let queryRaw;
let encodingRaw;
let connParamsRaw;
if (protocols) {
const protocolList = protocols.split(",").map((p) => p.trim());
for (const protocol of protocolList) {
if (protocol.startsWith("query.")) {
queryRaw = decodeURIComponent(protocol.substring("query.".length));
} else if (protocol.startsWith("encoding.")) {
encodingRaw = protocol.substring("encoding.".length);
} else if (protocol.startsWith("conn_params.")) {
connParamsRaw = decodeURIComponent(
protocol.substring("conn_params.".length)
);
}
}
}
return { queryRaw, encodingRaw, connParamsRaw };
}
var OPENAPI_ENCODING = _zod.z.string().openapi({
description: "The encoding format to use for the response (json, cbor)",
example: "json"
});
var OPENAPI_ACTOR_QUERY = _zod.z.string().openapi({
description: "Actor query information"
});
var OPENAPI_CONN_PARAMS = _zod.z.string().openapi({
description: "Connection parameters"
});
var OPENAPI_ACTOR_ID = _zod.z.string().openapi({
description: "Actor ID (used in some endpoints)",
example: "actor-123456"
});
var OPENAPI_CONN_ID = _zod.z.string().openapi({
description: "Connection ID",
example: "conn-123456"
});
var OPENAPI_CONN_TOKEN = _zod.z.string().openapi({
description: "Connection token"
});
function buildOpenApiResponses(schema, validateBody) {
return {
200: {
description: "Success",
content: validateBody ? {
"application/json": {
schema
}
} : {}
},
400: {
description: "User error"
},
500: {
description: "Internal error"
}
};
}
function createManagerRouter(registryConfig, runConfig, inlineClientDriver, managerDriver, validateBody) {
var _a, _b, _c;
const router = new (0, _zodopenapi.OpenAPIHono)({ strict: false }).basePath(
runConfig.basePath
);
router.use("*", loggerMiddleware(_chunkOBXZ7YJ7cjs.logger2.call(void 0, )));
if (runConfig.cors || ((_a = runConfig.studio) == null ? void 0 : _a.cors)) {
router.use("*", async (c, next) => {
var _a2, _b2, _c2, _d, _e, _f, _g;
const path3 = c.req.path;
if (path3.endsWith("/actors/connect/websocket") || path3.includes("/actors/raw/websocket/") || // inspectors implement their own CORS handling
path3.endsWith("/inspect") || path3.endsWith("/actors/inspect")) {
return next();
}
return _cors.cors.call(void 0, {
..._nullishCoalesce(runConfig.cors, () => ( {})),
..._nullishCoalesce(((_a2 = runConfig.studio) == null ? void 0 : _a2.cors), () => ( {})),
origin: (origin, c2) => {