Skip to content

Commit 992dd66

Browse files
committed
fix(appkit): snapshot cache existence before generation; seed dev-playground
Fix a bootstrap-vs-drift misclassification found in live warehouse testing: generateFromEntryPoint read queryCacheFileExists() at the fatal throw site, but generateQueriesFromDescribe and syncMetricViewsTypes both persist the cache during the run — so the file always existed by then and the fatal message always said "drift", making the bootstrap branch dead code. Capture the existence snapshot at function entry (before any generation writes) and thread it to the throw. The mocked unit test passed while the bug was live because it stubbed the predicate directly; the test now ties the mock to the in-memory cache file so a regression (moving the check back) fails. Exclude .appkit/ from Biome: the committed cache is a generated artifact whose serialization saveCache owns, so the formatter must not govern it (a regen would otherwise trip lint). Seed apps/dev-playground/.appkit/ from a live blocking run against a warehouse (16 queries + 2 metrics + 1 serving endpoint). Verified end-to-end: a warm all-hit blocking re-run makes ZERO warehouse round-trips (0 new, 16 from cache, 0.00s) and the cache is byte-stable across a no-op regen (deterministic diff). xavier loop: iteration 5 -- Phase 3B integration + 2E ordering bugfix Co-authored-by: Isaac Signed-off-by: Atila Fassina <atila@fassina.eu>
1 parent efe7314 commit 992dd66

5 files changed

