zitejs 0.9.50 → 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 +120 -10
- package/dist/esm/dev/index.js +44 -44
- package/dist/esm/sync/lib.d.ts +3 -1
- package/dist/esm/sync/lib.js +120 -10
- 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
|
@@ -106,9 +106,7 @@ function fieldJsdoc(schemaField, table) {
|
|
|
106
106
|
}
|
|
107
107
|
if (def.type === "datetime") {
|
|
108
108
|
const tpl = def.template;
|
|
109
|
-
const timeParts = [
|
|
110
|
-
"Date+time field (ISO 8601 timestamp)",
|
|
111
|
-
];
|
|
109
|
+
const timeParts = ["Date+time field (ISO 8601 timestamp)"];
|
|
112
110
|
if (tpl.dateFormat)
|
|
113
111
|
timeParts.push(`date: ${tpl.dateFormat}`);
|
|
114
112
|
if (tpl.timeFormat)
|
|
@@ -156,7 +154,8 @@ function fieldJsdoc(schemaField, table) {
|
|
|
156
154
|
}
|
|
157
155
|
if ((def.type === "single_select" || def.type === "multiple_select") &&
|
|
158
156
|
def.template?.options) {
|
|
159
|
-
const count = (def.template.options ?? [])
|
|
157
|
+
const count = (def.template.options ?? [])
|
|
158
|
+
.length;
|
|
160
159
|
if (count > MAX_SELECT_OPTIONS) {
|
|
161
160
|
parts.push(`${count - MAX_SELECT_OPTIONS} more options omitted`);
|
|
162
161
|
}
|
|
@@ -395,11 +394,12 @@ const AIRTABLE_FIELD_TYPE_MAP = {
|
|
|
395
394
|
externalSyncSource: "unknown",
|
|
396
395
|
aiText: "string",
|
|
397
396
|
};
|
|
398
|
-
function airtableTsType(field) {
|
|
397
|
+
function airtableTsType(field, lock, depth) {
|
|
398
|
+
const choices = field.config?.choices;
|
|
399
399
|
if ((field.type === "singleSelect" || field.type === "multipleSelects") &&
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const literals =
|
|
400
|
+
choices &&
|
|
401
|
+
choices.length > 0) {
|
|
402
|
+
const literals = choices
|
|
403
403
|
.slice(0, MAX_SELECT_OPTIONS)
|
|
404
404
|
.map((o) => `"${o.replace(/"/g, '\\"')}"`)
|
|
405
405
|
.join(" | ");
|
|
@@ -408,15 +408,121 @@ function airtableTsType(field) {
|
|
|
408
408
|
return `(${union})[]`;
|
|
409
409
|
return union;
|
|
410
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
|
+
}
|
|
411
428
|
return AIRTABLE_FIELD_TYPE_MAP[field.type] ?? "unknown";
|
|
412
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
|
+
}
|
|
413
505
|
function generateAirtableTs(lock) {
|
|
414
506
|
if (lock.tables.length === 0)
|
|
415
507
|
return null;
|
|
416
508
|
const lines = [
|
|
417
509
|
"// Auto-generated by zitejs generate from zite.lock. Do not edit manually.",
|
|
418
510
|
"// Airtable SDK — uses createAirtableClient from zitejs/runtime.",
|
|
419
|
-
"//
|
|
511
|
+
"//",
|
|
512
|
+
"// Each exported table client has these methods:",
|
|
513
|
+
"// findAll(options?: { offset?: string; limit?: number; filters?: unknown })",
|
|
514
|
+
"// => Promise<{ records: T[]; offset: string | undefined; hasMore: boolean }>",
|
|
515
|
+
"// findOne(params: { id?: string; filters?: unknown }) => Promise<T | undefined>",
|
|
516
|
+
"// create(data: { record: Partial<T> }) => Promise<T>",
|
|
517
|
+
"// bulkCreate(records: Partial<T>[]) => Promise<T[]>",
|
|
518
|
+
"// update(id: string, data: { record: Partial<T> }) => Promise<T>",
|
|
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",
|
|
420
526
|
"",
|
|
421
527
|
"import { createAirtableClient } from 'zitejs/runtime';",
|
|
422
528
|
"",
|
|
@@ -428,7 +534,11 @@ function generateAirtableTs(lock) {
|
|
|
428
534
|
for (const field of table.fields) {
|
|
429
535
|
if (field.sdkName === "id")
|
|
430
536
|
continue;
|
|
431
|
-
const
|
|
537
|
+
const jsdoc = airtableFieldJsdoc(field, table);
|
|
538
|
+
if (jsdoc) {
|
|
539
|
+
lines.push(` /** ${jsdoc} */`);
|
|
540
|
+
}
|
|
541
|
+
const tsType = airtableTsType(field, lock);
|
|
432
542
|
lines.push(` ${field.sdkName}: ${tsType};`);
|
|
433
543
|
}
|
|
434
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
|
@@ -95,9 +95,7 @@ function fieldJsdoc(schemaField, table) {
|
|
|
95
95
|
}
|
|
96
96
|
if (def.type === "datetime") {
|
|
97
97
|
const tpl = def.template;
|
|
98
|
-
const timeParts = [
|
|
99
|
-
"Date+time field (ISO 8601 timestamp)",
|
|
100
|
-
];
|
|
98
|
+
const timeParts = ["Date+time field (ISO 8601 timestamp)"];
|
|
101
99
|
if (tpl.dateFormat)
|
|
102
100
|
timeParts.push(`date: ${tpl.dateFormat}`);
|
|
103
101
|
if (tpl.timeFormat)
|
|
@@ -145,7 +143,8 @@ function fieldJsdoc(schemaField, table) {
|
|
|
145
143
|
}
|
|
146
144
|
if ((def.type === "single_select" || def.type === "multiple_select") &&
|
|
147
145
|
def.template?.options) {
|
|
148
|
-
const count = (def.template.options ?? [])
|
|
146
|
+
const count = (def.template.options ?? [])
|
|
147
|
+
.length;
|
|
149
148
|
if (count > MAX_SELECT_OPTIONS) {
|
|
150
149
|
parts.push(`${count - MAX_SELECT_OPTIONS} more options omitted`);
|
|
151
150
|
}
|
|
@@ -384,11 +383,12 @@ const AIRTABLE_FIELD_TYPE_MAP = {
|
|
|
384
383
|
externalSyncSource: "unknown",
|
|
385
384
|
aiText: "string",
|
|
386
385
|
};
|
|
387
|
-
function airtableTsType(field) {
|
|
386
|
+
function airtableTsType(field, lock, depth) {
|
|
387
|
+
const choices = field.config?.choices;
|
|
388
388
|
if ((field.type === "singleSelect" || field.type === "multipleSelects") &&
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const literals =
|
|
389
|
+
choices &&
|
|
390
|
+
choices.length > 0) {
|
|
391
|
+
const literals = choices
|
|
392
392
|
.slice(0, MAX_SELECT_OPTIONS)
|
|
393
393
|
.map((o) => `"${o.replace(/"/g, '\\"')}"`)
|
|
394
394
|
.join(" | ");
|
|
@@ -397,15 +397,121 @@ function airtableTsType(field) {
|
|
|
397
397
|
return `(${union})[]`;
|
|
398
398
|
return union;
|
|
399
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
|
+
}
|
|
400
417
|
return AIRTABLE_FIELD_TYPE_MAP[field.type] ?? "unknown";
|
|
401
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
|
+
}
|
|
402
494
|
export function generateAirtableTs(lock) {
|
|
403
495
|
if (lock.tables.length === 0)
|
|
404
496
|
return null;
|
|
405
497
|
const lines = [
|
|
406
498
|
"// Auto-generated by zitejs generate from zite.lock. Do not edit manually.",
|
|
407
499
|
"// Airtable SDK — uses createAirtableClient from zitejs/runtime.",
|
|
408
|
-
"//
|
|
500
|
+
"//",
|
|
501
|
+
"// Each exported table client has these methods:",
|
|
502
|
+
"// findAll(options?: { offset?: string; limit?: number; filters?: unknown })",
|
|
503
|
+
"// => Promise<{ records: T[]; offset: string | undefined; hasMore: boolean }>",
|
|
504
|
+
"// findOne(params: { id?: string; filters?: unknown }) => Promise<T | undefined>",
|
|
505
|
+
"// create(data: { record: Partial<T> }) => Promise<T>",
|
|
506
|
+
"// bulkCreate(records: Partial<T>[]) => Promise<T[]>",
|
|
507
|
+
"// update(id: string, data: { record: Partial<T> }) => Promise<T>",
|
|
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",
|
|
409
515
|
"",
|
|
410
516
|
"import { createAirtableClient } from 'zitejs/runtime';",
|
|
411
517
|
"",
|
|
@@ -417,7 +523,11 @@ export function generateAirtableTs(lock) {
|
|
|
417
523
|
for (const field of table.fields) {
|
|
418
524
|
if (field.sdkName === "id")
|
|
419
525
|
continue;
|
|
420
|
-
const
|
|
526
|
+
const jsdoc = airtableFieldJsdoc(field, table);
|
|
527
|
+
if (jsdoc) {
|
|
528
|
+
lines.push(` /** ${jsdoc} */`);
|
|
529
|
+
}
|
|
530
|
+
const tsType = airtableTsType(field, lock);
|
|
421
531
|
lines.push(` ${field.sdkName}: ${tsType};`);
|
|
422
532
|
}
|
|
423
533
|
lines.push("};");
|