diff --git a/frontend/src/components/actors/actor-inspector-context.tsx b/frontend/src/components/actors/actor-inspector-context.tsx index 239bf5d682..f2a74acf2a 100644 --- a/frontend/src/components/actors/actor-inspector-context.tsx +++ b/frontend/src/components/actors/actor-inspector-context.tsx @@ -12,6 +12,10 @@ import { createContext, useContext, useMemo, useRef } from "react"; import type ReconnectingWebSocket from "reconnectingwebsocket"; import { type Connection, + type DatabaseColumn, + type DatabaseForeignKey, + type DatabaseSchema, + type DatabaseTableInfo, decodeWorkflowHistoryTransport, type QueueStatus, type ToServer, @@ -48,6 +52,13 @@ export const actorInspectorQueriesKeys = { ["actor", actorId, "is-workflow-enabled"] as const, }; +export type { + DatabaseColumn, + DatabaseForeignKey, + DatabaseSchema, + DatabaseTableInfo, +}; + type QueueStatusSummary = { size: number; maxSize: number; @@ -59,33 +70,6 @@ type QueueStatusSummary = { }>; }; -export type DatabaseColumn = { - cid: number; - name: string; - type: string; - notnull: boolean; - dflt_value: string | null; - pk: boolean | null; -}; - -export type DatabaseForeignKey = { - id: number; - table: string; - from: string; - to: string; -}; - -export type DatabaseTableInfo = { - table: { schema: string; name: string; type: string }; - columns: DatabaseColumn[]; - foreignKeys: DatabaseForeignKey[]; - records: number; -}; - -export type DatabaseSchema = { - tables: DatabaseTableInfo[]; -}; - interface ActorInspectorApi { ping: () => Promise; executeAction: (name: string, args: unknown[]) => Promise; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a013f68ce..41d8a63b62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4013,24 +4013,6 @@ importers: specifier: ^3.9.1 version: 3.9.1(@types/node@20.19.13)(rollup@4.57.1)(typescript@5.9.3)(vite@5.4.21(@types/node@20.19.13)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) - frontend/packages/example-registry: - dependencies: - '@rivet-gg/icons': - specifier: workspace:* - version: link:../icons - devDependencies: - '@types/node': - specifier: ^22.13.9 - version: 22.19.10 - puppeteer: - specifier: ^23.11.1 - version: 23.11.1(typescript@5.9.3) - tsx: - specifier: ^4.20.6 - version: 4.21.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 frontend/packages/icons: dependencies: '@fortawesome/fontawesome-svg-core': @@ -4143,36 +4125,6 @@ importers: specifier: ^4.22.0 version: 4.44.0(@cloudflare/workers-types@4.20251014.0) - rivetkit-typescript/packages/db: - dependencies: - better-sqlite3: - specifier: ^11.10.0 - version: 11.10.0 - drizzle-kit: - specifier: ^0.31.2 - version: 0.31.5 - rivetkit: - specifier: workspace:* - version: link:../rivetkit - devDependencies: - '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 - '@types/node': - specifier: ^24.0.4 - version: 24.7.1 - drizzle-orm: - specifier: ^0.44.2 - version: 0.44.6(@cloudflare/workers-types@4.20251014.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@11.10.0)(bun-types@1.3.0(@types/react@19.2.13))(kysely@0.28.8)(pg@8.17.2)(sql.js@1.13.0) - tsup: - specifier: ^8.3.6 - version: 8.5.1(@microsoft/api-extractor@7.53.2(@types/node@24.7.1))(@swc/core@1.15.11(@swc/helpers@0.5.17))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - typescript: - specifier: ^5.5.2 - version: 5.9.3 - vitest: - specifier: ^3.1.1 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@24.7.1)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) rivetkit-typescript/packages/devtools: dependencies: '@floating-ui/react': @@ -10284,12 +10236,9 @@ packages: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true - basic-ftp@5.0.5: - resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} - engines: {node: '>=10.0.0'} - bcp-47-match@2.0.3: resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + bcryptjs@2.4.3: resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} @@ -14478,6 +14427,7 @@ packages: prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true prelude-ls@1.2.1: @@ -15442,11 +15392,9 @@ packages: stream-replace-string@2.0.0: resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} - streamx@2.23.0: - resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} - strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} @@ -20321,21 +20269,6 @@ snapshots: dependencies: cross-spawn: 7.0.6 - '@puppeteer/browsers@2.6.1': - dependencies: - debug: 4.4.3 - extract-zip: 2.0.1 - progress: 2.0.3 - proxy-agent: 6.5.0 - semver: 7.7.4 - tar-fs: 3.1.1 - unbzip2-stream: 1.4.3 - yargs: 17.7.2 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a - - supports-color '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -21981,12 +21914,6 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.2))(typescript@5.9.2)': - dependencies: - '@trpc/server': 11.6.0(typescript@5.9.2) - typescript: 5.9.2 - '@tootallnate/quickjs-emscripten@0.23.0': {} - '@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@trpc/server': 11.6.0(typescript@5.9.3) @@ -22342,10 +22269,6 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@types/yauzl@2.10.3': - dependencies: - '@types/node': 22.19.10 - optional: true '@uiw/codemirror-extensions-basic-setup@4.25.1(@codemirror/autocomplete@6.18.7)(@codemirror/commands@6.8.1)(@codemirror/language@6.11.3)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.2)': dependencies: '@codemirror/autocomplete': 6.18.7 @@ -23309,9 +23232,8 @@ snapshots: baseline-browser-mapping@2.9.19: {} - basic-ftp@5.0.5: {} - bcp-47-match@2.0.3: {} + bcryptjs@2.4.3: {} better-opn@3.0.2: @@ -27412,7 +27334,6 @@ snapshots: dependencies: minipass: 7.1.2 - mitt@3.0.1: {} mkdirp-classic@0.5.3: optional: true @@ -27589,8 +27510,6 @@ snapshots: nested-error-stacks@2.0.1: {} - netmask@2.0.2: {} - next@15.5.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.93.2): dependencies: '@next/env': 15.5.9 @@ -29478,16 +29397,8 @@ snapshots: stream-replace-string@2.0.0: {} - streamx@2.23.0: - dependencies: - events-universal: 1.0.1 - fast-fifo: 1.3.2 - text-decoder: 1.2.3 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - strict-event-emitter@0.5.1: {} + strict-uri-encode@2.0.0: {} string-argv@0.3.2: {} @@ -29700,17 +29611,6 @@ snapshots: pump: 3.0.3 tar-stream: 2.2.0 optional: true - tar-fs@3.1.1: - dependencies: - pump: 3.0.3 - tar-stream: 3.1.7 - optionalDependencies: - bare-fs: 4.5.2 - bare-path: 3.0.0 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a tar-stream@2.2.0: dependencies: @@ -29720,14 +29620,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 optional: true - tar-stream@3.1.7: - dependencies: - b4a: 1.7.3 - fast-fifo: 1.3.2 - streamx: 2.23.0 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a tar@7.5.7: dependencies: @@ -30169,8 +30061,6 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typed-query-selector@2.12.0: {} - typescript-plugin-css-modules@5.2.0(ts-node@10.9.2(@swc/core@1.15.11(@swc/helpers@0.5.17))(@types/node@20.19.13)(typescript@5.9.3))(typescript@5.9.3): dependencies: '@types/postcss-modules-local-by-default': 4.0.2 diff --git a/rivetkit-typescript/packages/rivetkit/package.json b/rivetkit-typescript/packages/rivetkit/package.json index b53fc95c12..9894d01fcb 100644 --- a/rivetkit-typescript/packages/rivetkit/package.json +++ b/rivetkit-typescript/packages/rivetkit/package.json @@ -204,7 +204,7 @@ "scripts": { "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/actor/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/serve-test-suite/mod.ts src/test/mod.ts src/inspector/mod.ts src/workflow/mod.ts src/db/mod.ts src/db/drizzle/mod.ts", "build:browser": "tsup --config tsup.browser.config.ts", - "build:schema": "./scripts/compile-bare.ts compile schemas/client-protocol/v1.bare -o dist/schemas/client-protocol/v1.ts && ./scripts/compile-bare.ts compile schemas/client-protocol/v2.bare -o dist/schemas/client-protocol/v2.ts && ./scripts/compile-bare.ts compile schemas/client-protocol/v3.bare -o dist/schemas/client-protocol/v3.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v1.bare -o dist/schemas/file-system-driver/v1.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v2.bare -o dist/schemas/file-system-driver/v2.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v3.bare -o dist/schemas/file-system-driver/v3.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v1.bare -o dist/schemas/actor-persist/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v2.bare -o dist/schemas/actor-persist/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v3.bare -o dist/schemas/actor-persist/v3.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v4.bare -o dist/schemas/actor-persist/v4.ts && ./scripts/compile-bare.ts compile schemas/persist/v1.bare -o dist/schemas/persist/v1.ts && ./scripts/compile-bare.ts compile schemas/transport/v1.bare -o dist/schemas/transport/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v1.bare -o dist/schemas/actor-inspector/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v2.bare -o dist/schemas/actor-inspector/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v3.bare -o dist/schemas/actor-inspector/v3.ts", + "build:schema": "./scripts/compile-bare.ts compile schemas/client-protocol/v1.bare -o dist/schemas/client-protocol/v1.ts && ./scripts/compile-bare.ts compile schemas/client-protocol/v2.bare -o dist/schemas/client-protocol/v2.ts && ./scripts/compile-bare.ts compile schemas/client-protocol/v3.bare -o dist/schemas/client-protocol/v3.ts && ./scripts/compile-bare.ts compile schemas/client-protocol/v4.bare -o dist/schemas/client-protocol/v4.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v1.bare -o dist/schemas/file-system-driver/v1.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v2.bare -o dist/schemas/file-system-driver/v2.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v3.bare -o dist/schemas/file-system-driver/v3.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v1.bare -o dist/schemas/actor-persist/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v2.bare -o dist/schemas/actor-persist/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v3.bare -o dist/schemas/actor-persist/v3.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v4.bare -o dist/schemas/actor-persist/v4.ts && ./scripts/compile-bare.ts compile schemas/persist/v1.bare -o dist/schemas/persist/v1.ts && ./scripts/compile-bare.ts compile schemas/transport/v1.bare -o dist/schemas/transport/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v1.bare -o dist/schemas/actor-inspector/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v2.bare -o dist/schemas/actor-inspector/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v3.bare -o dist/schemas/actor-inspector/v3.ts", "check-types": "tsc --noEmit", "lint": "biome check .", "lint:fix": "biome check --write .", diff --git a/rivetkit-typescript/packages/rivetkit/schemas/client-protocol/v4.bare b/rivetkit-typescript/packages/rivetkit/schemas/client-protocol/v4.bare new file mode 100644 index 0000000000..d88fa58ecc --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/schemas/client-protocol/v4.bare @@ -0,0 +1,117 @@ +# MARK: Message To Client +type Init struct { + actorId: str + connectionId: str +} + +type Error struct { + group: str + code: str + message: str + metadata: optional + actionId: optional +} + +type ActionResponse struct { + id: uint + output: data +} + +type Event struct { + name: str + # CBOR array + args: data +} + +type ToClientBody union { + Init | + Error | + ActionResponse | + Event +} + +type ToClient struct { + body: ToClientBody +} + +# MARK: Message To Server +type ActionRequest struct { + id: uint + name: str + # CBOR array + args: data +} + +type SubscriptionRequest struct { + eventName: str + subscribe: bool +} + +type ToServerBody union { + ActionRequest | + SubscriptionRequest +} + +type ToServer struct { + body: ToServerBody +} + +# MARK: HTTP Action +type HttpActionRequest struct { + # CBOR array + args: data +} + +type HttpActionResponse struct { + output: data +} + +# MARK: HTTP Queue + +type HttpQueueSendRequest struct { + body: data + name: optional + wait: optional + timeout: optional +} + +type HttpQueueSendResponse struct { + status: str + response: optional +} + +# MARK: HTTP Error +type HttpResponseError struct { + group: str + code: str + message: str + metadata: optional +} + +# MARK: HTTP Resolve +type HttpResolveRequest void + +type HttpResolveResponse struct { + actorId: str +} + +# MARK: HTTP Inspector Queue +type HttpInspectorQueueMessageSummary struct { + id: uint + name: str + createdAtMs: uint +} + +type HttpInspectorQueueResponse struct { + size: uint + maxSize: uint + truncated: bool + messages: list +} + +# MARK: HTTP Inspector Workflow History +type HttpInspectorWorkflowHistoryResponse struct { + # CBOR-encoded workflow history, absent if not enabled or no history + history: optional + isWorkflowEnabled: bool +} diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/router-endpoints.ts b/rivetkit-typescript/packages/rivetkit/src/actor/router-endpoints.ts index df2dd71534..2882d9d67a 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/router-endpoints.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/router-endpoints.ts @@ -1,5 +1,6 @@ import * as cbor from "cbor-x"; import type { Context as HonoContext, HonoRequest } from "hono"; +import z from "zod"; import type { AnyConn } from "@/actor/conn/mod"; import { ActionContext } from "@/actor/contexts"; import * as errors from "@/actor/errors"; @@ -13,6 +14,7 @@ import { WS_PROTOCOL_ENCODING, } from "@/common/actor-router-consts"; import { stringifyError } from "@/common/utils"; +import type { QueueStatus } from "@/inspector/mod"; import type { RegistryConfig } from "@/registry/config"; import type * as protocol from "@/schemas/client-protocol/mod"; import { @@ -371,3 +373,45 @@ export function getRequestConnParams(req: HonoRequest): unknown { ); } } + +export async function handleQueueRequest( + c: HonoContext, + actorDriver: ActorDriver, +) { + const limit = z.coerce.number().positive().safeParse(c.req.query("limit")); + + if (!limit.success) { + throw new errors.InvalidRequest("Invalid limit parameter"); + } + const actor = await actorDriver.loadActor(c.env.actorId); + const queueNames = await actor.inspector.getQueueStatus(limit.data); + return c.json({ queues: queueNames }); +} + +export async function handleWorkflowHistoryRequest( + c: HonoContext, + actorDriver: ActorDriver, +) { + const actor = await actorDriver.loadActor(c.env.actorId); + if (!actor.inspector.isWorkflowEnabled()) { + throw new errors.InvalidRequest("Workflow history is not enabled"); + } + + const history = actor.inspector.getWorkflowHistory(); + // TODO: +} + +export async function handleDatabaseSchemaRequest( + c: HonoContext, + actorDriver: ActorDriver, +) { + const actor = await actorDriver.loadActor(c.env.actorId); + if (!actor.inspector.isDatabaseEnabled()) { + throw new errors.InvalidRequest("Database is not enabled"); + } + + const schemaBuffer = await actor.inspector.getDatabaseSchema(); + return c.body(schemaBuffer, 200, { + "Content-Type": "application/octet-stream", + }); +} \ No newline at end of file diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/router.ts b/rivetkit-typescript/packages/rivetkit/src/actor/router.ts index f85a93f9de..c27c9939bf 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/router.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/router.ts @@ -1,13 +1,16 @@ import { Hono } from "hono"; +import z from "zod"; +import * as errors from "@/actor/errors"; import { type ActionOpts, type ActionOutput, type ConnsMessageOpts, + getRequestEncoding, handleAction, + handleQueueRequest, handleQueueSend, handleRawRequest, } from "@/actor/router-endpoints"; - import { PATH_CONNECT, PATH_INSPECTOR_CONNECT, @@ -20,11 +23,20 @@ import { } from "@/common/router"; import { noopNext } from "@/common/utils"; import { inspectorLogger } from "@/inspector/log"; -import { timingSafeEqual } from "@/utils/crypto"; -import { getNodeEnv } from "@/utils/env-vars"; - import type { RegistryConfig } from "@/registry/config"; +import { + HttpInspectorQueueResponseSchema, + HttpInspectorWorkflowHistoryResponseSchema, +} from "@/schemas/client-protocol-zod/mod"; +import { + CURRENT_VERSION as CLIENT_PROTOCOL_CURRENT_VERSION, + HTTP_INSPECTOR_QUEUE_RESPONSE_VERSIONED, + HTTP_INSPECTOR_WORKFLOW_HISTORY_RESPONSE_VERSIONED, +} from "@/schemas/client-protocol/versioned"; +import { contentTypeForEncoding, serializeWithEncoding } from "@/serde"; import { type GetUpgradeWebSocket, VERSION } from "@/utils"; +import { timingSafeEqual } from "@/utils/crypto"; +import { getNodeEnv } from "@/utils/env-vars"; import { CONN_DRIVER_SYMBOL } from "./conn/mod"; import type { ActorDriver } from "./driver"; import { loggerWithoutContext } from "./log"; @@ -174,7 +186,9 @@ export function createActorRouter( return undefined; } - const userToken = c.req.header("Authorization")?.replace("Bearer ", ""); + const userToken = c.req + .header("Authorization") + ?.replace("Bearer ", ""); if (!userToken) { return c.text("Unauthorized", 401); } @@ -197,7 +211,9 @@ export function createActorRouter( const actor = await actorDriver.loadActor(c.env.actorId); const isStateEnabled = actor.inspector.isStateEnabled(); - const state = isStateEnabled ? actor.inspector.getStateJson() : undefined; + const state = isStateEnabled + ? actor.inspector.getStateJson() + : undefined; return c.json({ state, isStateEnabled }); }); @@ -249,8 +265,29 @@ export function createActorRouter( const actor = await actorDriver.loadActor(c.env.actorId); const limit = parseInt(c.req.query("limit") ?? "50", 10); + const encoding = getRequestEncoding(c.req); const status = await actor.inspector.getQueueStatusJson(limit); - return c.json(status); + const body = serializeWithEncoding( + encoding, + status, + HTTP_INSPECTOR_QUEUE_RESPONSE_VERSIONED, + CLIENT_PROTOCOL_CURRENT_VERSION, + HttpInspectorQueueResponseSchema, + (v) => v, + (v) => ({ + size: BigInt(v.size), + maxSize: BigInt(v.maxSize), + truncated: v.truncated, + messages: v.messages.map((m) => ({ + id: BigInt(m.id), + name: m.name, + createdAtMs: BigInt(m.createdAtMs), + })), + }), + ); + return c.body(body as Uint8Array as any, 200, { + "Content-Type": contentTypeForEncoding(encoding), + }); }); router.get("/inspector/traces", async (c) => { @@ -279,8 +316,25 @@ export function createActorRouter( if (authResponse) return authResponse; const actor = await actorDriver.loadActor(c.env.actorId); - const result = actor.inspector.getWorkflowHistoryJson(); - return c.json(result); + const encoding = getRequestEncoding(c.req); + const isWorkflowEnabled = actor.inspector.isWorkflowEnabled(); + const rawHistory = actor.inspector.getWorkflowHistory(); + const jsonResult = actor.inspector.getWorkflowHistoryJson(); + const body = serializeWithEncoding( + encoding, + jsonResult, + HTTP_INSPECTOR_WORKFLOW_HISTORY_RESPONSE_VERSIONED, + CLIENT_PROTOCOL_CURRENT_VERSION, + HttpInspectorWorkflowHistoryResponseSchema, + (v) => v, + (_v) => ({ + history: rawHistory, + isWorkflowEnabled, + }), + ); + return c.body(body as Uint8Array as any, 200, { + "Content-Type": contentTypeForEncoding(encoding), + }); }); router.get("/inspector/summary", async (c) => { @@ -341,6 +395,12 @@ export function createActorRouter( ); }); + router.get("/queue", async (c) => { + return handleQueueRequest(c, actorDriver); + }); + + router.get("/"); + router.all("/request/*", async (c) => { // 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 diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/mod.browser.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/mod.browser.ts index c18bcb704c..8241b1820b 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/mod.browser.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/mod.browser.ts @@ -6,3 +6,4 @@ export { decodeWorkflowHistoryTransport, encodeWorkflowHistoryTransport, } from "./transport"; +export * from './types'; \ No newline at end of file diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/mod.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/mod.ts index 642fd09037..d7a7d67024 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/mod.ts @@ -1,4 +1,8 @@ export * from "../schemas/actor-inspector/mod"; export * from "../schemas/actor-inspector/versioned"; -export { decodeWorkflowHistoryTransport, encodeWorkflowHistoryTransport } from "./transport"; export type { WorkflowHistory as TransportWorkflowHistory } from "../schemas/transport/mod"; +export { + decodeWorkflowHistoryTransport, + encodeWorkflowHistoryTransport, +} from "./transport"; +export * from "./types"; diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/types.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/types.ts new file mode 100644 index 0000000000..748746e102 --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/types.ts @@ -0,0 +1,27 @@ + +export type DatabaseColumn = { + cid: number; + name: string; + type: string; + notnull: boolean; + dflt_value: string | null; + pk: boolean | null; +}; + +export type DatabaseForeignKey = { + id: number; + table: string; + from: string; + to: string; +}; + +export type DatabaseTableInfo = { + table: { schema: string; name: string; type: string }; + columns: DatabaseColumn[]; + foreignKeys: DatabaseForeignKey[]; + records: number; +}; + +export type DatabaseSchema = { + tables: DatabaseTableInfo[]; +}; \ No newline at end of file diff --git a/rivetkit-typescript/packages/rivetkit/src/schemas/client-protocol-zod/mod.ts b/rivetkit-typescript/packages/rivetkit/src/schemas/client-protocol-zod/mod.ts index 27cba64f43..723b91f9e3 100644 --- a/rivetkit-typescript/packages/rivetkit/src/schemas/client-protocol-zod/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/schemas/client-protocol-zod/mod.ts @@ -116,3 +116,32 @@ export const HttpResolveResponseSchema = z.object({ actorId: z.string(), }); export type HttpResolveResponse = z.infer; + +// MARK: HTTP Inspector Queue +export const HttpInspectorQueueMessageSummarySchema = z.object({ + id: z.number(), + name: z.string(), + createdAtMs: z.number(), +}); +export type HttpInspectorQueueMessageSummary = z.infer< + typeof HttpInspectorQueueMessageSummarySchema +>; + +export const HttpInspectorQueueResponseSchema = z.object({ + size: z.number(), + maxSize: z.number(), + truncated: z.boolean(), + messages: z.array(HttpInspectorQueueMessageSummarySchema), +}); +export type HttpInspectorQueueResponse = z.infer< + typeof HttpInspectorQueueResponseSchema +>; + +// MARK: HTTP Inspector Workflow History +export const HttpInspectorWorkflowHistoryResponseSchema = z.object({ + history: z.unknown().nullable(), + isWorkflowEnabled: z.boolean(), +}); +export type HttpInspectorWorkflowHistoryResponse = z.infer< + typeof HttpInspectorWorkflowHistoryResponseSchema +>; diff --git a/rivetkit-typescript/packages/rivetkit/src/schemas/client-protocol/versioned.ts b/rivetkit-typescript/packages/rivetkit/src/schemas/client-protocol/versioned.ts index b5b7e6508e..cfe7e2daae 100644 --- a/rivetkit-typescript/packages/rivetkit/src/schemas/client-protocol/versioned.ts +++ b/rivetkit-typescript/packages/rivetkit/src/schemas/client-protocol/versioned.ts @@ -2,8 +2,9 @@ import { createVersionedDataHandler } from "vbare"; import * as v1 from "../../../dist/schemas/client-protocol/v1"; import * as v2 from "../../../dist/schemas/client-protocol/v2"; import * as v3 from "../../../dist/schemas/client-protocol/v3"; +import * as v4 from "../../../dist/schemas/client-protocol/v4"; -export const CURRENT_VERSION = 3; +export const CURRENT_VERSION = 4; // Converter from v1 to v2: Remove connectionToken from Init message const v1ToV2 = (v1Data: v1.ToClient): v2.ToClient => { @@ -54,7 +55,17 @@ const v3ToV2 = (v3Data: v3.ToClient): v2.ToClient => { return v3Data as unknown as v2.ToClient; }; -// ToServer identity converters (ToServer is identical across v1, v2, and v3) +// Converter from v3 to v4: No changes needed for ToClient +const v3ToV4 = (v3Data: v3.ToClient): v4.ToClient => { + return v3Data as unknown as v4.ToClient; +}; + +// Converter from v4 to v3: No changes needed for ToClient +const v4ToV3 = (v4Data: v4.ToClient): v3.ToClient => { + return v4Data as unknown as v3.ToClient; +}; + +// ToServer identity converters (ToServer is identical across v1, v2, v3, and v4) const v1ToServerV2 = (v1Data: v1.ToServer): v2.ToServer => { return v1Data as unknown as v2.ToServer; }; @@ -71,7 +82,15 @@ const v2ToServerV1 = (v2Data: v2.ToServer): v1.ToServer => { return v2Data as unknown as v1.ToServer; }; -export const TO_SERVER_VERSIONED = createVersionedDataHandler({ +const v3ToServerV4 = (v3Data: v3.ToServer): v4.ToServer => { + return v3Data as unknown as v4.ToServer; +}; + +const v4ToServerV3 = (v4Data: v4.ToServer): v3.ToServer => { + return v4Data as unknown as v3.ToServer; +}; + +export const TO_SERVER_VERSIONED = createVersionedDataHandler({ deserializeVersion: (bytes, version) => { switch (version) { case 1: @@ -80,6 +99,8 @@ export const TO_SERVER_VERSIONED = createVersionedDataHandler({ return v2.decodeToServer(bytes); case 3: return v3.decodeToServer(bytes); + case 4: + return v4.decodeToServer(bytes); default: throw new Error(`Unknown version ${version}`); } @@ -92,15 +113,17 @@ export const TO_SERVER_VERSIONED = createVersionedDataHandler({ return v2.encodeToServer(data as v2.ToServer); case 3: return v3.encodeToServer(data as v3.ToServer); + case 4: + return v4.encodeToServer(data as v4.ToServer); default: throw new Error(`Unknown version ${version}`); } }, - deserializeConverters: () => [v1ToServerV2, v2ToServerV3], - serializeConverters: () => [v3ToServerV2, v2ToServerV1], + deserializeConverters: () => [v1ToServerV2, v2ToServerV3, v3ToServerV4], + serializeConverters: () => [v4ToServerV3, v3ToServerV2, v2ToServerV1], }); -export const TO_CLIENT_VERSIONED = createVersionedDataHandler({ +export const TO_CLIENT_VERSIONED = createVersionedDataHandler({ deserializeVersion: (bytes, version) => { switch (version) { case 1: @@ -109,6 +132,8 @@ export const TO_CLIENT_VERSIONED = createVersionedDataHandler({ return v2.decodeToClient(bytes); case 3: return v3.decodeToClient(bytes); + case 4: + return v4.decodeToClient(bytes); default: throw new Error(`Unknown version ${version}`); } @@ -121,16 +146,18 @@ export const TO_CLIENT_VERSIONED = createVersionedDataHandler({ return v2.encodeToClient(data as v2.ToClient); case 3: return v3.encodeToClient(data as v3.ToClient); + case 4: + return v4.encodeToClient(data as v4.ToClient); default: throw new Error(`Unknown version ${version}`); } }, - deserializeConverters: () => [v1ToV2, v2ToV3], - serializeConverters: () => [v3ToV2, v2ToV1], + deserializeConverters: () => [v1ToV2, v2ToV3, v3ToV4], + serializeConverters: () => [v4ToV3, v3ToV2, v2ToV1], }); export const HTTP_ACTION_REQUEST_VERSIONED = - createVersionedDataHandler({ + createVersionedDataHandler({ deserializeVersion: (bytes, version) => { switch (version) { case 1: @@ -139,6 +166,8 @@ export const HTTP_ACTION_REQUEST_VERSIONED = return v2.decodeHttpActionRequest(bytes); case 3: return v3.decodeHttpActionRequest(bytes); + case 4: + return v4.decodeHttpActionRequest(bytes); default: throw new Error(`Unknown version ${version}`); } @@ -157,6 +186,10 @@ export const HTTP_ACTION_REQUEST_VERSIONED = return v3.encodeHttpActionRequest( data as v3.HttpActionRequest, ); + case 4: + return v4.encodeHttpActionRequest( + data as v4.HttpActionRequest, + ); default: throw new Error(`Unknown version ${version}`); } @@ -166,7 +199,7 @@ export const HTTP_ACTION_REQUEST_VERSIONED = }); export const HTTP_ACTION_RESPONSE_VERSIONED = - createVersionedDataHandler({ + createVersionedDataHandler({ deserializeVersion: (bytes, version) => { switch (version) { case 1: @@ -175,6 +208,8 @@ export const HTTP_ACTION_RESPONSE_VERSIONED = return v2.decodeHttpActionResponse(bytes); case 3: return v3.decodeHttpActionResponse(bytes); + case 4: + return v4.decodeHttpActionResponse(bytes); default: throw new Error(`Unknown version ${version}`); } @@ -193,6 +228,10 @@ export const HTTP_ACTION_RESPONSE_VERSIONED = return v3.encodeHttpActionResponse( data as v3.HttpActionResponse, ); + case 4: + return v4.encodeHttpActionResponse( + data as v4.HttpActionResponse, + ); default: throw new Error(`Unknown version ${version}`); } @@ -202,11 +241,13 @@ export const HTTP_ACTION_RESPONSE_VERSIONED = }); export const HTTP_QUEUE_SEND_REQUEST_VERSIONED = - createVersionedDataHandler({ + createVersionedDataHandler({ deserializeVersion: (bytes, version) => { switch (version) { case 3: return v3.decodeHttpQueueSendRequest(bytes); + case 4: + return v4.decodeHttpQueueSendRequest(bytes); default: throw new Error( `HttpQueueSendRequest only exists in version 3+, got version ${version}`, @@ -219,6 +260,10 @@ export const HTTP_QUEUE_SEND_REQUEST_VERSIONED = return v3.encodeHttpQueueSendRequest( data as v3.HttpQueueSendRequest, ); + case 4: + return v4.encodeHttpQueueSendRequest( + data as v4.HttpQueueSendRequest, + ); default: throw new Error( `HttpQueueSendRequest only exists in version 3+, got version ${version}`, @@ -230,11 +275,13 @@ export const HTTP_QUEUE_SEND_REQUEST_VERSIONED = }); export const HTTP_QUEUE_SEND_RESPONSE_VERSIONED = - createVersionedDataHandler({ + createVersionedDataHandler({ deserializeVersion: (bytes, version) => { switch (version) { case 3: return v3.decodeHttpQueueSendResponse(bytes); + case 4: + return v4.decodeHttpQueueSendResponse(bytes); default: throw new Error( `HttpQueueSendResponse only exists in version 3+, got version ${version}`, @@ -247,6 +294,10 @@ export const HTTP_QUEUE_SEND_RESPONSE_VERSIONED = return v3.encodeHttpQueueSendResponse( data as v3.HttpQueueSendResponse, ); + case 4: + return v4.encodeHttpQueueSendResponse( + data as v4.HttpQueueSendResponse, + ); default: throw new Error( `HttpQueueSendResponse only exists in version 3+, got version ${version}`, @@ -258,7 +309,7 @@ export const HTTP_QUEUE_SEND_RESPONSE_VERSIONED = }); export const HTTP_RESPONSE_ERROR_VERSIONED = - createVersionedDataHandler({ + createVersionedDataHandler({ deserializeVersion: (bytes, version) => { switch (version) { case 1: @@ -267,6 +318,8 @@ export const HTTP_RESPONSE_ERROR_VERSIONED = return v2.decodeHttpResponseError(bytes); case 3: return v3.decodeHttpResponseError(bytes); + case 4: + return v4.decodeHttpResponseError(bytes); default: throw new Error(`Unknown version ${version}`); } @@ -285,6 +338,10 @@ export const HTTP_RESPONSE_ERROR_VERSIONED = return v3.encodeHttpResponseError( data as v3.HttpResponseError, ); + case 4: + return v4.encodeHttpResponseError( + data as v4.HttpResponseError, + ); default: throw new Error(`Unknown version ${version}`); } @@ -294,7 +351,7 @@ export const HTTP_RESPONSE_ERROR_VERSIONED = }); export const HTTP_RESOLVE_RESPONSE_VERSIONED = - createVersionedDataHandler({ + createVersionedDataHandler({ deserializeVersion: (bytes, version) => { switch (version) { case 1: @@ -303,6 +360,8 @@ export const HTTP_RESOLVE_RESPONSE_VERSIONED = return v2.decodeHttpResolveResponse(bytes); case 3: return v3.decodeHttpResolveResponse(bytes); + case 4: + return v4.decodeHttpResolveResponse(bytes); default: throw new Error(`Unknown version ${version}`); } @@ -321,6 +380,10 @@ export const HTTP_RESOLVE_RESPONSE_VERSIONED = return v3.encodeHttpResolveResponse( data as v3.HttpResolveResponse, ); + case 4: + return v4.encodeHttpResolveResponse( + data as v4.HttpResolveResponse, + ); default: throw new Error(`Unknown version ${version}`); } @@ -328,3 +391,59 @@ export const HTTP_RESOLVE_RESPONSE_VERSIONED = deserializeConverters: () => [], serializeConverters: () => [], }); + +export const HTTP_INSPECTOR_QUEUE_RESPONSE_VERSIONED = + createVersionedDataHandler({ + deserializeVersion: (bytes, version) => { + switch (version) { + case 4: + return v4.decodeHttpInspectorQueueResponse(bytes); + default: + throw new Error( + `HttpInspectorQueueResponse only exists in version 4+, got version ${version}`, + ); + } + }, + serializeVersion: (data, version) => { + switch (version) { + case 4: + return v4.encodeHttpInspectorQueueResponse( + data as v4.HttpInspectorQueueResponse, + ); + default: + throw new Error( + `HttpInspectorQueueResponse only exists in version 4+, got version ${version}`, + ); + } + }, + deserializeConverters: () => [], + serializeConverters: () => [], + }); + +export const HTTP_INSPECTOR_WORKFLOW_HISTORY_RESPONSE_VERSIONED = + createVersionedDataHandler({ + deserializeVersion: (bytes, version) => { + switch (version) { + case 4: + return v4.decodeHttpInspectorWorkflowHistoryResponse(bytes); + default: + throw new Error( + `HttpInspectorWorkflowHistoryResponse only exists in version 4+, got version ${version}`, + ); + } + }, + serializeVersion: (data, version) => { + switch (version) { + case 4: + return v4.encodeHttpInspectorWorkflowHistoryResponse( + data as v4.HttpInspectorWorkflowHistoryResponse, + ); + default: + throw new Error( + `HttpInspectorWorkflowHistoryResponse only exists in version 4+, got version ${version}`, + ); + } + }, + deserializeConverters: () => [], + serializeConverters: () => [], + });