@databricks/appkit 0.43.1 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CLAUDE.md +0 -1
  2. package/NOTICE.md +1 -0
  3. package/dist/appkit/package.js +1 -1
  4. package/dist/connectors/sql-warehouse/arrow-schema.js +283 -0
  5. package/dist/connectors/sql-warehouse/arrow-schema.js.map +1 -0
  6. package/dist/connectors/sql-warehouse/client.js +166 -60
  7. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  8. package/dist/errors/base.d.ts +18 -0
  9. package/dist/errors/base.d.ts.map +1 -1
  10. package/dist/errors/base.js +20 -0
  11. package/dist/errors/base.js.map +1 -1
  12. package/dist/errors/execution.d.ts +34 -2
  13. package/dist/errors/execution.d.ts.map +1 -1
  14. package/dist/errors/execution.js +40 -5
  15. package/dist/errors/execution.js.map +1 -1
  16. package/dist/plugins/agents/agents.d.ts.map +1 -1
  17. package/dist/plugins/agents/agents.js.map +1 -1
  18. package/dist/plugins/analytics/analytics.d.ts +84 -8
  19. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  20. package/dist/plugins/analytics/analytics.js +296 -61
  21. package/dist/plugins/analytics/analytics.js.map +1 -1
  22. package/dist/plugins/analytics/index.js +1 -1
  23. package/dist/plugins/analytics/result-delivery.js +287 -0
  24. package/dist/plugins/analytics/result-delivery.js.map +1 -0
  25. package/dist/plugins/analytics/types.d.ts +8 -0
  26. package/dist/plugins/analytics/types.d.ts.map +1 -1
  27. package/dist/plugins/analytics/types.js.map +1 -1
  28. package/dist/plugins/files/plugin.js +1 -1
  29. package/dist/plugins/genie/genie.js +1 -1
  30. package/dist/plugins/jobs/types.d.ts +1 -1
  31. package/dist/plugins/lakebase/lakebase.js +1 -1
  32. package/dist/shared/src/index.d.ts +1 -0
  33. package/dist/shared/src/sse/analytics.d.ts +1 -0
  34. package/dist/shared/src/sse/analytics.js +40 -0
  35. package/dist/shared/src/sse/analytics.js.map +1 -0
  36. package/dist/stream/arrow-stream-processor.js +93 -117
  37. package/dist/stream/arrow-stream-processor.js.map +1 -1
  38. package/dist/stream/defaults.js +1 -1
  39. package/dist/stream/defaults.js.map +1 -1
  40. package/dist/stream/sse-writer.js +3 -2
  41. package/dist/stream/sse-writer.js.map +1 -1
  42. package/dist/stream/stream-manager.d.ts.map +1 -1
  43. package/dist/stream/stream-manager.js +15 -9
  44. package/dist/stream/stream-manager.js.map +1 -1
  45. package/dist/stream/types.js.map +1 -1
  46. package/dist/type-generator/query-registry.js +32 -2
  47. package/dist/type-generator/query-registry.js.map +1 -1
  48. package/docs/api/appkit/Class.AppKitError.md +40 -8
  49. package/docs/api/appkit/Class.AuthenticationError.md +56 -16
  50. package/docs/api/appkit/Class.ConfigurationError.md +57 -17
  51. package/docs/api/appkit/Class.ConnectionError.md +57 -17
  52. package/docs/api/appkit/Class.ExecutionError.md +80 -22
  53. package/docs/api/appkit/Class.InitializationError.md +55 -15
  54. package/docs/api/appkit/Class.ServerError.md +55 -15
  55. package/docs/api/appkit/Class.TunnelError.md +56 -16
  56. package/docs/api/appkit/Class.ValidationError.md +55 -15
  57. package/docs/api/appkit-ui/ui/ContextMenu.md +2 -2
  58. package/docs/api/appkit-ui/ui/Drawer.md +0 -56
  59. package/docs/api/appkit-ui/ui/DropdownMenu.md +3 -3
  60. package/docs/api/appkit-ui/ui/HoverCard.md +2 -2
  61. package/docs/api/appkit-ui/ui/Menubar.md +3 -3
  62. package/docs/api/appkit-ui/ui/Popover.md +2 -2
  63. package/docs/api/appkit-ui/ui/Select.md +2 -2
  64. package/docs/api/appkit-ui/ui/Tooltip.md +2 -2
  65. package/llms.txt +0 -1
  66. package/package.json +2 -2
  67. package/sbom.cdx.json +1 -1
  68. package/docs/api/appkit-ui/ui/ChartContainer.md +0 -343
