@databricks/appkit 0.51.0 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CLAUDE.md +3 -0
  2. package/dist/agents/databricks.d.ts +2 -3
  3. package/dist/agents/databricks.d.ts.map +1 -1
  4. package/dist/agents/databricks.js +5 -4
  5. package/dist/agents/databricks.js.map +1 -1
  6. package/dist/agents/supervisor-api.d.ts.map +1 -1
  7. package/dist/agents/supervisor-api.js +3 -1
  8. package/dist/agents/supervisor-api.js.map +1 -1
  9. package/dist/appkit/package.js +1 -1
  10. package/dist/cache/index.d.ts.map +1 -1
  11. package/dist/cache/index.js +4 -2
  12. package/dist/cache/index.js.map +1 -1
  13. package/dist/connectors/files/client.js +2 -1
  14. package/dist/connectors/files/client.js.map +1 -1
  15. package/dist/connectors/genie/client.d.ts +2 -4
  16. package/dist/connectors/genie/client.js +2 -3
  17. package/dist/connectors/genie/client.js.map +1 -1
  18. package/dist/connectors/jobs/client.d.ts +2 -2
  19. package/dist/connectors/jobs/client.js +2 -1
  20. package/dist/connectors/jobs/client.js.map +1 -1
  21. package/dist/connectors/serving/client.d.ts +1 -1
  22. package/dist/connectors/serving/client.js +2 -1
  23. package/dist/connectors/serving/client.js.map +1 -1
  24. package/dist/connectors/sql-warehouse/client.js +2 -1
  25. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  26. package/dist/connectors/sql-warehouse/defaults.js.map +1 -1
  27. package/dist/connectors/sql-warehouse/warehouse-status-emitter.js.map +1 -1
  28. package/dist/connectors/vector-search/client.js.map +1 -1
  29. package/dist/context/client-options.js.map +1 -1
  30. package/dist/context/execution-context.d.ts +1 -1
  31. package/dist/context/service-context.d.ts +2 -1
  32. package/dist/context/service-context.d.ts.map +1 -1
  33. package/dist/context/service-context.js +8 -5
  34. package/dist/context/service-context.js.map +1 -1
  35. package/dist/core/appkit.d.ts +2 -1
  36. package/dist/core/appkit.d.ts.map +1 -1
  37. package/dist/core/appkit.js.map +1 -1
  38. package/dist/index.d.ts +6 -1
  39. package/dist/index.js +4 -1
  40. package/dist/internal-telemetry/reporter.js.map +1 -1
  41. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  42. package/dist/plugins/analytics/analytics.js.map +1 -1
  43. package/dist/plugins/analytics/query.js.map +1 -1
  44. package/dist/plugins/analytics/result-delivery.js.map +1 -1
  45. package/dist/plugins/files/plugin.js +4 -3
  46. package/dist/plugins/files/plugin.js.map +1 -1
  47. package/dist/plugins/files/types.d.ts +2 -1
  48. package/dist/plugins/files/types.d.ts.map +1 -1
  49. package/dist/plugins/jobs/plugin.js.map +1 -1
  50. package/dist/plugins/jobs/types.d.ts +2 -1
  51. package/dist/plugins/jobs/types.d.ts.map +1 -1
  52. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  53. package/dist/plugins/lakebase/lakebase.js +5 -4
  54. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  55. package/dist/shared/src/schemas/manifest.d.ts +2 -2
  56. package/dist/stream/arrow-stream-processor.js.map +1 -1
  57. package/dist/type-generator/errors.js +59 -1
  58. package/dist/type-generator/errors.js.map +1 -1
  59. package/dist/type-generator/index.js +105 -22
  60. package/dist/type-generator/index.js.map +1 -1
  61. package/dist/type-generator/mv-registry/describe.js.map +1 -1
  62. package/dist/type-generator/query-registry.js +60 -21
  63. package/dist/type-generator/query-registry.js.map +1 -1
  64. package/dist/type-generator/serving/fetcher.js +2 -1
  65. package/dist/type-generator/serving/fetcher.js.map +1 -1
  66. package/dist/type-generator/serving/generator.js +3 -2
  67. package/dist/type-generator/serving/generator.js.map +1 -1
  68. package/dist/type-generator/statement-result.js.map +1 -1
  69. package/dist/type-generator/types.js.map +1 -1
  70. package/dist/type-generator/vite-plugin.d.ts.map +1 -1
  71. package/dist/type-generator/vite-plugin.js +3 -2
  72. package/dist/type-generator/vite-plugin.js.map +1 -1
  73. package/dist/type-generator/warehouse-status.js.map +1 -1
  74. package/dist/workspace-client/client.js +57 -0
  75. package/dist/workspace-client/client.js.map +1 -0
  76. package/dist/workspace-client/errors.d.ts +2 -0
  77. package/dist/workspace-client/errors.js +3 -0
  78. package/dist/workspace-client/factory.d.ts +19 -0
  79. package/dist/workspace-client/factory.d.ts.map +1 -0
  80. package/dist/workspace-client/factory.js +24 -0
  81. package/dist/workspace-client/factory.js.map +1 -0
  82. package/dist/workspace-client/index.d.ts +4 -0
  83. package/dist/workspace-client/index.js +5 -0
  84. package/dist/workspace-client/legacy.d.ts +29 -0
  85. package/dist/workspace-client/legacy.d.ts.map +1 -0
  86. package/dist/workspace-client/legacy.js +23 -0
  87. package/dist/workspace-client/legacy.js.map +1 -0
  88. package/dist/workspace-client/types.d.ts +49 -0
  89. package/dist/workspace-client/types.d.ts.map +1 -0
  90. package/docs/api/appkit/Class.DatabricksAdapter.md +2 -3
  91. package/docs/api/appkit/Function.createApp.md +9 -9
  92. package/docs/api/appkit/Function.createWorkspaceClient.md +28 -0
  93. package/docs/api/appkit/Interface.WorkspaceClient.md +119 -0
  94. package/docs/api/appkit/Interface.WorkspaceClientOptions.md +47 -0
  95. package/docs/api/appkit.md +3 -0
  96. package/docs/development/type-generation.md +17 -2
  97. package/llms.txt +3 -0
  98. package/package.json +1 -1
  99. package/sbom.cdx.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/sql-warehouse/client.ts"],"sourcesContent":["import {\n Context,\n type sql,\n type WorkspaceClient,\n} from \"@databricks/sdk-experimental\";\nimport type { TelemetryOptions } from \"shared\";\nimport {\n AppKitError,\n ConfigurationError,\n ConnectionError,\n ExecutionError,\n ValidationError,\n} from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport {\n ArrowStreamProcessor,\n type RefreshChunkLink,\n} from \"../../stream/arrow-stream-processor\";\nimport type { TelemetryProvider } from \"../../telemetry\";\nimport {\n type Counter,\n type Histogram,\n type Span,\n SpanKind,\n SpanStatusCode,\n TelemetryManager,\n} from \"../../telemetry\";\nimport { buildEmptyArrowIPCBase64 } from \"./arrow-schema\";\nimport { executeStatementDefaults } from \"./defaults\";\nimport { WarehousePollBackoff } from \"./warehouse-poll-backoff\";\nimport { WarehouseStatusEmitter } from \"./warehouse-status-emitter\";\n\nconst logger = createLogger(\"connectors:sql-warehouse\");\n\n/**\n * Real column names from a statement's result manifest (positional\n * `column_N` fallback for any blank ones). Databricks encodes ARROW_STREAM\n * schemas positionally (`col_0`, …); these names let consumers relabel an\n * Arrow result to match the JSON path. Returns `undefined` when the manifest\n * carries no columns.\n */\nfunction arrowColumnNames(\n response: sql.StatementResponse,\n): string[] | undefined {\n const cols = response.manifest?.schema?.columns;\n if (!cols || cols.length === 0) return undefined;\n return cols.map((c, i) =>\n c.name && c.name.length > 0 ? c.name : `column_${i}`,\n );\n}\n\n/**\n * Maximum size for inline Arrow IPC attachments (25 MiB decoded — the\n * Databricks Statement Execution API hard cap on INLINE responses).\n *\n * The analytics route streams the decoded attachment straight back on the\n * query response body; this cap bounds the single in-memory copy. Larger\n * results fall through to `disposition: \"EXTERNAL_LINKS\"`, which streams\n * chunk-by-chunk and is not bound by this limit.\n */\nconst MAX_INLINE_ATTACHMENT_BYTES = 25 * 1024 * 1024;\n\n/**\n * Safety cap on how many additional EXTERNAL_LINKS chunks\n * {@link SQLWarehouseConnector._resolveAllExternalLinks} will follow when the\n * manifest omits `total_chunk_count`. High enough to cover any real result;\n * only bounds a misbehaving warehouse with a cyclic `next_chunk_index`.\n */\nconst MAX_EXTERNAL_CHUNK_FOLLOWS = 10_000;\n\ninterface SQLWarehouseConfig {\n timeout?: number;\n telemetry?: TelemetryOptions;\n}\n\n/**\n * Default ceiling for how long {@link SQLWarehouseConnector.ensureWarehouseRunning}\n * will wait for a warehouse to reach the RUNNING state before giving up.\n *\n * Five minutes covers a cold-start of a classic warehouse on most workspaces;\n * serverless typically reaches RUNNING within ~30s.\n */\nexport const DEFAULT_WAREHOUSE_STARTUP_TIMEOUT_MS = 5 * 60 * 1000;\n\n/**\n * Window during which a recent `RUNNING` observation lets subsequent calls\n * to {@link SQLWarehouseConnector.ensureWarehouseRunning} short-circuit\n * without making any SDK calls. Sized to roughly the upper bound of how\n * long Databricks keeps a warehouse \"stickily\" available between requests\n * — past that, we re-verify.\n */\nconst WAREHOUSE_RUNNING_CACHE_TTL_MS = 30_000;\n\n/**\n * A single observation of the warehouse state, emitted by\n * {@link SQLWarehouseConnector.ensureWarehouseRunning} so callers can stream\n * progress to clients (e.g. over SSE).\n *\n * Note: `health.summary` from the SDK is intentionally NOT exposed on this\n * type. It's free-form operator-oriented diagnostic text (cluster IDs,\n * capacity-failure reasons, internal RPC errors) that must not reach end\n * users. The raw value stays in the OTel span attributes for server-side\n * debugging only.\n */\nexport interface WarehouseStatusUpdate {\n /** Current state from the SDK (RUNNING | STARTING | STOPPED | STOPPING | DELETED | DELETING). */\n state: sql.State;\n /** Milliseconds elapsed since `ensureWarehouseRunning` was called. */\n elapsedMs: number;\n /** 1-based attempt counter — useful for tests and telemetry. */\n attempt: number;\n}\n\n/** Options for {@link SQLWarehouseConnector.ensureWarehouseRunning}. */\ninterface EnsureWarehouseRunningOptions {\n /** Invoked every time the warehouse state changes during the wait. */\n onStatus: (update: WarehouseStatusUpdate) => void;\n /** Aborts the wait. The connector treats abort as `ExecutionError.canceled()`. */\n signal?: AbortSignal;\n /** Hard ceiling on the total wait. Defaults to {@link DEFAULT_WAREHOUSE_STARTUP_TIMEOUT_MS}. */\n timeoutMs?: number;\n /**\n * When `true` (default), a `STOPPED` warehouse is auto-started.\n * When `false`, `STOPPED` surfaces as `ConfigurationError` immediately —\n * useful for cost-controlled deployments that don't want any caller to\n * trigger billable warehouse starts.\n */\n autoStart?: boolean;\n /** @internal Fired once when `warehouses.start` is issued for this poll. */\n onWarehouseStartIssued?: () => void;\n}\n\n/** In-flight warehouse readiness work shared across concurrent callers. */\ninterface WarehouseReadinessInFlight {\n promise: Promise<void>;\n refCount: number;\n sharedController: AbortController;\n subscribers: Set<(update: WarehouseStatusUpdate) => void>;\n /** Last emitted update; replayed to late joiners. */\n lastUpdate: WarehouseStatusUpdate | null;\n /** `true` once this poll has called `warehouses.start`. */\n warehouseStartIssued: boolean;\n}\n\nexport class SQLWarehouseConnector {\n private readonly name = \"sql-warehouse\";\n\n private config: SQLWarehouseConfig;\n\n // Lazy-initialized: only created when Arrow format is used\n private _arrowProcessor: ArrowStreamProcessor | null = null;\n\n /**\n * Per-warehouse cache of the last RUNNING observation timestamp. Used by\n * {@link ensureWarehouseRunning} to short-circuit warm-path callers; see\n * {@link WAREHOUSE_RUNNING_CACHE_TTL_MS}.\n */\n private _recentlyRunning = new Map<string, number>();\n /** Per-warehouse readiness singleflight — one poll loop per cold start. */\n private _readinessInFlight = new Map<string, WarehouseReadinessInFlight>();\n // telemetry\n private readonly telemetry: TelemetryProvider;\n private readonly telemetryMetrics: {\n queryCount: Counter;\n queryDuration: Histogram;\n };\n\n constructor(config: SQLWarehouseConfig) {\n this.config = config;\n\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.telemetryMetrics = {\n queryCount: this.telemetry.getMeter().createCounter(\"query.count\", {\n description: \"Total number of queries executed\",\n unit: \"1\",\n }),\n queryDuration: this.telemetry\n .getMeter()\n .createHistogram(\"query.duration\", {\n description: \"Duration of queries executed\",\n unit: \"ms\",\n }),\n };\n }\n\n /**\n * Lazily initializes and returns the ArrowStreamProcessor.\n * Only created on first Arrow format query to avoid unnecessary allocation.\n */\n private get arrowProcessor(): ArrowStreamProcessor {\n if (!this._arrowProcessor) {\n this._arrowProcessor = new ArrowStreamProcessor({\n timeout: this.config.timeout || executeStatementDefaults.timeout,\n retries: ArrowStreamProcessor.DEFAULT_RETRIES,\n });\n }\n return this._arrowProcessor;\n }\n\n async executeStatement(\n workspaceClient: WorkspaceClient,\n input: sql.ExecuteStatementRequest,\n signal?: AbortSignal,\n ) {\n const startTime = Date.now();\n let success = false;\n\n // if signal is aborted, throw an error\n if (signal?.aborted) {\n throw ExecutionError.canceled();\n }\n\n return this.telemetry.startActiveSpan(\n \"sql.query\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"db.warehouse_id\": input.warehouse_id || \"\",\n \"db.catalog\": input.catalog ?? \"\",\n \"db.schema\": input.schema ?? \"\",\n \"db.statement\": input.statement?.substring(0, 500) || \"\",\n \"db.has_parameters\": !!input.parameters,\n },\n },\n async (span: Span) => {\n let abortHandler: (() => void) | undefined;\n let isAborted = false;\n\n if (signal) {\n abortHandler = () => {\n // abort span if not recording\n if (!span.isRecording()) return;\n isAborted = true;\n span.setAttribute(\"cancelled\", true);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: \"Query cancelled by client\",\n });\n span.end();\n };\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n try {\n // validate required fields\n if (!input.statement) {\n throw ValidationError.missingField(\"statement\");\n }\n\n if (!input.warehouse_id) {\n throw ValidationError.missingField(\"warehouse_id\");\n }\n\n const body: sql.ExecuteStatementRequest = {\n statement: input.statement,\n parameters: input.parameters,\n warehouse_id: input.warehouse_id,\n catalog: input.catalog,\n schema: input.schema,\n wait_timeout:\n input.wait_timeout || executeStatementDefaults.wait_timeout,\n disposition:\n input.disposition || executeStatementDefaults.disposition,\n format: input.format || executeStatementDefaults.format,\n byte_limit: input.byte_limit,\n row_limit: input.row_limit,\n on_wait_timeout:\n input.on_wait_timeout || executeStatementDefaults.on_wait_timeout,\n };\n\n span.addEvent(\"statement.submitting\", {\n \"db.warehouse_id\": input.warehouse_id,\n });\n\n const response =\n await workspaceClient.statementExecution.executeStatement(\n body,\n this._createContext(signal),\n );\n\n if (!response) {\n throw ConnectionError.apiFailure(\"SQL Warehouse\");\n }\n const status = response.status;\n const statementId = response.statement_id as string;\n\n span.setAttribute(\"db.statement_id\", statementId);\n span.addEvent(\"statement.submitted\", {\n \"db.statement_id\": response.statement_id,\n \"db.status\": status?.state,\n });\n\n let result:\n | sql.StatementResponse\n | { result: { statement_id: string; status: sql.StatementStatus } };\n\n switch (status?.state) {\n case \"RUNNING\":\n case \"PENDING\":\n span.addEvent(\"statement.polling_started\", {\n \"db.status\": response.status?.state,\n });\n result = await this._pollForStatementResult(\n workspaceClient,\n statementId,\n this.config.timeout,\n signal,\n );\n break;\n case \"SUCCEEDED\":\n result = await this._transformDataArray(\n response,\n workspaceClient,\n signal,\n );\n break;\n case \"FAILED\":\n throw ExecutionError.statementFailed(\n status.error?.message,\n status.error?.error_code,\n );\n case \"CANCELED\":\n throw ExecutionError.canceled();\n case \"CLOSED\":\n throw ExecutionError.resultsClosed();\n default:\n throw ExecutionError.unknownState(\n String(status?.state ?? \"unknown\"),\n );\n }\n\n const resultData = result.result as any;\n const rowCount =\n resultData?.data?.length ?? resultData?.data_array?.length ?? 0;\n\n if (rowCount > 0) {\n span.setAttribute(\"db.result.row_count\", rowCount);\n }\n\n const duration = Date.now() - startTime;\n logger.event()?.setContext(\"sql-warehouse\", {\n warehouse_id: input.warehouse_id,\n rows_returned: rowCount,\n query_duration_ms: duration,\n });\n\n success = true;\n // only set success status if not aborted\n if (!isAborted) {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n return result;\n } catch (error) {\n // only record error if not already handled by abort\n if (!isAborted) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n\n logger.error(\n \"Statement execution failed: %s\",\n error instanceof Error ? error.message : String(error),\n );\n }\n\n this._rethrowStatementError(error);\n } finally {\n // remove abort handler\n if (abortHandler && signal) {\n signal.removeEventListener(\"abort\", abortHandler);\n }\n\n const duration = Date.now() - startTime;\n\n // end span if not already ended by abort handler\n if (!isAborted) {\n span.end();\n }\n\n const attributes = {\n \"db.warehouse_id\": input.warehouse_id,\n \"db.catalog\": input.catalog ?? \"\",\n \"db.schema\": input.schema ?? \"\",\n \"db.statement\": input.statement?.substring(0, 500) || \"\",\n success: success.toString(),\n };\n\n this.telemetryMetrics.queryCount.add(1, attributes);\n this.telemetryMetrics.queryDuration.record(duration, attributes);\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n /**\n * Wait until the SQL warehouse is in the `RUNNING` state, auto-starting it\n * if currently `STOPPED`. Emits a {@link WarehouseStatusUpdate} whenever\n * the observed state changes, so callers can surface progress (e.g. over\n * SSE) instead of letting the UI freeze on a cold warehouse. Equal\n * successive observations are de-duplicated.\n *\n * Fast path: if this connector recently observed the warehouse RUNNING\n * (within {@link WAREHOUSE_RUNNING_CACHE_TTL_MS}), the call returns\n * immediately without any SDK round-trip or status emission. This keeps\n * cache-hit analytics requests off the Databricks control plane.\n *\n * Concurrent callers for the same `warehouseId` share a single in-flight\n * poll loop (singleflight): only the owner issues `get`/`start`; joiners\n * receive broadcast `onStatus` updates and share the result promise.\n * When every waiter disconnects before `warehouses.start`, the shared poll\n * is aborted on the next microtask if still unclaimed (kills true orphans\n * without a fixed grace window). After `start` is issued, the poll runs to\n * completion even with no waiters.\n *\n * Behaviour by initial state:\n * - `RUNNING`: emits one update, caches the observation, returns.\n * - `STOPPED`: emits a synthetic `STARTING` update, calls\n * `workspaceClient.warehouses.start`, then polls until `RUNNING`.\n * When `autoStart: false`, throws `ConfigurationError` instead.\n * - `STARTING` / `STOPPING`: polls until `RUNNING`.\n * - `DELETED` / `DELETING`: throws `ConfigurationError.resourceNotFound`.\n *\n * Aborts and timeouts surface as `ExecutionError`. The poll loop uses\n * exponential backoff with ±15% jitter (1s → 30s cap) and runs inside a\n * `sql.warehouseReady` telemetry span.\n */\n async ensureWarehouseRunning(\n workspaceClient: WorkspaceClient,\n warehouseId: string,\n opts: EnsureWarehouseRunningOptions,\n ): Promise<void> {\n const {\n onStatus,\n signal,\n timeoutMs = DEFAULT_WAREHOUSE_STARTUP_TIMEOUT_MS,\n autoStart = true,\n } = opts;\n\n if (signal?.aborted) throw ExecutionError.canceled();\n if (!warehouseId) throw ValidationError.missingField(\"warehouse_id\");\n if (this._isRecentlyRunning(warehouseId)) return;\n\n const existing = this._readinessInFlight.get(warehouseId);\n if (existing && !existing.sharedController.signal.aborted) {\n return this._waitOnReadiness(existing, onStatus, signal, true);\n }\n\n const entry = this._spawnReadinessInFlight(workspaceClient, warehouseId, {\n timeoutMs,\n autoStart,\n });\n this._readinessInFlight.set(warehouseId, entry);\n return this._waitOnReadiness(entry, onStatus, signal, false);\n }\n\n private _waitOnReadiness(\n entry: WarehouseReadinessInFlight,\n onStatus: (update: WarehouseStatusUpdate) => void,\n signal: AbortSignal | undefined,\n join: boolean,\n ): Promise<void> {\n if (signal?.aborted) throw ExecutionError.canceled();\n if (join) entry.refCount++;\n entry.subscribers.add(onStatus);\n if (entry.lastUpdate) onStatus(entry.lastUpdate);\n return this._joinWarehouseReadiness(entry, signal);\n }\n\n private _spawnReadinessInFlight(\n workspaceClient: WorkspaceClient,\n warehouseId: string,\n opts: { timeoutMs: number; autoStart: boolean },\n ): WarehouseReadinessInFlight {\n const sharedController = new AbortController();\n const entry: WarehouseReadinessInFlight = {\n refCount: 1,\n sharedController,\n subscribers: new Set(),\n lastUpdate: null,\n warehouseStartIssued: false,\n promise: Promise.resolve(),\n };\n\n entry.promise = this.telemetry\n .startActiveSpan(\n \"sql.warehouseReady\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"db.warehouse_id\": warehouseId,\n \"db.warehouse.startup_timeout_ms\": opts.timeoutMs,\n },\n },\n async (span: Span) => {\n try {\n await this._pollUntilWarehouseRunning(\n workspaceClient,\n warehouseId,\n {\n onStatus: (update) => this._broadcastReadiness(entry, update),\n signal: sharedController.signal,\n timeoutMs: opts.timeoutMs,\n autoStart: opts.autoStart,\n onWarehouseStartIssued: () => {\n entry.warehouseStartIssued = true;\n },\n },\n span,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n } catch (error) {\n this._throwSanitizedReadinessError(error, warehouseId, span);\n } finally {\n span.end();\n }\n },\n { name: this.name, includePrefix: true },\n )\n .finally(() => {\n if (this._readinessInFlight.get(warehouseId) === entry) {\n this._readinessInFlight.delete(warehouseId);\n }\n });\n\n entry.promise.catch(() => {});\n return entry;\n }\n\n private _broadcastReadiness(\n entry: WarehouseReadinessInFlight,\n update: WarehouseStatusUpdate,\n ): void {\n entry.lastUpdate = update;\n for (const subscriber of entry.subscribers) {\n try {\n subscriber(update);\n } catch {\n // One consumer must not block updates to the rest.\n }\n }\n }\n\n /** Ref-counted await; caller abort rejects locally. */\n private _joinWarehouseReadiness(\n entry: WarehouseReadinessInFlight,\n callerSignal?: AbortSignal,\n ): Promise<void> {\n if (!callerSignal) return entry.promise;\n\n return new Promise<void>((resolve, reject) => {\n let done = false;\n const finish = (outcome: \"ok\" | \"abort\" | \"err\", error?: unknown) => {\n if (done) return;\n done = true;\n callerSignal.removeEventListener(\"abort\", onAbort);\n if (outcome === \"ok\") resolve();\n else if (outcome === \"abort\") reject(ExecutionError.canceled());\n else reject(error);\n };\n const onAbort = () => {\n this._releaseReadinessWaiter(entry);\n finish(\"abort\");\n };\n callerSignal.addEventListener(\"abort\", onAbort, { once: true });\n entry.promise.then(\n () => finish(\"ok\"),\n (error) => finish(\"err\", error),\n );\n });\n }\n\n private _releaseReadinessWaiter(entry: WarehouseReadinessInFlight): void {\n if (entry.refCount > 0) entry.refCount--;\n if (entry.refCount > 0 || entry.sharedController.signal.aborted) return;\n\n if (entry.warehouseStartIssued) {\n // Start already issued — finish warming for the process.\n return;\n }\n\n // Defer abort to the next microtask so a synchronous StrictMode\n // remount can rejoin before the shared poll is cancelled.\n queueMicrotask(() => {\n if (entry.refCount <= 0 && !entry.sharedController.signal.aborted) {\n entry.sharedController.abort(\"all warehouse readiness waiters aborted\");\n }\n });\n }\n\n private async _pollUntilWarehouseRunning(\n workspaceClient: WorkspaceClient,\n warehouseId: string,\n opts: EnsureWarehouseRunningOptions,\n span: Span,\n ): Promise<void> {\n const {\n onStatus,\n signal,\n timeoutMs = DEFAULT_WAREHOUSE_STARTUP_TIMEOUT_MS,\n autoStart = true,\n onWarehouseStartIssued,\n } = opts;\n const startTime = Date.now();\n const emitter = new WarehouseStatusEmitter(span, startTime, onStatus);\n let didStart = false;\n const backoff = new WarehousePollBackoff();\n\n while (true) {\n if (signal?.aborted) throw ExecutionError.canceled();\n if (Date.now() - startTime > timeoutMs) {\n throw ExecutionError.statementFailed(\n `SQL warehouse did not reach RUNNING within ${timeoutMs}ms`,\n );\n }\n\n const info = await workspaceClient.warehouses.get(\n { id: warehouseId },\n this._createContext(signal),\n );\n const state = info?.state;\n const summary = info?.health?.summary;\n\n switch (state) {\n case \"RUNNING\":\n emitter.emit(state, summary);\n this._recentlyRunning.set(warehouseId, Date.now());\n span.setAttribute(\"db.warehouse.attempts\", emitter.attempt);\n return;\n case \"DELETED\":\n case \"DELETING\":\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n `The configured SQL warehouse is ${state}. Update DATABRICKS_WAREHOUSE_ID to point at an active warehouse.`,\n );\n case \"STOPPED\":\n if (!autoStart) {\n throw new ConfigurationError(\n \"The configured SQL warehouse is STOPPED and analytics auto-start is disabled. Start the warehouse manually or set analytics.autoStartWarehouse=true.\",\n );\n }\n if (!didStart) {\n emitter.emit(\"STARTING\", summary);\n onWarehouseStartIssued?.();\n await workspaceClient.warehouses.start(\n { id: warehouseId },\n this._createContext(signal),\n );\n didStart = true;\n } else {\n emitter.emit(state, summary);\n }\n break;\n case \"STARTING\":\n case \"STOPPING\":\n emitter.emit(state, summary);\n break;\n default:\n throw ExecutionError.unknownState(String(state ?? \"unknown\"));\n }\n\n const sleepMs = backoff.next();\n if (Date.now() + sleepMs - startTime >= timeoutMs) {\n throw ExecutionError.statementFailed(\n `SQL warehouse did not reach RUNNING within ${timeoutMs}ms`,\n );\n }\n await this._sleepRespectingAbort(sleepMs, signal);\n }\n }\n\n /**\n * `true` if this connector observed `warehouseId` in the RUNNING state\n * within the recent-cache TTL.\n */\n private _isRecentlyRunning(warehouseId: string): boolean {\n const observedAt = this._recentlyRunning.get(warehouseId);\n return (\n observedAt !== undefined &&\n Date.now() - observedAt < WAREHOUSE_RUNNING_CACHE_TTL_MS\n );\n }\n\n /**\n * Final landing for any error thrown by the readiness poll loop. Records\n * the original on the span and (at debug level) on the logger, then\n * rethrows either the structured AppKitError unchanged or a curated\n * ExecutionError — never the raw SDK message, which can contain operator\n * internals.\n *\n * The `logger.debug` covers environments where OTel isn't configured\n * (common in dev) so the raw error isn't lost just because no span\n * exporter is hooked up.\n */\n private _throwSanitizedReadinessError(\n error: unknown,\n warehouseId: string,\n span: Span,\n ): never {\n if (error instanceof AppKitError) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.code });\n throw error;\n }\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n );\n logger.debug(\n \"Warehouse readiness check raw error for %s: %O\",\n warehouseId,\n error,\n );\n const wrapped = ExecutionError.statementFailed(\n \"Warehouse readiness check failed\",\n );\n span.setStatus({ code: SpanStatusCode.ERROR, message: wrapped.code });\n throw wrapped;\n }\n\n /**\n * Sleep for `ms` milliseconds, but resolve early (and reject with\n * `ExecutionError.canceled()`) if `signal` aborts mid-sleep. Used by the\n * warehouse readiness loop so that client disconnects don't keep the loop\n * polling for the full interval.\n */\n private _sleepRespectingAbort(\n ms: number,\n signal?: AbortSignal,\n ): Promise<void> {\n if (signal?.aborted) return Promise.reject(ExecutionError.canceled());\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(ExecutionError.canceled());\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n }\n\n private async _pollForStatementResult(\n workspaceClient: WorkspaceClient,\n statementId: string,\n timeout = executeStatementDefaults.timeout,\n signal?: AbortSignal,\n ) {\n return this.telemetry.startActiveSpan(\n \"sql.poll\",\n {\n attributes: {\n \"db.statement_id\": statementId,\n \"db.polling.timeout\": timeout,\n },\n },\n async (span: Span) => {\n try {\n const startTime = Date.now();\n let delay = 1000;\n const maxDelayBetweenPolls = 5000; // max 5 seconds between polls\n let pollCount = 0;\n\n while (true) {\n pollCount++;\n span.setAttribute(\"db.polling.current_attempt\", pollCount);\n\n // check if timeout exceeded\n const elapsedTime = Date.now() - startTime;\n if (elapsedTime > timeout) {\n const error = ExecutionError.statementFailed(\n `Polling timeout exceeded after ${timeout}ms (elapsed: ${elapsedTime}ms)`,\n );\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n }\n\n if (signal?.aborted) {\n const error = ExecutionError.canceled();\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n }\n\n span.addEvent(\"polling.attempt\", {\n \"poll.attempt\": pollCount,\n \"poll.delay_ms\": delay,\n \"poll.elapsed_ms\": elapsedTime,\n });\n\n const response =\n await workspaceClient.statementExecution.getStatement(\n {\n statement_id: statementId,\n },\n this._createContext(signal),\n );\n if (!response) {\n throw ConnectionError.apiFailure(\"SQL Warehouse\");\n }\n\n const status = response.status;\n\n span.addEvent(\"polling.status_check\", {\n \"db.status\": status?.state,\n \"poll.attempt\": pollCount,\n });\n\n switch (status?.state) {\n case \"PENDING\":\n case \"RUNNING\":\n // continue polling\n break;\n case \"SUCCEEDED\":\n span.setAttribute(\"db.polling.attempts\", pollCount);\n span.setAttribute(\"db.polling.total_duration_ms\", elapsedTime);\n span.addEvent(\"polling.completed\", {\n \"poll.attempts\": pollCount,\n \"poll.duration_ms\": elapsedTime,\n });\n span.setStatus({ code: SpanStatusCode.OK });\n return this._transformDataArray(\n response,\n workspaceClient,\n signal,\n );\n case \"FAILED\":\n throw ExecutionError.statementFailed(\n status.error?.message,\n status.error?.error_code,\n );\n case \"CANCELED\":\n throw ExecutionError.canceled();\n case \"CLOSED\":\n throw ExecutionError.resultsClosed();\n default:\n throw ExecutionError.unknownState(\n String(status?.state ?? \"unknown\"),\n );\n }\n\n // continue polling after delay\n await new Promise((resolve) => setTimeout(resolve, delay));\n delay = Math.min(delay * 2, maxDelayBetweenPolls);\n }\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n\n // error logging is handled by executeStatement's catch block (gated on isAborted)\n this._rethrowStatementError(error);\n } finally {\n span.end();\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n private async _transformDataArray(\n response: sql.StatementResponse,\n workspaceClient: WorkspaceClient,\n signal?: AbortSignal,\n ) {\n if (response.manifest?.format === \"ARROW_STREAM\") {\n const result = response.result as\n | (sql.ResultData & { attachment?: string })\n | undefined;\n\n // Inline Arrow: pass the base64 IPC attachment through unmodified so\n // the analytics route can stream it to the client, where the existing\n // ArrowClient infrastructure decodes it into a Table. Validate size\n // here to fail fast on runaway payloads.\n if (result?.attachment) {\n return this._validateArrowAttachment(response, result.attachment);\n }\n\n // External links: data fetched separately via statement_id. An empty\n // array is a zero-row result (some warehouses emit `external_links: []`\n // rather than omitting it) — it must NOT go down the streaming path\n // (`streamChunks([])` rejects), so fall through to synthesize an empty\n // Arrow table below.\n if (result?.external_links && result.external_links.length > 0) {\n return this.updateWithArrowStatus(response, workspaceClient, signal);\n }\n\n // Empty result with a known schema: synthesize a zero-row Arrow IPC\n // attachment so the client always receives an Arrow Table for\n // ARROW_STREAM, regardless of whether the warehouse returned data.\n // Note: an empty array (`data_array: []`) is truthy, so length-check\n // explicitly — otherwise zero-row responses fall through to the JSON\n // row transform below and return `[]` JSON rows instead of an Arrow\n // table.\n const hasNoRows =\n !result?.data_array ||\n (Array.isArray(result.data_array) && result.data_array.length === 0);\n if (hasNoRows && response.manifest?.schema?.columns) {\n const synthesized = buildEmptyArrowIPCBase64(\n response.manifest.schema.columns,\n );\n return {\n ...response,\n result: { ...(result ?? {}), attachment: synthesized },\n };\n }\n\n // Inline data_array under ARROW_STREAM (rare): fall through to the\n // row transform below. The hook will receive `type: \"result\"` rows;\n // callers asking for ARROW_STREAM should not hit this path with\n // current Databricks warehouses.\n }\n\n if (!response.result?.data_array || !response.manifest?.schema?.columns) {\n return response;\n }\n\n const columns = response.manifest.schema.columns;\n\n const transformedData = response.result.data_array.map((row) => {\n const obj: Record<string, unknown> = {};\n row.forEach((value, index) => {\n const column = columns[index];\n const columnName = column?.name || `column_${index}`;\n\n // attempt to parse JSON strings for string columns\n if (\n column?.type_name === \"STRING\" &&\n typeof value === \"string\" &&\n value &&\n (value[0] === \"{\" || value[0] === \"[\")\n ) {\n try {\n obj[columnName] = JSON.parse(value);\n } catch {\n // if parsing fails, keep as string\n obj[columnName] = value;\n }\n } else {\n obj[columnName] = value;\n }\n });\n return obj;\n });\n\n // remove data_array\n const { data_array: _data_array, ...restResult } = response.result;\n return {\n ...response,\n result: {\n ...restResult,\n data: transformedData,\n },\n };\n }\n\n /**\n * Validate a base64 Arrow IPC attachment (INLINE ARROW_STREAM) and attach\n * the real column names from the result manifest.\n *\n * Databricks warehouses encode the Arrow schema positionally (`col_0`, …);\n * the real, aliased names live only in `manifest.schema.columns`. Rather\n * than decode/re-encode the payload server-side, we carry the names on the\n * result (`columnNames`) so the route can hand them to the client in a\n * response header and the client relabels the decoded Table — the one\n * mechanism used for both INLINE and EXTERNAL_LINKS.\n */\n private _validateArrowAttachment(\n response: sql.StatementResponse,\n attachment: string,\n ) {\n // Cap the size to protect against unbounded inline payloads from\n // misbehaving warehouses. `MAX_INLINE_ATTACHMENT_BYTES` tracks the API's\n // ~25 MiB inline hard cap and bounds memory if a server returns a runaway\n // response.\n //\n // Strip whitespace (rare but legal in base64) and account for trailing\n // `=` padding so the byte count is exact rather than an upper bound.\n const stripped = attachment.replace(/\\s+/g, \"\");\n const padding = stripped.endsWith(\"==\")\n ? 2\n : stripped.endsWith(\"=\")\n ? 1\n : 0;\n const decodedSize = Math.floor((stripped.length * 3) / 4) - padding;\n if (decodedSize > MAX_INLINE_ATTACHMENT_BYTES) {\n throw ExecutionError.statementFailed(\n `Inline Arrow attachment exceeds maximum size (${decodedSize} > ${MAX_INLINE_ATTACHMENT_BYTES} bytes)`,\n );\n }\n\n const columnNames = arrowColumnNames(response);\n if (columnNames) {\n return {\n ...response,\n result: {\n ...(response.result as sql.ResultData & {\n attachment?: string;\n columnNames?: string[];\n }),\n // `statement_id` is a top-level field, not on `ResultData` — carry it\n // onto the result (as the EXTERNAL_LINKS path does) so the route can\n // advertise it in `X-Appkit-Arrow-Columns-Ref` for wide inline schemas.\n statement_id: response.statement_id,\n columnNames,\n },\n };\n }\n\n return response;\n }\n\n private async updateWithArrowStatus(\n response: sql.StatementResponse,\n workspaceClient: WorkspaceClient,\n signal?: AbortSignal,\n ): Promise<{\n result: {\n statement_id: string;\n status: sql.StatementStatus;\n columnNames?: string[];\n external_links?: sql.ExternalLink[];\n refreshChunkLink?: RefreshChunkLink;\n };\n }> {\n const statementId = response.statement_id as string;\n return {\n result: {\n statement_id: statementId,\n status: {\n state: response.status?.state,\n error: response.status?.error,\n } as sql.StatementStatus,\n columnNames: arrowColumnNames(response),\n // Resolve the pre-signed links for EVERY chunk in the caller's own\n // execution context. Streaming these directly (see\n // {@link streamExternalLinks}) avoids a second `getStatement` under\n // the ambient service-principal context — which would fail for\n // `.obo.sql` (user-owned) statements.\n external_links: await this._resolveAllExternalLinks(\n workspaceClient,\n statementId,\n response,\n signal,\n ),\n // Bound to this caller's identity so the streamer can re-mint an\n // expired chunk link mid-download (links live <= 15 min; a large/slow\n // result can outlast the earliest ones).\n refreshChunkLink: this._makeChunkLinkRefresher(\n workspaceClient,\n statementId,\n ),\n },\n };\n }\n\n /**\n * Resolve pre-signed links for EVERY chunk of an EXTERNAL_LINKS result.\n *\n * The execute/getStatement response carries only the first chunk's links\n * (each link, except the last, exposes `next_chunk_index`); the remaining\n * chunks are fetched with `getStatementResultChunkN`. Runs in the caller's\n * identity context (user creds for `.obo.sql`), so there is no cross-identity\n * fetch. Only the tiny link metadata is resolved eagerly — the bytes still\n * stream one chunk at a time downstream. Without this a multi-chunk result\n * would be silently truncated to its first chunk.\n */\n private async _resolveAllExternalLinks(\n workspaceClient: WorkspaceClient,\n statementId: string,\n response: sql.StatementResponse,\n signal?: AbortSignal,\n ): Promise<sql.ExternalLink[] | undefined> {\n const first = response.result?.external_links;\n if (!first || first.length === 0) return first;\n\n const links: sql.ExternalLink[] = [...first];\n // Bound the follow loop so a warehouse returning a cyclic/never-ending\n // `next_chunk_index` can't spin forever. The manifest's chunk count is the\n // natural bound; fall back to a generous safety cap if it's absent (real\n // results still terminate earlier when `next_chunk_index` becomes null) so\n // a missing count doesn't silently truncate a genuine multi-chunk result.\n const maxFetches =\n response.manifest?.total_chunk_count ?? MAX_EXTERNAL_CHUNK_FOLLOWS;\n let next = this._nextChunkIndex(first);\n for (let fetches = 0; next != null && fetches < maxFetches; fetches++) {\n if (signal?.aborted) throw ExecutionError.canceled();\n const chunk =\n await workspaceClient.statementExecution.getStatementResultChunkN(\n { statement_id: statementId, chunk_index: next },\n this._createContext(signal),\n );\n const chunkLinks = chunk.external_links ?? [];\n if (chunkLinks.length === 0) break;\n links.push(...chunkLinks);\n next = this._nextChunkIndex(chunkLinks);\n }\n return links;\n }\n\n /** The `next_chunk_index` advertised by a chunk's links, if any. */\n private _nextChunkIndex(links: sql.ExternalLink[]): number | undefined {\n for (const link of links) {\n if (link.next_chunk_index != null) return link.next_chunk_index;\n }\n return undefined;\n }\n\n /**\n * A closure that re-mints a single chunk's pre-signed link via\n * `getStatementResultChunkN`, bound to the caller's workspace client +\n * statement id. Created here (in the caller's identity context) so the\n * streamer — which runs outside that context — can refresh an expired link\n * for `.obo.sql` statements without a cross-identity `getStatement`.\n */\n private _makeChunkLinkRefresher(\n workspaceClient: WorkspaceClient,\n statementId: string,\n ): RefreshChunkLink {\n return async (chunkIndex, signal) => {\n const chunk =\n await workspaceClient.statementExecution.getStatementResultChunkN(\n { statement_id: statementId, chunk_index: chunkIndex },\n this._createContext(signal),\n );\n return chunk.external_links?.find((l) => l.chunk_index === chunkIndex);\n };\n }\n\n /**\n * Stream already-resolved EXTERNAL_LINKS chunks as raw Arrow IPC bytes, one\n * chunk in memory at a time. Takes the `external_links` the caller already\n * has from `executeStatement` (see {@link updateWithArrowStatus}), so there\n * is no extra `getStatement` round-trip and no execution-context mismatch —\n * the pre-signed URLs need no auth to download.\n */\n streamExternalLinks(\n chunks: sql.ExternalLink[],\n signal?: AbortSignal,\n refresh?: RefreshChunkLink,\n ): AsyncGenerator<Uint8Array, void, unknown> {\n return this.arrowProcessor.streamChunks(chunks, signal, refresh);\n }\n\n /**\n * Fetch just the real column names for a completed statement from its result\n * manifest — no result data. Backs the `/columns/:statementId` fallback the\n * client uses when a very wide schema's names exceed the response-header\n * size limit. Stateless: re-derives from the warehouse, no server cache.\n */\n async getColumnNames(\n workspaceClient: WorkspaceClient,\n jobId: string,\n signal?: AbortSignal,\n ): Promise<string[] | undefined> {\n const response = await workspaceClient.statementExecution.getStatement(\n { statement_id: jobId },\n this._createContext(signal),\n );\n return arrowColumnNames(response);\n }\n\n /**\n * Normalize an error caught during statement execution/polling and rethrow.\n * Abort and already-structured `AppKitError`s pass through untouched;\n * anything else is wrapped as an `ExecutionError`, preserving the SDK's\n * structured `ApiError.errorCode` (e.g. \"INVALID_PARAMETER_VALUE\",\n * \"BAD_REQUEST\") so callers can branch on a stable identifier rather than\n * substring-matching the message.\n */\n private _rethrowStatementError(error: unknown): never {\n if (error instanceof Error && error.name === \"AbortError\") {\n throw error;\n }\n if (error instanceof AppKitError) {\n throw error;\n }\n const sdkErrorCode =\n error && typeof error === \"object\" && \"errorCode\" in error\n ? (error as { errorCode?: unknown }).errorCode\n : undefined;\n throw ExecutionError.statementFailed(\n error instanceof Error ? error.message : String(error),\n typeof sdkErrorCode === \"string\" ? sdkErrorCode : undefined,\n );\n }\n\n // create context for cancellation token\n private _createContext(signal?: AbortSignal) {\n return new Context({\n cancellationToken: {\n isCancellationRequested: signal?.aborted ?? false,\n onCancellationRequested: (cb: () => void) => {\n signal?.addEventListener(\"abort\", cb, { once: true });\n },\n },\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/sql-warehouse/client.ts"],"sourcesContent":["import type { TelemetryOptions } from \"shared\";\nimport {\n AppKitError,\n ConfigurationError,\n ConnectionError,\n ExecutionError,\n ValidationError,\n} from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport {\n ArrowStreamProcessor,\n type RefreshChunkLink,\n} from \"../../stream/arrow-stream-processor\";\nimport type { TelemetryProvider } from \"../../telemetry\";\nimport {\n type Counter,\n type Histogram,\n type Span,\n SpanKind,\n SpanStatusCode,\n TelemetryManager,\n} from \"../../telemetry\";\nimport {\n Context,\n type sql,\n type WorkspaceClient,\n} from \"../../workspace-client\";\nimport { buildEmptyArrowIPCBase64 } from \"./arrow-schema\";\nimport { executeStatementDefaults } from \"./defaults\";\nimport { WarehousePollBackoff } from \"./warehouse-poll-backoff\";\nimport { WarehouseStatusEmitter } from \"./warehouse-status-emitter\";\n\nconst logger = createLogger(\"connectors:sql-warehouse\");\n\n/**\n * Real column names from a statement's result manifest (positional\n * `column_N` fallback for any blank ones). Databricks encodes ARROW_STREAM\n * schemas positionally (`col_0`, …); these names let consumers relabel an\n * Arrow result to match the JSON path. Returns `undefined` when the manifest\n * carries no columns.\n */\nfunction arrowColumnNames(\n response: sql.StatementResponse,\n): string[] | undefined {\n const cols = response.manifest?.schema?.columns;\n if (!cols || cols.length === 0) return undefined;\n return cols.map((c, i) =>\n c.name && c.name.length > 0 ? c.name : `column_${i}`,\n );\n}\n\n/**\n * Maximum size for inline Arrow IPC attachments (25 MiB decoded — the\n * Databricks Statement Execution API hard cap on INLINE responses).\n *\n * The analytics route streams the decoded attachment straight back on the\n * query response body; this cap bounds the single in-memory copy. Larger\n * results fall through to `disposition: \"EXTERNAL_LINKS\"`, which streams\n * chunk-by-chunk and is not bound by this limit.\n */\nconst MAX_INLINE_ATTACHMENT_BYTES = 25 * 1024 * 1024;\n\n/**\n * Safety cap on how many additional EXTERNAL_LINKS chunks\n * {@link SQLWarehouseConnector._resolveAllExternalLinks} will follow when the\n * manifest omits `total_chunk_count`. High enough to cover any real result;\n * only bounds a misbehaving warehouse with a cyclic `next_chunk_index`.\n */\nconst MAX_EXTERNAL_CHUNK_FOLLOWS = 10_000;\n\ninterface SQLWarehouseConfig {\n timeout?: number;\n telemetry?: TelemetryOptions;\n}\n\n/**\n * Default ceiling for how long {@link SQLWarehouseConnector.ensureWarehouseRunning}\n * will wait for a warehouse to reach the RUNNING state before giving up.\n *\n * Five minutes covers a cold-start of a classic warehouse on most workspaces;\n * serverless typically reaches RUNNING within ~30s.\n */\nexport const DEFAULT_WAREHOUSE_STARTUP_TIMEOUT_MS = 5 * 60 * 1000;\n\n/**\n * Window during which a recent `RUNNING` observation lets subsequent calls\n * to {@link SQLWarehouseConnector.ensureWarehouseRunning} short-circuit\n * without making any SDK calls. Sized to roughly the upper bound of how\n * long Databricks keeps a warehouse \"stickily\" available between requests\n * — past that, we re-verify.\n */\nconst WAREHOUSE_RUNNING_CACHE_TTL_MS = 30_000;\n\n/**\n * A single observation of the warehouse state, emitted by\n * {@link SQLWarehouseConnector.ensureWarehouseRunning} so callers can stream\n * progress to clients (e.g. over SSE).\n *\n * Note: `health.summary` from the SDK is intentionally NOT exposed on this\n * type. It's free-form operator-oriented diagnostic text (cluster IDs,\n * capacity-failure reasons, internal RPC errors) that must not reach end\n * users. The raw value stays in the OTel span attributes for server-side\n * debugging only.\n */\nexport interface WarehouseStatusUpdate {\n /** Current state from the SDK (RUNNING | STARTING | STOPPED | STOPPING | DELETED | DELETING). */\n state: sql.State;\n /** Milliseconds elapsed since `ensureWarehouseRunning` was called. */\n elapsedMs: number;\n /** 1-based attempt counter — useful for tests and telemetry. */\n attempt: number;\n}\n\n/** Options for {@link SQLWarehouseConnector.ensureWarehouseRunning}. */\ninterface EnsureWarehouseRunningOptions {\n /** Invoked every time the warehouse state changes during the wait. */\n onStatus: (update: WarehouseStatusUpdate) => void;\n /** Aborts the wait. The connector treats abort as `ExecutionError.canceled()`. */\n signal?: AbortSignal;\n /** Hard ceiling on the total wait. Defaults to {@link DEFAULT_WAREHOUSE_STARTUP_TIMEOUT_MS}. */\n timeoutMs?: number;\n /**\n * When `true` (default), a `STOPPED` warehouse is auto-started.\n * When `false`, `STOPPED` surfaces as `ConfigurationError` immediately —\n * useful for cost-controlled deployments that don't want any caller to\n * trigger billable warehouse starts.\n */\n autoStart?: boolean;\n /** @internal Fired once when `warehouses.start` is issued for this poll. */\n onWarehouseStartIssued?: () => void;\n}\n\n/** In-flight warehouse readiness work shared across concurrent callers. */\ninterface WarehouseReadinessInFlight {\n promise: Promise<void>;\n refCount: number;\n sharedController: AbortController;\n subscribers: Set<(update: WarehouseStatusUpdate) => void>;\n /** Last emitted update; replayed to late joiners. */\n lastUpdate: WarehouseStatusUpdate | null;\n /** `true` once this poll has called `warehouses.start`. */\n warehouseStartIssued: boolean;\n}\n\nexport class SQLWarehouseConnector {\n private readonly name = \"sql-warehouse\";\n\n private config: SQLWarehouseConfig;\n\n // Lazy-initialized: only created when Arrow format is used\n private _arrowProcessor: ArrowStreamProcessor | null = null;\n\n /**\n * Per-warehouse cache of the last RUNNING observation timestamp. Used by\n * {@link ensureWarehouseRunning} to short-circuit warm-path callers; see\n * {@link WAREHOUSE_RUNNING_CACHE_TTL_MS}.\n */\n private _recentlyRunning = new Map<string, number>();\n /** Per-warehouse readiness singleflight — one poll loop per cold start. */\n private _readinessInFlight = new Map<string, WarehouseReadinessInFlight>();\n // telemetry\n private readonly telemetry: TelemetryProvider;\n private readonly telemetryMetrics: {\n queryCount: Counter;\n queryDuration: Histogram;\n };\n\n constructor(config: SQLWarehouseConfig) {\n this.config = config;\n\n this.telemetry = TelemetryManager.getProvider(\n this.name,\n this.config.telemetry,\n );\n this.telemetryMetrics = {\n queryCount: this.telemetry.getMeter().createCounter(\"query.count\", {\n description: \"Total number of queries executed\",\n unit: \"1\",\n }),\n queryDuration: this.telemetry\n .getMeter()\n .createHistogram(\"query.duration\", {\n description: \"Duration of queries executed\",\n unit: \"ms\",\n }),\n };\n }\n\n /**\n * Lazily initializes and returns the ArrowStreamProcessor.\n * Only created on first Arrow format query to avoid unnecessary allocation.\n */\n private get arrowProcessor(): ArrowStreamProcessor {\n if (!this._arrowProcessor) {\n this._arrowProcessor = new ArrowStreamProcessor({\n timeout: this.config.timeout || executeStatementDefaults.timeout,\n retries: ArrowStreamProcessor.DEFAULT_RETRIES,\n });\n }\n return this._arrowProcessor;\n }\n\n async executeStatement(\n workspaceClient: WorkspaceClient,\n input: sql.ExecuteStatementRequest,\n signal?: AbortSignal,\n ) {\n const startTime = Date.now();\n let success = false;\n\n // if signal is aborted, throw an error\n if (signal?.aborted) {\n throw ExecutionError.canceled();\n }\n\n return this.telemetry.startActiveSpan(\n \"sql.query\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"db.warehouse_id\": input.warehouse_id || \"\",\n \"db.catalog\": input.catalog ?? \"\",\n \"db.schema\": input.schema ?? \"\",\n \"db.statement\": input.statement?.substring(0, 500) || \"\",\n \"db.has_parameters\": !!input.parameters,\n },\n },\n async (span: Span) => {\n let abortHandler: (() => void) | undefined;\n let isAborted = false;\n\n if (signal) {\n abortHandler = () => {\n // abort span if not recording\n if (!span.isRecording()) return;\n isAborted = true;\n span.setAttribute(\"cancelled\", true);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: \"Query cancelled by client\",\n });\n span.end();\n };\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n try {\n // validate required fields\n if (!input.statement) {\n throw ValidationError.missingField(\"statement\");\n }\n\n if (!input.warehouse_id) {\n throw ValidationError.missingField(\"warehouse_id\");\n }\n\n const body: sql.ExecuteStatementRequest = {\n statement: input.statement,\n parameters: input.parameters,\n warehouse_id: input.warehouse_id,\n catalog: input.catalog,\n schema: input.schema,\n wait_timeout:\n input.wait_timeout || executeStatementDefaults.wait_timeout,\n disposition:\n input.disposition || executeStatementDefaults.disposition,\n format: input.format || executeStatementDefaults.format,\n byte_limit: input.byte_limit,\n row_limit: input.row_limit,\n on_wait_timeout:\n input.on_wait_timeout || executeStatementDefaults.on_wait_timeout,\n };\n\n span.addEvent(\"statement.submitting\", {\n \"db.warehouse_id\": input.warehouse_id,\n });\n\n const response =\n await workspaceClient.statementExecution.executeStatement(\n body,\n this._createContext(signal),\n );\n\n if (!response) {\n throw ConnectionError.apiFailure(\"SQL Warehouse\");\n }\n const status = response.status;\n const statementId = response.statement_id as string;\n\n span.setAttribute(\"db.statement_id\", statementId);\n span.addEvent(\"statement.submitted\", {\n \"db.statement_id\": response.statement_id,\n \"db.status\": status?.state,\n });\n\n let result:\n | sql.StatementResponse\n | { result: { statement_id: string; status: sql.StatementStatus } };\n\n switch (status?.state) {\n case \"RUNNING\":\n case \"PENDING\":\n span.addEvent(\"statement.polling_started\", {\n \"db.status\": response.status?.state,\n });\n result = await this._pollForStatementResult(\n workspaceClient,\n statementId,\n this.config.timeout,\n signal,\n );\n break;\n case \"SUCCEEDED\":\n result = await this._transformDataArray(\n response,\n workspaceClient,\n signal,\n );\n break;\n case \"FAILED\":\n throw ExecutionError.statementFailed(\n status.error?.message,\n status.error?.error_code,\n );\n case \"CANCELED\":\n throw ExecutionError.canceled();\n case \"CLOSED\":\n throw ExecutionError.resultsClosed();\n default:\n throw ExecutionError.unknownState(\n String(status?.state ?? \"unknown\"),\n );\n }\n\n const resultData = result.result as any;\n const rowCount =\n resultData?.data?.length ?? resultData?.data_array?.length ?? 0;\n\n if (rowCount > 0) {\n span.setAttribute(\"db.result.row_count\", rowCount);\n }\n\n const duration = Date.now() - startTime;\n logger.event()?.setContext(\"sql-warehouse\", {\n warehouse_id: input.warehouse_id,\n rows_returned: rowCount,\n query_duration_ms: duration,\n });\n\n success = true;\n // only set success status if not aborted\n if (!isAborted) {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n return result;\n } catch (error) {\n // only record error if not already handled by abort\n if (!isAborted) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n\n logger.error(\n \"Statement execution failed: %s\",\n error instanceof Error ? error.message : String(error),\n );\n }\n\n this._rethrowStatementError(error);\n } finally {\n // remove abort handler\n if (abortHandler && signal) {\n signal.removeEventListener(\"abort\", abortHandler);\n }\n\n const duration = Date.now() - startTime;\n\n // end span if not already ended by abort handler\n if (!isAborted) {\n span.end();\n }\n\n const attributes = {\n \"db.warehouse_id\": input.warehouse_id,\n \"db.catalog\": input.catalog ?? \"\",\n \"db.schema\": input.schema ?? \"\",\n \"db.statement\": input.statement?.substring(0, 500) || \"\",\n success: success.toString(),\n };\n\n this.telemetryMetrics.queryCount.add(1, attributes);\n this.telemetryMetrics.queryDuration.record(duration, attributes);\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n /**\n * Wait until the SQL warehouse is in the `RUNNING` state, auto-starting it\n * if currently `STOPPED`. Emits a {@link WarehouseStatusUpdate} whenever\n * the observed state changes, so callers can surface progress (e.g. over\n * SSE) instead of letting the UI freeze on a cold warehouse. Equal\n * successive observations are de-duplicated.\n *\n * Fast path: if this connector recently observed the warehouse RUNNING\n * (within {@link WAREHOUSE_RUNNING_CACHE_TTL_MS}), the call returns\n * immediately without any SDK round-trip or status emission. This keeps\n * cache-hit analytics requests off the Databricks control plane.\n *\n * Concurrent callers for the same `warehouseId` share a single in-flight\n * poll loop (singleflight): only the owner issues `get`/`start`; joiners\n * receive broadcast `onStatus` updates and share the result promise.\n * When every waiter disconnects before `warehouses.start`, the shared poll\n * is aborted on the next microtask if still unclaimed (kills true orphans\n * without a fixed grace window). After `start` is issued, the poll runs to\n * completion even with no waiters.\n *\n * Behaviour by initial state:\n * - `RUNNING`: emits one update, caches the observation, returns.\n * - `STOPPED`: emits a synthetic `STARTING` update, calls\n * `workspaceClient.warehouses.start`, then polls until `RUNNING`.\n * When `autoStart: false`, throws `ConfigurationError` instead.\n * - `STARTING` / `STOPPING`: polls until `RUNNING`.\n * - `DELETED` / `DELETING`: throws `ConfigurationError.resourceNotFound`.\n *\n * Aborts and timeouts surface as `ExecutionError`. The poll loop uses\n * exponential backoff with ±15% jitter (1s → 30s cap) and runs inside a\n * `sql.warehouseReady` telemetry span.\n */\n async ensureWarehouseRunning(\n workspaceClient: WorkspaceClient,\n warehouseId: string,\n opts: EnsureWarehouseRunningOptions,\n ): Promise<void> {\n const {\n onStatus,\n signal,\n timeoutMs = DEFAULT_WAREHOUSE_STARTUP_TIMEOUT_MS,\n autoStart = true,\n } = opts;\n\n if (signal?.aborted) throw ExecutionError.canceled();\n if (!warehouseId) throw ValidationError.missingField(\"warehouse_id\");\n if (this._isRecentlyRunning(warehouseId)) return;\n\n const existing = this._readinessInFlight.get(warehouseId);\n if (existing && !existing.sharedController.signal.aborted) {\n return this._waitOnReadiness(existing, onStatus, signal, true);\n }\n\n const entry = this._spawnReadinessInFlight(workspaceClient, warehouseId, {\n timeoutMs,\n autoStart,\n });\n this._readinessInFlight.set(warehouseId, entry);\n return this._waitOnReadiness(entry, onStatus, signal, false);\n }\n\n private _waitOnReadiness(\n entry: WarehouseReadinessInFlight,\n onStatus: (update: WarehouseStatusUpdate) => void,\n signal: AbortSignal | undefined,\n join: boolean,\n ): Promise<void> {\n if (signal?.aborted) throw ExecutionError.canceled();\n if (join) entry.refCount++;\n entry.subscribers.add(onStatus);\n if (entry.lastUpdate) onStatus(entry.lastUpdate);\n return this._joinWarehouseReadiness(entry, signal);\n }\n\n private _spawnReadinessInFlight(\n workspaceClient: WorkspaceClient,\n warehouseId: string,\n opts: { timeoutMs: number; autoStart: boolean },\n ): WarehouseReadinessInFlight {\n const sharedController = new AbortController();\n const entry: WarehouseReadinessInFlight = {\n refCount: 1,\n sharedController,\n subscribers: new Set(),\n lastUpdate: null,\n warehouseStartIssued: false,\n promise: Promise.resolve(),\n };\n\n entry.promise = this.telemetry\n .startActiveSpan(\n \"sql.warehouseReady\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"db.warehouse_id\": warehouseId,\n \"db.warehouse.startup_timeout_ms\": opts.timeoutMs,\n },\n },\n async (span: Span) => {\n try {\n await this._pollUntilWarehouseRunning(\n workspaceClient,\n warehouseId,\n {\n onStatus: (update) => this._broadcastReadiness(entry, update),\n signal: sharedController.signal,\n timeoutMs: opts.timeoutMs,\n autoStart: opts.autoStart,\n onWarehouseStartIssued: () => {\n entry.warehouseStartIssued = true;\n },\n },\n span,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n } catch (error) {\n this._throwSanitizedReadinessError(error, warehouseId, span);\n } finally {\n span.end();\n }\n },\n { name: this.name, includePrefix: true },\n )\n .finally(() => {\n if (this._readinessInFlight.get(warehouseId) === entry) {\n this._readinessInFlight.delete(warehouseId);\n }\n });\n\n entry.promise.catch(() => {});\n return entry;\n }\n\n private _broadcastReadiness(\n entry: WarehouseReadinessInFlight,\n update: WarehouseStatusUpdate,\n ): void {\n entry.lastUpdate = update;\n for (const subscriber of entry.subscribers) {\n try {\n subscriber(update);\n } catch {\n // One consumer must not block updates to the rest.\n }\n }\n }\n\n /** Ref-counted await; caller abort rejects locally. */\n private _joinWarehouseReadiness(\n entry: WarehouseReadinessInFlight,\n callerSignal?: AbortSignal,\n ): Promise<void> {\n if (!callerSignal) return entry.promise;\n\n return new Promise<void>((resolve, reject) => {\n let done = false;\n const finish = (outcome: \"ok\" | \"abort\" | \"err\", error?: unknown) => {\n if (done) return;\n done = true;\n callerSignal.removeEventListener(\"abort\", onAbort);\n if (outcome === \"ok\") resolve();\n else if (outcome === \"abort\") reject(ExecutionError.canceled());\n else reject(error);\n };\n const onAbort = () => {\n this._releaseReadinessWaiter(entry);\n finish(\"abort\");\n };\n callerSignal.addEventListener(\"abort\", onAbort, { once: true });\n entry.promise.then(\n () => finish(\"ok\"),\n (error) => finish(\"err\", error),\n );\n });\n }\n\n private _releaseReadinessWaiter(entry: WarehouseReadinessInFlight): void {\n if (entry.refCount > 0) entry.refCount--;\n if (entry.refCount > 0 || entry.sharedController.signal.aborted) return;\n\n if (entry.warehouseStartIssued) {\n // Start already issued — finish warming for the process.\n return;\n }\n\n // Defer abort to the next microtask so a synchronous StrictMode\n // remount can rejoin before the shared poll is cancelled.\n queueMicrotask(() => {\n if (entry.refCount <= 0 && !entry.sharedController.signal.aborted) {\n entry.sharedController.abort(\"all warehouse readiness waiters aborted\");\n }\n });\n }\n\n private async _pollUntilWarehouseRunning(\n workspaceClient: WorkspaceClient,\n warehouseId: string,\n opts: EnsureWarehouseRunningOptions,\n span: Span,\n ): Promise<void> {\n const {\n onStatus,\n signal,\n timeoutMs = DEFAULT_WAREHOUSE_STARTUP_TIMEOUT_MS,\n autoStart = true,\n onWarehouseStartIssued,\n } = opts;\n const startTime = Date.now();\n const emitter = new WarehouseStatusEmitter(span, startTime, onStatus);\n let didStart = false;\n const backoff = new WarehousePollBackoff();\n\n while (true) {\n if (signal?.aborted) throw ExecutionError.canceled();\n if (Date.now() - startTime > timeoutMs) {\n throw ExecutionError.statementFailed(\n `SQL warehouse did not reach RUNNING within ${timeoutMs}ms`,\n );\n }\n\n const info = await workspaceClient.warehouses.get(\n { id: warehouseId },\n this._createContext(signal),\n );\n const state = info?.state;\n const summary = info?.health?.summary;\n\n switch (state) {\n case \"RUNNING\":\n emitter.emit(state, summary);\n this._recentlyRunning.set(warehouseId, Date.now());\n span.setAttribute(\"db.warehouse.attempts\", emitter.attempt);\n return;\n case \"DELETED\":\n case \"DELETING\":\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n `The configured SQL warehouse is ${state}. Update DATABRICKS_WAREHOUSE_ID to point at an active warehouse.`,\n );\n case \"STOPPED\":\n if (!autoStart) {\n throw new ConfigurationError(\n \"The configured SQL warehouse is STOPPED and analytics auto-start is disabled. Start the warehouse manually or set analytics.autoStartWarehouse=true.\",\n );\n }\n if (!didStart) {\n emitter.emit(\"STARTING\", summary);\n onWarehouseStartIssued?.();\n await workspaceClient.warehouses.start(\n { id: warehouseId },\n this._createContext(signal),\n );\n didStart = true;\n } else {\n emitter.emit(state, summary);\n }\n break;\n case \"STARTING\":\n case \"STOPPING\":\n emitter.emit(state, summary);\n break;\n default:\n throw ExecutionError.unknownState(String(state ?? \"unknown\"));\n }\n\n const sleepMs = backoff.next();\n if (Date.now() + sleepMs - startTime >= timeoutMs) {\n throw ExecutionError.statementFailed(\n `SQL warehouse did not reach RUNNING within ${timeoutMs}ms`,\n );\n }\n await this._sleepRespectingAbort(sleepMs, signal);\n }\n }\n\n /**\n * `true` if this connector observed `warehouseId` in the RUNNING state\n * within the recent-cache TTL.\n */\n private _isRecentlyRunning(warehouseId: string): boolean {\n const observedAt = this._recentlyRunning.get(warehouseId);\n return (\n observedAt !== undefined &&\n Date.now() - observedAt < WAREHOUSE_RUNNING_CACHE_TTL_MS\n );\n }\n\n /**\n * Final landing for any error thrown by the readiness poll loop. Records\n * the original on the span and (at debug level) on the logger, then\n * rethrows either the structured AppKitError unchanged or a curated\n * ExecutionError — never the raw SDK message, which can contain operator\n * internals.\n *\n * The `logger.debug` covers environments where OTel isn't configured\n * (common in dev) so the raw error isn't lost just because no span\n * exporter is hooked up.\n */\n private _throwSanitizedReadinessError(\n error: unknown,\n warehouseId: string,\n span: Span,\n ): never {\n if (error instanceof AppKitError) {\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.code });\n throw error;\n }\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n );\n logger.debug(\n \"Warehouse readiness check raw error for %s: %O\",\n warehouseId,\n error,\n );\n const wrapped = ExecutionError.statementFailed(\n \"Warehouse readiness check failed\",\n );\n span.setStatus({ code: SpanStatusCode.ERROR, message: wrapped.code });\n throw wrapped;\n }\n\n /**\n * Sleep for `ms` milliseconds, but resolve early (and reject with\n * `ExecutionError.canceled()`) if `signal` aborts mid-sleep. Used by the\n * warehouse readiness loop so that client disconnects don't keep the loop\n * polling for the full interval.\n */\n private _sleepRespectingAbort(\n ms: number,\n signal?: AbortSignal,\n ): Promise<void> {\n if (signal?.aborted) return Promise.reject(ExecutionError.canceled());\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(ExecutionError.canceled());\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n }\n\n private async _pollForStatementResult(\n workspaceClient: WorkspaceClient,\n statementId: string,\n timeout = executeStatementDefaults.timeout,\n signal?: AbortSignal,\n ) {\n return this.telemetry.startActiveSpan(\n \"sql.poll\",\n {\n attributes: {\n \"db.statement_id\": statementId,\n \"db.polling.timeout\": timeout,\n },\n },\n async (span: Span) => {\n try {\n const startTime = Date.now();\n let delay = 1000;\n const maxDelayBetweenPolls = 5000; // max 5 seconds between polls\n let pollCount = 0;\n\n while (true) {\n pollCount++;\n span.setAttribute(\"db.polling.current_attempt\", pollCount);\n\n // check if timeout exceeded\n const elapsedTime = Date.now() - startTime;\n if (elapsedTime > timeout) {\n const error = ExecutionError.statementFailed(\n `Polling timeout exceeded after ${timeout}ms (elapsed: ${elapsedTime}ms)`,\n );\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n }\n\n if (signal?.aborted) {\n const error = ExecutionError.canceled();\n span.recordException(error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n }\n\n span.addEvent(\"polling.attempt\", {\n \"poll.attempt\": pollCount,\n \"poll.delay_ms\": delay,\n \"poll.elapsed_ms\": elapsedTime,\n });\n\n const response =\n await workspaceClient.statementExecution.getStatement(\n {\n statement_id: statementId,\n },\n this._createContext(signal),\n );\n if (!response) {\n throw ConnectionError.apiFailure(\"SQL Warehouse\");\n }\n\n const status = response.status;\n\n span.addEvent(\"polling.status_check\", {\n \"db.status\": status?.state,\n \"poll.attempt\": pollCount,\n });\n\n switch (status?.state) {\n case \"PENDING\":\n case \"RUNNING\":\n // continue polling\n break;\n case \"SUCCEEDED\":\n span.setAttribute(\"db.polling.attempts\", pollCount);\n span.setAttribute(\"db.polling.total_duration_ms\", elapsedTime);\n span.addEvent(\"polling.completed\", {\n \"poll.attempts\": pollCount,\n \"poll.duration_ms\": elapsedTime,\n });\n span.setStatus({ code: SpanStatusCode.OK });\n return this._transformDataArray(\n response,\n workspaceClient,\n signal,\n );\n case \"FAILED\":\n throw ExecutionError.statementFailed(\n status.error?.message,\n status.error?.error_code,\n );\n case \"CANCELED\":\n throw ExecutionError.canceled();\n case \"CLOSED\":\n throw ExecutionError.resultsClosed();\n default:\n throw ExecutionError.unknownState(\n String(status?.state ?? \"unknown\"),\n );\n }\n\n // continue polling after delay\n await new Promise((resolve) => setTimeout(resolve, delay));\n delay = Math.min(delay * 2, maxDelayBetweenPolls);\n }\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n\n // error logging is handled by executeStatement's catch block (gated on isAborted)\n this._rethrowStatementError(error);\n } finally {\n span.end();\n }\n },\n { name: this.name, includePrefix: true },\n );\n }\n\n private async _transformDataArray(\n response: sql.StatementResponse,\n workspaceClient: WorkspaceClient,\n signal?: AbortSignal,\n ) {\n if (response.manifest?.format === \"ARROW_STREAM\") {\n const result = response.result as\n | (sql.ResultData & { attachment?: string })\n | undefined;\n\n // Inline Arrow: pass the base64 IPC attachment through unmodified so\n // the analytics route can stream it to the client, where the existing\n // ArrowClient infrastructure decodes it into a Table. Validate size\n // here to fail fast on runaway payloads.\n if (result?.attachment) {\n return this._validateArrowAttachment(response, result.attachment);\n }\n\n // External links: data fetched separately via statement_id. An empty\n // array is a zero-row result (some warehouses emit `external_links: []`\n // rather than omitting it) — it must NOT go down the streaming path\n // (`streamChunks([])` rejects), so fall through to synthesize an empty\n // Arrow table below.\n if (result?.external_links && result.external_links.length > 0) {\n return this.updateWithArrowStatus(response, workspaceClient, signal);\n }\n\n // Empty result with a known schema: synthesize a zero-row Arrow IPC\n // attachment so the client always receives an Arrow Table for\n // ARROW_STREAM, regardless of whether the warehouse returned data.\n // Note: an empty array (`data_array: []`) is truthy, so length-check\n // explicitly — otherwise zero-row responses fall through to the JSON\n // row transform below and return `[]` JSON rows instead of an Arrow\n // table.\n const hasNoRows =\n !result?.data_array ||\n (Array.isArray(result.data_array) && result.data_array.length === 0);\n if (hasNoRows && response.manifest?.schema?.columns) {\n const synthesized = buildEmptyArrowIPCBase64(\n response.manifest.schema.columns,\n );\n return {\n ...response,\n result: { ...(result ?? {}), attachment: synthesized },\n };\n }\n\n // Inline data_array under ARROW_STREAM (rare): fall through to the\n // row transform below. The hook will receive `type: \"result\"` rows;\n // callers asking for ARROW_STREAM should not hit this path with\n // current Databricks warehouses.\n }\n\n if (!response.result?.data_array || !response.manifest?.schema?.columns) {\n return response;\n }\n\n const columns = response.manifest.schema.columns;\n\n const transformedData = response.result.data_array.map((row) => {\n const obj: Record<string, unknown> = {};\n row.forEach((value, index) => {\n const column = columns[index];\n const columnName = column?.name || `column_${index}`;\n\n // attempt to parse JSON strings for string columns\n if (\n column?.type_name === \"STRING\" &&\n typeof value === \"string\" &&\n value &&\n (value[0] === \"{\" || value[0] === \"[\")\n ) {\n try {\n obj[columnName] = JSON.parse(value);\n } catch {\n // if parsing fails, keep as string\n obj[columnName] = value;\n }\n } else {\n obj[columnName] = value;\n }\n });\n return obj;\n });\n\n // remove data_array\n const { data_array: _data_array, ...restResult } = response.result;\n return {\n ...response,\n result: {\n ...restResult,\n data: transformedData,\n },\n };\n }\n\n /**\n * Validate a base64 Arrow IPC attachment (INLINE ARROW_STREAM) and attach\n * the real column names from the result manifest.\n *\n * Databricks warehouses encode the Arrow schema positionally (`col_0`, …);\n * the real, aliased names live only in `manifest.schema.columns`. Rather\n * than decode/re-encode the payload server-side, we carry the names on the\n * result (`columnNames`) so the route can hand them to the client in a\n * response header and the client relabels the decoded Table — the one\n * mechanism used for both INLINE and EXTERNAL_LINKS.\n */\n private _validateArrowAttachment(\n response: sql.StatementResponse,\n attachment: string,\n ) {\n // Cap the size to protect against unbounded inline payloads from\n // misbehaving warehouses. `MAX_INLINE_ATTACHMENT_BYTES` tracks the API's\n // ~25 MiB inline hard cap and bounds memory if a server returns a runaway\n // response.\n //\n // Strip whitespace (rare but legal in base64) and account for trailing\n // `=` padding so the byte count is exact rather than an upper bound.\n const stripped = attachment.replace(/\\s+/g, \"\");\n const padding = stripped.endsWith(\"==\")\n ? 2\n : stripped.endsWith(\"=\")\n ? 1\n : 0;\n const decodedSize = Math.floor((stripped.length * 3) / 4) - padding;\n if (decodedSize > MAX_INLINE_ATTACHMENT_BYTES) {\n throw ExecutionError.statementFailed(\n `Inline Arrow attachment exceeds maximum size (${decodedSize} > ${MAX_INLINE_ATTACHMENT_BYTES} bytes)`,\n );\n }\n\n const columnNames = arrowColumnNames(response);\n if (columnNames) {\n return {\n ...response,\n result: {\n ...(response.result as sql.ResultData & {\n attachment?: string;\n columnNames?: string[];\n }),\n // `statement_id` is a top-level field, not on `ResultData` — carry it\n // onto the result (as the EXTERNAL_LINKS path does) so the route can\n // advertise it in `X-Appkit-Arrow-Columns-Ref` for wide inline schemas.\n statement_id: response.statement_id,\n columnNames,\n },\n };\n }\n\n return response;\n }\n\n private async updateWithArrowStatus(\n response: sql.StatementResponse,\n workspaceClient: WorkspaceClient,\n signal?: AbortSignal,\n ): Promise<{\n result: {\n statement_id: string;\n status: sql.StatementStatus;\n columnNames?: string[];\n external_links?: sql.ExternalLink[];\n refreshChunkLink?: RefreshChunkLink;\n };\n }> {\n const statementId = response.statement_id as string;\n return {\n result: {\n statement_id: statementId,\n status: {\n state: response.status?.state,\n error: response.status?.error,\n } as sql.StatementStatus,\n columnNames: arrowColumnNames(response),\n // Resolve the pre-signed links for EVERY chunk in the caller's own\n // execution context. Streaming these directly (see\n // {@link streamExternalLinks}) avoids a second `getStatement` under\n // the ambient service-principal context — which would fail for\n // `.obo.sql` (user-owned) statements.\n external_links: await this._resolveAllExternalLinks(\n workspaceClient,\n statementId,\n response,\n signal,\n ),\n // Bound to this caller's identity so the streamer can re-mint an\n // expired chunk link mid-download (links live <= 15 min; a large/slow\n // result can outlast the earliest ones).\n refreshChunkLink: this._makeChunkLinkRefresher(\n workspaceClient,\n statementId,\n ),\n },\n };\n }\n\n /**\n * Resolve pre-signed links for EVERY chunk of an EXTERNAL_LINKS result.\n *\n * The execute/getStatement response carries only the first chunk's links\n * (each link, except the last, exposes `next_chunk_index`); the remaining\n * chunks are fetched with `getStatementResultChunkN`. Runs in the caller's\n * identity context (user creds for `.obo.sql`), so there is no cross-identity\n * fetch. Only the tiny link metadata is resolved eagerly — the bytes still\n * stream one chunk at a time downstream. Without this a multi-chunk result\n * would be silently truncated to its first chunk.\n */\n private async _resolveAllExternalLinks(\n workspaceClient: WorkspaceClient,\n statementId: string,\n response: sql.StatementResponse,\n signal?: AbortSignal,\n ): Promise<sql.ExternalLink[] | undefined> {\n const first = response.result?.external_links;\n if (!first || first.length === 0) return first;\n\n const links: sql.ExternalLink[] = [...first];\n // Bound the follow loop so a warehouse returning a cyclic/never-ending\n // `next_chunk_index` can't spin forever. The manifest's chunk count is the\n // natural bound; fall back to a generous safety cap if it's absent (real\n // results still terminate earlier when `next_chunk_index` becomes null) so\n // a missing count doesn't silently truncate a genuine multi-chunk result.\n const maxFetches =\n response.manifest?.total_chunk_count ?? MAX_EXTERNAL_CHUNK_FOLLOWS;\n let next = this._nextChunkIndex(first);\n for (let fetches = 0; next != null && fetches < maxFetches; fetches++) {\n if (signal?.aborted) throw ExecutionError.canceled();\n const chunk =\n await workspaceClient.statementExecution.getStatementResultChunkN(\n { statement_id: statementId, chunk_index: next },\n this._createContext(signal),\n );\n const chunkLinks = chunk.external_links ?? [];\n if (chunkLinks.length === 0) break;\n links.push(...chunkLinks);\n next = this._nextChunkIndex(chunkLinks);\n }\n return links;\n }\n\n /** The `next_chunk_index` advertised by a chunk's links, if any. */\n private _nextChunkIndex(links: sql.ExternalLink[]): number | undefined {\n for (const link of links) {\n if (link.next_chunk_index != null) return link.next_chunk_index;\n }\n return undefined;\n }\n\n /**\n * A closure that re-mints a single chunk's pre-signed link via\n * `getStatementResultChunkN`, bound to the caller's workspace client +\n * statement id. Created here (in the caller's identity context) so the\n * streamer — which runs outside that context — can refresh an expired link\n * for `.obo.sql` statements without a cross-identity `getStatement`.\n */\n private _makeChunkLinkRefresher(\n workspaceClient: WorkspaceClient,\n statementId: string,\n ): RefreshChunkLink {\n return async (chunkIndex, signal) => {\n const chunk =\n await workspaceClient.statementExecution.getStatementResultChunkN(\n { statement_id: statementId, chunk_index: chunkIndex },\n this._createContext(signal),\n );\n return chunk.external_links?.find((l) => l.chunk_index === chunkIndex);\n };\n }\n\n /**\n * Stream already-resolved EXTERNAL_LINKS chunks as raw Arrow IPC bytes, one\n * chunk in memory at a time. Takes the `external_links` the caller already\n * has from `executeStatement` (see {@link updateWithArrowStatus}), so there\n * is no extra `getStatement` round-trip and no execution-context mismatch —\n * the pre-signed URLs need no auth to download.\n */\n streamExternalLinks(\n chunks: sql.ExternalLink[],\n signal?: AbortSignal,\n refresh?: RefreshChunkLink,\n ): AsyncGenerator<Uint8Array, void, unknown> {\n return this.arrowProcessor.streamChunks(chunks, signal, refresh);\n }\n\n /**\n * Fetch just the real column names for a completed statement from its result\n * manifest — no result data. Backs the `/columns/:statementId` fallback the\n * client uses when a very wide schema's names exceed the response-header\n * size limit. Stateless: re-derives from the warehouse, no server cache.\n */\n async getColumnNames(\n workspaceClient: WorkspaceClient,\n jobId: string,\n signal?: AbortSignal,\n ): Promise<string[] | undefined> {\n const response = await workspaceClient.statementExecution.getStatement(\n { statement_id: jobId },\n this._createContext(signal),\n );\n return arrowColumnNames(response);\n }\n\n /**\n * Normalize an error caught during statement execution/polling and rethrow.\n * Abort and already-structured `AppKitError`s pass through untouched;\n * anything else is wrapped as an `ExecutionError`, preserving the SDK's\n * structured `ApiError.errorCode` (e.g. \"INVALID_PARAMETER_VALUE\",\n * \"BAD_REQUEST\") so callers can branch on a stable identifier rather than\n * substring-matching the message.\n */\n private _rethrowStatementError(error: unknown): never {\n if (error instanceof Error && error.name === \"AbortError\") {\n throw error;\n }\n if (error instanceof AppKitError) {\n throw error;\n }\n const sdkErrorCode =\n error && typeof error === \"object\" && \"errorCode\" in error\n ? (error as { errorCode?: unknown }).errorCode\n : undefined;\n throw ExecutionError.statementFailed(\n error instanceof Error ? error.message : String(error),\n typeof sdkErrorCode === \"string\" ? sdkErrorCode : undefined,\n );\n }\n\n // create context for cancellation token\n private _createContext(signal?: AbortSignal) {\n return new Context({\n cancellationToken: {\n isCancellationRequested: signal?.aborted ?? false,\n onCancellationRequested: (cb: () => void) => {\n signal?.addEventListener(\"abort\", cb, { once: true });\n },\n },\n });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/connectors/sql-warehouse/defaults.ts"],"sourcesContent":["import type { sql } from \"@databricks/sdk-experimental\";\n\ninterface ExecuteStatementDefaults {\n wait_timeout: string;\n disposition: sql.ExecuteStatementRequest[\"disposition\"];\n format: sql.ExecuteStatementRequest[\"format\"];\n on_wait_timeout: sql.ExecuteStatementRequest[\"on_wait_timeout\"];\n timeout: number;\n}\n\n// @TODO: Make these configurable globally and validate right values\nexport const executeStatementDefaults: ExecuteStatementDefaults = {\n wait_timeout: \"30s\",\n disposition: \"INLINE\",\n format: \"JSON_ARRAY\",\n on_wait_timeout: \"CONTINUE\",\n timeout: 60000,\n};\n"],"mappings":";AAWA,MAAa,2BAAqD;CAChE,cAAc;CACd,aAAa;CACb,QAAQ;CACR,iBAAiB;CACjB,SAAS;CACV"}
