From 23bbf3c616bc1bb159a4204987035e3f42b8e5bb Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 7 Apr 2026 01:45:27 -0700 Subject: [PATCH] chore: engine npm package --- .github/workflows/publish.yaml | 2 +- engine/package.json | 8 - .../src/components/actors/actor-database.tsx | 43 +- .../components/actors/actor-status-label.tsx | 5 +- .../actors/database/database-table.tsx | 30 +- .../actors/workflow/workflow-to-xyflow.ts | 3 +- .../actors/workflow/workflow-visualizer.tsx | 4 +- frontend/src/components/onboarding/footer.tsx | 4 +- .../components/use-deployment-logs-stream.ts | 16 +- frontend/vite.engine.config.ts | 1 - rivetkit-json-schema/registry-config.json | 144 +- .../artifacts/registry-config.json | 144 +- .../packages/devtools/src/mod.tsx | 4 +- .../engine-runner-protocol/src/index.ts | 3400 +++++++++-------- .../packages/engine-runner/src/mod.ts | 11 +- .../packages/engine-runner/src/tunnel.ts | 8 +- .../src/websocket-tunnel-adapter.ts | 61 +- .../packages/next-js/src/mod.ts | 12 +- .../packages/react/src/mod.typecheck.ts | 2 - .../packages/rivetkit-native/index.d.ts | 171 +- .../packages/rivetkit-native/wrapper.d.ts | 21 +- .../dynamic-isolate-runtime/src/index.cts | 1 - .../dynamic-isolate-runtime/tsconfig.json | 5 +- .../driver-test-suite/db-pragma-migration.ts | 4 +- .../driver-test-suite/lifecycle-hooks.ts | 4 +- .../driver-test-suite/registry-dynamic.ts | 5 +- .../driver-test-suite/registry-static.ts | 13 +- .../fixtures/driver-test-suite/sandbox.ts | 28 +- .../fixtures/driver-test-suite/sleep-db.ts | 82 +- .../fixtures/driver-test-suite/sleep.ts | 34 +- .../fixtures/driver-test-suite/workflow.ts | 12 +- .../packages/rivetkit/package.json | 2 +- .../packages/rivetkit/runtime/index.ts | 197 +- .../rivetkit/src/actor-gateway/actor-path.ts | 20 +- .../rivetkit/src/actor-gateway/gateway.ts | 7 +- .../src/actor-gateway/resolve-query.ts | 8 +- .../packages/rivetkit/src/actor/config.ts | 10 +- .../packages/rivetkit/src/actor/conn/mod.ts | 10 +- .../packages/rivetkit/src/actor/definition.ts | 3 +- .../src/actor/instance/connection-manager.ts | 4 +- .../rivetkit/src/actor/instance/mod.ts | 105 +- .../src/actor/instance/schedule-manager.ts | 5 +- .../src/actor/instance/tracked-websocket.ts | 62 +- .../src/actor/router-websocket-endpoints.ts | 16 +- .../packages/rivetkit/src/actor/router.ts | 20 +- .../rivetkit/src/agent-os/actor/cron.ts | 5 +- .../rivetkit/src/agent-os/actor/index.ts | 4 +- .../rivetkit/src/agent-os/actor/process.ts | 7 +- .../rivetkit/src/agent-os/actor/session.ts | 43 +- .../rivetkit/src/agent-os/fs/database-vfs.ts | 3 +- .../rivetkit/src/client/actor-common.ts | 41 +- .../rivetkit/src/client/actor-handle.ts | 14 +- .../rivetkit/src/client/actor-query.ts | 5 +- .../packages/rivetkit/src/client/client.ts | 5 +- .../packages/rivetkit/src/client/config.ts | 6 +- .../packages/rivetkit/src/client/raw-utils.ts | 11 +- .../packages/rivetkit/src/client/utils.ts | 8 +- .../src/common/inline-websocket-adapter.ts | 5 +- .../packages/rivetkit/src/common/router.ts | 2 +- .../packages/rivetkit/src/db/drizzle/mod.ts | 16 +- .../packages/rivetkit/src/db/mod.ts | 6 +- .../rivetkit/src/db/native-database.test.ts | 5 +- .../rivetkit/src/db/native-database.ts | 10 +- .../driver-helpers/resolve-gateway-target.ts | 5 +- .../rivetkit/src/driver-test-suite/mod.ts | 14 +- .../driver-test-suite/tests/actor-agent-os.ts | 29 +- .../tests/actor-conn-hibernation.ts | 3 +- .../tests/actor-db-pragma-migration.ts | 44 +- .../tests/actor-db-stress.ts | 16 +- .../src/driver-test-suite/tests/actor-db.ts | 17 +- .../tests/actor-error-handling.ts | 124 +- .../tests/actor-inspector.ts | 29 +- .../driver-test-suite/tests/actor-sandbox.ts | 4 +- .../driver-test-suite/tests/actor-schedule.ts | 11 +- .../driver-test-suite/tests/actor-sleep-db.ts | 1647 ++++---- .../driver-test-suite/tests/actor-sleep.ts | 38 +- .../driver-test-suite/tests/actor-workflow.ts | 72 +- .../driver-test-suite/tests/dynamic-reload.ts | 29 +- .../tests/gateway-query-url.ts | 20 +- .../tests/gateway-routing.ts | 110 +- .../tests/hibernatable-websocket-protocol.ts | 360 +- .../driver-test-suite/tests/raw-websocket.ts | 2 +- .../rivetkit/src/driver-test-suite/utils.ts | 8 +- .../src/drivers/engine/actor-driver.ts | 75 +- .../packages/rivetkit/src/dynamic/internal.ts | 17 +- .../rivetkit/src/dynamic/isolate-runtime.ts | 16 +- .../engine-client/actor-websocket-client.ts | 11 +- .../rivetkit/src/engine-client/driver.ts | 4 +- .../rivetkit/src/engine-client/mod.ts | 41 +- .../rivetkit/src/engine-process/mod.ts | 8 +- .../rivetkit/src/inspector/actor-inspector.ts | 6 +- .../packages/rivetkit/src/inspector/utils.ts | 4 +- .../rivetkit/src/registry/config/index.ts | 193 +- .../src/registry/config/serverless.ts | 40 +- .../packages/rivetkit/src/registry/index.ts | 46 +- .../rivetkit/src/runtime-router/router.ts | 16 +- .../rivetkit/src/serverless/configure.ts | 13 +- .../rivetkit/src/serverless/router.ts | 5 +- .../packages/rivetkit/src/test/mod.ts | 9 +- .../packages/rivetkit/src/utils/serve.ts | 30 +- .../packages/rivetkit/src/workflow/context.ts | 5 +- .../packages/rivetkit/src/workflow/driver.ts | 5 +- .../packages/rivetkit/src/workflow/mod.ts | 5 +- .../rivetkit/tests/actor-resolution.test.ts | 9 +- .../tests/agent-os-session-lifecycle.test.ts | 13 +- .../tests/driver-engine-dynamic.test.ts | 449 +-- .../rivetkit/tests/driver-engine-ping.test.ts | 16 +- .../rivetkit/tests/driver-engine.test.ts | 31 +- .../tests/driver-registry-variants.ts | 9 +- .../hibernatable-websocket-ack-state.test.ts | 3 +- .../rivetkit/tests/parse-actor-path.test.ts | 4 +- .../tests/registry-constructor.test.ts | 10 +- .../remote-engine-client-public-token.test.ts | 6 +- .../tests/resolve-gateway-target.test.ts | 12 +- .../packages/sqlite-native/index.d.ts | 180 - .../packages/sqlite-native/index.js | 324 -- .../npm/darwin-arm64/package.json | 13 - .../sqlite-native/npm/darwin-x64/package.json | 13 - .../npm/linux-arm64-gnu/package.json | 13 - .../npm/linux-x64-gnu/package.json | 13 - .../npm/win32-x64-msvc/package.json | 13 - .../packages/sqlite-native/package.json | 36 - .../packages/sqlite-native/turbo.json | 24 - .../packages/sqlite-vfs/package.json | 49 - .../packages/workflow-engine/src/context.ts | 15 +- .../packages/workflow-engine/src/types.ts | 6 +- .../workflow-engine/tests/driver-kv.test.ts | 230 +- .../tests/driver-scheduling.test.ts | 70 +- .../tests/eviction-cancel.test.ts | 112 +- .../workflow-engine/tests/removals.test.ts | 128 +- website/src/content/docs/actors/limits.mdx | 2 +- .../docs/actors/quickstart/backend.mdx | 10 +- website/src/content/docs/actors/versions.mdx | 24 +- .../content/docs/agent-os/agents/claude.mdx | 2 +- .../content/docs/agent-os/agents/codex.mdx | 2 +- .../content/docs/agent-os/agents/opencode.mdx | 2 +- .../src/content/docs/agent-os/agents/pi.mdx | 2 +- .../src/content/docs/general/http-server.mdx | 2 +- .../docs/general/registry-configuration.mdx | 4 +- .../content/docs/general/runtime-modes.mdx | 10 +- 140 files changed, 4799 insertions(+), 5140 deletions(-) delete mode 100644 engine/package.json delete mode 100644 rivetkit-typescript/packages/sqlite-native/index.d.ts delete mode 100644 rivetkit-typescript/packages/sqlite-native/index.js delete mode 100644 rivetkit-typescript/packages/sqlite-native/npm/darwin-arm64/package.json delete mode 100644 rivetkit-typescript/packages/sqlite-native/npm/darwin-x64/package.json delete mode 100644 rivetkit-typescript/packages/sqlite-native/npm/linux-arm64-gnu/package.json delete mode 100644 rivetkit-typescript/packages/sqlite-native/npm/linux-x64-gnu/package.json delete mode 100644 rivetkit-typescript/packages/sqlite-native/npm/win32-x64-msvc/package.json delete mode 100644 rivetkit-typescript/packages/sqlite-native/package.json delete mode 100644 rivetkit-typescript/packages/sqlite-native/turbo.json delete mode 100644 rivetkit-typescript/packages/sqlite-vfs/package.json diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 1d215fb42e..3bde3d0223 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -397,7 +397,7 @@ jobs: # ---- build TypeScript packages (turbo dep graph picks up native) ---- - name: Build TypeScript packages - run: pnpm build -F rivetkit -F '@rivetkit/*' -F '!@rivetkit/shared-data' -F '!@rivetkit/engine-frontend' -F '!@rivetkit/mcp-hub' -F '!@rivetkit/sqlite-native' -F '!@rivetkit/sqlite-wasm' -F '!@rivetkit/rivetkit-native' + run: pnpm build -F rivetkit -F '@rivetkit/*' -F '!@rivetkit/shared-data' -F '!@rivetkit/engine-frontend' -F '!@rivetkit/mcp-hub' -F '!@rivetkit/rivetkit-native' - name: Pack inspector run: npx turbo build:pack-inspector -F rivetkit diff --git a/engine/package.json b/engine/package.json deleted file mode 100644 index 910b5b9f03..0000000000 --- a/engine/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@rivetkit/engine", - "version": "2.3.0-rc.4", - "keywords": [], - "author": "", - "license": "ISC", - "packageManager": "pnpm@10.13.1" -} diff --git a/frontend/src/components/actors/actor-database.tsx b/frontend/src/components/actors/actor-database.tsx index 359d99fc16..2fd53f114c 100644 --- a/frontend/src/components/actors/actor-database.tsx +++ b/frontend/src/components/actors/actor-database.tsx @@ -137,7 +137,9 @@ function ActorDatabaseBrowser({ actorId }: ActorDatabaseProps) { return [...(currentTable?.columns ?? [])] .filter((column) => Boolean(column.pk)) .sort( - (a, b) => Number(a.pk ?? Number.MAX_SAFE_INTEGER) - Number(b.pk ?? Number.MAX_SAFE_INTEGER), + (a, b) => + Number(a.pk ?? Number.MAX_SAFE_INTEGER) - + Number(b.pk ?? Number.MAX_SAFE_INTEGER), ); }, [currentTable]); const canEditRows = @@ -164,9 +166,9 @@ function ActorDatabaseBrowser({ actorId }: ActorDatabaseProps) { }, [currentTable?.columns]); const [editingCell, setEditingCell] = useState(null); const [editingValue, setEditingValue] = useState(""); - const [stagedEdits, setStagedEdits] = useState>( - {}, - ); + const [stagedEdits, setStagedEdits] = useState< + Record + >({}); const [isApplyingEdits, setIsApplyingEdits] = useState(false); const [tableEditError, setTableEditError] = useState(null); const { mutateAsync: executeDatabaseSql } = useMutation( @@ -340,7 +342,9 @@ function ActorDatabaseBrowser({ actorId }: ActorDatabaseProps) { setEditingValue(event.target.value)} + onChange={(event) => + setEditingValue(event.target.value) + } onBlur={commitCellEdit} onKeyDown={(event) => { if (event.key === "Enter") { @@ -362,7 +366,13 @@ function ActorDatabaseBrowser({ actorId }: ActorDatabaseProps) { stagedEdit ? stagedEdit.nextValue : context.value, ); }, - [commitCellEdit, editingCell, editingValue, primaryKeyColumns, stagedEdits], + [ + commitCellEdit, + editingCell, + editingValue, + primaryKeyColumns, + stagedEdits, + ], ); const getBrowserCellClassName = useCallback( @@ -452,7 +462,9 @@ function ActorDatabaseBrowser({ actorId }: ActorDatabaseProps) { variant="ghost" size="icon-sm" disabled={!hasPrevPage} - onClick={() => setPage((current) => current - 1)} + onClick={() => + setPage((current) => current - 1) + } > @@ -468,7 +480,9 @@ function ActorDatabaseBrowser({ actorId }: ActorDatabaseProps) { variant="ghost" size="icon-sm" disabled={!hasNextPage} - onClick={() => setPage((current) => current + 1)} + onClick={() => + setPage((current) => current + 1) + } > @@ -605,9 +619,7 @@ function ActorDatabaseSqlShell({ actorId }: ActorDatabaseProps) { sql, ]); const canRun = - sql.trim() !== "" && - bindingError === null && - propertiesError === null; + sql.trim() !== "" && bindingError === null && propertiesError === null; useEffect(() => { if ( @@ -827,10 +839,7 @@ function TableSelect({ function areObjectRows(rows: unknown[]): rows is Record[] { return rows.every( - (row) => - row !== null && - typeof row === "object" && - !Array.isArray(row), + (row) => row !== null && typeof row === "object" && !Array.isArray(row), ); } @@ -839,9 +848,7 @@ function createResultColumns(rows: unknown[]): DatabaseColumn[] { return []; } - const names = Array.from( - new Set(rows.flatMap((row) => Object.keys(row))), - ); + const names = Array.from(new Set(rows.flatMap((row) => Object.keys(row)))); return names.map((name, cid) => ({ cid, name, diff --git a/frontend/src/components/actors/actor-status-label.tsx b/frontend/src/components/actors/actor-status-label.tsx index effe358ac7..5d883b8d49 100644 --- a/frontend/src/components/actors/actor-status-label.tsx +++ b/frontend/src/components/actors/actor-status-label.tsx @@ -166,7 +166,10 @@ export function RunnerPoolError({ error }: { error: RivetActorError }) {

Internal error occurred in runner pool

)) .with("downgrade", () => ( -

Runner pool was downgraded to an unsupported version. Revert to the higher version.

+

+ Runner pool was downgraded to an unsupported version. + Revert to the higher version. +

)) .with("serverless_stream_ended_early", () => (

diff --git a/frontend/src/components/actors/database/database-table.tsx b/frontend/src/components/actors/database/database-table.tsx index 2a23dd0ce2..48d2fb70da 100644 --- a/frontend/src/components/actors/database/database-table.tsx +++ b/frontend/src/components/actors/database/database-table.tsx @@ -15,7 +15,13 @@ import { type SortingState, useReactTable as useTable, } from "@tanstack/react-table"; -import { Fragment, type ReactNode, useCallback, useMemo, useState } from "react"; +import { + Fragment, + type ReactNode, + useCallback, + useMemo, + useState, +} from "react"; import { Badge, Button, @@ -49,7 +55,9 @@ interface DatabaseTableProps { enableSorting?: boolean; enableColumnResizing?: boolean; renderCell?: (context: DatabaseTableCellContext) => ReactNode; - getCellClassName?: (context: DatabaseTableCellContext) => string | undefined; + getCellClassName?: ( + context: DatabaseTableCellContext, + ) => string | undefined; onCellDoubleClick?: (context: DatabaseTableCellContext) => void; } @@ -209,7 +217,7 @@ export function DatabaseTable({ {table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map((cell) => ( + {row.getVisibleCells().map((cell) => (() => { const column = dataColumnsByName.get( cell.column.id, @@ -218,7 +226,9 @@ export function DatabaseTable({ ? { column, row: row.original, - value: row.original[column.name], + value: row.original[ + column.name + ], rowIndex: row.index, } : null; @@ -230,7 +240,7 @@ export function DatabaseTable({ cellContext ? getCellClassName?.( cellContext, - ) + ) : undefined, )} style={{ @@ -248,19 +258,21 @@ export function DatabaseTable({

{cellContext && renderCell - ? renderCell(cellContext) + ? renderCell( + cellContext, + ) : flexRender( cell.column .columnDef .cell, cell.getContext(), - )} + )}
); - })() - ))} + })(), + )} ))} diff --git a/frontend/src/components/actors/workflow/workflow-to-xyflow.ts b/frontend/src/components/actors/workflow/workflow-to-xyflow.ts index 6abe4883a7..717d76d37a 100644 --- a/frontend/src/components/actors/workflow/workflow-to-xyflow.ts +++ b/frontend/src/components/actors/workflow/workflow-to-xyflow.ts @@ -201,8 +201,7 @@ function itemToNodeData( onReplayStep?: (entryId: string) => void; } = {}, ) { - const { id, startedAt, completedAt, kind, retryCount, error } = - item.entry; + const { id, startedAt, completedAt, kind, retryCount, error } = item.entry; const duration = startedAt != null && completedAt != null ? completedAt - startedAt diff --git a/frontend/src/components/actors/workflow/workflow-visualizer.tsx b/frontend/src/components/actors/workflow/workflow-visualizer.tsx index 99177131ad..deab4aa04c 100644 --- a/frontend/src/components/actors/workflow/workflow-visualizer.tsx +++ b/frontend/src/components/actors/workflow/workflow-visualizer.tsx @@ -198,7 +198,9 @@ export function WorkflowVisualizer({ : "cursor-default hover:bg-accent hover:text-accent-foreground", )} onClick={() => { - contextMenu.onReplayStep(contextMenu.entryId); + contextMenu.onReplayStep( + contextMenu.entryId, + ); setContextMenu(null); }} > diff --git a/frontend/src/components/onboarding/footer.tsx b/frontend/src/components/onboarding/footer.tsx index b6cb4d744a..85e1b75229 100644 --- a/frontend/src/components/onboarding/footer.tsx +++ b/frontend/src/components/onboarding/footer.tsx @@ -24,9 +24,7 @@ export function OnboardingFooter() { variant="link" size="xs" asChild - endIcon={ - - } + endIcon={} > { }, }); }); - diff --git a/rivetkit-json-schema/registry-config.json b/rivetkit-json-schema/registry-config.json index 0490551568..7a3ea83489 100644 --- a/rivetkit-json-schema/registry-config.json +++ b/rivetkit-json-schema/registry-config.json @@ -64,18 +64,22 @@ "type": "string" } }, - "serveManager": { - "description": "Whether to start the local RivetKit server. Auto-determined based on endpoint and NODE_ENV if not specified.", - "type": "boolean" + "staticDir": { + "description": "Directory to serve static files from. When set, registry.start() serves static files alongside the actor API.", + "type": "string" }, - "managerBasePath": { + "httpBasePath": { "description": "Base path for the local RivetKit API. Default: '/'", "type": "string" }, - "managerPort": { - "description": "Port to run the manager on. Default: 6420", + "httpPort": { + "description": "Port to run the local HTTP server on. Default: 6421", "type": "number" }, + "httpHost": { + "description": "Host to bind the local HTTP server to.", + "type": "string" + }, "inspector": { "description": "Inspector configuration for debugging and development.", "type": "object", @@ -95,74 +99,66 @@ }, "additionalProperties": false }, - "serverless": { - "description": "Configuration for serverless deployment mode.", + "startEngine": { + "description": "Starts the full Rust engine process locally. Default: false", + "type": "boolean" + }, + "engineVersion": { + "description": "Version of the local engine package to use. Defaults to the current RivetKit version.", + "type": "string" + }, + "configurePool": { + "description": "Automatically configure serverless runners in the engine.", "type": "object", "properties": { - "spawnEngine": { - "description": "Downloads and starts the full Rust engine process. Auto-enabled in development mode when no endpoint is provided. Default: false", - "type": "boolean" + "name": { + "description": "Name of the runner pool.", + "type": "string" }, - "engineVersion": { - "description": "Version of the engine to download. Defaults to the current RivetKit version.", + "url": { + "description": "URL of the serverless platform to configure runners.", "type": "string" }, - "configureRunnerPool": { - "description": "Automatically configure serverless runners in the engine.", + "headers": { + "description": "Headers to include in requests to the serverless platform.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "requestLifespan": { + "description": "Maximum lifespan of a request in seconds.", + "type": "number" + }, + "metadata": { + "description": "Additional metadata to pass to the serverless platform.", "type": "object", - "properties": { - "name": { - "description": "Name of the runner pool.", - "type": "string" - }, - "url": { - "description": "URL of the serverless platform to configure runners.", - "type": "string" - }, - "headers": { - "description": "Headers to include in requests to the serverless platform.", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - }, - "maxRunners": { - "description": "Maximum number of runners in the pool.", - "type": "number" - }, - "minRunners": { - "description": "Minimum number of runners to keep warm.", - "type": "number" - }, - "requestLifespan": { - "description": "Maximum lifespan of a request in milliseconds.", - "type": "number" - }, - "runnersMargin": { - "description": "Buffer margin for scaling runners.", - "type": "number" - }, - "slotsPerRunner": { - "description": "Number of actor slots per runner.", - "type": "number" - }, - "metadata": { - "description": "Additional metadata to pass to the serverless platform.", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } + "propertyNames": { + "type": "string" }, - "required": [ - "url" - ], - "additionalProperties": false + "additionalProperties": {} }, + "metadataPollInterval": { + "description": "Interval in milliseconds between metadata polls from the engine. Defaults to 10000 milliseconds (10 seconds).", + "type": "number" + }, + "drainOnVersionUpgrade": { + "description": "Drain runners when a new version is deployed. Defaults to true.", + "type": "boolean" + } + }, + "required": [ + "url" + ], + "additionalProperties": false + }, + "serverless": { + "description": "Configuration for serverless deployment mode.", + "type": "object", + "properties": { "basePath": { "description": "Base path for serverless API routes. Default: '/api/rivet'", "type": "string" @@ -178,24 +174,24 @@ }, "additionalProperties": false }, - "runner": { - "description": "Configuration for runner mode.", + "envoy": { + "description": "Configuration for envoy mode.", "type": "object", "properties": { "totalSlots": { "description": "Total number of actor slots available. Default: 100000", "type": "number" }, - "runnerName": { - "description": "Name of this runner. Default: 'default'", + "poolName": { + "description": "Name of this envoy pool. Default: 'default'", "type": "string" }, - "runnerKey": { - "description": "Authentication key for the runner.", + "envoyKey": { + "description": "Deprecated. Authentication key for the envoy.", "type": "string" }, "version": { - "description": "Version number of this runner. Default: 1", + "description": "Version number of this envoy. Default: 1", "type": "number" } }, @@ -207,4 +203,4 @@ ], "additionalProperties": false, "title": "RivetKit Registry Configuration" -} +} \ No newline at end of file diff --git a/rivetkit-typescript/artifacts/registry-config.json b/rivetkit-typescript/artifacts/registry-config.json index 0490551568..7a3ea83489 100644 --- a/rivetkit-typescript/artifacts/registry-config.json +++ b/rivetkit-typescript/artifacts/registry-config.json @@ -64,18 +64,22 @@ "type": "string" } }, - "serveManager": { - "description": "Whether to start the local RivetKit server. Auto-determined based on endpoint and NODE_ENV if not specified.", - "type": "boolean" + "staticDir": { + "description": "Directory to serve static files from. When set, registry.start() serves static files alongside the actor API.", + "type": "string" }, - "managerBasePath": { + "httpBasePath": { "description": "Base path for the local RivetKit API. Default: '/'", "type": "string" }, - "managerPort": { - "description": "Port to run the manager on. Default: 6420", + "httpPort": { + "description": "Port to run the local HTTP server on. Default: 6421", "type": "number" }, + "httpHost": { + "description": "Host to bind the local HTTP server to.", + "type": "string" + }, "inspector": { "description": "Inspector configuration for debugging and development.", "type": "object", @@ -95,74 +99,66 @@ }, "additionalProperties": false }, - "serverless": { - "description": "Configuration for serverless deployment mode.", + "startEngine": { + "description": "Starts the full Rust engine process locally. Default: false", + "type": "boolean" + }, + "engineVersion": { + "description": "Version of the local engine package to use. Defaults to the current RivetKit version.", + "type": "string" + }, + "configurePool": { + "description": "Automatically configure serverless runners in the engine.", "type": "object", "properties": { - "spawnEngine": { - "description": "Downloads and starts the full Rust engine process. Auto-enabled in development mode when no endpoint is provided. Default: false", - "type": "boolean" + "name": { + "description": "Name of the runner pool.", + "type": "string" }, - "engineVersion": { - "description": "Version of the engine to download. Defaults to the current RivetKit version.", + "url": { + "description": "URL of the serverless platform to configure runners.", "type": "string" }, - "configureRunnerPool": { - "description": "Automatically configure serverless runners in the engine.", + "headers": { + "description": "Headers to include in requests to the serverless platform.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "requestLifespan": { + "description": "Maximum lifespan of a request in seconds.", + "type": "number" + }, + "metadata": { + "description": "Additional metadata to pass to the serverless platform.", "type": "object", - "properties": { - "name": { - "description": "Name of the runner pool.", - "type": "string" - }, - "url": { - "description": "URL of the serverless platform to configure runners.", - "type": "string" - }, - "headers": { - "description": "Headers to include in requests to the serverless platform.", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string" - } - }, - "maxRunners": { - "description": "Maximum number of runners in the pool.", - "type": "number" - }, - "minRunners": { - "description": "Minimum number of runners to keep warm.", - "type": "number" - }, - "requestLifespan": { - "description": "Maximum lifespan of a request in milliseconds.", - "type": "number" - }, - "runnersMargin": { - "description": "Buffer margin for scaling runners.", - "type": "number" - }, - "slotsPerRunner": { - "description": "Number of actor slots per runner.", - "type": "number" - }, - "metadata": { - "description": "Additional metadata to pass to the serverless platform.", - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } + "propertyNames": { + "type": "string" }, - "required": [ - "url" - ], - "additionalProperties": false + "additionalProperties": {} }, + "metadataPollInterval": { + "description": "Interval in milliseconds between metadata polls from the engine. Defaults to 10000 milliseconds (10 seconds).", + "type": "number" + }, + "drainOnVersionUpgrade": { + "description": "Drain runners when a new version is deployed. Defaults to true.", + "type": "boolean" + } + }, + "required": [ + "url" + ], + "additionalProperties": false + }, + "serverless": { + "description": "Configuration for serverless deployment mode.", + "type": "object", + "properties": { "basePath": { "description": "Base path for serverless API routes. Default: '/api/rivet'", "type": "string" @@ -178,24 +174,24 @@ }, "additionalProperties": false }, - "runner": { - "description": "Configuration for runner mode.", + "envoy": { + "description": "Configuration for envoy mode.", "type": "object", "properties": { "totalSlots": { "description": "Total number of actor slots available. Default: 100000", "type": "number" }, - "runnerName": { - "description": "Name of this runner. Default: 'default'", + "poolName": { + "description": "Name of this envoy pool. Default: 'default'", "type": "string" }, - "runnerKey": { - "description": "Authentication key for the runner.", + "envoyKey": { + "description": "Deprecated. Authentication key for the envoy.", "type": "string" }, "version": { - "description": "Version number of this runner. Default: 1", + "description": "Version number of this envoy. Default: 1", "type": "number" } }, @@ -207,4 +203,4 @@ ], "additionalProperties": false, "title": "RivetKit Registry Configuration" -} +} \ No newline at end of file diff --git a/rivetkit-typescript/packages/devtools/src/mod.tsx b/rivetkit-typescript/packages/devtools/src/mod.tsx index 47462d4b19..23cf312459 100644 --- a/rivetkit-typescript/packages/devtools/src/mod.tsx +++ b/rivetkit-typescript/packages/devtools/src/mod.tsx @@ -71,8 +71,8 @@ const openDevtools = () => { if (config.namespace) { url.searchParams.set("ns", config.namespace); } - if (config.runnerName) { - url.searchParams.set("r", config.runnerName); + if (config.poolName) { + url.searchParams.set("r", config.poolName); } window.open(url.toString(), "_blank"); }; diff --git a/rivetkit-typescript/packages/engine-runner-protocol/src/index.ts b/rivetkit-typescript/packages/engine-runner-protocol/src/index.ts index 1ee90bbfd4..adb913efdd 100644 --- a/rivetkit-typescript/packages/engine-runner-protocol/src/index.ts +++ b/rivetkit-typescript/packages/engine-runner-protocol/src/index.ts @@ -1,2189 +1,2515 @@ // @generated - post-processed by build.rs -import * as bare from "@rivetkit/bare-ts" +import * as bare from "@rivetkit/bare-ts"; -const DEFAULT_CONFIG = /* @__PURE__ */ bare.Config({}) +const DEFAULT_CONFIG = /* @__PURE__ */ bare.Config({}); -export type i64 = bigint -export type u16 = number -export type u32 = number -export type u64 = bigint +export type i64 = bigint; +export type u16 = number; +export type u32 = number; +export type u64 = bigint; -export type Id = string +export type Id = string; export function readId(bc: bare.ByteCursor): Id { - return bare.readString(bc) + return bare.readString(bc); } export function writeId(bc: bare.ByteCursor, x: Id): void { - bare.writeString(bc, x) + bare.writeString(bc, x); } -export type Json = string +export type Json = string; export function readJson(bc: bare.ByteCursor): Json { - return bare.readString(bc) + return bare.readString(bc); } export function writeJson(bc: bare.ByteCursor, x: Json): void { - bare.writeString(bc, x) + bare.writeString(bc, x); } -export type GatewayId = ArrayBuffer +export type GatewayId = ArrayBuffer; export function readGatewayId(bc: bare.ByteCursor): GatewayId { - return bare.readFixedData(bc, 4) + return bare.readFixedData(bc, 4); } export function writeGatewayId(bc: bare.ByteCursor, x: GatewayId): void { - assert(x.byteLength === 4) - bare.writeFixedData(bc, x) + assert(x.byteLength === 4); + bare.writeFixedData(bc, x); } -export type RequestId = ArrayBuffer +export type RequestId = ArrayBuffer; export function readRequestId(bc: bare.ByteCursor): RequestId { - return bare.readFixedData(bc, 4) + return bare.readFixedData(bc, 4); } export function writeRequestId(bc: bare.ByteCursor, x: RequestId): void { - assert(x.byteLength === 4) - bare.writeFixedData(bc, x) + assert(x.byteLength === 4); + bare.writeFixedData(bc, x); } -export type MessageIndex = u16 +export type MessageIndex = u16; export function readMessageIndex(bc: bare.ByteCursor): MessageIndex { - return bare.readU16(bc) + return bare.readU16(bc); } export function writeMessageIndex(bc: bare.ByteCursor, x: MessageIndex): void { - bare.writeU16(bc, x) + bare.writeU16(bc, x); } /** * Basic types */ -export type KvKey = ArrayBuffer +export type KvKey = ArrayBuffer; export function readKvKey(bc: bare.ByteCursor): KvKey { - return bare.readData(bc) + return bare.readData(bc); } export function writeKvKey(bc: bare.ByteCursor, x: KvKey): void { - bare.writeData(bc, x) + bare.writeData(bc, x); } -export type KvValue = ArrayBuffer +export type KvValue = ArrayBuffer; export function readKvValue(bc: bare.ByteCursor): KvValue { - return bare.readData(bc) + return bare.readData(bc); } export function writeKvValue(bc: bare.ByteCursor, x: KvValue): void { - bare.writeData(bc, x) + bare.writeData(bc, x); } export type KvMetadata = { - readonly version: ArrayBuffer - readonly updateTs: i64 -} + readonly version: ArrayBuffer; + readonly updateTs: i64; +}; export function readKvMetadata(bc: bare.ByteCursor): KvMetadata { - return { - version: bare.readData(bc), - updateTs: bare.readI64(bc), - } + return { + version: bare.readData(bc), + updateTs: bare.readI64(bc), + }; } export function writeKvMetadata(bc: bare.ByteCursor, x: KvMetadata): void { - bare.writeData(bc, x.version) - bare.writeI64(bc, x.updateTs) + bare.writeData(bc, x.version); + bare.writeI64(bc, x.updateTs); } /** * Query types */ -export type KvListAllQuery = null +export type KvListAllQuery = null; export type KvListRangeQuery = { - readonly start: KvKey - readonly end: KvKey - readonly exclusive: boolean -} + readonly start: KvKey; + readonly end: KvKey; + readonly exclusive: boolean; +}; export function readKvListRangeQuery(bc: bare.ByteCursor): KvListRangeQuery { - return { - start: readKvKey(bc), - end: readKvKey(bc), - exclusive: bare.readBool(bc), - } + return { + start: readKvKey(bc), + end: readKvKey(bc), + exclusive: bare.readBool(bc), + }; } -export function writeKvListRangeQuery(bc: bare.ByteCursor, x: KvListRangeQuery): void { - writeKvKey(bc, x.start) - writeKvKey(bc, x.end) - bare.writeBool(bc, x.exclusive) +export function writeKvListRangeQuery( + bc: bare.ByteCursor, + x: KvListRangeQuery, +): void { + writeKvKey(bc, x.start); + writeKvKey(bc, x.end); + bare.writeBool(bc, x.exclusive); } export type KvListPrefixQuery = { - readonly key: KvKey -} + readonly key: KvKey; +}; export function readKvListPrefixQuery(bc: bare.ByteCursor): KvListPrefixQuery { - return { - key: readKvKey(bc), - } + return { + key: readKvKey(bc), + }; } -export function writeKvListPrefixQuery(bc: bare.ByteCursor, x: KvListPrefixQuery): void { - writeKvKey(bc, x.key) +export function writeKvListPrefixQuery( + bc: bare.ByteCursor, + x: KvListPrefixQuery, +): void { + writeKvKey(bc, x.key); } export type KvListQuery = - | { readonly tag: "KvListAllQuery"; readonly val: KvListAllQuery } - | { readonly tag: "KvListRangeQuery"; readonly val: KvListRangeQuery } - | { readonly tag: "KvListPrefixQuery"; readonly val: KvListPrefixQuery } + | { readonly tag: "KvListAllQuery"; readonly val: KvListAllQuery } + | { readonly tag: "KvListRangeQuery"; readonly val: KvListRangeQuery } + | { readonly tag: "KvListPrefixQuery"; readonly val: KvListPrefixQuery }; export function readKvListQuery(bc: bare.ByteCursor): KvListQuery { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "KvListAllQuery", val: null } - case 1: - return { tag: "KvListRangeQuery", val: readKvListRangeQuery(bc) } - case 2: - return { tag: "KvListPrefixQuery", val: readKvListPrefixQuery(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "KvListAllQuery", val: null }; + case 1: + return { tag: "KvListRangeQuery", val: readKvListRangeQuery(bc) }; + case 2: + return { tag: "KvListPrefixQuery", val: readKvListPrefixQuery(bc) }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeKvListQuery(bc: bare.ByteCursor, x: KvListQuery): void { - switch (x.tag) { - case "KvListAllQuery": { - bare.writeU8(bc, 0) - break - } - case "KvListRangeQuery": { - bare.writeU8(bc, 1) - writeKvListRangeQuery(bc, x.val) - break - } - case "KvListPrefixQuery": { - bare.writeU8(bc, 2) - writeKvListPrefixQuery(bc, x.val) - break - } - } + switch (x.tag) { + case "KvListAllQuery": { + bare.writeU8(bc, 0); + break; + } + case "KvListRangeQuery": { + bare.writeU8(bc, 1); + writeKvListRangeQuery(bc, x.val); + break; + } + case "KvListPrefixQuery": { + bare.writeU8(bc, 2); + writeKvListPrefixQuery(bc, x.val); + break; + } + } } function read0(bc: bare.ByteCursor): readonly KvKey[] { - const len = bare.readUintSafe(bc) - if (len === 0) { - return [] - } - const result = [readKvKey(bc)] - for (let i = 1; i < len; i++) { - result[i] = readKvKey(bc) - } - return result + const len = bare.readUintSafe(bc); + if (len === 0) { + return []; + } + const result = [readKvKey(bc)]; + for (let i = 1; i < len; i++) { + result[i] = readKvKey(bc); + } + return result; } function write0(bc: bare.ByteCursor, x: readonly KvKey[]): void { - bare.writeUintSafe(bc, x.length) - for (let i = 0; i < x.length; i++) { - writeKvKey(bc, x[i]) - } + bare.writeUintSafe(bc, x.length); + for (let i = 0; i < x.length; i++) { + writeKvKey(bc, x[i]); + } } /** * Request types */ export type KvGetRequest = { - readonly keys: readonly KvKey[] -} + readonly keys: readonly KvKey[]; +}; export function readKvGetRequest(bc: bare.ByteCursor): KvGetRequest { - return { - keys: read0(bc), - } + return { + keys: read0(bc), + }; } export function writeKvGetRequest(bc: bare.ByteCursor, x: KvGetRequest): void { - write0(bc, x.keys) + write0(bc, x.keys); } function read1(bc: bare.ByteCursor): boolean | null { - return bare.readBool(bc) ? bare.readBool(bc) : null + return bare.readBool(bc) ? bare.readBool(bc) : null; } function write1(bc: bare.ByteCursor, x: boolean | null): void { - bare.writeBool(bc, x != null) - if (x != null) { - bare.writeBool(bc, x) - } + bare.writeBool(bc, x != null); + if (x != null) { + bare.writeBool(bc, x); + } } function read2(bc: bare.ByteCursor): u64 | null { - return bare.readBool(bc) ? bare.readU64(bc) : null + return bare.readBool(bc) ? bare.readU64(bc) : null; } function write2(bc: bare.ByteCursor, x: u64 | null): void { - bare.writeBool(bc, x != null) - if (x != null) { - bare.writeU64(bc, x) - } + bare.writeBool(bc, x != null); + if (x != null) { + bare.writeU64(bc, x); + } } export type KvListRequest = { - readonly query: KvListQuery - readonly reverse: boolean | null - readonly limit: u64 | null -} + readonly query: KvListQuery; + readonly reverse: boolean | null; + readonly limit: u64 | null; +}; export function readKvListRequest(bc: bare.ByteCursor): KvListRequest { - return { - query: readKvListQuery(bc), - reverse: read1(bc), - limit: read2(bc), - } + return { + query: readKvListQuery(bc), + reverse: read1(bc), + limit: read2(bc), + }; } -export function writeKvListRequest(bc: bare.ByteCursor, x: KvListRequest): void { - writeKvListQuery(bc, x.query) - write1(bc, x.reverse) - write2(bc, x.limit) +export function writeKvListRequest( + bc: bare.ByteCursor, + x: KvListRequest, +): void { + writeKvListQuery(bc, x.query); + write1(bc, x.reverse); + write2(bc, x.limit); } function read3(bc: bare.ByteCursor): readonly KvValue[] { - const len = bare.readUintSafe(bc) - if (len === 0) { - return [] - } - const result = [readKvValue(bc)] - for (let i = 1; i < len; i++) { - result[i] = readKvValue(bc) - } - return result + const len = bare.readUintSafe(bc); + if (len === 0) { + return []; + } + const result = [readKvValue(bc)]; + for (let i = 1; i < len; i++) { + result[i] = readKvValue(bc); + } + return result; } function write3(bc: bare.ByteCursor, x: readonly KvValue[]): void { - bare.writeUintSafe(bc, x.length) - for (let i = 0; i < x.length; i++) { - writeKvValue(bc, x[i]) - } + bare.writeUintSafe(bc, x.length); + for (let i = 0; i < x.length; i++) { + writeKvValue(bc, x[i]); + } } export type KvPutRequest = { - readonly keys: readonly KvKey[] - readonly values: readonly KvValue[] -} + readonly keys: readonly KvKey[]; + readonly values: readonly KvValue[]; +}; export function readKvPutRequest(bc: bare.ByteCursor): KvPutRequest { - return { - keys: read0(bc), - values: read3(bc), - } + return { + keys: read0(bc), + values: read3(bc), + }; } export function writeKvPutRequest(bc: bare.ByteCursor, x: KvPutRequest): void { - write0(bc, x.keys) - write3(bc, x.values) + write0(bc, x.keys); + write3(bc, x.values); } export type KvDeleteRequest = { - readonly keys: readonly KvKey[] -} + readonly keys: readonly KvKey[]; +}; export function readKvDeleteRequest(bc: bare.ByteCursor): KvDeleteRequest { - return { - keys: read0(bc), - } + return { + keys: read0(bc), + }; } -export function writeKvDeleteRequest(bc: bare.ByteCursor, x: KvDeleteRequest): void { - write0(bc, x.keys) +export function writeKvDeleteRequest( + bc: bare.ByteCursor, + x: KvDeleteRequest, +): void { + write0(bc, x.keys); } export type KvDeleteRangeRequest = { - readonly start: KvKey - readonly end: KvKey -} + readonly start: KvKey; + readonly end: KvKey; +}; -export function readKvDeleteRangeRequest(bc: bare.ByteCursor): KvDeleteRangeRequest { - return { - start: readKvKey(bc), - end: readKvKey(bc), - } +export function readKvDeleteRangeRequest( + bc: bare.ByteCursor, +): KvDeleteRangeRequest { + return { + start: readKvKey(bc), + end: readKvKey(bc), + }; } -export function writeKvDeleteRangeRequest(bc: bare.ByteCursor, x: KvDeleteRangeRequest): void { - writeKvKey(bc, x.start) - writeKvKey(bc, x.end) +export function writeKvDeleteRangeRequest( + bc: bare.ByteCursor, + x: KvDeleteRangeRequest, +): void { + writeKvKey(bc, x.start); + writeKvKey(bc, x.end); } -export type KvDropRequest = null +export type KvDropRequest = null; /** * Response types */ export type KvErrorResponse = { - readonly message: string -} + readonly message: string; +}; export function readKvErrorResponse(bc: bare.ByteCursor): KvErrorResponse { - return { - message: bare.readString(bc), - } + return { + message: bare.readString(bc), + }; } -export function writeKvErrorResponse(bc: bare.ByteCursor, x: KvErrorResponse): void { - bare.writeString(bc, x.message) +export function writeKvErrorResponse( + bc: bare.ByteCursor, + x: KvErrorResponse, +): void { + bare.writeString(bc, x.message); } function read4(bc: bare.ByteCursor): readonly KvMetadata[] { - const len = bare.readUintSafe(bc) - if (len === 0) { - return [] - } - const result = [readKvMetadata(bc)] - for (let i = 1; i < len; i++) { - result[i] = readKvMetadata(bc) - } - return result + const len = bare.readUintSafe(bc); + if (len === 0) { + return []; + } + const result = [readKvMetadata(bc)]; + for (let i = 1; i < len; i++) { + result[i] = readKvMetadata(bc); + } + return result; } function write4(bc: bare.ByteCursor, x: readonly KvMetadata[]): void { - bare.writeUintSafe(bc, x.length) - for (let i = 0; i < x.length; i++) { - writeKvMetadata(bc, x[i]) - } + bare.writeUintSafe(bc, x.length); + for (let i = 0; i < x.length; i++) { + writeKvMetadata(bc, x[i]); + } } export type KvGetResponse = { - readonly keys: readonly KvKey[] - readonly values: readonly KvValue[] - readonly metadata: readonly KvMetadata[] -} + readonly keys: readonly KvKey[]; + readonly values: readonly KvValue[]; + readonly metadata: readonly KvMetadata[]; +}; export function readKvGetResponse(bc: bare.ByteCursor): KvGetResponse { - return { - keys: read0(bc), - values: read3(bc), - metadata: read4(bc), - } + return { + keys: read0(bc), + values: read3(bc), + metadata: read4(bc), + }; } -export function writeKvGetResponse(bc: bare.ByteCursor, x: KvGetResponse): void { - write0(bc, x.keys) - write3(bc, x.values) - write4(bc, x.metadata) +export function writeKvGetResponse( + bc: bare.ByteCursor, + x: KvGetResponse, +): void { + write0(bc, x.keys); + write3(bc, x.values); + write4(bc, x.metadata); } export type KvListResponse = { - readonly keys: readonly KvKey[] - readonly values: readonly KvValue[] - readonly metadata: readonly KvMetadata[] -} + readonly keys: readonly KvKey[]; + readonly values: readonly KvValue[]; + readonly metadata: readonly KvMetadata[]; +}; export function readKvListResponse(bc: bare.ByteCursor): KvListResponse { - return { - keys: read0(bc), - values: read3(bc), - metadata: read4(bc), - } + return { + keys: read0(bc), + values: read3(bc), + metadata: read4(bc), + }; } -export function writeKvListResponse(bc: bare.ByteCursor, x: KvListResponse): void { - write0(bc, x.keys) - write3(bc, x.values) - write4(bc, x.metadata) +export function writeKvListResponse( + bc: bare.ByteCursor, + x: KvListResponse, +): void { + write0(bc, x.keys); + write3(bc, x.values); + write4(bc, x.metadata); } -export type KvPutResponse = null +export type KvPutResponse = null; -export type KvDeleteResponse = null +export type KvDeleteResponse = null; -export type KvDropResponse = null +export type KvDropResponse = null; /** * Request/Response unions */ export type KvRequestData = - | { readonly tag: "KvGetRequest"; readonly val: KvGetRequest } - | { readonly tag: "KvListRequest"; readonly val: KvListRequest } - | { readonly tag: "KvPutRequest"; readonly val: KvPutRequest } - | { readonly tag: "KvDeleteRequest"; readonly val: KvDeleteRequest } - | { readonly tag: "KvDeleteRangeRequest"; readonly val: KvDeleteRangeRequest } - | { readonly tag: "KvDropRequest"; readonly val: KvDropRequest } + | { readonly tag: "KvGetRequest"; readonly val: KvGetRequest } + | { readonly tag: "KvListRequest"; readonly val: KvListRequest } + | { readonly tag: "KvPutRequest"; readonly val: KvPutRequest } + | { readonly tag: "KvDeleteRequest"; readonly val: KvDeleteRequest } + | { + readonly tag: "KvDeleteRangeRequest"; + readonly val: KvDeleteRangeRequest; + } + | { readonly tag: "KvDropRequest"; readonly val: KvDropRequest }; export function readKvRequestData(bc: bare.ByteCursor): KvRequestData { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "KvGetRequest", val: readKvGetRequest(bc) } - case 1: - return { tag: "KvListRequest", val: readKvListRequest(bc) } - case 2: - return { tag: "KvPutRequest", val: readKvPutRequest(bc) } - case 3: - return { tag: "KvDeleteRequest", val: readKvDeleteRequest(bc) } - case 4: - return { tag: "KvDeleteRangeRequest", val: readKvDeleteRangeRequest(bc) } - case 5: - return { tag: "KvDropRequest", val: null } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } -} - -export function writeKvRequestData(bc: bare.ByteCursor, x: KvRequestData): void { - switch (x.tag) { - case "KvGetRequest": { - bare.writeU8(bc, 0) - writeKvGetRequest(bc, x.val) - break - } - case "KvListRequest": { - bare.writeU8(bc, 1) - writeKvListRequest(bc, x.val) - break - } - case "KvPutRequest": { - bare.writeU8(bc, 2) - writeKvPutRequest(bc, x.val) - break - } - case "KvDeleteRequest": { - bare.writeU8(bc, 3) - writeKvDeleteRequest(bc, x.val) - break - } - case "KvDeleteRangeRequest": { - bare.writeU8(bc, 4) - writeKvDeleteRangeRequest(bc, x.val) - break - } - case "KvDropRequest": { - bare.writeU8(bc, 5) - break - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "KvGetRequest", val: readKvGetRequest(bc) }; + case 1: + return { tag: "KvListRequest", val: readKvListRequest(bc) }; + case 2: + return { tag: "KvPutRequest", val: readKvPutRequest(bc) }; + case 3: + return { tag: "KvDeleteRequest", val: readKvDeleteRequest(bc) }; + case 4: + return { + tag: "KvDeleteRangeRequest", + val: readKvDeleteRangeRequest(bc), + }; + case 5: + return { tag: "KvDropRequest", val: null }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } +} + +export function writeKvRequestData( + bc: bare.ByteCursor, + x: KvRequestData, +): void { + switch (x.tag) { + case "KvGetRequest": { + bare.writeU8(bc, 0); + writeKvGetRequest(bc, x.val); + break; + } + case "KvListRequest": { + bare.writeU8(bc, 1); + writeKvListRequest(bc, x.val); + break; + } + case "KvPutRequest": { + bare.writeU8(bc, 2); + writeKvPutRequest(bc, x.val); + break; + } + case "KvDeleteRequest": { + bare.writeU8(bc, 3); + writeKvDeleteRequest(bc, x.val); + break; + } + case "KvDeleteRangeRequest": { + bare.writeU8(bc, 4); + writeKvDeleteRangeRequest(bc, x.val); + break; + } + case "KvDropRequest": { + bare.writeU8(bc, 5); + break; + } + } } export type KvResponseData = - | { readonly tag: "KvErrorResponse"; readonly val: KvErrorResponse } - | { readonly tag: "KvGetResponse"; readonly val: KvGetResponse } - | { readonly tag: "KvListResponse"; readonly val: KvListResponse } - | { readonly tag: "KvPutResponse"; readonly val: KvPutResponse } - | { readonly tag: "KvDeleteResponse"; readonly val: KvDeleteResponse } - | { readonly tag: "KvDropResponse"; readonly val: KvDropResponse } + | { readonly tag: "KvErrorResponse"; readonly val: KvErrorResponse } + | { readonly tag: "KvGetResponse"; readonly val: KvGetResponse } + | { readonly tag: "KvListResponse"; readonly val: KvListResponse } + | { readonly tag: "KvPutResponse"; readonly val: KvPutResponse } + | { readonly tag: "KvDeleteResponse"; readonly val: KvDeleteResponse } + | { readonly tag: "KvDropResponse"; readonly val: KvDropResponse }; export function readKvResponseData(bc: bare.ByteCursor): KvResponseData { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "KvErrorResponse", val: readKvErrorResponse(bc) } - case 1: - return { tag: "KvGetResponse", val: readKvGetResponse(bc) } - case 2: - return { tag: "KvListResponse", val: readKvListResponse(bc) } - case 3: - return { tag: "KvPutResponse", val: null } - case 4: - return { tag: "KvDeleteResponse", val: null } - case 5: - return { tag: "KvDropResponse", val: null } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } -} - -export function writeKvResponseData(bc: bare.ByteCursor, x: KvResponseData): void { - switch (x.tag) { - case "KvErrorResponse": { - bare.writeU8(bc, 0) - writeKvErrorResponse(bc, x.val) - break - } - case "KvGetResponse": { - bare.writeU8(bc, 1) - writeKvGetResponse(bc, x.val) - break - } - case "KvListResponse": { - bare.writeU8(bc, 2) - writeKvListResponse(bc, x.val) - break - } - case "KvPutResponse": { - bare.writeU8(bc, 3) - break - } - case "KvDeleteResponse": { - bare.writeU8(bc, 4) - break - } - case "KvDropResponse": { - bare.writeU8(bc, 5) - break - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "KvErrorResponse", val: readKvErrorResponse(bc) }; + case 1: + return { tag: "KvGetResponse", val: readKvGetResponse(bc) }; + case 2: + return { tag: "KvListResponse", val: readKvListResponse(bc) }; + case 3: + return { tag: "KvPutResponse", val: null }; + case 4: + return { tag: "KvDeleteResponse", val: null }; + case 5: + return { tag: "KvDropResponse", val: null }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } +} + +export function writeKvResponseData( + bc: bare.ByteCursor, + x: KvResponseData, +): void { + switch (x.tag) { + case "KvErrorResponse": { + bare.writeU8(bc, 0); + writeKvErrorResponse(bc, x.val); + break; + } + case "KvGetResponse": { + bare.writeU8(bc, 1); + writeKvGetResponse(bc, x.val); + break; + } + case "KvListResponse": { + bare.writeU8(bc, 2); + writeKvListResponse(bc, x.val); + break; + } + case "KvPutResponse": { + bare.writeU8(bc, 3); + break; + } + case "KvDeleteResponse": { + bare.writeU8(bc, 4); + break; + } + case "KvDropResponse": { + bare.writeU8(bc, 5); + break; + } + } } /** * Core */ export enum StopCode { - Ok = "Ok", - Error = "Error", + Ok = "Ok", + Error = "Error", } export function readStopCode(bc: bare.ByteCursor): StopCode { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return StopCode.Ok - case 1: - return StopCode.Error - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return StopCode.Ok; + case 1: + return StopCode.Error; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeStopCode(bc: bare.ByteCursor, x: StopCode): void { - switch (x) { - case StopCode.Ok: { - bare.writeU8(bc, 0) - break - } - case StopCode.Error: { - bare.writeU8(bc, 1) - break - } - } + switch (x) { + case StopCode.Ok: { + bare.writeU8(bc, 0); + break; + } + case StopCode.Error: { + bare.writeU8(bc, 1); + break; + } + } } export type ActorName = { - readonly metadata: Json -} + readonly metadata: Json; +}; export function readActorName(bc: bare.ByteCursor): ActorName { - return { - metadata: readJson(bc), - } + return { + metadata: readJson(bc), + }; } export function writeActorName(bc: bare.ByteCursor, x: ActorName): void { - writeJson(bc, x.metadata) + writeJson(bc, x.metadata); } function read5(bc: bare.ByteCursor): string | null { - return bare.readBool(bc) ? bare.readString(bc) : null + return bare.readBool(bc) ? bare.readString(bc) : null; } function write5(bc: bare.ByteCursor, x: string | null): void { - bare.writeBool(bc, x != null) - if (x != null) { - bare.writeString(bc, x) - } + bare.writeBool(bc, x != null); + if (x != null) { + bare.writeString(bc, x); + } } function read6(bc: bare.ByteCursor): ArrayBuffer | null { - return bare.readBool(bc) ? bare.readData(bc) : null + return bare.readBool(bc) ? bare.readData(bc) : null; } function write6(bc: bare.ByteCursor, x: ArrayBuffer | null): void { - bare.writeBool(bc, x != null) - if (x != null) { - bare.writeData(bc, x) - } + bare.writeBool(bc, x != null); + if (x != null) { + bare.writeData(bc, x); + } } export type ActorConfig = { - readonly name: string - readonly key: string | null - readonly createTs: i64 - readonly input: ArrayBuffer | null -} + readonly name: string; + readonly key: string | null; + readonly createTs: i64; + readonly input: ArrayBuffer | null; +}; export function readActorConfig(bc: bare.ByteCursor): ActorConfig { - return { - name: bare.readString(bc), - key: read5(bc), - createTs: bare.readI64(bc), - input: read6(bc), - } + return { + name: bare.readString(bc), + key: read5(bc), + createTs: bare.readI64(bc), + input: read6(bc), + }; } export function writeActorConfig(bc: bare.ByteCursor, x: ActorConfig): void { - bare.writeString(bc, x.name) - write5(bc, x.key) - bare.writeI64(bc, x.createTs) - write6(bc, x.input) + bare.writeString(bc, x.name); + write5(bc, x.key); + bare.writeI64(bc, x.createTs); + write6(bc, x.input); } export type ActorCheckpoint = { - readonly actorId: Id - readonly generation: u32 - readonly index: i64 -} + readonly actorId: Id; + readonly generation: u32; + readonly index: i64; +}; export function readActorCheckpoint(bc: bare.ByteCursor): ActorCheckpoint { - return { - actorId: readId(bc), - generation: bare.readU32(bc), - index: bare.readI64(bc), - } + return { + actorId: readId(bc), + generation: bare.readU32(bc), + index: bare.readI64(bc), + }; } -export function writeActorCheckpoint(bc: bare.ByteCursor, x: ActorCheckpoint): void { - writeId(bc, x.actorId) - bare.writeU32(bc, x.generation) - bare.writeI64(bc, x.index) +export function writeActorCheckpoint( + bc: bare.ByteCursor, + x: ActorCheckpoint, +): void { + writeId(bc, x.actorId); + bare.writeU32(bc, x.generation); + bare.writeI64(bc, x.index); } /** * Intent */ -export type ActorIntentSleep = null +export type ActorIntentSleep = null; -export type ActorIntentStop = null +export type ActorIntentStop = null; export type ActorIntent = - | { readonly tag: "ActorIntentSleep"; readonly val: ActorIntentSleep } - | { readonly tag: "ActorIntentStop"; readonly val: ActorIntentStop } + | { readonly tag: "ActorIntentSleep"; readonly val: ActorIntentSleep } + | { readonly tag: "ActorIntentStop"; readonly val: ActorIntentStop }; export function readActorIntent(bc: bare.ByteCursor): ActorIntent { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "ActorIntentSleep", val: null } - case 1: - return { tag: "ActorIntentStop", val: null } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "ActorIntentSleep", val: null }; + case 1: + return { tag: "ActorIntentStop", val: null }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeActorIntent(bc: bare.ByteCursor, x: ActorIntent): void { - switch (x.tag) { - case "ActorIntentSleep": { - bare.writeU8(bc, 0) - break - } - case "ActorIntentStop": { - bare.writeU8(bc, 1) - break - } - } + switch (x.tag) { + case "ActorIntentSleep": { + bare.writeU8(bc, 0); + break; + } + case "ActorIntentStop": { + bare.writeU8(bc, 1); + break; + } + } } /** * State */ -export type ActorStateRunning = null +export type ActorStateRunning = null; export type ActorStateStopped = { - readonly code: StopCode - readonly message: string | null -} + readonly code: StopCode; + readonly message: string | null; +}; export function readActorStateStopped(bc: bare.ByteCursor): ActorStateStopped { - return { - code: readStopCode(bc), - message: read5(bc), - } + return { + code: readStopCode(bc), + message: read5(bc), + }; } -export function writeActorStateStopped(bc: bare.ByteCursor, x: ActorStateStopped): void { - writeStopCode(bc, x.code) - write5(bc, x.message) +export function writeActorStateStopped( + bc: bare.ByteCursor, + x: ActorStateStopped, +): void { + writeStopCode(bc, x.code); + write5(bc, x.message); } export type ActorState = - | { readonly tag: "ActorStateRunning"; readonly val: ActorStateRunning } - | { readonly tag: "ActorStateStopped"; readonly val: ActorStateStopped } + | { readonly tag: "ActorStateRunning"; readonly val: ActorStateRunning } + | { readonly tag: "ActorStateStopped"; readonly val: ActorStateStopped }; export function readActorState(bc: bare.ByteCursor): ActorState { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "ActorStateRunning", val: null } - case 1: - return { tag: "ActorStateStopped", val: readActorStateStopped(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "ActorStateRunning", val: null }; + case 1: + return { tag: "ActorStateStopped", val: readActorStateStopped(bc) }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeActorState(bc: bare.ByteCursor, x: ActorState): void { - switch (x.tag) { - case "ActorStateRunning": { - bare.writeU8(bc, 0) - break - } - case "ActorStateStopped": { - bare.writeU8(bc, 1) - writeActorStateStopped(bc, x.val) - break - } - } + switch (x.tag) { + case "ActorStateRunning": { + bare.writeU8(bc, 0); + break; + } + case "ActorStateStopped": { + bare.writeU8(bc, 1); + writeActorStateStopped(bc, x.val); + break; + } + } } /** * MARK: Events */ export type EventActorIntent = { - readonly intent: ActorIntent -} + readonly intent: ActorIntent; +}; export function readEventActorIntent(bc: bare.ByteCursor): EventActorIntent { - return { - intent: readActorIntent(bc), - } + return { + intent: readActorIntent(bc), + }; } -export function writeEventActorIntent(bc: bare.ByteCursor, x: EventActorIntent): void { - writeActorIntent(bc, x.intent) +export function writeEventActorIntent( + bc: bare.ByteCursor, + x: EventActorIntent, +): void { + writeActorIntent(bc, x.intent); } export type EventActorStateUpdate = { - readonly state: ActorState -} + readonly state: ActorState; +}; -export function readEventActorStateUpdate(bc: bare.ByteCursor): EventActorStateUpdate { - return { - state: readActorState(bc), - } +export function readEventActorStateUpdate( + bc: bare.ByteCursor, +): EventActorStateUpdate { + return { + state: readActorState(bc), + }; } -export function writeEventActorStateUpdate(bc: bare.ByteCursor, x: EventActorStateUpdate): void { - writeActorState(bc, x.state) +export function writeEventActorStateUpdate( + bc: bare.ByteCursor, + x: EventActorStateUpdate, +): void { + writeActorState(bc, x.state); } function read7(bc: bare.ByteCursor): i64 | null { - return bare.readBool(bc) ? bare.readI64(bc) : null + return bare.readBool(bc) ? bare.readI64(bc) : null; } function write7(bc: bare.ByteCursor, x: i64 | null): void { - bare.writeBool(bc, x != null) - if (x != null) { - bare.writeI64(bc, x) - } + bare.writeBool(bc, x != null); + if (x != null) { + bare.writeI64(bc, x); + } } export type EventActorSetAlarm = { - readonly alarmTs: i64 | null -} + readonly alarmTs: i64 | null; +}; -export function readEventActorSetAlarm(bc: bare.ByteCursor): EventActorSetAlarm { - return { - alarmTs: read7(bc), - } +export function readEventActorSetAlarm( + bc: bare.ByteCursor, +): EventActorSetAlarm { + return { + alarmTs: read7(bc), + }; } -export function writeEventActorSetAlarm(bc: bare.ByteCursor, x: EventActorSetAlarm): void { - write7(bc, x.alarmTs) +export function writeEventActorSetAlarm( + bc: bare.ByteCursor, + x: EventActorSetAlarm, +): void { + write7(bc, x.alarmTs); } export type Event = - | { readonly tag: "EventActorIntent"; readonly val: EventActorIntent } - | { readonly tag: "EventActorStateUpdate"; readonly val: EventActorStateUpdate } - | { readonly tag: "EventActorSetAlarm"; readonly val: EventActorSetAlarm } + | { readonly tag: "EventActorIntent"; readonly val: EventActorIntent } + | { + readonly tag: "EventActorStateUpdate"; + readonly val: EventActorStateUpdate; + } + | { readonly tag: "EventActorSetAlarm"; readonly val: EventActorSetAlarm }; export function readEvent(bc: bare.ByteCursor): Event { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "EventActorIntent", val: readEventActorIntent(bc) } - case 1: - return { tag: "EventActorStateUpdate", val: readEventActorStateUpdate(bc) } - case 2: - return { tag: "EventActorSetAlarm", val: readEventActorSetAlarm(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "EventActorIntent", val: readEventActorIntent(bc) }; + case 1: + return { + tag: "EventActorStateUpdate", + val: readEventActorStateUpdate(bc), + }; + case 2: + return { + tag: "EventActorSetAlarm", + val: readEventActorSetAlarm(bc), + }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeEvent(bc: bare.ByteCursor, x: Event): void { - switch (x.tag) { - case "EventActorIntent": { - bare.writeU8(bc, 0) - writeEventActorIntent(bc, x.val) - break - } - case "EventActorStateUpdate": { - bare.writeU8(bc, 1) - writeEventActorStateUpdate(bc, x.val) - break - } - case "EventActorSetAlarm": { - bare.writeU8(bc, 2) - writeEventActorSetAlarm(bc, x.val) - break - } - } + switch (x.tag) { + case "EventActorIntent": { + bare.writeU8(bc, 0); + writeEventActorIntent(bc, x.val); + break; + } + case "EventActorStateUpdate": { + bare.writeU8(bc, 1); + writeEventActorStateUpdate(bc, x.val); + break; + } + case "EventActorSetAlarm": { + bare.writeU8(bc, 2); + writeEventActorSetAlarm(bc, x.val); + break; + } + } } export type EventWrapper = { - readonly checkpoint: ActorCheckpoint - readonly inner: Event -} + readonly checkpoint: ActorCheckpoint; + readonly inner: Event; +}; export function readEventWrapper(bc: bare.ByteCursor): EventWrapper { - return { - checkpoint: readActorCheckpoint(bc), - inner: readEvent(bc), - } + return { + checkpoint: readActorCheckpoint(bc), + inner: readEvent(bc), + }; } export function writeEventWrapper(bc: bare.ByteCursor, x: EventWrapper): void { - writeActorCheckpoint(bc, x.checkpoint) - writeEvent(bc, x.inner) + writeActorCheckpoint(bc, x.checkpoint); + writeEvent(bc, x.inner); } export type HibernatingRequest = { - readonly gatewayId: GatewayId - readonly requestId: RequestId -} + readonly gatewayId: GatewayId; + readonly requestId: RequestId; +}; -export function readHibernatingRequest(bc: bare.ByteCursor): HibernatingRequest { - return { - gatewayId: readGatewayId(bc), - requestId: readRequestId(bc), - } +export function readHibernatingRequest( + bc: bare.ByteCursor, +): HibernatingRequest { + return { + gatewayId: readGatewayId(bc), + requestId: readRequestId(bc), + }; } -export function writeHibernatingRequest(bc: bare.ByteCursor, x: HibernatingRequest): void { - writeGatewayId(bc, x.gatewayId) - writeRequestId(bc, x.requestId) +export function writeHibernatingRequest( + bc: bare.ByteCursor, + x: HibernatingRequest, +): void { + writeGatewayId(bc, x.gatewayId); + writeRequestId(bc, x.requestId); } function read8(bc: bare.ByteCursor): readonly HibernatingRequest[] { - const len = bare.readUintSafe(bc) - if (len === 0) { - return [] - } - const result = [readHibernatingRequest(bc)] - for (let i = 1; i < len; i++) { - result[i] = readHibernatingRequest(bc) - } - return result + const len = bare.readUintSafe(bc); + if (len === 0) { + return []; + } + const result = [readHibernatingRequest(bc)]; + for (let i = 1; i < len; i++) { + result[i] = readHibernatingRequest(bc); + } + return result; } function write8(bc: bare.ByteCursor, x: readonly HibernatingRequest[]): void { - bare.writeUintSafe(bc, x.length) - for (let i = 0; i < x.length; i++) { - writeHibernatingRequest(bc, x[i]) - } + bare.writeUintSafe(bc, x.length); + for (let i = 0; i < x.length; i++) { + writeHibernatingRequest(bc, x[i]); + } } export type CommandStartActor = { - readonly config: ActorConfig - readonly hibernatingRequests: readonly HibernatingRequest[] -} + readonly config: ActorConfig; + readonly hibernatingRequests: readonly HibernatingRequest[]; +}; export function readCommandStartActor(bc: bare.ByteCursor): CommandStartActor { - return { - config: readActorConfig(bc), - hibernatingRequests: read8(bc), - } + return { + config: readActorConfig(bc), + hibernatingRequests: read8(bc), + }; } -export function writeCommandStartActor(bc: bare.ByteCursor, x: CommandStartActor): void { - writeActorConfig(bc, x.config) - write8(bc, x.hibernatingRequests) +export function writeCommandStartActor( + bc: bare.ByteCursor, + x: CommandStartActor, +): void { + writeActorConfig(bc, x.config); + write8(bc, x.hibernatingRequests); } -export type CommandStopActor = null +export type CommandStopActor = null; export type Command = - | { readonly tag: "CommandStartActor"; readonly val: CommandStartActor } - | { readonly tag: "CommandStopActor"; readonly val: CommandStopActor } + | { readonly tag: "CommandStartActor"; readonly val: CommandStartActor } + | { readonly tag: "CommandStopActor"; readonly val: CommandStopActor }; export function readCommand(bc: bare.ByteCursor): Command { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "CommandStartActor", val: readCommandStartActor(bc) } - case 1: - return { tag: "CommandStopActor", val: null } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "CommandStartActor", val: readCommandStartActor(bc) }; + case 1: + return { tag: "CommandStopActor", val: null }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeCommand(bc: bare.ByteCursor, x: Command): void { - switch (x.tag) { - case "CommandStartActor": { - bare.writeU8(bc, 0) - writeCommandStartActor(bc, x.val) - break - } - case "CommandStopActor": { - bare.writeU8(bc, 1) - break - } - } + switch (x.tag) { + case "CommandStartActor": { + bare.writeU8(bc, 0); + writeCommandStartActor(bc, x.val); + break; + } + case "CommandStopActor": { + bare.writeU8(bc, 1); + break; + } + } } export type CommandWrapper = { - readonly checkpoint: ActorCheckpoint - readonly inner: Command -} + readonly checkpoint: ActorCheckpoint; + readonly inner: Command; +}; export function readCommandWrapper(bc: bare.ByteCursor): CommandWrapper { - return { - checkpoint: readActorCheckpoint(bc), - inner: readCommand(bc), - } + return { + checkpoint: readActorCheckpoint(bc), + inner: readCommand(bc), + }; } -export function writeCommandWrapper(bc: bare.ByteCursor, x: CommandWrapper): void { - writeActorCheckpoint(bc, x.checkpoint) - writeCommand(bc, x.inner) +export function writeCommandWrapper( + bc: bare.ByteCursor, + x: CommandWrapper, +): void { + writeActorCheckpoint(bc, x.checkpoint); + writeCommand(bc, x.inner); } /** * We redeclare this so its top level */ export type ActorCommandKeyData = - | { readonly tag: "CommandStartActor"; readonly val: CommandStartActor } - | { readonly tag: "CommandStopActor"; readonly val: CommandStopActor } - -export function readActorCommandKeyData(bc: bare.ByteCursor): ActorCommandKeyData { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "CommandStartActor", val: readCommandStartActor(bc) } - case 1: - return { tag: "CommandStopActor", val: null } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } -} - -export function writeActorCommandKeyData(bc: bare.ByteCursor, x: ActorCommandKeyData): void { - switch (x.tag) { - case "CommandStartActor": { - bare.writeU8(bc, 0) - writeCommandStartActor(bc, x.val) - break - } - case "CommandStopActor": { - bare.writeU8(bc, 1) - break - } - } -} - -export function encodeActorCommandKeyData(x: ActorCommandKeyData, config?: Partial): Uint8Array { - const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG - const bc = new bare.ByteCursor( - new Uint8Array(fullConfig.initialBufferLength), - fullConfig, - ) - writeActorCommandKeyData(bc, x) - return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset) -} - -export function decodeActorCommandKeyData(bytes: Uint8Array): ActorCommandKeyData { - const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG) - const result = readActorCommandKeyData(bc) - if (bc.offset < bc.view.byteLength) { - throw new bare.BareError(bc.offset, "remaining bytes") - } - return result + | { readonly tag: "CommandStartActor"; readonly val: CommandStartActor } + | { readonly tag: "CommandStopActor"; readonly val: CommandStopActor }; + +export function readActorCommandKeyData( + bc: bare.ByteCursor, +): ActorCommandKeyData { + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "CommandStartActor", val: readCommandStartActor(bc) }; + case 1: + return { tag: "CommandStopActor", val: null }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } +} + +export function writeActorCommandKeyData( + bc: bare.ByteCursor, + x: ActorCommandKeyData, +): void { + switch (x.tag) { + case "CommandStartActor": { + bare.writeU8(bc, 0); + writeCommandStartActor(bc, x.val); + break; + } + case "CommandStopActor": { + bare.writeU8(bc, 1); + break; + } + } +} + +export function encodeActorCommandKeyData( + x: ActorCommandKeyData, + config?: Partial, +): Uint8Array { + const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG; + const bc = new bare.ByteCursor( + new Uint8Array(fullConfig.initialBufferLength), + fullConfig, + ); + writeActorCommandKeyData(bc, x); + return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset); +} + +export function decodeActorCommandKeyData( + bytes: Uint8Array, +): ActorCommandKeyData { + const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG); + const result = readActorCommandKeyData(bc); + if (bc.offset < bc.view.byteLength) { + throw new bare.BareError(bc.offset, "remaining bytes"); + } + return result; } export type MessageId = { - /** - * Globally unique ID - */ - readonly gatewayId: GatewayId - /** - * Unique ID to the gateway - */ - readonly requestId: RequestId - /** - * Unique ID to the request - */ - readonly messageIndex: MessageIndex -} + /** + * Globally unique ID + */ + readonly gatewayId: GatewayId; + /** + * Unique ID to the gateway + */ + readonly requestId: RequestId; + /** + * Unique ID to the request + */ + readonly messageIndex: MessageIndex; +}; export function readMessageId(bc: bare.ByteCursor): MessageId { - return { - gatewayId: readGatewayId(bc), - requestId: readRequestId(bc), - messageIndex: readMessageIndex(bc), - } + return { + gatewayId: readGatewayId(bc), + requestId: readRequestId(bc), + messageIndex: readMessageIndex(bc), + }; } export function writeMessageId(bc: bare.ByteCursor, x: MessageId): void { - writeGatewayId(bc, x.gatewayId) - writeRequestId(bc, x.requestId) - writeMessageIndex(bc, x.messageIndex) + writeGatewayId(bc, x.gatewayId); + writeRequestId(bc, x.requestId); + writeMessageIndex(bc, x.messageIndex); } function read9(bc: bare.ByteCursor): ReadonlyMap { - const len = bare.readUintSafe(bc) - const result = new Map() - for (let i = 0; i < len; i++) { - const offset = bc.offset - const key = bare.readString(bc) - if (result.has(key)) { - bc.offset = offset - throw new bare.BareError(offset, "duplicated key") - } - result.set(key, bare.readString(bc)) - } - return result + const len = bare.readUintSafe(bc); + const result = new Map(); + for (let i = 0; i < len; i++) { + const offset = bc.offset; + const key = bare.readString(bc); + if (result.has(key)) { + bc.offset = offset; + throw new bare.BareError(offset, "duplicated key"); + } + result.set(key, bare.readString(bc)); + } + return result; } function write9(bc: bare.ByteCursor, x: ReadonlyMap): void { - bare.writeUintSafe(bc, x.size) - for (const kv of x) { - bare.writeString(bc, kv[0]) - bare.writeString(bc, kv[1]) - } + bare.writeUintSafe(bc, x.size); + for (const kv of x) { + bare.writeString(bc, kv[0]); + bare.writeString(bc, kv[1]); + } } /** * HTTP */ export type ToClientRequestStart = { - readonly actorId: Id - readonly method: string - readonly path: string - readonly headers: ReadonlyMap - readonly body: ArrayBuffer | null - readonly stream: boolean -} - -export function readToClientRequestStart(bc: bare.ByteCursor): ToClientRequestStart { - return { - actorId: readId(bc), - method: bare.readString(bc), - path: bare.readString(bc), - headers: read9(bc), - body: read6(bc), - stream: bare.readBool(bc), - } -} - -export function writeToClientRequestStart(bc: bare.ByteCursor, x: ToClientRequestStart): void { - writeId(bc, x.actorId) - bare.writeString(bc, x.method) - bare.writeString(bc, x.path) - write9(bc, x.headers) - write6(bc, x.body) - bare.writeBool(bc, x.stream) + readonly actorId: Id; + readonly method: string; + readonly path: string; + readonly headers: ReadonlyMap; + readonly body: ArrayBuffer | null; + readonly stream: boolean; +}; + +export function readToClientRequestStart( + bc: bare.ByteCursor, +): ToClientRequestStart { + return { + actorId: readId(bc), + method: bare.readString(bc), + path: bare.readString(bc), + headers: read9(bc), + body: read6(bc), + stream: bare.readBool(bc), + }; +} + +export function writeToClientRequestStart( + bc: bare.ByteCursor, + x: ToClientRequestStart, +): void { + writeId(bc, x.actorId); + bare.writeString(bc, x.method); + bare.writeString(bc, x.path); + write9(bc, x.headers); + write6(bc, x.body); + bare.writeBool(bc, x.stream); } export type ToClientRequestChunk = { - readonly body: ArrayBuffer - readonly finish: boolean -} + readonly body: ArrayBuffer; + readonly finish: boolean; +}; -export function readToClientRequestChunk(bc: bare.ByteCursor): ToClientRequestChunk { - return { - body: bare.readData(bc), - finish: bare.readBool(bc), - } +export function readToClientRequestChunk( + bc: bare.ByteCursor, +): ToClientRequestChunk { + return { + body: bare.readData(bc), + finish: bare.readBool(bc), + }; } -export function writeToClientRequestChunk(bc: bare.ByteCursor, x: ToClientRequestChunk): void { - bare.writeData(bc, x.body) - bare.writeBool(bc, x.finish) +export function writeToClientRequestChunk( + bc: bare.ByteCursor, + x: ToClientRequestChunk, +): void { + bare.writeData(bc, x.body); + bare.writeBool(bc, x.finish); } -export type ToClientRequestAbort = null +export type ToClientRequestAbort = null; export type ToServerResponseStart = { - readonly status: u16 - readonly headers: ReadonlyMap - readonly body: ArrayBuffer | null - readonly stream: boolean -} - -export function readToServerResponseStart(bc: bare.ByteCursor): ToServerResponseStart { - return { - status: bare.readU16(bc), - headers: read9(bc), - body: read6(bc), - stream: bare.readBool(bc), - } -} - -export function writeToServerResponseStart(bc: bare.ByteCursor, x: ToServerResponseStart): void { - bare.writeU16(bc, x.status) - write9(bc, x.headers) - write6(bc, x.body) - bare.writeBool(bc, x.stream) + readonly status: u16; + readonly headers: ReadonlyMap; + readonly body: ArrayBuffer | null; + readonly stream: boolean; +}; + +export function readToServerResponseStart( + bc: bare.ByteCursor, +): ToServerResponseStart { + return { + status: bare.readU16(bc), + headers: read9(bc), + body: read6(bc), + stream: bare.readBool(bc), + }; +} + +export function writeToServerResponseStart( + bc: bare.ByteCursor, + x: ToServerResponseStart, +): void { + bare.writeU16(bc, x.status); + write9(bc, x.headers); + write6(bc, x.body); + bare.writeBool(bc, x.stream); } export type ToServerResponseChunk = { - readonly body: ArrayBuffer - readonly finish: boolean -} + readonly body: ArrayBuffer; + readonly finish: boolean; +}; -export function readToServerResponseChunk(bc: bare.ByteCursor): ToServerResponseChunk { - return { - body: bare.readData(bc), - finish: bare.readBool(bc), - } +export function readToServerResponseChunk( + bc: bare.ByteCursor, +): ToServerResponseChunk { + return { + body: bare.readData(bc), + finish: bare.readBool(bc), + }; } -export function writeToServerResponseChunk(bc: bare.ByteCursor, x: ToServerResponseChunk): void { - bare.writeData(bc, x.body) - bare.writeBool(bc, x.finish) +export function writeToServerResponseChunk( + bc: bare.ByteCursor, + x: ToServerResponseChunk, +): void { + bare.writeData(bc, x.body); + bare.writeBool(bc, x.finish); } -export type ToServerResponseAbort = null +export type ToServerResponseAbort = null; /** * WebSocket */ export type ToClientWebSocketOpen = { - readonly actorId: Id - readonly path: string - readonly headers: ReadonlyMap -} - -export function readToClientWebSocketOpen(bc: bare.ByteCursor): ToClientWebSocketOpen { - return { - actorId: readId(bc), - path: bare.readString(bc), - headers: read9(bc), - } -} - -export function writeToClientWebSocketOpen(bc: bare.ByteCursor, x: ToClientWebSocketOpen): void { - writeId(bc, x.actorId) - bare.writeString(bc, x.path) - write9(bc, x.headers) + readonly actorId: Id; + readonly path: string; + readonly headers: ReadonlyMap; +}; + +export function readToClientWebSocketOpen( + bc: bare.ByteCursor, +): ToClientWebSocketOpen { + return { + actorId: readId(bc), + path: bare.readString(bc), + headers: read9(bc), + }; +} + +export function writeToClientWebSocketOpen( + bc: bare.ByteCursor, + x: ToClientWebSocketOpen, +): void { + writeId(bc, x.actorId); + bare.writeString(bc, x.path); + write9(bc, x.headers); } export type ToClientWebSocketMessage = { - readonly data: ArrayBuffer - readonly binary: boolean -} + readonly data: ArrayBuffer; + readonly binary: boolean; +}; -export function readToClientWebSocketMessage(bc: bare.ByteCursor): ToClientWebSocketMessage { - return { - data: bare.readData(bc), - binary: bare.readBool(bc), - } +export function readToClientWebSocketMessage( + bc: bare.ByteCursor, +): ToClientWebSocketMessage { + return { + data: bare.readData(bc), + binary: bare.readBool(bc), + }; } -export function writeToClientWebSocketMessage(bc: bare.ByteCursor, x: ToClientWebSocketMessage): void { - bare.writeData(bc, x.data) - bare.writeBool(bc, x.binary) +export function writeToClientWebSocketMessage( + bc: bare.ByteCursor, + x: ToClientWebSocketMessage, +): void { + bare.writeData(bc, x.data); + bare.writeBool(bc, x.binary); } function read10(bc: bare.ByteCursor): u16 | null { - return bare.readBool(bc) ? bare.readU16(bc) : null + return bare.readBool(bc) ? bare.readU16(bc) : null; } function write10(bc: bare.ByteCursor, x: u16 | null): void { - bare.writeBool(bc, x != null) - if (x != null) { - bare.writeU16(bc, x) - } + bare.writeBool(bc, x != null); + if (x != null) { + bare.writeU16(bc, x); + } } export type ToClientWebSocketClose = { - readonly code: u16 | null - readonly reason: string | null -} + readonly code: u16 | null; + readonly reason: string | null; +}; -export function readToClientWebSocketClose(bc: bare.ByteCursor): ToClientWebSocketClose { - return { - code: read10(bc), - reason: read5(bc), - } +export function readToClientWebSocketClose( + bc: bare.ByteCursor, +): ToClientWebSocketClose { + return { + code: read10(bc), + reason: read5(bc), + }; } -export function writeToClientWebSocketClose(bc: bare.ByteCursor, x: ToClientWebSocketClose): void { - write10(bc, x.code) - write5(bc, x.reason) +export function writeToClientWebSocketClose( + bc: bare.ByteCursor, + x: ToClientWebSocketClose, +): void { + write10(bc, x.code); + write5(bc, x.reason); } export type ToServerWebSocketOpen = { - readonly canHibernate: boolean -} + readonly canHibernate: boolean; +}; -export function readToServerWebSocketOpen(bc: bare.ByteCursor): ToServerWebSocketOpen { - return { - canHibernate: bare.readBool(bc), - } +export function readToServerWebSocketOpen( + bc: bare.ByteCursor, +): ToServerWebSocketOpen { + return { + canHibernate: bare.readBool(bc), + }; } -export function writeToServerWebSocketOpen(bc: bare.ByteCursor, x: ToServerWebSocketOpen): void { - bare.writeBool(bc, x.canHibernate) +export function writeToServerWebSocketOpen( + bc: bare.ByteCursor, + x: ToServerWebSocketOpen, +): void { + bare.writeBool(bc, x.canHibernate); } export type ToServerWebSocketMessage = { - readonly data: ArrayBuffer - readonly binary: boolean -} + readonly data: ArrayBuffer; + readonly binary: boolean; +}; -export function readToServerWebSocketMessage(bc: bare.ByteCursor): ToServerWebSocketMessage { - return { - data: bare.readData(bc), - binary: bare.readBool(bc), - } +export function readToServerWebSocketMessage( + bc: bare.ByteCursor, +): ToServerWebSocketMessage { + return { + data: bare.readData(bc), + binary: bare.readBool(bc), + }; } -export function writeToServerWebSocketMessage(bc: bare.ByteCursor, x: ToServerWebSocketMessage): void { - bare.writeData(bc, x.data) - bare.writeBool(bc, x.binary) +export function writeToServerWebSocketMessage( + bc: bare.ByteCursor, + x: ToServerWebSocketMessage, +): void { + bare.writeData(bc, x.data); + bare.writeBool(bc, x.binary); } export type ToServerWebSocketMessageAck = { - readonly index: MessageIndex -} + readonly index: MessageIndex; +}; -export function readToServerWebSocketMessageAck(bc: bare.ByteCursor): ToServerWebSocketMessageAck { - return { - index: readMessageIndex(bc), - } +export function readToServerWebSocketMessageAck( + bc: bare.ByteCursor, +): ToServerWebSocketMessageAck { + return { + index: readMessageIndex(bc), + }; } -export function writeToServerWebSocketMessageAck(bc: bare.ByteCursor, x: ToServerWebSocketMessageAck): void { - writeMessageIndex(bc, x.index) +export function writeToServerWebSocketMessageAck( + bc: bare.ByteCursor, + x: ToServerWebSocketMessageAck, +): void { + writeMessageIndex(bc, x.index); } export type ToServerWebSocketClose = { - readonly code: u16 | null - readonly reason: string | null - readonly hibernate: boolean -} - -export function readToServerWebSocketClose(bc: bare.ByteCursor): ToServerWebSocketClose { - return { - code: read10(bc), - reason: read5(bc), - hibernate: bare.readBool(bc), - } -} - -export function writeToServerWebSocketClose(bc: bare.ByteCursor, x: ToServerWebSocketClose): void { - write10(bc, x.code) - write5(bc, x.reason) - bare.writeBool(bc, x.hibernate) + readonly code: u16 | null; + readonly reason: string | null; + readonly hibernate: boolean; +}; + +export function readToServerWebSocketClose( + bc: bare.ByteCursor, +): ToServerWebSocketClose { + return { + code: read10(bc), + reason: read5(bc), + hibernate: bare.readBool(bc), + }; +} + +export function writeToServerWebSocketClose( + bc: bare.ByteCursor, + x: ToServerWebSocketClose, +): void { + write10(bc, x.code); + write5(bc, x.reason); + bare.writeBool(bc, x.hibernate); } /** * To Server */ export type ToServerTunnelMessageKind = - /** - * HTTP - */ - | { readonly tag: "ToServerResponseStart"; readonly val: ToServerResponseStart } - | { readonly tag: "ToServerResponseChunk"; readonly val: ToServerResponseChunk } - | { readonly tag: "ToServerResponseAbort"; readonly val: ToServerResponseAbort } - /** - * WebSocket - */ - | { readonly tag: "ToServerWebSocketOpen"; readonly val: ToServerWebSocketOpen } - | { readonly tag: "ToServerWebSocketMessage"; readonly val: ToServerWebSocketMessage } - | { readonly tag: "ToServerWebSocketMessageAck"; readonly val: ToServerWebSocketMessageAck } - | { readonly tag: "ToServerWebSocketClose"; readonly val: ToServerWebSocketClose } - -export function readToServerTunnelMessageKind(bc: bare.ByteCursor): ToServerTunnelMessageKind { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "ToServerResponseStart", val: readToServerResponseStart(bc) } - case 1: - return { tag: "ToServerResponseChunk", val: readToServerResponseChunk(bc) } - case 2: - return { tag: "ToServerResponseAbort", val: null } - case 3: - return { tag: "ToServerWebSocketOpen", val: readToServerWebSocketOpen(bc) } - case 4: - return { tag: "ToServerWebSocketMessage", val: readToServerWebSocketMessage(bc) } - case 5: - return { tag: "ToServerWebSocketMessageAck", val: readToServerWebSocketMessageAck(bc) } - case 6: - return { tag: "ToServerWebSocketClose", val: readToServerWebSocketClose(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } -} - -export function writeToServerTunnelMessageKind(bc: bare.ByteCursor, x: ToServerTunnelMessageKind): void { - switch (x.tag) { - case "ToServerResponseStart": { - bare.writeU8(bc, 0) - writeToServerResponseStart(bc, x.val) - break - } - case "ToServerResponseChunk": { - bare.writeU8(bc, 1) - writeToServerResponseChunk(bc, x.val) - break - } - case "ToServerResponseAbort": { - bare.writeU8(bc, 2) - break - } - case "ToServerWebSocketOpen": { - bare.writeU8(bc, 3) - writeToServerWebSocketOpen(bc, x.val) - break - } - case "ToServerWebSocketMessage": { - bare.writeU8(bc, 4) - writeToServerWebSocketMessage(bc, x.val) - break - } - case "ToServerWebSocketMessageAck": { - bare.writeU8(bc, 5) - writeToServerWebSocketMessageAck(bc, x.val) - break - } - case "ToServerWebSocketClose": { - bare.writeU8(bc, 6) - writeToServerWebSocketClose(bc, x.val) - break - } - } + /** + * HTTP + */ + | { + readonly tag: "ToServerResponseStart"; + readonly val: ToServerResponseStart; + } + | { + readonly tag: "ToServerResponseChunk"; + readonly val: ToServerResponseChunk; + } + | { + readonly tag: "ToServerResponseAbort"; + readonly val: ToServerResponseAbort; + } + /** + * WebSocket + */ + | { + readonly tag: "ToServerWebSocketOpen"; + readonly val: ToServerWebSocketOpen; + } + | { + readonly tag: "ToServerWebSocketMessage"; + readonly val: ToServerWebSocketMessage; + } + | { + readonly tag: "ToServerWebSocketMessageAck"; + readonly val: ToServerWebSocketMessageAck; + } + | { + readonly tag: "ToServerWebSocketClose"; + readonly val: ToServerWebSocketClose; + }; + +export function readToServerTunnelMessageKind( + bc: bare.ByteCursor, +): ToServerTunnelMessageKind { + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { + tag: "ToServerResponseStart", + val: readToServerResponseStart(bc), + }; + case 1: + return { + tag: "ToServerResponseChunk", + val: readToServerResponseChunk(bc), + }; + case 2: + return { tag: "ToServerResponseAbort", val: null }; + case 3: + return { + tag: "ToServerWebSocketOpen", + val: readToServerWebSocketOpen(bc), + }; + case 4: + return { + tag: "ToServerWebSocketMessage", + val: readToServerWebSocketMessage(bc), + }; + case 5: + return { + tag: "ToServerWebSocketMessageAck", + val: readToServerWebSocketMessageAck(bc), + }; + case 6: + return { + tag: "ToServerWebSocketClose", + val: readToServerWebSocketClose(bc), + }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } +} + +export function writeToServerTunnelMessageKind( + bc: bare.ByteCursor, + x: ToServerTunnelMessageKind, +): void { + switch (x.tag) { + case "ToServerResponseStart": { + bare.writeU8(bc, 0); + writeToServerResponseStart(bc, x.val); + break; + } + case "ToServerResponseChunk": { + bare.writeU8(bc, 1); + writeToServerResponseChunk(bc, x.val); + break; + } + case "ToServerResponseAbort": { + bare.writeU8(bc, 2); + break; + } + case "ToServerWebSocketOpen": { + bare.writeU8(bc, 3); + writeToServerWebSocketOpen(bc, x.val); + break; + } + case "ToServerWebSocketMessage": { + bare.writeU8(bc, 4); + writeToServerWebSocketMessage(bc, x.val); + break; + } + case "ToServerWebSocketMessageAck": { + bare.writeU8(bc, 5); + writeToServerWebSocketMessageAck(bc, x.val); + break; + } + case "ToServerWebSocketClose": { + bare.writeU8(bc, 6); + writeToServerWebSocketClose(bc, x.val); + break; + } + } } export type ToServerTunnelMessage = { - readonly messageId: MessageId - readonly messageKind: ToServerTunnelMessageKind -} + readonly messageId: MessageId; + readonly messageKind: ToServerTunnelMessageKind; +}; -export function readToServerTunnelMessage(bc: bare.ByteCursor): ToServerTunnelMessage { - return { - messageId: readMessageId(bc), - messageKind: readToServerTunnelMessageKind(bc), - } +export function readToServerTunnelMessage( + bc: bare.ByteCursor, +): ToServerTunnelMessage { + return { + messageId: readMessageId(bc), + messageKind: readToServerTunnelMessageKind(bc), + }; } -export function writeToServerTunnelMessage(bc: bare.ByteCursor, x: ToServerTunnelMessage): void { - writeMessageId(bc, x.messageId) - writeToServerTunnelMessageKind(bc, x.messageKind) +export function writeToServerTunnelMessage( + bc: bare.ByteCursor, + x: ToServerTunnelMessage, +): void { + writeMessageId(bc, x.messageId); + writeToServerTunnelMessageKind(bc, x.messageKind); } /** * To Client */ export type ToClientTunnelMessageKind = - /** - * HTTP - */ - | { readonly tag: "ToClientRequestStart"; readonly val: ToClientRequestStart } - | { readonly tag: "ToClientRequestChunk"; readonly val: ToClientRequestChunk } - | { readonly tag: "ToClientRequestAbort"; readonly val: ToClientRequestAbort } - /** - * WebSocket - */ - | { readonly tag: "ToClientWebSocketOpen"; readonly val: ToClientWebSocketOpen } - | { readonly tag: "ToClientWebSocketMessage"; readonly val: ToClientWebSocketMessage } - | { readonly tag: "ToClientWebSocketClose"; readonly val: ToClientWebSocketClose } - -export function readToClientTunnelMessageKind(bc: bare.ByteCursor): ToClientTunnelMessageKind { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "ToClientRequestStart", val: readToClientRequestStart(bc) } - case 1: - return { tag: "ToClientRequestChunk", val: readToClientRequestChunk(bc) } - case 2: - return { tag: "ToClientRequestAbort", val: null } - case 3: - return { tag: "ToClientWebSocketOpen", val: readToClientWebSocketOpen(bc) } - case 4: - return { tag: "ToClientWebSocketMessage", val: readToClientWebSocketMessage(bc) } - case 5: - return { tag: "ToClientWebSocketClose", val: readToClientWebSocketClose(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } -} - -export function writeToClientTunnelMessageKind(bc: bare.ByteCursor, x: ToClientTunnelMessageKind): void { - switch (x.tag) { - case "ToClientRequestStart": { - bare.writeU8(bc, 0) - writeToClientRequestStart(bc, x.val) - break - } - case "ToClientRequestChunk": { - bare.writeU8(bc, 1) - writeToClientRequestChunk(bc, x.val) - break - } - case "ToClientRequestAbort": { - bare.writeU8(bc, 2) - break - } - case "ToClientWebSocketOpen": { - bare.writeU8(bc, 3) - writeToClientWebSocketOpen(bc, x.val) - break - } - case "ToClientWebSocketMessage": { - bare.writeU8(bc, 4) - writeToClientWebSocketMessage(bc, x.val) - break - } - case "ToClientWebSocketClose": { - bare.writeU8(bc, 5) - writeToClientWebSocketClose(bc, x.val) - break - } - } + /** + * HTTP + */ + | { + readonly tag: "ToClientRequestStart"; + readonly val: ToClientRequestStart; + } + | { + readonly tag: "ToClientRequestChunk"; + readonly val: ToClientRequestChunk; + } + | { + readonly tag: "ToClientRequestAbort"; + readonly val: ToClientRequestAbort; + } + /** + * WebSocket + */ + | { + readonly tag: "ToClientWebSocketOpen"; + readonly val: ToClientWebSocketOpen; + } + | { + readonly tag: "ToClientWebSocketMessage"; + readonly val: ToClientWebSocketMessage; + } + | { + readonly tag: "ToClientWebSocketClose"; + readonly val: ToClientWebSocketClose; + }; + +export function readToClientTunnelMessageKind( + bc: bare.ByteCursor, +): ToClientTunnelMessageKind { + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { + tag: "ToClientRequestStart", + val: readToClientRequestStart(bc), + }; + case 1: + return { + tag: "ToClientRequestChunk", + val: readToClientRequestChunk(bc), + }; + case 2: + return { tag: "ToClientRequestAbort", val: null }; + case 3: + return { + tag: "ToClientWebSocketOpen", + val: readToClientWebSocketOpen(bc), + }; + case 4: + return { + tag: "ToClientWebSocketMessage", + val: readToClientWebSocketMessage(bc), + }; + case 5: + return { + tag: "ToClientWebSocketClose", + val: readToClientWebSocketClose(bc), + }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } +} + +export function writeToClientTunnelMessageKind( + bc: bare.ByteCursor, + x: ToClientTunnelMessageKind, +): void { + switch (x.tag) { + case "ToClientRequestStart": { + bare.writeU8(bc, 0); + writeToClientRequestStart(bc, x.val); + break; + } + case "ToClientRequestChunk": { + bare.writeU8(bc, 1); + writeToClientRequestChunk(bc, x.val); + break; + } + case "ToClientRequestAbort": { + bare.writeU8(bc, 2); + break; + } + case "ToClientWebSocketOpen": { + bare.writeU8(bc, 3); + writeToClientWebSocketOpen(bc, x.val); + break; + } + case "ToClientWebSocketMessage": { + bare.writeU8(bc, 4); + writeToClientWebSocketMessage(bc, x.val); + break; + } + case "ToClientWebSocketClose": { + bare.writeU8(bc, 5); + writeToClientWebSocketClose(bc, x.val); + break; + } + } } export type ToClientTunnelMessage = { - readonly messageId: MessageId - readonly messageKind: ToClientTunnelMessageKind -} + readonly messageId: MessageId; + readonly messageKind: ToClientTunnelMessageKind; +}; -export function readToClientTunnelMessage(bc: bare.ByteCursor): ToClientTunnelMessage { - return { - messageId: readMessageId(bc), - messageKind: readToClientTunnelMessageKind(bc), - } +export function readToClientTunnelMessage( + bc: bare.ByteCursor, +): ToClientTunnelMessage { + return { + messageId: readMessageId(bc), + messageKind: readToClientTunnelMessageKind(bc), + }; } -export function writeToClientTunnelMessage(bc: bare.ByteCursor, x: ToClientTunnelMessage): void { - writeMessageId(bc, x.messageId) - writeToClientTunnelMessageKind(bc, x.messageKind) +export function writeToClientTunnelMessage( + bc: bare.ByteCursor, + x: ToClientTunnelMessage, +): void { + writeMessageId(bc, x.messageId); + writeToClientTunnelMessageKind(bc, x.messageKind); } export type ToClientPing = { - readonly ts: i64 -} + readonly ts: i64; +}; export function readToClientPing(bc: bare.ByteCursor): ToClientPing { - return { - ts: bare.readI64(bc), - } + return { + ts: bare.readI64(bc), + }; } export function writeToClientPing(bc: bare.ByteCursor, x: ToClientPing): void { - bare.writeI64(bc, x.ts) + bare.writeI64(bc, x.ts); } function read11(bc: bare.ByteCursor): ReadonlyMap { - const len = bare.readUintSafe(bc) - const result = new Map() - for (let i = 0; i < len; i++) { - const offset = bc.offset - const key = bare.readString(bc) - if (result.has(key)) { - bc.offset = offset - throw new bare.BareError(offset, "duplicated key") - } - result.set(key, readActorName(bc)) - } - return result + const len = bare.readUintSafe(bc); + const result = new Map(); + for (let i = 0; i < len; i++) { + const offset = bc.offset; + const key = bare.readString(bc); + if (result.has(key)) { + bc.offset = offset; + throw new bare.BareError(offset, "duplicated key"); + } + result.set(key, readActorName(bc)); + } + return result; } function write11(bc: bare.ByteCursor, x: ReadonlyMap): void { - bare.writeUintSafe(bc, x.size) - for (const kv of x) { - bare.writeString(bc, kv[0]) - writeActorName(bc, kv[1]) - } + bare.writeUintSafe(bc, x.size); + for (const kv of x) { + bare.writeString(bc, kv[0]); + writeActorName(bc, kv[1]); + } } function read12(bc: bare.ByteCursor): ReadonlyMap | null { - return bare.readBool(bc) ? read11(bc) : null + return bare.readBool(bc) ? read11(bc) : null; } -function write12(bc: bare.ByteCursor, x: ReadonlyMap | null): void { - bare.writeBool(bc, x != null) - if (x != null) { - write11(bc, x) - } +function write12( + bc: bare.ByteCursor, + x: ReadonlyMap | null, +): void { + bare.writeBool(bc, x != null); + if (x != null) { + write11(bc, x); + } } function read13(bc: bare.ByteCursor): Json | null { - return bare.readBool(bc) ? readJson(bc) : null + return bare.readBool(bc) ? readJson(bc) : null; } function write13(bc: bare.ByteCursor, x: Json | null): void { - bare.writeBool(bc, x != null) - if (x != null) { - writeJson(bc, x) - } + bare.writeBool(bc, x != null); + if (x != null) { + writeJson(bc, x); + } } /** * MARK: To Server */ export type ToServerInit = { - readonly name: string - readonly version: u32 - readonly totalSlots: u32 - readonly prepopulateActorNames: ReadonlyMap | null - readonly metadata: Json | null -} + readonly name: string; + readonly version: u32; + readonly totalSlots: u32; + readonly prepopulateActorNames: ReadonlyMap | null; + readonly metadata: Json | null; +}; export function readToServerInit(bc: bare.ByteCursor): ToServerInit { - return { - name: bare.readString(bc), - version: bare.readU32(bc), - totalSlots: bare.readU32(bc), - prepopulateActorNames: read12(bc), - metadata: read13(bc), - } + return { + name: bare.readString(bc), + version: bare.readU32(bc), + totalSlots: bare.readU32(bc), + prepopulateActorNames: read12(bc), + metadata: read13(bc), + }; } export function writeToServerInit(bc: bare.ByteCursor, x: ToServerInit): void { - bare.writeString(bc, x.name) - bare.writeU32(bc, x.version) - bare.writeU32(bc, x.totalSlots) - write12(bc, x.prepopulateActorNames) - write13(bc, x.metadata) + bare.writeString(bc, x.name); + bare.writeU32(bc, x.version); + bare.writeU32(bc, x.totalSlots); + write12(bc, x.prepopulateActorNames); + write13(bc, x.metadata); } -export type ToServerEvents = readonly EventWrapper[] +export type ToServerEvents = readonly EventWrapper[]; export function readToServerEvents(bc: bare.ByteCursor): ToServerEvents { - const len = bare.readUintSafe(bc) - if (len === 0) { - return [] - } - const result = [readEventWrapper(bc)] - for (let i = 1; i < len; i++) { - result[i] = readEventWrapper(bc) - } - return result -} - -export function writeToServerEvents(bc: bare.ByteCursor, x: ToServerEvents): void { - bare.writeUintSafe(bc, x.length) - for (let i = 0; i < x.length; i++) { - writeEventWrapper(bc, x[i]) - } + const len = bare.readUintSafe(bc); + if (len === 0) { + return []; + } + const result = [readEventWrapper(bc)]; + for (let i = 1; i < len; i++) { + result[i] = readEventWrapper(bc); + } + return result; +} + +export function writeToServerEvents( + bc: bare.ByteCursor, + x: ToServerEvents, +): void { + bare.writeUintSafe(bc, x.length); + for (let i = 0; i < x.length; i++) { + writeEventWrapper(bc, x[i]); + } } function read14(bc: bare.ByteCursor): readonly ActorCheckpoint[] { - const len = bare.readUintSafe(bc) - if (len === 0) { - return [] - } - const result = [readActorCheckpoint(bc)] - for (let i = 1; i < len; i++) { - result[i] = readActorCheckpoint(bc) - } - return result + const len = bare.readUintSafe(bc); + if (len === 0) { + return []; + } + const result = [readActorCheckpoint(bc)]; + for (let i = 1; i < len; i++) { + result[i] = readActorCheckpoint(bc); + } + return result; } function write14(bc: bare.ByteCursor, x: readonly ActorCheckpoint[]): void { - bare.writeUintSafe(bc, x.length) - for (let i = 0; i < x.length; i++) { - writeActorCheckpoint(bc, x[i]) - } + bare.writeUintSafe(bc, x.length); + for (let i = 0; i < x.length; i++) { + writeActorCheckpoint(bc, x[i]); + } } export type ToServerAckCommands = { - readonly lastCommandCheckpoints: readonly ActorCheckpoint[] -} + readonly lastCommandCheckpoints: readonly ActorCheckpoint[]; +}; -export function readToServerAckCommands(bc: bare.ByteCursor): ToServerAckCommands { - return { - lastCommandCheckpoints: read14(bc), - } +export function readToServerAckCommands( + bc: bare.ByteCursor, +): ToServerAckCommands { + return { + lastCommandCheckpoints: read14(bc), + }; } -export function writeToServerAckCommands(bc: bare.ByteCursor, x: ToServerAckCommands): void { - write14(bc, x.lastCommandCheckpoints) +export function writeToServerAckCommands( + bc: bare.ByteCursor, + x: ToServerAckCommands, +): void { + write14(bc, x.lastCommandCheckpoints); } -export type ToServerStopping = null +export type ToServerStopping = null; export type ToServerPong = { - readonly ts: i64 -} + readonly ts: i64; +}; export function readToServerPong(bc: bare.ByteCursor): ToServerPong { - return { - ts: bare.readI64(bc), - } + return { + ts: bare.readI64(bc), + }; } export function writeToServerPong(bc: bare.ByteCursor, x: ToServerPong): void { - bare.writeI64(bc, x.ts) + bare.writeI64(bc, x.ts); } export type ToServerKvRequest = { - readonly actorId: Id - readonly requestId: u32 - readonly data: KvRequestData -} + readonly actorId: Id; + readonly requestId: u32; + readonly data: KvRequestData; +}; export function readToServerKvRequest(bc: bare.ByteCursor): ToServerKvRequest { - return { - actorId: readId(bc), - requestId: bare.readU32(bc), - data: readKvRequestData(bc), - } + return { + actorId: readId(bc), + requestId: bare.readU32(bc), + data: readKvRequestData(bc), + }; } -export function writeToServerKvRequest(bc: bare.ByteCursor, x: ToServerKvRequest): void { - writeId(bc, x.actorId) - bare.writeU32(bc, x.requestId) - writeKvRequestData(bc, x.data) +export function writeToServerKvRequest( + bc: bare.ByteCursor, + x: ToServerKvRequest, +): void { + writeId(bc, x.actorId); + bare.writeU32(bc, x.requestId); + writeKvRequestData(bc, x.data); } export type ToServer = - | { readonly tag: "ToServerInit"; readonly val: ToServerInit } - | { readonly tag: "ToServerEvents"; readonly val: ToServerEvents } - | { readonly tag: "ToServerAckCommands"; readonly val: ToServerAckCommands } - | { readonly tag: "ToServerStopping"; readonly val: ToServerStopping } - | { readonly tag: "ToServerPong"; readonly val: ToServerPong } - | { readonly tag: "ToServerKvRequest"; readonly val: ToServerKvRequest } - | { readonly tag: "ToServerTunnelMessage"; readonly val: ToServerTunnelMessage } + | { readonly tag: "ToServerInit"; readonly val: ToServerInit } + | { readonly tag: "ToServerEvents"; readonly val: ToServerEvents } + | { readonly tag: "ToServerAckCommands"; readonly val: ToServerAckCommands } + | { readonly tag: "ToServerStopping"; readonly val: ToServerStopping } + | { readonly tag: "ToServerPong"; readonly val: ToServerPong } + | { readonly tag: "ToServerKvRequest"; readonly val: ToServerKvRequest } + | { + readonly tag: "ToServerTunnelMessage"; + readonly val: ToServerTunnelMessage; + }; export function readToServer(bc: bare.ByteCursor): ToServer { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "ToServerInit", val: readToServerInit(bc) } - case 1: - return { tag: "ToServerEvents", val: readToServerEvents(bc) } - case 2: - return { tag: "ToServerAckCommands", val: readToServerAckCommands(bc) } - case 3: - return { tag: "ToServerStopping", val: null } - case 4: - return { tag: "ToServerPong", val: readToServerPong(bc) } - case 5: - return { tag: "ToServerKvRequest", val: readToServerKvRequest(bc) } - case 6: - return { tag: "ToServerTunnelMessage", val: readToServerTunnelMessage(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "ToServerInit", val: readToServerInit(bc) }; + case 1: + return { tag: "ToServerEvents", val: readToServerEvents(bc) }; + case 2: + return { + tag: "ToServerAckCommands", + val: readToServerAckCommands(bc), + }; + case 3: + return { tag: "ToServerStopping", val: null }; + case 4: + return { tag: "ToServerPong", val: readToServerPong(bc) }; + case 5: + return { tag: "ToServerKvRequest", val: readToServerKvRequest(bc) }; + case 6: + return { + tag: "ToServerTunnelMessage", + val: readToServerTunnelMessage(bc), + }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeToServer(bc: bare.ByteCursor, x: ToServer): void { - switch (x.tag) { - case "ToServerInit": { - bare.writeU8(bc, 0) - writeToServerInit(bc, x.val) - break - } - case "ToServerEvents": { - bare.writeU8(bc, 1) - writeToServerEvents(bc, x.val) - break - } - case "ToServerAckCommands": { - bare.writeU8(bc, 2) - writeToServerAckCommands(bc, x.val) - break - } - case "ToServerStopping": { - bare.writeU8(bc, 3) - break - } - case "ToServerPong": { - bare.writeU8(bc, 4) - writeToServerPong(bc, x.val) - break - } - case "ToServerKvRequest": { - bare.writeU8(bc, 5) - writeToServerKvRequest(bc, x.val) - break - } - case "ToServerTunnelMessage": { - bare.writeU8(bc, 6) - writeToServerTunnelMessage(bc, x.val) - break - } - } -} - -export function encodeToServer(x: ToServer, config?: Partial): Uint8Array { - const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG - const bc = new bare.ByteCursor( - new Uint8Array(fullConfig.initialBufferLength), - fullConfig, - ) - writeToServer(bc, x) - return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset) + switch (x.tag) { + case "ToServerInit": { + bare.writeU8(bc, 0); + writeToServerInit(bc, x.val); + break; + } + case "ToServerEvents": { + bare.writeU8(bc, 1); + writeToServerEvents(bc, x.val); + break; + } + case "ToServerAckCommands": { + bare.writeU8(bc, 2); + writeToServerAckCommands(bc, x.val); + break; + } + case "ToServerStopping": { + bare.writeU8(bc, 3); + break; + } + case "ToServerPong": { + bare.writeU8(bc, 4); + writeToServerPong(bc, x.val); + break; + } + case "ToServerKvRequest": { + bare.writeU8(bc, 5); + writeToServerKvRequest(bc, x.val); + break; + } + case "ToServerTunnelMessage": { + bare.writeU8(bc, 6); + writeToServerTunnelMessage(bc, x.val); + break; + } + } +} + +export function encodeToServer( + x: ToServer, + config?: Partial, +): Uint8Array { + const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG; + const bc = new bare.ByteCursor( + new Uint8Array(fullConfig.initialBufferLength), + fullConfig, + ); + writeToServer(bc, x); + return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset); } export function decodeToServer(bytes: Uint8Array): ToServer { - const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG) - const result = readToServer(bc) - if (bc.offset < bc.view.byteLength) { - throw new bare.BareError(bc.offset, "remaining bytes") - } - return result + const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG); + const result = readToServer(bc); + if (bc.offset < bc.view.byteLength) { + throw new bare.BareError(bc.offset, "remaining bytes"); + } + return result; } /** * MARK: To Client */ export type ProtocolMetadata = { - readonly runnerLostThreshold: i64 - readonly actorStopThreshold: i64 - readonly serverlessDrainGracePeriod: i64 | null -} + readonly runnerLostThreshold: i64; + readonly actorStopThreshold: i64; + readonly serverlessDrainGracePeriod: i64 | null; +}; export function readProtocolMetadata(bc: bare.ByteCursor): ProtocolMetadata { - return { - runnerLostThreshold: bare.readI64(bc), - actorStopThreshold: bare.readI64(bc), - serverlessDrainGracePeriod: read7(bc), - } + return { + runnerLostThreshold: bare.readI64(bc), + actorStopThreshold: bare.readI64(bc), + serverlessDrainGracePeriod: read7(bc), + }; } -export function writeProtocolMetadata(bc: bare.ByteCursor, x: ProtocolMetadata): void { - bare.writeI64(bc, x.runnerLostThreshold) - bare.writeI64(bc, x.actorStopThreshold) - write7(bc, x.serverlessDrainGracePeriod) +export function writeProtocolMetadata( + bc: bare.ByteCursor, + x: ProtocolMetadata, +): void { + bare.writeI64(bc, x.runnerLostThreshold); + bare.writeI64(bc, x.actorStopThreshold); + write7(bc, x.serverlessDrainGracePeriod); } export type ToClientInit = { - readonly runnerId: Id - readonly metadata: ProtocolMetadata -} + readonly runnerId: Id; + readonly metadata: ProtocolMetadata; +}; export function readToClientInit(bc: bare.ByteCursor): ToClientInit { - return { - runnerId: readId(bc), - metadata: readProtocolMetadata(bc), - } + return { + runnerId: readId(bc), + metadata: readProtocolMetadata(bc), + }; } export function writeToClientInit(bc: bare.ByteCursor, x: ToClientInit): void { - writeId(bc, x.runnerId) - writeProtocolMetadata(bc, x.metadata) + writeId(bc, x.runnerId); + writeProtocolMetadata(bc, x.metadata); } -export type ToClientCommands = readonly CommandWrapper[] +export type ToClientCommands = readonly CommandWrapper[]; export function readToClientCommands(bc: bare.ByteCursor): ToClientCommands { - const len = bare.readUintSafe(bc) - if (len === 0) { - return [] - } - const result = [readCommandWrapper(bc)] - for (let i = 1; i < len; i++) { - result[i] = readCommandWrapper(bc) - } - return result -} - -export function writeToClientCommands(bc: bare.ByteCursor, x: ToClientCommands): void { - bare.writeUintSafe(bc, x.length) - for (let i = 0; i < x.length; i++) { - writeCommandWrapper(bc, x[i]) - } + const len = bare.readUintSafe(bc); + if (len === 0) { + return []; + } + const result = [readCommandWrapper(bc)]; + for (let i = 1; i < len; i++) { + result[i] = readCommandWrapper(bc); + } + return result; +} + +export function writeToClientCommands( + bc: bare.ByteCursor, + x: ToClientCommands, +): void { + bare.writeUintSafe(bc, x.length); + for (let i = 0; i < x.length; i++) { + writeCommandWrapper(bc, x[i]); + } } export type ToClientAckEvents = { - readonly lastEventCheckpoints: readonly ActorCheckpoint[] -} + readonly lastEventCheckpoints: readonly ActorCheckpoint[]; +}; export function readToClientAckEvents(bc: bare.ByteCursor): ToClientAckEvents { - return { - lastEventCheckpoints: read14(bc), - } + return { + lastEventCheckpoints: read14(bc), + }; } -export function writeToClientAckEvents(bc: bare.ByteCursor, x: ToClientAckEvents): void { - write14(bc, x.lastEventCheckpoints) +export function writeToClientAckEvents( + bc: bare.ByteCursor, + x: ToClientAckEvents, +): void { + write14(bc, x.lastEventCheckpoints); } export type ToClientKvResponse = { - readonly requestId: u32 - readonly data: KvResponseData -} + readonly requestId: u32; + readonly data: KvResponseData; +}; -export function readToClientKvResponse(bc: bare.ByteCursor): ToClientKvResponse { - return { - requestId: bare.readU32(bc), - data: readKvResponseData(bc), - } +export function readToClientKvResponse( + bc: bare.ByteCursor, +): ToClientKvResponse { + return { + requestId: bare.readU32(bc), + data: readKvResponseData(bc), + }; } -export function writeToClientKvResponse(bc: bare.ByteCursor, x: ToClientKvResponse): void { - bare.writeU32(bc, x.requestId) - writeKvResponseData(bc, x.data) +export function writeToClientKvResponse( + bc: bare.ByteCursor, + x: ToClientKvResponse, +): void { + bare.writeU32(bc, x.requestId); + writeKvResponseData(bc, x.data); } export type ToClient = - | { readonly tag: "ToClientInit"; readonly val: ToClientInit } - | { readonly tag: "ToClientCommands"; readonly val: ToClientCommands } - | { readonly tag: "ToClientAckEvents"; readonly val: ToClientAckEvents } - | { readonly tag: "ToClientKvResponse"; readonly val: ToClientKvResponse } - | { readonly tag: "ToClientTunnelMessage"; readonly val: ToClientTunnelMessage } - | { readonly tag: "ToClientPing"; readonly val: ToClientPing } + | { readonly tag: "ToClientInit"; readonly val: ToClientInit } + | { readonly tag: "ToClientCommands"; readonly val: ToClientCommands } + | { readonly tag: "ToClientAckEvents"; readonly val: ToClientAckEvents } + | { readonly tag: "ToClientKvResponse"; readonly val: ToClientKvResponse } + | { + readonly tag: "ToClientTunnelMessage"; + readonly val: ToClientTunnelMessage; + } + | { readonly tag: "ToClientPing"; readonly val: ToClientPing }; export function readToClient(bc: bare.ByteCursor): ToClient { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "ToClientInit", val: readToClientInit(bc) } - case 1: - return { tag: "ToClientCommands", val: readToClientCommands(bc) } - case 2: - return { tag: "ToClientAckEvents", val: readToClientAckEvents(bc) } - case 3: - return { tag: "ToClientKvResponse", val: readToClientKvResponse(bc) } - case 4: - return { tag: "ToClientTunnelMessage", val: readToClientTunnelMessage(bc) } - case 5: - return { tag: "ToClientPing", val: readToClientPing(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "ToClientInit", val: readToClientInit(bc) }; + case 1: + return { tag: "ToClientCommands", val: readToClientCommands(bc) }; + case 2: + return { tag: "ToClientAckEvents", val: readToClientAckEvents(bc) }; + case 3: + return { + tag: "ToClientKvResponse", + val: readToClientKvResponse(bc), + }; + case 4: + return { + tag: "ToClientTunnelMessage", + val: readToClientTunnelMessage(bc), + }; + case 5: + return { tag: "ToClientPing", val: readToClientPing(bc) }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeToClient(bc: bare.ByteCursor, x: ToClient): void { - switch (x.tag) { - case "ToClientInit": { - bare.writeU8(bc, 0) - writeToClientInit(bc, x.val) - break - } - case "ToClientCommands": { - bare.writeU8(bc, 1) - writeToClientCommands(bc, x.val) - break - } - case "ToClientAckEvents": { - bare.writeU8(bc, 2) - writeToClientAckEvents(bc, x.val) - break - } - case "ToClientKvResponse": { - bare.writeU8(bc, 3) - writeToClientKvResponse(bc, x.val) - break - } - case "ToClientTunnelMessage": { - bare.writeU8(bc, 4) - writeToClientTunnelMessage(bc, x.val) - break - } - case "ToClientPing": { - bare.writeU8(bc, 5) - writeToClientPing(bc, x.val) - break - } - } -} - -export function encodeToClient(x: ToClient, config?: Partial): Uint8Array { - const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG - const bc = new bare.ByteCursor( - new Uint8Array(fullConfig.initialBufferLength), - fullConfig, - ) - writeToClient(bc, x) - return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset) + switch (x.tag) { + case "ToClientInit": { + bare.writeU8(bc, 0); + writeToClientInit(bc, x.val); + break; + } + case "ToClientCommands": { + bare.writeU8(bc, 1); + writeToClientCommands(bc, x.val); + break; + } + case "ToClientAckEvents": { + bare.writeU8(bc, 2); + writeToClientAckEvents(bc, x.val); + break; + } + case "ToClientKvResponse": { + bare.writeU8(bc, 3); + writeToClientKvResponse(bc, x.val); + break; + } + case "ToClientTunnelMessage": { + bare.writeU8(bc, 4); + writeToClientTunnelMessage(bc, x.val); + break; + } + case "ToClientPing": { + bare.writeU8(bc, 5); + writeToClientPing(bc, x.val); + break; + } + } +} + +export function encodeToClient( + x: ToClient, + config?: Partial, +): Uint8Array { + const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG; + const bc = new bare.ByteCursor( + new Uint8Array(fullConfig.initialBufferLength), + fullConfig, + ); + writeToClient(bc, x); + return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset); } export function decodeToClient(bytes: Uint8Array): ToClient { - const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG) - const result = readToClient(bc) - if (bc.offset < bc.view.byteLength) { - throw new bare.BareError(bc.offset, "remaining bytes") - } - return result + const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG); + const result = readToClient(bc); + if (bc.offset < bc.view.byteLength) { + throw new bare.BareError(bc.offset, "remaining bytes"); + } + return result; } /** * MARK: To Runner */ export type ToRunnerPing = { - readonly gatewayId: GatewayId - readonly requestId: RequestId - readonly ts: i64 -} + readonly gatewayId: GatewayId; + readonly requestId: RequestId; + readonly ts: i64; +}; export function readToRunnerPing(bc: bare.ByteCursor): ToRunnerPing { - return { - gatewayId: readGatewayId(bc), - requestId: readRequestId(bc), - ts: bare.readI64(bc), - } + return { + gatewayId: readGatewayId(bc), + requestId: readRequestId(bc), + ts: bare.readI64(bc), + }; } export function writeToRunnerPing(bc: bare.ByteCursor, x: ToRunnerPing): void { - writeGatewayId(bc, x.gatewayId) - writeRequestId(bc, x.requestId) - bare.writeI64(bc, x.ts) + writeGatewayId(bc, x.gatewayId); + writeRequestId(bc, x.requestId); + bare.writeI64(bc, x.ts); } -export type ToRunnerClose = null +export type ToRunnerClose = null; /** * We have to re-declare the entire union since BARE will not generate the * ser/de for ToClient if it's not a top-level type */ export type ToRunner = - | { readonly tag: "ToRunnerPing"; readonly val: ToRunnerPing } - | { readonly tag: "ToRunnerClose"; readonly val: ToRunnerClose } - | { readonly tag: "ToClientCommands"; readonly val: ToClientCommands } - | { readonly tag: "ToClientAckEvents"; readonly val: ToClientAckEvents } - | { readonly tag: "ToClientTunnelMessage"; readonly val: ToClientTunnelMessage } + | { readonly tag: "ToRunnerPing"; readonly val: ToRunnerPing } + | { readonly tag: "ToRunnerClose"; readonly val: ToRunnerClose } + | { readonly tag: "ToClientCommands"; readonly val: ToClientCommands } + | { readonly tag: "ToClientAckEvents"; readonly val: ToClientAckEvents } + | { + readonly tag: "ToClientTunnelMessage"; + readonly val: ToClientTunnelMessage; + }; export function readToRunner(bc: bare.ByteCursor): ToRunner { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "ToRunnerPing", val: readToRunnerPing(bc) } - case 1: - return { tag: "ToRunnerClose", val: null } - case 2: - return { tag: "ToClientCommands", val: readToClientCommands(bc) } - case 3: - return { tag: "ToClientAckEvents", val: readToClientAckEvents(bc) } - case 4: - return { tag: "ToClientTunnelMessage", val: readToClientTunnelMessage(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "ToRunnerPing", val: readToRunnerPing(bc) }; + case 1: + return { tag: "ToRunnerClose", val: null }; + case 2: + return { tag: "ToClientCommands", val: readToClientCommands(bc) }; + case 3: + return { tag: "ToClientAckEvents", val: readToClientAckEvents(bc) }; + case 4: + return { + tag: "ToClientTunnelMessage", + val: readToClientTunnelMessage(bc), + }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeToRunner(bc: bare.ByteCursor, x: ToRunner): void { - switch (x.tag) { - case "ToRunnerPing": { - bare.writeU8(bc, 0) - writeToRunnerPing(bc, x.val) - break - } - case "ToRunnerClose": { - bare.writeU8(bc, 1) - break - } - case "ToClientCommands": { - bare.writeU8(bc, 2) - writeToClientCommands(bc, x.val) - break - } - case "ToClientAckEvents": { - bare.writeU8(bc, 3) - writeToClientAckEvents(bc, x.val) - break - } - case "ToClientTunnelMessage": { - bare.writeU8(bc, 4) - writeToClientTunnelMessage(bc, x.val) - break - } - } -} - -export function encodeToRunner(x: ToRunner, config?: Partial): Uint8Array { - const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG - const bc = new bare.ByteCursor( - new Uint8Array(fullConfig.initialBufferLength), - fullConfig, - ) - writeToRunner(bc, x) - return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset) + switch (x.tag) { + case "ToRunnerPing": { + bare.writeU8(bc, 0); + writeToRunnerPing(bc, x.val); + break; + } + case "ToRunnerClose": { + bare.writeU8(bc, 1); + break; + } + case "ToClientCommands": { + bare.writeU8(bc, 2); + writeToClientCommands(bc, x.val); + break; + } + case "ToClientAckEvents": { + bare.writeU8(bc, 3); + writeToClientAckEvents(bc, x.val); + break; + } + case "ToClientTunnelMessage": { + bare.writeU8(bc, 4); + writeToClientTunnelMessage(bc, x.val); + break; + } + } +} + +export function encodeToRunner( + x: ToRunner, + config?: Partial, +): Uint8Array { + const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG; + const bc = new bare.ByteCursor( + new Uint8Array(fullConfig.initialBufferLength), + fullConfig, + ); + writeToRunner(bc, x); + return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset); } export function decodeToRunner(bytes: Uint8Array): ToRunner { - const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG) - const result = readToRunner(bc) - if (bc.offset < bc.view.byteLength) { - throw new bare.BareError(bc.offset, "remaining bytes") - } - return result + const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG); + const result = readToRunner(bc); + if (bc.offset < bc.view.byteLength) { + throw new bare.BareError(bc.offset, "remaining bytes"); + } + return result; } /** * MARK: To Gateway */ export type ToGatewayPong = { - readonly requestId: RequestId - readonly ts: i64 -} + readonly requestId: RequestId; + readonly ts: i64; +}; export function readToGatewayPong(bc: bare.ByteCursor): ToGatewayPong { - return { - requestId: readRequestId(bc), - ts: bare.readI64(bc), - } + return { + requestId: readRequestId(bc), + ts: bare.readI64(bc), + }; } -export function writeToGatewayPong(bc: bare.ByteCursor, x: ToGatewayPong): void { - writeRequestId(bc, x.requestId) - bare.writeI64(bc, x.ts) +export function writeToGatewayPong( + bc: bare.ByteCursor, + x: ToGatewayPong, +): void { + writeRequestId(bc, x.requestId); + bare.writeI64(bc, x.ts); } export type ToGateway = - | { readonly tag: "ToGatewayPong"; readonly val: ToGatewayPong } - | { readonly tag: "ToServerTunnelMessage"; readonly val: ToServerTunnelMessage } + | { readonly tag: "ToGatewayPong"; readonly val: ToGatewayPong } + | { + readonly tag: "ToServerTunnelMessage"; + readonly val: ToServerTunnelMessage; + }; export function readToGateway(bc: bare.ByteCursor): ToGateway { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "ToGatewayPong", val: readToGatewayPong(bc) } - case 1: - return { tag: "ToServerTunnelMessage", val: readToServerTunnelMessage(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { tag: "ToGatewayPong", val: readToGatewayPong(bc) }; + case 1: + return { + tag: "ToServerTunnelMessage", + val: readToServerTunnelMessage(bc), + }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } } export function writeToGateway(bc: bare.ByteCursor, x: ToGateway): void { - switch (x.tag) { - case "ToGatewayPong": { - bare.writeU8(bc, 0) - writeToGatewayPong(bc, x.val) - break - } - case "ToServerTunnelMessage": { - bare.writeU8(bc, 1) - writeToServerTunnelMessage(bc, x.val) - break - } - } -} - -export function encodeToGateway(x: ToGateway, config?: Partial): Uint8Array { - const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG - const bc = new bare.ByteCursor( - new Uint8Array(fullConfig.initialBufferLength), - fullConfig, - ) - writeToGateway(bc, x) - return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset) + switch (x.tag) { + case "ToGatewayPong": { + bare.writeU8(bc, 0); + writeToGatewayPong(bc, x.val); + break; + } + case "ToServerTunnelMessage": { + bare.writeU8(bc, 1); + writeToServerTunnelMessage(bc, x.val); + break; + } + } +} + +export function encodeToGateway( + x: ToGateway, + config?: Partial, +): Uint8Array { + const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG; + const bc = new bare.ByteCursor( + new Uint8Array(fullConfig.initialBufferLength), + fullConfig, + ); + writeToGateway(bc, x); + return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset); } export function decodeToGateway(bytes: Uint8Array): ToGateway { - const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG) - const result = readToGateway(bc) - if (bc.offset < bc.view.byteLength) { - throw new bare.BareError(bc.offset, "remaining bytes") - } - return result + const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG); + const result = readToGateway(bc); + if (bc.offset < bc.view.byteLength) { + throw new bare.BareError(bc.offset, "remaining bytes"); + } + return result; } /** * MARK: Serverless */ export type ToServerlessServerInit = { - readonly runnerId: Id - readonly runnerProtocolVersion: u16 -} - -export function readToServerlessServerInit(bc: bare.ByteCursor): ToServerlessServerInit { - return { - runnerId: readId(bc), - runnerProtocolVersion: bare.readU16(bc), - } -} - -export function writeToServerlessServerInit(bc: bare.ByteCursor, x: ToServerlessServerInit): void { - writeId(bc, x.runnerId) - bare.writeU16(bc, x.runnerProtocolVersion) -} - -export type ToServerlessServer = - | { readonly tag: "ToServerlessServerInit"; readonly val: ToServerlessServerInit } - -export function readToServerlessServer(bc: bare.ByteCursor): ToServerlessServer { - const offset = bc.offset - const tag = bare.readU8(bc) - switch (tag) { - case 0: - return { tag: "ToServerlessServerInit", val: readToServerlessServerInit(bc) } - default: { - bc.offset = offset - throw new bare.BareError(offset, "invalid tag") - } - } + readonly runnerId: Id; + readonly runnerProtocolVersion: u16; +}; + +export function readToServerlessServerInit( + bc: bare.ByteCursor, +): ToServerlessServerInit { + return { + runnerId: readId(bc), + runnerProtocolVersion: bare.readU16(bc), + }; +} + +export function writeToServerlessServerInit( + bc: bare.ByteCursor, + x: ToServerlessServerInit, +): void { + writeId(bc, x.runnerId); + bare.writeU16(bc, x.runnerProtocolVersion); +} + +export type ToServerlessServer = { + readonly tag: "ToServerlessServerInit"; + readonly val: ToServerlessServerInit; +}; + +export function readToServerlessServer( + bc: bare.ByteCursor, +): ToServerlessServer { + const offset = bc.offset; + const tag = bare.readU8(bc); + switch (tag) { + case 0: + return { + tag: "ToServerlessServerInit", + val: readToServerlessServerInit(bc), + }; + default: { + bc.offset = offset; + throw new bare.BareError(offset, "invalid tag"); + } + } +} + +export function writeToServerlessServer( + bc: bare.ByteCursor, + x: ToServerlessServer, +): void { + switch (x.tag) { + case "ToServerlessServerInit": { + bare.writeU8(bc, 0); + writeToServerlessServerInit(bc, x.val); + break; + } + } +} + +export function encodeToServerlessServer( + x: ToServerlessServer, + config?: Partial, +): Uint8Array { + const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG; + const bc = new bare.ByteCursor( + new Uint8Array(fullConfig.initialBufferLength), + fullConfig, + ); + writeToServerlessServer(bc, x); + return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset); +} + +export function decodeToServerlessServer( + bytes: Uint8Array, +): ToServerlessServer { + const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG); + const result = readToServerlessServer(bc); + if (bc.offset < bc.view.byteLength) { + throw new bare.BareError(bc.offset, "remaining bytes"); + } + return result; } -export function writeToServerlessServer(bc: bare.ByteCursor, x: ToServerlessServer): void { - switch (x.tag) { - case "ToServerlessServerInit": { - bare.writeU8(bc, 0) - writeToServerlessServerInit(bc, x.val) - break - } - } -} - -export function encodeToServerlessServer(x: ToServerlessServer, config?: Partial): Uint8Array { - const fullConfig = config != null ? bare.Config(config) : DEFAULT_CONFIG - const bc = new bare.ByteCursor( - new Uint8Array(fullConfig.initialBufferLength), - fullConfig, - ) - writeToServerlessServer(bc, x) - return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset) -} - -export function decodeToServerlessServer(bytes: Uint8Array): ToServerlessServer { - const bc = new bare.ByteCursor(bytes, DEFAULT_CONFIG) - const result = readToServerlessServer(bc) - if (bc.offset < bc.view.byteLength) { - throw new bare.BareError(bc.offset, "remaining bytes") - } - return result -} - - function assert(condition: boolean, message?: string): asserts condition { - if (!condition) throw new Error(message ?? "Assertion failed") + if (!condition) throw new Error(message ?? "Assertion failed"); } diff --git a/rivetkit-typescript/packages/engine-runner/src/mod.ts b/rivetkit-typescript/packages/engine-runner/src/mod.ts index 724f8ecd49..5d6317cc1e 100644 --- a/rivetkit-typescript/packages/engine-runner/src/mod.ts +++ b/rivetkit-typescript/packages/engine-runner/src/mod.ts @@ -12,9 +12,7 @@ import { unreachable, } from "./utils"; import { importWebSocket } from "./websocket.js"; -import { - v4 as uuidv4, -} from "uuid"; +import { v4 as uuidv4 } from "uuid"; export type { HibernatingWebSocketMetadata }; export { RunnerActor, type ActorConfig }; @@ -1253,9 +1251,10 @@ export class Runner { actorState = { tag: "ActorStateStopped", val: { - code: actor.stopIntentSent || this.#draining - ? protocol.StopCode.Ok - : protocol.StopCode.Error, + code: + actor.stopIntentSent || this.#draining + ? protocol.StopCode.Ok + : protocol.StopCode.Error, message: null, }, }; diff --git a/rivetkit-typescript/packages/engine-runner/src/tunnel.ts b/rivetkit-typescript/packages/engine-runner/src/tunnel.ts index ad2b650e74..3c63aa549c 100644 --- a/rivetkit-typescript/packages/engine-runner/src/tunnel.ts +++ b/rivetkit-typescript/packages/engine-runner/src/tunnel.ts @@ -10,7 +10,13 @@ import { stringifyToClientTunnelMessageKind, stringifyToServerTunnelMessageKind, } from "./stringify"; -import { arraysEqual, idToStr, MAX_PAYLOAD_SIZE, stringifyError, unreachable } from "./utils"; +import { + arraysEqual, + idToStr, + MAX_PAYLOAD_SIZE, + stringifyError, + unreachable, +} from "./utils"; import { HIBERNATABLE_SYMBOL, WebSocketTunnelAdapter, diff --git a/rivetkit-typescript/packages/engine-runner/src/websocket-tunnel-adapter.ts b/rivetkit-typescript/packages/engine-runner/src/websocket-tunnel-adapter.ts index a40f0830d9..9323c6ce64 100644 --- a/rivetkit-typescript/packages/engine-runner/src/websocket-tunnel-adapter.ts +++ b/rivetkit-typescript/packages/engine-runner/src/websocket-tunnel-adapter.ts @@ -1,7 +1,16 @@ import type { Logger } from "pino"; -import { VirtualWebSocket, type UniversalWebSocket, type RivetMessageEvent } from "@rivetkit/virtual-websocket"; +import { + VirtualWebSocket, + type UniversalWebSocket, + type RivetMessageEvent, +} from "@rivetkit/virtual-websocket"; import type { Tunnel } from "./tunnel"; -import { MAX_PAYLOAD_SIZE, wrappingAddU16, wrappingLteU16, wrappingSubU16 } from "./utils"; +import { + MAX_PAYLOAD_SIZE, + wrappingAddU16, + wrappingLteU16, + wrappingSubU16, +} from "./utils"; export const HIBERNATABLE_SYMBOL = Symbol("hibernatable"); @@ -77,18 +86,28 @@ export class WebSocketTunnelAdapter { messageData = data; } else if (data instanceof ArrayBuffer) { - if (data.byteLength > MAX_PAYLOAD_SIZE) throw new Error("WebSocket message too large"); + if (data.byteLength > MAX_PAYLOAD_SIZE) + throw new Error("WebSocket message too large"); isBinary = true; messageData = data; } else if (ArrayBuffer.isView(data)) { - if (data.byteLength > MAX_PAYLOAD_SIZE) throw new Error("WebSocket message too large"); + if (data.byteLength > MAX_PAYLOAD_SIZE) + throw new Error("WebSocket message too large"); isBinary = true; const view = data; - const buffer = view.buffer instanceof SharedArrayBuffer - ? new Uint8Array(view.buffer, view.byteOffset, view.byteLength).slice().buffer - : view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength); + const buffer = + view.buffer instanceof SharedArrayBuffer + ? new Uint8Array( + view.buffer, + view.byteOffset, + view.byteLength, + ).slice().buffer + : view.buffer.slice( + view.byteOffset, + view.byteOffset + view.byteLength, + ); messageData = buffer as ArrayBuffer; } else { throw new Error("Unsupported data type"); @@ -101,7 +120,11 @@ export class WebSocketTunnelAdapter { _handleOpen(requestId: ArrayBuffer): void { if (this.#readyState !== 0) return; this.#readyState = 1; - this.#ws.dispatchEvent({ type: "open", rivetRequestId: requestId, target: this.#ws }); + this.#ws.dispatchEvent({ + type: "open", + rivetRequestId: requestId, + target: this.#ws, + }); } // Called by Tunnel when message is received @@ -147,7 +170,10 @@ export class WebSocketTunnelAdapter { expectedIndex, receivedIndex: serverMessageIndex, closeReason, - gap: wrappingSubU16(wrappingSubU16(serverMessageIndex, previousIndex), 1), + gap: wrappingSubU16( + wrappingSubU16(serverMessageIndex, previousIndex), + 1, + ), }); this.#close(1008, closeReason, true); return true; @@ -162,7 +188,10 @@ export class WebSocketTunnelAdapter { if (this.#binaryType === "nodebuffer") { messageData = Buffer.from(data); } else if (this.#binaryType === "arraybuffer") { - messageData = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); + messageData = data.buffer.slice( + data.byteOffset, + data.byteOffset + data.byteLength, + ); } } @@ -178,7 +207,11 @@ export class WebSocketTunnelAdapter { } // Called by Tunnel when close is received - _handleClose(_requestId: ArrayBuffer, code?: number, reason?: string): void { + _handleClose( + _requestId: ArrayBuffer, + code?: number, + reason?: string, + ): void { this.#close(code, reason, true); } @@ -192,7 +225,11 @@ export class WebSocketTunnelAdapter { this.#close(code, reason, true); } - #close(code: number | undefined, reason: string | undefined, sendCallback: boolean): void { + #close( + code: number | undefined, + reason: string | undefined, + sendCallback: boolean, + ): void { if (this.#readyState >= 2) return; this.#readyState = 2; diff --git a/rivetkit-typescript/packages/next-js/src/mod.ts b/rivetkit-typescript/packages/next-js/src/mod.ts index e150bd3f42..9c246c6767 100644 --- a/rivetkit-typescript/packages/next-js/src/mod.ts +++ b/rivetkit-typescript/packages/next-js/src/mod.ts @@ -11,9 +11,6 @@ import { logger } from "./log"; const DEV_ENVOY_VERSION = Math.floor(Date.now() / 1000); export const toNextHandler = (registry: Registry) => { - // Don't run server locally since we're using the fetch handler directly - registry.config.serveManager = false; - // Set basePath to "/" since Next.js route strips the /api/rivet prefix registry.config.serverless = { ...registry.config.serverless, @@ -32,16 +29,11 @@ export const toNextHandler = (registry: Registry) => { // Set these on the registry's config directly since the legacy inputConfig // isn't used by the serverless router - registry.config.serverless.spawnEngine = true; - registry.config.serverless.configurePool = { + registry.config.startEngine = true; + registry.config.configurePool = { url: `${publicUrl}/api/rivet`, requestLifespan: 300, - maxConcurrentActors: 100_000, metadata: { provider: "next-js" }, - - minRunners: 0, - maxRunners: 100_000, - slotsPerRunner: 1, }; // Set envoy version to enable hot-reloading on code changes diff --git a/rivetkit-typescript/packages/react/src/mod.typecheck.ts b/rivetkit-typescript/packages/react/src/mod.typecheck.ts index 893870cae5..7f2503ea6b 100644 --- a/rivetkit-typescript/packages/react/src/mod.typecheck.ts +++ b/rivetkit-typescript/packages/react/src/mod.typecheck.ts @@ -42,8 +42,6 @@ const actorStateFromFactory = rivetFromFactory.useActor({ if (actorState.connection) { void actorState.connection.increment(1); - // @ts-expect-error action args should be typed - void actorState.connection.increment("bad"); } actorState.useEvent("updated", (payload) => { diff --git a/rivetkit-typescript/packages/rivetkit-native/index.d.ts b/rivetkit-typescript/packages/rivetkit-native/index.d.ts index 3f618fb1f3..c8d1339f8f 100644 --- a/rivetkit-typescript/packages/rivetkit-native/index.d.ts +++ b/rivetkit-typescript/packages/rivetkit-native/index.d.ts @@ -4,50 +4,54 @@ /* auto-generated by NAPI-RS */ export interface JsBindParam { - kind: string - intValue?: number - floatValue?: number - textValue?: string - blobValue?: Buffer + kind: string; + intValue?: number; + floatValue?: number; + textValue?: string; + blobValue?: Buffer; } export interface ExecuteResult { - changes: number + changes: number; } export interface QueryResult { - columns: Array - rows: Array> + columns: Array; + rows: Array>; } /** Open a native SQLite database backed by the envoy's KV channel. */ -export declare function openDatabaseFromEnvoy(jsHandle: JsEnvoyHandle, actorId: string, preloadedEntries?: Array | undefined | null): Promise +export declare function openDatabaseFromEnvoy( + jsHandle: JsEnvoyHandle, + actorId: string, + preloadedEntries?: Array | undefined | null, +): Promise; /** Configuration for starting the native envoy client. */ export interface JsEnvoyConfig { - endpoint: string - token: string - namespace: string - poolName: string - version: number - metadata?: any - notGlobal: boolean - /** - * Log level for the Rust tracing subscriber (e.g. "trace", "debug", "info", "warn", "error"). - * Falls back to RIVET_LOG_LEVEL, then LOG_LEVEL, then RUST_LOG env vars. Defaults to "warn". - */ - logLevel?: string + endpoint: string; + token: string; + namespace: string; + poolName: string; + version: number; + metadata?: any; + notGlobal: boolean; + /** + * Log level for the Rust tracing subscriber (e.g. "trace", "debug", "info", "warn", "error"). + * Falls back to RIVET_LOG_LEVEL, then LOG_LEVEL, then RUST_LOG env vars. Defaults to "warn". + */ + logLevel?: string; } /** Options for KV list operations. */ export interface JsKvListOptions { - reverse?: boolean - limit?: number + reverse?: boolean; + limit?: number; } /** A key-value entry returned from KV list operations. */ export interface JsKvEntry { - key: Buffer - value: Buffer + key: Buffer; + value: Buffer; } /** A single hibernating request entry. */ export interface HibernatingRequestEntry { - gatewayId: Buffer - requestId: Buffer + gatewayId: Buffer; + requestId: Buffer; } /** * Start the native envoy client synchronously. @@ -55,40 +59,93 @@ export interface HibernatingRequestEntry { * Returns a handle immediately. The caller must call `await handle.started()` * to wait for the connection to be ready. */ -export declare function startEnvoySyncJs(config: JsEnvoyConfig, eventCallback: (event: any) => void): JsEnvoyHandle +export declare function startEnvoySyncJs( + config: JsEnvoyConfig, + eventCallback: (event: any) => void, +): JsEnvoyHandle; /** Start the native envoy client asynchronously. */ -export declare function startEnvoyJs(config: JsEnvoyConfig, eventCallback: (event: any) => void): JsEnvoyHandle +export declare function startEnvoyJs( + config: JsEnvoyConfig, + eventCallback: (event: any) => void, +): JsEnvoyHandle; /** Native SQLite database handle exposed to JavaScript. */ export declare class JsNativeDatabase { - takeLastKvError(): string | null - run(sql: string, params?: Array | undefined | null): Promise - query(sql: string, params?: Array | undefined | null): Promise - exec(sql: string): Promise - close(): Promise + takeLastKvError(): string | null; + run( + sql: string, + params?: Array | undefined | null, + ): Promise; + query( + sql: string, + params?: Array | undefined | null, + ): Promise; + exec(sql: string): Promise; + close(): Promise; } /** Native envoy handle exposed to JavaScript via N-API. */ export declare class JsEnvoyHandle { - started(): Promise - shutdown(immediate: boolean): void - get envoyKey(): string - sleepActor(actorId: string, generation?: number | undefined | null): void - stopActor(actorId: string, generation?: number | undefined | null, error?: string | undefined | null): void - destroyActor(actorId: string, generation?: number | undefined | null): void - setAlarm(actorId: string, alarmTs?: number | undefined | null, generation?: number | undefined | null): void - kvGet(actorId: string, keys: Array): Promise> - kvPut(actorId: string, entries: Array): Promise - kvDelete(actorId: string, keys: Array): Promise - kvDeleteRange(actorId: string, start: Buffer, end: Buffer): Promise - kvListAll(actorId: string, options?: JsKvListOptions | undefined | null): Promise> - kvListRange(actorId: string, start: Buffer, end: Buffer, exclusive?: boolean | undefined | null, options?: JsKvListOptions | undefined | null): Promise> - kvListPrefix(actorId: string, prefix: Buffer, options?: JsKvListOptions | undefined | null): Promise> - kvDrop(actorId: string): Promise - restoreHibernatingRequests(actorId: string, requests: Array): void - sendHibernatableWebSocketMessageAck(gatewayId: Buffer, requestId: Buffer, clientMessageIndex: number): void - /** Send a message on an open WebSocket connection identified by messageIdHex. */ - sendWsMessage(gatewayId: Buffer, requestId: Buffer, data: Buffer, binary: boolean): Promise - /** Close an open WebSocket connection. */ - closeWebsocket(gatewayId: Buffer, requestId: Buffer, code?: number | undefined | null, reason?: string | undefined | null): Promise - startServerless(payload: Buffer): Promise - respondCallback(responseId: string, data: any): Promise + started(): Promise; + shutdown(immediate: boolean): void; + get envoyKey(): string; + sleepActor(actorId: string, generation?: number | undefined | null): void; + stopActor( + actorId: string, + generation?: number | undefined | null, + error?: string | undefined | null, + ): void; + destroyActor(actorId: string, generation?: number | undefined | null): void; + setAlarm( + actorId: string, + alarmTs?: number | undefined | null, + generation?: number | undefined | null, + ): void; + kvGet( + actorId: string, + keys: Array, + ): Promise>; + kvPut(actorId: string, entries: Array): Promise; + kvDelete(actorId: string, keys: Array): Promise; + kvDeleteRange(actorId: string, start: Buffer, end: Buffer): Promise; + kvListAll( + actorId: string, + options?: JsKvListOptions | undefined | null, + ): Promise>; + kvListRange( + actorId: string, + start: Buffer, + end: Buffer, + exclusive?: boolean | undefined | null, + options?: JsKvListOptions | undefined | null, + ): Promise>; + kvListPrefix( + actorId: string, + prefix: Buffer, + options?: JsKvListOptions | undefined | null, + ): Promise>; + kvDrop(actorId: string): Promise; + restoreHibernatingRequests( + actorId: string, + requests: Array, + ): void; + sendHibernatableWebSocketMessageAck( + gatewayId: Buffer, + requestId: Buffer, + clientMessageIndex: number, + ): void; + /** Send a message on an open WebSocket connection identified by messageIdHex. */ + sendWsMessage( + gatewayId: Buffer, + requestId: Buffer, + data: Buffer, + binary: boolean, + ): Promise; + /** Close an open WebSocket connection. */ + closeWebsocket( + gatewayId: Buffer, + requestId: Buffer, + code?: number | undefined | null, + reason?: string | undefined | null, + ): Promise; + startServerless(payload: Buffer): Promise; + respondCallback(responseId: string, data: any): Promise; } diff --git a/rivetkit-typescript/packages/rivetkit-native/wrapper.d.ts b/rivetkit-typescript/packages/rivetkit-native/wrapper.d.ts index bc2770b887..699b6041ad 100644 --- a/rivetkit-typescript/packages/rivetkit-native/wrapper.d.ts +++ b/rivetkit-typescript/packages/rivetkit-native/wrapper.d.ts @@ -29,9 +29,16 @@ export interface EnvoyHandle { sleepActor(actorId: string, generation?: number): void; stopActor(actorId: string, generation?: number, error?: string): void; destroyActor(actorId: string, generation?: number): void; - setAlarm(actorId: string, alarmTs: number | null, generation?: number): void; + setAlarm( + actorId: string, + alarmTs: number | null, + generation?: number, + ): void; kvGet(actorId: string, keys: Uint8Array[]): Promise<(Uint8Array | null)[]>; - kvListAll(actorId: string, options?: KvListOptions): Promise<[Uint8Array, Uint8Array][]>; + kvListAll( + actorId: string, + options?: KvListOptions, + ): Promise<[Uint8Array, Uint8Array][]>; kvListRange( actorId: string, start: Uint8Array, @@ -46,7 +53,11 @@ export interface EnvoyHandle { ): Promise<[Uint8Array, Uint8Array][]>; kvPut(actorId: string, entries: [Uint8Array, Uint8Array][]): Promise; kvDelete(actorId: string, keys: Uint8Array[]): Promise; - kvDeleteRange(actorId: string, start: Uint8Array, end: Uint8Array): Promise; + kvDeleteRange( + actorId: string, + start: Uint8Array, + end: Uint8Array, + ): Promise; kvDrop(actorId: string): Promise; restoreHibernatingRequests( actorId: string, @@ -105,7 +116,9 @@ export interface EnvoyConfig { actorId: string, generation: number, config: import("@rivetkit/engine-envoy-protocol").ActorConfig, - preloadedKv: import("@rivetkit/engine-envoy-protocol").PreloadedKv | null, + preloadedKv: + | import("@rivetkit/engine-envoy-protocol").PreloadedKv + | null, ) => Promise; onActorStop: ( envoyHandle: EnvoyHandle, diff --git a/rivetkit-typescript/packages/rivetkit/dynamic-isolate-runtime/src/index.cts b/rivetkit-typescript/packages/rivetkit/dynamic-isolate-runtime/src/index.cts index 92678c1164..1e93af5fb9 100644 --- a/rivetkit-typescript/packages/rivetkit/dynamic-isolate-runtime/src/index.cts +++ b/rivetkit-typescript/packages/rivetkit/dynamic-isolate-runtime/src/index.cts @@ -433,7 +433,6 @@ async function getRuntimeState(): Promise { use: { [bootstrapConfig.actorName]: actorDefinition, }, - serveManager: false, noWelcome: true, test: { enabled: false }, }); diff --git a/rivetkit-typescript/packages/rivetkit/dynamic-isolate-runtime/tsconfig.json b/rivetkit-typescript/packages/rivetkit/dynamic-isolate-runtime/tsconfig.json index 8fd0d1bbfe..f53ddc0467 100644 --- a/rivetkit-typescript/packages/rivetkit/dynamic-isolate-runtime/tsconfig.json +++ b/rivetkit-typescript/packages/rivetkit/dynamic-isolate-runtime/tsconfig.json @@ -3,11 +3,12 @@ "compilerOptions": { "baseUrl": "..", "target": "ES2022", - "module": "CommonJS", - "moduleResolution": "Node", + "module": "ESNext", + "moduleResolution": "Bundler", "types": ["node"], "lib": ["ES2022", "DOM"], "resolveJsonModule": true, + "noEmit": true, "paths": { "@/*": ["./src/*"] } diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/db-pragma-migration.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/db-pragma-migration.ts index e7b24a5d35..9a80fde48d 100644 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/db-pragma-migration.ts +++ b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/db-pragma-migration.ts @@ -29,9 +29,7 @@ export const dbPragmaMigrationActor = actor({ }), actions: { insertItem: async (c, name: string) => { - await c.db.execute( - `INSERT INTO items (name) VALUES ('${name}')`, - ); + await c.db.execute(`INSERT INTO items (name) VALUES ('${name}')`); const results = await c.db.execute<{ id: number }>( "SELECT last_insert_rowid() as id", ); diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/lifecycle-hooks.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/lifecycle-hooks.ts index 230a2c537b..b6b8a26e72 100644 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/lifecycle-hooks.ts +++ b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/lifecycle-hooks.ts @@ -11,7 +11,9 @@ export const beforeConnectTimeoutActor = actor({ }, onBeforeConnect: async (_c, _params: {}) => { // Delay longer than the configured timeout - await new Promise((resolve) => setTimeout(resolve, ON_BEFORE_CONNECT_DELAY)); + await new Promise((resolve) => + setTimeout(resolve, ON_BEFORE_CONNECT_DELAY), + ); }, actions: { ping: () => "pong", diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-dynamic.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-dynamic.ts index 401eb73bfd..f3ad7bbdd5 100644 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-dynamic.ts +++ b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-dynamic.ts @@ -18,10 +18,7 @@ const RIVETKIT_SOURCE_ALIAS = { rivetkit: path.join(PACKAGE_ROOT, "src/mod.ts"), "rivetkit/agent-os": path.join(PACKAGE_ROOT, "src/agent-os/index.ts"), "rivetkit/db": path.join(PACKAGE_ROOT, "src/db/mod.ts"), - "rivetkit/db/drizzle": path.join( - PACKAGE_ROOT, - "src/db/drizzle/mod.ts", - ), + "rivetkit/db/drizzle": path.join(PACKAGE_ROOT, "src/db/drizzle/mod.ts"), "rivetkit/dynamic": path.join(PACKAGE_ROOT, "src/dynamic/mod.ts"), "rivetkit/errors": path.join(PACKAGE_ROOT, "src/actor/errors.ts"), "rivetkit/sandbox": path.join(PACKAGE_ROOT, "src/sandbox/index.ts"), diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts index e944ef86da..3dc0f84730 100644 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts +++ b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/registry-static.ts @@ -36,10 +36,7 @@ import { import { destroyActor, destroyObserver } from "./destroy"; import { customTimeoutActor, errorHandlingActor } from "./error-handling"; import { fileSystemHibernationCleanupActor } from "./file-system-hibernation-cleanup"; -import { - hibernationActor, - hibernationSleepWindowActor, -} from "./hibernation"; +import { hibernationActor, hibernationSleepWindowActor } from "./hibernation"; import { inlineClientActor } from "./inline-client"; import { beforeConnectTimeoutActor, @@ -147,7 +144,7 @@ import { } from "./workflow"; let agentOsTestActor: - | (Awaited["agentOsTestActor"]) + | Awaited["agentOsTestActor"] | undefined; try { @@ -327,9 +324,9 @@ export const registry = setup({ stateChangeRecursionActor, ...(agentOsTestActor ? { - // From agent-os.ts - agentOsTestActor, - } + // From agent-os.ts + agentOsTestActor, + } : {}), }, }); diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sandbox.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sandbox.ts index 75e50a3fa8..5c99c35d8c 100644 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sandbox.ts +++ b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sandbox.ts @@ -19,8 +19,7 @@ function dockerSocketRequest( body?: unknown, ): Promise { return new Promise((resolve, reject) => { - const payload = - body === undefined ? undefined : JSON.stringify(body); + const payload = body === undefined ? undefined : JSON.stringify(body); const req = httpRequest( { socketPath: DOCKER_SOCKET_PATH, @@ -193,7 +192,9 @@ export const dockerSandboxControlActor = actor({ }, ); assertDockerSuccess(createContainer, "docker container create"); - const container = JSON.parse(createContainer.body) as { Id?: string }; + const container = JSON.parse(createContainer.body) as { + Id?: string; + }; if (!container.Id) { throw new Error( `docker container create returned no id: ${createContainer.body}`, @@ -212,15 +213,20 @@ export const dockerSandboxControlActor = actor({ "POST", `/containers/${containerId}/stop?t=5`, ); - assertDockerSuccess(stopContainer, "docker container stop", [ - 304, - 404, - ]); + assertDockerSuccess( + stopContainer, + "docker container stop", + [304, 404], + ); const deleteContainer = await dockerSocketRequest( "DELETE", `/containers/${containerId}?force=true`, ); - assertDockerSuccess(deleteContainer, "docker container delete", [404]); + assertDockerSuccess( + deleteContainer, + "docker container delete", + [404], + ); }, getSandboxUrl: async (_c, sandboxId: string) => { const containerInfo = await inspectContainer(sandboxId); @@ -232,9 +238,9 @@ export const dockerSandboxControlActor = actor({ export const dockerSandboxActor = sandboxActor({ createProvider: (c) => { - const controller = c.client().dockerSandboxControlActor.getOrCreate( - DOCKER_SANDBOX_CONTROL_KEY, - ); + const controller = c + .client() + .dockerSandboxControlActor.getOrCreate(DOCKER_SANDBOX_CONTROL_KEY); const provider: SandboxProvider = { name: "docker", diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sleep-db.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sleep-db.ts index f458cef718..e1261b63f9 100644 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sleep-db.ts +++ b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sleep-db.ts @@ -1,10 +1,7 @@ import type { UniversalWebSocket } from "rivetkit"; import { actor, event, queue } from "rivetkit"; import { db } from "rivetkit/db"; -import { - RAW_WS_HANDLER_DELAY, - RAW_WS_HANDLER_SLEEP_TIMEOUT, -} from "./sleep"; +import { RAW_WS_HANDLER_DELAY, RAW_WS_HANDLER_SLEEP_TIMEOUT } from "./sleep"; export const SLEEP_DB_TIMEOUT = 1000; @@ -320,11 +317,13 @@ export const sleepWaitUntil = actor({ await c.db.execute( `INSERT INTO sleep_log (event, created_at) VALUES ('sleep-start', ${Date.now()})`, ); - c.waitUntil((async () => { - await c.db.execute( - `INSERT INTO sleep_log (event, created_at) VALUES ('waituntil-write', ${Date.now()})`, - ); - })()); + c.waitUntil( + (async () => { + await c.db.execute( + `INSERT INTO sleep_log (event, created_at) VALUES ('waituntil-write', ${Date.now()})`, + ); + })(), + ); }, actions: { triggerSleep: (c) => { @@ -374,17 +373,21 @@ export const sleepNestedWaitUntil = actor({ await c.db.execute( `INSERT INTO sleep_log (event, created_at) VALUES ('sleep-start', ${Date.now()})`, ); - c.waitUntil((async () => { - await c.db.execute( - `INSERT INTO sleep_log (event, created_at) VALUES ('outer-waituntil', ${Date.now()})`, - ); - // Nested waitUntil inside a waitUntil callback - c.waitUntil((async () => { + c.waitUntil( + (async () => { await c.db.execute( - `INSERT INTO sleep_log (event, created_at) VALUES ('nested-waituntil', ${Date.now()})`, + `INSERT INTO sleep_log (event, created_at) VALUES ('outer-waituntil', ${Date.now()})`, + ); + // Nested waitUntil inside a waitUntil callback + c.waitUntil( + (async () => { + await c.db.execute( + `INSERT INTO sleep_log (event, created_at) VALUES ('nested-waituntil', ${Date.now()})`, + ); + })(), ); - })()); - })()); + })(), + ); }, actions: { triggerSleep: (c) => { @@ -605,13 +608,17 @@ export const sleepWaitUntilRejects = actor({ `INSERT INTO sleep_log (event, created_at) VALUES ('sleep', ${Date.now()})`, ); // Register a waitUntil that rejects. Shutdown should still complete. - c.waitUntil(Promise.reject(new Error("waitUntil intentional rejection"))); + c.waitUntil( + Promise.reject(new Error("waitUntil intentional rejection")), + ); // Also register one that succeeds, to verify it still runs. - c.waitUntil((async () => { - await c.db.execute( - `INSERT INTO sleep_log (event, created_at) VALUES ('waituntil-after-reject', ${Date.now()})`, - ); - })()); + c.waitUntil( + (async () => { + await c.db.execute( + `INSERT INTO sleep_log (event, created_at) VALUES ('waituntil-after-reject', ${Date.now()})`, + ); + })(), + ); }, actions: { triggerSleep: (c) => { @@ -659,12 +666,14 @@ export const sleepWaitUntilState = actor({ }, onSleep: async (c) => { c.state.sleepCount += 1; - c.waitUntil((async () => { - c.state.waitUntilRan = true; - await c.db.execute( - `INSERT INTO sleep_log (event, created_at) VALUES ('waituntil-state', ${Date.now()})`, - ); - })()); + c.waitUntil( + (async () => { + c.state.waitUntilRan = true; + await c.db.execute( + `INSERT INTO sleep_log (event, created_at) VALUES ('waituntil-state', ${Date.now()})`, + ); + })(), + ); }, actions: { triggerSleep: (c) => { @@ -818,7 +827,11 @@ const EXCEEDS_GRACE_HANDLER_DELAY = 2000; const EXCEEDS_GRACE_PERIOD = 200; const EXCEEDS_GRACE_SLEEP_TIMEOUT = 100; -export { EXCEEDS_GRACE_HANDLER_DELAY, EXCEEDS_GRACE_PERIOD, EXCEEDS_GRACE_SLEEP_TIMEOUT }; +export { + EXCEEDS_GRACE_HANDLER_DELAY, + EXCEEDS_GRACE_PERIOD, + EXCEEDS_GRACE_SLEEP_TIMEOUT, +}; // Number of sequential DB writes the handler performs. The loop runs long // enough that shutdown (close()) runs between two writes. The write that @@ -880,13 +893,16 @@ export const sleepWsActiveDbExceedsGrace = actor({ c.log.warn({ msg: "websocket send failed during active db write test", error: - error instanceof Error ? error.message : String(error), + error instanceof Error + ? error.message + : String(error), }); }); } catch (error) { c.log.warn({ msg: "websocket send failed during active db write test", - error: error instanceof Error ? error.message : String(error), + error: + error instanceof Error ? error.message : String(error), }); } }; diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sleep.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sleep.ts index e28e875870..34869200ed 100644 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sleep.ts +++ b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/sleep.ts @@ -131,11 +131,15 @@ export const sleepRawWsAddEventListenerMessage = export const sleepRawWsAddEventListenerClose = createAsyncRawWebSocketSleepActor("listener", "close"); -export const sleepRawWsOnMessage = - createAsyncRawWebSocketSleepActor("property", "message"); +export const sleepRawWsOnMessage = createAsyncRawWebSocketSleepActor( + "property", + "message", +); -export const sleepRawWsOnClose = - createAsyncRawWebSocketSleepActor("property", "close"); +export const sleepRawWsOnClose = createAsyncRawWebSocketSleepActor( + "property", + "close", +); export const sleepWithLongRpc = actor({ state: { startCount: 0, sleepCount: 0 }, @@ -337,7 +341,12 @@ export const sleepRawWsSendOnSleep = actor({ onSleep: (c) => { c.state.sleepCount += 1; for (const ws of c.vars.websockets) { - ws.send(JSON.stringify({ type: "sleeping", sleepCount: c.state.sleepCount })); + ws.send( + JSON.stringify({ + type: "sleeping", + sleepCount: c.state.sleepCount, + }), + ); } }, onWebSocket: (c, websocket: UniversalWebSocket) => { @@ -346,7 +355,9 @@ export const sleepRawWsSendOnSleep = actor({ websocket.send(JSON.stringify({ type: "connected" })); websocket.addEventListener("close", () => { - c.vars.websockets = c.vars.websockets.filter((ws) => ws !== websocket); + c.vars.websockets = c.vars.websockets.filter( + (ws) => ws !== websocket, + ); }); }, actions: { @@ -378,7 +389,12 @@ export const sleepRawWsDelayedSendOnSleep = actor({ // Wait before sending await new Promise((resolve) => setTimeout(resolve, 100)); for (const ws of c.vars.websockets) { - ws.send(JSON.stringify({ type: "sleeping", sleepCount: c.state.sleepCount })); + ws.send( + JSON.stringify({ + type: "sleeping", + sleepCount: c.state.sleepCount, + }), + ); } // Wait after sending before completing sleep await new Promise((resolve) => setTimeout(resolve, 100)); @@ -389,7 +405,9 @@ export const sleepRawWsDelayedSendOnSleep = actor({ websocket.send(JSON.stringify({ type: "connected" })); websocket.addEventListener("close", () => { - c.vars.websockets = c.vars.websockets.filter((ws) => ws !== websocket); + c.vars.websockets = c.vars.websockets.filter( + (ws) => ws !== websocket, + ); }); }, actions: { diff --git a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/workflow.ts b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/workflow.ts index 11cc4ed8f8..bb1f7244e6 100644 --- a/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/workflow.ts +++ b/rivetkit-typescript/packages/rivetkit/fixtures/driver-test-suite/workflow.ts @@ -432,13 +432,11 @@ export const workflowSleepActor = actor({ export const workflowTryActor = actor({ state: { innerWrites: 0, - tryStepFailure: null as - | { - kind: string; - message: string; - attempts: number; - } - | null, + tryStepFailure: null as { + kind: string; + message: string; + attempts: number; + } | null, tryJoinFailure: null as string | null, }, run: workflow(async (ctx) => { diff --git a/rivetkit-typescript/packages/rivetkit/package.json b/rivetkit-typescript/packages/rivetkit/package.json index 4badf79814..9bbc8aeaaf 100644 --- a/rivetkit-typescript/packages/rivetkit/package.json +++ b/rivetkit-typescript/packages/rivetkit/package.json @@ -328,8 +328,8 @@ "@rivetkit/bare-ts": "^0.6.2", "@rivetkit/engine-cli": "workspace:*", "@rivetkit/engine-envoy-protocol": "workspace:*", - "@rivetkit/rivetkit-native": "workspace:*", "@rivetkit/engine-runner": "workspace:*", + "@rivetkit/rivetkit-native": "workspace:*", "@rivetkit/fast-json-patch": "^3.1.2", "@rivetkit/on-change": "^6.0.2-rc.1", "@rivetkit/traces": "workspace:*", diff --git a/rivetkit-typescript/packages/rivetkit/runtime/index.ts b/rivetkit-typescript/packages/rivetkit/runtime/index.ts index c883b1d7aa..f02747c525 100644 --- a/rivetkit-typescript/packages/rivetkit/runtime/index.ts +++ b/rivetkit-typescript/packages/rivetkit/runtime/index.ts @@ -2,7 +2,11 @@ import invariant from "invariant"; import { convertRegistryConfigToClientConfig } from "@/client/config"; import { createClientWithDriver } from "@/client/client"; import { configureBaseLogger, configureDefaultLogger } from "@/common/log"; -import { ENGINE_ENDPOINT, ENGINE_PORT, ensureEngineProcess } from "@/engine-process/mod"; +import { + ENGINE_ENDPOINT, + ENGINE_PORT, + ensureEngineProcess, +} from "@/engine-process/mod"; import { getDatacenters, updateRunnerConfig, @@ -62,7 +66,7 @@ export class Runtime { #actorDriver?: EngineActorDriver; #startKind?: StartKind; - managerPort?: number; + httpPort?: number; #serverlessRouter?: ReturnType["router"]; get config() { @@ -77,12 +81,12 @@ export class Runtime { registry: Registry, config: RegistryConfig, engineClient: EngineControlClient, - managerPort?: number, + httpPort?: number, ) { this.#registry = registry; this.#config = config; this.#engineClient = engineClient; - this.managerPort = managerPort; + this.httpPort = httpPort; } static async create( @@ -98,17 +102,15 @@ export class Runtime { configureDefaultLogger(config.logging?.level); } - const shouldSpawnEngine = - config.serverless.spawnEngine || (config.serveManager && !config.endpoint); - if (shouldSpawnEngine) { + if (config.startEngine) { config.endpoint = ENGINE_ENDPOINT; logger().debug({ msg: "spawning engine", - version: config.serverless.engineVersion, + version: config.engineVersion, }); await ensureEngineProcess({ - version: config.serverless.engineVersion, + version: config.engineVersion, }); } @@ -117,95 +119,99 @@ export class Runtime { ); await ensureLocalRunnerConfig(config); - let managerPort: number | undefined; - if (config.serveManager) { - const configuredManagerPort = config.managerPort; - const serveRuntime = detectRuntime(); - let upgradeWebSocket: any; - const getUpgradeWebSocket: GetUpgradeWebSocket = () => - upgradeWebSocket; - engineClient.setGetUpgradeWebSocket(getUpgradeWebSocket); - - const { router: runtimeRouter } = buildRuntimeRouter( - config, - engineClient, - getUpgradeWebSocket, - serveRuntime, - ); + const runtime = new Runtime(registry, config, engineClient); - managerPort = await findFreePort(config.managerPort); + logger().info({ + msg: "rivetkit ready", + driver: "engine", + definitions: Object.keys(config.use).length, + ...(engineClient.extraStartupLog?.() ?? {}), + }); - if (managerPort !== configuredManagerPort) { - logger().warn({ - msg: `port ${configuredManagerPort} is in use, using ${managerPort}`, - }); - } + return runtime; + } - logger().debug({ - msg: "serving runtime router", - port: managerPort, + async ensureHttpServer(): Promise { + if (this.httpPort) { + return; + } + + const configuredHttpPort = this.#config.httpPort; + const serveRuntime = detectRuntime(); + let upgradeWebSocket: any; + const getUpgradeWebSocket: GetUpgradeWebSocket = () => upgradeWebSocket; + this.#engineClient.setGetUpgradeWebSocket(getUpgradeWebSocket); + + const { router: runtimeRouter } = buildRuntimeRouter( + this.#config, + this.#engineClient, + getUpgradeWebSocket, + serveRuntime, + ); + + const httpPort = await findFreePort(configuredHttpPort); + if (httpPort !== configuredHttpPort) { + logger().warn({ + msg: `port ${configuredHttpPort} is in use, using ${httpPort}`, }); + } - if ( - config.publicEndpoint === - `http://127.0.0.1:${configuredManagerPort}` - ) { - config.publicEndpoint = `http://127.0.0.1:${managerPort}`; - config.serverless.publicEndpoint = config.publicEndpoint; - } - config.managerPort = managerPort; - - let serverApp = runtimeRouter; - if (config.publicDir) { - let dirExists = false; - try { - const fsSync = getNodeFsSync(); - dirExists = fsSync.existsSync(config.publicDir); - } catch { - // Node fs not available. - } + logger().debug({ + msg: "serving local HTTP server", + port: httpPort, + }); - if (dirExists) { - const { Hono } = await import("hono"); - const serveStaticFn = - await loadRuntimeServeStatic(serveRuntime); - const wrapper = new Hono(); - wrapper.use( - "*", - serveStaticFn({ root: `./${config.publicDir}` }), - ); - wrapper.route("/", runtimeRouter); - serverApp = wrapper; - } + if ( + this.#config.publicEndpoint === + `http://127.0.0.1:${configuredHttpPort}` + ) { + this.#config.publicEndpoint = `http://127.0.0.1:${httpPort}`; + this.#config.serverless.publicEndpoint = + this.#config.publicEndpoint; + } + this.#config.httpPort = httpPort; + + let serverApp = runtimeRouter; + if (this.#config.staticDir) { + let dirExists = false; + try { + const fsSync = getNodeFsSync(); + dirExists = fsSync.existsSync(this.#config.staticDir); + } catch { + // Node fs not available. } - const out = await crossPlatformServe( - config, - managerPort, - serverApp, - serveRuntime, - ); - upgradeWebSocket = out.upgradeWebSocket; - - if (out.closeServer && process.env.NODE_ENV !== "production") { - const shutdown = () => { - out.closeServer!(); - }; - process.on("SIGTERM", shutdown); - process.on("SIGINT", shutdown); + if (dirExists) { + const { Hono } = await import("hono"); + const serveStaticFn = + await loadRuntimeServeStatic(serveRuntime); + const wrapper = new Hono(); + wrapper.use( + "*", + serveStaticFn({ root: `./${this.#config.staticDir}` }), + ); + wrapper.route("/", runtimeRouter); + serverApp = wrapper; } } - const runtime = new Runtime(registry, config, engineClient, managerPort); - - logger().info({ - msg: "rivetkit ready", - driver: "engine", - definitions: Object.keys(config.use).length, - ...(engineClient.extraStartupLog?.() ?? {}), - }); + const out = await crossPlatformServe( + this.#config, + httpPort, + serverApp, + serveRuntime, + ); + upgradeWebSocket = out.upgradeWebSocket; + + if (out.closeServer && process.env.NODE_ENV !== "production") { + const shutdown = () => { + out.closeServer!(); + }; + process.on("SIGTERM", shutdown); + process.on("SIGINT", shutdown); + } - return runtime; + this.httpPort = httpPort; } startServerless(): void { @@ -217,7 +223,7 @@ export class Runtime { this.#printWelcome(); - if (this.#config.serverless.configurePool) { + if (this.#config.configurePool) { // biome-ignore lint/nursery/noFloatingPromises: intentional configureServerlessPool(this.#config); } @@ -247,8 +253,8 @@ export class Runtime { #printWelcome(): void { if (this.#config.noWelcome) return; - const inspectorUrl = this.managerPort - ? getInspectorUrl(this.#config, this.managerPort) + const inspectorUrl = this.httpPort + ? getInspectorUrl(this.#config, this.httpPort) : undefined; console.log(); @@ -261,9 +267,10 @@ export class Runtime { } if (this.#config.endpoint) { - const endpointType = this.#config.endpoint === ENGINE_ENDPOINT - ? "local native" - : "remote"; + const endpointType = + this.#config.endpoint === ENGINE_ENDPOINT + ? "local native" + : "remote"; logLine("Endpoint", `${this.#config.endpoint} (${endpointType})`); } @@ -271,11 +278,11 @@ export class Runtime { logLine("Client", this.#config.publicEndpoint); } - if (this.#config.publicDir) { + if (this.#config.staticDir) { try { const fsSync = getNodeFsSync(); - if (fsSync.existsSync(this.#config.publicDir)) { - logLine("Static", `./${this.#config.publicDir}`); + if (fsSync.existsSync(this.#config.staticDir)) { + logLine("Static", `./${this.#config.staticDir}`); } } catch { // Node fs not available. diff --git a/rivetkit-typescript/packages/rivetkit/src/actor-gateway/actor-path.ts b/rivetkit-typescript/packages/rivetkit/src/actor-gateway/actor-path.ts index eb3100d004..d82bb21d0e 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor-gateway/actor-path.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor-gateway/actor-path.ts @@ -101,7 +101,12 @@ export function parseActorPath(path: string): ParsedActorPath | null { if (hasRvt) { const rvtParams = extractRvtParamsFromRaw(rawQueryStr); const actorQueryString = stripRvtQueryParams(rawQueryStr); - return parseQueryActorPath(basePath, segments, rvtParams, actorQueryString); + return parseQueryActorPath( + basePath, + segments, + rvtParams, + actorQueryString, + ); } // Direct path: pass the raw query string through unchanged. @@ -225,9 +230,7 @@ function splitKey(raw: string | undefined): string[] { return raw.split(","); } -function extractRvtParams( - rvtRaw: Array<[string, string]>, -): RvtParams { +function extractRvtParams(rvtRaw: Array<[string, string]>): RvtParams { const params = new Map(); for (const [rawKey, value] of rvtRaw) { @@ -315,10 +318,7 @@ function extractRvtParams( }; } -function buildActorQuery( - name: string, - rvt: RvtParams, -): ActorGatewayQuery { +function buildActorQuery(name: string, rvt: RvtParams): ActorGatewayQuery { switch (rvt.method) { case "get": { if ( @@ -405,9 +405,7 @@ function splitPathAndQuery(path: string): [string, string | null] { * Extract rvt-* params from a raw query string, decoding their values * using form-urlencoded rules (`+` as space, then percent-decode). */ -function extractRvtParamsFromRaw( - rawQuery: string, -): Array<[string, string]> { +function extractRvtParamsFromRaw(rawQuery: string): Array<[string, string]> { const params: Array<[string, string]> = []; for (const part of rawQuery.split("&")) { const eqPos = part.indexOf("="); diff --git a/rivetkit-typescript/packages/rivetkit/src/actor-gateway/gateway.ts b/rivetkit-typescript/packages/rivetkit/src/actor-gateway/gateway.ts index d313198607..d46d488730 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor-gateway/gateway.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor-gateway/gateway.ts @@ -150,11 +150,8 @@ export async function actorGateway( // Strip basePath from the request path let strippedPath = c.req.path; - if ( - config.managerBasePath && - strippedPath.startsWith(config.managerBasePath) - ) { - strippedPath = strippedPath.slice(config.managerBasePath.length); + if (config.httpBasePath && strippedPath.startsWith(config.httpBasePath)) { + strippedPath = strippedPath.slice(config.httpBasePath.length); // Ensure the path starts with / if (!strippedPath.startsWith("/")) { strippedPath = `/${strippedPath}`; diff --git a/rivetkit-typescript/packages/rivetkit/src/actor-gateway/resolve-query.ts b/rivetkit-typescript/packages/rivetkit/src/actor-gateway/resolve-query.ts index e7a7b73ec4..e2a925cc73 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor-gateway/resolve-query.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor-gateway/resolve-query.ts @@ -35,11 +35,7 @@ export async function resolvePathBasedActorPath( assertQueryNamespaceMatchesConfig(config, actorPathInfo.namespace); - const actorId = await resolveQueryActorId( - engineClient, - c, - actorPathInfo, - ); + const actorId = await resolveQueryActorId(engineClient, c, actorPathInfo); logger().debug({ msg: "resolved query gateway path to actor", @@ -104,7 +100,7 @@ function assertQueryNamespaceMatchesConfig( return; } - throw new errors.InvalidRequest( + throw new errors.InvalidRequest( `query gateway namespace '${namespace}' does not match runtime namespace '${config.namespace}'`, ); } diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/config.ts b/rivetkit-typescript/packages/rivetkit/src/actor/config.ts index 05cdc21dfd..8f5c9b25f8 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/config.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/config.ts @@ -223,10 +223,7 @@ const InstanceActorOptionsBaseSchema = z onBeforeConnectTimeout: z.number().positive().default(5000), onConnectTimeout: z.number().positive().default(5000), sleepGracePeriod: z.number().positive().optional(), - onSleepTimeout: z - .number() - .positive() - .default(DEFAULT_ON_SLEEP_TIMEOUT), + onSleepTimeout: z.number().positive().default(DEFAULT_ON_SLEEP_TIMEOUT), onDestroyTimeout: z.number().positive().default(5000), stateSaveInterval: z.number().positive().default(1_000), actionTimeout: z.number().positive().default(60_000), @@ -243,7 +240,10 @@ const InstanceActorOptionsBaseSchema = z noSleep: z.boolean().default(false), sleepTimeout: z.number().positive().default(30_000), maxQueueSize: z.number().positive().default(1000), - maxQueueMessageSize: z.number().positive().default(64 * 1024), + maxQueueMessageSize: z + .number() + .positive() + .default(64 * 1024), /** Override RivetKit's workflow preload budget for this actor. Set to 0 to disable workflow preloading. */ preloadMaxWorkflowBytes: z.number().nonnegative().optional(), /** Override RivetKit's connections preload budget for this actor. Set to 0 to disable connections preloading. */ diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/conn/mod.ts b/rivetkit-typescript/packages/rivetkit/src/actor/conn/mod.ts index 4b1ea2e60a..83e24d60a0 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/conn/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/conn/mod.ts @@ -270,7 +270,11 @@ export class Conn< try { if (driver.disconnect) { try { - await driver.disconnect(this.#actor, this, reason); + await driver.disconnect( + this.#actor, + this, + reason, + ); } catch (error) { this.#actor.rLog.warn({ msg: "conn driver disconnect failed, continuing connection cleanup", @@ -286,7 +290,9 @@ export class Conn< }); } - await this.#actor.connectionManager.connDisconnected(this); + await this.#actor.connectionManager.connDisconnected( + this, + ); } finally { this[CONN_DRIVER_SYMBOL] = undefined; } diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/definition.ts b/rivetkit-typescript/packages/rivetkit/src/actor/definition.ts index b2498b8d3f..9d46a7de06 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/definition.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/definition.ts @@ -70,7 +70,8 @@ export class ActorDefinition< E, Q >, - > implements BaseActorDefinition { +> implements BaseActorDefinition +{ #config: ActorConfig; constructor(config: ActorConfig) { diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/instance/connection-manager.ts b/rivetkit-typescript/packages/rivetkit/src/actor/instance/connection-manager.ts index cfbd830942..4415b28576 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/instance/connection-manager.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/instance/connection-manager.ts @@ -106,7 +106,9 @@ export class ConnectionManager< ): Promise> { this.#actor.assertReady(); if (this.#actor.isStopping) - throw new errors.ActorStopping("Cannot accept new connections while actor is stopping"); + throw new errors.ActorStopping( + "Cannot accept new connections while actor is stopping", + ); // TODO: Add back // const url = request?.url; diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts b/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts index c77607136c..4b05044917 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts @@ -201,8 +201,7 @@ const ACTIVE_ASYNC_REGION_ERROR_MESSAGES: Record< keyof ActiveAsyncRegionCounts, string > = { - keepAwake: - "active keep awake count went below 0, this is a RivetKit bug", + keepAwake: "active keep awake count went below 0, this is a RivetKit bug", internalKeepAwake: "active internal keep awake count went below 0, this is a RivetKit bug", websocketCallbacks: @@ -287,18 +286,18 @@ export function isStaticActorInstance( export type ExtractActorState = A extends ActorInstance - ? State - : never; + ? State + : never; export type ExtractActorConnParams = A extends ActorInstance - ? ConnParams - : never; + ? ConnParams + : never; export type ExtractActorConnState = A extends ActorInstance - ? ConnState - : never; + ? ConnState + : never; // MARK: - Main ActorInstance Class export class ActorInstance< @@ -310,7 +309,8 @@ export class ActorInstance< DB extends AnyDatabaseProvider, E extends EventSchemaConfig = Record, Q extends QueueSchemaConfig = Record, -> implements BaseActorInstance { +> implements BaseActorInstance +{ // MARK: - Core Properties actorContext: ActorContext; #config: ActorConfig; @@ -968,15 +968,15 @@ export class ActorInstance< // is intentional and safe. try { this.#abortController.abort(); - } catch { } + } catch {} // Wait for run handler to complete await this.#waitForRunHandler( this.overrides.runStopTimeout !== undefined ? Math.min( - this.#config.options.runStopTimeout, - this.overrides.runStopTimeout, - ) + this.#config.options.runStopTimeout, + this.overrides.runStopTimeout, + ) : this.#config.options.runStopTimeout, ); @@ -1025,7 +1025,9 @@ export class ActorInstance< return; } if (this.#stopCalled) { - this.#rLog.warn({ msg: "already stopping actor during hard crash" }); + this.#rLog.warn({ + msg: "already stopping actor during hard crash", + }); return; } this.#stopCalled = true; @@ -1041,7 +1043,7 @@ export class ActorInstance< try { this.#abortController.abort(); - } catch { } + } catch {} } finally { this.#shutdownComplete = true; await this.#cleanupDatabase(); @@ -1100,7 +1102,7 @@ export class ActorInstance< // modes. try { this.#abortController.abort(); - } catch { } + } catch {} const destroy = this.driver.startDestroy.bind( this.driver, @@ -1137,14 +1139,14 @@ export class ActorInstance< async processMessage( message: { body: - | { - tag: "ActionRequest"; - val: { id: bigint; name: string; args: unknown }; - } - | { - tag: "SubscriptionRequest"; - val: { eventName: string; subscribe: boolean }; - }; + | { + tag: "ActionRequest"; + val: { id: bigint; name: string; args: unknown }; + } + | { + tag: "SubscriptionRequest"; + val: { eventName: string; subscribe: boolean }; + }; }, conn: Conn, ) { @@ -1515,9 +1517,9 @@ export class ActorInstance< if (this.overrides.sleepGracePeriod !== undefined) { return this.#config.options.sleepGracePeriod !== undefined ? Math.min( - this.#config.options.sleepGracePeriod, - this.overrides.sleepGracePeriod, - ) + this.#config.options.sleepGracePeriod, + this.overrides.sleepGracePeriod, + ) : this.overrides.sleepGracePeriod; } @@ -1528,16 +1530,16 @@ export class ActorInstance< const effectiveOnSleepTimeout = this.overrides.onSleepTimeout !== undefined ? Math.min( - this.#config.options.onSleepTimeout, - this.overrides.onSleepTimeout, - ) + this.#config.options.onSleepTimeout, + this.overrides.onSleepTimeout, + ) : this.#config.options.onSleepTimeout; const effectiveWaitUntilTimeout = this.overrides.waitUntilTimeout !== undefined ? Math.min( - this.#config.options.waitUntilTimeout, - this.overrides.waitUntilTimeout, - ) + this.#config.options.waitUntilTimeout, + this.overrides.waitUntilTimeout, + ) : this.#config.options.waitUntilTimeout; const usesDefaultLegacyTimeouts = @@ -1981,10 +1983,7 @@ export class ActorInstance< if (remaining <= 0) { throw new DeadlineError(); } - await deadline( - result, - remaining, - ); + await deadline(result, remaining); } }, ); @@ -2017,10 +2016,10 @@ export class ActorInstance< result, this.overrides.onDestroyTimeout !== undefined ? Math.min( - this.#config.options - .onDestroyTimeout, - this.overrides.onDestroyTimeout, - ) + this.#config.options + .onDestroyTimeout, + this.overrides.onDestroyTimeout, + ) : this.#config.options.onDestroyTimeout, ); } @@ -2150,16 +2149,16 @@ export class ActorInstance< overrideRawDatabaseClient: this.driver .overrideRawDatabaseClient ? () => - this.driver.overrideRawDatabaseClient!( - this.#actorId, - ) + this.driver.overrideRawDatabaseClient!( + this.#actorId, + ) : undefined, overrideDrizzleDatabaseClient: this.driver .overrideDrizzleDatabaseClient ? () => - this.driver.overrideDrizzleDatabaseClient!( - this.#actorId, - ) + this.driver.overrideDrizzleDatabaseClient!( + this.#actorId, + ) : undefined, kv: { batchPut: (entries: [Uint8Array, Uint8Array][]) => @@ -2169,7 +2168,11 @@ export class ActorInstance< batchDelete: (keys: Uint8Array[]) => this.driver.kvBatchDelete(this.#actorId, keys), deleteRange: (start: Uint8Array, end: Uint8Array) => - this.driver.kvDeleteRange(this.#actorId, start, end), + this.driver.kvDeleteRange( + this.#actorId, + start, + end, + ), }, metrics: this.#metrics, log: this.#rLog, @@ -2329,7 +2332,8 @@ export class ActorInstance< let timeoutHandle: ReturnType | undefined; const timedOut = await Promise.race([ Promise.allSettled(promises.slice(0, batch)).then(() => { - if (timeoutHandle !== undefined) clearTimeout(timeoutHandle); + if (timeoutHandle !== undefined) + clearTimeout(timeoutHandle); return false; }), new Promise((resolve) => { @@ -2377,7 +2381,8 @@ export class ActorInstance< let timeoutHandle: ReturnType | undefined; const timedOut = await Promise.race([ this.#preventSleepClearedPromise.promise.then(() => { - if (timeoutHandle !== undefined) clearTimeout(timeoutHandle); + if (timeoutHandle !== undefined) + clearTimeout(timeoutHandle); return false; }), new Promise((resolve) => { diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/instance/schedule-manager.ts b/rivetkit-typescript/packages/rivetkit/src/actor/instance/schedule-manager.ts index 599b7bd392..da3e25a323 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/instance/schedule-manager.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/instance/schedule-manager.ts @@ -262,7 +262,10 @@ export class ScheduleManager< // event is persisted and will be re-armed by initializeAlarms() on // next wake. if (!this.#actor.isStopping) { - if (insertIndex === 0 || this.#persist.scheduledEvents.length === 1) { + if ( + insertIndex === 0 || + this.#persist.scheduledEvents.length === 1 + ) { this.#actor.log.info({ msg: "setting alarm for new event", timestamp: newEvent.timestamp, diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/instance/tracked-websocket.ts b/rivetkit-typescript/packages/rivetkit/src/actor/instance/tracked-websocket.ts index 7afce4e0c4..f3a1b1c8a3 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/instance/tracked-websocket.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/instance/tracked-websocket.ts @@ -21,18 +21,12 @@ export class TrackedWebSocket implements UniversalWebSocket { #options: TrackedWebSocketOptions; #listeners = new Map(); #onopen: ((event: RivetEvent) => void | Promise) | null = null; - #onclose: - | ((event: RivetCloseEvent) => void | Promise) - | null = null; + #onclose: ((event: RivetCloseEvent) => void | Promise) | null = null; #onerror: ((event: RivetEvent) => void | Promise) | null = null; - #onmessage: - | ((event: RivetMessageEvent) => void | Promise) - | null = null; + #onmessage: ((event: RivetMessageEvent) => void | Promise) | null = + null; - constructor( - inner: UniversalWebSocket, - options: TrackedWebSocketOptions, - ) { + constructor(inner: UniversalWebSocket, options: TrackedWebSocketOptions) { this.#inner = inner; this.#options = options; @@ -96,11 +90,13 @@ export class TrackedWebSocket implements UniversalWebSocket { send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void { try { - const result = (this.#inner as { - send( - data: string | ArrayBufferLike | Blob | ArrayBufferView, - ): unknown; - }).send(data); + const result = ( + this.#inner as { + send( + data: string | ArrayBufferLike | Blob | ArrayBufferView, + ): unknown; + } + ).send(data); void Promise.resolve(result).catch((error) => { this.#options.onError("send", error); }); @@ -145,15 +141,13 @@ export class TrackedWebSocket implements UniversalWebSocket { this.#onopen = fn; } - get onclose(): - | ((event: RivetCloseEvent) => void | Promise) - | null { + get onclose(): ((event: RivetCloseEvent) => void | Promise) | null { return this.#onclose; } - set onclose( - fn: ((event: RivetCloseEvent) => void | Promise) | null, - ) { + set onclose(fn: + | ((event: RivetCloseEvent) => void | Promise) + | null,) { this.#onclose = fn; } @@ -171,9 +165,9 @@ export class TrackedWebSocket implements UniversalWebSocket { return this.#onmessage; } - set onmessage( - fn: ((event: RivetMessageEvent) => void | Promise) | null, - ) { + set onmessage(fn: + | ((event: RivetMessageEvent) => void | Promise) + | null,) { this.#onmessage = fn; } @@ -207,7 +201,9 @@ export class TrackedWebSocket implements UniversalWebSocket { ...(event.message !== undefined ? { message: event.message } : {}), - ...(event.error !== undefined ? { error: event.error } : {}), + ...(event.error !== undefined + ? { error: event.error } + : {}), } satisfies RivetEvent; } } @@ -225,10 +221,12 @@ export class TrackedWebSocket implements UniversalWebSocket { if (this.#onopen) this.#callHandler(type, this.#onopen, event); break; case "close": - if (this.#onclose) this.#callHandler(type, this.#onclose, event); + if (this.#onclose) + this.#callHandler(type, this.#onclose, event); break; case "error": - if (this.#onerror) this.#callHandler(type, this.#onerror, event); + if (this.#onerror) + this.#callHandler(type, this.#onerror, event); break; case "message": if (this.#onmessage) @@ -237,11 +235,7 @@ export class TrackedWebSocket implements UniversalWebSocket { } } - #callHandler( - type: string, - handler: WebSocketListener, - event: any, - ): void { + #callHandler(type: string, handler: WebSocketListener, event: any): void { try { const result = handler(event); if (this.#isPromiseLike(result)) { @@ -252,9 +246,7 @@ export class TrackedWebSocket implements UniversalWebSocket { } } - #isPromiseLike( - value: unknown, - ): value is PromiseLike { + #isPromiseLike(value: unknown): value is PromiseLike { return ( typeof value === "object" && value !== null && diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/router-websocket-endpoints.ts b/rivetkit-typescript/packages/rivetkit/src/actor/router-websocket-endpoints.ts index 890c16eb00..47e084b2eb 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/router-websocket-endpoints.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/router-websocket-endpoints.ts @@ -180,7 +180,6 @@ export async function routeWebSocket( ); createdConn = conn; - // Create handler // // This must call actor.connectionManager.connectConn in onOpen. @@ -216,8 +215,8 @@ export async function routeWebSocket( onMessage: (_evt: { data: any }, ws: WSContext) => { ws.close(1011, "actor.not_loaded"); }, - onClose: (_event: any, _ws: WSContext) => { }, - onError: (_error: unknown) => { }, + onClose: (_event: any, _ws: WSContext) => {}, + onError: (_error: unknown) => {}, }; } } @@ -356,12 +355,7 @@ export async function handleWebSocketConnect( export async function handleRawWebSocket( setWebSocket: (ws: UniversalWebSocket) => void, - { - request, - actor, - closePromiseResolvers, - conn, - }: WebSocketHandlerOpts, + { request, actor, closePromiseResolvers, conn }: WebSocketHandlerOpts, ): Promise { return { conn, @@ -409,7 +403,7 @@ export async function handleRawWebSocket( // this is called synchronously within onOpen. actor.handleRawWebSocket(conn, ws, request); }, - onMessage: (_evt: any, _wsContext: any) => { }, + onMessage: (_evt: any, _wsContext: any) => {}, onClose: (evt: any, ws: any) => { // Resolve the close promise closePromiseResolvers.resolve(); @@ -423,7 +417,7 @@ export async function handleRawWebSocket( }); }); }, - onError: (_error: any, _ws: any) => { }, + onError: (_error: any, _ws: any) => {}, }; } diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/router.ts b/rivetkit-typescript/packages/rivetkit/src/actor/router.ts index e5b7ea8b31..7e343d82f1 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/router.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/router.ts @@ -19,10 +19,7 @@ import { loggerMiddleware, } from "@/common/router"; import { noopNext } from "@/common/utils"; -import { - isSqliteBindingArray, - isSqliteBindingObject, -} from "@/db/shared"; +import { isSqliteBindingArray, isSqliteBindingObject } from "@/db/shared"; import { inspectorLogger } from "@/inspector/log"; import type { RegistryConfig } from "@/registry/config"; import { type GetUpgradeWebSocket, VERSION } from "@/utils"; @@ -55,7 +52,9 @@ export interface MetadataResponse { async function loadStaticActor(actorDriver: ActorDriver, actorId: string) { const actor = await actorDriver.loadActor(actorId); if (!isStaticActorInstance(actor)) { - throw new Error("dynamic actor cannot be handled by static actor router"); + throw new Error( + "dynamic actor cannot be handled by static actor router", + ); } return actor; } @@ -475,13 +474,12 @@ export function createActorRouter( // TODO: This is not a clean way of doing this since `/http/` might exist mid-path // Strip the /http prefix from the URL to get the original path const requestUrl = - c.req.url || - c.req.raw.url || - `http://actor${c.req.path || "/"}`; - const url = requestUrl.startsWith("http://") || + c.req.url || c.req.raw.url || `http://actor${c.req.path || "/"}`; + const url = + requestUrl.startsWith("http://") || requestUrl.startsWith("https://") - ? new URL(requestUrl) - : new URL(requestUrl, "http://actor"); + ? new URL(requestUrl) + : new URL(requestUrl, "http://actor"); const originalPath = url.pathname.replace(/^\/request/, "") || "/"; // Create a new request with the corrected URL diff --git a/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/cron.ts b/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/cron.ts index 33c7deb53c..b6c839f7bd 100644 --- a/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/cron.ts +++ b/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/cron.ts @@ -1,6 +1,9 @@ import type { CronAction, CronJobInfo } from "@rivet-dev/agent-os-core"; import type { AgentOsActorConfig } from "../config"; -import type { AgentOsActionContext, SerializableCronJobOptions } from "../types"; +import type { + AgentOsActionContext, + SerializableCronJobOptions, +} from "../types"; import { ensureVm } from "./index"; // Build cron scheduling actions for the actor factory. diff --git a/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/index.ts b/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/index.ts index 30bdf37cfa..d6e1efb901 100644 --- a/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/index.ts +++ b/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/index.ts @@ -68,9 +68,7 @@ async function ensureVm( return agentOs; } -function buildVmOptions( - userOptions?: AgentOsOptions, -): AgentOsOptions { +function buildVmOptions(userOptions?: AgentOsOptions): AgentOsOptions { const userMounts = userOptions?.mounts ?? []; // Check if the user already provided a mount at /home/user. If so, respect diff --git a/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/process.ts b/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/process.ts index 1ba2e2e1ca..276de42064 100644 --- a/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/process.ts +++ b/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/process.ts @@ -12,7 +12,9 @@ import { ensureVm, syncPreventSleep } from "./index"; type ExecResult = Awaited< ReturnType >; -type ExecOptions = Parameters[1]; +type ExecOptions = Parameters< + import("@rivet-dev/agent-os-core").AgentOs["exec"] +>[1]; type SpawnOptions = Parameters< import("@rivet-dev/agent-os-core").AgentOs["spawn"] >[2]; @@ -81,7 +83,8 @@ export function buildProcessActions( command, }); - agentOs.waitProcess(pid) + agentOs + .waitProcess(pid) .then((exitCode) => { broadcastProcessEvent(c, "processExit", { pid, exitCode }); c.log.info({ diff --git a/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/session.ts b/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/session.ts index 49d1715f5f..8bf1e78b00 100644 --- a/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/session.ts +++ b/rivetkit-typescript/packages/rivetkit/src/agent-os/actor/session.ts @@ -134,7 +134,10 @@ export function subscribeToSession( parsedConfig: AgentOsActorConfig, ): void { agentOs.onSessionEvent(sessionId, (event) => { - c.broadcast("sessionEvent", JSON.parse(JSON.stringify({ sessionId, event }))); + c.broadcast( + "sessionEvent", + JSON.parse(JSON.stringify({ sessionId, event })), + ); // Persist event to SQLite for sleep/wake recovery. persistSessionEvent(c, sessionId, event).catch((err) => @@ -153,7 +156,10 @@ export function subscribeToSession( }); agentOs.onPermissionRequest(sessionId, (request) => { - c.broadcast("permissionRequest", JSON.parse(JSON.stringify({ sessionId, request }))); + c.broadcast( + "permissionRequest", + JSON.parse(JSON.stringify({ sessionId, request })), + ); if (parsedConfig.onPermissionRequest) { runHook(c, "onPermissionRequest", () => @@ -176,7 +182,10 @@ export function buildSessionActions( options?: CreateSessionOptions, ): Promise => { const agentOs = await ensureVm(c, config); - const { sessionId } = await agentOs.createSession(agentType, options); + const { sessionId } = await agentOs.createSession( + agentType, + options, + ); subscribeToSession(c, agentOs, sessionId, config); // Persist session metadata to SQLite for sleep/wake recovery. @@ -203,7 +212,9 @@ export function buildSessionActions( ): Promise => { assertSessionExists(c, sessionId); const agentOs = await ensureVm(c, config); - const info = agentOs.listSessions().find((s) => s.sessionId === sessionId); + const info = agentOs + .listSessions() + .find((s) => s.sessionId === sessionId); if (!info) { throw new Error(`session not found: ${sessionId}`); } @@ -305,7 +316,9 @@ export function buildPromptActions( if (!agentOs) { throw new Error("VM not initialized"); } - return stripFunctions(agentOs.cancelSession(sessionId)) as JsonRpcResponse; + return stripFunctions( + agentOs.cancelSession(sessionId), + ) as JsonRpcResponse; }, respondPermission: async ( @@ -319,7 +332,9 @@ export function buildPromptActions( if (!agentOs) { throw new Error("VM not initialized"); } - return stripFunctions(agentOs.respondPermission(sessionId, permissionId, reply)) as JsonRpcResponse; + return stripFunctions( + agentOs.respondPermission(sessionId, permissionId, reply), + ) as JsonRpcResponse; }, }; } @@ -339,7 +354,9 @@ export function buildConfigActions( if (!agentOs) { throw new Error("VM not initialized"); } - return stripFunctions(agentOs.setSessionMode(sessionId, modeId)) as JsonRpcResponse; + return stripFunctions( + agentOs.setSessionMode(sessionId, modeId), + ) as JsonRpcResponse; }, getModes: async ( @@ -364,7 +381,9 @@ export function buildConfigActions( if (!agentOs) { throw new Error("VM not initialized"); } - return stripFunctions(agentOs.setSessionModel(sessionId, model)) as JsonRpcResponse; + return stripFunctions( + agentOs.setSessionModel(sessionId, model), + ) as JsonRpcResponse; }, setThoughtLevel: async ( @@ -377,7 +396,9 @@ export function buildConfigActions( if (!agentOs) { throw new Error("VM not initialized"); } - return stripFunctions(agentOs.setSessionThoughtLevel(sessionId, level)) as JsonRpcResponse; + return stripFunctions( + agentOs.setSessionThoughtLevel(sessionId, level), + ) as JsonRpcResponse; }, getConfigOptions: async ( @@ -431,7 +452,9 @@ export function buildConfigActions( if (!agentOs) { throw new Error("VM not initialized"); } - return stripFunctions(agentOs.rawSessionSend(sessionId, method, params)) as JsonRpcResponse; + return stripFunctions( + agentOs.rawSessionSend(sessionId, method, params), + ) as JsonRpcResponse; }, }; } diff --git a/rivetkit-typescript/packages/rivetkit/src/agent-os/fs/database-vfs.ts b/rivetkit-typescript/packages/rivetkit/src/agent-os/fs/database-vfs.ts index 8af1d51080..3ca6345772 100644 --- a/rivetkit-typescript/packages/rivetkit/src/agent-os/fs/database-vfs.ts +++ b/rivetkit-typescript/packages/rivetkit/src/agent-os/fs/database-vfs.ts @@ -426,7 +426,8 @@ export function createDatabaseVfs( ); for (const desc of descendants) { - const newDescPath = newPrefix + desc.path.slice(prefix.length); + const newDescPath = + newPrefix + desc.path.slice(prefix.length); await db.execute( "UPDATE agent_os_fs_entries SET path = ? WHERE path = ?", newDescPath, diff --git a/rivetkit-typescript/packages/rivetkit/src/client/actor-common.ts b/rivetkit-typescript/packages/rivetkit/src/client/actor-common.ts index e44a2af43d..2cc2247d70 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/actor-common.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/actor-common.ts @@ -1,4 +1,7 @@ -import type { BaseActorDefinition, AnyActorDefinition } from "@/actor/definition"; +import type { + BaseActorDefinition, + AnyActorDefinition, +} from "@/actor/definition"; import type { EventSchemaConfig, InferEventArgs, @@ -33,7 +36,17 @@ export type ActorActionFunction< */ export type ActorDefinitionActions = // biome-ignore lint/suspicious/noExplicitAny: safe to use any here - AD extends BaseActorDefinition + AD extends BaseActorDefinition< + any, + any, + any, + any, + any, + any, + any, + any, + infer R + > ? { [K in keyof R]: R[K] extends ( ...args: infer Args @@ -79,7 +92,17 @@ type ActorEventSubscribe = { export type ActorDefinitionQueueSend = // biome-ignore lint/suspicious/noExplicitAny: safe to use any here - AD extends BaseActorDefinition + AD extends BaseActorDefinition< + any, + any, + any, + any, + any, + any, + any, + infer Q, + any + > ? Q extends QueueSchemaConfig ? { send: ActorQueueSend } : never @@ -87,7 +110,17 @@ export type ActorDefinitionQueueSend = export type ActorDefinitionEventSubscriptions = // biome-ignore lint/suspicious/noExplicitAny: safe to use any here - AD extends BaseActorDefinition + AD extends BaseActorDefinition< + any, + any, + any, + any, + any, + any, + infer E, + any, + any + > ? E extends EventSchemaConfig ? { on: ActorEventSubscribe; diff --git a/rivetkit-typescript/packages/rivetkit/src/client/actor-handle.ts b/rivetkit-typescript/packages/rivetkit/src/client/actor-handle.ts index 2406c9dfc9..1e1586678d 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/actor-handle.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/actor-handle.ts @@ -161,9 +161,9 @@ export class ActorHandleRaw { actorId ? { msg: "using direct actor gateway target", actorId } : { - msg: "using query gateway target for action", - query: this.#actorResolutionState, - }, + msg: "using query gateway target for action", + query: this.#actorResolutionState, + }, ); logger().debug({ @@ -185,10 +185,10 @@ export class ActorHandleRaw { [HEADER_ENCODING]: this.#encoding, ...(this.#params !== undefined ? { - [HEADER_CONN_PARAMS]: JSON.stringify( - this.#params, - ), - } + [HEADER_CONN_PARAMS]: JSON.stringify( + this.#params, + ), + } : {}), }, body: opts.args, diff --git a/rivetkit-typescript/packages/rivetkit/src/client/actor-query.ts b/rivetkit-typescript/packages/rivetkit/src/client/actor-query.ts index 8fde2ecf30..d489e3d385 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/actor-query.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/actor-query.ts @@ -1,6 +1,9 @@ import * as errors from "@/actor/errors"; import { stringifyError } from "@/common/utils"; -import { type GatewayTarget, type EngineControlClient } from "@/driver-helpers/mod"; +import { + type GatewayTarget, + type EngineControlClient, +} from "@/driver-helpers/mod"; import type { ActorQuery } from "@/client/query"; import { ActorSchedulingError } from "./errors"; import { logger } from "./log"; diff --git a/rivetkit-typescript/packages/rivetkit/src/client/client.ts b/rivetkit-typescript/packages/rivetkit/src/client/client.ts index 2ec7addf7d..28abb4b5ca 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/client.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/client.ts @@ -184,7 +184,10 @@ export class ClientRaw { /** * Creates an instance of Client. */ - public constructor(driver: EngineControlClient, encoding: Encoding | undefined) { + public constructor( + driver: EngineControlClient, + encoding: Encoding | undefined, + ) { this.#driver = driver; this.#encodingKind = encoding ?? "bare"; diff --git a/rivetkit-typescript/packages/rivetkit/src/client/config.ts b/rivetkit-typescript/packages/rivetkit/src/client/config.ts index 68edebc3e5..fa4526c163 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/config.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/config.ts @@ -40,9 +40,9 @@ export const ClientConfigSchemaBase = z.object({ hasWarnedMissingEndpoint = true; console.warn( `[rivetkit] No endpoint provided to client. Defaulting to ${DEFAULT_ENDPOINT}. ` + - `Starting in 2.2.0, an explicit endpoint will be required. ` + - `Pass an endpoint to createClient() or createRivetKit(), ` + - `or set the RIVET_ENDPOINT environment variable.`, + `Starting in 2.2.0, an explicit endpoint will be required. ` + + `Pass an endpoint to createClient() or createRivetKit(), ` + + `or set the RIVET_ENDPOINT environment variable.`, ); } return resolved ?? DEFAULT_ENDPOINT; diff --git a/rivetkit-typescript/packages/rivetkit/src/client/raw-utils.ts b/rivetkit-typescript/packages/rivetkit/src/client/raw-utils.ts index dbcebfa4d8..39673ceef6 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/raw-utils.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/raw-utils.ts @@ -65,11 +65,14 @@ export async function rawHttpFetch( try { logger().debug( "directId" in target - ? { msg: "sending raw http request to actor", actorId: target.directId } + ? { + msg: "sending raw http request to actor", + actorId: target.directId, + } : { - msg: "sending raw http request with actor query", - query: target, - }, + msg: "sending raw http request with actor query", + query: target, + }, ); // Build the URL with normalized path diff --git a/rivetkit-typescript/packages/rivetkit/src/client/utils.ts b/rivetkit-typescript/packages/rivetkit/src/client/utils.ts index 52e501ba8a..c4d61f02e4 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/utils.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/utils.ts @@ -90,8 +90,8 @@ export interface HttpRequestOpts< requestVersionedDataHandler: VersionedDataHandler | undefined; requestVersion: number | undefined; responseVersionedDataHandler: - | VersionedDataHandler - | undefined; + | VersionedDataHandler + | undefined; responseVersion: number | undefined; requestZodSchema: z.ZodType; responseZodSchema: z.ZodType; @@ -152,8 +152,8 @@ export async function sendHttpRequest< ...opts.headers, ...(contentType ? { - "Content-Type": contentType, - } + "Content-Type": contentType, + } : {}), "User-Agent": httpUserAgent(), }, diff --git a/rivetkit-typescript/packages/rivetkit/src/common/inline-websocket-adapter.ts b/rivetkit-typescript/packages/rivetkit/src/common/inline-websocket-adapter.ts index e695746035..9b9ecb1622 100644 --- a/rivetkit-typescript/packages/rivetkit/src/common/inline-websocket-adapter.ts +++ b/rivetkit-typescript/packages/rivetkit/src/common/inline-websocket-adapter.ts @@ -153,10 +153,7 @@ export class InlineWebSocketAdapter { if (!next) { continue; } - this.#dispatchClientMessage( - next.data, - next.rivetMessageIndex, - ); + this.#dispatchClientMessage(next.data, next.rivetMessageIndex); } } catch (err) { this.#handleError(err); diff --git a/rivetkit-typescript/packages/rivetkit/src/common/router.ts b/rivetkit-typescript/packages/rivetkit/src/common/router.ts index 409b51179c..1286fab6b9 100644 --- a/rivetkit-typescript/packages/rivetkit/src/common/router.ts +++ b/rivetkit-typescript/packages/rivetkit/src/common/router.ts @@ -118,7 +118,7 @@ export interface MetadataResponse { kind: MetadataEnvoyKind; version?: number; }; - envoyProtocolVersion: number, + envoyProtocolVersion: number; actorNames: ReturnType; /** * Endpoint that the client should connect to to access this envoy. diff --git a/rivetkit-typescript/packages/rivetkit/src/db/drizzle/mod.ts b/rivetkit-typescript/packages/rivetkit/src/db/drizzle/mod.ts index 571533932d..18943ec062 100644 --- a/rivetkit-typescript/packages/rivetkit/src/db/drizzle/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/db/drizzle/mod.ts @@ -4,11 +4,7 @@ import { type SqliteRemoteDatabase, } from "drizzle-orm/sqlite-proxy"; import type { DatabaseProvider, RawAccess, SqliteDatabase } from "../config"; -import { - AsyncMutex, - isSqliteBindingObject, - toSqliteBindings, -} from "../shared"; +import { AsyncMutex, isSqliteBindingObject, toSqliteBindings } from "../shared"; export * from "./sqlite-core"; @@ -110,7 +106,10 @@ function createProxyCallback( await db.run(sql, toSqliteBindings(params)); result = { rows: [] }; } else { - const queryResult = await db.query(sql, toSqliteBindings(params)); + const queryResult = await db.query( + sql, + toSqliteBindings(params), + ); // drizzle's mapResultRow accesses rows by column index (positional arrays) // so we return raw arrays for all methods @@ -255,10 +254,7 @@ export function db< isSqliteBindingObject(args[0]) ? toSqliteBindings(args[0]) : toSqliteBindings(args); - const result = await db.query( - query, - bindings, - ); + const result = await db.query(query, bindings); rows = result.rows.map((row: unknown[]) => { const obj: Record = {}; for ( diff --git a/rivetkit-typescript/packages/rivetkit/src/db/mod.ts b/rivetkit-typescript/packages/rivetkit/src/db/mod.ts index bc06c0dac8..9c4eaa377b 100644 --- a/rivetkit-typescript/packages/rivetkit/src/db/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/db/mod.ts @@ -1,9 +1,5 @@ import type { DatabaseProvider, RawAccess } from "./config"; -import { - AsyncMutex, - isSqliteBindingObject, - toSqliteBindings, -} from "./shared"; +import { AsyncMutex, isSqliteBindingObject, toSqliteBindings } from "./shared"; export type { RawAccess } from "./config"; diff --git a/rivetkit-typescript/packages/rivetkit/src/db/native-database.test.ts b/rivetkit-typescript/packages/rivetkit/src/db/native-database.test.ts index 1415a1535d..2940402185 100644 --- a/rivetkit-typescript/packages/rivetkit/src/db/native-database.test.ts +++ b/rivetkit-typescript/packages/rivetkit/src/db/native-database.test.ts @@ -1,5 +1,8 @@ import { describe, expect, test } from "vitest"; -import { wrapJsNativeDatabase, type JsNativeDatabaseLike } from "./native-database"; +import { + wrapJsNativeDatabase, + type JsNativeDatabaseLike, +} from "./native-database"; function createDatabase( overrides: Partial = {}, diff --git a/rivetkit-typescript/packages/rivetkit/src/db/native-database.ts b/rivetkit-typescript/packages/rivetkit/src/db/native-database.ts index 04603de464..bd2caf5015 100644 --- a/rivetkit-typescript/packages/rivetkit/src/db/native-database.ts +++ b/rivetkit-typescript/packages/rivetkit/src/db/native-database.ts @@ -24,8 +24,14 @@ interface NativeRunResult { export interface JsNativeDatabaseLike { exec(sql: string): Promise; - query(sql: string, params?: NativeBindParam[] | null): Promise; - run(sql: string, params?: NativeBindParam[] | null): Promise; + query( + sql: string, + params?: NativeBindParam[] | null, + ): Promise; + run( + sql: string, + params?: NativeBindParam[] | null, + ): Promise; takeLastKvError?(): string | null; close(): Promise; } diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-helpers/resolve-gateway-target.ts b/rivetkit-typescript/packages/rivetkit/src/driver-helpers/resolve-gateway-target.ts index e2c063e298..694aeed57c 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-helpers/resolve-gateway-target.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-helpers/resolve-gateway-target.ts @@ -1,5 +1,8 @@ import { ActorNotFound, InvalidRequest } from "@/actor/errors"; -import type { GatewayTarget, EngineControlClient } from "@/engine-client/driver"; +import type { + GatewayTarget, + EngineControlClient, +} from "@/engine-client/driver"; /** * Resolves a GatewayTarget to a concrete actor ID string. diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/mod.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/mod.ts index 4542fa8318..52e1edc263 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/mod.ts @@ -118,11 +118,19 @@ export function runDriverTests( driverTestConfigPartial: Omit, ) { describe("Driver Tests", () => { - const clientTypes: ClientType[] = driverTestConfigPartial.clientTypes - ?? (driverTestConfigPartial.skip?.inline ? ["http"] : ["http", "inline"]); + const clientTypes: ClientType[] = + driverTestConfigPartial.clientTypes ?? + (driverTestConfigPartial.skip?.inline + ? ["http"] + : ["http", "inline"]); for (const clientType of clientTypes) { describe(`client type (${clientType})`, () => { - const encodings: Encoding[] = driverTestConfigPartial.encodings ?? ["bare", "cbor", "json"]; + const encodings: Encoding[] = + driverTestConfigPartial.encodings ?? [ + "bare", + "cbor", + "json", + ]; for (const encoding of encodings) { describe(`encoding (${encoding})`, () => { diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-agent-os.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-agent-os.ts index f77e2f8f2c..4d1792d4aa 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-agent-os.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-agent-os.ts @@ -107,7 +107,9 @@ export function runActorAgentOsTests(driverTestConfig: DriverTestConfig) { await actor.writeFile("/home/user/todelete.txt", "gone"); await actor.deleteFile("/home/user/todelete.txt"); - expect(await actor.exists("/home/user/todelete.txt")).toBe(false); + expect(await actor.exists("/home/user/todelete.txt")).toBe( + false, + ); }, 60_000); test("writeFiles and readFiles batch operations", async (c) => { @@ -129,12 +131,12 @@ export function runActorAgentOsTests(driverTestConfig: DriverTestConfig) { "/home/user/batch-a.txt", "/home/user/batch-b.txt", ]); - expect( - new TextDecoder().decode(readResults[0].content), - ).toBe("aaa"); - expect( - new TextDecoder().decode(readResults[1].content), - ).toBe("bbb"); + expect(new TextDecoder().decode(readResults[0].content)).toBe( + "aaa", + ); + expect(new TextDecoder().decode(readResults[1].content)).toBe( + "bbb", + ); }, 60_000); test("readdirRecursive lists nested files", async (c) => { @@ -183,10 +185,7 @@ export function runActorAgentOsTests(driverTestConfig: DriverTestConfig) { ]); // Write a script that exits with code 42. - await actor.writeFile( - "/tmp/exit42.js", - 'process.exit(42);', - ); + await actor.writeFile("/tmp/exit42.js", "process.exit(42);"); const { pid } = await actor.spawn("node", ["/tmp/exit42.js"]); expect(typeof pid).toBe("number"); @@ -207,7 +206,7 @@ export function runActorAgentOsTests(driverTestConfig: DriverTestConfig) { // Write a long-running script. await actor.writeFile( "/tmp/long.js", - 'setTimeout(() => {}, 30000);', + "setTimeout(() => {}, 30000);", ); const { pid } = await actor.spawn("node", ["/tmp/long.js"]); @@ -228,7 +227,7 @@ export function runActorAgentOsTests(driverTestConfig: DriverTestConfig) { await actor.writeFile( "/tmp/hang.js", - 'setTimeout(() => {}, 60000);', + "setTimeout(() => {}, 60000);", ); const { pid } = await actor.spawn("node", ["/tmp/hang.js"]); @@ -273,7 +272,9 @@ server.listen(9876, "127.0.0.1", () => { "http://127.0.0.1:9876/test", ); expect(result.status).toBe(200); - expect(new TextDecoder().decode(result.body)).toBe("vm-response"); + expect(new TextDecoder().decode(result.body)).toBe( + "vm-response", + ); }, 60_000); // --- Cron --- diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-conn-hibernation.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-conn-hibernation.ts index 9a1bd358fa..6b20b23ef7 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-conn-hibernation.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-conn-hibernation.ts @@ -122,8 +122,7 @@ export function runActorConnHibernationTests( const ping = await hibernatingActor.ping(); expect(ping).toBe("pong"); - const actorCounts = - await hibernatingActor.getActorCounts(); + const actorCounts = await hibernatingActor.getActorCounts(); expect(actorCounts.sleepCount).toBe(i + 1); expect(actorCounts.wakeCount).toBe(i + 2); expect(openCount).toBe(1); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db-pragma-migration.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db-pragma-migration.ts index df9f666815..6a666c0d84 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db-pragma-migration.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db-pragma-migration.ts @@ -16,14 +16,10 @@ export function runActorDbPragmaMigrationTests( test( "applies all migrations on first start", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); - const actor = - client.dbPragmaMigrationActor.getOrCreate([ - `pragma-init-${crypto.randomUUID()}`, - ]); + const { client } = await setupDriverTest(c, driverTestConfig); + const actor = client.dbPragmaMigrationActor.getOrCreate([ + `pragma-init-${crypto.randomUUID()}`, + ]); // user_version should be set to 2 after migrations const version = await actor.getUserVersion(); @@ -41,14 +37,10 @@ export function runActorDbPragmaMigrationTests( test( "inserts with default status from migration v2", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); - const actor = - client.dbPragmaMigrationActor.getOrCreate([ - `pragma-default-${crypto.randomUUID()}`, - ]); + const { client } = await setupDriverTest(c, driverTestConfig); + const actor = client.dbPragmaMigrationActor.getOrCreate([ + `pragma-default-${crypto.randomUUID()}`, + ]); await actor.insertItem("test-item"); const items = await actor.getItems(); @@ -63,14 +55,10 @@ export function runActorDbPragmaMigrationTests( test( "inserts with explicit status", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); - const actor = - client.dbPragmaMigrationActor.getOrCreate([ - `pragma-explicit-${crypto.randomUUID()}`, - ]); + const { client } = await setupDriverTest(c, driverTestConfig); + const actor = client.dbPragmaMigrationActor.getOrCreate([ + `pragma-explicit-${crypto.randomUUID()}`, + ]); await actor.insertItemWithStatus("done-item", "completed"); const items = await actor.getItems(); @@ -85,13 +73,9 @@ export function runActorDbPragmaMigrationTests( test( "migrations are idempotent across sleep/wake", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + const { client } = await setupDriverTest(c, driverTestConfig); const key = `pragma-sleep-${crypto.randomUUID()}`; - const actor = - client.dbPragmaMigrationActor.getOrCreate([key]); + const actor = client.dbPragmaMigrationActor.getOrCreate([key]); // Insert data before sleep await actor.insertItemWithStatus("before-sleep", "pending"); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db-stress.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db-stress.ts index ccc2aad103..5e2d15d254 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db-stress.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db-stress.ts @@ -18,10 +18,7 @@ export function runActorDbStressTests(driverTestConfig: DriverTestConfig) { test( "destroy during long-running DB operation completes without crash", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Start multiple actors and kick off long DB operations, // then destroy them mid-flight. The test passes if no @@ -66,10 +63,7 @@ export function runActorDbStressTests(driverTestConfig: DriverTestConfig) { test( "rapid create-insert-destroy cycles handle DB lifecycle correctly", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + const { client } = await setupDriverTest(c, driverTestConfig); // Perform rapid cycles of create -> insert -> destroy. // This exercises the close_database path racing with @@ -96,10 +90,7 @@ export function runActorDbStressTests(driverTestConfig: DriverTestConfig) { test( "DB operations complete without excessive blocking", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + const { client } = await setupDriverTest(c, driverTestConfig); const actor = client.dbStressActor.getOrCreate([ `stress-health-${crypto.randomUUID()}`, @@ -124,6 +115,5 @@ export function runActorDbStressTests(driverTestConfig: DriverTestConfig) { }, STRESS_TEST_TIMEOUT_MS, ); - }); } diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db.ts index 01f692fd0d..36ad864e50 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-db.ts @@ -214,11 +214,8 @@ export function runActorDbTests(driverTestConfig: DriverTestConfig) { test.skipIf(driverTestConfig.skip?.sleep)( "preserves committed rows across a hard crash and restart", async (c) => { - const { - client, - hardCrashActor, - hardCrashPreservesData, - } = await setupDriverTest(c, driverTestConfig); + const { client, hardCrashActor, hardCrashPreservesData } = + await setupDriverTest(c, driverTestConfig); if (!hardCrashPreservesData) { return; } @@ -239,10 +236,9 @@ export function runActorDbTests(driverTestConfig: DriverTestConfig) { const actorId = await actor.resolve(); await hardCrashActor(actorId); - const hardCrashPollAttempts = - driverTestConfig.useRealTimers - ? REAL_TIMER_HARD_CRASH_POLL_ATTEMPTS - : LIFECYCLE_POLL_ATTEMPTS; + const hardCrashPollAttempts = driverTestConfig.useRealTimers + ? REAL_TIMER_HARD_CRASH_POLL_ATTEMPTS + : LIFECYCLE_POLL_ATTEMPTS; const hardCrashPollIntervalMs = driverTestConfig.useRealTimers ? REAL_TIMER_HARD_CRASH_POLL_INTERVAL_MS @@ -301,8 +297,7 @@ export function runActorDbTests(driverTestConfig: DriverTestConfig) { // under concurrent test load. let count = 0; for (let i = 0; i < LIFECYCLE_POLL_ATTEMPTS; i++) { - count = - await actor.getDisconnectInsertCount(); + count = await actor.getDisconnectInsertCount(); if (count >= 1) { break; } diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-error-handling.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-error-handling.ts index 7f815f7949..e30e0af9b4 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-error-handling.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-error-handling.ts @@ -71,61 +71,75 @@ export function runActorErrorHandlingTests(driverTestConfig: DriverTestConfig) { }); }); - describe.skipIf(!driverTestConfig.useRealTimers)("Action Timeout", () => { - test("should handle action timeouts with custom duration", async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - - // Call an action that should time out - const handle = client.errorHandlingActor.getOrCreate(); - - // This should throw a timeout error because errorHandlingActor has - // a 500ms timeout and this action tries to run for much longer - const timeoutPromise = handle.timeoutAction(); - - try { - await timeoutPromise; - // If we get here, the test failed - timeout didn't occur - expect(true).toBe(false); // This should not be reached - } catch (error: any) { - // Verify it's a timeout error - expect(error.message).toMatch(/timed out/i); - } - }); - - test("should successfully run actions within timeout", async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - - // Call an action with a delay shorter than the timeout - const handle = client.errorHandlingActor.getOrCreate(); - - // This should succeed because 200ms < 500ms timeout - const result = await handle.delayedAction(200); - expect(result).toBe("Completed after 200ms"); - }); - - test("should respect different timeouts for different actors", async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - - // The following actors have different timeout settings: - // customTimeoutActor: 200ms timeout - // standardTimeoutActor: default timeout (much longer) - - // This should fail - 300ms delay with 200ms timeout - try { - await client.customTimeoutActor.getOrCreate().slowAction(); - // Should not reach here - expect(true).toBe(false); - } catch (error: any) { - expect(error.message).toMatch(/timed out/i); - } - - // This should succeed - 50ms delay with 200ms timeout - const quickResult = await client.customTimeoutActor - .getOrCreate() - .quickAction(); - expect(quickResult).toBe("Quick action completed"); - }); - }); + describe.skipIf(!driverTestConfig.useRealTimers)( + "Action Timeout", + () => { + test("should handle action timeouts with custom duration", async (c) => { + const { client } = await setupDriverTest( + c, + driverTestConfig, + ); + + // Call an action that should time out + const handle = client.errorHandlingActor.getOrCreate(); + + // This should throw a timeout error because errorHandlingActor has + // a 500ms timeout and this action tries to run for much longer + const timeoutPromise = handle.timeoutAction(); + + try { + await timeoutPromise; + // If we get here, the test failed - timeout didn't occur + expect(true).toBe(false); // This should not be reached + } catch (error: any) { + // Verify it's a timeout error + expect(error.message).toMatch(/timed out/i); + } + }); + + test("should successfully run actions within timeout", async (c) => { + const { client } = await setupDriverTest( + c, + driverTestConfig, + ); + + // Call an action with a delay shorter than the timeout + const handle = client.errorHandlingActor.getOrCreate(); + + // This should succeed because 200ms < 500ms timeout + const result = await handle.delayedAction(200); + expect(result).toBe("Completed after 200ms"); + }); + + test("should respect different timeouts for different actors", async (c) => { + const { client } = await setupDriverTest( + c, + driverTestConfig, + ); + + // The following actors have different timeout settings: + // customTimeoutActor: 200ms timeout + // standardTimeoutActor: default timeout (much longer) + + // This should fail - 300ms delay with 200ms timeout + try { + await client.customTimeoutActor + .getOrCreate() + .slowAction(); + // Should not reach here + expect(true).toBe(false); + } catch (error: any) { + expect(error.message).toMatch(/timed out/i); + } + + // This should succeed - 50ms delay with 200ms timeout + const quickResult = await client.customTimeoutActor + .getOrCreate() + .quickAction(); + expect(quickResult).toBe("Quick action completed"); + }); + }, + ); describe("Error Recovery", () => { test("should continue working after errors", async (c) => { diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inspector.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inspector.ts index 36f581ffd6..1a38817a4c 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inspector.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inspector.ts @@ -37,7 +37,7 @@ export function runActorInspectorTests(driverTestConfig: DriverTestConfig) { const response = await fetch( buildInspectorUrl(gatewayUrl, "/inspector/state"), { - headers: { Authorization: "Bearer token" }, + headers: { Authorization: "Bearer token" }, }, ); expect(response.status).toBe(200); @@ -60,12 +60,12 @@ export function runActorInspectorTests(driverTestConfig: DriverTestConfig) { const patchResponse = await fetch( buildInspectorUrl(gatewayUrl, "/inspector/state"), { - method: "PATCH", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer token", - }, - body: JSON.stringify({ state: { count: 42 } }), + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer token", + }, + body: JSON.stringify({ state: { count: 42 } }), }, ); expect(patchResponse.status).toBe(200); @@ -112,7 +112,7 @@ export function runActorInspectorTests(driverTestConfig: DriverTestConfig) { const response = await fetch( buildInspectorUrl(gatewayUrl, "/inspector/rpcs"), { - headers: { Authorization: "Bearer token" }, + headers: { Authorization: "Bearer token" }, }, ); expect(response.status).toBe(200); @@ -395,10 +395,7 @@ export function runActorInspectorTests(driverTestConfig: DriverTestConfig) { const data = (await response.json()) as { rows: Array<{ value: string }>; }; - expect(data.rows).toEqual([ - { value: "alpha" }, - { value: "beta" }, - ]); + expect(data.rows).toEqual([{ value: "alpha" }, { value: "beta" }]); }); test("GET /inspector/database/rows returns SQLite rows", async (c) => { @@ -555,7 +552,7 @@ export function runActorInspectorTests(driverTestConfig: DriverTestConfig) { const response = await fetch( buildInspectorUrl(gatewayUrl, "/inspector/summary"), { - headers: { Authorization: "Bearer token" }, + headers: { Authorization: "Bearer token" }, }, ); expect(response.status).toBe(200); @@ -599,7 +596,7 @@ export function runActorInspectorTests(driverTestConfig: DriverTestConfig) { const response = await fetch( buildInspectorUrl(gatewayUrl, "/inspector/summary"), { - headers: { Authorization: "Bearer token" }, + headers: { Authorization: "Bearer token" }, }, ); expect(response.status).toBe(200); @@ -634,7 +631,7 @@ export function runActorInspectorTests(driverTestConfig: DriverTestConfig) { const response = await fetch( buildInspectorUrl(gatewayUrl, "/inspector/state"), { - headers: { Authorization: "Bearer wrong-token" }, + headers: { Authorization: "Bearer wrong-token" }, }, ); expect(response.status).toBe(401); @@ -652,7 +649,7 @@ export function runActorInspectorTests(driverTestConfig: DriverTestConfig) { const response = await fetch( buildInspectorUrl(gatewayUrl, "/inspector/metrics"), { - headers: { Authorization: "Bearer token" }, + headers: { Authorization: "Bearer token" }, }, ); expect(response.status).toBe(200); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sandbox.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sandbox.ts index 649065194f..3df1d35f87 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sandbox.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sandbox.ts @@ -86,7 +86,9 @@ export function runActorSandboxTests(driverTestConfig: DriverTestConfig) { await sandbox.listFsEntries({ path: "/home/sandbox" }), ).not.toEqual( expect.arrayContaining([ - expect.objectContaining({ name: testDir.split("/").at(-1) }), + expect.objectContaining({ + name: testDir.split("/").at(-1), + }), ]), ); }, 180_000); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-schedule.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-schedule.ts index e6f3ce198b..310dca5649 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-schedule.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-schedule.ts @@ -72,15 +72,14 @@ export function runActorScheduleTests(driverTestConfig: DriverTestConfig) { await vi.waitFor(async () => { const logCount = await actor.getLogCount(); - const scheduledCount = - await actor.getScheduledCount(); + const scheduledCount = await actor.getScheduledCount(); expect(logCount).toBe(1); expect(scheduledCount).toBe(1); }); }); - test("multiple scheduled tasks execute in order", async (c) => { + test("multiple scheduled tasks execute in order", async (c) => { const { client } = await setupDriverTest( c, driverTestConfig, @@ -118,11 +117,7 @@ export function runActorScheduleTests(driverTestConfig: DriverTestConfig) { await waitFor(driverTestConfig, 500); await vi.waitFor(async () => { const history3 = await scheduled.getTaskHistory(); - expect(history3).toEqual([ - "first", - "second", - "third", - ]); + expect(history3).toEqual(["first", "second", "third"]); }); }); }); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sleep-db.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sleep-db.ts index 1353df4651..c1d6db6b05 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sleep-db.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sleep-db.ts @@ -15,14 +15,20 @@ import { setupDriverTest, waitFor } from "../utils"; type LogEntry = { id: number; event: string; created_at: number }; -async function connectRawWebSocket(handle: { webSocket(): Promise }) { +async function connectRawWebSocket(handle: { + webSocket(): Promise; +}) { const ws = await handle.webSocket(); await new Promise((resolve, reject) => { ws.addEventListener("open", () => resolve(), { once: true }); - ws.addEventListener("error", () => reject(new Error("websocket error")), { - once: true, - }); + ws.addEventListener( + "error", + () => reject(new Error("websocket error")), + { + once: true, + }, + ); }); await new Promise((resolve, reject) => { @@ -55,1055 +61,924 @@ export function runActorSleepDbTests(driverTestConfig: DriverTestConfig) { : describe.sequential; describeSleepDbTests("Actor Sleep Database Tests", () => { - test("onSleep can write to c.db", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + test("onSleep can write to c.db", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - const actor = client.sleepWithDb.getOrCreate(); + const actor = client.sleepWithDb.getOrCreate(); - // Insert a log entry while awake - await actor.insertLogEntry("before-sleep"); + // Insert a log entry while awake + await actor.insertLogEntry("before-sleep"); - // Trigger sleep - await actor.triggerSleep(); + // Trigger sleep + await actor.triggerSleep(); - // Wait for sleep to complete - await waitFor(driverTestConfig, 250); + // Wait for sleep to complete + await waitFor(driverTestConfig, 250); - // Wake the actor by calling an action - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); - expect(counts.onSleepDbWriteSuccess).toBe(true); - expect(counts.onSleepDbWriteError).toBeNull(); + // Wake the actor by calling an action + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); + expect(counts.onSleepDbWriteSuccess).toBe(true); + expect(counts.onSleepDbWriteError).toBeNull(); - // Verify both wake and sleep events were logged to the DB - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("wake"); - expect(events).toContain("before-sleep"); - expect(events).toContain("sleep"); - }); + // Verify both wake and sleep events were logged to the DB + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("wake"); + expect(events).toContain("before-sleep"); + expect(events).toContain("sleep"); + }); - test("c.db works after sleep-wake cycle", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + test("c.db works after sleep-wake cycle", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - const actor = client.sleepWithDb.getOrCreate([ - "db-after-wake", - ]); + const actor = client.sleepWithDb.getOrCreate(["db-after-wake"]); - // Insert before sleep - await actor.insertLogEntry("before"); + // Insert before sleep + await actor.insertLogEntry("before"); - // Let it auto-sleep - await waitFor(driverTestConfig, SLEEP_DB_TIMEOUT + 250); + // Let it auto-sleep + await waitFor(driverTestConfig, SLEEP_DB_TIMEOUT + 250); - // Wake it by calling an action that uses the DB - await actor.insertLogEntry("after-wake"); + // Wake it by calling an action that uses the DB + await actor.insertLogEntry("after-wake"); - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("before"); - expect(events).toContain("sleep"); - expect(events).toContain("wake"); - expect(events).toContain("after-wake"); - }); + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("before"); + expect(events).toContain("sleep"); + expect(events).toContain("wake"); + expect(events).toContain("after-wake"); + }); - test("scheduled alarm can use c.db after sleep-wake", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + test("scheduled alarm can use c.db after sleep-wake", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - const actor = client.sleepWithDb.getOrCreate([ - "alarm-db-wake", - ]); + const actor = client.sleepWithDb.getOrCreate(["alarm-db-wake"]); - // Schedule an alarm that fires after the actor would sleep - await actor.setAlarm(SLEEP_DB_TIMEOUT + 500); + // Schedule an alarm that fires after the actor would sleep + await actor.setAlarm(SLEEP_DB_TIMEOUT + 500); - // Wait for the actor to sleep and then wake from alarm - await waitFor(driverTestConfig, SLEEP_DB_TIMEOUT + 750); + // Wait for the actor to sleep and then wake from alarm + await waitFor(driverTestConfig, SLEEP_DB_TIMEOUT + 750); - // Verify the alarm wrote to the DB - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("alarm"); - }); + // Verify the alarm wrote to the DB + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("alarm"); + }); - test("scheduled action stays awake until db work completes", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + test("scheduled action stays awake until db work completes", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - const actor = client.sleepWithSlowScheduledDb.getOrCreate([ - "slow-scheduled-db", - ]); + const actor = client.sleepWithSlowScheduledDb.getOrCreate([ + "slow-scheduled-db", + ]); - await actor.scheduleSlowAlarm( - 50, - SLEEP_DB_TIMEOUT + 250, - ); + await actor.scheduleSlowAlarm(50, SLEEP_DB_TIMEOUT + 250); - await waitFor( - driverTestConfig, - 50 + (SLEEP_DB_TIMEOUT + 250) + SLEEP_DB_TIMEOUT + 250, - ); + await waitFor( + driverTestConfig, + 50 + (SLEEP_DB_TIMEOUT + 250) + SLEEP_DB_TIMEOUT + 250, + ); - await vi.waitFor( - async () => { - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); - }, - { - timeout: 5_000, - interval: 50, - }, - ); + await vi.waitFor( + async () => { + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); + }, + { + timeout: 5_000, + interval: 50, + }, + ); - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("slow-alarm-start"); - expect(events).toContain("slow-alarm-finish"); - expect(events.indexOf("slow-alarm-finish")).toBeLessThan( - events.indexOf("sleep"), - ); + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("slow-alarm-start"); + expect(events).toContain("slow-alarm-finish"); + expect(events.indexOf("slow-alarm-finish")).toBeLessThan( + events.indexOf("sleep"), + ); + }); + + test("onDisconnect can write to c.db during sleep shutdown", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); + + // Create actor with a connection + const handle = client.sleepWithDbConn.getOrCreate([ + "disconnect-db-write", + ]); + const connection = handle.connect(); + + // Wait for connection to be established + await vi.waitFor(async () => { + expect(connection.isConnected).toBe(true); }); - test("onDisconnect can write to c.db during sleep shutdown", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Insert a log entry while awake + await connection.insertLogEntry("before-sleep"); - // Create actor with a connection - const handle = client.sleepWithDbConn.getOrCreate([ - "disconnect-db-write", - ]); - const connection = handle.connect(); + // Trigger sleep, then dispose the connection. + // During the sleep shutdown sequence, onDisconnect is called + // with the DB still open (step 6 in the shutdown sequence). + await connection.triggerSleep(); + await connection.dispose(); - // Wait for connection to be established - await vi.waitFor(async () => { - expect(connection.isConnected).toBe(true); - }); + // Wait for sleep to fully complete + await waitFor(driverTestConfig, 500); - // Insert a log entry while awake - await connection.insertLogEntry("before-sleep"); + // Wake the actor by calling an action + const counts = await handle.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - // Trigger sleep, then dispose the connection. - // During the sleep shutdown sequence, onDisconnect is called - // with the DB still open (step 6 in the shutdown sequence). - await connection.triggerSleep(); - await connection.dispose(); + // Verify events were logged to the DB + const entries = await handle.getLogEntries(); + const events = entries.map((e: LogEntry) => e.event); - // Wait for sleep to fully complete - await waitFor(driverTestConfig, 500); + // CURRENT BEHAVIOR: onDisconnect runs during sleep shutdown + // and the DB is still open at that point, so the write should succeed. + expect(events).toContain("before-sleep"); + expect(events).toContain("sleep"); + expect(events).toContain("disconnect"); + }); - // Wake the actor by calling an action - const counts = await handle.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + test("async websocket close handler can use c.db before sleep completes", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // Verify events were logged to the DB - const entries = await handle.getLogEntries(); - const events = entries.map( - (e: LogEntry) => e.event, - ); + const actor = client.sleepWithRawWsCloseDb.getOrCreate([ + "raw-ws-close-db", + ]); + const ws = await connectRawWebSocket(actor); - // CURRENT BEHAVIOR: onDisconnect runs during sleep shutdown - // and the DB is still open at that point, so the write should succeed. - expect(events).toContain("before-sleep"); - expect(events).toContain("sleep"); - expect(events).toContain("disconnect"); + await new Promise((resolve, reject) => { + ws.addEventListener("close", () => resolve(), { once: true }); + ws.addEventListener( + "error", + () => reject(new Error("websocket error")), + { once: true }, + ); + ws.close(); }); - test("async websocket close handler can use c.db before sleep completes", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + await waitFor(driverTestConfig, RAW_WS_HANDLER_DELAY + 150); - const actor = client.sleepWithRawWsCloseDb.getOrCreate([ - "raw-ws-close-db", - ]); - const ws = await connectRawWebSocket(actor); + const status = await actor.getStatus(); + expect(status.sleepCount).toBe(1); + expect(status.startCount).toBe(2); + expect(status.closeStarted).toBe(1); + expect(status.closeFinished).toBe(1); - await new Promise((resolve, reject) => { - ws.addEventListener("close", () => resolve(), { once: true }); - ws.addEventListener( - "error", - () => reject(new Error("websocket error")), - { once: true }, - ); - ws.close(); - }); + const entries = await actor.getLogEntries(); + const events = entries.map((entry: LogEntry) => entry.event); + expect(events).toContain("sleep"); + expect(events).toContain("close-start"); + expect(events).toContain("close-finish"); + }); - await waitFor(driverTestConfig, RAW_WS_HANDLER_DELAY + 150); + test("async websocket addEventListener close handler can use c.db before sleep completes", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - const status = await actor.getStatus(); - expect(status.sleepCount).toBe(1); - expect(status.startCount).toBe(2); - expect(status.closeStarted).toBe(1); - expect(status.closeFinished).toBe(1); + const actor = client.sleepWithRawWsCloseDbListener.getOrCreate([ + "raw-ws-close-db-listener", + ]); + const ws = await connectRawWebSocket(actor); - const entries = await actor.getLogEntries(); - const events = entries.map((entry: LogEntry) => entry.event); - expect(events).toContain("sleep"); - expect(events).toContain("close-start"); - expect(events).toContain("close-finish"); + await new Promise((resolve, reject) => { + ws.addEventListener("close", () => resolve(), { once: true }); + ws.addEventListener( + "error", + () => reject(new Error("websocket error")), + { once: true }, + ); + ws.close(); }); - test("async websocket addEventListener close handler can use c.db before sleep completes", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + await waitFor(driverTestConfig, RAW_WS_HANDLER_DELAY + 150); - const actor = - client.sleepWithRawWsCloseDbListener.getOrCreate([ - "raw-ws-close-db-listener", - ]); - const ws = await connectRawWebSocket(actor); + const status = await actor.getStatus(); + expect(status.sleepCount).toBe(1); + expect(status.startCount).toBe(2); + expect(status.closeStarted).toBe(1); + expect(status.closeFinished).toBe(1); - await new Promise((resolve, reject) => { - ws.addEventListener("close", () => resolve(), { once: true }); - ws.addEventListener( - "error", - () => reject(new Error("websocket error")), - { once: true }, - ); - ws.close(); - }); + const entries = await actor.getLogEntries(); + const events = entries.map((entry: LogEntry) => entry.event); + expect(events).toContain("sleep"); + expect(events).toContain("close-start"); + expect(events).toContain("close-finish"); + }); - await waitFor(driverTestConfig, RAW_WS_HANDLER_DELAY + 150); + test("broadcast works in onSleep", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - const status = await actor.getStatus(); - expect(status.sleepCount).toBe(1); - expect(status.startCount).toBe(2); - expect(status.closeStarted).toBe(1); - expect(status.closeFinished).toBe(1); + const handle = client.sleepWithDbAction.getOrCreate([ + "broadcast-in-onsleep", + ]); + const connection = handle.connect(); - const entries = await actor.getLogEntries(); - const events = entries.map((entry: LogEntry) => entry.event); - expect(events).toContain("sleep"); - expect(events).toContain("close-start"); - expect(events).toContain("close-finish"); + // Wait for connection to be established + await vi.waitFor(async () => { + expect(connection.isConnected).toBe(true); }); - test("broadcast works in onSleep", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = client.sleepWithDbAction.getOrCreate([ - "broadcast-in-onsleep", - ]); - const connection = handle.connect(); + // Listen for the "sleeping" event + let sleepingEventReceived = false; + connection.on("sleeping", () => { + sleepingEventReceived = true; + }); - // Wait for connection to be established - await vi.waitFor(async () => { - expect(connection.isConnected).toBe(true); - }); + // Insert a log entry while awake + await connection.insertLogEntry("before-sleep"); - // Listen for the "sleeping" event - let sleepingEventReceived = false; - connection.on("sleeping", () => { - sleepingEventReceived = true; - }); + // Trigger sleep + await connection.triggerSleep(); - // Insert a log entry while awake - await connection.insertLogEntry("before-sleep"); + // Wait for sleep to fully complete + await waitFor(driverTestConfig, 1500); + await connection.dispose(); - // Trigger sleep - await connection.triggerSleep(); + // Broadcast now works during onSleep since assertReady + // only blocks after #shutdownComplete is set. + expect(sleepingEventReceived).toBe(true); - // Wait for sleep to fully complete - await waitFor(driverTestConfig, 1500); - await connection.dispose(); + // Wake the actor + const counts = await handle.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - // Broadcast now works during onSleep since assertReady - // only blocks after #shutdownComplete is set. - expect(sleepingEventReceived).toBe(true); + // Both "sleep-start" and "sleep-end" should be written + // since broadcast no longer throws. + const entries = await handle.getLogEntries(); + const events = entries.map((e: LogEntry) => e.event); - // Wake the actor - const counts = await handle.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + expect(events).toContain("before-sleep"); + expect(events).toContain("sleep-start"); + expect(events).toContain("sleep-end"); + }); - // Both "sleep-start" and "sleep-end" should be written - // since broadcast no longer throws. - const entries = await handle.getLogEntries(); - const events = entries.map( - (e: LogEntry) => e.event, - ); + test("action via handle during sleep is queued and runs on woken instance", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); + + // CURRENT BEHAVIOR: When an action is sent via a stateless + // handle while the actor is sleeping, the file-system driver + // queues the action. Once the actor finishes sleeping and + // wakes back up, the action executes on the new instance. + + const handle = client.sleepWithDbAction.getOrCreate([ + "action-during-sleep-handle", + ]); + + // Insert a log entry while awake + await handle.insertLogEntry("before-sleep"); + + // Trigger sleep + await handle.triggerSleep(); + + // Immediately try to call an action via the handle. + // This action arrives while the actor is shutting down or asleep. + let actionResult: { succeeded: boolean; error?: string }; + try { + await handle.insertLogEntry("during-sleep"); + actionResult = { succeeded: true }; + } catch (error) { + actionResult = { + succeeded: false, + error: + error instanceof Error ? error.message : String(error), + }; + } - expect(events).toContain("before-sleep"); - expect(events).toContain("sleep-start"); - expect(events).toContain("sleep-end"); - }); + // Wait for everything to settle + await waitFor(driverTestConfig, 1000); + + // Wake the actor and check state. The sleep/start counts + // may be >1/2 because the action arriving during sleep + // wakes the actor, which may auto-sleep and wake again. + const counts = await handle.getCounts(); + expect(counts.sleepCount).toBeGreaterThanOrEqual(1); + expect(counts.startCount).toBeGreaterThanOrEqual(2); + + const entries = await handle.getLogEntries(); + const events = entries.map((e: LogEntry) => e.event); + + // CURRENT BEHAVIOR: The action succeeds because the driver + // wakes the actor to process it. The action runs on the new + // instance after wake. + expect(actionResult.succeeded).toBe(true); + expect(events).toContain("before-sleep"); + expect(events).toContain("during-sleep"); + }); - test("action via handle during sleep is queued and runs on woken instance", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + test("waitUntil works in onSleep", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // CURRENT BEHAVIOR: When an action is sent via a stateless - // handle while the actor is sleeping, the file-system driver - // queues the action. Once the actor finishes sleeping and - // wakes back up, the action executes on the new instance. + const actor = client.sleepWaitUntil.getOrCreate([ + "waituntil-onsleep", + ]); - const handle = client.sleepWithDbAction.getOrCreate([ - "action-during-sleep-handle", - ]); + // Trigger sleep + await actor.triggerSleep(); - // Insert a log entry while awake - await handle.insertLogEntry("before-sleep"); - - // Trigger sleep - await handle.triggerSleep(); - - // Immediately try to call an action via the handle. - // This action arrives while the actor is shutting down or asleep. - let actionResult: { succeeded: boolean; error?: string }; - try { - await handle.insertLogEntry("during-sleep"); - actionResult = { succeeded: true }; - } catch (error) { - actionResult = { - succeeded: false, - error: - error instanceof Error - ? error.message - : String(error), - }; - } + // Wait for sleep to complete + await waitFor(driverTestConfig, 500); - // Wait for everything to settle - await waitFor(driverTestConfig, 1000); + // Wake the actor + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - // Wake the actor and check state. The sleep/start counts - // may be >1/2 because the action arriving during sleep - // wakes the actor, which may auto-sleep and wake again. - const counts = await handle.getCounts(); - expect(counts.sleepCount).toBeGreaterThanOrEqual(1); - expect(counts.startCount).toBeGreaterThanOrEqual(2); + // Verify the waitUntil'd write appeared in the DB + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("sleep-start"); + expect(events).toContain("waituntil-write"); + }); - const entries = await handle.getLogEntries(); - const events = entries.map( - (e: LogEntry) => e.event, - ); + test("nested waitUntil inside waitUntil is drained before shutdown", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // CURRENT BEHAVIOR: The action succeeds because the driver - // wakes the actor to process it. The action runs on the new - // instance after wake. - expect(actionResult.succeeded).toBe(true); - expect(events).toContain("before-sleep"); - expect(events).toContain("during-sleep"); - }); + const actor = client.sleepNestedWaitUntil.getOrCreate([ + "nested-waituntil", + ]); - test("waitUntil works in onSleep", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Trigger sleep + await actor.triggerSleep(); - const actor = client.sleepWaitUntil.getOrCreate([ - "waituntil-onsleep", - ]); + // Wait for sleep to complete + await waitFor(driverTestConfig, 500); - // Trigger sleep - await actor.triggerSleep(); + // Wake the actor + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - // Wait for sleep to complete - await waitFor(driverTestConfig, 500); + // Verify both outer and nested waitUntil writes appeared + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("sleep-start"); + expect(events).toContain("outer-waituntil"); + expect(events).toContain("nested-waituntil"); + }); - // Wake the actor - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + test("enqueue works during onSleep", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // Verify the waitUntil'd write appeared in the DB - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("sleep-start"); - expect(events).toContain("waituntil-write"); - }); + const actor = client.sleepEnqueue.getOrCreate(["enqueue-onsleep"]); - test("nested waitUntil inside waitUntil is drained before shutdown", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Trigger sleep + await actor.triggerSleep(); - const actor = client.sleepNestedWaitUntil.getOrCreate([ - "nested-waituntil", - ]); + // Wait for sleep to complete + await waitFor(driverTestConfig, 500); - // Trigger sleep - await actor.triggerSleep(); + // Wake the actor + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.enqueueSuccess).toBe(true); + expect(counts.enqueueError).toBeNull(); + }); - // Wait for sleep to complete - await waitFor(driverTestConfig, 500); + test("schedule.after in onSleep persists and fires on wake", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // Wake the actor - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + const actor = client.sleepScheduleAfter.getOrCreate([ + "schedule-after-onsleep", + ]); - // Verify both outer and nested waitUntil writes appeared - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("sleep-start"); - expect(events).toContain("outer-waituntil"); - expect(events).toContain("nested-waituntil"); - }); + // Trigger sleep + await actor.triggerSleep(); - test("enqueue works during onSleep", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Wait for sleep to complete + await waitFor(driverTestConfig, 500); - const actor = client.sleepEnqueue.getOrCreate([ - "enqueue-onsleep", - ]); + // Wake the actor by calling an action, then wait for + // the scheduled alarm to fire (it was scheduled with + // 100ms delay, re-armed on wake via initializeAlarms) + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - // Trigger sleep - await actor.triggerSleep(); + // Wait for the scheduled action to fire after wake + await waitFor(driverTestConfig, 500); - // Wait for sleep to complete - await waitFor(driverTestConfig, 500); + // Verify the scheduled action wrote to the DB + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("sleep"); + expect(events).toContain("scheduled-action"); + }); - // Wake the actor - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.enqueueSuccess).toBe(true); - expect(counts.enqueueError).toBeNull(); - }); + test("action via WebSocket connection during sleep shutdown succeeds", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - test("schedule.after in onSleep persists and fires on wake", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Actions from pre-existing connections during the graceful + // shutdown window should succeed since assertReady() only + // blocks after #shutdownComplete is set. - const actor = client.sleepScheduleAfter.getOrCreate([ - "schedule-after-onsleep", - ]); + const handle = client.sleepWithDbAction.getOrCreate([ + "ws-during-sleep", + ]); + const connection = handle.connect(); - // Trigger sleep - await actor.triggerSleep(); + // Wait for connection to be established + await vi.waitFor(async () => { + expect(connection.isConnected).toBe(true); + }); - // Wait for sleep to complete - await waitFor(driverTestConfig, 500); + // Insert a log entry while awake + await connection.insertLogEntry("before-sleep"); - // Wake the actor by calling an action, then wait for - // the scheduled alarm to fire (it was scheduled with - // 100ms delay, re-armed on wake via initializeAlarms) - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + // Trigger sleep via the connection + await connection.triggerSleep(); - // Wait for the scheduled action to fire after wake - await waitFor(driverTestConfig, 500); + // Send an action via the WebSocket connection during the + // graceful shutdown window. This should succeed. + await connection.insertLogEntry("ws-during-sleep"); - // Verify the scheduled action wrote to the DB - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("sleep"); - expect(events).toContain("scheduled-action"); - }); + // Wait for sleep to fully complete + await waitFor(driverTestConfig, 1500); - test("action via WebSocket connection during sleep shutdown succeeds", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Dispose the connection + await connection.dispose(); - // Actions from pre-existing connections during the graceful - // shutdown window should succeed since assertReady() only - // blocks after #shutdownComplete is set. + // Wake the actor + const counts = await handle.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - const handle = client.sleepWithDbAction.getOrCreate([ - "ws-during-sleep", - ]); - const connection = handle.connect(); + // Get log entries after waking + const entries = await handle.getLogEntries(); + const events = entries.map((e: LogEntry) => e.event); - // Wait for connection to be established - await vi.waitFor(async () => { - expect(connection.isConnected).toBe(true); - }); + expect(events).toContain("before-sleep"); + expect(events).toContain("sleep-start"); + expect(events).toContain("ws-during-sleep"); + }); + test("new connections rejected during sleep shutdown", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // Insert a log entry while awake - await connection.insertLogEntry("before-sleep"); + // The sleepWithDbAction actor has a 500ms delay in + // onSleep, giving us a window to attempt a new connection + // while the actor is actively shutting down. - // Trigger sleep via the connection - await connection.triggerSleep(); + const handle = client.sleepWithDbAction.getOrCreate([ + "conn-rejected-during-sleep", + ]); + const firstConn = handle.connect(); - // Send an action via the WebSocket connection during the - // graceful shutdown window. This should succeed. - await connection.insertLogEntry("ws-during-sleep"); + // Wait for first connection + await vi.waitFor(async () => { + expect(firstConn.isConnected).toBe(true); + }); - // Wait for sleep to fully complete - await waitFor(driverTestConfig, 1500); + // Trigger sleep (the actor will be in onSleep for ~500ms) + await firstConn.triggerSleep(); - // Dispose the connection - await connection.dispose(); + // Wait a moment for the shutdown to start + await waitFor(driverTestConfig, 100); - // Wake the actor - const counts = await handle.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + // Attempt a new connection during shutdown. + // The file-system driver queues the connection until + // the actor wakes, so this should not throw. The + // connection will be established on the new instance. + const secondConn = handle.connect(); - // Get log entries after waking - const entries = await handle.getLogEntries(); - const events = entries.map( - (e: LogEntry) => e.event, - ); + // Wait for sleep to complete and actor to wake + await waitFor(driverTestConfig, 2000); - expect(events).toContain("before-sleep"); - expect(events).toContain("sleep-start"); - expect(events).toContain("ws-during-sleep"); + // The second connection should eventually connect + // on the woken instance + await vi.waitFor(async () => { + expect(secondConn.isConnected).toBe(true); }); - test("new connections rejected during sleep shutdown", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); - // The sleepWithDbAction actor has a 500ms delay in - // onSleep, giving us a window to attempt a new connection - // while the actor is actively shutting down. + // Verify the actor went through a sleep-wake cycle + const counts = await handle.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - const handle = client.sleepWithDbAction.getOrCreate([ - "conn-rejected-during-sleep", - ]); - const firstConn = handle.connect(); - - // Wait for first connection - await vi.waitFor(async () => { - expect(firstConn.isConnected).toBe(true); - }); + await firstConn.dispose(); + await secondConn.dispose(); + }); - // Trigger sleep (the actor will be in onSleep for ~500ms) - await firstConn.triggerSleep(); + test("new raw WebSocket during sleep shutdown is rejected or queued", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); + + // The sleepWithRawWs actor has a 500ms delay in onSleep. + // A raw WebSocket request during shutdown is rejected by + // the manager driver with "Actor stopping" because the + // handleRawWebSocket guard blocks new WebSocket handlers + // when #stopCalled is true. + + const handle = client.sleepWithRawWs.getOrCreate([ + "raw-ws-rejected-during-sleep", + ]); + + // Trigger sleep + await handle.triggerSleep(); + + // Wait a moment for shutdown to begin + await waitFor(driverTestConfig, 100); + + // Attempt a raw WebSocket during shutdown. + // This should be rejected by the driver/guard. + let wsError: string | undefined; + let queuedWs: WebSocket | undefined; + try { + queuedWs = await handle.webSocket(); + } catch (error) { + wsError = + error instanceof Error ? error.message : String(error); + } - // Wait a moment for the shutdown to start - await waitFor(driverTestConfig, 100); + // Current behavior varies by timing. The raw websocket + // may be rejected during shutdown, or it may be queued + // and connected on the woken instance. + expect(Boolean(wsError || queuedWs)).toBe(true); + if (wsError) { + expect(wsError).toContain("stopping"); + } + if (queuedWs) { + await new Promise((resolve, reject) => { + const onMessage = (event: MessageEvent) => { + const data = JSON.parse(String(event.data)); + if (data.type === "connected") { + cleanup(); + resolve(); + } + }; + const onClose = () => { + cleanup(); + reject(new Error("websocket closed before connect")); + }; + const cleanup = () => { + queuedWs!.removeEventListener("message", onMessage); + queuedWs!.removeEventListener("close", onClose); + }; - // Attempt a new connection during shutdown. - // The file-system driver queues the connection until - // the actor wakes, so this should not throw. The - // connection will be established on the new instance. - const secondConn = handle.connect(); + queuedWs.addEventListener("message", onMessage); + queuedWs.addEventListener("close", onClose, { + once: true, + }); + }); + queuedWs.close(); + } - // Wait for sleep to complete and actor to wake - await waitFor(driverTestConfig, 2000); + // Wait for sleep to complete + await waitFor(driverTestConfig, 1500); - // The second connection should eventually connect - // on the woken instance - await vi.waitFor(async () => { - expect(secondConn.isConnected).toBe(true); - }); + // Verify the actor can still wake and function normally + const counts = await handle.getCounts(); + expect(counts.sleepCount).toBeGreaterThanOrEqual(1); + expect(counts.startCount).toBeGreaterThanOrEqual(2); + }); - // Verify the actor went through a sleep-wake cycle - const counts = await handle.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + test("onSleep throwing does not prevent clean shutdown", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - await firstConn.dispose(); - await secondConn.dispose(); - }); + const actor = client.sleepOnSleepThrows.getOrCreate([ + "onsleep-throws", + ]); - test("new raw WebSocket during sleep shutdown is rejected or queued", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Trigger sleep. The onSleep handler throws after + // writing "sleep-before-throw" to the DB. + await actor.triggerSleep(); - // The sleepWithRawWs actor has a 500ms delay in onSleep. - // A raw WebSocket request during shutdown is rejected by - // the manager driver with "Actor stopping" because the - // handleRawWebSocket guard blocks new WebSocket handlers - // when #stopCalled is true. + // Wait for sleep to complete + await waitFor(driverTestConfig, 500); - const handle = client.sleepWithRawWs.getOrCreate([ - "raw-ws-rejected-during-sleep", - ]); + // Wake the actor. It should have shut down cleanly + // despite the throw, because #shutdownComplete is set + // in the finally block. + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - // Trigger sleep - await handle.triggerSleep(); - - // Wait a moment for shutdown to begin - await waitFor(driverTestConfig, 100); - - // Attempt a raw WebSocket during shutdown. - // This should be rejected by the driver/guard. - let wsError: string | undefined; - let queuedWs: WebSocket | undefined; - try { - queuedWs = await handle.webSocket(); - } catch (error) { - wsError = error instanceof Error - ? error.message - : String(error); - } + // Verify the DB write before the throw was persisted + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("sleep-before-throw"); + }); - // Current behavior varies by timing. The raw websocket - // may be rejected during shutdown, or it may be queued - // and connected on the woken instance. - expect(Boolean(wsError || queuedWs)).toBe(true); - if (wsError) { - expect(wsError).toContain("stopping"); - } - if (queuedWs) { - await new Promise((resolve, reject) => { - const onMessage = (event: MessageEvent) => { - const data = JSON.parse(String(event.data)); - if (data.type === "connected") { - cleanup(); - resolve(); - } - }; - const onClose = () => { - cleanup(); - reject(new Error("websocket closed before connect")); - }; - const cleanup = () => { - queuedWs!.removeEventListener("message", onMessage); - queuedWs!.removeEventListener("close", onClose); - }; - - queuedWs.addEventListener("message", onMessage); - queuedWs.addEventListener("close", onClose, { - once: true, - }); - }); - queuedWs.close(); - } + test("waitUntil rejection during shutdown does not block shutdown", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // Wait for sleep to complete - await waitFor(driverTestConfig, 1500); + const actor = client.sleepWaitUntilRejects.getOrCreate([ + "waituntil-rejects", + ]); - // Verify the actor can still wake and function normally - const counts = await handle.getCounts(); - expect(counts.sleepCount).toBeGreaterThanOrEqual(1); - expect(counts.startCount).toBeGreaterThanOrEqual(2); - }); + // Trigger sleep. The onSleep handler registers a + // rejecting waitUntil and a succeeding one. + await actor.triggerSleep(); - test("onSleep throwing does not prevent clean shutdown", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Wait for sleep to complete + await waitFor(driverTestConfig, 500); - const actor = client.sleepOnSleepThrows.getOrCreate([ - "onsleep-throws", - ]); + // Wake the actor. Shutdown should have completed + // despite the rejection. + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - // Trigger sleep. The onSleep handler throws after - // writing "sleep-before-throw" to the DB. - await actor.triggerSleep(); + // The succeeding waitUntil should still have run + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("sleep"); + expect(events).toContain("waituntil-after-reject"); + }); - // Wait for sleep to complete - await waitFor(driverTestConfig, 500); + test("double sleep call is a no-op", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // Wake the actor. It should have shut down cleanly - // despite the throw, because #shutdownComplete is set - // in the finally block. - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + // Use a connection to send the sleep trigger, because + // a handle-based action goes through the driver which + // would wake the actor for a second cycle. + const handle = client.sleepWithDbAction.getOrCreate([ + "double-sleep", + ]); + const connection = handle.connect(); - // Verify the DB write before the throw was persisted - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("sleep-before-throw"); + await vi.waitFor(async () => { + expect(connection.isConnected).toBe(true); }); - test("waitUntil rejection during shutdown does not block shutdown", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Trigger sleep twice rapidly via the connection. + // The second call should be a no-op because + // #sleepCalled is already true. + await connection.triggerSleep(); + try { + await connection.triggerSleep(); + } catch { + // May throw if actor already stopping + } - const actor = client.sleepWaitUntilRejects.getOrCreate([ - "waituntil-rejects", - ]); + // Wait for sleep to complete + await waitFor(driverTestConfig, 1500); + await connection.dispose(); - // Trigger sleep. The onSleep handler registers a - // rejecting waitUntil and a succeeding one. - await actor.triggerSleep(); + // Wake the actor. It should have gone through exactly + // one sleep-wake cycle. + const counts = await handle.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); + }); - // Wait for sleep to complete - await waitFor(driverTestConfig, 500); + test("state mutations in waitUntil callback are persisted", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // Wake the actor. Shutdown should have completed - // despite the rejection. - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + const actor = client.sleepWaitUntilState.getOrCreate([ + "waituntil-state-persist", + ]); - // The succeeding waitUntil should still have run - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("sleep"); - expect(events).toContain("waituntil-after-reject"); - }); + // Trigger sleep. The onSleep handler registers a + // waitUntil that mutates c.state.waitUntilRan. + await actor.triggerSleep(); - test("double sleep call is a no-op", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Wait for sleep to complete + await waitFor(driverTestConfig, 500); - // Use a connection to send the sleep trigger, because - // a handle-based action goes through the driver which - // would wake the actor for a second cycle. - const handle = client.sleepWithDbAction.getOrCreate([ - "double-sleep", - ]); - const connection = handle.connect(); + // Wake the actor and verify the state mutation + // from the waitUntil callback was persisted. + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); + expect(counts.waitUntilRan).toBe(true); - await vi.waitFor(async () => { - expect(connection.isConnected).toBe(true); - }); + // Verify the DB write from waitUntil was also persisted + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("waituntil-state"); + }); - // Trigger sleep twice rapidly via the connection. - // The second call should be a no-op because - // #sleepCalled is already true. - await connection.triggerSleep(); - try { - await connection.triggerSleep(); - } catch { - // May throw if actor already stopping - } + test("alarm does not fire during shutdown", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - // Wait for sleep to complete - await waitFor(driverTestConfig, 1500); - await connection.dispose(); + const actor = client.sleepWithDb.getOrCreate([ + "alarm-no-fire-during-shutdown", + ]); - // Wake the actor. It should have gone through exactly - // one sleep-wake cycle. - const counts = await handle.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); - }); + // Schedule an alarm with a very short delay + await actor.setAlarm(50); - test("state mutations in waitUntil callback are persisted", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Immediately trigger sleep. The cancelAlarm call in + // onStop should prevent the alarm from firing during + // the shutdown sequence. + await actor.triggerSleep(); - const actor = client.sleepWaitUntilState.getOrCreate([ - "waituntil-state-persist", - ]); + // Wait for sleep to fully complete + await waitFor(driverTestConfig, 500); - // Trigger sleep. The onSleep handler registers a - // waitUntil that mutates c.state.waitUntilRan. - await actor.triggerSleep(); + // Wake the actor. The alarm should fire on the new + // instance (re-armed by initializeAlarms on wake). + const counts = await actor.getCounts(); + expect(counts.sleepCount).toBe(1); + expect(counts.startCount).toBe(2); - // Wait for sleep to complete - await waitFor(driverTestConfig, 500); + // Wait for the alarm to fire on the woken instance + await waitFor(driverTestConfig, 500); - // Wake the actor and verify the state mutation - // from the waitUntil callback was persisted. - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); - expect(counts.waitUntilRan).toBe(true); + const entries = await actor.getLogEntries(); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("alarm"); + }); - // Verify the DB write from waitUntil was also persisted - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("waituntil-state"); - }); + test( + "ws handler exceeding grace period should still complete db writes", + async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - test("alarm does not fire during shutdown", async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); - - const actor = client.sleepWithDb.getOrCreate([ - "alarm-no-fire-during-shutdown", + const actor = client.sleepWsMessageExceedsGrace.getOrCreate([ + "ws-exceeds-grace", ]); + const ws = await connectRawWebSocket(actor); - // Schedule an alarm with a very short delay - await actor.setAlarm(50); + // Send a message that starts slow async DB work + ws.send("slow-db-work"); + + // Wait for the handler to confirm it started + await new Promise((resolve) => { + const onMessage = (event: MessageEvent) => { + const data = JSON.parse(String(event.data)); + if (data.type === "started") { + ws.removeEventListener("message", onMessage); + resolve(); + } + }; + ws.addEventListener("message", onMessage); + }); - // Immediately trigger sleep. The cancelAlarm call in - // onStop should prevent the alarm from firing during - // the shutdown sequence. + // Trigger sleep while the handler is still doing slow + // work. The grace period (200ms) is much shorter than the + // handler delay (2000ms), so shutdown will time out and + // clean up the database while the handler is still running. await actor.triggerSleep(); - // Wait for sleep to fully complete - await waitFor(driverTestConfig, 500); + // Wait for the handler to finish and the actor to complete + // its sleep cycle. The handler runs for 2000ms. After that + // the actor sleeps (the timed-out shutdown already ran, but + // the handler promise still resolves in the background). + await waitFor( + driverTestConfig, + EXCEEDS_GRACE_HANDLER_DELAY + + EXCEEDS_GRACE_SLEEP_TIMEOUT + + 500, + ); - // Wake the actor. The alarm should fire on the new - // instance (re-armed by initializeAlarms on wake). - const counts = await actor.getCounts(); - expect(counts.sleepCount).toBe(1); - expect(counts.startCount).toBe(2); + // Wake the actor and check what happened. + const status = await actor.getStatus(); + expect(status.sleepCount).toBeGreaterThanOrEqual(1); + expect(status.startCount).toBeGreaterThanOrEqual(2); - // Wait for the alarm to fire on the woken instance - await waitFor(driverTestConfig, 500); + // The handler started. + expect(status.messageStarted).toBe(1); + + // Exceeding the configured grace period stops later DB + // work in the async handler before it can finish. + expect(status.messageFinished).toBe(0); const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("alarm"); - }); + const events = entries.map((e: { event: string }) => e.event); + expect(events).toContain("msg-start"); + expect(events).not.toContain("msg-finish"); + }, + { timeout: 15_000 }, + ); + + test( + "concurrent ws handlers with cached db ref get errors when grace period exceeded", + async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); - test( - "ws handler exceeding grace period should still complete db writes", - async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + const actor = + client.sleepWsConcurrentDbExceedsGrace.getOrCreate([ + "ws-concurrent-exceeds-grace", + ]); + const ws = await connectRawWebSocket(actor); - const actor = - client.sleepWsMessageExceedsGrace.getOrCreate([ - "ws-exceeds-grace", - ]); - const ws = await connectRawWebSocket(actor); - - // Send a message that starts slow async DB work - ws.send("slow-db-work"); - - // Wait for the handler to confirm it started - await new Promise((resolve) => { - const onMessage = (event: MessageEvent) => { - const data = JSON.parse(String(event.data)); - if (data.type === "started") { - ws.removeEventListener( - "message", - onMessage, - ); + const MESSAGE_COUNT = 3; + let startedCount = 0; + + // Set up listener for "started" confirmations + const allStarted = new Promise((resolve) => { + const onMessage = (event: MessageEvent) => { + const data = JSON.parse(String(event.data)); + if (data.type === "started") { + startedCount++; + if (startedCount === MESSAGE_COUNT) { + ws.removeEventListener("message", onMessage); resolve(); } - }; - ws.addEventListener("message", onMessage); - }); + } + }; + ws.addEventListener("message", onMessage); + }); - // Trigger sleep while the handler is still doing slow - // work. The grace period (200ms) is much shorter than the - // handler delay (2000ms), so shutdown will time out and - // clean up the database while the handler is still running. - await actor.triggerSleep(); - - // Wait for the handler to finish and the actor to complete - // its sleep cycle. The handler runs for 2000ms. After that - // the actor sleeps (the timed-out shutdown already ran, but - // the handler promise still resolves in the background). - await waitFor( - driverTestConfig, - EXCEEDS_GRACE_HANDLER_DELAY + - EXCEEDS_GRACE_SLEEP_TIMEOUT + - 500, + // Send multiple messages rapidly. Each handler captures + // c.db before awaiting and uses the cached reference after + // the delay. Multiple handlers will try to use the cached + // db reference after VFS teardown. + for (let i = 0; i < MESSAGE_COUNT; i++) { + ws.send( + JSON.stringify({ + type: "slow-db-work", + index: i, + }), ); + } - // Wake the actor and check what happened. - const status = await actor.getStatus(); - expect(status.sleepCount).toBeGreaterThanOrEqual(1); - expect(status.startCount).toBeGreaterThanOrEqual(2); + // Wait for all handlers to confirm they started + await allStarted; - // The handler started. - expect(status.messageStarted).toBe(1); + // Trigger sleep while all handlers are doing slow work + await actor.triggerSleep(); - // Exceeding the configured grace period stops later DB - // work in the async handler before it can finish. - expect(status.messageFinished).toBe(0); + // Wait for handlers to finish + actor to sleep and wake + await waitFor( + driverTestConfig, + EXCEEDS_GRACE_HANDLER_DELAY + + MESSAGE_COUNT * 50 + + EXCEEDS_GRACE_SLEEP_TIMEOUT + + 500, + ); - const entries = await actor.getLogEntries(); - const events = entries.map( - (e: { event: string }) => e.event, - ); - expect(events).toContain("msg-start"); - expect(events).not.toContain("msg-finish"); - }, - { timeout: 15_000 }, - ); + // Wake the actor. All handlers should have completed + // their DB writes successfully. + const status = await actor.getStatus(); + expect(status.sleepCount).toBeGreaterThanOrEqual(1); + expect(status.startCount).toBeGreaterThanOrEqual(2); + expect(status.handlerStarted).toBe(MESSAGE_COUNT); + + // Exceeding the shutdown grace period cuts off the + // handlers before their delayed DB writes can finish. + expect(status.handlerFinished).toBe(0); + expect(status.handlerErrors).toEqual([]); + }, + { timeout: 15_000 }, + ); + + test( + "active db writes interrupted by sleep produce db error", + async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); + + const actor = client.sleepWsActiveDbExceedsGrace.getOrCreate([ + "ws-active-db-exceeds-grace", + ]); + const ws = await connectRawWebSocket(actor); - test( - "concurrent ws handlers with cached db ref get errors when grace period exceeded", - async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); + // Start the write loop + ws.send("start-writes"); + + // Wait for confirmation + await new Promise((resolve) => { + const onMessage = (event: MessageEvent) => { + const data = JSON.parse(String(event.data)); + if (data.type === "started") { + ws.removeEventListener("message", onMessage); + resolve(); + } + }; + ws.addEventListener("message", onMessage); + }); - const actor = - client.sleepWsConcurrentDbExceedsGrace.getOrCreate( - ["ws-concurrent-exceeds-grace"], - ); - const ws = await connectRawWebSocket(actor); - - const MESSAGE_COUNT = 3; - let startedCount = 0; - - // Set up listener for "started" confirmations - const allStarted = new Promise((resolve) => { - const onMessage = (event: MessageEvent) => { - const data = JSON.parse(String(event.data)); - if (data.type === "started") { - startedCount++; - if (startedCount === MESSAGE_COUNT) { - ws.removeEventListener( - "message", - onMessage, - ); - resolve(); - } - } - }; - ws.addEventListener("message", onMessage); - }); + // Trigger sleep while writes are in progress. + await actor.triggerSleep(); - // Send multiple messages rapidly. Each handler captures - // c.db before awaiting and uses the cached reference after - // the delay. Multiple handlers will try to use the cached - // db reference after VFS teardown. - for (let i = 0; i < MESSAGE_COUNT; i++) { - ws.send( - JSON.stringify({ - type: "slow-db-work", - index: i, - }), + await vi.waitFor( + async () => { + const status = await actor.getStatus(); + expect(status.sleepCount).toBeGreaterThanOrEqual(1); + expect(status.startCount).toBeGreaterThanOrEqual(2); + + const entries = await actor.getLogEntries(); + const writeEntries = entries.filter( + (e: { event: string }) => + e.event.startsWith("write-"), ); - } - - // Wait for all handlers to confirm they started - await allStarted; - - // Trigger sleep while all handlers are doing slow work - await actor.triggerSleep(); - - // Wait for handlers to finish + actor to sleep and wake - await waitFor( - driverTestConfig, - EXCEEDS_GRACE_HANDLER_DELAY + - MESSAGE_COUNT * 50 + - EXCEEDS_GRACE_SLEEP_TIMEOUT + - 500, - ); - - // Wake the actor. All handlers should have completed - // their DB writes successfully. - const status = await actor.getStatus(); - expect(status.sleepCount).toBeGreaterThanOrEqual(1); - expect(status.startCount).toBeGreaterThanOrEqual(2); - expect(status.handlerStarted).toBe(MESSAGE_COUNT); - - // Exceeding the shutdown grace period cuts off the - // handlers before their delayed DB writes can finish. - expect(status.handlerFinished).toBe(0); - expect(status.handlerErrors).toEqual([]); - }, - { timeout: 15_000 }, - ); - - test( - "active db writes interrupted by sleep produce db error", - async (c) => { - const { client } = await setupDriverTest( - c, - driverTestConfig, - ); - - const actor = - client.sleepWsActiveDbExceedsGrace.getOrCreate([ - "ws-active-db-exceeds-grace", - ]); - const ws = await connectRawWebSocket(actor); - - // Start the write loop - ws.send("start-writes"); - - // Wait for confirmation - await new Promise((resolve) => { - const onMessage = (event: MessageEvent) => { - const data = JSON.parse(String(event.data)); - if (data.type === "started") { - ws.removeEventListener( - "message", - onMessage, - ); - resolve(); - } - }; - ws.addEventListener("message", onMessage); - }); - - // Trigger sleep while writes are in progress. - await actor.triggerSleep(); - - await vi.waitFor( - async () => { - const status = await actor.getStatus(); - expect(status.sleepCount).toBeGreaterThanOrEqual(1); - expect(status.startCount).toBeGreaterThanOrEqual(2); - - const entries = await actor.getLogEntries(); - const writeEntries = entries.filter( - (e: { event: string }) => - e.event.startsWith("write-"), - ); - expect(writeEntries.length).toBeGreaterThan(0); - expect(writeEntries.length).toBeLessThan( - ACTIVE_DB_WRITE_COUNT, - ); - }, - { - timeout: 10_000, - interval: 50, - }, - ); + expect(writeEntries.length).toBeGreaterThan(0); + expect(writeEntries.length).toBeLessThan( + ACTIVE_DB_WRITE_COUNT, + ); + }, + { + timeout: 10_000, + interval: 50, + }, + ); - // Verify the DB has fewer rows than the full count. - const entries = await actor.getLogEntries(); - const writeEntries = entries.filter( - (e: { event: string }) => - e.event.startsWith("write-"), - ); - expect(writeEntries.length).toBeGreaterThan(0); - expect(writeEntries.length).toBeLessThan( - ACTIVE_DB_WRITE_COUNT, - ); - }, - { timeout: 30_000 }, - ); - }); + // Verify the DB has fewer rows than the full count. + const entries = await actor.getLogEntries(); + const writeEntries = entries.filter((e: { event: string }) => + e.event.startsWith("write-"), + ); + expect(writeEntries.length).toBeGreaterThan(0); + expect(writeEntries.length).toBeLessThan(ACTIVE_DB_WRITE_COUNT); + }, + { timeout: 30_000 }, + ); + }); } diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sleep.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sleep.ts index 72ddb826aa..c28c2466fa 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sleep.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-sleep.ts @@ -17,9 +17,7 @@ async function waitForRawWebSocketMessage(ws: WebSocket) { const onClose = (event: { code?: number }) => { cleanup(); reject( - new Error( - `websocket closed early: ${event.code ?? "unknown"}`, - ), + new Error(`websocket closed early: ${event.code ?? "unknown"}`), ); }; const onError = () => { @@ -38,14 +36,20 @@ async function waitForRawWebSocketMessage(ws: WebSocket) { }); } -async function connectRawWebSocket(handle: { webSocket(): Promise }) { +async function connectRawWebSocket(handle: { + webSocket(): Promise; +}) { const ws = await handle.webSocket(); await new Promise((resolve, reject) => { ws.addEventListener("open", () => resolve(), { once: true }); - ws.addEventListener("error", () => reject(new Error("websocket error")), { - once: true, - }); + ws.addEventListener( + "error", + () => reject(new Error("websocket error")), + { + once: true, + }, + ); }); await waitForRawWebSocketMessage(ws); @@ -55,9 +59,13 @@ async function connectRawWebSocket(handle: { webSocket(): Promise }) async function closeRawWebSocket(ws: WebSocket) { await new Promise((resolve, reject) => { ws.addEventListener("close", () => resolve(), { once: true }); - ws.addEventListener("error", () => reject(new Error("websocket error")), { - once: true, - }); + ws.addEventListener( + "error", + () => reject(new Error("websocket error")), + { + once: true, + }, + ); ws.close(); }); } @@ -125,8 +133,8 @@ export function runActorSleepTests(driverTestConfig: DriverTestConfig) { const sleepActor2 = client.sleep.getOrCreate(); await vi.waitFor( async () => { - const { startCount, sleepCount } = - await sleepActor2.getCounts(); + const { startCount, sleepCount } = + await sleepActor2.getCounts(); expect(sleepCount).toBeGreaterThanOrEqual(1); expect(startCount).toBe(sleepCount + 1); }, @@ -822,8 +830,7 @@ export function runActorSleepTests(driverTestConfig: DriverTestConfig) { // Verify sleep happened { - const { startCount, sleepCount } = - await sleepActor.getCounts(); + const { startCount, sleepCount } = await sleepActor.getCounts(); expect(sleepCount).toBe(1); expect(startCount).toBe(2); } @@ -885,8 +892,7 @@ export function runActorSleepTests(driverTestConfig: DriverTestConfig) { // Verify sleep happened { - const { startCount, sleepCount } = - await sleepActor.getCounts(); + const { startCount, sleepCount } = await sleepActor.getCounts(); expect(sleepCount).toBe(1); expect(startCount).toBe(2); } diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-workflow.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-workflow.ts index 2ee0d82a56..53aa96d0b5 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-workflow.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-workflow.ts @@ -222,7 +222,8 @@ export function runActorWorkflowTests(driverTestConfig: DriverTestConfig) { for ( let i = 0; i < 40 && - (state.tryStepFailure === null || state.tryJoinFailure === null); + (state.tryStepFailure === null || + state.tryJoinFailure === null); i++ ) { await waitFor(driverTestConfig, 50); @@ -382,34 +383,33 @@ export function runActorWorkflowTests(driverTestConfig: DriverTestConfig) { ); }); - test.skipIf(driverTestConfig.skip?.sleep)( - "completed workflows sleep instead of destroying the actor", - async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - const actor = client.workflowCompleteActor.getOrCreate([ - "workflow-complete", - ]); - - let state = await actor.getState(); - for ( - let i = 0; - i < 20 && - (state.sleepCount === 0 || state.startCount < 2); - i++ - ) { - await waitFor(driverTestConfig, 100); - try { - state = await actor.getState(); - } catch (error) { - if (!isActorStoppingConnectionError(error)) { - throw error; - } + test.skipIf(driverTestConfig.skip?.sleep)( + "completed workflows sleep instead of destroying the actor", + async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); + const actor = client.workflowCompleteActor.getOrCreate([ + "workflow-complete", + ]); + + let state = await actor.getState(); + for ( + let i = 0; + i < 20 && (state.sleepCount === 0 || state.startCount < 2); + i++ + ) { + await waitFor(driverTestConfig, 100); + try { + state = await actor.getState(); + } catch (error) { + if (!isActorStoppingConnectionError(error)) { + throw error; } } - expect(state.runCount).toBeGreaterThan(0); - expect(state.sleepCount).toBeGreaterThan(0); - expect(state.startCount).toBeGreaterThan(1); - }, + } + expect(state.runCount).toBeGreaterThan(0); + expect(state.sleepCount).toBeGreaterThan(0); + expect(state.startCount).toBeGreaterThan(1); + }, ); test("workflow steps can destroy the actor", async (c) => { @@ -426,16 +426,14 @@ export function runActorWorkflowTests(driverTestConfig: DriverTestConfig) { expect(wasDestroyed, "actor onDestroy not called").toBeTruthy(); }); - await vi.waitFor(async () => { - let actorRunning = false; - try { - await client.workflowDestroyActor - .get([actorKey]) - .resolve(); - actorRunning = true; - } catch (err) { - expect((err as ActorError).group).toBe("actor"); - expect((err as ActorError).code).toBe("not_found"); + await vi.waitFor(async () => { + let actorRunning = false; + try { + await client.workflowDestroyActor.get([actorKey]).resolve(); + actorRunning = true; + } catch (err) { + expect((err as ActorError).group).toBe("actor"); + expect((err as ActorError).code).toBe("not_found"); } expect(actorRunning, "actor still running").toBeFalsy(); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/dynamic-reload.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/dynamic-reload.ts index 9460b13671..7176175b61 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/dynamic-reload.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/dynamic-reload.ts @@ -3,22 +3,21 @@ import type { DriverTestConfig } from "../mod"; import { setupDriverTest, waitFor } from "../utils"; export function runDynamicReloadTests(driverTestConfig: DriverTestConfig) { - describe.skipIf(!driverTestConfig.isDynamic || driverTestConfig.skip?.sleep)( - "Dynamic Actor Reload Tests", - () => { - test("reload forces dynamic actor to sleep and reload on next request", async (c) => { - const { client } = await setupDriverTest(c, driverTestConfig); - const actor = client.sleep.getOrCreate(); + describe.skipIf( + !driverTestConfig.isDynamic || driverTestConfig.skip?.sleep, + )("Dynamic Actor Reload Tests", () => { + test("reload forces dynamic actor to sleep and reload on next request", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); + const actor = client.sleep.getOrCreate(); - const { startCount: before } = await actor.getCounts(); - expect(before).toBe(1); + const { startCount: before } = await actor.getCounts(); + expect(before).toBe(1); - await actor.reload(); - await waitFor(driverTestConfig, 250); + await actor.reload(); + await waitFor(driverTestConfig, 250); - const { startCount: after } = await actor.getCounts(); - expect(after).toBe(2); - }); - }, - ); + const { startCount: after } = await actor.getCounts(); + expect(after).toBe(2); + }); + }); } diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-query-url.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-query-url.ts index 7c2b425127..59e24ed29e 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-query-url.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-query-url.ts @@ -23,14 +23,20 @@ export function runGatewayQueryUrlTests(driverTestConfig: DriverTestConfig) { const gatewayUrl = await handle.getGatewayUrl(); const parsedUrl = new URL(gatewayUrl); - expect(parsedUrl.searchParams.get("rvt-namespace")).toBeTruthy(); - expect(parsedUrl.searchParams.get("rvt-method")).toBe("getOrCreate"); - expect(parsedUrl.searchParams.get("rvt-crash-policy")).toBe("sleep"); + expect( + parsedUrl.searchParams.get("rvt-namespace"), + ).toBeTruthy(); + expect(parsedUrl.searchParams.get("rvt-method")).toBe( + "getOrCreate", + ); + expect(parsedUrl.searchParams.get("rvt-crash-policy")).toBe( + "sleep", + ); const response = await fetch( buildGatewayInspectorUrl(gatewayUrl, "/inspector/state"), { - headers: { Authorization: "Bearer token" }, + headers: { Authorization: "Bearer token" }, }, ); @@ -55,13 +61,15 @@ export function runGatewayQueryUrlTests(driverTestConfig: DriverTestConfig) { .get(["existing-gateway-query"]) .getGatewayUrl(); const parsedUrl = new URL(gatewayUrl); - expect(parsedUrl.searchParams.get("rvt-namespace")).toBeTruthy(); + expect( + parsedUrl.searchParams.get("rvt-namespace"), + ).toBeTruthy(); expect(parsedUrl.searchParams.get("rvt-method")).toBe("get"); const response = await fetch( buildGatewayInspectorUrl(gatewayUrl, "/inspector/state"), { - headers: { Authorization: "Bearer token" }, + headers: { Authorization: "Bearer token" }, }, ); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-routing.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-routing.ts index 8e86290f57..06f0a67f14 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-routing.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/gateway-routing.ts @@ -24,15 +24,12 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { const actorId = await handle.resolve(); // Make a direct request using header-based routing - const response = await fetch( - `${endpoint}/api/hello`, - { - headers: { - "x-rivet-target": "actor", - "x-rivet-actor": actorId, - }, + const response = await fetch(`${endpoint}/api/hello`, { + headers: { + "x-rivet-target": "actor", + "x-rivet-actor": actorId, }, - ); + }); expect(response.ok).toBe(true); const data = await response.json(); @@ -48,14 +45,11 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { driverTestConfig, ); - const response = await fetch( - `${endpoint}/api/hello`, - { - headers: { - "x-rivet-target": "actor", - }, + const response = await fetch(`${endpoint}/api/hello`, { + headers: { + "x-rivet-target": "actor", }, - ); + }); expect(response.ok).toBe(false); }, @@ -134,54 +128,44 @@ export function runGatewayRoutingTests(driverTestConfig: DriverTestConfig) { }, ); - httpOnlyTest( - "rejects unknown rvt-* params", - async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = client.rawHttpActor.getOrCreate([ - "query-unknown-param", - ]); - await handle.fetch("api/hello"); - - const gatewayUrl = await handle.getGatewayUrl(); - const parsedUrl = new URL(gatewayUrl); - const namespace = - parsedUrl.searchParams.get("rvt-namespace")!; - const runner = parsedUrl.searchParams.get("rvt-runner")!; - - const queryUrl = new URL( - `${endpoint}/gateway/rawHttpActor/api/hello`, - ); - queryUrl.searchParams.set("rvt-namespace", namespace); - queryUrl.searchParams.set("rvt-method", "getOrCreate"); - queryUrl.searchParams.set("rvt-key", "query-unknown-param"); - queryUrl.searchParams.set("rvt-runner", runner); - queryUrl.searchParams.set("rvt-bogus", "invalid"); - - const response = await fetch(queryUrl.toString()); - expect(response.ok).toBe(false); - }, - ); - - httpOnlyTest( - "rejects duplicate scalar rvt-* params", - async (c) => { - const { endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - // Manually build URL with duplicate rvt-namespace - const url = `${endpoint}/gateway/rawHttpActor/api/hello?rvt-namespace=a&rvt-namespace=b&rvt-method=get&rvt-key=dup`; - - const response = await fetch(url); - expect(response.ok).toBe(false); - }, - ); + httpOnlyTest("rejects unknown rvt-* params", async (c) => { + const { client, endpoint } = await setupDriverTest( + c, + driverTestConfig, + ); + + const handle = client.rawHttpActor.getOrCreate([ + "query-unknown-param", + ]); + await handle.fetch("api/hello"); + + const gatewayUrl = await handle.getGatewayUrl(); + const parsedUrl = new URL(gatewayUrl); + const namespace = parsedUrl.searchParams.get("rvt-namespace")!; + const runner = parsedUrl.searchParams.get("rvt-runner")!; + + const queryUrl = new URL( + `${endpoint}/gateway/rawHttpActor/api/hello`, + ); + queryUrl.searchParams.set("rvt-namespace", namespace); + queryUrl.searchParams.set("rvt-method", "getOrCreate"); + queryUrl.searchParams.set("rvt-key", "query-unknown-param"); + queryUrl.searchParams.set("rvt-runner", runner); + queryUrl.searchParams.set("rvt-bogus", "invalid"); + + const response = await fetch(queryUrl.toString()); + expect(response.ok).toBe(false); + }); + + httpOnlyTest("rejects duplicate scalar rvt-* params", async (c) => { + const { endpoint } = await setupDriverTest(c, driverTestConfig); + + // Manually build URL with duplicate rvt-namespace + const url = `${endpoint}/gateway/rawHttpActor/api/hello?rvt-namespace=a&rvt-namespace=b&rvt-method=get&rvt-key=dup`; + + const response = await fetch(url); + expect(response.ok).toBe(false); + }); httpOnlyTest( "strips rvt-* params before forwarding to actor", diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/hibernatable-websocket-protocol.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/hibernatable-websocket-protocol.ts index e61bff8299..ada141317d 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/hibernatable-websocket-protocol.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/hibernatable-websocket-protocol.ts @@ -40,9 +40,9 @@ async function waitForMatchingJsonMessages( matcher: (message: Record) => boolean, timeoutMs: number, ): Promise>> { - return await new Promise>>( - (resolve, reject) => { - const messages: Array> = []; + return await new Promise>>( + (resolve, reject) => { + const messages: Array> = []; const timeout = setTimeout(() => { cleanup(); reject( @@ -51,40 +51,46 @@ async function waitForMatchingJsonMessages( ), ); }, timeoutMs); - const onMessage = (event: { data: string }) => { - let parsed: Record | undefined; - try { - parsed = JSON.parse(event.data as string); - } catch { - return; - } - if (!parsed) { - return; - } - if (!matcher(parsed)) { - return; - } + const onMessage = (event: { data: string }) => { + let parsed: Record | undefined; + try { + parsed = JSON.parse(event.data as string); + } catch { + return; + } + if (!parsed) { + return; + } + if (!matcher(parsed)) { + return; + } messages.push(parsed); if (messages.length >= count) { cleanup(); resolve(messages); } }; - const onClose = (event: unknown) => { - cleanup(); - reject(event); - }; - const cleanup = () => { - clearTimeout(timeout); - ws.removeEventListener("message", onMessage as (event: any) => void); - ws.removeEventListener("close", onClose as (event: any) => void); - }; - ws.addEventListener("message", onMessage as (event: any) => void); - ws.addEventListener("close", onClose as (event: any) => void, { - once: true, - }); - }, - ); + const onClose = (event: unknown) => { + cleanup(); + reject(event); + }; + const cleanup = () => { + clearTimeout(timeout); + ws.removeEventListener( + "message", + onMessage as (event: any) => void, + ); + ws.removeEventListener( + "close", + onClose as (event: any) => void, + ); + }; + ws.addEventListener("message", onMessage as (event: any) => void); + ws.addEventListener("close", onClose as (event: any) => void, { + once: true, + }); + }, + ); } async function readHibernatableAckState(websocket: WebSocket): Promise<{ @@ -133,154 +139,157 @@ async function readHibernatableAckState(websocket: WebSocket): Promise<{ export function runHibernatableWebSocketProtocolTests( driverTestConfig: DriverTestConfig, ) { - describe.skipIf( - !driverTestConfig.features?.hibernatableWebSocketProtocol, - )("hibernatable websocket protocol", () => { - test( - "replays only unacked indexed websocket messages after sleep and wake", - async (c) => { - if (driverTestConfig.clientType !== "http") { - return; - } + describe.skipIf(!driverTestConfig.features?.hibernatableWebSocketProtocol)( + "hibernatable websocket protocol", + () => { + test("replays only unacked indexed websocket messages after sleep and wake", async (c) => { + if (driverTestConfig.clientType !== "http") { + return; + } - const { client } = await setupDriverTest(c, driverTestConfig); - const actor = client.rawWebSocketActor.getOrCreate([ - "hibernatable-replay", - ]); - const ws = await actor.webSocket(); + const { client } = await setupDriverTest(c, driverTestConfig); + const actor = client.rawWebSocketActor.getOrCreate([ + "hibernatable-replay", + ]); + const ws = await actor.webSocket(); - try { - expect(await waitForJsonMessage(ws, 4_000)).toMatchObject({ - type: "welcome", - }); + try { + expect(await waitForJsonMessage(ws, 4_000)).toMatchObject({ + type: "welcome", + }); - const firstProbePromise = waitForMatchingJsonMessages( - ws, - 1, - (message) => message.type === "indexedAckProbe", - 1_000, - ); - ws.send( - JSON.stringify({ + const firstProbePromise = waitForMatchingJsonMessages( + ws, + 1, + (message) => message.type === "indexedAckProbe", + 1_000, + ); + ws.send( + JSON.stringify({ + type: "indexedAckProbe", + payload: "durable-before-sleep", + }), + ); + expect((await firstProbePromise)[0]).toMatchObject({ type: "indexedAckProbe", - payload: "durable-before-sleep", - }), - ); - expect((await firstProbePromise)[0]).toMatchObject({ - type: "indexedAckProbe", - rivetMessageIndex: 1, - }); + rivetMessageIndex: 1, + }); - await vi.waitFor( - async () => { - expect(await readHibernatableAckState(ws)).toEqual({ - lastSentIndex: 1, - lastAckedIndex: 1, - pendingIndexes: [], - }); - }, - { timeout: HIBERNATABLE_ACK_SETTLE_TIMEOUT_MS, interval: 50 }, - ); + await vi.waitFor( + async () => { + expect(await readHibernatableAckState(ws)).toEqual({ + lastSentIndex: 1, + lastAckedIndex: 1, + pendingIndexes: [], + }); + }, + { + timeout: HIBERNATABLE_ACK_SETTLE_TIMEOUT_MS, + interval: 50, + }, + ); - const sleepScheduledPromise = waitForMatchingJsonMessages( - ws, - 1, - (message) => message.type === "sleepScheduled", - 1_000, - ); - ws.send( - JSON.stringify({ - type: "scheduleSleep", - }), - ); - await sleepScheduledPromise; - await waitFor(driverTestConfig, 250); + const sleepScheduledPromise = waitForMatchingJsonMessages( + ws, + 1, + (message) => message.type === "sleepScheduled", + 1_000, + ); + ws.send( + JSON.stringify({ + type: "scheduleSleep", + }), + ); + await sleepScheduledPromise; + await waitFor(driverTestConfig, 250); - const replayedMessagesPromise = waitForMatchingJsonMessages( - ws, - 2, - (message) => message.type === "indexedEcho", - 6_000, - ); - ws.send( - JSON.stringify({ - type: "indexedEcho", - payload: "after-sleep-1", - }), - ); - ws.send( - JSON.stringify({ - type: "indexedEcho", - payload: "after-sleep-2", - }), - ); + const replayedMessagesPromise = waitForMatchingJsonMessages( + ws, + 2, + (message) => message.type === "indexedEcho", + 6_000, + ); + ws.send( + JSON.stringify({ + type: "indexedEcho", + payload: "after-sleep-1", + }), + ); + ws.send( + JSON.stringify({ + type: "indexedEcho", + payload: "after-sleep-2", + }), + ); - const replayedIndexes = (await replayedMessagesPromise).map( - (message) => message.rivetMessageIndex as number, - ); + const replayedIndexes = (await replayedMessagesPromise).map( + (message) => message.rivetMessageIndex as number, + ); - expect(replayedIndexes).toEqual([3, 4]); + expect(replayedIndexes).toEqual([3, 4]); - await vi.waitFor( - async () => { - expect(await readHibernatableAckState(ws)).toEqual({ - lastSentIndex: 4, - lastAckedIndex: 4, - pendingIndexes: [], - }); - }, - { timeout: HIBERNATABLE_ACK_SETTLE_TIMEOUT_MS, interval: 50 }, - ); + await vi.waitFor( + async () => { + expect(await readHibernatableAckState(ws)).toEqual({ + lastSentIndex: 4, + lastAckedIndex: 4, + pendingIndexes: [], + }); + }, + { + timeout: HIBERNATABLE_ACK_SETTLE_TIMEOUT_MS, + interval: 50, + }, + ); - const actorObservedOrderPromise = waitForMatchingJsonMessages( - ws, - 1, - (message) => message.type === "indexedMessageOrder", - 1_000, - ); - ws.send( - JSON.stringify({ - type: "getIndexedMessageOrder", - }), - ); - expect((await actorObservedOrderPromise)[0].order).toEqual([1, 3, 4]); - } finally { - ws.close(); - } - }, - 20_000, - ); + const actorObservedOrderPromise = + waitForMatchingJsonMessages( + ws, + 1, + (message) => message.type === "indexedMessageOrder", + 1_000, + ); + ws.send( + JSON.stringify({ + type: "getIndexedMessageOrder", + }), + ); + expect((await actorObservedOrderPromise)[0].order).toEqual([ + 1, 3, 4, + ]); + } finally { + ws.close(); + } + }, 20_000); - test( - "cleans up stale hibernatable websocket connections on restore", - async (c) => { - if (driverTestConfig.clientType !== "http") { - return; - } + test("cleans up stale hibernatable websocket connections on restore", async (c) => { + if (driverTestConfig.clientType !== "http") { + return; + } - const { client } = await setupDriverTest(c, driverTestConfig); - const conn = client.fileSystemHibernationCleanupActor - .getOrCreate() - .connect(); - let wakeConn: typeof conn | undefined; - let connDisposed = false; + const { client } = await setupDriverTest(c, driverTestConfig); + const conn = client.fileSystemHibernationCleanupActor + .getOrCreate() + .connect(); + let wakeConn: typeof conn | undefined; + let connDisposed = false; - try { - expect(await conn.ping()).toBe("pong"); - await conn.triggerSleep(); - await waitFor(driverTestConfig, 700); + try { + expect(await conn.ping()).toBe("pong"); + await conn.triggerSleep(); + await waitFor(driverTestConfig, 700); - // Disconnect the original client while the actor is asleep so the - // persisted websocket metadata is stale on the next wake. - await conn.dispose(); - connDisposed = true; - await waitFor(driverTestConfig, 100); + // Disconnect the original client while the actor is asleep so the + // persisted websocket metadata is stale on the next wake. + await conn.dispose(); + connDisposed = true; + await waitFor(driverTestConfig, 100); - // Wake the actor through a new connection so restore must clean up - // the stale persisted websocket from the sleeping generation. - wakeConn = client.fileSystemHibernationCleanupActor - .getOrCreate() - .connect(); + // Wake the actor through a new connection so restore must clean up + // the stale persisted websocket from the sleeping generation. + wakeConn = client.fileSystemHibernationCleanupActor + .getOrCreate() + .connect(); await vi.waitFor( async () => { @@ -288,8 +297,8 @@ export function runHibernatableWebSocketProtocolTests( expect(counts.sleepCount).toBeGreaterThanOrEqual(1); expect(counts.wakeCount).toBeGreaterThanOrEqual(2); }, - { timeout: 5_000, interval: 100 }, - ); + { timeout: 5_000, interval: 100 }, + ); await vi.waitFor( async () => { @@ -297,16 +306,15 @@ export function runHibernatableWebSocketProtocolTests( await wakeConn!.getDisconnectWakeCounts(); expect(disconnectWakeCounts).toEqual([2]); }, - { timeout: 5_000, interval: 100 }, - ); - } finally { - await wakeConn?.dispose().catch(() => undefined); - if (!connDisposed) { - await conn.dispose().catch(() => undefined); + { timeout: 5_000, interval: 100 }, + ); + } finally { + await wakeConn?.dispose().catch(() => undefined); + if (!connDisposed) { + await conn.dispose().catch(() => undefined); + } } - } - }, - 15_000, - ); - }); + }, 15_000); + }, + ); } diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket.ts index 87df9f5353..e6e462e3f3 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/raw-websocket.ts @@ -770,7 +770,7 @@ export function runRawWebSocketTests(driverTestConfig: DriverTestConfig) { type: "indexedAckProbe", payload: "x".repeat( HIBERNATABLE_WEBSOCKET_BUFFERED_MESSAGE_SIZE_THRESHOLD + - 8_000, + 8_000, ), }), ); diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/utils.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/utils.ts index 895fe8b3b8..a9df4736be 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/utils.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/utils.ts @@ -33,8 +33,7 @@ export async function setupDriverTest( hardCrashActor, hardCrashPreservesData, cleanup, - } = - await driverTestConfig.start(); + } = await driverTestConfig.start(); let client: Client; if (driverTestConfig.clientType === "http") { @@ -52,10 +51,7 @@ export async function setupDriverTest( } else if (driverTestConfig.clientType === "inline") { // Use inline client from driver const encoding = driverTestConfig.encoding ?? "bare"; - const managerDriver = createTestInlineClientDriver( - endpoint, - encoding, - ); + const managerDriver = createTestInlineClientDriver(endpoint, encoding); const runConfig = ClientConfigSchema.parse({ encoding: encoding, }); diff --git a/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts b/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts index b0170d2b29..018839b183 100644 --- a/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts +++ b/rivetkit-typescript/packages/rivetkit/src/drivers/engine/actor-driver.ts @@ -72,7 +72,10 @@ import { stringifyError, VERSION, } from "@/utils"; -import { wrapJsNativeDatabase, type JsNativeDatabaseLike } from "@/db/native-database"; +import { + wrapJsNativeDatabase, + type JsNativeDatabaseLike, +} from "@/db/native-database"; import { logger } from "./log"; const ENVOY_SSE_PING_INTERVAL = 1000; @@ -134,19 +137,17 @@ export class EngineActorDriver implements ActorDriver { >(); #actorRouter: ActorRouter; - #envoyStarted: PromiseWithResolvers = promiseWithResolvers( - (reason) => - logger().warn({ - msg: "unhandled envoy started promise rejection", - reason, - }), + #envoyStarted: PromiseWithResolvers = promiseWithResolvers((reason) => + logger().warn({ + msg: "unhandled envoy started promise rejection", + reason, + }), ); - #envoyStopped: PromiseWithResolvers = promiseWithResolvers( - (reason) => - logger().warn({ - msg: "unhandled envoy stopped promise rejection", - reason, - }), + #envoyStopped: PromiseWithResolvers = promiseWithResolvers((reason) => + logger().warn({ + msg: "unhandled envoy stopped promise rejection", + reason, + }), ); #isEnvoyStopped: boolean = false; #isShuttingDown: boolean = false; @@ -623,17 +624,15 @@ export class EngineActorDriver implements ActorDriver { limit?: number; }, ): Promise<[Uint8Array, Uint8Array][]> { - const result = await this.#envoy.kvListPrefix( - actorId, - prefix, - options, - ); + const result = await this.#envoy.kvListPrefix(actorId, prefix, options); logger().info({ msg: "kvListPrefix called", actorId, prefixStr: new TextDecoder().decode(prefix), entriesCount: result.length, - keys: result.map(([key]: [Uint8Array, ...unknown[]]) => new TextDecoder().decode(key)), + keys: result.map(([key]: [Uint8Array, ...unknown[]]) => + new TextDecoder().decode(key), + ), }); return result; } @@ -791,7 +790,7 @@ export class EngineActorDriver implements ActorDriver { return streamSSE(c, async (stream) => { // NOTE: onAbort does not work reliably - stream.onAbort(() => { }); + stream.onAbort(() => {}); c.req.raw.signal.addEventListener("abort", () => { logger().debug("SSE aborted"); }); @@ -1104,12 +1103,12 @@ export class EngineActorDriver implements ActorDriver { const error = innerError instanceof Error ? new Error( - `Failed to start actor ${actorId}: ${innerError.message}`, - { cause: innerError }, - ) + `Failed to start actor ${actorId}: ${innerError.message}`, + { cause: innerError }, + ) : new Error( - `Failed to start actor ${actorId}: ${String(innerError)}`, - ); + `Failed to start actor ${actorId}: ${String(innerError)}`, + ); handler.actor = undefined; handler.actorStartError = error; handler.actorStartPromise?.reject(error); @@ -1182,7 +1181,10 @@ export class EngineActorDriver implements ActorDriver { if (handler.actor) { try { - if (reason === "crash" && isStaticActorInstance(handler.actor)) { + if ( + reason === "crash" && + isStaticActorInstance(handler.actor) + ) { await handler.actor.debugForceCrash(); } else if (reason !== "crash") { await handler.actor.onStop(reason); @@ -1231,10 +1233,7 @@ export class EngineActorDriver implements ActorDriver { return await this.#actorRouter.fetch(request, { actorId }); } - #routeOverlayRequest( - actorId: string, - request: Request, - ): Response | null { + #routeOverlayRequest(actorId: string, request: Request): Response | null { const url = new URL(request.url); switch (`${request.method} ${url.pathname}`) { case "PUT /dynamic/reload": @@ -1377,7 +1376,10 @@ export class EngineActorDriver implements ActorDriver { const run = async () => { // Process message - if (isHibernatable && typeof event.rivetMessageIndex === "number") { + if ( + isHibernatable && + typeof event.rivetMessageIndex === "number" + ) { this.#recordInboundHibernatableWebSocketMessage( gatewayIdBuf, requestIdBuf, @@ -1641,11 +1643,11 @@ export class EngineActorDriver implements ActorDriver { const handler = this.#actors.get(actorId); const actorName = actorInstance && - "config" in actorInstance && - actorInstance.config && - typeof actorInstance.config === "object" && - "name" in actorInstance.config && - typeof actorInstance.config.name === "string" + "config" in actorInstance && + actorInstance.config && + typeof actorInstance.config === "object" && + "name" in actorInstance.config && + typeof actorInstance.config.name === "string" ? actorInstance.config.name : handler?.actorName; if (!actorName) { @@ -1757,5 +1759,4 @@ export class EngineActorDriver implements ActorDriver { const metaEntries = await this.#hwsLoadAll(actor.id); await this.#envoy.restoreHibernatingRequests(actor.id, metaEntries); } - } diff --git a/rivetkit-typescript/packages/rivetkit/src/dynamic/internal.ts b/rivetkit-typescript/packages/rivetkit/src/dynamic/internal.ts index 6f89f38bed..630bae9c65 100644 --- a/rivetkit-typescript/packages/rivetkit/src/dynamic/internal.ts +++ b/rivetkit-typescript/packages/rivetkit/src/dynamic/internal.ts @@ -1,8 +1,5 @@ import type { ActorKey } from "@/actor/mod"; -import type { - ActorConfig, - GlobalActorOptionsInput, -} from "@/actor/config"; +import type { ActorConfig, GlobalActorOptionsInput } from "@/actor/config"; import { ActorConfigSchema } from "@/actor/config"; import type { AnyActorDefinition, @@ -60,9 +57,13 @@ abstract class DynamicActorContextBase { } } -export class DynamicActorLoaderContext extends DynamicActorContextBase {} +export class DynamicActorLoaderContext< + TInput = unknown, +> extends DynamicActorContextBase {} -export class DynamicActorAuthContext extends DynamicActorContextBase { +export class DynamicActorAuthContext< + TInput = unknown, +> extends DynamicActorContextBase { readonly request: Request | undefined; constructor( @@ -102,7 +103,7 @@ export interface DynamicActorConfigInput< export class DynamicActorDefinition implements - BaseActorDefinition< + BaseActorDefinition< any, any, any, @@ -125,7 +126,7 @@ export class DynamicActorDefinition AnyDatabaseProvider, EventSchemaConfig, QueueSchemaConfig - >; + >; constructor(input: DynamicActorConfigInput) { this.#loader = input.load; diff --git a/rivetkit-typescript/packages/rivetkit/src/dynamic/isolate-runtime.ts b/rivetkit-typescript/packages/rivetkit/src/dynamic/isolate-runtime.ts index 27168febc9..658f72ff85 100644 --- a/rivetkit-typescript/packages/rivetkit/src/dynamic/isolate-runtime.ts +++ b/rivetkit-typescript/packages/rivetkit/src/dynamic/isolate-runtime.ts @@ -539,12 +539,7 @@ export class DynamicActorIsolateRuntime { const input = await requestToEnvelope(request); const envelope = (await refs.fetch.apply( undefined, - [ - input.url, - input.method, - input.headers, - input.bodyBase64 ?? null, - ], + [input.url, input.method, input.headers, input.bodyBase64 ?? null], { arguments: { copy: true, @@ -1672,9 +1667,7 @@ function resolveEsmPackageEntry(packageName: string): string | undefined { // inside the sandbox where the fs polyfill lacks it. try { const runtimeRequire = createRuntimeRequire(); - const nodeFs = runtimeRequire( - ["node", "fs"].join(":"), - ) as { + const nodeFs = runtimeRequire(["node", "fs"].join(":")) as { realpathSync: (p: string) => string; }; return nodeFs.realpathSync(resolved); @@ -1784,9 +1777,8 @@ function resolveSecureExecEntryPath(): string { if (resolved) return resolved; } - const pnpmResolved = resolvePnpmVirtualStorePackageEntry( - packageSpecifier, - ); + const pnpmResolved = + resolvePnpmVirtualStorePackageEntry(packageSpecifier); if (pnpmResolved) { return pnpmResolved; } diff --git a/rivetkit-typescript/packages/rivetkit/src/engine-client/actor-websocket-client.ts b/rivetkit-typescript/packages/rivetkit/src/engine-client/actor-websocket-client.ts index acd7d4e859..de34103e26 100644 --- a/rivetkit-typescript/packages/rivetkit/src/engine-client/actor-websocket-client.ts +++ b/rivetkit-typescript/packages/rivetkit/src/engine-client/actor-websocket-client.ts @@ -245,7 +245,11 @@ export function buildActorQueryGatewayUrl( } params.append("rvt-runner", runnerName); pushKeyQueryParams(params, query.getOrCreateForKey.key); - pushInputQueryParam(params, query.getOrCreateForKey.input, maxInputSize); + pushInputQueryParam( + params, + query.getOrCreateForKey.input, + maxInputSize, + ); if (query.getOrCreateForKey.region !== undefined) { params.append("rvt-region", query.getOrCreateForKey.region); } @@ -278,10 +282,7 @@ export function buildActorQueryGatewayUrl( return combineUrlPath(endpoint, gatewayPath); } -function pushKeyQueryParams( - params: URLSearchParams, - key: string[], -): void { +function pushKeyQueryParams(params: URLSearchParams, key: string[]): void { if (key.length > 0) { params.append("rvt-key", key.join(",")); } diff --git a/rivetkit-typescript/packages/rivetkit/src/engine-client/driver.ts b/rivetkit-typescript/packages/rivetkit/src/engine-client/driver.ts index a019d69af7..e2ed179dae 100644 --- a/rivetkit-typescript/packages/rivetkit/src/engine-client/driver.ts +++ b/rivetkit-typescript/packages/rivetkit/src/engine-client/driver.ts @@ -9,9 +9,7 @@ export type GatewayTarget = { directId: string } | ActorQuery; export interface EngineControlClient { getForId(input: GetForIdInput): Promise; getWithKey(input: GetWithKeyInput): Promise; - getOrCreateWithKey( - input: GetOrCreateWithKeyInput, - ): Promise; + getOrCreateWithKey(input: GetOrCreateWithKeyInput): Promise; createActor(input: CreateInput): Promise; listActors(input: ListActorsInput): Promise; diff --git a/rivetkit-typescript/packages/rivetkit/src/engine-client/mod.ts b/rivetkit-typescript/packages/rivetkit/src/engine-client/mod.ts index 5e9f5d2c1c..c2b9a1ac30 100644 --- a/rivetkit-typescript/packages/rivetkit/src/engine-client/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/engine-client/mod.ts @@ -42,7 +42,6 @@ import { logger } from "./log"; import { lookupMetadataCached } from "./metadata"; import { createWebSocketProxy } from "./ws-proxy"; - export class RemoteEngineControlClient implements EngineControlClient { #config: ClientConfig; #metadataPromise: Promise | undefined; @@ -163,13 +162,7 @@ export class RemoteEngineControlClient implements EngineControlClient { ): Promise { await this.#metadataPromise; - const { - name, - key, - input: actorInput, - region, - crashPolicy, - } = input; + const { name, key, input: actorInput, region, crashPolicy } = input; logger().info({ msg: "getOrCreateWithKey: getting or creating actor via engine api", @@ -274,7 +267,12 @@ export class RemoteEngineControlClient implements EngineControlClient { const gatewayUrl = this.#buildGatewayUrlForTarget(target, path); - return openWebSocketToGateway(this.#config, gatewayUrl, encoding, params); + return openWebSocketToGateway( + this.#config, + gatewayUrl, + encoding, + params, + ); } async buildGatewayUrl(target: GatewayTarget): Promise { @@ -364,13 +362,8 @@ export class RemoteEngineControlClient implements EngineControlClient { throw new Error("kvBatchPut not supported on remote engine client"); } - async kvBatchDelete( - _actorId: string, - _keys: Uint8Array[], - ): Promise { - throw new Error( - "kvBatchDelete not supported on remote engine client", - ); + async kvBatchDelete(_actorId: string, _keys: Uint8Array[]): Promise { + throw new Error("kvBatchDelete not supported on remote engine client"); } async kvDeleteRange( @@ -378,9 +371,7 @@ export class RemoteEngineControlClient implements EngineControlClient { _start: Uint8Array, _end: Uint8Array, ): Promise { - throw new Error( - "kvDeleteRange not supported on remote engine client", - ); + throw new Error("kvDeleteRange not supported on remote engine client"); } displayInformation(): RuntimeDisplayInformation { @@ -395,7 +386,12 @@ export class RemoteEngineControlClient implements EngineControlClient { const endpoint = getEndpoint(this.#config); if ("directId" in target) { - return buildActorGatewayUrl(endpoint, target.directId, this.#config.token, path); + return buildActorGatewayUrl( + endpoint, + target.directId, + this.#config.token, + path, + ); } if ("getForId" in target) { @@ -416,7 +412,9 @@ export class RemoteEngineControlClient implements EngineControlClient { path, this.#config.maxInputSize, undefined, - "getOrCreateForKey" in target ? this.#config.poolName : undefined, + "getOrCreateForKey" in target + ? this.#config.poolName + : undefined, ); } @@ -428,7 +426,6 @@ export class RemoteEngineControlClient implements EngineControlClient { throw new Error("unreachable: unknown gateway target type"); } - } function requestPath(req: Request): string { diff --git a/rivetkit-typescript/packages/rivetkit/src/engine-process/mod.ts b/rivetkit-typescript/packages/rivetkit/src/engine-process/mod.ts index 6a24b918e4..f25e49e24a 100644 --- a/rivetkit-typescript/packages/rivetkit/src/engine-process/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/engine-process/mod.ts @@ -91,7 +91,7 @@ export async function ensureEngineProcess( const childProcess = getNodeChildProcess(); const child = childProcess.spawn(binaryPath, ["start"], { - cwd: path.dirname(binaryPath), + cwd: storageRoot, stdio: ["inherit", "pipe", "pipe"], env: { ...process.env, @@ -148,7 +148,7 @@ export async function ensureEngineProcess( logger().debug({ msg: "spawned engine process", pid: child.pid, - cwd: path.dirname(binaryPath), + cwd: storageRoot, }); child.once("exit", (code, signal) => { @@ -337,7 +337,9 @@ async function waitForEngineHealth(): Promise { attempt: i + 1, maxRetries, }); - await new Promise((resolve) => setTimeout(resolve, HEALTH_INTERVAL)); + await new Promise((resolve) => + setTimeout(resolve, HEALTH_INTERVAL), + ); } } diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/actor-inspector.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/actor-inspector.ts index 2842090119..fd057cd406 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/actor-inspector.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/actor-inspector.ts @@ -145,7 +145,11 @@ export class ActorInspector { )) as { count: number }[]; tableInfos.push({ - table: { schema: "main", name: table.name, type: table.type }, + table: { + schema: "main", + name: table.name, + type: table.type, + }, columns: columns.map((column) => ({ cid: column.cid, name: column.name, diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts index 1b78fcd453..db55cc3390 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts @@ -22,11 +22,11 @@ export const secureInspector = (config: RegistryConfig) => export function getInspectorUrl( config: RegistryConfig, - managerPort: number, + httpPort: number, ): string | undefined { if (!config.inspector.enabled) return undefined; const base = - config.inspector.defaultEndpoint ?? `http://127.0.0.1:${managerPort}`; + config.inspector.defaultEndpoint ?? `http://127.0.0.1:${httpPort}`; return new URL("/ui/", base).href; } diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts b/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts index 49ffe5f6be..6f64b5752b 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts @@ -1,6 +1,9 @@ import { z } from "zod"; import { getRunMetadata } from "@/actor/config"; -import type { BaseActorDefinition, AnyActorDefinition } from "@/actor/definition"; +import type { + BaseActorDefinition, + AnyActorDefinition, +} from "@/actor/definition"; import { KEYS, queueMetadataKey, @@ -9,21 +12,25 @@ import { import { type Logger, LogLevelSchema } from "@/common/log"; import { ENGINE_ENDPOINT } from "@/engine-process/constants"; import { InspectorConfigSchema } from "@/inspector/config"; -import { DeepReadonly } from "@/utils"; +import { DeepReadonly, VERSION } from "@/utils"; import { tryParseEndpoint } from "@/utils/endpoint-parser"; import { getRivetEndpoint, getRivetEngine, getRivetNamespace, + getRivetRunEngine, + getRivetRunEngineVersion, getRivetToken, isDev, } from "@/utils/env-vars"; import { EnvoyConfigSchema } from "./envoy"; -import { ServerlessConfigSchema } from "./serverless"; +import { ConfigurePoolSchema, ServerlessConfigSchema } from "./serverless"; export const ActorsSchema = z.record( z.string(), - z.custom>(), + z.custom< + BaseActorDefinition + >(), ); export type RegistryActors = z.infer; @@ -106,12 +113,7 @@ export const RegistryConfigSchema = z // TODO: // client: ClientConfigSchema.optional(), - // MARK: Manager - /** - * Whether to start the local RivetKit server. - * Auto-determined based on endpoint and NODE_ENV if not specified. - */ - serveManager: z.boolean().optional(), + // MARK: Local HTTP /** * Directory to serve static files from. * @@ -119,7 +121,7 @@ export const RegistryConfigSchema = z * directory. This is used by `registry.start()` to serve a frontend * alongside the actor API. */ - publicDir: z.string().optional(), + staticDir: z.string().optional(), /** * @experimental * @@ -127,23 +129,42 @@ export const RegistryConfigSchema = z * For example, if the base path is `/foo`, then the route `/actors` * will be available at `/foo/actors`. */ - managerBasePath: z.string().optional().default("/"), + httpBasePath: z.string().optional().default("/"), /** * @experimental * - * What port to run the manager on. + * What port to run the local HTTP server on. */ - managerPort: z.number().optional().default(6420), + httpPort: z.number().optional().default(6421), /** * @experimental * - * What host to bind the local RivetKit server to. + * What host to bind the local HTTP server to. */ - managerHost: z.string().optional(), + httpHost: z.string().optional(), /** @experimental */ inspector: InspectorConfigSchema, + // MARK: Engine + /** + * @experimental + * + * Starts the full Rust engine process locally. + */ + startEngine: z.boolean().default(() => getRivetRunEngine()), + /** @experimental */ + engineVersion: z + .string() + .optional() + .default(() => getRivetRunEngineVersion() ?? VERSION), + /** + * @experimental + * + * Automatically configure serverless envoys in the engine. + */ + configurePool: ConfigurePoolSchema.optional(), + // MARK: Runtime-specific serverless: ServerlessConfigSchema.optional().default(() => ServerlessConfigSchema.parse({}), @@ -158,44 +179,33 @@ export const RegistryConfigSchema = z // Parse endpoint string (env var fallback is applied via transform above) const parsedEndpoint = config.endpoint ? tryParseEndpoint(ctx, { - endpoint: config.endpoint, - path: ["endpoint"], - namespace: config.namespace, - token: config.token, - }) + endpoint: config.endpoint, + path: ["endpoint"], + namespace: config.namespace, + token: config.token, + }) : undefined; - if (parsedEndpoint && config.serveManager) { - ctx.addIssue({ - code: "custom", - message: "cannot specify both endpoint and serveManager", - }); - } - - // Can't spawn engine AND connect to remote endpoint - if (config.serverless.spawnEngine && parsedEndpoint) { + // Can't start a local engine and connect to a remote endpoint. + if (config.startEngine && parsedEndpoint) { ctx.addIssue({ code: "custom", - message: "cannot specify both spawnEngine and endpoint", + message: "cannot specify both startEngine and endpoint", }); } - // configurePool requires an engine (via endpoint or spawnEngine) - if ( - config.serverless.configurePool && - !parsedEndpoint && - !config.serverless.spawnEngine - ) { + // configurePool requires an engine (via endpoint or startEngine). + if (config.configurePool && !parsedEndpoint && !config.startEngine) { ctx.addIssue({ code: "custom", message: - "configurePool requires either endpoint or spawnEngine", + "configurePool requires either endpoint or startEngine", }); } // Flatten the endpoint and apply defaults for namespace/token - // If spawnEngine is enabled, set endpoint to the engine endpoint - const endpoint = config.serverless.spawnEngine + // If startEngine is enabled, set endpoint to the engine endpoint. + const endpoint = config.startEngine ? ENGINE_ENDPOINT : parsedEndpoint?.endpoint; // Namespace priority: parsed from endpoint URL > config value (includes env var) > "default" @@ -207,9 +217,9 @@ export const RegistryConfigSchema = z // Parse publicEndpoint string (env var fallback is applied via transform in serverless schema) const parsedPublicEndpoint = config.serverless.publicEndpoint ? tryParseEndpoint(ctx, { - endpoint: config.serverless.publicEndpoint, - path: ["serverless", "publicEndpoint"], - }) + endpoint: config.serverless.publicEndpoint, + path: ["serverless", "publicEndpoint"], + }) : undefined; // Validate that publicEndpoint namespace matches backend namespace if specified @@ -224,40 +234,25 @@ export const RegistryConfigSchema = z }); } - // Determine serveManager: default to true in dev mode without endpoint, false otherwise - const serveManager = config.serveManager ?? (isDevEnv && !endpoint); - - // In dev mode, fall back to 127.0.0.1 if serving manager + // In dev mode, clients connect directly to the local Rivet Engine. const publicEndpoint = parsedPublicEndpoint?.endpoint ?? - (isDevEnv && (serveManager || config.serverless.spawnEngine) - ? `http://127.0.0.1:${config.managerPort}` - : undefined); + (isDevEnv && config.startEngine ? ENGINE_ENDPOINT : undefined); // We extract publicNamespace to validate that it matches the backend // namespace (see validation above), not for functional use. const publicNamespace = parsedPublicEndpoint?.namespace; const publicToken = parsedPublicEndpoint?.token ?? config.serverless.publicToken; - // If endpoint is set or spawning engine, we'll use engine driver - disable manager inspector - const willUseEngine = !!endpoint || config.serverless.spawnEngine; - const inspector = willUseEngine - ? { - ...config.inspector, - enabled: { manager: false, actor: true }, - } - : config.inspector; - + // If endpoint is set or starting the engine, we'll use the engine driver. return { ...config, endpoint, namespace, token, - serveManager, publicEndpoint, publicNamespace, publicToken, - inspector, serverless: { ...config.serverless, publicEndpoint, @@ -336,7 +331,7 @@ export const DocInspectorConfigSchema = z .optional() .describe("Inspector configuration for debugging and development."); -export const DocConfigureRunnerPoolSchema = z +export const DocConfigurePoolSchema = z .object({ name: z.string().optional().describe("Name of the runner pool."), url: z @@ -348,26 +343,10 @@ export const DocConfigureRunnerPoolSchema = z .describe( "Headers to include in requests to the serverless platform.", ), - maxRunners: z - .number() - .optional() - .describe("Maximum number of runners in the pool."), - minRunners: z - .number() - .optional() - .describe("Minimum number of runners to keep warm."), requestLifespan: z .number() .optional() - .describe("Maximum lifespan of a request in milliseconds."), - runnersMargin: z - .number() - .optional() - .describe("Buffer margin for scaling runners."), - slotsPerRunner: z - .number() - .optional() - .describe("Number of actor slots per runner."), + .describe("Maximum lifespan of a request in seconds."), metadata: z .record(z.string(), z.unknown()) .optional() @@ -380,26 +359,17 @@ export const DocConfigureRunnerPoolSchema = z .describe( "Interval in milliseconds between metadata polls from the engine. Defaults to 10000 milliseconds (10 seconds).", ), + drainOnVersionUpgrade: z + .boolean() + .optional() + .describe( + "Drain runners when a new version is deployed. Defaults to true.", + ), }) .optional(); export const DocServerlessConfigSchema = z .object({ - spawnEngine: z - .boolean() - .optional() - .describe( - "Downloads and starts the full Rust engine process. Auto-enabled in development mode when no endpoint is provided. Default: false", - ), - engineVersion: z - .string() - .optional() - .describe( - "Version of the engine to download. Defaults to the current RivetKit version.", - ), - configureRunnerPool: DocConfigureRunnerPoolSchema.describe( - "Automatically configure serverless runners in the engine.", - ), basePath: z .string() .optional() @@ -497,27 +467,40 @@ export const DocRegistryConfigSchema = z .describe( "Additional headers to include in requests to Rivet Engine.", ), - serveManager: z - .boolean() - .optional() - .describe( - "Whether to start the local RivetKit server. Auto-determined based on endpoint and NODE_ENV if not specified.", - ), - publicDir: z + staticDir: z .string() .optional() .describe( - "Directory to serve static files from. When set, the local RivetKit server serves static files alongside the actor API. Used by registry.start().", + "Directory to serve static files from. When set, registry.start() serves static files alongside the actor API.", ), - managerBasePath: z + httpBasePath: z .string() .optional() .describe("Base path for the local RivetKit API. Default: '/'"), - managerPort: z + httpPort: z .number() .optional() - .describe("Port to run the manager on. Default: 6420"), + .describe("Port to run the local HTTP server on. Default: 6421"), + httpHost: z + .string() + .optional() + .describe("Host to bind the local HTTP server to."), inspector: DocInspectorConfigSchema, + startEngine: z + .boolean() + .optional() + .describe( + "Starts the full Rust engine process locally. Default: false", + ), + engineVersion: z + .string() + .optional() + .describe( + "Version of the local engine package to use. Defaults to the current RivetKit version.", + ), + configurePool: DocConfigurePoolSchema.describe( + "Automatically configure serverless runners in the engine.", + ), serverless: DocServerlessConfigSchema.optional(), envoy: DocEnvoyConfigSchema.optional(), }) diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/config/serverless.ts b/rivetkit-typescript/packages/rivetkit/src/registry/config/serverless.ts index e02e36d8c4..9af9be42de 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/config/serverless.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/config/serverless.ts @@ -1,11 +1,5 @@ import { z } from "zod/v4"; -import { VERSION } from "@/utils"; -import { - getRivetRunEngineVersion, - getRivetRunEngine, - getRivetPublicEndpoint, - getRivetPublicToken, -} from "@/utils/env-vars"; +import { getRivetPublicEndpoint, getRivetPublicToken } from "@/utils/env-vars"; export const ConfigurePoolSchema = z .object({ @@ -13,46 +7,14 @@ export const ConfigurePoolSchema = z url: z.string(), headers: z.record(z.string(), z.string()).optional(), requestLifespan: z.number().optional(), - maxConcurrentActors: z.number().optional(), metadata: z.record(z.string(), z.unknown()).optional(), metadataPollInterval: z.number().optional(), drainOnVersionUpgrade: z.boolean().optional(), - - // Deprecated - maxRunners: z.number().optional(), - minRunners: z.number().optional(), - runnersMargin: z.number().optional(), - slotsPerRunner: z.number().optional(), }) .optional(); export const ServerlessConfigSchema = z.object({ - // MARK: Run Engine - /** - * @experimental - * - * Downloads and starts the full Rust engine process. - * Auto-enabled in development mode when no endpoint is provided. - */ - spawnEngine: z.boolean().default(() => getRivetRunEngine()), - - /** @experimental */ - engineVersion: z - .string() - .optional() - .default(() => getRivetRunEngineVersion() ?? VERSION), - - /** - * @experimental - * - * Automatically configure serverless envoys in the engine. - * Can only be used when envoyKind is "serverless". - * If true, uses default configuration. Can also provide custom configuration. - */ - configurePool: ConfigurePoolSchema.optional(), - // MARK: Routing - // TODO: serverlessBasePath? better naming? basePath: z.string().optional().default("/api/rivet"), // MARK: Public Endpoint Configuration diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/index.ts b/rivetkit-typescript/packages/rivetkit/src/registry/index.ts index 8528f32e0c..23dd0ee370 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/index.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/index.ts @@ -1,4 +1,5 @@ import { Runtime } from "../../runtime"; +import { ENGINE_ENDPOINT } from "@/engine-process/constants"; import { type RegistryActors, type RegistryConfig, @@ -33,17 +34,14 @@ export class Registry { constructor(config: RegistryConfigInput) { this.#config = config; - // Start the local runtime or engine before /api/rivet is hit so clients can - // reach the public endpoint preemptively. This waits one tick because some + // Start the local engine before /api/rivet is hit so clients can + // reach the endpoint preemptively. This waits one tick because some // integrations mutate registry config immediately after setup() returns. - if (config.serverless?.spawnEngine || config.serveManager) { + if (config.startEngine) { setTimeout(() => { const parsedConfig = this.parseConfig(); - if ( - parsedConfig.serverless.spawnEngine || - parsedConfig.serveManager - ) { + if (parsedConfig.startEngine) { // biome-ignore lint/nursery/noFloatingPromises: fire-and-forget auto-prepare this.#ensureRuntime(); } @@ -103,7 +101,7 @@ export class Registry { * Starts the server, serving both the actor API and static files. * * This is the simplest way to run RivetKit. It starts a local runtime - * server, serves static files from the configured `publicDir` (default + * server, serves static files from the configured `staticDir` (default * `"public"`), and starts the actor envoy. * * When an endpoint is configured (via config or RIVET_ENDPOINT env var), @@ -116,30 +114,22 @@ export class Registry { * ``` */ public start() { - // Default publicDir to "public" if not explicitly set - if (this.#config.publicDir === undefined) { - this.#config.publicDir = "public"; + // Default staticDir to "public" if not explicitly set. + if (this.#config.staticDir === undefined) { + this.#config.staticDir = "public"; } - // Force serveManager when there's no remote endpoint so the - // local runtime starts and serves the API + static files. - // When an endpoint IS configured, the config transform handles - // the mode (serveManager defaults to false, spawnEngine may be - // true, etc.) and we just start the envoy. - if (this.#config.serveManager === undefined) { - const hasEndpoint = !!( - this.#config.endpoint || - (typeof process !== "undefined" && - (process.env.RIVET_ENGINE || process.env.RIVET_ENDPOINT)) - ); - const willSpawnEngine = !!this.#config.serverless?.spawnEngine; - if (!hasEndpoint && !willSpawnEngine) { - this.#config.serveManager = true; - } + if (this.#config.serverless === undefined) { + this.#config.serverless = {}; + } + if (this.#config.serverless.publicEndpoint === undefined) { + this.#config.serverless.publicEndpoint = ENGINE_ENDPOINT; } - // biome-ignore lint/nursery/noFloatingPromises: fire-and-forget - this.#ensureRuntime().then((runtime) => runtime.startEnvoy()); + this.#ensureRuntime().then(async (runtime) => { + await runtime.ensureHttpServer(); + await runtime.startEnvoy(); + }); } } diff --git a/rivetkit-typescript/packages/rivetkit/src/runtime-router/router.ts b/rivetkit-typescript/packages/rivetkit/src/runtime-router/router.ts index 65f93cc9a5..508a9f753d 100644 --- a/rivetkit-typescript/packages/rivetkit/src/runtime-router/router.ts +++ b/rivetkit-typescript/packages/rivetkit/src/runtime-router/router.ts @@ -55,7 +55,7 @@ export function buildRuntimeRouter( getUpgradeWebSocket: GetUpgradeWebSocket | undefined, runtime: Runtime = "node", ) { - return createRouter(config.managerBasePath, (router) => { + return createRouter(config.httpBasePath, (router) => { // Actor gateway router.use( "*", @@ -94,9 +94,9 @@ export function buildRuntimeRouter( const actorIdsParsed = actor_ids ? actor_ids - .split(",") - .map((id) => id.trim()) - .filter((id) => id.length > 0) + .split(",") + .map((id) => id.trim()) + .filter((id) => id.length > 0) : undefined; const actors: ActorOutput[] = []; @@ -153,12 +153,13 @@ export function buildRuntimeRouter( // If no name is provided, try all registered actor types // Actor IDs are globally unique, so we'll find it in one of them for (const actorName of Object.keys(config.use)) { - const actorOutput = - await engineClient.getForId({ + const actorOutput = await engineClient.getForId( + { c, name: actorName, actorId, - }); + }, + ); if (actorOutput) { actors.push(actorOutput); break; // Found the actor, no need to check other names @@ -425,7 +426,6 @@ export function buildRuntimeRouter( return c.text(`Error: ${error}`, 500); } }); - } if (config.inspector.enabled) { diff --git a/rivetkit-typescript/packages/rivetkit/src/serverless/configure.ts b/rivetkit-typescript/packages/rivetkit/src/serverless/configure.ts index 3c34ec82f0..6a11e34971 100644 --- a/rivetkit-typescript/packages/rivetkit/src/serverless/configure.ts +++ b/rivetkit-typescript/packages/rivetkit/src/serverless/configure.ts @@ -26,7 +26,7 @@ export async function configureServerlessPool( } // Prepare the configuration - const customConfig = config.serverless.configurePool; + const customConfig = config.configurePool; invariant(customConfig, "configurePool should exist"); const clientConfig = convertRegistryConfigToClientConfig(config); @@ -50,19 +50,18 @@ export async function configureServerlessPool( url: customConfig.url, headers: customConfig.headers ?? {}, request_lifespan: customConfig.requestLifespan ?? 15 * 60, - max_concurrent_actors: customConfig.maxConcurrentActors ?? 100_000, metadata_poll_interval: customConfig.metadataPollInterval ?? 1000, - max_runners: customConfig.maxRunners ?? 100_000, - min_runners: customConfig.minRunners ?? 0, - runners_margin: customConfig.runnersMargin ?? 0, - slots_per_runner: customConfig.slotsPerRunner ?? 1, + // Deprecated engine fields with hardcoded defaults. + max_runners: 100_000, + min_runners: 0, + runners_margin: 0, + slots_per_runner: 1, }, metadata: customConfig.metadata ?? {}, drain_on_version_upgrade: customConfig.drainOnVersionUpgrade ?? true, - metadataPollInterval: customConfig.metadataPollInterval ?? 1000, }; await updateRunnerConfig(clientConfig, poolName, { datacenters: Object.fromEntries( diff --git a/rivetkit-typescript/packages/rivetkit/src/serverless/router.ts b/rivetkit-typescript/packages/rivetkit/src/serverless/router.ts index bb66730b79..0a1c17b9a5 100644 --- a/rivetkit-typescript/packages/rivetkit/src/serverless/router.ts +++ b/rivetkit-typescript/packages/rivetkit/src/serverless/router.ts @@ -35,11 +35,10 @@ export function buildServerlessRouter(config: RegistryConfig) { if (!parseResult.success) { throw new InvalidRequest( parseResult.error.issues[0]?.message ?? - "invalid serverless start headers", + "invalid serverless start headers", ); } - const { endpoint, token, poolName, namespace } = - parseResult.data; + const { endpoint, token, poolName, namespace } = parseResult.data; logger().debug({ msg: "received serverless runner start request", diff --git a/rivetkit-typescript/packages/rivetkit/src/test/mod.ts b/rivetkit-typescript/packages/rivetkit/src/test/mod.ts index 18b79a0698..1f7115ac37 100644 --- a/rivetkit-typescript/packages/rivetkit/src/test/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/test/mod.ts @@ -14,8 +14,7 @@ export async function setupTest>( registry: A, ): Promise> { registry.config.test = { ...registry.config.test, enabled: true }; - registry.config.serveManager = true; - registry.config.managerPort = 10_000 + Math.floor(Math.random() * 40_000); + registry.config.httpPort = 10_000 + Math.floor(Math.random() * 40_000); registry.config.inspector = { enabled: true, token: () => "token", @@ -25,8 +24,10 @@ export async function setupTest>( await runtime.startEnvoy(); await new Promise((resolve) => setTimeout(resolve, 250)); - invariant(runtime.managerPort, "missing runtime manager port"); - const endpoint = `http://127.0.0.1:${runtime.managerPort}`; + await runtime.ensureHttpServer(); + + invariant(runtime.httpPort, "missing runtime HTTP port"); + const endpoint = `http://127.0.0.1:${runtime.httpPort}`; const client = createClient({ endpoint, diff --git a/rivetkit-typescript/packages/rivetkit/src/utils/serve.ts b/rivetkit-typescript/packages/rivetkit/src/utils/serve.ts index 31bc22f76e..a97f14d331 100644 --- a/rivetkit-typescript/packages/rivetkit/src/utils/serve.ts +++ b/rivetkit-typescript/packages/rivetkit/src/utils/serve.ts @@ -6,7 +6,7 @@ import { logger } from "@/registry/log"; // TODO: Go back to dynamic import for this import getPort from "get-port"; -const DEFAULT_PORT = 6420; +const DEFAULT_PORT = 6421; export type ServeStatic = typeof import("@hono/node-server/serve-static").serveStatic; const serveStaticLoaderPromises: Partial< @@ -37,7 +37,7 @@ export async function findFreePort( export async function crossPlatformServe( config: RegistryConfig, - managerPort: number, + httpPort: number, app: Hono, runtime: Runtime = detectRuntime(), ): Promise<{ upgradeWebSocket: any; closeServer?: () => void }> { @@ -45,13 +45,13 @@ export async function crossPlatformServe( switch (runtime) { case "deno": - return serveDeno(config, managerPort, app); + return serveDeno(config, httpPort, app); case "bun": - return serveBun(config, managerPort, app); + return serveBun(config, httpPort, app); case "node": - return serveNode(config, managerPort, app); + return serveNode(config, httpPort, app); default: - return serveNode(config, managerPort, app); + return serveNode(config, httpPort, app); } } @@ -87,7 +87,7 @@ export async function loadRuntimeServeStatic( async function serveNode( config: RegistryConfig, - managerPort: number, + httpPort: number, app: Hono, ): Promise<{ upgradeWebSocket: any; closeServer: () => void }> { // Import @hono/node-server using string variable to prevent static analysis @@ -130,8 +130,8 @@ async function serveNode( }); // Start server - const port = managerPort; - const hostname = config.managerHost; + const port = httpPort; + const hostname = config.httpHost; const server = serve({ fetch: app.fetch, port, hostname }, () => logger().info({ msg: "server listening", port, hostname }), ); @@ -146,7 +146,7 @@ async function serveNode( async function serveDeno( config: RegistryConfig, - managerPort: number, + httpPort: number, app: Hono, ): Promise<{ upgradeWebSocket: any }> { // Import hono/deno using string variable to prevent static analysis @@ -166,8 +166,8 @@ async function serveDeno( process.exit(1); } - const port = config.managerPort; - const hostname = config.managerHost; + const port = httpPort; + const hostname = config.httpHost; // Use Deno.serve Deno.serve({ port, hostname }, app.fetch); @@ -178,7 +178,7 @@ async function serveDeno( async function serveBun( config: RegistryConfig, - managerPort: number, + httpPort: number, app: Hono, ): Promise<{ upgradeWebSocket: any }> { // Import hono/bun using string variable to prevent static analysis @@ -200,8 +200,8 @@ async function serveBun( const { websocket, upgradeWebSocket } = createBunWebSocket(); - const port = config.managerPort; - const hostname = config.managerHost; + const port = httpPort; + const hostname = config.httpHost; // Use Bun.serve // @ts-expect-error - Bun global diff --git a/rivetkit-typescript/packages/rivetkit/src/workflow/context.ts b/rivetkit-typescript/packages/rivetkit/src/workflow/context.ts index e98847d38a..b5f53c1e53 100644 --- a/rivetkit-typescript/packages/rivetkit/src/workflow/context.ts +++ b/rivetkit-typescript/packages/rivetkit/src/workflow/context.ts @@ -1,7 +1,10 @@ import type { RunContext } from "@/actor/contexts/run"; import type { Client } from "@/client/client"; import type { Registry } from "@/registry"; -import type { BaseActorDefinition, AnyActorDefinition } from "@/actor/definition"; +import type { + BaseActorDefinition, + AnyActorDefinition, +} from "@/actor/definition"; import type { AnyDatabaseProvider, InferDatabaseClient, diff --git a/rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts b/rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts index 73c4db124d..7d2346d622 100644 --- a/rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts +++ b/rivetkit-typescript/packages/rivetkit/src/workflow/driver.ts @@ -1,5 +1,8 @@ import type { RunContext } from "@/actor/contexts/run"; -import type { AnyActorInstance, AnyStaticActorInstance } from "@/actor/instance/mod"; +import type { + AnyActorInstance, + AnyStaticActorInstance, +} from "@/actor/instance/mod"; import { makeWorkflowKey, workflowStoragePrefix } from "@/actor/instance/keys"; import type { EngineDriver, diff --git a/rivetkit-typescript/packages/rivetkit/src/workflow/mod.ts b/rivetkit-typescript/packages/rivetkit/src/workflow/mod.ts index a73757344b..1421658c8b 100644 --- a/rivetkit-typescript/packages/rivetkit/src/workflow/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/workflow/mod.ts @@ -1,7 +1,10 @@ import { ACTOR_CONTEXT_INTERNAL_SYMBOL } from "@/actor/contexts/base/actor"; import type { RunContext } from "@/actor/contexts/run"; import type { AnyDatabaseProvider } from "@/actor/database"; -import type { AnyActorInstance, AnyStaticActorInstance } from "@/actor/instance/mod"; +import type { + AnyActorInstance, + AnyStaticActorInstance, +} from "@/actor/instance/mod"; import type { EventSchemaConfig, QueueSchemaConfig } from "@/actor/schema"; import { RUN_FUNCTION_CONFIG_SYMBOL } from "@/actor/config"; import { stringifyError } from "@/utils"; diff --git a/rivetkit-typescript/packages/rivetkit/tests/actor-resolution.test.ts b/rivetkit-typescript/packages/rivetkit/tests/actor-resolution.test.ts index f67829d97d..45719bffb3 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/actor-resolution.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/actor-resolution.test.ts @@ -260,7 +260,10 @@ describe("actor resolution flow", () => { await vi.waitFor(() => { expect(conn.connStatus).toBe("connected"); }); - expect(sendTargets).toEqual([expectedDirectTarget, expectedDirectTarget]); + expect(sendTargets).toEqual([ + expectedDirectTarget, + expectedDirectTarget, + ]); expect(gatewayTargets).toEqual([expectedDirectTarget]); expect(webSocketCalls).toHaveLength(2); expect(webSocketCalls[0]?.target).toEqual(expectedDirectTarget); @@ -293,7 +296,9 @@ describe("actor resolution flow", () => { }); }); -function createMockDriver(overrides: Partial): EngineControlClient { +function createMockDriver( + overrides: Partial, +): EngineControlClient { return { getForId: async () => undefined, getWithKey: async () => undefined, diff --git a/rivetkit-typescript/packages/rivetkit/tests/agent-os-session-lifecycle.test.ts b/rivetkit-typescript/packages/rivetkit/tests/agent-os-session-lifecycle.test.ts index aa968b0782..34cf9b8df2 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/agent-os-session-lifecycle.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/agent-os-session-lifecycle.test.ts @@ -14,7 +14,10 @@ describe("agentOS session lifecycle", () => { beforeAll(async () => { mock = new LLMock({ port: 0, logLevel: "silent" }); mock.addFixtures([ - { match: { predicate: () => true }, response: { content: "Hello from mock LLM" } }, + { + match: { predicate: () => true }, + response: { content: "Hello from mock LLM" }, + }, ]); mockUrl = await mock.start(); mockPort = Number(new URL(mockUrl).port); @@ -36,7 +39,9 @@ describe("agentOS session lifecycle", () => { test("writeFile, readFile, exec", async (c) => { const { client } = await setupTest(c, createRegistry()); - const actor = (client as any).vm.getOrCreate([`basic-${crypto.randomUUID()}`]); + const actor = (client as any).vm.getOrCreate([ + `basic-${crypto.randomUUID()}`, + ]); await actor.writeFile("/tmp/test.txt", "hello"); const data = await actor.readFile("/tmp/test.txt"); @@ -49,7 +54,9 @@ describe("agentOS session lifecycle", () => { test("create session, send prompt, close session", async (c) => { const { client } = await setupTest(c, createRegistry()); - const actor = (client as any).vm.getOrCreate([`session-${crypto.randomUUID()}`]); + const actor = (client as any).vm.getOrCreate([ + `session-${crypto.randomUUID()}`, + ]); const session = await actor.createSession("pi", { env: { diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver-engine-dynamic.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver-engine-dynamic.test.ts index b64c7bc842..3c5acff189 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/driver-engine-dynamic.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/driver-engine-dynamic.test.ts @@ -1,4 +1,8 @@ -import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; +import { + createServer, + type IncomingMessage, + type ServerResponse, +} from "node:http"; import { existsSync } from "node:fs"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; @@ -20,8 +24,7 @@ const hasEngineEndpointEnv = !!( process.env.RIVET_NAMESPACE_ENDPOINT || process.env.RIVET_API_ENDPOINT ); -const initialDynamicSourceUrlEnv = - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL; +const initialDynamicSourceUrlEnv = process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL; const initialSecureExecSpecifierEnv = process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER; @@ -36,238 +39,243 @@ type DynamicHandle = { }>; putText: (key: string, value: string) => Promise; getText: (key: string) => Promise; - listText: (prefix: string) => Promise>; + listText: ( + prefix: string, + ) => Promise>; triggerSleep: () => Promise; scheduleAlarm: (duration: number) => Promise; webSocket: (path?: string) => Promise; }; type DynamicAuthHandle = DynamicHandle & { - fetch: (input: string | URL | Request, init?: RequestInit) => Promise; + fetch: ( + input: string | URL | Request, + init?: RequestInit, + ) => Promise; }; describe.skipIf(!hasSecureExecDist || !hasEngineEndpointEnv)( "engine dynamic actor runtime", () => { - let sourceServer: - | { - url: string; - close: () => Promise; - } - | undefined; - - afterEach(async () => { - if (sourceServer) { - await sourceServer.close(); - sourceServer = undefined; - } - if (initialDynamicSourceUrlEnv === undefined) { - delete process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL; - } else { - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = - initialDynamicSourceUrlEnv; - } - if (initialSecureExecSpecifierEnv === undefined) { - delete process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER; - } else { - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = - initialSecureExecSpecifierEnv; - } - }); - - test("loads dynamic actor source from URL", async () => { - sourceServer = await startSourceServer(DYNAMIC_SOURCE); - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = sourceServer.url; - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( - SECURE_EXEC_DIST_PATH, - ).href; - - const runtime = await createDynamicEngineRuntime(); - const client = createClient({ - endpoint: runtime.endpoint, - namespace: runtime.namespace, - poolName: runtime.runnerName, - encoding: "json", - disableMetadataLookup: true, - }); - const bareClient = createClient({ - endpoint: runtime.endpoint, - namespace: runtime.namespace, - poolName: runtime.runnerName, - encoding: "bare", - disableMetadataLookup: true, - }); - - try { - const actor = client.dynamicFromUrl.getOrCreate([ - "url-loader", - ]) as unknown as DynamicHandle; - expect(await actor.increment(2)).toBe(2); - expect(await actor.increment(3)).toBe(5); - expect(await actor.getSourceCodeLength()).toBeGreaterThan(0); - - const bareActor = bareClient.dynamicFromUrl.getOrCreate([ - "url-loader", - ]) as unknown as DynamicHandle; - expect(await bareActor.increment(1)).toBe(6); - - const state = await actor.getState(); - expect(state.count).toBe(6); - expect(state.wakeCount).toBeGreaterThanOrEqual(1); - } finally { - await client.dispose(); - await bareClient.dispose(); - await runtime.cleanup(); - } - }, 180_000); - - test("supports actions, kv, websockets, alarms, and sleep/wake from actor-loaded source", async () => { - sourceServer = await startSourceServer(DYNAMIC_SOURCE); - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = sourceServer.url; - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( - SECURE_EXEC_DIST_PATH, - ).href; - - const runtime = await createDynamicEngineRuntime(); - const client = createClient({ - endpoint: runtime.endpoint, - namespace: runtime.namespace, - poolName: runtime.runnerName, - encoding: "json", - disableMetadataLookup: true, + let sourceServer: + | { + url: string; + close: () => Promise; + } + | undefined; + + afterEach(async () => { + if (sourceServer) { + await sourceServer.close(); + sourceServer = undefined; + } + if (initialDynamicSourceUrlEnv === undefined) { + delete process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL; + } else { + process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = + initialDynamicSourceUrlEnv; + } + if (initialSecureExecSpecifierEnv === undefined) { + delete process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER; + } else { + process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = + initialSecureExecSpecifierEnv; + } }); - let ws: WebSocket | undefined; - - try { - const actor = client.dynamicFromActor.getOrCreate([ - "actor-loader", - ]) as unknown as DynamicHandle; - - expect(await actor.increment(1)).toBe(1); - expect(await actor.getSourceCodeLength()).toBeGreaterThan(0); - - await actor.putText("prefix-a", "alpha"); - await actor.putText("prefix-b", "beta"); - expect(await actor.getText("prefix-a")).toBe("alpha"); - expect( - (await actor.listText("prefix-")).sort((a, b) => - a.key.localeCompare(b.key), - ), - ).toEqual([ - { key: "prefix-a", value: "alpha" }, - { key: "prefix-b", value: "beta" }, - ]); - - ws = await actor.webSocket(); - const welcome = await readWebSocketJson(ws); - expect(welcome).toMatchObject({ type: "welcome" }); - ws.send(JSON.stringify({ type: "ping" })); - expect(await readWebSocketJson(ws)).toEqual({ type: "pong" }); - ws.close(); - ws = undefined; - - const beforeSleep = await actor.getState(); - await actor.triggerSleep(); - await wait(350); - - const afterSleep = await actor.getState(); - expect(afterSleep.sleepCount).toBeGreaterThanOrEqual( - beforeSleep.sleepCount + 1, - ); - expect(afterSleep.wakeCount).toBeGreaterThanOrEqual( - beforeSleep.wakeCount + 1, - ); + test("loads dynamic actor source from URL", async () => { + sourceServer = await startSourceServer(DYNAMIC_SOURCE); + process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = sourceServer.url; + process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( + SECURE_EXEC_DIST_PATH, + ).href; + + const runtime = await createDynamicEngineRuntime(); + const client = createClient({ + endpoint: runtime.endpoint, + namespace: runtime.namespace, + poolName: runtime.runnerName, + encoding: "json", + disableMetadataLookup: true, + }); + const bareClient = createClient({ + endpoint: runtime.endpoint, + namespace: runtime.namespace, + poolName: runtime.runnerName, + encoding: "bare", + disableMetadataLookup: true, + }); - const beforeAlarm = await actor.getState(); - await actor.scheduleAlarm(500); - await wait(900); + try { + const actor = client.dynamicFromUrl.getOrCreate([ + "url-loader", + ]) as unknown as DynamicHandle; + expect(await actor.increment(2)).toBe(2); + expect(await actor.increment(3)).toBe(5); + expect(await actor.getSourceCodeLength()).toBeGreaterThan(0); + + const bareActor = bareClient.dynamicFromUrl.getOrCreate([ + "url-loader", + ]) as unknown as DynamicHandle; + expect(await bareActor.increment(1)).toBe(6); + + const state = await actor.getState(); + expect(state.count).toBe(6); + expect(state.wakeCount).toBeGreaterThanOrEqual(1); + } finally { + await client.dispose(); + await bareClient.dispose(); + await runtime.cleanup(); + } + }, 180_000); + + test("supports actions, kv, websockets, alarms, and sleep/wake from actor-loaded source", async () => { + sourceServer = await startSourceServer(DYNAMIC_SOURCE); + process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = sourceServer.url; + process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( + SECURE_EXEC_DIST_PATH, + ).href; + + const runtime = await createDynamicEngineRuntime(); + const client = createClient({ + endpoint: runtime.endpoint, + namespace: runtime.namespace, + poolName: runtime.runnerName, + encoding: "json", + disableMetadataLookup: true, + }); - const afterAlarm = await actor.getState(); - expect(afterAlarm.alarmCount).toBeGreaterThanOrEqual( - beforeAlarm.alarmCount + 1, - ); - expect(afterAlarm.sleepCount).toBeGreaterThanOrEqual( - beforeAlarm.sleepCount + 1, - ); - expect(afterAlarm.wakeCount).toBeGreaterThanOrEqual( - beforeAlarm.wakeCount + 1, - ); - } finally { - ws?.close(); - await client.dispose(); - await runtime.cleanup(); - } - }, 180_000); - - test("authenticates dynamic actor actions, raw requests, and websockets", async () => { - sourceServer = await startSourceServer(DYNAMIC_SOURCE); - process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = sourceServer.url; - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( - SECURE_EXEC_DIST_PATH, - ).href; - - const runtime = await createDynamicEngineRuntime(); - const client = createClient({ - endpoint: runtime.endpoint, - namespace: runtime.namespace, - poolName: runtime.runnerName, - encoding: "json", - disableMetadataLookup: true, - }); + let ws: WebSocket | undefined; + + try { + const actor = client.dynamicFromActor.getOrCreate([ + "actor-loader", + ]) as unknown as DynamicHandle; + + expect(await actor.increment(1)).toBe(1); + expect(await actor.getSourceCodeLength()).toBeGreaterThan(0); + + await actor.putText("prefix-a", "alpha"); + await actor.putText("prefix-b", "beta"); + expect(await actor.getText("prefix-a")).toBe("alpha"); + expect( + (await actor.listText("prefix-")).sort((a, b) => + a.key.localeCompare(b.key), + ), + ).toEqual([ + { key: "prefix-a", value: "alpha" }, + { key: "prefix-b", value: "beta" }, + ]); + + ws = await actor.webSocket(); + const welcome = await readWebSocketJson(ws); + expect(welcome).toMatchObject({ type: "welcome" }); + ws.send(JSON.stringify({ type: "ping" })); + expect(await readWebSocketJson(ws)).toEqual({ type: "pong" }); + ws.close(); + ws = undefined; + + const beforeSleep = await actor.getState(); + await actor.triggerSleep(); + await wait(350); + + const afterSleep = await actor.getState(); + expect(afterSleep.sleepCount).toBeGreaterThanOrEqual( + beforeSleep.sleepCount + 1, + ); + expect(afterSleep.wakeCount).toBeGreaterThanOrEqual( + beforeSleep.wakeCount + 1, + ); - let ws: WebSocket | undefined; + const beforeAlarm = await actor.getState(); + await actor.scheduleAlarm(500); + await wait(900); - try { - const unauthorized = client.dynamicWithAuth.getOrCreate([ - "auth-unauthorized", - ]) as unknown as DynamicAuthHandle; - await expect(unauthorized.increment(1)).rejects.toMatchObject({ - group: "user", - code: "unauthorized", + const afterAlarm = await actor.getState(); + expect(afterAlarm.alarmCount).toBeGreaterThanOrEqual( + beforeAlarm.alarmCount + 1, + ); + expect(afterAlarm.sleepCount).toBeGreaterThanOrEqual( + beforeAlarm.sleepCount + 1, + ); + expect(afterAlarm.wakeCount).toBeGreaterThanOrEqual( + beforeAlarm.wakeCount + 1, + ); + } finally { + ws?.close(); + await client.dispose(); + await runtime.cleanup(); + } + }, 180_000); + + test("authenticates dynamic actor actions, raw requests, and websockets", async () => { + sourceServer = await startSourceServer(DYNAMIC_SOURCE); + process.env.RIVETKIT_DYNAMIC_TEST_SOURCE_URL = sourceServer.url; + process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( + SECURE_EXEC_DIST_PATH, + ).href; + + const runtime = await createDynamicEngineRuntime(); + const client = createClient({ + endpoint: runtime.endpoint, + namespace: runtime.namespace, + poolName: runtime.runnerName, + encoding: "json", + disableMetadataLookup: true, }); - const unauthorizedResponse = await unauthorized.fetch("/auth"); - expect(unauthorizedResponse.status).toBe(400); - expect(await unauthorizedResponse.json()).toMatchObject({ - group: "user", - code: "unauthorized", - }); + let ws: WebSocket | undefined; - const headerAuthorized = client.dynamicWithAuth.getOrCreate([ - "auth-header", - ]) as unknown as DynamicAuthHandle; - const headerResponse = await headerAuthorized.fetch("/auth", { - headers: { - "x-dynamic-auth": "allow", - }, - }); - expect(headerResponse.status).toBe(200); - expect(await headerResponse.json()).toEqual({ - method: "GET", - token: "allow", - }); + try { + const unauthorized = client.dynamicWithAuth.getOrCreate([ + "auth-unauthorized", + ]) as unknown as DynamicAuthHandle; + await expect(unauthorized.increment(1)).rejects.toMatchObject({ + group: "user", + code: "unauthorized", + }); + + const unauthorizedResponse = await unauthorized.fetch("/auth"); + expect(unauthorizedResponse.status).toBe(400); + expect(await unauthorizedResponse.json()).toMatchObject({ + group: "user", + code: "unauthorized", + }); - const paramsAuthorized = client.dynamicWithAuth.getOrCreate( - ["auth-params"], - { - params: { - token: "allow", + const headerAuthorized = client.dynamicWithAuth.getOrCreate([ + "auth-header", + ]) as unknown as DynamicAuthHandle; + const headerResponse = await headerAuthorized.fetch("/auth", { + headers: { + "x-dynamic-auth": "allow", }, - }, - ) as unknown as DynamicAuthHandle; - expect(await paramsAuthorized.increment(1)).toBe(1); + }); + expect(headerResponse.status).toBe(200); + expect(await headerResponse.json()).toEqual({ + method: "GET", + token: "allow", + }); - ws = await paramsAuthorized.webSocket(); - expect(await readWebSocketJson(ws)).toMatchObject({ - type: "welcome", - }); - } finally { - ws?.close(); - await client.dispose(); - await runtime.cleanup(); - } - }, 180_000); + const paramsAuthorized = client.dynamicWithAuth.getOrCreate( + ["auth-params"], + { + params: { + token: "allow", + }, + }, + ) as unknown as DynamicAuthHandle; + expect(await paramsAuthorized.increment(1)).toBe(1); + + ws = await paramsAuthorized.webSocket(); + expect(await readWebSocketJson(ws)).toMatchObject({ + type: "welcome", + }); + } finally { + ws?.close(); + await client.dispose(); + await runtime.cleanup(); + } + }, 180_000); }, ); @@ -275,7 +283,8 @@ async function createDynamicEngineRuntime() { return await createTestRuntime( join(__dirname, "../fixtures/driver-test-suite/dynamic-registry.ts"), async (registry) => { - const endpoint = process.env.RIVET_ENDPOINT || "http://127.0.0.1:6420"; + const endpoint = + process.env.RIVET_ENDPOINT || "http://127.0.0.1:6420"; const namespaceEndpoint = process.env.RIVET_NAMESPACE_ENDPOINT || process.env.RIVET_API_ENDPOINT || @@ -315,7 +324,9 @@ async function createDynamicEngineRuntime() { convertRegistryConfigToClientConfig(parsedConfig), ); - const runnersUrl = new URL(`${endpoint.replace(/\/$/, "")}/runners`); + const runnersUrl = new URL( + `${endpoint.replace(/\/$/, "")}/runners`, + ); runnersUrl.searchParams.set("namespace", namespace); runnersUrl.searchParams.set("name", runnerName); let probeError: unknown; @@ -326,7 +337,9 @@ async function createDynamicEngineRuntime() { headers: { Authorization: `Bearer ${token}` }, }); if (!runnerResponse.ok) { - const errorBody = await runnerResponse.text().catch(() => ""); + const errorBody = await runnerResponse + .text() + .catch(() => ""); probeError = new Error( `List runners failed: ${runnerResponse.status} ${runnerResponse.statusText} ${errorBody}`, ); @@ -365,7 +378,7 @@ async function createDynamicEngineRuntime() { }, engineClient, cleanup: async () => { - ((engineClient as any).shutdown?.()); + (engineClient as any).shutdown?.(); }, }; }, @@ -389,7 +402,9 @@ async function startSourceServer(source: string): Promise<{ res.end(source); }); - await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + await new Promise((resolve) => + server.listen(0, "127.0.0.1", resolve), + ); const address = server.address(); if (!address || typeof address === "string") { throw new Error("failed to get dynamic source server address"); diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver-engine-ping.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver-engine-ping.test.ts index d34ca2f0e0..6d32f416b7 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/driver-engine-ping.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/driver-engine-ping.test.ts @@ -75,7 +75,10 @@ describe("engine driver smoke test", () => { it("WebSocket echo works", async () => { const { actor_id } = await createActor(); try { - const wsEndpoint = RIVET_ENDPOINT.replace("http://", "ws://").replace("https://", "wss://"); + const wsEndpoint = RIVET_ENDPOINT.replace( + "http://", + "ws://", + ).replace("https://", "wss://"); const ws = new WebSocket(`${wsEndpoint}/ws`, [ "rivet", "rivet_target.actor", @@ -84,7 +87,10 @@ describe("engine driver smoke test", () => { ]); const result = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(new Error("WebSocket timeout")), 10_000); + const timeout = setTimeout( + () => reject(new Error("WebSocket timeout")), + 10_000, + ); ws.addEventListener("open", () => { ws.send("ping"); @@ -98,7 +104,11 @@ describe("engine driver smoke test", () => { ws.addEventListener("error", (e) => { clearTimeout(timeout); - reject(new Error(`WebSocket error: ${(e as any)?.message ?? "unknown"}`)); + reject( + new Error( + `WebSocket error: ${(e as any)?.message ?? "unknown"}`, + ), + ); }); }); diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver-engine.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver-engine.test.ts index 9eb6d6772d..1ba1ae6772 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/driver-engine.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/driver-engine.test.ts @@ -36,9 +36,7 @@ async function refreshRunnerMetadata( } for (const registryVariant of getDriverRegistryVariants(__dirname)) { - const describeVariant = registryVariant.skip - ? describe.skip - : describe; + const describeVariant = registryVariant.skip ? describe.skip : describe; const variantName = registryVariant.skipReason ? `${registryVariant.name} (${registryVariant.skipReason})` : registryVariant.name; @@ -64,8 +62,7 @@ for (const registryVariant of getDriverRegistryVariants(__dirname)) { const poolName = process.env.RIVET_POOL_NAME || `test-driver-${crypto.randomUUID().slice(0, 8)}`; - const token = - process.env.RIVET_TOKEN || "dev"; + const token = process.env.RIVET_TOKEN || "dev"; // Create a fresh namespace for test isolation const nsResp = await fetch(`${endpoint}/namespaces`, { @@ -74,10 +71,15 @@ for (const registryVariant of getDriverRegistryVariants(__dirname)) { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, - body: JSON.stringify({ name: namespace, display_name: namespace }), + body: JSON.stringify({ + name: namespace, + display_name: namespace, + }), }); if (!nsResp.ok) { - throw new Error(`Create namespace failed: ${nsResp.status} ${await nsResp.text()}`); + throw new Error( + `Create namespace failed: ${nsResp.status} ${await nsResp.text()}`, + ); } // Configure registry @@ -90,9 +92,15 @@ for (const registryVariant of getDriverRegistryVariants(__dirname)) { }; const parsedConfig = registry.parseConfig(); - const clientConfig = convertRegistryConfigToClientConfig(parsedConfig); - const engineClient = new RemoteEngineControlClient(clientConfig); - const inlineClient = createClientWithDriver(engineClient, clientConfig); + const clientConfig = + convertRegistryConfigToClientConfig(parsedConfig); + const engineClient = new RemoteEngineControlClient( + clientConfig, + ); + const inlineClient = createClientWithDriver( + engineClient, + clientConfig, + ); let actorDriver: EngineActorDriver | undefined; // Start serverless HTTP server @@ -173,7 +181,8 @@ for (const registryVariant of getDriverRegistryVariants(__dirname)) { token, }, engineClient, - hardCrashActor: actorDriver.hardCrashActor.bind(actorDriver), + hardCrashActor: + actorDriver.hardCrashActor.bind(actorDriver), cleanup: async () => { await actorDriver.shutdown(false); await new Promise((resolve) => diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver-registry-variants.ts b/rivetkit-typescript/packages/rivetkit/tests/driver-registry-variants.ts index 1e327590d6..21dc1beffd 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/driver-registry-variants.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/driver-registry-variants.ts @@ -88,14 +88,15 @@ function getDynamicVariantSkipReason(): string | undefined { return `Dynamic registry parity requires secure-exec dist at one of: ${SECURE_EXEC_DIST_CANDIDATE_PATHS.join(", ")}.`; } - process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = pathToFileURL( - secureExecDistPath, - ).href; + process.env.RIVETKIT_DYNAMIC_SECURE_EXEC_SPECIFIER = + pathToFileURL(secureExecDistPath).href; return undefined; } -export function getDriverRegistryVariants(currentDir: string): DriverRegistryVariant[] { +export function getDriverRegistryVariants( + currentDir: string, +): DriverRegistryVariant[] { return [ { name: "static", diff --git a/rivetkit-typescript/packages/rivetkit/tests/hibernatable-websocket-ack-state.test.ts b/rivetkit-typescript/packages/rivetkit/tests/hibernatable-websocket-ack-state.test.ts index bdfa3a70a5..c9f24bcef4 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/hibernatable-websocket-ack-state.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/hibernatable-websocket-ack-state.test.ts @@ -41,7 +41,8 @@ describe("hibernatable websocket ack state", () => { handleInboundHibernatableWebSocketMessage({ connId: "conn-1", hibernatable, - messageLength: HIBERNATABLE_WEBSOCKET_BUFFERED_MESSAGE_SIZE_THRESHOLD, + messageLength: + HIBERNATABLE_WEBSOCKET_BUFFERED_MESSAGE_SIZE_THRESHOLD, rivetMessageIndex: 1, ackState, saveState, diff --git a/rivetkit-typescript/packages/rivetkit/tests/parse-actor-path.test.ts b/rivetkit-typescript/packages/rivetkit/tests/parse-actor-path.test.ts index 421f93aaca..8641c59392 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/parse-actor-path.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/parse-actor-path.test.ts @@ -401,7 +401,9 @@ describe("parseActorPath", () => { test("rejects an empty actor name", () => { expect(() => - parseActorPath("/gateway/?rvt-namespace=default&rvt-method=get"), + parseActorPath( + "/gateway/?rvt-namespace=default&rvt-method=get", + ), ).toThrowError(InvalidRequest); }); }); diff --git a/rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts b/rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts index a7cd94422f..2913b95e79 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/registry-constructor.test.ts @@ -47,10 +47,10 @@ describe("Registry constructor", () => { test("reads config mutations made before the prestart tick", async () => { const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); const initialTimeoutCalls = setTimeoutSpy.mock.calls.length; - let managerPort: number | undefined; + let engineVersion: string | undefined; vi.spyOn(Runtime, "create").mockImplementation(async (registry) => { - managerPort = registry.parseConfig().managerPort; + engineVersion = registry.parseConfig().engineVersion; return createMockRuntime(); }); @@ -59,14 +59,14 @@ describe("Registry constructor", () => { use: { test: testActor, }, - serveManager: true, + startEngine: true, }); - registry.config.managerPort = 7777; + registry.config.engineVersion = "9.9.9-test"; expect(setTimeoutSpy.mock.calls).toHaveLength(initialTimeoutCalls + 1); await vi.runAllTimersAsync(); - expect(managerPort).toBe(7777); + expect(engineVersion).toBe("9.9.9-test"); }); }); diff --git a/rivetkit-typescript/packages/rivetkit/tests/remote-engine-client-public-token.test.ts b/rivetkit-typescript/packages/rivetkit/tests/remote-engine-client-public-token.test.ts index 2e7a2507ec..a18fdcd49b 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/remote-engine-client-public-token.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/remote-engine-client-public-token.test.ts @@ -47,7 +47,8 @@ describe.sequential("RemoteEngineControlClient public token usage", () => { const driver = new RemoteEngineControlClient( ClientConfigSchema.parse({ - endpoint: "https://default:backend-http-token@backend-http.example/manager", + endpoint: + "https://default:backend-http-token@backend-http.example/manager", }), ); @@ -111,7 +112,8 @@ describe.sequential("RemoteEngineControlClient public token usage", () => { const driver = new RemoteEngineControlClient( ClientConfigSchema.parse({ - endpoint: "https://default:backend-ws-token@backend-ws.example/manager", + endpoint: + "https://default:backend-ws-token@backend-ws.example/manager", }), ); diff --git a/rivetkit-typescript/packages/rivetkit/tests/resolve-gateway-target.test.ts b/rivetkit-typescript/packages/rivetkit/tests/resolve-gateway-target.test.ts index 2b5e8e861c..65bdcc551c 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/resolve-gateway-target.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/resolve-gateway-target.test.ts @@ -18,7 +18,9 @@ describe("resolveGatewayTarget", () => { test("resolves getForKey targets and reports missing actors", async () => { const driver = createMockDriver({ getWithKey: async ({ key }) => - key[0] === "room" ? actorOutput("resolved-key-actor") : undefined, + key[0] === "room" + ? actorOutput("resolved-key-actor") + : undefined, }); await expect( @@ -48,7 +50,9 @@ describe("resolveGatewayTarget", () => { const createCalls: Array> = []; const driver = createMockDriver({ getOrCreateWithKey: async (input) => { - getOrCreateCalls.push(input as unknown as Record); + getOrCreateCalls.push( + input as unknown as Record, + ); return actorOutput("get-or-create-actor"); }, createActor: async (input) => { @@ -109,7 +113,9 @@ describe("resolveGatewayTarget", () => { }); }); -function createMockDriver(overrides: Partial = {}): EngineControlClient { +function createMockDriver( + overrides: Partial = {}, +): EngineControlClient { return { getForId: async () => undefined, getWithKey: async () => undefined, diff --git a/rivetkit-typescript/packages/sqlite-native/index.d.ts b/rivetkit-typescript/packages/sqlite-native/index.d.ts deleted file mode 100644 index 49ed86044b..0000000000 --- a/rivetkit-typescript/packages/sqlite-native/index.d.ts +++ /dev/null @@ -1,180 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ - -/* auto-generated by NAPI-RS */ - -/** - * Typed bind parameter passed from JavaScript. - * - * Replaces `Vec` for statement parameters, avoiding 20x - * serialization overhead for blob data. Instead of JSON arrays of numbers, - * blobs are passed as `Buffer` (a single memcpy from JS heap to Rust). - * - * See docs-internal/engine/NATIVE_SQLITE_REVIEW_FIXES.md M7. - */ -export interface BindParam { - /** One of: "null", "int", "float", "text", "blob" */ - kind: string - intValue?: number - floatValue?: number - textValue?: string - blobValue?: Buffer -} -/** Configuration for connecting to the KV channel endpoint. */ -export interface ConnectConfig { - url: string - token?: string - namespace: string -} -/** Result of an execute() call. */ -export interface ExecuteResult { - changes: number -} -/** Result of a query() call. */ -export interface QueryResult { - columns: Array - rows: Array> -} -/** - * Open the shared KV channel WebSocket connection. - * - * In production, token is the engine's admin_token (RIVET__AUTH__ADMIN_TOKEN). - * In local dev, token is config.token (RIVET_TOKEN), optional in dev mode. - */ -export declare function connect(config: ConnectConfig): KvChannel -/** - * Open a database for an actor. Sends ActorOpenRequest optimistically. - * - * VFS registration and sqlite3_open_v2 run inside `spawn_blocking` because - * they trigger synchronous VFS callbacks that call `Handle::block_on()` for - * KV I/O. This is safe from a blocking thread but would deadlock or freeze - * the Node.js main thread if called via `rt.block_on()`. - */ -export declare function openDatabase(channel: KvChannel, actorId: string): Promise -/** - * Execute a statement (INSERT, UPDATE, DELETE, CREATE, etc.). - * - * SQLite operations run on tokio's blocking thread pool via `spawn_blocking`. - * VFS callbacks call `Handle::block_on()` from blocking threads (not tokio - * worker threads), which is safe. The Node.js main thread is never blocked. - * - * Three threading approaches were considered: - * - * 1. **spawn_blocking** (chosen): napi `async fn` dispatches to tokio's - * blocking thread pool (default cap 512). Simplest, idiomatic, tokio - * manages the pool. Minor downside: thread may change between queries - * (slightly worse cache locality). - * - * 2. **Dedicated thread per actor**: One `std::thread` per actor, receives - * SQL via mpsc, sends results via oneshot. Best cache locality, but - * requires manual lifecycle management and one idle thread per open actor. - * - * 3. **Channel + block-in-place**: Sync napi function, VFS callbacks send - * requests via `std::sync::mpsc` and block on `recv()`. Does NOT solve - * the core problem because the Node.js main thread is still blocked. - * - * See docs-internal/engine/NATIVE_SQLITE_REVIEW_FINDINGS.md Finding 1. - */ -export declare function execute(db: NativeDatabase, sql: string, params?: Array | undefined | null): Promise -/** - * Run a query (SELECT, PRAGMA, etc.). - * - * See `execute` for threading model documentation. - */ -export declare function query(db: NativeDatabase, sql: string, params?: Array | undefined | null): Promise -/** - * Execute multi-statement SQL without parameters. - * Uses sqlite3_prepare_v2 in a loop with tail pointer tracking to handle - * multiple statements (e.g., migrations). Returns columns and rows from - * the last statement that produced results. - * - * See `execute` for threading model documentation. - */ -export declare function exec(db: NativeDatabase, sql: string): Promise -/** - * Close the database connection and release the actor lock. - * Sends ActorCloseRequest to the server. - * - * Locks the db mutex and takes the Option, so concurrent/subsequent - * execute/query/exec operations see None and return "database is closed". - */ -export declare function closeDatabase(db: NativeDatabase): Promise -/** Close the KV channel WebSocket connection. */ -export declare function disconnect(channel: KvChannel): Promise -/** Per-operation metrics snapshot. */ -export interface OpMetricsSnapshot { - count: number - totalDurationUs: number - minDurationUs: number - maxDurationUs: number - avgDurationUs: number -} -/** All KV channel metrics (Layer 1). */ -export interface KvChannelMetricsSnapshot { - get: OpMetricsSnapshot - put: OpMetricsSnapshot - delete: OpMetricsSnapshot - deleteRange: OpMetricsSnapshot - actorOpen: OpMetricsSnapshot - actorClose: OpMetricsSnapshot - keysTotal: number - requestsTotal: number - batchAtomicCommits: number - batchAtomicPages: number -} -/** SQL execution metrics (Layer 0). */ -export interface SqlMetricsSnapshot { - execute: OpMetricsSnapshot - query: OpMetricsSnapshot - exec: OpMetricsSnapshot - spawnBlockingWait: OpMetricsSnapshot - sqliteStep: OpMetricsSnapshot - stmtCache: OpMetricsSnapshot - resultSerialize: OpMetricsSnapshot -} -/** VFS callback metrics. */ -export interface VfsMetricsSnapshot { - xreadCount: number - xreadUs: number - xwriteCount: number - xwriteUs: number - xwriteBufferedCount: number - xsyncCount: number - xsyncUs: number - commitAtomicCount: number - commitAtomicUs: number - commitAtomicPages: number -} -/** All metrics across all layers. */ -export interface AllMetricsSnapshot { - kvChannel: KvChannelMetricsSnapshot - sql: SqlMetricsSnapshot - vfs: VfsMetricsSnapshot -} -/** Get a snapshot of all metrics across all layers. */ -export declare function getMetrics(channel: KvChannel): AllMetricsSnapshot -export type JsKvChannel = KvChannel -/** - * A shared WebSocket connection to the KV channel server. - * One per process, shared across all actors. - * - * The tokio runtime is owned here so it is dropped when the channel is dropped, - * ensuring clean process exit after disconnect. The runtime MUST NOT be dropped - * before all actors have closed their databases. - */ -export declare class KvChannel { } -export type JsNativeDatabase = NativeDatabase -/** - * An open SQLite database backed by KV storage via the channel. - * - * The `db` field is wrapped in `Arc>>` so that - * `close_database` can atomically take the handle while concurrent - * `execute`/`query`/`exec` closures hold an Arc clone. Any operation - * that finds `None` returns a "database is closed" error. This prevents - * use-after-free if `close_database` runs between pointer extraction - * and `spawn_blocking` task execution. - * - * Field order matters for drop safety: `stmt_cache` is declared before `db` - * so cached statements are finalized before the database connection is closed. - */ -export declare class NativeDatabase { } diff --git a/rivetkit-typescript/packages/sqlite-native/index.js b/rivetkit-typescript/packages/sqlite-native/index.js deleted file mode 100644 index 167fd643d5..0000000000 --- a/rivetkit-typescript/packages/sqlite-native/index.js +++ /dev/null @@ -1,324 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/* prettier-ignore */ - -/* auto-generated by NAPI-RS */ - -const { existsSync, readFileSync } = require('fs') -const { join } = require('path') - -const { platform, arch } = process - -let nativeBinding = null -let localFileExisted = false -let loadError = null - -function isMusl() { - // For Node 10 - if (!process.report || typeof process.report.getReport !== 'function') { - try { - const lddPath = require('child_process').execSync('which ldd').toString().trim() - return readFileSync(lddPath, 'utf8').includes('musl') - } catch (e) { - return true - } - } else { - const { glibcVersionRuntime } = process.report.getReport().header - return !glibcVersionRuntime - } -} - -switch (platform) { - case 'android': - switch (arch) { - case 'arm64': - localFileExisted = existsSync(join(__dirname, 'sqlite-native.android-arm64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.android-arm64.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-android-arm64') - } - } catch (e) { - loadError = e - } - break - case 'arm': - localFileExisted = existsSync(join(__dirname, 'sqlite-native.android-arm-eabi.node')) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.android-arm-eabi.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-android-arm-eabi') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Android ${arch}`) - } - break - case 'win32': - switch (arch) { - case 'x64': - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.win32-x64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.win32-x64-msvc.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-win32-x64-msvc') - } - } catch (e) { - loadError = e - } - break - case 'ia32': - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.win32-ia32-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.win32-ia32-msvc.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-win32-ia32-msvc') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.win32-arm64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.win32-arm64-msvc.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-win32-arm64-msvc') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Windows: ${arch}`) - } - break - case 'darwin': - localFileExisted = existsSync(join(__dirname, 'sqlite-native.darwin-universal.node')) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.darwin-universal.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-darwin-universal') - } - break - } catch {} - switch (arch) { - case 'x64': - localFileExisted = existsSync(join(__dirname, 'sqlite-native.darwin-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.darwin-x64.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-darwin-x64') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.darwin-arm64.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.darwin-arm64.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-darwin-arm64') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on macOS: ${arch}`) - } - break - case 'freebsd': - if (arch !== 'x64') { - throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) - } - localFileExisted = existsSync(join(__dirname, 'sqlite-native.freebsd-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.freebsd-x64.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-freebsd-x64') - } - } catch (e) { - loadError = e - } - break - case 'linux': - switch (arch) { - case 'x64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.linux-x64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.linux-x64-musl.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-linux-x64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.linux-x64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.linux-x64-gnu.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-linux-x64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.linux-arm64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.linux-arm64-musl.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-linux-arm64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.linux-arm64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.linux-arm64-gnu.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-linux-arm64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.linux-arm-musleabihf.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.linux-arm-musleabihf.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-linux-arm-musleabihf') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.linux-arm-gnueabihf.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.linux-arm-gnueabihf.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-linux-arm-gnueabihf') - } - } catch (e) { - loadError = e - } - } - break - case 'riscv64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.linux-riscv64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.linux-riscv64-musl.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-linux-riscv64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.linux-riscv64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.linux-riscv64-gnu.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-linux-riscv64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 's390x': - localFileExisted = existsSync( - join(__dirname, 'sqlite-native.linux-s390x-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./sqlite-native.linux-s390x-gnu.node') - } else { - nativeBinding = require('@rivetkit/sqlite-native-linux-s390x-gnu') - } - } catch (e) { - loadError = e - } - break - default: - throw new Error(`Unsupported architecture on Linux: ${arch}`) - } - break - default: - throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) -} - -if (!nativeBinding) { - if (loadError) { - throw loadError - } - throw new Error(`Failed to load native binding`) -} - -const { KvChannel, NativeDatabase, connect, openDatabase, execute, query, exec, closeDatabase, disconnect, getMetrics } = nativeBinding - -module.exports.KvChannel = KvChannel -module.exports.NativeDatabase = NativeDatabase -module.exports.connect = connect -module.exports.openDatabase = openDatabase -module.exports.execute = execute -module.exports.query = query -module.exports.exec = exec -module.exports.closeDatabase = closeDatabase -module.exports.disconnect = disconnect -module.exports.getMetrics = getMetrics diff --git a/rivetkit-typescript/packages/sqlite-native/npm/darwin-arm64/package.json b/rivetkit-typescript/packages/sqlite-native/npm/darwin-arm64/package.json deleted file mode 100644 index 6f456a9ab3..0000000000 --- a/rivetkit-typescript/packages/sqlite-native/npm/darwin-arm64/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@rivetkit/sqlite-native-darwin-arm64", - "version": "2.1.6", - "description": "Native SQLite addon for RivetKit - macOS arm64", - "license": "Apache-2.0", - "os": ["darwin"], - "cpu": ["arm64"], - "main": "sqlite-native.darwin-arm64.node", - "files": ["sqlite-native.darwin-arm64.node"], - "engines": { - "node": ">= 20.0.0" - } -} diff --git a/rivetkit-typescript/packages/sqlite-native/npm/darwin-x64/package.json b/rivetkit-typescript/packages/sqlite-native/npm/darwin-x64/package.json deleted file mode 100644 index 933de7d328..0000000000 --- a/rivetkit-typescript/packages/sqlite-native/npm/darwin-x64/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@rivetkit/sqlite-native-darwin-x64", - "version": "2.1.6", - "description": "Native SQLite addon for RivetKit - macOS x64", - "license": "Apache-2.0", - "os": ["darwin"], - "cpu": ["x64"], - "main": "sqlite-native.darwin-x64.node", - "files": ["sqlite-native.darwin-x64.node"], - "engines": { - "node": ">= 20.0.0" - } -} diff --git a/rivetkit-typescript/packages/sqlite-native/npm/linux-arm64-gnu/package.json b/rivetkit-typescript/packages/sqlite-native/npm/linux-arm64-gnu/package.json deleted file mode 100644 index f797a36b3e..0000000000 --- a/rivetkit-typescript/packages/sqlite-native/npm/linux-arm64-gnu/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@rivetkit/sqlite-native-linux-arm64-gnu", - "version": "2.1.6", - "description": "Native SQLite addon for RivetKit - Linux arm64 GNU", - "license": "Apache-2.0", - "os": ["linux"], - "cpu": ["arm64"], - "main": "sqlite-native.linux-arm64-gnu.node", - "files": ["sqlite-native.linux-arm64-gnu.node"], - "engines": { - "node": ">= 20.0.0" - } -} diff --git a/rivetkit-typescript/packages/sqlite-native/npm/linux-x64-gnu/package.json b/rivetkit-typescript/packages/sqlite-native/npm/linux-x64-gnu/package.json deleted file mode 100644 index 716c52c8bc..0000000000 --- a/rivetkit-typescript/packages/sqlite-native/npm/linux-x64-gnu/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@rivetkit/sqlite-native-linux-x64-gnu", - "version": "2.1.6", - "description": "Native SQLite addon for RivetKit - Linux x64 GNU", - "license": "Apache-2.0", - "os": ["linux"], - "cpu": ["x64"], - "main": "sqlite-native.linux-x64-gnu.node", - "files": ["sqlite-native.linux-x64-gnu.node"], - "engines": { - "node": ">= 20.0.0" - } -} diff --git a/rivetkit-typescript/packages/sqlite-native/npm/win32-x64-msvc/package.json b/rivetkit-typescript/packages/sqlite-native/npm/win32-x64-msvc/package.json deleted file mode 100644 index a5045b2a9b..0000000000 --- a/rivetkit-typescript/packages/sqlite-native/npm/win32-x64-msvc/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@rivetkit/sqlite-native-win32-x64-msvc", - "version": "2.1.6", - "description": "Native SQLite addon for RivetKit - Windows x64 MSVC", - "license": "Apache-2.0", - "os": ["win32"], - "cpu": ["x64"], - "main": "sqlite-native.win32-x64-msvc.node", - "files": ["sqlite-native.win32-x64-msvc.node"], - "engines": { - "node": ">= 20.0.0" - } -} diff --git a/rivetkit-typescript/packages/sqlite-native/package.json b/rivetkit-typescript/packages/sqlite-native/package.json deleted file mode 100644 index a0b6a1f8fc..0000000000 --- a/rivetkit-typescript/packages/sqlite-native/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@rivetkit/sqlite-native", - "version": "2.1.6", - "description": "DEPRECATED: Use @rivetkit/rivetkit-native instead. Native SQLite addon for RivetKit backed by KV channel protocol", - "deprecated": "Use @rivetkit/rivetkit-native which provides both native SQLite and envoy client through a unified N-API addon.", - "license": "Apache-2.0", - "main": "index.js", - "types": "index.d.ts", - "engines": { - "node": ">= 20.0.0" - }, - "napi": { - "name": "sqlite-native", - "triples": { - "defaults": false, - "additional": [ - "x86_64-unknown-linux-gnu", - "aarch64-unknown-linux-gnu", - "x86_64-apple-darwin", - "aarch64-apple-darwin", - "x86_64-pc-windows-msvc" - ] - } - }, - "files": [ - "index.js", - "index.d.ts", - "package.json" - ], - "scripts": { - "build": "napi build --platform --release" - }, - "devDependencies": { - "@napi-rs/cli": "^2.18.0" - } -} diff --git a/rivetkit-typescript/packages/sqlite-native/turbo.json b/rivetkit-typescript/packages/sqlite-native/turbo.json deleted file mode 100644 index 1269f41c8d..0000000000 --- a/rivetkit-typescript/packages/sqlite-native/turbo.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "extends": ["//"], - "tasks": { - "build": { - "inputs": [ - "src/**", - "scripts/**", - "Cargo.toml", - "Cargo.lock", - "build.rs", - "package.json", - "index.js", - "index.d.ts" - ], - "outputs": [ - "sqlite-native.*.node", - "npm/**/*.node", - "target/release/librivetkit_sqlite_native.*", - "target/release/rivetkit_sqlite_native.dll" - ] - } - } -} diff --git a/rivetkit-typescript/packages/sqlite-vfs/package.json b/rivetkit-typescript/packages/sqlite-vfs/package.json deleted file mode 100644 index 5def0d8bc9..0000000000 --- a/rivetkit-typescript/packages/sqlite-vfs/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@rivetkit/sqlite-wasm", - "version": "2.3.0-rc.4", - "description": "SQLite VFS backed by KV storage for RivetKit", - "license": "Apache-2.0", - "type": "module", - "files": [ - "dist", - "src", - "schemas", - "package.json" - ], - "exports": { - ".": { - "import": { - "types": "./dist/tsup/index.d.ts", - "default": "./dist/tsup/index.js" - }, - "require": { - "types": "./dist/tsup/index.d.cts", - "default": "./dist/tsup/index.cjs" - } - } - }, - "engines": { - "node": ">=20.0.0" - }, - "scripts": { - "build": "pnpm run generate:empty-db-page && pnpm run compile:bare && tsup src/index.ts", - "generate:empty-db-page": "tsx scripts/generate-empty-db-page.ts", - "compile:bare": "tsx scripts/compile-bare.ts compile schemas/file-meta/v1.bare -o dist/schemas/file-meta/v1.ts", - "check-types": "pnpm run generate:empty-db-page && pnpm run compile:bare && tsc --noEmit", - "test": "pnpm --filter @rivetkit/sqlite-wasm-test test" - }, - "dependencies": { - "@rivetkit/bare-ts": "^0.6.2", - "@rivetkit/sqlite": "^0.1.1", - "vbare": "^0.0.4" - }, - "devDependencies": { - "@bare-ts/tools": "^0.13.0", - "@types/node": "^22.13.1", - "commander": "^12.0.0", - "tsx": "^4.7.0", - "tsup": "^8.4.0", - "typescript": "^5.7.3", - "vitest": "^3.1.1" - } -} diff --git a/rivetkit-typescript/packages/workflow-engine/src/context.ts b/rivetkit-typescript/packages/workflow-engine/src/context.ts index 77c76b57de..8ee0b84fbc 100644 --- a/rivetkit-typescript/packages/workflow-engine/src/context.ts +++ b/rivetkit-typescript/packages/workflow-engine/src/context.ts @@ -954,15 +954,12 @@ export class WorkflowContextImpl implements WorkflowContextInterface { willRetry: false, }); throw markErrorReported( - attachTryStepFailure( - new CriticalError(error.message), - { - kind: "timeout", - stepName: config.name, - attempts: metadata.attempts, - error: extractErrorInfo(error), - }, - ), + attachTryStepFailure(new CriticalError(error.message), { + kind: "timeout", + stepName: config.name, + attempts: metadata.attempts, + error: extractErrorInfo(error), + }), ); } diff --git a/rivetkit-typescript/packages/workflow-engine/src/types.ts b/rivetkit-typescript/packages/workflow-engine/src/types.ts index 076b465300..f737b68c47 100644 --- a/rivetkit-typescript/packages/workflow-engine/src/types.ts +++ b/rivetkit-typescript/packages/workflow-engine/src/types.ts @@ -422,11 +422,7 @@ export interface TryStepConfig extends StepConfig { catch?: readonly TryStepCatchKind[]; } -export type TryBlockCatchKind = - | "step" - | "join" - | "race" - | "rollback"; +export type TryBlockCatchKind = "step" | "join" | "race" | "rollback"; export interface TryBlockFailure { source: "step" | "join" | "race" | "block"; diff --git a/rivetkit-typescript/packages/workflow-engine/tests/driver-kv.test.ts b/rivetkit-typescript/packages/workflow-engine/tests/driver-kv.test.ts index eef035ed9d..0ab0f73840 100644 --- a/rivetkit-typescript/packages/workflow-engine/tests/driver-kv.test.ts +++ b/rivetkit-typescript/packages/workflow-engine/tests/driver-kv.test.ts @@ -16,126 +16,122 @@ function encode(value: string): Uint8Array { } for (const mode of modes) { - describe( - `Workflow Engine Driver KV (${mode})`, - { - sequential: true, - }, - () => { - let driver: InMemoryDriver; - - beforeEach(() => { - driver = new InMemoryDriver(); - driver.latency = 0; - }); - - it("should set and get values", async () => { - const key = encode("key-a"); - const value = encode("value-a"); - - await driver.set(key, value); + describe(`Workflow Engine Driver KV (${mode})`, { + sequential: true, + }, () => { + let driver: InMemoryDriver; - const result = await driver.get(key); - expect(result).toEqual(value); - }); - - it("should return null for missing keys", async () => { - const result = await driver.get(encode("missing")); - expect(result).toBeNull(); - }); - - it("should overwrite existing keys", async () => { - const key = encode("key-b"); - await driver.set(key, encode("first")); - await driver.set(key, encode("second")); - - const result = await driver.get(key); - expect(result).toEqual(encode("second")); - }); - - it("should delete keys", async () => { - const key = encode("key-c"); - await driver.set(key, encode("value")); - await driver.delete(key); - - const result = await driver.get(key); - expect(result).toBeNull(); - }); - - it("should list keys by prefix", async () => { - await driver.set(buildNameKey(0), encode("one")); - await driver.set(buildNameKey(1), encode("two")); - await driver.set(buildWorkflowStateKey(), encode("state")); - - const entries = await driver.list(buildNamePrefix()); - const indices = entries.map((entry) => parseNameKey(entry.key)); - - expect(indices).toEqual([0, 1]); - }); - - it("should delete only keys with a prefix", async () => { - const firstNameKey = buildNameKey(0); - const stateKey = buildWorkflowStateKey(); - - await driver.set(firstNameKey, encode("name")); - await driver.set(stateKey, encode("state")); - - await driver.deletePrefix(buildNamePrefix()); - - expect(await driver.get(firstNameKey)).toBeNull(); - expect(await driver.get(stateKey)).not.toBeNull(); - }); - - it("should delete only keys within a half-open range", async () => { - const keyA = encode("range-0"); - const keyB = encode("range-1"); - const keyBChild = encode("range-1-child"); - const keyC = encode("range-2"); - - await driver.set(keyA, encode("a")); - await driver.set(keyB, encode("b")); - await driver.set(keyBChild, encode("b-child")); - await driver.set(keyC, encode("c")); - - await driver.deleteRange(keyB, keyC); - - expect(await driver.get(keyA)).toEqual(encode("a")); - expect(await driver.get(keyB)).toBeNull(); - expect(await driver.get(keyBChild)).toBeNull(); - expect(await driver.get(keyC)).toEqual(encode("c")); - }); - - it("should list name keys in sorted order", async () => { - await driver.set(buildNameKey(1), encode("two")); - await driver.set(buildNameKey(0), encode("one")); - - const entries = await driver.list(buildNamePrefix()); - const indices = entries.map((entry) => parseNameKey(entry.key)); - - expect(indices).toEqual([0, 1]); - }); - - it("should batch writes", async () => { - const keyA = encode("batch-a"); - const keyB = encode("batch-b"); - - await driver.batch([ - { key: keyA, value: encode("one") }, - { key: keyB, value: encode("two") }, - ]); + beforeEach(() => { + driver = new InMemoryDriver(); + driver.latency = 0; + }); - expect(await driver.get(keyA)).toEqual(encode("one")); - expect(await driver.get(keyB)).toEqual(encode("two")); - }); + it("should set and get values", async () => { + const key = encode("key-a"); + const value = encode("value-a"); - it("should batch overwrite existing keys", async () => { - const key = encode("batch-c"); - await driver.set(key, encode("old")); + await driver.set(key, value); - await driver.batch([{ key, value: encode("new") }]); + const result = await driver.get(key); + expect(result).toEqual(value); + }); - expect(await driver.get(key)).toEqual(encode("new")); - }); - }, - ); + it("should return null for missing keys", async () => { + const result = await driver.get(encode("missing")); + expect(result).toBeNull(); + }); + + it("should overwrite existing keys", async () => { + const key = encode("key-b"); + await driver.set(key, encode("first")); + await driver.set(key, encode("second")); + + const result = await driver.get(key); + expect(result).toEqual(encode("second")); + }); + + it("should delete keys", async () => { + const key = encode("key-c"); + await driver.set(key, encode("value")); + await driver.delete(key); + + const result = await driver.get(key); + expect(result).toBeNull(); + }); + + it("should list keys by prefix", async () => { + await driver.set(buildNameKey(0), encode("one")); + await driver.set(buildNameKey(1), encode("two")); + await driver.set(buildWorkflowStateKey(), encode("state")); + + const entries = await driver.list(buildNamePrefix()); + const indices = entries.map((entry) => parseNameKey(entry.key)); + + expect(indices).toEqual([0, 1]); + }); + + it("should delete only keys with a prefix", async () => { + const firstNameKey = buildNameKey(0); + const stateKey = buildWorkflowStateKey(); + + await driver.set(firstNameKey, encode("name")); + await driver.set(stateKey, encode("state")); + + await driver.deletePrefix(buildNamePrefix()); + + expect(await driver.get(firstNameKey)).toBeNull(); + expect(await driver.get(stateKey)).not.toBeNull(); + }); + + it("should delete only keys within a half-open range", async () => { + const keyA = encode("range-0"); + const keyB = encode("range-1"); + const keyBChild = encode("range-1-child"); + const keyC = encode("range-2"); + + await driver.set(keyA, encode("a")); + await driver.set(keyB, encode("b")); + await driver.set(keyBChild, encode("b-child")); + await driver.set(keyC, encode("c")); + + await driver.deleteRange(keyB, keyC); + + expect(await driver.get(keyA)).toEqual(encode("a")); + expect(await driver.get(keyB)).toBeNull(); + expect(await driver.get(keyBChild)).toBeNull(); + expect(await driver.get(keyC)).toEqual(encode("c")); + }); + + it("should list name keys in sorted order", async () => { + await driver.set(buildNameKey(1), encode("two")); + await driver.set(buildNameKey(0), encode("one")); + + const entries = await driver.list(buildNamePrefix()); + const indices = entries.map((entry) => parseNameKey(entry.key)); + + expect(indices).toEqual([0, 1]); + }); + + it("should batch writes", async () => { + const keyA = encode("batch-a"); + const keyB = encode("batch-b"); + + await driver.batch([ + { key: keyA, value: encode("one") }, + { key: keyB, value: encode("two") }, + ]); + + expect(await driver.get(keyA)).toEqual(encode("one")); + expect(await driver.get(keyB)).toEqual(encode("two")); + }); + + it("should batch overwrite existing keys", async () => { + const key = encode("batch-c"); + await driver.set(key, encode("old")); + + await driver.batch([{ key, value: encode("new") }]); + + expect(await driver.get(key)).toEqual(encode("new")); + }); + }); } diff --git a/rivetkit-typescript/packages/workflow-engine/tests/driver-scheduling.test.ts b/rivetkit-typescript/packages/workflow-engine/tests/driver-scheduling.test.ts index 804ad4a1e5..ba102a1cbd 100644 --- a/rivetkit-typescript/packages/workflow-engine/tests/driver-scheduling.test.ts +++ b/rivetkit-typescript/packages/workflow-engine/tests/driver-scheduling.test.ts @@ -4,41 +4,37 @@ import { InMemoryDriver } from "../src/testing.js"; const modes = ["yield", "live"] as const; for (const mode of modes) { - describe( - `Workflow Engine Driver Scheduling (${mode})`, - { - sequential: true, - }, - () => { - let driver: InMemoryDriver; - - beforeEach(() => { - driver = new InMemoryDriver(); - driver.latency = 0; - }); - - it("should set and clear alarms", async () => { - const wakeAt = Date.now() + 1000; - - await driver.setAlarm("wf-1", wakeAt); - expect(driver.getAlarm("wf-1")).toBe(wakeAt); - - await driver.clearAlarm("wf-1"); - expect(driver.getAlarm("wf-1")).toBeUndefined(); - }); - - it("should return due alarms", async () => { - await driver.setAlarm("wf-due", Date.now() - 1); - await driver.setAlarm("wf-later", Date.now() + 1000); - - const due = driver.getDueAlarms(); - expect(due).toContain("wf-due"); - expect(due).not.toContain("wf-later"); - }); - - it("should expose worker poll interval", async () => { - expect(driver.workerPollInterval).toBeGreaterThan(0); - }); - }, - ); + describe(`Workflow Engine Driver Scheduling (${mode})`, { + sequential: true, + }, () => { + let driver: InMemoryDriver; + + beforeEach(() => { + driver = new InMemoryDriver(); + driver.latency = 0; + }); + + it("should set and clear alarms", async () => { + const wakeAt = Date.now() + 1000; + + await driver.setAlarm("wf-1", wakeAt); + expect(driver.getAlarm("wf-1")).toBe(wakeAt); + + await driver.clearAlarm("wf-1"); + expect(driver.getAlarm("wf-1")).toBeUndefined(); + }); + + it("should return due alarms", async () => { + await driver.setAlarm("wf-due", Date.now() - 1); + await driver.setAlarm("wf-later", Date.now() + 1000); + + const due = driver.getDueAlarms(); + expect(due).toContain("wf-due"); + expect(due).not.toContain("wf-later"); + }); + + it("should expose worker poll interval", async () => { + expect(driver.workerPollInterval).toBeGreaterThan(0); + }); + }); } diff --git a/rivetkit-typescript/packages/workflow-engine/tests/eviction-cancel.test.ts b/rivetkit-typescript/packages/workflow-engine/tests/eviction-cancel.test.ts index 5e566db014..3ea1dc1a83 100644 --- a/rivetkit-typescript/packages/workflow-engine/tests/eviction-cancel.test.ts +++ b/rivetkit-typescript/packages/workflow-engine/tests/eviction-cancel.test.ts @@ -9,80 +9,60 @@ import { const modes = ["yield", "live"] as const; for (const mode of modes) { - describe( - `Workflow Engine Eviction and Cancellation (${mode})`, - { - sequential: true, - }, - () => { - let driver: InMemoryDriver; + describe(`Workflow Engine Eviction and Cancellation (${mode})`, { + sequential: true, + }, () => { + let driver: InMemoryDriver; - beforeEach(() => { - driver = new InMemoryDriver(); - driver.latency = 0; - }); + beforeEach(() => { + driver = new InMemoryDriver(); + driver.latency = 0; + }); - it("should surface eviction through the abort event", async () => { - const workflow = async (ctx: WorkflowContextInterface) => { - await new Promise((resolve) => { - if (ctx.abortSignal.aborted) { - resolve(); - return; - } - ctx.abortSignal.addEventListener( - "abort", - () => resolve(), - { - once: true, - }, - ); + it("should surface eviction through the abort event", async () => { + const workflow = async (ctx: WorkflowContextInterface) => { + await new Promise((resolve) => { + if (ctx.abortSignal.aborted) { + resolve(); + return; + } + ctx.abortSignal.addEventListener("abort", () => resolve(), { + once: true, }); - return ctx.isEvicted(); - }; - - const handle = runWorkflow( - "wf-1", - workflow, - undefined, - driver, - { - mode, - }, - ); - handle.evict(); + }); + return ctx.isEvicted(); + }; - const result = await handle.result; - expect(result.state).toBe("completed"); - expect(result.output).toBe(true); + const handle = runWorkflow("wf-1", workflow, undefined, driver, { + mode, }); + handle.evict(); - it("should cancel workflow and clear alarms", async () => { - const workflow = async (_ctx: WorkflowContextInterface) => { - return "done"; - }; + const result = await handle.result; + expect(result.state).toBe("completed"); + expect(result.output).toBe(true); + }); - const handle = runWorkflow( - "wf-1", - workflow, - undefined, - driver, - { - mode, - }, - ); - await driver.setAlarm("wf-1", Date.now() + 1000); + it("should cancel workflow and clear alarms", async () => { + const workflow = async (_ctx: WorkflowContextInterface) => { + return "done"; + }; - await handle.cancel(); + const handle = runWorkflow("wf-1", workflow, undefined, driver, { + mode, + }); + await driver.setAlarm("wf-1", Date.now() + 1000); - await expect(handle.result).rejects.toThrow(EvictedError); - expect(await handle.getState()).toBe("cancelled"); - expect(driver.getAlarm("wf-1")).toBeUndefined(); + await handle.cancel(); - await expect( - runWorkflow("wf-1", workflow, undefined, driver, { mode }) - .result, - ).rejects.toThrow(EvictedError); - }); - }, - ); + await expect(handle.result).rejects.toThrow(EvictedError); + expect(await handle.getState()).toBe("cancelled"); + expect(driver.getAlarm("wf-1")).toBeUndefined(); + + await expect( + runWorkflow("wf-1", workflow, undefined, driver, { mode }) + .result, + ).rejects.toThrow(EvictedError); + }); + }); } diff --git a/rivetkit-typescript/packages/workflow-engine/tests/removals.test.ts b/rivetkit-typescript/packages/workflow-engine/tests/removals.test.ts index 14136407ed..b8d8f29506 100644 --- a/rivetkit-typescript/packages/workflow-engine/tests/removals.test.ts +++ b/rivetkit-typescript/packages/workflow-engine/tests/removals.test.ts @@ -9,76 +9,72 @@ import { const modes = ["yield", "live"] as const; for (const mode of modes) { - describe( - `Workflow Engine Removed Entries (${mode})`, - { - sequential: true, - }, - () => { - let driver: InMemoryDriver; + describe(`Workflow Engine Removed Entries (${mode})`, { + sequential: true, + }, () => { + let driver: InMemoryDriver; - beforeEach(() => { - driver = new InMemoryDriver(); - driver.latency = 0; - }); + beforeEach(() => { + driver = new InMemoryDriver(); + driver.latency = 0; + }); - it("should skip removed entries of all kinds", async () => { - const workflow1 = async (ctx: WorkflowContextInterface) => { - await ctx.step("old-step", async () => "old"); - await ctx.loop({ - name: "old-loop", - state: { count: 0 }, - run: async (_ctx, state) => { - if (state.count >= 1) { - return Loop.break("done"); - } - return Loop.continue({ count: state.count + 1 }); - }, - }); - await ctx.queue.send("old-message", "message-data"); - await ctx.sleep("old-sleep", 0); - await ctx.queue.next("old-listen", { - names: ["old-message"], - }); - await ctx.join("old-join", { - branch: { - run: async () => "ok", - }, - }); - await ctx.race("old-race", [ - { - name: "fast", - run: async () => "fast", - }, - ]); - return "done"; - }; + it("should skip removed entries of all kinds", async () => { + const workflow1 = async (ctx: WorkflowContextInterface) => { + await ctx.step("old-step", async () => "old"); + await ctx.loop({ + name: "old-loop", + state: { count: 0 }, + run: async (_ctx, state) => { + if (state.count >= 1) { + return Loop.break("done"); + } + return Loop.continue({ count: state.count + 1 }); + }, + }); + await ctx.queue.send("old-message", "message-data"); + await ctx.sleep("old-sleep", 0); + await ctx.queue.next("old-listen", { + names: ["old-message"], + }); + await ctx.join("old-join", { + branch: { + run: async () => "ok", + }, + }); + await ctx.race("old-race", [ + { + name: "fast", + run: async () => "fast", + }, + ]); + return "done"; + }; - await runWorkflow("wf-1", workflow1, undefined, driver, { - mode, - }).result; + await runWorkflow("wf-1", workflow1, undefined, driver, { + mode, + }).result; - const workflow2 = async (ctx: WorkflowContextInterface) => { - await ctx.removed("old-step", "step"); - await ctx.removed("old-loop", "loop"); - await ctx.removed("old-sleep", "sleep"); - await ctx.removed("old-listen", "message"); - await ctx.removed("old-join", "join"); - await ctx.removed("old-race", "race"); - return "updated"; - }; + const workflow2 = async (ctx: WorkflowContextInterface) => { + await ctx.removed("old-step", "step"); + await ctx.removed("old-loop", "loop"); + await ctx.removed("old-sleep", "sleep"); + await ctx.removed("old-listen", "message"); + await ctx.removed("old-join", "join"); + await ctx.removed("old-race", "race"); + return "updated"; + }; - const result = await runWorkflow( - "wf-1", - workflow2, - undefined, - driver, - { mode }, - ).result; + const result = await runWorkflow( + "wf-1", + workflow2, + undefined, + driver, + { mode }, + ).result; - expect(result.state).toBe("completed"); - expect(result.output).toBe("updated"); - }); - }, - ); + expect(result.state).toBe("completed"); + expect(result.output).toBe("updated"); + }); + }); } diff --git a/website/src/content/docs/actors/limits.mdx b/website/src/content/docs/actors/limits.mdx index a3a8eb2378..23bb5618a0 100644 --- a/website/src/content/docs/actors/limits.mdx +++ b/website/src/content/docs/actors/limits.mdx @@ -140,7 +140,7 @@ These timeouts control how actors are shut down when a serverless request reache | Name | Soft Limit | Hard Limit | Description | |------|------------|------------|-------------| -| Request lifespan | 900 seconds (15 min) | — | Total lifespan of a serverless request before drain begins. Configurable via `requestLifespan` in [`configureRunnerPool`](/docs/connect/registry-configuration). | +| Request lifespan | 900 seconds (15 min) | — | Total lifespan of a serverless request before drain begins. Configurable via `requestLifespan` in [`configurePool`](/docs/connect/registry-configuration). | | Serverless drain grace period | — | 10 seconds | Time reserved at the end of a request for actors to stop gracefully. Configurable via [engine config](/docs/self-hosting/configuration) (`pegboard.serverless_drain_grace_period`). | ### Actor Lifecycle diff --git a/website/src/content/docs/actors/quickstart/backend.mdx b/website/src/content/docs/actors/quickstart/backend.mdx index 687e32082f..b247c57664 100644 --- a/website/src/content/docs/actors/quickstart/backend.mdx +++ b/website/src/content/docs/actors/quickstart/backend.mdx @@ -75,14 +75,7 @@ deno run --allow-net --allow-read --allow-env --watch index.ts -Your server is now running on `http://localhost:6420`. To change the port, pass `managerPort` in the setup config: - -```ts @nocheck -const registry = setup({ - use: { counter }, - managerPort: 3000, -}); -``` +Your server is now running on `http://localhost:6420`. Clients connect directly to the Rivet Engine on this port. @@ -221,4 +214,3 @@ See the [React documentation](/docs/clients/react) for more information. ## Configuration Options - diff --git a/website/src/content/docs/actors/versions.mdx b/website/src/content/docs/actors/versions.mdx index a3dad44837..c0c42e05f7 100644 --- a/website/src/content/docs/actors/versions.mdx +++ b/website/src/content/docs/actors/versions.mdx @@ -17,7 +17,7 @@ Versions are not configured by default. See [Registry Configuration](/docs/conne -`RIVET_RUNNER_VERSION` is only needed when self-hosting or using a custom runner. Rivet Compute handles versioning automatically. +`RIVET_ENVOY_VERSION` is only needed when self-hosting or using a custom runner. Rivet Compute handles versioning automatically. ### Example Scenario @@ -70,7 +70,7 @@ Configure the runner version using an environment variable or programmatically: ```bash {{"title": "Environment Variable"}} -RIVET_RUNNER_VERSION=2 +RIVET_ENVOY_VERSION=2 ``` ```typescript {{"title": "Programmatic"}} @@ -80,7 +80,7 @@ const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor }, - runner: { + envoy: { version: 2, }, }); @@ -103,13 +103,13 @@ We recommend injecting a build-time value that increments with every deployment. Generate the version at build time and bake it into the image as an environment variable: ```bash @nocheck -docker build --build-arg RIVET_RUNNER_VERSION=$(date +%s) . +docker build --build-arg RIVET_ENVOY_VERSION=$(date +%s) . ``` ```dockerfile @nocheck FROM node:20-slim -ARG RIVET_RUNNER_VERSION -ENV RIVET_RUNNER_VERSION=$RIVET_RUNNER_VERSION +ARG RIVET_ENVOY_VERSION +ENV RIVET_ENVOY_VERSION=$RIVET_ENVOY_VERSION WORKDIR /app COPY . . RUN npm install && npm run build @@ -129,7 +129,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { env: { - RIVET_RUNNER_VERSION: String(Math.floor(Date.now() / 1000)), + RIVET_ENVOY_VERSION: String(Math.floor(Date.now() / 1000)), }, }; @@ -147,7 +147,7 @@ import { defineConfig } from "vite"; export default defineConfig({ define: { - "process.env.RIVET_RUNNER_VERSION": JSON.stringify( + "process.env.RIVET_ENVOY_VERSION": JSON.stringify( String(Math.floor(Date.now() / 1000)) ), }, @@ -163,17 +163,17 @@ Set the version from your CI pipeline: ```yaml @nocheck # GitHub Actions env: - RIVET_RUNNER_VERSION: ${{ github.run_number }} + RIVET_ENVOY_VERSION: ${{ github.run_number }} ``` ```bash @nocheck # Railway / Render / generic CI -export RIVET_RUNNER_VERSION=$(date +%s) +export RIVET_ENVOY_VERSION=$(date +%s) ``` ```bash @nocheck # Git commit count -export RIVET_RUNNER_VERSION=$(git rev-list --count HEAD) +export RIVET_ENVOY_VERSION=$(git rev-list --count HEAD) ``` @@ -198,7 +198,7 @@ const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor }, - runner: { + envoy: { version: BUILD_VERSION, }, }); diff --git a/website/src/content/docs/agent-os/agents/claude.mdx b/website/src/content/docs/agent-os/agents/claude.mdx index bab530fab0..7316647859 100644 --- a/website/src/content/docs/agent-os/agents/claude.mdx +++ b/website/src/content/docs/agent-os/agents/claude.mdx @@ -8,7 +8,7 @@ skill: false Claude agent documentation is coming soon. -```ts +```ts @nocheck import claude from "@rivet-dev/agent-os-claude"; const vm = await AgentOs.create({ software: [common, claude] }); diff --git a/website/src/content/docs/agent-os/agents/codex.mdx b/website/src/content/docs/agent-os/agents/codex.mdx index 9966740d48..0bd49847b7 100644 --- a/website/src/content/docs/agent-os/agents/codex.mdx +++ b/website/src/content/docs/agent-os/agents/codex.mdx @@ -8,7 +8,7 @@ skill: false Codex agent documentation is coming soon. -```ts +```ts @nocheck import codex from "@rivet-dev/agent-os-codex-agent"; const vm = await AgentOs.create({ software: [common, ...codex] }); diff --git a/website/src/content/docs/agent-os/agents/opencode.mdx b/website/src/content/docs/agent-os/agents/opencode.mdx index 4b49023a35..70d110ac54 100644 --- a/website/src/content/docs/agent-os/agents/opencode.mdx +++ b/website/src/content/docs/agent-os/agents/opencode.mdx @@ -8,7 +8,7 @@ skill: false OpenCode agent documentation is coming soon. -```ts +```ts @nocheck import opencode from "@rivet-dev/agent-os-opencode"; const vm = await AgentOs.create({ software: [common, opencode] }); diff --git a/website/src/content/docs/agent-os/agents/pi.mdx b/website/src/content/docs/agent-os/agents/pi.mdx index fdddf817be..45656b39d5 100644 --- a/website/src/content/docs/agent-os/agents/pi.mdx +++ b/website/src/content/docs/agent-os/agents/pi.mdx @@ -53,7 +53,7 @@ Pi scans two directories for `.js` extension files: | `~/.pi/agent/extensions/` | Global — applies to all Pi sessions | | `/.pi/extensions/` | Project — applies only when cwd matches | -```ts +```ts @nocheck const extensionCode = ` module.exports = function(pi) { // Modify the system prompt before each agent turn diff --git a/website/src/content/docs/general/http-server.mdx b/website/src/content/docs/general/http-server.mdx index 940e8d967f..e7b53a7f83 100644 --- a/website/src/content/docs/general/http-server.mdx +++ b/website/src/content/docs/general/http-server.mdx @@ -19,7 +19,7 @@ const registry = setup({ use: { myActor } }); registry.start(); ``` -Run with `npx tsx --watch index.ts` (Node.js), `bun --watch index.ts` (Bun), or `deno run --allow-net --allow-read --allow-env --watch index.ts` (Deno). The server starts on `http://localhost:6420` by default. +Run with `npx tsx --watch index.ts` (Node.js), `bun --watch index.ts` (Bun), or `deno run --allow-net --allow-read --allow-env --watch index.ts` (Deno). Clients connect to the Rivet Engine on `http://localhost:6420`. ### With Fetch Handlers diff --git a/website/src/content/docs/general/registry-configuration.mdx b/website/src/content/docs/general/registry-configuration.mdx index d34e44ee71..63e560d7f9 100644 --- a/website/src/content/docs/general/registry-configuration.mdx +++ b/website/src/content/docs/general/registry-configuration.mdx @@ -90,13 +90,13 @@ app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); export default app; ``` -```typescript Runner +```typescript Envoy import { actor, setup } from "rivetkit"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); -registry.startRunner(); +registry.startEnvoy(); ``` diff --git a/website/src/content/docs/general/runtime-modes.mdx b/website/src/content/docs/general/runtime-modes.mdx index 2bf1aa80d0..ad4964662e 100644 --- a/website/src/content/docs/general/runtime-modes.mdx +++ b/website/src/content/docs/general/runtime-modes.mdx @@ -103,14 +103,14 @@ import { actor, setup } from "rivetkit"; const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor } }); -registry.startRunner(); +registry.startEnvoy(); ``` The runner runs in the background, ready to run actors. ### Architecture -On startup, your backend calls `registry.startRunner()` which opens a persistent connection to the Rivet Engine. When a client creates an actor, the engine sends a command through this connection to start the actor on your backend. +On startup, your backend calls `registry.startEnvoy()` which opens a persistent connection to the Rivet Engine. When a client creates an actor, the engine sends a command through this connection to start the actor on your backend. Runners architecture diagram @@ -131,8 +131,8 @@ const myActor = actor({ state: {}, actions: {} }); const registry = setup({ use: { myActor }, - runner: { - runnerName: "gpu-workers", + envoy: { + poolName: "gpu-workers", }, }); ``` @@ -144,4 +144,4 @@ const registry = setup({ | Auto | `registry.start()` | Simplest setup. Starts server, serves static files, and runs actors. | | Serverless | `registry.serve()` | Fetch handler for serverless platforms | | Serverless | `registry.handler()` | Integrating with existing routers (Hono, Elysia, etc.) | -| Runner | `registry.startRunner()` | Long-running processes without HTTP endpoints | +| Runner | `registry.startEnvoy()` | Long-running processes without HTTP endpoints |