@@ -0,0 +1,287 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+ import { ExecutionError } from "../../errors/execution.js";
3
+ import "../../errors/index.js";
4
+ import { Type, tableFromIPC } from "apache-arrow";
5
+
6
+ //#region src/plugins/analytics/result-delivery.ts
7
+ /**
8
+ * Centralized disposition/format fallback for analytics result delivery.
9
+ *
10
+ * Two warehouse capability profiles must both work, and their supported
11
+ * combinations are mutually exclusive per warehouse:
12
+ *
13
+ * - **Reyden**: `ARROW_STREAM + INLINE` (and `JSON_ARRAY + INLINE`); does NOT
14
+ * support `EXTERNAL_LINKS`.
15
+ * - **Normal SQL warehouse**: `JSON_ARRAY + INLINE` and
16
+ * `ARROW_STREAM + EXTERNAL_LINKS`; rejects `ARROW_STREAM + INLINE`.
17
+ *
18
+ * The fallback logic lives here (rather than inline in the plugin route) so it
19
+ * can be unit-tested against a fake executor without HTTP, and so the brittle
20
+ * "which rejection is this" decision is expressed once.
21
+ */
22
+ const logger = createLogger("analytics:delivery");
23
+ /**
24
+ * Whether an error is a disposition/format *capability* rejection — the only
25
+ * class of error a fallback should react to. Gated on the two structured codes
26
+ * Databricks emits for capability mismatches (`INVALID_PARAMETER_VALUE`,
27
+ * `NOT_IMPLEMENTED`); auth, permission, and SQL errors carry other codes and
28
+ * never match. This is deliberately message-independent: a Databricks wording
29
+ * change must not turn a capability rejection into a hard 500.
30
+ */
31
+ function isCapabilityRejection(err) {
32
+ const structuredCode = err instanceof ExecutionError ? err.errorCode : void 0;
33
+ if (structuredCode === "INVALID_PARAMETER_VALUE" || structuredCode === "NOT_IMPLEMENTED") return true;
34
+ const lower = (err instanceof Error ? err.message : String(err)).toLowerCase();
35
+ return lower.includes("invalid_parameter_value") || lower.includes("not_implemented");
36
+ }
37
+ function classifyDispositionRejection(err) {
38
+ if (!isCapabilityRejection(err)) return null;
39
+ const msg = err instanceof Error ? err.message : String(err);
40
+ const lower = msg.toLowerCase();
41
+ if (/external[_\s]?links/.test(lower) && (lower.includes("not yet implemented") || lower.includes("not implemented") || lower.includes("not supported"))) return "external-links-unsupported";
42
+ if (!lower.includes("inline")) return null;
43
+ if (/only supports\s+arrow_stream/i.test(msg) || /must be\s+arrow_stream/i.test(msg)) return "needs-arrow-inline";
44
+ if (/only supports\s+json_array/i.test(msg) || /must be\s+json_array/i.test(msg) || /arrow_stream\s+(is\s+|was\s+)?not\s+supported/i.test(msg)) return "needs-json-inline";
45
+ return null;
46
+ }
47
+ /** Structured error: the warehouse supports no Arrow delivery mode at all. */
48
+ function arrowDeliveryUnsupported() {
49
+ return ExecutionError.statementFailed("Warehouse supports neither ARROW_STREAM+INLINE nor ARROW_STREAM+EXTERNAL_LINKS", "ARROW_DELIVERY_UNSUPPORTED", "This warehouse cannot return Arrow results. Re-run the query with format=\"JSON_ARRAY\".");
50
+ }
51
+ const errMessage = (err) => err instanceof Error ? err.message : String(err);
52
+ /**
53
+ * Produce the raw Arrow IPC bytes for an ARROW_STREAM query, applying the
54
+ * capability fallback:
55
+ *
56
+ * 1. `INLINE + ARROW_STREAM` — on success the base64 attachment is decoded
57
+ * once and yielded as a `Uint8Array` view (no Arrow parsing, no stash).
58
+ * 2. On a `needs-json-inline` rejection, `EXTERNAL_LINKS + ARROW_STREAM` —
59
+ * the pre-signed chunks resolved in the executor's own context are
60
+ * streamed one at a time.
61
+ * 3. If EXTERNAL_LINKS is also unsupported, a structured
62
+ * `ARROW_DELIVERY_UNSUPPORTED` error is thrown.
63
+ *
64
+ * `out` is populated with the real column names and statement id before the
65
+ * first chunk is yielded, so the caller can emit them (e.g. as a header)
66
+ * ahead of the body.
67
+ */
68
+ async function* deliverArrowBytes(executor, streamer, query, processedParams, out, signal, opts) {
69
+ if (opts?.capabilityHint !== "external") try {
70
+ const result = await executor.query(query, processedParams, {
71
+ disposition: "INLINE",
72
+ format: "ARROW_STREAM"
73
+ }, signal);
74
+ if (result?.attachment) {
75
+ opts?.onCapabilityResolved?.("inline");
76
+ out.columnNames = result.columnNames;
77
+ out.statementId = result.statement_id;
78
+ const decoded = Buffer.from(result.attachment, "base64");
79
+ yield new Uint8Array(decoded.buffer, decoded.byteOffset, decoded.byteLength);
80
+ return;
81
+ }
82
+ logger.warn("ARROW_STREAM INLINE returned no attachment; falling back to EXTERNAL_LINKS");
83
+ } catch (err) {
84
+ if (signal?.aborted) throw err;
85
+ if (!isCapabilityRejection(err)) throw err;
86
+ logger.warn("ARROW_STREAM INLINE rejected by warehouse, falling back to EXTERNAL_LINKS: %s", errMessage(err));
87
+ }
88
+ let ext;
89
+ try {
90
+ ext = await executor.query(query, processedParams, {
91
+ disposition: "EXTERNAL_LINKS",
92
+ format: "ARROW_STREAM"
93
+ }, signal);
94
+ } catch (err) {
95
+ if (signal?.aborted) throw err;
96
+ if (classifyDispositionRejection(err) === "external-links-unsupported") throw arrowDeliveryUnsupported();
97
+ throw err;
98
+ }
99
+ if (!ext?.external_links) throw ExecutionError.missingData("external_links");
100
+ opts?.onCapabilityResolved?.("external");
101
+ out.columnNames = ext.columnNames;
102
+ out.statementId = ext.statement_id;
103
+ yield* streamer.streamExternalLinks(ext.external_links, signal, ext.refreshChunkLink);
104
+ }
105
+ /**
106
+ * Produce JSON rows for a JSON_ARRAY query, applying the capability fallback:
107
+ *
108
+ * 1. `INLINE + JSON_ARRAY` — the native, common path.
109
+ * 2. On a `needs-arrow-inline` rejection (warehouse only accepts ARROW_STREAM
110
+ * for INLINE, e.g. Reyden), retry `INLINE + ARROW_STREAM` and decode the
111
+ * attachment to plain rows server-side so the caller's JSON contract holds.
112
+ *
113
+ * EXTERNAL_LINKS is never used for the JSON fallback.
114
+ */
115
+ async function deliverJsonResult(executor, query, processedParams, signal) {
116
+ try {
117
+ const result = await executor.query(query, processedParams, {
118
+ disposition: "INLINE",
119
+ format: "JSON_ARRAY"
120
+ }, signal);
121
+ return {
122
+ data: result?.data,
123
+ status: result?.status,
124
+ statement_id: result?.statement_id
125
+ };
126
+ } catch (err) {
127
+ if (signal?.aborted) throw err;
128
+ if (classifyDispositionRejection(err) !== "needs-arrow-inline") throw err;
129
+ logger.warn("JSON_ARRAY INLINE rejected by warehouse, retrying as ARROW_STREAM INLINE and decoding server-side: %s", errMessage(err));
130
+ }
131
+ const arrowResult = await executor.query(query, processedParams, {
132
+ disposition: "INLINE",
133
+ format: "ARROW_STREAM"
134
+ }, signal);
135
+ if (!arrowResult?.attachment) throw ExecutionError.missingData("ARROW_STREAM attachment");
136
+ return {
137
+ data: decodeArrowAttachmentToRows(arrowResult.attachment, arrowResult.columnNames),
138
+ status: arrowResult.status,
139
+ statement_id: arrowResult.statement_id
140
+ };
141
+ }
142
+ /**
143
+ * Hard caps on the server-side JSON_ARRAY fallback. The materializer builds
144
+ * every row as a plain JS object on the Node main thread (O(rows × cols)
145
+ * allocations), so a runaway result blocks the event loop and pressures GC.
146
+ * Cap on rows AND decoded bytes — either dimension can blow up independently.
147
+ */
148
+ const JSON_ARRAY_FALLBACK_MAX_ROWS = 1e5;
149
+ const JSON_ARRAY_FALLBACK_MAX_BYTES = 64 * 1024 * 1024;
150
+ /** Render an apache-arrow Decimal cell to a fixed-point string. */
151
+ function formatDecimalCell(value, scale) {
152
+ const unscaled = value.toString();
153
+ if (scale <= 0) return unscaled;
154
+ const negative = unscaled.startsWith("-");
155
+ let digits = negative ? unscaled.slice(1) : unscaled;
156
+ if (digits.length <= scale) digits = digits.padStart(scale + 1, "0");
157
+ const point = digits.length - scale;
158
+ const out = `${digits.slice(0, point)}.${digits.slice(point)}`;
159
+ return negative ? `-${out}` : out;
160
+ }
161
+ /**
162
+ * Render an apache-arrow Timestamp cell to ISO-8601 ms precision — `Z` for
163
+ * zoned columns, no `Z` for TIMESTAMP_NTZ — matching native JSON_ARRAY.
164
+ */
165
+ function formatTimestampCell(epochMs, hasTimezone) {
166
+ const iso = new Date(epochMs).toISOString();
167
+ return hasTimezone ? iso : iso.slice(0, -1);
168
+ }
169
+ /** Render an apache-arrow Date cell to `yyyy-MM-dd` (UTC). */
170
+ function formatDateCell(epochMs) {
171
+ return new Date(epochMs).toISOString().slice(0, 10);
172
+ }
173
+ /**
174
+ * Render a single scalar Arrow value to the string form the warehouse emits
175
+ * under native JSON_ARRAY. Used for both top-level cells and (recursively, via
176
+ * {@link renderNestedValue}) scalar leaves inside List/Struct/Map columns,
177
+ * keyed on the Arrow field type so a nested `DECIMAL` keeps its scale and a
178
+ * nested `TIMESTAMP` becomes ISO-8601 instead of raw epoch-ms.
179
+ */
180
+ function formatScalarCell(value, type) {
181
+ switch (type.typeId) {
182
+ case Type.Decimal: return formatDecimalCell(value, type.scale);
183
+ case Type.Timestamp: return formatTimestampCell(Number(value), type.timezone != null);
184
+ case Type.Date: return formatDateCell(Number(value));
185
+ default:
186
+ if (value instanceof Uint8Array) return Buffer.from(value).toString("base64");
187
+ if (value instanceof Date) return value.toISOString();
188
+ return String(value);
189
+ }
190
+ }
191
+ /**
192
+ * Recursively render a nested Arrow value (List / Struct / Map, or a scalar
193
+ * leaf) to the plain JS shape the warehouse nests inside a JSON_ARRAY cell:
194
+ * structs and maps become objects, lists become arrays, and every scalar leaf
195
+ * is stringified per {@link formatScalarCell}. The caller `JSON.stringify`s the
196
+ * result so nested columns match the warehouse's native `data_array` exactly.
197
+ */
198
+ function renderNestedValue(value, type) {
199
+ if (value == null) return null;
200
+ switch (type.typeId) {
201
+ case Type.List:
202
+ case Type.FixedSizeList: {
203
+ const itemType = type.children[0].type;
204
+ const out = [];
205
+ for (const item of value) out.push(renderNestedValue(item, itemType));
206
+ return out;
207
+ }
208
+ case Type.Struct: {
209
+ const fields = type.children;
210
+ const obj = {};
211
+ for (const field of fields) obj[field.name] = renderNestedValue(value[field.name], field.type);
212
+ return obj;
213
+ }
214
+ case Type.Map: {
215
+ const valueType = type.children[0].type.children[1].type;
216
+ const obj = {};
217
+ for (const [k, v] of value) obj[String(k)] = renderNestedValue(v, valueType);
218
+ return obj;
219
+ }
220
+ default: return formatScalarCell(value, type);
221
+ }
222
+ }
223
+ /** Parse a STRING cell that looks like JSON into an object/array. */
224
+ function maybeParseJsonString(value) {
225
+ if (value && (value[0] === "{" || value[0] === "[")) try {
226
+ return JSON.parse(value);
227
+ } catch {
228
+ return value;
229
+ }
230
+ return value;
231
+ }
232
+ /**
233
+ * Decode a base64 Arrow IPC attachment to plain row objects, matching what the
234
+ * warehouse emits natively under JSON_ARRAY so callers cannot tell which path
235
+ * served the query.
236
+ *
237
+ * The output key uses the manifest name (`columnNames[i]`) when available —
238
+ * Databricks encodes the Arrow schema positionally (col_0, …) — while the
239
+ * vector is still fetched by the Arrow field's own name.
240
+ */
241
+ function decodeArrowAttachmentToRows(attachment, columnNames) {
242
+ const decoded = Buffer.from(attachment, "base64");
243
+ if (decoded.byteLength > JSON_ARRAY_FALLBACK_MAX_BYTES) throw ExecutionError.statementFailed(`Result attachment is ${decoded.byteLength} bytes; JSON_ARRAY fallback materializer caps at ${JSON_ARRAY_FALLBACK_MAX_BYTES} bytes. Re-issue the query with format="ARROW_STREAM" to stream the full result.`, "RESULT_TOO_LARGE_FOR_JSON_FALLBACK", "Result too large for JSON format. Re-run with ARROW_STREAM format.");
244
+ const table = tableFromIPC(new Uint8Array(decoded.buffer, decoded.byteOffset, decoded.byteLength));
245
+ if (table.numRows > JSON_ARRAY_FALLBACK_MAX_ROWS) throw ExecutionError.statementFailed(`Result has ${table.numRows} rows; JSON_ARRAY fallback materializer caps at ${JSON_ARRAY_FALLBACK_MAX_ROWS}. Re-issue the query with format="ARROW_STREAM" to stream the full result.`, "RESULT_TOO_LARGE_FOR_JSON_FALLBACK", `Result too large for JSON format (over ${JSON_ARRAY_FALLBACK_MAX_ROWS} rows). Re-run with ARROW_STREAM format.`);
246
+ const columns = table.schema.fields.map((f, i) => {
247
+ return [
248
+ columnNames?.[i] && columnNames[i].length > 0 ? columnNames[i] : f.name,
249
+ table.getChild(f.name),
250
+ f.type
251
+ ];
252
+ });
253
+ const rows = [];
254
+ for (let i = 0; i < table.numRows; i++) {
255
+ const row = {};
256
+ for (const [name, col, type] of columns) {
257
+ const v = col?.get(i);
258
+ if (v == null) {
259
+ row[name] = null;
260
+ continue;
261
+ }
262
+ switch (type.typeId) {
263
+ case Type.Decimal:
264
+ case Type.Timestamp:
265
+ case Type.Date:
266
+ row[name] = formatScalarCell(v, type);
267
+ continue;
268
+ case Type.List:
269
+ case Type.FixedSizeList:
270
+ case Type.Struct:
271
+ case Type.Map:
272
+ row[name] = JSON.stringify(renderNestedValue(v, type));
273
+ continue;
274
+ }
275
+ if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") row[name] = String(v);
276
+ else if (typeof v === "string") row[name] = maybeParseJsonString(v);
277
+ else if (v instanceof Uint8Array) row[name] = Buffer.from(v).toString("base64");
278
+ else row[name] = formatScalarCell(v, type);
279
+ }
280
+ rows.push(row);
281
+ }
282
+ return rows;
283
+ }
284
+
285
+ //#endregion
286
+ export { deliverArrowBytes, deliverJsonResult };
287
+ //# sourceMappingURL=result-delivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result-delivery.js","names":[],"sources":["../../../src/plugins/analytics/result-delivery.ts"],"sourcesContent":["import type { sql } from \"@databricks/sdk-experimental\";\nimport { type DataType, type Field, Type, tableFromIPC } from \"apache-arrow\";\nimport type { SQLTypeMarker } from \"shared\";\nimport { ExecutionError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { RefreshChunkLink } from \"../../stream/arrow-stream-processor\";\n\n/**\n * Centralized disposition/format fallback for analytics result delivery.\n *\n * Two warehouse capability profiles must both work, and their supported\n * combinations are mutually exclusive per warehouse:\n *\n * - **Reyden**: `ARROW_STREAM + INLINE` (and `JSON_ARRAY + INLINE`); does NOT\n * support `EXTERNAL_LINKS`.\n * - **Normal SQL warehouse**: `JSON_ARRAY + INLINE` and\n * `ARROW_STREAM + EXTERNAL_LINKS`; rejects `ARROW_STREAM + INLINE`.\n *\n * The fallback logic lives here (rather than inline in the plugin route) so it\n * can be unit-tested against a fake executor without HTTP, and so the brittle\n * \"which rejection is this\" decision is expressed once.\n */\n\nconst logger = createLogger(\"analytics:delivery\");\n\n/**\n * Minimal query surface the fallback needs. The analytics plugin (and its\n * `asUser(req)` proxy) satisfy it, so the same delivery logic runs unchanged\n * under service-principal and on-behalf-of identities.\n */\nexport interface QueryExecutor {\n query(\n query: string,\n parameters: Record<string, SQLTypeMarker | null | undefined> | undefined,\n formatParameters: { disposition: string; format: string },\n signal?: AbortSignal,\n ): Promise<\n | {\n attachment?: string;\n data?: Record<string, unknown>[];\n external_links?: sql.ExternalLink[];\n columnNames?: string[];\n statement_id?: string;\n status?: unknown;\n refreshChunkLink?: RefreshChunkLink;\n }\n | undefined\n >;\n}\n\n/** Streams already-resolved EXTERNAL_LINKS chunks; the connector provides it. */\nexport interface ArrowChunkStreamer {\n streamExternalLinks(\n chunks: sql.ExternalLink[],\n signal?: AbortSignal,\n refresh?: RefreshChunkLink,\n ): AsyncGenerator<Uint8Array, void, unknown>;\n}\n\n/** The arrow delivery mode a warehouse actually supports. */\nexport type ArrowCapability = \"inline\" | \"external\";\n\n/** Optional per-warehouse capability memo, avoiding a doomed probe per query. */\ninterface ArrowDeliveryOptions {\n /**\n * When `\"external\"`, skip the `INLINE+ARROW_STREAM` attempt and go straight\n * to `EXTERNAL_LINKS` — a standard warehouse rejects INLINE arrow on every\n * query, so once we've learned that, the probe is pure waste. `\"inline\"` or\n * undefined attempts INLINE first (the common/Reyden path).\n */\n capabilityHint?: ArrowCapability;\n /**\n * Called once the working delivery mode is known, so the caller can memoize\n * it per warehouse for subsequent queries.\n */\n onCapabilityResolved?: (capability: ArrowCapability) => void;\n}\n\n/**\n * How a warehouse rejected a disposition/format combination.\n *\n * Only the two structured error codes Databricks emits for capability\n * mismatches (`INVALID_PARAMETER_VALUE`, `NOT_IMPLEMENTED`) gate a fallback —\n * auth, permission, and SQL errors never match, so they propagate untouched.\n * Message matching only *disambiguates* which capability is missing once the\n * code has confirmed it's a capability error.\n */\ntype DispositionRejection =\n | \"needs-arrow-inline\" // INLINE requires ARROW_STREAM\n | \"needs-json-inline\" // INLINE requires JSON_ARRAY\n | \"external-links-unsupported\" // EXTERNAL_LINKS not implemented\n | null;\n\n/**\n * Whether an error is a disposition/format *capability* rejection — the only\n * class of error a fallback should react to. Gated on the two structured codes\n * Databricks emits for capability mismatches (`INVALID_PARAMETER_VALUE`,\n * `NOT_IMPLEMENTED`); auth, permission, and SQL errors carry other codes and\n * never match. This is deliberately message-independent: a Databricks wording\n * change must not turn a capability rejection into a hard 500.\n */\nfunction isCapabilityRejection(err: unknown): boolean {\n const structuredCode =\n err instanceof ExecutionError ? err.errorCode : undefined;\n if (\n structuredCode === \"INVALID_PARAMETER_VALUE\" ||\n structuredCode === \"NOT_IMPLEMENTED\"\n ) {\n return true;\n }\n const lower = (\n err instanceof Error ? err.message : String(err)\n ).toLowerCase();\n return (\n lower.includes(\"invalid_parameter_value\") ||\n lower.includes(\"not_implemented\")\n );\n}\n\nexport function classifyDispositionRejection(\n err: unknown,\n): DispositionRejection {\n // Auth / permission / SQL errors carry other codes → never fall back.\n if (!isCapabilityRejection(err)) return null;\n\n const msg = err instanceof Error ? err.message : String(err);\n const lower = msg.toLowerCase();\n\n // EXTERNAL_LINKS disposition not implemented (Reyden).\n if (\n /external[_\\s]?links/.test(lower) &&\n (lower.includes(\"not yet implemented\") ||\n lower.includes(\"not implemented\") ||\n lower.includes(\"not supported\"))\n ) {\n return \"external-links-unsupported\";\n }\n\n // Remaining cases are INLINE disposition/format mismatches.\n if (!lower.includes(\"inline\")) return null;\n\n if (\n /only supports\\s+arrow_stream/i.test(msg) ||\n /must be\\s+arrow_stream/i.test(msg)\n ) {\n return \"needs-arrow-inline\";\n }\n if (\n /only supports\\s+json_array/i.test(msg) ||\n /must be\\s+json_array/i.test(msg) ||\n /arrow_stream\\s+(is\\s+|was\\s+)?not\\s+supported/i.test(msg)\n ) {\n return \"needs-json-inline\";\n }\n\n return null;\n}\n\n/** Structured error: the warehouse supports no Arrow delivery mode at all. */\nexport function arrowDeliveryUnsupported(): ExecutionError {\n return ExecutionError.statementFailed(\n \"Warehouse supports neither ARROW_STREAM+INLINE nor ARROW_STREAM+EXTERNAL_LINKS\",\n \"ARROW_DELIVERY_UNSUPPORTED\",\n 'This warehouse cannot return Arrow results. Re-run the query with format=\"JSON_ARRAY\".',\n );\n}\n\nconst errMessage = (err: unknown): string =>\n err instanceof Error ? err.message : String(err);\n\n/**\n * Produce the raw Arrow IPC bytes for an ARROW_STREAM query, applying the\n * capability fallback:\n *\n * 1. `INLINE + ARROW_STREAM` — on success the base64 attachment is decoded\n * once and yielded as a `Uint8Array` view (no Arrow parsing, no stash).\n * 2. On a `needs-json-inline` rejection, `EXTERNAL_LINKS + ARROW_STREAM` —\n * the pre-signed chunks resolved in the executor's own context are\n * streamed one at a time.\n * 3. If EXTERNAL_LINKS is also unsupported, a structured\n * `ARROW_DELIVERY_UNSUPPORTED` error is thrown.\n *\n * `out` is populated with the real column names and statement id before the\n * first chunk is yielded, so the caller can emit them (e.g. as a header)\n * ahead of the body.\n */\nexport async function* deliverArrowBytes(\n executor: QueryExecutor,\n streamer: ArrowChunkStreamer,\n query: string,\n processedParams: Record<string, SQLTypeMarker | null | undefined> | undefined,\n out: { columnNames?: string[]; statementId?: string },\n signal?: AbortSignal,\n opts?: ArrowDeliveryOptions,\n): AsyncGenerator<Uint8Array, void, unknown> {\n // Skip the INLINE probe when a prior query already learned this warehouse\n // only serves arrow via EXTERNAL_LINKS (standard warehouses reject INLINE\n // arrow on every query, so probing each time is pure waste).\n if (opts?.capabilityHint !== \"external\") {\n try {\n const result = await executor.query(\n query,\n processedParams,\n { disposition: \"INLINE\", format: \"ARROW_STREAM\" },\n signal,\n );\n if (result?.attachment) {\n opts?.onCapabilityResolved?.(\"inline\");\n out.columnNames = result.columnNames;\n out.statementId = result.statement_id;\n // Decode base64 once; yield a view over the Buffer (no copy, no parse).\n const decoded = Buffer.from(result.attachment, \"base64\");\n yield new Uint8Array(\n decoded.buffer,\n decoded.byteOffset,\n decoded.byteLength,\n );\n return;\n }\n // INLINE succeeded but returned no attachment (rare: inline data_array\n // under ARROW_STREAM). Fall through to EXTERNAL_LINKS.\n logger.warn(\n \"ARROW_STREAM INLINE returned no attachment; falling back to EXTERNAL_LINKS\",\n );\n } catch (err: unknown) {\n if (signal?.aborted) throw err;\n // Any capability-coded rejection of INLINE+ARROW_STREAM means this\n // warehouse won't serve arrow inline — try EXTERNAL_LINKS. We\n // intentionally do NOT require the message to match a specific phrase\n // (\"must be JSON_ARRAY\", …): the errorCode gate already excludes auth/SQL\n // errors, and relying on exact wording would turn a Databricks message\n // reword into a hard 500 on every standard warehouse. Worst case for a\n // genuinely bad parameter that happens to carry a capability code: one\n // wasted re-execution that fails identically on EXTERNAL_LINKS and\n // propagates.\n if (!isCapabilityRejection(err)) throw err;\n logger.warn(\n \"ARROW_STREAM INLINE rejected by warehouse, falling back to EXTERNAL_LINKS: %s\",\n errMessage(err),\n );\n }\n }\n\n let ext: Awaited<ReturnType<QueryExecutor[\"query\"]>>;\n try {\n ext = await executor.query(\n query,\n processedParams,\n { disposition: \"EXTERNAL_LINKS\", format: \"ARROW_STREAM\" },\n signal,\n );\n } catch (err: unknown) {\n if (signal?.aborted) throw err;\n // Neither INLINE nor EXTERNAL_LINKS Arrow is supported — surface a clear,\n // actionable error instead of a raw warehouse rejection.\n if (classifyDispositionRejection(err) === \"external-links-unsupported\") {\n throw arrowDeliveryUnsupported();\n }\n throw err;\n }\n\n if (!ext?.external_links) {\n throw ExecutionError.missingData(\"external_links\");\n }\n opts?.onCapabilityResolved?.(\"external\");\n out.columnNames = ext.columnNames;\n out.statementId = ext.statement_id;\n // Stream the pre-signed links resolved in THIS request's execution context\n // (user creds for `.obo.sql`, service principal otherwise). Pre-signed URLs\n // need no auth to download, so there is no second `getStatement` under a\n // mismatched identity. `refreshChunkLink` (also bound to this context) lets\n // the streamer re-mint a link that expires mid-download.\n yield* streamer.streamExternalLinks(\n ext.external_links,\n signal,\n ext.refreshChunkLink,\n );\n}\n\n/** JSON row result plus the metadata the SSE `result` message forwards. */\ninterface JsonResult {\n data: Record<string, unknown>[] | undefined;\n status: unknown;\n statement_id?: string;\n}\n\n/**\n * Produce JSON rows for a JSON_ARRAY query, applying the capability fallback:\n *\n * 1. `INLINE + JSON_ARRAY` — the native, common path.\n * 2. On a `needs-arrow-inline` rejection (warehouse only accepts ARROW_STREAM\n * for INLINE, e.g. Reyden), retry `INLINE + ARROW_STREAM` and decode the\n * attachment to plain rows server-side so the caller's JSON contract holds.\n *\n * EXTERNAL_LINKS is never used for the JSON fallback.\n */\nexport async function deliverJsonResult(\n executor: QueryExecutor,\n query: string,\n processedParams: Record<string, SQLTypeMarker | null | undefined> | undefined,\n signal?: AbortSignal,\n): Promise<JsonResult> {\n try {\n const result = await executor.query(\n query,\n processedParams,\n { disposition: \"INLINE\", format: \"JSON_ARRAY\" },\n signal,\n );\n return {\n data: result?.data,\n status: result?.status,\n statement_id: result?.statement_id,\n };\n } catch (err: unknown) {\n if (signal?.aborted) throw err;\n if (classifyDispositionRejection(err) !== \"needs-arrow-inline\") throw err;\n logger.warn(\n \"JSON_ARRAY INLINE rejected by warehouse, retrying as ARROW_STREAM INLINE and decoding server-side: %s\",\n errMessage(err),\n );\n }\n\n const arrowResult = await executor.query(\n query,\n processedParams,\n { disposition: \"INLINE\", format: \"ARROW_STREAM\" },\n signal,\n );\n if (!arrowResult?.attachment) {\n throw ExecutionError.missingData(\"ARROW_STREAM attachment\");\n }\n const rows = decodeArrowAttachmentToRows(\n arrowResult.attachment,\n arrowResult.columnNames,\n );\n return {\n data: rows,\n status: arrowResult.status,\n statement_id: arrowResult.statement_id,\n };\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Server-side Arrow → JSON row materializer (JSON fallback only)\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Hard caps on the server-side JSON_ARRAY fallback. The materializer builds\n * every row as a plain JS object on the Node main thread (O(rows × cols)\n * allocations), so a runaway result blocks the event loop and pressures GC.\n * Cap on rows AND decoded bytes — either dimension can blow up independently.\n */\nconst JSON_ARRAY_FALLBACK_MAX_ROWS = 100_000;\nconst JSON_ARRAY_FALLBACK_MAX_BYTES = 64 * 1024 * 1024;\n\n/** Render an apache-arrow Decimal cell to a fixed-point string. */\nfunction formatDecimalCell(\n value: { toString(): string },\n scale: number,\n): string {\n const unscaled = value.toString();\n if (scale <= 0) return unscaled;\n const negative = unscaled.startsWith(\"-\");\n let digits = negative ? unscaled.slice(1) : unscaled;\n if (digits.length <= scale) digits = digits.padStart(scale + 1, \"0\");\n const point = digits.length - scale;\n const out = `${digits.slice(0, point)}.${digits.slice(point)}`;\n return negative ? `-${out}` : out;\n}\n\n/**\n * Render an apache-arrow Timestamp cell to ISO-8601 ms precision — `Z` for\n * zoned columns, no `Z` for TIMESTAMP_NTZ — matching native JSON_ARRAY.\n */\nfunction formatTimestampCell(epochMs: number, hasTimezone: boolean): string {\n const iso = new Date(epochMs).toISOString();\n return hasTimezone ? iso : iso.slice(0, -1);\n}\n\n/** Render an apache-arrow Date cell to `yyyy-MM-dd` (UTC). */\nfunction formatDateCell(epochMs: number): string {\n return new Date(epochMs).toISOString().slice(0, 10);\n}\n\n/**\n * Render a single scalar Arrow value to the string form the warehouse emits\n * under native JSON_ARRAY. Used for both top-level cells and (recursively, via\n * {@link renderNestedValue}) scalar leaves inside List/Struct/Map columns,\n * keyed on the Arrow field type so a nested `DECIMAL` keeps its scale and a\n * nested `TIMESTAMP` becomes ISO-8601 instead of raw epoch-ms.\n */\nfunction formatScalarCell(value: unknown, type: DataType): string {\n switch (type.typeId) {\n case Type.Decimal:\n return formatDecimalCell(\n value as { toString(): string },\n (type as DataType & { scale: number }).scale,\n );\n case Type.Timestamp:\n return formatTimestampCell(\n Number(value),\n (type as DataType & { timezone?: string | null }).timezone != null,\n );\n case Type.Date:\n return formatDateCell(Number(value));\n default:\n if (value instanceof Uint8Array) {\n return Buffer.from(value).toString(\"base64\");\n }\n if (value instanceof Date) return value.toISOString();\n return String(value);\n }\n}\n\n/**\n * Recursively render a nested Arrow value (List / Struct / Map, or a scalar\n * leaf) to the plain JS shape the warehouse nests inside a JSON_ARRAY cell:\n * structs and maps become objects, lists become arrays, and every scalar leaf\n * is stringified per {@link formatScalarCell}. The caller `JSON.stringify`s the\n * result so nested columns match the warehouse's native `data_array` exactly.\n */\nfunction renderNestedValue(value: unknown, type: DataType): unknown {\n if (value == null) return null;\n switch (type.typeId) {\n case Type.List:\n case Type.FixedSizeList: {\n const itemType = (type as DataType & { children: Field[] }).children[0]\n .type;\n const out: unknown[] = [];\n for (const item of value as Iterable<unknown>) {\n out.push(renderNestedValue(item, itemType));\n }\n return out;\n }\n case Type.Struct: {\n const fields = (type as DataType & { children: Field[] }).children;\n const obj: Record<string, unknown> = {};\n for (const field of fields) {\n obj[field.name] = renderNestedValue(\n (value as Record<string, unknown>)[field.name],\n field.type,\n );\n }\n return obj;\n }\n case Type.Map: {\n // A Map is a List<Struct<key, value>>; the value child carries the type.\n const entriesType = (type as DataType & { children: Field[] }).children[0]\n .type;\n const valueType = (entriesType as DataType & { children: Field[] })\n .children[1].type;\n const obj: Record<string, unknown> = {};\n for (const [k, v] of value as Iterable<[unknown, unknown]>) {\n obj[String(k)] = renderNestedValue(v, valueType);\n }\n return obj;\n }\n default:\n return formatScalarCell(value, type);\n }\n}\n\n/** Parse a STRING cell that looks like JSON into an object/array. */\nfunction maybeParseJsonString(value: string): unknown {\n if (value && (value[0] === \"{\" || value[0] === \"[\")) {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n return value;\n}\n\n/**\n * Decode a base64 Arrow IPC attachment to plain row objects, matching what the\n * warehouse emits natively under JSON_ARRAY so callers cannot tell which path\n * served the query.\n *\n * The output key uses the manifest name (`columnNames[i]`) when available —\n * Databricks encodes the Arrow schema positionally (col_0, …) — while the\n * vector is still fetched by the Arrow field's own name.\n */\nexport function decodeArrowAttachmentToRows(\n attachment: string,\n columnNames?: string[],\n): Record<string, unknown>[] {\n const decoded = Buffer.from(attachment, \"base64\");\n if (decoded.byteLength > JSON_ARRAY_FALLBACK_MAX_BYTES) {\n throw ExecutionError.statementFailed(\n `Result attachment is ${decoded.byteLength} bytes; JSON_ARRAY fallback materializer caps at ${JSON_ARRAY_FALLBACK_MAX_BYTES} bytes. Re-issue the query with format=\"ARROW_STREAM\" to stream the full result.`,\n \"RESULT_TOO_LARGE_FOR_JSON_FALLBACK\",\n \"Result too large for JSON format. Re-run with ARROW_STREAM format.\",\n );\n }\n const table = tableFromIPC(\n new Uint8Array(decoded.buffer, decoded.byteOffset, decoded.byteLength),\n );\n if (table.numRows > JSON_ARRAY_FALLBACK_MAX_ROWS) {\n throw ExecutionError.statementFailed(\n `Result has ${table.numRows} rows; JSON_ARRAY fallback materializer caps at ${JSON_ARRAY_FALLBACK_MAX_ROWS}. Re-issue the query with format=\"ARROW_STREAM\" to stream the full result.`,\n \"RESULT_TOO_LARGE_FOR_JSON_FALLBACK\",\n `Result too large for JSON format (over ${JSON_ARRAY_FALLBACK_MAX_ROWS} rows). Re-run with ARROW_STREAM format.`,\n );\n }\n // Resolve child vectors once (getChild walks fields on every call). The\n // output key uses the manifest name; the vector is fetched by field name.\n const columns = table.schema.fields.map((f, i) => {\n const outName =\n columnNames?.[i] && columnNames[i].length > 0 ? columnNames[i] : f.name;\n return [outName, table.getChild(f.name), f.type] as const;\n });\n const rows: Record<string, unknown>[] = [];\n for (let i = 0; i < table.numRows; i++) {\n const row: Record<string, unknown> = {};\n for (const [name, col, type] of columns) {\n const v = col?.get(i);\n if (v == null) {\n row[name] = null;\n continue;\n }\n switch (type.typeId) {\n case Type.Decimal:\n case Type.Timestamp:\n case Type.Date:\n row[name] = formatScalarCell(v, type);\n continue;\n // Nested columns (STRUCT / ARRAY / MAP) arrive on the native\n // JSON_ARRAY wire as a JSON string with every scalar leaf stringified\n // by type; render the same shape and stringify so callers cannot tell\n // the fallback path from the native one.\n case Type.List:\n case Type.FixedSizeList:\n case Type.Struct:\n case Type.Map:\n row[name] = JSON.stringify(renderNestedValue(v, type));\n continue;\n }\n if (\n typeof v === \"number\" ||\n typeof v === \"bigint\" ||\n typeof v === \"boolean\"\n ) {\n row[name] = String(v);\n } else if (typeof v === \"string\") {\n row[name] = maybeParseJsonString(v);\n } else if (v instanceof Uint8Array) {\n row[name] = Buffer.from(v).toString(\"base64\");\n } else {\n row[name] = formatScalarCell(v, type);\n }\n }\n rows.push(row);\n }\n return rows;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,SAAS,aAAa,qBAAqB;;;;;;;;;AA8EjD,SAAS,sBAAsB,KAAuB;CACpD,MAAM,iBACJ,eAAe,iBAAiB,IAAI,YAAY;AAClD,KACE,mBAAmB,6BACnB,mBAAmB,kBAEnB,QAAO;CAET,MAAM,SACJ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EAChD,aAAa;AACf,QACE,MAAM,SAAS,0BAA0B,IACzC,MAAM,SAAS,kBAAkB;;AAIrC,SAAgB,6BACd,KACsB;AAEtB,KAAI,CAAC,sBAAsB,IAAI,CAAE,QAAO;CAExC,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;CAC5D,MAAM,QAAQ,IAAI,aAAa;AAG/B,KACE,sBAAsB,KAAK,MAAM,KAChC,MAAM,SAAS,sBAAsB,IACpC,MAAM,SAAS,kBAAkB,IACjC,MAAM,SAAS,gBAAgB,EAEjC,QAAO;AAIT,KAAI,CAAC,MAAM,SAAS,SAAS,CAAE,QAAO;AAEtC,KACE,gCAAgC,KAAK,IAAI,IACzC,0BAA0B,KAAK,IAAI,CAEnC,QAAO;AAET,KACE,8BAA8B,KAAK,IAAI,IACvC,wBAAwB,KAAK,IAAI,IACjC,iDAAiD,KAAK,IAAI,CAE1D,QAAO;AAGT,QAAO;;;AAIT,SAAgB,2BAA2C;AACzD,QAAO,eAAe,gBACpB,kFACA,8BACA,2FACD;;AAGH,MAAM,cAAc,QAClB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;;;;;;;;;;;;;;;;AAkBlD,gBAAuB,kBACrB,UACA,UACA,OACA,iBACA,KACA,QACA,MAC2C;AAI3C,KAAI,MAAM,mBAAmB,WAC3B,KAAI;EACF,MAAM,SAAS,MAAM,SAAS,MAC5B,OACA,iBACA;GAAE,aAAa;GAAU,QAAQ;GAAgB,EACjD,OACD;AACD,MAAI,QAAQ,YAAY;AACtB,SAAM,uBAAuB,SAAS;AACtC,OAAI,cAAc,OAAO;AACzB,OAAI,cAAc,OAAO;GAEzB,MAAM,UAAU,OAAO,KAAK,OAAO,YAAY,SAAS;AACxD,SAAM,IAAI,WACR,QAAQ,QACR,QAAQ,YACR,QAAQ,WACT;AACD;;AAIF,SAAO,KACL,6EACD;UACM,KAAc;AACrB,MAAI,QAAQ,QAAS,OAAM;AAU3B,MAAI,CAAC,sBAAsB,IAAI,CAAE,OAAM;AACvC,SAAO,KACL,iFACA,WAAW,IAAI,CAChB;;CAIL,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,SAAS,MACnB,OACA,iBACA;GAAE,aAAa;GAAkB,QAAQ;GAAgB,EACzD,OACD;UACM,KAAc;AACrB,MAAI,QAAQ,QAAS,OAAM;AAG3B,MAAI,6BAA6B,IAAI,KAAK,6BACxC,OAAM,0BAA0B;AAElC,QAAM;;AAGR,KAAI,CAAC,KAAK,eACR,OAAM,eAAe,YAAY,iBAAiB;AAEpD,OAAM,uBAAuB,WAAW;AACxC,KAAI,cAAc,IAAI;AACtB,KAAI,cAAc,IAAI;AAMtB,QAAO,SAAS,oBACd,IAAI,gBACJ,QACA,IAAI,iBACL;;;;;;;;;;;;AAoBH,eAAsB,kBACpB,UACA,OACA,iBACA,QACqB;AACrB,KAAI;EACF,MAAM,SAAS,MAAM,SAAS,MAC5B,OACA,iBACA;GAAE,aAAa;GAAU,QAAQ;GAAc,EAC/C,OACD;AACD,SAAO;GACL,MAAM,QAAQ;GACd,QAAQ,QAAQ;GAChB,cAAc,QAAQ;GACvB;UACM,KAAc;AACrB,MAAI,QAAQ,QAAS,OAAM;AAC3B,MAAI,6BAA6B,IAAI,KAAK,qBAAsB,OAAM;AACtE,SAAO,KACL,yGACA,WAAW,IAAI,CAChB;;CAGH,MAAM,cAAc,MAAM,SAAS,MACjC,OACA,iBACA;EAAE,aAAa;EAAU,QAAQ;EAAgB,EACjD,OACD;AACD,KAAI,CAAC,aAAa,WAChB,OAAM,eAAe,YAAY,0BAA0B;AAM7D,QAAO;EACL,MALW,4BACX,YAAY,YACZ,YAAY,YACb;EAGC,QAAQ,YAAY;EACpB,cAAc,YAAY;EAC3B;;;;;;;;AAaH,MAAM,+BAA+B;AACrC,MAAM,gCAAgC,KAAK,OAAO;;AAGlD,SAAS,kBACP,OACA,OACQ;CACR,MAAM,WAAW,MAAM,UAAU;AACjC,KAAI,SAAS,EAAG,QAAO;CACvB,MAAM,WAAW,SAAS,WAAW,IAAI;CACzC,IAAI,SAAS,WAAW,SAAS,MAAM,EAAE,GAAG;AAC5C,KAAI,OAAO,UAAU,MAAO,UAAS,OAAO,SAAS,QAAQ,GAAG,IAAI;CACpE,MAAM,QAAQ,OAAO,SAAS;CAC9B,MAAM,MAAM,GAAG,OAAO,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,MAAM,MAAM;AAC5D,QAAO,WAAW,IAAI,QAAQ;;;;;;AAOhC,SAAS,oBAAoB,SAAiB,aAA8B;CAC1E,MAAM,MAAM,IAAI,KAAK,QAAQ,CAAC,aAAa;AAC3C,QAAO,cAAc,MAAM,IAAI,MAAM,GAAG,GAAG;;;AAI7C,SAAS,eAAe,SAAyB;AAC/C,QAAO,IAAI,KAAK,QAAQ,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG;;;;;;;;;AAUrD,SAAS,iBAAiB,OAAgB,MAAwB;AAChE,SAAQ,KAAK,QAAb;EACE,KAAK,KAAK,QACR,QAAO,kBACL,OACC,KAAsC,MACxC;EACH,KAAK,KAAK,UACR,QAAO,oBACL,OAAO,MAAM,EACZ,KAAiD,YAAY,KAC/D;EACH,KAAK,KAAK,KACR,QAAO,eAAe,OAAO,MAAM,CAAC;EACtC;AACE,OAAI,iBAAiB,WACnB,QAAO,OAAO,KAAK,MAAM,CAAC,SAAS,SAAS;AAE9C,OAAI,iBAAiB,KAAM,QAAO,MAAM,aAAa;AACrD,UAAO,OAAO,MAAM;;;;;;;;;;AAW1B,SAAS,kBAAkB,OAAgB,MAAyB;AAClE,KAAI,SAAS,KAAM,QAAO;AAC1B,SAAQ,KAAK,QAAb;EACE,KAAK,KAAK;EACV,KAAK,KAAK,eAAe;GACvB,MAAM,WAAY,KAA0C,SAAS,GAClE;GACH,MAAM,MAAiB,EAAE;AACzB,QAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,kBAAkB,MAAM,SAAS,CAAC;AAE7C,UAAO;;EAET,KAAK,KAAK,QAAQ;GAChB,MAAM,SAAU,KAA0C;GAC1D,MAAM,MAA+B,EAAE;AACvC,QAAK,MAAM,SAAS,OAClB,KAAI,MAAM,QAAQ,kBACf,MAAkC,MAAM,OACzC,MAAM,KACP;AAEH,UAAO;;EAET,KAAK,KAAK,KAAK;GAIb,MAAM,YAFe,KAA0C,SAAS,GACrE,KAEA,SAAS,GAAG;GACf,MAAM,MAA+B,EAAE;AACvC,QAAK,MAAM,CAAC,GAAG,MAAM,MACnB,KAAI,OAAO,EAAE,IAAI,kBAAkB,GAAG,UAAU;AAElD,UAAO;;EAET,QACE,QAAO,iBAAiB,OAAO,KAAK;;;;AAK1C,SAAS,qBAAqB,OAAwB;AACpD,KAAI,UAAU,MAAM,OAAO,OAAO,MAAM,OAAO,KAC7C,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN,SAAO;;AAGX,QAAO;;;;;;;;;;;AAYT,SAAgB,4BACd,YACA,aAC2B;CAC3B,MAAM,UAAU,OAAO,KAAK,YAAY,SAAS;AACjD,KAAI,QAAQ,aAAa,8BACvB,OAAM,eAAe,gBACnB,wBAAwB,QAAQ,WAAW,mDAAmD,8BAA8B,mFAC5H,sCACA,qEACD;CAEH,MAAM,QAAQ,aACZ,IAAI,WAAW,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,WAAW,CACvE;AACD,KAAI,MAAM,UAAU,6BAClB,OAAM,eAAe,gBACnB,cAAc,MAAM,QAAQ,kDAAkD,6BAA6B,6EAC3G,sCACA,0CAA0C,6BAA6B,0CACxE;CAIH,MAAM,UAAU,MAAM,OAAO,OAAO,KAAK,GAAG,MAAM;AAGhD,SAAO;GADL,cAAc,MAAM,YAAY,GAAG,SAAS,IAAI,YAAY,KAAK,EAAE;GACpD,MAAM,SAAS,EAAE,KAAK;GAAE,EAAE;GAAK;GAChD;CACF,MAAM,OAAkC,EAAE;AAC1C,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,SAAS,KAAK;EACtC,MAAM,MAA+B,EAAE;AACvC,OAAK,MAAM,CAAC,MAAM,KAAK,SAAS,SAAS;GACvC,MAAM,IAAI,KAAK,IAAI,EAAE;AACrB,OAAI,KAAK,MAAM;AACb,QAAI,QAAQ;AACZ;;AAEF,WAAQ,KAAK,QAAb;IACE,KAAK,KAAK;IACV,KAAK,KAAK;IACV,KAAK,KAAK;AACR,SAAI,QAAQ,iBAAiB,GAAG,KAAK;AACrC;IAKF,KAAK,KAAK;IACV,KAAK,KAAK;IACV,KAAK,KAAK;IACV,KAAK,KAAK;AACR,SAAI,QAAQ,KAAK,UAAU,kBAAkB,GAAG,KAAK,CAAC;AACtD;;AAEJ,OACE,OAAO,MAAM,YACb,OAAO,MAAM,YACb,OAAO,MAAM,UAEb,KAAI,QAAQ,OAAO,EAAE;YACZ,OAAO,MAAM,SACtB,KAAI,QAAQ,qBAAqB,EAAE;YAC1B,aAAa,WACtB,KAAI,QAAQ,OAAO,KAAK,EAAE,CAAC,SAAS,SAAS;OAE7C,KAAI,QAAQ,iBAAiB,GAAG,KAAK;;AAGzC,OAAK,KAAK,IAAI;;AAEhB,QAAO"}
@@ -17,6 +17,14 @@ interface IAnalyticsConfig extends BasePluginConfig {
17
17
  * `ConfigurationError`.
18
18
  */
19
19
  autoStartWarehouse?: boolean;
20
+ /**
21
+ * Fail-fast ceiling (ms) for an `ARROW_STREAM` query to produce its first
22
+ * byte (warehouse readiness + execute + first chunk). Past this, a stuck or
23
+ * overloaded warehouse returns a `503` (`WAREHOUSE_UNAVAILABLE`) instead of
24
+ * hanging until the client disconnects. Defaults to 2 min. Once the first
25
+ * byte arrives the stream is not time-bounded.
26
+ */
27
+ arrowFirstByteTimeoutMs?: number;
20
28
  }
21
29
  //#endregion
22
30
  export { IAnalyticsConfig };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/analytics/types.ts"],"mappings":";;;;UAEiB,gBAAA,SAAyB,gBAAA;EACxC,OAAA;;AADF;;;EAME,yBAAA;EANwC;;;;;;;EAcxC,kBAAA;AAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/analytics/types.ts"],"mappings":";;;;UAEiB,gBAAA,SAAyB,gBAAA;EACxC,OAAA;;AADF;;;EAME,yBAAA;EANwC;;;;;;;EAcxC,kBAAA;;;;;;;;EAQA,uBAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../src/plugins/analytics/types.ts"],"sourcesContent":["import type { BasePluginConfig } from \"shared\";\n\nexport interface IAnalyticsConfig extends BasePluginConfig {\n timeout?: number;\n /**\n * Maximum time (ms) the analytics route waits for a STOPPED/STARTING SQL\n * warehouse to reach RUNNING before failing the request. Defaults to 5 min.\n */\n warehouseStartupTimeoutMs?: number;\n /**\n * When `true` (default), a `STOPPED` SQL warehouse is auto-started on the\n * first analytics request that reaches it. Set to `false` for cost-\n * controlled deployments where billable warehouse starts must not be\n * triggered by user requests; in that case `STOPPED` surfaces as a\n * `ConfigurationError`.\n */\n autoStartWarehouse?: boolean;\n}\n\n/**\n * SQL warehouse lifecycle states surfaced by the analytics route.\n * Mirrors the states emitted by the Databricks SQL SDK (`sql.State`).\n */\nexport type WarehouseState =\n | \"RUNNING\"\n | \"STARTING\"\n | \"STOPPED\"\n | \"STOPPING\"\n | \"DELETED\"\n | \"DELETING\";\n\n/**\n * Snapshot of warehouse readiness streamed to the client over SSE before the\n * SQL result. Lets the UI render a \"warehouse starting…\" affordance instead\n * of a frozen spinner during cold starts.\n *\n * Note: the SDK's `health.summary` is intentionally NOT forwarded here. It's\n * free-form operator-oriented diagnostic text (cluster IDs, capacity-failure\n * reasons, internal RPC errors) that must not reach end users; it stays in\n * server-side telemetry only.\n */\nexport interface WarehouseStatus {\n state: WarehouseState;\n /** Milliseconds elapsed since the route began waiting for the warehouse. */\n elapsedMs: number;\n}\n\n/**\n * Discriminated union of every SSE message shape emitted by\n * `POST /api/analytics/query/:query_key`. Useful for typing the client-side\n * `onMessage` handler (and is the source of truth re-mirrored in\n * `appkit-ui` since that package can't depend on `appkit`).\n */\nexport type AnalyticsStreamMessage =\n | { type: \"warehouse_status\"; status: WarehouseStatus }\n | { type: \"result\"; data: unknown[] }\n | {\n type: \"arrow\";\n statement_id: string;\n status: { state: string };\n }\n | { type: \"error\"; error: string; code?: string };\n\n/**\n * Supported response formats for analytics queries.\n *\n * \"JSON\" and \"ARROW\" are legacy aliases kept for backwards compatibility\n * with appkit/appkit-ui < 0.33.0 — safe to remove once no consumer is on\n * a pre-0.33.0 version. The route handler normalizes them to their\n * canonical equivalents before any downstream code reads the value.\n */\nexport type AnalyticsFormat =\n | \"JSON_ARRAY\"\n | \"ARROW_STREAM\"\n /** @deprecated Use \"JSON_ARRAY\". Safe to remove once no consumer is on appkit < 0.33.0. */\n | \"JSON\"\n /** @deprecated Use \"ARROW_STREAM\". Safe to remove once no consumer is on appkit < 0.33.0. */\n | \"ARROW\";\n\n/** Canonical (post-normalization) analytics format values. */\ntype CanonicalAnalyticsFormat = \"JSON_ARRAY\" | \"ARROW_STREAM\";\n\n/**\n * Map a (possibly legacy) AnalyticsFormat to its canonical form.\n * Legacy values come from appkit/appkit-ui < 0.33.0 and can be removed\n * along with the deprecated aliases once no such consumer remains.\n */\nexport function normalizeAnalyticsFormat(\n f: AnalyticsFormat,\n): CanonicalAnalyticsFormat {\n if (f === \"JSON\") return \"JSON_ARRAY\";\n if (f === \"ARROW\") return \"ARROW_STREAM\";\n return f;\n}\n\nexport interface IAnalyticsQueryRequest {\n parameters?: Record<string, any>;\n format?: AnalyticsFormat;\n}\n\nexport interface AnalyticsQueryResponse {\n chunk_index: number;\n row_offset: number;\n row_count: number;\n data: any[];\n}\n"],"mappings":";;;;;;AAuFA,SAAgB,yBACd,GAC0B;AAC1B,KAAI,MAAM,OAAQ,QAAO;AACzB,KAAI,MAAM,QAAS,QAAO;AAC1B,QAAO"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../src/plugins/analytics/types.ts"],"sourcesContent":["import type { BasePluginConfig } from \"shared\";\n\nexport interface IAnalyticsConfig extends BasePluginConfig {\n timeout?: number;\n /**\n * Maximum time (ms) the analytics route waits for a STOPPED/STARTING SQL\n * warehouse to reach RUNNING before failing the request. Defaults to 5 min.\n */\n warehouseStartupTimeoutMs?: number;\n /**\n * When `true` (default), a `STOPPED` SQL warehouse is auto-started on the\n * first analytics request that reaches it. Set to `false` for cost-\n * controlled deployments where billable warehouse starts must not be\n * triggered by user requests; in that case `STOPPED` surfaces as a\n * `ConfigurationError`.\n */\n autoStartWarehouse?: boolean;\n /**\n * Fail-fast ceiling (ms) for an `ARROW_STREAM` query to produce its first\n * byte (warehouse readiness + execute + first chunk). Past this, a stuck or\n * overloaded warehouse returns a `503` (`WAREHOUSE_UNAVAILABLE`) instead of\n * hanging until the client disconnects. Defaults to 2 min. Once the first\n * byte arrives the stream is not time-bounded.\n */\n arrowFirstByteTimeoutMs?: number;\n}\n\n/**\n * SQL warehouse lifecycle states surfaced by the analytics route.\n * Mirrors the states emitted by the Databricks SQL SDK (`sql.State`).\n */\nexport type WarehouseState =\n | \"RUNNING\"\n | \"STARTING\"\n | \"STOPPED\"\n | \"STOPPING\"\n | \"DELETED\"\n | \"DELETING\";\n\n/**\n * Snapshot of warehouse readiness streamed to the client over SSE before the\n * SQL result. Lets the UI render a \"warehouse starting…\" affordance instead\n * of a frozen spinner during cold starts.\n *\n * Note: the SDK's `health.summary` is intentionally NOT forwarded here. It's\n * free-form operator-oriented diagnostic text (cluster IDs, capacity-failure\n * reasons, internal RPC errors) that must not reach end users; it stays in\n * server-side telemetry only.\n */\nexport interface WarehouseStatus {\n state: WarehouseState;\n /** Milliseconds elapsed since the route began waiting for the warehouse. */\n elapsedMs: number;\n}\n\n/**\n * Discriminated union of every SSE message shape emitted by\n * `POST /api/analytics/query/:query_key`. Useful for typing the client-side\n * `onMessage` handler (and is the source of truth re-mirrored in\n * `appkit-ui` since that package can't depend on `appkit`).\n */\nexport type AnalyticsStreamMessage =\n | { type: \"warehouse_status\"; status: WarehouseStatus }\n | { type: \"result\"; data: unknown[] }\n | {\n type: \"arrow\";\n statement_id: string;\n status: { state: string };\n }\n | { type: \"error\"; error: string; code?: string };\n\n/**\n * Supported response formats for analytics queries.\n *\n * \"JSON\" and \"ARROW\" are legacy aliases kept for backwards compatibility\n * with appkit/appkit-ui < 0.33.0 — safe to remove once no consumer is on\n * a pre-0.33.0 version. The route handler normalizes them to their\n * canonical equivalents before any downstream code reads the value.\n */\nexport type AnalyticsFormat =\n | \"JSON_ARRAY\"\n | \"ARROW_STREAM\"\n /** @deprecated Use \"JSON_ARRAY\". Safe to remove once no consumer is on appkit < 0.33.0. */\n | \"JSON\"\n /** @deprecated Use \"ARROW_STREAM\". Safe to remove once no consumer is on appkit < 0.33.0. */\n | \"ARROW\";\n\n/** Canonical (post-normalization) analytics format values. */\ntype CanonicalAnalyticsFormat = \"JSON_ARRAY\" | \"ARROW_STREAM\";\n\n/**\n * Map a (possibly legacy) AnalyticsFormat to its canonical form.\n * Legacy values come from appkit/appkit-ui < 0.33.0 and can be removed\n * along with the deprecated aliases once no such consumer remains.\n */\nexport function normalizeAnalyticsFormat(\n f: AnalyticsFormat,\n): CanonicalAnalyticsFormat {\n if (f === \"JSON\") return \"JSON_ARRAY\";\n if (f === \"ARROW\") return \"ARROW_STREAM\";\n return f;\n}\n\nexport interface IAnalyticsQueryRequest {\n parameters?: Record<string, any>;\n format?: AnalyticsFormat;\n}\n\nexport interface AnalyticsQueryResponse {\n chunk_index: number;\n row_offset: number;\n row_count: number;\n data: any[];\n}\n"],"mappings":";;;;;;AA+FA,SAAgB,yBACd,GAC0B;AAC1B,KAAI,MAAM,OAAQ,QAAO;AACzB,KAAI,MAAM,QAAS,QAAO;AAC1B,QAAO"}
