Skip to content

feat: add database inspector#4211

Closed
jog1t wants to merge 1 commit into02-11-feat_add_xyflowfrom
02-16-feat_add_database_inspector
Closed

feat: add database inspector#4211
jog1t wants to merge 1 commit into02-11-feat_add_xyflowfrom
02-16-feat_add_database_inspector

Conversation

@jog1t
Copy link
Contributor

@jog1t jog1t commented Feb 16, 2026

Description

Implemented the database inspector feature for actors, allowing users to browse and query SQLite databases within the actor inspector UI. This feature enables viewing table schemas, browsing data with pagination, and exploring foreign key relationships.

The implementation includes:

  • New database table component with column resizing and sorting capabilities
  • Database schema retrieval API in the actor inspector
  • Table row pagination with configurable limits
  • Foreign key relationship visualization
  • UI integration in the actor details panel

This feature requires RivetKit version 2.0.42 or higher, which includes the new actor inspector protocol v3 with database inspection capabilities.

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Tested with various SQLite database schemas and data types, including tables with foreign key relationships. Verified pagination works correctly with large datasets and that the UI properly handles different column types.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@railway-app railway-app bot temporarily deployed to rivet-frontend / rivet-pr-4211 February 16, 2026 21:42 Destroyed
@railway-app
Copy link

railway-app bot commented Feb 16, 2026

🚅 Deployed to the rivet-pr-4211 environment in rivet-frontend

Service Status Web Updated (UTC)
website 😴 Sleeping (View Logs) Web Feb 19, 2026 at 11:04 am
mcp-hub ✅ Success (View Logs) Web Feb 18, 2026 at 12:22 am
frontend-cloud ❌ Build Failed (View Logs) Web Feb 16, 2026 at 9:43 pm
frontend-inspector ❌ Build Failed (View Logs) Web Feb 16, 2026 at 9:43 pm
ladle ❌ Build Failed (View Logs) Web Feb 16, 2026 at 9:43 pm

This was referenced Feb 16, 2026
Copy link
Contributor Author

jog1t commented Feb 16, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label merge-queue to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@jog1t jog1t marked this pull request as ready for review February 16, 2026 21:43
@graphite-app
Copy link
Contributor

graphite-app bot commented Feb 16, 2026

Graphite Automations

"Test" took an action on this PR • (02/16/26)

1 assignee was added to this PR based on Kacper Wojciechowski's automation.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 16, 2026

More templates

@rivetkit/cloudflare-workers

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/cloudflare-workers@4211

@rivetkit/framework-base

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/framework-base@4211

@rivetkit/next-js

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/next-js@4211

@rivetkit/react

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/react@4211

rivetkit

pnpm add https://pkg.pr.new/rivet-dev/rivet/rivetkit@4211

@rivetkit/sql-loader

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/sql-loader@4211

@rivetkit/sqlite-vfs

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/sqlite-vfs@4211

@rivetkit/traces

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/traces@4211

@rivetkit/workflow-engine

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/workflow-engine@4211

@rivetkit/virtual-websocket

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/virtual-websocket@4211

@rivetkit/engine-runner

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/engine-runner@4211

@rivetkit/engine-runner-protocol

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/engine-runner-protocol@4211

commit: 6bbc16d

@claude
Copy link

claude bot commented Feb 16, 2026

Pull Request Review: Database Inspector Feature

This PR adds a database inspector for browsing SQLite databases inside Rivet Actors. The overall structure is sound, but there are several important issues to address before merging.


Critical Issues

1. Column Schema Uses Sample Row Instead of PRAGMA — Empty Types and No Foreign Keys

getDatabaseSchema() in actor-inspector.ts infers column names by sampling a single row:

const sample = await db.execute(`SELECT * FROM ${quoted} LIMIT 1`);
const columnNames = sample?.[0] ? Object.keys(sample[0]) : [];

columns: columnNames.map((name, cid) => ({
    cid, name,
    type: "",       // always empty
    notnull: 0,      // always zero
    dflt_value: null,
    pk: 0,           // always zero
})),
foreignKeys: [],    // always empty

Two problems with this approach:

  • Empty tables show 0 columns. If the table has no rows, sample[0] is undefined and columnNames is [], so the schema shows no columns at all.
  • Column type, NOT NULL, PK, and foreign key info are never populated. The DatabaseColumn type and the frontend UI both reference col.type and ForeignKey badges, but these are always empty/zero.

SQLite provides exactly the right pragmas for this:

