@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 +2 -2
- package/dist/{chunk-SCVVMUAF.js → chunk-MV752EAZ.js} +144 -10
- package/dist/chunk-MV752EAZ.js.map +1 -0
- package/dist/index.cjs +168 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -7
- package/dist/index.d.ts +18 -7
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/parity.cjs +124 -7
- package/dist/parity.cjs.map +1 -1
- package/dist/parity.js +1 -1
- package/dist/{workspace-context-VLBKMX3V.js → workspace-context-PCQAQSCO.js} +3 -3
- package/dist/{workspace-context-VLBKMX3V.js.map → workspace-context-PCQAQSCO.js.map} +1 -1
- package/package.json +1 -2
- package/dist/chunk-SCVVMUAF.js.map +0 -1
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,
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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-
|
|
242
|
-
//# sourceMappingURL=chunk-
|
|
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":";;;;AAwCO,IAAM,sBAAN,MAAyD;AAAA,EAC7C,IAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA,EAEjB,YAAY,OAAA,EAAiC;AAC3C,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA,CAAgB,OAAA,CAAQ,IAAI,CAAA;AACxC,IAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AACrB,IAAA,IAAA,CAAK,SAAA,GAAY,QAAQ,SAAA,IAAa,KAAA;AACtC,IAAA,IAAA,CAAK,UAAA,GAAa,QAAQ,UAAA,IAAc,CAAA;AACxC,IAAA,IAAA,CAAK,gBAAA,GAAmB,QAAQ,gBAAA,IAAoB,GAAA;AACpD,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AAAA,EACxB;AAAA,EAEA,GAAA,CAAO,MAAc,KAAA,EAA2E;AAC9F,IAAA,OAAO,IAAA,CAAK,OAAA,CAAW,KAAA,EAAO,IAAA,EAAM,QAAW,KAAK,CAAA;AAAA,EACtD;AAAA,EAEA,IAAA,CACE,IAAA,EACA,IAAA,EACA,KAAA,EACA,OAAA,EACY;AACZ,IAAA,OAAO,KAAK,OAAA,CAAW,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM,OAAO,OAAO,CAAA;AAAA,EAC3D;AAAA,EAEA,MAAc,OAAA,CACZ,MAAA,EACA,IAAA,EACA,IAAA,EACA,QAA+D,EAAC,EAChE,OAAA,GAAmC,EAAC,EACxB;AACZ,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,CAAA,EAAG,KAAK,IAAI,CAAA,EAAG,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,CAAA,CAAA,EAAI,IAAI,EAAE,CAAA,CAAE,CAAA;AAC7E,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAChD,MAAA,IAAI,KAAA,KAAU,QAAW,GAAA,CAAI,YAAA,CAAa,IAAI,GAAA,EAAK,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IAClE;AACA,IAAA,MAAM,uBAAA,GACJ,OAAO,IAAA,KAAS,QAAA,IAChB,SAAS,IAAA,IACT,OAAQ,KAAiC,iBAAA,KAAsB,QAAA;AACjE,IAAA,MAAM,QAAA,GACJ,OAAA,CAAQ,KAAA,KAAU,QAAA,IACjB,QAAQ,KAAA,KAAU,OAAA,KAChB,OAAA,CAAQ,KAAA,KAAU,UACjB,MAAA,KAAW,KAAA,IACX,OAAA,CAAQ,OAAA,CAAQ,cAAc,CAAA,IAC9B,uBAAA,CAAA;AACN,IAAA,MAAM,UAAA,GAAa,QAAA,GAAY,OAAA,CAAQ,UAAA,IAAc,KAAK,UAAA,GAAc,CAAA;AAExE,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,IAAK,OAAA,IAAW,CAAA,EAAG;AACpC,MAAA,MAAM,KAAA,GAAQ,OAAO,IAAA,CAAK,KAAA,KAAU,aAAa,MAAM,IAAA,CAAK,KAAA,EAAM,GAAI,IAAA,CAAK,KAAA;AAC3E,MAAA,IAAI,QAAA;AACJ,MAAA,IAAI;AACF,QAAA,QAAA,GAAW,MAAM,IAAA,CAAK,SAAA,CAAU,GAAA,EAAK;AAAA,UACnC,MAAA;AAAA,UACA,OAAA,EAAS;AAAA,YACP,aAAA,EAAe,UAAU,KAAK,CAAA,CAAA;AAAA,YAC9B,GAAI,IAAA,KAAS,KAAA,CAAA,GAAY,EAAE,cAAA,EAAgB,kBAAA,KAAuB,EAAC;AAAA,YACnE,GAAI,QAAQ,cAAA,GAAiB,EAAE,mBAAmB,OAAA,CAAQ,cAAA,KAAmB;AAAC,WAChF;AAAA,UACA,GAAI,IAAA,KAAS,KAAA,CAAA,GAAY,EAAE,IAAA,EAAM,KAAK,SAAA,CAAU,IAAI,CAAA,EAAE,GAAI,EAAC;AAAA,UAC3D,GAAI,KAAK,MAAA,GAAS,EAAE,QAAQ,IAAA,CAAK,MAAA,KAAW;AAAC,SAC9C,CAAA;AAAA,MACH,SAAS,KAAA,EAAO;AACd,QAAA,IAAI,IAAA,CAAK,MAAA,EAAQ,OAAA,IAAW,OAAA,IAAW,YAAY,MAAM,KAAA;AACzD,QAAA,MAAM,MAAM,IAAA,CAAK,gBAAA,GAAmB,CAAA,IAAK,OAAA,EAAS,KAAK,MAAM,CAAA;AAC7D,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,SAAS,EAAA,EAAI;AACf,QAAA,IAAI,QAAA,CAAS,MAAA,KAAW,GAAA,EAAK,OAAO,MAAA;AACpC,QAAA,OAAQ,MAAM,SAAS,IAAA,EAAK;AAAA,MAC9B;AACA,MAAA,IAAI,iBAAA,CAAkB,QAAA,CAAS,MAAM,CAAA,IAAK,UAAU,UAAA,EAAY;AAC9D,QAAA,MAAM,KAAA,CAAM,aAAa,QAAA,EAAU,OAAA,EAAS,KAAK,gBAAgB,CAAA,EAAG,KAAK,MAAM,CAAA;AAC/E,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAA,CAAW,MAAM,QAAA,CAAS,IAAA,IAAQ,KAAA,CAAM,KAAK,CAAA,CAAE,IAAA,CAAK,YAAY,CAAA;AACtE,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,YAAA,EAAe,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,QAAA,CAAS,UAAU,CAAA,EAAA,EAAK,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,GAAI,CAAC,CAAA;AAAA,OAClF;AAAA,IACF;AAAA,EACF;AACF,CAAA;AAEA,eAAsB,oBAAA,CACpB,MAAA,EACA,KAAA,EACA,OAAA,GAAgC,EAAC,EACC;AAClC,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,IAAK,QAAQ,SAAA,IAAa,GAAA,CAAA;AACpD,EAAA,MAAM,QAAA,GAAW,QAAQ,cAAA,IAAkB,GAAA;AAC3C,EAAA,IAAI,OAAA,GAAU,MAAM,MAAA,CAAO,GAAA,CAA6B,wBAAA,EAA0B;AAAA,IAChF,MAAA,EAAQ;AAAA,GACT,CAAA;AACD,EAAA,OAAO,CAAC,aAAA,CAAc,OAAO,KAAK,IAAA,CAAK,GAAA,KAAQ,QAAA,EAAU;AACvD,IAAA,MAAM,KAAA,CAAM,QAAA,EAAU,OAAA,CAAQ,MAAM,CAAA;AACpC,IAAA,OAAA,GAAU,MAAM,MAAA,CAAO,GAAA,CAA6B,wBAAA,EAA0B;AAAA,MAC5E,MAAA,EAAQ;AAAA,KACT,CAAA;AAAA,EACH;AACA,EAAA,OAAO,OAAA;AACT;AAEA,eAAsB,0BAAA,CACpB,MAAA,EACA,WAAA,EACA,OAAA,GAAgC,EAAC,EACC;AAClC,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,IAAK,QAAQ,SAAA,IAAa,GAAA,CAAA;AACpD,EAAA,MAAM,QAAA,GAAW,QAAQ,cAAA,IAAkB,GAAA;AAC3C,EAAA,MAAM,IAAA,GAAO,CAAA,wBAAA,EAA2B,kBAAA,CAAmB,WAAW,CAAC,CAAA,CAAA;AACvE,EAAA,IAAI,OAAA,GAAU,MAAM,MAAA,CAAO,GAAA,CAA6B,IAAI,CAAA;AAC5D,EAAA,OAAO,CAAC,mBAAA,CAAoB,OAAO,KAAK,IAAA,CAAK,GAAA,KAAQ,QAAA,EAAU;AAC7D,IAAA,MAAM,KAAA,CAAM,QAAA,EAAU,OAAA,CAAQ,MAAM,CAAA;AACpC,IAAA,OAAA,GAAU,MAAM,MAAA,CAAO,GAAA,CAA6B,IAAI,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,gBAAgB,IAAA,EAAsB;AAC7C,EAAA,MAAM,KAAA,GAAQ,KAAK,IAAA,EAAK;AACxB,EAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,MAAM,4CAA4C,CAAA;AACxE,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,KAAA,CAAM,QAAA,CAAS,KAAK,CAAA,GAAI,KAAA,GAAQ,CAAA,QAAA,EAAW,KAAK,CAAA,CAAE,CAAA;AACtE,EAAA,IAAI,GAAA,CAAI,QAAA,IAAY,GAAA,CAAI,QAAA,EAAU;AAChC,IAAA,MAAM,IAAI,MAAM,yDAAyD,CAAA;AAAA,EAC3E;AACA,EAAA,OAAO,GAAA,CAAI,MAAA;AACb;AAEA,SAAS,kBAAkB,MAAA,EAAyB;AAClD,EAAA,OAAO,WAAW,GAAA,IAAO,MAAA,KAAW,GAAA,IAAQ,MAAA,IAAU,OAAO,MAAA,IAAU,GAAA;AACzE;AAEA,SAAS,YAAA,CAAa,QAAA,EAAoB,OAAA,EAAiB,IAAA,EAAsB;AAC/E,EAAA,MAAM,gBAAA,GAAmB,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAA;AAC3D,EAAA,MAAM,aAAa,gBAAA,KAAqB,IAAA,GAAO,MAAA,CAAO,GAAA,GAAM,OAAO,gBAAgB,CAAA;AACnF,EAAA,OAAO,MAAA,CAAO,SAAS,UAAU,CAAA,IAAK,cAAc,CAAA,GAAI,UAAA,GAAa,GAAA,GAAO,IAAA,GAAO,CAAA,IAAK,OAAA;AAC1F;AAEA,SAAS,cAAc,GAAA,EAAuC;AAC5D,EAAA,MAAM,QAAQ,GAAA,CAAI,KAAA;AAClB,EAAA,OAAO,CAAC,cAAc,SAAA,EAAW,gBAAgB,EAAE,QAAA,CAAS,KAAA,EAAO,oBAAoB,EAAE,CAAA;AAC3F;AAEA,SAAS,oBAAoB,SAAA,EAA6C;AACxE,EAAA,MAAM,SAAS,SAAA,CAAU,MAAA;AACzB,EAAA,OAAO,CAAC,aAAa,QAAA,EAAU,UAAA,EAAY,QAAQ,CAAA,CAAE,QAAA,CAAS,MAAA,EAAQ,KAAA,IAAS,EAAE,CAAA;AACnF;AAEA,SAAS,KAAA,CAAM,IAAY,MAAA,EAAqC;AAC9D,EAAA,IAAI,CAAC,MAAA,EAAQ,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AACpE,EAAA,IAAI,OAAO,OAAA,EAAS;AAClB,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,MACb,MAAA,CAAO,MAAA,IAAU,IAAI,YAAA,CAAa,6BAA6B,YAAY;AAAA,KAC7E;AAAA,EACF;AACA,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,IAAA,EAAM,EAAE,CAAA;AACjC,IAAA,MAAA,CAAO,iBAAiB,OAAA,EAAS,OAAA,EAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AACxD,IAAA,SAAS,IAAA,GAAO;AACd,MAAA,MAAA,EAAQ,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAC5C,MAAA,OAAA,EAAQ;AAAA,IACV;AACA,IAAA,SAAS,OAAA,GAAU;AACjB,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,MAAA,CAAO,QAAQ,MAAA,IAAU,IAAI,YAAA,CAAa,2BAAA,EAA6B,YAAY,CAAC,CAAA;AAAA,IACtF;AAAA,EACF,CAAC,CAAA;AACH;;;AC/KA,eAAsB,sBAAA,CACpB,OAAA,GAAmC,EAAC,EACT;AAC3B,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,IAAO,OAAA,CAAQ,GAAA;AACnC,EAAA,MAAM,WAAA,GAAc,mBAAmB,GAAG,CAAA;AAC1C,EAAA,MAAM,UAAU,GAAA,CAAI,gBAAA;AACpB,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,IAAU,GAAA,CAAI,eAAA;AACrC,EAAA,MAAM,SAAS,OAAA,CAAQ,MAAA,IAAU,mBAAA,CAAoB,GAAA,EAAK,QAAQ,SAAS,CAAA;AAC3E,EAAA,MAAM,aAAA,uBAAoB,GAAA,EAAY;AAEtC,EAAA,MAAM,OAAO,CAAC,KAAA,KACZ,CAAC,OAAA,EAAS,QAAQ,KAAK,CAAA,CAAE,MAAA,CAAO,CAAC,SAAyB,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAE,KAAK,GAAG,CAAA;AAEnF,EAAA,eAAe,GAAA,CAAI,KAAa,MAAA,EAAyD;AACvF,IAAA,MAAM,EAAE,WAAW,UAAA,EAAW,GAAI,kBAAkB,GAAA,EAAK,MAAA,IAAU,EAAE,CAAA;AACrE,IAAA,MAAM,SAAA,GAAY,MAAM,MAAA,CAAO,IAAA,CAAwB,yBAAA,EAA2B;AAAA,MAChF,SAAA;AAAA,MACA,YAAA,EAAc,WAAA;AAAA,MACd,GAAI,OAAA,GAAU,EAAE,OAAA,KAAY,EAAC;AAAA,MAC7B,GAAI,MAAA,GAAS,EAAE,MAAA,KAAW,EAAC;AAAA,MAC3B,GAAI,UAAA,CAAW,MAAA,GAAS,IAAI,EAAE,UAAA,KAAe,EAAC;AAAA,MAC9C,YAAA,EAAc,KAAA;AAAA,MACd,eAAA,EAAiB;AAAA,KAClB,CAAA;AACD,IAAA,OAAA,CAAQ,WAAA,GAAc;AAAA,MACpB,GAAA;AAAA,MACA,GAAI,UAAU,YAAA,GAAe,EAAE,aAAa,SAAA,CAAU,YAAA,KAAiB;AAAC,KACzE,CAAA;AACD,IAAA,MAAM,KAAA,GAAQ,UAAU,MAAA,EAAQ,KAAA;AAChC,IAAA,IAAI,QAAA,GAAW,SAAA;AACf,IAAA,IAAI,UAAU,WAAA,IAAe,KAAA,KAAU,YAAY,KAAA,KAAU,UAAA,IAAc,UAAU,QAAA,EAAU;AAC7F,MAAA,QAAA,GAAY,MAAM,0BAAA,CAA2B,MAAA,EAAQ,SAAA,CAAU,gBAAgB,EAAA,EAAI;AAAA,QACjF,gBAAgB,OAAA,CAAQ,cAAA;AAAA,QACxB,WAAW,OAAA,CAAQ;AAAA,OACpB,CAAA;AAAA,IACH;AACA,IAAA,IAAI,QAAA,CAAS,MAAA,EAAQ,KAAA,KAAU,WAAA,EAAa;AAC1C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,qBAAA,EAAwB,QAAA,CAAS,MAAA,EAAQ,KAAA,IAAS,SAAS,CAAA,EAAA,EACzD,QAAA,CAAS,MAAA,EAAQ,KAAA,EAAO,OAAA,IAAW,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,GAAG,CACrD,CAAA;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,MAAA;AAAA,IACT,IAAA;AAAA,IACA,MAAM,IAAA,CAAK,GAAA,EAAK,MAAA,EAAQ;AACtB,MAAA,MAAM,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,IACvB,CAAA;AAAA,IACA,MAAM,KAAA,CAAM,GAAA,EAAK,MAAA,EAAQ;AACvB,MAAA,MAAM,QAAA,GAAW,MAAM,GAAA,CAAI,GAAA,EAAK,MAAM,CAAA;AACtC,MAAA,OAAO,mBAAmB,QAAQ,CAAA;AAAA,IACpC,CAAA;AAAA,IACA,MAAM,YAAY,OAAA,EAAuB;AACvC,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SACF;AAAA,MACF;AACA,MAAA,IAAI,CAAC,QAAQ,MAAA,EAAQ;AACnB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,8CAAA,EAAiD,QAAQ,KAAK,CAAA,iCAAA;AAAA,SAChE;AAAA,MACF;AACA,MAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,OAAA,CAAQ,MAAM,CAAA;AAClD,MAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAM,CAAA,EAAA,EAAK,CAAA,CAAE,IAAI,CAAA,GAAA,EAAM,CAAA,CAAE,IAAI,CAAA,CAAE,CAAA,CAAE,KAAK,IAAI,CAAA;AACnE,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA;AACjC,MAAA,MAAM,GAAA,CAAI,CAAA,wBAAA,EAA2B,MAAM,CAAA,EAAA,EAAK,GAAG,CAAA,aAAA,CAAe,CAAA;AAClE,MAAA,aAAA,CAAc,IAAI,MAAM,CAAA;AACxB,MAAA,MAAM,IAAA,GAAO,MAAM,kBAAA,CAAmB,OAAA,EAAS,OAAO,CAAA;AACtD,MAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,QAAA,MAAM,YAAA,GAAe,OAAA,CAClB,GAAA,CAAI,CAAC,MAAO,YAAA,CAAa,IAAA,CAAK,CAAA,CAAE,IAAI,CAAA,GAAI,sBAAA,GAAyB,GAAI,CAAA,CACrE,KAAK,IAAI,CAAA;AACZ,QAAA,MAAM,GAAA,CAAI,eAAe,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAC,CAAA,SAAA,EAAY,YAAY,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA;AAAA,MAC9E;AAAA,IACF,CAAA;AAAA,IACA,MAAM,KAAA,GAAQ;AACZ,MAAA,IAAI,OAAA,CAAQ,eAAA,KAAoB,KAAA,IAAS,GAAA,CAAI,2BAA2B,GAAA,EAAK;AAC3E,QAAA,KAAA,MAAW,SAAS,CAAC,GAAG,aAAa,CAAA,CAAE,SAAQ,EAAG;AAChD,UAAA,MAAM,GAAA,CAAI,CAAA,qBAAA,EAAwB,KAAK,CAAA,CAAE,CAAA;AAAA,QAC3C;AAAA,MACF;AACA,MAAA,aAAA,CAAc,KAAA,EAAM;AAAA,IACtB;AAAA,GACF;AACF;AAGO,SAAS,iBAAA,CACd,KACA,MAAA,EAC6E;AAC7E,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,IAAI,KAAA,GAAuB,IAAA;AAC3B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,IAAA,MAAM,EAAA,GAAK,IAAI,CAAC,CAAA;AAChB,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,GAAA,IAAO,EAAA;AACP,MAAA,IAAI,EAAA,KAAO,OAAO,KAAA,GAAQ,IAAA;AAC1B,MAAA;AAAA,IACF;AACA,IAAA,IAAI,EAAA,KAAO,GAAA,IAAO,EAAA,KAAO,GAAA,IAAO,OAAO,GAAA,EAAK;AAC1C,MAAA,KAAA,GAAQ,EAAA;AACR,MAAA,GAAA,IAAO,EAAA;AACP,MAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAO,GAAA,EAAK;AACd,MAAA,KAAA,IAAS,CAAA;AACT,MAAA,GAAA,IAAO,KAAK,KAAK,CAAA,CAAA;AACjB,MAAA;AAAA,IACF;AACA,IAAA,GAAA,IAAO,EAAA;AAAA,EACT;AACA,EAAA,IAAI,KAAA,KAAU,OAAO,MAAA,EAAQ;AAC3B,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,KAAK,CAAA,kBAAA,EAAqB,MAAA,CAAO,MAAM,CAAA,qBAAA,CAAuB,CAAA;AAAA,EAC3F;AACA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,GAAA;AAAA,IACX,UAAA,EAAY,MAAA,CAAO,GAAA,CAAI,CAAC,OAAO,CAAA,MAAO;AAAA,MACpC,IAAA,EAAM,CAAA,CAAA,EAAI,CAAA,GAAI,CAAC,CAAA,CAAA;AAAA,MACf,OAAO,KAAA,KAAU,IAAA,IAAQ,UAAU,MAAA,GAAY,IAAA,GAAO,OAAO,KAAK;AAAA,KACpE,CAAE;AAAA,GACJ;AACF;AAGO,SAAS,mBAAmB,QAAA,EAAwD;AACzF,EAAA,MAAM,OAAA,GAAU,CAAC,GAAI,QAAA,CAAS,UAAU,MAAA,EAAQ,OAAA,IAAW,EAAG,CAAA,CAAE,IAAA;AAAA,IAC9D,CAAC,CAAA,EAAG,CAAA,KAAA,CAAO,EAAE,QAAA,IAAY,CAAA,KAAM,EAAE,QAAA,IAAY,CAAA;AAAA,GAC/C;AACA,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,MAAA,EAAQ,UAAA,IAAc,EAAC;AAC7C,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,KAAA,KAAU;AACzB,IAAA,MAAM,MAA+B,EAAC;AACtC,IAAA,OAAA,CAAQ,OAAA,CAAQ,CAAC,MAAA,EAAQ,CAAA,KAAM;AAC7B,MAAA,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,GAAI,YAAA,CAAa,KAAA,CAAM,CAAC,CAAA,IAAK,IAAA,EAAM,MAAA,CAAO,SAAA,IAAa,QAAQ,CAAA;AAAA,IAChF,CAAC,CAAA;AACD,IAAA,OAAO,GAAA;AAAA,EACT,CAAC,CAAA;AACH;AAEA,IAAM,aAAA,GAAgB,8CAAA;AAEtB,SAAS,YAAA,CAAa,OAAsB,QAAA,EAA2B;AACrE,EAAA,IAAI,KAAA,KAAU,MAAM,OAAO,IAAA;AAC3B,EAAA,IAAI,cAAc,IAAA,CAAK,QAAQ,CAAA,EAAG,OAAO,OAAO,KAAK,CAAA;AACrD,EAAA,IAAI,WAAA,CAAY,IAAA,CAAK,QAAQ,CAAA,SAAU,KAAA,KAAU,MAAA;AACjD,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,mBAAmB,GAAA,EAAiD;AAC3E,EAAA,IAAI,GAAA,CAAI,uBAAA,EAAyB,OAAO,GAAA,CAAI,uBAAA;AAC5C,EAAA,MAAM,WAAW,GAAA,CAAI,oBAAA;AACrB,EAAA,MAAM,KAAA,GAAQ,QAAA,EAAU,KAAA,CAAM,wBAAwB,CAAA;AACtD,EAAA,IAAI,KAAA,GAAQ,CAAC,CAAA,EAAG,OAAO,MAAM,CAAC,CAAA;AAC9B,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,GACF;AACF;AAOO,SAAS,mBAAA,CACd,KACA,SAAA,EACqB;AACrB,EAAA,MAAM,OAAO,GAAA,CAAI,eAAA;AACjB,EAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,uCAAuC,CAAA;AAClE,EAAA,OAAO,IAAI,mBAAA,CAAoB;AAAA,IAC7B,IAAA;AAAA,IACA,KAAA,EAAO,oBAAA,CAAqB,IAAA,EAAM,GAAA,EAAK,SAAS,CAAA;AAAA,IAChD,GAAI,SAAA,GAAY,EAAE,SAAA,KAAc;AAAC,GAClC,CAAA;AACH;AAEO,SAAS,oBAAA,CACd,IAAA,EACA,GAAA,EACA,SAAA,GAA0B,KAAA,EACQ;AAClC,EAAA,IAAI,GAAA,CAAI,iBAAA,EAAmB,OAAO,GAAA,CAAI,iBAAA;AACtC,EAAA,MAAM,WAAW,GAAA,CAAI,oBAAA;AACrB,EAAA,IAAI,QAAA,KAAa,UAAA,IAAc,QAAA,KAAa,WAAA,EAAa;AACvD,IAAA,IAAI,MAAA,GAAsD,IAAA;AAC1D,IAAA,OAAO,YAAY;AACjB,MAAA,IAAI,UAAU,IAAA,CAAK,GAAA,KAAQ,MAAA,CAAO,SAAA,SAAkB,MAAA,CAAO,KAAA;AAC3D,MAAA,MAAM,YAAA,GAAe,MAAM,4BAAA,CAA6B,QAAA,EAAU,GAAG,CAAA;AACrE,MAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB;AAAA,QACjC,UAAA,EAAY,iDAAA;AAAA,QACZ,aAAA,EAAe,YAAA;AAAA,QACf,kBAAA,EAAoB,sCAAA;AAAA,QACpB,KAAA,EAAO,IAAI,gBAAA,IAAoB;AAAA,OAChC,CAAA;AACD,MAAA,IAAI,IAAI,oBAAA,EAAsB,MAAA,CAAO,GAAA,CAAI,WAAA,EAAa,IAAI,oBAAoB,CAAA;AAC9E,MAAA,MAAM,QAAA,GAAW,MAAM,SAAA,CAAU,CAAA,EAAG,KAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,cAAA,CAAA,EAAkB;AAAA,QAC3E,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,mCAAA,EAAoC;AAAA,QAC/D,IAAA,EAAM,OAAO,QAAA;AAAS,OACvB,CAAA;AACD,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,uCAAA,EAA0C,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,MAC7E;AACA,MAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,MAAA,IAAI,OAAO,IAAA,CAAK,YAAA,KAAiB,QAAA,IAAY,CAAC,KAAK,YAAA,EAAc;AAC/D,QAAA,MAAM,IAAI,MAAM,yDAAyD,CAAA;AAAA,MAC3E;AACA,MAAA,MAAM,YAAY,OAAO,IAAA,CAAK,UAAA,KAAe,QAAA,GAAW,KAAK,UAAA,GAAa,IAAA;AAC1E,MAAA,MAAA,GAAS;AAAA,QACP,OAAO,IAAA,CAAK,YAAA;AAAA,QACZ,SAAA,EAAW,KAAK,GAAA,EAAI,GAAI,KAAK,GAAA,CAAI,CAAA,EAAG,SAAA,GAAY,GAAG,CAAA,GAAI;AAAA,OACzD;AACA,MAAA,OAAO,IAAA,CAAK,YAAA;AAAA,IACd,CAAA;AAAA,EACF;AACA,EAAA,MAAM,WAAW,GAAA,CAAI,oBAAA;AACrB,EAAA,MAAM,eAAe,GAAA,CAAI,wBAAA;AACzB,EAAA,IAAI,YAAY,YAAA,EAAc;AAC5B,IAAA,IAAI,MAAA,GAAsD,IAAA;AAC1D,IAAA,OAAO,YAAY;AACjB,MAAA,IAAI,UAAU,IAAA,CAAK,GAAA,KAAQ,MAAA,CAAO,SAAA,SAAkB,MAAA,CAAO,KAAA;AAC3D,MAAA,MAAM,QAAA,GAAW,MAAM,SAAA,CAAU,CAAA,EAAG,KAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,cAAA,CAAA,EAAkB;AAAA,QAC3E,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB,mCAAA;AAAA,UAChB,aAAA,EAAe,CAAA,MAAA,EAAS,MAAA,CAAO,IAAA,CAAK,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,YAAY,CAAA,CAAE,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAC,CAAA;AAAA,SACvF;AAAA,QACA,IAAA,EAAM,IAAI,eAAA,CAAgB;AAAA,UACxB,UAAA,EAAY,oBAAA;AAAA,UACZ,KAAA,EAAO,IAAI,gBAAA,IAAoB;AAAA,SAChC,EAAE,QAAA;AAAS,OACb,CAAA;AACD,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iCAAA,EAAoC,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,MACvE;AACA,MAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAClC,MAAA,MAAA,GAAS;AAAA,QACP,OAAO,IAAA,CAAK,YAAA;AAAA,QACZ,WAAW,IAAA,CAAK,GAAA,OAAU,IAAA,CAAK,UAAA,IAAc,QAAQ,GAAA,IAAO;AAAA,OAC9D;AACA,MAAA,OAAO,IAAA,CAAK,YAAA;AAAA,IACd,CAAA;AAAA,EACF;AACA,EAAA,IAAI,GAAA,CAAI,gBAAA,EAAkB,OAAO,GAAA,CAAI,gBAAA;AACrC,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,GACF;AACF;AAEA,eAAe,4BAAA,CACb,UACA,GAAA,EACiB;AACjB,EAAA,IAAI,aAAa,UAAA,EAAY;AAC3B,IAAA,MAAM,QAAA,GAAW,IAAI,yBAAA,IAA6B,uBAAA;AAClD,IAAA,MAAMA,MAAAA,GAAQ,GAAA,CAAI,QAAQ,CAAA,EAAG,IAAA,EAAK;AAClC,IAAA,IAAI,CAACA,MAAAA,EAAO,MAAM,IAAI,KAAA,CAAM,CAAA,0CAAA,EAA6C,QAAQ,CAAA,SAAA,CAAW,CAAA;AAC5F,IAAA,OAAOA,MAAAA;AAAA,EACT;AACA,EAAA,MAAM,OAAO,GAAA,CAAI,8BAAA;AACjB,EAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,0DAA0D,CAAA;AACrF,EAAA,MAAM,SAAS,MAAM,QAAA,CAAS,IAAA,EAAM,MAAM,GAAG,IAAA,EAAK;AAClD,EAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,IAAI,CAAA,SAAA,CAAW,CAAA;AACxE,EAAA,OAAO,KAAA;AACT","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
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
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
|