Lines changed: 268 additions & 13 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"version": "1",
3+
"endpoints": {
4+
"default": {
5+
"hash": "e66c960c5d57436d7fc4e21086650a534892a1548ccf2b333db2af6046832471",
6+
"requestType": "{\n messages?: {\n role?: \"user\" | \"assistant\";\n content?: string;\n }[];\n /** @openapi integer, nullable */\n n?: number | null;\n max_tokens?: number;\n /** @openapi double, nullable */\n top_p?: number | null;\n reasoning_effort?: \"low\" | \"medium\" | \"high\" | \"max\" | null;\n /** @openapi double, nullable */\n temperature?: number | null;\n stop?: string | string[] | null;\n /** @openapi integer, nullable */\n top_k?: number | null;\n}",
7+
"responseType": "{\n model?: string;\n choices?: {\n index?: number;\n message?: {\n role?: \"user\" | \"assistant\";\n content?: string;\n };\n finish_reason?: string;\n }[];\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n } | null;\n object?: string;\n id?: string;\n created?: number;\n}",
8+
"chunkType": "{\n model?: string;\n choices?: {\n index?: number;\n delta?: {\n role?: \"user\" | \"assistant\";\n content?: string;\n };\n finish_reason?: string | null;\n }[];\n object?: string;\n id?: string;\n created?: number;\n}",
9+
"requestKeys": [
10+
"messages",
11+
"n",
12+
"max_tokens",
13+
"top_p",
14+
"reasoning_effort",
15+
"temperature",
16+
"stop",
17+
"top_k"
18+
]
19+
}
20+
}
21+
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
{
2+
"version": "3",
3+
"queries": {
4+
"app_activity_heatmap": {
5+
"hash": "744281be86b14c2b14ed76b94060e4af",
6+
"type": "{\n name: \"app_activity_heatmap\";\n parameters: {\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n app_name: string;\n /** @sqlType STRING */\n day_of_week: string;\n /** @sqlType DECIMAL(35,2) */\n spend: number;\n }>;\n }",
7+
"retry": false
8+
},
9+
"apps_list": {
10+
"hash": "4b7393b43f8e7beaa52174ed4d918c04",
11+
"type": "{\n name: \"apps_list\";\n parameters: Record<string, never>;\n result: Array<{\n /** @sqlType STRING */\n id: string;\n /** @sqlType STRING */\n name: string;\n /** @sqlType STRING */\n creator: string;\n /** @sqlType STRING */\n tags: string;\n /** @sqlType DECIMAL(38,6) */\n totalSpend: number;\n /** @sqlType DATE */\n createdAt: string;\n }>;\n }",
12+
"retry": false
13+
},
14+
"cost_recommendations": {
15+
"hash": "730c7d8b5e2726981088b5975157b0da",
16+
"type": "{\n name: \"cost_recommendations\";\n parameters: Record<string, never>;\n result: Array<{\n /** @sqlType INT */\n dummy: number;\n }>;\n }",
17+
"retry": false
18+
},
19+
"dashboard_fare_distribution": {
20+
"hash": "d280e96e06e1b5189d8fe734901e6e72",
21+
"type": "{\n name: \"dashboard_fare_distribution\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n fare_bucket: string;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n avg_distance: number;\n }>;\n }",
22+
"retry": false
23+
},
24+
"dashboard_hourly_heatmap": {
25+
"hash": "827ca45db36ccf6f9c86c653c6175d69",
26+
"type": "{\n name: \"dashboard_hourly_heatmap\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType INT */\n day_of_week: number;\n /** @sqlType INT */\n hour_of_day: number;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n }>;\n }",
27+
"retry": false
28+
},
29+
"dashboard_kpi_sparklines": {
30+
"hash": "1b3c14b03e1de455ff78f2d3feaec047",
31+
"type": "{\n name: \"dashboard_kpi_sparklines\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType DATE */\n trip_date: string;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n total_revenue: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n /** @sqlType DOUBLE */\n avg_distance: number;\n }>;\n }",
32+
"retry": false
33+
},
34+
"dashboard_kpis": {
35+
"hash": "24ef19b8b1218edb8f247f6d79c915ff",
36+
"type": "{\n name: \"dashboard_kpis\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType BIGINT */\n total_trips: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n /** @sqlType DOUBLE */\n avg_distance: number;\n /** @sqlType DOUBLE */\n max_fare: number;\n /** @sqlType DOUBLE */\n min_fare: number;\n }>;\n }",
37+
"retry": false
38+
},
39+
"dashboard_top_zone": {
40+
"hash": "fe3ed4ef76e4e7eff329c88072a44fe9",
41+
"type": "{\n name: \"dashboard_top_zone\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType INT */\n pickup_zip: number;\n /** @sqlType BIGINT */\n trip_count: number;\n }>;\n }",
42+
"retry": false
43+
},
44+
"dashboard_top_zones": {
45+
"hash": "363c554c5a060ee2236f4e9fa1cf3c61",
46+
"type": "{\n name: \"dashboard_top_zones\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMin: SQLStringMarker;\n /** STRING - use sql.string() */\n fareMax: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n pickup_zip: string;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n total_revenue: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n }>;\n }",
47+
"retry": false
48+
},
49+
"dashboard_trips_over_time": {
50+
"hash": "eb8bed65ed02787ed050a016472b72f6",
51+
"type": "{\n name: \"dashboard_trips_over_time\";\n parameters: {\n /** STRING - use sql.string() */\n dateFrom: SQLStringMarker;\n /** STRING - use sql.string() */\n dateTo: SQLStringMarker;\n /** STRING - use sql.string() */\n pickupZip: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType DATE */\n trip_date: string;\n /** @sqlType BIGINT */\n trip_count: number;\n /** @sqlType DOUBLE */\n avg_fare: number;\n /** @sqlType DOUBLE */\n total_revenue: number;\n }>;\n }",
52+
"retry": false
53+
},
54+
"example": {
55+
"hash": "aeb02ed3e8a6c77279099406f8709543",
56+
"type": "{\n name: \"example\";\n parameters: Record<string, never>;\n result: Array<{\n /** @sqlType BOOLEAN */\n \"(1 = 1)\": boolean;\n }>;\n }",
57+
"retry": false
58+
},
59+
"spend_data": {
60+
"hash": "caa0430652fe15eff658e48e6dac2446",
61+
"type": "{\n name: \"spend_data\";\n parameters: {\n /** STRING - use sql.string() */\n groupBy: SQLStringMarker;\n /** STRING - use sql.string() */\n aggregationLevel: SQLStringMarker;\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n /** STRING - use sql.string() */\n appId: SQLStringMarker;\n /** STRING - use sql.string() */\n creator: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n group_key: string;\n /** @sqlType TIMESTAMP */\n aggregation_period: string;\n /** @sqlType DECIMAL(38,6) */\n cost_usd: number;\n }>;\n }",
62+
"retry": false
63+
},
64+
"spend_summary": {
65+
"hash": "bbe188624c3f5904c3a7593cb32982d5",
66+
"type": "{\n name: \"spend_summary\";\n parameters: {\n /** STRING - use sql.string() */\n aggregationLevel: SQLStringMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n };\n result: Array<{\n /** @sqlType DECIMAL(33,0) */\n total: number;\n /** @sqlType DECIMAL(33,0) */\n average: number;\n /** @sqlType DECIMAL(33,0) */\n forecasted: number;\n }>;\n }",
67+
"retry": false
68+
},
69+
"sql_helpers_test": {
70+
"hash": "1322df4ba9c107e8d23e2a04bae860c5",
71+
"type": "{\n name: \"sql_helpers_test\";\n parameters: {\n /** STRING - use sql.string() */\n stringParam: SQLStringMarker;\n /** NUMERIC - use sql.numeric() */\n numberParam: SQLNumberMarker;\n /** BOOLEAN - use sql.boolean() */\n booleanParam: SQLBooleanMarker;\n /** DATE - use sql.date() */\n dateParam: SQLDateMarker;\n /** TIMESTAMP - use sql.timestamp() */\n timestampParam: SQLTimestampMarker;\n /** STRING - use sql.string() */\n binaryParam: SQLStringMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n string_value: string;\n /** @sqlType INT */\n number_value: number;\n /** @sqlType BOOLEAN */\n boolean_value: boolean;\n /** @sqlType STRING */\n date_value: string;\n /** @sqlType STRING */\n timestamp_value: string;\n /** @sqlType BINARY */\n binary_value: string;\n /** @sqlType STRING */\n binary_hex: string;\n /** @sqlType INT */\n binary_length: number;\n }>;\n }",
72+
"retry": false
73+
},
74+
"top_contributors": {
75+
"hash": "2d58759cca2fe31dae06475a23080120",
76+
"type": "{\n name: \"top_contributors\";\n parameters: {\n /** STRING - use sql.string() */\n aggregationLevel: SQLStringMarker;\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n app_name: string;\n /** @sqlType DECIMAL(38,6) */\n total_cost_usd: number;\n }>;\n }",
77+
"retry": false
78+
},
79+
"untagged_apps": {
80+
"hash": "5946262b49710b8ab458d1bf950ff8c9",
81+
"type": "{\n name: \"untagged_apps\";\n parameters: {\n /** STRING - use sql.string() */\n aggregationLevel: SQLStringMarker;\n /** DATE - use sql.date() */\n startDate: SQLDateMarker;\n /** DATE - use sql.date() */\n endDate: SQLDateMarker;\n };\n result: Array<{\n /** @sqlType STRING */\n app_name: string;\n /** @sqlType STRING */\n creator: string;\n /** @sqlType DECIMAL(38,6) */\n total_cost_usd: number;\n /** @sqlType DECIMAL(38,10) */\n avg_period_cost_usd: number;\n }>;\n }",
82+
"retry": false
83+
}
84+
},
85+
"metrics": {
86+
"customers": {
87+
"hash": "26269cc37c4ea648a8a9cafb66a402dd",
88+
"schema": {
89+
"key": "customers",
90+
"source": "appkit_demo.public.customer_metrics",
91+
"lane": "obo",
92+
"measures": [
93+
{
94+
"name": "active_accounts",
95+
"type": "bigint",
96+
"isMeasure": true,
97+
"displayName": "Active Accounts",
98+
"format": "#,##0"
99+
},
100+
{
101+
"name": "churn_rate",
102+
"type": "decimal",
103+
"isMeasure": true,
104+
"displayName": "Churn Rate"
105+
},
106+
{
107+
"name": "avg_ltv",
108+
"type": "double",
109+
"isMeasure": true,
110+
"displayName": "Average LTV",
111+
"format": "$#,##0.00"
112+
}
113+
],
114+
"dimensions": [
115+
{
116+
"name": "segment",
117+
"type": "string",
118+
"isMeasure": false,
119+
"displayName": "Customer Segment"
120+
},
121+
{
122+
"name": "region",
123+
"type": "string",
124+
"isMeasure": false,
125+
"displayName": "Region"
126+
},
127+
{
128+
"name": "csm_email",
129+
"type": "string",
130+
"isMeasure": false,
131+
"displayName": "CSM Email"
132+
}
133+
]
134+
},
135+
"retry": false
136+
},
137+
"revenue": {
138+
"hash": "8d0592a864778470213ca22517f21fae",
139+
"schema": {
140+
"key": "revenue",
141+
"source": "appkit_demo.public.revenue_metrics",
142+
"lane": "sp",
143+
"measures": [
144+
{
145+
"name": "mrr",
146+
"type": "double",
147+
"isMeasure": true,
148+
"displayName": "Monthly Recurring Revenue",
149+
"format": "$#,##0.00"
150+
},
151+
{
152+
"name": "arr",
153+
"type": "double",
154+
"isMeasure": true,
155+
"description": "Annualized contract value across all active subscriptions",
156+
"displayName": "Annual Recurring Revenue",
157+
"format": "$#,##0.00"
158+
},
159+
{
160+
"name": "new_arr",
161+
"type": "double",
162+
"isMeasure": true,
163+
"displayName": "New ARR",
164+
"format": "$#,##0.00"
165+
},
166+
{
167+
"name": "churned_arr",
168+
"type": "double",
169+
"isMeasure": true,
170+
"displayName": "Churned ARR",
171+
"format": "$#,##0.00"
172+
}
173+
],
174+
"dimensions": [
175+
{
176+
"name": "region",
177+
"type": "string",
178+
"isMeasure": false,
179+
"displayName": "Region"
180+
},
181+
{
182+
"name": "segment",
183+
"type": "string",
184+
"isMeasure": false,
185+
"displayName": "Customer Segment"
186+
},
187+
{
188+
"name": "created_at",
189+
"type": "timestamp_ltz",
190+
"isMeasure": false,
191+
"displayName": "Subscription Start",
192+
"timeGrains": [
193+
"day",
194+
"hour",
195+
"minute",
196+
"month",
197+
"quarter",
198+
"week",
199+
"year"
200+
]
201+
}
202+
]
203+
},
204+
"retry": false
205+
}
206+
}
207+
}

biome.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"!**/*.d.ts",
1515
"!**/build",
1616
"!**/coverage",
17+
"!**/.appkit",
1718
"!packages/appkit-ui/src/react/ui",
1819
"!**/routeTree.gen.ts",
1920
"!docs/.docusaurus",

packages/appkit/src/type-generator/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,13 @@ export async function generateFromEntryPoint(options: {
329329

330330
logger.debug("Starting type generation...");
331331

332+
// Snapshot BEFORE any generation writes the cache: generateQueriesFromDescribe
333+
// and syncMetricViewsTypes both persist to .appkit/types-cache.json during the
334+
// run, so reading existence at the throw site would always report "exists".
335+
// Capturing here is what lets the fatal message tell bootstrap (no cache yet)
336+
// apart from drift (cache present but stale/missing the failing key).
337+
const cacheExistedAtStart = await queryCacheFileExists();
338+
332339
let queryRegistry: QuerySchema[] = [];
333340
let syntaxErrors: QuerySyntaxError[] = [];
334341
let fatalErrors: QueryFatalError[] = [];
@@ -404,8 +411,7 @@ export async function generateFromEntryPoint(options: {
404411
throw new TypegenSyntaxError(syntaxErrors, warehouseId, fatalErrors);
405412
}
406413
if (fatalErrors.length > 0) {
407-
const cacheExists = await queryCacheFileExists();
408-
throw new TypegenFatalError(fatalErrors, warehouseId, cacheExists);
414+
throw new TypegenFatalError(fatalErrors, warehouseId, cacheExistedAtStart);
409415
}
410416

411417
logger.debug("Type generation complete!");

0 commit comments

Comments
 (0)