PRAGMA table_info("tablename");          -- cid, name, type, notnull, dflt_value, pk
PRAGMA foreign_key_list("tablename");    -- id, seq, table, from, to, ...

The backend should use these instead of the sample-row approach.


2. Indentation Bug in #setupDatabase() try/catch

The new catch block in instance/mod.ts has severely inconsistent indentation that makes the logic ambiguous. Based on the diff, throw error appears at the same indentation level as the if statement (outside it), making the non-Error wrapping code unreachable dead code.

The intended structure should be clearly:

} catch (error) {
    if (error instanceof Error) {
        this.#rLog.error({ msg: "database setup failed", error: stringifyError(error) });
        throw error;
    }
    const wrappedError = new Error(`Database setup failed: ${String(error)}`);
    this.#rLog.error({ msg: "database setup failed with non-Error object", error: String(error) });
    throw wrappedError;
}

Please verify the brace positions are correct and clean up the indentation.


Moderate Issues

3. Database Tab Visible for All Actors Regardless of isDatabaseEnabled

In actors-actor-details.tsx, the Database tab is unconditionally visible for all actors. The actorDatabaseEnabledQueryOptions mechanism exists to track whether the actor has database enabled (populated via isDatabaseEnabled in the Init message), but the tab does not use it. Actors without a configured database will see the Database tab and an empty "No tables found" state with no explanation.

The tab should either be conditionally shown or the empty state should explain that this actor has no database configured.

4. DbMutex Duplicated Across Two Files

An identical DbMutex class is implemented in both src/db/drizzle/mod.ts and src/db/mod.ts. This should be extracted to a shared utility (e.g., src/db/mutex.ts) and imported in both places.

5. actorDatabaseEnabledQueryOptions Returns a Never-Resolving Promise

The queryFn is () => new Promise<boolean>(() => {}) which never resolves. While data is set via setQueryData when the Init message arrives, any consumer that tries to await this before Init will hang. A comment explaining the intentional pattern here would help, and a fallback (resolve to false after a timeout) would improve robustness.


Minor Issues

6. Logging Convention Violations

Per project conventions, log messages should be lowercase. In handler.ts:

"Failed to get database schema"     →  "failed to get database schema"
"Failed to get database table rows" →  "failed to get database table rows"

7. Commented-Out Code in actor-traces.tsx

The ToggleGroup view switcher is commented out with {/* */}. Commented-out code should be removed or replaced with a tracked TODO.

8. staleTime: 0 for Database Schema Query

staleTime: 0 means the schema is re-fetched on every focus/mount. Since the schema rarely changes mid-session, a short staleTime (e.g., 30 seconds) would reduce unnecessary WebSocket round-trips.


Positive Notes

  • The DbMutex addition to serialize wa-sqlite access is the right fix for concurrent access issues.
  • The thenable detection fix (typeof maybeThenable.then === "function" instead of instanceof Promise) is more robust and handles cross-realm promises correctly.
  • The escapeDoubleQuotes helper correctly prevents SQL identifier injection.
  • The #activeKeepAwakeCount guards around action handling properly prevent premature sleep during long-running actions.
  • The versioned protocol upgrade (v2→v3) with downgrade converters looks correct.
  • The MAX 500 row cap in getDatabaseTableRows is a sensible guard.

