Skip to content

feat: SQLite event store, env scoping, and interactive TUI#60

Merged
dylanjha merged 7 commits intomainfrom
dj/sqlite
Mar 20, 2026
Merged

feat: SQLite event store, env scoping, and interactive TUI#60
dylanjha merged 7 commits intomainfrom
dj/sqlite

Conversation

@dylanjha
Copy link
Copy Markdown
Contributor

@dylanjha dylanjha commented Mar 19, 2026

Summary

  • Replace JSON file storage with bun:sqlite (built into the binary, no dependencies)
  • Removes the 100-event cap — events are now stored indefinitely per environment.
  • Environment ID in config: Fetch and store environmentId from Mux /whoami during login. All event queries are scoped to the current environment. This will gracefully handle if environments are renamed because we are no longer using the name as the primary key
  • Interactive TUI (mux webhooks events browse)**: Browse, filter by event type, and replay stored events in a terminal UI. Side-by-side detail view shows actions alongside the JSON payload.
  • --forward-to persistence: browse and replay remember the last --forward-to URL per environment. listen saves the URL but requires it explicitly each run. This makes replaying easier b/c you don't have to explicitly pass the forward URL
Screen.Recording.2026-03-19.at.11.37.39.AM.mov

Test plan

  • pnpm run check passes
  • pnpm run test passes (666 tests)
  • tsc --noEmit passes
  • Manual: mux webhooks listen --forward-to http://localhost:3000/webhooks captures events
  • Manual: mux webhooks events browse shows events, filters, and replays work
  • Manual: mux webhooks events replay --all forwards stored events
  • Manual: Verify --forward-to URL is remembered across browse/replay invocations

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk due to a full rewrite of local webhook event persistence (JSON→SQLite) and updated environment-scoped querying, which could affect event capture/replay behavior and existing stored data migration expectations.

Overview
Switches local webhook event storage from a capped events.json file to an indexed SQLite DB (events.db, WAL) and updates the events API to be environment-scoped (environmentId required for list/get/all, plus new type-filter helpers and closeDb for tests).

Updates login/validation to fetch and persist the Mux environmentId (via /system/v1/whoami), and extends config with environmentId + a per-environment persisted forwardUrl plus updateEnvironment().

Adds mux webhooks events browse, an interactive TUI to list events, filter by type, inspect payloads, and optionally replay/forward events; events list, events replay, and webhooks listen are updated to use the new environment-scoped store and to remember --forward-to per environment.

Written by Cursor Bugbot for commit cf89051. This will update automatically on new commits. Configure here.

dylanjha and others added 7 commits March 6, 2026 13:13
Add optional `environmentId` field to the Environment config interface.
Update `validateCredentials` to call `/whoami` instead of `assets.list`,
which both validates credentials and retrieves the Mux environment ID in
a single request. The ID is persisted to the config on login.

Existing configs without `environmentId` remain valid (field is optional).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite the webhook events store to use bun:sqlite instead of a JSON
file. Key changes:

- Schema: events(id PK, type, timestamp, environment_id, payload)
- Indexes on timestamp and type for efficient queries
- WAL mode for safe concurrent access from multiple CLI sessions
- INSERT OR IGNORE for deduplication (replaces manual array check)
- No event cap (previously limited to 100)
- Operations are now synchronous (bun:sqlite is sync)

Database stored at ~/.local/share/mux/events.db (XDG data dir).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use environmentId from config when storing events, falling back to the
environment name for backwards compatibility with pre-migration configs.
Remove unnecessary await since SQLite operations are synchronous.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make environmentId a required parameter on listEvents, getAllEvents, and
getEventById — the CLI always has an environment context. The events
list and replay commands resolve the current environment and pass its ID
to scope all queries.

Falls back to environment name for configs predating the environmentId
field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `mux webhooks events browse` command with an interactive terminal UI
for exploring stored webhook events. Features:

- Browse events with keyboard navigation
- Filter events by type (f to filter, c to clear)
- View full event payload
- Copy payload or event ID to clipboard
- Forward/replay events to a local URL (--forward-to)

Uses the same TUI components (SelectList, ActionMenu) as assets manage.
Adds getEventTypes and listEventsByType queries to the events store.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Store the last-used --forward-to URL in the environment config so it
doesn't need to be re-entered on every invocation. Auto-recall applies
to browse and replay commands. Listen always requires --forward-to
explicitly but saves it for the other commands to use.

Add updateEnvironment helper for partial config updates and forwardUrl
field to the Environment interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Primary key missing environment_id breaks environment scoping
    • Updated the events schema to use a composite primary key of (id, environment_id), allowing identical event IDs across environments while preserving per-environment deduplication.

Create PR

Or push these changes by commenting:

@cursor push 2ce65a3978
Preview (2ce65a3978)
diff --git a/src/lib/events-store.test.ts b/src/lib/events-store.test.ts
--- a/src/lib/events-store.test.ts
+++ b/src/lib/events-store.test.ts
@@ -74,6 +74,16 @@
     expect(events).toHaveLength(1);
   });
 
+  test('appendEvent allows same event ID in different environments', () => {
+    appendEvent(makeEvent('evt_shared', 'video.asset.ready', 'env-aaa'));
+    appendEvent(makeEvent('evt_shared', 'video.asset.ready', 'env-bbb'));
+
+    expect(getEventById('evt_shared', 'env-aaa')).toBeDefined();
+    expect(getEventById('evt_shared', 'env-bbb')).toBeDefined();
+    expect(listEvents('env-aaa')).toHaveLength(1);
+    expect(listEvents('env-bbb')).toHaveLength(1);
+  });
+
   test('appendEvent does not cap at 100 events', () => {
     for (let i = 0; i < 150; i++) {
       appendEvent(makeEvent(`evt_${i}`));

diff --git a/src/lib/events-store.ts b/src/lib/events-store.ts
--- a/src/lib/events-store.ts
+++ b/src/lib/events-store.ts
@@ -23,11 +23,12 @@
   db.run('PRAGMA journal_mode = WAL');
   db.run(`
     CREATE TABLE IF NOT EXISTS events (
-      id          TEXT PRIMARY KEY,
+      id          TEXT NOT NULL,
       type        TEXT NOT NULL,
       timestamp   TEXT NOT NULL,
       environment_id TEXT NOT NULL,
-      payload     TEXT NOT NULL
+      payload     TEXT NOT NULL,
+      PRIMARY KEY (id, environment_id)
     )
   `);
   db.run(

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment thread src/lib/events-store.ts
environment_id TEXT NOT NULL,
payload TEXT NOT NULL
)
`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Primary key missing environment_id breaks environment scoping

Medium Severity

The table uses id TEXT PRIMARY KEY but every query filters by environment_id, and getEventById requires both id and environmentId. The INSERT OR IGNORE silently drops an event if a different environment already stored an event with the same id. That second environment's queries will then never find the event. The primary key needs to be the composite (id, environment_id) to match the scoping model.

Additional Locations (1)
Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There will not be any case where the same event ID exists between two environments. So I think we don't need the composite primary key

@dylanjha dylanjha merged commit c257102 into main Mar 20, 2026
9 checks passed
@dylanjha dylanjha deleted the dj/sqlite branch March 20, 2026 03:53
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