zitejs 0.9.111 → 0.9.112
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/sync/lib.d.ts +2 -0
- package/dist/cjs/sync/lib.js +49 -11
- package/dist/cjs/sync/lib.test.js +50 -0
- package/dist/cjs/sync/sdkNames.test.js +28 -0
- package/dist/esm/sync/lib.d.ts +2 -0
- package/dist/esm/sync/lib.js +49 -11
- package/dist/esm/sync/lib.test.js +50 -0
- package/dist/esm/sync/sdkNames.test.js +28 -0
- package/package.json +1 -1
package/dist/cjs/sync/lib.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type ZiteSchemaField = {
|
|
|
9
9
|
export type ZiteSchemaTable = {
|
|
10
10
|
id: string;
|
|
11
11
|
sdkName: string;
|
|
12
|
+
name?: string;
|
|
12
13
|
primaryFieldId?: string;
|
|
13
14
|
fields: ZiteSchemaField[];
|
|
14
15
|
};
|
|
@@ -45,6 +46,7 @@ export type AirtableLockField = {
|
|
|
45
46
|
export type AirtableLockTable = {
|
|
46
47
|
id: string;
|
|
47
48
|
sdkName: string;
|
|
49
|
+
name?: string;
|
|
48
50
|
primaryFieldId: string;
|
|
49
51
|
fields: AirtableLockField[];
|
|
50
52
|
};
|
package/dist/cjs/sync/lib.js
CHANGED
|
@@ -268,9 +268,12 @@ function fieldJsdoc(schemaField, table, schema) {
|
|
|
268
268
|
const tpl = def.template;
|
|
269
269
|
if (tpl.tableId) {
|
|
270
270
|
// The SDK name, not the raw `tbl...` id — the id appears nowhere the
|
|
271
|
-
// reader can act on, while the SDK name is the property on `zite`.
|
|
271
|
+
// reader can act on, while the SDK name is the property on `zite`. Its
|
|
272
|
+
// display name rides along, since after a rename the two differ.
|
|
272
273
|
const linked = schema?.tables.find((t) => t.id === tpl.tableId);
|
|
273
|
-
parts.push(
|
|
274
|
+
parts.push(linked
|
|
275
|
+
? `Links to ${linked.sdkName}${linked.name ? ` ("${linked.name}")` : ""}`
|
|
276
|
+
: `Links to ${tpl.tableId}`);
|
|
274
277
|
}
|
|
275
278
|
if (tpl.allowMultiple === false)
|
|
276
279
|
parts.push("Single record only");
|
|
@@ -383,6 +386,10 @@ function generateSchema(database, existingSchema) {
|
|
|
383
386
|
table: {
|
|
384
387
|
id: table.id,
|
|
385
388
|
sdkName: "",
|
|
389
|
+
// Always the live name — never `existingTable?.name`. Locking it the way
|
|
390
|
+
// `sdkName` is locked would freeze it at the first name the table ever
|
|
391
|
+
// had, which is the one thing this is here to avoid.
|
|
392
|
+
name: table.name,
|
|
386
393
|
primaryFieldId: table.primaryFieldId,
|
|
387
394
|
fields: [],
|
|
388
395
|
},
|
|
@@ -441,6 +448,13 @@ function buildLinkTableComments(schema) {
|
|
|
441
448
|
const tablesById = new Map();
|
|
442
449
|
for (const t of schema.tables)
|
|
443
450
|
tablesById.set(t.id, t);
|
|
451
|
+
// The link name itself is a literal SQL identifier ("use these exact names"),
|
|
452
|
+
// so it stays sdkName-derived; the display names ride alongside as a gloss.
|
|
453
|
+
const displayByPascal = new Map();
|
|
454
|
+
for (const t of schema.tables) {
|
|
455
|
+
if (t.name)
|
|
456
|
+
displayByPascal.set((0, sdkNames_js_1.toPascalCase)(t.sdkName), t.name);
|
|
457
|
+
}
|
|
444
458
|
const seen = new Set();
|
|
445
459
|
const entries = [];
|
|
446
460
|
for (const table of schema.tables) {
|
|
@@ -470,7 +484,15 @@ function buildLinkTableComments(schema) {
|
|
|
470
484
|
const col2 = isSelf
|
|
471
485
|
? `target${second}Id`
|
|
472
486
|
: `${second.charAt(0).toLowerCase()}${second.slice(1)}Id`;
|
|
473
|
-
|
|
487
|
+
const firstName = displayByPascal.get(first);
|
|
488
|
+
const secondName = displayByPascal.get(second);
|
|
489
|
+
entries.push({
|
|
490
|
+
name: linkName,
|
|
491
|
+
cols: [col1, col2],
|
|
492
|
+
gloss: firstName && secondName
|
|
493
|
+
? `joins "${firstName}" and "${secondName}"`
|
|
494
|
+
: undefined,
|
|
495
|
+
});
|
|
474
496
|
}
|
|
475
497
|
}
|
|
476
498
|
if (entries.length === 0)
|
|
@@ -480,7 +502,8 @@ function buildLinkTableComments(schema) {
|
|
|
480
502
|
"// Link tables for zite.sql() JOINs (use these exact names):",
|
|
481
503
|
];
|
|
482
504
|
for (const e of entries) {
|
|
483
|
-
lines.push(`// "${commentSafe(e.name)}" — columns: "${commentSafe(e.cols[0])}", "${commentSafe(e.cols[1])}"`
|
|
505
|
+
lines.push(`// "${commentSafe(e.name)}" — columns: "${commentSafe(e.cols[0])}", "${commentSafe(e.cols[1])}"` +
|
|
506
|
+
(e.gloss ? ` (${commentSafe(e.gloss)})` : ""));
|
|
484
507
|
}
|
|
485
508
|
return lines;
|
|
486
509
|
}
|
|
@@ -567,7 +590,12 @@ function generateDbTs(inputSchema) {
|
|
|
567
590
|
for (const table of tables) {
|
|
568
591
|
const className = (0, sdkNames_js_1.toPascalCase)(table.sdkName);
|
|
569
592
|
const recordType = `${className}RecordType`;
|
|
570
|
-
|
|
593
|
+
// A schema written before `name` existed keeps the old wording exactly —
|
|
594
|
+
// sandbox boot regenerates unconditionally, so any drift here would show up
|
|
595
|
+
// as an unexplained diff in every project's next commit.
|
|
596
|
+
lines.push(table.name
|
|
597
|
+
? `/** A record in the "${commentSafe(table.name)}" table, as it is read back. */`
|
|
598
|
+
: `/** A ${table.sdkName} record as it is read back. */`);
|
|
571
599
|
lines.push(`export type ${recordType} = {`);
|
|
572
600
|
lines.push(" id: string;");
|
|
573
601
|
for (const field of table.fields) {
|
|
@@ -591,7 +619,9 @@ function generateDbTs(inputSchema) {
|
|
|
591
619
|
// input than they store (an attachments field takes a URL string; a linked
|
|
592
620
|
// record takes one id or many).
|
|
593
621
|
const writableFields = table.fields.filter((f) => f.sdkName !== "id" && !READ_ONLY_FIELD_TYPES.has(f.definition.type));
|
|
594
|
-
lines.push(
|
|
622
|
+
lines.push(table.name
|
|
623
|
+
? `/** What you may write when creating or updating a record in the "${commentSafe(table.name)}" table. */`
|
|
624
|
+
: `/** What you may write when creating or updating a ${table.sdkName}. */`);
|
|
595
625
|
lines.push(`export type ${className}RecordInput = {`);
|
|
596
626
|
for (const field of writableFields) {
|
|
597
627
|
lines.push(` ${field.sdkName}: ${tsTypeForSchemaField(field.definition, "write")};`);
|
|
@@ -603,7 +633,8 @@ function generateDbTs(inputSchema) {
|
|
|
603
633
|
lines.push("export const zite = {");
|
|
604
634
|
for (const table of tables) {
|
|
605
635
|
const className = (0, sdkNames_js_1.toPascalCase)(table.sdkName);
|
|
606
|
-
lines.push(` ${table.sdkName}: createTableClient<${className}RecordType, ${className}RecordInput>('${className}'),`
|
|
636
|
+
lines.push(` ${table.sdkName}: createTableClient<${className}RecordType, ${className}RecordInput>('${className}'),` +
|
|
637
|
+
(table.name ? ` // "${commentSafe(table.name)}"` : ""));
|
|
607
638
|
}
|
|
608
639
|
lines.push(` sql: createSqlClient(),`);
|
|
609
640
|
// No `notifications` here: it is a platform primitive, not something backed
|
|
@@ -1002,7 +1033,9 @@ function airtableFieldJsdoc(field, table, lock) {
|
|
|
1002
1033
|
const linkedTableId = field.config?.linkedTableId;
|
|
1003
1034
|
if (linkedTableId) {
|
|
1004
1035
|
const linked = lock?.tables.find((t) => t.id === linkedTableId);
|
|
1005
|
-
parts.push(
|
|
1036
|
+
parts.push(linked
|
|
1037
|
+
? `Links to ${linked.sdkName}${linked.name ? ` ("${linked.name}")` : ""}`
|
|
1038
|
+
: `Links to ${linkedTableId}`);
|
|
1006
1039
|
}
|
|
1007
1040
|
if (field.config?.prefersSingleRecordLink)
|
|
1008
1041
|
parts.push("Single record only");
|
|
@@ -1160,7 +1193,9 @@ function generateAirtableTs(inputLock) {
|
|
|
1160
1193
|
lines.push(...AIRTABLE_ATTACHMENT_TYPE);
|
|
1161
1194
|
for (const table of lock.tables) {
|
|
1162
1195
|
const recordType = `${table.sdkName}RecordType`;
|
|
1163
|
-
lines.push(
|
|
1196
|
+
lines.push(table.name
|
|
1197
|
+
? `/** A record in the "${commentSafe(table.name)}" table, as it is read back. */`
|
|
1198
|
+
: `/** A ${commentSafe(table.sdkName)} record as it is read back. */`);
|
|
1164
1199
|
lines.push(`export type ${recordType} = {`);
|
|
1165
1200
|
lines.push(" id: string;");
|
|
1166
1201
|
for (const field of table.fields) {
|
|
@@ -1182,7 +1217,9 @@ function generateAirtableTs(inputLock) {
|
|
|
1182
1217
|
// Read-only fields are omitted rather than typed: Airtable rejects a write
|
|
1183
1218
|
// to a formula, rollup, lookup or autonumber with a 422.
|
|
1184
1219
|
const writableFields = table.fields.filter((f) => f.sdkName !== "id" && !READ_ONLY_AIRTABLE_FIELDS.has(f.type));
|
|
1185
|
-
lines.push(
|
|
1220
|
+
lines.push(table.name
|
|
1221
|
+
? `/** What you may write when creating or updating a record in the "${commentSafe(table.name)}" table. */`
|
|
1222
|
+
: `/** What you may write when creating or updating a ${commentSafe(table.sdkName)}. */`);
|
|
1186
1223
|
lines.push(`export type ${table.sdkName}RecordInput = {`);
|
|
1187
1224
|
for (const field of writableFields) {
|
|
1188
1225
|
lines.push(` ${field.sdkName}: ${airtableTsType(field, lock, 0, "write")};`);
|
|
@@ -1192,7 +1229,8 @@ function generateAirtableTs(inputLock) {
|
|
|
1192
1229
|
lines.push(`export const ${table.sdkName} = createAirtableClient<${recordType}, ${table.sdkName}RecordInput>(`);
|
|
1193
1230
|
lines.push(` '${lock.integrationId}',`);
|
|
1194
1231
|
lines.push(` '${table.sdkName}',`);
|
|
1195
|
-
lines.push(` { tableId: '${table.id}' },`
|
|
1232
|
+
lines.push(` { tableId: '${table.id}' },` +
|
|
1233
|
+
(table.name ? ` // "${commentSafe(table.name)}"` : ""));
|
|
1196
1234
|
lines.push(`);`);
|
|
1197
1235
|
lines.push("");
|
|
1198
1236
|
}
|
|
@@ -282,6 +282,56 @@ const duplicateIdentifierErrorsIn = (source) => {
|
|
|
282
282
|
(0, vitest_1.expect)(syntaxErrorsIn((0, lib_js_1.generateDbTs)(schemaWithFieldNamed(name)))).toEqual([]);
|
|
283
283
|
});
|
|
284
284
|
});
|
|
285
|
+
(0, vitest_1.describe)('generateDbTs table names in comments', () => {
|
|
286
|
+
const schemaWithTableNamed = (name) => ({
|
|
287
|
+
tables: [
|
|
288
|
+
{
|
|
289
|
+
id: 'tbl1',
|
|
290
|
+
sdkName: 'table1',
|
|
291
|
+
...(name === undefined ? {} : { name }),
|
|
292
|
+
primaryFieldId: 'fld1',
|
|
293
|
+
fields: [
|
|
294
|
+
{
|
|
295
|
+
id: 'fld1',
|
|
296
|
+
sdkName: 'slot',
|
|
297
|
+
definition: {
|
|
298
|
+
type: 'single_line_text',
|
|
299
|
+
name: 'Slot',
|
|
300
|
+
template: {},
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
// The whole point: `sdkName` is locked to the table's id, so after a rename
|
|
308
|
+
// `table1` is the only thing a reader sees. The display name is what says
|
|
309
|
+
// which table that is.
|
|
310
|
+
(0, vitest_1.it)('names the table beside its locked sdkName', () => {
|
|
311
|
+
const out = (0, lib_js_1.generateDbTs)(schemaWithTableNamed('Time Slots'));
|
|
312
|
+
(0, vitest_1.expect)(out).toContain('/** A record in the "Time Slots" table, as it is read back. */');
|
|
313
|
+
(0, vitest_1.expect)(out).toContain('/** What you may write when creating or updating a record in the "Time Slots" table. */');
|
|
314
|
+
(0, vitest_1.expect)(out).toContain(`table1: createTableClient<Table1RecordType, Table1RecordInput>('Table1'), // "Time Slots"`);
|
|
315
|
+
});
|
|
316
|
+
// Sandbox boot regenerates unconditionally against the committed schema, so a
|
|
317
|
+
// schema written before `name` existed has to keep producing what it did
|
|
318
|
+
// before — otherwise every project's next commit carries an unexplained diff.
|
|
319
|
+
(0, vitest_1.it)('emits the pre-name wording when the schema carries no name', () => {
|
|
320
|
+
const out = (0, lib_js_1.generateDbTs)(schemaWithTableNamed(undefined));
|
|
321
|
+
(0, vitest_1.expect)(out).toContain('/** A table1 record as it is read back. */');
|
|
322
|
+
(0, vitest_1.expect)(out).toContain('/** What you may write when creating or updating a table1. */');
|
|
323
|
+
(0, vitest_1.expect)(out).toContain(`('Table1'),\n`);
|
|
324
|
+
(0, vitest_1.expect)(out).not.toContain('" table, as it is read back');
|
|
325
|
+
});
|
|
326
|
+
// Same hazard as a field name, one level up: a table's display name is user
|
|
327
|
+
// text and now reaches a JSDoc.
|
|
328
|
+
vitest_1.it.each([
|
|
329
|
+
['a comment-close sequence', 'Slots */ console.log(1); /*'],
|
|
330
|
+
['a newline', 'Time\nSlots'],
|
|
331
|
+
])('emits parseable TypeScript for a table named with %s', (_what, name) => {
|
|
332
|
+
(0, vitest_1.expect)(syntaxErrorsIn((0, lib_js_1.generateDbTs)(schemaWithTableNamed(name)))).toEqual([]);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
285
335
|
(0, vitest_1.describe)('generateApiTs endpoint file names', () => {
|
|
286
336
|
// An endpoint filename is the LLM's raw `writeFile` path argument — nothing
|
|
287
337
|
// validates its characters — and it lands in an import specifier, a comment
|
|
@@ -207,3 +207,31 @@ const recordTypeKeys = (source, typeName) => {
|
|
|
207
207
|
(0, vitest_1.expect)(keys).toContain("sql2");
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
|
+
(0, vitest_1.describe)("table display names", () => {
|
|
211
|
+
// `sdkName` is locked to the id so user code keeps compiling; `name` is the
|
|
212
|
+
// opposite and must always take the live value. Locking it too — the easy
|
|
213
|
+
// mistake, since the line above it does exactly that — freezes it at the
|
|
214
|
+
// table's first name and defeats the point.
|
|
215
|
+
(0, vitest_1.it)("follows a rename while the sdkName stays locked", () => {
|
|
216
|
+
const first = (0, lib_js_1.generateSchema)(database([{ id: "tbl_1", name: "Slots", fields: [field("fld_1", "At")] }]));
|
|
217
|
+
(0, vitest_1.expect)(first.tables[0].sdkName).toBe("slots");
|
|
218
|
+
(0, vitest_1.expect)(first.tables[0].name).toBe("Slots");
|
|
219
|
+
const renamed = (0, lib_js_1.generateSchema)(database([
|
|
220
|
+
{ id: "tbl_1", name: "Time Slots", fields: [field("fld_1", "At")] },
|
|
221
|
+
]), first);
|
|
222
|
+
(0, vitest_1.expect)(renamed.tables[0].sdkName).toBe("slots");
|
|
223
|
+
(0, vitest_1.expect)(renamed.tables[0].name).toBe("Time Slots");
|
|
224
|
+
(0, vitest_1.expect)((0, lib_js_1.generateDbTs)(renamed)).toContain('/** A record in the "Time Slots" table, as it is read back. */');
|
|
225
|
+
});
|
|
226
|
+
// `buildLegacySchemaSeed` (restly, migration) builds a seed carrying only
|
|
227
|
+
// `sdkName`. Reading `name` off the seed would emit `undefined` for every
|
|
228
|
+
// table on every newly migrated project.
|
|
229
|
+
(0, vitest_1.it)("takes the live name even when the seed has none", () => {
|
|
230
|
+
const seed = {
|
|
231
|
+
tables: [{ id: "tbl_1", sdkName: "salesDeals", fields: [] }],
|
|
232
|
+
};
|
|
233
|
+
const schema = (0, lib_js_1.generateSchema)(database([{ id: "tbl_1", name: "Deals", fields: [] }]), seed);
|
|
234
|
+
(0, vitest_1.expect)(schema.tables[0].sdkName).toBe("salesDeals");
|
|
235
|
+
(0, vitest_1.expect)(schema.tables[0].name).toBe("Deals");
|
|
236
|
+
});
|
|
237
|
+
});
|
package/dist/esm/sync/lib.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type ZiteSchemaField = {
|
|
|
9
9
|
export type ZiteSchemaTable = {
|
|
10
10
|
id: string;
|
|
11
11
|
sdkName: string;
|
|
12
|
+
name?: string;
|
|
12
13
|
primaryFieldId?: string;
|
|
13
14
|
fields: ZiteSchemaField[];
|
|
14
15
|
};
|
|
@@ -45,6 +46,7 @@ export type AirtableLockField = {
|
|
|
45
46
|
export type AirtableLockTable = {
|
|
46
47
|
id: string;
|
|
47
48
|
sdkName: string;
|
|
49
|
+
name?: string;
|
|
48
50
|
primaryFieldId: string;
|
|
49
51
|
fields: AirtableLockField[];
|
|
50
52
|
};
|
package/dist/esm/sync/lib.js
CHANGED
|
@@ -255,9 +255,12 @@ function fieldJsdoc(schemaField, table, schema) {
|
|
|
255
255
|
const tpl = def.template;
|
|
256
256
|
if (tpl.tableId) {
|
|
257
257
|
// The SDK name, not the raw `tbl...` id — the id appears nowhere the
|
|
258
|
-
// reader can act on, while the SDK name is the property on `zite`.
|
|
258
|
+
// reader can act on, while the SDK name is the property on `zite`. Its
|
|
259
|
+
// display name rides along, since after a rename the two differ.
|
|
259
260
|
const linked = schema?.tables.find((t) => t.id === tpl.tableId);
|
|
260
|
-
parts.push(
|
|
261
|
+
parts.push(linked
|
|
262
|
+
? `Links to ${linked.sdkName}${linked.name ? ` ("${linked.name}")` : ""}`
|
|
263
|
+
: `Links to ${tpl.tableId}`);
|
|
261
264
|
}
|
|
262
265
|
if (tpl.allowMultiple === false)
|
|
263
266
|
parts.push("Single record only");
|
|
@@ -370,6 +373,10 @@ export function generateSchema(database, existingSchema) {
|
|
|
370
373
|
table: {
|
|
371
374
|
id: table.id,
|
|
372
375
|
sdkName: "",
|
|
376
|
+
// Always the live name — never `existingTable?.name`. Locking it the way
|
|
377
|
+
// `sdkName` is locked would freeze it at the first name the table ever
|
|
378
|
+
// had, which is the one thing this is here to avoid.
|
|
379
|
+
name: table.name,
|
|
373
380
|
primaryFieldId: table.primaryFieldId,
|
|
374
381
|
fields: [],
|
|
375
382
|
},
|
|
@@ -428,6 +435,13 @@ function buildLinkTableComments(schema) {
|
|
|
428
435
|
const tablesById = new Map();
|
|
429
436
|
for (const t of schema.tables)
|
|
430
437
|
tablesById.set(t.id, t);
|
|
438
|
+
// The link name itself is a literal SQL identifier ("use these exact names"),
|
|
439
|
+
// so it stays sdkName-derived; the display names ride alongside as a gloss.
|
|
440
|
+
const displayByPascal = new Map();
|
|
441
|
+
for (const t of schema.tables) {
|
|
442
|
+
if (t.name)
|
|
443
|
+
displayByPascal.set(toPascalCase(t.sdkName), t.name);
|
|
444
|
+
}
|
|
431
445
|
const seen = new Set();
|
|
432
446
|
const entries = [];
|
|
433
447
|
for (const table of schema.tables) {
|
|
@@ -457,7 +471,15 @@ function buildLinkTableComments(schema) {
|
|
|
457
471
|
const col2 = isSelf
|
|
458
472
|
? `target${second}Id`
|
|
459
473
|
: `${second.charAt(0).toLowerCase()}${second.slice(1)}Id`;
|
|
460
|
-
|
|
474
|
+
const firstName = displayByPascal.get(first);
|
|
475
|
+
const secondName = displayByPascal.get(second);
|
|
476
|
+
entries.push({
|
|
477
|
+
name: linkName,
|
|
478
|
+
cols: [col1, col2],
|
|
479
|
+
gloss: firstName && secondName
|
|
480
|
+
? `joins "${firstName}" and "${secondName}"`
|
|
481
|
+
: undefined,
|
|
482
|
+
});
|
|
461
483
|
}
|
|
462
484
|
}
|
|
463
485
|
if (entries.length === 0)
|
|
@@ -467,7 +489,8 @@ function buildLinkTableComments(schema) {
|
|
|
467
489
|
"// Link tables for zite.sql() JOINs (use these exact names):",
|
|
468
490
|
];
|
|
469
491
|
for (const e of entries) {
|
|
470
|
-
lines.push(`// "${commentSafe(e.name)}" — columns: "${commentSafe(e.cols[0])}", "${commentSafe(e.cols[1])}"`
|
|
492
|
+
lines.push(`// "${commentSafe(e.name)}" — columns: "${commentSafe(e.cols[0])}", "${commentSafe(e.cols[1])}"` +
|
|
493
|
+
(e.gloss ? ` (${commentSafe(e.gloss)})` : ""));
|
|
471
494
|
}
|
|
472
495
|
return lines;
|
|
473
496
|
}
|
|
@@ -554,7 +577,12 @@ export function generateDbTs(inputSchema) {
|
|
|
554
577
|
for (const table of tables) {
|
|
555
578
|
const className = toPascalCase(table.sdkName);
|
|
556
579
|
const recordType = `${className}RecordType`;
|
|
557
|
-
|
|
580
|
+
// A schema written before `name` existed keeps the old wording exactly —
|
|
581
|
+
// sandbox boot regenerates unconditionally, so any drift here would show up
|
|
582
|
+
// as an unexplained diff in every project's next commit.
|
|
583
|
+
lines.push(table.name
|
|
584
|
+
? `/** A record in the "${commentSafe(table.name)}" table, as it is read back. */`
|
|
585
|
+
: `/** A ${table.sdkName} record as it is read back. */`);
|
|
558
586
|
lines.push(`export type ${recordType} = {`);
|
|
559
587
|
lines.push(" id: string;");
|
|
560
588
|
for (const field of table.fields) {
|
|
@@ -578,7 +606,9 @@ export function generateDbTs(inputSchema) {
|
|
|
578
606
|
// input than they store (an attachments field takes a URL string; a linked
|
|
579
607
|
// record takes one id or many).
|
|
580
608
|
const writableFields = table.fields.filter((f) => f.sdkName !== "id" && !READ_ONLY_FIELD_TYPES.has(f.definition.type));
|
|
581
|
-
lines.push(
|
|
609
|
+
lines.push(table.name
|
|
610
|
+
? `/** What you may write when creating or updating a record in the "${commentSafe(table.name)}" table. */`
|
|
611
|
+
: `/** What you may write when creating or updating a ${table.sdkName}. */`);
|
|
582
612
|
lines.push(`export type ${className}RecordInput = {`);
|
|
583
613
|
for (const field of writableFields) {
|
|
584
614
|
lines.push(` ${field.sdkName}: ${tsTypeForSchemaField(field.definition, "write")};`);
|
|
@@ -590,7 +620,8 @@ export function generateDbTs(inputSchema) {
|
|
|
590
620
|
lines.push("export const zite = {");
|
|
591
621
|
for (const table of tables) {
|
|
592
622
|
const className = toPascalCase(table.sdkName);
|
|
593
|
-
lines.push(` ${table.sdkName}: createTableClient<${className}RecordType, ${className}RecordInput>('${className}'),`
|
|
623
|
+
lines.push(` ${table.sdkName}: createTableClient<${className}RecordType, ${className}RecordInput>('${className}'),` +
|
|
624
|
+
(table.name ? ` // "${commentSafe(table.name)}"` : ""));
|
|
594
625
|
}
|
|
595
626
|
lines.push(` sql: createSqlClient(),`);
|
|
596
627
|
// No `notifications` here: it is a platform primitive, not something backed
|
|
@@ -989,7 +1020,9 @@ function airtableFieldJsdoc(field, table, lock) {
|
|
|
989
1020
|
const linkedTableId = field.config?.linkedTableId;
|
|
990
1021
|
if (linkedTableId) {
|
|
991
1022
|
const linked = lock?.tables.find((t) => t.id === linkedTableId);
|
|
992
|
-
parts.push(
|
|
1023
|
+
parts.push(linked
|
|
1024
|
+
? `Links to ${linked.sdkName}${linked.name ? ` ("${linked.name}")` : ""}`
|
|
1025
|
+
: `Links to ${linkedTableId}`);
|
|
993
1026
|
}
|
|
994
1027
|
if (field.config?.prefersSingleRecordLink)
|
|
995
1028
|
parts.push("Single record only");
|
|
@@ -1147,7 +1180,9 @@ export function generateAirtableTs(inputLock) {
|
|
|
1147
1180
|
lines.push(...AIRTABLE_ATTACHMENT_TYPE);
|
|
1148
1181
|
for (const table of lock.tables) {
|
|
1149
1182
|
const recordType = `${table.sdkName}RecordType`;
|
|
1150
|
-
lines.push(
|
|
1183
|
+
lines.push(table.name
|
|
1184
|
+
? `/** A record in the "${commentSafe(table.name)}" table, as it is read back. */`
|
|
1185
|
+
: `/** A ${commentSafe(table.sdkName)} record as it is read back. */`);
|
|
1151
1186
|
lines.push(`export type ${recordType} = {`);
|
|
1152
1187
|
lines.push(" id: string;");
|
|
1153
1188
|
for (const field of table.fields) {
|
|
@@ -1169,7 +1204,9 @@ export function generateAirtableTs(inputLock) {
|
|
|
1169
1204
|
// Read-only fields are omitted rather than typed: Airtable rejects a write
|
|
1170
1205
|
// to a formula, rollup, lookup or autonumber with a 422.
|
|
1171
1206
|
const writableFields = table.fields.filter((f) => f.sdkName !== "id" && !READ_ONLY_AIRTABLE_FIELDS.has(f.type));
|
|
1172
|
-
lines.push(
|
|
1207
|
+
lines.push(table.name
|
|
1208
|
+
? `/** What you may write when creating or updating a record in the "${commentSafe(table.name)}" table. */`
|
|
1209
|
+
: `/** What you may write when creating or updating a ${commentSafe(table.sdkName)}. */`);
|
|
1173
1210
|
lines.push(`export type ${table.sdkName}RecordInput = {`);
|
|
1174
1211
|
for (const field of writableFields) {
|
|
1175
1212
|
lines.push(` ${field.sdkName}: ${airtableTsType(field, lock, 0, "write")};`);
|
|
@@ -1179,7 +1216,8 @@ export function generateAirtableTs(inputLock) {
|
|
|
1179
1216
|
lines.push(`export const ${table.sdkName} = createAirtableClient<${recordType}, ${table.sdkName}RecordInput>(`);
|
|
1180
1217
|
lines.push(` '${lock.integrationId}',`);
|
|
1181
1218
|
lines.push(` '${table.sdkName}',`);
|
|
1182
|
-
lines.push(` { tableId: '${table.id}' },`
|
|
1219
|
+
lines.push(` { tableId: '${table.id}' },` +
|
|
1220
|
+
(table.name ? ` // "${commentSafe(table.name)}"` : ""));
|
|
1183
1221
|
lines.push(`);`);
|
|
1184
1222
|
lines.push("");
|
|
1185
1223
|
}
|
|
@@ -277,6 +277,56 @@ describe('generateDbTs field names in comments', () => {
|
|
|
277
277
|
expect(syntaxErrorsIn(generateDbTs(schemaWithFieldNamed(name)))).toEqual([]);
|
|
278
278
|
});
|
|
279
279
|
});
|
|
280
|
+
describe('generateDbTs table names in comments', () => {
|
|
281
|
+
const schemaWithTableNamed = (name) => ({
|
|
282
|
+
tables: [
|
|
283
|
+
{
|
|
284
|
+
id: 'tbl1',
|
|
285
|
+
sdkName: 'table1',
|
|
286
|
+
...(name === undefined ? {} : { name }),
|
|
287
|
+
primaryFieldId: 'fld1',
|
|
288
|
+
fields: [
|
|
289
|
+
{
|
|
290
|
+
id: 'fld1',
|
|
291
|
+
sdkName: 'slot',
|
|
292
|
+
definition: {
|
|
293
|
+
type: 'single_line_text',
|
|
294
|
+
name: 'Slot',
|
|
295
|
+
template: {},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
});
|
|
302
|
+
// The whole point: `sdkName` is locked to the table's id, so after a rename
|
|
303
|
+
// `table1` is the only thing a reader sees. The display name is what says
|
|
304
|
+
// which table that is.
|
|
305
|
+
it('names the table beside its locked sdkName', () => {
|
|
306
|
+
const out = generateDbTs(schemaWithTableNamed('Time Slots'));
|
|
307
|
+
expect(out).toContain('/** A record in the "Time Slots" table, as it is read back. */');
|
|
308
|
+
expect(out).toContain('/** What you may write when creating or updating a record in the "Time Slots" table. */');
|
|
309
|
+
expect(out).toContain(`table1: createTableClient<Table1RecordType, Table1RecordInput>('Table1'), // "Time Slots"`);
|
|
310
|
+
});
|
|
311
|
+
// Sandbox boot regenerates unconditionally against the committed schema, so a
|
|
312
|
+
// schema written before `name` existed has to keep producing what it did
|
|
313
|
+
// before — otherwise every project's next commit carries an unexplained diff.
|
|
314
|
+
it('emits the pre-name wording when the schema carries no name', () => {
|
|
315
|
+
const out = generateDbTs(schemaWithTableNamed(undefined));
|
|
316
|
+
expect(out).toContain('/** A table1 record as it is read back. */');
|
|
317
|
+
expect(out).toContain('/** What you may write when creating or updating a table1. */');
|
|
318
|
+
expect(out).toContain(`('Table1'),\n`);
|
|
319
|
+
expect(out).not.toContain('" table, as it is read back');
|
|
320
|
+
});
|
|
321
|
+
// Same hazard as a field name, one level up: a table's display name is user
|
|
322
|
+
// text and now reaches a JSDoc.
|
|
323
|
+
it.each([
|
|
324
|
+
['a comment-close sequence', 'Slots */ console.log(1); /*'],
|
|
325
|
+
['a newline', 'Time\nSlots'],
|
|
326
|
+
])('emits parseable TypeScript for a table named with %s', (_what, name) => {
|
|
327
|
+
expect(syntaxErrorsIn(generateDbTs(schemaWithTableNamed(name)))).toEqual([]);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
280
330
|
describe('generateApiTs endpoint file names', () => {
|
|
281
331
|
// An endpoint filename is the LLM's raw `writeFile` path argument — nothing
|
|
282
332
|
// validates its characters — and it lands in an import specifier, a comment
|
|
@@ -205,3 +205,31 @@ describe("normalizeSchemaNames", () => {
|
|
|
205
205
|
expect(keys).toContain("sql2");
|
|
206
206
|
});
|
|
207
207
|
});
|
|
208
|
+
describe("table display names", () => {
|
|
209
|
+
// `sdkName` is locked to the id so user code keeps compiling; `name` is the
|
|
210
|
+
// opposite and must always take the live value. Locking it too — the easy
|
|
211
|
+
// mistake, since the line above it does exactly that — freezes it at the
|
|
212
|
+
// table's first name and defeats the point.
|
|
213
|
+
it("follows a rename while the sdkName stays locked", () => {
|
|
214
|
+
const first = generateSchema(database([{ id: "tbl_1", name: "Slots", fields: [field("fld_1", "At")] }]));
|
|
215
|
+
expect(first.tables[0].sdkName).toBe("slots");
|
|
216
|
+
expect(first.tables[0].name).toBe("Slots");
|
|
217
|
+
const renamed = generateSchema(database([
|
|
218
|
+
{ id: "tbl_1", name: "Time Slots", fields: [field("fld_1", "At")] },
|
|
219
|
+
]), first);
|
|
220
|
+
expect(renamed.tables[0].sdkName).toBe("slots");
|
|
221
|
+
expect(renamed.tables[0].name).toBe("Time Slots");
|
|
222
|
+
expect(generateDbTs(renamed)).toContain('/** A record in the "Time Slots" table, as it is read back. */');
|
|
223
|
+
});
|
|
224
|
+
// `buildLegacySchemaSeed` (restly, migration) builds a seed carrying only
|
|
225
|
+
// `sdkName`. Reading `name` off the seed would emit `undefined` for every
|
|
226
|
+
// table on every newly migrated project.
|
|
227
|
+
it("takes the live name even when the seed has none", () => {
|
|
228
|
+
const seed = {
|
|
229
|
+
tables: [{ id: "tbl_1", sdkName: "salesDeals", fields: [] }],
|
|
230
|
+
};
|
|
231
|
+
const schema = generateSchema(database([{ id: "tbl_1", name: "Deals", fields: [] }]), seed);
|
|
232
|
+
expect(schema.tables[0].sdkName).toBe("salesDeals");
|
|
233
|
+
expect(schema.tables[0].name).toBe("Deals");
|
|
234
|
+
});
|
|
235
|
+
});
|