@@ -19,8 +19,8 @@ import { defineTool, executeFromRegistry, toolsFromRegistry } from "../../core/a
19
19
  import { FILES_DOWNLOAD_DEFAULTS, FILES_MAX_UPLOAD_SIZE, FILES_READ_DEFAULTS, FILES_WRITE_DEFAULTS } from "./defaults.js";
20
20
  import { parentDirectory, sanitizeFilename } from "./helpers.js";
21
21
  import manifest_default from "./manifest.js";
22
- import { ApiError } from "@databricks/sdk-experimental";
23
22
  import { z } from "zod";
23
+ import { ApiError } from "@databricks/sdk-experimental";
24
24
  import { STATUS_CODES } from "node:http";
25
25
  import { Readable } from "node:stream";
26
26
 
@@ -11,8 +11,8 @@ import { buildToolkitEntries } from "../../core/agent/build-toolkit.js";
11
11
  import { defineTool, executeFromRegistry, toolsFromRegistry } from "../../core/agent/tools/define-tool.js";
12
12
  import { genieStreamDefaults } from "./defaults.js";
13
13
  import manifest_default from "./manifest.js";
14
- import { randomUUID } from "node:crypto";
15
14
  import { z } from "zod";
15
+ import { randomUUID } from "node:crypto";
16
16
 
