@databricks/appkit-ui 0.38.1 → 0.40.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.
- package/dist/cli/commands/generate-types.js +114 -5
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/cli/commands/spawn-lock.js +116 -0
- package/dist/cli/commands/spawn-lock.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/react/charts/index.d.ts +1 -0
- package/dist/react/charts/index.js +1 -0
- package/dist/react/charts/loading.d.ts +24 -0
- package/dist/react/charts/loading.d.ts.map +1 -0
- package/dist/react/charts/loading.js +20 -2
- package/dist/react/charts/loading.js.map +1 -1
- package/dist/react/charts/wrapper.d.ts.map +1 -1
- package/dist/react/charts/wrapper.js +9 -2
- package/dist/react/charts/wrapper.js.map +1 -1
- package/dist/react/hooks/index.d.ts +3 -1
- package/dist/react/hooks/index.js +2 -0
- package/dist/react/hooks/types.d.ts +28 -1
- package/dist/react/hooks/types.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.js +77 -45
- package/dist/react/hooks/use-analytics-query.js.map +1 -1
- package/dist/react/hooks/use-analytics-warehouse-status.js +48 -0
- package/dist/react/hooks/use-analytics-warehouse-status.js.map +1 -0
- package/dist/react/hooks/use-chart-data.d.ts +8 -0
- package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
- package/dist/react/hooks/use-chart-data.js +3 -2
- package/dist/react/hooks/use-chart-data.js.map +1 -1
- package/dist/react/hooks/use-resource-status.d.ts +92 -0
- package/dist/react/hooks/use-resource-status.d.ts.map +1 -0
- package/dist/react/hooks/use-resource-status.js +199 -0
- package/dist/react/hooks/use-resource-status.js.map +1 -0
- package/dist/react/index.d.ts +5 -2
- package/dist/react/index.js +5 -2
- package/dist/react/resource-status-indicator.d.ts +78 -0
- package/dist/react/resource-status-indicator.d.ts.map +1 -0
- package/dist/react/resource-status-indicator.js +155 -0
- package/dist/react/resource-status-indicator.js.map +1 -0
- package/dist/react/ui/index.js +1 -1
- package/dist/react/ui/sonner.js +1 -1
- package/docs/development/type-generation.md +13 -0
- package/docs/plugins/analytics.md +156 -0
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1,15 +1,33 @@
|
|
|
1
|
+
import { acquireSpawnLock, getSpawnLockPath, releaseSpawnLock } from "./spawn-lock.js";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
|
-
import { Command } from "commander";
|
|
4
|
+
import { Command, Option } from "commander";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
4
6
|
|
|
5
7
|
//#region src/cli/commands/generate-types.ts
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
9
|
+
* Resolve the typegen pre-flight mode for the CLI. Defaults to "non-blocking" —
|
|
10
|
+
* a one-shot CLI can't describe in the background, so by default it never
|
|
11
|
+
* describes at all: it skips the warehouse probe AND every DESCRIBE, emits
|
|
12
|
+
* best-available types (cache where the SQL hash matches, else `result: unknown`)
|
|
13
|
+
* and returns immediately, never blocking on — or failing because of — a
|
|
14
|
+
* warehouse, even a RUNNING one. Pass `--wait` (commander sets `wait: true`)
|
|
15
|
+
* for a deliberate/CI invocation that should wait for a starting warehouse and
|
|
16
|
+
* fail fast on a stopped one.
|
|
17
|
+
*/
|
|
18
|
+
function resolveTypegenMode(options) {
|
|
19
|
+
return options?.wait ? "blocking" : "non-blocking";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate types command implementation. Runs the library generate (which, in
|
|
23
|
+
* non-blocking mode, writes degraded types and returns immediately). This is the
|
|
24
|
+
* SAME work the worker performs in blocking mode in the background.
|
|
8
25
|
*/
|
|
9
26
|
async function runGenerateTypes(rootDir, outFile, warehouseId, options) {
|
|
10
27
|
try {
|
|
11
28
|
const resolvedRootDir = rootDir || process.cwd();
|
|
12
29
|
const noCache = options?.noCache || false;
|
|
30
|
+
const mode = resolveTypegenMode(options);
|
|
13
31
|
const typeGen = await import("@databricks/appkit/type-generator");
|
|
14
32
|
const resolvedWarehouseId = warehouseId || process.env.DATABRICKS_WAREHOUSE_ID;
|
|
15
33
|
if (resolvedWarehouseId) {
|
|
@@ -20,7 +38,8 @@ async function runGenerateTypes(rootDir, outFile, warehouseId, options) {
|
|
|
20
38
|
queryFolder,
|
|
21
39
|
outFile: resolvedOutFile,
|
|
22
40
|
warehouseId: resolvedWarehouseId,
|
|
23
|
-
noCache
|
|
41
|
+
noCache,
|
|
42
|
+
mode
|
|
24
43
|
});
|
|
25
44
|
console.log(`Generated query types: ${resolvedOutFile}`);
|
|
26
45
|
}
|
|
@@ -37,15 +56,105 @@ async function runGenerateTypes(rootDir, outFile, warehouseId, options) {
|
|
|
37
56
|
console.error("Please install @databricks/appkit to use this command.");
|
|
38
57
|
process.exit(1);
|
|
39
58
|
}
|
|
59
|
+
if (error instanceof Error && (error.name === "TypegenSyntaxError" || error.name === "TypegenFatalError")) {
|
|
60
|
+
console.error(error.message);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
40
63
|
throw error;
|
|
41
64
|
}
|
|
42
65
|
}
|
|
43
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Spawn the detached blocking worker that refreshes real types in the background
|
|
68
|
+
* after the foreground non-blocking generate has already written degraded types.
|
|
69
|
+
*
|
|
70
|
+
* Re-invokes THIS CLI (`process.execPath` + `process.argv[1]` — the bin entry
|
|
71
|
+
* that launched us) with `generate-types --wait --worker-lock <lockPath>` plus
|
|
72
|
+
* the same positional target options the foreground used, so the worker writes
|
|
73
|
+
* to the same out file / reads the same query folder. The worker is:
|
|
74
|
+
* - `detached: true` + `.unref()` so it outlives this process (install/dev-setup
|
|
75
|
+
* can finish and exit while the worker keeps warming the warehouse).
|
|
76
|
+
* - `stdio: "ignore"` so it never holds the parent's pipes open or interleaves
|
|
77
|
+
* output into the install/dev log.
|
|
78
|
+
*
|
|
79
|
+
* Spawning is wrapped so any failure is non-fatal: the caller still has degraded
|
|
80
|
+
* types and exits 0.
|
|
81
|
+
*
|
|
82
|
+
* @param lockPath - the acquired single-flight lock; passed to the worker so it
|
|
83
|
+
* releases the SAME lock when it finishes.
|
|
84
|
+
* @param targets - the foreground's positional args, forwarded verbatim.
|
|
85
|
+
* @returns true if the worker was spawned, false if spawning threw.
|
|
86
|
+
*/
|
|
87
|
+
function spawnTypegenWorker(lockPath, targets) {
|
|
88
|
+
const cliEntry = process.argv[1];
|
|
89
|
+
const positionals = [];
|
|
90
|
+
for (const value of [
|
|
91
|
+
targets.rootDir,
|
|
92
|
+
targets.outFile,
|
|
93
|
+
targets.warehouseId
|
|
94
|
+
]) {
|
|
95
|
+
if (value === void 0) break;
|
|
96
|
+
positionals.push(value);
|
|
97
|
+
}
|
|
98
|
+
const args = [
|
|
99
|
+
...process.execArgv,
|
|
100
|
+
cliEntry,
|
|
101
|
+
"generate-types",
|
|
102
|
+
"--wait",
|
|
103
|
+
"--worker-lock",
|
|
104
|
+
lockPath,
|
|
105
|
+
...positionals
|
|
106
|
+
];
|
|
107
|
+
try {
|
|
108
|
+
spawn(process.execPath, args, {
|
|
109
|
+
detached: true,
|
|
110
|
+
stdio: "ignore"
|
|
111
|
+
}).unref();
|
|
112
|
+
return true;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error(`Could not start background type refresh: ${error instanceof Error ? error.message : String(error)}`);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The command action. Orchestrates the non-blocking foreground contract:
|
|
120
|
+
* 1. Run the library generate (writes degraded types immediately in non-blocking
|
|
121
|
+
* mode; does the full blocking lifecycle when this is the worker).
|
|
122
|
+
* 2. If this is a non-blocking, non-worker invocation, try to spawn the detached
|
|
123
|
+
* blocking worker behind the single-flight lock. If the lock is already held
|
|
124
|
+
* by a live worker, skip (single-flight) with a one-line note. Either way the
|
|
125
|
+
* foreground returns normally (exit 0).
|
|
126
|
+
* 3. If this IS the worker (`--worker-lock` present), it ran blocking above and
|
|
127
|
+
* releases the lock here (and via a process-exit guard, so a hard failure /
|
|
128
|
+
* process.exit still frees it).
|
|
129
|
+
*/
|
|
130
|
+
async function generateTypesAction(rootDir, outFile, warehouseId, options) {
|
|
131
|
+
const isWorker = typeof options.workerLock === "string";
|
|
132
|
+
if (isWorker && options.workerLock) {
|
|
133
|
+
const lockPath = options.workerLock;
|
|
134
|
+
process.once("exit", () => releaseSpawnLock(lockPath));
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
await runGenerateTypes(rootDir, outFile, warehouseId, options);
|
|
138
|
+
} finally {
|
|
139
|
+
if (isWorker && options.workerLock) releaseSpawnLock(options.workerLock);
|
|
140
|
+
}
|
|
141
|
+
if (!isWorker && resolveTypegenMode(options) === "non-blocking") {
|
|
142
|
+
const lockPath = getSpawnLockPath(rootDir || process.cwd());
|
|
143
|
+
if (acquireSpawnLock(lockPath)) spawnTypegenWorker(lockPath, {
|
|
144
|
+
rootDir,
|
|
145
|
+
outFile,
|
|
146
|
+
warehouseId
|
|
147
|
+
});
|
|
148
|
+
else console.log("Type refresh already in progress, skipping.");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const generateTypesCommand = new Command("generate-types").description("Generate TypeScript types from SQL queries").argument("[rootDir]", "Root directory of the project", process.cwd()).argument("[outFile]", "Output file path", path.join(process.cwd(), "shared/appkit-types/analytics.d.ts")).argument("[warehouseId]", "Databricks warehouse ID").option("--no-cache", "Disable caching for type generation").option("--wait", "Wait for warehouse readiness instead of degrading (use for CI)").addOption(new Option("--worker-lock <path>", "Internal: detached worker lock path").hideHelp()).addHelpText("after", `
|
|
44
152
|
Examples:
|
|
45
153
|
$ appkit generate-types
|
|
46
154
|
$ appkit generate-types . shared/appkit-types/analytics.d.ts
|
|
47
155
|
$ appkit generate-types . shared/appkit-types/analytics.d.ts my-warehouse-id
|
|
48
|
-
$ appkit generate-types --no-cache
|
|
156
|
+
$ appkit generate-types --no-cache
|
|
157
|
+
$ appkit generate-types --wait # CI: wait for the warehouse and fail on a cold one`).action(generateTypesAction);
|
|
49
158
|
|
|
50
159
|
//#endregion
|
|
51
160
|
export { generateTypesCommand };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-types.js","names":[],"sources":["../../../src/cli/commands/generate-types.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Command } from \"commander\";\n\n/**\n * Generate types command implementation\n */\nasync function runGenerateTypes(\n rootDir?: string,\n outFile?: string,\n warehouseId?: string,\n options?: { noCache?: boolean },\n) {\n try {\n const resolvedRootDir = rootDir || process.cwd();\n const noCache = options?.noCache || false;\n\n const typeGen = await import(\"@databricks/appkit/type-generator\");\n\n // Generate analytics query types (requires warehouse ID)\n const resolvedWarehouseId =\n warehouseId || process.env.DATABRICKS_WAREHOUSE_ID;\n\n if (resolvedWarehouseId) {\n const resolvedOutFile =\n outFile ||\n path.join(process.cwd(), \"shared/appkit-types/analytics.d.ts\");\n\n const queryFolder = path.join(resolvedRootDir, \"config/queries\");\n if (fs.existsSync(queryFolder)) {\n await typeGen.generateFromEntryPoint({\n queryFolder,\n outFile: resolvedOutFile,\n warehouseId: resolvedWarehouseId,\n noCache,\n });\n console.log(`Generated query types: ${resolvedOutFile}`);\n }\n } else {\n console.error(\n \"Skipping query type generation: no warehouse ID. Set DATABRICKS_WAREHOUSE_ID or pass as argument.\",\n );\n }\n\n // Generate serving endpoint types (no warehouse required)\n const servingOutFile = path.join(\n process.cwd(),\n \"shared/appkit-types/serving.d.ts\",\n );\n await typeGen.generateServingTypes({\n outFile: servingOutFile,\n noCache,\n });\n console.log(`Generated serving types: ${servingOutFile}`);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"Cannot find module\")\n ) {\n console.error(\n \"Error: The 'generate-types' command is only available in @databricks/appkit.\",\n );\n console.error(\"Please install @databricks/appkit to use this command.\");\n process.exit(1);\n }\n throw error;\n }\n}\n\nexport const generateTypesCommand = new Command(\"generate-types\")\n .description(\"Generate TypeScript types from SQL queries\")\n .argument(\"[rootDir]\", \"Root directory of the project\", process.cwd())\n .argument(\n \"[outFile]\",\n \"Output file path\",\n path.join(process.cwd(), \"shared/appkit-types/analytics.d.ts\"),\n )\n .argument(\"[warehouseId]\", \"Databricks warehouse ID\")\n .option(\"--no-cache\", \"Disable caching for type generation\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit generate-types\n $ appkit generate-types . shared/appkit-types/analytics.d.ts\n $ appkit generate-types . shared/appkit-types/analytics.d.ts my-warehouse-id\n $ appkit generate-types --no-cache`,\n )\n .action(runGenerateTypes);\n"],"mappings":";;;;;;;;AAOA,eAAe,iBACb,SACA,SACA,aACA,SACA;AACA,KAAI;EACF,MAAM,kBAAkB,WAAW,QAAQ,KAAK;EAChD,MAAM,UAAU,SAAS,WAAW;EAEpC,MAAM,UAAU,MAAM,OAAO;EAG7B,MAAM,sBACJ,eAAe,QAAQ,IAAI;AAE7B,MAAI,qBAAqB;GACvB,MAAM,kBACJ,WACA,KAAK,KAAK,QAAQ,KAAK,EAAE,qCAAqC;GAEhE,MAAM,cAAc,KAAK,KAAK,iBAAiB,iBAAiB;AAChE,OAAI,GAAG,WAAW,YAAY,EAAE;AAC9B,UAAM,QAAQ,uBAAuB;KACnC;KACA,SAAS;KACT,aAAa;KACb;KACD,CAAC;AACF,YAAQ,IAAI,0BAA0B,kBAAkB;;QAG1D,SAAQ,MACN,oGACD;EAIH,MAAM,iBAAiB,KAAK,KAC1B,QAAQ,KAAK,EACb,mCACD;AACD,QAAM,QAAQ,qBAAqB;GACjC,SAAS;GACT;GACD,CAAC;AACF,UAAQ,IAAI,4BAA4B,iBAAiB;UAClD,OAAO;AACd,MACE,iBAAiB,SACjB,MAAM,QAAQ,SAAS,qBAAqB,EAC5C;AACA,WAAQ,MACN,+EACD;AACD,WAAQ,MAAM,yDAAyD;AACvE,WAAQ,KAAK,EAAE;;AAEjB,QAAM;;;AAIV,MAAa,uBAAuB,IAAI,QAAQ,iBAAiB,CAC9D,YAAY,6CAA6C,CACzD,SAAS,aAAa,iCAAiC,QAAQ,KAAK,CAAC,CACrE,SACC,aACA,oBACA,KAAK,KAAK,QAAQ,KAAK,EAAE,qCAAqC,CAC/D,CACA,SAAS,iBAAiB,0BAA0B,CACpD,OAAO,cAAc,sCAAsC,CAC3D,YACC,SACA;;;;;sCAMD,CACA,OAAO,iBAAiB"}
|
|
1
|
+
{"version":3,"file":"generate-types.js","names":[],"sources":["../../../src/cli/commands/generate-types.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Command, Option } from \"commander\";\nimport {\n acquireSpawnLock,\n getSpawnLockPath,\n releaseSpawnLock,\n} from \"./spawn-lock.js\";\n\n/**\n * Resolve the typegen pre-flight mode for the CLI. Defaults to \"non-blocking\" —\n * a one-shot CLI can't describe in the background, so by default it never\n * describes at all: it skips the warehouse probe AND every DESCRIBE, emits\n * best-available types (cache where the SQL hash matches, else `result: unknown`)\n * and returns immediately, never blocking on — or failing because of — a\n * warehouse, even a RUNNING one. Pass `--wait` (commander sets `wait: true`)\n * for a deliberate/CI invocation that should wait for a starting warehouse and\n * fail fast on a stopped one.\n */\nexport function resolveTypegenMode(options?: {\n wait?: boolean;\n}): \"non-blocking\" | \"blocking\" {\n return options?.wait ? \"blocking\" : \"non-blocking\";\n}\n\n/** Options parsed by commander for the generate-types command. */\ninterface GenerateTypesOptions {\n noCache?: boolean;\n wait?: boolean;\n /**\n * Internal: present only on the detached worker invocation. Carries the path\n * of the single-flight lock this worker must release when it finishes. Its\n * presence is what marks an invocation as \"the worker\" — workers always run\n * with `--wait`, so they never spawn another worker (only non-blocking runs\n * spawn), which terminates the recursion.\n */\n workerLock?: string;\n}\n\n/**\n * Generate types command implementation. Runs the library generate (which, in\n * non-blocking mode, writes degraded types and returns immediately). This is the\n * SAME work the worker performs in blocking mode in the background.\n */\nasync function runGenerateTypes(\n rootDir?: string,\n outFile?: string,\n warehouseId?: string,\n options?: GenerateTypesOptions,\n) {\n try {\n const resolvedRootDir = rootDir || process.cwd();\n const noCache = options?.noCache || false;\n const mode = resolveTypegenMode(options);\n\n const typeGen = await import(\"@databricks/appkit/type-generator\");\n\n // Generate analytics query types (requires warehouse ID)\n const resolvedWarehouseId =\n warehouseId || process.env.DATABRICKS_WAREHOUSE_ID;\n\n if (resolvedWarehouseId) {\n const resolvedOutFile =\n outFile ||\n path.join(process.cwd(), \"shared/appkit-types/analytics.d.ts\");\n\n const queryFolder = path.join(resolvedRootDir, \"config/queries\");\n if (fs.existsSync(queryFolder)) {\n await typeGen.generateFromEntryPoint({\n queryFolder,\n outFile: resolvedOutFile,\n warehouseId: resolvedWarehouseId,\n noCache,\n mode,\n });\n console.log(`Generated query types: ${resolvedOutFile}`);\n }\n } else {\n console.error(\n \"Skipping query type generation: no warehouse ID. Set DATABRICKS_WAREHOUSE_ID or pass as argument.\",\n );\n }\n\n // Generate serving endpoint types (no warehouse required)\n const servingOutFile = path.join(\n process.cwd(),\n \"shared/appkit-types/serving.d.ts\",\n );\n await typeGen.generateServingTypes({\n outFile: servingOutFile,\n noCache,\n });\n console.log(`Generated serving types: ${servingOutFile}`);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"Cannot find module\")\n ) {\n console.error(\n \"Error: The 'generate-types' command is only available in @databricks/appkit.\",\n );\n console.error(\"Please install @databricks/appkit to use this command.\");\n process.exit(1);\n }\n // TypegenSyntaxError / TypegenFatalError carry a complete, actionable\n // message (which queries failed and how to debug them). The stack trace\n // points into appkit internals and is noise for app developers, so print\n // only the message and exit non-zero instead of letting it bubble up.\n if (\n error instanceof Error &&\n (error.name === \"TypegenSyntaxError\" ||\n error.name === \"TypegenFatalError\")\n ) {\n console.error(error.message);\n process.exit(1);\n }\n throw error;\n }\n}\n\n/**\n * Spawn the detached blocking worker that refreshes real types in the background\n * after the foreground non-blocking generate has already written degraded types.\n *\n * Re-invokes THIS CLI (`process.execPath` + `process.argv[1]` — the bin entry\n * that launched us) with `generate-types --wait --worker-lock <lockPath>` plus\n * the same positional target options the foreground used, so the worker writes\n * to the same out file / reads the same query folder. The worker is:\n * - `detached: true` + `.unref()` so it outlives this process (install/dev-setup\n * can finish and exit while the worker keeps warming the warehouse).\n * - `stdio: \"ignore\"` so it never holds the parent's pipes open or interleaves\n * output into the install/dev log.\n *\n * Spawning is wrapped so any failure is non-fatal: the caller still has degraded\n * types and exits 0.\n *\n * @param lockPath - the acquired single-flight lock; passed to the worker so it\n * releases the SAME lock when it finishes.\n * @param targets - the foreground's positional args, forwarded verbatim.\n * @returns true if the worker was spawned, false if spawning threw.\n */\nexport function spawnTypegenWorker(\n lockPath: string,\n targets: { rootDir?: string; outFile?: string; warehouseId?: string },\n): boolean {\n // The script the runtime launched us with (the `appkit` bin shim). Re-running\n // it under the same node binary reproduces this exact CLI in the worker.\n const cliEntry = process.argv[1];\n\n // Forward the positionals in declaration order (rootDir, outFile,\n // warehouseId). Stop at the first undefined so we never pass a literal\n // \"undefined\" — commander would treat it as a positional value. (rootDir is\n // effectively always set by commander's default, but guard anyway.)\n const positionals: string[] = [];\n for (const value of [targets.rootDir, targets.outFile, targets.warehouseId]) {\n if (value === undefined) break;\n positionals.push(value);\n }\n\n const args = [\n // Forward the parent's node/loader flags so the worker runs under the same\n // runtime. Critically this carries tsx's `--require`/`--import` when the CLI\n // is run from source (`tsx index.ts …`); without them the worker would be\n // `node index.ts …`, which can't parse TypeScript and dies silently — the\n // degraded types would then never refresh. Empty for the built bin (plain\n // `node bin/appkit.js`), so production behaviour is unchanged.\n ...process.execArgv,\n cliEntry,\n \"generate-types\",\n \"--wait\",\n \"--worker-lock\",\n lockPath,\n ...positionals,\n ];\n\n try {\n const child = spawn(process.execPath, args, {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n return true;\n } catch (error) {\n // Non-fatal: the foreground already wrote degraded types. Log and move on.\n console.error(\n `Could not start background type refresh: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return false;\n }\n}\n\n/**\n * The command action. Orchestrates the non-blocking foreground contract:\n * 1. Run the library generate (writes degraded types immediately in non-blocking\n * mode; does the full blocking lifecycle when this is the worker).\n * 2. If this is a non-blocking, non-worker invocation, try to spawn the detached\n * blocking worker behind the single-flight lock. If the lock is already held\n * by a live worker, skip (single-flight) with a one-line note. Either way the\n * foreground returns normally (exit 0).\n * 3. If this IS the worker (`--worker-lock` present), it ran blocking above and\n * releases the lock here (and via a process-exit guard, so a hard failure /\n * process.exit still frees it).\n */\nasync function generateTypesAction(\n rootDir: string | undefined,\n outFile: string | undefined,\n warehouseId: string | undefined,\n options: GenerateTypesOptions,\n) {\n const isWorker = typeof options.workerLock === \"string\";\n\n // A worker must always free its lock, even if the blocking generate throws or\n // calls process.exit (TypegenFatalError → exit 1). The exit handler covers the\n // process.exit / uncaught paths; the finally covers the normal return.\n if (isWorker && options.workerLock) {\n const lockPath = options.workerLock;\n process.once(\"exit\", () => releaseSpawnLock(lockPath));\n }\n\n try {\n await runGenerateTypes(rootDir, outFile, warehouseId, options);\n } finally {\n if (isWorker && options.workerLock) {\n releaseSpawnLock(options.workerLock);\n }\n }\n\n // Only a non-blocking, non-worker invocation spawns. A worker is always\n // --wait (so resolveTypegenMode → \"blocking\"), which both prevents recursion\n // and means we never get here for a worker.\n if (!isWorker && resolveTypegenMode(options) === \"non-blocking\") {\n const resolvedRootDir = rootDir || process.cwd();\n const lockPath = getSpawnLockPath(resolvedRootDir);\n\n if (acquireSpawnLock(lockPath)) {\n spawnTypegenWorker(lockPath, { rootDir, outFile, warehouseId });\n } else {\n console.log(\"Type refresh already in progress, skipping.\");\n }\n }\n}\n\nexport const generateTypesCommand = new Command(\"generate-types\")\n .description(\"Generate TypeScript types from SQL queries\")\n .argument(\"[rootDir]\", \"Root directory of the project\", process.cwd())\n .argument(\n \"[outFile]\",\n \"Output file path\",\n path.join(process.cwd(), \"shared/appkit-types/analytics.d.ts\"),\n )\n .argument(\"[warehouseId]\", \"Databricks warehouse ID\")\n .option(\"--no-cache\", \"Disable caching for type generation\")\n .option(\n \"--wait\",\n \"Wait for warehouse readiness instead of degrading (use for CI)\",\n )\n // Internal: marks the detached background worker and carries the lock it must\n // release. Hidden from --help; users should never pass it.\n .addOption(\n new Option(\n \"--worker-lock <path>\",\n \"Internal: detached worker lock path\",\n ).hideHelp(),\n )\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit generate-types\n $ appkit generate-types . shared/appkit-types/analytics.d.ts\n $ appkit generate-types . shared/appkit-types/analytics.d.ts my-warehouse-id\n $ appkit generate-types --no-cache\n $ appkit generate-types --wait # CI: wait for the warehouse and fail on a cold one`,\n )\n .action(generateTypesAction);\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,SAAgB,mBAAmB,SAEH;AAC9B,QAAO,SAAS,OAAO,aAAa;;;;;;;AAsBtC,eAAe,iBACb,SACA,SACA,aACA,SACA;AACA,KAAI;EACF,MAAM,kBAAkB,WAAW,QAAQ,KAAK;EAChD,MAAM,UAAU,SAAS,WAAW;EACpC,MAAM,OAAO,mBAAmB,QAAQ;EAExC,MAAM,UAAU,MAAM,OAAO;EAG7B,MAAM,sBACJ,eAAe,QAAQ,IAAI;AAE7B,MAAI,qBAAqB;GACvB,MAAM,kBACJ,WACA,KAAK,KAAK,QAAQ,KAAK,EAAE,qCAAqC;GAEhE,MAAM,cAAc,KAAK,KAAK,iBAAiB,iBAAiB;AAChE,OAAI,GAAG,WAAW,YAAY,EAAE;AAC9B,UAAM,QAAQ,uBAAuB;KACnC;KACA,SAAS;KACT,aAAa;KACb;KACA;KACD,CAAC;AACF,YAAQ,IAAI,0BAA0B,kBAAkB;;QAG1D,SAAQ,MACN,oGACD;EAIH,MAAM,iBAAiB,KAAK,KAC1B,QAAQ,KAAK,EACb,mCACD;AACD,QAAM,QAAQ,qBAAqB;GACjC,SAAS;GACT;GACD,CAAC;AACF,UAAQ,IAAI,4BAA4B,iBAAiB;UAClD,OAAO;AACd,MACE,iBAAiB,SACjB,MAAM,QAAQ,SAAS,qBAAqB,EAC5C;AACA,WAAQ,MACN,+EACD;AACD,WAAQ,MAAM,yDAAyD;AACvE,WAAQ,KAAK,EAAE;;AAMjB,MACE,iBAAiB,UAChB,MAAM,SAAS,wBACd,MAAM,SAAS,sBACjB;AACA,WAAQ,MAAM,MAAM,QAAQ;AAC5B,WAAQ,KAAK,EAAE;;AAEjB,QAAM;;;;;;;;;;;;;;;;;;;;;;;;AAyBV,SAAgB,mBACd,UACA,SACS;CAGT,MAAM,WAAW,QAAQ,KAAK;CAM9B,MAAM,cAAwB,EAAE;AAChC,MAAK,MAAM,SAAS;EAAC,QAAQ;EAAS,QAAQ;EAAS,QAAQ;EAAY,EAAE;AAC3E,MAAI,UAAU,OAAW;AACzB,cAAY,KAAK,MAAM;;CAGzB,MAAM,OAAO;EAOX,GAAG,QAAQ;EACX;EACA;EACA;EACA;EACA;EACA,GAAG;EACJ;AAED,KAAI;AAKF,EAJc,MAAM,QAAQ,UAAU,MAAM;GAC1C,UAAU;GACV,OAAO;GACR,CAAC,CACI,OAAO;AACb,SAAO;UACA,OAAO;AAEd,UAAQ,MACN,4CACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAEzD;AACD,SAAO;;;;;;;;;;;;;;;AAgBX,eAAe,oBACb,SACA,SACA,aACA,SACA;CACA,MAAM,WAAW,OAAO,QAAQ,eAAe;AAK/C,KAAI,YAAY,QAAQ,YAAY;EAClC,MAAM,WAAW,QAAQ;AACzB,UAAQ,KAAK,cAAc,iBAAiB,SAAS,CAAC;;AAGxD,KAAI;AACF,QAAM,iBAAiB,SAAS,SAAS,aAAa,QAAQ;WACtD;AACR,MAAI,YAAY,QAAQ,WACtB,kBAAiB,QAAQ,WAAW;;AAOxC,KAAI,CAAC,YAAY,mBAAmB,QAAQ,KAAK,gBAAgB;EAE/D,MAAM,WAAW,iBADO,WAAW,QAAQ,KAAK,CACE;AAElD,MAAI,iBAAiB,SAAS,CAC5B,oBAAmB,UAAU;GAAE;GAAS;GAAS;GAAa,CAAC;MAE/D,SAAQ,IAAI,8CAA8C;;;AAKhE,MAAa,uBAAuB,IAAI,QAAQ,iBAAiB,CAC9D,YAAY,6CAA6C,CACzD,SAAS,aAAa,iCAAiC,QAAQ,KAAK,CAAC,CACrE,SACC,aACA,oBACA,KAAK,KAAK,QAAQ,KAAK,EAAE,qCAAqC,CAC/D,CACA,SAAS,iBAAiB,0BAA0B,CACpD,OAAO,cAAc,sCAAsC,CAC3D,OACC,UACA,iEACD,CAGA,UACC,IAAI,OACF,wBACA,sCACD,CAAC,UAAU,CACb,CACA,YACC,SACA;;;;;;wFAOD,CACA,OAAO,oBAAoB"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/cli/commands/spawn-lock.ts
|
|
5
|
+
/**
|
|
6
|
+
* How long a spawn lock is considered fresh. A held lock newer than this means a
|
|
7
|
+
* background worker is genuinely in flight, so the foreground skips spawning;
|
|
8
|
+
* older than this the lock is presumed orphaned (the worker crashed/was killed
|
|
9
|
+
* before it could release) and is stolen.
|
|
10
|
+
*
|
|
11
|
+
* Must comfortably exceed the worker's worst-case runtime: the blocking preflight
|
|
12
|
+
* wait cap (PREFLIGHT_WAIT_MAX_MS = 5 min in the type-generator) plus a DESCRIBE
|
|
13
|
+
* budget. Six minutes leaves ~1 min of headroom over a worker that waits the full
|
|
14
|
+
* preflight window and then describes.
|
|
15
|
+
*/
|
|
16
|
+
const SPAWN_LOCK_STALE_MS = 360 * 1e3;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the on-disk path of the single-flight spawn lock for a project.
|
|
19
|
+
*
|
|
20
|
+
* Lives alongside the type-generator cache (`node_modules/.databricks/appkit/`)
|
|
21
|
+
* so it shares the same already-creatable, gitignored, per-project location and
|
|
22
|
+
* doesn't introduce a new directory. The lock is keyed only by project root, so
|
|
23
|
+
* concurrent `generate-types` invocations for the same project (postinstall +
|
|
24
|
+
* predev, say) contend for one lock and only one wins the spawn.
|
|
25
|
+
*
|
|
26
|
+
* @param rootDir - project root (the resolved first CLI argument / cwd).
|
|
27
|
+
* @returns absolute path to the lock file.
|
|
28
|
+
*/
|
|
29
|
+
function getSpawnLockPath(rootDir) {
|
|
30
|
+
return path.join(rootDir, "node_modules", ".databricks", "appkit", ".appkit-typegen-worker.lock");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Try to acquire the single-flight spawn lock.
|
|
34
|
+
*
|
|
35
|
+
* Atomic create via `fs.writeFileSync(lockPath, ..., { flag: "wx" })` — `wx`
|
|
36
|
+
* fails (EEXIST) if the file already exists, so the create itself is the
|
|
37
|
+
* mutual-exclusion primitive (no check-then-create race between two foreground
|
|
38
|
+
* processes). The lock body records pid + timestamp purely for debugging.
|
|
39
|
+
*
|
|
40
|
+
* On EEXIST we stat the existing lock:
|
|
41
|
+
* - fresh (mtime within {@link staleMs}) → a worker is in flight, return false.
|
|
42
|
+
* - stale (mtime older than staleMs) → presumed orphaned; unlink and recreate.
|
|
43
|
+
* The recreate also uses `wx`, so if a competing process steals it first we
|
|
44
|
+
* lose the race cleanly and return false.
|
|
45
|
+
*
|
|
46
|
+
* Any unexpected error (permission, ENOENT on a missing parent dir we couldn't
|
|
47
|
+
* create, …) is swallowed and reported as "not acquired": failing to take the
|
|
48
|
+
* lock must never break the foreground — at worst we skip the background refresh.
|
|
49
|
+
*
|
|
50
|
+
* @param lockPath - path returned by {@link getSpawnLockPath}.
|
|
51
|
+
* @param staleMs - age beyond which a held lock is stolen. Defaults to
|
|
52
|
+
* {@link SPAWN_LOCK_STALE_MS}.
|
|
53
|
+
* @returns true if this caller now owns the lock (and must release it), false if
|
|
54
|
+
* another live worker holds it or the lock couldn't be taken.
|
|
55
|
+
*/
|
|
56
|
+
function acquireSpawnLock(lockPath, staleMs = SPAWN_LOCK_STALE_MS) {
|
|
57
|
+
const body = `${process.pid} ${Date.now()}\n`;
|
|
58
|
+
try {
|
|
59
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
60
|
+
} catch {}
|
|
61
|
+
try {
|
|
62
|
+
fs.writeFileSync(lockPath, body, { flag: "wx" });
|
|
63
|
+
return true;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (!isErrnoException(error) || error.code !== "EEXIST") return false;
|
|
66
|
+
}
|
|
67
|
+
let mtimeMs;
|
|
68
|
+
try {
|
|
69
|
+
mtimeMs = fs.statSync(lockPath).mtimeMs;
|
|
70
|
+
} catch {
|
|
71
|
+
return tryCreate(lockPath, body);
|
|
72
|
+
}
|
|
73
|
+
if (Date.now() - mtimeMs < staleMs) return false;
|
|
74
|
+
try {
|
|
75
|
+
fs.unlinkSync(lockPath);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (isErrnoException(error) && error.code !== "ENOENT") return false;
|
|
78
|
+
}
|
|
79
|
+
return tryCreate(lockPath, body);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Release the spawn lock. Unlink, ignoring ENOENT (already gone — e.g. it was
|
|
83
|
+
* stolen as stale by another process, or never existed). Any other error is
|
|
84
|
+
* swallowed: a failed release at worst leaves a stale lock that the next caller
|
|
85
|
+
* will steal after {@link SPAWN_LOCK_STALE_MS}.
|
|
86
|
+
*
|
|
87
|
+
* @param lockPath - path returned by {@link getSpawnLockPath}.
|
|
88
|
+
*/
|
|
89
|
+
function releaseSpawnLock(lockPath) {
|
|
90
|
+
try {
|
|
91
|
+
fs.unlinkSync(lockPath);
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Attempt an atomic `wx` create, returning whether it succeeded. EEXIST (a
|
|
96
|
+
* racing creator beat us) and any other error map to false.
|
|
97
|
+
*/
|
|
98
|
+
function tryCreate(lockPath, body) {
|
|
99
|
+
try {
|
|
100
|
+
fs.writeFileSync(lockPath, body, { flag: "wx" });
|
|
101
|
+
return true;
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Narrow an unknown caught value to a Node errno exception so `.code` is safe to
|
|
108
|
+
* read.
|
|
109
|
+
*/
|
|
110
|
+
function isErrnoException(error) {
|
|
111
|
+
return error instanceof Error && "code" in error;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
export { acquireSpawnLock, getSpawnLockPath, releaseSpawnLock };
|
|
116
|
+
//# sourceMappingURL=spawn-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-lock.js","names":[],"sources":["../../../src/cli/commands/spawn-lock.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * How long a spawn lock is considered fresh. A held lock newer than this means a\n * background worker is genuinely in flight, so the foreground skips spawning;\n * older than this the lock is presumed orphaned (the worker crashed/was killed\n * before it could release) and is stolen.\n *\n * Must comfortably exceed the worker's worst-case runtime: the blocking preflight\n * wait cap (PREFLIGHT_WAIT_MAX_MS = 5 min in the type-generator) plus a DESCRIBE\n * budget. Six minutes leaves ~1 min of headroom over a worker that waits the full\n * preflight window and then describes.\n */\nexport const SPAWN_LOCK_STALE_MS = 6 * 60 * 1000;\n\n/**\n * Resolve the on-disk path of the single-flight spawn lock for a project.\n *\n * Lives alongside the type-generator cache (`node_modules/.databricks/appkit/`)\n * so it shares the same already-creatable, gitignored, per-project location and\n * doesn't introduce a new directory. The lock is keyed only by project root, so\n * concurrent `generate-types` invocations for the same project (postinstall +\n * predev, say) contend for one lock and only one wins the spawn.\n *\n * @param rootDir - project root (the resolved first CLI argument / cwd).\n * @returns absolute path to the lock file.\n */\nexport function getSpawnLockPath(rootDir: string): string {\n return path.join(\n rootDir,\n \"node_modules\",\n \".databricks\",\n \"appkit\",\n \".appkit-typegen-worker.lock\",\n );\n}\n\n/**\n * Try to acquire the single-flight spawn lock.\n *\n * Atomic create via `fs.writeFileSync(lockPath, ..., { flag: \"wx\" })` — `wx`\n * fails (EEXIST) if the file already exists, so the create itself is the\n * mutual-exclusion primitive (no check-then-create race between two foreground\n * processes). The lock body records pid + timestamp purely for debugging.\n *\n * On EEXIST we stat the existing lock:\n * - fresh (mtime within {@link staleMs}) → a worker is in flight, return false.\n * - stale (mtime older than staleMs) → presumed orphaned; unlink and recreate.\n * The recreate also uses `wx`, so if a competing process steals it first we\n * lose the race cleanly and return false.\n *\n * Any unexpected error (permission, ENOENT on a missing parent dir we couldn't\n * create, …) is swallowed and reported as \"not acquired\": failing to take the\n * lock must never break the foreground — at worst we skip the background refresh.\n *\n * @param lockPath - path returned by {@link getSpawnLockPath}.\n * @param staleMs - age beyond which a held lock is stolen. Defaults to\n * {@link SPAWN_LOCK_STALE_MS}.\n * @returns true if this caller now owns the lock (and must release it), false if\n * another live worker holds it or the lock couldn't be taken.\n */\nexport function acquireSpawnLock(\n lockPath: string,\n staleMs: number = SPAWN_LOCK_STALE_MS,\n): boolean {\n const body = `${process.pid} ${Date.now()}\\n`;\n\n try {\n fs.mkdirSync(path.dirname(lockPath), { recursive: true });\n } catch {\n // Parent dir creation is best-effort; the create below will surface any real\n // problem and we'll treat it as \"not acquired\".\n }\n\n try {\n fs.writeFileSync(lockPath, body, { flag: \"wx\" });\n return true;\n } catch (error) {\n if (!isErrnoException(error) || error.code !== \"EEXIST\") {\n // Unexpected failure — don't let lock IO break the foreground.\n return false;\n }\n }\n\n // Lock exists — decide fresh vs stale.\n let mtimeMs: number;\n try {\n mtimeMs = fs.statSync(lockPath).mtimeMs;\n } catch {\n // It vanished between the failed create and the stat (released by the\n // worker). Try once more to take it.\n return tryCreate(lockPath, body);\n }\n\n if (Date.now() - mtimeMs < staleMs) {\n // A worker is genuinely in flight.\n return false;\n }\n\n // Stale: steal it. Unlink (ignore ENOENT — someone else may have cleaned up)\n // then re-create with `wx` so we still lose cleanly to a racing stealer.\n try {\n fs.unlinkSync(lockPath);\n } catch (error) {\n if (isErrnoException(error) && error.code !== \"ENOENT\") {\n return false;\n }\n }\n return tryCreate(lockPath, body);\n}\n\n/**\n * Release the spawn lock. Unlink, ignoring ENOENT (already gone — e.g. it was\n * stolen as stale by another process, or never existed). Any other error is\n * swallowed: a failed release at worst leaves a stale lock that the next caller\n * will steal after {@link SPAWN_LOCK_STALE_MS}.\n *\n * @param lockPath - path returned by {@link getSpawnLockPath}.\n */\nexport function releaseSpawnLock(lockPath: string): void {\n try {\n fs.unlinkSync(lockPath);\n } catch {\n // ENOENT or any other error — releasing is best-effort.\n }\n}\n\n/**\n * Attempt an atomic `wx` create, returning whether it succeeded. EEXIST (a\n * racing creator beat us) and any other error map to false.\n */\nfunction tryCreate(lockPath: string, body: string): boolean {\n try {\n fs.writeFileSync(lockPath, body, { flag: \"wx\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Narrow an unknown caught value to a Node errno exception so `.code` is safe to\n * read.\n */\nfunction isErrnoException(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,MAAa,sBAAsB,MAAS;;;;;;;;;;;;;AAc5C,SAAgB,iBAAiB,SAAyB;AACxD,QAAO,KAAK,KACV,SACA,gBACA,eACA,UACA,8BACD;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BH,SAAgB,iBACd,UACA,UAAkB,qBACT;CACT,MAAM,OAAO,GAAG,QAAQ,IAAI,GAAG,KAAK,KAAK,CAAC;AAE1C,KAAI;AACF,KAAG,UAAU,KAAK,QAAQ,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;SACnD;AAKR,KAAI;AACF,KAAG,cAAc,UAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAChD,SAAO;UACA,OAAO;AACd,MAAI,CAAC,iBAAiB,MAAM,IAAI,MAAM,SAAS,SAE7C,QAAO;;CAKX,IAAI;AACJ,KAAI;AACF,YAAU,GAAG,SAAS,SAAS,CAAC;SAC1B;AAGN,SAAO,UAAU,UAAU,KAAK;;AAGlC,KAAI,KAAK,KAAK,GAAG,UAAU,QAEzB,QAAO;AAKT,KAAI;AACF,KAAG,WAAW,SAAS;UAChB,OAAO;AACd,MAAI,iBAAiB,MAAM,IAAI,MAAM,SAAS,SAC5C,QAAO;;AAGX,QAAO,UAAU,UAAU,KAAK;;;;;;;;;;AAWlC,SAAgB,iBAAiB,UAAwB;AACvD,KAAI;AACF,KAAG,WAAW,SAAS;SACjB;;;;;;AASV,SAAS,UAAU,UAAkB,MAAuB;AAC1D,KAAI;AACF,KAAG,cAAc,UAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAChD,SAAO;SACD;AACN,SAAO;;;;;;;AAQX,SAAS,iBAAiB,OAAgD;AACxE,QAAO,iBAAiB,SAAS,UAAU"}
|
package/dist/cli/index.js
CHANGED
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { codemodCommand } from \"./commands/codemod/index.js\";\nimport { docsCommand } from \"./commands/docs.js\";\nimport { generateTypesCommand } from \"./commands/generate-types.js\";\nimport { lintCommand } from \"./commands/lint.js\";\nimport { pluginCommand } from \"./commands/plugin/index.js\";\nimport { setupCommand } from \"./commands/setup.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgPath = join(__dirname, \"../../package.json\");\nconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\nconst cmd = new Command();\n\ncmd\n .name(\"appkit\")\n .description(\"CLI tools for Databricks AppKit\")\n .version(pkg.version);\n\ncmd.addCommand(setupCommand);\ncmd.addCommand(generateTypesCommand);\ncmd.addCommand(lintCommand);\ncmd.addCommand(docsCommand);\ncmd.addCommand(pluginCommand);\ncmd.addCommand(codemodCommand);\n\
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport \"dotenv/config\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { codemodCommand } from \"./commands/codemod/index.js\";\nimport { docsCommand } from \"./commands/docs.js\";\nimport { generateTypesCommand } from \"./commands/generate-types.js\";\nimport { lintCommand } from \"./commands/lint.js\";\nimport { pluginCommand } from \"./commands/plugin/index.js\";\nimport { setupCommand } from \"./commands/setup.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgPath = join(__dirname, \"../../package.json\");\nconst pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n\nconst cmd = new Command();\n\ncmd\n .name(\"appkit\")\n .description(\"CLI tools for Databricks AppKit\")\n .version(pkg.version);\n\ncmd.addCommand(setupCommand);\ncmd.addCommand(generateTypesCommand);\ncmd.addCommand(lintCommand);\ncmd.addCommand(docsCommand);\ncmd.addCommand(pluginCommand);\ncmd.addCommand(codemodCommand);\n\nawait cmd.parseAsync();\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAM,UAAU,KADE,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACzB,qBAAqB;AACrD,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC;AAEtD,MAAM,MAAM,IAAI,SAAS;AAEzB,IACG,KAAK,SAAS,CACd,YAAY,kCAAkC,CAC9C,QAAQ,IAAI,QAAQ;AAEvB,IAAI,WAAW,aAAa;AAC5B,IAAI,WAAW,qBAAqB;AACpC,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,YAAY;AAC3B,IAAI,WAAW,cAAc;AAC7B,IAAI,WAAW,eAAe;AAE9B,MAAM,IAAI,YAAY"}
|
|
@@ -10,6 +10,7 @@ import { ScatterChart } from "./scatter/index.js";
|
|
|
10
10
|
import { UseChartDataOptions, UseChartDataResult, useChartData } from "../hooks/use-chart-data.js";
|
|
11
11
|
import { BaseChart, BaseChartProps } from "./base.js";
|
|
12
12
|
import { createChart } from "./create-chart.js";
|
|
13
|
+
import { LoadingSkeleton, ResourceWaitingPlaceholder } from "./loading.js";
|
|
13
14
|
import { ChartWrapper, ChartWrapperProps } from "./wrapper.js";
|
|
14
15
|
import { NormalizedHeatmapData, normalizeChartData, normalizeHeatmapData } from "./normalize.js";
|
|
15
16
|
import { CHART_COLOR_VARS, CHART_COLOR_VARS_CATEGORICAL, CHART_COLOR_VARS_DIVERGING, CHART_COLOR_VARS_SEQUENTIAL, FALLBACK_COLORS_CATEGORICAL, FALLBACK_COLORS_DIVERGING, FALLBACK_COLORS_SEQUENTIAL } from "./constants.js";
|
|
@@ -7,6 +7,7 @@ import { buildCartesianOption, buildHeatmapOption, buildHorizontalBarOption, bui
|
|
|
7
7
|
import { useAllThemeColors, useThemeColors } from "./theme.js";
|
|
8
8
|
import { BaseChart } from "./base.js";
|
|
9
9
|
import { useChartData } from "../hooks/use-chart-data.js";
|
|
10
|
+
import { LoadingSkeleton, ResourceWaitingPlaceholder } from "./loading.js";
|
|
10
11
|
import { ChartWrapper } from "./wrapper.js";
|
|
11
12
|
import { createChart } from "./create-chart.js";
|
|
12
13
|
import { AreaChart } from "./area/index.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/react/charts/loading.d.ts
|
|
4
|
+
declare function LoadingSkeleton({
|
|
5
|
+
height
|
|
6
|
+
}: {
|
|
7
|
+
height?: number | string;
|
|
8
|
+
}): react_jsx_runtime0.JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* Non-shimmery placeholder for cold-starting backing resources. Use this
|
|
11
|
+
* over {@link LoadingSkeleton} when `useChartData` reports a non-null
|
|
12
|
+
* `warehouseStatus` — a shimmer during a 30s–2min wait is misleading.
|
|
13
|
+
* The global {@link ResourceStatusIndicator} surfaces the "why".
|
|
14
|
+
*/
|
|
15
|
+
declare function ResourceWaitingPlaceholder({
|
|
16
|
+
height,
|
|
17
|
+
message
|
|
18
|
+
}: {
|
|
19
|
+
height?: number | string;
|
|
20
|
+
message?: string;
|
|
21
|
+
}): react_jsx_runtime0.JSX.Element;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { LoadingSkeleton, ResourceWaitingPlaceholder };
|
|
24
|
+
//# sourceMappingURL=loading.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loading.d.ts","names":[],"sources":["../../../src/react/charts/loading.tsx"],"mappings":";;;iBAEgB,eAAA,CAAA;EACd;AAAA;EAEA,MAAA;AAAA,IACD,kBAAA,CAAA,GAAA,CAAA,OAAA;AAJD;;;;;;AAAA,iBAgBgB,0BAAA,CAAA;EACd,MAAA;EACA;AAAA;EAEA,MAAA;EACA,OAAA;AAAA,IACD,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Loader2Icon } from "lucide-react";
|
|
2
3
|
|
|
3
4
|
//#region src/react/charts/loading.tsx
|
|
4
5
|
function LoadingSkeleton({ height = 300 }) {
|
|
@@ -7,7 +8,24 @@ function LoadingSkeleton({ height = 300 }) {
|
|
|
7
8
|
style: { height }
|
|
8
9
|
});
|
|
9
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Non-shimmery placeholder for cold-starting backing resources. Use this
|
|
13
|
+
* over {@link LoadingSkeleton} when `useChartData` reports a non-null
|
|
14
|
+
* `warehouseStatus` — a shimmer during a 30s–2min wait is misleading.
|
|
15
|
+
* The global {@link ResourceStatusIndicator} surfaces the "why".
|
|
16
|
+
*/
|
|
17
|
+
function ResourceWaitingPlaceholder({ height = 300, message = "Waiting for warehouse…" }) {
|
|
18
|
+
return /* @__PURE__ */ jsx("output", {
|
|
19
|
+
className: "w-full rounded border border-dashed border-border/60 bg-muted/30 flex items-center justify-center text-muted-foreground",
|
|
20
|
+
style: { height },
|
|
21
|
+
"aria-live": "polite",
|
|
22
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
23
|
+
className: "flex items-center gap-2 text-xs",
|
|
24
|
+
children: [/* @__PURE__ */ jsx(Loader2Icon, { className: "h-3.5 w-3.5 animate-spin opacity-60" }), /* @__PURE__ */ jsx("span", { children: message })]
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
}
|
|
10
28
|
|
|
11
29
|
//#endregion
|
|
12
|
-
export { LoadingSkeleton };
|
|
30
|
+
export { LoadingSkeleton, ResourceWaitingPlaceholder };
|
|
13
31
|
//# sourceMappingURL=loading.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loading.js","names":[],"sources":["../../../src/react/charts/loading.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"loading.js","names":[],"sources":["../../../src/react/charts/loading.tsx"],"sourcesContent":["import { Loader2Icon } from \"lucide-react\";\n\nexport function LoadingSkeleton({\n height = 300,\n}: {\n height?: number | string;\n}) {\n return (\n <div className=\"w-full animate-pulse bg-muted rounded\" style={{ height }} />\n );\n}\n\n/**\n * Non-shimmery placeholder for cold-starting backing resources. Use this\n * over {@link LoadingSkeleton} when `useChartData` reports a non-null\n * `warehouseStatus` — a shimmer during a 30s–2min wait is misleading.\n * The global {@link ResourceStatusIndicator} surfaces the \"why\".\n */\nexport function ResourceWaitingPlaceholder({\n height = 300,\n message = \"Waiting for warehouse…\",\n}: {\n height?: number | string;\n message?: string;\n}) {\n return (\n <output\n className=\"w-full rounded border border-dashed border-border/60 bg-muted/30 flex items-center justify-center text-muted-foreground\"\n style={{ height }}\n aria-live=\"polite\"\n >\n <span className=\"flex items-center gap-2 text-xs\">\n <Loader2Icon className=\"h-3.5 w-3.5 animate-spin opacity-60\" />\n <span>{message}</span>\n </span>\n </output>\n );\n}\n"],"mappings":";;;;AAEA,SAAgB,gBAAgB,EAC9B,SAAS,OAGR;AACD,QACE,oBAAC;EAAI,WAAU;EAAwC,OAAO,EAAE,QAAQ;GAAI;;;;;;;;AAUhF,SAAgB,2BAA2B,EACzC,SAAS,KACT,UAAU,4BAIT;AACD,QACE,oBAAC;EACC,WAAU;EACV,OAAO,EAAE,QAAQ;EACjB,aAAU;YAEV,qBAAC;GAAK,WAAU;cACd,oBAAC,eAAY,WAAU,wCAAwC,EAC/D,oBAAC,oBAAM,UAAe;IACjB;GACA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper.d.ts","names":[],"sources":["../../../src/react/charts/wrapper.tsx"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"wrapper.d.ts","names":[],"sources":["../../../src/react/charts/wrapper.tsx"],"mappings":";;;;;UA+BU,sBAAA;;EAER,QAAA;EAFQ;EAIR,UAAA,GAAa,MAAA;EAJiB;EAM9B,MAAA,GAAS,UAAA;EAAA;EAET,WAAA,OAAkB,IAAA,EAAM,CAAA,KAAM,CAAA;EAAA;EAE9B,IAAA;AAAA;AAAA,UAGQ,qBAAA;EATR;EAWA,IAAA,EAAM,SAAA;EATN;EAWA,QAAA;EACA,UAAA;EACA,MAAA;EACA,WAAA;AAAA;AAAA,UAGQ,WAAA;EAbR;EAeA,MAAA;EAfI;EAiBJ,SAAA;EAd6B;EAgB7B,SAAA;EAde;EAgBf,MAAA;EAhBM;EAkBN,QAAA,GAAW,IAAA,EAAM,SAAA,KAAc,SAAA;AAAA;AAAA,KAGrB,iBAAA,GAAoB,WAAA,IAC7B,sBAAA,GAAyB,qBAAA;;;;AAjBf;;;;;;;;;;;;;;AAgBb;;;;;iBA8GgB,YAAA,CAAa,KAAA,EAAO,iBAAA,GAAiB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -3,17 +3,24 @@ import { useChartData } from "../hooks/use-chart-data.js";
|
|
|
3
3
|
import { ChartErrorBoundary } from "./chart-error-boundary.js";
|
|
4
4
|
import { EmptyState } from "./empty.js";
|
|
5
5
|
import { ErrorState } from "./error.js";
|
|
6
|
-
import { LoadingSkeleton } from "./loading.js";
|
|
6
|
+
import { LoadingSkeleton, ResourceWaitingPlaceholder } from "./loading.js";
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
8
8
|
|
|
9
9
|
//#region src/react/charts/wrapper.tsx
|
|
10
|
+
const WAREHOUSE_ERROR_STATES = new Set(["DELETED", "DELETING"]);
|
|
11
|
+
function isWarehouseWaiting(loading, data, warehouseStatus) {
|
|
12
|
+
if (!loading || data || !warehouseStatus) return false;
|
|
13
|
+
if (warehouseStatus.state === "RUNNING" || WAREHOUSE_ERROR_STATES.has(warehouseStatus.state)) return false;
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
10
16
|
function QueryModeContent({ queryKey, parameters, format, transformer, height, className, ariaLabel, testId, children }) {
|
|
11
|
-
const { data, loading, error, isEmpty } = useChartData({
|
|
17
|
+
const { data, loading, error, isEmpty, warehouseStatus } = useChartData({
|
|
12
18
|
queryKey,
|
|
13
19
|
parameters,
|
|
14
20
|
format,
|
|
15
21
|
transformer
|
|
16
22
|
});
|
|
23
|
+
if (isWarehouseWaiting(loading, data, warehouseStatus)) return /* @__PURE__ */ jsx(ResourceWaitingPlaceholder, { height: height ?? 300 });
|
|
17
24
|
if (loading) return /* @__PURE__ */ jsx(LoadingSkeleton, { height: height ?? 300 });
|
|
18
25
|
if (error) return /* @__PURE__ */ jsx(ErrorState, { error });
|
|
19
26
|
if (isEmpty || !data) return /* @__PURE__ */ jsx(EmptyState, {});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper.js","names":[],"sources":["../../../src/react/charts/wrapper.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { useChartData } from \"../hooks/use-chart-data\";\nimport { ChartErrorBoundary } from \"./chart-error-boundary\";\nimport { EmptyState } from \"./empty\";\nimport { ErrorState } from \"./error\";\nimport { LoadingSkeleton } from \"./loading\";\nimport type { ChartData, DataFormat } from \"./types\";\nimport { isArrowTable } from \"./types\";\n\n// ============================================================================\n// Props Types\n// ============================================================================\n\ninterface ChartWrapperQueryProps {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /** Data format preference */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n /** Direct data - not used in query mode */\n data?: never;\n}\n\ninterface ChartWrapperDataProps {\n /** Direct data (Arrow Table or JSON array) */\n data: ChartData;\n /** Not used in data mode */\n queryKey?: never;\n parameters?: never;\n format?: never;\n transformer?: never;\n}\n\ninterface CommonProps {\n /** Chart height in pixels */\n height?: number;\n /** Additional CSS classes */\n className?: string;\n /** Accessibility label */\n ariaLabel?: string;\n /** Test ID for automated testing */\n testId?: string;\n /** Render function receiving the chart data */\n children: (data: ChartData) => ReactNode;\n}\n\nexport type ChartWrapperProps = CommonProps &\n (ChartWrapperQueryProps | ChartWrapperDataProps);\n\n// ============================================================================\n// Query Mode Content\n// ============================================================================\n\nfunction QueryModeContent({\n queryKey,\n parameters,\n format,\n transformer,\n height,\n className,\n ariaLabel,\n testId,\n children,\n}: CommonProps & ChartWrapperQueryProps) {\n const { data, loading, error, isEmpty } = useChartData({\n queryKey,\n parameters,\n format,\n transformer,\n });\n\n if (loading) return <LoadingSkeleton height={height ?? 300} />;\n if (error) return <ErrorState error={error} />;\n if (isEmpty || !data) return <EmptyState />;\n\n return (\n <ChartErrorBoundary\n fallback={<ErrorState error=\"Failed to render chart\" />}\n >\n <div\n className={className}\n style={{ height }}\n aria-label={ariaLabel}\n data-testid={testId}\n role=\"img\"\n >\n {children(data)}\n </div>\n </ChartErrorBoundary>\n );\n}\n\n// ============================================================================\n// Data Mode Content\n// ============================================================================\n\nfunction DataModeContent({\n data,\n height,\n className,\n ariaLabel,\n testId,\n children,\n}: CommonProps & ChartWrapperDataProps) {\n const isEmpty = isArrowTable(data)\n ? data.numRows === 0\n : !Array.isArray(data) || data.length === 0;\n\n if (isEmpty) return <EmptyState />;\n\n return (\n <ChartErrorBoundary\n fallback={<ErrorState error=\"Failed to render chart\" />}\n >\n <div\n className={className}\n style={{ height }}\n aria-label={ariaLabel}\n data-testid={testId}\n role=\"img\"\n >\n {children(data)}\n </div>\n </ChartErrorBoundary>\n );\n}\n\n// ============================================================================\n// Main Wrapper Component\n// ============================================================================\n\n/**\n * Wrapper component for charts.\n * Handles data fetching (query mode) or direct data injection (data mode).\n *\n * @example Query mode - fetches data from analytics endpoint\n * ```tsx\n * <ChartWrapper\n * queryKey=\"spend_data\"\n * parameters={{ limit: 100 }}\n * format=\"auto\"\n * >\n * {(data) => <MyChart data={data} />}\n * </ChartWrapper>\n * ```\n *\n * @example Data mode - uses provided data directly\n * ```tsx\n * <ChartWrapper data={myArrowTable}>\n * {(data) => <MyChart data={data} />}\n * </ChartWrapper>\n * ```\n */\nexport function ChartWrapper(props: ChartWrapperProps) {\n const { height = 300, className, ariaLabel, testId, children } = props;\n\n // Data mode: use provided data directly\n if (\"data\" in props && props.data !== undefined) {\n return (\n <DataModeContent\n data={props.data}\n height={height}\n className={className}\n ariaLabel={ariaLabel}\n testId={testId}\n >\n {children}\n </DataModeContent>\n );\n }\n\n // Query mode: fetch data from analytics endpoint\n if (\"queryKey\" in props && props.queryKey !== undefined) {\n return (\n <QueryModeContent\n queryKey={props.queryKey}\n parameters={props.parameters}\n format={props.format}\n transformer={props.transformer}\n height={height}\n className={className}\n ariaLabel={ariaLabel}\n testId={testId}\n >\n {children}\n </QueryModeContent>\n );\n }\n\n // Should never reach here due to TypeScript, but safety fallback\n return <ErrorState error=\"Chart requires either 'queryKey' or 'data' prop\" />;\n}\n"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"wrapper.js","names":[],"sources":["../../../src/react/charts/wrapper.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { WarehouseStatus } from \"../hooks/types\";\nimport { useChartData } from \"../hooks/use-chart-data\";\nimport { ChartErrorBoundary } from \"./chart-error-boundary\";\nimport { EmptyState } from \"./empty\";\nimport { ErrorState } from \"./error\";\nimport { LoadingSkeleton, ResourceWaitingPlaceholder } from \"./loading\";\nimport type { ChartData, DataFormat } from \"./types\";\nimport { isArrowTable } from \"./types\";\n\nconst WAREHOUSE_ERROR_STATES = new Set([\"DELETED\", \"DELETING\"]);\n\nfunction isWarehouseWaiting(\n loading: boolean,\n data: ChartData | null,\n warehouseStatus: WarehouseStatus | null,\n): boolean {\n if (!loading || data || !warehouseStatus) return false;\n if (\n warehouseStatus.state === \"RUNNING\" ||\n WAREHOUSE_ERROR_STATES.has(warehouseStatus.state)\n ) {\n return false;\n }\n return true;\n}\n\n// ============================================================================\n// Props Types\n// ============================================================================\n\ninterface ChartWrapperQueryProps {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /** Data format preference */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n /** Direct data - not used in query mode */\n data?: never;\n}\n\ninterface ChartWrapperDataProps {\n /** Direct data (Arrow Table or JSON array) */\n data: ChartData;\n /** Not used in data mode */\n queryKey?: never;\n parameters?: never;\n format?: never;\n transformer?: never;\n}\n\ninterface CommonProps {\n /** Chart height in pixels */\n height?: number;\n /** Additional CSS classes */\n className?: string;\n /** Accessibility label */\n ariaLabel?: string;\n /** Test ID for automated testing */\n testId?: string;\n /** Render function receiving the chart data */\n children: (data: ChartData) => ReactNode;\n}\n\nexport type ChartWrapperProps = CommonProps &\n (ChartWrapperQueryProps | ChartWrapperDataProps);\n\n// ============================================================================\n// Query Mode Content\n// ============================================================================\n\nfunction QueryModeContent({\n queryKey,\n parameters,\n format,\n transformer,\n height,\n className,\n ariaLabel,\n testId,\n children,\n}: CommonProps & ChartWrapperQueryProps) {\n const { data, loading, error, isEmpty, warehouseStatus } = useChartData({\n queryKey,\n parameters,\n format,\n transformer,\n });\n\n if (isWarehouseWaiting(loading, data, warehouseStatus)) {\n return <ResourceWaitingPlaceholder height={height ?? 300} />;\n }\n if (loading) return <LoadingSkeleton height={height ?? 300} />;\n if (error) return <ErrorState error={error} />;\n if (isEmpty || !data) return <EmptyState />;\n\n return (\n <ChartErrorBoundary\n fallback={<ErrorState error=\"Failed to render chart\" />}\n >\n <div\n className={className}\n style={{ height }}\n aria-label={ariaLabel}\n data-testid={testId}\n role=\"img\"\n >\n {children(data)}\n </div>\n </ChartErrorBoundary>\n );\n}\n\n// ============================================================================\n// Data Mode Content\n// ============================================================================\n\nfunction DataModeContent({\n data,\n height,\n className,\n ariaLabel,\n testId,\n children,\n}: CommonProps & ChartWrapperDataProps) {\n const isEmpty = isArrowTable(data)\n ? data.numRows === 0\n : !Array.isArray(data) || data.length === 0;\n\n if (isEmpty) return <EmptyState />;\n\n return (\n <ChartErrorBoundary\n fallback={<ErrorState error=\"Failed to render chart\" />}\n >\n <div\n className={className}\n style={{ height }}\n aria-label={ariaLabel}\n data-testid={testId}\n role=\"img\"\n >\n {children(data)}\n </div>\n </ChartErrorBoundary>\n );\n}\n\n// ============================================================================\n// Main Wrapper Component\n// ============================================================================\n\n/**\n * Wrapper component for charts.\n * Handles data fetching (query mode) or direct data injection (data mode).\n *\n * @example Query mode - fetches data from analytics endpoint\n * ```tsx\n * <ChartWrapper\n * queryKey=\"spend_data\"\n * parameters={{ limit: 100 }}\n * format=\"auto\"\n * >\n * {(data) => <MyChart data={data} />}\n * </ChartWrapper>\n * ```\n *\n * @example Data mode - uses provided data directly\n * ```tsx\n * <ChartWrapper data={myArrowTable}>\n * {(data) => <MyChart data={data} />}\n * </ChartWrapper>\n * ```\n */\nexport function ChartWrapper(props: ChartWrapperProps) {\n const { height = 300, className, ariaLabel, testId, children } = props;\n\n // Data mode: use provided data directly\n if (\"data\" in props && props.data !== undefined) {\n return (\n <DataModeContent\n data={props.data}\n height={height}\n className={className}\n ariaLabel={ariaLabel}\n testId={testId}\n >\n {children}\n </DataModeContent>\n );\n }\n\n // Query mode: fetch data from analytics endpoint\n if (\"queryKey\" in props && props.queryKey !== undefined) {\n return (\n <QueryModeContent\n queryKey={props.queryKey}\n parameters={props.parameters}\n format={props.format}\n transformer={props.transformer}\n height={height}\n className={className}\n ariaLabel={ariaLabel}\n testId={testId}\n >\n {children}\n </QueryModeContent>\n );\n }\n\n // Should never reach here due to TypeScript, but safety fallback\n return <ErrorState error=\"Chart requires either 'queryKey' or 'data' prop\" />;\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,yBAAyB,IAAI,IAAI,CAAC,WAAW,WAAW,CAAC;AAE/D,SAAS,mBACP,SACA,MACA,iBACS;AACT,KAAI,CAAC,WAAW,QAAQ,CAAC,gBAAiB,QAAO;AACjD,KACE,gBAAgB,UAAU,aAC1B,uBAAuB,IAAI,gBAAgB,MAAM,CAEjD,QAAO;AAET,QAAO;;AAkDT,SAAS,iBAAiB,EACxB,UACA,YACA,QACA,aACA,QACA,WACA,WACA,QACA,YACuC;CACvC,MAAM,EAAE,MAAM,SAAS,OAAO,SAAS,oBAAoB,aAAa;EACtE;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,mBAAmB,SAAS,MAAM,gBAAgB,CACpD,QAAO,oBAAC,8BAA2B,QAAQ,UAAU,MAAO;AAE9D,KAAI,QAAS,QAAO,oBAAC,mBAAgB,QAAQ,UAAU,MAAO;AAC9D,KAAI,MAAO,QAAO,oBAAC,cAAkB,QAAS;AAC9C,KAAI,WAAW,CAAC,KAAM,QAAO,oBAAC,eAAa;AAE3C,QACE,oBAAC;EACC,UAAU,oBAAC,cAAW,OAAM,2BAA2B;YAEvD,oBAAC;GACY;GACX,OAAO,EAAE,QAAQ;GACjB,cAAY;GACZ,eAAa;GACb,MAAK;aAEJ,SAAS,KAAK;IACX;GACa;;AAQzB,SAAS,gBAAgB,EACvB,MACA,QACA,WACA,WACA,QACA,YACsC;AAKtC,KAJgB,aAAa,KAAK,GAC9B,KAAK,YAAY,IACjB,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,WAAW,EAE/B,QAAO,oBAAC,eAAa;AAElC,QACE,oBAAC;EACC,UAAU,oBAAC,cAAW,OAAM,2BAA2B;YAEvD,oBAAC;GACY;GACX,OAAO,EAAE,QAAQ;GACjB,cAAY;GACZ,eAAa;GACb,MAAK;aAEJ,SAAS,KAAK;IACX;GACa;;;;;;;;;;;;;;;;;;;;;;;;AA8BzB,SAAgB,aAAa,OAA0B;CACrD,MAAM,EAAE,SAAS,KAAK,WAAW,WAAW,QAAQ,aAAa;AAGjE,KAAI,UAAU,SAAS,MAAM,SAAS,OACpC,QACE,oBAAC;EACC,MAAM,MAAM;EACJ;EACG;EACA;EACH;EAEP;GACe;AAKtB,KAAI,cAAc,SAAS,MAAM,aAAa,OAC5C,QACE,oBAAC;EACC,UAAU,MAAM;EAChB,YAAY,MAAM;EAClB,QAAQ,MAAM;EACd,aAAa,MAAM;EACX;EACG;EACA;EACH;EAEP;GACgB;AAKvB,QAAO,oBAAC,cAAW,OAAM,oDAAoD"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { AnalyticsFormat, InferResultByFormat, InferRowType, InferServingChunk, InferServingRequest, InferServingResponse, PluginRegistry, QueryRegistry, ServingAlias, ServingEndpointRegistry, TypedArrowTable, UseAnalyticsQueryOptions, UseAnalyticsQueryResult, WarehouseState, WarehouseStatus } from "./types.js";
|
|
1
2
|
import { UseChartDataOptions, UseChartDataResult, useChartData } from "./use-chart-data.js";
|
|
2
|
-
import {
|
|
3
|
+
import { AggregatedResourceStatus, ResourceSeverity, ResourceStatus, ResourceStatusFilter, ResourceStatusProvider, ResourceStatusProviderProps, useResourceStatus, useResourceStatusPublisher } from "./use-resource-status.js";
|
|
4
|
+
import { ResourceKindRenderer, ResourceStatusIndicator, ResourceStatusIndicatorProps, ResourceStatusToasterOptions, useResourceStatusToaster } from "../resource-status-indicator.js";
|
|
3
5
|
import { AgentChatEvent, UseAgentChatOptions, UseAgentChatResult, useAgentChat } from "./use-agent-chat.js";
|
|
4
6
|
import { useAnalyticsQuery } from "./use-analytics-query.js";
|
|
5
7
|
import { useIsMobile } from "./use-mobile.js";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { ResourceStatusProvider, useResourceStatus, useResourceStatusPublisher } from "./use-resource-status.js";
|
|
1
2
|
import { useAnalyticsQuery } from "./use-analytics-query.js";
|
|
2
3
|
import { useChartData } from "./use-chart-data.js";
|
|
4
|
+
import { ResourceStatusIndicator, useResourceStatusToaster } from "../resource-status-indicator.js";
|
|
3
5
|
import { useAgentChat } from "./use-agent-chat.js";
|
|
4
6
|
import { useIsMobile } from "./use-mobile.js";
|
|
5
7
|
import { usePluginClientConfig } from "./use-plugin-config.js";
|
|
@@ -35,6 +35,27 @@ interface UseAnalyticsQueryOptions<F extends AnalyticsFormat = "JSON_ARRAY"> {
|
|
|
35
35
|
/** Whether to automatically start the query when the hook is mounted. Default is true. */
|
|
36
36
|
autoStart?: boolean;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* SQL warehouse lifecycle state surfaced by `useAnalyticsQuery`.
|
|
40
|
+
* Mirrors the states emitted by the analytics plugin (which mirror the
|
|
41
|
+
* Databricks SQL SDK `sql.State`).
|
|
42
|
+
*/
|
|
43
|
+
type WarehouseState = "RUNNING" | "STARTING" | "STOPPED" | "STOPPING" | "DELETED" | "DELETING";
|
|
44
|
+
/**
|
|
45
|
+
* Snapshot of warehouse readiness streamed by the analytics route before the
|
|
46
|
+
* SQL result. Useful for rendering "warehouse starting…" affordances during
|
|
47
|
+
* cold starts instead of a frozen spinner.
|
|
48
|
+
*
|
|
49
|
+
* Note: the SDK's `health.summary` is intentionally NOT included on the wire
|
|
50
|
+
* — it's free-form operator-oriented diagnostic text (cluster IDs, capacity
|
|
51
|
+
* reasons, internal RPC errors) that must not reach end users; it stays in
|
|
52
|
+
* server-side telemetry only.
|
|
53
|
+
*/
|
|
54
|
+
interface WarehouseStatus {
|
|
55
|
+
state: WarehouseState;
|
|
56
|
+
/** Milliseconds elapsed since the route began waiting for the warehouse. */
|
|
57
|
+
elapsedMs: number;
|
|
58
|
+
}
|
|
38
59
|
/** Result state returned by useAnalyticsQuery */
|
|
39
60
|
interface UseAnalyticsQueryResult<T> {
|
|
40
61
|
/** Latest query result data */
|
|
@@ -43,6 +64,12 @@ interface UseAnalyticsQueryResult<T> {
|
|
|
43
64
|
loading: boolean;
|
|
44
65
|
/** Error state of the query */
|
|
45
66
|
error: string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Latest warehouse status emitted by the server while waiting for the SQL
|
|
69
|
+
* warehouse to reach RUNNING. `null` until the first status event arrives;
|
|
70
|
+
* remains `null` for cache hits where the server skips the readiness check.
|
|
71
|
+
*/
|
|
72
|
+
warehouseStatus: WarehouseStatus | null;
|
|
46
73
|
}
|
|
47
74
|
/**
|
|
48
75
|
* Query Registry for type-safe analytics queries.
|
|
@@ -132,5 +159,5 @@ type InferServingRequest<K> = K extends AugmentedRegistry<ServingEndpointRegistr
|
|
|
132
159
|
request: infer Req;
|
|
133
160
|
} ? Req : Record<string, unknown> : Record<string, unknown>;
|
|
134
161
|
//#endregion
|
|
135
|
-
export { AnalyticsFormat, InferParams, InferResultByFormat, InferRowType, InferServingChunk, InferServingRequest, InferServingResponse, PluginRegistry, QueryKey, QueryRegistry, ServingAlias, ServingEndpointRegistry, TypedArrowTable, UseAnalyticsQueryOptions, UseAnalyticsQueryResult };
|
|
162
|
+
export { AnalyticsFormat, InferParams, InferResultByFormat, InferRowType, InferServingChunk, InferServingRequest, InferServingResponse, PluginRegistry, QueryKey, QueryRegistry, ServingAlias, ServingEndpointRegistry, TypedArrowTable, UseAnalyticsQueryOptions, UseAnalyticsQueryResult, WarehouseState, WarehouseStatus };
|
|
136
163
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/hooks/types.ts"],"mappings":";;;;;AAaA;;;;;KAAY,eAAA;;;;;;;;;;;UAkBK,eAAA,cACF,MAAA,oBAA0B,MAAA,2BAC/B,KAAA;EAKiB;AAQ3B;;;EAR2B,SAAhB,SAAA,GAAY,IAAA;AAAA;;UAQN,wBAAA,WACL,eAAA;EAGD;EAAT,MAAA,GAAS,CAAA;EAMT;EAHA,iBAAA;EAGS;EAAT,SAAA;AAAA;;UAIe,uBAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/hooks/types.ts"],"mappings":";;;;;AAaA;;;;;KAAY,eAAA;;;;;;;;;;;UAkBK,eAAA,cACF,MAAA,oBAA0B,MAAA,2BAC/B,KAAA;EAKiB;AAQ3B;;;EAR2B,SAAhB,SAAA,GAAY,IAAA;AAAA;;UAQN,wBAAA,WACL,eAAA;EAGD;EAAT,MAAA,GAAS,CAAA;EAMT;EAHA,iBAAA;EAGS;EAAT,SAAA;AAAA;;;;AA0BF;;KAlBY,cAAA;;;;;;;AAyBZ;;;;UAPiB,eAAA;EACf,KAAA,EAAO,cAAA;EAQD;EANN,SAAA;AAAA;;UAIe,uBAAA;EAYiB;EAVhC,IAAA,EAAM,CAAA;EA+BS;EA7Bf,OAAA;;EAEA,KAAA;EA4BC;;;;;EAtBD,eAAA,EAAiB,eAAA;AAAA;AA8BnB;;;;;;;;;;;;;;;;;;AAAA,UATiB,aAAA;EAAA,CACd,GAAA;IACC,IAAA;IACA,UAAA,EAAY,MAAA;IACZ,MAAA;EAAA;AAAA;;KAKQ,iBAAA,0BACE,CAAA,mBAAoB,CAAA,WAAY,CAAA,GAAI,CAAA,CAAE,CAAA;;KAIxC,QAAA,GAAW,iBAAA,CAAkB,aAAA,2BAErC,iBAAA,CAAkB,aAAA;;;;AAMtB;KAAY,WAAA,SAAoB,CAAA,SAAU,iBAAA,CAAkB,aAAA,IACxD,aAAA,CAAc,CAAA;EAAa,MAAA;AAAA,IACzB,CAAA,GACA,CAAA,GACF,CAAA;;;;;KAMQ,YAAA,MAAkB,CAAA,SAAU,iBAAA,CAAkB,aAAA,IACtD,aAAA,CAAc,CAAA;EAAa,MAAA,EAAQ,KAAA;AAAA,IACjC,CAAA,SAAU,MAAA,oBACR,CAAA,GACA,MAAA,oBACF,MAAA,oBACF,MAAA;;;;;;KAOQ,mBAAA,iBAAoC,eAAA,IAAmB,CAAA,oCAG/D,eAAA,CAAgB,YAAA,CAAa,CAAA,KAC7B,WAAA,CAAY,CAAA,EAAG,CAAA;;;;KAKP,WAAA,MAAiB,CAAA,SAAU,iBAAA,CAAkB,aAAA,IACrD,aAAA,CAAc,CAAA;EAAa,UAAA;AAAA,IACzB,CAAA,GACA,MAAA,oBACF,MAAA;AAAA,UAEa,cAAA;EAAA,CACd,GAAA,WAAc,MAAA;AAAA;;;;;;;;;;;;;;;UA2BA,uBAAA;;KAGL,YAAA,GACV,iBAAA,CAAkB,uBAAA,2BAEd,iBAAA,CAAkB,uBAAA;;KAGZ,iBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,uBAAA,IACxB,uBAAA,CAAwB,CAAA;EAAa,KAAA;AAAA,IACnC,CAAA;;KAKI,oBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,uBAAA,IACxB,uBAAA,CAAwB,CAAA;EAAa,QAAA;AAAA,IACnC,CAAA;;KAKI,mBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,uBAAA,IACxB,uBAAA,CAAwB,CAAA;EAAa,OAAA;AAAA,IACnC,GAAA,GACA,MAAA,oBACF,MAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-analytics-query.d.ts","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"use-analytics-query.d.ts","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"mappings":";;;;;AAgLA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,iBAAA,wBAEJ,QAAA,GAAW,QAAA,YACX,eAAA,gBAAA,CAEV,QAAA,EAAU,CAAA,EACV,UAAA,GAAa,WAAA,CAAY,CAAA,UACzB,OAAA,GAAS,wBAAA,CAAyB,CAAA,IACjC,uBAAA,CAAwB,mBAAA,CAAoB,CAAA,EAAG,CAAA,EAAG,CAAA"}
|