@databricks/appkit-ui 0.45.0 → 0.47.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/CLAUDE.md +1 -0
- package/dist/cli/commands/generate-types.js +9 -4
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/schemas/metric-fqn.js +14 -0
- package/dist/schemas/metric-fqn.js.map +1 -0
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/docs/api/appkit/Class.Plugin.md +2 -0
- package/docs/api/appkit/Class.ResourceRegistry.md +1 -1
- package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
- package/docs/api/appkit/Interface.GenerationParams.md +58 -0
- package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
- package/docs/api/appkit.md +1 -0
- package/docs/development/type-generation.md +3 -3
- package/docs/plugins/analytics.md +172 -23
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -125,6 +125,7 @@ npx @databricks/appkit docs <query>
|
|
|
125
125
|
- [Interface: FileResource](./docs/api/appkit/Interface.FileResource.md): Describes the file or directory being acted upon.
|
|
126
126
|
- [Interface: FunctionTool](./docs/api/appkit/Interface.FunctionTool.md): Properties
|
|
127
127
|
- [Interface: GenerateDatabaseCredentialRequest](./docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md): Request parameters for generating database OAuth credentials
|
|
128
|
+
- [Interface: GenerationParams](./docs/api/appkit/Interface.GenerationParams.md): Optional generation parameters forwarded to the OpenAI-compatible serving
|
|
128
129
|
- [Interface: IJobsConfig](./docs/api/appkit/Interface.IJobsConfig.md): Configuration for the Jobs plugin.
|
|
129
130
|
- [Interface: ITelemetry](./docs/api/appkit/Interface.ITelemetry.md): Plugin-facing interface for OpenTelemetry instrumentation.
|
|
130
131
|
- [Interface: JobAPI](./docs/api/appkit/Interface.JobAPI.md): User-facing API for a single configured job.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { METRIC_CONFIG_FILE } from "../../schemas/metric-fqn.js";
|
|
1
2
|
import { acquireSpawnLock, getSpawnLockPath, releaseSpawnLock } from "./spawn-lock.js";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -33,16 +34,20 @@ async function runGenerateTypes(rootDir, outFile, warehouseId, options) {
|
|
|
33
34
|
if (resolvedWarehouseId) {
|
|
34
35
|
const resolvedOutFile = outFile || path.join(process.cwd(), "shared/appkit-types/analytics.d.ts");
|
|
35
36
|
const queryFolder = path.join(resolvedRootDir, "config/queries");
|
|
36
|
-
|
|
37
|
+
const metricViewsFolder = path.join(resolvedRootDir, "config/metric-views");
|
|
38
|
+
const hasQueries = fs.existsSync(queryFolder);
|
|
39
|
+
const hasMetricViews = fs.existsSync(metricViewsFolder);
|
|
40
|
+
if (hasQueries || hasMetricViews) {
|
|
37
41
|
await typeGen.generateFromEntryPoint({
|
|
38
|
-
queryFolder,
|
|
42
|
+
queryFolder: hasQueries ? queryFolder : void 0,
|
|
43
|
+
metricViewsFolder: hasMetricViews ? metricViewsFolder : void 0,
|
|
39
44
|
outFile: resolvedOutFile,
|
|
40
45
|
warehouseId: resolvedWarehouseId,
|
|
41
46
|
noCache,
|
|
42
47
|
mode
|
|
43
48
|
});
|
|
44
|
-
console.log(`Generated query types: ${resolvedOutFile}`);
|
|
45
|
-
const metricConfig = path.join(
|
|
49
|
+
if (hasQueries) console.log(`Generated query types: ${resolvedOutFile}`);
|
|
50
|
+
const metricConfig = path.join(metricViewsFolder, METRIC_CONFIG_FILE);
|
|
46
51
|
if (fs.existsSync(metricConfig)) {
|
|
47
52
|
const typesDir = path.dirname(resolvedOutFile);
|
|
48
53
|
console.log(`Generated metric types: ${path.join(typesDir, "metric-views.d.ts")}`);
|
|
@@ -1 +1 @@
|
|
|
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 const metricConfig = path.join(queryFolder, \"metric-views.json\");\n if (fs.existsSync(metricConfig)) {\n const typesDir = path.dirname(resolvedOutFile);\n console.log(\n `Generated metric types: ${path.join(typesDir, \"metric-views.d.ts\")}`,\n );\n }\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;IAExD,MAAM,eAAe,KAAK,KAAK,aAAa,oBAAoB;AAChE,QAAI,GAAG,WAAW,aAAa,EAAE;KAC/B,MAAM,WAAW,KAAK,QAAQ,gBAAgB;AAC9C,aAAQ,IACN,2BAA2B,KAAK,KAAK,UAAU,oBAAoB,GACpE;;;QAIL,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"}
|
|
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 { METRIC_CONFIG_FILE } from \"../../schemas/metric-fqn\";\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 const metricViewsFolder = path.join(\n resolvedRootDir,\n \"config/metric-views\",\n );\n const hasQueries = fs.existsSync(queryFolder);\n const hasMetricViews = fs.existsSync(metricViewsFolder);\n\n // Generate when either config surface exists. Metric-view types are\n // independent of `.sql` queries — an app can declare metric views in\n // `config/metric-views/` without a `config/queries/` folder.\n if (hasQueries || hasMetricViews) {\n await typeGen.generateFromEntryPoint({\n queryFolder: hasQueries ? queryFolder : undefined,\n metricViewsFolder: hasMetricViews ? metricViewsFolder : undefined,\n outFile: resolvedOutFile,\n warehouseId: resolvedWarehouseId,\n noCache,\n mode,\n });\n\n if (hasQueries) {\n console.log(`Generated query types: ${resolvedOutFile}`);\n }\n\n const metricConfig = path.join(metricViewsFolder, METRIC_CONFIG_FILE);\n if (fs.existsSync(metricConfig)) {\n const typesDir = path.dirname(resolvedOutFile);\n console.log(\n `Generated metric types: ${path.join(typesDir, \"metric-views.d.ts\")}`,\n );\n }\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":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/schemas/metric-fqn.ts
|
|
2
|
+
/**
|
|
3
|
+
* Basename of the metric-view declarations file, resolved inside a
|
|
4
|
+
* `config/metric-views/` folder. Single source of truth shared by the analytics
|
|
5
|
+
* runtime (`plugins/analytics/mv`), the type-generator, its Vite watcher, and
|
|
6
|
+
* the `generate-types` CLI. It lives in this zod-free module (rather than the
|
|
7
|
+
* canonical `metric-source.ts` schema) so the type-generator can import it
|
|
8
|
+
* without pulling zod into its locked dependency graph.
|
|
9
|
+
*/
|
|
10
|
+
const METRIC_CONFIG_FILE = "definitions.json";
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { METRIC_CONFIG_FILE };
|
|
14
|
+
//# sourceMappingURL=metric-fqn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-fqn.js","names":[],"sources":["../../src/schemas/metric-fqn.ts"],"sourcesContent":["/**\n * Unity Catalog object-name grammar\n * the single source of truth for metric view FQN (Fully Qualified Name).\n *\n * A metric view's `source` FQN is validated in two places that must agree:\n *\n * 1. The canonical Zod schema (`./metric-source.ts`), which composes the\n * three-part FQN regex from {@link UC_FQN_PATTERN} for IDE/CI and the\n * generated JSON schema (`docs/static/schemas/metric-source.schema.json`).\n * 2. The type-generator runtime (`packages/appkit/src/type-generator/mv-registry/config.ts`),\n * which imports {@link UC_FQN_PATTERN} as a plain value to validate each\n * dot-split segment.\n *\n *\n * A Unity Catalog object name:\n * - cannot exceed 255 characters ({@link MAX_UC_OBJECT_NAME_LENGTH}); and\n * - cannot contain any of these characters:\n * - period (`.`)\n * - space (U+0020)\n * - forward slash (`/`)\n * - all ASCII control characters (U+0000-U+001F)\n * - the DELETE character (U+007F)\n *\n * Every other character is permitted in a quoted name, including non-ASCII\n * letters (the docs demonstrate Chinese/Russian/Portuguese names) and hyphens.\n *\n * @note the regex is the per-segment character set; structure (3 parts) and length are intentionally left to the callers.\n */\n\nexport const MAX_UC_OBJECT_NAME_LENGTH = 255;\n\n/**\n * Basename of the metric-view declarations file, resolved inside a\n * `config/metric-views/` folder. Single source of truth shared by the analytics\n * runtime (`plugins/analytics/mv`), the type-generator, its Vite watcher, and\n * the `generate-types` CLI. It lives in this zod-free module (rather than the\n * canonical `metric-source.ts` schema) so the type-generator can import it\n * without pulling zod into its locked dependency graph.\n */\nexport const METRIC_CONFIG_FILE = \"definitions.json\";\n\n/**\n * Matches a single, non-empty Unity Catalog object name as it may appear in a backtick-quoted (delimited) identifier.\n *\n * @example\n * UC_FQN_PATTERN.test(\"revenue_metrics\"); // true\n * UC_FQN_PATTERN.test(\"prod-data\"); // true (hyphens are UC-legal)\n * UC_FQN_PATTERN.test(\"cafe\\u0301\"); // true (non-ASCII is UC-legal)\n * UC_FQN_PATTERN.test(\"bad name\"); // false (space is prohibited)\n * UC_FQN_PATTERN.test(\"a/b\"); // false (slash is prohibited)\n */\n// biome-ignore lint/suspicious/noControlCharactersInRegex: UC explicitly prohibits ASCII control characters in object names; this negated class encodes that rule.\nexport const UC_FQN_PATTERN = /^[^\\x00-\\x20\\x7f./]+$/;\n\n/** A metric view FQN is exactly three segments: catalog.schema.metric_view. */\nconst FQN_SEGMENT_COUNT = 3;\n\n/**\n * Total predicate: is `fqn` a well-formed three-part UC metric view FQN?\n * Well-formed = exactly three non-empty, dot-separated segments, each a valid Unity Catalog object name per {@link UC_FQN_PATTERN}.\n * @example\n * isValidFqn(\"main.analytics.revenue\"); // true\n * isValidFqn(\"prod-data.analytics.rev\"); // true (hyphens are UC-legal)\n * isValidFqn(\"main.analytics\"); // false (only two segments)\n */\nexport function isValidFqn(fqn: string): boolean {\n const segments = fqn.split(\".\");\n if (segments.length !== FQN_SEGMENT_COUNT) {\n return false;\n }\n return segments.every((segment) => UC_FQN_PATTERN.test(segment));\n}\n\n/**\n * Quote a dot-separated FQN for safe interpolation into a Spark/Databricks SQL\n * statement.\n *\n * Each dot-split segment is wrapped in backtick-quoted-identifier syntax. The\n * one character that can break out of a backtick-quoted identifier is the\n * backtick itself, escaped by doubling (`` ` `` → `` `` ``) — so every backtick\n * inside a segment is doubled before the segment is wrapped. Control characters\n * and newlines have no valid escape inside a quoted identifier, so a segment\n * containing one is rejected outright.\n *\n * This is a pure, standalone escaper: it is intentionally independent of FQN\n * naming validation ({@link isValidFqn}). Naming validation decides whether an\n * FQN is an acceptable metric source; this function only guarantees that\n * whatever it is handed cannot break out of the quoted identifier it produces.\n * Grammar and quoting live together here so a metric source is validated and\n * escaped against one shared source of truth.\n *\n * An ordinary identifier is unchanged apart from the wrapping backticks:\n * `catalog.schema.view` → `` `catalog`.`schema`.`view` ``.\n *\n * @param fqn - Dot-separated identifier (e.g. `catalog.schema.view`).\n * @returns The backtick-quoted, escaped identifier ready for interpolation.\n * @throws If any segment contains a control character or newline.\n */\nexport function quoteFqnForSql(fqn: string): string {\n return fqn.split(\".\").map(quoteIdentifier).join(\".\");\n}\n\n/**\n * The Unicode \"control\" category (`\\p{Cc}`): C0 (incl. `\\n`, `\\r`, `\\t`), DEL,\n * and C1 — every control character/newline. These have no valid escape inside\n * a backtick-quoted identifier, so a name containing one cannot be safely\n * quoted and is rejected.\n */\nconst CONTROL_OR_NEWLINE = /\\p{Cc}/u;\n\nexport function isValidColumnName(name: string): boolean {\n return name.length > 0 && !CONTROL_OR_NEWLINE.test(name);\n}\n\n/**\n * Quote a single identifier (one column/measure/dimension name, or one FQN\n * segment) as a backtick-delimited identifier for safe SQL interpolation.\n *\n * @throws If `name` contains a control character or newline.\n */\nexport function quoteIdentifier(name: string): string {\n if (CONTROL_OR_NEWLINE.test(name)) {\n throw new Error(\n `Cannot quote identifier \"${name}\" for SQL: it contains a control character or newline, which has no valid escape inside a backtick-quoted identifier.`,\n );\n }\n // Double every backtick, then wrap in backticks.\n return `\\`${name.replace(/`/g, \"``\")}\\``;\n}\n"],"mappings":";;;;;;;;;AAuCA,MAAa,qBAAqB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../../shared/src/plugin.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../../shared/src/plugin.ts"],"mappings":";;;;;;KAgSY,iBAAA,GAAoB,MAAA;;KAGpB,eAAA,GAAkB,MAAA,SAAe,iBAAA;;KAGjC,mBAAA,GAAsB,MAAA,SAAe,MAAA"}
|
|
@@ -224,6 +224,8 @@ abortActiveOperations(): void;
|
|
|
224
224
|
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
+
Cancel in-flight work (abort signals, SSE streams). Runs in the first phase of graceful shutdown, BEFORE any plugin's `shutdown()` hook — so it must not tear down shared resources (e.g. connection pools) that other plugins' hooks may still need. Put teardown in `shutdown()`.
|
|
228
|
+
|
|
227
229
|
#### Returns[](#returns-1 "Direct link to Returns")
|
|
228
230
|
|
|
229
231
|
`void`
|
|
@@ -249,7 +249,7 @@ const registry = ResourceRegistry.getInstance();
|
|
|
249
249
|
const result = registry.validate();
|
|
250
250
|
|
|
251
251
|
if (!result.valid) {
|
|
252
|
-
console.error(
|
|
252
|
+
console.error(ResourceRegistry.formatMissingResources(result.missing));
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
```
|
|
@@ -35,6 +35,17 @@ When true, the thread used for a chat request against this agent is deleted from
|
|
|
35
35
|
|
|
36
36
|
***
|
|
37
37
|
|
|
38
|
+
### generationParams?[](#generationparams "Direct link to generationParams?")
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
optional generationParams: GenerationParams;
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Optional generation parameters (`temperature`, `top_p`, `stop`, `frequency_penalty`, `presence_penalty`) forwarded to the OpenAI-compatible serving request body. Only set keys are sent. Applied only when AppKit builds the adapter itself (string or omitted `model`); when you pass a pre-built `AgentAdapter`, configure generation params on it directly.
|
|
46
|
+
|
|
47
|
+
***
|
|
48
|
+
|
|
38
49
|
### instructions[](#instructions "Direct link to instructions")
|
|
39
50
|
|
|
40
51
|
```ts
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Interface: GenerationParams
|
|
2
|
+
|
|
3
|
+
Optional generation parameters forwarded to the OpenAI-compatible serving request body. Names match the serving API wire keys. Only keys that are set are sent — undefined values are omitted so the endpoint applies its own defaults. Ranges are not validated here; the serving endpoint validates.
|
|
4
|
+
|
|
5
|
+
## Properties[](#properties "Direct link to Properties")
|
|
6
|
+
|
|
7
|
+
### frequency\_penalty?[](#frequency_penalty "Direct link to frequency_penalty?")
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
optional frequency_penalty: number;
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Penalize tokens by frequency.
|
|
15
|
+
|
|
16
|
+
***
|
|
17
|
+
|
|
18
|
+
### presence\_penalty?[](#presence_penalty "Direct link to presence_penalty?")
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
optional presence_penalty: number;
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Penalize tokens by prior presence.
|
|
26
|
+
|
|
27
|
+
***
|
|
28
|
+
|
|
29
|
+
### stop?[](#stop "Direct link to stop?")
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
optional stop: string | string[];
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Stop sequence(s) that end generation.
|
|
37
|
+
|
|
38
|
+
***
|
|
39
|
+
|
|
40
|
+
### temperature?[](#temperature "Direct link to temperature?")
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
optional temperature: number;
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Sampling temperature.
|
|
48
|
+
|
|
49
|
+
***
|
|
50
|
+
|
|
51
|
+
### top\_p?[](#top_p "Direct link to top_p?")
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
optional top_p: number;
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Nucleus sampling probability mass (`top_p`).
|
|
@@ -31,6 +31,17 @@ Mirrors `AgentDefinition.ephemeral` — skip thread persistence.
|
|
|
31
31
|
|
|
32
32
|
***
|
|
33
33
|
|
|
34
|
+
### generationParams?[](#generationparams "Direct link to generationParams?")
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
optional generationParams: GenerationParams;
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Mirrors `AgentDefinition.generationParams`.
|
|
42
|
+
|
|
43
|
+
***
|
|
44
|
+
|
|
34
45
|
### instructions[](#instructions "Direct link to instructions")
|
|
35
46
|
|
|
36
47
|
```ts
|
package/docs/api/appkit.md
CHANGED
|
@@ -47,6 +47,7 @@ Documentation merge entry for Typedoc — combines the stable `@databricks/appki
|
|
|
47
47
|
| [FileResource](./docs/api/appkit/Interface.FileResource.md) | Describes the file or directory being acted upon. |
|
|
48
48
|
| [FunctionTool](./docs/api/appkit/Interface.FunctionTool.md) | - |
|
|
49
49
|
| [GenerateDatabaseCredentialRequest](./docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md) | Request parameters for generating database OAuth credentials |
|
|
50
|
+
| [GenerationParams](./docs/api/appkit/Interface.GenerationParams.md) | Optional generation parameters forwarded to the OpenAI-compatible serving request body. Names match the serving API wire keys. Only keys that are set are sent — undefined values are omitted so the endpoint applies its own defaults. Ranges are not validated here; the serving endpoint validates. |
|
|
50
51
|
| [IJobsConfig](./docs/api/appkit/Interface.IJobsConfig.md) | Configuration for the Jobs plugin. |
|
|
51
52
|
| [ITelemetry](./docs/api/appkit/Interface.ITelemetry.md) | Plugin-facing interface for OpenTelemetry instrumentation. Provides a thin abstraction over OpenTelemetry APIs for plugins. |
|
|
52
53
|
| [JobAPI](./docs/api/appkit/Interface.JobAPI.md) | User-facing API for a single configured job. |
|
|
@@ -88,13 +88,13 @@ In blocking mode the generator starts a stopped warehouse, waits (bounded) for i
|
|
|
88
88
|
|
|
89
89
|
## Metric-view types[](#metric-view-types "Direct link to Metric-view types")
|
|
90
90
|
|
|
91
|
-
`generate-types` (and the Vite plugin) emit metric-view types **additively** — there is no separate command. When a `config/
|
|
91
|
+
`generate-types` (and the Vite plugin) emit metric-view types **additively** — there is no separate command. When a `config/metric-views/definitions.json` file is present, the same run that generates your query types also DESCRIBEs each declared [UC Metric View](./docs/plugins/analytics.md) and writes `metric-views.d.ts` into `shared/appkit-types/`:
|
|
92
92
|
|
|
93
93
|
* `metric-views.d.ts` — augments the `MetricRegistry` interface so `useMetricView('<key>', …)` is autocompleted and type-checked. Each view's measures, dimensions, and their semantic metadata (SQL type, display name, format, time grains) are encoded at the type level.
|
|
94
94
|
|
|
95
|
-
If `metric-views.json` is absent the metric path stays dormant (nothing is emitted). When present it follows the **same** warehouse-readiness contract as query types: in the default non-blocking run a view that can't be described yet — a cold warehouse, or a bad/unreachable source — is written with permissive types and a warning, while under `--wait` that same situation fails the build so CI never ships incomplete metric types. A malformed `
|
|
95
|
+
If `config/metric-views/definitions.json` is absent the metric path stays dormant (nothing is emitted). When present it follows the **same** warehouse-readiness contract as query types: in the default non-blocking run a view that can't be described yet — a cold warehouse, or a bad/unreachable source — is written with permissive types and a warning, while under `--wait` that same situation fails the build so CI never ships incomplete metric types. A malformed `definitions.json` (invalid JSON, or a source that isn't a three-part UC FQN) fails fast in every mode.
|
|
96
96
|
|
|
97
|
-
`
|
|
97
|
+
`definitions.json` is keyed by metric key; each entry names the three-part UC FQN of the view and, optionally, the executor it runs as (`app_service_principal`, the default, or `user`):
|
|
98
98
|
|
|
99
99
|
```json
|
|
100
100
|
{
|
|
@@ -86,12 +86,154 @@ The analytics plugin exposes these endpoints (mounted under `/api/analytics`):
|
|
|
86
86
|
|
|
87
87
|
* `POST /api/analytics/query/:query_key`
|
|
88
88
|
* `GET /api/analytics/arrow-result/:jobId`
|
|
89
|
+
* `POST /api/analytics/metric/:key` — measure a Unity Catalog Metric View (see [Metric views](#metric-views))
|
|
89
90
|
|
|
90
91
|
## Format options[](#format-options "Direct link to Format options")
|
|
91
92
|
|
|
92
93
|
* `format: "JSON"` (default) returns JSON rows
|
|
93
94
|
* `format: "ARROW"` returns an Arrow "statement\_id" payload over SSE, then the client fetches binary Arrow from `/api/analytics/arrow-result/:jobId`
|
|
94
95
|
|
|
96
|
+
## Metric views[](#metric-views "Direct link to Metric views")
|
|
97
|
+
|
|
98
|
+
`POST /api/analytics/metric/:key` measures a [Unity Catalog Metric View](https://docs.databricks.com/en/metric-views/index.html) that you declared in `config/metric-views/definitions.json`. Instead of writing SQL, the caller sends a structured request — which measures to aggregate, which dimensions to group by, and an optional filter — and the plugin builds and runs the `SELECT MEASURE(...) ... GROUP BY ALL` for you against the view.
|
|
99
|
+
|
|
100
|
+
The route is **dormant until `config/metric-views/definitions.json` exists**: with no config file, every metric key returns `404`. Declaring the file (and generating types) is covered in [Metric-view types](./docs/development/type-generation.md#metric-view-types); this section documents the runtime endpoint that config activates.
|
|
101
|
+
|
|
102
|
+
### Request body[](#request-body "Direct link to Request body")
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
POST /api/analytics/metric/:key
|
|
106
|
+
Content-Type: application/json
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
"measures": ["arr", "revenue"],
|
|
110
|
+
"dimensions": ["region", "order_date"],
|
|
111
|
+
"timeGrain": "month",
|
|
112
|
+
"timeDimension": "order_date",
|
|
113
|
+
"filter": { "member": "region", "operator": "in", "values": ["EMEA", "APAC"] },
|
|
114
|
+
"limit": 100
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`:key` is a metric key from `definitions.json`. The body fields:
|
|
120
|
+
|
|
121
|
+
| Field | Type | Required | Description |
|
|
122
|
+
| --------------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
123
|
+
| `measures` | `string[]` | yes | Measures to aggregate. At least 1, at most 50. Each becomes `MEASURE(<name>) AS <name>`. |
|
|
124
|
+
| `dimensions` | `string[]` | no | Dimensions to group by (max 20). Selected verbatim and grouped via `GROUP BY ALL`. |
|
|
125
|
+
| `filter` | object | no | Structured predicate tree translated into a parameterized `WHERE` clause (see [Filters](#filters)). |
|
|
126
|
+
| `timeGrain` | `string` | no | Bucket a time dimension via `date_trunc('<grain>', …)` — e.g. `day`, `month`. Requires `timeDimension`. |
|
|
127
|
+
| `timeDimension` | `string` | no | The single dimension `timeGrain` buckets. Must be one of `dimensions`. Required whenever `timeGrain` is set. |
|
|
128
|
+
| `limit` | `number` | no | Positive integer row cap (max 100000). |
|
|
129
|
+
| `format` | `string` | no | `JSON_ARRAY` (default). `JSON` is accepted as a deprecated alias for it; Arrow formats (`ARROW`, `ARROW_STREAM`) are rejected on this route. |
|
|
130
|
+
|
|
131
|
+
Measures and dimensions must be unique across both lists — a name cannot repeat, nor appear as both a measure and a dimension.
|
|
132
|
+
|
|
133
|
+
### How the request becomes SQL[](#how-the-request-becomes-sql "Direct link to How the request becomes SQL")
|
|
134
|
+
|
|
135
|
+
Given a view registered as `catalog.schema.revenue_metrics`, the request above produces (measures and dimensions are sorted for a deterministic SELECT list):
|
|
136
|
+
|
|
137
|
+
```sql
|
|
138
|
+
SELECT MEASURE(`arr`) AS `arr`, MEASURE(`revenue`) AS `revenue`,
|
|
139
|
+
date_trunc('month', `order_date`) AS `order_date`, `region`
|
|
140
|
+
FROM `catalog`.`schema`.`revenue_metrics`
|
|
141
|
+
WHERE `region` IN (:f_0, :f_1)
|
|
142
|
+
GROUP BY ALL
|
|
143
|
+
LIMIT 100
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The metric view's FQN and every measure/dimension identifier are backtick-quoted; filter values are bound as parameters (`:f_0`, `:f_1`, …), never interpolated into the SQL string.
|
|
148
|
+
|
|
149
|
+
### Filters[](#filters "Direct link to Filters")
|
|
150
|
+
|
|
151
|
+
`filter` is a recursive tree. A leaf is a single predicate:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{ "member": "region", "operator": "equals", "values": ["EMEA"] }
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Predicates combine with `and` / `or` groups, which can nest:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"and": [
|
|
163
|
+
{ "member": "region", "operator": "in", "values": ["EMEA", "APAC"] },
|
|
164
|
+
{
|
|
165
|
+
"or": [
|
|
166
|
+
{ "member": "segment", "operator": "equals", "values": ["Enterprise"] },
|
|
167
|
+
{ "member": "deal_size", "operator": "gt", "values": [50000] }
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The operator vocabulary:
|
|
176
|
+
|
|
177
|
+
| Operator | SQL | Values |
|
|
178
|
+
| --------------------------- | ----------------------- | ------------------ |
|
|
179
|
+
| `equals` | `=` | exactly one |
|
|
180
|
+
| `notEquals` | `<>` | exactly one |
|
|
181
|
+
| `in` | `IN (…)` | one or more |
|
|
182
|
+
| `notIn` | `NOT IN (…)` | one or more |
|
|
183
|
+
| `gt` / `gte` / `lt` / `lte` | `>` / `>=` / `<` / `<=` | exactly one |
|
|
184
|
+
| `contains` | `LIKE :param` | exactly one string |
|
|
185
|
+
| `notContains` | `NOT LIKE :param` | exactly one string |
|
|
186
|
+
| `set` | `IS NOT NULL` | none |
|
|
187
|
+
| `notSet` | `IS NULL` | none |
|
|
188
|
+
|
|
189
|
+
For `contains` / `notContains`, the `%…%` wildcards are applied to the *bound parameter value* (`%value%`), not written into the SQL text — so the value is never interpolated, consistent with every other operator.
|
|
190
|
+
|
|
191
|
+
Empty groups of either kind (`{ "or": [] }`, `{ "and": [] }`) are rejected with `400` — every `and` / `or` group must contain at least one predicate. To send no filter, omit the `filter` field entirely rather than passing an empty group.
|
|
192
|
+
|
|
193
|
+
Filters are bounded to keep hostile input from exhausting the server: nesting depth ≤ 8, ≤ 100 children per `and` / `or` group, and ≤ 1000 values per predicate. A request that exceeds a cap is rejected with `400`.
|
|
194
|
+
|
|
195
|
+
### Executors (cache scope)[](#executors-cache-scope "Direct link to Executors (cache scope)")
|
|
196
|
+
|
|
197
|
+
Each entry in `definitions.json` names the executor the query runs as, which also sets the cache scope. This is fixed by config, not the request:
|
|
198
|
+
|
|
199
|
+
| `executor` | Runs as | Cache |
|
|
200
|
+
| --------------------------------- | ---------------------------------- | ----------------------- |
|
|
201
|
+
| `app_service_principal` (default) | The app service principal | Shared across all users |
|
|
202
|
+
| `user` | The requesting user (on-behalf-of) | Per user |
|
|
203
|
+
|
|
204
|
+
This mirrors the `<key>.sql` vs `<key>.obo.sql` distinction for [file-based queries](#execution-context).
|
|
205
|
+
|
|
206
|
+
### Response[](#response "Direct link to Response")
|
|
207
|
+
|
|
208
|
+
The response is the same SSE stream as `POST /api/analytics/query/:query_key`. If the SQL warehouse is cold it first emits `warehouse_status` events (see [Warehouse readiness](#warehouse-readiness)), then a single `result` event with the rows as objects:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"type": "result",
|
|
213
|
+
"data": [
|
|
214
|
+
{
|
|
215
|
+
"region": "EMEA",
|
|
216
|
+
"order_date": "2025-01-01",
|
|
217
|
+
"arr": 1200000,
|
|
218
|
+
"revenue": 340000
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
On failure it emits an `error` event instead.
|
|
226
|
+
|
|
227
|
+
### Errors and behavior[](#errors-and-behavior "Direct link to Errors and behavior")
|
|
228
|
+
|
|
229
|
+
| Status | Body | When |
|
|
230
|
+
| ------ | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
231
|
+
| `404` | `{ "error": "Metric not found" }` | `:key` is not declared in `definitions.json` (also the response for every key when the file is absent). |
|
|
232
|
+
| `400` | `{ "error": "Invalid metric request body (fields: …)", "code": … }` | The request body fails validation. The message names only the offending field paths, never the submitted values. |
|
|
233
|
+
| `503` | `{ "error": "Metric registry not available", "code": "METRIC_REGISTRY_LOAD_FAILED" }` | `definitions.json` is present but malformed or unreadable. |
|
|
234
|
+
|
|
235
|
+
Editing `definitions.json` is picked up on the next request — no server restart is needed. A previously malformed file that you fix likewise starts working on the next request.
|
|
236
|
+
|
|
95
237
|
## Frontend usage[](#frontend-usage "Direct link to Frontend usage")
|
|
96
238
|
|
|
97
239
|
### useAnalyticsQuery[](#useanalyticsquery "Direct link to useAnalyticsQuery")
|
|
@@ -101,7 +243,11 @@ React hook that subscribes to an analytics query over SSE and returns its latest
|
|
|
101
243
|
```ts
|
|
102
244
|
import { useAnalyticsQuery } from "@databricks/appkit-ui/react";
|
|
103
245
|
|
|
104
|
-
const { data, loading, error } = useAnalyticsQuery(
|
|
246
|
+
const { data, loading, error } = useAnalyticsQuery(
|
|
247
|
+
queryKey,
|
|
248
|
+
parameters,
|
|
249
|
+
options,
|
|
250
|
+
);
|
|
105
251
|
|
|
106
252
|
```
|
|
107
253
|
|
|
@@ -109,8 +255,8 @@ const { data, loading, error } = useAnalyticsQuery(queryKey, parameters, options
|
|
|
109
255
|
|
|
110
256
|
```ts
|
|
111
257
|
{
|
|
112
|
-
data: T | null;
|
|
113
|
-
loading: boolean;
|
|
258
|
+
data: T | null; // query result (typed array for JSON, TypedArrowTable for ARROW)
|
|
259
|
+
loading: boolean; // true while the query is executing
|
|
114
260
|
error: string | null; // error message, or null on success
|
|
115
261
|
warehouseStatus: WarehouseStatus | null; // see "Warehouse readiness" below
|
|
116
262
|
}
|
|
@@ -139,8 +285,10 @@ This means a cold start no longer freezes the UI on a stalled spinner. Render th
|
|
|
139
285
|
import { useAnalyticsQuery } from "@databricks/appkit-ui/react";
|
|
140
286
|
|
|
141
287
|
function SpendTable() {
|
|
142
|
-
const { data, loading, error, warehouseStatus } =
|
|
143
|
-
|
|
288
|
+
const { data, loading, error, warehouseStatus } = useAnalyticsQuery(
|
|
289
|
+
"spend_summary",
|
|
290
|
+
params,
|
|
291
|
+
);
|
|
144
292
|
|
|
145
293
|
if (warehouseStatus && warehouseStatus.state !== "RUNNING") {
|
|
146
294
|
return <div>Warehouse is {warehouseStatus.state.toLowerCase()}…</div>;
|
|
@@ -184,10 +332,7 @@ export function AppShell({ children }) {
|
|
|
184
332
|
If you already render your own `<Toaster />` for unrelated app toasts, drop the indicator and call `useResourceStatusToaster()` instead so resource-status toasts share that single Toaster:
|
|
185
333
|
|
|
186
334
|
```tsx
|
|
187
|
-
import {
|
|
188
|
-
useResourceStatusToaster,
|
|
189
|
-
Toaster,
|
|
190
|
-
} from "@databricks/appkit-ui/react";
|
|
335
|
+
import { useResourceStatusToaster, Toaster } from "@databricks/appkit-ui/react";
|
|
191
336
|
|
|
192
337
|
function App() {
|
|
193
338
|
useResourceStatusToaster();
|
|
@@ -207,7 +352,8 @@ For a fully custom toast body, pass `render` (rendered through `toast.custom`):
|
|
|
207
352
|
<ResourceStatusIndicator
|
|
208
353
|
render={(agg) => (
|
|
209
354
|
<div className="rounded-lg border bg-background p-3 shadow">
|
|
210
|
-
{agg.worst?.kind} {agg.worst?.state.toLowerCase()} ({agg.activeCount}
|
|
355
|
+
{agg.worst?.kind} {agg.worst?.state.toLowerCase()} ({agg.activeCount}{" "}
|
|
356
|
+
waiting)
|
|
211
357
|
</div>
|
|
212
358
|
)}
|
|
213
359
|
/>
|
|
@@ -221,8 +367,7 @@ To override copy for a specific kind without rewriting the whole UI, pass `rende
|
|
|
221
367
|
renderers={{
|
|
222
368
|
warehouse: {
|
|
223
369
|
title: () => "Spinning up your data",
|
|
224
|
-
description: (_s, agg) =>
|
|
225
|
-
`${agg.affectedLabels.length} chart(s) waiting`,
|
|
370
|
+
description: (_s, agg) => `${agg.affectedLabels.length} chart(s) waiting`,
|
|
226
371
|
},
|
|
227
372
|
}}
|
|
228
373
|
/>
|
|
@@ -254,11 +399,9 @@ import { useEffect, useId } from "react";
|
|
|
254
399
|
|
|
255
400
|
function useLakebaseReadiness() {
|
|
256
401
|
const id = useId();
|
|
257
|
-
const { publish, unpublish } = useResourceStatusPublisher(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
{ kindHint: "lakebase" },
|
|
261
|
-
);
|
|
402
|
+
const { publish, unpublish } = useResourceStatusPublisher(id, "lakebase", {
|
|
403
|
+
kindHint: "lakebase",
|
|
404
|
+
});
|
|
262
405
|
|
|
263
406
|
useEffect(() => {
|
|
264
407
|
publish({
|
|
@@ -288,21 +431,27 @@ import { sql } from "@databricks/appkit-ui/js";
|
|
|
288
431
|
import { Skeleton } from "@databricks/appkit-ui";
|
|
289
432
|
|
|
290
433
|
function SpendTable() {
|
|
291
|
-
const params = useMemo(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
434
|
+
const params = useMemo(
|
|
435
|
+
() => ({
|
|
436
|
+
startDate: sql.date("2025-01-01"),
|
|
437
|
+
endDate: sql.date("2025-12-31"),
|
|
438
|
+
}),
|
|
439
|
+
[],
|
|
440
|
+
);
|
|
295
441
|
|
|
296
442
|
const { data, loading, error } = useAnalyticsQuery("spend_summary", params);
|
|
297
443
|
|
|
298
444
|
if (loading) return <Skeleton className="h-32 w-full" />;
|
|
299
445
|
if (error) return <div className="text-destructive">{error}</div>;
|
|
300
|
-
if (!data?.length)
|
|
446
|
+
if (!data?.length)
|
|
447
|
+
return <div className="text-muted-foreground">No results</div>;
|
|
301
448
|
|
|
302
449
|
return (
|
|
303
450
|
<ul>
|
|
304
451
|
{data.map((row) => (
|
|
305
|
-
<li key={row.id}>
|
|
452
|
+
<li key={row.id}>
|
|
453
|
+
{row.name}: ${row.cost_usd}
|
|
454
|
+
</li>
|
|
306
455
|
))}
|
|
307
456
|
</ul>
|
|
308
457
|
);
|
package/llms.txt
CHANGED
|
@@ -125,6 +125,7 @@ npx @databricks/appkit docs <query>
|
|
|
125
125
|
- [Interface: FileResource](./docs/api/appkit/Interface.FileResource.md): Describes the file or directory being acted upon.
|
|
126
126
|
- [Interface: FunctionTool](./docs/api/appkit/Interface.FunctionTool.md): Properties
|
|
127
127
|
- [Interface: GenerateDatabaseCredentialRequest](./docs/api/appkit/Interface.GenerateDatabaseCredentialRequest.md): Request parameters for generating database OAuth credentials
|
|
128
|
+
- [Interface: GenerationParams](./docs/api/appkit/Interface.GenerationParams.md): Optional generation parameters forwarded to the OpenAI-compatible serving
|
|
128
129
|
- [Interface: IJobsConfig](./docs/api/appkit/Interface.IJobsConfig.md): Configuration for the Jobs plugin.
|
|
129
130
|
- [Interface: ITelemetry](./docs/api/appkit/Interface.ITelemetry.md): Plugin-facing interface for OpenTelemetry instrumentation.
|
|
130
131
|
- [Interface: JobAPI](./docs/api/appkit/Interface.JobAPI.md): User-facing API for a single configured job.
|