diff --git a/knip.json b/knip.json index 6963fc6a9..12cc73f4f 100644 --- a/knip.json +++ b/knip.json @@ -10,6 +10,7 @@ "packages/appkit": { "ignoreDependencies": [ "vitest", + "@databricks/sdk-apps", "@databricks/sdk-auth", "@databricks/sdk-core", "@databricks/sdk-experimental", diff --git a/packages/appkit/package.json b/packages/appkit/package.json index 041761741..2facc55aa 100644 --- a/packages/appkit/package.json +++ b/packages/appkit/package.json @@ -71,6 +71,7 @@ "dependencies": { "@ast-grep/napi": "0.37.0", "@databricks/lakebase": "workspace:*", + "@databricks/sdk-apps": "0.54.0", "@databricks/sdk-auth": "0.51.0", "@databricks/sdk-core": "0.51.0", "@databricks/sdk-experimental": "0.17.0", diff --git a/packages/appkit/src/testing/mock-workspace-client.ts b/packages/appkit/src/testing/mock-workspace-client.ts index 27a47048a..dda9da370 100644 --- a/packages/appkit/src/testing/mock-workspace-client.ts +++ b/packages/appkit/src/testing/mock-workspace-client.ts @@ -69,6 +69,7 @@ const FACADE_SERVICES = [ "warehouses", "genie", "database", + "apps", "jobs", "experiments", "vectorSearch", diff --git a/packages/shared/package.json b/packages/shared/package.json index 844833fd2..9c2121e65 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -48,6 +48,7 @@ "dependencies": { "@ast-grep/napi": "0.37.0", "@clack/prompts": "1.0.1", + "@databricks/sdk-apps": "0.54.0", "@databricks/sdk-auth": "0.51.0", "@databricks/sdk-core": "0.51.0", "@databricks/sdk-experimental": "0.17.0", diff --git a/packages/shared/src/workspace-client/client.ts b/packages/shared/src/workspace-client/client.ts index 335a4aeab..0681d901d 100644 --- a/packages/shared/src/workspace-client/client.ts +++ b/packages/shared/src/workspace-client/client.ts @@ -2,10 +2,11 @@ * `AppKitWorkspaceClient` — the facade implementation. Construct via * `createWorkspaceClient(...)`; this class is internal. * - * Every service accessor delegates to a single lazily-constructed legacy SDK - * client. This is the seam: to migrate a service to the modular SDK, 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. + * 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. */ import { buildLegacyWorkspaceClient, @@ -24,6 +25,8 @@ import { buildGenieClient, type FunctionsClient, type GenieClient, + buildAppsClient, + type AppsClient, buildJobsClient, buildExperimentsClient, type ExperimentsClient, @@ -58,6 +61,7 @@ export class AppKitWorkspaceClient implements WorkspaceClient { #currentUser?: ScimClient; #genie?: GenieClient; #database?: DatabaseClient; + #apps?: AppsClient; #jobs?: JobsClient; #modelServing?: ModelServingClient; #experiments?: ExperimentsClient; @@ -97,6 +101,14 @@ export class AppKitWorkspaceClient implements WorkspaceClient { return this.#genie; } + // Migrated to the modular SDK — built lazily, independent of the legacy client. + get apps(): AppsClient { + if (!this.#apps) { + this.#apps = buildAppsClient(this.#opts); + } + return this.#apps; + } + // Migrated to the modular SDK — built lazily, independent of the legacy client. get database(): DatabaseClient { if (!this.#database) { diff --git a/packages/shared/src/workspace-client/modular.ts b/packages/shared/src/workspace-client/modular.ts index a8b322f12..af20ba684 100644 --- a/packages/shared/src/workspace-client/modular.ts +++ b/packages/shared/src/workspace-client/modular.ts @@ -8,12 +8,15 @@ * * Migrated services are built here as per-service clients; the facade delegates * their accessors to these instead of the legacy monolithic client. Currently - * `warehouses`, `statementExecution`, `currentUser` (SCIM), `genie`, `jobs`, `vectorSearch` - * and `tables` (UC) are migrated, plus the auth + raw-request seam ({@link buildWorkspaceAuth}); - * every other service still routes through `legacy.ts`. + * `warehouses`, `statementExecution`, `currentUser` (SCIM), `genie`, `jobs`, + * `files`, `vectorSearch`, `tables`, `modelServing`, `volumes`, `functions`, + * `connections`, `database`, `postgres`, `experiments` and `apps` are + * migrated, plus the auth + raw-request seam ({@link buildWorkspaceAuth}). + * Only `servingEndpoints`, `config` and `apiClient` still route through + * `legacy.ts`. * * NOTE: statementExecution relies on a pinned pnpm patch - * (`patches/@databricks__sdk-statementexecution@0.46.0.patch`) that restores the + * (`patches/@databricks__sdk-statementexecution@0.52.0.patch`) that restores the * undocumented Reyden `attachment` response field, which the SDK's generated * unmarshal transform would otherwise strip. * @@ -33,6 +36,7 @@ import { execFile } from "node:child_process"; import { STATUS_CODES } from "node:http"; import { promisify } from "node:util"; +import type { AppsClient } from "@databricks/sdk-apps/v1"; import { type Credentials, newTokenCredentials, @@ -547,6 +551,14 @@ export function buildScimClient(opts: WorkspaceClientOptions): ScimClient { return new ScimClient(mapToClientOptions(opts)); } +/** Build a modular Apps client from wrapper options. */ +export function buildAppsClient(opts: WorkspaceClientOptions): AppsClient { + return lazyClient( + () => import("@databricks/sdk-apps/v1").then((m) => m.AppsClient), + mapToClientOptions(opts), + ); +} + /** * Defer loading an SDK client's module until its first method call, so app * boot doesn't pay for clients it never uses. Generated methods return either @@ -715,6 +727,7 @@ export function buildPostgresClient( // ── Client type re-exports (for the facade accessor types) ─────────────── export type { DatabaseClient } from "@databricks/sdk-database/v1"; export type { ExperimentsClient } from "@databricks/sdk-experiments/v1"; +export type { AppsClient } from "@databricks/sdk-apps/v1"; export type { FilesClient } from "@databricks/sdk-files/v2"; export type { DirectoryEntry as FilesDirectoryEntry, @@ -766,6 +779,11 @@ export type { ListDatabaseInstancesRequest, ListDatabaseInstancesResponse, } from "@databricks/sdk-database/v1"; +export type { + App, + ListAppsRequest, + ListAppsResponse, +} from "@databricks/sdk-apps/v1"; export type { GenieGetMessageQueryResultResponse, GenieMessage, diff --git a/packages/shared/src/workspace-client/tests/modular.test.ts b/packages/shared/src/workspace-client/tests/modular.test.ts index e8a938375..595b774e5 100644 --- a/packages/shared/src/workspace-client/tests/modular.test.ts +++ b/packages/shared/src/workspace-client/tests/modular.test.ts @@ -42,6 +42,12 @@ vi.mock("@databricks/sdk-warehouses/v1", () => ({ vi.mock("@databricks/sdk-statementexecution/v1", () => ({ StatementExecutionClient: vi.fn().mockImplementation((opts) => ({ opts })), })); +vi.mock("@databricks/sdk-apps/v1", () => ({ + AppsClient: vi.fn().mockImplementation((opts) => { + ctorOpts.push(opts); + return { opts, getApp: async () => ({}) }; + }), +})); vi.mock("@databricks/sdk-experiments/v1", () => ({ ExperimentsClient: vi.fn().mockImplementation((opts) => { ctorOpts.push(opts); @@ -164,6 +170,7 @@ import { buildConnectionsClient, buildDatabaseClient, buildExperimentsClient, + buildAppsClient, buildGenieClient, buildModelServingClient, buildVolumesClient, @@ -394,6 +401,26 @@ describe("modular mapToClientOptions (via buildWarehousesClient)", () => { expect(Reflect.get(client, "then")).toBeUndefined(); }); + test("apps passes the mapped options (normalized host, credentials) to the client", async () => { + await buildAppsClient({ + host: "x.cloud.databricks.com", + token: "user-token", + clientOptions: { + product: "@databricks/appkit", + productVersion: "0.64.0", + }, + } as never).getApp({} as never); + expect(ctorOpts[0].host).toBe("https://x.cloud.databricks.com"); + expect(ctorOpts[0].credentials).toMatchObject({ + kind: "pat", + token: "user-token", + }); + expect(m2mOpts).toEqual([]); + expect(await sentUserAgent(ctorOpts[0].httpClient)).toBe( + "@databricks/appkit/0.64.0", + ); + }); + test("database maps host normalization + SP M2M credentials through mapToClientOptions", async () => { process.env.DATABRICKS_CLIENT_ID = "sp-client-id"; process.env.DATABRICKS_CLIENT_SECRET = "sp-secret"; diff --git a/packages/shared/src/workspace-client/types.ts b/packages/shared/src/workspace-client/types.ts index 95fa14523..b337d1128 100644 --- a/packages/shared/src/workspace-client/types.ts +++ b/packages/shared/src/workspace-client/types.ts @@ -19,6 +19,7 @@ import type { WarehousesClient, WorkspaceAuth, ScimClient, + AppsClient, GenieClient, DatabaseClient, JobsClient, @@ -66,6 +67,9 @@ export interface WorkspaceClient extends WorkspaceAuth { /** Lakebase provisioned database instances + credentials (modular SDK). */ readonly database: DatabaseClient; + /** Databricks Apps (modular SDK). */ + readonly apps: AppsClient; + /** Jobs (modular SDK, Jobs API 2.2). */ readonly jobs: JobsClient; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64e02c17e..bff41d96a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -281,6 +281,9 @@ importers: '@databricks/lakebase': specifier: workspace:* version: link:../lakebase + '@databricks/sdk-apps': + specifier: 0.54.0 + version: 0.54.0 '@databricks/sdk-auth': specifier: 0.51.0 version: 0.51.0 @@ -653,6 +656,9 @@ importers: '@clack/prompts': specifier: 1.0.1 version: 1.0.1 + '@databricks/sdk-apps': + specifier: 0.54.0 + version: 0.54.0 '@databricks/sdk-auth': specifier: 0.51.0 version: 0.51.0 @@ -2075,6 +2081,10 @@ packages: engines: {node: ^20 || ^22 || ^24 || ^25, pnpm: '>=10'} hasBin: true + '@databricks/sdk-apps@0.54.0': + resolution: {integrity: sha512-7cz5Smp/OXfwLn7jGKs0yQjOy1dMbOuPz6l6WoiTnVakw5RClagnLp56hePo2D6HlXMcFRMzTNe5BbW89dgveg==} + engines: {node: '>=22.0.0'} + '@databricks/sdk-auth@0.51.0': resolution: {integrity: sha512-CswN+io9XB7rrcHo9BG/2qIdRCUMb8gFjiZmqVDG2qRmyhzY7Wv1tvwzA+U8uBplmdzAcdhuxMvHQ7vKGFWYqQ==} engines: {node: '>=22.0.0'} @@ -14423,6 +14433,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@databricks/sdk-apps@0.54.0': + dependencies: + '@databricks/sdk-auth': 0.51.0 + '@databricks/sdk-core': 0.51.0 + '@databricks/sdk-options': 0.51.0 + '@js-temporal/polyfill': 0.5.1 + json-bigint: 1.0.0 + zod: 4.3.6 + '@databricks/sdk-auth@0.51.0': dependencies: '@databricks/sdk-core': 0.51.0