zitejs 0.9.101 → 0.9.103

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.
@@ -16,6 +16,16 @@ export type ZiteSchema = {
16
16
  };
17
17
  export declare function toPascalCase(name: string): string;
18
18
  export declare function toCamelCase(name: string): string;
19
+ /**
20
+ * camelCase for a FRESH sdkName, reading acronym runs as words: "VIP" ->
21
+ * "vip", "APIKey" -> "apiKey" (plain toCamelCase yields "vIP" / "aPIKey").
22
+ *
23
+ * Deliberately not folded into toCamelCase: that also derives endpoint
24
+ * identifiers from existing filenames on every generate, where normalizing
25
+ * would rename a working app's `api.sendSMS`. This is only for names being
26
+ * chosen for the first time — generateSchema preserves existing sdkNames.
27
+ */
28
+ export declare function toSdkName(name: string): string;
19
29
  /**
20
30
  * Build a ZiteSchema from a Database API response.
21
31
  *
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toPascalCase = toPascalCase;
4
4
  exports.toCamelCase = toCamelCase;
5
+ exports.toSdkName = toSdkName;
5
6
  exports.generateSchema = generateSchema;
6
7
  exports.generateDbTs = generateDbTs;
7
8
  exports.generateApiTs = generateApiTs;
@@ -151,7 +152,9 @@ const DATE_FORMAT_EXAMPLES = {
151
152
  };
152
153
  const withDateFormatExample = (format) => {
153
154
  const example = DATE_FORMAT_EXAMPLES[format];
154
- return example ? `"${format}" format (e.g. ${example})` : `"${format}" format`;
155
+ return example
156
+ ? `"${format}" format (e.g. ${example})`
157
+ : `"${format}" format`;
155
158
  };
156
159
  function toPascalCase(name) {
157
160
  const pascal = name
@@ -168,6 +171,21 @@ function toCamelCase(name) {
168
171
  const pascal = toPascalCase(name);
169
172
  return pascal.charAt(0).toLowerCase() + pascal.slice(1);
170
173
  }
174
+ /**
175
+ * camelCase for a FRESH sdkName, reading acronym runs as words: "VIP" ->
176
+ * "vip", "APIKey" -> "apiKey" (plain toCamelCase yields "vIP" / "aPIKey").
177
+ *
178
+ * Deliberately not folded into toCamelCase: that also derives endpoint
179
+ * identifiers from existing filenames on every generate, where normalizing
180
+ * would rename a working app's `api.sendSMS`. This is only for names being
181
+ * chosen for the first time — generateSchema preserves existing sdkNames.
182
+ */
183
+ function toSdkName(name) {
184
+ const deAcronymed = name
185
+ .replace(/([A-Z]+)([A-Z][a-z])/g, (_, run, next) => run.charAt(0) + run.slice(1).toLowerCase() + next)
186
+ .replace(/([A-Z])([A-Z]+)/g, (_, first, rest) => first + rest.toLowerCase());
187
+ return toCamelCase(deAcronymed);
188
+ }
171
189
  /**
172
190
  * Existing sdkNames are preserved across syncs so user code keeps compiling,
173
191
  * but schemas written before the sanitizer stripped trailing symbols can carry
@@ -196,7 +214,9 @@ function tsTypeForSchemaField(def, variant = "read") {
196
214
  .map((o) => `"${o.label.replace(/"/g, '\\"')}"`)
197
215
  .join(" | ");
198
216
  const union = `${literals} | string`;
199
- const read = def.type === "multiple_select" ? `(${union})[] | null` : `${union} | null`;
217
+ const read = def.type === "multiple_select"
218
+ ? `(${union})[] | null`
219
+ : `${union} | null`;
200
220
  if (variant === "write") {
201
221
  return def.type === "multiple_select"
202
222
  ? `${union} | (${union})[] | null`
@@ -334,13 +354,13 @@ function generateSchema(database, existingSchema) {
334
354
  const { id: _id, order: _order, ...definition } = field;
335
355
  fields.push({
336
356
  id: field.id,
337
- sdkName: keepValidSdkName(existing?.sdkName) ?? toCamelCase(field.name),
357
+ sdkName: keepValidSdkName(existing?.sdkName) ?? toSdkName(field.name),
338
358
  definition,
339
359
  });
340
360
  }
341
361
  tables.push({
342
362
  id: table.id,
343
- sdkName: keepValidSdkName(existingTable?.sdkName) ?? toCamelCase(table.name),
363
+ sdkName: keepValidSdkName(existingTable?.sdkName) ?? toSdkName(table.name),
344
364
  primaryFieldId: table.primaryFieldId,
345
365
  fields,
346
366
  });
@@ -447,7 +467,7 @@ function generateDbTs(schema) {
447
467
  lines.push("// .findAll({ filters?, sort?, offset?, limit?, fields? }) → { records: T[], hasMore: boolean }");
448
468
  lines.push("// filters: { fieldName: value } for equality, or { fieldName: { <op>: value } }");
449
469
  lines.push("// operators: contains, not, in, notIn, lt, lte, gt, gte");
450
- lines.push('// `not: null` means "is set". An empty `in: []` matches nothing; an', '// empty `notIn: []` applies no filter.');
470
+ lines.push('// `not: null` means "is set". An empty `in: []` matches nothing; an', "// empty `notIn: []` applies no filter.");
451
471
  lines.push("// sort is currently IGNORED here — it is stripped before the request.", "// (it does work on zite.auth.findAllUsers). Order in SQL instead.");
452
472
  lines.push("// limit defaults to 500, max 2000; offset is a row count (a number)");
453
473
  lines.push("// .findOne({ id?, filters?, fields? }) → T | undefined");
@@ -620,12 +640,52 @@ function inspectEndpointFile(source) {
620
640
  * words are legal object keys.
621
641
  */
622
642
  const RESERVED_IDENTIFIERS = new Set([
623
- "await", "break", "case", "catch", "class", "const", "continue", "debugger",
624
- "default", "delete", "do", "else", "enum", "export", "extends", "false",
625
- "finally", "for", "function", "if", "implements", "import", "in",
626
- "instanceof", "interface", "let", "new", "null", "package", "private",
627
- "protected", "public", "return", "static", "super", "switch", "this",
628
- "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield",
643
+ "await",
644
+ "break",
645
+ "case",
646
+ "catch",
647
+ "class",
648
+ "const",
649
+ "continue",
650
+ "debugger",
651
+ "default",
652
+ "delete",
653
+ "do",
654
+ "else",
655
+ "enum",
656
+ "export",
657
+ "extends",
658
+ "false",
659
+ "finally",
660
+ "for",
661
+ "function",
662
+ "if",
663
+ "implements",
664
+ "import",
665
+ "in",
666
+ "instanceof",
667
+ "interface",
668
+ "let",
669
+ "new",
670
+ "null",
671
+ "package",
672
+ "private",
673
+ "protected",
674
+ "public",
675
+ "return",
676
+ "static",
677
+ "super",
678
+ "switch",
679
+ "this",
680
+ "throw",
681
+ "true",
682
+ "try",
683
+ "typeof",
684
+ "var",
685
+ "void",
686
+ "while",
687
+ "with",
688
+ "yield",
629
689
  ]);
630
690
  const toSafeIdentifier = (camelName) => RESERVED_IDENTIFIERS.has(camelName) ? `_${camelName}` : camelName;
