Repository navigation
feat(appkit): migrate analytics to the modular @databricks/sdk-* - #562
Open
MarioCadenas wants to merge 17 commits into
Open
MarioCadenas wants to merge 17 commits into
MarioCadenas wants to merge 17 commits into
Conversation
MarioCadenas
force-pushed
the
analytics-migration-sdk
branch
from
September 10, 2026 14:43
9e1a7d5 to
2931182
Compare
Contributor
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 1.2 MB (+15 KB) | 453 KB (+5.9 KB) |
| Type declarations | 458 KB (+2.2 KB) | 166 KB (+896 B) |
| Source maps | 2.5 MB (+27 KB) | 848 KB (+10 KB) |
| Other | 11 KB | 3.7 KB |
| Total | 4.2 MB (+45 KB) | 1.4 MB (+17 KB) |
Per-entry composition (own code — deps external (as shipped))
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
. |
104 KB (+1.8 KB) | 2.5 KB | 106 KB (+1.8 KB) | external | 336 KB (+4.5 KB) |
./beta |
100 KB (+1.9 KB) | 486 B (+1 B) | 101 KB (+1.9 KB) | external | 302 KB (+4.6 KB) |
./testing |
44 KB (+1.7 KB) | 32 KB (-41 B) | 76 KB (+1.7 KB) | external | 219 KB (+4.4 KB) |
./tsdown |
520 B | 0 B | 520 B | external | 813 B |
./type-generator |
25 KB (+1.6 KB) | 0 B | 25 KB (+1.6 KB) | external | 71 KB (+4.4 KB) |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
. |
index.js |
initial | 99 KB |
. |
utils.js |
initial | 4.6 KB |
. |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./beta |
beta.js |
initial | 82 KB |
./beta |
stream-manager.js |
initial | 5.9 KB |
./beta |
modular.js |
initial | 4.8 KB |
./beta |
service-context.js |
initial | 4.2 KB |
./beta |
databricks.js |
initial | 3.3 KB |
./beta |
client.js |
initial | 596 B |
./beta |
index.js |
initial | 20 B |
./beta |
supervisor-api.js |
lazy | 193 B |
./beta |
databricks.js |
lazy | 178 B |
./beta |
index.js |
lazy | 115 B |
./testing |
manifest.js |
initial | 30 KB |
./testing |
index.js |
initial | 10 KB |
./testing |
wide-event-emitter.js |
initial | 2.9 KB |
./testing |
index.js |
lazy | 28 KB |
./testing |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./testing |
utils.js |
lazy | 1.8 KB |
./tsdown |
index.js |
initial | 520 B |
./type-generator |
index.js |
initial | 25 KB |
@databricks/appkit-ui
npm tarball (packed): 397 KB (+94 B) — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 429 KB | 146 KB |
| Type declarations | 266 KB (+217 B) | 98 KB (+130 B) |
| Source maps | 853 KB | 285 KB |
| CSS | 16 KB | 3.2 KB |
| Total | 1.5 MB (+217 B) | 533 KB (+130 B) |
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
./js |
5.3 KB | 49 KB | 55 KB | 208 KB | 14 KB |
./js/beta |
2.1 KB | 0 B | 2.1 KB | 0 B | 5.0 KB |
./react |
433 KB | 49 KB | 481 KB | 1.3 MB | 177 KB |
./react/beta |
4.9 KB | 0 B | 4.9 KB | 0 B | 12 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
./js |
index.js |
initial | 5.2 KB |
./js |
chunk |
initial | 120 B |
./js |
apache-arrow |
lazy | 49 KB |
./js/beta |
beta.js |
initial | 2.1 KB |
./react |
index.js |
initial | 431 KB |
./react |
tslib |
initial | 2.1 KB |
./react |
apache-arrow |
lazy | 49 KB |
./react/beta |
beta.js |
initial | 4.9 KB |
Contributor
🤖 AppKit PR bot🔬 Run evalsStart an eval for this PR from the evals-monitor app: Go to Evals Monitor → 📦 Try this PR's app templateScaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh run download 38918766917 -R databricks/appkit -n appkit-template-0.88.1-pr.69dce07-analytics-migration-sdk-562 -D appkit-pr-562 \
&& unzip -o "appkit-pr-562/appkit-template-0.88.1-pr.69dce07-analytics-migration-sdk-562.zip" -d "appkit-pr-562" \
&& databricks apps init --template "appkit-pr-562"The template pins |
MarioCadenas
force-pushed
the
analytics-migration-sdk
branch
from
September 14, 2026 09:47
613983c to
53566af
Compare
MarioCadenas
marked this pull request as ready for review
September 14, 2026 10:50
MarioCadenas
force-pushed
the
analytics-migration-sdk
branch
2 times, most recently
from
September 15, 2026 14:46
b8820bd to
647b566
Compare
MarioCadenas
force-pushed
the
analytics-migration-sdk
branch
2 times, most recently
from
October 7, 2026 10:50
0f3558e to
277a1fa
Compare
MarioCadenas
force-pushed
the
analytics-migration-sdk
branch
from
October 7, 2026 13:59
65b833d to
515abf0
Compare
MarioCadenas
added this pull request to stack #636
October 7, 2026 13:59
This was referenced Oct 7, 2026
MarioCadenas
force-pushed
the
analytics-migration-sdk
branch
from
October 8, 2026 14:37
515abf0 to
4576243
Compare
MarioCadenas
removed this pull request from stack #636
October 13, 2026 08:32
MarioCadenas
added this pull request to stack #664
October 13, 2026 08:33
MarioCadenas
force-pushed
the
analytics-migration-sdk
branch
from
October 13, 2026 09:01
4576243 to
c589b96
Compare
MarioCadenas
removed this pull request from stack #664
October 13, 2026 15:47
MarioCadenas
added this pull request to stack #669
October 13, 2026 15:48
MarioCadenas
force-pushed
the
analytics-migration-sdk
branch
from
October 14, 2026 14:21
c589b96 to
5998dfb
Compare
Migrate the analytics stack (SQLWarehouseConnector + type-generator) off
the legacy monolithic @databricks/sdk-experimental onto the new modular
per-service @databricks/sdk-* SDK (v0.46.0, ESM-only), behind the existing
workspace-client facade seam. The two services analytics depends on —
warehouses and statementExecution — move together; every other service
still routes through the legacy client (mixed state by design).
- New packages/shared/src/workspace-client/modular.ts is the sole importer
of @databricks/sdk-* (oxlint no-restricted-imports boundary), mirroring
legacy.ts. Builds per-service WarehousesClient / StatementExecutionClient;
maps wrapper options -> ClientOptions (host scheme-normalization, PAT
empty-token guard, profile); stamps process-global client-info
(sanitized, best-effort).
- Connector + type-generator rewritten to the modular API: method renames
(getStatement -> getStatementResult, getStatementResultChunkN ->
getResultData), camelCase response model, CallOptions { signal }.
- statementExecution relies on a pinned pnpm patch that restores the
undocumented Reyden `attachment` field the SDK's unmarshal transform
would otherwise strip.
- Coerce the SDK's bigint row/byte counts back to number at the connector
boundary so INLINE + ARROW_STREAM results stay JSON-serializable
(cache / SSE frames).
- Read the modular ApiError's `.code` (not only the legacy `.errorCode`) so
the arrow disposition/format capability-rejection fallback still fires.
Verified against live warehouses (standard + Reyden serverless): JSON and
arrow (INLINE attachment + EXTERNAL_LINKS), OBO, warehouse auto-start, and
metric views. Full appkit + shared suite green (3877 tests).
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…onse mappers
Completes the analytics SDK migration: DatabricksStatementExecutionResponse is
now camelCase (aligned with the modular SDK's StatementResponse), so the
translation shims the migration introduced are no longer needed.
- Delete `toDescribeResponse` — `describeAdaptive` narrows the SDK response onto
the domain type directly (one cast: the SDK types DESCRIBE cells as
JsonValue[][], but for a DESCRIBE they are always string/null).
- Delete the three duplicated `asSdkResponse` test helpers; fixtures are now
authored in the camelCase domain shape and feed both the SDK-mock and the
snake-free parsers directly.
- `error_code` stays snake ONLY where it reads the raw Databricks error wire
shape (errors.ts auth classification, `{"error_code":...}` JSON bodies) — a
network payload, not our type.
Internal type only (not exported, not in API docs) — no breaking change.
Net -117 LOC. Verified: typecheck, 4355 tests, build (attw + publint), lint, format.
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Rebasing the analytics SDK migration onto #540 (createTestApp + never-crash mock client) surfaced that #540's mock hardcodes the LEGACY warehouse method names. Point them at the modular API so warehouse-readiness calls resolve: - mock-workspace-client.ts DEFAULT_RESPONSES: warehouses.get/start -> getWarehouse/startWarehouse. - mock-workspace-client.test.ts: never-crash table + convergence asserts. (fixtures.ts + analytics.integration.test.ts adaptations landed inside the migration commit during rebase conflict resolution.) Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
appkit bundles `shared` inline, whose code imports the modular @databricks/sdk-* packages, but only @databricks/sdk-experimental was declared. A published `@databricks/appkit` install (npm, incl. the Databricks Apps runtime) would therefore fail at runtime with "Cannot find module @databricks/sdk-statementexecution" on the analytics path. Declare sdk-auth/core/options/warehouses/statementexecution at 0.46.0 (mirroring shared) and exempt them from knip's unused check like sdk-experimental (they are imported only by the inlined `shared` workspace, which knip does not analyze). Known follow-up: the pnpm patch restoring Reyden's stripped `attachment` field is a workspace-only mechanism, does not reach consumers, and cannot be bundled under tsdown `unbundle` mode. Standard warehouses (dataArray + external_links) are unaffected; Reyden INLINE+ARROW_STREAM needs the upstream SDK `attachment` fix. See SDK_MIGRATION_GAPS.md. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…r SDK client
The modular @databricks/sdk-* default credential chain resolves auth only
from a ~/.databrickscfg profile and reads no DATABRICKS_* env vars, unlike
the legacy sdk-experimental. The Databricks Apps runtime injects the app's
service-principal credentials via env vars only (no config file), so a
deployed app built a modular client with no credentials and every request
failed ("Warehouse readiness check failed").
Resolve the service principal from the environment in mapToClientOptions
when no explicit token/profile is given: M2M from DATABRICKS_CLIENT_ID +
DATABRICKS_CLIENT_SECRET (what Apps injects), else PAT from DATABRICKS_TOKEN,
else fall through to the profile default chain for local dev. The explicit
token path (asUser OBO) is unchanged and still guarded by token !== undefined
so an empty/invalid OBO token fails loudly instead of silently falling
through to the service principal.
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The 5 modular @databricks/sdk-* deps were added to packages/appkit/package.json but the appkit importer in pnpm-lock.yaml was never regenerated, so CI's `pnpm install --frozen-lockfile` failed with ERR_PNPM_OUTDATED_LOCKFILE. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…ients The modular SDK's client-info `setProduct` validates the product as a token and rejects `@databricks/appkit` (the `@`/`/`), so the migrated clients sent a sanitized `databricks-appkit` User-Agent. Databricks-side dashboards filter AppKit's analytics/warehouse traffic on the literal `@databricks/appkit` UA, so that silently dropped AppKit out of them. Set the User-Agent on a per-client `ClientOptions.httpClient` transport wrapper (`buildHttpClient`) that prepends `@databricks/appkit/<version>` (+ userAgentExtra) to each request, replacing the process-global `setProduct` sanitization/latch. This keeps the exact legacy string and ships inside appkit's bundled dist, so it reaches deployed apps — a pnpm patch of the validator would not (pnpm patches apply only at workspace install, not to the npm-installed tarball). Also corrects the service-principal auth comments: the SDK's default chain does read DATABRICKS_* env; the deployed failure was its M2M strategy using the raw scheme-less DATABRICKS_HOST for OAuth discovery, which the normalized-host resolution already works around. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The dev-mode warehouse discovery moved from service-context to resources/warehouse.ts (#592). Note why it stays on the legacy apiClient: the modular listWarehouses request has no skip_cannot_use filter. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…ent patch sdk-core/sdk-auth/sdk-options 0.46.0 -> 0.51.0, sdk-warehouses -> 0.53.0, sdk-statementexecution -> 0.52.0. The Reyden `attachment` field is still stripped by statementexecution 0.52.0, so the patch is re-created against that version (same three edits: schema, transform, ResultData type). These versions normalize scheme-less hosts in the request path and in OAuth discovery; AppKit's own normalizeHost/env-M2M resolution is kept for now. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
sdk-auth 0.51.0's newM2mCredentials caches only the token endpoint and mints a new OAuth token on every request, unlike the legacy SDK which reused it until expiry. Wrap the env service-principal credentials in a cache that refreshes 40s before expiry (the legacy margin) and shares one in-flight fetch across concurrent callers. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
… HTTP status Typed modular clients throw sdk-core's ApiError, which carries the HTTP status in httpStatusCode rather than statusCode. isUnauthorized missed those 401s, so an expired user token no longer became IdentityExpiredError, and Plugin.execute and the server error handler returned 500 for typed 4xx failures. Both now read either field through a shared httpStatusOf. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The modular SDK's default chain mints a token on every request: its CLI strategy shells out to `databricks auth token` per call, and its M2M strategy fetches a new OAuth token per call. The legacy SDK reused the token until expiry. Local dev with a CLI profile and CI typegen with an M2M profile paid this on every API call. Resolve the profile once and pick PAT, M2M or CLI in the SDK's order, wrapping M2M and CLI in the existing token cache. Profiles that pin an authType or a groupId keep the SDK's own uncached selection. Deployed apps are unaffected: env M2M was already cached and OBO uses the forwarded token. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
`databricks auth login` writes `auth_type = databricks-cli`, and the cached default chain skipped every profile with an auth type, so the common local setup still ran `databricks auth token` on every request. Pin the named strategy (pat, oauth-m2m or databricks-cli) and cache it. Other auth types and groupId profiles still use the SDK's own selection. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The cache interceptor passed through only AppKitError and the legacy SDK's ApiError; anything else became ExecutionError.statementFailed (500). The modular SDK's ApiError is a different class, so a cached call failing with a 4xx (e.g. a jobs run that doesn't exist, 400) reached the client as a 500. Also pass through any error that carries an HTTP status (httpStatusOf). Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…lar clients Review findings against the legacy SDK: - Host-only config read ~/.databrickscfg anyway, so a [DEFAULT] profile's credential could be sent to a different DATABRICKS_HOST, and a host-only .env failed to boot. Like the legacy SDK, a configured host now skips the config file and uses the Databricks CLI token for that host (cached). - DATABRICKS_AUTH_TYPE was ignored (pat still ran as the SP). It now pins the strategy; an unsupported value fails with an actionable message. - Modular clients do not retry. The transport now retries 429 and the legacy transient errors with the legacy backoff (up to 300s). Streamed bodies are never retried. - Modular errors were not instanceof the exported ApiError and had no statusCode, so the retry interceptor retried 4xx. The exported ApiError now matches both classes and modular errors expose statusCode/errorCode. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The header still said the seam migrates no service, which is no longer true once warehouses and statementExecution use the modular SDK. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
MarioCadenas
force-pushed
the
analytics-migration-sdk
branch
from
October 16, 2026 14:46
7897594 to
db1d298
Compare
atilafassina
approved these changes
Oct 16, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Migrates the analytics stack (
SQLWarehouseConnector+ type-generator) off the legacy monolithic@databricks/sdk-experimentalonto the new modular per-service@databricks/sdk-*SDK (v0.46.0, ESM-only), behind the existingpackages/shared/src/workspace-client/facade seam.Analytics doesn't touch the SDK directly — it delegates to
SQLWarehouseConnector, which uses two services:warehousesandstatementExecution. Both move together in this increment; every other service (files/genie/jobs/serving/currentUser/apiClient) still routes through the legacy client behind the same facade (the seam's designed incremental state).How
packages/shared/src/workspace-client/modular.ts— the sole importer of@databricks/sdk-*(oxlintno-restricted-importsboundary), mirroringlegacy.ts. Builds per-serviceWarehousesClient/StatementExecutionClient; maps wrapper options →ClientOptions(host scheme-normalization, credential resolution, and ahttpClienttransport that stamps the User-Agent).getStatement→getStatementResult,getStatementResultChunkN→getResultData), camelCase response model,CallOptions { signal }for cancellation.patches/@databricks__sdk-statementexecution@0.46.0.patch) restores the undocumented Reydenattachmentresponse field that the SDK's generated unmarshal transform would otherwise strip. Version-pinned — re-apply on any SDK bump; an upstream fix is preferred long-term.Authentication (the foundation for migrating the remaining services)
Auth resolution is centralized in one service-agnostic function,
mapToClientOptions(opts)inmodular.ts, which produces theClientOptions { host, credentials, httpClient }that every modular client constructor accepts. Warehouses and statementExecution build through it today; each remaining service will build through the same function, so the rest of the migration needs no per-service auth work (the credentials are generic bearerTokenCredentials, not service-scoped).Resolution order — restores the legacy SDK's behavior for the modes AppKit uses:
asUserOBO) → PAT, guarded bytoken !== undefinedso an empty/invalid OBO token fails loudly instead of silently falling through to the service principal (privilege-escalation guard);DATABRICKS_CLIENT_ID+DATABRICKS_CLIENT_SECRET→ OAuth M2M (what the Databricks Apps runtime injects), elseDATABRICKS_TOKEN→ PAT, else the SDK's profile-based default chain.Why resolve the SP explicitly rather than lean on the SDK's default chain: the default chain does read the
DATABRICKS_*env vars, but its M2M strategy performs OAuth token-endpoint discovery against the rawDATABRICKS_HOST, which the Apps runtime injects scheme-less (e.g.e2-dogfood.staging.cloud.databricks.com). Discovery then throwsInvalid URLand every deployed request fails as "Warehouse readiness check failed". Resolving the SP here with the scheme-normalized host avoids that (it's the credential-side facet of the host-normalization fix below). Verified end-to-end on a deployed app.Not a full 1-to-1 with legacy's auth surface: the modular SDK 0.46.0 supports only PAT, OAuth M2M, and OAuth U2M — there are no factories for Azure (MSI/CLI/SP), GCP, or the instance-metadata service that legacy unified-auth covered. This is behaviorally equivalent for AppKit's real environments (Databricks Apps = SP via env; local dev = profile / PAT / env) but a reduced surface for those exotic modes; noted for release notes.
Per-service OBO scopes: the auth code is uniform, but each OBO-consuming service needs its matching
user_api_scopesdeclared in the app'sdatabricks.yml—sql(warehouse/statement),files.files,dashboards.genie,serving.serving-endpoints. That's app configuration, not auth code.Runtime bugs found + fixed (caught against live warehouses / a deployed app, not unit tests)
hoststraight tofetch; a bareDATABRICKS_HOST→Invalid URL. Fixed vianormalizeHoston the request host.setProduct("@databricks/appkit")throws (the@//aren't token chars) and client-info is process-global. AppKit dashboards filter traffic on the literal@databricks/appkitUA, so we set it on a per-clienthttpClienttransport wrapper instead — preserving the exact string. (A pnpm patch of the validator wouldn't reach deployed apps, which is where the dashboard traffic originates.)ApiError.code— the modular ApiError carries the Databricks error code on.code, not.errorCode; the connector now reads either, so the arrow disposition/format capability-rejection fallback (INLINE↔EXTERNAL_LINKS) fires again.bigint; Reyden's cached INLINE+ARROW_STREAM result then brokeJSON.stringify. Counts are coerced back tonumberat the connector boundary.DATABRICKS_HOSTthe Apps runtime injects, so OAuth token discovery failed (Invalid URL) and every deployed query died as "Warehouse readiness check failed". Fixed by resolving the SP with a scheme-normalized host (see Authentication).Testing
appkit+sharedsuites green, including new regression tests for the.coderead, bigint normalization, the env-based credential resolution (M2M-from-env, PAT-from-env, OBO-token-wins, M2M-needs-host), and the@databricks/appkitUser-Agent stamping.pnpm -r typecheck, lint, format clean;pnpm install --frozen-lockfilegreen (appkit importer synced inpnpm-lock.yaml)..obo.sql), warehouse auto-start, metric views.asUser,user_api_scopes: [sql]) query both execute against the SQL warehouse.Notes
This pull request and its description were written by Isaac.