1
+ {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/connectors/sql-warehouse/defaults.ts"],"sourcesContent":["import type { sql } from \"../../workspace-client\";\n\ninterface ExecuteStatementDefaults {\n wait_timeout: string;\n disposition: sql.ExecuteStatementRequest[\"disposition\"];\n format: sql.ExecuteStatementRequest[\"format\"];\n on_wait_timeout: sql.ExecuteStatementRequest[\"on_wait_timeout\"];\n timeout: number;\n}\n\n// @TODO: Make these configurable globally and validate right values\nexport const executeStatementDefaults: ExecuteStatementDefaults = {\n wait_timeout: \"30s\",\n disposition: \"INLINE\",\n format: \"JSON_ARRAY\",\n on_wait_timeout: \"CONTINUE\",\n timeout: 60000,\n};\n"],"mappings":";AAWA,MAAa,2BAAqD;CAChE,cAAc;CACd,aAAa;CACb,QAAQ;CACR,iBAAiB;CACjB,SAAS;CACV"}
@@ -1 +1 @@
1
- {"version":3,"file":"warehouse-status-emitter.js","names":[],"sources":["../../../src/connectors/sql-warehouse/warehouse-status-emitter.ts"],"sourcesContent":["import type { sql } from \"@databricks/sdk-experimental\";\nimport type { Span } from \"../../telemetry\";\nimport type { WarehouseStatusUpdate } from \"./client\";\n\n/**\n * Tracks state-change emissions for the warehouse readiness loop. Records\n * every poll on the OTel span and forwards real state changes (with de-dup)\n * to the route's `onStatus` callback. The raw SDK `health.summary` is\n * recorded on the span only — never on the wire.\n */\nexport class WarehouseStatusEmitter {\n attempt = 0;\n private lastEmittedState: sql.State | null = null;\n\n constructor(\n private readonly span: Span,\n private readonly startTime: number,\n private readonly onStatus: (update: WarehouseStatusUpdate) => void,\n ) {}\n\n emit(state: sql.State, summary: string | undefined): void {\n this.attempt += 1;\n this.span.addEvent(\"warehouse.status\", {\n \"db.warehouse.state\": state,\n \"db.warehouse.attempt\": this.attempt,\n \"db.warehouse.elapsed_ms\": Date.now() - this.startTime,\n ...(summary ? { \"db.warehouse.summary\": summary } : {}),\n });\n if (state === this.lastEmittedState) return;\n this.lastEmittedState = state;\n try {\n this.onStatus({\n state,\n elapsedMs: Date.now() - this.startTime,\n attempt: this.attempt,\n });\n } catch (err) {\n this.span.addEvent(\"warehouse.onStatus.error\", {\n \"exception.message\": err instanceof Error ? err.message : String(err),\n });\n }\n }\n}\n"],"mappings":";;;;;;;AAUA,IAAa,yBAAb,MAAoC;CAClC,UAAU;CACV,AAAQ,mBAAqC;CAE7C,YACE,AAAiB,MACjB,AAAiB,WACjB,AAAiB,UACjB;EAHiB;EACA;EACA;;CAGnB,KAAK,OAAkB,SAAmC;AACxD,OAAK,WAAW;AAChB,OAAK,KAAK,SAAS,oBAAoB;GACrC,sBAAsB;GACtB,wBAAwB,KAAK;GAC7B,2BAA2B,KAAK,KAAK,GAAG,KAAK;GAC7C,GAAI,UAAU,EAAE,wBAAwB,SAAS,GAAG,EAAE;GACvD,CAAC;AACF,MAAI,UAAU,KAAK,iBAAkB;AACrC,OAAK,mBAAmB;AACxB,MAAI;AACF,QAAK,SAAS;IACZ;IACA,WAAW,KAAK,KAAK,GAAG,KAAK;IAC7B,SAAS,KAAK;IACf,CAAC;WACK,KAAK;AACZ,QAAK,KAAK,SAAS,4BAA4B,EAC7C,qBAAqB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACtE,CAAC"}
1
+ {"version":3,"file":"warehouse-status-emitter.js","names":[],"sources":["../../../src/connectors/sql-warehouse/warehouse-status-emitter.ts"],"sourcesContent":["import type { Span } from \"../../telemetry\";\nimport type { sql } from \"../../workspace-client\";\nimport type { WarehouseStatusUpdate } from \"./client\";\n\n/**\n * Tracks state-change emissions for the warehouse readiness loop. Records\n * every poll on the OTel span and forwards real state changes (with de-dup)\n * to the route's `onStatus` callback. The raw SDK `health.summary` is\n * recorded on the span only — never on the wire.\n */\nexport class WarehouseStatusEmitter {\n attempt = 0;\n private lastEmittedState: sql.State | null = null;\n\n constructor(\n private readonly span: Span,\n private readonly startTime: number,\n private readonly onStatus: (update: WarehouseStatusUpdate) => void,\n ) {}\n\n emit(state: sql.State, summary: string | undefined): void {\n this.attempt += 1;\n this.span.addEvent(\"warehouse.status\", {\n \"db.warehouse.state\": state,\n \"db.warehouse.attempt\": this.attempt,\n \"db.warehouse.elapsed_ms\": Date.now() - this.startTime,\n ...(summary ? { \"db.warehouse.summary\": summary } : {}),\n });\n if (state === this.lastEmittedState) return;\n this.lastEmittedState = state;\n try {\n this.onStatus({\n state,\n elapsedMs: Date.now() - this.startTime,\n attempt: this.attempt,\n });\n } catch (err) {\n this.span.addEvent(\"warehouse.onStatus.error\", {\n \"exception.message\": err instanceof Error ? err.message : String(err),\n });\n }\n }\n}\n"],"mappings":";;;;;;;AAUA,IAAa,yBAAb,MAAoC;CAClC,UAAU;CACV,AAAQ,mBAAqC;CAE7C,YACE,AAAiB,MACjB,AAAiB,WACjB,AAAiB,UACjB;EAHiB;EACA;EACA;;CAGnB,KAAK,OAAkB,SAAmC;AACxD,OAAK,WAAW;AAChB,OAAK,KAAK,SAAS,oBAAoB;GACrC,sBAAsB;GACtB,wBAAwB,KAAK;GAC7B,2BAA2B,KAAK,KAAK,GAAG,KAAK;GAC7C,GAAI,UAAU,EAAE,wBAAwB,SAAS,GAAG,EAAE;GACvD,CAAC;AACF,MAAI,UAAU,KAAK,iBAAkB;AACrC,OAAK,mBAAmB;AACxB,MAAI;AACF,QAAK,SAAS;IACZ;IACA,WAAW,KAAK,KAAK,GAAG,KAAK;IAC7B,SAAS,KAAK;IACf,CAAC;WACK,KAAK;AACZ,QAAK,KAAK,SAAS,4BAA4B,EAC7C,qBAAqB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACtE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/vector-search/client.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { TelemetryProvider } from \"../../telemetry\";\nimport {\n type Span,\n SpanKind,\n SpanStatusCode,\n TelemetryManager,\n} from \"../../telemetry\";\nimport type {\n VectorSearchConnectorConfig,\n VsNextPageParams,\n VsQueryParams,\n VsRawResponse,\n} from \"./types\";\n\nconst logger = createLogger(\"connectors:vector-search\");\n\nexport class VectorSearchConnector {\n private readonly telemetry: TelemetryProvider;\n\n constructor(config: VectorSearchConnectorConfig = {}) {\n this.telemetry = TelemetryManager.getProvider(\n \"vector-search\",\n config.telemetry,\n );\n }\n\n async query(\n workspaceClient: WorkspaceClient,\n params: VsQueryParams,\n signal?: AbortSignal,\n ): Promise<VsRawResponse> {\n if (signal?.aborted) {\n throw new Error(\"Query cancelled before execution\");\n }\n\n const body: Record<string, unknown> = {\n columns: params.columns,\n num_results: params.numResults,\n query_type: params.queryType.toUpperCase(),\n debug_level: 1,\n };\n\n if (params.queryText) body.query_text = params.queryText;\n if (params.queryVector) body.query_vector = params.queryVector;\n if (params.filters && Object.keys(params.filters).length > 0) {\n body.filters = params.filters;\n }\n if (params.reranker) {\n body.reranker = {\n model: \"databricks_reranker\",\n parameters: { columns_to_rerank: params.reranker.columnsToRerank },\n };\n }\n\n logger.debug(\n \"Querying VS index %s (type=%s, num_results=%d)\",\n params.indexName,\n params.queryType,\n params.numResults,\n );\n\n return this.telemetry.startActiveSpan(\n \"vector-search.query\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"vs.index_name\": params.indexName,\n \"vs.query_type\": params.queryType,\n \"vs.num_results\": params.numResults,\n \"vs.has_filters\": !!(\n params.filters && Object.keys(params.filters).length > 0\n ),\n \"vs.has_reranker\": !!params.reranker,\n },\n },\n async (span: Span) => {\n const startTime = Date.now();\n try {\n const response = (await workspaceClient.apiClient.request({\n method: \"POST\",\n path: `/api/2.0/vector-search/indexes/${params.indexName}/query`,\n payload: body,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n raw: false,\n query: {},\n })) as VsRawResponse;\n\n const duration = Date.now() - startTime;\n span.setAttribute(\"vs.result_count\", response.result.row_count);\n span.setAttribute(\n \"vs.query_time_ms\",\n response.debug_info?.response_time ?? 0,\n );\n span.setAttribute(\"vs.duration_ms\", duration);\n span.setStatus({ code: SpanStatusCode.OK });\n\n logger.event()?.setContext(\"vector-search\", {\n index_name: params.indexName,\n query_type: params.queryType,\n result_count: response.result.row_count,\n query_time_ms: response.debug_info?.response_time ?? 0,\n duration_ms: duration,\n });\n\n return response;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n },\n { name: \"vector-search\", includePrefix: true },\n );\n }\n\n async queryNextPage(\n workspaceClient: WorkspaceClient,\n params: VsNextPageParams,\n signal?: AbortSignal,\n ): Promise<VsRawResponse> {\n if (signal?.aborted) {\n throw new Error(\"Query cancelled before execution\");\n }\n\n logger.debug(\n \"Fetching next page for index %s (endpoint=%s)\",\n params.indexName,\n params.endpointName,\n );\n\n return this.telemetry.startActiveSpan(\n \"vector-search.queryNextPage\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"vs.index_name\": params.indexName,\n \"vs.endpoint_name\": params.endpointName,\n },\n },\n async (span: Span) => {\n try {\n const response = (await workspaceClient.apiClient.request({\n method: \"POST\",\n path: `/api/2.0/vector-search/indexes/${params.indexName}/query-next-page`,\n payload: {\n endpoint_name: params.endpointName,\n page_token: params.pageToken,\n },\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n raw: false,\n query: {},\n })) as VsRawResponse;\n\n span.setAttribute(\"vs.result_count\", response.result.row_count);\n span.setStatus({ code: SpanStatusCode.OK });\n return response;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n },\n { name: \"vector-search\", includePrefix: true },\n );\n }\n}\n"],"mappings":";;;;AAgBA,MAAM,SAAS,aAAa,2BAA2B"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/vector-search/client.ts"],"sourcesContent":["import { createLogger } from \"../../logging/logger\";\nimport type { TelemetryProvider } from \"../../telemetry\";\nimport {\n type Span,\n SpanKind,\n SpanStatusCode,\n TelemetryManager,\n} from \"../../telemetry\";\nimport type { WorkspaceClient } from \"../../workspace-client\";\nimport type {\n VectorSearchConnectorConfig,\n VsNextPageParams,\n VsQueryParams,\n VsRawResponse,\n} from \"./types\";\n\nconst logger = createLogger(\"connectors:vector-search\");\n\nexport class VectorSearchConnector {\n private readonly telemetry: TelemetryProvider;\n\n constructor(config: VectorSearchConnectorConfig = {}) {\n this.telemetry = TelemetryManager.getProvider(\n \"vector-search\",\n config.telemetry,\n );\n }\n\n async query(\n workspaceClient: WorkspaceClient,\n params: VsQueryParams,\n signal?: AbortSignal,\n ): Promise<VsRawResponse> {\n if (signal?.aborted) {\n throw new Error(\"Query cancelled before execution\");\n }\n\n const body: Record<string, unknown> = {\n columns: params.columns,\n num_results: params.numResults,\n query_type: params.queryType.toUpperCase(),\n debug_level: 1,\n };\n\n if (params.queryText) body.query_text = params.queryText;\n if (params.queryVector) body.query_vector = params.queryVector;\n if (params.filters && Object.keys(params.filters).length > 0) {\n body.filters = params.filters;\n }\n if (params.reranker) {\n body.reranker = {\n model: \"databricks_reranker\",\n parameters: { columns_to_rerank: params.reranker.columnsToRerank },\n };\n }\n\n logger.debug(\n \"Querying VS index %s (type=%s, num_results=%d)\",\n params.indexName,\n params.queryType,\n params.numResults,\n );\n\n return this.telemetry.startActiveSpan(\n \"vector-search.query\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"vs.index_name\": params.indexName,\n \"vs.query_type\": params.queryType,\n \"vs.num_results\": params.numResults,\n \"vs.has_filters\": !!(\n params.filters && Object.keys(params.filters).length > 0\n ),\n \"vs.has_reranker\": !!params.reranker,\n },\n },\n async (span: Span) => {\n const startTime = Date.now();\n try {\n const response = (await workspaceClient.apiClient.request({\n method: \"POST\",\n path: `/api/2.0/vector-search/indexes/${params.indexName}/query`,\n payload: body,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n raw: false,\n query: {},\n })) as VsRawResponse;\n\n const duration = Date.now() - startTime;\n span.setAttribute(\"vs.result_count\", response.result.row_count);\n span.setAttribute(\n \"vs.query_time_ms\",\n response.debug_info?.response_time ?? 0,\n );\n span.setAttribute(\"vs.duration_ms\", duration);\n span.setStatus({ code: SpanStatusCode.OK });\n\n logger.event()?.setContext(\"vector-search\", {\n index_name: params.indexName,\n query_type: params.queryType,\n result_count: response.result.row_count,\n query_time_ms: response.debug_info?.response_time ?? 0,\n duration_ms: duration,\n });\n\n return response;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n },\n { name: \"vector-search\", includePrefix: true },\n );\n }\n\n async queryNextPage(\n workspaceClient: WorkspaceClient,\n params: VsNextPageParams,\n signal?: AbortSignal,\n ): Promise<VsRawResponse> {\n if (signal?.aborted) {\n throw new Error(\"Query cancelled before execution\");\n }\n\n logger.debug(\n \"Fetching next page for index %s (endpoint=%s)\",\n params.indexName,\n params.endpointName,\n );\n\n return this.telemetry.startActiveSpan(\n \"vector-search.queryNextPage\",\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"db.system\": \"databricks\",\n \"vs.index_name\": params.indexName,\n \"vs.endpoint_name\": params.endpointName,\n },\n },\n async (span: Span) => {\n try {\n const response = (await workspaceClient.apiClient.request({\n method: \"POST\",\n path: `/api/2.0/vector-search/indexes/${params.indexName}/query-next-page`,\n payload: {\n endpoint_name: params.endpointName,\n page_token: params.pageToken,\n },\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n raw: false,\n query: {},\n })) as VsRawResponse;\n\n span.setAttribute(\"vs.result_count\", response.result.row_count);\n span.setStatus({ code: SpanStatusCode.OK });\n return response;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n },\n { name: \"vector-search\", includePrefix: true },\n );\n }\n}\n"],"mappings":";;;;AAgBA,MAAM,SAAS,aAAa,2BAA2B"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-options.js","names":["productVersion","productName"],"sources":["../../src/context/client-options.ts"],"sourcesContent":["import type { ClientOptions } from \"@databricks/sdk-experimental\";\nimport { coerce } from \"semver\";\nimport {\n name as productName,\n version as productVersion,\n} from \"../../package.json\";\n\nconst normalizedVersion = (coerce(productVersion)?.version ??\n productVersion) as ClientOptions[\"productVersion\"];\n\n/**\n * SDK client options that stamp every `apiClient.request()` with an AppKit\n * User-Agent (`@databricks/appkit/<version>`), so outbound Databricks traffic\n * is attributable to AppKit. Use this for every `WorkspaceClient` AppKit\n * constructs at runtime.\n */\nexport function getClientOptions(): ClientOptions {\n const isDev = process.env.NODE_ENV === \"development\";\n\n return {\n product: productName,\n productVersion: normalizedVersion,\n ...(isDev && { userAgentExtra: { mode: \"dev\" } }),\n };\n}\n\n/**\n * Product/version User-Agent string matching the SDK stamp, for raw `fetch`\n * call sites that bypass the SDK's `apiClient` and have no client to derive it\n * from (e.g. the MCP connector).\n */\nexport const APPKIT_USER_AGENT = `${productName}/${normalizedVersion}`;\n"],"mappings":";;;;AAOA,MAAM,oBAAqB,OAAOA,QAAe,EAAE,WACjDA;;;;;;;AAQF,SAAgB,mBAAkC;AAGhD,QAAO;EACL,SAASC;EACT,gBAAgB;EAChB,GALY,QAAQ,IAAI,aAAa,iBAKxB,EAAE,gBAAgB,EAAE,MAAM,OAAO,EAAE;EACjD;;;;;;;AAQH,MAAa,oBAAoB,GAAGA,KAAY,GAAG"}
1
+ {"version":3,"file":"client-options.js","names":["productVersion","productName"],"sources":["../../src/context/client-options.ts"],"sourcesContent":["import { coerce } from \"semver\";\nimport {\n name as productName,\n version as productVersion,\n} from \"../../package.json\";\nimport type { ClientOptions } from \"../workspace-client\";\n\nconst normalizedVersion = (coerce(productVersion)?.version ??\n productVersion) as ClientOptions[\"productVersion\"];\n\n/**\n * SDK client options that stamp every `apiClient.request()` with an AppKit\n * User-Agent (`@databricks/appkit/<version>`), so outbound Databricks traffic\n * is attributable to AppKit. Use this for every `WorkspaceClient` AppKit\n * constructs at runtime.\n */\nexport function getClientOptions(): ClientOptions {\n const isDev = process.env.NODE_ENV === \"development\";\n\n return {\n product: productName,\n productVersion: normalizedVersion,\n ...(isDev && { userAgentExtra: { mode: \"dev\" } }),\n };\n}\n\n/**\n * Product/version User-Agent string matching the SDK stamp, for raw `fetch`\n * call sites that bypass the SDK's `apiClient` and have no client to derive it\n * from (e.g. the MCP connector).\n */\nexport const APPKIT_USER_AGENT = `${productName}/${normalizedVersion}`;\n"],"mappings":";;;;AAOA,MAAM,oBAAqB,OAAOA,QAAe,EAAE,WACjDA;;;;;;;AAQF,SAAgB,mBAAkC;AAGhD,QAAO;EACL,SAASC;EACT,gBAAgB;EAChB,GALY,QAAQ,IAAI,aAAa,iBAKxB,EAAE,gBAAgB,EAAE,MAAM,OAAO,EAAE;EACjD;;;;;;;AAQH,MAAa,oBAAoB,GAAGA,KAAY,GAAG"}
@@ -1,5 +1,5 @@
1
1
  import { ExecutionContext } from "./user-context.js";
2
- import "@databricks/sdk-experimental";
2
+ import "../index.js";
3
3
 
4
4
  //#region src/context/execution-context.d.ts
5
5
  /**
@@ -1,5 +1,6 @@
1
+ import { WorkspaceClient } from "../workspace-client/types.js";
2
+ import "../workspace-client/index.js";
1
3
  import "./user-context.js";
2
- import { WorkspaceClient } from "@databricks/sdk-experimental";
3
4
 
4
5
  //#region src/context/service-context.d.ts
5
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"service-context.d.ts","names":[],"sources":["../../src/context/service-context.ts"],"mappings":";;;;;;AAmBA;;UAAiB,mBAAA;EAEP;EAAR,MAAA,EAAQ,eAAA;EAMK;EAJb,aAAA;EAIoB;EAFpB,WAAA,GAAc,OAAA;EAJN;EAMR,WAAA,EAAa,OAAA;AAAA"}
1
+ {"version":3,"file":"service-context.d.ts","names":[],"sources":["../../src/context/service-context.ts"],"mappings":";;;;;;;;AAoBA;UAAiB,mBAAA;;EAEf,MAAA,EAAQ,eAAA;EAIM;EAFd,aAAA;EAIoB;EAFpB,WAAA,GAAc,OAAA;EAJd;EAMA,WAAA,EAAa,OAAA;AAAA"}
@@ -2,9 +2,11 @@ import { AuthenticationError } from "../errors/authentication.js";
2
2
  import { ConfigurationError } from "../errors/configuration.js";
3
3
  import { InitializationError } from "../errors/initialization.js";
4
4
  import "../errors/index.js";
5
+ import { ConfigError } from "../workspace-client/legacy.js";
6
+ import { createWorkspaceClient } from "../workspace-client/factory.js";
7
+ import "../workspace-client/index.js";
5
8
  import { getClientOptions } from "./client-options.js";
6
9
  import { createHash } from "node:crypto";
7
- import { ConfigError, WorkspaceClient } from "@databricks/sdk-experimental";
8
10
 
9
11
  //#region src/context/service-context.ts
10
12
  /**
@@ -60,11 +62,12 @@ var ServiceContext = class ServiceContext {
60
62
  if (!host) throw ConfigurationError.missingEnvVar("DATABRICKS_HOST");
61
63
  const serviceCtx = ServiceContext.get();
62
64
  return {
63
- client: new WorkspaceClient({
65
+ client: createWorkspaceClient({
64
66
  token,
65
67
  host,
66
- authType: "pat"
67
- }, getClientOptions()),
68
+ authType: "pat",
69
+ clientOptions: getClientOptions()
70
+ }),
68
71
  userId,
69
72
  userName,
70
73
  userEmail,
@@ -83,7 +86,7 @@ var ServiceContext = class ServiceContext {
83
86
  }
84
87
  static async createContext(options, client) {
85
88
  try {
86
- const wsClient = client ?? new WorkspaceClient({}, getClientOptions());
89
+ const wsClient = client ?? createWorkspaceClient({ clientOptions: getClientOptions() });
87
90
  const [resolvedWorkspaceId, currentUser, resolvedWarehouseId] = await Promise.all([
88
91
  ServiceContext.getWorkspaceId(wsClient),
89
92
  wsClient.currentUser.me(),
@@ -1 +1 @@
1
- {"version":3,"file":"service-context.js","names":[],"sources":["../../src/context/service-context.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n type ClientOptions,\n ConfigError,\n type sql,\n WorkspaceClient,\n} from \"@databricks/sdk-experimental\";\nimport {\n AuthenticationError,\n ConfigurationError,\n InitializationError,\n} from \"../errors\";\nimport { getClientOptions } from \"./client-options\";\nimport type { UserContext } from \"./user-context\";\n\n/**\n * Service context holds the service principal client and shared resources.\n * This is initialized once at app startup and shared across all requests.\n */\nexport interface ServiceContextState {\n /** WorkspaceClient authenticated as the service principal */\n client: WorkspaceClient;\n /** The service principal's user ID */\n serviceUserId: string;\n /** Promise that resolves to the warehouse ID (only present when a plugin requires `SQL_WAREHOUSE` resource) */\n warehouseId?: Promise<string>;\n /** Promise that resolves to the workspace ID */\n workspaceId: Promise<string>;\n}\n\n/**\n * ServiceContext is a singleton that manages the service principal's\n * WorkspaceClient and shared resources like warehouse/workspace IDs.\n *\n * It's initialized once at app startup and provides the foundation\n * for both service principal and user context execution.\n */\nexport class ServiceContext {\n private static instance: ServiceContextState | null = null;\n private static initPromise: Promise<ServiceContextState> | null = null;\n\n /**\n * Initialize the service context. Should be called once at app startup.\n * Safe to call multiple times - will return the same instance.\n *\n * @param options - Which shared resources to resolve (derived from plugin manifests).\n * @param client - Optional pre-configured WorkspaceClient to use instead\n * of creating one from environment credentials.\n */\n static async initialize(\n options?: { warehouseId?: boolean },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n if (ServiceContext.instance) {\n return ServiceContext.instance;\n }\n\n if (ServiceContext.initPromise) {\n return ServiceContext.initPromise;\n }\n\n ServiceContext.initPromise = ServiceContext.createContext(options, client);\n ServiceContext.instance = await ServiceContext.initPromise;\n return ServiceContext.instance;\n }\n\n /**\n * Get the initialized service context.\n * @throws Error if not initialized\n */\n static get(): ServiceContextState {\n if (!ServiceContext.instance) {\n throw InitializationError.notInitialized(\n \"ServiceContext\",\n \"Call ServiceContext.initialize() first\",\n );\n }\n return ServiceContext.instance;\n }\n\n /**\n * Check if the service context has been initialized.\n */\n static isInitialized(): boolean {\n return ServiceContext.instance !== null;\n }\n\n /**\n * Create a user context from request headers.\n *\n * @param token - The user's access token from x-forwarded-access-token header\n * @param userId - The user's ID from x-forwarded-user header\n * @param userName - Optional user name\n * @throws Error if token is not provided\n */\n static createUserContext(\n token: string,\n userId: string,\n userName?: string,\n userEmail?: string,\n ): UserContext {\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n const host = process.env.DATABRICKS_HOST;\n if (!host) {\n throw ConfigurationError.missingEnvVar(\"DATABRICKS_HOST\");\n }\n\n const serviceCtx = ServiceContext.get();\n\n // Create user client with the OAuth token from Databricks Apps\n // Note: We use authType: \"pat\" because the token is passed as a Bearer token\n // just like a PAT, even though it's technically an OAuth token\n const userClient = new WorkspaceClient(\n {\n token,\n host,\n authType: \"pat\",\n },\n getClientOptions(),\n );\n\n const tokenFingerprint = createHash(\"sha256\")\n .update(token)\n .digest(\"hex\")\n .slice(0, 16);\n\n return {\n client: userClient,\n userId,\n userName,\n userEmail,\n tokenFingerprint,\n warehouseId: serviceCtx.warehouseId,\n workspaceId: serviceCtx.workspaceId,\n isUserContext: true,\n };\n }\n\n /**\n * Get the client options for WorkspaceClient.\n * Exposed for testing purposes.\n */\n static getClientOptions(): ClientOptions {\n return getClientOptions();\n }\n\n private static async createContext(\n options?: { warehouseId?: boolean },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n try {\n const wsClient = client ?? new WorkspaceClient({}, getClientOptions());\n\n const [resolvedWorkspaceId, currentUser, resolvedWarehouseId] =\n await Promise.all([\n ServiceContext.getWorkspaceId(wsClient),\n wsClient.currentUser.me(),\n options?.warehouseId\n ? ServiceContext.getWarehouseId(wsClient)\n : Promise.resolve(undefined as string | undefined),\n ]);\n\n if (!currentUser.id) {\n throw ConfigurationError.resourceNotFound(\"Service user ID\");\n }\n\n const warehouseId =\n options?.warehouseId && resolvedWarehouseId !== undefined\n ? Promise.resolve(resolvedWarehouseId)\n : undefined;\n\n return {\n client: wsClient,\n serviceUserId: currentUser.id,\n warehouseId,\n workspaceId: Promise.resolve(resolvedWorkspaceId),\n };\n } catch (e) {\n if (e instanceof ConfigError) {\n throw ConfigurationError.databricksAuthenticationSetupFailed(\n e.baseMessage,\n { cause: e },\n );\n }\n throw e;\n }\n }\n\n private static async getWorkspaceId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WORKSPACE_ID) {\n return process.env.DATABRICKS_WORKSPACE_ID;\n }\n\n const response = (await client.apiClient.request({\n path: \"/api/2.0/preview/scim/v2/Me\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {},\n responseHeaders: [\"x-databricks-org-id\"],\n })) as { \"x-databricks-org-id\": string };\n\n if (!response[\"x-databricks-org-id\"]) {\n throw ConfigurationError.resourceNotFound(\"Workspace ID\");\n }\n\n return response[\"x-databricks-org-id\"];\n }\n\n private static async getWarehouseId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WAREHOUSE_ID) {\n return process.env.DATABRICKS_WAREHOUSE_ID;\n }\n\n const agenticMode =\n process.env.DATABRICKS_APPS_AGENTIC_MODE === \"true\" ||\n process.env.DATABRICKS_APPS_AGENTIC_MODE === \"1\";\n\n if (process.env.NODE_ENV === \"development\" && !agenticMode) {\n const response = (await client.apiClient.request({\n path: \"/api/2.0/sql/warehouses\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {\n skip_cannot_use: \"true\",\n },\n })) as { warehouses: sql.EndpointInfo[] };\n\n const priorities: Record<sql.State, number> = {\n RUNNING: 0,\n STOPPED: 1,\n STARTING: 2,\n STOPPING: 3,\n DELETED: 99,\n DELETING: 99,\n };\n\n const warehouses = (response.warehouses || []).sort((a, b) => {\n return (\n priorities[a.state as sql.State] - priorities[b.state as sql.State]\n );\n });\n\n if (response.warehouses.length === 0) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n const firstWarehouse = warehouses[0];\n if (\n firstWarehouse.state === \"DELETED\" ||\n firstWarehouse.state === \"DELETING\" ||\n !firstWarehouse.id\n ) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n return firstWarehouse.id;\n }\n\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n /**\n * Reset the service context. Only for testing purposes.\n */\n static reset(): void {\n ServiceContext.instance = null;\n ServiceContext.initPromise = null;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service-context.js","names":[],"sources":["../../src/context/service-context.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n AuthenticationError,\n ConfigurationError,\n InitializationError,\n} from \"../errors\";\nimport {\n type ClientOptions,\n ConfigError,\n createWorkspaceClient,\n type sql,\n type WorkspaceClient,\n} from \"../workspace-client\";\nimport { getClientOptions } from \"./client-options\";\nimport type { UserContext } from \"./user-context\";\n\n/**\n * Service context holds the service principal client and shared resources.\n * This is initialized once at app startup and shared across all requests.\n */\nexport interface ServiceContextState {\n /** WorkspaceClient authenticated as the service principal */\n client: WorkspaceClient;\n /** The service principal's user ID */\n serviceUserId: string;\n /** Promise that resolves to the warehouse ID (only present when a plugin requires `SQL_WAREHOUSE` resource) */\n warehouseId?: Promise<string>;\n /** Promise that resolves to the workspace ID */\n workspaceId: Promise<string>;\n}\n\n/**\n * ServiceContext is a singleton that manages the service principal's\n * WorkspaceClient and shared resources like warehouse/workspace IDs.\n *\n * It's initialized once at app startup and provides the foundation\n * for both service principal and user context execution.\n */\nexport class ServiceContext {\n private static instance: ServiceContextState | null = null;\n private static initPromise: Promise<ServiceContextState> | null = null;\n\n /**\n * Initialize the service context. Should be called once at app startup.\n * Safe to call multiple times - will return the same instance.\n *\n * @param options - Which shared resources to resolve (derived from plugin manifests).\n * @param client - Optional pre-configured WorkspaceClient to use instead\n * of creating one from environment credentials.\n */\n static async initialize(\n options?: { warehouseId?: boolean },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n if (ServiceContext.instance) {\n return ServiceContext.instance;\n }\n\n if (ServiceContext.initPromise) {\n return ServiceContext.initPromise;\n }\n\n ServiceContext.initPromise = ServiceContext.createContext(options, client);\n ServiceContext.instance = await ServiceContext.initPromise;\n return ServiceContext.instance;\n }\n\n /**\n * Get the initialized service context.\n * @throws Error if not initialized\n */\n static get(): ServiceContextState {\n if (!ServiceContext.instance) {\n throw InitializationError.notInitialized(\n \"ServiceContext\",\n \"Call ServiceContext.initialize() first\",\n );\n }\n return ServiceContext.instance;\n }\n\n /**\n * Check if the service context has been initialized.\n */\n static isInitialized(): boolean {\n return ServiceContext.instance !== null;\n }\n\n /**\n * Create a user context from request headers.\n *\n * @param token - The user's access token from x-forwarded-access-token header\n * @param userId - The user's ID from x-forwarded-user header\n * @param userName - Optional user name\n * @throws Error if token is not provided\n */\n static createUserContext(\n token: string,\n userId: string,\n userName?: string,\n userEmail?: string,\n ): UserContext {\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n const host = process.env.DATABRICKS_HOST;\n if (!host) {\n throw ConfigurationError.missingEnvVar(\"DATABRICKS_HOST\");\n }\n\n const serviceCtx = ServiceContext.get();\n\n // Create user client with the OAuth token from Databricks Apps\n // Note: We use authType: \"pat\" because the token is passed as a Bearer token\n // just like a PAT, even though it's technically an OAuth token\n const userClient = createWorkspaceClient({\n token,\n host,\n authType: \"pat\",\n clientOptions: getClientOptions(),\n });\n\n const tokenFingerprint = createHash(\"sha256\")\n .update(token)\n .digest(\"hex\")\n .slice(0, 16);\n\n return {\n client: userClient,\n userId,\n userName,\n userEmail,\n tokenFingerprint,\n warehouseId: serviceCtx.warehouseId,\n workspaceId: serviceCtx.workspaceId,\n isUserContext: true,\n };\n }\n\n /**\n * Get the client options for WorkspaceClient.\n * Exposed for testing purposes.\n */\n static getClientOptions(): ClientOptions {\n return getClientOptions();\n }\n\n private static async createContext(\n options?: { warehouseId?: boolean },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n try {\n const wsClient =\n client ?? createWorkspaceClient({ clientOptions: getClientOptions() });\n\n const [resolvedWorkspaceId, currentUser, resolvedWarehouseId] =\n await Promise.all([\n ServiceContext.getWorkspaceId(wsClient),\n wsClient.currentUser.me(),\n options?.warehouseId\n ? ServiceContext.getWarehouseId(wsClient)\n : Promise.resolve(undefined as string | undefined),\n ]);\n\n if (!currentUser.id) {\n throw ConfigurationError.resourceNotFound(\"Service user ID\");\n }\n\n const warehouseId =\n options?.warehouseId && resolvedWarehouseId !== undefined\n ? Promise.resolve(resolvedWarehouseId)\n : undefined;\n\n return {\n client: wsClient,\n serviceUserId: currentUser.id,\n warehouseId,\n workspaceId: Promise.resolve(resolvedWorkspaceId),\n };\n } catch (e) {\n if (e instanceof ConfigError) {\n throw ConfigurationError.databricksAuthenticationSetupFailed(\n e.baseMessage,\n { cause: e },\n );\n }\n throw e;\n }\n }\n\n private static async getWorkspaceId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WORKSPACE_ID) {\n return process.env.DATABRICKS_WORKSPACE_ID;\n }\n\n const response = (await client.apiClient.request({\n path: \"/api/2.0/preview/scim/v2/Me\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {},\n responseHeaders: [\"x-databricks-org-id\"],\n })) as { \"x-databricks-org-id\": string };\n\n if (!response[\"x-databricks-org-id\"]) {\n throw ConfigurationError.resourceNotFound(\"Workspace ID\");\n }\n\n return response[\"x-databricks-org-id\"];\n }\n\n private static async getWarehouseId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WAREHOUSE_ID) {\n return process.env.DATABRICKS_WAREHOUSE_ID;\n }\n\n const agenticMode =\n process.env.DATABRICKS_APPS_AGENTIC_MODE === \"true\" ||\n process.env.DATABRICKS_APPS_AGENTIC_MODE === \"1\";\n\n if (process.env.NODE_ENV === \"development\" && !agenticMode) {\n const response = (await client.apiClient.request({\n path: \"/api/2.0/sql/warehouses\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {\n skip_cannot_use: \"true\",\n },\n })) as { warehouses: sql.EndpointInfo[] };\n\n const priorities: Record<sql.State, number> = {\n RUNNING: 0,\n STOPPED: 1,\n STARTING: 2,\n STOPPING: 3,\n DELETED: 99,\n DELETING: 99,\n };\n\n const warehouses = (response.warehouses || []).sort((a, b) => {\n return (\n priorities[a.state as sql.State] - priorities[b.state as sql.State]\n );\n });\n\n if (response.warehouses.length === 0) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n const firstWarehouse = warehouses[0];\n if (\n firstWarehouse.state === \"DELETED\" ||\n firstWarehouse.state === \"DELETING\" ||\n !firstWarehouse.id\n ) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n return firstWarehouse.id;\n }\n\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n /**\n * Reset the service context. Only for testing purposes.\n */\n static reset(): void {\n ServiceContext.instance = null;\n ServiceContext.initPromise = null;\n }\n}\n"],"mappings":"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"}
@@ -1,9 +1,10 @@
1
1
  import { PluginConstructor, PluginData, PluginMap } from "../shared/src/plugin.js";
2
2
  import { CacheConfig } from "../shared/src/cache.js";
3
3
  import "../shared/src/index.js";
4
+ import { WorkspaceClient } from "../workspace-client/types.js";
5
+ import "../workspace-client/index.js";
4
6
  import { TelemetryConfig } from "../telemetry/types.js";
5
7
  import "../telemetry/index.js";
6
- import { WorkspaceClient } from "@databricks/sdk-experimental";
7
8
 
8
9
  //#region src/core/appkit.d.ts
9
10
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"appkit.d.ts","names":[],"sources":["../../src/core/appkit.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqXsB,SAAA,WACV,UAAA,CAAW,iBAAA,qBAAA,CAErB,MAAA;EACE,OAAA,GAAU,CAAA;EACV,SAAA,GAAY,eAAA;EACZ,KAAA,GAAQ,WAAA;EACR,MAAA,GAAS,eAAA;EACT,cAAA,IAAkB,MAAA,EAAQ,SAAA,CAAU,CAAA,aAAc,OAAA;EAClD,wBAAA;AAAA,IAED,OAAA,CAAQ,SAAA,CAAU,CAAA"}
1
+ {"version":3,"file":"appkit.d.ts","names":[],"sources":["../../src/core/appkit.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqXsB,SAAA,WACV,UAAA,CAAW,iBAAA,qBAAA,CAErB,MAAA;EACE,OAAA,GAAU,CAAA;EACV,SAAA,GAAY,eAAA;EACZ,KAAA,GAAQ,WAAA;EACR,MAAA,GAAS,eAAA;EACT,cAAA,IAAkB,MAAA,EAAQ,SAAA,CAAU,CAAA,aAAc,OAAA;EAClD,wBAAA;AAAA,IAED,OAAA,CAAQ,SAAA,CAAU,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"appkit.js","names":["#context","#pluginInstances","#setupPromises","productVersion"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type {\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n PluginMap,\n} from \"shared\";\nimport { version as productVersion } from \"../../package.json\";\nimport { CacheManager } from \"../cache\";\nimport { ServiceContext } from \"../context\";\nimport {\n isInternalTelemetryEnabled,\n TelemetryReporter,\n} from \"../internal-telemetry\";\nimport { createLogger } from \"../logging/logger\";\nimport { isPlainObject } from \"../plugin/plugin\";\nimport { uiVariants } from \"../plugins/ui-variants\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\nimport { LifecycleManager } from \"./lifecycle-manager\";\nimport { isToolProvider, PluginContext } from \"./plugin-context\";\n\nconst logger = createLogger(\"appkit\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n #context: PluginContext;\n\n private constructor(config: { plugins: TPlugins }) {\n const { plugins, ...globalConfig } = config;\n\n this.#context = new PluginContext();\n\n const pluginEntries = Object.entries(plugins);\n\n const corePlugins = pluginEntries.filter(([_, p]) => {\n return (p?.plugin?.phase ?? \"normal\") === \"core\";\n });\n const normalPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"normal\",\n );\n const deferredPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"deferred\",\n );\n\n for (const [name, pluginData] of corePlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of normalPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of deferredPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n }\n\n private createAndRegisterPlugin<T extends PluginConstructor>(\n config: Omit<{ plugins: TPlugins }, \"plugins\">,\n name: string,\n pluginData: OptionalConfigPluginDef<T>,\n extraData?: Record<string, unknown>,\n ) {\n const { plugin: Plugin, config: pluginConfig } = pluginData;\n const baseConfig = {\n ...config,\n ...Plugin.DEFAULT_CONFIG,\n ...pluginConfig,\n name,\n ...extraData,\n };\n const pluginInstance = new Plugin(baseConfig);\n\n if (typeof pluginInstance.attachContext === \"function\") {\n pluginInstance.attachContext({\n context: this.#context,\n telemetryConfig: baseConfig.telemetry,\n });\n }\n\n this.#pluginInstances[name] = pluginInstance;\n\n this.#context.registerPlugin(name, pluginInstance);\n if (isToolProvider(pluginInstance)) {\n this.#context.registerToolProvider(name, pluginInstance);\n }\n\n this.#setupPromises.push(pluginInstance.setup());\n\n const self = this;\n\n Object.defineProperty(this, name, {\n get() {\n const plugin = self.#pluginInstances[name];\n return self.wrapWithAsUser(plugin);\n },\n enumerable: true,\n });\n }\n\n /**\n * Binds all function properties in an exports object to the given context.\n * Recurses into plain objects to handle nested APIs (e.g., volume APIs).\n */\n private bindExportMethods(\n exports: Record<string, unknown>,\n context: BasePlugin,\n ) {\n for (const key in exports) {\n if (!Object.hasOwn(exports, key)) continue;\n const val = exports[key];\n if (typeof val === \"function\") {\n exports[key] = (val as (...args: unknown[]) => unknown).bind(context);\n } else if (isPlainObject(val)) {\n this.bindExportMethods(val as Record<string, unknown>, context);\n }\n }\n }\n\n /**\n * Wraps a plugin's exports with an `asUser` method that returns\n * a user-scoped version of the exports.\n *\n * When `exports()` returns a callable (function), it is returned as-is\n * since the plugin manages its own `asUser` per-call (e.g. files plugin).\n * When it returns a plain object, the standard `asUser` wrapper is added.\n *\n * The OBO-side wrapping lives inside `Plugin.asUser` — calling\n * `plugin.asUser(req).exports()` returns exports whose functions already\n * run inside the user's AsyncLocalStorage scope. AppKit only adapts the\n * shape; it does not own the user-context concept.\n */\n private wrapWithAsUser<T extends BasePlugin>(plugin: T) {\n // If plugin doesn't implement exports(), return empty object\n const pluginExports = plugin.exports?.() ?? {};\n\n // If exports is a function, the plugin manages its own asUser pattern\n if (typeof pluginExports === \"function\") {\n return pluginExports;\n }\n\n const objExports = pluginExports as Record<string, unknown>;\n this.bindExportMethods(objExports, plugin);\n\n // If plugin doesn't support asUser (no asUser method), return exports as-is\n if (typeof (plugin as any).asUser !== \"function\") {\n return objExports;\n }\n\n return {\n ...objExports,\n /**\n * Execute operations using the user's identity from the request.\n * Returns user-scoped exports where all methods execute with the\n * user's Databricks credentials instead of the service principal.\n */\n asUser: (req: import(\"express\").Request) =>\n (plugin as any).asUser(req).exports() as Record<string, unknown>,\n };\n }\n\n static async _createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n ): Promise<PluginMap<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const withDefaults = AppKit.withDefaultPlugins(config.plugins as T);\n const rawPlugins = AppKit.filterDevOnlyPlugins(withDefaults);\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n\n // Derive ServiceContext needs from what manifests declared\n const needsWarehouse = registry\n .getRequired()\n .some((r) => r.type === ResourceType.SQL_WAREHOUSE);\n await ServiceContext.initialize(\n { warehouseId: needsWarehouse },\n config?.client,\n );\n\n // Validate env vars\n registry.enforceValidation();\n\n const preparedPlugins = AppKit.preparePlugins(rawPlugins);\n const mergedConfig = {\n plugins: preparedPlugins,\n };\n\n const instance = new AppKit(mergedConfig);\n\n await Promise.all(instance.#setupPromises);\n await instance.#context.emitLifecycle(\"setup:complete\");\n\n const handle = instance as unknown as PluginMap<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(handle);\n logger.debug(\"onPluginsReady hook completed\");\n }\n\n if (isInternalTelemetryEnabled(config)) {\n AppKit.bootstrapInternalTelemetry();\n }\n\n const serverPlugin = instance.#pluginInstances.server;\n if (serverPlugin && typeof (serverPlugin as any).start === \"function\") {\n await (serverPlugin as any).start();\n }\n\n // Core owns graceful shutdown: install the signal handlers once every\n // plugin has started. Applies uniformly whether or not a server plugin\n // is present — server-less apps still get their telemetry flushed and\n // plugin shutdown() hooks run.\n new LifecycleManager(instance.#context).installSignalHandlers();\n\n return handle;\n }\n\n private static bootstrapInternalTelemetry(): void {\n const serviceCtx = ServiceContext.get();\n const reporter = TelemetryReporter.initialize({\n workspaceId: serviceCtx.workspaceId,\n client: serviceCtx.client,\n appId: process.env.DATABRICKS_CLIENT_ID || \"\",\n appkitVersion: productVersion,\n });\n reporter.start();\n reporter.sendStartup().catch(() => {});\n }\n\n /**\n * Injects framework-owned default plugins the app author shouldn't have to\n * register by hand. Runs before {@link filterDevOnlyPlugins}, so a default\n * that is itself `devOnly` (like `ui-variants`) is present in dev and stripped\n * in prod through the exact same guard as any user plugin — never alive in a\n * deployed app.\n *\n * Currently injects the dev-only `ui-variants` recorder that backs the\n * `<Variants>` UI picker, so neither the developer nor their coding agent has\n * to remember to add it. If the app already registered it explicitly, that\n * entry wins and no duplicate is added.\n */\n private static withDefaultPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n const list = (plugins ?? []) as PluginData<\n PluginConstructor,\n unknown,\n string\n >[];\n\n const defaults: PluginData<PluginConstructor, unknown, string>[] = [\n uiVariants(),\n ];\n\n const missing = defaults.filter(\n (def) => !list.some((p) => p?.name === def.name),\n );\n\n return [...list, ...missing] as T;\n }\n\n /**\n * Drops plugins whose manifest declares `devOnly: true` unless\n * `NODE_ENV === \"development\"`. Runs before resource collection so a skipped\n * plugin is never constructed, never injects routes, and has its resource\n * requirements ignored — the framework, not the app author, enforces that\n * dev-only tooling can never run in a deployed app.\n *\n * This is the primary guard; plugins that expose a mutating dev endpoint\n * should still keep an in-handler `NODE_ENV` check as fail-safe defense\n * against being mounted through a path that bypasses this filter.\n */\n private static filterDevOnlyPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n if (!plugins || process.env.NODE_ENV === \"development\") return plugins;\n\n return plugins.filter((pluginData) => {\n const isDevOnly = pluginData?.plugin?.manifest?.devOnly === true;\n if (isDevOnly) {\n logger.debug(\n \"Skipping dev-only plugin %s (NODE_ENV=%s)\",\n pluginData?.name ?? \"unknown\",\n process.env.NODE_ENV ?? \"<unset>\",\n );\n }\n return !isDevOnly;\n }) as T;\n }\n\n private static preparePlugins(\n plugins: PluginData<PluginConstructor, unknown, string>[],\n ) {\n const result: InputPluginMap = {};\n for (const currentPlugin of plugins) {\n result[currentPlugin.name] = {\n plugin: currentPlugin.plugin,\n config: currentPlugin.config as Record<string, unknown>,\n };\n }\n return result;\n }\n}\n\n/**\n * Bootstraps AppKit with the provided configuration.\n *\n * Initializes telemetry, cache, and service context, then registers plugins\n * in phase order (core, normal, deferred) and awaits their setup.\n * If a `onPluginsReady` callback is provided it runs after plugin setup but\n * before the server starts, giving you access to the full appkit handle\n * for registering custom routes or performing async setup.\n * The returned object maps each plugin name to its `exports()` API,\n * with an `asUser(req)` method for user-scoped execution.\n *\n * @returns A `PluginMap` keyed by plugin name with typed exports\n *\n * @example Minimal server\n * ```ts\n * import { createApp, server } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server()],\n * });\n * ```\n *\n * @example Server with custom routes via onPluginsReady\n * ```ts\n * import { createApp, server, analytics } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server(), analytics({})],\n * onPluginsReady(appkit) {\n * appkit.server.extend((app) => {\n * app.get(\"/custom\", (_req, res) => res.json({ ok: true }));\n * });\n * },\n * });\n * ```\n */\nexport async function createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n>(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n): Promise<PluginMap<T>> {\n return AppKit._createApp(config);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"appkit.js","names":["#context","#pluginInstances","#setupPromises","productVersion"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type {\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n PluginMap,\n} from \"shared\";\nimport { version as productVersion } from \"../../package.json\";\nimport { CacheManager } from \"../cache\";\nimport { ServiceContext } from \"../context\";\nimport {\n isInternalTelemetryEnabled,\n TelemetryReporter,\n} from \"../internal-telemetry\";\nimport { createLogger } from \"../logging/logger\";\nimport { isPlainObject } from \"../plugin/plugin\";\nimport { uiVariants } from \"../plugins/ui-variants\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\nimport type { WorkspaceClient } from \"../workspace-client\";\nimport { LifecycleManager } from \"./lifecycle-manager\";\nimport { isToolProvider, PluginContext } from \"./plugin-context\";\n\nconst logger = createLogger(\"appkit\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n #context: PluginContext;\n\n private constructor(config: { plugins: TPlugins }) {\n const { plugins, ...globalConfig } = config;\n\n this.#context = new PluginContext();\n\n const pluginEntries = Object.entries(plugins);\n\n const corePlugins = pluginEntries.filter(([_, p]) => {\n return (p?.plugin?.phase ?? \"normal\") === \"core\";\n });\n const normalPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"normal\",\n );\n const deferredPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"deferred\",\n );\n\n for (const [name, pluginData] of corePlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of normalPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of deferredPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n }\n\n private createAndRegisterPlugin<T extends PluginConstructor>(\n config: Omit<{ plugins: TPlugins }, \"plugins\">,\n name: string,\n pluginData: OptionalConfigPluginDef<T>,\n extraData?: Record<string, unknown>,\n ) {\n const { plugin: Plugin, config: pluginConfig } = pluginData;\n const baseConfig = {\n ...config,\n ...Plugin.DEFAULT_CONFIG,\n ...pluginConfig,\n name,\n ...extraData,\n };\n const pluginInstance = new Plugin(baseConfig);\n\n if (typeof pluginInstance.attachContext === \"function\") {\n pluginInstance.attachContext({\n context: this.#context,\n telemetryConfig: baseConfig.telemetry,\n });\n }\n\n this.#pluginInstances[name] = pluginInstance;\n\n this.#context.registerPlugin(name, pluginInstance);\n if (isToolProvider(pluginInstance)) {\n this.#context.registerToolProvider(name, pluginInstance);\n }\n\n this.#setupPromises.push(pluginInstance.setup());\n\n const self = this;\n\n Object.defineProperty(this, name, {\n get() {\n const plugin = self.#pluginInstances[name];\n return self.wrapWithAsUser(plugin);\n },\n enumerable: true,\n });\n }\n\n /**\n * Binds all function properties in an exports object to the given context.\n * Recurses into plain objects to handle nested APIs (e.g., volume APIs).\n */\n private bindExportMethods(\n exports: Record<string, unknown>,\n context: BasePlugin,\n ) {\n for (const key in exports) {\n if (!Object.hasOwn(exports, key)) continue;\n const val = exports[key];\n if (typeof val === \"function\") {\n exports[key] = (val as (...args: unknown[]) => unknown).bind(context);\n } else if (isPlainObject(val)) {\n this.bindExportMethods(val as Record<string, unknown>, context);\n }\n }\n }\n\n /**\n * Wraps a plugin's exports with an `asUser` method that returns\n * a user-scoped version of the exports.\n *\n * When `exports()` returns a callable (function), it is returned as-is\n * since the plugin manages its own `asUser` per-call (e.g. files plugin).\n * When it returns a plain object, the standard `asUser` wrapper is added.\n *\n * The OBO-side wrapping lives inside `Plugin.asUser` — calling\n * `plugin.asUser(req).exports()` returns exports whose functions already\n * run inside the user's AsyncLocalStorage scope. AppKit only adapts the\n * shape; it does not own the user-context concept.\n */\n private wrapWithAsUser<T extends BasePlugin>(plugin: T) {\n // If plugin doesn't implement exports(), return empty object\n const pluginExports = plugin.exports?.() ?? {};\n\n // If exports is a function, the plugin manages its own asUser pattern\n if (typeof pluginExports === \"function\") {\n return pluginExports;\n }\n\n const objExports = pluginExports as Record<string, unknown>;\n this.bindExportMethods(objExports, plugin);\n\n // If plugin doesn't support asUser (no asUser method), return exports as-is\n if (typeof (plugin as any).asUser !== \"function\") {\n return objExports;\n }\n\n return {\n ...objExports,\n /**\n * Execute operations using the user's identity from the request.\n * Returns user-scoped exports where all methods execute with the\n * user's Databricks credentials instead of the service principal.\n */\n asUser: (req: import(\"express\").Request) =>\n (plugin as any).asUser(req).exports() as Record<string, unknown>,\n };\n }\n\n static async _createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n ): Promise<PluginMap<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const withDefaults = AppKit.withDefaultPlugins(config.plugins as T);\n const rawPlugins = AppKit.filterDevOnlyPlugins(withDefaults);\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n\n // Derive ServiceContext needs from what manifests declared\n const needsWarehouse = registry\n .getRequired()\n .some((r) => r.type === ResourceType.SQL_WAREHOUSE);\n await ServiceContext.initialize(\n { warehouseId: needsWarehouse },\n config?.client,\n );\n\n // Validate env vars\n registry.enforceValidation();\n\n const preparedPlugins = AppKit.preparePlugins(rawPlugins);\n const mergedConfig = {\n plugins: preparedPlugins,\n };\n\n const instance = new AppKit(mergedConfig);\n\n await Promise.all(instance.#setupPromises);\n await instance.#context.emitLifecycle(\"setup:complete\");\n\n const handle = instance as unknown as PluginMap<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(handle);\n logger.debug(\"onPluginsReady hook completed\");\n }\n\n if (isInternalTelemetryEnabled(config)) {\n AppKit.bootstrapInternalTelemetry();\n }\n\n const serverPlugin = instance.#pluginInstances.server;\n if (serverPlugin && typeof (serverPlugin as any).start === \"function\") {\n await (serverPlugin as any).start();\n }\n\n // Core owns graceful shutdown: install the signal handlers once every\n // plugin has started. Applies uniformly whether or not a server plugin\n // is present — server-less apps still get their telemetry flushed and\n // plugin shutdown() hooks run.\n new LifecycleManager(instance.#context).installSignalHandlers();\n\n return handle;\n }\n\n private static bootstrapInternalTelemetry(): void {\n const serviceCtx = ServiceContext.get();\n const reporter = TelemetryReporter.initialize({\n workspaceId: serviceCtx.workspaceId,\n client: serviceCtx.client,\n appId: process.env.DATABRICKS_CLIENT_ID || \"\",\n appkitVersion: productVersion,\n });\n reporter.start();\n reporter.sendStartup().catch(() => {});\n }\n\n /**\n * Injects framework-owned default plugins the app author shouldn't have to\n * register by hand. Runs before {@link filterDevOnlyPlugins}, so a default\n * that is itself `devOnly` (like `ui-variants`) is present in dev and stripped\n * in prod through the exact same guard as any user plugin — never alive in a\n * deployed app.\n *\n * Currently injects the dev-only `ui-variants` recorder that backs the\n * `<Variants>` UI picker, so neither the developer nor their coding agent has\n * to remember to add it. If the app already registered it explicitly, that\n * entry wins and no duplicate is added.\n */\n private static withDefaultPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n const list = (plugins ?? []) as PluginData<\n PluginConstructor,\n unknown,\n string\n >[];\n\n const defaults: PluginData<PluginConstructor, unknown, string>[] = [\n uiVariants(),\n ];\n\n const missing = defaults.filter(\n (def) => !list.some((p) => p?.name === def.name),\n );\n\n return [...list, ...missing] as T;\n }\n\n /**\n * Drops plugins whose manifest declares `devOnly: true` unless\n * `NODE_ENV === \"development\"`. Runs before resource collection so a skipped\n * plugin is never constructed, never injects routes, and has its resource\n * requirements ignored — the framework, not the app author, enforces that\n * dev-only tooling can never run in a deployed app.\n *\n * This is the primary guard; plugins that expose a mutating dev endpoint\n * should still keep an in-handler `NODE_ENV` check as fail-safe defense\n * against being mounted through a path that bypasses this filter.\n */\n private static filterDevOnlyPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n if (!plugins || process.env.NODE_ENV === \"development\") return plugins;\n\n return plugins.filter((pluginData) => {\n const isDevOnly = pluginData?.plugin?.manifest?.devOnly === true;\n if (isDevOnly) {\n logger.debug(\n \"Skipping dev-only plugin %s (NODE_ENV=%s)\",\n pluginData?.name ?? \"unknown\",\n process.env.NODE_ENV ?? \"<unset>\",\n );\n }\n return !isDevOnly;\n }) as T;\n }\n\n private static preparePlugins(\n plugins: PluginData<PluginConstructor, unknown, string>[],\n ) {\n const result: InputPluginMap = {};\n for (const currentPlugin of plugins) {\n result[currentPlugin.name] = {\n plugin: currentPlugin.plugin,\n config: currentPlugin.config as Record<string, unknown>,\n };\n }\n return result;\n }\n}\n\n/**\n * Bootstraps AppKit with the provided configuration.\n *\n * Initializes telemetry, cache, and service context, then registers plugins\n * in phase order (core, normal, deferred) and awaits their setup.\n * If a `onPluginsReady` callback is provided it runs after plugin setup but\n * before the server starts, giving you access to the full appkit handle\n * for registering custom routes or performing async setup.\n * The returned object maps each plugin name to its `exports()` API,\n * with an `asUser(req)` method for user-scoped execution.\n *\n * @returns A `PluginMap` keyed by plugin name with typed exports\n *\n * @example Minimal server\n * ```ts\n * import { createApp, server } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server()],\n * });\n * ```\n *\n * @example Server with custom routes via onPluginsReady\n * ```ts\n * import { createApp, server, analytics } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server(), analytics({})],\n * onPluginsReady(appkit) {\n * appkit.server.extend((app) => {\n * app.get(\"/custom\", (_req, res) => res.json({ ok: true }));\n * });\n * },\n * });\n * ```\n */\nexport async function createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n>(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n): Promise<PluginMap<T>> {\n return AppKit._createApp(config);\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,11 @@ import { CacheConfig } from "./shared/src/cache.js";
4
4
  import { StreamExecutionSettings } from "./shared/src/execute.js";
5
5
  import { isSQLTypeMarker, sql } from "./shared/src/sql/helpers.js";
6
6
  import "./shared/src/index.js";
7
+ import { ApiError } from "./workspace-client/errors.js";
8
+ import { WorkspaceClientOptions } from "./workspace-client/legacy.js";
9
+ import { WorkspaceClient } from "./workspace-client/types.js";
10
+ import { createWorkspaceClient } from "./workspace-client/factory.js";
11
+ import "./workspace-client/index.js";
7
12
  import { ExecutionResult } from "./plugin/execution-result.js";
8
13
  import { CacheManager } from "./cache/index.js";
9
14
  import { ITelemetry, TelemetryConfig } from "./telemetry/types.js";
@@ -49,4 +54,4 @@ import "./plugins/ga-exports.generated.js";
49
54
  import { extractServingEndpoints, findServerFile } from "./type-generator/serving/server-file-extractor.js";
50
55
  import { appKitServingTypesPlugin } from "./type-generator/serving/vite-plugin.js";
51
56
  import { appKitTypesPlugin } from "./type-generator/vite-plugin.js";
52
- export { AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, type EndpointConfig, ExecutionError, type ExecutionResult, type FileAction, type FilePolicy, type FilePolicyUser, type FileResource, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type IJobsConfig, type ITelemetry, InitializationError, type JobAPI, type JobConfig, type JobHandle, type JobsConnectorConfig, type JobsExport, type LakebasePool, type LakebasePoolConfig, type LakebasePoolManager, Plugin, type PluginData, type PluginManifest, PolicyDeniedError, READ_ACTIONS, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, type ServingEndpointEntry, type ServingEndpointRegistry, type ServingFactory, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, ValidationError, type ValidationResult, WRITE_ACTIONS, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };
57
+ export { ApiError, AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, type EndpointConfig, ExecutionError, type ExecutionResult, type FileAction, type FilePolicy, type FilePolicyUser, type FileResource, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type IJobsConfig, type ITelemetry, InitializationError, type JobAPI, type JobConfig, type JobHandle, type JobsConnectorConfig, type JobsExport, type LakebasePool, type LakebasePoolConfig, type LakebasePoolManager, Plugin, type PluginData, type PluginManifest, PolicyDeniedError, READ_ACTIONS, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, type ServingEndpointEntry, type ServingEndpointRegistry, type ServingFactory, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, ValidationError, type ValidationResult, WRITE_ACTIONS, type WorkspaceClient, type WorkspaceClientOptions, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, createWorkspaceClient, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };