zitejs 0.9.103 → 0.9.105
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/auth/index.js +16 -4
- package/dist/cjs/auth/index.test.js +39 -0
- package/dist/cjs/sync/lib.js +51 -26
- package/dist/esm/auth/index.js +16 -4
- package/dist/esm/auth/index.test.js +39 -0
- package/dist/esm/sync/lib.js +51 -26
- package/package.json +1 -1
package/dist/cjs/auth/index.js
CHANGED
|
@@ -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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
});
|
package/dist/cjs/sync/lib.js
CHANGED
|
@@ -14,48 +14,73 @@ const AUTH_USERS_TABLE_ID = "zite_user";
|
|
|
14
14
|
/**
|
|
15
15
|
* What a field's value looks like when a record is READ back.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
17
|
+
* **These are deliberately optimistic, and that is load-bearing.** Every column
|
|
18
|
+
* is nullable with no default (`recordsTableManager`) and only the six text
|
|
19
|
+
* types get NULL rewritten to `''`, so the wire really does answer `null` for
|
|
20
|
+
* the rest. They are typed `T | undefined` anyway, matching the pre-monorepo
|
|
21
|
+
* SDK — `| null` appears zero times in its generator.
|
|
22
|
+
*
|
|
23
|
+
* Two things have been tried and both broke apps, so before "fixing" this:
|
|
24
|
+
*
|
|
25
|
+
* 1. **Typing the truth** (`9a44ab3`). 7880 of 7880 audited 1.0 endpoints
|
|
26
|
+
* declare an `outputSchema` written as a mirror of this type with every cell
|
|
27
|
+
* `.optional()`, and `createEndpoint` constrains `execute`'s return against
|
|
28
|
+
* it. `| null` fails the app's typecheck on lines nobody edited — 52 of 144
|
|
29
|
+
* apps, ~2,100 sites.
|
|
30
|
+
* 2. **Making the runtime emit `undefined`** so the type becomes true. The
|
|
31
|
+
* idiom that breaks is `.filter(m => m.homeScore !== null)`, which silently
|
|
32
|
+
* becomes a no-op and lets empty rows through: wrong results, no error.
|
|
33
|
+
* 10 of 328 exported apps do exactly this.
|
|
34
|
+
*
|
|
35
|
+
* TODO: make these accurate in a deliberate zitejs major, once apps are
|
|
36
|
+
* migrated. Each app pins its own version, so the change only reaches apps that
|
|
37
|
+
* opt in by bumping — which is the only way to do it without breaking (2).
|
|
38
|
+
*
|
|
39
|
+
* Writes are unaffected — `null` is how you clear a cell. See
|
|
40
|
+
* `FIELD_INPUT_TYPE_MAP` and `withNull`.
|
|
23
41
|
*/
|
|
24
42
|
const FIELD_TYPE_MAP = {
|
|
25
|
-
// Text: NULL is rewritten to '' on read, so these
|
|
43
|
+
// Text: NULL is rewritten to '' on read, so these are never even absent.
|
|
26
44
|
single_line_text: "string",
|
|
27
45
|
long_text: "string",
|
|
28
46
|
rich_text: "string",
|
|
29
47
|
email: "string",
|
|
30
48
|
url: "string",
|
|
31
49
|
phone_number: "string",
|
|
32
|
-
number: "number
|
|
33
|
-
currency: "number
|
|
34
|
-
percent: "number
|
|
35
|
-
rating: "number
|
|
50
|
+
number: "number",
|
|
51
|
+
currency: "number",
|
|
52
|
+
percent: "number",
|
|
53
|
+
rating: "number",
|
|
36
54
|
// DECIMAL seconds, unlike Airtable's, which is formatted to "HH:mm:ss".
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
// 1.0 typed this `string`, which was simply wrong — base-runner groups it
|
|
56
|
+
// with the numeric types — so it stays a number rather than reverting.
|
|
57
|
+
duration: "number",
|
|
58
|
+
checkbox: "boolean",
|
|
59
|
+
single_select: "string",
|
|
60
|
+
multiple_select: "string[]",
|
|
61
|
+
date: "string",
|
|
62
|
+
datetime: "string",
|
|
63
|
+
attachments: "ZiteAttachment[]",
|
|
44
64
|
// `string | string[]`, matching the pre-monorepo type. base-runner does force
|
|
45
65
|
// an array on read today, but 1.0 code is written with `typeof x === 'string'`
|
|
46
66
|
// guards — narrowing to `string[]` turns those branches into `never` and
|
|
47
67
|
// fails the app's typecheck for no gain.
|
|
48
68
|
linked_record: "string | string[]",
|
|
49
69
|
user: "string | string[]",
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
// `any`, not `unknown`, and deliberately: these are values the platform
|
|
71
|
+
// cannot type — a lookup is whatever the far side holds. 1.0 said `any`, and
|
|
72
|
+
// `unknown` buys nothing because there is no narrowing an app could have
|
|
73
|
+
// written in advance: `sum + (x || 0)` is a TS2365 on `unknown`, and the
|
|
74
|
+
// guarded spellings already narrowed fine under `any`.
|
|
75
|
+
lookup: "any",
|
|
76
|
+
rollup: "any",
|
|
52
77
|
autonumber: "number",
|
|
53
78
|
// JSONB change metadata (`{ type: "PublicAPI", apiKeyId: 3 }`), not a string.
|
|
54
|
-
source: "
|
|
55
|
-
formula: "
|
|
79
|
+
source: "any",
|
|
80
|
+
formula: "any",
|
|
56
81
|
created_at: "string",
|
|
57
82
|
updated_at: "string",
|
|
58
|
-
updated_by: "string
|
|
83
|
+
updated_by: "string",
|
|
59
84
|
};
|
|
60
85
|
/**
|
|
61
86
|
* Field types whose WRITE shape differs from their read shape — the API accepts
|
|
@@ -214,9 +239,9 @@ function tsTypeForSchemaField(def, variant = "read") {
|
|
|
214
239
|
.map((o) => `"${o.label.replace(/"/g, '\\"')}"`)
|
|
215
240
|
.join(" | ");
|
|
216
241
|
const union = `${literals} | string`;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
242
|
+
// No `| null` on the read side — see FIELD_TYPE_MAP. The write side keeps
|
|
243
|
+
// it below, since null is how a select cell is cleared.
|
|
244
|
+
const read = def.type === "multiple_select" ? `(${union})[]` : union;
|
|
220
245
|
if (variant === "write") {
|
|
221
246
|
return def.type === "multiple_select"
|
|
222
247
|
? `${union} | (${union})[] | null`
|
package/dist/esm/auth/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createAuthClient } from 'better-auth/react';
|
|
2
2
|
import { magicLinkClient, inferAdditionalFields, } from 'better-auth/client/plugins';
|
|
3
|
+
import { USAGE_TOKEN_QUERY_PARAM } from './constants.js';
|
|
3
4
|
const authClient = createAuthClient({
|
|
4
5
|
baseURL: '',
|
|
5
6
|
plugins: [
|
|
@@ -50,10 +51,21 @@ export function loginWithRedirect(opts) {
|
|
|
50
51
|
// the address bar of every logged-out visitor to the front page, which is the
|
|
51
52
|
// common case. Only pass a destination when it IS one.
|
|
52
53
|
const isRoot = target.pathname === '/' && target.search === '' && target.hash === '';
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const params = new URLSearchParams();
|
|
55
|
+
if (!isRoot)
|
|
56
|
+
params.set('redirectUrl', target.toString());
|
|
57
|
+
// The editor preview rides its usageToken on the app URL; the sign-in page
|
|
58
|
+
// only shows its editor surface ("Preview as" picker) when that token reaches
|
|
59
|
+
// it. Capturing the current URL carries it implicitly — but an explicit
|
|
60
|
+
// `redirectUrl` names a bare path, and would silently drop it. Forward it
|
|
61
|
+
// top-level either way, so how the app phrases its redirect can't decide
|
|
62
|
+
// whether the editor gets a preview. Published visitors never have one.
|
|
63
|
+
const usageToken = new URL(window.location.href).searchParams.get(USAGE_TOKEN_QUERY_PARAM) ??
|
|
64
|
+
window._ziteUsageToken;
|
|
65
|
+
if (usageToken)
|
|
66
|
+
params.set(USAGE_TOKEN_QUERY_PARAM, usageToken);
|
|
67
|
+
const query = params.toString();
|
|
68
|
+
window.location.href = query ? `/auth/login?${query}` : '/auth/login';
|
|
57
69
|
}
|
|
58
70
|
export function logout(opts) {
|
|
59
71
|
signOut().then(() => {
|
|
@@ -112,4 +112,43 @@ describe('loginWithRedirect', () => {
|
|
|
112
112
|
expect(params.get('view')).toBeNull();
|
|
113
113
|
expect(params.get('redirectUrl')).toBe('https://app.zite.so/pricing');
|
|
114
114
|
});
|
|
115
|
+
it('forwards the editor usageToken past an explicit redirectUrl', () => {
|
|
116
|
+
// The editor preview rides its token on the app URL. An explicit
|
|
117
|
+
// redirectUrl names a bare path, which used to silently drop it — and with
|
|
118
|
+
// it the sign-in page's whole editor surface ("Preview as" picker).
|
|
119
|
+
const location = stubLocation('https://app.zite.so/?usageToken=tok-123');
|
|
120
|
+
authExports.loginWithRedirect({ redirectUrl: '/dashboard' });
|
|
121
|
+
const params = new URLSearchParams(location.href.split('?')[1] ?? '');
|
|
122
|
+
expect(params.get('usageToken')).toBe('tok-123');
|
|
123
|
+
expect(params.get('redirectUrl')).toBe('https://app.zite.so/dashboard');
|
|
124
|
+
});
|
|
125
|
+
it('forwards the usageToken top-level on a bare call too', () => {
|
|
126
|
+
const location = stubLocation('https://app.zite.so/orders/123?usageToken=tok-123');
|
|
127
|
+
authExports.loginWithRedirect();
|
|
128
|
+
const params = new URLSearchParams(location.href.split('?')[1] ?? '');
|
|
129
|
+
expect(params.get('usageToken')).toBe('tok-123');
|
|
130
|
+
});
|
|
131
|
+
it('carries a token to a root destination that would otherwise say nothing', () => {
|
|
132
|
+
const location = stubLocation('https://app.zite.so/?usageToken=tok-123');
|
|
133
|
+
// The token is the whole search string here only if nothing else rides
|
|
134
|
+
// along — an explicit root override drops the query, exercising the
|
|
135
|
+
// token-only branch.
|
|
136
|
+
authExports.loginWithRedirect({ redirectUrl: '/' });
|
|
137
|
+
expect(location.href).toBe('/auth/login?usageToken=tok-123');
|
|
138
|
+
});
|
|
139
|
+
it('falls back to window._ziteUsageToken when the URL was scrubbed', () => {
|
|
140
|
+
// app-runner's injected boot script moves the token off the address bar
|
|
141
|
+
// into this global before the bundle runs.
|
|
142
|
+
const location = stubLocation('https://app.zite.so/pricing');
|
|
143
|
+
window._ziteUsageToken = 'tok-456';
|
|
144
|
+
authExports.loginWithRedirect({ redirectUrl: '/dashboard' });
|
|
145
|
+
const params = new URLSearchParams(location.href.split('?')[1] ?? '');
|
|
146
|
+
expect(params.get('usageToken')).toBe('tok-456');
|
|
147
|
+
});
|
|
148
|
+
it('adds no token param for ordinary visitors', () => {
|
|
149
|
+
const location = stubLocation('https://app.zite.so/pricing');
|
|
150
|
+
authExports.loginWithRedirect();
|
|
151
|
+
const params = new URLSearchParams(location.href.split('?')[1] ?? '');
|
|
152
|
+
expect(params.get('usageToken')).toBeNull();
|
|
153
|
+
});
|
|
115
154
|
});
|
package/dist/esm/sync/lib.js
CHANGED
|
@@ -3,48 +3,73 @@ const AUTH_USERS_TABLE_ID = "zite_user";
|
|
|
3
3
|
/**
|
|
4
4
|
* What a field's value looks like when a record is READ back.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* **These are deliberately optimistic, and that is load-bearing.** Every column
|
|
7
|
+
* is nullable with no default (`recordsTableManager`) and only the six text
|
|
8
|
+
* types get NULL rewritten to `''`, so the wire really does answer `null` for
|
|
9
|
+
* the rest. They are typed `T | undefined` anyway, matching the pre-monorepo
|
|
10
|
+
* SDK — `| null` appears zero times in its generator.
|
|
11
|
+
*
|
|
12
|
+
* Two things have been tried and both broke apps, so before "fixing" this:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Typing the truth** (`9a44ab3`). 7880 of 7880 audited 1.0 endpoints
|
|
15
|
+
* declare an `outputSchema` written as a mirror of this type with every cell
|
|
16
|
+
* `.optional()`, and `createEndpoint` constrains `execute`'s return against
|
|
17
|
+
* it. `| null` fails the app's typecheck on lines nobody edited — 52 of 144
|
|
18
|
+
* apps, ~2,100 sites.
|
|
19
|
+
* 2. **Making the runtime emit `undefined`** so the type becomes true. The
|
|
20
|
+
* idiom that breaks is `.filter(m => m.homeScore !== null)`, which silently
|
|
21
|
+
* becomes a no-op and lets empty rows through: wrong results, no error.
|
|
22
|
+
* 10 of 328 exported apps do exactly this.
|
|
23
|
+
*
|
|
24
|
+
* TODO: make these accurate in a deliberate zitejs major, once apps are
|
|
25
|
+
* migrated. Each app pins its own version, so the change only reaches apps that
|
|
26
|
+
* opt in by bumping — which is the only way to do it without breaking (2).
|
|
27
|
+
*
|
|
28
|
+
* Writes are unaffected — `null` is how you clear a cell. See
|
|
29
|
+
* `FIELD_INPUT_TYPE_MAP` and `withNull`.
|
|
12
30
|
*/
|
|
13
31
|
const FIELD_TYPE_MAP = {
|
|
14
|
-
// Text: NULL is rewritten to '' on read, so these
|
|
32
|
+
// Text: NULL is rewritten to '' on read, so these are never even absent.
|
|
15
33
|
single_line_text: "string",
|
|
16
34
|
long_text: "string",
|
|
17
35
|
rich_text: "string",
|
|
18
36
|
email: "string",
|
|
19
37
|
url: "string",
|
|
20
38
|
phone_number: "string",
|
|
21
|
-
number: "number
|
|
22
|
-
currency: "number
|
|
23
|
-
percent: "number
|
|
24
|
-
rating: "number
|
|
39
|
+
number: "number",
|
|
40
|
+
currency: "number",
|
|
41
|
+
percent: "number",
|
|
42
|
+
rating: "number",
|
|
25
43
|
// DECIMAL seconds, unlike Airtable's, which is formatted to "HH:mm:ss".
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
// 1.0 typed this `string`, which was simply wrong — base-runner groups it
|
|
45
|
+
// with the numeric types — so it stays a number rather than reverting.
|
|
46
|
+
duration: "number",
|
|
47
|
+
checkbox: "boolean",
|
|
48
|
+
single_select: "string",
|
|
49
|
+
multiple_select: "string[]",
|
|
50
|
+
date: "string",
|
|
51
|
+
datetime: "string",
|
|
52
|
+
attachments: "ZiteAttachment[]",
|
|
33
53
|
// `string | string[]`, matching the pre-monorepo type. base-runner does force
|
|
34
54
|
// an array on read today, but 1.0 code is written with `typeof x === 'string'`
|
|
35
55
|
// guards — narrowing to `string[]` turns those branches into `never` and
|
|
36
56
|
// fails the app's typecheck for no gain.
|
|
37
57
|
linked_record: "string | string[]",
|
|
38
58
|
user: "string | string[]",
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
// `any`, not `unknown`, and deliberately: these are values the platform
|
|
60
|
+
// cannot type — a lookup is whatever the far side holds. 1.0 said `any`, and
|
|
61
|
+
// `unknown` buys nothing because there is no narrowing an app could have
|
|
62
|
+
// written in advance: `sum + (x || 0)` is a TS2365 on `unknown`, and the
|
|
63
|
+
// guarded spellings already narrowed fine under `any`.
|
|
64
|
+
lookup: "any",
|
|
65
|
+
rollup: "any",
|
|
41
66
|
autonumber: "number",
|
|
42
67
|
// JSONB change metadata (`{ type: "PublicAPI", apiKeyId: 3 }`), not a string.
|
|
43
|
-
source: "
|
|
44
|
-
formula: "
|
|
68
|
+
source: "any",
|
|
69
|
+
formula: "any",
|
|
45
70
|
created_at: "string",
|
|
46
71
|
updated_at: "string",
|
|
47
|
-
updated_by: "string
|
|
72
|
+
updated_by: "string",
|
|
48
73
|
};
|
|
49
74
|
/**
|
|
50
75
|
* Field types whose WRITE shape differs from their read shape — the API accepts
|
|
@@ -203,9 +228,9 @@ function tsTypeForSchemaField(def, variant = "read") {
|
|
|
203
228
|
.map((o) => `"${o.label.replace(/"/g, '\\"')}"`)
|
|
204
229
|
.join(" | ");
|
|
205
230
|
const union = `${literals} | string`;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
231
|
+
// No `| null` on the read side — see FIELD_TYPE_MAP. The write side keeps
|
|
232
|
+
// it below, since null is how a select cell is cleared.
|
|
233
|
+
const read = def.type === "multiple_select" ? `(${union})[]` : union;
|
|
209
234
|
if (variant === "write") {
|
|
210
235
|
return def.type === "multiple_select"
|
|
211
236
|
? `${union} | (${union})[] | null`
|