zitejs 0.9.104 → 0.9.106

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.
@@ -7,6 +7,7 @@ exports.logout = logout;
7
7
  exports.updateProfile = updateProfile;
8
8
  const react_1 = require("better-auth/react");
9
9
  const plugins_1 = require("better-auth/client/plugins");
10
+ const constants_js_1 = require("./constants.js");
10
11
  const authClient = (0, react_1.createAuthClient)({
11
12
  baseURL: '',
12
13
  plugins: [
@@ -57,10 +58,21 @@ function loginWithRedirect(opts) {
57
58
  // the address bar of every logged-out visitor to the front page, which is the
58
59
  // common case. Only pass a destination when it IS one.
59
60
  const isRoot = target.pathname === '/' && target.search === '' && target.hash === '';
60
- window.location.href = isRoot
61
- ? '/auth/login'
62
- : '/auth/login?' +
63
- new URLSearchParams({ redirectUrl: target.toString() }).toString();
61
+ const params = new URLSearchParams();
62
+ if (!isRoot)
63
+ params.set('redirectUrl', target.toString());
64
+ // The editor preview rides its usageToken on the app URL; the sign-in page
65
+ // only shows its editor surface ("Preview as" picker) when that token reaches
66
+ // it. Capturing the current URL carries it implicitly — but an explicit
67
+ // `redirectUrl` names a bare path, and would silently drop it. Forward it
68
+ // top-level either way, so how the app phrases its redirect can't decide
69
+ // whether the editor gets a preview. Published visitors never have one.
70
+ const usageToken = new URL(window.location.href).searchParams.get(constants_js_1.USAGE_TOKEN_QUERY_PARAM) ??
71
+ window._ziteUsageToken;
72
+ if (usageToken)
73
+ params.set(constants_js_1.USAGE_TOKEN_QUERY_PARAM, usageToken);
74
+ const query = params.toString();
75
+ window.location.href = query ? `/auth/login?${query}` : '/auth/login';
64
76
  }
65
77
  function logout(opts) {
66
78
  (0, exports.signOut)().then(() => {
@@ -147,4 +147,43 @@ function redirectParamOf(href) {
147
147
  (0, vitest_1.expect)(params.get('view')).toBeNull();
148
148
  (0, vitest_1.expect)(params.get('redirectUrl')).toBe('https://app.zite.so/pricing');
149
149
  });
150
+ (0, vitest_1.it)('forwards the editor usageToken past an explicit redirectUrl', () => {
151
+ // The editor preview rides its token on the app URL. An explicit
152
+ // redirectUrl names a bare path, which used to silently drop it — and with
153
+ // it the sign-in page's whole editor surface ("Preview as" picker).
154
+ const location = stubLocation('https://app.zite.so/?usageToken=tok-123');
155
+ authExports.loginWithRedirect({ redirectUrl: '/dashboard' });
156
+ const params = new URLSearchParams(location.href.split('?')[1] ?? '');
157
+ (0, vitest_1.expect)(params.get('usageToken')).toBe('tok-123');
158
+ (0, vitest_1.expect)(params.get('redirectUrl')).toBe('https://app.zite.so/dashboard');
159
+ });
160
+ (0, vitest_1.it)('forwards the usageToken top-level on a bare call too', () => {
161
+ const location = stubLocation('https://app.zite.so/orders/123?usageToken=tok-123');
162
+ authExports.loginWithRedirect();
163
+ const params = new URLSearchParams(location.href.split('?')[1] ?? '');
164
+ (0, vitest_1.expect)(params.get('usageToken')).toBe('tok-123');
165
+ });
166
+ (0, vitest_1.it)('carries a token to a root destination that would otherwise say nothing', () => {
167
+ const location = stubLocation('https://app.zite.so/?usageToken=tok-123');
168
+ // The token is the whole search string here only if nothing else rides
169
+ // along — an explicit root override drops the query, exercising the
170
+ // token-only branch.
171
+ authExports.loginWithRedirect({ redirectUrl: '/' });
172
+ (0, vitest_1.expect)(location.href).toBe('/auth/login?usageToken=tok-123');
173
+ });
174
+ (0, vitest_1.it)('falls back to window._ziteUsageToken when the URL was scrubbed', () => {
175
+ // app-runner's injected boot script moves the token off the address bar
176
+ // into this global before the bundle runs.
177
+ const location = stubLocation('https://app.zite.so/pricing');
178
+ window._ziteUsageToken = 'tok-456';
179
+ authExports.loginWithRedirect({ redirectUrl: '/dashboard' });
180
+ const params = new URLSearchParams(location.href.split('?')[1] ?? '');
181
+ (0, vitest_1.expect)(params.get('usageToken')).toBe('tok-456');
182
+ });
183
+ (0, vitest_1.it)('adds no token param for ordinary visitors', () => {
184
+ const location = stubLocation('https://app.zite.so/pricing');
185
+ authExports.loginWithRedirect();
186
+ const params = new URLSearchParams(location.href.split('?')[1] ?? '');
187
+ (0, vitest_1.expect)(params.get('usageToken')).toBeNull();
188
+ });
150
189
  });
@@ -14,18 +14,7 @@ export type ZiteSchemaTable = {
14
14
  export type ZiteSchema = {
15
15
  tables: ZiteSchemaTable[];
16
16
  };
17
- export declare function toPascalCase(name: string): string;
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;
17
+ export { toPascalCase, toCamelCase, toSdkName } from "./sdkNames.js";
29
18
  /**
30
19
  * Build a ZiteSchema from a Database API response.
31
20
  *
@@ -39,7 +28,7 @@ export declare function generateSchema(database: Database, existingSchema?: Zite
39
28
  * Generate .zite/db.ts purely from ZiteSchema.
40
29
  * Pure function — no external data needed beyond what's in the schema file.
41
30
  */
42
- export declare function generateDbTs(schema: ZiteSchema): string;
31
+ export declare function generateDbTs(inputSchema: ZiteSchema): string;
43
32
  export type EndpointFileInfo = {
44
33
  fileName: string;
45
34
  content?: string;
@@ -1,8 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toPascalCase = toPascalCase;
4
- exports.toCamelCase = toCamelCase;
5
- exports.toSdkName = toSdkName;
3
+ exports.toSdkName = exports.toCamelCase = exports.toPascalCase = void 0;
6
4
  exports.generateSchema = generateSchema;
7
5
  exports.generateDbTs = generateDbTs;
8
6
  exports.generateApiTs = generateApiTs;
@@ -10,6 +8,7 @@ exports.generateAirtableTs = generateAirtableTs;
10
8
  exports.generateBackendWrapperTs = generateBackendWrapperTs;
11
9
  exports.generateEmailSdk = generateEmailSdk;
12
10
  const parser_1 = require("@babel/parser");
11
+ const sdkNames_js_1 = require("./sdkNames.js");
13
12
  const AUTH_USERS_TABLE_ID = "zite_user";
14
13
  /**
15
14
  * What a field's value looks like when a record is READ back.
@@ -181,47 +180,12 @@ const withDateFormatExample = (format) => {
181
180
  ? `"${format}" format (e.g. ${example})`
182
181
  : `"${format}" format`;
183
182
  };
184
- function toPascalCase(name) {
185
- const pascal = name
186
- .replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase())
187
- .replace(/^(.)/, (_, c) => c.toUpperCase())
188
- // Drop leftover non-alphanumerics (e.g. a trailing ">" from "</p>") so the
189
- // sdkName is always a valid identifier in .zite/db.ts.
190
- .replace(/[^a-zA-Z0-9]+/g, "");
191
- if (!pascal)
192
- return "_";
193
- return /^[0-9]/.test(pascal) ? `_${pascal}` : pascal; // can't start with a digit
194
- }
195
- function toCamelCase(name) {
196
- const pascal = toPascalCase(name);
197
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
198
- }
199
- /**
200
- * camelCase for a FRESH sdkName, reading acronym runs as words: "VIP" ->
201
- * "vip", "APIKey" -> "apiKey" (plain toCamelCase yields "vIP" / "aPIKey").
202
- *
203
- * Deliberately not folded into toCamelCase: that also derives endpoint
204
- * identifiers from existing filenames on every generate, where normalizing
205
- * would rename a working app's `api.sendSMS`. This is only for names being
206
- * chosen for the first time — generateSchema preserves existing sdkNames.
207
- */
208
- function toSdkName(name) {
209
- const deAcronymed = name
210
- .replace(/([A-Z]+)([A-Z][a-z])/g, (_, run, next) => run.charAt(0) + run.slice(1).toLowerCase() + next)
211
- .replace(/([A-Z])([A-Z]+)/g, (_, first, rest) => first + rest.toLowerCase());
212
- return toCamelCase(deAcronymed);
213
- }
214
- /**
215
- * Existing sdkNames are preserved across syncs so user code keeps compiling,
216
- * but schemas written before the sanitizer stripped trailing symbols can carry
217
- * invalid identifiers (e.g. "timeSeconds)"). Keep an existing name only when
218
- * it's a valid identifier; otherwise fall through to recomputing it.
219
- */
220
- function keepValidSdkName(sdkName) {
221
- if (!sdkName)
222
- return undefined;
223
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(sdkName) ? sdkName : undefined;
224
- }
183
+ // Owned by ./sdkNames.ts (which must not import back into this module);
184
+ // re-exported so the published surface is unchanged.
185
+ var sdkNames_js_2 = require("./sdkNames.js");
186
+ Object.defineProperty(exports, "toPascalCase", { enumerable: true, get: function () { return sdkNames_js_2.toPascalCase; } });
187
+ Object.defineProperty(exports, "toCamelCase", { enumerable: true, get: function () { return sdkNames_js_2.toCamelCase; } });
188
+ Object.defineProperty(exports, "toSdkName", { enumerable: true, get: function () { return sdkNames_js_2.toSdkName; } });
225
189
  /**
226
190
  * `null` is how you CLEAR a cell — every scalar write schema in
227
191
  * `flexible-inputs.ts` ends in `.nullable()` and update runs with
@@ -364,7 +328,7 @@ function generateSchema(database, existingSchema) {
364
328
  for (const t of existingSchema?.tables ?? []) {
365
329
  existingTableById.set(t.id, t);
366
330
  }
367
- const tables = [];
331
+ const drafts = [];
368
332
  for (const table of database.tables) {
369
333
  if (table.id === AUTH_USERS_TABLE_ID)
370
334
  continue;
@@ -373,23 +337,44 @@ function generateSchema(database, existingSchema) {
373
337
  for (const f of existingTable?.fields ?? []) {
374
338
  existingFieldById.set(f.id, f);
375
339
  }
376
- const fields = [];
377
- for (const field of table.fields) {
378
- const existing = existingFieldById.get(field.id);
340
+ // `sdkName` is filled in by `allocateSchemaNames` below; the draft carries
341
+ // what each name would like to be and whether user code already depends on
342
+ // it. A name from the existing schema is locked — it is what the app is
343
+ // compiling against — and only moves if it is reserved or collides.
344
+ const fields = table.fields.map((field) => {
345
+ const existing = (0, sdkNames_js_1.keepValidSdkName)(existingFieldById.get(field.id)?.sdkName);
379
346
  const { id: _id, order: _order, ...definition } = field;
380
- fields.push({
381
- id: field.id,
382
- sdkName: keepValidSdkName(existing?.sdkName) ?? toSdkName(field.name),
383
- definition,
384
- });
385
- }
386
- tables.push({
387
- id: table.id,
388
- sdkName: keepValidSdkName(existingTable?.sdkName) ?? toSdkName(table.name),
389
- primaryFieldId: table.primaryFieldId,
347
+ return {
348
+ field: { id: field.id, sdkName: "", definition },
349
+ draft: {
350
+ preferred: existing ?? (0, sdkNames_js_1.toSdkName)(field.name),
351
+ locked: existing !== undefined,
352
+ },
353
+ };
354
+ });
355
+ const existingName = (0, sdkNames_js_1.keepValidSdkName)(existingTable?.sdkName);
356
+ drafts.push({
357
+ table: {
358
+ id: table.id,
359
+ sdkName: "",
360
+ primaryFieldId: table.primaryFieldId,
361
+ fields: [],
362
+ },
363
+ draft: {
364
+ preferred: existingName ?? (0, sdkNames_js_1.toSdkName)(table.name),
365
+ locked: existingName !== undefined,
366
+ },
390
367
  fields,
391
368
  });
392
369
  }
370
+ const { tables, renames } = (0, sdkNames_js_1.allocateSchemaNames)(drafts);
371
+ if (renames.length > 0) {
372
+ // Never silent: a rename means the accessor a developer would reach for is
373
+ // not the one that got emitted.
374
+ for (const line of (0, sdkNames_js_1.describeRenames)(renames)) {
375
+ console.warn(`[zitejs] SDK name collision resolved: ${line}`);
376
+ }
377
+ }
393
378
  return { tables };
394
379
  }
395
380
  function generateSentinelSdkTypes() {
@@ -445,8 +430,8 @@ function buildLinkTableComments(schema) {
445
430
  const target = tablesById.get(targetId);
446
431
  if (!target)
447
432
  continue;
448
- const sourceSdk = toPascalCase(table.sdkName);
449
- const targetSdk = toPascalCase(target.sdkName);
433
+ const sourceSdk = (0, sdkNames_js_1.toPascalCase)(table.sdkName);
434
+ const targetSdk = (0, sdkNames_js_1.toPascalCase)(target.sdkName);
450
435
  const [first, second] = [sourceSdk, targetSdk].sort();
451
436
  const linkName = `${first}${second}`;
452
437
  if (seen.has(linkName))
@@ -477,8 +462,20 @@ function buildLinkTableComments(schema) {
477
462
  * Generate .zite/db.ts purely from ZiteSchema.
478
463
  * Pure function — no external data needed beyond what's in the schema file.
479
464
  */
480
- function generateDbTs(schema) {
465
+ function generateDbTs(inputSchema) {
481
466
  const lines = [];
467
+ // Defensive, and idempotent: a schema whose names are already unique and
468
+ // unreserved normalizes to itself. It matters for a `zite.schema.json`
469
+ // committed before allocation existed — the sandbox's boot-time
470
+ // `zitejs generate` reads that file directly, with no `generateSchema` pass
471
+ // in front of it, and would otherwise emit an object literal with a
472
+ // duplicate key (TS1117) that no build can recover from.
473
+ const { schema, renames } = (0, sdkNames_js_1.normalizeSchemaNames)(inputSchema);
474
+ if (renames.length > 0) {
475
+ for (const line of (0, sdkNames_js_1.describeRenames)(renames)) {
476
+ console.warn(`[zitejs] Repaired SDK name in zite.schema.json: ${line}`);
477
+ }
478
+ }
482
479
  const tables = schema.tables.filter((table) => table.id !== AUTH_USERS_TABLE_ID);
483
480
  lines.push("// Auto-generated by zitejs generate. Do not edit manually.");
484
481
  lines.push("//");
@@ -542,7 +539,7 @@ function generateDbTs(schema) {
542
539
  lines.push("");
543
540
  lines.push(...ATTACHMENT_TYPES);
544
541
  for (const table of tables) {
545
- const className = toPascalCase(table.sdkName);
542
+ const className = (0, sdkNames_js_1.toPascalCase)(table.sdkName);
546
543
  const recordType = `${className}RecordType`;
547
544
  lines.push(`/** A ${table.sdkName} record as it is read back. */`);
548
545
  lines.push(`export type ${recordType} = {`);
@@ -579,7 +576,7 @@ function generateDbTs(schema) {
579
576
  lines.push(...generateSentinelSdkTypes());
580
577
  lines.push("export const zite = {");
581
578
  for (const table of tables) {
582
- const className = toPascalCase(table.sdkName);
579
+ const className = (0, sdkNames_js_1.toPascalCase)(table.sdkName);
583
580
  lines.push(` ${table.sdkName}: createTableClient<${className}RecordType, ${className}RecordInput>('${className}'),`);
584
581
  }
585
582
  lines.push(` sql: createSqlClient(),`);
@@ -734,7 +731,7 @@ function generateApiTs(endpointFiles) {
734
731
  continue;
735
732
  }
736
733
  const baseName = fileName.replace(/\.(ts|js)$/, "");
737
- const key = toCamelCase(baseName);
734
+ const key = (0, sdkNames_js_1.toCamelCase)(baseName);
738
735
  const ident = toSafeIdentifier(key);
739
736
  // Distinct files can collide on one identifier (`send-email.ts` and
740
737
  // `send_email.ts` both camelCase to `sendEmail`), which used to emit the
@@ -750,7 +747,7 @@ function generateApiTs(endpointFiles) {
750
747
  baseName,
751
748
  key,
752
749
  ident,
753
- pascal: toPascalCase(key),
750
+ pascal: (0, sdkNames_js_1.toPascalCase)(key),
754
751
  stream: shape?.stream ?? false,
755
752
  // No content to inspect means a bare-filename caller, which historically
756
753
  // assumed a default export — keep that assumption.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Allocation of the identifiers `generateDbTs` emits.
3
+ *
4
+ * Every accessor on the generated `zite` object, and every field key on a
5
+ * record type, has to be unique — and `generateSchema` used to choose them with
6
+ * `toSdkName` and no memory at all. Two consequences, both seen in the wild:
7
+ *
8
+ * - A table named "Notifications" allocated `notifications`, which
9
+ * `generateDbTs` also emits for the platform's notifications client. The
10
+ * sentinel is written last, so it won: the table lost its client entirely
11
+ * and typed as `{ create(params: NotificationsCreateParams) }`.
12
+ * - Two fields whose display names normalize alike both claimed one accessor,
13
+ * and the second silently overwrote the first in the emitted type. No
14
+ * error, no diagnostic — just a column you could no longer read. Zite 1.0's
15
+ * generator de-duplicated with numeric suffixes; 2.0 dropped that.
16
+ *
17
+ * `allocateSchemaNames` is idempotent: a schema whose names are already unique
18
+ * and unreserved allocates to itself. That is what lets `generateDbTs` run it
19
+ * defensively over its input — a schema file written before this existed
20
+ * repairs itself on the next generate instead of emitting TypeScript that
21
+ * cannot compile.
22
+ */
23
+ import type { ZiteSchema, ZiteSchemaField, ZiteSchemaTable } from "./lib.js";
24
+ export declare function toPascalCase(name: string): string;
25
+ export declare function toCamelCase(name: string): string;
26
+ /**
27
+ * camelCase for a FRESH sdkName, reading acronym runs as words: "VIP" ->
28
+ * "vip", "APIKey" -> "apiKey" (plain toCamelCase yields "vIP" / "aPIKey").
29
+ *
30
+ * Deliberately not folded into toCamelCase: that also derives endpoint
31
+ * identifiers from existing filenames on every generate, where normalizing
32
+ * would rename a working app's `api.sendSMS`. This is only for names being
33
+ * chosen for the first time — generateSchema preserves existing sdkNames.
34
+ */
35
+ export declare function toSdkName(name: string): string;
36
+ /**
37
+ * Accessors `generateDbTs` puts on the `zite` object itself, after the tables.
38
+ * A table that allocates one of these produces a duplicate key.
39
+ */
40
+ export declare const RESERVED_TABLE_ACCESSORS: readonly ["sql", "notifications", "auth"];
41
+ /**
42
+ * Never safe as a generated key, in any namespace. Assigning `__proto__` in an
43
+ * object literal sets the prototype rather than defining a property, and
44
+ * `constructor` / `prototype` shadow members every object already carries.
45
+ */
46
+ export declare const RESERVED_IDENTIFIERS: readonly ["__proto__", "constructor", "prototype"];
47
+ /**
48
+ * `id` is emitted by hand on every record type and filtered out of every input
49
+ * type, so a field that allocated it vanished from both. Reserving it means a
50
+ * field genuinely named "ID" gets a usable accessor instead.
51
+ */
52
+ export declare const RESERVED_FIELD_ACCESSORS: readonly ["id"];
53
+ /** A name that could not be kept, reported so a rename is never silent. */
54
+ export interface SdkNameRename {
55
+ kind: "table" | "field";
56
+ /** The table this happened in; the table itself for `kind: 'table'`. */
57
+ tableId: string;
58
+ fieldId?: string;
59
+ from: string;
60
+ to: string;
61
+ }
62
+ /**
63
+ * Existing sdkNames are preserved across syncs so user code keeps compiling,
64
+ * but a schema written before the sanitizer stripped trailing symbols can carry
65
+ * an invalid identifier (e.g. `timeSeconds)`). Keep one only when it is a valid
66
+ * identifier; otherwise the caller recomputes it from the display name.
67
+ */
68
+ export declare function keepValidSdkName(sdkName: string | undefined): string | undefined;
69
+ /** What a table/field would like to be called, and how hard it is holding on. */
70
+ interface NameDraft {
71
+ preferred: string;
72
+ /**
73
+ * True when the name is already baked into a committed schema and user code
74
+ * is compiling against it. Locked names are claimed first, so a fresh name
75
+ * can never displace one; a locked name still moves when it is reserved or
76
+ * when two locked names collide, because the alternative is a file that does
77
+ * not compile.
78
+ */
79
+ locked: boolean;
80
+ }
81
+ export interface SchemaNameDraft {
82
+ table: ZiteSchemaTable;
83
+ draft: NameDraft;
84
+ fields: Array<{
85
+ field: ZiteSchemaField;
86
+ draft: NameDraft;
87
+ }>;
88
+ }
89
+ /**
90
+ * Resolve every drafted name to a unique, unreserved one.
91
+ *
92
+ * Two passes per namespace: locked names claim first in declaration order, then
93
+ * fresh ones fill in around them. Without that ordering a new table could take
94
+ * an accessor an existing app already imports.
95
+ */
96
+ export declare function allocateSchemaNames(drafts: SchemaNameDraft[]): {
97
+ tables: ZiteSchemaTable[];
98
+ renames: SdkNameRename[];
99
+ };
100
+ /**
101
+ * Repair a schema whose names may already be unique — the idempotent case — or
102
+ * may predate allocation entirely. Everything is treated as locked: these names
103
+ * are what user code compiles against, so only a genuine conflict moves one.
104
+ */
105
+ export declare function normalizeSchemaNames(schema: ZiteSchema): {
106
+ schema: ZiteSchema;
107
+ renames: SdkNameRename[];
108
+ };
109
+ /** One line per rename, for a generator that has no logger of its own. */
110
+ export declare function describeRenames(renames: SdkNameRename[]): string[];
111
+ export {};