Skip to content

feat(appkit): add the service-principal typed DatabasePlugin API - #526

Open
ditadi wants to merge 3 commits into
mainfrom
stack/database-mvp/02-typed-api
Open

feat(appkit): add the service-principal typed DatabasePlugin API#526
ditadi wants to merge 3 commits into
mainfrom
stack/database-mvp/02-typed-api

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 #525. Review in order.

What

Turns the runtime from #525 into a plugin you can actually use. database({ schema }) publishes one typed client per table, plus transactions and parameterized SQL, and the existing typegen flow learns to derive the declarations that make all of it typed from the schema file alone.

This is the first PR in the series with an exported surface. The plugin runs as the app's service principal and registers no HTTP routes — generated CRUD arrives in the next PR, so everything here is reachable only from server code you write.

import { database, defineSchema, id, text, timestamp } from "@databricks/appkit/beta";

const schema = defineSchema((t) => {
  const notes = t.table("notes", {
    id: id(),
    body: text().notNull(),
    createdAt: timestamp().defaultNow(),
  });
  return { notes };
});

const appkit = await createApp({ plugins: [database({ schema })] });

const recent = await appkit.database.notes.order({ createdAt: "desc" }).limit(10).toArray();

await appkit.database.transaction(async (tx) => {
  const note = await tx.notes.create({ body: "hello" });
  await tx.sql`insert into audit (note_id) values (${note.id})`;
});

Changes

The plugin (plugins/database/)

  • database({ schema }) binds one plugin instance to one finalized schema. lifecycle.ts owns setup: it validates the schema, builds the Lakebase pool, and publishes the export surface only once it is ready. Setup is single-flight, and a failure ends the pool rather than leaving a half-open plugin.
  • Each table gets an EntityClient with a chainable read side — where, order, select, include, limit, offset, terminated by toArray, first, find, or count — and the keyed writes create, update, upsert, and delete. Every call goes through the DataPath from feat(appkit): add the database runtime and harden its schema builder #525, so the bounds, the parameterization, and the private-column projection hold here by construction.
  • transaction(cb) hands the callback a client bound to that transaction; the tagged sql template is available both at the top level and inside a transaction, and interpolates values only.
  • Reads and writes carry different interceptor policies (defaults.ts): neither retries, and mutations are never cached.

Errors (database/errors.ts)

DatabasePluginError maps a small closed set of categories onto stable status codes and client messages. A driver error never reaches the caller: it is classified, and the original is logged with its SQLSTATE before the safe error replaces it. The same is true for schema-validation and setup failures, so an operator can diagnose what the client is deliberately not shown.

Schema-derived types (type-generator/database/)

walk-schema.ts walks a finalized schema and generate.ts renders it into appkit-types/database.d.ts, which augments the DatabaseRegistry interface. That is what makes appkit.database.notes know its own columns, filters, and relations. The generator loads the schema file through jiti (pinned at 2.6.1), so a TypeScript schema needs no build step first.

It is wired into both existing entry points — the Vite plugin regenerates on change during development, and appkit generate-types emits it in CI — following the same shape the analytics and serving generators already use.

Exports

defineSchema, the column builders, and database are exported from @databricks/appkit/beta; DatabaseRegistry is exported from the root so the generated declaration file can augment it.

Verification

  • pnpm vitest run — 4057 passing, 1 skipped; the new suites cover the plugin lifecycle, the entity client, the generated types, and the schema walker
  • pnpm -r typecheck — clean across all packages
  • pnpm run generate:types, pnpm run sync:template, and pnpm run docs:build produce no drift
  • pnpm install --frozen-lockfilejiti adds 3 lines to the lockfile and nothing else

Comment thread packages/appkit/src/plugins/database/entity-client.ts
Comment thread packages/appkit/src/plugins/database/entity-client.ts
Comment thread packages/appkit/src/database/errors.ts Outdated
Comment thread packages/appkit/src/database/runtime/engine/drizzle-data-path.ts
Base automatically changed from stack/database-mvp/01-runtime to main August 24, 2026 15:51
Expose the hardened runtime as one service-principal plugin with typed entity clients,
transactions, tagged SQL, and schema-derived declarations in the existing typegen flow.

Driver, setup, and unclassified failures are logged with their original cause before
the safe error replaces them, so operators can diagnose what the client never sees.

Signed-off-by: ditadi <victordperd@gmail.com>
@ditadi
ditadi force-pushed the stack/database-mvp/02-typed-api branch from 441393c to 84e6494 Compare August 24, 2026 16:05
@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit ⚠️ over budget

npm tarball (packed): 984 KB (+97 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 1010 KB (+105 KB) 351 KB (+34 KB)
Type declarations 379 KB (+35 KB) 133 KB (+13 KB)
Source maps 2.0 MB (+215 KB) 661 KB (+66 KB)
Other 11 KB 3.7 KB
Total 3.3 MB (+354 KB) 1.1 MB (+113 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 95 KB (+6.5 KB) 2.5 KB 97 KB (+6.5 KB) external 311 KB (+22 KB)
./beta 65 KB (+14 KB) 457 B 65 KB (+14 KB) external 196 KB (+48 KB)
./testing 17 KB 0 B 17 KB external 50 KB
./tsdown 520 B 0 B 520 B external 813 B
./type-generator 22 KB (+1.5 KB) 0 B 22 KB (+1.5 KB) external 65 KB (+4.3 KB)

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 91 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 49 KB
./beta stream-manager.js initial 5.8 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta databricks.js initial 3.0 KB
./beta configuration.js initial 2.1 KB
./beta service-context.js initial 1.3 KB
./beta client.js initial 434 B
./beta client-options.js initial 220 B
./beta supervisor-api.js lazy 192 B
./beta databricks.js lazy 142 B
./beta index.js lazy 123 B
./testing index.js initial 17 KB
./tsdown index.js initial 520 B
./type-generator index.js initial 22 KB

@databricks/appkit-ui

npm tarball (packed): 348 KB (+5 B) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 394 KB 132 KB
Type declarations 228 KB (+32 B) 83 KB (+8 B)
Source maps 764 KB 252 KB
CSS 16 KB 3.2 KB
Total 1.4 MB (+32 B) 471 KB (+8 B)
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 20 B 0 B 20 B 0 B 0 B
./react 432 KB 49 KB 481 KB 1.3 MB 177 KB
./react/beta 1.0 KB 0 B 1.0 KB 0 B 1.9 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 20 B
./react index.js initial 430 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 1.0 KB

⚠️ Over budget: a package's shipped tarball, or a browser entry's consumer bundle (deps included), grew by more than 5% (and >10 KB). This check will fail — reduce the size, or acknowledge the increase by updating bundle-size-baseline.json.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 32748831942 -R databricks/appkit -n appkit-template-0.64.0-pr.dbbdeaf-stack-database-mvp-02-typed-api-526 -D appkit-pr-526 \
  && unzip -o "appkit-pr-526/appkit-template-0.64.0-pr.dbbdeaf-stack-database-mvp-02-typed-api-526.zip" -d "appkit-pr-526" \
  && databricks apps init --template "appkit-pr-526"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

Signed-off-by: ditadi <victordperd@gmail.com>
@ditadi

ditadi commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Signed-off-by: GitHub <noreply@github.com>

Copilot AI commented Aug 24, 2026

Copy link
Copy Markdown

@copilot resolve the merge conflicts in this pull request

Resolved by merging origin/main into this branch and fixing the conflict; included in commit 5b2663f.

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.

3 participants