UNPKG

@rivetkit/core

Version:

1,567 lines (1,469 loc) 107 kB
"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) => {