631
691
  function generateApiTs(endpointFiles) {
@@ -955,7 +1015,7 @@ function generateAirtableTs(lock) {
955
1015
  '// each carries its current user-facing name in quotes (// "Task Name"), which is',
956
1016
  "// the source of truth for what it means.",
957
1017
  "// Values are RAW; format them for display using each field's comment.",
958
- "// Fields marked \"Links to X\" hold Airtable record ids (rec...) from",
1018
+ '// Fields marked "Links to X" hold Airtable record ids (rec...) from',
959
1019
  "// findOne/findAll — never invent one.",
960
1020
  "// Read-only fields are absent from the *RecordInput types: Airtable rejects a",
961
1021
  "// write to a computed one (formula, rollup, lookup, autonumber, the created/",
@@ -1066,11 +1126,31 @@ function generateBackendWrapperTs(envVarNames = []) {
1066
1126
  " * compile and throw. `Omit` keeps any index signature merged in by",
1067
1127
  " * `.zite/user-extensions.d.ts`, so a migrated user-sync app still reads its",
1068
1128
  " * synced columns off this.",
1129
+ " *",
1130
+ " * The `Pick` intersection is not redundant. On an app that DOES merge in",
1131
+ " * `[key: string]: any`, `keyof User` widens to `string`, `Exclude` then",
1132
+ " * removes nothing, and the `Omit` collapses to a bare index signature —",
1133
+ " * which would silently degrade `user.id` from `string` to `any`. The `Pick`",
1134
+ " * names those members explicitly, so they survive the collapse.",
1135
+ " *",
1136
+ " * Only `id` and `email` are pinned that way, deliberately. On a user-sync",
1137
+ " * app `context.user` IS the synced row (`buildUserContext` returns it",
1138
+ " * whole), and only those two are guaranteed by the runtime: the id is the",
1139
+ " * record id and the email is merged in explicitly. `firstName`/`lastName`",
1140
+ " * come from whatever the app mapped those columns to, so pinning them to",
1141
+ " * `string` would reject a legitimate read on a table that types them",
1142
+ " * differently. Non-widened apps get both from the `Omit` regardless.",
1143
+ " *",
1144
+ " * On such an app `user.createdAt` still compiles, as `any`. That is",
1145
+ " * deliberate: a synced user table may legitimately have its own",
1146
+ " * `createdAt` column, and typing it `never` to close the hole would break",
1147
+ " * exactly the legacy apps `user-extensions.d.ts` exists to keep compiling.",
1069
1148
  " */",
1070
1149
  "export type ZiteEndpointUser = Omit<",
1071
1150
  " User,",
1072
1151
  " 'name' | 'emailVerified' | 'image' | 'createdAt' | 'updatedAt'",
1073
- ">;",
1152
+ "> &",
1153
+ " Pick<User, 'id' | 'email'>;",
1074
1154
  "",
1075
1155
  'export interface ZiteRequestContext extends Omit<_ZiteRequestContext, "user"> {',
1076
1156
  " user: ZiteEndpointUser;",
package/dist/esm/cli.js CHANGED
File without changes
@@ -16,6 +16,16 @@ export type ZiteSchema = {
16
16
  };
17
17
  export declare function toPascalCase(name: string): string;
18
18
  export declare function toCamelCase(name: string): string;
19
+ /**
20
+ * camelCase for a FRESH sdkName, reading acronym runs as words: "VIP" ->
21
+ * "vip", "APIKey" -> "apiKey" (plain toCamelCase yields "vIP" / "aPIKey").
22
+ *
23
+ * Deliberately not folded into toCamelCase: that also derives endpoint
24
+ * identifiers from existing filenames on every generate, where normalizing
25
+ * would rename a working app's `api.sendSMS`. This is only for names being
26
+ * chosen for the first time — generateSchema preserves existing sdkNames.
27
+ */
28
+ export declare function toSdkName(name: string): string;
19
29
  /**
20
30
  * Build a ZiteSchema from a Database API response.
21
31
  *
@@ -141,7 +141,9 @@ const DATE_FORMAT_EXAMPLES = {
141
141
  };
142
142
  const withDateFormatExample = (format) => {
143
143
  const example = DATE_FORMAT_EXAMPLES[format];
144
- return example ? `"${format}" format (e.g. ${example})` : `"${format}" format`;
144
+ return example
145
+ ? `"${format}" format (e.g. ${example})`
146
+ : `"${format}" format`;
145
147
  };
146
148
  export function toPascalCase(name) {
147
149
  const pascal = name
@@ -158,6 +160,21 @@ export function toCamelCase(name) {
158
160
  const pascal = toPascalCase(name);
159
161
  return pascal.charAt(0).toLowerCase() + pascal.slice(1);
160
162
  }
163
+ /**
164
+ * camelCase for a FRESH sdkName, reading acronym runs as words: "VIP" ->
165
+ * "vip", "APIKey" -> "apiKey" (plain toCamelCase yields "vIP" / "aPIKey").
166
+ *
167
+ * Deliberately not folded into toCamelCase: that also derives endpoint
168
+ * identifiers from existing filenames on every generate, where normalizing
169
+ * would rename a working app's `api.sendSMS`. This is only for names being
170
+ * chosen for the first time — generateSchema preserves existing sdkNames.
171
+ */
172
+ export function toSdkName(name) {
173
+ const deAcronymed = name
174
+ .replace(/([A-Z]+)([A-Z][a-z])/g, (_, run, next) => run.charAt(0) + run.slice(1).toLowerCase() + next)
175
+ .replace(/([A-Z])([A-Z]+)/g, (_, first, rest) => first + rest.toLowerCase());
176
+ return toCamelCase(deAcronymed);
177
+ }
161
178
  /**
162
179
  * Existing sdkNames are preserved across syncs so user code keeps compiling,
163
180
  * but schemas written before the sanitizer stripped trailing symbols can carry
@@ -186,7 +203,9 @@ function tsTypeForSchemaField(def, variant = "read") {
186
203
  .map((o) => `"${o.label.replace(/"/g, '\\"')}"`)
187
204
  .join(" | ");
188
205
  const union = `${literals} | string`;
189
- const read = def.type === "multiple_select" ? `(${union})[] | null` : `${union} | null`;
206
+ const read = def.type === "multiple_select"
207
+ ? `(${union})[] | null`
208
+ : `${union} | null`;
190
209
  if (variant === "write") {
191
210
  return def.type === "multiple_select"
192
211
  ? `${union} | (${union})[] | null`
@@ -324,13 +343,13 @@ export function generateSchema(database, existingSchema) {
324
343
  const { id: _id, order: _order, ...definition } = field;
325
344
  fields.push({
326
345
  id: field.id,
327
- sdkName: keepValidSdkName(existing?.sdkName) ?? toCamelCase(field.name),
346
+ sdkName: keepValidSdkName(existing?.sdkName) ?? toSdkName(field.name),
328
347
  definition,
329
348
  });
330
349
  }
331
350
  tables.push({
332
351
  id: table.id,
333
- sdkName: keepValidSdkName(existingTable?.sdkName) ?? toCamelCase(table.name),
352
+ sdkName: keepValidSdkName(existingTable?.sdkName) ?? toSdkName(table.name),
334
353
  primaryFieldId: table.primaryFieldId,
335
354
  fields,
336
355
  });
@@ -437,7 +456,7 @@ export function generateDbTs(schema) {
437
456
  lines.push("// .findAll({ filters?, sort?, offset?, limit?, fields? }) → { records: T[], hasMore: boolean }");
438
457
  lines.push("// filters: { fieldName: value } for equality, or { fieldName: { <op>: value } }");
439
458
  lines.push("// operators: contains, not, in, notIn, lt, lte, gt, gte");
440
- lines.push('// `not: null` means "is set". An empty `in: []` matches nothing; an', '// empty `notIn: []` applies no filter.');
459
+ lines.push('// `not: null` means "is set". An empty `in: []` matches nothing; an', "// empty `notIn: []` applies no filter.");
441
460
  lines.push("// sort is currently IGNORED here — it is stripped before the request.", "// (it does work on zite.auth.findAllUsers). Order in SQL instead.");
442
461
  lines.push("// limit defaults to 500, max 2000; offset is a row count (a number)");
443
462
  lines.push("// .findOne({ id?, filters?, fields? }) → T | undefined");
@@ -610,12 +629,52 @@ function inspectEndpointFile(source) {
610
629
  * words are legal object keys.
611
630
  */
612
631
  const RESERVED_IDENTIFIERS = new Set([
613
- "await", "break", "case", "catch", "class", "const", "continue", "debugger",
614
- "default", "delete", "do", "else", "enum", "export", "extends", "false",
615
- "finally", "for", "function", "if", "implements", "import", "in",
616
- "instanceof", "interface", "let", "new", "null", "package", "private",
617
- "protected", "public", "return", "static", "super", "switch", "this",
618
- "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield",
632
+ "await",
633
+ "break",
634
+ "case",
635
+ "catch",
636
+ "class",
637
+ "const",
638
+ "continue",
639
+ "debugger",
640
+ "default",
641
+ "delete",
642
+ "do",
643
+ "else",
644
+ "enum",
645
+ "export",
646
+ "extends",
647
+ "false",
648
+ "finally",
649
+ "for",
650
+ "function",
651
+ "if",
652
+ "implements",
653
+ "import",
654
+ "in",
655
+ "instanceof",
656
+ "interface",
657
+ "let",
658
+ "new",
659
+ "null",
660
+ "package",
661
+ "private",
662
+ "protected",
663
+ "public",
664
+ "return",
665
+ "static",
666
+ "super",
667
+ "switch",
668
+ "this",
669
+ "throw",
670
+ "true",
671
+ "try",
672
+ "typeof",
673
+ "var",
674
+ "void",
675
+ "while",
676
+ "with",
677
+ "yield",
619
678
  ]);
620
679
  const toSafeIdentifier = (camelName) => RESERVED_IDENTIFIERS.has(camelName) ? `_${camelName}` : camelName;
621
680
  export function generateApiTs(endpointFiles) {
@@ -945,7 +1004,7 @@ export function generateAirtableTs(lock) {
945
1004
  '// each carries its current user-facing name in quotes (// "Task Name"), which is',
946
1005
  "// the source of truth for what it means.",
947
1006
  "// Values are RAW; format them for display using each field's comment.",
948
- "// Fields marked \"Links to X\" hold Airtable record ids (rec...) from",
1007
+ '// Fields marked "Links to X" hold Airtable record ids (rec...) from',
949
1008
  "// findOne/findAll — never invent one.",
950
1009
  "// Read-only fields are absent from the *RecordInput types: Airtable rejects a",
951
1010
  "// write to a computed one (formula, rollup, lookup, autonumber, the created/",
@@ -1056,11 +1115,31 @@ export function generateBackendWrapperTs(envVarNames = []) {
1056
1115
  " * compile and throw. `Omit` keeps any index signature merged in by",
1057
1116
  " * `.zite/user-extensions.d.ts`, so a migrated user-sync app still reads its",
1058
1117
  " * synced columns off this.",
1118
+ " *",
1119
+ " * The `Pick` intersection is not redundant. On an app that DOES merge in",
1120
+ " * `[key: string]: any`, `keyof User` widens to `string`, `Exclude` then",
1121
+ " * removes nothing, and the `Omit` collapses to a bare index signature —",
1122
+ " * which would silently degrade `user.id` from `string` to `any`. The `Pick`",
1123
+ " * names those members explicitly, so they survive the collapse.",
1124
+ " *",
1125
+ " * Only `id` and `email` are pinned that way, deliberately. On a user-sync",
1126
+ " * app `context.user` IS the synced row (`buildUserContext` returns it",
1127
+ " * whole), and only those two are guaranteed by the runtime: the id is the",
1128
+ " * record id and the email is merged in explicitly. `firstName`/`lastName`",
1129
+ " * come from whatever the app mapped those columns to, so pinning them to",
1130
+ " * `string` would reject a legitimate read on a table that types them",
1131
+ " * differently. Non-widened apps get both from the `Omit` regardless.",
1132
+ " *",
1133
+ " * On such an app `user.createdAt` still compiles, as `any`. That is",
1134
+ " * deliberate: a synced user table may legitimately have its own",
1135
+ " * `createdAt` column, and typing it `never` to close the hole would break",
1136
+ " * exactly the legacy apps `user-extensions.d.ts` exists to keep compiling.",
1059
1137
  " */",
1060
1138
  "export type ZiteEndpointUser = Omit<",
1061
1139
  " User,",
1062
1140
  " 'name' | 'emailVerified' | 'image' | 'createdAt' | 'updatedAt'",
1063
- ">;",
1141
+ "> &",
1142
+ " Pick<User, 'id' | 'email'>;",
1064
1143
  "",
1065
1144
  'export interface ZiteRequestContext extends Omit<_ZiteRequestContext, "user"> {',
1066
1145
  " user: ZiteEndpointUser;",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zitejs",
3
- "version": "0.9.101",
3
+ "version": "0.9.103",
4
4
  "description": "The Zite framework — build apps on Zite Database",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createCaller = void 0;
4
- var index_js_1 = require("../caller/index.js");
5
- Object.defineProperty(exports, "createCaller", { enumerable: true, get: function () { return index_js_1.createCaller; } });
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createTableClient = void 0;
4
- var index_js_1 = require("../runtime/index.js");
5
- Object.defineProperty(exports, "createTableClient", { enumerable: true, get: function () { return index_js_1.createTableClient; } });
@@ -1,2 +0,0 @@
1
- export { createCaller } from '../caller/index.js';
2
- export type { EndpointConfig } from '../caller/index.js';
@@ -1 +0,0 @@
1
- export { createCaller } from '../caller/index.js';
@@ -1,2 +0,0 @@
1
- export { createTableClient } from '../runtime/index.js';
2
- export type { TableClient } from '../runtime/index.js';
@@ -1 +0,0 @@
1
- export { createTableClient } from '../runtime/index.js';