UNPKG

@rivetkit/core

Version:

303 lines (274 loc) 7.45 kB
import type { Context as HonoContext } from "hono"; import invariant from "invariant"; import { type ActorRouter, createActorRouter } from "@/actor/router"; import { handleRawWebSocketHandler, handleWebSocketConnect, } from "@/actor/router-endpoints"; import { createClientWithDriver } from "@/client/client"; import { InlineWebSocketAdapter2 } from "@/common/inline-websocket-adapter2"; import { noopNext } from "@/common/utils"; import type { ActorDriver, ActorOutput, CreateInput, GetForIdInput, GetOrCreateWithKeyInput, GetWithKeyInput, ManagerDriver, } from "@/driver-helpers/mod"; import { createInlineClientDriver } from "@/inline-client-driver/mod"; import { ManagerInspector } from "@/inspector/manager"; import { type Actor, ActorFeature, type ActorId } from "@/inspector/mod"; import { type Encoding, PATH_CONNECT_WEBSOCKET, PATH_RAW_WEBSOCKET_PREFIX, type RegistryConfig, type RunConfig, } from "@/mod"; import type { ActorState, FileSystemGlobalState } from "./global-state"; import { logger } from "./log"; import { generateActorId } from "./utils"; export class FileSystemManagerDriver implements ManagerDriver { #registryConfig: RegistryConfig; #runConfig: RunConfig; #state: FileSystemGlobalState; #actorDriver: ActorDriver; #actorRouter: ActorRouter; inspector?: ManagerInspector; constructor( registryConfig: RegistryConfig, runConfig: RunConfig, state: FileSystemGlobalState, ) { this.#registryConfig = registryConfig; this.#runConfig = runConfig; this.#state = state; if (runConfig.studio.enabled) { if (!this.#runConfig.studio.token()) { this.#runConfig.studio.token = () => this.#state.getOrCreateInspectorAccessToken(); } const startedAt = new Date().toISOString(); function transformActor(actorState: ActorState): Actor { return { id: actorState.id as ActorId, name: actorState.name, key: actorState.key, startedAt: startedAt, createdAt: actorState.createdAt?.toISOString() || new Date().toISOString(), features: [ ActorFeature.State, ActorFeature.Connections, ActorFeature.Console, ActorFeature.EventsMonitoring, ActorFeature.Database, ], }; } this.inspector = new ManagerInspector(() => { return { getAllActors: async ({ cursor, limit }) => { const itr = this.#state.getActorsIterator({ cursor }); const actors: Actor[] = []; for await (const actor of itr) { actors.push(transformActor(actor)); if (limit && actors.length >= limit) { break; } } return actors; }, getActorById: async (id) => { try { const result = await this.#state.loadActorStateOrError(id); return transformActor(result); } catch { return null; } }, getBuilds: async () => { return Object.keys(this.#registryConfig.use).map((name) => ({ name, })); }, createActor: async (input) => { const { actorId } = await this.createActor(input); try { const result = await this.#state.loadActorStateOrError(actorId); return transformActor(result); } catch { return null; } }, }; }); } // Actors run on the same node as the manager, so we create a dummy actor router that we route requests to const inlineClient = createClientWithDriver(createInlineClientDriver(this)); this.#actorDriver = runConfig.driver.actor( registryConfig, runConfig, this, inlineClient, ); this.#actorRouter = createActorRouter(this.#runConfig, this.#actorDriver); } async sendRequest(actorId: string, actorRequest: Request): Promise<Response> { return await this.#actorRouter.fetch(actorRequest, { actorId, }); } async openWebSocket( path: string, actorId: string, encoding: Encoding, params: unknown, ): Promise<WebSocket> { // TODO: // Handle raw WebSocket paths if (path === PATH_CONNECT_WEBSOCKET) { // Handle standard connect const wsHandler = await handleWebSocketConnect( undefined, this.#runConfig, this.#actorDriver, actorId, encoding, params, undefined, ); return new InlineWebSocketAdapter2(wsHandler); } else if (path.startsWith(PATH_RAW_WEBSOCKET_PREFIX)) { // Handle websocket proxy const wsHandler = await handleRawWebSocketHandler( undefined, path, this.#actorDriver, actorId, undefined, ); return new InlineWebSocketAdapter2(wsHandler); } else { throw new Error(`Unreachable path: ${path}`); } } async proxyRequest( c: HonoContext, actorRequest: Request, actorId: string, ): Promise<Response> { return await this.#actorRouter.fetch(actorRequest, { actorId, }); } async proxyWebSocket( c: HonoContext, path: string, actorId: string, encoding: Encoding, connParams: unknown, authData: unknown, ): Promise<Response> { const upgradeWebSocket = this.#runConfig.getUpgradeWebSocket?.(); invariant(upgradeWebSocket, "missing getUpgradeWebSocket"); // Handle raw WebSocket paths if (path === PATH_CONNECT_WEBSOCKET) { // Handle standard connect const wsHandler = await handleWebSocketConnect( c, this.#runConfig, this.#actorDriver, actorId, encoding, connParams, authData, ); return upgradeWebSocket(() => wsHandler)(c, noopNext()); } else if (path.startsWith(PATH_RAW_WEBSOCKET_PREFIX)) { // Handle websocket proxy const wsHandler = await handleRawWebSocketHandler( c, path, this.#actorDriver, actorId, authData, ); return upgradeWebSocket(() => wsHandler)(c, noopNext()); } else { throw new Error(`Unreachable path: ${path}`); } } async getForId({ actorId }: GetForIdInput): Promise<ActorOutput | undefined> { // Validate the actor exists const actor = await this.#state.loadActor(actorId); if (!actor.state) { return undefined; } try { // Load actor state return { actorId, name: actor.state.name, key: actor.state.key, }; } catch (error) { logger().error("failed to read actor state", { actorId, error }); return undefined; } } async getWithKey({ name, key, }: GetWithKeyInput): Promise<ActorOutput | undefined> { // Generate the deterministic actor ID const actorId = generateActorId(name, key); // Check if actor exists const actor = await this.#state.loadActor(actorId); if (actor.state) { return { actorId, name, key, }; } return undefined; } async getOrCreateWithKey( input: GetOrCreateWithKeyInput, ): Promise<ActorOutput> { // Generate the deterministic actor ID const actorId = generateActorId(input.name, input.key); // Use the atomic getOrCreateActor method const actorEntry = await this.#state.loadOrCreateActor( actorId, input.name, input.key, input.input, ); invariant(actorEntry.state, "must have state"); return { actorId: actorEntry.state.id, name: actorEntry.state.name, key: actorEntry.state.key, }; } async createActor({ name, key, input }: CreateInput): Promise<ActorOutput> { // Generate the deterministic actor ID const actorId = generateActorId(name, key); await this.#state.createActor(actorId, name, key, input); return { actorId, name, key, }; } extraStartupLog() { return { instances: this.#state.actorCountOnStartup, data: this.#state.storagePath, }; } }