@databricks/appkit 0.65.0 → 0.66.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 +18 -0
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +9 -1
- package/dist/beta.js +6 -1
- package/dist/cli/commands/generate-types.js +12 -3
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/database/contract/index.js +3 -0
- package/dist/database/contract/registry.d.ts +26 -0
- package/dist/database/contract/registry.d.ts.map +1 -0
- package/dist/database/contract/wire.d.ts +8 -0
- package/dist/database/contract/wire.d.ts.map +1 -0
- package/dist/database/contract/wire.js +37 -0
- package/dist/database/contract/wire.js.map +1 -0
- package/dist/database/errors.js +71 -0
- package/dist/database/errors.js.map +1 -0
- package/dist/database/runtime/data-path.js +39 -0
- package/dist/database/runtime/data-path.js.map +1 -0
- package/dist/database/runtime/engine/drizzle-data-path.js +194 -0
- package/dist/database/runtime/engine/drizzle-data-path.js.map +1 -0
- package/dist/database/runtime/engine/translate.js +176 -0
- package/dist/database/runtime/engine/translate.js.map +1 -0
- package/dist/database/schema-builder/columns.d.ts +39 -0
- package/dist/database/schema-builder/columns.d.ts.map +1 -0
- package/dist/database/schema-builder/columns.js +162 -0
- package/dist/database/schema-builder/columns.js.map +1 -0
- package/dist/database/schema-builder/define-schema.d.ts +12 -0
- package/dist/database/schema-builder/define-schema.d.ts.map +1 -0
- package/dist/database/schema-builder/define-schema.js +207 -0
- package/dist/database/schema-builder/define-schema.js.map +1 -0
- package/dist/database/schema-builder/engine/relations.js +37 -0
- package/dist/database/schema-builder/engine/relations.js.map +1 -0
- package/dist/database/schema-builder/engine/tables.js +124 -0
- package/dist/database/schema-builder/engine/tables.js.map +1 -0
- package/dist/database/schema-builder/fk.d.ts +9 -0
- package/dist/database/schema-builder/fk.d.ts.map +1 -0
- package/dist/database/schema-builder/fk.js +71 -0
- package/dist/database/schema-builder/fk.js.map +1 -0
- package/dist/database/schema-builder/index.js +6 -0
- package/dist/database/schema-builder/relations.js +51 -0
- package/dist/database/schema-builder/relations.js.map +1 -0
- package/dist/database/schema-builder/types.d.ts +121 -0
- package/dist/database/schema-builder/types.d.ts.map +1 -0
- package/dist/database/schema-builder/types.js +43 -0
- package/dist/database/schema-builder/types.js.map +1 -0
- package/dist/database/schema-builder/validators.js +53 -0
- package/dist/database/schema-builder/validators.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/plugins/beta-exports.generated.d.ts +3 -1
- package/dist/plugins/beta-exports.generated.js +2 -0
- package/dist/plugins/database/database.d.ts +37 -0
- package/dist/plugins/database/database.d.ts.map +1 -0
- package/dist/plugins/database/database.js +68 -0
- package/dist/plugins/database/database.js.map +1 -0
- package/dist/plugins/database/defaults.js +12 -0
- package/dist/plugins/database/defaults.js.map +1 -0
- package/dist/plugins/database/entity-client.js +148 -0
- package/dist/plugins/database/entity-client.js.map +1 -0
- package/dist/plugins/database/entity-types.d.ts +76 -0
- package/dist/plugins/database/entity-types.d.ts.map +1 -0
- package/dist/plugins/database/index.d.ts +3 -0
- package/dist/plugins/database/index.js +3 -0
- package/dist/plugins/database/lifecycle.js +107 -0
- package/dist/plugins/database/lifecycle.js.map +1 -0
- package/dist/plugins/database/manifest.js +84 -0
- package/dist/plugins/database/manifest.js.map +1 -0
- package/dist/plugins/database/types.d.ts +10 -0
- package/dist/plugins/database/types.d.ts.map +1 -0
- package/dist/type-generator/database/generate.d.ts +15 -0
- package/dist/type-generator/database/generate.d.ts.map +1 -0
- package/dist/type-generator/database/generate.js +104 -0
- package/dist/type-generator/database/generate.js.map +1 -0
- package/dist/type-generator/database/index.js +3 -0
- package/dist/type-generator/database/walk-schema.js +65 -0
- package/dist/type-generator/database/walk-schema.js.map +1 -0
- package/dist/type-generator/index.d.ts +148 -0
- package/dist/type-generator/index.d.ts.map +1 -0
- package/dist/type-generator/index.js +3 -1
- package/dist/type-generator/index.js.map +1 -1
- package/dist/type-generator/mv-registry/types.d.ts +99 -0
- package/dist/type-generator/mv-registry/types.d.ts.map +1 -0
- package/dist/type-generator/preflight.d.ts +16 -0
- package/dist/type-generator/preflight.d.ts.map +1 -0
- package/dist/type-generator/serving/generator.d.ts +20 -0
- package/dist/type-generator/serving/generator.d.ts.map +1 -0
- package/dist/type-generator/types.d.ts +72 -0
- package/dist/type-generator/types.d.ts.map +1 -0
- package/dist/type-generator/vite-plugin.d.ts +1 -0
- package/dist/type-generator/vite-plugin.d.ts.map +1 -1
- package/dist/type-generator/vite-plugin.js +41 -8
- package/dist/type-generator/vite-plugin.js.map +1 -1
- package/dist/type-generator/warehouse-status.d.ts +1 -0
- package/docs/api/appkit/Function.bigid.md +10 -0
- package/docs/api/appkit/Function.bigint.md +10 -0
- package/docs/api/appkit/Function.boolean.md +10 -0
- package/docs/api/appkit/Function.database.md +56 -0
- package/docs/api/appkit/Function.defineSchema.md +17 -0
- package/docs/api/appkit/Function.enumColumn.md +17 -0
- package/docs/api/appkit/Function.fk.md +18 -0
- package/docs/api/appkit/Function.id.md +10 -0
- package/docs/api/appkit/Function.integer.md +10 -0
- package/docs/api/appkit/Function.jsonb.md +10 -0
- package/docs/api/appkit/Function.text.md +10 -0
- package/docs/api/appkit/Function.timestamp.md +19 -0
- package/docs/api/appkit/Function.uuid.md +10 -0
- package/docs/api/appkit/Function.varchar.md +16 -0
- package/docs/api/appkit/Interface.DatabaseRegistry.md +3 -0
- package/docs/api/appkit/Interface.Schema.md +28 -0
- package/docs/api/appkit/TypeAlias.DatabaseExports.md +35 -0
- package/docs/api/appkit/TypeAlias.IDatabaseConfig.md +25 -0
- package/docs/api/appkit.md +18 -0
- package/llms.txt +18 -0
- package/package.json +4 -3
- package/sbom.cdx.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.d.ts","names":[],"sources":["../../../src/type-generator/serving/generator.ts"],"mappings":";;;UAyCU,2BAAA;EACR,OAAA;EACA,SAAA,GAAY,MAAA,SAAe,cAAA;EAC3B,OAAA;AAAA;;;;;;;;;iBAWoB,oBAAA,CACpB,OAAA,EAAS,2BAAA,GACR,OAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//#region src/type-generator/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Databricks statement execution response interface for DESCRIBE QUERY /
|
|
4
|
+
* DESCRIBE TABLE EXTENDED.
|
|
5
|
+
*
|
|
6
|
+
* Two result shapes matter here:
|
|
7
|
+
* - `result.data_array` — rows already materialized as JSON arrays. Present
|
|
8
|
+
* when the warehouse returns `JSON_ARRAY` (and what every mocked test
|
|
9
|
+
* builds).
|
|
10
|
+
* - `result.attachment` — a base64-encoded Arrow IPC stream. Present when the
|
|
11
|
+
* statement runs with `format: "ARROW_STREAM"` + `disposition: "INLINE"`,
|
|
12
|
+
* which is the SDK's default disposition. The single row lands here and
|
|
13
|
+
* `data_array` is left undefined. {@link normalizeResultRows} decodes this
|
|
14
|
+
* back into `data_array` so downstream parsers stay shape-agnostic.
|
|
15
|
+
*
|
|
16
|
+
* @property statement_id - the id of the statement
|
|
17
|
+
* @property status - the status of the statement
|
|
18
|
+
* @property manifest - result metadata; `manifest.format` echoes the wire
|
|
19
|
+
* format (`ARROW_STREAM`, `JSON_ARRAY`, ...) the warehouse chose.
|
|
20
|
+
* @property result - the result; either `data_array` (rows as
|
|
21
|
+
* `[col_name, data_type, comment]` arrays) or `attachment` (base64 Arrow IPC)
|
|
22
|
+
*/
|
|
23
|
+
interface DatabricksStatementExecutionResponse {
|
|
24
|
+
statement_id: string;
|
|
25
|
+
status: {
|
|
26
|
+
state: string;
|
|
27
|
+
error?: {
|
|
28
|
+
error_code?: string;
|
|
29
|
+
message?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
manifest?: {
|
|
33
|
+
format?: string;
|
|
34
|
+
};
|
|
35
|
+
result?: {
|
|
36
|
+
data_array?: (string | null)[][]; /** Base64-encoded Arrow IPC stream (ARROW_STREAM + INLINE disposition). */
|
|
37
|
+
attachment?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Set when the result spans multiple chunks (rows exceeded INLINE's size
|
|
40
|
+
* limit). Its presence means this response holds only the FIRST chunk;
|
|
41
|
+
* {@link normalizeResultRows} throws rather than emit truncated types.
|
|
42
|
+
*/
|
|
43
|
+
next_chunk_index?: number; /** Companion to {@link next_chunk_index}: link to fetch the next chunk. */
|
|
44
|
+
next_chunk_internal_link?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A genuine SQL error: `DESCRIBE QUERY` ran against a *reachable* warehouse and
|
|
49
|
+
* the warehouse reported the statement as FAILED (bad table, syntax error,
|
|
50
|
+
* incompatible type, …). Distinct from a connectivity failure (warehouse
|
|
51
|
+
* unreachable), which is non-fatal and never recorded here.
|
|
52
|
+
* @property name - the query name
|
|
53
|
+
* @property message - the SQL error message reported by the warehouse
|
|
54
|
+
*/
|
|
55
|
+
interface QuerySyntaxError {
|
|
56
|
+
name: string;
|
|
57
|
+
message: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A non-SQL fatal error while attempting to describe a query: authentication,
|
|
61
|
+
* authorization, invalid warehouse/configuration, malformed SDK request, or
|
|
62
|
+
* any other setup problem that should not be treated as an offline warehouse.
|
|
63
|
+
* @property name - the query name
|
|
64
|
+
* @property message - the fatal error message
|
|
65
|
+
*/
|
|
66
|
+
interface QueryFatalError {
|
|
67
|
+
name: string;
|
|
68
|
+
message: string;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { DatabricksStatementExecutionResponse, QueryFatalError, QuerySyntaxError };
|
|
72
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/type-generator/types.ts"],"mappings":";;AAqBA;;;;;;;;;;;;;;;;;;;AAkGA;UAlGiB,oCAAA;EACf,YAAA;EACA,MAAA;IACE,KAAA;IACA,KAAA;MAAU,UAAA;MAAqB,OAAA;IAAA;EAAA;EAEjC,QAAA;IACE,MAAA;EAAA;EAEF,MAAA;IACE,UAAA;IAEA,UAAA;;;;;;IAMA,gBAAA;IAEA,wBAAA;EAAA;AAAA;;;;;;;;;UA8Ea,gBAAA;EACf,IAAA;EACA,OAAA;AAAA;;;;;;;;UAUe,eAAA;EACf,IAAA;EACA,OAAA;AAAA"}
|
|
@@ -17,6 +17,7 @@ interface AppKitTypesPluginOptions {
|
|
|
17
17
|
* Folders to watch for changes. Defaults to `config/queries` and
|
|
18
18
|
* `config/metric-views`. When overridden, include a `queries` folder and/or a
|
|
19
19
|
* `metric-views` folder — they are resolved by their trailing path segment.
|
|
20
|
+
* Database schema sources are watched independently.
|
|
20
21
|
*/
|
|
21
22
|
watchFolders?: string[];
|
|
22
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.d.ts","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"mappings":";;;;;AAGmC;
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"mappings":";;;;;AAGmC;UAqCzB,wBAAA;EAER,OAAA;EAFgC;;;;;;EAShC,SAAA;EAgB+B;;;;;;EAT/B,YAAA;AAAA;;;;;;;iBASc,iBAAA,CAAkB,OAAA,GAAU,wBAAA,GAA2B,MAAA"}
|
|
@@ -2,6 +2,8 @@ import { createWorkspaceClient } from "../shared/src/workspace-client/factory.js
|
|
|
2
2
|
import { createLogger } from "../logging/logger.js";
|
|
3
3
|
import { METRIC_CONFIG_FILE } from "../shared/src/schemas/metric-fqn.js";
|
|
4
4
|
import { getWarehouseState, startWarehouse, waitUntilRunning } from "./warehouse-status.js";
|
|
5
|
+
import { DATABASE_TYPES_FILE, DatabaseTypegenError, generateDatabaseTypes } from "./database/generate.js";
|
|
6
|
+
import "./database/index.js";
|
|
5
7
|
import { ANALYTICS_TYPES_FILE, TYPES_DIR, TypegenFatalError, TypegenSyntaxError, generateFromEntryPoint } from "./index.js";
|
|
6
8
|
import path from "node:path";
|
|
7
9
|
import { existsSync } from "node:fs";
|
|
@@ -27,6 +29,7 @@ function appKitTypesPlugin(options) {
|
|
|
27
29
|
let watchFolders;
|
|
28
30
|
let queryFolder;
|
|
29
31
|
let metricViewsFolder;
|
|
32
|
+
let databaseFolder;
|
|
30
33
|
let inFlight = null;
|
|
31
34
|
let queued = false;
|
|
32
35
|
let pendingMode = "non-blocking";
|
|
@@ -45,11 +48,8 @@ function appKitTypesPlugin(options) {
|
|
|
45
48
|
async function generateOnce(mode) {
|
|
46
49
|
try {
|
|
47
50
|
const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || "";
|
|
48
|
-
if (!warehouseId)
|
|
49
|
-
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
await generateFromEntryPoint({
|
|
51
|
+
if (!warehouseId) logger.debug("Warehouse ID not found. Skipping type generation.");
|
|
52
|
+
else if (hasAnalyticsSources()) await generateFromEntryPoint({
|
|
53
53
|
outFile,
|
|
54
54
|
queryFolder,
|
|
55
55
|
metricViewsFolder,
|
|
@@ -58,8 +58,12 @@ function appKitTypesPlugin(options) {
|
|
|
58
58
|
mode,
|
|
59
59
|
mvOutFile
|
|
60
60
|
});
|
|
61
|
+
await generateDatabaseTypes({
|
|
62
|
+
schemaFile: path.join(databaseFolder, "schema.ts"),
|
|
63
|
+
outFile: path.join(path.dirname(outFile), DATABASE_TYPES_FILE)
|
|
64
|
+
});
|
|
61
65
|
} catch (error) {
|
|
62
|
-
const isTypegenError = error instanceof TypegenSyntaxError || error instanceof TypegenFatalError;
|
|
66
|
+
const isTypegenError = error instanceof TypegenSyntaxError || error instanceof TypegenFatalError || error instanceof DatabaseTypegenError;
|
|
63
67
|
if (process.env.NODE_ENV === "production") {
|
|
64
68
|
if (isTypegenError) error.stack = error.message;
|
|
65
69
|
throw error;
|
|
@@ -69,6 +73,14 @@ function appKitTypesPlugin(options) {
|
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
/**
|
|
76
|
+
* Whether this project has anything for the warehouse-backed passes to read.
|
|
77
|
+
* A database-only project activates the plugin without them, so the query and
|
|
78
|
+
* metric-view work — and the warehouse it would warm up — must stay dormant.
|
|
79
|
+
*/
|
|
80
|
+
function hasAnalyticsSources() {
|
|
81
|
+
return queryFolder !== void 0 && existsSync(queryFolder) || metricViewsFolder !== void 0 && existsSync(metricViewsFolder);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
72
84
|
* Single-flight wrapper around {@link generateOnce}. The initial build, the
|
|
73
85
|
* .sql watcher, and the DEV warehouse watch all route through here so they can
|
|
74
86
|
* never run typegen concurrently (which would race-write the .d.ts).
|
|
@@ -134,6 +146,7 @@ function appKitTypesPlugin(options) {
|
|
|
134
146
|
*/
|
|
135
147
|
function armWarehouseWatch() {
|
|
136
148
|
if (process.env.NODE_ENV === "production") return;
|
|
149
|
+
if (!hasAnalyticsSources()) return;
|
|
137
150
|
const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || "";
|
|
138
151
|
if (!warehouseId) return;
|
|
139
152
|
watchController?.abort();
|
|
@@ -167,13 +180,16 @@ function appKitTypesPlugin(options) {
|
|
|
167
180
|
return {
|
|
168
181
|
name: "appkit-types",
|
|
169
182
|
apply() {
|
|
170
|
-
|
|
183
|
+
const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || "";
|
|
184
|
+
const typesDir = path.dirname(path.resolve(process.cwd(), options?.outFile ?? `shared/${TYPES_DIR}/${ANALYTICS_TYPES_FILE}`));
|
|
185
|
+
const hasDatabase = existsSync(path.join(process.cwd(), "config", "database", "schema.ts")) || existsSync(path.join(typesDir, DATABASE_TYPES_FILE));
|
|
186
|
+
if (!warehouseId && !hasDatabase) {
|
|
171
187
|
logger.debug("Warehouse ID not found. Skipping type generation.");
|
|
172
188
|
return false;
|
|
173
189
|
}
|
|
174
190
|
const hasQueries = existsSync(path.join(process.cwd(), "config", "queries"));
|
|
175
191
|
const hasMetricViews = existsSync(path.join(process.cwd(), "config", "metric-views"));
|
|
176
|
-
if (!hasQueries && !hasMetricViews) return false;
|
|
192
|
+
if (!hasQueries && !hasMetricViews && !hasDatabase) return false;
|
|
177
193
|
return true;
|
|
178
194
|
},
|
|
179
195
|
configResolved(config) {
|
|
@@ -182,6 +198,7 @@ function appKitTypesPlugin(options) {
|
|
|
182
198
|
mvOutFile = options?.mvOutFile !== void 0 ? path.resolve(projectRoot, options.mvOutFile) : void 0;
|
|
183
199
|
const defaultQueryFolder = path.join(process.cwd(), "config", "queries");
|
|
184
200
|
const defaultMetricViewsFolder = path.join(process.cwd(), "config", "metric-views");
|
|
201
|
+
databaseFolder = path.join(process.cwd(), "config", "database");
|
|
185
202
|
watchFolders = options?.watchFolders ?? [defaultQueryFolder, defaultMetricViewsFolder];
|
|
186
203
|
if (options?.watchFolders) {
|
|
187
204
|
queryFolder = watchFolders.find((f) => path.basename(f) === "queries");
|
|
@@ -198,7 +215,17 @@ function appKitTypesPlugin(options) {
|
|
|
198
215
|
},
|
|
199
216
|
configureServer(server) {
|
|
200
217
|
server.watcher.add(watchFolders);
|
|
218
|
+
server.watcher.add(databaseFolder);
|
|
219
|
+
const isDatabaseSource = (changedFile) => {
|
|
220
|
+
const normalizedFile = path.resolve(changedFile);
|
|
221
|
+
const relative = path.relative(databaseFolder, normalizedFile);
|
|
222
|
+
return !relative.startsWith("..") && !path.isAbsolute(relative) && /\.(?:[cm]?ts|tsx)$/.test(normalizedFile);
|
|
223
|
+
};
|
|
201
224
|
server.watcher.on("change", (changedFile) => {
|
|
225
|
+
if (isDatabaseSource(changedFile)) {
|
|
226
|
+
runGenerate("non-blocking");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
202
229
|
const isWatchedFile = watchFolders.some((folder) => changedFile.startsWith(folder));
|
|
203
230
|
const isMetricConfig = metricViewsFolder !== void 0 && path.basename(changedFile) === METRIC_CONFIG_FILE && path.dirname(path.resolve(changedFile)) === path.resolve(metricViewsFolder);
|
|
204
231
|
if (isWatchedFile && (changedFile.endsWith(".sql") || isMetricConfig)) {
|
|
@@ -206,6 +233,12 @@ function appKitTypesPlugin(options) {
|
|
|
206
233
|
armWarehouseWatch();
|
|
207
234
|
}
|
|
208
235
|
});
|
|
236
|
+
server.watcher.on("add", (file) => {
|
|
237
|
+
if (isDatabaseSource(file)) runGenerate("non-blocking");
|
|
238
|
+
});
|
|
239
|
+
server.watcher.on("unlink", (file) => {
|
|
240
|
+
if (isDatabaseSource(file)) runGenerate("non-blocking");
|
|
241
|
+
});
|
|
209
242
|
server.httpServer?.once("close", () => {
|
|
210
243
|
watchController?.abort();
|
|
211
244
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.js","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { Plugin } from \"vite\";\n\nimport { METRIC_CONFIG_FILE } from \"../../../shared/src/schemas/metric-fqn\";\nimport { createLogger } from \"../logging/logger\";\nimport { createWorkspaceClient } from \"../workspace-client\";\nimport {\n ANALYTICS_TYPES_FILE,\n generateFromEntryPoint,\n TYPES_DIR,\n TypegenFatalError,\n TypegenSyntaxError,\n} from \"./index\";\nimport type { PreflightMode } from \"./preflight\";\nimport {\n getWarehouseState,\n startWarehouse,\n waitUntilRunning,\n} from \"./warehouse-status\";\n\nconst logger = createLogger(\"type-generator:vite-plugin\");\n\n/**\n * How long the DEV background watcher waits for a STARTING warehouse to reach\n * RUNNING before giving up. Short relative to the CLI's preflight budget: this\n * is a best-effort \"regenerate once the warehouse warms up\" convenience, not a\n * gate, so we'd rather stop polling than hold a detached task open for minutes.\n */\nconst DEV_WAREHOUSE_WATCH_MAX_MS = 60_000;\n\n/**\n * Options for the AppKit types plugin.\n */\ninterface AppKitTypesPluginOptions {\n /* Path to the output d.ts file (relative to client folder). */\n outFile?: string;\n /**\n * Path to the metric registry `.ts` file (relative to client folder).\n * Defaults to a sibling of `outFile`, computed by the generator. The\n * generated source carries both the `declare module` augmentation and the\n * runtime `metricViewsMetadata` const, so it is a real `.ts`, not a `.d.ts`.\n */\n mvOutFile?: string;\n /**\n * Folders to watch for changes. Defaults to `config/queries` and\n * `config/metric-views`. When overridden, include a `queries` folder and/or a\n * `metric-views` folder — they are resolved by their trailing path segment.\n */\n watchFolders?: string[];\n}\n\n/**\n * Vite plugin to generate types for AppKit queries.\n * Calls generateFromEntryPoint under the hood.\n * @param options - Options to override default values.\n * @returns Vite plugin to generate types for AppKit queries.\n */\nexport function appKitTypesPlugin(options?: AppKitTypesPluginOptions): Plugin {\n let outFile: string;\n let mvOutFile: string | undefined;\n let watchFolders: string[];\n // The queries + metric-views config folders, resolved in `configResolved`.\n // Passed explicitly into generateFromEntryPoint so neither is inferred from\n // `watchFolders` ordering (which used to assume queries was `watchFolders[0]`).\n let queryFolder: string | undefined;\n let metricViewsFolder: string | undefined;\n\n // Single-flight state for runGenerate(). `inFlight` is the promise of the\n // currently-running drain (null when idle); `queued` records that a trigger\n // arrived while a run was active so exactly ONE trailing run fires afterwards\n // (latest-wins — coalesces any number of overlapping triggers into a single\n // rerun). `queued` is read/cleared synchronously inside the drain loop so a\n // trigger landing in any window is caught before the drain exits.\n //\n // `pendingMode` is the mode the next generate should run in (latest-wins, like\n // `queued`): the foreground build runs non-blocking in dev (instant degrade)\n // while the background warehouse watch runs blocking (real DESCRIBEs). A\n // blocking watch trigger that lands while a non-blocking foreground run is in\n // flight therefore still describes when its trailing run fires.\n let inFlight: Promise<void> | null = null;\n let queued = false;\n let pendingMode: PreflightMode = \"non-blocking\";\n\n // The currently-armed DEV background warehouse watch, if any. Aborting it\n // stops a pending waitUntilRunning (server shutdown, or a newer arm replacing\n // an older one).\n let watchController: AbortController | null = null;\n\n /**\n * Generate types once in the given preflight {@link PreflightMode}. Never\n * throws in dev (logs instead); in production it rethrows so the build fails.\n * This is the un-guarded core — callers should go through {@link runGenerate}\n * so concurrent triggers can't race-write the .d.ts.\n *\n * @param mode - preflight policy for this run. The foreground build passes a\n * NODE_ENV-derived mode (blocking in production, non-blocking in dev so it\n * degrades instantly); the background warehouse watch passes \"blocking\" so\n * its regenerate actually DESCRIBEs and lands real (non-degraded) types.\n */\n async function generateOnce(mode: PreflightMode) {\n try {\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n\n if (!warehouseId) {\n logger.debug(\"Warehouse ID not found. Skipping type generation.\");\n return;\n }\n\n await generateFromEntryPoint({\n outFile,\n queryFolder,\n metricViewsFolder,\n warehouseId,\n noCache: false,\n mode,\n mvOutFile,\n });\n } catch (error) {\n // TypegenSyntaxError / TypegenFatalError carry a complete, actionable\n // report in their message. Their stack frames and attached query arrays\n // point into appkit internals and only add noise, so surface just the\n // message — both when failing the prod build and when logging in dev.\n const isTypegenError =\n error instanceof TypegenSyntaxError ||\n error instanceof TypegenFatalError;\n\n // throw in production to fail the build\n if (process.env.NODE_ENV === \"production\") {\n if (isTypegenError) error.stack = error.message;\n throw error;\n }\n\n if (isTypegenError) {\n logger.error(\"%s\", error.message);\n } else {\n logger.error(\"Error generating types: %O\", error);\n }\n }\n }\n\n /**\n * Single-flight wrapper around {@link generateOnce}. The initial build, the\n * .sql watcher, and the DEV warehouse watch all route through here so they can\n * never run typegen concurrently (which would race-write the .d.ts).\n *\n * If a run is already in flight, this does NOT start a second one — it records\n * the requested mode and sets a trailing flag so exactly one more run fires\n * after the current finishes, coalescing any number of overlapping triggers\n * (latest-wins, including the mode: a blocking watch trigger that arrives mid\n * non-blocking foreground run still describes when its trailing run fires).\n *\n * @param mode - preflight policy for this run. Recorded into `pendingMode`,\n * which the drain reads for each generate (latest trigger wins).\n * @returns A promise that resolves when this trigger's work (including any\n * trailing run it scheduled) has completed.\n */\n function runGenerate(mode: PreflightMode): Promise<void> {\n pendingMode = mode;\n\n if (inFlight) {\n // A run is active: remember that another trigger arrived and ride out the\n // current run. One trailing run then covers all coalesced triggers and\n // runs in the latest requested mode (recorded above).\n queued = true;\n return inFlight;\n }\n\n // Drain in a loop rather than recursing after a single queued-check: a\n // trigger can land in the window between generateOnce() resolving and the\n // check, so we re-test `queued` until it's clear. Critically, `inFlight` is\n // cleared synchronously in the SAME tick as the final `queued === false`\n // observation — never deferred to a .finally microtask — so there's no\n // window where a trigger sees `inFlight` set but the drain has already\n // decided to exit. The guard stays held for the whole drain, so concurrent\n // triggers only ever set the flag; they never start a parallel generate.\n const drain = async (): Promise<void> => {\n while (true) {\n queued = false;\n // Snapshot the mode synchronously alongside clearing `queued` so a\n // trigger landing during this generate is observed (via `queued`) on the\n // next loop with its own mode, not silently dropped.\n const runMode = pendingMode;\n await generateOnce(runMode);\n // Synchronous check + clear, atomic w.r.t. other (synchronous) callers.\n if (!queued) {\n inFlight = null;\n return;\n }\n }\n };\n\n inFlight = drain();\n return inFlight;\n }\n\n /**\n * DEV-only: get the warehouse to RUNNING in the background and regenerate with\n * real (non-degraded) types once it is — without blocking dev startup. The\n * foreground build only ever degrades in dev (instant `unknown`/cached types),\n * so this is what lands actual DESCRIBE results in the editor for EVERY\n * reachable warehouse state, not just one that happens to already be warm.\n *\n * Post-probe behaviour by state:\n * - RUNNING → describe right away (the dev foreground degraded, so a running\n * warehouse would otherwise never get real types). `waitUntilRunning`\n * returns immediately for an already-running warehouse, then the blocking\n * regenerate fires.\n * - STARTING → it's already coming up; just wait for RUNNING, then describe.\n * - STOPPED / STOPPING → kick off a start, wait for RUNNING, then describe.\n * - DELETED / DELETING → return (a deleted warehouse can't be started, and\n * blocking typegen would treat it as fatal); leave the degraded types.\n *\n * No-op in production or without a warehouse id. Replaces any previously-armed\n * watch (aborting it first). Fully self-contained: it never throws into the\n * caller and never re-arms itself. The whole lifecycle is abortable via the\n * shared {@link watchController} — its signal is threaded into\n * `waitUntilRunning`, so a dev-server shutdown cancels a pending wait — and the\n * regenerate routes through {@link runGenerate} so it can't race-write the\n * .d.ts with the foreground degrade or a `.sql` re-trigger.\n *\n * The regenerate runs in \"blocking\" mode (not the foreground's non-blocking)\n * so it actually DESCRIBEs the now-RUNNING warehouse and lands real types —\n * the whole point of warming the warehouse in the background.\n */\n function armWarehouseWatch(): void {\n if (process.env.NODE_ENV === \"production\") return;\n\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n if (!warehouseId) return;\n\n // Supersede any in-flight watch so we never run two concurrently.\n watchController?.abort();\n const controller = new AbortController();\n watchController = controller;\n const { signal } = controller;\n\n void (async () => {\n try {\n const client = createWorkspaceClient();\n const state = await getWarehouseState(client, warehouseId);\n\n // A deleted/deleting warehouse can't be started and blocking typegen\n // would treat it as fatal — leave the degraded types and stop. Every\n // other state (including RUNNING) proceeds to wait-then-describe so the\n // dev editor gets real types, not just the foreground's degraded ones.\n if (state === \"DELETED\" || state === \"DELETING\") {\n return;\n }\n\n // Stopped/stopping won't reach RUNNING on its own — nudge it. RUNNING and\n // STARTING need no start (RUNNING is already up; STARTING is coming up),\n // so don't issue a redundant one. A failed start is non-fatal: give up\n // silently rather than throw out of the detached task (the developer\n // still has degraded/cached types).\n let startedByUs = false;\n if (state === \"STOPPED\" || state === \"STOPPING\") {\n try {\n logger.debug(\"Warehouse is %s; starting it.\", state);\n await startWarehouse(client, warehouseId);\n startedByUs = true;\n } catch {\n return;\n }\n }\n\n // Wait for RUNNING. For an already-RUNNING warehouse this returns on the\n // first poll; for STARTING/STOPPED it polls (abortably) until the\n // warehouse warms up, a terminal state, or the deadline.\n const final = await waitUntilRunning(client, warehouseId, {\n maxMs: DEV_WAREHOUSE_WATCH_MAX_MS,\n signal,\n // We just issued the start, so the first poll(s) often still report\n // STOPPED/STOPPING before the start propagates. Poll through those\n // instead of bailing, or the regenerate would never fire. When we\n // didn't start it (RUNNING/STARTING branch), keep the default terminal\n // states.\n treatStoppedAsTransient: startedByUs,\n });\n\n if (final === \"RUNNING\" && !signal.aborted) {\n logger.debug(\"Warehouse is RUNNING; regenerating types.\");\n // Blocking: the warehouse is RUNNING now, so describe it and emit real\n // (non-degraded) types — unlike the foreground dev run, which degraded.\n // Routed through the single-flight guard so it coalesces with the\n // foreground degrade / any `.sql` re-trigger instead of racing them.\n await runGenerate(\"blocking\");\n }\n } catch {\n // Detached background task: any failure (timeout, abort, connectivity,\n // auth) is non-fatal — the developer still has degraded/cached types.\n }\n })();\n }\n\n return {\n name: \"appkit-types\",\n\n apply() {\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n\n if (!warehouseId) {\n logger.debug(\"Warehouse ID not found. Skipping type generation.\");\n return false;\n }\n\n // Run when either config surface exists. Metric-view types are\n // independent of `.sql` queries, so a metric-only project (a\n // `config/metric-views/` with no `config/queries/`) must still activate\n // the plugin.\n const hasQueries = existsSync(\n path.join(process.cwd(), \"config\", \"queries\"),\n );\n const hasMetricViews = existsSync(\n path.join(process.cwd(), \"config\", \"metric-views\"),\n );\n if (!hasQueries && !hasMetricViews) {\n return false;\n }\n\n return true;\n },\n\n configResolved(config) {\n const projectRoot = path.resolve(config.root, \"..\");\n outFile = path.resolve(\n projectRoot,\n options?.outFile ?? `shared/${TYPES_DIR}/${ANALYTICS_TYPES_FILE}`,\n );\n // The metric out-path resolves against projectRoot only when explicitly\n // provided; an unset option passes through as undefined so the generator\n // computes its sibling-of-outFile default. In the all-defaults case the\n // final path is identical (the default outFile above lives in\n // shared/<TYPES_DIR>/), and a customized outFile now keeps its metric\n // sibling next to it instead of pinning it under shared/.\n mvOutFile =\n options?.mvOutFile !== undefined\n ? path.resolve(projectRoot, options.mvOutFile)\n : undefined;\n\n const defaultQueryFolder = path.join(process.cwd(), \"config\", \"queries\");\n const defaultMetricViewsFolder = path.join(\n process.cwd(),\n \"config\",\n \"metric-views\",\n );\n watchFolders = options?.watchFolders ?? [\n defaultQueryFolder,\n defaultMetricViewsFolder,\n ];\n\n // Resolve the two config folders explicitly rather than assuming a\n // position in `watchFolders`. With a custom `watchFolders`, match by the\n // trailing segment; otherwise use the computed defaults.\n if (options?.watchFolders) {\n queryFolder = watchFolders.find((f) => path.basename(f) === \"queries\");\n metricViewsFolder = watchFolders.find(\n (f) => path.basename(f) === \"metric-views\",\n );\n } else {\n queryFolder = defaultQueryFolder;\n metricViewsFolder = defaultMetricViewsFolder;\n }\n },\n\n buildStart() {\n // Production: block the build on this generate (and surface failures).\n // The watch is a dev-only no-op, so just run typegen.\n if (process.env.NODE_ENV === \"production\") {\n return runGenerate(\"blocking\");\n }\n\n // Dev: don't block startup waiting on typegen. The foreground generate runs\n // non-blocking — it skips the warehouse entirely and writes degraded\n // (cached/`unknown`) types instantly. Then arm the warehouse watch so the\n // warehouse gets a one-shot BLOCKING regenerate (real types) in the\n // background for EVERY reachable state: RUNNING describes right away, while\n // STARTING/STOPPED are waited (and started) until they reach RUNNING.\n void runGenerate(\"non-blocking\");\n armWarehouseWatch();\n },\n\n configureServer(server) {\n server.watcher.add(watchFolders);\n\n server.watcher.on(\"change\", (changedFile) => {\n const isWatchedFile = watchFolders.some((folder) =>\n changedFile.startsWith(folder),\n );\n\n // The metric config is `definitions.json` — a far more generic name\n // than the old `metric-views.json`. Match it by DIRECTORY, not bare\n // basename: only a `definitions.json` sitting directly in the\n // metric-views folder is the config (a `definitions.json` elsewhere in\n // a watched tree must not trigger a regenerate).\n const isMetricConfig =\n metricViewsFolder !== undefined &&\n path.basename(changedFile) === METRIC_CONFIG_FILE &&\n path.dirname(path.resolve(changedFile)) ===\n path.resolve(metricViewsFolder);\n\n if (isWatchedFile && (changedFile.endsWith(\".sql\") || isMetricConfig)) {\n // Route through the single-flight runner (was fire-and-forget\n // generate(), which could race the initial build / watch). This is a\n // dev-only hook, so degrade instantly (non-blocking), then re-arm the\n // warehouse watch so the edited query or metric-view source is\n // re-described in the background against the running warehouse (or\n // once a still-starting one warms up), landing fresh\n // blocking-described types.\n void runGenerate(\"non-blocking\");\n armWarehouseWatch();\n }\n });\n\n // Tear down any pending warehouse watch when the dev server closes so a\n // long backoff can't keep the process alive after shutdown.\n server.httpServer?.once(\"close\", () => {\n watchController?.abort();\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vite-plugin.js","names":[],"sources":["../../src/type-generator/vite-plugin.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { Plugin } from \"vite\";\n\nimport { METRIC_CONFIG_FILE } from \"../../../shared/src/schemas/metric-fqn\";\nimport { createLogger } from \"../logging/logger\";\nimport { createWorkspaceClient } from \"../workspace-client\";\nimport {\n DATABASE_TYPES_FILE,\n DatabaseTypegenError,\n generateDatabaseTypes,\n} from \"./database\";\nimport {\n ANALYTICS_TYPES_FILE,\n generateFromEntryPoint,\n TYPES_DIR,\n TypegenFatalError,\n TypegenSyntaxError,\n} from \"./index\";\nimport type { PreflightMode } from \"./preflight\";\nimport {\n getWarehouseState,\n startWarehouse,\n waitUntilRunning,\n} from \"./warehouse-status\";\n\nconst logger = createLogger(\"type-generator:vite-plugin\");\n\n/**\n * How long the DEV background watcher waits for a STARTING warehouse to reach\n * RUNNING before giving up. Short relative to the CLI's preflight budget: this\n * is a best-effort \"regenerate once the warehouse warms up\" convenience, not a\n * gate, so we'd rather stop polling than hold a detached task open for minutes.\n */\nconst DEV_WAREHOUSE_WATCH_MAX_MS = 60_000;\n\n/**\n * Options for the AppKit types plugin.\n */\ninterface AppKitTypesPluginOptions {\n /* Path to the output d.ts file (relative to client folder). */\n outFile?: string;\n /**\n * Path to the metric registry `.ts` file (relative to client folder).\n * Defaults to a sibling of `outFile`, computed by the generator. The\n * generated source carries both the `declare module` augmentation and the\n * runtime `metricViewsMetadata` const, so it is a real `.ts`, not a `.d.ts`.\n */\n mvOutFile?: string;\n /**\n * Folders to watch for changes. Defaults to `config/queries` and\n * `config/metric-views`. When overridden, include a `queries` folder and/or a\n * `metric-views` folder — they are resolved by their trailing path segment.\n * Database schema sources are watched independently.\n */\n watchFolders?: string[];\n}\n\n/**\n * Vite plugin to generate types for AppKit queries.\n * Calls generateFromEntryPoint under the hood.\n * @param options - Options to override default values.\n * @returns Vite plugin to generate types for AppKit queries.\n */\nexport function appKitTypesPlugin(options?: AppKitTypesPluginOptions): Plugin {\n let outFile: string;\n let mvOutFile: string | undefined;\n let watchFolders: string[];\n // The queries + metric-views config folders, resolved in `configResolved`.\n // Passed explicitly into generateFromEntryPoint so neither is inferred from\n // `watchFolders` ordering (which used to assume queries was `watchFolders[0]`).\n let queryFolder: string | undefined;\n let metricViewsFolder: string | undefined;\n let databaseFolder: string;\n\n // Single-flight state for runGenerate(). `inFlight` is the promise of the\n // currently-running drain (null when idle); `queued` records that a trigger\n // arrived while a run was active so exactly ONE trailing run fires afterwards\n // (latest-wins — coalesces any number of overlapping triggers into a single\n // rerun). `queued` is read/cleared synchronously inside the drain loop so a\n // trigger landing in any window is caught before the drain exits.\n //\n // `pendingMode` is the mode the next generate should run in (latest-wins, like\n // `queued`): the foreground build runs non-blocking in dev (instant degrade)\n // while the background warehouse watch runs blocking (real DESCRIBEs). A\n // blocking watch trigger that lands while a non-blocking foreground run is in\n // flight therefore still describes when its trailing run fires.\n let inFlight: Promise<void> | null = null;\n let queued = false;\n let pendingMode: PreflightMode = \"non-blocking\";\n\n // The currently-armed DEV background warehouse watch, if any. Aborting it\n // stops a pending waitUntilRunning (server shutdown, or a newer arm replacing\n // an older one).\n let watchController: AbortController | null = null;\n\n /**\n * Generate types once in the given preflight {@link PreflightMode}. Never\n * throws in dev (logs instead); in production it rethrows so the build fails.\n * This is the un-guarded core — callers should go through {@link runGenerate}\n * so concurrent triggers can't race-write the .d.ts.\n *\n * @param mode - preflight policy for this run. The foreground build passes a\n * NODE_ENV-derived mode (blocking in production, non-blocking in dev so it\n * degrades instantly); the background warehouse watch passes \"blocking\" so\n * its regenerate actually DESCRIBEs and lands real (non-degraded) types.\n */\n async function generateOnce(mode: PreflightMode) {\n try {\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n\n if (!warehouseId) {\n logger.debug(\"Warehouse ID not found. Skipping type generation.\");\n } else if (hasAnalyticsSources()) {\n await generateFromEntryPoint({\n outFile,\n queryFolder,\n metricViewsFolder,\n warehouseId,\n noCache: false,\n mode,\n mvOutFile,\n });\n }\n\n // Database declarations need no warehouse. Generating them last keeps the\n // query and metric-view outputs independent of a schema failure.\n await generateDatabaseTypes({\n schemaFile: path.join(databaseFolder, \"schema.ts\"),\n outFile: path.join(path.dirname(outFile), DATABASE_TYPES_FILE),\n });\n } catch (error) {\n // TypegenSyntaxError / TypegenFatalError carry a complete, actionable\n // report in their message. Their stack frames and attached query arrays\n // point into appkit internals and only add noise, so surface just the\n // message — both when failing the prod build and when logging in dev.\n const isTypegenError =\n error instanceof TypegenSyntaxError ||\n error instanceof TypegenFatalError ||\n error instanceof DatabaseTypegenError;\n\n // throw in production to fail the build\n if (process.env.NODE_ENV === \"production\") {\n if (isTypegenError) error.stack = error.message;\n throw error;\n }\n\n if (isTypegenError) {\n logger.error(\"%s\", error.message);\n } else {\n logger.error(\"Error generating types: %O\", error);\n }\n }\n }\n\n /**\n * Whether this project has anything for the warehouse-backed passes to read.\n * A database-only project activates the plugin without them, so the query and\n * metric-view work — and the warehouse it would warm up — must stay dormant.\n */\n function hasAnalyticsSources(): boolean {\n return (\n (queryFolder !== undefined && existsSync(queryFolder)) ||\n (metricViewsFolder !== undefined && existsSync(metricViewsFolder))\n );\n }\n\n /**\n * Single-flight wrapper around {@link generateOnce}. The initial build, the\n * .sql watcher, and the DEV warehouse watch all route through here so they can\n * never run typegen concurrently (which would race-write the .d.ts).\n *\n * If a run is already in flight, this does NOT start a second one — it records\n * the requested mode and sets a trailing flag so exactly one more run fires\n * after the current finishes, coalescing any number of overlapping triggers\n * (latest-wins, including the mode: a blocking watch trigger that arrives mid\n * non-blocking foreground run still describes when its trailing run fires).\n *\n * @param mode - preflight policy for this run. Recorded into `pendingMode`,\n * which the drain reads for each generate (latest trigger wins).\n * @returns A promise that resolves when this trigger's work (including any\n * trailing run it scheduled) has completed.\n */\n function runGenerate(mode: PreflightMode): Promise<void> {\n pendingMode = mode;\n\n if (inFlight) {\n // A run is active: remember that another trigger arrived and ride out the\n // current run. One trailing run then covers all coalesced triggers and\n // runs in the latest requested mode (recorded above).\n queued = true;\n return inFlight;\n }\n\n // Drain in a loop rather than recursing after a single queued-check: a\n // trigger can land in the window between generateOnce() resolving and the\n // check, so we re-test `queued` until it's clear. Critically, `inFlight` is\n // cleared synchronously in the SAME tick as the final `queued === false`\n // observation — never deferred to a .finally microtask — so there's no\n // window where a trigger sees `inFlight` set but the drain has already\n // decided to exit. The guard stays held for the whole drain, so concurrent\n // triggers only ever set the flag; they never start a parallel generate.\n const drain = async (): Promise<void> => {\n while (true) {\n queued = false;\n // Snapshot the mode synchronously alongside clearing `queued` so a\n // trigger landing during this generate is observed (via `queued`) on the\n // next loop with its own mode, not silently dropped.\n const runMode = pendingMode;\n await generateOnce(runMode);\n // Synchronous check + clear, atomic w.r.t. other (synchronous) callers.\n if (!queued) {\n inFlight = null;\n return;\n }\n }\n };\n\n inFlight = drain();\n return inFlight;\n }\n\n /**\n * DEV-only: get the warehouse to RUNNING in the background and regenerate with\n * real (non-degraded) types once it is — without blocking dev startup. The\n * foreground build only ever degrades in dev (instant `unknown`/cached types),\n * so this is what lands actual DESCRIBE results in the editor for EVERY\n * reachable warehouse state, not just one that happens to already be warm.\n *\n * Post-probe behaviour by state:\n * - RUNNING → describe right away (the dev foreground degraded, so a running\n * warehouse would otherwise never get real types). `waitUntilRunning`\n * returns immediately for an already-running warehouse, then the blocking\n * regenerate fires.\n * - STARTING → it's already coming up; just wait for RUNNING, then describe.\n * - STOPPED / STOPPING → kick off a start, wait for RUNNING, then describe.\n * - DELETED / DELETING → return (a deleted warehouse can't be started, and\n * blocking typegen would treat it as fatal); leave the degraded types.\n *\n * No-op in production or without a warehouse id. Replaces any previously-armed\n * watch (aborting it first). Fully self-contained: it never throws into the\n * caller and never re-arms itself. The whole lifecycle is abortable via the\n * shared {@link watchController} — its signal is threaded into\n * `waitUntilRunning`, so a dev-server shutdown cancels a pending wait — and the\n * regenerate routes through {@link runGenerate} so it can't race-write the\n * .d.ts with the foreground degrade or a `.sql` re-trigger.\n *\n * The regenerate runs in \"blocking\" mode (not the foreground's non-blocking)\n * so it actually DESCRIBEs the now-RUNNING warehouse and lands real types —\n * the whole point of warming the warehouse in the background.\n */\n function armWarehouseWatch(): void {\n if (process.env.NODE_ENV === \"production\") return;\n if (!hasAnalyticsSources()) return;\n\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n if (!warehouseId) return;\n\n // Supersede any in-flight watch so we never run two concurrently.\n watchController?.abort();\n const controller = new AbortController();\n watchController = controller;\n const { signal } = controller;\n\n void (async () => {\n try {\n const client = createWorkspaceClient();\n const state = await getWarehouseState(client, warehouseId);\n\n // A deleted/deleting warehouse can't be started and blocking typegen\n // would treat it as fatal — leave the degraded types and stop. Every\n // other state (including RUNNING) proceeds to wait-then-describe so the\n // dev editor gets real types, not just the foreground's degraded ones.\n if (state === \"DELETED\" || state === \"DELETING\") {\n return;\n }\n\n // Stopped/stopping won't reach RUNNING on its own — nudge it. RUNNING and\n // STARTING need no start (RUNNING is already up; STARTING is coming up),\n // so don't issue a redundant one. A failed start is non-fatal: give up\n // silently rather than throw out of the detached task (the developer\n // still has degraded/cached types).\n let startedByUs = false;\n if (state === \"STOPPED\" || state === \"STOPPING\") {\n try {\n logger.debug(\"Warehouse is %s; starting it.\", state);\n await startWarehouse(client, warehouseId);\n startedByUs = true;\n } catch {\n return;\n }\n }\n\n // Wait for RUNNING. For an already-RUNNING warehouse this returns on the\n // first poll; for STARTING/STOPPED it polls (abortably) until the\n // warehouse warms up, a terminal state, or the deadline.\n const final = await waitUntilRunning(client, warehouseId, {\n maxMs: DEV_WAREHOUSE_WATCH_MAX_MS,\n signal,\n // We just issued the start, so the first poll(s) often still report\n // STOPPED/STOPPING before the start propagates. Poll through those\n // instead of bailing, or the regenerate would never fire. When we\n // didn't start it (RUNNING/STARTING branch), keep the default terminal\n // states.\n treatStoppedAsTransient: startedByUs,\n });\n\n if (final === \"RUNNING\" && !signal.aborted) {\n logger.debug(\"Warehouse is RUNNING; regenerating types.\");\n // Blocking: the warehouse is RUNNING now, so describe it and emit real\n // (non-degraded) types — unlike the foreground dev run, which degraded.\n // Routed through the single-flight guard so it coalesces with the\n // foreground degrade / any `.sql` re-trigger instead of racing them.\n await runGenerate(\"blocking\");\n }\n } catch {\n // Detached background task: any failure (timeout, abort, connectivity,\n // auth) is non-fatal — the developer still has degraded/cached types.\n }\n })();\n }\n\n return {\n name: \"appkit-types\",\n\n apply() {\n const warehouseId = process.env.DATABRICKS_WAREHOUSE_ID || \"\";\n const typesDir = path.dirname(\n path.resolve(\n process.cwd(),\n options?.outFile ?? `shared/${TYPES_DIR}/${ANALYTICS_TYPES_FILE}`,\n ),\n );\n // A declared schema needs no warehouse, and an already-generated\n // declaration must still be neutralized after its schema is deleted.\n const hasDatabase =\n existsSync(\n path.join(process.cwd(), \"config\", \"database\", \"schema.ts\"),\n ) || existsSync(path.join(typesDir, DATABASE_TYPES_FILE));\n\n if (!warehouseId && !hasDatabase) {\n logger.debug(\"Warehouse ID not found. Skipping type generation.\");\n return false;\n }\n\n // Run when either config surface exists. Metric-view types are\n // independent of `.sql` queries, so a metric-only project (a\n // `config/metric-views/` with no `config/queries/`) must still activate\n // the plugin.\n const hasQueries = existsSync(\n path.join(process.cwd(), \"config\", \"queries\"),\n );\n const hasMetricViews = existsSync(\n path.join(process.cwd(), \"config\", \"metric-views\"),\n );\n if (!hasQueries && !hasMetricViews && !hasDatabase) {\n return false;\n }\n\n return true;\n },\n\n configResolved(config) {\n const projectRoot = path.resolve(config.root, \"..\");\n outFile = path.resolve(\n projectRoot,\n options?.outFile ?? `shared/${TYPES_DIR}/${ANALYTICS_TYPES_FILE}`,\n );\n // The metric out-path resolves against projectRoot only when explicitly\n // provided; an unset option passes through as undefined so the generator\n // computes its sibling-of-outFile default. In the all-defaults case the\n // final path is identical (the default outFile above lives in\n // shared/<TYPES_DIR>/), and a customized outFile now keeps its metric\n // sibling next to it instead of pinning it under shared/.\n mvOutFile =\n options?.mvOutFile !== undefined\n ? path.resolve(projectRoot, options.mvOutFile)\n : undefined;\n\n const defaultQueryFolder = path.join(process.cwd(), \"config\", \"queries\");\n const defaultMetricViewsFolder = path.join(\n process.cwd(),\n \"config\",\n \"metric-views\",\n );\n databaseFolder = path.join(process.cwd(), \"config\", \"database\");\n watchFolders = options?.watchFolders ?? [\n defaultQueryFolder,\n defaultMetricViewsFolder,\n ];\n\n // Resolve the two config folders explicitly rather than assuming a\n // position in `watchFolders`. With a custom `watchFolders`, match by the\n // trailing segment; otherwise use the computed defaults.\n if (options?.watchFolders) {\n queryFolder = watchFolders.find((f) => path.basename(f) === \"queries\");\n metricViewsFolder = watchFolders.find(\n (f) => path.basename(f) === \"metric-views\",\n );\n } else {\n queryFolder = defaultQueryFolder;\n metricViewsFolder = defaultMetricViewsFolder;\n }\n },\n\n buildStart() {\n // Production: block the build on this generate (and surface failures).\n // The watch is a dev-only no-op, so just run typegen.\n if (process.env.NODE_ENV === \"production\") {\n return runGenerate(\"blocking\");\n }\n\n // Dev: don't block startup waiting on typegen. The foreground generate runs\n // non-blocking — it skips the warehouse entirely and writes degraded\n // (cached/`unknown`) types instantly. Then arm the warehouse watch so the\n // warehouse gets a one-shot BLOCKING regenerate (real types) in the\n // background for EVERY reachable state: RUNNING describes right away, while\n // STARTING/STOPPED are waited (and started) until they reach RUNNING.\n void runGenerate(\"non-blocking\");\n armWarehouseWatch();\n },\n\n configureServer(server) {\n server.watcher.add(watchFolders);\n server.watcher.add(databaseFolder);\n\n const isDatabaseSource = (changedFile: string): boolean => {\n const normalizedFile = path.resolve(changedFile);\n const relative = path.relative(databaseFolder, normalizedFile);\n return (\n !relative.startsWith(\"..\") &&\n !path.isAbsolute(relative) &&\n /\\.(?:[cm]?ts|tsx)$/.test(normalizedFile)\n );\n };\n\n server.watcher.on(\"change\", (changedFile) => {\n if (isDatabaseSource(changedFile)) {\n void runGenerate(\"non-blocking\");\n return;\n }\n\n const isWatchedFile = watchFolders.some((folder) =>\n changedFile.startsWith(folder),\n );\n\n // The metric config is `definitions.json` — a far more generic name\n // than the old `metric-views.json`. Match it by DIRECTORY, not bare\n // basename: only a `definitions.json` sitting directly in the\n // metric-views folder is the config (a `definitions.json` elsewhere in\n // a watched tree must not trigger a regenerate).\n const isMetricConfig =\n metricViewsFolder !== undefined &&\n path.basename(changedFile) === METRIC_CONFIG_FILE &&\n path.dirname(path.resolve(changedFile)) ===\n path.resolve(metricViewsFolder);\n\n if (isWatchedFile && (changedFile.endsWith(\".sql\") || isMetricConfig)) {\n // Route through the single-flight runner (was fire-and-forget\n // generate(), which could race the initial build / watch). This is a\n // dev-only hook, so degrade instantly (non-blocking), then re-arm the\n // warehouse watch so the edited query or metric-view source is\n // re-described in the background against the running warehouse (or\n // once a still-starting one warms up), landing fresh\n // blocking-described types.\n void runGenerate(\"non-blocking\");\n armWarehouseWatch();\n }\n });\n // Creation/deletion support is database-specific; query watchers retain\n // their existing change-only behavior.\n server.watcher.on(\"add\", (file) => {\n if (isDatabaseSource(file)) void runGenerate(\"non-blocking\");\n });\n server.watcher.on(\"unlink\", (file) => {\n if (isDatabaseSource(file)) void runGenerate(\"non-blocking\");\n });\n\n // Tear down any pending warehouse watch when the dev server closes so a\n // long backoff can't keep the process alive after shutdown.\n server.httpServer?.once(\"close\", () => {\n watchController?.abort();\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "../workspace-client/index.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Function: database()
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
function database<TSchema>(config: IDatabaseConfig<TSchema>): {
|
|
5
|
+
config: IDatabaseConfig<TSchema>;
|
|
6
|
+
name: "database";
|
|
7
|
+
plugin: PluginConstructor<BasePluginConfig, DatabasePlugin<TSchema>>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Create a typed database plugin registration for a finalized schema.
|
|
13
|
+
|
|
14
|
+
## Type Parameters[](#type-parameters "Direct link to Type Parameters")
|
|
15
|
+
|
|
16
|
+
| Type Parameter |
|
|
17
|
+
| --------------------------------------------------------------------------- |
|
|
18
|
+
| `TSchema` *extends* [`Schema`](./docs/api/appkit/Interface.Schema.md) |
|
|
19
|
+
|
|
20
|
+
## Parameters[](#parameters "Direct link to Parameters")
|
|
21
|
+
|
|
22
|
+
| Parameter | Type |
|
|
23
|
+
| --------- | ------------------------------------------------------------------------------------ |
|
|
24
|
+
| `config` | [`IDatabaseConfig`](./docs/api/appkit/TypeAlias.IDatabaseConfig.md)<`TSchema`> |
|
|
25
|
+
|
|
26
|
+
## Returns[](#returns "Direct link to Returns")
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
{
|
|
30
|
+
config: IDatabaseConfig<TSchema>;
|
|
31
|
+
name: "database";
|
|
32
|
+
plugin: PluginConstructor<BasePluginConfig, DatabasePlugin<TSchema>>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### config[](#config "Direct link to config")
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
config: IDatabaseConfig<TSchema>;
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### name[](#name "Direct link to name")
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
name: "database";
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### plugin[](#plugin "Direct link to plugin")
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
plugin: PluginConstructor<BasePluginConfig, DatabasePlugin<TSchema>>;
|
|
55
|
+
|
|
56
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Function: defineSchema()
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
function defineSchema(builder: (context: SchemaBuilderContext) => Record<string, AppKitTable>, options?: DefineSchemaOptions): Schema;
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## Parameters[](#parameters "Direct link to Parameters")
|
|
9
|
+
|
|
10
|
+
| Parameter | Type |
|
|
11
|
+
| ---------- | ------------------------------------------------------------------------ |
|
|
12
|
+
| `builder` | (`context`: `SchemaBuilderContext`) => `Record`<`string`, `AppKitTable`> |
|
|
13
|
+
| `options?` | `DefineSchemaOptions` |
|
|
14
|
+
|
|
15
|
+
## Returns[](#returns "Direct link to Returns")
|
|
16
|
+
|
|
17
|
+
[`Schema`](./docs/api/appkit/Interface.Schema.md)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Function: enumColumn()
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
function enumColumn(name: string, values: readonly string[]): ColumnBuilder;
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## Parameters[](#parameters "Direct link to Parameters")
|
|
9
|
+
|
|
10
|
+
| Parameter | Type |
|
|
11
|
+
| --------- | -------------------- |
|
|
12
|
+
| `name` | `string` |
|
|
13
|
+
| `values` | readonly `string`\[] |
|
|
14
|
+
|
|
15
|
+
## Returns[](#returns "Direct link to Returns")
|
|
16
|
+
|
|
17
|
+
`ColumnBuilder`
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Function: fk()
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
function fk(ref: FkRef): ColumnBuilder;
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
Declare foreign-key to another column.
|
|
9
|
+
|
|
10
|
+
## Parameters[](#parameters "Direct link to Parameters")
|
|
11
|
+
|
|
12
|
+
| Parameter | Type |
|
|
13
|
+
| --------- | ------- |
|
|
14
|
+
| `ref` | `FkRef` |
|
|
15
|
+
|
|
16
|
+
## Returns[](#returns "Direct link to Returns")
|
|
17
|
+
|
|
18
|
+
`ColumnBuilder`
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Function: timestamp()
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
function timestamp(opts?: {
|
|
5
|
+
withTimezone?: boolean;
|
|
6
|
+
}): ColumnBuilder;
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Parameters[](#parameters "Direct link to Parameters")
|
|
11
|
+
|
|
12
|
+
| Parameter | Type |
|
|
13
|
+
| -------------------- | ------------------------------- |
|
|
14
|
+
| `opts?` | { `withTimezone?`: `boolean`; } |
|
|
15
|
+
| `opts.withTimezone?` | `boolean` |
|
|
16
|
+
|
|
17
|
+
## Returns[](#returns "Direct link to Returns")
|
|
18
|
+
|
|
19
|
+
`ColumnBuilder`
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Function: varchar()
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
function varchar(length: number): ColumnBuilder;
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## Parameters[](#parameters "Direct link to Parameters")
|
|
9
|
+
|
|
10
|
+
| Parameter | Type | Default value |
|
|
11
|
+
| --------- | -------- | ------------- |
|
|
12
|
+
| `length` | `number` | `255` |
|
|
13
|
+
|
|
14
|
+
## Returns[](#returns "Direct link to Returns")
|
|
15
|
+
|
|
16
|
+
`ColumnBuilder`
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Interface: Schema
|
|
2
|
+
|
|
3
|
+
## Properties[](#properties "Direct link to Properties")
|
|
4
|
+
|
|
5
|
+
### $engine[](#engine "Direct link to $engine")
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
readonly $engine: Readonly<Record<string, EngineTable>>;
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
***
|
|
13
|
+
|
|
14
|
+
### $schemaName[](#schemaname "Direct link to $schemaName")
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
readonly $schemaName: string;
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
***
|
|
22
|
+
|
|
23
|
+
### $tables[](#tables "Direct link to $tables")
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
readonly $tables: Readonly<Record<string, AppKitTable>>;
|
|
27
|
+
|
|
28
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Type Alias: DatabaseExports
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
type DatabaseExports = TransactionClient & {
|
|
5
|
+
transaction: Promise<T>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Typed database API published by the plugin.
|
|
11
|
+
|
|
12
|
+
## Type Declaration[](#type-declaration "Direct link to Type Declaration")
|
|
13
|
+
|
|
14
|
+
### transaction()[](#transaction "Direct link to transaction()")
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
transaction<T>(callback: (tx: TransactionClient) => Promise<T>): Promise<T>;
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
#### Type Parameters[](#type-parameters "Direct link to Type Parameters")
|
|
22
|
+
|
|
23
|
+
| Type Parameter |
|
|
24
|
+
| -------------- |
|
|
25
|
+
| `T` |
|
|
26
|
+
|
|
27
|
+
#### Parameters[](#parameters "Direct link to Parameters")
|
|
28
|
+
|
|
29
|
+
| Parameter | Type |
|
|
30
|
+
| ---------- | --------------------------------------------- |
|
|
31
|
+
| `callback` | (`tx`: `TransactionClient`) => `Promise`<`T`> |
|
|
32
|
+
|
|
33
|
+
#### Returns[](#returns "Direct link to Returns")
|
|
34
|
+
|
|
35
|
+
`Promise`<`T`>
|