Comment on lines +152 to +171
async getDatabaseTableRows(
table: string,
limit: number,
offset: number,
): Promise<ArrayBuffer> {
if (!this.isDatabaseEnabled()) {
throw new actorErrors.DatabaseNotEnabled();
}

const db = this.actor.db;
const safeLimit = Math.max(0, Math.min(Math.floor(limit), 500));
const safeOffset = Math.max(0, Math.floor(offset));
const quoted = `"${escapeDoubleQuotes(table)}"`;
const result = await db.execute(
`SELECT * FROM ${quoted} LIMIT ? OFFSET ?`,
safeLimit,
safeOffset,
);
return bufferToArrayBuffer(cbor.encode(result));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security vulnerability: getDatabaseTableRows() accepts any table name from the client without validation. Unlike getDatabaseSchema() which filters out system tables (line 115), this method allows querying any table including SQLite system tables like sqlite_master, sqlite_sequence, etc.

Impact: Malicious clients could read sensitive system metadata or internal tables.

Fix: Add table name validation:

async getDatabaseTableRows(
    table: string,
    limit: number,
    offset: number,
): Promise<ArrayBuffer> {
    if (!this.isDatabaseEnabled()) {
        throw new actorErrors.DatabaseNotEnabled();
    }

    // Validate table name - reject system and internal tables
    if (table.startsWith('sqlite_') || table.startsWith('__drizzle_')) {
        throw new Error('Cannot query system or internal tables');
    }

    const db = this.actor.db;
    const safeLimit = Math.max(0, Math.min(Math.floor(limit), 500));
    const safeOffset = Math.max(0, Math.floor(offset));
    const quoted = `"${escapeDoubleQuotes(table)}"`;
    const result = await db.execute(
        `SELECT * FROM ${quoted} LIMIT ? OFFSET ?`,
        safeLimit,
        safeOffset,
    );
    return bufferToArrayBuffer(cbor.encode(result));
}
Suggested change
async getDatabaseTableRows(
table: string,
limit: number,
offset: number,
): Promise<ArrayBuffer> {
if (!this.isDatabaseEnabled()) {
throw new actorErrors.DatabaseNotEnabled();
}
const db = this.actor.db;
const safeLimit = Math.max(0, Math.min(Math.floor(limit), 500));
const safeOffset = Math.max(0, Math.floor(offset));
const quoted = `"${escapeDoubleQuotes(table)}"`;
const result = await db.execute(
`SELECT * FROM ${quoted} LIMIT ? OFFSET ?`,
safeLimit,
safeOffset,
);
return bufferToArrayBuffer(cbor.encode(result));
}
async getDatabaseTableRows(
table: string,
limit: number,
offset: number,
): Promise<ArrayBuffer> {
if (!this.isDatabaseEnabled()) {
throw new actorErrors.DatabaseNotEnabled();
}
// Validate table name - reject system and internal tables
if (table.startsWith('sqlite_') || table.startsWith('__drizzle_')) {
throw new Error('Cannot query system or internal tables');
}
const db = this.actor.db;
const safeLimit = Math.max(0, Math.min(Math.floor(limit), 500));
const safeOffset = Math.max(0, Math.floor(offset));
const quoted = `"${escapeDoubleQuotes(table)}"`;
const result = await db.execute(
`SELECT * FROM ${quoted} LIMIT ? OFFSET ?`,
safeLimit,
safeOffset,
);
return bufferToArrayBuffer(cbor.encode(result));
}

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +1 to +20
import { Button, Flex, ScrollArea, WithTooltip } from "@/components";
import {
faChevronLeft,
faChevronRight,
faRefresh,
faTable,
faTableCells,
Icon,
} from "@rivet-gg/icons";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { ShimmerLine } from "../shimmer-line";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { useActorInspector } from "./actor-inspector-context";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports need to be sorted according to Biome's rules. Run 'biome check --write .' to automatically fix import sorting.

Spotted by Graphite Agent (based on CI logs)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@@ -193,7 +193,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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the build script to run the schema generation step first by changing it to: "build": "npm run build:schema && 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",

Spotted by Graphite Agent (based on CI logs)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment on lines 245 to 247
},
close: async () => {
await waDb.close();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition: close() is not protected by the mutex while execute() is. If close() is called while a query is running, it will cause database corruption or "file is not a database" errors.

close: async () => {
    await mutex.run(() => waDb.close());
},

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@graphite-app
Copy link
Contributor

graphite-app bot commented Feb 19, 2026

Merge activity

  • Feb 19, 11:17 PM UTC: NathanFlurry added this pull request to the Graphite merge queue.
  • Feb 19, 11:18 PM UTC: CI is running for this pull request on a draft pull request (#4244) due to your merge queue CI optimization settings.
  • Feb 19, 11:19 PM UTC: Merged by the Graphite merge queue via draft PR: #4244.

graphite-app bot pushed a commit that referenced this pull request Feb 19, 2026
# Description

Implemented the database inspector feature for actors, allowing users to browse and query SQLite databases within the actor inspector UI. This feature enables viewing table schemas, browsing data with pagination, and exploring foreign key relationships.

The implementation includes:
- New database table component with column resizing and sorting capabilities
- Database schema retrieval API in the actor inspector
- Table row pagination with configurable limits
- Foreign key relationship visualization
- UI integration in the actor details panel

This feature requires RivetKit version 2.0.42 or higher, which includes the new actor inspector protocol v3 with database inspection capabilities.

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

Tested with various SQLite database schemas and data types, including tables with foreign key relationships. Verified pagination works correctly with large datasets and that the UI properly handles different column types.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
@graphite-app graphite-app bot closed this Feb 19, 2026
@graphite-app graphite-app bot deleted the 02-16-feat_add_database_inspector branch February 19, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant