Skip to content

feat(appkit-ui): call named database operations from the client and hooks - #691

Open
ditadi wants to merge 3 commits into
stack/database-stable/04-operation-generationfrom
stack/database-stable/05-client-operations
Open

ditadi wants to merge 3 commits into
stack/database-stable/04-operation-generationfrom
stack/database-stable/05-client-operations

Conversation

@ditadi

@ditadi ditadi commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

What did you change, and why?

Why: Browser code needs to call operations by name with generated types, and its state must not outlive a session.

Change (additive):

  • Adds databaseApi.execute, useDatabaseQuery for GET operations, and useDatabaseMutation for writes. Parameterless operations support execute(name), useDatabaseQuery(name), and mutate(). Parameterized operations still require their schema-derived input.
  • No-argument calls send no JSON input query parameter or request body. Pass undefined in the input position when supplying options; option positions stay consistent.
  • A name the server did not publish fails with NOT_EXPOSED without a request. A changed caller or a 401 clears hook state, and a late answer fails with SESSION_CHANGED. A write without a usable answer reports OUTCOME_UNKNOWN. A mutation never retries and restarts the mounted queries.
  • skipToken, exported from the React beta entry, holds a dependent query idle until valid input exists. It sends nothing, aborts an old read, clears its displayed state, and makes refetch() a no-op. The token is accepted only by query hooks, never by imperative calls or mutations.
  • Mutation state is scoped to the current operation identity and session. Changing A → B → A clears stale data/errors; earlier writes still finish and refresh reads without populating the newly selected operation.
  • entity-client.ts is the previous client.ts, moved verbatim, and the entity hooks import it from there. databaseApi keeps its entity calls until the next PR.
  • Safe to deploy alone: no server publishes operations yet, and the entity client and hooks are unchanged.

Interface

New in @databricks/appkit-ui/js/beta and @databricks/appkit-ui/react/beta. Names, inputs, and outputs come from the generated DatabricksAppKitDatabaseOperations.

// Simplified argument aliases; void input permits omitting the argument.
type InputArgs<I> = [I] extends [void] ? [input?: I] : [input: I];
type Args<I, O> = [I] extends [void]
  ? [input?: I, options?: O]
  : [input: I, options?: O];

databaseApi.execute<N extends DatabaseOperationName>(
  name: N,
  ...args: Args<DatabaseOperationInput<N>, { signal?: AbortSignal }>
): Promise<DatabaseOperationOutput<N>>;

// GET operations only.
function useDatabaseQuery<N extends DatabaseQueryName>(
  name: N,
  ...args: Args<DatabaseOperationInput<N> | typeof skipToken, { enabled?: boolean }>
): {
  data: DatabaseOperationOutput<N> | undefined;
  isLoading: boolean;
  error: DatabaseApiError | null;
  refetch: () => void;
};

// POST, PATCH, PUT, and DELETE operations. Mounting sends nothing.
function useDatabaseMutation<N extends DatabaseMutationName>(name: N): {
  // Never rejects and never retries.
  mutate: (...args: InputArgs<DatabaseOperationInput<N>>) => Promise<
    | { data: DatabaseOperationOutput<N>; error: null }
    | { data: undefined; error: DatabaseApiError }
  >;
  data: DatabaseOperationOutput<N> | undefined;
  isLoading: boolean;
  error: DatabaseApiError | null;
  reset: () => void;
};

type DatabaseApiErrorCode =
  | DatabaseErrorCategory
  | "NOT_EXPOSED" // the server did not publish the name; nothing was sent
  | "UNAUTHENTICATED"
  | "SESSION_CHANGED" // the answer belongs to an earlier actor
  | "OUTCOME_UNKNOWN"; // a write may have committed without a usable answer

Example, as the playground uses it in the last PR of this stack:

const boards = useDatabaseQuery("boards.list");
const notes = useDatabaseQuery(
  "notes.list",
  boardId === undefined ? skipToken : { board_id: boardId },
);
const createNote = useDatabaseMutation("notes.create");

async function add(body: string) {
  const outcome = await createNote.mutate({ board_id: boardId, body });
  if (outcome.error === null) setDraft(""); // mounted queries reload after a success
}

useDatabaseMutation("notes.list"); // type error: a GET operation is not a mutation

How do you know it works?

Testing: The named-operation client, HTTP, query/mutation hook, and compiler-probe suites pass (104 tests at the final stack tip), and appkit-ui typecheck passes. Coverage includes parameterless calls, required schema-derived inputs, dependent queries, cancellation and late responses, operation-name switches including A → B → A, session changes, and unknown write outcomes. Type probes reject skipToken in imperative calls and mutations. The entity APIs remain intact at this layer until the cutover PR.

Stack

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

Design: #683. Previous layer: #690. Next layer: #692.

Restacked onto the updated design without changing this layer's implementation patch.


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): 416 KB (+19 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 445 KB (+17 KB) 153 KB (+6.3 KB)
Type declarations 277 KB (+9.5 KB) 102 KB (+3.8 KB)
Source maps 889 KB (+36 KB) 299 KB (+13 KB)
CSS 16 KB 3.2 KB
Total 1.6 MB (+62 KB) 557 KB (+23 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 3.4 KB (+1.3 KB) 0 B 3.4 KB (+1.3 KB) 0 B 9.3 KB (+4.3 KB)
./react 433 KB 49 KB 481 KB 1.3 MB 177 KB
./react/beta 6.8 KB (+2.0 KB) 0 B 6.8 KB (+2.0 KB) 0 B 19 KB (+6.4 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 3.4 KB
./react index.js initial 431 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 6.8 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 42205549744 -R databricks/appkit -n appkit-template-0.90.0-pr.033e330-stack-database-stable-05-client-operations-691 -D appkit-pr-691 \
  && unzip -o "appkit-pr-691/appkit-template-0.90.0-pr.033e330-stack-database-stable-05-client-operations-691.zip" -d "appkit-pr-691" \
  && databricks apps init --template "appkit-pr-691"

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/05-client-operations branch from ac81ca5 to 7ff200e 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 MarioCadenas October 28, 2026 11:05
@ditadi
ditadi force-pushed the stack/database-stable/05-client-operations branch from 7ff200e to e654d2b Compare November 3, 2026 13:01
@ditadi
ditadi force-pushed the stack/database-stable/05-client-operations branch from e654d2b to e1dec02 Compare November 3, 2026 13:47
@ditadi
ditadi removed this pull request from stack #693 November 3, 2026 15:18
@ditadi
ditadi force-pushed the stack/database-stable/05-client-operations branch from e1dec02 to c442046 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/05-client-operations branch from c442046 to f56ee2f Compare November 4, 2026 19:15
@ditadi
ditadi force-pushed the stack/database-stable/05-client-operations branch from f56ee2f to 6474069 Compare November 4, 2026 20:06
…ooks

Add databaseApi.execute, useDatabaseQuery, and useDatabaseMutation.

A name the server did not publish fails with NOT_EXPOSED and sends

nothing. Answers carry the actor discriminator, so a changed session

clears every query, and a mutation restarts mounted queries.

The entity client and hooks stay until the plugin serves operations.

Signed-off-by: ditadi <victordperd@gmail.com>
Signed-off-by: ditadi <victordperd@gmail.com>
Signed-off-by: ditadi <victordperd@gmail.com>
@ditadi
ditadi force-pushed the stack/database-stable/05-client-operations branch from 6474069 to 974befb 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