Skip to content

feat(appkit): generate the database operation registry and browser contracts - #690

Open
ditadi wants to merge 5 commits into
stack/database-stable/03-operation-authorizationfrom
stack/database-stable/04-operation-generation
Open

ditadi wants to merge 5 commits into
stack/database-stable/03-operation-authorizationfrom
stack/database-stable/04-operation-generation

Conversation

@ditadi

@ditadi ditadi commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

What did you change, and why?

Why: A compiled server has no TypeScript sources to scan, and the browser needs operation types without importing server code.

Change (build time only):

  • Scans server/database/routes/*.ts and writes server/database/routes.gen.ts, a lazy-import registry that satisfies DatabaseRoutes, and shared/appkit-types/database-operations.d.ts, the exact JSON contract of each operation.
  • The Vite plugin and appkit generate-types run it. With NODE_ENV=production, an operation without authorization fails generation. An unsupported Zod schema fails with the operation and the slot it came from.
  • Operations without an input schema generate input: void, regardless of HTTP method, primary keys, or writable columns. Explicit input schemas still render their exact wire types. Operations without an output schema generate output: void, and explicit output schemas render their exact wire types.
  • CI type-checks the built declarations and runs a bundled server that loads its operations without sources.
  • A declared schema generates a canonical empty registry before the first operation exists. Adding the first route and removing the last route update that registry; an app with neither schema nor routes creates nothing.
  • Watches shared modules throughout server/database/, including row schemas and policies. Generated .gen.ts and .d.ts files are excluded to prevent generation loops.
  • Safe to deploy alone: generation does not register HTTP handlers.

Interface

Generation scans the *.ts files directly in server/database/routes/ (skipping *.test.ts, *.d.ts, and *.gen.ts) and writes two files that apps commit. For the playground of the last PR of this stack:

// server/database/routes.gen.ts: consumed by the database plugin
import type { DatabaseRoutes } from "@databricks/appkit/beta";

export const databaseRoutes = {
  schema: () => import("../../config/database/schema"),
  modules: [
    { file: "server/database/routes/boards.ts", load: () => import("./routes/boards") },
    { file: "server/database/routes/notes.ts", load: () => import("./routes/notes") },
  ],
} satisfies DatabaseRoutes;
// shared/appkit-types/database-operations.d.ts: the browser client reads these types
declare global {
  interface DatabricksAppKitDatabaseOperations {
    "notes.update": {
      method: "PATCH";
      input: { "id": number; "body": string };
      output: { "id": number; "board_id": number; "author": string; "body": string; "created_at": string };
    };
    "notes.delete": { method: "DELETE"; input: { "id": number }; output: void };
    "notes.count": { method: "GET"; input: void; output: number };
  }
}
  • The AppKit Vite plugin runs it when the dev server starts and when files under config/database/ or server/database/ change, including shared schemas and policies. Generated outputs do not trigger another run. appkit generate-types runs it from the CLI.
  • An operation without authorize is skipped with a warning naming its file#export. With NODE_ENV=production, it fails generation instead.
  • A Zod schema the client cannot type exactly fails generation with the operation and the slot: z.date(), tuples, z.json(), output transforms, coercion, and preprocessing. A failed run writes an empty interface.

How do you know it works?

Testing: Operation generation, generated wire-type equality, and Vite integration pass with the rest of the type generator (626 tests at this layer). A schema that throws on import fails generation and neutralizes the contracts. Coverage includes shared schema/policy add/change/unlink events, ignored generated files, schema-only bootstrapping, final route deletion, strict mode, and explicit/omitted input contracts. The source-less production bundle check loads all three operations under plain Node after TypeScript sources are removed. Repository build, docs, lint/format, and recursive typechecks pass.

Stack

#683 → #687 → #688 → #689 → #690 → #691 → #692

Design: #683. Previous layer: #689. Next layer: #691.

Review feedback: refactor(appkit): generate void outputs for operations without an output schema drops the entity check and the default output type, and keeps loading the schema so a broken import still neutralizes the contracts.


Stack created with GitHub Stacks CLI • Give Feedback 💬

@ditadi
ditadi added this pull request to stack #693 October 23, 2026 15:01
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

📦 Bundle size report

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

@databricks/appkit

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

dist raw gzip
JS (runtime) 1.3 MB (+19 KB) 467 KB (+7.9 KB)
Type declarations 476 KB (+11 KB) 172 KB (+3.8 KB)
Source maps 2.6 MB (+48 KB) 876 KB (+17 KB)
Other 11 KB 3.7 KB
Total 4.3 MB (+78 KB) 1.5 MB (+29 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 110 KB (+4.2 KB) 2.5 KB 113 KB (+4.2 KB) external 356 KB (+12 KB)
./beta 103 KB (-577 B) 482 B 103 KB (-577 B) external 308 KB (-2.5 KB)
./testing 45 KB (+273 B) 36 KB (+3.9 KB) 81 KB (+4.2 KB) external 235 KB (+12 KB)
./tsdown 520 B 0 B 520 B external 813 B
./type-generator 30 KB (+3.8 KB) 0 B 30 KB (+3.8 KB) external 85 KB (+11 KB)

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 106 KB
. utils.js initial 4.6 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 82 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 176 B
./beta index.js lazy 114 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 32 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 30 KB

@databricks/appkit-ui

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

dist raw gzip
JS (runtime) 427 KB (-1.9 KB) 146 KB (-761 B)
Type declarations 266 KB (-900 B) 98 KB (-211 B)
Source maps 849 KB (-4.4 KB) 284 KB (-1.4 KB)
CSS 16 KB 3.2 KB
Total 1.5 MB (-7.2 KB) 531 KB (-2.4 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 (+15 B) 0 B 2.1 KB (+15 B) 0 B 5.0 KB (+22 B)
./react 433 KB 49 KB 481 KB 1.3 MB 177 KB
./react/beta 4.7 KB (-184 B) 0 B 4.7 KB (-184 B) 0 B 12 KB (-539 B)

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.7 KB

@github-actions

github-actions Bot commented Oct 23, 2026 •

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 42205552517 -R databricks/appkit -n appkit-template-0.90.0-pr.7dd34f2-stack-database-stable-04-operation-generation-690 -D appkit-pr-690 \
  && unzip -o "appkit-pr-690/appkit-template-0.90.0-pr.7dd34f2-stack-database-stable-04-operation-generation-690.zip" -d "appkit-pr-690" \
  && databricks apps init --template "appkit-pr-690"

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.

@ditadi
ditadi force-pushed the stack/database-stable/04-operation-generation branch from faf1728 to c679be4 Compare October 26, 2026 10:41
@ditadi
ditadi marked this pull request as ready for review October 28, 2026 11:05
@ditadi
ditadi requested a review from a team as a code owner October 28, 2026 11:05
@ditadi
ditadi requested a review from calvarjorge October 28, 2026 11:05
@ditadi
ditadi force-pushed the stack/database-stable/04-operation-generation branch from c679be4 to c6b8418 Compare November 3, 2026 13:01
@ditadi
ditadi force-pushed the stack/database-stable/04-operation-generation branch from c6b8418 to ad8c8e0 Compare November 3, 2026 13:47
@ditadi
ditadi requested review from MarioCadenas and removed request for calvarjorge November 3, 2026 13:55
@ditadi
ditadi removed this pull request from stack #693 November 3, 2026 15:18
@ditadi
ditadi force-pushed the stack/database-stable/04-operation-generation branch from ad8c8e0 to 1ea5e57 Compare November 3, 2026 15:18
@ditadi
ditadi added this pull request to stack #704 November 3, 2026 15:19
@ditadi
ditadi force-pushed the stack/database-stable/04-operation-generation branch 2 times, most recently from 151e6e8 to da12ac4 Compare November 4, 2026 20:06
…ntracts

Scan server/database/routes/*.ts and write a lazy routes.gen.ts

registry and the exact JSON contract of each operation. The Vite

plugin and appkit generate-types run it, strictly in production.

An app without that directory generates nothing new.

CI checks the built declarations and a server bundle that loads

its operations without sources.

Signed-off-by: ditadi <victordperd@gmail.com>
Signed-off-by: ditadi <victordperd@gmail.com>
Signed-off-by: ditadi <victordperd@gmail.com>
ditadi and others added 2 commits November 6, 2026 10:49
Signed-off-by: ditadi <victordperd@gmail.com>
…put schema

The contract generator renders an omitted output slot as void, the way it
already renders an omitted input, instead of deriving a row type from the
operation's table. Operation generation still loads the schema, so a schema
that cannot be imported fails it and neutralizes the contracts on its own.

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: ditadi <victordperd@gmail.com>
@ditadi
ditadi force-pushed the stack/database-stable/04-operation-generation branch from da12ac4 to 435a876 Compare November 6, 2026 10:49

This branch has not been deployed

No deployments
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