zitejs 0.9.40 → 0.9.42

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.
@@ -12,9 +12,11 @@ const DB_ENVIRONMENTS = {
12
12
  local: "http://localhost:2507/api/v1",
13
13
  };
14
14
  const env = process.env.ZITE_ENV ?? "production";
15
- const BASE_URL = process.env.ZITE_DB_URL ?? DB_ENVIRONMENTS[env] ?? DB_ENVIRONMENTS.production;
15
+ const BASE_URL = process.env.ZITE_DB_URL ??
16
+ DB_ENVIRONMENTS[env] ??
17
+ DB_ENVIRONMENTS.production;
16
18
  const TOKEN = process.env.ZITE_DB_TOKEN ?? "";
17
- async function fetchBaseMetadata(baseId) {
19
+ async function fetchDatabase(baseId) {
18
20
  const url = `${BASE_URL}/bases/${encodeURIComponent(baseId)}`;
19
21
  const res = await fetch(url, {
20
22
  headers: { Authorization: `Bearer ${TOKEN}` },
@@ -35,6 +37,15 @@ function resolveBaseId() {
35
37
  return undefined;
36
38
  }
37
39
  }
40
+ function readExistingSchema() {
41
+ try {
42
+ if ((0, fs_1.existsSync)("zite.schema.json")) {
43
+ return JSON.parse((0, fs_1.readFileSync)("zite.schema.json", "utf-8"));
44
+ }
45
+ }
46
+ catch { }
47
+ return undefined;
48
+ }
38
49
  async function runSync() {
39
50
  if (!TOKEN) {
40
51
  throw new Error("ZITE_DB_TOKEN is required.");
@@ -44,12 +55,13 @@ async function runSync() {
44
55
  throw new Error("Could not determine base ID.");
45
56
  }
46
57
  console.log(`Syncing database ${baseId}...`);
47
- const base = await fetchBaseMetadata(baseId);
48
- console.log(`Found ${base.tables?.length ?? 0} tables`);
49
- const schema = (0, lib_js_1.generateSchema)(base);
58
+ const database = await fetchDatabase(baseId);
59
+ console.log(`Found ${database.tables?.length ?? 0} tables`);
60
+ const existingSchema = readExistingSchema();
61
+ const schema = (0, lib_js_1.generateSchema)(database, existingSchema);
50
62
  (0, fs_1.writeFileSync)("zite.schema.json", JSON.stringify(schema, null, 2));
51
63
  console.log("Wrote zite.schema.json");
52
- const dbTs = (0, lib_js_1.generateDbTs)(base, schema);
64
+ const dbTs = (0, lib_js_1.generateDbTs)(schema);
53
65
  const dotZite = ".zite";
54
66
  (0, fs_1.mkdirSync)(dotZite, { recursive: true });
55
67
  (0, fs_1.writeFileSync)((0, path_1.join)(dotZite, "db.ts"), dbTs);
@@ -11,6 +11,7 @@ exports.generateBackendWrapperTs = generateBackendWrapperTs;
11
11
  const FIELD_TYPE_MAP = {
12
12
  single_line_text: "string",
13
13
  long_text: "string",
14
+ rich_text: "string",
14
15
  email: "string",
15
16
  url: "string",
16
17
  phone_number: "string",
@@ -29,6 +30,25 @@ const FIELD_TYPE_MAP = {
29
30
  lookup: "unknown",
30
31
  autonumber: "number",
31
32
  source: "string",
33
+ formula: "unknown",
34
+ created_at: "string",
35
+ updated_at: "string",
36
+ };
37
+ const MAX_SELECT_OPTIONS = 100;
38
+ const NUMBER_FORMAT_EXAMPLES = {
39
+ local: "1,000,000.50",
40
+ comma_period: "1,000,000.50",
41
+ period_comma: "1.000.000,50",
42
+ space_comma: "1 000 000,50",
43
+ space_period: "1 000 000.50",
44
+ no_separator: "1000000.50",
45
+ };
46
+ const DURATION_FORMAT_EXAMPLES = {
47
+ "h:mm": "1:23",
48
+ "h:mm:ss": "1:23:03",
49
+ "h:mm:ss.s": "1:23:03.0",
50
+ "h:mm:ss.ss": "1:23:03.00",
51
+ "h:mm:ss.sss": "1:23:03.000",
32
52
  };
33
53
  function toPascalCase(name) {
34
54
  return name
@@ -39,33 +59,157 @@ function toCamelCase(name) {
39
59
  const pascal = toPascalCase(name);
40
60
  return pascal.charAt(0).toLowerCase() + pascal.slice(1);
41
61
  }
42
- function tsTypeForField(field) {
43
- if (FIELD_TYPE_MAP[field.type])
44
- return FIELD_TYPE_MAP[field.type];
45
- const lowerName = field.name?.toLowerCase() ?? "";
46
- if (lowerName === "createdat" ||
47
- lowerName === "created_at" ||
48
- lowerName === "updatedat" ||
49
- lowerName === "updated_at") {
50
- return "string";
62
+ function tsTypeForSchemaField(def) {
63
+ if (def.type === "single_select" || def.type === "multiple_select") {
64
+ const options = def.template
65
+ ?.options;
66
+ if (options && options.length > 0) {
67
+ const literals = options
68
+ .slice(0, MAX_SELECT_OPTIONS)
69
+ .map((o) => `"${o.label.replace(/"/g, '\\"')}"`)
70
+ .join(" | ");
71
+ const union = `${literals} | string`;
72
+ if (def.type === "multiple_select")
73
+ return `(${union})[]`;
74
+ return union;
75
+ }
51
76
  }
77
+ if (FIELD_TYPE_MAP[def.type])
78
+ return FIELD_TYPE_MAP[def.type];
52
79
  return "string";
53
80
  }
54
- function generateSchema(base) {
55
- const schema = { tables: {} };
56
- for (const table of base.tables ?? []) {
57
- const tableName = toCamelCase(table.name);
58
- const fields = {};
59
- for (const field of table.fields ?? []) {
60
- fields[toCamelCase(field.name)] = { id: field.id };
81
+ function fieldJsdoc(schemaField, table) {
82
+ const def = schemaField.definition;
83
+ const parts = [];
84
+ if (table.primaryFieldId === schemaField.id)
85
+ parts.push("Primary field");
86
+ if (def.type === "created_at" ||
87
+ def.type === "updated_at" ||
88
+ def.type === "lookup" ||
89
+ def.type === "formula" ||
90
+ def.type === "autonumber") {
91
+ parts.push("Read-only");
92
+ }
93
+ if (def.type === "linked_record") {
94
+ const tpl = def.template;
95
+ if (tpl.tableId)
96
+ parts.push(`Links to table ${tpl.tableId}`);
97
+ if (tpl.allowMultiple === false)
98
+ parts.push("Single record only");
99
+ }
100
+ parts.push(`"${def.name}"`);
101
+ if (def.type === "date") {
102
+ const tpl = def.template;
103
+ if (tpl.dateFormat)
104
+ parts.push(`Date-only field (YYYY-MM-DD string), display as "${tpl.dateFormat}" format`);
105
+ }
106
+ if (def.type === "datetime") {
107
+ const tpl = def.template;
108
+ const timeParts = [
109
+ "Date+time field (ISO 8601 timestamp)",
110
+ ];
111
+ if (tpl.dateFormat)
112
+ timeParts.push(`date: ${tpl.dateFormat}`);
113
+ if (tpl.timeFormat)
114
+ timeParts.push(`time: ${tpl.timeFormat}`);
115
+ if (tpl.timezone)
116
+ timeParts.push(`tz: ${tpl.timezone}`);
117
+ if (tpl.displayTimeZone)
118
+ timeParts.push("timezone displayed to user");
119
+ parts.push(timeParts.join(", "));
120
+ }
121
+ if (def.type === "currency") {
122
+ const tpl = def.template;
123
+ if (tpl.currencySymbol) {
124
+ const formatHint = NUMBER_FORMAT_EXAMPLES[tpl.numberFormat ?? "local"] ?? "";
125
+ parts.push(`Value is a raw number; display as currency with symbol "${tpl.currencySymbol}", ${tpl.decimalPlaces ?? 2} decimal places, format: ${tpl.numberFormat ?? "local"} (e.g. ${formatHint})`);
126
+ }
127
+ }
128
+ if (def.type === "number") {
129
+ const tpl = def.template;
130
+ const formatHint = NUMBER_FORMAT_EXAMPLES[tpl.numberFormat ?? "local"] ?? "";
131
+ parts.push(`${tpl.decimalPlaces ?? 0} decimal places, format: ${tpl.numberFormat ?? "local"} (e.g. ${formatHint})`);
132
+ }
133
+ if (def.type === "percent") {
134
+ const tpl = def.template;
135
+ if (tpl.decimalPlaces !== undefined)
136
+ parts.push(`${tpl.decimalPlaces} decimal places`);
137
+ }
138
+ if (def.type === "duration") {
139
+ const tpl = def.template;
140
+ if (tpl.format) {
141
+ const example = DURATION_FORMAT_EXAMPLES[tpl.format] ?? "";
142
+ parts.push(`Value is in seconds; display in "${tpl.format}" format (${example})`);
143
+ }
144
+ }
145
+ if (def.type === "rating") {
146
+ const tpl = def.template;
147
+ if (tpl.maxRating)
148
+ parts.push(`Max rating: ${tpl.maxRating}`);
149
+ }
150
+ if (def.type === "created_at") {
151
+ parts.push("Auto-set when record was created (ISO 8601)");
152
+ }
153
+ if (def.type === "updated_at") {
154
+ parts.push("Auto-set when record was last modified (ISO 8601)");
155
+ }
156
+ if ((def.type === "single_select" || def.type === "multiple_select") &&
157
+ def.template?.options) {
158
+ const count = (def.template.options ?? []).length;
159
+ if (count > MAX_SELECT_OPTIONS) {
160
+ parts.push(`${count - MAX_SELECT_OPTIONS} more options omitted`);
161
+ }
162
+ }
163
+ if (parts.length <= 1)
164
+ return undefined;
165
+ return parts.join(". ");
166
+ }
167
+ /**
168
+ * Build a ZiteSchema from a Database API response.
169
+ *
170
+ * If `existingSchema` is provided, locked SDK names are preserved for
171
+ * tables/fields that already exist (matched by ID). New tables/fields
172
+ * get fresh camelCase SDK names. This is how `zitejs sync` preserves
173
+ * name stability across schema changes.
174
+ */
175
+ function generateSchema(database, existingSchema) {
176
+ const existingTableById = new Map();
177
+ for (const t of existingSchema?.tables ?? []) {
178
+ existingTableById.set(t.id, t);
179
+ }
180
+ const tables = [];
181
+ for (const table of database.tables) {
182
+ const existingTable = existingTableById.get(table.id);
183
+ const existingFieldById = new Map();
184
+ for (const f of existingTable?.fields ?? []) {
185
+ existingFieldById.set(f.id, f);
186
+ }
187
+ const fields = [];
188
+ for (const field of table.fields) {
189
+ const existing = existingFieldById.get(field.id);
190
+ const { id: _id, order: _order, ...definition } = field;
191
+ fields.push({
192
+ id: field.id,
193
+ sdkName: existing?.sdkName ?? toCamelCase(field.name),
194
+ definition,
195
+ });
61
196
  }
62
- schema.tables[tableName] = { id: table.id, fields };
197
+ tables.push({
198
+ id: table.id,
199
+ sdkName: existingTable?.sdkName ?? toCamelCase(table.name),
200
+ primaryFieldId: table.primaryFieldId,
201
+ fields,
202
+ });
63
203
  }
64
- return schema;
204
+ return { tables };
65
205
  }
66
- function generateDbTs(base, schema) {
206
+ /**
207
+ * Generate .zite/db.ts purely from ZiteSchema.
208
+ * Pure function — no external data needed beyond what's in the schema file.
209
+ */
210
+ function generateDbTs(schema) {
67
211
  const lines = [];
68
- lines.push("// Auto-generated by zitejs sync. Do not edit manually.");
212
+ lines.push("// Auto-generated by zitejs generate. Do not edit manually.");
69
213
  lines.push("//");
70
214
  lines.push("// Usage in endpoint files (src/api/*.ts):");
71
215
  lines.push('// import { zite } from "zitejs/db";');
@@ -97,26 +241,28 @@ function generateDbTs(base, schema) {
97
241
  lines.push('// - Load the "zite:sql" skill for full SQL reference (formulas, lookups, etc.)');
98
242
  lines.push("import { createTableClient, createSqlClient } from 'zitejs/runtime';");
99
243
  lines.push("");
100
- for (const table of base.tables ?? []) {
101
- const className = toPascalCase(table.name);
244
+ for (const table of schema.tables) {
245
+ const className = toPascalCase(table.sdkName);
102
246
  const recordType = `${className}RecordType`;
103
247
  lines.push(`export type ${recordType} = {`);
104
248
  lines.push(" id: string;");
105
- for (const field of table.fields ?? []) {
106
- const fieldName = toCamelCase(field.name);
107
- if (fieldName === "id")
249
+ for (const field of table.fields) {
250
+ if (field.sdkName === "id")
108
251
  continue;
109
- const tsType = tsTypeForField(field);
110
- lines.push(` ${fieldName}: ${tsType};`);
252
+ const tsType = tsTypeForSchemaField(field.definition);
253
+ const jsdoc = fieldJsdoc(field, table);
254
+ if (jsdoc) {
255
+ lines.push(` /** ${jsdoc} */`);
256
+ }
257
+ lines.push(` ${field.sdkName}: ${tsType};`);
111
258
  }
112
259
  lines.push("};");
113
260
  lines.push("");
114
261
  }
115
262
  lines.push("export const zite = {");
116
- for (const table of base.tables ?? []) {
117
- const className = toPascalCase(table.name);
118
- const propName = toCamelCase(table.name);
119
- lines.push(` ${propName}: createTableClient<${className}RecordType>('${className}'),`);
263
+ for (const table of schema.tables) {
264
+ const className = toPascalCase(table.sdkName);
265
+ lines.push(` ${table.sdkName}: createTableClient<${className}RecordType>('${className}'),`);
120
266
  }
121
267
  lines.push(` sql: createSqlClient(),`);
122
268
  lines.push("};");
@@ -144,14 +290,12 @@ function generateApiTs(endpointFiles) {
144
290
  lines.push(`export const ${name} = createCaller(${name}Endpoint, '${name}');`);
145
291
  }
146
292
  lines.push("");
147
- // Inferred input/output types per endpoint (e.g. GetDashboardInputType, GetDashboardOutputType)
148
293
  for (const name of endpointNames) {
149
294
  const pascal = toPascalCase(name);
150
295
  lines.push(`export type ${pascal}InputType = Parameters<typeof ${name}Endpoint.execute>[0]['input'];`);
151
296
  lines.push(`export type ${pascal}OutputType = Awaited<ReturnType<typeof ${name}Endpoint.execute>>;`);
152
297
  }
153
298
  lines.push("");
154
- // Also export as a single api object
155
299
  lines.push("export const api = {");
156
300
  for (const name of endpointNames) {
157
301
  lines.push(` ${name},`);
@@ -160,6 +304,11 @@ function generateApiTs(endpointFiles) {
160
304
  lines.push("");
161
305
  return lines.join("\n");
162
306
  }
307
+ function tsTypeForField(field) {
308
+ if (FIELD_TYPE_MAP[field.type])
309
+ return FIELD_TYPE_MAP[field.type];
310
+ return "string";
311
+ }
163
312
  function generateUserTs(usersTableFields) {
164
313
  const lines = [
165
314
  "// Auto-generated by zitejs sync. Do not edit manually.",