zitejs 0.9.51 → 0.9.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/dev/index.js +39 -39
- package/dist/cjs/sync/lib.d.ts +3 -1
- package/dist/cjs/sync/lib.js +108 -6
- package/dist/esm/dev/index.js +44 -44
- package/dist/esm/sync/lib.d.ts +3 -1
- package/dist/esm/sync/lib.js +108 -6
- package/package.json +1 -1
package/dist/cjs/dev/index.js
CHANGED
|
@@ -15,18 +15,18 @@ function debounce(key, fn, ms) {
|
|
|
15
15
|
debounceTimers.set(key, setTimeout(fn, ms));
|
|
16
16
|
}
|
|
17
17
|
function findAppDirs() {
|
|
18
|
-
const appsDir =
|
|
18
|
+
const appsDir = "apps";
|
|
19
19
|
if (!(0, fs_2.existsSync)(appsDir))
|
|
20
20
|
return [];
|
|
21
21
|
return (0, fs_2.readdirSync)(appsDir, { withFileTypes: true })
|
|
22
|
-
.filter(d => d.isDirectory())
|
|
23
|
-
.map(d => d.name);
|
|
22
|
+
.filter((d) => d.isDirectory())
|
|
23
|
+
.map((d) => d.name);
|
|
24
24
|
}
|
|
25
25
|
function getFlowId(appDir) {
|
|
26
26
|
try {
|
|
27
|
-
const configPath = (0, path_1.join)(
|
|
27
|
+
const configPath = (0, path_1.join)("apps", appDir, "zite.config.json");
|
|
28
28
|
if ((0, fs_2.existsSync)(configPath)) {
|
|
29
|
-
const config = JSON.parse((0, fs_2.readFileSync)(configPath,
|
|
29
|
+
const config = JSON.parse((0, fs_2.readFileSync)(configPath, "utf-8"));
|
|
30
30
|
return config.id;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -34,32 +34,32 @@ function getFlowId(appDir) {
|
|
|
34
34
|
return undefined;
|
|
35
35
|
}
|
|
36
36
|
function regenerateAppTypedWrappers(appDir) {
|
|
37
|
-
const outDir = (0, path_1.join)(
|
|
37
|
+
const outDir = (0, path_1.join)("apps", appDir, ".zite");
|
|
38
38
|
(0, fs_2.mkdirSync)(outDir, { recursive: true });
|
|
39
|
-
(0, fs_2.writeFileSync)((0, path_1.join)(outDir,
|
|
40
|
-
(0, fs_2.writeFileSync)((0, path_1.join)(outDir,
|
|
41
|
-
(0, fs_2.writeFileSync)((0, path_1.join)(outDir,
|
|
39
|
+
(0, fs_2.writeFileSync)((0, path_1.join)(outDir, "user.ts"), (0, lib_js_1.generateUserTs)());
|
|
40
|
+
(0, fs_2.writeFileSync)((0, path_1.join)(outDir, "auth.ts"), (0, lib_js_1.generateAuthWrapperTs)());
|
|
41
|
+
(0, fs_2.writeFileSync)((0, path_1.join)(outDir, "backend.ts"), (0, lib_js_1.generateBackendWrapperTs)());
|
|
42
42
|
}
|
|
43
43
|
function regenerateAppApiTs(appDir) {
|
|
44
|
-
const apiDir = (0, path_1.join)(
|
|
44
|
+
const apiDir = (0, path_1.join)("apps", appDir, "src", "api");
|
|
45
45
|
if (!(0, fs_2.existsSync)(apiDir))
|
|
46
46
|
return;
|
|
47
|
-
const endpointFiles = (0, fs_2.readdirSync)(apiDir).filter((f) => f.endsWith(
|
|
47
|
+
const endpointFiles = (0, fs_2.readdirSync)(apiDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
48
48
|
const content = (0, lib_js_1.generateApiTs)(endpointFiles);
|
|
49
49
|
if (content) {
|
|
50
|
-
const outDir = (0, path_1.join)(
|
|
50
|
+
const outDir = (0, path_1.join)("apps", appDir, ".zite");
|
|
51
51
|
(0, fs_2.mkdirSync)(outDir, { recursive: true });
|
|
52
|
-
(0, fs_2.writeFileSync)((0, path_1.join)(outDir,
|
|
52
|
+
(0, fs_2.writeFileSync)((0, path_1.join)(outDir, "api.ts"), content);
|
|
53
53
|
console.log(`Updated apps/${appDir}/.zite/api.ts`);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
function regenerateAppAirtableSdk(appDir) {
|
|
57
|
-
const lockPath = (0, path_1.join)(
|
|
57
|
+
const lockPath = (0, path_1.join)("apps", appDir, "zite.lock");
|
|
58
58
|
console.log(`[airtable-sdk] Checking ${lockPath} exists: ${(0, fs_2.existsSync)(lockPath)}`);
|
|
59
59
|
if (!(0, fs_2.existsSync)(lockPath))
|
|
60
60
|
return;
|
|
61
61
|
try {
|
|
62
|
-
const lockContent = JSON.parse((0, fs_2.readFileSync)(lockPath,
|
|
62
|
+
const lockContent = JSON.parse((0, fs_2.readFileSync)(lockPath, "utf-8"));
|
|
63
63
|
const integrations = lockContent.integrations ?? {};
|
|
64
64
|
console.log(`[airtable-sdk] Found ${Object.keys(integrations).length} integrations in lock:`, Object.keys(integrations));
|
|
65
65
|
for (const [integrationId, integration] of Object.entries(integrations)) {
|
|
@@ -75,9 +75,9 @@ function regenerateAppAirtableSdk(appDir) {
|
|
|
75
75
|
};
|
|
76
76
|
const content = (0, lib_js_1.generateAirtableTs)(airtableLock);
|
|
77
77
|
if (content) {
|
|
78
|
-
const outDir = (0, path_1.join)(
|
|
78
|
+
const outDir = (0, path_1.join)("apps", appDir, ".zite", "integrations");
|
|
79
79
|
(0, fs_2.mkdirSync)(outDir, { recursive: true });
|
|
80
|
-
(0, fs_2.writeFileSync)((0, path_1.join)(outDir,
|
|
80
|
+
(0, fs_2.writeFileSync)((0, path_1.join)(outDir, "airtable.ts"), content);
|
|
81
81
|
console.log(`Updated apps/${appDir}/.zite/integrations/airtable.ts`);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -96,16 +96,16 @@ async function runGenerate() {
|
|
|
96
96
|
// metadata from the backend API).
|
|
97
97
|
// 1. Generate root .zite/db.ts from zite.schema.json
|
|
98
98
|
try {
|
|
99
|
-
if ((0, fs_2.existsSync)(
|
|
100
|
-
const schema = JSON.parse((0, fs_2.readFileSync)(
|
|
99
|
+
if ((0, fs_2.existsSync)("zite.schema.json")) {
|
|
100
|
+
const schema = JSON.parse((0, fs_2.readFileSync)("zite.schema.json", "utf-8"));
|
|
101
101
|
const dbTs = (0, lib_js_1.generateDbTs)(schema);
|
|
102
|
-
(0, fs_2.mkdirSync)(
|
|
103
|
-
(0, fs_2.writeFileSync)((0, path_1.join)(
|
|
104
|
-
console.log(
|
|
102
|
+
(0, fs_2.mkdirSync)(".zite", { recursive: true });
|
|
103
|
+
(0, fs_2.writeFileSync)((0, path_1.join)(".zite", "db.ts"), dbTs);
|
|
104
|
+
console.log("Wrote .zite/db.ts");
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
catch (err) {
|
|
108
|
-
console.warn(
|
|
108
|
+
console.warn("DB SDK generation failed:", err instanceof Error ? err.message : err);
|
|
109
109
|
}
|
|
110
110
|
// 2. Find all apps and regenerate their .zite/ files
|
|
111
111
|
const appDirs = findAppDirs();
|
|
@@ -114,20 +114,20 @@ async function runGenerate() {
|
|
|
114
114
|
regenerateAppTypedWrappers(app);
|
|
115
115
|
regenerateAppAirtableSdk(app);
|
|
116
116
|
}
|
|
117
|
-
console.log(
|
|
117
|
+
console.log("Done!");
|
|
118
118
|
}
|
|
119
119
|
async function runDev() {
|
|
120
|
-
const env = process.env.ZITE_ENV ??
|
|
120
|
+
const env = process.env.ZITE_ENV ?? "production";
|
|
121
121
|
console.log(`zitejs dev — environment: ${env}`);
|
|
122
|
-
console.log(
|
|
122
|
+
console.log("");
|
|
123
123
|
// 1. Run initial sync (generates root .zite/db.ts)
|
|
124
124
|
try {
|
|
125
125
|
await (0, index_js_1.runSync)();
|
|
126
126
|
}
|
|
127
127
|
catch (err) {
|
|
128
|
-
console.warn(
|
|
128
|
+
console.warn("Initial sync failed (continuing with watcher):", err instanceof Error ? err.message : err);
|
|
129
129
|
}
|
|
130
|
-
console.log(
|
|
130
|
+
console.log("");
|
|
131
131
|
// 2. Find all apps and regenerate their .zite/ files
|
|
132
132
|
const appDirs = findAppDirs();
|
|
133
133
|
for (const app of appDirs) {
|
|
@@ -137,7 +137,7 @@ async function runDev() {
|
|
|
137
137
|
// 3. Watch each app's src/api/ for endpoint changes
|
|
138
138
|
let watchingAny = false;
|
|
139
139
|
for (const app of appDirs) {
|
|
140
|
-
const apiDir = (0, path_1.join)(
|
|
140
|
+
const apiDir = (0, path_1.join)("apps", app, "src", "api");
|
|
141
141
|
if (!(0, fs_2.existsSync)(apiDir))
|
|
142
142
|
continue;
|
|
143
143
|
watchingAny = true;
|
|
@@ -145,7 +145,7 @@ async function runDev() {
|
|
|
145
145
|
(0, fs_1.watch)(apiDir, { recursive: true }, (_event, filename) => {
|
|
146
146
|
if (!filename)
|
|
147
147
|
return;
|
|
148
|
-
if (!filename.endsWith(
|
|
148
|
+
if (!filename.endsWith(".ts") && !filename.endsWith(".js"))
|
|
149
149
|
return;
|
|
150
150
|
debounce(app, () => {
|
|
151
151
|
console.log(`Endpoint changed in ${app}: ${filename}`);
|
|
@@ -154,24 +154,24 @@ async function runDev() {
|
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
156
|
if (!watchingAny) {
|
|
157
|
-
console.log(
|
|
157
|
+
console.log("No apps with src/api/ found.");
|
|
158
158
|
}
|
|
159
159
|
// 4. Watch zite.schema.json for schema drift
|
|
160
|
-
if ((0, fs_2.existsSync)(
|
|
161
|
-
console.log(
|
|
162
|
-
(0, fs_1.watch)(
|
|
163
|
-
debounce(
|
|
164
|
-
console.log(
|
|
160
|
+
if ((0, fs_2.existsSync)("zite.schema.json")) {
|
|
161
|
+
console.log("Watching zite.schema.json for schema changes...");
|
|
162
|
+
(0, fs_1.watch)("zite.schema.json", () => {
|
|
163
|
+
debounce("schema", async () => {
|
|
164
|
+
console.log("Schema changed — re-running sync...");
|
|
165
165
|
try {
|
|
166
166
|
await (0, index_js_1.runSync)();
|
|
167
167
|
}
|
|
168
168
|
catch (err) {
|
|
169
|
-
console.error(
|
|
169
|
+
console.error("Re-sync failed:", err instanceof Error ? err.message : err);
|
|
170
170
|
}
|
|
171
171
|
}, 500);
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
|
-
console.log(
|
|
175
|
-
console.log(
|
|
174
|
+
console.log("");
|
|
175
|
+
console.log("Ready. Watching for changes... (Ctrl+C to stop)");
|
|
176
176
|
await new Promise(() => { });
|
|
177
177
|
}
|
package/dist/cjs/sync/lib.d.ts
CHANGED
|
@@ -40,11 +40,13 @@ export type AirtableLockField = {
|
|
|
40
40
|
id: string;
|
|
41
41
|
sdkName: string;
|
|
42
42
|
type: string;
|
|
43
|
-
|
|
43
|
+
name: string;
|
|
44
|
+
config?: Record<string, unknown>;
|
|
44
45
|
};
|
|
45
46
|
export type AirtableLockTable = {
|
|
46
47
|
id: string;
|
|
47
48
|
sdkName: string;
|
|
49
|
+
primaryFieldId: string;
|
|
48
50
|
fields: AirtableLockField[];
|
|
49
51
|
};
|
|
50
52
|
export type AirtableLock = {
|
package/dist/cjs/sync/lib.js
CHANGED
|
@@ -394,11 +394,12 @@ const AIRTABLE_FIELD_TYPE_MAP = {
|
|
|
394
394
|
externalSyncSource: "unknown",
|
|
395
395
|
aiText: "string",
|
|
396
396
|
};
|
|
397
|
-
function airtableTsType(field) {
|
|
397
|
+
function airtableTsType(field, lock, depth) {
|
|
398
|
+
const choices = field.config?.choices;
|
|
398
399
|
if ((field.type === "singleSelect" || field.type === "multipleSelects") &&
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const literals =
|
|
400
|
+
choices &&
|
|
401
|
+
choices.length > 0) {
|
|
402
|
+
const literals = choices
|
|
402
403
|
.slice(0, MAX_SELECT_OPTIONS)
|
|
403
404
|
.map((o) => `"${o.replace(/"/g, '\\"')}"`)
|
|
404
405
|
.join(" | ");
|
|
@@ -407,15 +408,106 @@ function airtableTsType(field) {
|
|
|
407
408
|
return `(${union})[]`;
|
|
408
409
|
return union;
|
|
409
410
|
}
|
|
411
|
+
if (field.type === "multipleLookupValues" && lock && (depth ?? 0) < 5) {
|
|
412
|
+
const linkFieldId = field.config?.recordLinkFieldId;
|
|
413
|
+
const targetFieldId = field.config?.fieldIdInLinkedTable;
|
|
414
|
+
if (linkFieldId && targetFieldId) {
|
|
415
|
+
const table = lock.tables.find((t) => t.fields.some((f) => f.id === linkFieldId));
|
|
416
|
+
const linkField = table?.fields.find((f) => f.id === linkFieldId);
|
|
417
|
+
const linkedTableId = linkField?.config?.linkedTableId;
|
|
418
|
+
if (linkedTableId) {
|
|
419
|
+
const linkedTable = lock.tables.find((t) => t.id === linkedTableId);
|
|
420
|
+
const targetField = linkedTable?.fields.find((f) => f.id === targetFieldId);
|
|
421
|
+
if (targetField) {
|
|
422
|
+
const resolved = airtableTsType(targetField, lock, (depth ?? 0) + 1);
|
|
423
|
+
return `${resolved}[] | undefined`;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
410
428
|
return AIRTABLE_FIELD_TYPE_MAP[field.type] ?? "unknown";
|
|
411
429
|
}
|
|
430
|
+
const READ_ONLY_AIRTABLE_FIELDS = new Set([
|
|
431
|
+
"autoNumber",
|
|
432
|
+
"count",
|
|
433
|
+
"formula",
|
|
434
|
+
"rollup",
|
|
435
|
+
"multipleLookupValues",
|
|
436
|
+
"createdTime",
|
|
437
|
+
"lastModifiedTime",
|
|
438
|
+
"createdBy",
|
|
439
|
+
"lastModifiedBy",
|
|
440
|
+
"button",
|
|
441
|
+
"externalSyncSource",
|
|
442
|
+
"aiText",
|
|
443
|
+
]);
|
|
444
|
+
function airtableFieldJsdoc(field, table) {
|
|
445
|
+
const parts = [];
|
|
446
|
+
if (table.primaryFieldId === field.id)
|
|
447
|
+
parts.push("Primary field");
|
|
448
|
+
if (READ_ONLY_AIRTABLE_FIELDS.has(field.type))
|
|
449
|
+
parts.push("Read-only; do not write");
|
|
450
|
+
if (field.type === "multipleRecordLinks") {
|
|
451
|
+
const linkedTableId = field.config?.linkedTableId;
|
|
452
|
+
if (linkedTableId)
|
|
453
|
+
parts.push(`Links to table ${linkedTableId}`);
|
|
454
|
+
if (field.config?.prefersSingleRecordLink)
|
|
455
|
+
parts.push("Single record only");
|
|
456
|
+
}
|
|
457
|
+
if (field.type === "multipleLookupValues")
|
|
458
|
+
parts.push("Lookup: value is an array — index or aggregate when displaying");
|
|
459
|
+
if (field.type === "percent")
|
|
460
|
+
parts.push("Raw decimal — multiply by 100 for display percentage");
|
|
461
|
+
parts.push(`"${field.name}"`);
|
|
462
|
+
if (field.type === "currency") {
|
|
463
|
+
const symbol = field.config?.symbol;
|
|
464
|
+
const precision = field.config?.precision;
|
|
465
|
+
if (symbol)
|
|
466
|
+
parts.push(`Display as currency with "${symbol}", ${precision ?? 2} decimals`);
|
|
467
|
+
}
|
|
468
|
+
if (field.type === "date") {
|
|
469
|
+
const fmt = field.config?.dateFormat;
|
|
470
|
+
if (fmt)
|
|
471
|
+
parts.push(`Date-only (ISO string), display as "${fmt}"`);
|
|
472
|
+
}
|
|
473
|
+
if (field.type === "dateTime") {
|
|
474
|
+
const dateFmt = field.config?.dateFormat;
|
|
475
|
+
const timeFmt = field.config?.timeFormat;
|
|
476
|
+
const tz = field.config?.timeZone;
|
|
477
|
+
const fmtParts = ["Date+time (ISO string)"];
|
|
478
|
+
if (dateFmt)
|
|
479
|
+
fmtParts.push(`date: ${dateFmt}`);
|
|
480
|
+
if (timeFmt)
|
|
481
|
+
fmtParts.push(`time: ${timeFmt}`);
|
|
482
|
+
if (tz)
|
|
483
|
+
fmtParts.push(`tz: ${tz}`);
|
|
484
|
+
parts.push(fmtParts.join(", "));
|
|
485
|
+
}
|
|
486
|
+
if (field.type === "rating") {
|
|
487
|
+
const max = field.config?.max;
|
|
488
|
+
if (max)
|
|
489
|
+
parts.push(`Max: ${max}`);
|
|
490
|
+
}
|
|
491
|
+
if (field.type === "duration") {
|
|
492
|
+
const fmt = field.config?.format;
|
|
493
|
+
if (fmt)
|
|
494
|
+
parts.push(`Value in seconds, display as "${fmt}"`);
|
|
495
|
+
}
|
|
496
|
+
if (field.type === "number" || field.type === "percent") {
|
|
497
|
+
const precision = field.config?.precision;
|
|
498
|
+
if (precision != null)
|
|
499
|
+
parts.push(`${precision} decimal places`);
|
|
500
|
+
}
|
|
501
|
+
if (parts.length <= 1)
|
|
502
|
+
return undefined;
|
|
503
|
+
return parts.join(". ");
|
|
504
|
+
}
|
|
412
505
|
function generateAirtableTs(lock) {
|
|
413
506
|
if (lock.tables.length === 0)
|
|
414
507
|
return null;
|
|
415
508
|
const lines = [
|
|
416
509
|
"// Auto-generated by zitejs generate from zite.lock. Do not edit manually.",
|
|
417
510
|
"// Airtable SDK — uses createAirtableClient from zitejs/runtime.",
|
|
418
|
-
"// The airtable package is externalized (not bundled per-endpoint).",
|
|
419
511
|
"//",
|
|
420
512
|
"// Each exported table client has these methods:",
|
|
421
513
|
"// findAll(options?: { offset?: string; limit?: number; filters?: unknown })",
|
|
@@ -425,6 +517,12 @@ function generateAirtableTs(lock) {
|
|
|
425
517
|
"// bulkCreate(records: Partial<T>[]) => Promise<T[]>",
|
|
426
518
|
"// update(id: string, data: { record: Partial<T> }) => Promise<T>",
|
|
427
519
|
"// delete(id: string) => Promise<{ deleted: true }>",
|
|
520
|
+
"//",
|
|
521
|
+
"// Usage tips:",
|
|
522
|
+
"// - Airtable has a strict rate limit of 5 requests/second per base",
|
|
523
|
+
"// - Use bulkCreate() instead of calling create() in a loop",
|
|
524
|
+
"// - findAll() offsets are opaque strings from previous calls, NOT numbers",
|
|
525
|
+
"// - Always destructure record properties individually in create/update calls",
|
|
428
526
|
"",
|
|
429
527
|
"import { createAirtableClient } from 'zitejs/runtime';",
|
|
430
528
|
"",
|
|
@@ -436,7 +534,11 @@ function generateAirtableTs(lock) {
|
|
|
436
534
|
for (const field of table.fields) {
|
|
437
535
|
if (field.sdkName === "id")
|
|
438
536
|
continue;
|
|
439
|
-
const
|
|
537
|
+
const jsdoc = airtableFieldJsdoc(field, table);
|
|
538
|
+
if (jsdoc) {
|
|
539
|
+
lines.push(` /** ${jsdoc} */`);
|
|
540
|
+
}
|
|
541
|
+
const tsType = airtableTsType(field, lock);
|
|
440
542
|
lines.push(` ${field.sdkName}: ${tsType};`);
|
|
441
543
|
}
|
|
442
544
|
lines.push("};");
|
package/dist/esm/dev/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { watch } from
|
|
2
|
-
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, } from
|
|
3
|
-
import { join } from
|
|
4
|
-
import { runSync } from
|
|
5
|
-
import { generateDbTs, generateApiTs, generateUserTs, generateAuthWrapperTs, generateBackendWrapperTs, generateAirtableTs, } from
|
|
1
|
+
import { watch } from "fs";
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { runSync } from "../sync/index.js";
|
|
5
|
+
import { generateDbTs, generateApiTs, generateUserTs, generateAuthWrapperTs, generateBackendWrapperTs, generateAirtableTs, } from "../sync/lib.js";
|
|
6
6
|
const debounceTimers = new Map();
|
|
7
7
|
function debounce(key, fn, ms) {
|
|
8
8
|
const existing = debounceTimers.get(key);
|
|
@@ -11,18 +11,18 @@ function debounce(key, fn, ms) {
|
|
|
11
11
|
debounceTimers.set(key, setTimeout(fn, ms));
|
|
12
12
|
}
|
|
13
13
|
function findAppDirs() {
|
|
14
|
-
const appsDir =
|
|
14
|
+
const appsDir = "apps";
|
|
15
15
|
if (!existsSync(appsDir))
|
|
16
16
|
return [];
|
|
17
17
|
return readdirSync(appsDir, { withFileTypes: true })
|
|
18
|
-
.filter(d => d.isDirectory())
|
|
19
|
-
.map(d => d.name);
|
|
18
|
+
.filter((d) => d.isDirectory())
|
|
19
|
+
.map((d) => d.name);
|
|
20
20
|
}
|
|
21
21
|
function getFlowId(appDir) {
|
|
22
22
|
try {
|
|
23
|
-
const configPath = join(
|
|
23
|
+
const configPath = join("apps", appDir, "zite.config.json");
|
|
24
24
|
if (existsSync(configPath)) {
|
|
25
|
-
const config = JSON.parse(readFileSync(configPath,
|
|
25
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
26
26
|
return config.id;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -30,32 +30,32 @@ function getFlowId(appDir) {
|
|
|
30
30
|
return undefined;
|
|
31
31
|
}
|
|
32
32
|
function regenerateAppTypedWrappers(appDir) {
|
|
33
|
-
const outDir = join(
|
|
33
|
+
const outDir = join("apps", appDir, ".zite");
|
|
34
34
|
mkdirSync(outDir, { recursive: true });
|
|
35
|
-
writeFileSync(join(outDir,
|
|
36
|
-
writeFileSync(join(outDir,
|
|
37
|
-
writeFileSync(join(outDir,
|
|
35
|
+
writeFileSync(join(outDir, "user.ts"), generateUserTs());
|
|
36
|
+
writeFileSync(join(outDir, "auth.ts"), generateAuthWrapperTs());
|
|
37
|
+
writeFileSync(join(outDir, "backend.ts"), generateBackendWrapperTs());
|
|
38
38
|
}
|
|
39
39
|
function regenerateAppApiTs(appDir) {
|
|
40
|
-
const apiDir = join(
|
|
40
|
+
const apiDir = join("apps", appDir, "src", "api");
|
|
41
41
|
if (!existsSync(apiDir))
|
|
42
42
|
return;
|
|
43
|
-
const endpointFiles = readdirSync(apiDir).filter((f) => f.endsWith(
|
|
43
|
+
const endpointFiles = readdirSync(apiDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
44
44
|
const content = generateApiTs(endpointFiles);
|
|
45
45
|
if (content) {
|
|
46
|
-
const outDir = join(
|
|
46
|
+
const outDir = join("apps", appDir, ".zite");
|
|
47
47
|
mkdirSync(outDir, { recursive: true });
|
|
48
|
-
writeFileSync(join(outDir,
|
|
48
|
+
writeFileSync(join(outDir, "api.ts"), content);
|
|
49
49
|
console.log(`Updated apps/${appDir}/.zite/api.ts`);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
function regenerateAppAirtableSdk(appDir) {
|
|
53
|
-
const lockPath = join(
|
|
53
|
+
const lockPath = join("apps", appDir, "zite.lock");
|
|
54
54
|
console.log(`[airtable-sdk] Checking ${lockPath} exists: ${existsSync(lockPath)}`);
|
|
55
55
|
if (!existsSync(lockPath))
|
|
56
56
|
return;
|
|
57
57
|
try {
|
|
58
|
-
const lockContent = JSON.parse(readFileSync(lockPath,
|
|
58
|
+
const lockContent = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
59
59
|
const integrations = lockContent.integrations ?? {};
|
|
60
60
|
console.log(`[airtable-sdk] Found ${Object.keys(integrations).length} integrations in lock:`, Object.keys(integrations));
|
|
61
61
|
for (const [integrationId, integration] of Object.entries(integrations)) {
|
|
@@ -71,9 +71,9 @@ function regenerateAppAirtableSdk(appDir) {
|
|
|
71
71
|
};
|
|
72
72
|
const content = generateAirtableTs(airtableLock);
|
|
73
73
|
if (content) {
|
|
74
|
-
const outDir = join(
|
|
74
|
+
const outDir = join("apps", appDir, ".zite", "integrations");
|
|
75
75
|
mkdirSync(outDir, { recursive: true });
|
|
76
|
-
writeFileSync(join(outDir,
|
|
76
|
+
writeFileSync(join(outDir, "airtable.ts"), content);
|
|
77
77
|
console.log(`Updated apps/${appDir}/.zite/integrations/airtable.ts`);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
@@ -92,16 +92,16 @@ export async function runGenerate() {
|
|
|
92
92
|
// metadata from the backend API).
|
|
93
93
|
// 1. Generate root .zite/db.ts from zite.schema.json
|
|
94
94
|
try {
|
|
95
|
-
if (existsSync(
|
|
96
|
-
const schema = JSON.parse(readFileSync(
|
|
95
|
+
if (existsSync("zite.schema.json")) {
|
|
96
|
+
const schema = JSON.parse(readFileSync("zite.schema.json", "utf-8"));
|
|
97
97
|
const dbTs = generateDbTs(schema);
|
|
98
|
-
mkdirSync(
|
|
99
|
-
writeFileSync(join(
|
|
100
|
-
console.log(
|
|
98
|
+
mkdirSync(".zite", { recursive: true });
|
|
99
|
+
writeFileSync(join(".zite", "db.ts"), dbTs);
|
|
100
|
+
console.log("Wrote .zite/db.ts");
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
catch (err) {
|
|
104
|
-
console.warn(
|
|
104
|
+
console.warn("DB SDK generation failed:", err instanceof Error ? err.message : err);
|
|
105
105
|
}
|
|
106
106
|
// 2. Find all apps and regenerate their .zite/ files
|
|
107
107
|
const appDirs = findAppDirs();
|
|
@@ -110,20 +110,20 @@ export async function runGenerate() {
|
|
|
110
110
|
regenerateAppTypedWrappers(app);
|
|
111
111
|
regenerateAppAirtableSdk(app);
|
|
112
112
|
}
|
|
113
|
-
console.log(
|
|
113
|
+
console.log("Done!");
|
|
114
114
|
}
|
|
115
115
|
export async function runDev() {
|
|
116
|
-
const env = process.env.ZITE_ENV ??
|
|
116
|
+
const env = process.env.ZITE_ENV ?? "production";
|
|
117
117
|
console.log(`zitejs dev — environment: ${env}`);
|
|
118
|
-
console.log(
|
|
118
|
+
console.log("");
|
|
119
119
|
// 1. Run initial sync (generates root .zite/db.ts)
|
|
120
120
|
try {
|
|
121
121
|
await runSync();
|
|
122
122
|
}
|
|
123
123
|
catch (err) {
|
|
124
|
-
console.warn(
|
|
124
|
+
console.warn("Initial sync failed (continuing with watcher):", err instanceof Error ? err.message : err);
|
|
125
125
|
}
|
|
126
|
-
console.log(
|
|
126
|
+
console.log("");
|
|
127
127
|
// 2. Find all apps and regenerate their .zite/ files
|
|
128
128
|
const appDirs = findAppDirs();
|
|
129
129
|
for (const app of appDirs) {
|
|
@@ -133,7 +133,7 @@ export async function runDev() {
|
|
|
133
133
|
// 3. Watch each app's src/api/ for endpoint changes
|
|
134
134
|
let watchingAny = false;
|
|
135
135
|
for (const app of appDirs) {
|
|
136
|
-
const apiDir = join(
|
|
136
|
+
const apiDir = join("apps", app, "src", "api");
|
|
137
137
|
if (!existsSync(apiDir))
|
|
138
138
|
continue;
|
|
139
139
|
watchingAny = true;
|
|
@@ -141,7 +141,7 @@ export async function runDev() {
|
|
|
141
141
|
watch(apiDir, { recursive: true }, (_event, filename) => {
|
|
142
142
|
if (!filename)
|
|
143
143
|
return;
|
|
144
|
-
if (!filename.endsWith(
|
|
144
|
+
if (!filename.endsWith(".ts") && !filename.endsWith(".js"))
|
|
145
145
|
return;
|
|
146
146
|
debounce(app, () => {
|
|
147
147
|
console.log(`Endpoint changed in ${app}: ${filename}`);
|
|
@@ -150,24 +150,24 @@ export async function runDev() {
|
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
if (!watchingAny) {
|
|
153
|
-
console.log(
|
|
153
|
+
console.log("No apps with src/api/ found.");
|
|
154
154
|
}
|
|
155
155
|
// 4. Watch zite.schema.json for schema drift
|
|
156
|
-
if (existsSync(
|
|
157
|
-
console.log(
|
|
158
|
-
watch(
|
|
159
|
-
debounce(
|
|
160
|
-
console.log(
|
|
156
|
+
if (existsSync("zite.schema.json")) {
|
|
157
|
+
console.log("Watching zite.schema.json for schema changes...");
|
|
158
|
+
watch("zite.schema.json", () => {
|
|
159
|
+
debounce("schema", async () => {
|
|
160
|
+
console.log("Schema changed — re-running sync...");
|
|
161
161
|
try {
|
|
162
162
|
await runSync();
|
|
163
163
|
}
|
|
164
164
|
catch (err) {
|
|
165
|
-
console.error(
|
|
165
|
+
console.error("Re-sync failed:", err instanceof Error ? err.message : err);
|
|
166
166
|
}
|
|
167
167
|
}, 500);
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
|
-
console.log(
|
|
171
|
-
console.log(
|
|
170
|
+
console.log("");
|
|
171
|
+
console.log("Ready. Watching for changes... (Ctrl+C to stop)");
|
|
172
172
|
await new Promise(() => { });
|
|
173
173
|
}
|
package/dist/esm/sync/lib.d.ts
CHANGED
|
@@ -40,11 +40,13 @@ export type AirtableLockField = {
|
|
|
40
40
|
id: string;
|
|
41
41
|
sdkName: string;
|
|
42
42
|
type: string;
|
|
43
|
-
|
|
43
|
+
name: string;
|
|
44
|
+
config?: Record<string, unknown>;
|
|
44
45
|
};
|
|
45
46
|
export type AirtableLockTable = {
|
|
46
47
|
id: string;
|
|
47
48
|
sdkName: string;
|
|
49
|
+
primaryFieldId: string;
|
|
48
50
|
fields: AirtableLockField[];
|
|
49
51
|
};
|
|
50
52
|
export type AirtableLock = {
|
package/dist/esm/sync/lib.js
CHANGED
|
@@ -383,11 +383,12 @@ const AIRTABLE_FIELD_TYPE_MAP = {
|
|
|
383
383
|
externalSyncSource: "unknown",
|
|
384
384
|
aiText: "string",
|
|
385
385
|
};
|
|
386
|
-
function airtableTsType(field) {
|
|
386
|
+
function airtableTsType(field, lock, depth) {
|
|
387
|
+
const choices = field.config?.choices;
|
|
387
388
|
if ((field.type === "singleSelect" || field.type === "multipleSelects") &&
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const literals =
|
|
389
|
+
choices &&
|
|
390
|
+
choices.length > 0) {
|
|
391
|
+
const literals = choices
|
|
391
392
|
.slice(0, MAX_SELECT_OPTIONS)
|
|
392
393
|
.map((o) => `"${o.replace(/"/g, '\\"')}"`)
|
|
393
394
|
.join(" | ");
|
|
@@ -396,15 +397,106 @@ function airtableTsType(field) {
|
|
|
396
397
|
return `(${union})[]`;
|
|
397
398
|
return union;
|
|
398
399
|
}
|
|
400
|
+
if (field.type === "multipleLookupValues" && lock && (depth ?? 0) < 5) {
|
|
401
|
+
const linkFieldId = field.config?.recordLinkFieldId;
|
|
402
|
+
const targetFieldId = field.config?.fieldIdInLinkedTable;
|
|
403
|
+
if (linkFieldId && targetFieldId) {
|
|
404
|
+
const table = lock.tables.find((t) => t.fields.some((f) => f.id === linkFieldId));
|
|
405
|
+
const linkField = table?.fields.find((f) => f.id === linkFieldId);
|
|
406
|
+
const linkedTableId = linkField?.config?.linkedTableId;
|
|
407
|
+
if (linkedTableId) {
|
|
408
|
+
const linkedTable = lock.tables.find((t) => t.id === linkedTableId);
|
|
409
|
+
const targetField = linkedTable?.fields.find((f) => f.id === targetFieldId);
|
|
410
|
+
if (targetField) {
|
|
411
|
+
const resolved = airtableTsType(targetField, lock, (depth ?? 0) + 1);
|
|
412
|
+
return `${resolved}[] | undefined`;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
399
417
|
return AIRTABLE_FIELD_TYPE_MAP[field.type] ?? "unknown";
|
|
400
418
|
}
|
|
419
|
+
const READ_ONLY_AIRTABLE_FIELDS = new Set([
|
|
420
|
+
"autoNumber",
|
|
421
|
+
"count",
|
|
422
|
+
"formula",
|
|
423
|
+
"rollup",
|
|
424
|
+
"multipleLookupValues",
|
|
425
|
+
"createdTime",
|
|
426
|
+
"lastModifiedTime",
|
|
427
|
+
"createdBy",
|
|
428
|
+
"lastModifiedBy",
|
|
429
|
+
"button",
|
|
430
|
+
"externalSyncSource",
|
|
431
|
+
"aiText",
|
|
432
|
+
]);
|
|
433
|
+
function airtableFieldJsdoc(field, table) {
|
|
434
|
+
const parts = [];
|
|
435
|
+
if (table.primaryFieldId === field.id)
|
|
436
|
+
parts.push("Primary field");
|
|
437
|
+
if (READ_ONLY_AIRTABLE_FIELDS.has(field.type))
|
|
438
|
+
parts.push("Read-only; do not write");
|
|
439
|
+
if (field.type === "multipleRecordLinks") {
|
|
440
|
+
const linkedTableId = field.config?.linkedTableId;
|
|
441
|
+
if (linkedTableId)
|
|
442
|
+
parts.push(`Links to table ${linkedTableId}`);
|
|
443
|
+
if (field.config?.prefersSingleRecordLink)
|
|
444
|
+
parts.push("Single record only");
|
|
445
|
+
}
|
|
446
|
+
if (field.type === "multipleLookupValues")
|
|
447
|
+
parts.push("Lookup: value is an array — index or aggregate when displaying");
|
|
448
|
+
if (field.type === "percent")
|
|
449
|
+
parts.push("Raw decimal — multiply by 100 for display percentage");
|
|
450
|
+
parts.push(`"${field.name}"`);
|
|
451
|
+
if (field.type === "currency") {
|
|
452
|
+
const symbol = field.config?.symbol;
|
|
453
|
+
const precision = field.config?.precision;
|
|
454
|
+
if (symbol)
|
|
455
|
+
parts.push(`Display as currency with "${symbol}", ${precision ?? 2} decimals`);
|
|
456
|
+
}
|
|
457
|
+
if (field.type === "date") {
|
|
458
|
+
const fmt = field.config?.dateFormat;
|
|
459
|
+
if (fmt)
|
|
460
|
+
parts.push(`Date-only (ISO string), display as "${fmt}"`);
|
|
461
|
+
}
|
|
462
|
+
if (field.type === "dateTime") {
|
|
463
|
+
const dateFmt = field.config?.dateFormat;
|
|
464
|
+
const timeFmt = field.config?.timeFormat;
|
|
465
|
+
const tz = field.config?.timeZone;
|
|
466
|
+
const fmtParts = ["Date+time (ISO string)"];
|
|
467
|
+
if (dateFmt)
|
|
468
|
+
fmtParts.push(`date: ${dateFmt}`);
|
|
469
|
+
if (timeFmt)
|
|
470
|
+
fmtParts.push(`time: ${timeFmt}`);
|
|
471
|
+
if (tz)
|
|
472
|
+
fmtParts.push(`tz: ${tz}`);
|
|
473
|
+
parts.push(fmtParts.join(", "));
|
|
474
|
+
}
|
|
475
|
+
if (field.type === "rating") {
|
|
476
|
+
const max = field.config?.max;
|
|
477
|
+
if (max)
|
|
478
|
+
parts.push(`Max: ${max}`);
|
|
479
|
+
}
|
|
480
|
+
if (field.type === "duration") {
|
|
481
|
+
const fmt = field.config?.format;
|
|
482
|
+
if (fmt)
|
|
483
|
+
parts.push(`Value in seconds, display as "${fmt}"`);
|
|
484
|
+
}
|
|
485
|
+
if (field.type === "number" || field.type === "percent") {
|
|
486
|
+
const precision = field.config?.precision;
|
|
487
|
+
if (precision != null)
|
|
488
|
+
parts.push(`${precision} decimal places`);
|
|
489
|
+
}
|
|
490
|
+
if (parts.length <= 1)
|
|
491
|
+
return undefined;
|
|
492
|
+
return parts.join(". ");
|
|
493
|
+
}
|
|
401
494
|
export function generateAirtableTs(lock) {
|
|
402
495
|
if (lock.tables.length === 0)
|
|
403
496
|
return null;
|
|
404
497
|
const lines = [
|
|
405
498
|
"// Auto-generated by zitejs generate from zite.lock. Do not edit manually.",
|
|
406
499
|
"// Airtable SDK — uses createAirtableClient from zitejs/runtime.",
|
|
407
|
-
"// The airtable package is externalized (not bundled per-endpoint).",
|
|
408
500
|
"//",
|
|
409
501
|
"// Each exported table client has these methods:",
|
|
410
502
|
"// findAll(options?: { offset?: string; limit?: number; filters?: unknown })",
|
|
@@ -414,6 +506,12 @@ export function generateAirtableTs(lock) {
|
|
|
414
506
|
"// bulkCreate(records: Partial<T>[]) => Promise<T[]>",
|
|
415
507
|
"// update(id: string, data: { record: Partial<T> }) => Promise<T>",
|
|
416
508
|
"// delete(id: string) => Promise<{ deleted: true }>",
|
|
509
|
+
"//",
|
|
510
|
+
"// Usage tips:",
|
|
511
|
+
"// - Airtable has a strict rate limit of 5 requests/second per base",
|
|
512
|
+
"// - Use bulkCreate() instead of calling create() in a loop",
|
|
513
|
+
"// - findAll() offsets are opaque strings from previous calls, NOT numbers",
|
|
514
|
+
"// - Always destructure record properties individually in create/update calls",
|
|
417
515
|
"",
|
|
418
516
|
"import { createAirtableClient } from 'zitejs/runtime';",
|
|
419
517
|
"",
|
|
@@ -425,7 +523,11 @@ export function generateAirtableTs(lock) {
|
|
|
425
523
|
for (const field of table.fields) {
|
|
426
524
|
if (field.sdkName === "id")
|
|
427
525
|
continue;
|
|
428
|
-
const
|
|
526
|
+
const jsdoc = airtableFieldJsdoc(field, table);
|
|
527
|
+
if (jsdoc) {
|
|
528
|
+
lines.push(` /** ${jsdoc} */`);
|
|
529
|
+
}
|
|
530
|
+
const tsType = airtableTsType(field, lock);
|
|
429
531
|
lines.push(` ${field.sdkName}: ${tsType};`);
|
|
430
532
|
}
|
|
431
533
|
lines.push("};");
|