From 58fcba81038f773be3fca5b5b12e85920526aede Mon Sep 17 00:00:00 2001 From: MarioCadenas Date: Fri, 16 Oct 2026 17:24:23 +0200 Subject: [PATCH 1/3] refactor(shared): remove the legacy @databricks/sdk-experimental SDK Every service already ran on the modular @databricks/sdk-* clients; this drops the legacy client and the dependency. - Remove legacy.ts, toLegacyWorkspaceClient() and the facade's servingEndpoints/config/apiClient accessors; options move to options.ts. - The persistent cache hands the modular client to Lakebase; doctor does the same instead of a hand-built legacy shape. - ApiError is AppKit's own class (same fields); boot auth errors map the modular SDK's credential errors via isAuthConfigError. - Local types replace the legacy serving/sql namespaces for raw JSON. - The cancellation bridge no longer needs the SDK's Context class. - Callers passing their own legacy-shaped client (agents, lakebase) still work: that support is structural and needs no SDK. - Drop the dependency from shared, appkit and the app template. Co-authored-by: Isaac Signed-off-by: MarioCadenas --- .oxlintrc.json | 2 +- CLAUDE.md | 2 +- docs/docs/plugins/testing.md | 14 ++- docs/docusaurus.config.ts | 2 +- knip.json | 1 - packages/appkit/package.json | 1 - packages/appkit/src/cache/index.ts | 2 +- packages/appkit/src/connectors/context.ts | 23 ++-- packages/appkit/src/connectors/mlflow/auth.ts | 1 - .../appkit/src/connectors/serving/client.ts | 10 +- .../connectors/serving/tests/client.test.ts | 5 +- .../src/connectors/tests/context.test.ts | 4 +- .../appkit/src/context/service-context.ts | 12 +- .../context/tests/execution-context.test.ts | 1 - .../src/context/tests/service-context.test.ts | 4 +- .../tests/user-agent-wire.integration.test.ts | 22 ++-- packages/appkit/src/core/agent/run-agent.ts | 1 - .../agent/tests/run-agent-identity.test.ts | 2 +- packages/appkit/src/resources/warehouse.ts | 24 +++- .../src/testing/mock-workspace-client.ts | 108 ++---------------- .../src/testing/tests/create-test-app.test.ts | 4 +- .../appkit/src/testing/tests/fixtures.test.ts | 1 - .../tests/mock-workspace-client.test.ts | 98 ++++------------ .../src/type-generator/statement-result.ts | 4 +- .../tests/typegen-client.test.ts | 10 +- .../tests/unreachable-warehouse-gate.test.ts | 8 +- packages/appkit/src/workspace-client/index.ts | 15 +-- packages/shared/package.json | 1 - .../cli/commands/doctor/databricks-client.ts | 10 +- packages/shared/src/index.ts | 13 +-- .../shared/src/workspace-client/client.ts | 72 ++++-------- .../shared/src/workspace-client/errors.ts | 36 ++++-- .../shared/src/workspace-client/factory.ts | 2 +- packages/shared/src/workspace-client/index.ts | 29 ++--- .../shared/src/workspace-client/legacy.ts | 92 --------------- .../shared/src/workspace-client/modular.ts | 26 ++++- .../shared/src/workspace-client/options.ts | 27 +++++ .../src/workspace-client/tests/legacy.test.ts | 69 ----------- packages/shared/src/workspace-client/types.ts | 56 +-------- pnpm-lock.yaml | 19 --- template/package-lock.json | 1 - template/package.json | 1 - template/pnpm-lock.yaml | 3 - 43 files changed, 240 insertions(+), 598 deletions(-) delete mode 100644 packages/shared/src/workspace-client/legacy.ts create mode 100644 packages/shared/src/workspace-client/options.ts delete mode 100644 packages/shared/src/workspace-client/tests/legacy.test.ts diff --git a/.oxlintrc.json b/.oxlintrc.json index 4d78b20bb..a457847df 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -27,7 +27,7 @@ "patterns": [ { "group": ["@databricks/sdk-*", "@databricks/sdk-*/**"], - "message": "Import the Databricks SDK only through the wrapper in packages/shared/src/workspace-client (legacy.ts for @databricks/sdk-experimental, modular.ts for the modular @databricks/sdk-* packages). Add a re-export there if you need a new symbol." + "message": "Import the Databricks SDK only through the wrapper in packages/shared/src/workspace-client (modular.ts builds the modular @databricks/sdk-* clients). Add a re-export there if you need a new symbol." } ] } diff --git a/CLAUDE.md b/CLAUDE.md index 542941427..755bb9204 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -415,7 +415,7 @@ This project uses conventional commits (enforced by commitlint): ## Important Context ### Key Dependencies -- `@databricks/sdk-experimental` v0.16.0 - Databricks services SDK +- Modular `@databricks/sdk-*` packages - Databricks services SDK (isolated in `packages/shared/src/workspace-client/`) - `express` - HTTP server - `zod` - Runtime validation - `OpenTelemetry` - Observability (traces, metrics, logs) diff --git a/docs/docs/plugins/testing.md b/docs/docs/plugins/testing.md index 7a7797c01..910356492 100644 --- a/docs/docs/plugins/testing.md +++ b/docs/docs/plugins/testing.md @@ -66,7 +66,8 @@ const app = await createTestApp({ responses: { "jobs.getRun": { state: "TERMINATED", result_state: "SUCCESS" }, "statementExecution.executeStatement": { status: { state: "SUCCEEDED" } }, - "apiClient.request": { results: [] }, + // Raw REST calls through the request() seam answer with a Response. + request: () => Response.json({ results: [] }), }, }); ``` @@ -207,7 +208,8 @@ const app = await createTestApp({ responses: { "jobs.getRun": { state: "TERMINATED", result_state: "SUCCESS" }, "statementExecution.executeStatement": { status: { state: "SUCCEEDED" } }, - "apiClient.request": { results: [] }, + // Raw REST calls through the request() seam answer with a Response. + request: () => Response.json({ results: [] }), }, }); ``` @@ -332,7 +334,7 @@ Instantiate the plugin **class** directly (`new MyAgentPlugin(...)`). The `analy const mock = createTestPluginContext({}, { responses: { "jobs.getRun": { state: "TERMINATED" }, - "servingEndpoints.query": (args, signal) => runFakeQuery(args), + "jobs.listRuns": (args) => runFakeListRuns(args), }, env: { MY_VAR: "test-value" }, strict: true, @@ -502,7 +504,7 @@ The kit uses both words deliberately: a **mock** records calls so you can assert ## Mocking Databricks services -Every core plugin's real work goes through `getWorkspaceClient()`. `createMockWorkspaceClient()` fakes that whole surface, so a plugin touching `jobs`, `genie`, `servingEndpoints`, or `files` is testable without hand-building a nested client: +Every core plugin's real work goes through `getWorkspaceClient()`. `createMockWorkspaceClient()` fakes that whole surface, so a plugin touching `jobs`, `genie`, `modelServing`, or `files` is testable without hand-building a nested client: ```ts import { createMockWorkspaceClient, getMock } from "@databricks/appkit/testing"; @@ -521,8 +523,8 @@ await client.genie.genieGetConversationMessage({ messageId: "m-1" }); // → und How it works, and what to expect: - The **facade is typed**, so `client.jbos` is a compile error. AppKit owns the interface, so it's a closed set, not an open-ended chase of the SDK. -- Each **service** is a proxy that mints a memoized mock per method. `client.jobs.getRun === client.jobs.getRun`, so call assertions are stable, and `toLegacyWorkspaceClient()` shares the same functions — one `responses` entry covers both views. -- `config.host` is a real **string** (not a mock), because AppKit builds URLs from it. `apiClient.userAgent()` is synchronous for the same reason, and `apiClient.request` resolves `{}` so destructuring its result doesn't throw. +- Each **service** is a proxy that mints a memoized mock per method. `client.jobs.getRun === client.jobs.getRun`, so call assertions are stable. +- `getHost()` resolves a real **string** (seed it with the `config: { host }` option or a `"config.host"` response), because AppKit builds URLs from it. `authenticate(headers)` stamps a test `Authorization` header, and `request(...)` resolves a `Response` with a `{}` JSON body unless you seed `request`. - Sensible defaults are built in: SQL statements succeed, warehouses report `RUNNING`, and `currentUser.me()` returns a service user. Pass `defaults: false` to script everything yourself. :::caution Undeclared methods return undefined diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 1717a834e..438698d5b 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -9,7 +9,7 @@ import webpack from "webpack"; // appkit is a Node/server package; the docs only reference its API (they never // execute the SDK in the browser). Stub every Node built-in for the client -// bundle so webpack doesn't try to bundle `@databricks/sdk-experimental`'s +// bundle so webpack doesn't try to bundle the Databricks SDK's // Node deps (fs, crypto, stream, …). `fallback` only triggers when a module is // unresolvable, so it's a no-op for the Node-target server bundle. const nodeBuiltinFallbacks = Object.fromEntries( diff --git a/knip.json b/knip.json index 12cc73f4f..8e2d0f8a1 100644 --- a/knip.json +++ b/knip.json @@ -13,7 +13,6 @@ "@databricks/sdk-apps", "@databricks/sdk-auth", "@databricks/sdk-core", - "@databricks/sdk-experimental", "@databricks/sdk-files", "@databricks/sdk-database", "@databricks/sdk-experiments", diff --git a/packages/appkit/package.json b/packages/appkit/package.json index 2facc55aa..1f5bfd160 100644 --- a/packages/appkit/package.json +++ b/packages/appkit/package.json @@ -74,7 +74,6 @@ "@databricks/sdk-apps": "0.54.0", "@databricks/sdk-auth": "0.51.0", "@databricks/sdk-core": "0.51.0", - "@databricks/sdk-experimental": "0.17.0", "@databricks/sdk-database": "0.51.0", "@databricks/sdk-files": "0.52.0", "@databricks/sdk-experiments": "0.52.0", diff --git a/packages/appkit/src/cache/index.ts b/packages/appkit/src/cache/index.ts index bc18c13aa..11b17cd66 100644 --- a/packages/appkit/src/cache/index.ts +++ b/packages/appkit/src/cache/index.ts @@ -181,7 +181,7 @@ export class CacheManager { try { const workspaceClient = createWorkspaceClient({ clientOptions: getClientOptions(), - }).toLegacyWorkspaceClient(); + }); const pool = createLakebasePool({ workspaceClient }); const persistentStorage = new PersistentStorage(config, pool); diff --git a/packages/appkit/src/connectors/context.ts b/packages/appkit/src/connectors/context.ts index d1cdd82dd..485f1dace 100644 --- a/packages/appkit/src/connectors/context.ts +++ b/packages/appkit/src/connectors/context.ts @@ -1,7 +1,14 @@ -import { type CancellationToken, Context } from "../workspace-client"; +/** + * The cancellation shape a caller-supplied legacy-style client reads from the + * second argument of `apiClient.request` (duck-typed, so no SDK import). + */ +interface CancellationToken { + readonly isCancellationRequested: boolean; + onCancellationRequested(callback: (e?: unknown) => unknown): void; +} /** - * Bridges {@link AbortSignal} to the SDK's {@link CancellationToken} so + * Bridges {@link AbortSignal} to a {@link CancellationToken} so * `apiClient.request` can abort the outbound HTTP request (and stop pulling a * response body) when the caller aborts. */ @@ -37,16 +44,14 @@ function cancellationTokenFromAbortSignal( } /** - * Wraps an optional {@link AbortSignal} in an SDK {@link Context} for the - * second argument of `apiClient.request`. Returns `undefined` when no signal - * is given, so callers can pass the result straight through. + * Wraps an optional {@link AbortSignal} as the request context a legacy-style + * `apiClient.request` reads (`context.cancellationToken`). Returns `undefined` + * when no signal is given, so callers can pass the result straight through. */ export function contextFromAbortSignal( signal?: AbortSignal, -): InstanceType | undefined { +): { cancellationToken: CancellationToken } | undefined { return signal - ? new Context({ - cancellationToken: cancellationTokenFromAbortSignal(signal), - }) + ? { cancellationToken: cancellationTokenFromAbortSignal(signal) } : undefined; } diff --git a/packages/appkit/src/connectors/mlflow/auth.ts b/packages/appkit/src/connectors/mlflow/auth.ts index 983419d95..5cf06c9d7 100644 --- a/packages/appkit/src/connectors/mlflow/auth.ts +++ b/packages/appkit/src/connectors/mlflow/auth.ts @@ -84,7 +84,6 @@ export function resolveWorkspaceClient( return createWorkspaceClient({ host: options.host, token: options.token, - authType: "pat", }); } return createWorkspaceClient( diff --git a/packages/appkit/src/connectors/serving/client.ts b/packages/appkit/src/connectors/serving/client.ts index 24fdf1f81..17e0401a5 100644 --- a/packages/appkit/src/connectors/serving/client.ts +++ b/packages/appkit/src/connectors/serving/client.ts @@ -1,5 +1,5 @@ import { createLogger } from "../../logging/logger"; -import type { serving, WorkspaceClient } from "../../workspace-client"; +import type { WorkspaceClient } from "../../workspace-client"; import { contextFromAbortSignal } from "../context"; const logger = createLogger("connectors:serving"); @@ -20,8 +20,8 @@ const logger = createLogger("connectors:serving"); // Needs: a data-plane AI Gateway client for chat completions + responses, // with streaming. // -// When those land, also drop the legacy `apiClient.request` fallback in -// `streamPath`, which only exists for caller-supplied legacy SDK clients. +// The `apiClient.request` fallback in `streamPath` is structural: it only +// serves callers that pass their own legacy-style client. /** * Structural shape of the AppKit workspace client's raw-request seam @@ -81,7 +81,7 @@ export async function invoke( client: WorkspaceClient, endpointName: string, body: Record, -): Promise { +): Promise> { // Strip `stream` from the body — the connector controls this const { stream: _stream, ...cleanBody } = body; @@ -98,7 +98,7 @@ export async function invoke( }); const text = await response.text(); - let json: serving.QueryEndpointResponse; + let json: Record; try { json = text.length === 0 ? {} : JSON.parse(text); } catch { diff --git a/packages/appkit/src/connectors/serving/tests/client.test.ts b/packages/appkit/src/connectors/serving/tests/client.test.ts index 0628d1c04..497681a30 100644 --- a/packages/appkit/src/connectors/serving/tests/client.test.ts +++ b/packages/appkit/src/connectors/serving/tests/client.test.ts @@ -1,6 +1,5 @@ import { afterEach, describe, expect, test, vi } from "vitest"; -import { Context } from "../../../workspace-client"; import { invoke, stream } from "../client"; function createMockClient(host = "https://test.databricks.com") { @@ -222,7 +221,7 @@ describe("Serving Connector", () => { ); }); - test("passes SDK Context when AbortSignal is provided", async () => { + test("passes a cancellation context when AbortSignal is provided", async () => { const client = createLegacyClient(); client.apiClient.request.mockResolvedValue({ contents: new ReadableStream(), @@ -235,7 +234,7 @@ describe("Serving Connector", () => { expect.objectContaining({ path: "/serving-endpoints/my-endpoint/invocations", }), - expect.any(Context), + expect.objectContaining({ cancellationToken: expect.anything() }), ); }); diff --git a/packages/appkit/src/connectors/tests/context.test.ts b/packages/appkit/src/connectors/tests/context.test.ts index 040862942..a667282e9 100644 --- a/packages/appkit/src/connectors/tests/context.test.ts +++ b/packages/appkit/src/connectors/tests/context.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it, vi } from "vitest"; -import { Context } from "../../workspace-client"; import { contextFromAbortSignal } from "../context"; describe("contextFromAbortSignal", () => { @@ -8,9 +7,8 @@ describe("contextFromAbortSignal", () => { expect(contextFromAbortSignal()).toBeUndefined(); }); - it("wraps a signal in an SDK Context", () => { + it("wraps a signal in a context carrying a cancellation token", () => { const ctx = contextFromAbortSignal(new AbortController().signal); - expect(ctx).toBeInstanceOf(Context); expect(ctx?.cancellationToken).toBeDefined(); }); diff --git a/packages/appkit/src/context/service-context.ts b/packages/appkit/src/context/service-context.ts index ef9142159..9eb416e54 100644 --- a/packages/appkit/src/context/service-context.ts +++ b/packages/appkit/src/context/service-context.ts @@ -8,8 +8,8 @@ import { import { WarehouseResource } from "../resources/warehouse"; import { type ClientOptions, - ConfigError, createWorkspaceClient, + isAuthConfigError, type WorkspaceClient, } from "../workspace-client"; import { type CallerContext, snapshotCallerContext } from "./caller-context"; @@ -125,13 +125,11 @@ export class ServiceContext { const serviceCtx = ServiceContext.get(); - // Create user client with the OAuth token from Databricks Apps - // Note: We use authType: "pat" because the token is passed as a Bearer token - // just like a PAT, even though it's technically an OAuth token + // Create user client with the OAuth token from Databricks Apps. An explicit + // token is sent as a Bearer token, like a PAT, though it's an OAuth token. const userClient = createWorkspaceClient({ token, host, - authType: "pat", clientOptions: getClientOptions(), }); @@ -213,9 +211,9 @@ export class ServiceContext { workspaceId: Promise.resolve(resolvedWorkspaceId), }); } catch (e) { - if (e instanceof ConfigError) { + if (isAuthConfigError(e)) { throw ConfigurationError.databricksAuthenticationSetupFailed( - e.baseMessage, + e.message, { cause: e }, ); } diff --git a/packages/appkit/src/context/tests/execution-context.test.ts b/packages/appkit/src/context/tests/execution-context.test.ts index 9668335ef..107fda02e 100644 --- a/packages/appkit/src/context/tests/execution-context.test.ts +++ b/packages/appkit/src/context/tests/execution-context.test.ts @@ -165,7 +165,6 @@ describe("caller execution context", () => { expect.objectContaining({ token: "token", host: "https://workspace.example.com", - authType: "pat", }), ); expect(context.client).toBe(client); diff --git a/packages/appkit/src/context/tests/service-context.test.ts b/packages/appkit/src/context/tests/service-context.test.ts index f2859edb4..f18593f54 100644 --- a/packages/appkit/src/context/tests/service-context.test.ts +++ b/packages/appkit/src/context/tests/service-context.test.ts @@ -288,7 +288,7 @@ describe("ServiceContext", () => { expect(userCtx.workspaceId).toBe(serviceCtx.workspaceId); }); - test("should create user client with PAT authType", () => { + test("should create the user client from the user token (PAT)", () => { ServiceContext.createUserContext("user-token", "user-1"); // The last call to MockWorkspaceClient should be for the user client @@ -299,7 +299,6 @@ describe("ServiceContext", () => { expect(lastCall[0]).toMatchObject({ token: "user-token", host: process.env.DATABRICKS_HOST, - authType: "pat", }); }); @@ -358,7 +357,6 @@ describe("ServiceContext", () => { expect.objectContaining({ host: "https://profile-host.example.com", token: "user-token", - authType: "pat", }), ); }); diff --git a/packages/appkit/src/context/tests/user-agent-wire.integration.test.ts b/packages/appkit/src/context/tests/user-agent-wire.integration.test.ts index c3cb34158..c559d4f55 100644 --- a/packages/appkit/src/context/tests/user-agent-wire.integration.test.ts +++ b/packages/appkit/src/context/tests/user-agent-wire.integration.test.ts @@ -29,7 +29,6 @@ describe("User-Agent attribution (wire)", () => { createWorkspaceClient({ host, token: "test-token", - authType: "pat", clientOptions: getClientOptions(), }); @@ -61,20 +60,19 @@ describe("User-Agent attribution (wire)", () => { capturedUserAgents = []; }); - test("SDK apiClient.request() stamps the AppKit User-Agent on the wire", async () => { - await newClient().apiClient.request({ + test("request() stamps the AppKit User-Agent on the wire", async () => { + await newClient().request({ path: "/api/2.0/preview/scim/v2/Me", method: "GET", - headers: new Headers(), - raw: false, }); expect(capturedUserAgents).toHaveLength(1); - const ua = capturedUserAgents[0]; - // AppKit product first, and the real SDK segments after it — proving this - // is the SDK-composed User-Agent, not a value a mock stubbed in. - expect(ua).toMatch(/^@databricks\/appkit\/\d+\.\d+\.\d+ /); - expect(ua).toContain("databricks-sdk-js/"); + // A real server received it, so this is the transport's User-Agent, not a + // mocked value. The raw seam sends AppKit's product segment only; typed + // modular clients append the SDK's own client-info after it. + expect(capturedUserAgents[0]).toMatch( + /^@databricks\/appkit\/\d+\.\d+\.\d+/, + ); }); test("raw-fetch upload path stamps the AppKit User-Agent on the wire", async () => { @@ -95,11 +93,9 @@ describe("User-Agent attribution (wire)", () => { const prev = process.env.NODE_ENV; process.env.NODE_ENV = "development"; try { - await newClient().apiClient.request({ + await newClient().request({ path: "/api/2.0/preview/scim/v2/Me", method: "GET", - headers: new Headers(), - raw: false, }); } finally { process.env.NODE_ENV = prev; diff --git a/packages/appkit/src/core/agent/run-agent.ts b/packages/appkit/src/core/agent/run-agent.ts index ed7290191..df67d93a5 100644 --- a/packages/appkit/src/core/agent/run-agent.ts +++ b/packages/appkit/src/core/agent/run-agent.ts @@ -138,7 +138,6 @@ export async function runAgent( client: createWorkspaceClient({ token, host, - authType: "pat", clientOptions: getClientOptions(), }), workspaceId: Promise.resolve(workspaceId), diff --git a/packages/appkit/src/core/agent/tests/run-agent-identity.test.ts b/packages/appkit/src/core/agent/tests/run-agent-identity.test.ts index 12ca5c830..79a7ddad7 100644 --- a/packages/appkit/src/core/agent/tests/run-agent-identity.test.ts +++ b/packages/appkit/src/core/agent/tests/run-agent-identity.test.ts @@ -118,7 +118,7 @@ describe("standalone caller identity", () => { expect(seen).toEqual(["user:alice"]); expect(factory).toHaveBeenCalledOnce(); expect(factory).toHaveBeenCalledWith( - expect.objectContaining({ token: "token-alice", authType: "pat" }), + expect.objectContaining({ token: "token-alice" }), ); expect(JSON.stringify(result)).not.toContain("token-alice"); expect(getCurrentPrincipalKey()).toBe("app"); diff --git a/packages/appkit/src/resources/warehouse.ts b/packages/appkit/src/resources/warehouse.ts index 73971deee..be1cf60a6 100644 --- a/packages/appkit/src/resources/warehouse.ts +++ b/packages/appkit/src/resources/warehouse.ts @@ -2,7 +2,22 @@ import { AsyncLocalStorage } from "node:async_hooks"; import { ConfigurationError, InitializationError } from "../errors"; import type { ResourceEntry } from "../registry/types"; -import type { sql, WorkspaceClient } from "../workspace-client"; +import type { WorkspaceClient } from "../workspace-client"; + +/** Warehouse lifecycle states, as in the raw `/api/2.0/sql/warehouses` body. */ +type WarehouseState = + | "RUNNING" + | "STOPPED" + | "STARTING" + | "STOPPING" + | "DELETED" + | "DELETING"; + +/** The fields read from one raw (snake_case) warehouse list entry. */ +interface RawWarehouse { + id?: string; + state?: WarehouseState; +} /** The SQL warehouse selected for the app lifecycle, separate from identity. */ export interface WarehouseBinding { @@ -84,9 +99,9 @@ async function discoverWarehouseId(client: WorkspaceClient): Promise { method: "GET", query: { skip_cannot_use: "true" }, }) - ).json()) as { warehouses?: sql.EndpointInfo[] }; + ).json()) as { warehouses?: RawWarehouse[] }; - const priorities: Record = { + const priorities: Record = { RUNNING: 0, STOPPED: 1, STARTING: 2, @@ -97,7 +112,8 @@ async function discoverWarehouseId(client: WorkspaceClient): Promise { const warehouses = (response.warehouses || []).sort((a, b) => { return ( - priorities[a.state as sql.State] - priorities[b.state as sql.State] + priorities[a.state as WarehouseState] - + priorities[b.state as WarehouseState] ); }); diff --git a/packages/appkit/src/testing/mock-workspace-client.ts b/packages/appkit/src/testing/mock-workspace-client.ts index dda9da370..151f44b0d 100644 --- a/packages/appkit/src/testing/mock-workspace-client.ts +++ b/packages/appkit/src/testing/mock-workspace-client.ts @@ -12,8 +12,6 @@ import type { WorkspaceClient } from "../workspace-client"; // .oxlintrc.json), so a local alias keeps the intent readable. type Any = any; -type LegacyClient = ReturnType; - /** Options for {@link createMockWorkspaceClient}. */ export interface CreateMockWorkspaceClientOptions { /** @@ -31,8 +29,8 @@ export interface CreateMockWorkspaceClientOptions { */ strict?: boolean; - /** Seed `config`; `host` must stay a real string. */ - config?: Partial; + /** Seed the workspace host `getHost()` resolves; must be a real string. */ + config?: { host?: string }; /** Apply the canned defaults (SQL succeeds, warehouse RUNNING). Default true. */ defaults?: boolean; @@ -63,7 +61,7 @@ const DEFAULT_RESPONSES: Record = { }, }; -/** Generically proxied; `config`/`apiClient` are seeded below instead. */ +/** Generically proxied service accessors. */ const FACADE_SERVICES = [ "files", "warehouses", @@ -80,7 +78,6 @@ const FACADE_SERVICES = [ "connections", "statementExecution", "modelServing", - "servingEndpoints", "currentUser", ] as const; @@ -99,9 +96,6 @@ const PASSTHROUGH_DENY: ReadonlySet = new Set([ "asymmetricMatch", ]); -/** `apiClient` members read synchronously; a seeded value must not be resolved. */ -const SYNC_API_CLIENT_MEMBERS: ReadonlySet = new Set(["userAgent"]); - /** Distinguishes "no passthrough rule applied" from a rule answering `undefined`. */ const NOT_PASSTHROUGH = Symbol("not-passthrough"); @@ -199,92 +193,17 @@ export function createMockWorkspaceClient( return proxy; } - /** Pull `"config.*"` / `"apiClient.*"` entries out so they seed real values. */ - function seededOverrides(namespace: string): Record { - const prefix = `${namespace}.`; - const out: Record = {}; - for (const [key, value] of Object.entries(merged)) { - if (key.startsWith(prefix)) out[key.slice(prefix.length)] = value; - } - return out; - } - - // `host` is read as a string and throws if falsy, so it cannot be a mock. - const configTarget: Record = { - host: "https://test.databricks.com", - authenticate: vi.fn((headers?: Headers) => { - headers?.set?.("Authorization", "Bearer test-token"); - }), - ensureResolved: vi.fn().mockResolvedValue(undefined), - ...config, - ...seededOverrides("config"), - }; - - // userAgent() must be synchronous (a Promise stringifies to "[object Promise]" - // inside a Headers value); request resolves {} so destructuring works. - const apiClientTarget: Record = { - userAgent: vi.fn().mockReturnValue("appkit-test/1.0"), - request: vi.fn().mockResolvedValue({}), - }; - for (const [key, value] of Object.entries(seededOverrides("apiClient"))) { - const fn = typeof value === "function" ? vi.fn(value) : vi.fn(); - if (typeof value !== "function") { - // Match the member's own shape. `userAgent()` is read straight into a - // Headers value, so resolving a seed would put "[object Promise]" there — - // the failure the synchronous default exists to avoid. Seed a function to - // decide for yourself. - if (SYNC_API_CLIENT_MEMBERS.has(key)) fn.mockReturnValue(value); - else fn.mockResolvedValue(value); - } - apiClientTarget[key] = fn; - fns.set(`apiClient.${key}`, fn); - } - for (const key of ["userAgent", "request"]) { - if (!fns.has(`apiClient.${key}`)) { - fns.set(`apiClient.${key}`, apiClientTarget[key] as Mock); - } - } - for (const [key, value] of Object.entries(configTarget)) { - if (typeof value === "function" && !fns.has(`config.${key}`)) { - fns.set(`config.${key}`, value as Mock); - } - } - - const configProxy = new Proxy(configTarget, { - get: neverCrashGet("config", mint), - }); - const apiClientProxy = new Proxy(apiClientTarget, { - get: neverCrashGet("apiClient", mint), - }); - - /** Memoized; routes facade names onto the same objects, others onto the floor. */ - let legacy: LegacyClient | undefined; - function toLegacyWorkspaceClient(): LegacyClient { - legacy ??= new Proxy( - {}, - { - get: (target: Any, prop: Any): Any => { - // Same passthrough rules as `neverCrashGet` — shared, not copied, so a - // key added to PASSTHROUGH_DENY cannot cover one trap and miss this - // one (miss `then` here and `await client` hangs again). - const passthrough = passthroughFor(target, prop); - if (passthrough !== NOT_PASSTHROUGH) return passthrough; - if (prop === "config") return configProxy; - if (prop === "apiClient") return apiClientProxy; - if (prop === "toLegacyWorkspaceClient") { - return toLegacyWorkspaceClient; - } - return service(String(prop)); - }, - }, - ) as LegacyClient; - return legacy; - } + // `host` is read as a string, so it cannot be a mock. A `"config.host"` + // response seeds it too. + const host: string = + (merged["config.host"] as string | undefined) ?? + config.host ?? + "https://test.databricks.com"; // Modular auth seam. Seed via `responses: { request: ... }` (a `Response`, or // a function returning one); `request` defaults to a fresh `{}` JSON body. const seamDefaults: Record Any> = { - getHost: async () => configTarget.host, + getHost: async () => host, authenticate: async (headers: Headers) => { headers.set("Authorization", "Bearer test-token"); }, @@ -304,9 +223,6 @@ export function createMockWorkspaceClient( ...(Object.fromEntries( FACADE_SERVICES.map((name) => [name, service(name)]), ) as Pick), - config: configProxy as WorkspaceClient["config"], - apiClient: apiClientProxy as WorkspaceClient["apiClient"], - toLegacyWorkspaceClient, }; clientFns.set(client, fns); @@ -318,7 +234,7 @@ export function createMockWorkspaceClient( * so `expect(client.jobs.getRun).toHaveBeenCalled()` does not typecheck. * * Minting is idempotent, so this can be called before the code under test runs. - * Throws for a non-function member such as `"config.host"`. + * Throws for a path that is not a mocked function. */ export function getMock(client: MockWorkspaceClient, path: string): Mock { const fns = clientFns.get(client); @@ -342,7 +258,7 @@ export function getMock(client: MockWorkspaceClient, path: string): Mock { if (typeof resolved !== "function") { throw new Error( `getMock: "${path}" is not a mocked function (got ${typeof resolved}). ` + - "Members seeded with a real value, such as config.host, have no mock.", + "Only methods have a mock.", ); } return resolved as Mock; diff --git a/packages/appkit/src/testing/tests/create-test-app.test.ts b/packages/appkit/src/testing/tests/create-test-app.test.ts index 994f950c9..09ed8e9a4 100644 --- a/packages/appkit/src/testing/tests/create-test-app.test.ts +++ b/packages/appkit/src/testing/tests/create-test-app.test.ts @@ -298,13 +298,13 @@ describe("createTestApp", () => { }); }); - test("apiClient.request has zero calls after boot", async () => { + test("request has zero calls after boot", async () => { await withApp({ plugins: [probe()] }, async (app) => { // A canary for two hazards at once: DATABRICKS_WORKSPACE_ID must // short-circuit the SCIM probe in getWorkspaceId, and internal telemetry // must stay off. If either regresses, request assertions get polluted and // this fails loudly. - expect(getMock(app.client, "apiClient.request")).toHaveBeenCalledTimes(0); + expect(getMock(app.client, "request")).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/appkit/src/testing/tests/fixtures.test.ts b/packages/appkit/src/testing/tests/fixtures.test.ts index 4382b060e..ffd4e55bc 100644 --- a/packages/appkit/src/testing/tests/fixtures.test.ts +++ b/packages/appkit/src/testing/tests/fixtures.test.ts @@ -379,6 +379,5 @@ describe("createApiError — genuine ApiError factory", () => { }); // Should have response (undefined or null) and details (array) expect(error).toHaveProperty("response"); - expect(error).toHaveProperty("errorInfoType"); }); }); diff --git a/packages/appkit/src/testing/tests/mock-workspace-client.test.ts b/packages/appkit/src/testing/tests/mock-workspace-client.test.ts index 1dada4f5c..b022d45e1 100644 --- a/packages/appkit/src/testing/tests/mock-workspace-client.test.ts +++ b/packages/appkit/src/testing/tests/mock-workspace-client.test.ts @@ -23,7 +23,6 @@ describe("createMockWorkspaceClient", () => { ["files", "listDirectory", undefined], ["genie", "genieGetConversationMessage", undefined], ["jobs", "getRun", undefined], - ["servingEndpoints", "get", undefined], ["warehouses", "getWarehouse", { state: "RUNNING" }], ["warehouses", "startWarehouse", undefined], ["statementExecution", "executeStatement", SUCCEEDED], @@ -34,37 +33,13 @@ describe("createMockWorkspaceClient", () => { await expect(svc(client, service)[method]({})).resolves.toEqual(expected); }); - test("config and apiClient are reachable, and not mocks where it matters", () => { + test("the auth seam is reachable: host is a real string, request a Response", async () => { const client = mk(); - // Both read directly by production code — a Promise or mock here breaks it. - expect(typeof client.config.host).toBe("string"); - expect(client.config.host).toBeTruthy(); - expect(typeof client.apiClient.userAgent()).toBe("string"); - }); - - test("a seeded userAgent stays synchronous, not a Promise", async () => { - // Resolving it would put "[object Promise]" into a Headers value. - const client = mk({ responses: { "apiClient.userAgent": "custom/9" } }); - expect(client.apiClient.userAgent()).toBe("custom/9"); - const headers = new Headers(); - headers.set("user-agent", client.apiClient.userAgent()); - expect(headers.get("user-agent")).toBe("custom/9"); - // `request` is genuinely async, so its seeds still resolve. - await expect( - mk({ responses: { "apiClient.request": { ok: 1 } } }).apiClient.request( - {} as never, - ), - ).resolves.toEqual({ ok: 1 }); - }); - - test("apiClient.request is depth-2 and destructurable", async () => { - await expect(mk().apiClient.request({} as never)).resolves.toEqual({}); - const client = mk({ - responses: { "apiClient.request": { results: [] } }, - }); - await expect(client.apiClient.request({} as never)).resolves.toEqual({ - results: [], - }); + const host = await client.getHost(); + expect(typeof host).toBe("string"); + expect(host).toBeTruthy(); + const res = await client.request({ method: "GET", path: "/x" }); + await expect(res.json()).resolves.toEqual({}); }); }); @@ -105,50 +80,33 @@ describe("createMockWorkspaceClient", () => { ).resolves.toBeUndefined(); }); - test("the config option overrides defaults and adds members", () => { - const authenticate = vi.fn(); - const client = mk({ - config: { host: "https://custom.example.com", authenticate }, - }); - expect(client.config.host).toBe("https://custom.example.com"); - expect(client.config.authenticate).toBe(authenticate); + test("the config option seeds the host", async () => { + const client = mk({ config: { host: "https://custom.example.com" } }); + await expect(client.getHost()).resolves.toBe( + "https://custom.example.com", + ); }); - test('a "config.host" response stays a raw string, not a mock', () => { - expect( - mk({ responses: { "config.host": "https://a.b" } }).config.host, - ).toBe("https://a.b"); + test('a "config.host" response seeds the host as a raw string', async () => { + await expect( + mk({ responses: { "config.host": "https://a.b" } }).getHost(), + ).resolves.toBe("https://a.b"); }); - test("config.authenticate stamps a header; ensureResolved resolves", async () => { + test("authenticate stamps a header", async () => { const client = mk(); const headers = new Headers(); // Asserting only "was called" would pass against a mock that does nothing. - await client.config.authenticate(headers); + await client.authenticate(headers); expect(headers.get("Authorization")).toBe("Bearer test-token"); - await expect(client.config.ensureResolved()).resolves.toBeUndefined(); - }); - - test("an unknown member of a seeded namespace still hits the floor", () => { - expect( - typeof (mk().config as never as Record).nope, - ).toBe("function"); }); }); describe("memoization (call assertions depend on it)", () => { - test("methods, namespaces, and the legacy view share one identity", () => { + test("methods and namespaces share one identity", () => { const client = mk(); expect(client.jobs.getRun).toBe(client.jobs.getRun); expect(client.jobs).toBe(client.jobs); - expect(client.toLegacyWorkspaceClient().jobs.getRun).toBe( - client.jobs.getRun, - ); - }); - - test("un-faceted legacy services also work", async () => { - const legacy = mk().toLegacyWorkspaceClient(); - await expect(svc(legacy, "clusters").list({})).resolves.toBeUndefined(); }); }); @@ -164,9 +122,8 @@ describe("createMockWorkspaceClient", () => { // ownKeys stays default, so a service inspects as {} instead of minting a // mock per probed property. expect(inspect(client.jobs)).toBe("{}"); - expect(inspect(client.toLegacyWorkspaceClient())).toBe("{}"); - expect(inspect(client)).toContain("https://test.databricks.com"); - expect(() => JSON.stringify(client.config)).not.toThrow(); + expect(() => inspect(client)).not.toThrow(); + expect(() => JSON.stringify(client)).not.toThrow(); expect(() => expect(client.jobs).toEqual({})).not.toThrow(); const log = vi.spyOn(console, "log").mockImplementation(() => {}); @@ -228,14 +185,9 @@ describe("createMockWorkspaceClient", () => { expect(getRun).toHaveBeenCalledWith({ run_id: 7 }); }); - test("resolves seeded members and rejects non-function paths", () => { + test("resolves seam members and rejects non-clients", () => { const client = mk(); - expect(getMock(client, "apiClient.request")).toBe( - client.apiClient.request, - ); - expect(() => getMock(client, "config.host")).toThrow( - /not a mocked function/, - ); + expect(getMock(client, "request")).toBe(client.request); expect(() => getMock({} as never, "jobs.getRun")).toThrow( /not a createMockWorkspaceClient/, ); @@ -296,10 +248,8 @@ describe("createMockWorkspaceClient", () => { // @ts-expect-error - `getMessagez` is not a genie method void client.genie.getMessagez; - // `host` is `string | undefined` in the SDK, so the honest claim is that it - // narrows to a string — not that it is non-optional. - const host = client.config.host; - expect(typeof host).toBe("string"); + const host: Promise = client.getHost(); + void host; const getRun = getMock(client, "jobs.getRun"); getRun.mockResolvedValue({ state: "TERMINATED" }); diff --git a/packages/appkit/src/type-generator/statement-result.ts b/packages/appkit/src/type-generator/statement-result.ts index 9f5f791fe..7fbf6acf7 100644 --- a/packages/appkit/src/type-generator/statement-result.ts +++ b/packages/appkit/src/type-generator/statement-result.ts @@ -10,8 +10,8 @@ const logger = createLogger("type-generator:statement-result"); * read rows from `result.dataArray`, regardless of the wire format the * warehouse chose. * - * `@databricks/sdk-experimental`'s `executeStatement` defaults to an - * `ARROW_STREAM` disposition. With an `INLINE` disposition the single + * A warehouse can answer in the `ARROW_STREAM` format. With an `INLINE` + * disposition the single * DESCRIBE row is returned as a base64-encoded Arrow IPC stream in * `result.attachment` and `result.dataArray` is left undefined. The metric * and query type generators only ever read `result.dataArray`, so without diff --git a/packages/appkit/src/type-generator/tests/typegen-client.test.ts b/packages/appkit/src/type-generator/tests/typegen-client.test.ts index 910f5ab08..283c08337 100644 --- a/packages/appkit/src/type-generator/tests/typegen-client.test.ts +++ b/packages/appkit/src/type-generator/tests/typegen-client.test.ts @@ -86,9 +86,11 @@ describe("createTypegenWorkspaceClient with the real factory", () => { actual.createWorkspaceClient, ); - const { config } = createTypegenWorkspaceClient(); - expect(config.authType).toBe("pat"); - expect(config.host).toBe(DEV_HOST); - expect(config.token).toBe(DEV_TOKEN); + // The dev token is what gets sent, not the SP env credentials. + const client = createTypegenWorkspaceClient(); + const headers = new Headers(); + await client.authenticate(headers); + expect(headers.get("Authorization")).toBe(`Bearer ${DEV_TOKEN}`); + expect(await client.getHost()).toBe(DEV_HOST); }); }); diff --git a/packages/appkit/src/type-generator/tests/unreachable-warehouse-gate.test.ts b/packages/appkit/src/type-generator/tests/unreachable-warehouse-gate.test.ts index 0ffc0ed71..077febf9a 100644 --- a/packages/appkit/src/type-generator/tests/unreachable-warehouse-gate.test.ts +++ b/packages/appkit/src/type-generator/tests/unreachable-warehouse-gate.test.ts @@ -10,10 +10,10 @@ const mocks = vi.hoisted(() => ({ executeStatement: vi.fn(), })); -// Stub the wrapper, not `@databricks/sdk-experimental` underneath it: the -// wrapper re-exports SDK values (`ConfigError`, `Context`, `Time`, `TimeUnits`) -// that a bare SDK factory mock would drop, breaking module init. Spreading -// `importOriginal` keeps those intact while swapping only the factory. +// Stub the wrapper, not the SDK underneath it: the wrapper exports runtime +// values (`ApiError`, `isAuthConfigError`) that a bare factory mock would drop, +// breaking module init. Spreading `importOriginal` keeps those intact while +// swapping only the factory. vi.mock("../../workspace-client", async (importOriginal) => { const actual = await importOriginal(); diff --git a/packages/appkit/src/workspace-client/index.ts b/packages/appkit/src/workspace-client/index.ts index d87a83341..40a9d7e1c 100644 --- a/packages/appkit/src/workspace-client/index.ts +++ b/packages/appkit/src/workspace-client/index.ts @@ -1,13 +1,10 @@ /** - * AppKit workspace-client wrapper — the single entry point every AppKit module - * uses to reach a Databricks SDK client. Isolates `@databricks/sdk-experimental` - * to `./legacy.ts` so services can migrate to the modular SDK incrementally - * behind a stable facade. + * AppKit workspace-client wrapper: the single entry point every AppKit module + * uses to reach a Databricks SDK client. The modular `@databricks/sdk-*` + * packages are isolated to `shared/workspace-client`. */ -export { ApiError, ConfigError, Context, createWorkspaceClient } from "shared"; -// Forwards every wrapper type — legacy service namespaces (files/serving), -// the client option/waiter types, and the modular SDK client + model types -// (warehouses, statementExecution). `sql` is gone: its statement + warehouse -// types now come from the modular SDK. +export { ApiError, createWorkspaceClient, isAuthConfigError } from "shared"; +// Forwards every wrapper type: the facade, its options, and the modular SDK +// client + model types. export type * from "shared/workspace-client"; diff --git a/packages/shared/package.json b/packages/shared/package.json index 9c2121e65..44acdf3fd 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -51,7 +51,6 @@ "@databricks/sdk-apps": "0.54.0", "@databricks/sdk-auth": "0.51.0", "@databricks/sdk-core": "0.51.0", - "@databricks/sdk-experimental": "0.17.0", "@databricks/sdk-database": "0.51.0", "@databricks/sdk-files": "0.52.0", "@databricks/sdk-experiments": "0.52.0", diff --git a/packages/shared/src/cli/commands/doctor/databricks-client.ts b/packages/shared/src/cli/commands/doctor/databricks-client.ts index bb915b4f1..a3f6240a2 100644 --- a/packages/shared/src/cli/commands/doctor/databricks-client.ts +++ b/packages/shared/src/cli/commands/doctor/databricks-client.ts @@ -111,15 +111,9 @@ export async function getLakebasePool( // that dumps the raw SDK ApiError (stack + full response blob) to stderr on a // failed token fetch. doctor classifies and prints that failure itself, so the // library's dump is just noise on top of our clean one-line report. - // `@databricks/lakebase` is still typed against the legacy client: it calls - // `apiClient.request` and an argument-less `currentUser.me()`, which the - // modular SCIM client rejects, so hand it that exact shape. - const ws = client as WorkspaceClient; + // Lakebase takes the facade as is: it uses `request` and the typed `postgres`. const pool = appkit.createLakebasePool({ - workspaceClient: { - apiClient: ws.apiClient, - currentUser: { me: () => ws.currentUser.me({}) }, - }, + workspaceClient: client as WorkspaceClient, logger: { error: false }, }); return pool as unknown as LakebasePoolHandle; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 85e75f699..4bc6ebcd1 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -19,16 +19,11 @@ export { export * from "./sql"; export * from "./sse/analytics"; export * from "./tunnel"; -// Re-export the workspace-client's runtime *values* from the root so AppKit -// (and other consumers) reach them via `shared` — the import style the bundler -// inlines. Only values: the SDK `sql` type would collide with the `sql` query -// helper from `./sql`, so workspace-client types stay on `shared/workspace-client` -// (type-only imports carry no runtime cost). +// Re-export the workspace-client's runtime values from the root so AppKit (and +// other consumers) reach them via `shared`, the import style the bundler +// inlines. Types stay on `shared/workspace-client`. export { ApiError, - ConfigError, - Context, createWorkspaceClient, - Time, - TimeUnits, + isAuthConfigError, } from "./workspace-client"; diff --git a/packages/shared/src/workspace-client/client.ts b/packages/shared/src/workspace-client/client.ts index 0681d901d..5487aa89f 100644 --- a/packages/shared/src/workspace-client/client.ts +++ b/packages/shared/src/workspace-client/client.ts @@ -2,17 +2,10 @@ * `AppKitWorkspaceClient` — the facade implementation. Construct via * `createWorkspaceClient(...)`; this class is internal. * - * Migrated service accessors build their modular client lazily (see - * `modular.ts`); the rest (`servingEndpoints`, `config`, `apiClient`) delegate - * to a single lazily-constructed legacy SDK client. To migrate one, replace its - * getter here with a modular client instance (and update its connector + the - * accessor type in `types.ts`). No other AppKit module touches the SDK. + * Every service accessor builds its modular `@databricks/sdk-*` client lazily + * (see `modular.ts`); raw REST calls go through the auth + request seam. No + * other AppKit module touches the SDK. */ -import { - buildLegacyWorkspaceClient, - type LegacyWorkspaceClient, - type WorkspaceClientOptions, -} from "./legacy"; import { buildScimClient, buildStatementExecutionClient, @@ -50,11 +43,11 @@ import { type WorkspaceAuth, type WorkspaceRequest, } from "./modular"; +import type { WorkspaceClientOptions } from "./options"; import type { WorkspaceClient } from "./types"; export class AppKitWorkspaceClient implements WorkspaceClient { readonly #opts: WorkspaceClientOptions; - #legacy?: LegacyWorkspaceClient; #warehouses?: WarehousesClient; #statementExecution?: StatementExecutionClient; #auth?: WorkspaceAuth; @@ -77,7 +70,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { this.#opts = opts; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get files(): FilesClient { if (!this.#files) { this.#files = buildFilesClient(this.#opts); @@ -85,7 +78,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#files; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get warehouses(): WarehousesClient { if (!this.#warehouses) { this.#warehouses = buildWarehousesClient(this.#opts); @@ -93,7 +86,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#warehouses; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get genie(): GenieClient { if (!this.#genie) { this.#genie = buildGenieClient(this.#opts); @@ -101,7 +94,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#genie; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get apps(): AppsClient { if (!this.#apps) { this.#apps = buildAppsClient(this.#opts); @@ -109,7 +102,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#apps; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get database(): DatabaseClient { if (!this.#database) { this.#database = buildDatabaseClient(this.#opts); @@ -117,7 +110,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#database; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get jobs(): JobsClient { if (!this.#jobs) { this.#jobs = buildJobsClient(this.#opts); @@ -125,7 +118,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#jobs; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get experiments(): ExperimentsClient { if (!this.#experiments) { this.#experiments = buildExperimentsClient(this.#opts); @@ -133,7 +126,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#experiments; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get vectorSearch(): VectorSearchClient { if (!this.#vectorSearch) { this.#vectorSearch = buildVectorSearchClient(this.#opts); @@ -141,7 +134,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#vectorSearch; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get tables(): TablesClient { if (!this.#tables) { this.#tables = buildTablesClient(this.#opts); @@ -149,7 +142,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#tables; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get postgres(): PostgresClient { if (!this.#postgres) { this.#postgres = buildPostgresClient(this.#opts); @@ -157,7 +150,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#postgres; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get connections(): ConnectionsClient { if (!this.#connections) { this.#connections = buildConnectionsClient(this.#opts); @@ -165,7 +158,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#connections; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get functions(): FunctionsClient { if (!this.#functions) { this.#functions = buildFunctionsClient(this.#opts); @@ -173,7 +166,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#functions; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get volumes(): VolumesClient { if (!this.#volumes) { this.#volumes = buildVolumesClient(this.#opts); @@ -181,7 +174,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#volumes; } - // Migrated to the modular SDK — built lazily, independent of the legacy client. + // Migrated to the modular SDK — built lazily. get statementExecution(): StatementExecutionClient { if (!this.#statementExecution) { this.#statementExecution = buildStatementExecutionClient(this.#opts); @@ -189,8 +182,6 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#statementExecution; } - // Modular serving endpoints client. A new accessor because `servingEndpoints` - // is the legacy client's public type and can't change. get modelServing(): ModelServingClient { if (!this.#modelServing) { this.#modelServing = buildModelServingClient(this.#opts); @@ -198,11 +189,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#modelServing; } - get servingEndpoints() { - return this.#getLegacy().servingEndpoints; - } - - // Migrated to the modular SDK (SCIM) — built lazily, independent of the legacy client. + // Migrated to the modular SDK (SCIM) — built lazily. get currentUser(): ScimClient { if (!this.#currentUser) { this.#currentUser = buildScimClient(this.#opts); @@ -210,7 +197,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#currentUser; } - // Modular auth + raw-request seam — built lazily, independent of the legacy client. + // Modular auth + raw-request seam — built lazily. getHost(): Promise { return this.#getAuth().getHost(); } @@ -223,29 +210,10 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#getAuth().request(req); } - get config() { - return this.#getLegacy().config; - } - - get apiClient() { - return this.#getLegacy().apiClient; - } - - toLegacyWorkspaceClient(): LegacyWorkspaceClient { - return this.#getLegacy(); - } - #getAuth(): WorkspaceAuth { if (!this.#auth) { this.#auth = buildWorkspaceAuth(this.#opts); } return this.#auth; } - - #getLegacy(): LegacyWorkspaceClient { - if (!this.#legacy) { - this.#legacy = buildLegacyWorkspaceClient(this.#opts); - } - return this.#legacy; - } } diff --git a/packages/shared/src/workspace-client/errors.ts b/packages/shared/src/workspace-client/errors.ts index 408c6db4c..7a9de2c4a 100644 --- a/packages/shared/src/workspace-client/errors.ts +++ b/packages/shared/src/workspace-client/errors.ts @@ -1,17 +1,14 @@ /** * API error type surfaced by the AppKit workspace client. * - * Migrated services throw the modular `@databricks/sdk-core` `ApiError`, a - * different class from the legacy `@databricks/sdk-experimental` one, with the - * status in `httpStatusCode` and the code in `code`. To keep `instanceof - * ApiError`, `.statusCode` and `.errorCode` working for AppKit and its - * consumers, the exported `ApiError` matches both classes, and modular errors - * also expose the legacy field names. + * AppKit's own error class, with the field names AppKit and its consumers read + * (`statusCode`, `errorCode`). The modular `@databricks/sdk-core` `ApiError` + * keeps its status in `httpStatusCode` and its code in `code`, so it also + * exposes the AppKit names, and `instanceof ApiError` matches it too. */ import { ApiError as ModularApiError } from "@databricks/sdk-core/apierror"; -import { ApiError as LegacyApiError } from "@databricks/sdk-experimental"; -// Legacy-name aliases on modular errors. Setters store an own value, so call +// AppKit-name aliases on modular errors. Setters store an own value, so call // sites that assign `statusCode` (e.g. via Object.assign) keep working. for (const [name, source] of [ ["statusCode", "httpStatusCode"], @@ -35,8 +32,27 @@ for (const [name, source] of [ } } -export class ApiError extends LegacyApiError { +export class ApiError extends Error { + constructor( + message: string, + readonly errorCode: string, + readonly statusCode: number, + readonly response?: unknown, + readonly details: unknown[] = [], + ) { + super(message); + this.name = "ApiError"; + } + + /** Like the legacy SDK: only 429 is retryable by status alone. */ + isRetryable(): boolean { + return this.statusCode === 429; + } + static [Symbol.hasInstance](value: unknown): boolean { - return value instanceof LegacyApiError || value instanceof ModularApiError; + return ( + Function.prototype[Symbol.hasInstance].call(ApiError, value) || + value instanceof ModularApiError + ); } } diff --git a/packages/shared/src/workspace-client/factory.ts b/packages/shared/src/workspace-client/factory.ts index e1c38fb31..de57fcd08 100644 --- a/packages/shared/src/workspace-client/factory.ts +++ b/packages/shared/src/workspace-client/factory.ts @@ -2,7 +2,7 @@ * Public factory for constructing a wrapper instance. */ import { AppKitWorkspaceClient } from "./client"; -import type { WorkspaceClientOptions } from "./legacy"; +import type { WorkspaceClientOptions } from "./options"; import type { WorkspaceClient } from "./types"; /** diff --git a/packages/shared/src/workspace-client/index.ts b/packages/shared/src/workspace-client/index.ts index 95bd98cc7..b247bb5f4 100644 --- a/packages/shared/src/workspace-client/index.ts +++ b/packages/shared/src/workspace-client/index.ts @@ -1,27 +1,12 @@ /** - * AppKit workspace-client wrapper — the single entry point every AppKit module - * uses to reach a Databricks SDK client. Isolates `@databricks/sdk-experimental` - * to `./legacy.ts` so services can migrate to the modular SDK incrementally - * behind a stable facade. + * AppKit workspace-client wrapper: the single entry point every AppKit module + * uses to reach a Databricks SDK client. The modular `@databricks/sdk-*` + * packages are isolated to this directory. */ export { ApiError } from "./errors"; -export { ModularApiError } from "./modular"; export { createWorkspaceClient } from "./factory"; -export { resolveProfile } from "./modular"; -export type { - CancellationToken, - ClientOptions, - LegacyWorkspaceClient, - WorkspaceClientOptions, -} from "./legacy"; -// SDK value + type re-exports so AppKit modules import them from the wrapper. -export { - ConfigError, - Context, - loadConfigFile, - Time, - TimeUnits, -} from "./legacy"; -export type { serving, sql, WorkspaceClient } from "./types"; -// Modular SDK client + model types (warehouses). +export { isAuthConfigError, ModularApiError, resolveProfile } from "./modular"; +export type { ClientOptions, WorkspaceClientOptions } from "./options"; +export type { WorkspaceClient } from "./types"; +// Modular SDK client + model types. export type * from "./modular"; diff --git a/packages/shared/src/workspace-client/legacy.ts b/packages/shared/src/workspace-client/legacy.ts deleted file mode 100644 index 745cf0976..000000000 --- a/packages/shared/src/workspace-client/legacy.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * The single module in AppKit allowed to import `@databricks/sdk-experimental` - * directly (enforced by the oxlint `no-restricted-imports` boundary rule). Every - * other AppKit module reaches the SDK through the wrapper's re-exports and the - * {@link WorkspaceClient} facade. - * - * Isolating the SDK here is what makes the incremental migration to the modular - * Databricks SDK a localized change: to migrate a service, swap its getter in - * `client.ts` from the legacy delegate to the modular client — nothing else in - * the codebase imports the SDK, so the blast radius is one connector + its test. - */ - -import type { - ClientOptions, - WorkspaceClient as SdkWorkspaceClient, -} from "@databricks/sdk-experimental"; -import * as SDK from "@databricks/sdk-experimental"; - -const { WorkspaceClient: SdkWorkspaceClientCtor } = SDK; - -/** The concrete legacy SDK client type. */ -export type LegacyWorkspaceClient = SdkWorkspaceClient; - -/** - * Options used to construct the wrapper. Mirrors the subset of the old SDK's - * `Config` + `ClientOptions` that AppKit relies on today; we deliberately do - * NOT re-expose every old-SDK config knob. - */ -export interface WorkspaceClientOptions { - /** Databricks host, e.g. https://my-workspace.cloud.databricks.com. Defaults to DATABRICKS_HOST / profile resolution. */ - host?: string; - /** `~/.databrickscfg` profile name. Used when no host/token is provided. */ - profile?: string; - /** Bearer token. When set, `authType` defaults to "pat". */ - token?: string; - /** Authentication strategy passed to the legacy client. */ - authType?: "pat"; - /** - * SDK client options (product / productVersion / userAgentExtra) used to - * stamp the outbound User-Agent. Produced by `getClientOptions()`; omitted - * by build-time callers that don't stamp a User-Agent. - */ - clientOptions?: ClientOptions; -} - -/** - * Construct a legacy `WorkspaceClient` from wrapper options. - * - * Centralised so the wrapper facade, the `.toLegacyWorkspaceClient()` escape - * hatch, and any per-request OBO client all build it the same way. - */ -export function buildLegacyWorkspaceClient( - opts: WorkspaceClientOptions, -): LegacyWorkspaceClient { - // Check `token !== undefined`, NOT truthiness: an explicitly-passed token - // must stick to the PAT path even when it's an empty string. Falling through - // to the default-auth branch on an empty OBO token would silently - // authenticate as the service principal instead of failing — a privilege - // escalation in the OBO path. An invalid/empty token should blow up loudly. - const cfg = - opts.token !== undefined - ? { host: opts.host, token: opts.token, authType: opts.authType ?? "pat" } - : opts.host - ? { host: opts.host } - : opts.profile - ? { profile: opts.profile } - : {}; - return new SdkWorkspaceClientCtor(cfg, opts.clientOptions); -} - -// ── SDK type re-exports ────────────────────────────────────────────── -export type { - CancellationToken, - ClientOptions, -} from "@databricks/sdk-experimental"; -// ── SDK value re-exports ───────────────────────────────────────────── -// -// AppKit modules import these from the wrapper instead of the SDK so the -// boundary rule holds. `Context` bridges AbortSignal → CancellationToken -// (serving/jobs/sql-warehouse); `Time`/`TimeUnits` are re-exported by `shared`'s index; -// `ConfigError` is matched in service-context's auth-failure handling. -// -// These are sourced off the namespace import rather than `export { ... } from` -// because the SDK is CommonJS: its `Time` export is emitted as a getter that -// calls `__importDefault(...)`, which defeats Node's static named-export -// detection — a direct `export { Time }` throws "does not provide an export -// named 'Time'" at ESM link time. `Time` is only reachable via the module -// object, so we fall back to `SDK.default.Time` (matching the original genie -// connector's `SDK.Time ?? SDK.default.Time` guard). -export const { ConfigError, Context, TimeUnits, loadConfigFile } = SDK; -export const Time = - SDK.Time ?? (SDK as unknown as { default: typeof SDK }).default.Time; diff --git a/packages/shared/src/workspace-client/modular.ts b/packages/shared/src/workspace-client/modular.ts index 5bb351981..fc2c02022 100644 --- a/packages/shared/src/workspace-client/modular.ts +++ b/packages/shared/src/workspace-client/modular.ts @@ -45,10 +45,14 @@ import { tokenProviderFn, } from "@databricks/sdk-auth"; import { + DefaultCredentialsError, defaultCredentials, + M2mCredentialsError, newM2mCredentials, newPatCredentials, newU2mCredentials, + PatCredentialsError, + U2mCredentialsError, } from "@databricks/sdk-auth/credentials"; import { type HttpClient, @@ -74,7 +78,7 @@ import type { VectorSearchClient } from "@databricks/sdk-vectorsearch/v1"; import { WarehousesClient } from "@databricks/sdk-warehouses/v1"; import { ApiError } from "./errors"; -import type { WorkspaceClientOptions } from "./legacy"; +import type { WorkspaceClientOptions } from "./options"; /** * Prepend `https://` to a scheme-less host. The legacy SDK normalized the host @@ -182,6 +186,22 @@ function cachedDefaultCredentials( const execFileAsync = promisify(execFile); +/** Auth could not be configured (missing or unsupported credentials). */ +export class AuthConfigError extends Error { + override name = "AuthConfigError"; +} + +/** Whether `e` means auth isn't set up, from AppKit or the modular SDK. */ +export function isAuthConfigError(e: unknown): e is Error { + return ( + e instanceof AuthConfigError || + e instanceof DefaultCredentialsError || + e instanceof M2mCredentialsError || + e instanceof PatCredentialsError || + e instanceof U2mCredentialsError + ); +} + /** * Databricks CLI auth for a host, not a profile: what the legacy SDK used when * only a host was configured. The modular CLI strategy needs a profile name. @@ -202,7 +222,7 @@ function cliHostCredentials(host: string): TokenCredentials { } catch (e) { const err = e as { stderr?: unknown; message?: string }; const detail = String(err.stderr ?? err.message ?? e).trim(); - throw new Error( + throw new AuthConfigError( `cannot get an access token for ${host} from the Databricks CLI: ${detail}`, ); } @@ -224,7 +244,7 @@ function unsupportedCredentials(message: string): Credentials { return { name: () => "unsupported", authHeaders: async () => { - throw new Error(message); + throw new AuthConfigError(message); }, }; } diff --git a/packages/shared/src/workspace-client/options.ts b/packages/shared/src/workspace-client/options.ts new file mode 100644 index 000000000..9057966cf --- /dev/null +++ b/packages/shared/src/workspace-client/options.ts @@ -0,0 +1,27 @@ +/** + * Options used to construct the workspace client facade. AppKit's own shape, + * independent of any SDK: the modular clients are built from these in + * `modular.ts`. + */ + +/** User-Agent stamping: `/` plus extra segments. */ +export interface ClientOptions { + product?: string; + productVersion?: string; + userAgentExtra?: Record; +} + +export interface WorkspaceClientOptions { + /** Databricks host, e.g. https://my-workspace.cloud.databricks.com. Defaults to DATABRICKS_HOST / profile resolution. */ + host?: string; + /** `~/.databrickscfg` profile name. Used when no host/token is provided. */ + profile?: string; + /** Bearer token (PAT, or the user's OBO token). */ + token?: string; + /** + * SDK client options (product / productVersion / userAgentExtra) used to + * stamp the outbound User-Agent. Produced by `getClientOptions()`; omitted + * by build-time callers that don't stamp a User-Agent. + */ + clientOptions?: ClientOptions; +} diff --git a/packages/shared/src/workspace-client/tests/legacy.test.ts b/packages/shared/src/workspace-client/tests/legacy.test.ts deleted file mode 100644 index 95d67e544..000000000 --- a/packages/shared/src/workspace-client/tests/legacy.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { beforeEach, describe, expect, test, vi } from "vitest"; - -// The wrapper's own tests are the one place allowed to mock the SDK directly -// (everything else goes through the wrapper). We capture the config the SDK -// `WorkspaceClient` constructor receives so we can assert how wrapper options -// map onto SDK auth config. -const { ctorCalls } = vi.hoisted(() => ({ - ctorCalls: [] as Array<{ config: unknown; options: unknown }>, -})); - -vi.mock("@databricks/sdk-experimental", () => ({ - WorkspaceClient: vi.fn().mockImplementation((config, options) => { - ctorCalls.push({ config, options }); - return { config, options }; - }), - // Re-exported by legacy.ts on module load; stubbed so the import resolves. - ConfigError: class ConfigError extends Error {}, - Context: class Context {}, - Time: class Time {}, - TimeUnits: { milliseconds: 0 }, - loadConfigFile: vi.fn(), -})); - -import { buildLegacyWorkspaceClient } from "../legacy"; - -describe("buildLegacyWorkspaceClient", () => { - beforeEach(() => { - ctorCalls.length = 0; - }); - - test("a valid token uses the PAT path", () => { - buildLegacyWorkspaceClient({ token: "abc", host: "https://x" }); - expect(ctorCalls[0].config).toEqual({ - host: "https://x", - token: "abc", - authType: "pat", - }); - }); - - test("no token falls through to the default auth chain (empty config)", () => { - buildLegacyWorkspaceClient({}); - expect(ctorCalls[0].config).toEqual({}); - }); - - test("host without token passes host only (still default auth)", () => { - buildLegacyWorkspaceClient({ host: "https://x" }); - expect(ctorCalls[0].config).toEqual({ host: "https://x" }); - }); - - // Regression guard (PR #475 review): an explicit empty-string token must - // stick to the PAT path so the SDK rejects it loudly, NOT fall through to - // default auth and silently run as the service principal — that would be a - // privilege escalation in the OBO path. - test("an empty-string token stays on the PAT path, not SP fallback", () => { - buildLegacyWorkspaceClient({ token: "", host: "https://x" }); - expect(ctorCalls[0].config).toEqual({ - host: "https://x", - token: "", - authType: "pat", - }); - // Must NOT have degraded to the host-only / default-auth branch. - expect(ctorCalls[0].config).not.toEqual({ host: "https://x" }); - }); - - test("authType defaults to pat and is overridable", () => { - buildLegacyWorkspaceClient({ token: "abc", host: "https://x" }); - expect((ctorCalls[0].config as { authType: string }).authType).toBe("pat"); - }); -}); diff --git a/packages/shared/src/workspace-client/types.ts b/packages/shared/src/workspace-client/types.ts index b337d1128..3b172884d 100644 --- a/packages/shared/src/workspace-client/types.ts +++ b/packages/shared/src/workspace-client/types.ts @@ -1,19 +1,8 @@ /** - * The wrapper facade type and the SDK type-namespace re-exports used across - * AppKit. - * - * Accessors for migrated services are typed against their modular - * `@databricks/sdk-*` clients (built in `modular.ts`); the rest are still the - * legacy SDK's, delegated through `toLegacyWorkspaceClient()`. Migrating a - * service means changing its accessor's type here, its getter in `client.ts`, - * and the connector that consumes it. - * - * Type-namespace re-exports (`serving`, `sql`) point at - * `@databricks/sdk-experimental` so existing AppKit call-site shapes stay - * stable. They move to the modular `@databricks/sdk-` model exports - * as each service migrates. + * The wrapper facade type and the modular SDK type re-exports used across + * AppKit. Every service accessor is a modular `@databricks/sdk-*` client + * (built in `modular.ts`); raw REST calls use the inherited `request()` seam. */ -import type { LegacyWorkspaceClient } from "./legacy"; import type { StatementExecutionClient, WarehousesClient, @@ -34,15 +23,6 @@ import type { PostgresClient, } from "./modular"; -// Legacy SDK type namespaces for un-migrated services, re-exported so AppKit -// modules import them from the wrapper rather than the SDK directly. `sql` -// stays only for the dev-mode warehouse discovery in appkit's -// `resources/warehouse.ts`, which reads the raw (snake_case) -// `/api/2.0/sql/warehouses` body via the still-legacy `apiClient` and types it -// as `sql.EndpointInfo[]`. It is not on the modular `listWarehouses` because -// that request has no `skip_cannot_use` filter, so it could pick a warehouse -// the caller can't use. Statement + warehouse service types come from `./modular`. -export type { serving, sql } from "@databricks/sdk-experimental"; // Modular SDK client + model types (warehouses, statementExecution, genie, jobs, files). export type * from "./modular"; @@ -51,8 +31,6 @@ export type * from "./modular"; * modular Databricks SDK: each service is its own accessor, so services can be * migrated one at a time behind this stable interface. * - * Accessors are legacy-typed for now (delegated to the underlying legacy SDK - * client); see the module docblock. */ export interface WorkspaceClient extends WorkspaceAuth { /** UC Volumes / Files API (modular SDK). */ @@ -96,35 +74,9 @@ export interface WorkspaceClient extends WorkspaceAuth { /** Statement Execution (modular SDK). */ readonly statementExecution: StatementExecutionClient; - /** Serving endpoints (modular SDK). `servingEndpoints` below stays legacy-typed. */ + /** Serving endpoints (modular SDK). */ readonly modelServing: ModelServingClient; - /** Serving Endpoints. */ - readonly servingEndpoints: LegacyWorkspaceClient["servingEndpoints"]; - /** Current user (modular SDK SCIM client; `me({})` returns the caller). */ readonly currentUser: ScimClient; - - /** - * Legacy SDK `Config`. Prefer `getHost()` / `authenticate(headers)` (modular, - * inherited from `WorkspaceAuth`); kept for structural `WorkspaceClientLike` - * callers (supervisor adapter) and the files upload (moves to the modular - * `files.uploadFile` with the files migration) until they migrate. - */ - readonly config: LegacyWorkspaceClient["config"]; - - /** - * Legacy low-level HTTP transport. Prefer `request(...)` (modular, inherited - * from `WorkspaceAuth`); still used by serving SSE streaming, vector search, - * the files upload User-Agent, and the agents adapters, which take a - * structural `apiClient` shape. - */ - readonly apiClient: LegacyWorkspaceClient["apiClient"]; - - /** - * Returns the underlying legacy `@databricks/sdk-experimental` - * `WorkspaceClient`, for handoff to code still typed against the old SDK - * (`@databricks/lakebase`). Transitional. - */ - toLegacyWorkspaceClient(): LegacyWorkspaceClient; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bff41d96a..702d0fa74 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -293,9 +293,6 @@ importers: '@databricks/sdk-database': specifier: 0.51.0 version: 0.51.0 - '@databricks/sdk-experimental': - specifier: 0.17.0 - version: 0.17.0 '@databricks/sdk-experiments': specifier: 0.52.0 version: 0.52.0 @@ -668,9 +665,6 @@ importers: '@databricks/sdk-database': specifier: 0.51.0 version: 0.51.0 - '@databricks/sdk-experimental': - specifier: 0.17.0 - version: 0.17.0 '@databricks/sdk-experiments': specifier: 0.52.0 version: 0.52.0 @@ -2101,10 +2095,6 @@ packages: resolution: {integrity: sha512-HkoMiF7dNDt6WRW0xhi7oPlBJQfxJ9suJhEZRFt08VwLMaWcw2PiF8monfHlkD4lkufEYV6CTxi5njQkciqiHA==} engines: {node: '>=22.0', npm: '>=10.0.0'} - '@databricks/sdk-experimental@0.17.0': - resolution: {integrity: sha512-dOJIt4F2nBk6HKObnv7Xbmy/qLYTy2835qhXSuW0Qw1QAXui9plmCet1KqG3yeQcMTyncWGbnhjGdQi8GEGQSA==} - engines: {node: '>=22.0', npm: '>=10.0.0'} - '@databricks/sdk-experiments@0.52.0': resolution: {integrity: sha512-RVpS4fMtfTKI/7WdyeZIdLdy7lBV3liO/W/dLH+zNwga7REbp581zAxZNaomCYJG/y7SrqrtMo77CVDDKH4AKg==} engines: {node: '>=22.0.0'} @@ -14470,15 +14460,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@databricks/sdk-experimental@0.17.0': - dependencies: - google-auth-library: 10.5.0 - ini: 6.0.0 - reflect-metadata: 0.2.2 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - '@databricks/sdk-experiments@0.52.0': dependencies: '@databricks/sdk-auth': 0.51.0 diff --git a/template/package-lock.json b/template/package-lock.json index 5ea2633d0..28a006dcb 100644 --- a/template/package-lock.json +++ b/template/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "@databricks/appkit": "0.88.1", "@databricks/appkit-ui": "0.88.1", - "@databricks/sdk-experimental": "0.17.0", "clsx": "2.1.1", "embla-carousel-react": "8.6.0", "lucide-react": "0.546.0", diff --git a/template/package.json b/template/package.json index e8bc23102..5ba8d59b2 100644 --- a/template/package.json +++ b/template/package.json @@ -31,7 +31,6 @@ "dependencies": { "@databricks/appkit": "0.88.1", "@databricks/appkit-ui": "0.88.1", - "@databricks/sdk-experimental": "0.17.0", "clsx": "2.1.1", "embla-carousel-react": "8.6.0", "lucide-react": "0.546.0", diff --git a/template/pnpm-lock.yaml b/template/pnpm-lock.yaml index fd7d502fd..dade35092 100644 --- a/template/pnpm-lock.yaml +++ b/template/pnpm-lock.yaml @@ -23,9 +23,6 @@ importers: '@databricks/appkit-ui': specifier: 0.88.1 version: 0.88.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@databricks/sdk-experimental': - specifier: 0.17.0 - version: 0.17.0 clsx: specifier: 2.1.1 version: 2.1.1 From 12cb731b6a4317757a8705bda5419079b4dac837 Mon Sep 17 00:00:00 2001 From: MarioCadenas Date: Fri, 16 Oct 2026 17:24:48 +0200 Subject: [PATCH 2/3] docs: regenerate the AppKit API reference for the modular workspace client Brings the generated reference in line with the stack: the facade no longer exposes the legacy accessors, and the files/jobs/agents types now come from the modular SDK. Co-authored-by: Isaac Signed-off-by: MarioCadenas --- docs/docs/api/appkit/Class.ApiError.md | 102 +++++++++++++++ .../api/appkit/Class.DatabricksAdapter.md | 4 +- .../api/appkit/Class.SupervisorApiAdapter.md | 4 +- .../Function.generateDatabaseCredential.md | 4 +- .../api/appkit/Function.getWorkspaceClient.md | 4 +- .../api/appkit/Interface.DirectoryEntry.md | 57 +++++++++ .../api/appkit/Interface.DownloadResponse.md | 41 ++++++ docs/docs/api/appkit/Interface.JobAPI.md | 40 ++++-- .../appkit/Interface.LakebasePoolConfig.md | 2 +- .../api/appkit/Interface.WorkspaceClient.md | 121 ++++++++++++------ .../appkit/Interface.WorkspaceClientLike.md | 66 ---------- .../Interface.WorkspaceClientOptions.md | 16 +-- .../appkit/TypeAlias.FilesDirectoryEntry.md | 9 ++ .../appkit/TypeAlias.FilesDownloadResponse.md | 9 ++ docs/docs/api/appkit/TypeAlias.JobsExport.md | 2 +- .../appkit/TypeAlias.WorkspaceClientLike.md | 19 +++ docs/docs/api/appkit/index.md | 9 +- docs/docs/api/appkit/typedoc-sidebar.ts | 37 +++++- 18 files changed, 399 insertions(+), 147 deletions(-) create mode 100644 docs/docs/api/appkit/Class.ApiError.md create mode 100644 docs/docs/api/appkit/Interface.DirectoryEntry.md create mode 100644 docs/docs/api/appkit/Interface.DownloadResponse.md delete mode 100644 docs/docs/api/appkit/Interface.WorkspaceClientLike.md create mode 100644 docs/docs/api/appkit/TypeAlias.FilesDirectoryEntry.md create mode 100644 docs/docs/api/appkit/TypeAlias.FilesDownloadResponse.md create mode 100644 docs/docs/api/appkit/TypeAlias.WorkspaceClientLike.md diff --git a/docs/docs/api/appkit/Class.ApiError.md b/docs/docs/api/appkit/Class.ApiError.md new file mode 100644 index 000000000..d19af2f8f --- /dev/null +++ b/docs/docs/api/appkit/Class.ApiError.md @@ -0,0 +1,102 @@ +# Class: ApiError + +## Extends + +- `Error` + +## Constructors + +### Constructor + +```ts +new ApiError( + message: string, + errorCode: string, + statusCode: number, + response?: unknown, + details?: unknown[]): ApiError; +``` + +#### Parameters + +| Parameter | Type | Default value | +| ------ | ------ | ------ | +| `message` | `string` | `undefined` | +| `errorCode` | `string` | `undefined` | +| `statusCode` | `number` | `undefined` | +| `response?` | `unknown` | `undefined` | +| `details?` | `unknown`[] | `[]` | + +#### Returns + +`ApiError` + +#### Overrides + +```ts +Error.constructor +``` + +## Properties + +### details + +```ts +readonly details: unknown[] = []; +``` + +*** + +### errorCode + +```ts +readonly errorCode: string; +``` + +*** + +### response? + +```ts +readonly optional response: unknown; +``` + +*** + +### statusCode + +```ts +readonly statusCode: number; +``` + +## Methods + +### isRetryable() + +```ts +isRetryable(): boolean; +``` + +Like the legacy SDK: only 429 is retryable by status alone. + +#### Returns + +`boolean` + +*** + +### \[hasInstance\]() + +```ts +static hasInstance: boolean; +``` + +#### Parameters + +| Parameter | Type | +| ------ | ------ | +| `value` | `unknown` | + +#### Returns + +`boolean` diff --git a/docs/docs/api/appkit/Class.DatabricksAdapter.md b/docs/docs/api/appkit/Class.DatabricksAdapter.md index 7efc5e890..27b966876 100644 --- a/docs/docs/api/appkit/Class.DatabricksAdapter.md +++ b/docs/docs/api/appkit/Class.DatabricksAdapter.md @@ -100,7 +100,7 @@ Chat Completions endpoint (`/ai-gateway/mlflow/v1/chat/completions`). Unlike [fromModelServing](#frommodelserving), the target model is named in the request body (`model`, e.g. `"system.ai.claude-opus-5-5"`) rather than in the URL: the gateway is a single fixed path that routes by the body's `model`. Auth -and transport reuse the SDK's `apiClient.request`, same as the serving +and transport reuse the workspace client's `request()`, same as the serving path, so no bespoke `fetch()` + token handling. The request/response wire format and tool-calling loop are identical to the serving path. @@ -188,7 +188,7 @@ static fromServingEndpoint(options: ServingEndpointOptions): Promise; +function generateDatabaseCredential(workspaceClient: LakebaseWorkspaceClient, request: GenerateDatabaseCredentialRequest): Promise; ``` Generate OAuth credentials for Postgres database connection using the proper Postgres API. @@ -13,7 +13,7 @@ as a password when connecting to Lakebase Postgres databases. | Parameter | Type | Description | | ------ | ------ | ------ | -| `workspaceClient` | `WorkspaceClient` | Databricks workspace client for authentication | +| `workspaceClient` | `LakebaseWorkspaceClient` | Databricks workspace client for authentication | | `request` | [`GenerateDatabaseCredentialRequest`](Interface.GenerateDatabaseCredentialRequest.md) | Request parameters including endpoint path and optional UC claims | ## Returns diff --git a/docs/docs/api/appkit/Function.getWorkspaceClient.md b/docs/docs/api/appkit/Function.getWorkspaceClient.md index 9511b944d..3e533a342 100644 --- a/docs/docs/api/appkit/Function.getWorkspaceClient.md +++ b/docs/docs/api/appkit/Function.getWorkspaceClient.md @@ -1,7 +1,7 @@ # Function: getWorkspaceClient() ```ts -function getWorkspaceClient(config: Partial): WorkspaceClient; +function getWorkspaceClient(config: Partial): LakebaseWorkspaceClient; ``` Get workspace client from config or SDK default auth chain @@ -14,4 +14,4 @@ Get workspace client from config or SDK default auth chain ## Returns -`WorkspaceClient` +`LakebaseWorkspaceClient` diff --git a/docs/docs/api/appkit/Interface.DirectoryEntry.md b/docs/docs/api/appkit/Interface.DirectoryEntry.md new file mode 100644 index 000000000..8ddd1d0ec --- /dev/null +++ b/docs/docs/api/appkit/Interface.DirectoryEntry.md @@ -0,0 +1,57 @@ +# ~~Interface: DirectoryEntry~~ + +A single entry returned when listing a directory (Files API wire shape). + +## Deprecated + +Legacy snake_case shape; use [FilesDirectoryEntry](TypeAlias.FilesDirectoryEntry.md). + +## Properties + +### ~~file\_size?~~ + +```ts +optional file_size: number; +``` + +The length of the file in bytes. Omitted for directories. + +*** + +### ~~is\_directory?~~ + +```ts +optional is_directory: boolean; +``` + +True if the path is a directory. + +*** + +### ~~last\_modified?~~ + +```ts +optional last_modified: number; +``` + +Last modification time in milliseconds since unix epoch. + +*** + +### ~~name?~~ + +```ts +optional name: string; +``` + +The name of the file or directory (last path component). + +*** + +### ~~path?~~ + +```ts +optional path: string; +``` + +The absolute path of the file or directory. diff --git a/docs/docs/api/appkit/Interface.DownloadResponse.md b/docs/docs/api/appkit/Interface.DownloadResponse.md new file mode 100644 index 000000000..c838c6976 --- /dev/null +++ b/docs/docs/api/appkit/Interface.DownloadResponse.md @@ -0,0 +1,41 @@ +# ~~Interface: DownloadResponse~~ + +Response object for file downloads containing a readable stream. + +## Deprecated + +Legacy header-keyed shape; use [FilesDownloadResponse](TypeAlias.FilesDownloadResponse.md). + +## Properties + +### ~~content-length?~~ + +```ts +optional content-length: number; +``` + +*** + +### ~~content-type?~~ + +```ts +optional content-type: string; +``` + +*** + +### ~~contents?~~ + +```ts +optional contents: ReadableStream; +``` + +*** + +### ~~last-modified?~~ + +```ts +optional last-modified: string; +``` + +Last modified time in HTTP-date (RFC 7231) format. diff --git a/docs/docs/api/appkit/Interface.JobAPI.md b/docs/docs/api/appkit/Interface.JobAPI.md index 5ef330fac..644714b00 100644 --- a/docs/docs/api/appkit/Interface.JobAPI.md +++ b/docs/docs/api/appkit/Interface.JobAPI.md @@ -27,21 +27,24 @@ Cancel a specific run. ### getJob() ```ts -getJob(): Promise>; +getJob(): Promise>; ``` Get the job definition. #### Returns -`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<`Job`\>\> +`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<\{ +\}\>\> *** ### getRun() ```ts -getRun(runId: number): Promise>; +getRun(runId: number): Promise>; ``` Get a specific run by ID. @@ -54,14 +57,16 @@ Get a specific run by ID. #### Returns -`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<`Run`\>\> +`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<\{ +\}\>\> *** ### getRunOutput() ```ts -getRunOutput(runId: number): Promise>; +getRunOutput(runId: number): Promise>; ``` Get output of a specific run. @@ -74,21 +79,28 @@ Get output of a specific run. #### Returns -`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<`RunOutput`\>\> +`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<\{ +\}\>\> *** ### lastRun() ```ts -lastRun(): Promise>; +lastRun(): Promise>; ``` Get the most recent run for this job. #### Returns -`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<`BaseRun` \| `undefined`\>\> +`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\< + \| \{ +\} + \| `undefined`\>\> *** @@ -97,7 +109,8 @@ Get the most recent run for this job. ```ts listRuns(options?: { limit?: number; -}): Promise>; +}): Promise>; ``` List runs for this job. @@ -111,7 +124,8 @@ List runs for this job. #### Returns -`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<`BaseRun`[]\>\> +`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<\{ +\}[]\>\> *** @@ -139,7 +153,8 @@ Trigger and poll until completion, yielding status updates. ### runNow() ```ts -runNow(params?: Record): Promise>; +runNow(params?: Record): Promise>; ``` Trigger the configured job with validated params. Returns the run response. @@ -152,4 +167,5 @@ Trigger the configured job with validated params. Returns the run response. #### Returns -`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<`RunNowResponse`\>\> +`Promise`\<[`ExecutionResult`](TypeAlias.ExecutionResult.md)\<\{ +\}\>\> diff --git a/docs/docs/api/appkit/Interface.LakebasePoolConfig.md b/docs/docs/api/appkit/Interface.LakebasePoolConfig.md index 3f40f3021..d30f64e84 100644 --- a/docs/docs/api/appkit/Interface.LakebasePoolConfig.md +++ b/docs/docs/api/appkit/Interface.LakebasePoolConfig.md @@ -107,7 +107,7 @@ Telemetry configuration ### workspaceClient? ```ts -optional workspaceClient: WorkspaceClient; +optional workspaceClient: LakebaseWorkspaceClient; ``` Databricks workspace client for OAuth authentication diff --git a/docs/docs/api/appkit/Interface.WorkspaceClient.md b/docs/docs/api/appkit/Interface.WorkspaceClient.md index c926698c1..f65fd42ac 100644 --- a/docs/docs/api/appkit/Interface.WorkspaceClient.md +++ b/docs/docs/api/appkit/Interface.WorkspaceClient.md @@ -4,36 +4,29 @@ AppKit's workspace client facade. Mirrors the multi-client shape of the modular Databricks SDK: each service is its own accessor, so services can be migrated one at a time behind this stable interface. -Accessors are legacy-typed for now (delegated to the underlying legacy SDK -client); see the module docblock. - ## Extends - `WorkspaceAuth` ## Properties -### apiClient +### apps ```ts -readonly apiClient: ApiClient; +readonly apps: AppsClient; ``` -Legacy low-level HTTP transport. Prefer `request(...)` (modular, inherited -from `WorkspaceAuth`); still used by serving SSE streaming, vector search, -and the agents adapters, which take a structural `apiClient` shape. +Databricks Apps (modular SDK). *** -### config +### connections ```ts -readonly config: Config; +readonly connections: ConnectionsClient; ``` -Legacy SDK `Config`. Prefer `getHost()` / `authenticate(headers)` (modular, -inherited from `WorkspaceAuth`); kept for structural `WorkspaceClientLike` -callers (supervisor adapter) until they migrate. +Unity Catalog Connections (modular SDK). *** @@ -47,13 +40,43 @@ Current user (modular SDK SCIM client; `me({})` returns the caller). *** +### database + +```ts +readonly database: DatabaseClient; +``` + +Lakebase provisioned database instances + credentials (modular SDK). + +*** + +### experiments + +```ts +readonly experiments: ExperimentsClient; +``` + +MLflow Experiments (modular SDK). + +*** + ### files ```ts -readonly files: FilesService; +readonly files: FilesClient; ``` -UC Volumes / Files API. +UC Volumes / Files API (modular SDK). + +*** + +### functions + +```ts +readonly functions: FunctionsClient; +``` + +Unity Catalog Functions (modular SDK). *** @@ -70,20 +93,30 @@ Genie (modular SDK). ### jobs ```ts -readonly jobs: JobsService; +readonly jobs: JobsClient; ``` -Jobs. +Jobs (modular SDK, Jobs API 2.2). *** -### servingEndpoints +### modelServing ```ts -readonly servingEndpoints: ServingEndpointsService; +readonly modelServing: ModelServingClient; ``` -Serving Endpoints. +Serving endpoints (modular SDK). + +*** + +### postgres + +```ts +readonly postgres: PostgresClient; +``` + +Lakebase Postgres: autoscaling projects, branches, endpoints (modular SDK). *** @@ -97,6 +130,36 @@ Statement Execution (modular SDK). *** +### tables + +```ts +readonly tables: TablesClient; +``` + +Unity Catalog Tables (modular SDK). + +*** + +### vectorSearch + +```ts +readonly vectorSearch: VectorSearchClient; +``` + +Vector Search (modular SDK; index metadata only, queries use `request()`). + +*** + +### volumes + +```ts +readonly volumes: VolumesClient; +``` + +Unity Catalog Volumes (modular SDK). + +*** + ### warehouses ```ts @@ -161,7 +224,7 @@ request(req: WorkspaceRequest): Promise; Send a request through the modular transport (AppKit User-Agent + auth). Returns the raw `Response` (body unread, so it can stream); throws -ApiError on a non-2xx status. +[ApiError](Class.ApiError.md) on a non-2xx status. #### Parameters @@ -178,19 +241,3 @@ ApiError on a non-2xx status. ```ts WorkspaceAuth.request ``` - -*** - -### toLegacyWorkspaceClient() - -```ts -toLegacyWorkspaceClient(): WorkspaceClient; -``` - -Returns the underlying legacy `@databricks/sdk-experimental` -`WorkspaceClient`, for handoff to code still typed against the old SDK -(`@databricks/lakebase`). Transitional. - -#### Returns - -`WorkspaceClient` diff --git a/docs/docs/api/appkit/Interface.WorkspaceClientLike.md b/docs/docs/api/appkit/Interface.WorkspaceClientLike.md deleted file mode 100644 index 3ee3908d3..000000000 --- a/docs/docs/api/appkit/Interface.WorkspaceClientLike.md +++ /dev/null @@ -1,66 +0,0 @@ -# Interface: WorkspaceClientLike - -Structural shape of a Databricks SDK client used by [fromSupervisorApi](Function.fromSupervisorApi.md). -Only what we need: `apiClient.request` for streaming and -`config.ensureResolved` to materialise the host/credentials. - -Exported because [SupervisorApiAdapterOptions.workspaceClient](Interface.SupervisorApiAdapterOptions.md#workspaceclient) (a -public type) references it — callers passing their own client can name -the shape they need to satisfy. - -## Extends - -- `ApiClientLike` - -## Properties - -### apiClient - -```ts -apiClient: { - request: Promise; -}; -``` - -#### request() - -```ts -request(options: Record, context?: unknown): Promise; -``` - -##### Parameters - -| Parameter | Type | -| ------ | ------ | -| `options` | `Record`\<`string`, `unknown`\> | -| `context?` | `unknown` | - -##### Returns - -`Promise`\<`unknown`\> - -#### Inherited from - -```ts -ApiClientLike.apiClient -``` - -*** - -### config - -```ts -config: { - ensureResolved: Promise; -}; -``` - -#### ensureResolved() - -```ts -ensureResolved(): Promise; -``` - -##### Returns - -`Promise`\<`void`\> diff --git a/docs/docs/api/appkit/Interface.WorkspaceClientOptions.md b/docs/docs/api/appkit/Interface.WorkspaceClientOptions.md index 56229ac37..13967665e 100644 --- a/docs/docs/api/appkit/Interface.WorkspaceClientOptions.md +++ b/docs/docs/api/appkit/Interface.WorkspaceClientOptions.md @@ -1,21 +1,7 @@ # Interface: WorkspaceClientOptions -Options used to construct the wrapper. Mirrors the subset of the old SDK's -`Config` + `ClientOptions` that AppKit relies on today; we deliberately do -NOT re-expose every old-SDK config knob. - ## Properties -### authType? - -```ts -optional authType: "pat"; -``` - -Authentication strategy passed to the legacy client. - -*** - ### clientOptions? ```ts @@ -54,4 +40,4 @@ optional profile: string; optional token: string; ``` -Bearer token. When set, `authType` defaults to "pat". +Bearer token (PAT, or the user's OBO token). diff --git a/docs/docs/api/appkit/TypeAlias.FilesDirectoryEntry.md b/docs/docs/api/appkit/TypeAlias.FilesDirectoryEntry.md new file mode 100644 index 000000000..abf4fab80 --- /dev/null +++ b/docs/docs/api/appkit/TypeAlias.FilesDirectoryEntry.md @@ -0,0 +1,9 @@ +# Type Alias: FilesDirectoryEntry + +```ts +type FilesDirectoryEntry = WorkspaceFilesDirectoryEntry; +``` + +A single directory entry in the modular SDK shape (camelCase). `list()` and +the `/list` route still return the legacy [DirectoryEntry](Interface.DirectoryEntry.md); they move to +this type in a future release. diff --git a/docs/docs/api/appkit/TypeAlias.FilesDownloadResponse.md b/docs/docs/api/appkit/TypeAlias.FilesDownloadResponse.md new file mode 100644 index 000000000..5a1e85f23 --- /dev/null +++ b/docs/docs/api/appkit/TypeAlias.FilesDownloadResponse.md @@ -0,0 +1,9 @@ +# Type Alias: FilesDownloadResponse + +```ts +type FilesDownloadResponse = WorkspaceFilesDownloadResponse; +``` + +Download response in the modular SDK shape (`contentLength` is a `bigint`). +`download()` still returns the legacy [DownloadResponse](Interface.DownloadResponse.md); it moves to this +type in a future release. diff --git a/docs/docs/api/appkit/TypeAlias.JobsExport.md b/docs/docs/api/appkit/TypeAlias.JobsExport.md index 53d2fbeff..3d369e62c 100644 --- a/docs/docs/api/appkit/TypeAlias.JobsExport.md +++ b/docs/docs/api/appkit/TypeAlias.JobsExport.md @@ -21,7 +21,7 @@ Callable to select a job by key. ```ts // Trigger a configured job -const { run_id } = await appkit.jobs("etl").runNow(); +const { runId } = await appkit.jobs("etl").runNow(); // Trigger and poll until completion for await (const status of appkit.jobs("etl").runAndWait()) { diff --git a/docs/docs/api/appkit/TypeAlias.WorkspaceClientLike.md b/docs/docs/api/appkit/TypeAlias.WorkspaceClientLike.md new file mode 100644 index 000000000..7de59d329 --- /dev/null +++ b/docs/docs/api/appkit/TypeAlias.WorkspaceClientLike.md @@ -0,0 +1,19 @@ +# Type Alias: WorkspaceClientLike + +```ts +type WorkspaceClientLike = + | WorkspaceRequestClientLike + | ApiClientLike & { + config: { + ensureResolved: Promise; + }; +}; +``` + +Structural shape of a Databricks SDK client used by [fromSupervisorApi](Function.fromSupervisorApi.md). +Only what we need: `request` for streaming. A legacy SDK client +(`apiClient.request` + `config.ensureResolved`) is still accepted (deprecated). + +Exported because [SupervisorApiAdapterOptions.workspaceClient](Interface.SupervisorApiAdapterOptions.md#workspaceclient) (a +public type) references it — callers passing their own client can name +the shape they need to satisfy. diff --git a/docs/docs/api/appkit/index.md b/docs/docs/api/appkit/index.md index 59cfa0c69..ee6db8b67 100644 --- a/docs/docs/api/appkit/index.md +++ b/docs/docs/api/appkit/index.md @@ -14,6 +14,7 @@ surface with `@databricks/appkit/beta`. Not meant for application imports. | Class | Description | | ------ | ------ | +| [ApiError](Class.ApiError.md) | - | | [AppKitError](Class.AppKitError.md) | Base error class for all AppKit errors. Provides a consistent structure for error handling across the framework. | | [AppKitMcpClient](Class.AppKitMcpClient.md) | Lightweight MCP client for Databricks-hosted MCP servers. | | [AuthenticationError](Class.AuthenticationError.md) | Error thrown when authentication fails. Use for missing tokens, invalid credentials, or authorization failures. | @@ -57,8 +58,10 @@ surface with `@databricks/appkit/beta`. Not meant for application imports. | [DatabaseValidationIssue](Interface.DatabaseValidationIssue.md) | One rejected field; `path` names public columns, never their values. | | [DatabricksAuth](Interface.DatabricksAuth.md) | Resolved Databricks host + bearer token for the eval runner's REST calls. | | [DatasetRow](Interface.DatasetRow.md) | One row of a managed evaluation dataset. `inputs` are the kwargs passed to the agent for the turn; `expectations` (when present) is the row's ground truth / guidelines. Mirrors the `{inputs, expectations}` shape of `mlflow.genai` datasets and of the Unity Catalog table backing a managed eval dataset. | +| [~~DirectoryEntry~~](Interface.DirectoryEntry.md) | A single entry returned when listing a directory (Files API wire shape). | | [DiscoveredEval](Interface.DiscoveredEval.md) | An eval file found under `server/agents//evals/`. | | [DiscoveredEvalConfig](Interface.DiscoveredEvalConfig.md) | A per-agent `evals.config.ts` found under `server/agents//evals/`. | +| [~~DownloadResponse~~](Interface.DownloadResponse.md) | Response object for file downloads containing a readable stream. | | [DriveResult](Interface.DriveResult.md) | What a driver returns for a single `t.send`. | | [EndpointConfig](Interface.EndpointConfig.md) | - | | [EntityMutationHooks](Interface.EntityMutationHooks.md) | Mutation lifecycle for one entity. A before hook may return a replacement payload, which is revalidated against the trusted schema before it is persisted. Every hook, the mutation, and any write a hook issues through `ctx.app.database` share one transaction, so a rejection anywhere rolls all of them back. Throw `DatabaseValidationError` to answer a generated route with `422`; any other failure stays an opaque server error. | @@ -131,8 +134,7 @@ surface with `@databricks/appkit/beta`. Not meant for application imports. | [ToolProvider](Interface.ToolProvider.md) | - | | [ValidationResult](Interface.ValidationResult.md) | Result of validating all registered resources against the environment. | | [WorkspaceClient](Interface.WorkspaceClient.md) | AppKit's workspace client facade. Mirrors the multi-client shape of the modular Databricks SDK: each service is its own accessor, so services can be migrated one at a time behind this stable interface. | -| [WorkspaceClientLike](Interface.WorkspaceClientLike.md) | Structural shape of a Databricks SDK client used by [fromSupervisorApi](Function.fromSupervisorApi.md). Only what we need: `apiClient.request` for streaming and `config.ensureResolved` to materialise the host/credentials. | -| [WorkspaceClientOptions](Interface.WorkspaceClientOptions.md) | Options used to construct the wrapper. Mirrors the subset of the old SDK's `Config` + `ClientOptions` that AppKit relies on today; we deliberately do NOT re-expose every old-SDK config knob. | +| [WorkspaceClientOptions](Interface.WorkspaceClientOptions.md) | - | ## Type Aliases @@ -157,6 +159,8 @@ surface with `@databricks/appkit/beta`. Not meant for application imports. | [ExecutionResult](TypeAlias.ExecutionResult.md) | Discriminated union for plugin execution results. | | [FileAction](TypeAlias.FileAction.md) | Every action the files plugin can perform. | | [FilePolicy](TypeAlias.FilePolicy.md) | A policy function that decides whether `user` may perform `action` on `resource`. Return `true` to allow, `false` to deny. | +| [FilesDirectoryEntry](TypeAlias.FilesDirectoryEntry.md) | A single directory entry in the modular SDK shape (camelCase). `list()` and the `/list` route still return the legacy [DirectoryEntry](Interface.DirectoryEntry.md); they move to this type in a future release. | +| [FilesDownloadResponse](TypeAlias.FilesDownloadResponse.md) | Download response in the modular SDK shape (`contentLength` is a `bigint`). `download()` still returns the legacy [DownloadResponse](Interface.DownloadResponse.md); it moves to this type in a future release. | | [HostedTool](TypeAlias.HostedTool.md) | - | | [IAppRouter](TypeAlias.IAppRouter.md) | Express router type for plugin route registration | | [IDatabaseConfig](TypeAlias.IDatabaseConfig.md) | Configuration for one schema-bound DatabasePlugin instance. | @@ -178,6 +182,7 @@ surface with `@databricks/appkit/beta`. Not meant for application imports. | [TransactionClient](TypeAlias.TransactionClient.md) | Entity and SQL capabilities bound to one transaction. | | [~~UserContext~~](TypeAlias.UserContext.md) | - | | [UserScopedApp](TypeAlias.UserScopedApp.md) | - | +| [WorkspaceClientLike](TypeAlias.WorkspaceClientLike.md) | Structural shape of a Databricks SDK client used by [fromSupervisorApi](Function.fromSupervisorApi.md). Only what we need: `request` for streaming. A legacy SDK client (`apiClient.request` + `config.ensureResolved`) is still accepted (deprecated). | ## Variables diff --git a/docs/docs/api/appkit/typedoc-sidebar.ts b/docs/docs/api/appkit/typedoc-sidebar.ts index 82e59f192..dca9af23b 100644 --- a/docs/docs/api/appkit/typedoc-sidebar.ts +++ b/docs/docs/api/appkit/typedoc-sidebar.ts @@ -21,6 +21,11 @@ const typedocSidebar: SidebarsConfig = { type: "category", label: "Classes", items: [ + { + type: "doc", + id: "api/appkit/Class.ApiError", + label: "ApiError" + }, { type: "doc", id: "api/appkit/Class.AppKitError", @@ -217,6 +222,12 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.DatasetRow", label: "DatasetRow" }, + { + type: "doc", + id: "api/appkit/Interface.DirectoryEntry", + label: "DirectoryEntry", + className: "typedoc-sidebar-item-deprecated" + }, { type: "doc", id: "api/appkit/Interface.DiscoveredEval", @@ -227,6 +238,12 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.DiscoveredEvalConfig", label: "DiscoveredEvalConfig" }, + { + type: "doc", + id: "api/appkit/Interface.DownloadResponse", + label: "DownloadResponse", + className: "typedoc-sidebar-item-deprecated" + }, { type: "doc", id: "api/appkit/Interface.DriveResult", @@ -587,11 +604,6 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.WorkspaceClient", label: "WorkspaceClient" }, - { - type: "doc", - id: "api/appkit/Interface.WorkspaceClientLike", - label: "WorkspaceClientLike" - }, { type: "doc", id: "api/appkit/Interface.WorkspaceClientOptions", @@ -698,6 +710,16 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/TypeAlias.FilePolicy", label: "FilePolicy" }, + { + type: "doc", + id: "api/appkit/TypeAlias.FilesDirectoryEntry", + label: "FilesDirectoryEntry" + }, + { + type: "doc", + id: "api/appkit/TypeAlias.FilesDownloadResponse", + label: "FilesDownloadResponse" + }, { type: "doc", id: "api/appkit/TypeAlias.HostedTool", @@ -803,6 +825,11 @@ const typedocSidebar: SidebarsConfig = { type: "doc", id: "api/appkit/TypeAlias.UserScopedApp", label: "UserScopedApp" + }, + { + type: "doc", + id: "api/appkit/TypeAlias.WorkspaceClientLike", + label: "WorkspaceClientLike" } ] }, From 8a12c04b751d707c8a20f3cca0fd4821e1d748aa Mon Sep 17 00:00:00 2001 From: MarioCadenas Date: Fri, 16 Oct 2026 17:25:48 +0200 Subject: [PATCH 3/3] test(appkit): drop a stale legacy client key from the cache test mock Co-authored-by: Isaac Signed-off-by: MarioCadenas --- packages/appkit/src/cache/tests/cache-manager.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/appkit/src/cache/tests/cache-manager.test.ts b/packages/appkit/src/cache/tests/cache-manager.test.ts index fe1028fba..19d60f53f 100644 --- a/packages/appkit/src/cache/tests/cache-manager.test.ts +++ b/packages/appkit/src/cache/tests/cache-manager.test.ts @@ -55,9 +55,7 @@ vi.mock("../../workspace-client", async (importOriginal) => { await importOriginal(); return { ...actual, - createWorkspaceClient: vi.fn().mockImplementation(() => ({ - toLegacyWorkspaceClient: () => ({}), - })), + createWorkspaceClient: vi.fn().mockImplementation(() => ({})), }; });