Skip to content

feat(appkit): introduce workspace-client wrapper over the Databricks SDK#475

Open
MarioCadenas wants to merge 1 commit into
mainfrom
feat/workspace-client-wrapper
Open

feat(appkit): introduce workspace-client wrapper over the Databricks SDK#475
MarioCadenas wants to merge 1 commit into
mainfrom
feat/workspace-client-wrapper

Conversation

@MarioCadenas

@MarioCadenas MarioCadenas commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

Introduces packages/appkit/src/workspace-client/ — a facade over the legacy @databricks/sdk-experimental client that mirrors the modular Databricks SDK's multi-client shape. This prepares the library for an incremental, per-service migration to the modular SDK. Nothing is migrated yet — every service still delegates to the legacy client.

Supersedes the exploration in #414, which actually migrated services (files/warehouses/vectorSearch), added upstream patches, and introduced dual-error handling. This PR does only the seam.

Why introduce a wrapper that changes nothing?

Fair question — on its own this PR adds indirection that delegates every call straight back to the same SDK. The value is entirely in what it unblocks.

AppKit is on @databricks/sdk-experimental (one monolithic WorkspaceClient). The supported SDK is databricks/sdk-js — a modular architecture: a @databricks/sdk-core + -auth + -options foundation plus one @databricks/sdk-<service> package per API, each constructed like new FilesClient({ host, credentials }). We want to move onto it, but a big-bang migration is risky (that's what #414 was, and it was too large to land safely).

So the plan is migrate one service at a time, each in its own PR, with the app fully working after every step. This wrapper is the seam that makes that possible:

  • Every service goes through one accessor (wrapper.files, wrapper.genie, …), and a Biome boundary rule guarantees nothing else in appkit imports the SDK directly. So migrating a service is a localized change — one getter in client.ts, one accessor type, one connector — with a provably bounded blast radius.
  • Unmigrated services keep delegating to legacy, so main stays green between every migration PR — no half-broken state.
  • toLegacyWorkspaceClient() is the deliberate escape hatch for code still on the old SDK (notably @databricks/lakebase), so the seam can be complete even while the migration is partial.

That's why the boundary rule and escape hatch exist — they're the mechanism for a safe, incremental migration, not speculative abstraction.

Where this goes (for context, not in this PR)

Coverage was verified 2026-07-23: every service appkit uses now has a published modular package (sdk-files, sdk-warehouses, sdk-vectorsearch, sdk-statementexecution, sdk-scim for currentUser, sdk-modelserving for serving, sdk-genie, sdk-jobs), so full migration and eventual removal of sdk-experimental is a reachable end state. Rough sequence: clean services first (files/warehouses/vectorSearch, PoC-proven), then statementExecution + the raw-apiClient.request sites → sdk-core/http, then the divergent ones last (genie/jobs need a connector rewrite for the waiter idiom + camelCase fields). The honest caveat: the wrapper makes the wiring trivial but does not shrink the per-connector rewrite — that's real, case-by-case work per service.

Full plan lives in the AppKit roadmap under Pillar 4 → "SDK migration".

Design

  • legacy.ts is the only module that imports @databricks/sdk-experimental. It constructs the client and re-exports the SDK symbols AppKit uses (Context, ConfigError, Time, TimeUnits, type namespaces files/jobs/serving/sql, etc.).
  • The WorkspaceClient facade exposes per-service accessors (files, warehouses, genie, jobs, statementExecution, servingEndpoints, currentUser, config, apiClient), each legacy-typed and delegating to the underlying client. Migrating a service later is a localized swap: one getter + its accessor type + one connector — the rest of the codebase never touches the SDK.
  • createWorkspaceClient() replaces every new WorkspaceClient(...) site (runtime + build-time type-generator). toLegacyWorkspaceClient() is the escape hatch for @databricks/lakebase, which is still on the old SDK.
  • createApp({ client }) and the public package index now expose the wrapper type instead of the raw SDK client. (Breaking at the type level for anyone passing a raw SDK client to createApp — no known external users.)
  • A Biome noRestrictedImports boundary rule forbids importing @databricks/sdk-experimental outside workspace-client/, so the seam stays enforceable. Tests mock the wrapper rather than the SDK.

Note on Time

Time is sourced off the SDK namespace (SDK.Time ?? SDK.default.Time) rather than a static export { Time }. The SDK's CommonJS Time export is emitted as a getter calling __importDefault(...), which defeats Node's static ESM named-export detection — a direct re-export throws "does not provide an export named 'Time'" at link time. This mirrors the guard the genie connector already used.

Verification

  • pnpm build — clean, publint OK
  • pnpm -r typecheck — 0 errors, all packages
  • Biome boundary rule — 0 violations; @databricks/sdk-experimental imported only in workspace-client/
  • pnpm check — 0 errors, 63 warnings (identical to main baseline)
  • Full test suite — 3284 passed / 0 failed
  • Built playground boots past agent registration + plugin setup (fails only at Databricks auth, as expected without credentials) — confirms the ESM Time fix end-to-end

@MarioCadenas
MarioCadenas requested a review from a team as a code owner July 10, 2026 13:20
@MarioCadenas
MarioCadenas requested a review from ditadi July 10, 2026 13:20
@MarioCadenas
MarioCadenas marked this pull request as draft July 10, 2026 14:38
@MarioCadenas
MarioCadenas force-pushed the feat/workspace-client-wrapper branch from 01e0549 to 395c4a2 Compare July 23, 2026 14:23
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

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

@databricks/appkit

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

dist raw gzip
JS (runtime) 788 KB (+4.2 KB) 275 KB (+1.6 KB)
Type declarations 289 KB (+4.4 KB) 99 KB (+2.0 KB)
Source maps 1.5 MB (+8.5 KB) 513 KB (+3.5 KB)
Other 11 KB 3.7 KB
Total 2.6 MB (+17 KB) 890 KB (+7.1 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 85 KB (+69 B) 2.5 KB 88 KB (+69 B) external 279 KB (+114 B)
./beta 40 KB (+300 B) 231 B 40 KB (+300 B) external 120 KB (+682 B)
./type-generator 19 KB (+226 B) 0 B 19 KB (+226 B) external 54 KB (+645 B)

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 81 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 30 KB
./beta databricks.js initial 5.8 KB
./beta service-context.js initial 3.1 KB
./beta client.js initial 570 B
./beta databricks.js lazy 128 B
./beta index.js lazy 103 B
./type-generator index.js initial 19 KB

@databricks/appkit-ui

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

dist raw gzip
JS (runtime) 350 KB 116 KB
Type declarations 201 KB 72 KB
Source maps 669 KB 218 KB
CSS 16 KB 3.3 KB
Total 1.2 MB 410 KB
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 4.3 KB 49 KB 54 KB 208 KB 12 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 428 KB 49 KB 476 KB 1.3 MB 163 KB
./react/beta 20 B 0 B 20 B 0 B 0 B

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 4.2 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 426 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 20 B

@MarioCadenas
MarioCadenas force-pushed the feat/workspace-client-wrapper branch from 395c4a2 to f45fda6 Compare July 23, 2026 15:48
Add packages/appkit/src/workspace-client/ — a facade over the legacy
@databricks/sdk-experimental client that mirrors the modular Databricks
SDK's multi-client shape. This prepares the library for an incremental,
per-service migration to the modular SDK without migrating anything yet.

- legacy.ts is the ONLY module importing @databricks/sdk-experimental;
  it constructs the client and re-exports the SDK symbols AppKit uses.
- The WorkspaceClient facade exposes per-service accessors (files,
  warehouses, genie, jobs, statementExecution, servingEndpoints,
  currentUser, config, apiClient), each legacy-typed and delegating to
  the underlying client. Migrating a service later is a localized swap:
  one getter + its accessor type + one connector.
- createWorkspaceClient() replaces every `new WorkspaceClient(...)` site
  (runtime + build-time type-generator); toLegacyWorkspaceClient() is the
  escape hatch for @databricks/lakebase.
- createApp({ client }) and the public index now expose the wrapper type
  instead of the raw SDK client.
- A Biome noRestrictedImports boundary rule forbids importing
  @databricks/sdk-experimental outside workspace-client/, keeping the seam
  enforceable. Tests mock the wrapper rather than the SDK.

Time is sourced off the SDK namespace (SDK.Time ?? SDK.default.Time)
because the SDK's CommonJS `Time` export is a getter that defeats Node's
static ESM named-export detection — a direct `export { Time }` throws at
link time.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
@MarioCadenas
MarioCadenas force-pushed the feat/workspace-client-wrapper branch from f45fda6 to 9e0715b Compare July 23, 2026 16:02
@MarioCadenas
MarioCadenas marked this pull request as ready for review July 23, 2026 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant