@fabricorg/databricks-testkit 0.7.1 → 0.7.2

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.
package/README.md CHANGED
@@ -9,8 +9,8 @@ two profiles:
9
9
  - **local** — DuckDB in-process (`@duckdb/node-api`, optional peer). No
10
10
  workspace, no credentials, sub-second. The default.
11
11
  - **live** — a real Databricks SQL warehouse via the SQL Statement Execution
12
- API, built on `@fabric-harness/databricks`. Always gated by
13
- `DBX_TEST_LIVE=1`.
12
+ API through the testkit's narrow, injectable workspace transport. Always
13
+ gated by `DBX_TEST_LIVE=1`.
14
14
 
15
15
  Positional `?` params work in both profiles — the live context transparently
16
16
  rewrites them to the named markers the Statement Execution API requires.
@@ -1,7 +1,142 @@
1
1
  import { parseFixtureColumns, resolveFixtureRows } from './chunk-F3KU6VZS.js';
2
2
  import { readFile } from 'fs/promises';
3
- import { DatabricksRestClient, waitForDatabricksStatement } from '@fabric-harness/databricks';
4
3
 
4
+ // src/workspace-client.ts
5
+ var WorkspaceRestClient = class {
6
+ host;
7
+ token;
8
+ fetchImpl;
9
+ maxRetries;
10
+ retryBaseDelayMs;
11
+ signal;
12
+ constructor(options) {
13
+ this.host = workspaceOrigin(options.host);
14
+ this.token = options.token;
15
+ this.fetchImpl = options.fetchImpl ?? fetch;
16
+ this.maxRetries = options.maxRetries ?? 3;
17
+ this.retryBaseDelayMs = options.retryBaseDelayMs ?? 500;
18
+ this.signal = options.signal;
19
+ }
20
+ get(path, query) {
21
+ return this.request("GET", path, void 0, query);
22
+ }
23
+ post(path, body, query, options) {
24
+ return this.request("POST", path, body, query, options);
25
+ }
26
+ async request(method, path, body, query = {}, options = {}) {
27
+ const url = new URL(`${this.host}${path.startsWith("/") ? path : `/${path}`}`);
28
+ for (const [key, value] of Object.entries(query)) {
29
+ if (value !== void 0) url.searchParams.set(key, String(value));
30
+ }
31
+ const bodyHasIdempotencyToken = typeof body === "object" && body !== null && typeof body.idempotency_token === "string";
32
+ const canRetry = options.retry === "always" || options.retry !== "never" && (options.retry === "safe" || method === "GET" || Boolean(options.idempotencyKey) || bodyHasIdempotencyToken);
33
+ const maxRetries = canRetry ? options.maxRetries ?? this.maxRetries : 0;
34
+ for (let attempt = 0; ; attempt += 1) {
35
+ const token = typeof this.token === "function" ? await this.token() : this.token;
36
+ let response;
37
+ try {
38
+ response = await this.fetchImpl(url, {
39
+ method,
40
+ headers: {
41
+ authorization: `Bearer ${token}`,
42
+ ...body !== void 0 ? { "content-type": "application/json" } : {},
43
+ ...options.idempotencyKey ? { "idempotency-key": options.idempotencyKey } : {}
44
+ },
45
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
46
+ ...this.signal ? { signal: this.signal } : {}
47
+ });
48
+ } catch (error) {
49
+ if (this.signal?.aborted || attempt >= maxRetries) throw error;
50
+ await delay(this.retryBaseDelayMs * 2 ** attempt, this.signal);
51
+ continue;
52
+ }
53
+ if (response.ok) {
54
+ if (response.status === 204) return void 0;
55
+ return await response.json();
56
+ }
57
+ if (isRetryableStatus(response.status) && attempt < maxRetries) {
58
+ await delay(retryDelayMs(response, attempt, this.retryBaseDelayMs), this.signal);
59
+ continue;
60
+ }
61
+ const message = (await response.text()).split(token).join("[REDACTED]");
62
+ throw new Error(
63
+ `databricks: ${response.status} ${response.statusText}: ${message.slice(0, 2e3)}`
64
+ );
65
+ }
66
+ }
67
+ };
68
+ async function waitForDatabricksRun(client, runId, options = {}) {
69
+ const deadline = Date.now() + (options.timeoutMs ?? 6e5);
70
+ const interval = options.pollIntervalMs ?? 5e3;
71
+ let current = await client.get("/api/2.2/jobs/runs/get", {
72
+ run_id: runId
73
+ });
74
+ while (!isTerminalRun(current) && Date.now() < deadline) {
75
+ await delay(interval, options.signal);
76
+ current = await client.get("/api/2.2/jobs/runs/get", {
77
+ run_id: runId
78
+ });
79
+ }
80
+ return current;
81
+ }
82
+ async function waitForDatabricksStatement(client, statementId, options = {}) {
83
+ const deadline = Date.now() + (options.timeoutMs ?? 3e5);
84
+ const interval = options.pollIntervalMs ?? 2e3;
85
+ const path = `/api/2.0/sql/statements/${encodeURIComponent(statementId)}`;
86
+ let current = await client.get(path);
87
+ while (!isTerminalStatement(current) && Date.now() < deadline) {
88
+ await delay(interval, options.signal);
89
+ current = await client.get(path);
90
+ }
91
+ return current;
92
+ }
93
+ function workspaceOrigin(host) {
94
+ const value = host.trim();
95
+ if (!value) throw new Error("databricks: workspace host cannot be empty");
96
+ const url = new URL(value.includes("://") ? value : `https://${value}`);
97
+ if (url.username || url.password) {
98
+ throw new Error("databricks: workspace host must not contain credentials");
99
+ }
100
+ return url.origin;
101
+ }
102
+ function isRetryableStatus(status) {
103
+ return status === 429 || status === 499 || status >= 500 && status <= 599;
104
+ }
105
+ function retryDelayMs(response, attempt, base) {
106
+ const retryAfterHeader = response.headers.get("retry-after");
107
+ const retryAfter = retryAfterHeader === null ? Number.NaN : Number(retryAfterHeader);
108
+ return Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1e3 : base * 2 ** attempt;
109
+ }
110
+ function isTerminalRun(run) {
111
+ const state = run.state;
112
+ return ["TERMINATED", "SKIPPED", "INTERNAL_ERROR"].includes(state?.life_cycle_state ?? "");
113
+ }
114
+ function isTerminalStatement(statement) {
115
+ const status = statement.status;
116
+ return ["SUCCEEDED", "FAILED", "CANCELED", "CLOSED"].includes(status?.state ?? "");
117
+ }
118
+ function delay(ms, signal) {
119
+ if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));
120
+ if (signal.aborted) {
121
+ return Promise.reject(
122
+ signal.reason ?? new DOMException("The operation was aborted", "AbortError")
123
+ );
124
+ }
125
+ return new Promise((resolve, reject) => {
126
+ const timer = setTimeout(done, ms);
127
+ signal.addEventListener("abort", aborted, { once: true });
128
+ function done() {
129
+ signal?.removeEventListener("abort", aborted);
130
+ resolve();
131
+ }
132
+ function aborted() {
133
+ clearTimeout(timer);
134
+ reject(signal?.reason ?? new DOMException("The operation was aborted", "AbortError"));
135
+ }
136
+ });
137
+ }
138
+
139
+ // src/workspace-context.ts
5
140
  async function createWorkspaceContext(options = {}) {
6
141
  const env = options.env ?? process.env;
7
142
  const warehouseId = resolveWarehouseId(env);
@@ -28,11 +163,10 @@ async function createWorkspaceContext(options = {}) {
28
163
  const state = submitted.status?.state;
29
164
  let terminal = submitted;
30
165
  if (state !== "SUCCEEDED" && state !== "FAILED" && state !== "CANCELED" && state !== "CLOSED") {
31
- terminal = await waitForDatabricksStatement(
32
- client,
33
- submitted.statement_id ?? "",
34
- { pollIntervalMs: options.pollIntervalMs, timeoutMs: options.timeoutMs }
35
- );
166
+ terminal = await waitForDatabricksStatement(client, submitted.statement_id ?? "", {
167
+ pollIntervalMs: options.pollIntervalMs,
168
+ timeoutMs: options.timeoutMs
169
+ });
36
170
  }
37
171
  if (terminal.status?.state !== "SUCCEEDED") {
38
172
  throw new Error(
@@ -149,7 +283,7 @@ function resolveWarehouseId(env) {
149
283
  function createClientFromEnv(env, fetchImpl) {
150
284
  const host = env.DATABRICKS_HOST;
151
285
  if (!host) throw new Error("Live profile requires DATABRICKS_HOST");
152
- return new DatabricksRestClient({
286
+ return new WorkspaceRestClient({
153
287
  host,
154
288
  token: resolveTokenProvider(host, env, fetchImpl),
155
289
  ...fetchImpl ? { fetchImpl } : {}
@@ -237,6 +371,6 @@ async function resolveFederatedSubjectToken(authType, env) {
237
371
  return token;
238
372
  }
239
373
 
240
- export { createClientFromEnv, createWorkspaceContext, parseStatementRows, resolveTokenProvider, toNamedParameters };
241
- //# sourceMappingURL=chunk-SCVVMUAF.js.map
242
- //# sourceMappingURL=chunk-SCVVMUAF.js.map
374
+ export { createClientFromEnv, createWorkspaceContext, parseStatementRows, resolveTokenProvider, toNamedParameters, waitForDatabricksRun, waitForDatabricksStatement };
375
+ //# sourceMappingURL=chunk-MV752EAZ.js.map
376
+ //# sourceMappingURL=chunk-MV752EAZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workspace-client.ts","../src/workspace-context.ts"],"names":["token"],"mappings":"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","file":"chunk-MV752EAZ.js","sourcesContent":["/** Minimal request boundary used by the live Databricks certification suite. */\nexport interface WorkspaceClientLike {\n get<T>(path: string, query?: Record<string, string | number | boolean | undefined>): Promise<T>;\n post<T>(\n path: string,\n body?: unknown,\n query?: Record<string, string | number | boolean | undefined>,\n options?: WorkspaceRequestOptions,\n ): Promise<T>;\n}\n\nexport interface WorkspaceClientOptions {\n host: string;\n token: string | (() => string | Promise<string>);\n fetchImpl?: typeof fetch;\n maxRetries?: number;\n retryBaseDelayMs?: number;\n signal?: AbortSignal;\n}\n\nexport interface WorkspaceRequestOptions {\n retry?: 'safe' | 'always' | 'never';\n idempotencyKey?: string;\n maxRetries?: number;\n}\n\nexport interface WorkspaceWaitOptions {\n pollIntervalMs?: number;\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\n/**\n * Testkit-owned REST boundary for workspace certification endpoints.\n *\n * Harness 4 removed its generic public REST client in favor of generated,\n * capability-specific Databricks SDK clients. The testkit intentionally keeps\n * this narrow injectable transport because it certifies APIs across several\n * services and because its mock client implements the same two-method shape.\n */\nexport class WorkspaceRestClient implements WorkspaceClientLike {\n private readonly host: string;\n private readonly token: WorkspaceClientOptions['token'];\n private readonly fetchImpl: typeof fetch;\n private readonly maxRetries: number;\n private readonly retryBaseDelayMs: number;\n private readonly signal: AbortSignal | undefined;\n\n constructor(options: WorkspaceClientOptions) {\n this.host = workspaceOrigin(options.host);\n this.token = options.token;\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.maxRetries = options.maxRetries ?? 3;\n this.retryBaseDelayMs = options.retryBaseDelayMs ?? 500;\n this.signal = options.signal;\n }\n\n get<T>(path: string, query?: Record<string, string | number | boolean | undefined>): Promise<T> {\n return this.request<T>('GET', path, undefined, query);\n }\n\n post<T>(\n path: string,\n body?: unknown,\n query?: Record<string, string | number | boolean | undefined>,\n options?: WorkspaceRequestOptions,\n ): Promise<T> {\n return this.request<T>('POST', path, body, query, options);\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query: Record<string, string | number | boolean | undefined> = {},\n options: WorkspaceRequestOptions = {},\n ): Promise<T> {\n const url = new URL(`${this.host}${path.startsWith('/') ? path : `/${path}`}`);\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n const bodyHasIdempotencyToken =\n typeof body === 'object' &&\n body !== null &&\n typeof (body as Record<string, unknown>).idempotency_token === 'string';\n const canRetry =\n options.retry === 'always' ||\n (options.retry !== 'never' &&\n (options.retry === 'safe' ||\n method === 'GET' ||\n Boolean(options.idempotencyKey) ||\n bodyHasIdempotencyToken));\n const maxRetries = canRetry ? (options.maxRetries ?? this.maxRetries) : 0;\n\n for (let attempt = 0; ; attempt += 1) {\n const token = typeof this.token === 'function' ? await this.token() : this.token;\n let response: Response;\n try {\n response = await this.fetchImpl(url, {\n method,\n headers: {\n authorization: `Bearer ${token}`,\n ...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(options.idempotencyKey ? { 'idempotency-key': options.idempotencyKey } : {}),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n ...(this.signal ? { signal: this.signal } : {}),\n });\n } catch (error) {\n if (this.signal?.aborted || attempt >= maxRetries) throw error;\n await delay(this.retryBaseDelayMs * 2 ** attempt, this.signal);\n continue;\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n if (isRetryableStatus(response.status) && attempt < maxRetries) {\n await delay(retryDelayMs(response, attempt, this.retryBaseDelayMs), this.signal);\n continue;\n }\n\n const message = (await response.text()).split(token).join('[REDACTED]');\n throw new Error(\n `databricks: ${response.status} ${response.statusText}: ${message.slice(0, 2000)}`,\n );\n }\n }\n}\n\nexport async function waitForDatabricksRun(\n client: WorkspaceClientLike,\n runId: number,\n options: WorkspaceWaitOptions = {},\n): Promise<Record<string, unknown>> {\n const deadline = Date.now() + (options.timeoutMs ?? 600_000);\n const interval = options.pollIntervalMs ?? 5_000;\n let current = await client.get<Record<string, unknown>>('/api/2.2/jobs/runs/get', {\n run_id: runId,\n });\n while (!isTerminalRun(current) && Date.now() < deadline) {\n await delay(interval, options.signal);\n current = await client.get<Record<string, unknown>>('/api/2.2/jobs/runs/get', {\n run_id: runId,\n });\n }\n return current;\n}\n\nexport async function waitForDatabricksStatement(\n client: WorkspaceClientLike,\n statementId: string,\n options: WorkspaceWaitOptions = {},\n): Promise<Record<string, unknown>> {\n const deadline = Date.now() + (options.timeoutMs ?? 300_000);\n const interval = options.pollIntervalMs ?? 2_000;\n const path = `/api/2.0/sql/statements/${encodeURIComponent(statementId)}`;\n let current = await client.get<Record<string, unknown>>(path);\n while (!isTerminalStatement(current) && Date.now() < deadline) {\n await delay(interval, options.signal);\n current = await client.get<Record<string, unknown>>(path);\n }\n return current;\n}\n\nfunction workspaceOrigin(host: string): string {\n const value = host.trim();\n if (!value) throw new Error('databricks: workspace host cannot be empty');\n const url = new URL(value.includes('://') ? value : `https://${value}`);\n if (url.username || url.password) {\n throw new Error('databricks: workspace host must not contain credentials');\n }\n return url.origin;\n}\n\nfunction isRetryableStatus(status: number): boolean {\n return status === 429 || status === 499 || (status >= 500 && status <= 599);\n}\n\nfunction retryDelayMs(response: Response, attempt: number, base: number): number {\n const retryAfterHeader = response.headers.get('retry-after');\n const retryAfter = retryAfterHeader === null ? Number.NaN : Number(retryAfterHeader);\n return Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1000 : base * 2 ** attempt;\n}\n\nfunction isTerminalRun(run: Record<string, unknown>): boolean {\n const state = run.state as { life_cycle_state?: string } | undefined;\n return ['TERMINATED', 'SKIPPED', 'INTERNAL_ERROR'].includes(state?.life_cycle_state ?? '');\n}\n\nfunction isTerminalStatement(statement: Record<string, unknown>): boolean {\n const status = statement.status as { state?: string } | undefined;\n return ['SUCCEEDED', 'FAILED', 'CANCELED', 'CLOSED'].includes(status?.state ?? '');\n}\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));\n if (signal.aborted) {\n return Promise.reject(\n signal.reason ?? new DOMException('The operation was aborted', 'AbortError'),\n );\n }\n return new Promise((resolve, reject) => {\n const timer = setTimeout(done, ms);\n signal.addEventListener('abort', aborted, { once: true });\n function done() {\n signal?.removeEventListener('abort', aborted);\n resolve();\n }\n function aborted() {\n clearTimeout(timer);\n reject(signal?.reason ?? new DOMException('The operation was aborted', 'AbortError'));\n }\n });\n}\n","import { readFile } from 'node:fs/promises';\nimport type { ExecutionContext } from './context.js';\nimport { type TableFixture, parseFixtureColumns, resolveFixtureRows } from './fixtures.js';\nimport {\n type WorkspaceClientLike,\n WorkspaceRestClient,\n waitForDatabricksStatement,\n} from './workspace-client.js';\nexport type { WorkspaceClientLike } from './workspace-client.js';\n\nexport interface WorkspaceContextOptions {\n env?: Record<string, string | undefined>;\n /** Schema within the catalog used by qual(); defaults to DBX_TEST_SCHEMA. */\n schema?: string;\n /** Injectable client for hermetic tests; defaults to the testkit workspace client. */\n client?: WorkspaceClientLike;\n /** Injectable fetch for the OAuth M2M exchange. */\n fetchImpl?: typeof fetch;\n pollIntervalMs?: number;\n timeoutMs?: number;\n /** Drop tables seeded by this context on close. Defaults to true. */\n cleanupFixtures?: boolean;\n /** Observe submitted statements for redacted failure evidence. */\n onStatement?: (statement: { sql: string; statementId?: string }) => void;\n}\n\ninterface StatementResponse {\n statement_id?: string;\n status?: { state?: string; error?: { message?: string } };\n manifest?: { schema?: { columns?: { name: string; type_name?: string; position?: number }[] } };\n result?: { data_array?: (string | null)[][] };\n}\n\n/**\n * Live execution context on a Databricks SQL warehouse via the SQL Statement\n * Execution API (/api/2.0/sql/statements). The Statement Execution API takes\n * NAMED parameter markers, so positional `?` params are rewritten to\n * `:p1..:pn` — the ExecutionContext surface stays identical to the local\n * profile.\n */\nexport async function createWorkspaceContext(\n options: WorkspaceContextOptions = {},\n): Promise<ExecutionContext> {\n const env = options.env ?? process.env;\n const warehouseId = resolveWarehouseId(env);\n const catalog = env.DBX_TEST_CATALOG;\n const schema = options.schema ?? env.DBX_TEST_SCHEMA;\n const client = options.client ?? createClientFromEnv(env, options.fetchImpl);\n const fixtureTables = new Set<string>();\n\n const qual = (table: string) =>\n [catalog, schema, table].filter((part): part is string => Boolean(part)).join('.');\n\n async function run(sql: string, params?: readonly unknown[]): Promise<StatementResponse> {\n const { statement, parameters } = toNamedParameters(sql, params ?? []);\n const submitted = await client.post<StatementResponse>('/api/2.0/sql/statements', {\n statement,\n warehouse_id: warehouseId,\n ...(catalog ? { catalog } : {}),\n ...(schema ? { schema } : {}),\n ...(parameters.length > 0 ? { parameters } : {}),\n wait_timeout: '30s',\n on_wait_timeout: 'CONTINUE',\n });\n options.onStatement?.({\n sql,\n ...(submitted.statement_id ? { statementId: submitted.statement_id } : {}),\n });\n const state = submitted.status?.state;\n let terminal = submitted;\n if (state !== 'SUCCEEDED' && state !== 'FAILED' && state !== 'CANCELED' && state !== 'CLOSED') {\n terminal = (await waitForDatabricksStatement(client, submitted.statement_id ?? '', {\n pollIntervalMs: options.pollIntervalMs,\n timeoutMs: options.timeoutMs,\n })) as StatementResponse;\n }\n if (terminal.status?.state !== 'SUCCEEDED') {\n throw new Error(\n `Databricks statement ${terminal.status?.state ?? 'UNKNOWN'}: ${\n terminal.status?.error?.message ?? sql.slice(0, 200)\n }`,\n );\n }\n return terminal;\n }\n\n return {\n profile: 'live',\n qual,\n async exec(sql, params) {\n await run(sql, params);\n },\n async query(sql, params) {\n const response = await run(sql, params);\n return parseStatementRows(response);\n },\n async loadFixture(fixture: TableFixture) {\n if (!schema) {\n throw new Error(\n 'Live fixtures require DBX_TEST_SCHEMA (or a schema option) so tests cannot replace unscoped tables',\n );\n }\n if (!fixture.schema) {\n throw new Error(\n `Live fixtures require explicit column DDL for ${fixture.table} (schema inference is local-only)`,\n );\n }\n const columns = parseFixtureColumns(fixture.schema);\n const ddl = columns.map((c) => `\\`${c.name}\\` ${c.type}`).join(', ');\n const target = qual(fixture.table);\n await run(`CREATE OR REPLACE TABLE ${target} (${ddl}) USING DELTA`);\n fixtureTables.add(target);\n const rows = await resolveFixtureRows(fixture, columns);\n for (const row of rows) {\n const placeholders = columns\n .map((c) => (/TIMESTAMP/i.test(c.type) ? 'CAST(? AS TIMESTAMP)' : '?'))\n .join(', ');\n await run(`INSERT INTO ${qual(fixture.table)} VALUES (${placeholders})`, row);\n }\n },\n async close() {\n if (options.cleanupFixtures !== false && env.DBX_TEST_KEEP_FIXTURES !== '1') {\n for (const table of [...fixtureTables].reverse()) {\n await run(`DROP TABLE IF EXISTS ${table}`);\n }\n }\n fixtureTables.clear();\n },\n };\n}\n\n/** Rewrite positional `?` markers to the named `:pN` markers the Statement Execution API requires. */\nexport function toNamedParameters(\n sql: string,\n params: readonly unknown[],\n): { statement: string; parameters: { name: string; value: string | null }[] } {\n let out = '';\n let index = 0;\n let quote: string | null = null;\n for (let i = 0; i < sql.length; i++) {\n const ch = sql[i] as string;\n if (quote) {\n out += ch;\n if (ch === quote) quote = null;\n continue;\n }\n if (ch === \"'\" || ch === '\"' || ch === '`') {\n quote = ch;\n out += ch;\n continue;\n }\n if (ch === '?') {\n index += 1;\n out += `:p${index}`;\n continue;\n }\n out += ch;\n }\n if (index !== params.length) {\n throw new Error(`SQL has ${index} placeholders but ${params.length} params were provided`);\n }\n return {\n statement: out,\n parameters: params.map((value, i) => ({\n name: `p${i + 1}`,\n value: value === null || value === undefined ? null : String(value),\n })),\n };\n}\n\n/** Map a terminal statement response to normalized row objects. */\nexport function parseStatementRows(response: StatementResponse): Record<string, unknown>[] {\n const columns = [...(response.manifest?.schema?.columns ?? [])].sort(\n (a, b) => (a.position ?? 0) - (b.position ?? 0),\n );\n const data = response.result?.data_array ?? [];\n return data.map((tuple) => {\n const row: Record<string, unknown> = {};\n columns.forEach((column, i) => {\n row[column.name] = convertValue(tuple[i] ?? null, column.type_name ?? 'STRING');\n });\n return row;\n });\n}\n\nconst NUMERIC_TYPES = /^(INT|LONG|SHORT|BYTE|FLOAT|DOUBLE|DECIMAL)/i;\n\nfunction convertValue(value: string | null, typeName: string): unknown {\n if (value === null) return null;\n if (NUMERIC_TYPES.test(typeName)) return Number(value);\n if (/^BOOLEAN/i.test(typeName)) return value === 'true';\n return value;\n}\n\nfunction resolveWarehouseId(env: Record<string, string | undefined>): string {\n if (env.DATABRICKS_WAREHOUSE_ID) return env.DATABRICKS_WAREHOUSE_ID;\n const httpPath = env.DATABRICKS_HTTP_PATH;\n const match = httpPath?.match(/\\/warehouses\\/([\\w-]+)/);\n if (match?.[1]) return match[1];\n throw new Error(\n 'Live profile requires DATABRICKS_WAREHOUSE_ID or DATABRICKS_HTTP_PATH (/sql/1.0/warehouses/<id>)',\n );\n}\n\n/**\n * Auth precedence (ADR-0006): DATABRICKS_BEARER (pre-minted, CI) →\n * explicit env/file OIDC federation → DATABRICKS_CLIENT_ID/SECRET (OAuth\n * M2M) → DATABRICKS_TOKEN (legacy PAT).\n */\nexport function createClientFromEnv(\n env: Record<string, string | undefined>,\n fetchImpl?: typeof fetch,\n): WorkspaceClientLike {\n const host = env.DATABRICKS_HOST;\n if (!host) throw new Error('Live profile requires DATABRICKS_HOST');\n return new WorkspaceRestClient({\n host,\n token: resolveTokenProvider(host, env, fetchImpl),\n ...(fetchImpl ? { fetchImpl } : {}),\n });\n}\n\nexport function resolveTokenProvider(\n host: string,\n env: Record<string, string | undefined>,\n fetchImpl: typeof fetch = fetch,\n): string | (() => Promise<string>) {\n if (env.DATABRICKS_BEARER) return env.DATABRICKS_BEARER;\n const authType = env.DATABRICKS_AUTH_TYPE;\n if (authType === 'env-oidc' || authType === 'file-oidc') {\n let cached: { token: string; expiresAt: number } | null = null;\n return async () => {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n const subjectToken = await resolveFederatedSubjectToken(authType, env);\n const params = new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',\n subject_token: subjectToken,\n subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',\n scope: env.DATABRICKS_SCOPE ?? 'all-apis',\n });\n if (env.DATABRICKS_CLIENT_ID) params.set('client_id', env.DATABRICKS_CLIENT_ID);\n const response = await fetchImpl(`${host.replace(/\\/$/, '')}/oidc/v1/token`, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: params.toString(),\n });\n if (!response.ok) {\n throw new Error(`OAuth federated token exchange failed: ${response.status}`);\n }\n const body = (await response.json()) as { access_token?: unknown; expires_in?: unknown };\n if (typeof body.access_token !== 'string' || !body.access_token) {\n throw new Error('OAuth federated token exchange returned no access_token');\n }\n const expiresIn = typeof body.expires_in === 'number' ? body.expires_in : 3600;\n cached = {\n token: body.access_token,\n expiresAt: Date.now() + Math.max(1, expiresIn - 300) * 1000,\n };\n return body.access_token;\n };\n }\n const clientId = env.DATABRICKS_CLIENT_ID;\n const clientSecret = env.DATABRICKS_CLIENT_SECRET;\n if (clientId && clientSecret) {\n let cached: { token: string; expiresAt: number } | null = null;\n return async () => {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n const response = await fetchImpl(`${host.replace(/\\/$/, '')}/oidc/v1/token`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,\n },\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n scope: env.DATABRICKS_SCOPE ?? 'all-apis',\n }).toString(),\n });\n if (!response.ok) {\n throw new Error(`OAuth M2M token exchange failed: ${response.status}`);\n }\n const body = (await response.json()) as { access_token: string; expires_in?: number };\n cached = {\n token: body.access_token,\n expiresAt: Date.now() + ((body.expires_in ?? 3600) - 300) * 1000,\n };\n return body.access_token;\n };\n }\n if (env.DATABRICKS_TOKEN) return env.DATABRICKS_TOKEN;\n throw new Error(\n 'Live profile requires DATABRICKS_BEARER, env/file OIDC federation, DATABRICKS_CLIENT_ID/SECRET, or DATABRICKS_TOKEN',\n );\n}\n\nasync function resolveFederatedSubjectToken(\n authType: 'env-oidc' | 'file-oidc',\n env: Record<string, string | undefined>,\n): Promise<string> {\n if (authType === 'env-oidc') {\n const tokenEnv = env.DATABRICKS_OIDC_TOKEN_ENV ?? 'DATABRICKS_OIDC_TOKEN';\n const token = env[tokenEnv]?.trim();\n if (!token) throw new Error(`Federated OIDC token environment variable ${tokenEnv} is empty`);\n return token;\n }\n const path = env.DATABRICKS_OIDC_TOKEN_FILEPATH;\n if (!path) throw new Error('DATABRICKS_OIDC_TOKEN_FILEPATH is required for file-oidc');\n const token = (await readFile(path, 'utf8')).trim();\n if (!token) throw new Error(`Federated OIDC token file ${path} is empty`);\n return token;\n}\n"]}
package/dist/index.cjs CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  var promises = require('fs/promises');
4
- var databricks = require('@fabric-harness/databricks');
5
4
  var path = require('path');
6
5
  var experimentsDbPool = require('@fabricorg/experiments-db-pool');
7
6
  var crypto = require('crypto');
@@ -177,6 +176,146 @@ var init_duckdb_context = __esm({
177
176
  }
178
177
  });
179
178
 
179
+ // src/workspace-client.ts
180
+ async function waitForDatabricksRun(client, runId, options = {}) {
181
+ const deadline = Date.now() + (options.timeoutMs ?? 6e5);
182
+ const interval = options.pollIntervalMs ?? 5e3;
183
+ let current = await client.get("/api/2.2/jobs/runs/get", {
184
+ run_id: runId
185
+ });
186
+ while (!isTerminalRun(current) && Date.now() < deadline) {
187
+ await delay(interval, options.signal);
188
+ current = await client.get("/api/2.2/jobs/runs/get", {
189
+ run_id: runId
190
+ });
191
+ }
192
+ return current;
193
+ }
194
+ async function waitForDatabricksStatement(client, statementId, options = {}) {
195
+ const deadline = Date.now() + (options.timeoutMs ?? 3e5);
196
+ const interval = options.pollIntervalMs ?? 2e3;
197
+ const path = `/api/2.0/sql/statements/${encodeURIComponent(statementId)}`;
198
+ let current = await client.get(path);
199
+ while (!isTerminalStatement(current) && Date.now() < deadline) {
200
+ await delay(interval, options.signal);
201
+ current = await client.get(path);
202
+ }
203
+ return current;
204
+ }
205
+ function workspaceOrigin(host) {
206
+ const value = host.trim();
207
+ if (!value) throw new Error("databricks: workspace host cannot be empty");
208
+ const url = new URL(value.includes("://") ? value : `https://${value}`);
209
+ if (url.username || url.password) {
210
+ throw new Error("databricks: workspace host must not contain credentials");
211
+ }
212
+ return url.origin;
213
+ }
214
+ function isRetryableStatus(status) {
215
+ return status === 429 || status === 499 || status >= 500 && status <= 599;
216
+ }
217
+ function retryDelayMs(response, attempt, base) {
218
+ const retryAfterHeader = response.headers.get("retry-after");
219
+ const retryAfter = retryAfterHeader === null ? Number.NaN : Number(retryAfterHeader);
220
+ return Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1e3 : base * 2 ** attempt;
221
+ }
222
+ function isTerminalRun(run) {
223
+ const state = run.state;
224
+ return ["TERMINATED", "SKIPPED", "INTERNAL_ERROR"].includes(state?.life_cycle_state ?? "");
225
+ }
226
+ function isTerminalStatement(statement) {
227
+ const status = statement.status;
228
+ return ["SUCCEEDED", "FAILED", "CANCELED", "CLOSED"].includes(status?.state ?? "");
229
+ }
230
+ function delay(ms, signal) {
231
+ if (!signal) return new Promise((resolve2) => setTimeout(resolve2, ms));
232
+ if (signal.aborted) {
233
+ return Promise.reject(
234
+ signal.reason ?? new DOMException("The operation was aborted", "AbortError")
235
+ );
236
+ }
237
+ return new Promise((resolve2, reject) => {
238
+ const timer = setTimeout(done, ms);
239
+ signal.addEventListener("abort", aborted, { once: true });
240
+ function done() {
241
+ signal?.removeEventListener("abort", aborted);
242
+ resolve2();
243
+ }
244
+ function aborted() {
245
+ clearTimeout(timer);
246
+ reject(signal?.reason ?? new DOMException("The operation was aborted", "AbortError"));
247
+ }
248
+ });
249
+ }
250
+ var WorkspaceRestClient;
251
+ var init_workspace_client = __esm({
252
+ "src/workspace-client.ts"() {
253
+ WorkspaceRestClient = class {
254
+ host;
255
+ token;
256
+ fetchImpl;
257
+ maxRetries;
258
+ retryBaseDelayMs;
259
+ signal;
260
+ constructor(options) {
261
+ this.host = workspaceOrigin(options.host);
262
+ this.token = options.token;
263
+ this.fetchImpl = options.fetchImpl ?? fetch;
264
+ this.maxRetries = options.maxRetries ?? 3;
265
+ this.retryBaseDelayMs = options.retryBaseDelayMs ?? 500;
266
+ this.signal = options.signal;
267
+ }
268
+ get(path, query) {
269
+ return this.request("GET", path, void 0, query);
270
+ }
271
+ post(path, body, query, options) {
272
+ return this.request("POST", path, body, query, options);
273
+ }
274
+ async request(method, path, body, query = {}, options = {}) {
275
+ const url = new URL(`${this.host}${path.startsWith("/") ? path : `/${path}`}`);
276
+ for (const [key, value] of Object.entries(query)) {
277
+ if (value !== void 0) url.searchParams.set(key, String(value));
278
+ }
279
+ const bodyHasIdempotencyToken = typeof body === "object" && body !== null && typeof body.idempotency_token === "string";
280
+ const canRetry = options.retry === "always" || options.retry !== "never" && (options.retry === "safe" || method === "GET" || Boolean(options.idempotencyKey) || bodyHasIdempotencyToken);
281
+ const maxRetries = canRetry ? options.maxRetries ?? this.maxRetries : 0;
282
+ for (let attempt = 0; ; attempt += 1) {
283
+ const token = typeof this.token === "function" ? await this.token() : this.token;
284
+ let response;
285
+ try {
286
+ response = await this.fetchImpl(url, {
287
+ method,
288
+ headers: {
289
+ authorization: `Bearer ${token}`,
290
+ ...body !== void 0 ? { "content-type": "application/json" } : {},
291
+ ...options.idempotencyKey ? { "idempotency-key": options.idempotencyKey } : {}
292
+ },
293
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
294
+ ...this.signal ? { signal: this.signal } : {}
295
+ });
296
+ } catch (error) {
297
+ if (this.signal?.aborted || attempt >= maxRetries) throw error;
298
+ await delay(this.retryBaseDelayMs * 2 ** attempt, this.signal);
299
+ continue;
300
+ }
301
+ if (response.ok) {
302
+ if (response.status === 204) return void 0;
303
+ return await response.json();
304
+ }
305
+ if (isRetryableStatus(response.status) && attempt < maxRetries) {
306
+ await delay(retryDelayMs(response, attempt, this.retryBaseDelayMs), this.signal);
307
+ continue;
308
+ }
309
+ const message = (await response.text()).split(token).join("[REDACTED]");
310
+ throw new Error(
311
+ `databricks: ${response.status} ${response.statusText}: ${message.slice(0, 2e3)}`
312
+ );
313
+ }
314
+ }
315
+ };
316
+ }
317
+ });
318
+
180
319
  // src/workspace-context.ts
181
320
  var workspace_context_exports = {};
182
321
  __export(workspace_context_exports, {
@@ -212,11 +351,10 @@ async function createWorkspaceContext(options = {}) {
212
351
  const state = submitted.status?.state;
213
352
  let terminal = submitted;
214
353
  if (state !== "SUCCEEDED" && state !== "FAILED" && state !== "CANCELED" && state !== "CLOSED") {
215
- terminal = await databricks.waitForDatabricksStatement(
216
- client,
217
- submitted.statement_id ?? "",
218
- { pollIntervalMs: options.pollIntervalMs, timeoutMs: options.timeoutMs }
219
- );
354
+ terminal = await waitForDatabricksStatement(client, submitted.statement_id ?? "", {
355
+ pollIntervalMs: options.pollIntervalMs,
356
+ timeoutMs: options.timeoutMs
357
+ });
220
358
  }
221
359
  if (terminal.status?.state !== "SUCCEEDED") {
222
360
  throw new Error(
@@ -332,7 +470,7 @@ function resolveWarehouseId(env) {
332
470
  function createClientFromEnv(env, fetchImpl) {
333
471
  const host = env.DATABRICKS_HOST;
334
472
  if (!host) throw new Error("Live profile requires DATABRICKS_HOST");
335
- return new databricks.DatabricksRestClient({
473
+ return new WorkspaceRestClient({
336
474
  host,
337
475
  token: resolveTokenProvider(host, env, fetchImpl),
338
476
  ...fetchImpl ? { fetchImpl } : {}
@@ -423,6 +561,7 @@ var NUMERIC_TYPES;
423
561
  var init_workspace_context = __esm({
424
562
  "src/workspace-context.ts"() {
425
563
  init_fixtures();
564
+ init_workspace_client();
426
565
  NUMERIC_TYPES = /^(INT|LONG|SHORT|BYTE|FLOAT|DOUBLE|DECIMAL)/i;
427
566
  }
428
567
  });
@@ -751,6 +890,7 @@ function abortableDelay(ms, signal) {
751
890
  }
752
891
 
753
892
  // src/live-checks.ts
893
+ init_workspace_client();
754
894
  function sqlRoundTripCheck() {
755
895
  return {
756
896
  id: "sql",
@@ -839,7 +979,7 @@ function jobRunCheck() {
839
979
  const submitted = await client.post("/api/2.1/jobs/run-now", {
840
980
  job_id: jobId
841
981
  });
842
- const run = await databricks.waitForDatabricksRun(client, submitted.run_id, {
982
+ const run = await waitForDatabricksRun(client, submitted.run_id, {
843
983
  timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
844
984
  pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
845
985
  });
@@ -895,7 +1035,7 @@ function notebookSubmitCheck() {
895
1035
  ]
896
1036
  });
897
1037
  if (!submitted.run_id) throw new Error("Notebook submit returned no run_id");
898
- const run = await databricks.waitForDatabricksRun(client, submitted.run_id, {
1038
+ const run = await waitForDatabricksRun(client, submitted.run_id, {
899
1039
  timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
900
1040
  pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
901
1041
  });
@@ -963,7 +1103,7 @@ function dbtBuildCheck() {
963
1103
  request
964
1104
  );
965
1105
  if (!submitted.run_id) throw new Error("dbt submit returned no run_id");
966
- const run = await databricks.waitForDatabricksRun(client, submitted.run_id, {
1106
+ const run = await waitForDatabricksRun(client, submitted.run_id, {
967
1107
  timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
968
1108
  pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
969
1109
  });
@@ -1130,7 +1270,7 @@ async function waitForOperation(client, initial, options) {
1130
1270
  while (!operation.done) {
1131
1271
  if (!operation.name) throw new Error("Lakebase operation did not return an operation name");
1132
1272
  if (Date.now() >= deadline) throw new Error(`Lakebase operation '${operation.name}' timed out`);
1133
- await delay(options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
1273
+ await delay2(options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
1134
1274
  operation = await client.get(`/api/2.0/postgres/${operation.name}`);
1135
1275
  }
1136
1276
  if (operation.error) {
@@ -1140,7 +1280,7 @@ async function waitForOperation(client, initial, options) {
1140
1280
  }
1141
1281
  return operation.response;
1142
1282
  }
1143
- function delay(ms) {
1283
+ function delay2(ms) {
1144
1284
  return new Promise((resolve2) => setTimeout(resolve2, ms));
1145
1285
  }
1146
1286
 
@@ -1322,6 +1462,9 @@ function autoLoaderIngestionCheck(options = {}) {
1322
1462
  }
1323
1463
  };
1324
1464
  }
1465
+
1466
+ // src/resilience-checks.ts
1467
+ init_workspace_client();
1325
1468
  function safeName(prefix) {
1326
1469
  return `${prefix}_${crypto.randomUUID().replaceAll("-", "_")}`;
1327
1470
  }
@@ -1516,7 +1659,7 @@ function classicComputeCheck() {
1516
1659
  ]
1517
1660
  });
1518
1661
  if (!submitted.run_id) throw new Error("Classic compute submit returned no run_id");
1519
- const run = await databricks.waitForDatabricksRun(client, submitted.run_id, {
1662
+ const run = await waitForDatabricksRun(client, submitted.run_id, {
1520
1663
  timeoutMs: Number(env.DBX_TEST_CLASSIC_TIMEOUT_MS ?? 18e5),
1521
1664
  pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 15e3)
1522
1665
  });
@@ -1841,6 +1984,9 @@ function createFoundationTargetPacks() {
1841
1984
  ];
1842
1985
  }
1843
1986
  var foundationTargetPacks = createFoundationTargetPacks();
1987
+
1988
+ // src/jobs-orchestration.ts
1989
+ init_workspace_client();
1844
1990
  var DatabricksJobsAdapter = class {
1845
1991
  constructor(client) {
1846
1992
  this.client = client;
@@ -1864,7 +2010,7 @@ var DatabricksJobsAdapter = class {
1864
2010
  return normalizeJobRun(runId, raw);
1865
2011
  }
1866
2012
  async wait(runId, options = {}) {
1867
- const raw = await databricks.waitForDatabricksRun(this.client, runId, {
2013
+ const raw = await waitForDatabricksRun(this.client, runId, {
1868
2014
  timeoutMs: options.timeoutMs,
1869
2015
  pollIntervalMs: options.pollIntervalMs
1870
2016
  });
@@ -1885,7 +2031,7 @@ var DatabricksJobsAdapter = class {
1885
2031
  `Databricks run ${runId} reached ${run.lifeCycleState}/${run.resultState} before it could be canceled`
1886
2032
  );
1887
2033
  }
1888
- await delay2(pollIntervalMs);
2034
+ await delay3(pollIntervalMs);
1889
2035
  }
1890
2036
  throw new Error(`Databricks run ${runId} did not become active within ${timeoutMs}ms`);
1891
2037
  }
@@ -1907,7 +2053,7 @@ var DatabricksJobsAdapter = class {
1907
2053
  const run = await this.get(runId);
1908
2054
  const repair = run.repairs.find((candidate) => candidate.repairId === repairId);
1909
2055
  if (repair && isTerminal(repair.lifeCycleState)) return run;
1910
- await delay2(pollIntervalMs);
2056
+ await delay3(pollIntervalMs);
1911
2057
  }
1912
2058
  throw new Error(
1913
2059
  `Databricks repair ${repairId} for run ${runId} timed out after ${timeoutMs}ms`
@@ -2098,7 +2244,7 @@ function normalizeJobRun(fallbackRunId, raw) {
2098
2244
  function isTerminal(state) {
2099
2245
  return ["TERMINATED", "SKIPPED", "INTERNAL_ERROR", "BLOCKED"].includes(state);
2100
2246
  }
2101
- function delay2(ms) {
2247
+ function delay3(ms) {
2102
2248
  return new Promise((resolve2) => setTimeout(resolve2, ms));
2103
2249
  }
2104
2250
  function objectOf(value) {
@@ -3216,14 +3362,9 @@ function createBuiltinTargetPacks() {
3216
3362
  }
3217
3363
  var builtinTargetPacks = createBuiltinTargetPacks();
3218
3364
 
3219
- Object.defineProperty(exports, "waitForDatabricksRun", {
3220
- enumerable: true,
3221
- get: function () { return databricks.waitForDatabricksRun; }
3222
- });
3223
- Object.defineProperty(exports, "waitForDatabricksStatement", {
3224
- enumerable: true,
3225
- get: function () { return databricks.waitForDatabricksStatement; }
3226
- });
3365
+ // src/index.ts
3366
+ init_workspace_client();
3367
+
3227
3368
  exports.DatabricksAdvancedWorkloadsAdapter = DatabricksAdvancedWorkloadsAdapter;
3228
3369
  exports.DatabricksJobsAdapter = DatabricksJobsAdapter;
3229
3370
  exports.advancedStreamingCdcCheck = advancedStreamingCdcCheck;
@@ -3318,6 +3459,8 @@ exports.uploadVolumeFile = uploadVolumeFile;
3318
3459
  exports.validateJobGraph = validateJobGraph;
3319
3460
  exports.vectorSearchCheck = vectorSearchCheck;
3320
3461
  exports.volumeIOCheck = volumeIOCheck;
3462
+ exports.waitForDatabricksRun = waitForDatabricksRun;
3463
+ exports.waitForDatabricksStatement = waitForDatabricksStatement;
3321
3464
  exports.waitForPipelineUpdate = waitForPipelineUpdate;
3322
3465
  //# sourceMappingURL=index.cjs.map
3323
3466
  //# sourceMappingURL=index.cjs.map