17
17
  //#region src/plugins/genie/genie.ts
18
18
  const logger = createLogger("genie");
@@ -2,8 +2,8 @@ import { BasePluginConfig, IAppRequest } from "../../shared/src/plugin.js";
2
2
  import "../../shared/src/index.js";
3
3
  import { ExecutionResult } from "../../plugin/execution-result.js";
4
4
  import "../../plugin/index.js";
5
- import { jobs } from "@databricks/sdk-experimental";
6
5
  import { z } from "zod";
6
+ import { jobs } from "@databricks/sdk-experimental";
7
7
 
8
8
  //#region src/plugins/jobs/types.d.ts
9
9
  /** Supported task types for job parameter mapping. */
@@ -11,8 +11,8 @@ import { buildToolkitEntries } from "../../core/agent/build-toolkit.js";
11
11
  import { defineTool, executeFromRegistry, toolsFromRegistry } from "../../core/agent/tools/define-tool.js";
12
12
  import { assertReadOnlySql } from "../../core/agent/tools/sql-policy.js";
13
13
  import manifest_default from "./manifest.js";
14
- import { WorkspaceClient } from "@databricks/sdk-experimental";
15
14
  import { z } from "zod";
15
+ import { WorkspaceClient } from "@databricks/sdk-experimental";
16
16
 
