zitejs 0.9.108 → 0.9.110
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/runtime/filterConditionTypes.test-d.d.ts +1 -0
- package/dist/cjs/runtime/filterConditionTypes.test-d.js +40 -0
- package/dist/cjs/runtime/index.d.ts +7 -6
- package/dist/cjs/runtime/index.js +0 -7
- package/dist/cjs/sync/lib.js +18 -7
- package/dist/cjs/sync/lib.test.js +33 -0
- package/dist/cjs/sync/sdkNames.d.ts +8 -1
- package/dist/cjs/sync/sdkNames.js +8 -5
- package/dist/cjs/sync/sdkNames.test.js +11 -7
- package/dist/esm/runtime/filterConditionTypes.test-d.d.ts +1 -0
- package/dist/esm/runtime/filterConditionTypes.test-d.js +38 -0
- package/dist/esm/runtime/index.d.ts +7 -6
- package/dist/esm/runtime/index.js +0 -6
- package/dist/esm/sync/lib.js +18 -7
- package/dist/esm/sync/lib.test.js +34 -1
- package/dist/esm/sync/sdkNames.d.ts +8 -1
- package/dist/esm/sync/sdkNames.js +8 -5
- package/dist/esm/sync/sdkNames.test.js +11 -7
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
(0, vitest_1.describe)("FilterCondition in/notIn", () => {
|
|
5
|
+
// The idiom that regressed. `.filter(Boolean)` does not narrow in TypeScript,
|
|
6
|
+
// so this is `(string | undefined)[]` — compiling at all is the assertion.
|
|
7
|
+
(0, vitest_1.it)("accepts .map(…).filter(Boolean) over a nullable column", () => {
|
|
8
|
+
const keys = routes.map((r) => r.compositeRouteKey).filter(Boolean);
|
|
9
|
+
(0, vitest_1.expectTypeOf)(keys).toEqualTypeOf();
|
|
10
|
+
(0, vitest_1.expectTypeOf)().toMatchTypeOf();
|
|
11
|
+
(0, vitest_1.expectTypeOf)().toMatchTypeOf();
|
|
12
|
+
});
|
|
13
|
+
// One array of a union, not a union of arrays.
|
|
14
|
+
(0, vitest_1.it)("collapses a nullable column to a single array type", () => {
|
|
15
|
+
(0, vitest_1.expectTypeOf)().toEqualTypeOf();
|
|
16
|
+
});
|
|
17
|
+
// The unwrapping that the conditional exists for: an array-valued column is
|
|
18
|
+
// filtered by its ELEMENT type, not by arrays of arrays.
|
|
19
|
+
(0, vitest_1.it)("still unwraps an array-valued column to its element type", () => {
|
|
20
|
+
(0, vitest_1.expectTypeOf)().toEqualTypeOf();
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)("leaves a plain scalar column alone", () => {
|
|
23
|
+
(0, vitest_1.expectTypeOf)().toEqualTypeOf();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.describe)("RecordFilters", () => {
|
|
27
|
+
// Through the keyed wrapper, which is how app code actually reaches it.
|
|
28
|
+
(0, vitest_1.it)("accepts the idiom on a nullable key", () => {
|
|
29
|
+
const filters = {
|
|
30
|
+
compositeRouteKey: {
|
|
31
|
+
in: routes.map((r) => r.compositeRouteKey).filter(Boolean),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
(0, vitest_1.expectTypeOf)(filters).toMatchTypeOf();
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.it)("accepts an element-typed `in` on an array column", () => {
|
|
37
|
+
const filters = { tags: { in: ["urgent"] } };
|
|
38
|
+
(0, vitest_1.expectTypeOf)(filters).toMatchTypeOf();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { NotificationsCreateParams, NotificationsCreateResult } from "../notifications/index.js";
|
|
2
1
|
/**
|
|
3
2
|
* A comparison against one field. Mirrors base-runner's operator set
|
|
4
3
|
* (`LegacyWhereConditionOperators`); anything else in the object is ignored.
|
|
@@ -9,6 +8,11 @@ import type { NotificationsCreateParams, NotificationsCreateResult } from "../no
|
|
|
9
8
|
* the operands buys little while breaking real migrated code: dates are ISO
|
|
10
9
|
* strings here but 1.0 accepted `number | Date` on every comparison, and
|
|
11
10
|
* `contains` was `any`.
|
|
11
|
+
*
|
|
12
|
+
* On `in`/`notIn` the `[]` sits OUTSIDE the conditional. A naked `V`
|
|
13
|
+
* distributes, so a nullable column produced `string[] | undefined[]` — two
|
|
14
|
+
* separate array types, neither of which accepts the `(string | undefined)[]`
|
|
15
|
+
* that `.map(…).filter(Boolean)` yields.
|
|
12
16
|
*/
|
|
13
17
|
export type FilterCondition<V> = {
|
|
14
18
|
/** Substring match (text fields) or "has any of these" (linked records). */
|
|
@@ -16,9 +20,9 @@ export type FilterCondition<V> = {
|
|
|
16
20
|
/** Not equal, or — against `null` — "is set". */
|
|
17
21
|
not?: V | null;
|
|
18
22
|
/** An empty array matches NOTHING — the SDK's filters convert to the nested format, where `in: []` is "is one of nothing". */
|
|
19
|
-
in?: V extends Array<infer E> ? E
|
|
23
|
+
in?: (V extends Array<infer E> ? E : V)[];
|
|
20
24
|
/** An empty array applies no filter at all — "is not one of nothing" is always true. */
|
|
21
|
-
notIn?: V extends Array<infer E> ? E
|
|
25
|
+
notIn?: (V extends Array<infer E> ? E : V)[];
|
|
22
26
|
lt?: V | number | Date;
|
|
23
27
|
lte?: V | number | Date;
|
|
24
28
|
gt?: V | number | Date;
|
|
@@ -328,9 +332,6 @@ export interface EmailClient {
|
|
|
328
332
|
* app's connected email integration key.
|
|
329
333
|
*/
|
|
330
334
|
export declare function createEmailClient(integrationId: string): EmailClient;
|
|
331
|
-
export declare function createNotificationsClient(): {
|
|
332
|
-
create(params: NotificationsCreateParams): Promise<NotificationsCreateResult>;
|
|
333
|
-
};
|
|
334
335
|
export type { NotificationLink, NotificationsCreateParams, NotificationsCreateResult, } from "../notifications/index.js";
|
|
335
336
|
export { createCaller } from "../caller/index.js";
|
|
336
337
|
export type { EndpointConfig } from "../caller/index.js";
|
|
@@ -6,7 +6,6 @@ exports.createSqlClient = createSqlClient;
|
|
|
6
6
|
exports.createAuthClient = createAuthClient;
|
|
7
7
|
exports.createAirtableClient = createAirtableClient;
|
|
8
8
|
exports.createEmailClient = createEmailClient;
|
|
9
|
-
exports.createNotificationsClient = createNotificationsClient;
|
|
10
9
|
const sdkCall_js_1 = require("../internal/sdkCall.js");
|
|
11
10
|
const DB_INTEGRATION_ID = "databases";
|
|
12
11
|
function getBaseId() {
|
|
@@ -126,11 +125,5 @@ function createEmailClient(integrationId) {
|
|
|
126
125
|
send: (params) => (0, sdkCall_js_1.getSdkCall)()(integrationId, "Email", "send", params),
|
|
127
126
|
};
|
|
128
127
|
}
|
|
129
|
-
const NOTIFICATIONS_SDK_INTEGRATION_ID = "__notifications__";
|
|
130
|
-
function createNotificationsClient() {
|
|
131
|
-
return {
|
|
132
|
-
create: (params) => (0, sdkCall_js_1.getSdkCall)()(NOTIFICATIONS_SDK_INTEGRATION_ID, "ZiteNotifications", "create", params),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
128
|
var index_js_1 = require("../caller/index.js");
|
|
136
129
|
Object.defineProperty(exports, "createCaller", { enumerable: true, get: function () { return index_js_1.createCaller; } });
|
package/dist/cjs/sync/lib.js
CHANGED
|
@@ -174,10 +174,7 @@ const tsSingleQuoted = (value) => `'${JSON.stringify(value).slice(1, -1).replace
|
|
|
174
174
|
* Applied where the comment is written rather than at each contributor, so
|
|
175
175
|
* anything added to one later is covered by default.
|
|
176
176
|
*/
|
|
177
|
-
const commentSafe = (value) => value
|
|
178
|
-
.replace(/\*\//g, "* /")
|
|
179
|
-
.replace(/\s+/g, " ")
|
|
180
|
-
.trim();
|
|
177
|
+
const commentSafe = (value) => value.replace(/\*\//g, "* /").replace(/\s+/g, " ").trim();
|
|
181
178
|
const NUMBER_FORMAT_EXAMPLES = {
|
|
182
179
|
local: "1,000,000.50",
|
|
183
180
|
comma_period: "1,000,000.50",
|
|
@@ -563,7 +560,7 @@ function generateDbTs(inputSchema) {
|
|
|
563
560
|
lines.push("// - SELECT only — use .create/.update/.delete for writes");
|
|
564
561
|
lines.push('// - Load the "zite:sql" skill for full SQL reference (formulas, lookups, etc.)');
|
|
565
562
|
lines.push(...buildLinkTableComments(schema));
|
|
566
|
-
lines.push("import { createTableClient, createSqlClient,
|
|
563
|
+
lines.push("import { createTableClient, createSqlClient, createAuthClient } from 'zitejs/runtime';");
|
|
567
564
|
lines.push("");
|
|
568
565
|
lines.push(...ATTACHMENT_TYPES);
|
|
569
566
|
for (const table of tables) {
|
|
@@ -608,7 +605,10 @@ function generateDbTs(inputSchema) {
|
|
|
608
605
|
lines.push(` ${table.sdkName}: createTableClient<${className}RecordType, ${className}RecordInput>('${className}'),`);
|
|
609
606
|
}
|
|
610
607
|
lines.push(` sql: createSqlClient(),`);
|
|
611
|
-
|
|
608
|
+
// No `notifications` here: it is a platform primitive, not something backed
|
|
609
|
+
// by the project database, so it lives at `zitejs/notifications` alongside
|
|
610
|
+
// `zitejs/pdf` and `zitejs/schedules`. Keeping it on `zite` cost a table
|
|
611
|
+
// named "Notifications" its accessor — see RESERVED_TABLE_ACCESSORS.
|
|
612
612
|
lines.push(` auth: createAuthClient<ZiteAuthUser>(),`);
|
|
613
613
|
lines.push("};");
|
|
614
614
|
lines.push("");
|
|
@@ -902,7 +902,14 @@ const AIRTABLE_FIELD_INPUT_TYPE_MAP = {
|
|
|
902
902
|
singleCollaborator: "{ id: string } | { email: string }",
|
|
903
903
|
multipleCollaborators: "Array<{ id: string } | { email: string }>",
|
|
904
904
|
};
|
|
905
|
-
/**
|
|
905
|
+
/**
|
|
906
|
+
* Airtable's attachment object as the REST API really returns it.
|
|
907
|
+
*
|
|
908
|
+
* Not quite `airtable/lib/attachment.d.ts`: that declaration omits `width` and
|
|
909
|
+
* `height`, which the API does return for image attachments. 1.0 exposed them
|
|
910
|
+
* and apps read them, so leaving them out turns a working `photo.width` into a
|
|
911
|
+
* migration type error. Optional, because a non-image attachment has neither.
|
|
912
|
+
*/
|
|
906
913
|
const AIRTABLE_ATTACHMENT_TYPE = [
|
|
907
914
|
"export type AirtableAttachment = {",
|
|
908
915
|
" id: string;",
|
|
@@ -912,6 +919,10 @@ const AIRTABLE_ATTACHMENT_TYPE = [
|
|
|
912
919
|
" size: number;",
|
|
913
920
|
" /** MIME type. */",
|
|
914
921
|
" type: string;",
|
|
922
|
+
" /** Pixel width — images only. */",
|
|
923
|
+
" width?: number;",
|
|
924
|
+
" /** Pixel height — images only. */",
|
|
925
|
+
" height?: number;",
|
|
915
926
|
" thumbnails?: {",
|
|
916
927
|
" small: { url: string; width: number; height: number };",
|
|
917
928
|
" large: { url: string; width: number; height: number };",
|
|
@@ -51,6 +51,39 @@ const schemaWithSelectOption = (label) => ({
|
|
|
51
51
|
(0, vitest_1.expect)(out).toContain('"The \\"Premium\\" tier"');
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
|
+
(0, vitest_1.describe)('generateAirtableTs attachment type', () => {
|
|
55
|
+
const lockWithAttachment = {
|
|
56
|
+
integrationId: 'airtable',
|
|
57
|
+
tables: [
|
|
58
|
+
{
|
|
59
|
+
id: 'tbl1',
|
|
60
|
+
sdkName: 'Speakers',
|
|
61
|
+
primaryFieldId: 'fld1',
|
|
62
|
+
fields: [
|
|
63
|
+
{ id: 'fld1', sdkName: 'name', type: 'singleLineText', name: 'Name' },
|
|
64
|
+
{
|
|
65
|
+
id: 'fld2',
|
|
66
|
+
sdkName: 'headshot',
|
|
67
|
+
type: 'multipleAttachments',
|
|
68
|
+
name: 'Headshot',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
// Reported from a migrated app reading `photo.width` to lay out an image
|
|
75
|
+
// grid. Airtable returns width/height on image attachments and 1.0 exposed
|
|
76
|
+
// them; `airtable/lib/attachment.d.ts`, which this type was copied from,
|
|
77
|
+
// omits both — so the read became a TS2339 the app could not edit away.
|
|
78
|
+
vitest_1.it.each(['width', 'height'])('exposes %s, which Airtable returns for images', prop => {
|
|
79
|
+
const out = (0, lib_js_1.generateAirtableTs)(lockWithAttachment);
|
|
80
|
+
(0, vitest_1.expect)(out).not.toBeNull();
|
|
81
|
+
(0, vitest_1.expect)(out).toContain(` ${prop}?: number;`);
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)('emits parseable TypeScript', () => {
|
|
84
|
+
(0, vitest_1.expect)(syntaxErrorsIn((0, lib_js_1.generateAirtableTs)(lockWithAttachment) ?? '')).toEqual([]);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
54
87
|
(0, vitest_1.describe)('generateDbTs field names in comments', () => {
|
|
55
88
|
// A field's display name is user text and reaches a one-line JSDoc. A
|
|
56
89
|
// comment-close sequence in it would end the comment early and leave the
|
|
@@ -36,8 +36,15 @@ export declare function toSdkName(name: string): string;
|
|
|
36
36
|
/**
|
|
37
37
|
* Accessors `generateDbTs` puts on the `zite` object itself, after the tables.
|
|
38
38
|
* A table that allocates one of these produces a duplicate key.
|
|
39
|
+
*
|
|
40
|
+
* `notifications` used to be here and no longer is: it was a platform
|
|
41
|
+
* primitive hanging off `zite`, so a table named "Notifications" collided with
|
|
42
|
+
* it and — because the sentinel was written last — lost its client entirely.
|
|
43
|
+
* Moving it to `zitejs/notifications` removes the collision rather than
|
|
44
|
+
* managing it, and frees the name for the table that wants it. Everything left
|
|
45
|
+
* is genuinely backed by the project database.
|
|
39
46
|
*/
|
|
40
|
-
export declare const RESERVED_TABLE_ACCESSORS: readonly ["sql", "
|
|
47
|
+
export declare const RESERVED_TABLE_ACCESSORS: readonly ["sql", "auth"];
|
|
41
48
|
/**
|
|
42
49
|
* Never safe as a generated key, in any namespace. Assigning `__proto__` in an
|
|
43
50
|
* object literal sets the prototype rather than defining a property, and
|
|
@@ -66,12 +66,15 @@ function toSdkName(name) {
|
|
|
66
66
|
/**
|
|
67
67
|
* Accessors `generateDbTs` puts on the `zite` object itself, after the tables.
|
|
68
68
|
* A table that allocates one of these produces a duplicate key.
|
|
69
|
+
*
|
|
70
|
+
* `notifications` used to be here and no longer is: it was a platform
|
|
71
|
+
* primitive hanging off `zite`, so a table named "Notifications" collided with
|
|
72
|
+
* it and — because the sentinel was written last — lost its client entirely.
|
|
73
|
+
* Moving it to `zitejs/notifications` removes the collision rather than
|
|
74
|
+
* managing it, and frees the name for the table that wants it. Everything left
|
|
75
|
+
* is genuinely backed by the project database.
|
|
69
76
|
*/
|
|
70
|
-
exports.RESERVED_TABLE_ACCESSORS = [
|
|
71
|
-
"sql",
|
|
72
|
-
"notifications",
|
|
73
|
-
"auth",
|
|
74
|
-
];
|
|
77
|
+
exports.RESERVED_TABLE_ACCESSORS = ["sql", "auth"];
|
|
75
78
|
/**
|
|
76
79
|
* Never safe as a generated key, in any namespace. Assigning `__proto__` in an
|
|
77
80
|
* object literal sets the prototype rather than defining a property, and
|
|
@@ -33,17 +33,18 @@ const recordTypeKeys = (source, typeName) => {
|
|
|
33
33
|
};
|
|
34
34
|
(0, vitest_1.describe)("reserved platform accessors", () => {
|
|
35
35
|
// Sudzy Dashboard: a table named "Notifications" emitted a second
|
|
36
|
-
// `notifications:` key
|
|
36
|
+
// `notifications:` key and the platform client — written last — won, so the
|
|
37
37
|
// table lost findAll and typed as `{ create(NotificationsCreateParams) }`.
|
|
38
|
-
|
|
38
|
+
// The platform client has since moved to `zitejs/notifications`, so the name
|
|
39
|
+
// belongs to the table again and there is nothing left to collide with.
|
|
40
|
+
(0, vitest_1.it)("lets a table keep the notifications accessor", () => {
|
|
39
41
|
const schema = (0, lib_js_1.generateSchema)(database([{ id: "tbl_1", name: "Notifications", fields: [] }]));
|
|
40
|
-
(0, vitest_1.expect)(schema.tables[0].sdkName).toBe("
|
|
42
|
+
(0, vitest_1.expect)(schema.tables[0].sdkName).toBe("notifications");
|
|
41
43
|
const keys = ziteKeys((0, lib_js_1.generateDbTs)(schema));
|
|
42
|
-
(0, vitest_1.expect)(keys).toContain("notifications2");
|
|
43
44
|
(0, vitest_1.expect)(keys.filter((k) => k === "notifications")).toHaveLength(1);
|
|
44
45
|
(0, vitest_1.expect)(new Set(keys).size).toBe(keys.length);
|
|
45
46
|
});
|
|
46
|
-
vitest_1.it.each(["sql", "auth"
|
|
47
|
+
vitest_1.it.each(["sql", "auth"])("keeps the platform %s accessor for the platform", (reserved) => {
|
|
47
48
|
const schema = (0, lib_js_1.generateSchema)(database([{ id: "tbl_1", name: reserved, fields: [] }]));
|
|
48
49
|
(0, vitest_1.expect)(schema.tables[0].sdkName).toBe(`${reserved}2`);
|
|
49
50
|
const keys = ziteKeys((0, lib_js_1.generateDbTs)(schema));
|
|
@@ -192,14 +193,17 @@ const recordTypeKeys = (source, typeName) => {
|
|
|
192
193
|
// `zitejs generate` reads it with no generateSchema pass in front, so this is
|
|
193
194
|
// the only thing standing between it and a TS1117 it cannot build past.
|
|
194
195
|
(0, vitest_1.it)("repairs a committed schema that already holds the collision", () => {
|
|
196
|
+
// `sql`, not `notifications` — the latter stopped being a platform
|
|
197
|
+
// accessor when the client moved to `zitejs/notifications`, so a schema
|
|
198
|
+
// holding it is no longer a collision to repair.
|
|
195
199
|
const source = (0, lib_js_1.generateDbTs)({
|
|
196
200
|
tables: [
|
|
197
|
-
{ id: "tbl_1", sdkName: "
|
|
201
|
+
{ id: "tbl_1", sdkName: "sql", fields: [] },
|
|
198
202
|
{ id: "tbl_2", sdkName: "orders", fields: [] },
|
|
199
203
|
],
|
|
200
204
|
});
|
|
201
205
|
const keys = ziteKeys(source);
|
|
202
206
|
(0, vitest_1.expect)(new Set(keys).size).toBe(keys.length);
|
|
203
|
-
(0, vitest_1.expect)(keys).toContain("
|
|
207
|
+
(0, vitest_1.expect)(keys).toContain("sql2");
|
|
204
208
|
});
|
|
205
209
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expectTypeOf } from "vitest";
|
|
2
|
+
describe("FilterCondition in/notIn", () => {
|
|
3
|
+
// The idiom that regressed. `.filter(Boolean)` does not narrow in TypeScript,
|
|
4
|
+
// so this is `(string | undefined)[]` — compiling at all is the assertion.
|
|
5
|
+
it("accepts .map(…).filter(Boolean) over a nullable column", () => {
|
|
6
|
+
const keys = routes.map((r) => r.compositeRouteKey).filter(Boolean);
|
|
7
|
+
expectTypeOf(keys).toEqualTypeOf();
|
|
8
|
+
expectTypeOf().toMatchTypeOf();
|
|
9
|
+
expectTypeOf().toMatchTypeOf();
|
|
10
|
+
});
|
|
11
|
+
// One array of a union, not a union of arrays.
|
|
12
|
+
it("collapses a nullable column to a single array type", () => {
|
|
13
|
+
expectTypeOf().toEqualTypeOf();
|
|
14
|
+
});
|
|
15
|
+
// The unwrapping that the conditional exists for: an array-valued column is
|
|
16
|
+
// filtered by its ELEMENT type, not by arrays of arrays.
|
|
17
|
+
it("still unwraps an array-valued column to its element type", () => {
|
|
18
|
+
expectTypeOf().toEqualTypeOf();
|
|
19
|
+
});
|
|
20
|
+
it("leaves a plain scalar column alone", () => {
|
|
21
|
+
expectTypeOf().toEqualTypeOf();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe("RecordFilters", () => {
|
|
25
|
+
// Through the keyed wrapper, which is how app code actually reaches it.
|
|
26
|
+
it("accepts the idiom on a nullable key", () => {
|
|
27
|
+
const filters = {
|
|
28
|
+
compositeRouteKey: {
|
|
29
|
+
in: routes.map((r) => r.compositeRouteKey).filter(Boolean),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
expectTypeOf(filters).toMatchTypeOf();
|
|
33
|
+
});
|
|
34
|
+
it("accepts an element-typed `in` on an array column", () => {
|
|
35
|
+
const filters = { tags: { in: ["urgent"] } };
|
|
36
|
+
expectTypeOf(filters).toMatchTypeOf();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { NotificationsCreateParams, NotificationsCreateResult } from "../notifications/index.js";
|
|
2
1
|
/**
|
|
3
2
|
* A comparison against one field. Mirrors base-runner's operator set
|
|
4
3
|
* (`LegacyWhereConditionOperators`); anything else in the object is ignored.
|
|
@@ -9,6 +8,11 @@ import type { NotificationsCreateParams, NotificationsCreateResult } from "../no
|
|
|
9
8
|
* the operands buys little while breaking real migrated code: dates are ISO
|
|
10
9
|
* strings here but 1.0 accepted `number | Date` on every comparison, and
|
|
11
10
|
* `contains` was `any`.
|
|
11
|
+
*
|
|
12
|
+
* On `in`/`notIn` the `[]` sits OUTSIDE the conditional. A naked `V`
|
|
13
|
+
* distributes, so a nullable column produced `string[] | undefined[]` — two
|
|
14
|
+
* separate array types, neither of which accepts the `(string | undefined)[]`
|
|
15
|
+
* that `.map(…).filter(Boolean)` yields.
|
|
12
16
|
*/
|
|
13
17
|
export type FilterCondition<V> = {
|
|
14
18
|
/** Substring match (text fields) or "has any of these" (linked records). */
|
|
@@ -16,9 +20,9 @@ export type FilterCondition<V> = {
|
|
|
16
20
|
/** Not equal, or — against `null` — "is set". */
|
|
17
21
|
not?: V | null;
|
|
18
22
|
/** An empty array matches NOTHING — the SDK's filters convert to the nested format, where `in: []` is "is one of nothing". */
|
|
19
|
-
in?: V extends Array<infer E> ? E
|
|
23
|
+
in?: (V extends Array<infer E> ? E : V)[];
|
|
20
24
|
/** An empty array applies no filter at all — "is not one of nothing" is always true. */
|
|
21
|
-
notIn?: V extends Array<infer E> ? E
|
|
25
|
+
notIn?: (V extends Array<infer E> ? E : V)[];
|
|
22
26
|
lt?: V | number | Date;
|
|
23
27
|
lte?: V | number | Date;
|
|
24
28
|
gt?: V | number | Date;
|
|
@@ -328,9 +332,6 @@ export interface EmailClient {
|
|
|
328
332
|
* app's connected email integration key.
|
|
329
333
|
*/
|
|
330
334
|
export declare function createEmailClient(integrationId: string): EmailClient;
|
|
331
|
-
export declare function createNotificationsClient(): {
|
|
332
|
-
create(params: NotificationsCreateParams): Promise<NotificationsCreateResult>;
|
|
333
|
-
};
|
|
334
335
|
export type { NotificationLink, NotificationsCreateParams, NotificationsCreateResult, } from "../notifications/index.js";
|
|
335
336
|
export { createCaller } from "../caller/index.js";
|
|
336
337
|
export type { EndpointConfig } from "../caller/index.js";
|
|
@@ -117,10 +117,4 @@ export function createEmailClient(integrationId) {
|
|
|
117
117
|
send: (params) => getSdkCall()(integrationId, "Email", "send", params),
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
|
-
const NOTIFICATIONS_SDK_INTEGRATION_ID = "__notifications__";
|
|
121
|
-
export function createNotificationsClient() {
|
|
122
|
-
return {
|
|
123
|
-
create: (params) => getSdkCall()(NOTIFICATIONS_SDK_INTEGRATION_ID, "ZiteNotifications", "create", params),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
120
|
export { createCaller } from "../caller/index.js";
|
package/dist/esm/sync/lib.js
CHANGED
|
@@ -165,10 +165,7 @@ const tsSingleQuoted = (value) => `'${JSON.stringify(value).slice(1, -1).replace
|
|
|
165
165
|
* Applied where the comment is written rather than at each contributor, so
|
|
166
166
|
* anything added to one later is covered by default.
|
|
167
167
|
*/
|
|
168
|
-
const commentSafe = (value) => value
|
|
169
|
-
.replace(/\*\//g, "* /")
|
|
170
|
-
.replace(/\s+/g, " ")
|
|
171
|
-
.trim();
|
|
168
|
+
const commentSafe = (value) => value.replace(/\*\//g, "* /").replace(/\s+/g, " ").trim();
|
|
172
169
|
const NUMBER_FORMAT_EXAMPLES = {
|
|
173
170
|
local: "1,000,000.50",
|
|
174
171
|
comma_period: "1,000,000.50",
|
|
@@ -551,7 +548,7 @@ export function generateDbTs(inputSchema) {
|
|
|
551
548
|
lines.push("// - SELECT only — use .create/.update/.delete for writes");
|
|
552
549
|
lines.push('// - Load the "zite:sql" skill for full SQL reference (formulas, lookups, etc.)');
|
|
553
550
|
lines.push(...buildLinkTableComments(schema));
|
|
554
|
-
lines.push("import { createTableClient, createSqlClient,
|
|
551
|
+
lines.push("import { createTableClient, createSqlClient, createAuthClient } from 'zitejs/runtime';");
|
|
555
552
|
lines.push("");
|
|
556
553
|
lines.push(...ATTACHMENT_TYPES);
|
|
557
554
|
for (const table of tables) {
|
|
@@ -596,7 +593,10 @@ export function generateDbTs(inputSchema) {
|
|
|
596
593
|
lines.push(` ${table.sdkName}: createTableClient<${className}RecordType, ${className}RecordInput>('${className}'),`);
|
|
597
594
|
}
|
|
598
595
|
lines.push(` sql: createSqlClient(),`);
|
|
599
|
-
|
|
596
|
+
// No `notifications` here: it is a platform primitive, not something backed
|
|
597
|
+
// by the project database, so it lives at `zitejs/notifications` alongside
|
|
598
|
+
// `zitejs/pdf` and `zitejs/schedules`. Keeping it on `zite` cost a table
|
|
599
|
+
// named "Notifications" its accessor — see RESERVED_TABLE_ACCESSORS.
|
|
600
600
|
lines.push(` auth: createAuthClient<ZiteAuthUser>(),`);
|
|
601
601
|
lines.push("};");
|
|
602
602
|
lines.push("");
|
|
@@ -890,7 +890,14 @@ const AIRTABLE_FIELD_INPUT_TYPE_MAP = {
|
|
|
890
890
|
singleCollaborator: "{ id: string } | { email: string }",
|
|
891
891
|
multipleCollaborators: "Array<{ id: string } | { email: string }>",
|
|
892
892
|
};
|
|
893
|
-
/**
|
|
893
|
+
/**
|
|
894
|
+
* Airtable's attachment object as the REST API really returns it.
|
|
895
|
+
*
|
|
896
|
+
* Not quite `airtable/lib/attachment.d.ts`: that declaration omits `width` and
|
|
897
|
+
* `height`, which the API does return for image attachments. 1.0 exposed them
|
|
898
|
+
* and apps read them, so leaving them out turns a working `photo.width` into a
|
|
899
|
+
* migration type error. Optional, because a non-image attachment has neither.
|
|
900
|
+
*/
|
|
894
901
|
const AIRTABLE_ATTACHMENT_TYPE = [
|
|
895
902
|
"export type AirtableAttachment = {",
|
|
896
903
|
" id: string;",
|
|
@@ -900,6 +907,10 @@ const AIRTABLE_ATTACHMENT_TYPE = [
|
|
|
900
907
|
" size: number;",
|
|
901
908
|
" /** MIME type. */",
|
|
902
909
|
" type: string;",
|
|
910
|
+
" /** Pixel width — images only. */",
|
|
911
|
+
" width?: number;",
|
|
912
|
+
" /** Pixel height — images only. */",
|
|
913
|
+
" height?: number;",
|
|
903
914
|
" thumbnails?: {",
|
|
904
915
|
" small: { url: string; width: number; height: number };",
|
|
905
916
|
" large: { url: string; width: number; height: number };",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import ts from 'typescript';
|
|
3
|
-
import { generateApiTs, generateBackendWrapperTs, generateDbTs, generateEmailSdk, } from './lib.js';
|
|
3
|
+
import { generateAirtableTs, generateApiTs, generateBackendWrapperTs, generateDbTs, generateEmailSdk, } from './lib.js';
|
|
4
4
|
/**
|
|
5
5
|
* Syntax errors in the emitted source. `.zite/db.ts` sits at the repo root and
|
|
6
6
|
* every app imports it, so anything unparseable here fails typecheck for the
|
|
@@ -46,6 +46,39 @@ describe('generateDbTs select option literals', () => {
|
|
|
46
46
|
expect(out).toContain('"The \\"Premium\\" tier"');
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
|
+
describe('generateAirtableTs attachment type', () => {
|
|
50
|
+
const lockWithAttachment = {
|
|
51
|
+
integrationId: 'airtable',
|
|
52
|
+
tables: [
|
|
53
|
+
{
|
|
54
|
+
id: 'tbl1',
|
|
55
|
+
sdkName: 'Speakers',
|
|
56
|
+
primaryFieldId: 'fld1',
|
|
57
|
+
fields: [
|
|
58
|
+
{ id: 'fld1', sdkName: 'name', type: 'singleLineText', name: 'Name' },
|
|
59
|
+
{
|
|
60
|
+
id: 'fld2',
|
|
61
|
+
sdkName: 'headshot',
|
|
62
|
+
type: 'multipleAttachments',
|
|
63
|
+
name: 'Headshot',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
// Reported from a migrated app reading `photo.width` to lay out an image
|
|
70
|
+
// grid. Airtable returns width/height on image attachments and 1.0 exposed
|
|
71
|
+
// them; `airtable/lib/attachment.d.ts`, which this type was copied from,
|
|
72
|
+
// omits both — so the read became a TS2339 the app could not edit away.
|
|
73
|
+
it.each(['width', 'height'])('exposes %s, which Airtable returns for images', prop => {
|
|
74
|
+
const out = generateAirtableTs(lockWithAttachment);
|
|
75
|
+
expect(out).not.toBeNull();
|
|
76
|
+
expect(out).toContain(` ${prop}?: number;`);
|
|
77
|
+
});
|
|
78
|
+
it('emits parseable TypeScript', () => {
|
|
79
|
+
expect(syntaxErrorsIn(generateAirtableTs(lockWithAttachment) ?? '')).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
49
82
|
describe('generateDbTs field names in comments', () => {
|
|
50
83
|
// A field's display name is user text and reaches a one-line JSDoc. A
|
|
51
84
|
// comment-close sequence in it would end the comment early and leave the
|
|
@@ -36,8 +36,15 @@ export declare function toSdkName(name: string): string;
|
|
|
36
36
|
/**
|
|
37
37
|
* Accessors `generateDbTs` puts on the `zite` object itself, after the tables.
|
|
38
38
|
* A table that allocates one of these produces a duplicate key.
|
|
39
|
+
*
|
|
40
|
+
* `notifications` used to be here and no longer is: it was a platform
|
|
41
|
+
* primitive hanging off `zite`, so a table named "Notifications" collided with
|
|
42
|
+
* it and — because the sentinel was written last — lost its client entirely.
|
|
43
|
+
* Moving it to `zitejs/notifications` removes the collision rather than
|
|
44
|
+
* managing it, and frees the name for the table that wants it. Everything left
|
|
45
|
+
* is genuinely backed by the project database.
|
|
39
46
|
*/
|
|
40
|
-
export declare const RESERVED_TABLE_ACCESSORS: readonly ["sql", "
|
|
47
|
+
export declare const RESERVED_TABLE_ACCESSORS: readonly ["sql", "auth"];
|
|
41
48
|
/**
|
|
42
49
|
* Never safe as a generated key, in any namespace. Assigning `__proto__` in an
|
|
43
50
|
* object literal sets the prototype rather than defining a property, and
|
|
@@ -56,12 +56,15 @@ export function toSdkName(name) {
|
|
|
56
56
|
/**
|
|
57
57
|
* Accessors `generateDbTs` puts on the `zite` object itself, after the tables.
|
|
58
58
|
* A table that allocates one of these produces a duplicate key.
|
|
59
|
+
*
|
|
60
|
+
* `notifications` used to be here and no longer is: it was a platform
|
|
61
|
+
* primitive hanging off `zite`, so a table named "Notifications" collided with
|
|
62
|
+
* it and — because the sentinel was written last — lost its client entirely.
|
|
63
|
+
* Moving it to `zitejs/notifications` removes the collision rather than
|
|
64
|
+
* managing it, and frees the name for the table that wants it. Everything left
|
|
65
|
+
* is genuinely backed by the project database.
|
|
59
66
|
*/
|
|
60
|
-
export const RESERVED_TABLE_ACCESSORS = [
|
|
61
|
-
"sql",
|
|
62
|
-
"notifications",
|
|
63
|
-
"auth",
|
|
64
|
-
];
|
|
67
|
+
export const RESERVED_TABLE_ACCESSORS = ["sql", "auth"];
|
|
65
68
|
/**
|
|
66
69
|
* Never safe as a generated key, in any namespace. Assigning `__proto__` in an
|
|
67
70
|
* object literal sets the prototype rather than defining a property, and
|
|
@@ -31,17 +31,18 @@ const recordTypeKeys = (source, typeName) => {
|
|
|
31
31
|
};
|
|
32
32
|
describe("reserved platform accessors", () => {
|
|
33
33
|
// Sudzy Dashboard: a table named "Notifications" emitted a second
|
|
34
|
-
// `notifications:` key
|
|
34
|
+
// `notifications:` key and the platform client — written last — won, so the
|
|
35
35
|
// table lost findAll and typed as `{ create(NotificationsCreateParams) }`.
|
|
36
|
-
|
|
36
|
+
// The platform client has since moved to `zitejs/notifications`, so the name
|
|
37
|
+
// belongs to the table again and there is nothing left to collide with.
|
|
38
|
+
it("lets a table keep the notifications accessor", () => {
|
|
37
39
|
const schema = generateSchema(database([{ id: "tbl_1", name: "Notifications", fields: [] }]));
|
|
38
|
-
expect(schema.tables[0].sdkName).toBe("
|
|
40
|
+
expect(schema.tables[0].sdkName).toBe("notifications");
|
|
39
41
|
const keys = ziteKeys(generateDbTs(schema));
|
|
40
|
-
expect(keys).toContain("notifications2");
|
|
41
42
|
expect(keys.filter((k) => k === "notifications")).toHaveLength(1);
|
|
42
43
|
expect(new Set(keys).size).toBe(keys.length);
|
|
43
44
|
});
|
|
44
|
-
it.each(["sql", "auth"
|
|
45
|
+
it.each(["sql", "auth"])("keeps the platform %s accessor for the platform", (reserved) => {
|
|
45
46
|
const schema = generateSchema(database([{ id: "tbl_1", name: reserved, fields: [] }]));
|
|
46
47
|
expect(schema.tables[0].sdkName).toBe(`${reserved}2`);
|
|
47
48
|
const keys = ziteKeys(generateDbTs(schema));
|
|
@@ -190,14 +191,17 @@ describe("normalizeSchemaNames", () => {
|
|
|
190
191
|
// `zitejs generate` reads it with no generateSchema pass in front, so this is
|
|
191
192
|
// the only thing standing between it and a TS1117 it cannot build past.
|
|
192
193
|
it("repairs a committed schema that already holds the collision", () => {
|
|
194
|
+
// `sql`, not `notifications` — the latter stopped being a platform
|
|
195
|
+
// accessor when the client moved to `zitejs/notifications`, so a schema
|
|
196
|
+
// holding it is no longer a collision to repair.
|
|
193
197
|
const source = generateDbTs({
|
|
194
198
|
tables: [
|
|
195
|
-
{ id: "tbl_1", sdkName: "
|
|
199
|
+
{ id: "tbl_1", sdkName: "sql", fields: [] },
|
|
196
200
|
{ id: "tbl_2", sdkName: "orders", fields: [] },
|
|
197
201
|
],
|
|
198
202
|
});
|
|
199
203
|
const keys = ziteKeys(source);
|
|
200
204
|
expect(new Set(keys).size).toBe(keys.length);
|
|
201
|
-
expect(keys).toContain("
|
|
205
|
+
expect(keys).toContain("sql2");
|
|
202
206
|
});
|
|
203
207
|
});
|