Skip to content

feat(playground): add a DatabasePlugin example - #529

Open
ditadi wants to merge 1 commit into
stack/database-mvp/04-mutations-hooksfrom
stack/database-mvp/05-integration
Open

feat(playground): add a DatabasePlugin example#529
ditadi wants to merge 1 commit into
stack/database-mvp/04-mutations-hooksfrom
stack/database-mvp/05-integration

Conversation

@ditadi

@ditadi ditadi commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Stack

Each PR targets the one above it, so the diff shown here is only the delta on top of #528. Review in order.

What

The last PR in the series. No SDK behaviour changes here: this is the dev-playground page that runs the whole plugin against a live Postgres, plus one integration test that asserts the assembled stack from the outside.

The page is a small board/annotation app — three tables, a two-edge include, all three hook points, and a private column — chosen so every claim the earlier PRs make is visible rather than described.

Changes

The schema (config/database/schema.ts)

boards → notes → note_events, which is enough to exercise a nested include. notes.author_email is .private(), so it is writable from server code and refused on the wire — the page shows both halves of that.

The plugin does not create tables. The app expects them to exist, and the page says so when they do not.

The registration (server/index.ts)

Registered conditionally on LAKEBASE_ENDPOINT, the same way lakebase() already is in this file, so the playground still boots without a database configured.

crudRoutes enables boards and notes but not note_events: the audit trail is written by a hook and stays server-only, which is the exposure decision from #527 shown rather than explained.

The notes hooks use all three points:

  • beforeCreate runs an agent to redact personal names from the body before the insert, so the unredacted text never reaches the table, and stamps the private author_email. This is the intended shape of the hook API — a hook is server code and can call any other plugin. The comment in the file is explicit that it also holds the transaction open while the model answers, which is the trade being made.
  • serialize ships a 120-character preview on the list route and the whole body on the detail route.
  • afterCreate writes the note_events row inside the insert's own transaction, so the note and the event describing it commit together or not at all.

Two hand-written routes (server/index.ts)

Deliberately showing what a generated route is not for: one aggregate via the tagged sql template (a generated read shapes rows, it does not group them), and one nested include reached through the typed client.

The page (client/src/routes/database.route.tsx)

A board explorer over the generated routes, a panel that traces a write through its hooks, and a probe that fires the requests the plugin is supposed to refuse — writing a private column, an unknown field, an over-large limit — and shows the status and body that come back.

One integration test (plugins/database/tests/mvp.integration.test.ts)

Drives the plugin through its real assembly with only the driver mocked: config in, routes registered, request in, response out. It is the test that would catch a regression the unit suites each miss individually.

Verification

  • pnpm vitest run — 4166 passing, 1 skipped
  • pnpm -r typecheck — clean across all packages
  • pnpm run generate:types, pnpm run sync:template, and pnpm run docs:build produce no drift
  • Exercised manually against a live Lakebase instance: reads, writes, the hook chain, the redaction agent, the transactional rollback, and each refusal in the probe

Exercise the typed client, the generated routes, and the three hook points against a live
Postgres, and cover the assembled stack with one integration test.

Signed-off-by: ditadi <victordperd@gmail.com>
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