17
17
  //#region src/plugins/lakebase/lakebase.ts
18
18
  const logger = createLogger("lakebase");
@@ -6,4 +6,5 @@ import { PluginExecuteConfig, PluginExecutionSettings, RetryConfig, StreamConfig
6
6
  import { GenieAttachmentResponse, GenieMessageResponse, GenieStatementResponse, GenieStreamEvent } from "./genie.js";
7
7
  import { SQLBinaryMarker, SQLBooleanMarker, SQLDateMarker, SQLNumberMarker, SQLStringMarker, SQLTimestampMarker, SQLTypeMarker } from "./sql/types.js";
8
8
  import { isSQLTypeMarker, sql } from "./sql/helpers.js";
9
+ import "./sse/analytics.js";
9
10
  import { PendingFetch, PendingFileRead, TunnelConnection } from "./tunnel.js";
@@ -0,0 +1 @@
1
+ import { z } from "zod";
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+
3
+ //#region ../shared/src/sse/analytics.ts
4
+ /**
5
+ * Wire protocol for analytics SSE messages emitted by `/api/analytics/query`.
6
+ *
7
+ * The SSE channel carries only the JSON_ARRAY path (warehouse-readiness
8
+ * events + a `result` message of rows). ARROW_STREAM does NOT use SSE —
9
+ * the server streams the raw Arrow IPC bytes back on the query response body
10
+ * (`_handleArrowStreamQuery`) and the client reads them directly
11
+ * (`fetchArrowDirect`), so there is no `arrow` message type here.
12
+ *
13
+ * These schemas are the single source of truth for the JSON contract between
14
+ * the server (`AnalyticsPlugin._handleQueryRoute`) and the client
15
+ * (`useAnalyticsQuery`). Both sides validate with the same schema:
16
+ *
17
+ * - Server uses the typed builder (`makeResultMessage`) to construct messages
18
+ * with compile-time guarantees that all required fields are present.
19
+ * - Client calls `AnalyticsSseMessage.safeParse(JSON.parse(event.data))` to
20
+ * fail loudly on a malformed payload instead of silently treating an
21
+ * undefined field as data.
22
+ */
23
+ /** Successful row-shaped result (JSON_ARRAY format, or empty results). */
24
+ const AnalyticsResultMessage = z.object({
25
+ type: z.literal("result"),
26
+ data: z.array(z.unknown()).optional(),
27
+ status: z.unknown().optional(),
28
+ statement_id: z.string().optional()
29
+ });
30
+ function makeResultMessage(data, extras = {}) {
31
+ return {
32
+ type: "result",
33
+ data,
34
+ ...extras
35
+ };
36
+ }
37
+
38
+ //#endregion
39
+ export { makeResultMessage };
40
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.js","names":[],"sources":["../../../../../shared/src/sse/analytics.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * Wire protocol for analytics SSE messages emitted by `/api/analytics/query`.\n *\n * The SSE channel carries only the JSON_ARRAY path (warehouse-readiness\n * events + a `result` message of rows). ARROW_STREAM does NOT use SSE —\n * the server streams the raw Arrow IPC bytes back on the query response body\n * (`_handleArrowStreamQuery`) and the client reads them directly\n * (`fetchArrowDirect`), so there is no `arrow` message type here.\n *\n * These schemas are the single source of truth for the JSON contract between\n * the server (`AnalyticsPlugin._handleQueryRoute`) and the client\n * (`useAnalyticsQuery`). Both sides validate with the same schema:\n *\n * - Server uses the typed builder (`makeResultMessage`) to construct messages\n * with compile-time guarantees that all required fields are present.\n * - Client calls `AnalyticsSseMessage.safeParse(JSON.parse(event.data))` to\n * fail loudly on a malformed payload instead of silently treating an\n * undefined field as data.\n */\n\n/** Successful row-shaped result (JSON_ARRAY format, or empty results). */\nexport const AnalyticsResultMessage = z.object({\n type: z.literal(\"result\"),\n // `data` is intentionally `z.array(z.unknown())` rather than a deep\n // row schema. Validating every row's keys for shape costs O(rows × cols)\n // CPU and main-thread blocking time on the *client* (the schema is\n // also reused for `safeParse` in `useAnalyticsQuery`); for large JSON\n // results that pushes hundreds of ms to seconds of TBT into the\n // render pipeline. The server constructs `data` via the typed\n // `makeResultMessage` builder, so the per-row shape is enforced at\n // the source, not at validation time. The TS-level interface below\n // narrows `data` to `Record<string, unknown>[]` for callers.\n data: z.array(z.unknown()).optional(),\n // Status is opaque metadata forwarded from the warehouse — keep it as\n // `unknown` so we don't bake the SDK's detailed shape into the contract.\n status: z.unknown().optional(),\n statement_id: z.string().optional(),\n});\n\n/**\n * TS-level shape of a successful row-shaped result message.\n *\n * **Kept in sync by hand** with `AnalyticsResultMessage` above. The Zod\n * schema is intentionally loose (`z.array(z.unknown())`) to keep client\n * validation cheap; this interface narrows `data` to\n * `Record<string, unknown>[]` so consumers don't have to cast at every\n * call site. If you add a field to the Zod schema, add it here too.\n */\nexport interface AnalyticsResultMessage {\n type: \"result\";\n data?: Record<string, unknown>[];\n status?: unknown;\n statement_id?: string;\n}\n\n/**\n * Every message the analytics SSE stream may emit. Currently only the\n * row-shaped `result` message (JSON_ARRAY path); `warehouse_status` and\n * `error` events are handled off-schema by the client. ARROW_STREAM never\n * uses SSE.\n */\nexport const AnalyticsSseMessage = AnalyticsResultMessage;\nexport type AnalyticsSseMessage = z.infer<typeof AnalyticsSseMessage>;\n\n// ────────────────────────────────────────────────────────────────────────────\n// Typed builder — call from the server route handler. The compiler enforces\n// that every required field is supplied, and the return type narrows so\n// downstream code (executeStream / SSE writer) keeps full type information.\n// ────────────────────────────────────────────────────────────────────────────\n\nexport function makeResultMessage(\n data: Record<string, unknown>[] | undefined,\n extras: { status?: unknown; statement_id?: string } = {},\n): AnalyticsResultMessage {\n return { type: \"result\", data, ...extras };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,yBAAyB,EAAE,OAAO;CAC7C,MAAM,EAAE,QAAQ,SAAS;CAUzB,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,UAAU;CAGrC,QAAQ,EAAE,SAAS,CAAC,UAAU;CAC9B,cAAc,EAAE,QAAQ,CAAC,UAAU;CACpC,CAAC;AAiCF,SAAgB,kBACd,MACA,SAAsD,EAAE,EAChC;AACxB,QAAO;EAAE,MAAM;EAAU;EAAM,GAAG;EAAQ"}