zitejs 0.9.99 → 0.9.101
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/bundle/index.js +6 -5
- package/dist/cjs/runtime/index.d.ts +19 -2
- package/dist/cjs/sync/lib.js +87 -28
- package/dist/esm/bundle/index.js +6 -5
- package/dist/esm/runtime/index.d.ts +19 -2
- package/dist/esm/sync/lib.js +87 -28
- package/package.json +1 -1
package/dist/cjs/bundle/index.js
CHANGED
|
@@ -213,11 +213,12 @@ function getSdkImportSource(baseDir) {
|
|
|
213
213
|
/**
|
|
214
214
|
* `zitejs/db`, `zitejs/api`, `zitejs/integrations` and `zitejs/email` are
|
|
215
215
|
* tsconfig aliases onto generated files, not real package subpaths — they are
|
|
216
|
-
* absent from the `exports` map
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
216
|
+
* absent from the `exports` map. `zitejs/backend` IS a real subpath export, but
|
|
217
|
+
* it lands here too and `zitejs` is not in PREBUNDLED_LIBS, so nothing would
|
|
218
|
+
* resolve it in the worker either. Either way, marking one external doesn't
|
|
219
|
+
* defer resolution: it guarantees a module that fails to instantiate the first
|
|
220
|
+
* time the deployed endpoint is invoked, long after the build reported success.
|
|
221
|
+
* Fail here instead, where the message can name the file that's missing.
|
|
221
222
|
*/
|
|
222
223
|
const unresolvedAlias = (specifier, expectedPath) => {
|
|
223
224
|
throw new Error(`Cannot resolve '${specifier}': expected generated file at ${expectedPath}. ` +
|
|
@@ -90,10 +90,18 @@ export interface TableFindAllOptions<T = Record<string, unknown>> {
|
|
|
90
90
|
*/
|
|
91
91
|
sort?: unknown[];
|
|
92
92
|
filters?: RecordFilters<T>;
|
|
93
|
+
/**
|
|
94
|
+
* The nested filter format (`filters` is the legacy flat one). IGNORED here
|
|
95
|
+
* for the same reason as `sort` — not in `DatabasesFindAllParamsSchema`, so
|
|
96
|
+
* zod strips it — but kept so code already passing it still compiles. It IS
|
|
97
|
+
* honored by `zite.auth.findAllUsers`.
|
|
98
|
+
*/
|
|
99
|
+
filter?: unknown;
|
|
93
100
|
fields?: Array<FieldSelector<T>>;
|
|
94
101
|
}
|
|
95
102
|
export interface BulkCreateResult<T> {
|
|
96
|
-
|
|
103
|
+
/** Absent when `records: []` was passed — the dispatch short-circuits before setting it. */
|
|
104
|
+
success?: boolean;
|
|
97
105
|
records: T[];
|
|
98
106
|
}
|
|
99
107
|
export interface UpdateResult<T> {
|
|
@@ -173,7 +181,16 @@ export type FindAllAuthUsersResult<T extends AuthUser = AuthUser> = {
|
|
|
173
181
|
total: number;
|
|
174
182
|
hasMore: boolean;
|
|
175
183
|
};
|
|
176
|
-
|
|
184
|
+
/**
|
|
185
|
+
* `null` clears a value — `publicUpdateAuthUserInputSchema` accepts
|
|
186
|
+
* `string | null | undefined` on each of these, and null is the only way to
|
|
187
|
+
* unset a name.
|
|
188
|
+
*/
|
|
189
|
+
export type UpdateAuthUserProfile = {
|
|
190
|
+
firstName?: string | null;
|
|
191
|
+
lastName?: string | null;
|
|
192
|
+
image?: string | null;
|
|
193
|
+
};
|
|
177
194
|
export interface AuthClient<T extends AuthUser = AuthUser> {
|
|
178
195
|
findAllUsers(options?: FindAllAuthUsersOptions): Promise<FindAllAuthUsersResult<T>>;
|
|
179
196
|
updateUserProfile(userId: string, profile: UpdateAuthUserProfile): Promise<T>;
|
package/dist/cjs/sync/lib.js
CHANGED
|
@@ -10,33 +10,47 @@ exports.generateBackendWrapperTs = generateBackendWrapperTs;
|
|
|
10
10
|
exports.generateEmailSdk = generateEmailSdk;
|
|
11
11
|
const parser_1 = require("@babel/parser");
|
|
12
12
|
const AUTH_USERS_TABLE_ID = "zite_user";
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* What a field's value looks like when a record is READ back.
|
|
15
|
+
*
|
|
16
|
+
* `| null` on the nullable ones is load-bearing, not pedantry: every field
|
|
17
|
+
* column is created nullable with no default (`recordsTableManager`), and only
|
|
18
|
+
* the six text types get their NULL rewritten to `''` on the way out
|
|
19
|
+
* (`normalizeEmptyStringFields` keys on `emptyValue === ''`). The product's own
|
|
20
|
+
* test asserts `fld_number` and `fld_checkbox` read back as `null`. Typing them
|
|
21
|
+
* non-null let `task.estimate.toFixed(2)` compile and throw.
|
|
22
|
+
*/
|
|
14
23
|
const FIELD_TYPE_MAP = {
|
|
24
|
+
// Text: NULL is rewritten to '' on read, so these genuinely can't be null.
|
|
15
25
|
single_line_text: "string",
|
|
16
26
|
long_text: "string",
|
|
17
27
|
rich_text: "string",
|
|
18
28
|
email: "string",
|
|
19
29
|
url: "string",
|
|
20
30
|
phone_number: "string",
|
|
21
|
-
number: "number",
|
|
22
|
-
currency: "number",
|
|
23
|
-
percent: "number",
|
|
24
|
-
rating: "number",
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
number: "number | null",
|
|
32
|
+
currency: "number | null",
|
|
33
|
+
percent: "number | null",
|
|
34
|
+
rating: "number | null",
|
|
35
|
+
// DECIMAL seconds, unlike Airtable's, which is formatted to "HH:mm:ss".
|
|
36
|
+
duration: "number | null",
|
|
37
|
+
checkbox: "boolean | null",
|
|
38
|
+
single_select: "string | null",
|
|
39
|
+
multiple_select: "string[] | null",
|
|
29
40
|
date: "string | null",
|
|
30
41
|
datetime: "string | null",
|
|
31
|
-
attachments: "ZiteAttachment[]",
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
attachments: "ZiteAttachment[] | null",
|
|
43
|
+
// `string | string[]`, matching the pre-monorepo type. base-runner does force
|
|
44
|
+
// an array on read today, but 1.0 code is written with `typeof x === 'string'`
|
|
45
|
+
// guards — narrowing to `string[]` turns those branches into `never` and
|
|
46
|
+
// fails the app's typecheck for no gain.
|
|
47
|
+
linked_record: "string | string[]",
|
|
48
|
+
user: "string | string[]",
|
|
36
49
|
lookup: "unknown",
|
|
37
50
|
rollup: "unknown",
|
|
38
51
|
autonumber: "number",
|
|
39
|
-
|
|
52
|
+
// JSONB change metadata (`{ type: "PublicAPI", apiKeyId: 3 }`), not a string.
|
|
53
|
+
source: "unknown",
|
|
40
54
|
formula: "unknown",
|
|
41
55
|
created_at: "string",
|
|
42
56
|
updated_at: "string",
|
|
@@ -47,9 +61,18 @@ const FIELD_TYPE_MAP = {
|
|
|
47
61
|
* looser input than it stores.
|
|
48
62
|
*/
|
|
49
63
|
const FIELD_INPUT_TYPE_MAP = {
|
|
50
|
-
attachments: "ZiteAttachmentInput[]",
|
|
51
|
-
linked_record: "string | string[]
|
|
52
|
-
user: "string | string[]
|
|
64
|
+
attachments: "ZiteAttachmentInput[] | ZiteAttachmentInput",
|
|
65
|
+
linked_record: "string | string[]",
|
|
66
|
+
user: "string | string[]",
|
|
67
|
+
multiple_select: "string | string[]",
|
|
68
|
+
date: "string | Date",
|
|
69
|
+
datetime: "string | Date",
|
|
70
|
+
number: "string | number",
|
|
71
|
+
currency: "string | number",
|
|
72
|
+
percent: "string | number",
|
|
73
|
+
rating: "string | number",
|
|
74
|
+
duration: "string | number",
|
|
75
|
+
checkbox: "string | boolean",
|
|
53
76
|
};
|
|
54
77
|
/**
|
|
55
78
|
* Computed and system-managed fields, omitted from the generated input type
|
|
@@ -156,6 +179,13 @@ function keepValidSdkName(sdkName) {
|
|
|
156
179
|
return undefined;
|
|
157
180
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(sdkName) ? sdkName : undefined;
|
|
158
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* `null` is how you CLEAR a cell — every scalar write schema in
|
|
184
|
+
* `flexible-inputs.ts` ends in `.nullable()` and update runs with
|
|
185
|
+
* `allowExplicitNull`. A write type without it makes the documented way to
|
|
186
|
+
* empty a field a compile error.
|
|
187
|
+
*/
|
|
188
|
+
const withNull = (type) => type.includes("null") ? type : `${type} | null`;
|
|
159
189
|
function tsTypeForSchemaField(def, variant = "read") {
|
|
160
190
|
if (def.type === "single_select" || def.type === "multiple_select") {
|
|
161
191
|
const options = def.template
|
|
@@ -166,13 +196,19 @@ function tsTypeForSchemaField(def, variant = "read") {
|
|
|
166
196
|
.map((o) => `"${o.label.replace(/"/g, '\\"')}"`)
|
|
167
197
|
.join(" | ");
|
|
168
198
|
const union = `${literals} | string`;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
199
|
+
const read = def.type === "multiple_select" ? `(${union})[] | null` : `${union} | null`;
|
|
200
|
+
if (variant === "write") {
|
|
201
|
+
return def.type === "multiple_select"
|
|
202
|
+
? `${union} | (${union})[] | null`
|
|
203
|
+
: `${union} | null`;
|
|
204
|
+
}
|
|
205
|
+
return read;
|
|
172
206
|
}
|
|
173
207
|
}
|
|
174
|
-
if (variant === "write"
|
|
175
|
-
|
|
208
|
+
if (variant === "write") {
|
|
209
|
+
const write = FIELD_INPUT_TYPE_MAP[def.type] ?? FIELD_TYPE_MAP[def.type];
|
|
210
|
+
if (write)
|
|
211
|
+
return withNull(write);
|
|
176
212
|
}
|
|
177
213
|
if (FIELD_TYPE_MAP[def.type])
|
|
178
214
|
return FIELD_TYPE_MAP[def.type];
|
|
@@ -474,7 +510,11 @@ function generateDbTs(schema) {
|
|
|
474
510
|
if (jsdoc) {
|
|
475
511
|
lines.push(` /** ${jsdoc} */`);
|
|
476
512
|
}
|
|
477
|
-
|
|
513
|
+
// Optional, matching the pre-monorepo type (`required: ['id']` — "none of
|
|
514
|
+
// these are required to be defined aside from 'id'"). A `fields:`
|
|
515
|
+
// projection omits every key it wasn't asked for, so any field really can
|
|
516
|
+
// be absent on a read.
|
|
517
|
+
lines.push(` ${field.sdkName}?: ${tsType};`);
|
|
478
518
|
}
|
|
479
519
|
lines.push("};");
|
|
480
520
|
lines.push("");
|
|
@@ -712,14 +752,18 @@ const AIRTABLE_FIELD_TYPE_MAP = {
|
|
|
712
752
|
currency: "number",
|
|
713
753
|
percent: "number",
|
|
714
754
|
rating: "number",
|
|
715
|
-
|
|
755
|
+
// Formatted to "HH:mm:ss" before it reaches the app — `formatAirtableDuration`
|
|
756
|
+
// in BOTH dispatches. Unlike the Zite DB's, which is DECIMAL seconds.
|
|
757
|
+
duration: "string",
|
|
716
758
|
checkbox: "boolean",
|
|
717
759
|
date: "string",
|
|
718
760
|
dateTime: "string",
|
|
719
761
|
singleSelect: "string",
|
|
720
762
|
multipleSelects: "string[]",
|
|
721
763
|
multipleAttachments: "AirtableAttachment[]",
|
|
722
|
-
|
|
764
|
+
// `string | string[]`, matching the pre-monorepo type — see the note on the
|
|
765
|
+
// Zite DB's `linked_record`.
|
|
766
|
+
multipleRecordLinks: "string | string[]",
|
|
723
767
|
singleCollaborator: "{ id: string; email: string; name?: string }",
|
|
724
768
|
multipleCollaborators: "Array<{ id: string; email: string; name?: string }>",
|
|
725
769
|
// Read-only fields
|
|
@@ -732,7 +776,8 @@ const AIRTABLE_FIELD_TYPE_MAP = {
|
|
|
732
776
|
lastModifiedTime: "string",
|
|
733
777
|
createdBy: "{ id: string; email: string; name?: string }",
|
|
734
778
|
lastModifiedBy: "{ id: string; email: string; name?: string }",
|
|
735
|
-
button
|
|
779
|
+
// Both dispatches extract the URL string off the button object.
|
|
780
|
+
button: "string",
|
|
736
781
|
externalSyncSource: "unknown",
|
|
737
782
|
aiText: "string",
|
|
738
783
|
};
|
|
@@ -1013,8 +1058,22 @@ function generateBackendWrapperTs(envVarNames = []) {
|
|
|
1013
1058
|
// Narrows `user` to this app's generated User. ZiteScheduledContext is
|
|
1014
1059
|
// re-exported unchanged — a scheduled fire has no user at all, so there is
|
|
1015
1060
|
// nothing to narrow.
|
|
1061
|
+
"/**",
|
|
1062
|
+
" * The user an endpoint sees. NOT the full `User` from zitejs/auth — that is",
|
|
1063
|
+
" * the browser's better-auth session, and the runtime never sends its",
|
|
1064
|
+
" * `name`, `emailVerified`, `image`, `createdAt` or `updatedAt` to an",
|
|
1065
|
+
" * endpoint. Typing those here let `context.user.createdAt.getFullYear()`",
|
|
1066
|
+
" * compile and throw. `Omit` keeps any index signature merged in by",
|
|
1067
|
+
" * `.zite/user-extensions.d.ts`, so a migrated user-sync app still reads its",
|
|
1068
|
+
" * synced columns off this.",
|
|
1069
|
+
" */",
|
|
1070
|
+
"export type ZiteEndpointUser = Omit<",
|
|
1071
|
+
" User,",
|
|
1072
|
+
" 'name' | 'emailVerified' | 'image' | 'createdAt' | 'updatedAt'",
|
|
1073
|
+
">;",
|
|
1074
|
+
"",
|
|
1016
1075
|
'export interface ZiteRequestContext extends Omit<_ZiteRequestContext, "user"> {',
|
|
1017
|
-
" user:
|
|
1076
|
+
" user: ZiteEndpointUser;",
|
|
1018
1077
|
"}",
|
|
1019
1078
|
"",
|
|
1020
1079
|
"type SchemaLike<TOut, TIn = TOut> = { _output: TOut; _input: TIn; parse: (data: unknown) => TOut };",
|
package/dist/esm/bundle/index.js
CHANGED
|
@@ -177,11 +177,12 @@ function getSdkImportSource(baseDir) {
|
|
|
177
177
|
/**
|
|
178
178
|
* `zitejs/db`, `zitejs/api`, `zitejs/integrations` and `zitejs/email` are
|
|
179
179
|
* tsconfig aliases onto generated files, not real package subpaths — they are
|
|
180
|
-
* absent from the `exports` map
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
180
|
+
* absent from the `exports` map. `zitejs/backend` IS a real subpath export, but
|
|
181
|
+
* it lands here too and `zitejs` is not in PREBUNDLED_LIBS, so nothing would
|
|
182
|
+
* resolve it in the worker either. Either way, marking one external doesn't
|
|
183
|
+
* defer resolution: it guarantees a module that fails to instantiate the first
|
|
184
|
+
* time the deployed endpoint is invoked, long after the build reported success.
|
|
185
|
+
* Fail here instead, where the message can name the file that's missing.
|
|
185
186
|
*/
|
|
186
187
|
const unresolvedAlias = (specifier, expectedPath) => {
|
|
187
188
|
throw new Error(`Cannot resolve '${specifier}': expected generated file at ${expectedPath}. ` +
|
|
@@ -90,10 +90,18 @@ export interface TableFindAllOptions<T = Record<string, unknown>> {
|
|
|
90
90
|
*/
|
|
91
91
|
sort?: unknown[];
|
|
92
92
|
filters?: RecordFilters<T>;
|
|
93
|
+
/**
|
|
94
|
+
* The nested filter format (`filters` is the legacy flat one). IGNORED here
|
|
95
|
+
* for the same reason as `sort` — not in `DatabasesFindAllParamsSchema`, so
|
|
96
|
+
* zod strips it — but kept so code already passing it still compiles. It IS
|
|
97
|
+
* honored by `zite.auth.findAllUsers`.
|
|
98
|
+
*/
|
|
99
|
+
filter?: unknown;
|
|
93
100
|
fields?: Array<FieldSelector<T>>;
|
|
94
101
|
}
|
|
95
102
|
export interface BulkCreateResult<T> {
|
|
96
|
-
|
|
103
|
+
/** Absent when `records: []` was passed — the dispatch short-circuits before setting it. */
|
|
104
|
+
success?: boolean;
|
|
97
105
|
records: T[];
|
|
98
106
|
}
|
|
99
107
|
export interface UpdateResult<T> {
|
|
@@ -173,7 +181,16 @@ export type FindAllAuthUsersResult<T extends AuthUser = AuthUser> = {
|
|
|
173
181
|
total: number;
|
|
174
182
|
hasMore: boolean;
|
|
175
183
|
};
|
|
176
|
-
|
|
184
|
+
/**
|
|
185
|
+
* `null` clears a value — `publicUpdateAuthUserInputSchema` accepts
|
|
186
|
+
* `string | null | undefined` on each of these, and null is the only way to
|
|
187
|
+
* unset a name.
|
|
188
|
+
*/
|
|
189
|
+
export type UpdateAuthUserProfile = {
|
|
190
|
+
firstName?: string | null;
|
|
191
|
+
lastName?: string | null;
|
|
192
|
+
image?: string | null;
|
|
193
|
+
};
|
|
177
194
|
export interface AuthClient<T extends AuthUser = AuthUser> {
|
|
178
195
|
findAllUsers(options?: FindAllAuthUsersOptions): Promise<FindAllAuthUsersResult<T>>;
|
|
179
196
|
updateUserProfile(userId: string, profile: UpdateAuthUserProfile): Promise<T>;
|
package/dist/esm/sync/lib.js
CHANGED
|
@@ -1,32 +1,46 @@
|
|
|
1
1
|
import { parse } from "@babel/parser";
|
|
2
2
|
const AUTH_USERS_TABLE_ID = "zite_user";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* What a field's value looks like when a record is READ back.
|
|
5
|
+
*
|
|
6
|
+
* `| null` on the nullable ones is load-bearing, not pedantry: every field
|
|
7
|
+
* column is created nullable with no default (`recordsTableManager`), and only
|
|
8
|
+
* the six text types get their NULL rewritten to `''` on the way out
|
|
9
|
+
* (`normalizeEmptyStringFields` keys on `emptyValue === ''`). The product's own
|
|
10
|
+
* test asserts `fld_number` and `fld_checkbox` read back as `null`. Typing them
|
|
11
|
+
* non-null let `task.estimate.toFixed(2)` compile and throw.
|
|
12
|
+
*/
|
|
4
13
|
const FIELD_TYPE_MAP = {
|
|
14
|
+
// Text: NULL is rewritten to '' on read, so these genuinely can't be null.
|
|
5
15
|
single_line_text: "string",
|
|
6
16
|
long_text: "string",
|
|
7
17
|
rich_text: "string",
|
|
8
18
|
email: "string",
|
|
9
19
|
url: "string",
|
|
10
20
|
phone_number: "string",
|
|
11
|
-
number: "number",
|
|
12
|
-
currency: "number",
|
|
13
|
-
percent: "number",
|
|
14
|
-
rating: "number",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
number: "number | null",
|
|
22
|
+
currency: "number | null",
|
|
23
|
+
percent: "number | null",
|
|
24
|
+
rating: "number | null",
|
|
25
|
+
// DECIMAL seconds, unlike Airtable's, which is formatted to "HH:mm:ss".
|
|
26
|
+
duration: "number | null",
|
|
27
|
+
checkbox: "boolean | null",
|
|
28
|
+
single_select: "string | null",
|
|
29
|
+
multiple_select: "string[] | null",
|
|
19
30
|
date: "string | null",
|
|
20
31
|
datetime: "string | null",
|
|
21
|
-
attachments: "ZiteAttachment[]",
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
attachments: "ZiteAttachment[] | null",
|
|
33
|
+
// `string | string[]`, matching the pre-monorepo type. base-runner does force
|
|
34
|
+
// an array on read today, but 1.0 code is written with `typeof x === 'string'`
|
|
35
|
+
// guards — narrowing to `string[]` turns those branches into `never` and
|
|
36
|
+
// fails the app's typecheck for no gain.
|
|
37
|
+
linked_record: "string | string[]",
|
|
38
|
+
user: "string | string[]",
|
|
26
39
|
lookup: "unknown",
|
|
27
40
|
rollup: "unknown",
|
|
28
41
|
autonumber: "number",
|
|
29
|
-
|
|
42
|
+
// JSONB change metadata (`{ type: "PublicAPI", apiKeyId: 3 }`), not a string.
|
|
43
|
+
source: "unknown",
|
|
30
44
|
formula: "unknown",
|
|
31
45
|
created_at: "string",
|
|
32
46
|
updated_at: "string",
|
|
@@ -37,9 +51,18 @@ const FIELD_TYPE_MAP = {
|
|
|
37
51
|
* looser input than it stores.
|
|
38
52
|
*/
|
|
39
53
|
const FIELD_INPUT_TYPE_MAP = {
|
|
40
|
-
attachments: "ZiteAttachmentInput[]",
|
|
41
|
-
linked_record: "string | string[]
|
|
42
|
-
user: "string | string[]
|
|
54
|
+
attachments: "ZiteAttachmentInput[] | ZiteAttachmentInput",
|
|
55
|
+
linked_record: "string | string[]",
|
|
56
|
+
user: "string | string[]",
|
|
57
|
+
multiple_select: "string | string[]",
|
|
58
|
+
date: "string | Date",
|
|
59
|
+
datetime: "string | Date",
|
|
60
|
+
number: "string | number",
|
|
61
|
+
currency: "string | number",
|
|
62
|
+
percent: "string | number",
|
|
63
|
+
rating: "string | number",
|
|
64
|
+
duration: "string | number",
|
|
65
|
+
checkbox: "string | boolean",
|
|
43
66
|
};
|
|
44
67
|
/**
|
|
45
68
|
* Computed and system-managed fields, omitted from the generated input type
|
|
@@ -146,6 +169,13 @@ function keepValidSdkName(sdkName) {
|
|
|
146
169
|
return undefined;
|
|
147
170
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(sdkName) ? sdkName : undefined;
|
|
148
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* `null` is how you CLEAR a cell — every scalar write schema in
|
|
174
|
+
* `flexible-inputs.ts` ends in `.nullable()` and update runs with
|
|
175
|
+
* `allowExplicitNull`. A write type without it makes the documented way to
|
|
176
|
+
* empty a field a compile error.
|
|
177
|
+
*/
|
|
178
|
+
const withNull = (type) => type.includes("null") ? type : `${type} | null`;
|
|
149
179
|
function tsTypeForSchemaField(def, variant = "read") {
|
|
150
180
|
if (def.type === "single_select" || def.type === "multiple_select") {
|
|
151
181
|
const options = def.template
|
|
@@ -156,13 +186,19 @@ function tsTypeForSchemaField(def, variant = "read") {
|
|
|
156
186
|
.map((o) => `"${o.label.replace(/"/g, '\\"')}"`)
|
|
157
187
|
.join(" | ");
|
|
158
188
|
const union = `${literals} | string`;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
189
|
+
const read = def.type === "multiple_select" ? `(${union})[] | null` : `${union} | null`;
|
|
190
|
+
if (variant === "write") {
|
|
191
|
+
return def.type === "multiple_select"
|
|
192
|
+
? `${union} | (${union})[] | null`
|
|
193
|
+
: `${union} | null`;
|
|
194
|
+
}
|
|
195
|
+
return read;
|
|
162
196
|
}
|
|
163
197
|
}
|
|
164
|
-
if (variant === "write"
|
|
165
|
-
|
|
198
|
+
if (variant === "write") {
|
|
199
|
+
const write = FIELD_INPUT_TYPE_MAP[def.type] ?? FIELD_TYPE_MAP[def.type];
|
|
200
|
+
if (write)
|
|
201
|
+
return withNull(write);
|
|
166
202
|
}
|
|
167
203
|
if (FIELD_TYPE_MAP[def.type])
|
|
168
204
|
return FIELD_TYPE_MAP[def.type];
|
|
@@ -464,7 +500,11 @@ export function generateDbTs(schema) {
|
|
|
464
500
|
if (jsdoc) {
|
|
465
501
|
lines.push(` /** ${jsdoc} */`);
|
|
466
502
|
}
|
|
467
|
-
|
|
503
|
+
// Optional, matching the pre-monorepo type (`required: ['id']` — "none of
|
|
504
|
+
// these are required to be defined aside from 'id'"). A `fields:`
|
|
505
|
+
// projection omits every key it wasn't asked for, so any field really can
|
|
506
|
+
// be absent on a read.
|
|
507
|
+
lines.push(` ${field.sdkName}?: ${tsType};`);
|
|
468
508
|
}
|
|
469
509
|
lines.push("};");
|
|
470
510
|
lines.push("");
|
|
@@ -702,14 +742,18 @@ const AIRTABLE_FIELD_TYPE_MAP = {
|
|
|
702
742
|
currency: "number",
|
|
703
743
|
percent: "number",
|
|
704
744
|
rating: "number",
|
|
705
|
-
|
|
745
|
+
// Formatted to "HH:mm:ss" before it reaches the app — `formatAirtableDuration`
|
|
746
|
+
// in BOTH dispatches. Unlike the Zite DB's, which is DECIMAL seconds.
|
|
747
|
+
duration: "string",
|
|
706
748
|
checkbox: "boolean",
|
|
707
749
|
date: "string",
|
|
708
750
|
dateTime: "string",
|
|
709
751
|
singleSelect: "string",
|
|
710
752
|
multipleSelects: "string[]",
|
|
711
753
|
multipleAttachments: "AirtableAttachment[]",
|
|
712
|
-
|
|
754
|
+
// `string | string[]`, matching the pre-monorepo type — see the note on the
|
|
755
|
+
// Zite DB's `linked_record`.
|
|
756
|
+
multipleRecordLinks: "string | string[]",
|
|
713
757
|
singleCollaborator: "{ id: string; email: string; name?: string }",
|
|
714
758
|
multipleCollaborators: "Array<{ id: string; email: string; name?: string }>",
|
|
715
759
|
// Read-only fields
|
|
@@ -722,7 +766,8 @@ const AIRTABLE_FIELD_TYPE_MAP = {
|
|
|
722
766
|
lastModifiedTime: "string",
|
|
723
767
|
createdBy: "{ id: string; email: string; name?: string }",
|
|
724
768
|
lastModifiedBy: "{ id: string; email: string; name?: string }",
|
|
725
|
-
button
|
|
769
|
+
// Both dispatches extract the URL string off the button object.
|
|
770
|
+
button: "string",
|
|
726
771
|
externalSyncSource: "unknown",
|
|
727
772
|
aiText: "string",
|
|
728
773
|
};
|
|
@@ -1003,8 +1048,22 @@ export function generateBackendWrapperTs(envVarNames = []) {
|
|
|
1003
1048
|
// Narrows `user` to this app's generated User. ZiteScheduledContext is
|
|
1004
1049
|
// re-exported unchanged — a scheduled fire has no user at all, so there is
|
|
1005
1050
|
// nothing to narrow.
|
|
1051
|
+
"/**",
|
|
1052
|
+
" * The user an endpoint sees. NOT the full `User` from zitejs/auth — that is",
|
|
1053
|
+
" * the browser's better-auth session, and the runtime never sends its",
|
|
1054
|
+
" * `name`, `emailVerified`, `image`, `createdAt` or `updatedAt` to an",
|
|
1055
|
+
" * endpoint. Typing those here let `context.user.createdAt.getFullYear()`",
|
|
1056
|
+
" * compile and throw. `Omit` keeps any index signature merged in by",
|
|
1057
|
+
" * `.zite/user-extensions.d.ts`, so a migrated user-sync app still reads its",
|
|
1058
|
+
" * synced columns off this.",
|
|
1059
|
+
" */",
|
|
1060
|
+
"export type ZiteEndpointUser = Omit<",
|
|
1061
|
+
" User,",
|
|
1062
|
+
" 'name' | 'emailVerified' | 'image' | 'createdAt' | 'updatedAt'",
|
|
1063
|
+
">;",
|
|
1064
|
+
"",
|
|
1006
1065
|
'export interface ZiteRequestContext extends Omit<_ZiteRequestContext, "user"> {',
|
|
1007
|
-
" user:
|
|
1066
|
+
" user: ZiteEndpointUser;",
|
|
1008
1067
|
"}",
|
|
1009
1068
|
"",
|
|
1010
1069
|
"type SchemaLike<TOut, TIn = TOut> = { _output: TOut; _input: TIn; parse: (data: unknown) => TOut };",
|