Repository navigation
feat(appkit): operations-first API POC for the database plugin (defineQuery/defineMutation/definePolicy) - #682
MarioCadenas wants to merge 5 commits into
Conversation
Add defineQuery/defineMutation/definePolicy and a database({ routes })
option, auto-discovered from config/database/routes/*.ts. Every route runs
one pipeline: actor from x-forwarded-user, zod input validation, strict
authorize (only literal true allows; throw or non-decision is a 500),
then execute with the service-principal db. Mutations run authorize and
execute in one transaction. Routes that overlap generated CRUD fail setup.
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
orders.list and orders.approve under config/database/routes, with an example team_members permission table, setup SQL, and tests for team scoping, approval limits, non-pending state, and concurrent approval. Generated CRUD stays on for the note tables only. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 1.3 MB (+12 KB) | 462 KB (+4.2 KB) |
| Type declarations | 468 KB (+4.3 KB) | 170 KB (+1.6 KB) |
| Source maps | 2.5 MB (+24 KB) | 864 KB (+8.0 KB) |
| Other | 11 KB | 3.7 KB |
| Total | 4.3 MB (+40 KB) | 1.5 MB (+14 KB) |
Per-entry composition (own code — deps external (as shipped))
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
. |
106 KB | 2.5 KB | 108 KB | external | 343 KB |
./beta |
104 KB (+1.8 KB) | 485 B | 105 KB (+1.8 KB) | external | 314 KB (+5.2 KB) |
./testing |
45 KB | 32 KB | 77 KB | external | 223 KB |
./tsdown |
520 B | 0 B | 520 B | external | 813 B |
./type-generator |
26 KB | 0 B | 26 KB | external | 74 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
. |
index.js |
initial | 101 KB |
. |
utils.js |
initial | 4.6 KB |
. |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./beta |
beta.js |
initial | 84 KB |
./beta |
modular.js |
initial | 6.0 KB |
./beta |
stream-manager.js |
initial | 5.9 KB |
./beta |
service-context.js |
initial | 4.2 KB |
./beta |
databricks.js |
initial | 3.3 KB |
./beta |
client.js |
initial | 647 B |
./beta |
index.js |
initial | 20 B |
./beta |
supervisor-api.js |
lazy | 192 B |
./beta |
databricks.js |
lazy | 178 B |
./beta |
index.js |
lazy | 115 B |
./testing |
manifest.js |
initial | 32 KB |
./testing |
index.js |
initial | 10 KB |
./testing |
wide-event-emitter.js |
initial | 2.9 KB |
./testing |
index.js |
lazy | 28 KB |
./testing |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./testing |
utils.js |
lazy | 1.8 KB |
./tsdown |
index.js |
initial | 520 B |
./type-generator |
index.js |
initial | 26 KB |
@databricks/appkit-ui
npm tarball (packed): 397 KB — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 429 KB | 147 KB |
| Type declarations | 267 KB | 98 KB |
| Source maps | 853 KB | 286 KB |
| CSS | 16 KB | 3.2 KB |
| Total | 1.5 MB | 534 KB |
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
./js |
5.3 KB | 49 KB | 55 KB | 208 KB | 14 KB |
./js/beta |
2.1 KB | 0 B | 2.1 KB | 0 B | 5.0 KB |
./react |
433 KB | 49 KB | 481 KB | 1.3 MB | 177 KB |
./react/beta |
4.9 KB | 0 B | 4.9 KB | 0 B | 12 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
./js |
index.js |
initial | 5.2 KB |
./js |
chunk |
initial | 120 B |
./js |
apache-arrow |
lazy | 49 KB |
./js/beta |
beta.js |
initial | 2.1 KB |
./react |
index.js |
initial | 431 KB |
./react |
tslib |
initial | 2.1 KB |
./react |
apache-arrow |
lazy | 49 KB |
./react/beta |
beta.js |
initial | 4.9 KB |
|
Review 1 · Fail · 1 critical, 2 major Does the change do what the task asked? No. Gate: major and worse must be fixed. The main vertical slice works, including real PostgreSQL transactions, scoped reads, denials, rollback and concurrent approvals. It does not fully meet the task: authorization facts are not protected against concurrent changes, Express-equivalent route collisions are missed, and the concurrency test passes despite losing the successful write in its fake. The full suite, lint/format checks and recursive typechecking pass; the repository was left unchanged. Required
|
🤖 AppKit PR bot🔬 Run evalsStart an eval for this PR from the evals-monitor app: Go to Evals Monitor → 📦 Try this PR's app templateScaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh run download 39601044161 -R databricks/appkit -n appkit-template-0.89.0-pr.a76c20b-work-3-poc-operations-first-api-defin-682 -D appkit-pr-682 \
&& unzip -o "appkit-pr-682/appkit-template-0.89.0-pr.a76c20b-work-3-poc-operations-first-api-defin-682.zip" -d "appkit-pr-682" \
&& databricks apps init --template "appkit-pr-682"The template pins |
Letter case and a trailing slash no longer tell two routes apart, so GET /Notes or GET /notes/ can't shadow generated CRUD or each other. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
canApproveOrder now reads the order FOR UPDATE and the membership FOR SHARE inside the mutation's transaction, so total, team, creator and role can't change between the decision and the commit. The fake transaction undoes only its own writes, and the concurrency test asserts the winning approval persists. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
|
Review 2 · Pass Does the change do what the task asked? Yes. Gate: major and worse must be fixed. The POC meets the agreed scope, and F1, F2 and F3 are fixed. Real PostgreSQL and HTTP checks confirm stable authorization facts, safe concurrent approvals and expected request outcomes; the corrected concurrency test detects the old rollback bug. No remaining findings or critical regressions were found in this round. No findings. |
|
Review 3 · Fail · 3 major Does the change do what the task asked? No. Gate: major and worse must be fixed. The core orders slice works through Express and real Postgres, including scoped reads, concurrent approvals, policy locks and rollback. The change still has two reproduced integration faults and an incorrect Agents caller-scope description. All 223 focused tests, workspace typechecks and 12 current GitHub checks pass; the repository was left unchanged. Required
|
…n routes Map a DatabaseValidationError from an operation to 422 like generated CRUD instead of a generic 500. Recognize operations by a registry symbol and OperationError by shape, so route files that jiti loads with their own module copy (development export) still run. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The POC works as we agreed in the interview: the database plugin now serves authorized operations next to generated CRUD. Build, lint, typecheck, the full test suite (310 files pass, 1 skipped) and
pnpm docs:buildall pass. It has not been run against a real Postgres or Lakebase.What's new. Everything below is new proposed API on the existing database plugin, exported only from
@databricks/appkit/beta. Nothing else in AppKit changes.defineQuery,defineMutationanddefinePolicy. An operation with noauthorizefails at definition time.database({ routes: { "METHOD /path": op } }). Withoutroutes, setup loads everyconfig/database/routes/*.tsfile. A route that overlaps another route or a generated CRUD route fails setup.runOperation):x-forwarded-userheader. No header returns 401, and a user ID in the body or query is ignored.true.falseor{ deny }returns 403. A policy that throws or returns anything else returns 500 and never allows.transaction()now lets anOperationErrorthrough untouched, as it already does validation errors. Otherwise a deliberate 403 or 409 was logged as an unclassified database error.Playground example.
ordersplus an example-onlyteam_memberstable. The framework never requiresteam_members; it just gives the policies something to check.GET /ordersfilters by team in the SQL itself.POST /orders/:id/approvechecks role, approval limit and "not your own order". It then updates only if the order is still pending, and returns 409 otherwise.config/database/orders.sqlcreates and seeds the tables. The report hascurlcommands for allowed, denied and 409 requests.Tests. They cover all 10 required cases, plus the body not being able to replace the path ID or the caller. The concurrency test uses an in-memory fake that mimics Postgres's conditional update, so it doesn't prove the real row-lock behavior.
Things to know:
orders.sqlfirst, or database setup fails its table check.The commits are
528e4d9a(core) andfa0f7178(playground), both signed. The full report is in~/.herdr-projects/reports/app-kit/3/report.md: architecture, design choices, assumptions, the future CRUD-as-operations idea, the Agents sketch, and limitations.Also noticed:
tsc -p .fails on its client JSX/SVG files. They need their own tsconfig, andpnpm -r typecheckpasses (apps/dev-playground/tsconfig.json).Task: KIT-3 (app-kit/3)