@whatsaaaa/form-builder-schema 0.1.0 → 0.3.0
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/README.md +113 -6
- package/dist/index.d.ts +519 -29
- package/dist/index.js +840 -80
- package/dist/index.js.map +1 -1
- package/package.json +13 -10
package/dist/index.js
CHANGED
|
@@ -1,124 +1,884 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
-
// src/field-
|
|
2
|
+
// src/builder/build-field-schema.ts
|
|
3
|
+
/**
|
|
4
|
+
* The schema runs in this order: empty check, base schema, normalize rules, required check,
|
|
5
|
+
* then the type's default rules and the field's rules.
|
|
6
|
+
*/
|
|
7
|
+
function buildFieldSchema(checked, messages) {
|
|
8
|
+
const { field, path, type, kind } = checked;
|
|
9
|
+
const required = !!field.required;
|
|
10
|
+
const requiredMessage = messages.resolve(typeof field.required === "object" && field.required.message || messages.required, {
|
|
11
|
+
field,
|
|
12
|
+
path
|
|
13
|
+
});
|
|
14
|
+
const normalize = [];
|
|
15
|
+
const validate = [];
|
|
16
|
+
for (const rule of checked.rules) {
|
|
17
|
+
const message = rule.message === void 0 ? void 0 : messages.resolve(rule.message, {
|
|
18
|
+
field,
|
|
19
|
+
rule: rule.name,
|
|
20
|
+
path
|
|
21
|
+
});
|
|
22
|
+
const action = rule.def.create(rule.params, message);
|
|
23
|
+
if (rule.def.stage === "normalize") normalize.push(action);
|
|
24
|
+
else validate.push(action);
|
|
25
|
+
}
|
|
26
|
+
const base = type.schema(field, { path });
|
|
27
|
+
const requiredCheck = required ? kind.required?.(requiredMessage) : void 0;
|
|
28
|
+
const head = pipe(base, requiredCheck ? [...normalize, requiredCheck] : normalize);
|
|
29
|
+
const empty = required ? v.never(requiredMessage) : v.pipe(v.unknown(), v.transform(() => void 0));
|
|
30
|
+
const whenFilled = (schema) => v.lazy((input) => kind.isEmpty(input) ? empty : schema);
|
|
31
|
+
const schema = whenFilled(v.pipe(head, whenFilled(pipe(v.unknown(), validate))));
|
|
32
|
+
return required ? schema : v.optional(schema);
|
|
33
|
+
}
|
|
34
|
+
const pipe = (schema, actions) => actions.reduce((acc, action) => v.pipe(acc, action), schema);
|
|
35
|
+
// src/field-types/default-field-types.ts
|
|
3
36
|
const defaultFieldTypes = {
|
|
4
37
|
text: {
|
|
5
38
|
kind: "string",
|
|
6
|
-
|
|
39
|
+
schema: () => v.string()
|
|
7
40
|
},
|
|
8
41
|
password: {
|
|
9
42
|
kind: "string",
|
|
10
|
-
|
|
43
|
+
schema: () => v.string()
|
|
11
44
|
},
|
|
12
45
|
email: {
|
|
13
46
|
kind: "string",
|
|
14
|
-
|
|
15
|
-
defaultRules: [{
|
|
16
|
-
rule: "email",
|
|
17
|
-
message: "Invalid email"
|
|
18
|
-
}]
|
|
47
|
+
schema: () => v.string(),
|
|
48
|
+
defaultRules: [{ rule: "email" }]
|
|
19
49
|
},
|
|
20
50
|
number: {
|
|
21
51
|
kind: "number",
|
|
22
|
-
|
|
52
|
+
schema: () => v.number()
|
|
23
53
|
}
|
|
24
54
|
};
|
|
25
|
-
// src/
|
|
55
|
+
// src/kinds/default-kinds.ts
|
|
56
|
+
const isNil = (input) => input === void 0 || input === null;
|
|
57
|
+
const defaultKinds = {
|
|
58
|
+
string: {
|
|
59
|
+
isEmpty: (input) => isNil(input) || input === "",
|
|
60
|
+
required: (message) => v.nonEmpty(message)
|
|
61
|
+
},
|
|
62
|
+
number: { isEmpty: (input) => isNil(input) || input === "" || Number.isNaN(input) },
|
|
63
|
+
boolean: {
|
|
64
|
+
isEmpty: isNil,
|
|
65
|
+
required: (message) => v.value(true, message)
|
|
66
|
+
},
|
|
67
|
+
date: {
|
|
68
|
+
isEmpty: (input) => isNil(input) || input === "",
|
|
69
|
+
required: (message) => v.nonEmpty(message)
|
|
70
|
+
},
|
|
71
|
+
array: {
|
|
72
|
+
isEmpty: (input) => isNil(input) || Array.isArray(input) && input.length === 0,
|
|
73
|
+
required: (message) => v.minLength(1, message)
|
|
74
|
+
},
|
|
75
|
+
file: { isEmpty: isNil },
|
|
76
|
+
object: { isEmpty: isNil },
|
|
77
|
+
none: { isEmpty: () => true }
|
|
78
|
+
};
|
|
79
|
+
// src/rules/define-rule.ts
|
|
26
80
|
const defineRule = (def) => def;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
81
|
+
// src/rules/param-schemas.ts
|
|
82
|
+
/** A length, count or size: a whole number from 0. */
|
|
83
|
+
const count = v.pipe(v.number(), v.integer(), v.minValue(0));
|
|
84
|
+
const countParams = v.object({ value: count });
|
|
85
|
+
const isIsoValue = (input) => v.ISO_DATE_REGEX.test(input) || v.ISO_DATE_TIME_REGEX.test(input) || v.ISO_TIME_REGEX.test(input);
|
|
86
|
+
/** Valibot compares dates as ISO strings, so the param must be one too. */
|
|
87
|
+
const isoValue = v.pipe(v.string(), v.check(isIsoValue, "Use an ISO date, date-time or time, such as 2026-01-01"));
|
|
88
|
+
const valueSchema = (kind) => {
|
|
89
|
+
switch (kind) {
|
|
90
|
+
case "date": return isoValue;
|
|
91
|
+
case "string": return v.string();
|
|
92
|
+
case "boolean": return v.boolean();
|
|
93
|
+
default: return v.number();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
// src/rules/comparison-rules.ts
|
|
97
|
+
const ordered = ["number", "date"];
|
|
98
|
+
const any = [
|
|
99
|
+
"number",
|
|
100
|
+
"date",
|
|
101
|
+
"string",
|
|
102
|
+
"boolean"
|
|
103
|
+
];
|
|
104
|
+
const meta$6 = (label) => ({
|
|
105
|
+
label,
|
|
106
|
+
category: "comparison"
|
|
107
|
+
});
|
|
108
|
+
const valueParams = (kind) => v.object({ value: valueSchema(kind) });
|
|
109
|
+
const valuesParams = (kind) => v.object({ values: v.pipe(v.array(valueSchema(kind)), v.minLength(1, "Give at least one value")) });
|
|
110
|
+
/** Compares the value with a param of the same type. */
|
|
111
|
+
const comparisonRules = {
|
|
112
|
+
minValue: defineRule({
|
|
113
|
+
kinds: ordered,
|
|
114
|
+
params: valueParams,
|
|
115
|
+
meta: meta$6("Minimum"),
|
|
116
|
+
create: (p, m) => v.minValue(p.value, m)
|
|
117
|
+
}),
|
|
118
|
+
maxValue: defineRule({
|
|
119
|
+
kinds: ordered,
|
|
120
|
+
params: valueParams,
|
|
121
|
+
meta: meta$6("Maximum"),
|
|
122
|
+
create: (p, m) => v.maxValue(p.value, m)
|
|
123
|
+
}),
|
|
124
|
+
gtValue: defineRule({
|
|
125
|
+
kinds: ordered,
|
|
126
|
+
params: valueParams,
|
|
127
|
+
meta: meta$6("Greater than"),
|
|
128
|
+
create: (p, m) => v.gtValue(p.value, m)
|
|
129
|
+
}),
|
|
130
|
+
ltValue: defineRule({
|
|
131
|
+
kinds: ordered,
|
|
132
|
+
params: valueParams,
|
|
133
|
+
meta: meta$6("Less than"),
|
|
134
|
+
create: (p, m) => v.ltValue(p.value, m)
|
|
135
|
+
}),
|
|
136
|
+
value: defineRule({
|
|
137
|
+
kinds: any,
|
|
138
|
+
params: valueParams,
|
|
139
|
+
meta: meta$6("Equals"),
|
|
140
|
+
create: (p, m) => v.value(p.value, m)
|
|
141
|
+
}),
|
|
142
|
+
notValue: defineRule({
|
|
143
|
+
kinds: any,
|
|
144
|
+
params: valueParams,
|
|
145
|
+
meta: meta$6("Does not equal"),
|
|
146
|
+
create: (p, m) => v.notValue(p.value, m)
|
|
147
|
+
}),
|
|
148
|
+
values: defineRule({
|
|
149
|
+
kinds: ordered,
|
|
150
|
+
params: valuesParams,
|
|
151
|
+
meta: meta$6("One of"),
|
|
152
|
+
create: (p, m) => v.values(p.values, m)
|
|
153
|
+
}),
|
|
154
|
+
notValues: defineRule({
|
|
155
|
+
kinds: ordered,
|
|
156
|
+
params: valuesParams,
|
|
157
|
+
meta: meta$6("None of"),
|
|
158
|
+
create: (p, m) => v.notValues(p.values, m)
|
|
159
|
+
})
|
|
160
|
+
};
|
|
161
|
+
// src/rules/content-rules.ts
|
|
162
|
+
const meta$5 = (label, description) => ({
|
|
163
|
+
label,
|
|
164
|
+
description,
|
|
165
|
+
category: "content"
|
|
166
|
+
});
|
|
167
|
+
const regexError = (pattern, flags = "") => {
|
|
168
|
+
try {
|
|
169
|
+
new RegExp(pattern, flags);
|
|
170
|
+
return;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return error.message;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const FLAGS = /^[dgimsuvy]*$/;
|
|
176
|
+
const regexParams = v.pipe(v.object({
|
|
177
|
+
pattern: v.string(),
|
|
178
|
+
flags: v.optional(v.pipe(v.string(), v.regex(FLAGS, "Flags can be d, g, i, m, s, u, v and y")))
|
|
179
|
+
}), v.forward(v.check((p) => !FLAGS.test(p.flags ?? "") || regexError(p.pattern, p.flags) === void 0, (issue) => regexError(issue.input.pattern, issue.input.flags) ?? "Invalid pattern"), ["pattern"]));
|
|
180
|
+
const item = v.union([
|
|
181
|
+
v.string(),
|
|
182
|
+
v.number(),
|
|
183
|
+
v.boolean()
|
|
184
|
+
]);
|
|
185
|
+
const stringParams = v.object({ value: v.string() });
|
|
186
|
+
const contentParams = (kind) => v.object({ value: kind === "array" ? item : v.string() });
|
|
187
|
+
const contentRules = {
|
|
188
|
+
regex: defineRule({
|
|
189
|
+
kinds: ["string"],
|
|
190
|
+
params: regexParams,
|
|
191
|
+
meta: meta$5("Matches pattern", "A JavaScript regular expression, without the slashes."),
|
|
192
|
+
create: (p, m) => v.regex(new RegExp(p.pattern, p.flags?.replace(/[gy]/g, "")), m)
|
|
193
|
+
}),
|
|
194
|
+
startsWith: defineRule({
|
|
195
|
+
kinds: ["string"],
|
|
196
|
+
params: stringParams,
|
|
197
|
+
meta: meta$5("Starts with"),
|
|
198
|
+
create: (p, m) => v.startsWith(p.value, m)
|
|
199
|
+
}),
|
|
200
|
+
endsWith: defineRule({
|
|
201
|
+
kinds: ["string"],
|
|
202
|
+
params: stringParams,
|
|
203
|
+
meta: meta$5("Ends with"),
|
|
204
|
+
create: (p, m) => v.endsWith(p.value, m)
|
|
205
|
+
}),
|
|
206
|
+
includes: defineRule({
|
|
207
|
+
kinds: ["string", "array"],
|
|
208
|
+
params: contentParams,
|
|
209
|
+
meta: meta$5("Contains"),
|
|
210
|
+
create: (p, m) => v.includes(p.value, m)
|
|
211
|
+
}),
|
|
212
|
+
excludes: defineRule({
|
|
213
|
+
kinds: ["string", "array"],
|
|
214
|
+
params: contentParams,
|
|
215
|
+
meta: meta$5("Does not contain"),
|
|
216
|
+
create: (p, m) => v.excludes(p.value, m)
|
|
217
|
+
})
|
|
218
|
+
};
|
|
219
|
+
// src/rules/date-rules.ts
|
|
220
|
+
const iso = (label, description, create) => defineRule({
|
|
221
|
+
kinds: ["string", "date"],
|
|
222
|
+
meta: {
|
|
223
|
+
label,
|
|
224
|
+
description,
|
|
225
|
+
category: "date"
|
|
226
|
+
},
|
|
227
|
+
create: (_, message) => create(message)
|
|
228
|
+
});
|
|
229
|
+
const dateRules = {
|
|
230
|
+
isoDate: iso("ISO date", "Like 2026-10-28", (m) => v.isoDate(m)),
|
|
231
|
+
isoDateTime: iso("ISO date and time", "Like 2026-10-28T14:30", (m) => v.isoDateTime(m)),
|
|
232
|
+
isoTime: iso("ISO time", "Like 14:30", (m) => v.isoTime(m)),
|
|
233
|
+
isoTimeSecond: iso("ISO time with seconds", "Like 14:30:15", (m) => v.isoTimeSecond(m)),
|
|
234
|
+
isoTimestamp: iso("ISO timestamp", "Like 2026-10-28T14:30:15.000z", (m) => v.isoTimestamp(m)),
|
|
235
|
+
isoWeek: iso("ISO week", "Like 2026-W44", (m) => v.isoWeek(m))
|
|
236
|
+
};
|
|
237
|
+
// src/rules/file-rules.ts
|
|
238
|
+
const kinds$3 = ["file"];
|
|
239
|
+
const meta$4 = (label, description) => ({
|
|
240
|
+
label,
|
|
241
|
+
description,
|
|
242
|
+
category: "file"
|
|
243
|
+
});
|
|
244
|
+
const bytes = "In bytes, e.g. 1048576 for 1 MiB.";
|
|
245
|
+
const mimeTypeParams = v.object({ types: v.pipe(v.array(v.pipe(v.string(), v.regex(/^[\w.+-]+\/[\w.+-]+$/, "Use a MIME type, such as image/png"))), v.minLength(1, "Give at least one MIME type")) });
|
|
246
|
+
/** Size and type of a File or Blob. */
|
|
247
|
+
const fileRules = {
|
|
248
|
+
minSize: defineRule({
|
|
249
|
+
kinds: kinds$3,
|
|
250
|
+
params: countParams,
|
|
251
|
+
meta: meta$4("Minimum size", bytes),
|
|
252
|
+
create: (p, m) => v.minSize(p.value, m)
|
|
253
|
+
}),
|
|
254
|
+
maxSize: defineRule({
|
|
255
|
+
kinds: kinds$3,
|
|
256
|
+
params: countParams,
|
|
257
|
+
meta: meta$4("Maximum size", bytes),
|
|
258
|
+
create: (p, m) => v.maxSize(p.value, m)
|
|
259
|
+
}),
|
|
260
|
+
size: defineRule({
|
|
261
|
+
kinds: kinds$3,
|
|
262
|
+
params: countParams,
|
|
263
|
+
meta: meta$4("Exact size", bytes),
|
|
264
|
+
create: (p, m) => v.size(p.value, m)
|
|
265
|
+
}),
|
|
266
|
+
notSize: defineRule({
|
|
267
|
+
kinds: kinds$3,
|
|
268
|
+
params: countParams,
|
|
269
|
+
meta: meta$4("Not this size", bytes),
|
|
270
|
+
create: (p, m) => v.notSize(p.value, m)
|
|
271
|
+
}),
|
|
272
|
+
mimeType: defineRule({
|
|
273
|
+
kinds: kinds$3,
|
|
274
|
+
params: mimeTypeParams,
|
|
275
|
+
meta: meta$4("File type"),
|
|
276
|
+
create: (p, m) => v.mimeType(p.types, m)
|
|
277
|
+
})
|
|
278
|
+
};
|
|
279
|
+
// src/rules/format-rules.ts
|
|
280
|
+
/** A string format without params. */
|
|
281
|
+
const format = (label, create) => defineRule({
|
|
282
|
+
kinds: ["string"],
|
|
283
|
+
meta: {
|
|
284
|
+
label,
|
|
285
|
+
category: "format"
|
|
286
|
+
},
|
|
287
|
+
create: (_, message) => create(message)
|
|
288
|
+
});
|
|
289
|
+
const hashParams = v.object({ types: v.pipe(v.array(v.picklist([
|
|
290
|
+
"md4",
|
|
291
|
+
"md5",
|
|
292
|
+
"sha1",
|
|
293
|
+
"sha256",
|
|
294
|
+
"sha384",
|
|
295
|
+
"sha512",
|
|
296
|
+
"ripemd128",
|
|
297
|
+
"ripemd160",
|
|
298
|
+
"tiger128",
|
|
299
|
+
"tiger160",
|
|
300
|
+
"tiger192",
|
|
301
|
+
"crc32",
|
|
302
|
+
"crc32b",
|
|
303
|
+
"adler32"
|
|
304
|
+
])), v.minLength(1, "Pick at least one hash type")) });
|
|
305
|
+
/** Formats Valibot can check on a string. */
|
|
306
|
+
const formatRules = {
|
|
307
|
+
email: format("Email", (m) => v.email(m)),
|
|
308
|
+
rfcEmail: format("Email (RFC 5322)", (m) => v.rfcEmail(m)),
|
|
309
|
+
url: format("URL", (m) => v.url(m)),
|
|
310
|
+
domain: format("Domain", (m) => v.domain(m)),
|
|
311
|
+
slug: format("Slug", (m) => v.slug(m)),
|
|
312
|
+
uuid: format("UUID", (m) => v.uuid(m)),
|
|
313
|
+
ulid: format("ULID", (m) => v.ulid(m)),
|
|
314
|
+
cuid2: format("CUID2", (m) => v.cuid2(m)),
|
|
315
|
+
nanoid: format("Nano ID", (m) => v.nanoid(m)),
|
|
316
|
+
ip: format("IP address", (m) => v.ip(m)),
|
|
317
|
+
ipv4: format("IPv4 address", (m) => v.ipv4(m)),
|
|
318
|
+
ipv6: format("IPv6 address", (m) => v.ipv6(m)),
|
|
319
|
+
mac: format("MAC address", (m) => v.mac(m)),
|
|
320
|
+
mac48: format("MAC-48 address", (m) => v.mac48(m)),
|
|
321
|
+
mac64: format("MAC-64 address", (m) => v.mac64(m)),
|
|
322
|
+
hexColor: format("Hex colour", (m) => v.hexColor(m)),
|
|
323
|
+
hexadecimal: format("Hexadecimal", (m) => v.hexadecimal(m)),
|
|
324
|
+
octal: format("Octal", (m) => v.octal(m)),
|
|
325
|
+
decimal: format("Decimal number", (m) => v.decimal(m)),
|
|
326
|
+
digits: format("Digits only", (m) => v.digits(m)),
|
|
327
|
+
base64: format("Base64", (m) => v.base64(m)),
|
|
328
|
+
bic: format("BIC (SWIFT code)", (m) => v.bic(m)),
|
|
329
|
+
creditCard: format("Credit card number", (m) => v.creditCard(m)),
|
|
330
|
+
imei: format("IMEI", (m) => v.imei(m)),
|
|
331
|
+
isbn: format("ISBN", (m) => v.isbn(m)),
|
|
332
|
+
isrc: format("ISRC", (m) => v.isrc(m)),
|
|
333
|
+
emoji: format("Emoji", (m) => v.emoji(m)),
|
|
334
|
+
hash: defineRule({
|
|
31
335
|
kinds: ["string"],
|
|
32
|
-
params:
|
|
336
|
+
params: hashParams,
|
|
337
|
+
meta: {
|
|
338
|
+
label: "Hash",
|
|
339
|
+
category: "format"
|
|
340
|
+
},
|
|
341
|
+
create: (p, m) => v.hash(p.types, m)
|
|
342
|
+
})
|
|
343
|
+
};
|
|
344
|
+
// src/rules/length-rules.ts
|
|
345
|
+
const kinds$2 = ["string", "array"];
|
|
346
|
+
const meta$3 = (label) => ({
|
|
347
|
+
label,
|
|
348
|
+
category: "length"
|
|
349
|
+
});
|
|
350
|
+
/** Characters of a string, or items of an array */
|
|
351
|
+
const lengthRules = {
|
|
352
|
+
length: defineRule({
|
|
353
|
+
kinds: kinds$2,
|
|
354
|
+
params: countParams,
|
|
355
|
+
meta: meta$3("Exact length"),
|
|
356
|
+
create: (p, m) => v.length(p.value, m)
|
|
357
|
+
}),
|
|
358
|
+
minLength: defineRule({
|
|
359
|
+
kinds: kinds$2,
|
|
360
|
+
params: countParams,
|
|
361
|
+
meta: meta$3("Minimum length"),
|
|
33
362
|
create: (p, m) => v.minLength(p.value, m)
|
|
34
363
|
}),
|
|
35
364
|
maxLength: defineRule({
|
|
36
|
-
kinds:
|
|
37
|
-
params:
|
|
365
|
+
kinds: kinds$2,
|
|
366
|
+
params: countParams,
|
|
367
|
+
meta: meta$3("Maximum length"),
|
|
38
368
|
create: (p, m) => v.maxLength(p.value, m)
|
|
39
369
|
}),
|
|
40
|
-
|
|
370
|
+
notLength: defineRule({
|
|
371
|
+
kinds: kinds$2,
|
|
372
|
+
params: countParams,
|
|
373
|
+
meta: meta$3("Not this length"),
|
|
374
|
+
create: (p, m) => v.notLength(p.value, m)
|
|
375
|
+
})
|
|
376
|
+
};
|
|
377
|
+
// src/rules/normalize-rules.ts
|
|
378
|
+
const stage = "normalize";
|
|
379
|
+
const meta$2 = (label, description) => ({
|
|
380
|
+
label,
|
|
381
|
+
description,
|
|
382
|
+
category: "normalize"
|
|
383
|
+
});
|
|
384
|
+
const limit = v.object({ value: v.number() });
|
|
385
|
+
/**
|
|
386
|
+
* Rules that change the value instead of checking it. They run before the required check, so
|
|
387
|
+
* `' '` with `trim` is empty. Their `message` is never shown.
|
|
388
|
+
*/
|
|
389
|
+
const normalizeRules = {
|
|
390
|
+
trim: defineRule({
|
|
41
391
|
kinds: ["string"],
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}),
|
|
46
|
-
create: (p, m) => v.regex(new RegExp(p.pattern, p.flags), m)
|
|
392
|
+
stage,
|
|
393
|
+
meta: meta$2("Trim"),
|
|
394
|
+
create: () => v.trim()
|
|
47
395
|
}),
|
|
48
|
-
|
|
396
|
+
trimStart: defineRule({
|
|
49
397
|
kinds: ["string"],
|
|
50
|
-
|
|
51
|
-
|
|
398
|
+
stage,
|
|
399
|
+
meta: meta$2("Trim start"),
|
|
400
|
+
create: () => v.trimStart()
|
|
52
401
|
}),
|
|
53
|
-
|
|
402
|
+
trimEnd: defineRule({
|
|
54
403
|
kinds: ["string"],
|
|
55
|
-
|
|
56
|
-
|
|
404
|
+
stage,
|
|
405
|
+
meta: meta$2("Trim end"),
|
|
406
|
+
create: () => v.trimEnd()
|
|
57
407
|
}),
|
|
58
|
-
|
|
59
|
-
kinds: ["
|
|
60
|
-
|
|
61
|
-
|
|
408
|
+
toLowerCase: defineRule({
|
|
409
|
+
kinds: ["string"],
|
|
410
|
+
stage,
|
|
411
|
+
meta: meta$2("Lowercase"),
|
|
412
|
+
create: () => v.toLowerCase()
|
|
62
413
|
}),
|
|
63
|
-
|
|
414
|
+
toUpperCase: defineRule({
|
|
415
|
+
kinds: ["string"],
|
|
416
|
+
stage,
|
|
417
|
+
meta: meta$2("Uppercase"),
|
|
418
|
+
create: () => v.toUpperCase()
|
|
419
|
+
}),
|
|
420
|
+
normalize: defineRule({
|
|
421
|
+
kinds: ["string"],
|
|
422
|
+
stage,
|
|
423
|
+
params: v.object({ form: v.optional(v.picklist([
|
|
424
|
+
"NFC",
|
|
425
|
+
"NFD",
|
|
426
|
+
"NFKC",
|
|
427
|
+
"NFKD"
|
|
428
|
+
])) }),
|
|
429
|
+
meta: meta$2("Unicode normalize", "Defaults to NFC."),
|
|
430
|
+
create: (p) => v.normalize(p.form)
|
|
431
|
+
}),
|
|
432
|
+
toMinValue: defineRule({
|
|
64
433
|
kinds: ["number"],
|
|
65
|
-
|
|
66
|
-
|
|
434
|
+
stage,
|
|
435
|
+
params: limit,
|
|
436
|
+
meta: meta$2("Raise to minimum", "Replaces a smaller number with the minimum."),
|
|
437
|
+
create: (p) => v.toMinValue(p.value)
|
|
67
438
|
}),
|
|
68
|
-
|
|
439
|
+
toMaxValue: defineRule({
|
|
69
440
|
kinds: ["number"],
|
|
70
|
-
|
|
441
|
+
stage,
|
|
442
|
+
params: limit,
|
|
443
|
+
meta: meta$2("Lower to maximum", "Replaces a larger number with the maximum."),
|
|
444
|
+
create: (p) => v.toMaxValue(p.value)
|
|
445
|
+
})
|
|
446
|
+
};
|
|
447
|
+
// src/rules/number-rules.ts
|
|
448
|
+
const kinds$1 = ["number"];
|
|
449
|
+
const meta$1 = (label) => ({
|
|
450
|
+
label,
|
|
451
|
+
category: "number"
|
|
452
|
+
});
|
|
453
|
+
const divisor = v.pipe(v.number(), v.finite(), v.notValue(0, "Can't be 0"));
|
|
454
|
+
/** Checks only numbers have. */
|
|
455
|
+
const numberRules = {
|
|
456
|
+
integer: defineRule({
|
|
457
|
+
kinds: kinds$1,
|
|
458
|
+
meta: meta$1("Whole number"),
|
|
71
459
|
create: (_, m) => v.integer(m)
|
|
460
|
+
}),
|
|
461
|
+
safeInteger: defineRule({
|
|
462
|
+
kinds: kinds$1,
|
|
463
|
+
meta: meta$1("Safe whole number"),
|
|
464
|
+
create: (_, m) => v.safeInteger(m)
|
|
465
|
+
}),
|
|
466
|
+
finite: defineRule({
|
|
467
|
+
kinds: kinds$1,
|
|
468
|
+
meta: meta$1("Finite number"),
|
|
469
|
+
create: (_, m) => v.finite(m)
|
|
470
|
+
}),
|
|
471
|
+
multipleOf: defineRule({
|
|
472
|
+
kinds: kinds$1,
|
|
473
|
+
params: v.object({ value: divisor }),
|
|
474
|
+
meta: meta$1("Multiple of"),
|
|
475
|
+
create: (p, m) => v.multipleOf(p.value, m)
|
|
476
|
+
})
|
|
477
|
+
};
|
|
478
|
+
// src/rules/text-unit-rules.ts
|
|
479
|
+
const kinds = ["string"];
|
|
480
|
+
const meta = (label, description) => ({
|
|
481
|
+
label,
|
|
482
|
+
description,
|
|
483
|
+
category: "text"
|
|
484
|
+
});
|
|
485
|
+
const isLocale = (input) => {
|
|
486
|
+
try {
|
|
487
|
+
return Intl.getCanonicalLocales(input).length === 1;
|
|
488
|
+
} catch {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
const wordsParams = v.object({
|
|
493
|
+
locale: v.optional(v.pipe(v.string(), v.check(isLocale, "Use a language tag, such as en or sr-Latn"))),
|
|
494
|
+
value: count
|
|
495
|
+
});
|
|
496
|
+
const graphemesInfo = "Counts characters as people see them, so an emoji is one.";
|
|
497
|
+
const bytesInfo = "Counts UTF-8 bytes, e.g. to fit a database column,";
|
|
498
|
+
const textUnitRules = {
|
|
499
|
+
graphemes: defineRule({
|
|
500
|
+
kinds,
|
|
501
|
+
params: countParams,
|
|
502
|
+
meta: meta("Exact characters", graphemesInfo),
|
|
503
|
+
create: (p, m) => v.graphemes(p.value, m)
|
|
504
|
+
}),
|
|
505
|
+
minGraphemes: defineRule({
|
|
506
|
+
kinds,
|
|
507
|
+
params: countParams,
|
|
508
|
+
meta: meta("Minimum characters", graphemesInfo),
|
|
509
|
+
create: (p, m) => v.minGraphemes(p.value, m)
|
|
510
|
+
}),
|
|
511
|
+
maxGraphemes: defineRule({
|
|
512
|
+
kinds,
|
|
513
|
+
params: countParams,
|
|
514
|
+
meta: meta("Maximum characters", graphemesInfo),
|
|
515
|
+
create: (p, m) => v.maxGraphemes(p.value, m)
|
|
516
|
+
}),
|
|
517
|
+
notGraphemes: defineRule({
|
|
518
|
+
kinds,
|
|
519
|
+
params: countParams,
|
|
520
|
+
meta: meta("Not this many characters", graphemesInfo),
|
|
521
|
+
create: (p, m) => v.notGraphemes(p.value, m)
|
|
522
|
+
}),
|
|
523
|
+
words: defineRule({
|
|
524
|
+
kinds,
|
|
525
|
+
params: wordsParams,
|
|
526
|
+
meta: meta("Exact words"),
|
|
527
|
+
create: (p, m) => v.words(p.locale, p.value, m)
|
|
528
|
+
}),
|
|
529
|
+
minWords: defineRule({
|
|
530
|
+
kinds,
|
|
531
|
+
params: wordsParams,
|
|
532
|
+
meta: meta("Minimum words"),
|
|
533
|
+
create: (p, m) => v.minWords(p.locale, p.value, m)
|
|
534
|
+
}),
|
|
535
|
+
maxWords: defineRule({
|
|
536
|
+
kinds,
|
|
537
|
+
params: wordsParams,
|
|
538
|
+
meta: meta("Maximum words"),
|
|
539
|
+
create: (p, m) => v.maxWords(p.locale, p.value, m)
|
|
540
|
+
}),
|
|
541
|
+
notWords: defineRule({
|
|
542
|
+
kinds,
|
|
543
|
+
params: wordsParams,
|
|
544
|
+
meta: meta("Not this many words"),
|
|
545
|
+
create: (p, m) => v.notWords(p.locale, p.value, m)
|
|
546
|
+
}),
|
|
547
|
+
bytes: defineRule({
|
|
548
|
+
kinds,
|
|
549
|
+
params: countParams,
|
|
550
|
+
meta: meta("Exact bytes", bytesInfo),
|
|
551
|
+
create: (p, m) => v.bytes(p.value, m)
|
|
552
|
+
}),
|
|
553
|
+
minBytes: defineRule({
|
|
554
|
+
kinds,
|
|
555
|
+
params: countParams,
|
|
556
|
+
meta: meta("Minimum bytes", bytesInfo),
|
|
557
|
+
create: (p, m) => v.minBytes(p.value, m)
|
|
558
|
+
}),
|
|
559
|
+
maxBytes: defineRule({
|
|
560
|
+
kinds,
|
|
561
|
+
params: countParams,
|
|
562
|
+
meta: meta("Maximum bytes", bytesInfo),
|
|
563
|
+
create: (p, m) => v.maxBytes(p.value, m)
|
|
564
|
+
}),
|
|
565
|
+
notBytes: defineRule({
|
|
566
|
+
kinds,
|
|
567
|
+
params: countParams,
|
|
568
|
+
meta: meta("Not this many bytes", bytesInfo),
|
|
569
|
+
create: (p, m) => v.notBytes(p.value, m)
|
|
72
570
|
})
|
|
73
571
|
};
|
|
74
|
-
// src/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
572
|
+
// src/rules/default-rules.ts
|
|
573
|
+
/** Every built-in rule. Register single families with `includeDefaultRules: false`. */
|
|
574
|
+
const defaultRules = {
|
|
575
|
+
...lengthRules,
|
|
576
|
+
...textUnitRules,
|
|
577
|
+
...contentRules,
|
|
578
|
+
...formatRules,
|
|
579
|
+
...dateRules,
|
|
580
|
+
...comparisonRules,
|
|
581
|
+
...numberRules,
|
|
582
|
+
...fileRules,
|
|
583
|
+
...normalizeRules
|
|
584
|
+
};
|
|
585
|
+
// src/builder/registry.ts
|
|
586
|
+
/** Consumer entries replace defaults with the same key. */
|
|
587
|
+
function mergeRegistry(custom) {
|
|
588
|
+
const rules = custom.includeDefaultRules === false ? {} : defaultRules;
|
|
589
|
+
return {
|
|
590
|
+
kinds: Object.freeze({
|
|
591
|
+
...defaultKinds,
|
|
592
|
+
...custom.kinds
|
|
593
|
+
}),
|
|
594
|
+
fieldTypes: Object.freeze({
|
|
595
|
+
...defaultFieldTypes,
|
|
596
|
+
...custom.fieldTypes
|
|
597
|
+
}),
|
|
598
|
+
rules: Object.freeze({
|
|
599
|
+
...rules,
|
|
600
|
+
...custom.rules
|
|
601
|
+
})
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function lookup(record, key) {
|
|
605
|
+
return Object.hasOwn(record, key) ? record[key] : void 0;
|
|
606
|
+
}
|
|
607
|
+
// src/errors/definition-error.ts
|
|
608
|
+
function formatPath(path) {
|
|
609
|
+
return path.reduce((out, key) => {
|
|
610
|
+
if (typeof key === "number") return `${out}[${key}]`;
|
|
611
|
+
return out ? `${out}.${key}` : key;
|
|
612
|
+
}, "");
|
|
613
|
+
}
|
|
614
|
+
/** Thrown by `build()` when the definition has issues. Lists all of them. */
|
|
615
|
+
var DefinitionError = class extends Error {
|
|
616
|
+
issues;
|
|
617
|
+
constructor(issues) {
|
|
618
|
+
const lines = issues.map((issue) => ` ${formatPath(issue.path) || "(root)"}: ${issue.message}`);
|
|
619
|
+
super(`Invalid form definition:\n${lines.join("\n")}`);
|
|
620
|
+
this.name = "DefinitionError";
|
|
621
|
+
this.issues = issues;
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
// src/rules/rule-params.ts
|
|
625
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
626
|
+
function paramsFor(def, kind) {
|
|
627
|
+
if (typeof def.params !== "function") return def.params;
|
|
628
|
+
let byKind = cache.get(def);
|
|
629
|
+
if (!byKind) {
|
|
630
|
+
byKind = /* @__PURE__ */ new Map();
|
|
631
|
+
cache.set(def, byKind);
|
|
632
|
+
}
|
|
633
|
+
let params = byKind.get(kind);
|
|
634
|
+
if (!params) {
|
|
635
|
+
params = def.params(kind);
|
|
636
|
+
byKind.set(kind, params);
|
|
637
|
+
}
|
|
638
|
+
return params;
|
|
639
|
+
}
|
|
640
|
+
// src/shared/reserved-keys.ts
|
|
641
|
+
/**
|
|
642
|
+
* Field keys this package owns. A field type's props can't use them.
|
|
643
|
+
*/
|
|
644
|
+
const RESERVED_FIELD_KEYS = [
|
|
645
|
+
"name",
|
|
646
|
+
"type",
|
|
647
|
+
"label",
|
|
648
|
+
"description",
|
|
649
|
+
"placeholder",
|
|
650
|
+
"default",
|
|
651
|
+
"required",
|
|
652
|
+
"rules",
|
|
653
|
+
"ui"
|
|
654
|
+
];
|
|
655
|
+
/**
|
|
656
|
+
* Rule keys this package own. A rule's params can't use them.
|
|
657
|
+
*/
|
|
658
|
+
const RESERVED_RULE_KEYS = ["rule", "message"];
|
|
659
|
+
const fieldKeys = new Set(RESERVED_FIELD_KEYS);
|
|
660
|
+
const ruleKeys = new Set(RESERVED_RULE_KEYS);
|
|
661
|
+
const isReservedFieldKey = (key) => fieldKeys.has(key);
|
|
662
|
+
const isReservedRuleKey = (key) => ruleKeys.has(key);
|
|
663
|
+
// src/builder/validate-definition.ts
|
|
664
|
+
const formShape = v.object({
|
|
665
|
+
version: v.string(),
|
|
666
|
+
fields: v.array(v.unknown())
|
|
667
|
+
});
|
|
668
|
+
const fieldShape = v.looseObject({
|
|
669
|
+
name: v.optional(v.string()),
|
|
670
|
+
type: v.string(),
|
|
671
|
+
label: v.optional(v.string()),
|
|
672
|
+
description: v.optional(v.string()),
|
|
673
|
+
placeholder: v.optional(v.string()),
|
|
674
|
+
default: v.optional(v.unknown()),
|
|
675
|
+
required: v.optional(v.union([v.boolean(), v.strictObject({ message: v.optional(v.string()) })])),
|
|
676
|
+
rules: v.optional(v.array(v.unknown())),
|
|
677
|
+
ui: v.optional(v.record(v.string(), v.unknown()))
|
|
678
|
+
});
|
|
679
|
+
const ruleShape = v.looseObject({
|
|
680
|
+
rule: v.string(),
|
|
681
|
+
message: v.optional(v.string())
|
|
682
|
+
});
|
|
683
|
+
const noParams = v.object({});
|
|
684
|
+
/**
|
|
685
|
+
* Checks a whole definition and collects every issue, in document order. Never throws.
|
|
686
|
+
*/
|
|
687
|
+
function checkDefinition(json, registry) {
|
|
688
|
+
const issues = [];
|
|
689
|
+
const fields = [];
|
|
690
|
+
const form = v.safeParse(formShape, json);
|
|
691
|
+
if (!form.success) return {
|
|
692
|
+
issues: toIssues("invalid_shape", [], form.issues),
|
|
693
|
+
fields
|
|
694
|
+
};
|
|
695
|
+
if (form.output.version !== "1") return {
|
|
696
|
+
issues: [{
|
|
697
|
+
code: "unsupported_version",
|
|
698
|
+
path: ["version"],
|
|
699
|
+
message: `Unsupported version "${form.output.version}". This package reads version '1'.`
|
|
700
|
+
}],
|
|
701
|
+
fields
|
|
702
|
+
};
|
|
703
|
+
const names = /* @__PURE__ */ new Set();
|
|
704
|
+
form.output.fields.forEach((raw, i) => {
|
|
705
|
+
const field = checkField(raw, ["fields", i], registry, names, issues);
|
|
706
|
+
if (field) fields.push(field);
|
|
707
|
+
});
|
|
708
|
+
return {
|
|
709
|
+
issues,
|
|
710
|
+
fields
|
|
79
711
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
712
|
+
}
|
|
713
|
+
function checkField(raw, path, registry, names, issues) {
|
|
714
|
+
const shape = v.safeParse(fieldShape, raw);
|
|
715
|
+
if (!shape.success) {
|
|
716
|
+
issues.push(...toIssues("invalid_shape", path, shape.issues));
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const field = shape.output;
|
|
720
|
+
const issueCount = issues.length;
|
|
721
|
+
const type = lookup(registry.fieldTypes, field.type);
|
|
722
|
+
const hasValue = type?.kind !== "none";
|
|
723
|
+
if (!field.name) {
|
|
724
|
+
if (type && hasValue) issues.push(issue("missing_name", [...path, "name"], "A field needs a name"));
|
|
725
|
+
} else if (hasValue) {
|
|
726
|
+
if (names.has(field.name)) issues.push(issue("duplicate_name", [...path, "name"], `Duplicate name "${field.name}"`));
|
|
727
|
+
names.add(field.name);
|
|
728
|
+
}
|
|
729
|
+
if (!type) {
|
|
730
|
+
issues.push(issue("unknown_type", [...path, "type"], `Unknown field type "${field.type}"`));
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
const props = checkProps(field, type, path, issues);
|
|
734
|
+
if (!hasValue) {
|
|
735
|
+
for (const key of ["required", "rules"]) if (field[key] !== void 0) {
|
|
736
|
+
const message = `A "${field.type}" field has no value, so it can't have "${key}"`;
|
|
737
|
+
issues.push(issue("invalid_shape", [...path, key], message));
|
|
738
|
+
}
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const fieldRules = (shape.output.rules ?? []).flatMap((rule, j) => checkRule(rule, type?.kind, [
|
|
742
|
+
...path,
|
|
743
|
+
"rules",
|
|
744
|
+
j
|
|
745
|
+
], registry, issues) ?? []);
|
|
746
|
+
if (issues.length > issueCount || !field.name) return void 0;
|
|
747
|
+
const overridden = new Set(fieldRules.map((rule) => rule.name));
|
|
748
|
+
const defaultRules = (type?.defaultRules ?? []).filter((rule) => !overridden.has(rule.rule)).flatMap((rule) => checkRule(rule, type?.kind, path, registry, []) ?? []);
|
|
749
|
+
return {
|
|
750
|
+
name: field.name,
|
|
751
|
+
field: {
|
|
752
|
+
...field,
|
|
753
|
+
...props
|
|
754
|
+
},
|
|
755
|
+
path,
|
|
756
|
+
type,
|
|
757
|
+
kind: registry.kinds[type?.kind],
|
|
758
|
+
rules: [...defaultRules, ...fieldRules]
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function checkProps(field, type, path, issues) {
|
|
762
|
+
const propKeys = Object.keys(type.props?.entries ?? {});
|
|
763
|
+
const input = {};
|
|
764
|
+
for (const key of Object.keys(field)) {
|
|
765
|
+
if (isReservedFieldKey(key)) continue;
|
|
766
|
+
if (propKeys.includes(key)) input[key] = field[key];
|
|
767
|
+
else issues.push(issue("unknown_key", [...path, key], `Unknown key "${key}" on a "${field.type}" field`));
|
|
768
|
+
}
|
|
769
|
+
if (!type.props) return {};
|
|
770
|
+
const parsed = v.safeParse(type.props, input);
|
|
771
|
+
if (parsed.success) return parsed.output;
|
|
772
|
+
issues.push(...toIssues("invalid_props", path, parsed.issues));
|
|
773
|
+
return {};
|
|
774
|
+
}
|
|
775
|
+
function checkRule(raw, kind, path, registry, issues) {
|
|
776
|
+
const shape = v.safeParse(ruleShape, raw);
|
|
777
|
+
if (!shape.success) {
|
|
778
|
+
issues.push(...toIssues("invalid_shape", path, shape.issues));
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const rule = shape.output;
|
|
782
|
+
const def = lookup(registry.rules, rule.rule);
|
|
783
|
+
if (!def) {
|
|
784
|
+
issues.push(issue("unknown_rule", [...path, "rule"], `Unknown rule "${rule.rule}"`));
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (!def.kinds.includes(kind)) {
|
|
788
|
+
const message = `Rule "${rule.rule}" can't be used on ${kind} fields`;
|
|
789
|
+
issues.push(issue("kind_mismatch", [...path, "rule"], message));
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const issueCount = issues.length;
|
|
793
|
+
const params = paramsFor(def, kind);
|
|
794
|
+
const paramKeys = Object.keys(params?.entries ?? {});
|
|
795
|
+
const input = {};
|
|
796
|
+
for (const key of Object.keys(rule)) {
|
|
797
|
+
if (isReservedRuleKey(key)) continue;
|
|
798
|
+
if (paramKeys.includes(key)) input[key] = rule[key];
|
|
799
|
+
else issues.push(issue("unknown_param", [...path, key], `Rule "${rule.rule}" has no param "${key}"`));
|
|
800
|
+
}
|
|
801
|
+
const parsed = v.safeParse(params ?? noParams, input);
|
|
802
|
+
if (!parsed.success) issues.push(...toIssues("invalid_params", path, parsed.issues));
|
|
803
|
+
if (!parsed.success || issues.length > issueCount) return void 0;
|
|
804
|
+
return {
|
|
805
|
+
name: rule.rule,
|
|
806
|
+
def,
|
|
807
|
+
params: parsed.output,
|
|
808
|
+
message: rule.message
|
|
83
809
|
};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
if (!
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (!type) throw new Error(`${at}: unknown field type "${field.type}"`);
|
|
96
|
-
const userRules = field.rules ?? [];
|
|
97
|
-
const overridden = new Set(userRules.map((r) => r.rule));
|
|
98
|
-
const allRules = [...(type.defaultRules ?? []).filter((r) => !overridden.has(r.rule)), ...userRules];
|
|
99
|
-
const required = !!field.required;
|
|
100
|
-
const requiredMsg = typeof field.required === "object" ? field.required.message : void 0;
|
|
101
|
-
const actions = [];
|
|
102
|
-
if (required && type.kind === "string") actions.push(v.nonEmpty(requiredMsg ?? "Required"));
|
|
103
|
-
allRules.forEach((r, j) => {
|
|
104
|
-
actions.push(buildAction(r, type.kind, `${at}.rules[${j}]`));
|
|
810
|
+
}
|
|
811
|
+
function checkRegistry(registry) {
|
|
812
|
+
for (const [name, rule] of Object.entries(registry.rules)) for (const kind of rule.kinds) for (const key of Object.keys(paramsFor(rule, kind)?.entries ?? {})) if (isReservedRuleKey(key)) throw new TypeError(`Rule "${name}": params can't use the reserved key "${key}"`);
|
|
813
|
+
for (const [name, type] of Object.entries(registry.fieldTypes)) {
|
|
814
|
+
const at = `Field type "${name}"`;
|
|
815
|
+
if (!lookup(registry.kinds, type.kind)) throw new TypeError(`${at}: kind "${type.kind}" is not registered`);
|
|
816
|
+
if (type.kind !== "none" && !type.schema) throw new TypeError(`${at}: schema() is required unless the kind is 'none'`);
|
|
817
|
+
for (const key of Object.keys(type.props?.entries ?? {})) if (isReservedFieldKey(key)) throw new TypeError(`${at}: props can't use the reserved key "${key}"`);
|
|
818
|
+
const issues = [];
|
|
819
|
+
type.defaultRules?.forEach((rule, j) => {
|
|
820
|
+
checkRule(rule, type.kind, ["defaultRules", j], registry, issues);
|
|
105
821
|
});
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
822
|
+
const [first] = issues;
|
|
823
|
+
if (first) throw new TypeError(`${at}: ${formatPath(first.path)}: ${first.message}`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
function issue(code, path, message) {
|
|
827
|
+
return {
|
|
828
|
+
code,
|
|
829
|
+
path,
|
|
830
|
+
message
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
function toIssues(code, path, valibotIssues) {
|
|
834
|
+
return valibotIssues.map((valibotIssue) => {
|
|
835
|
+
const keys = valibotIssue.path?.map((item) => item.key) ?? [];
|
|
836
|
+
return issue(code, [...path, ...keys], valibotIssue.message);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
// src/builder/create-schema-builder.ts
|
|
840
|
+
function createSchemaBuilder(options = {}) {
|
|
841
|
+
const registry = mergeRegistry(options);
|
|
842
|
+
checkRegistry(registry);
|
|
843
|
+
const messages = {
|
|
844
|
+
required: options.messages?.required ?? "Required",
|
|
845
|
+
resolve: (text, ctx) => options.resolveMessage?.(text, ctx) ?? text
|
|
846
|
+
};
|
|
847
|
+
/** Every issue in the definition, in document order. Never throws on bad input. */
|
|
848
|
+
function validate(json) {
|
|
849
|
+
const { issues } = checkDefinition(json, registry);
|
|
850
|
+
return issues.length ? {
|
|
851
|
+
success: false,
|
|
852
|
+
issues
|
|
853
|
+
} : {
|
|
854
|
+
success: true,
|
|
855
|
+
issues: []
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function build(json) {
|
|
859
|
+
const { issues, fields } = checkDefinition(json, registry);
|
|
860
|
+
if (issues.length) throw new DefinitionError(issues);
|
|
114
861
|
const entries = {};
|
|
115
|
-
|
|
116
|
-
if (field.name in entries) throw new Error(`fields[${i}]: duplicate name "${field.name}"`);
|
|
117
|
-
entries[field.name] = buildField(field, i);
|
|
118
|
-
});
|
|
862
|
+
for (const field of fields) entries[field.name] = buildFieldSchema(field, messages);
|
|
119
863
|
return v.object(entries);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* The registered rules that can be used on a field of `kind`, e.g. for a rule picker.
|
|
867
|
+
*/
|
|
868
|
+
function rulesForKind(kind) {
|
|
869
|
+
return Object.entries(registry.rules).filter(([, def]) => def.kinds.includes(kind));
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
build,
|
|
873
|
+
validate,
|
|
874
|
+
rulesForKind,
|
|
875
|
+
kinds: registry.kinds,
|
|
876
|
+
fieldTypes: registry.fieldTypes,
|
|
877
|
+
rules: registry.rules
|
|
120
878
|
};
|
|
121
879
|
}
|
|
122
|
-
|
|
880
|
+
// src/field-types/define-field-type.ts
|
|
881
|
+
const defineFieldType = (def) => def;
|
|
882
|
+
export { DefinitionError, RESERVED_FIELD_KEYS, RESERVED_RULE_KEYS, comparisonRules, contentRules, createSchemaBuilder, dateRules, defaultFieldTypes, defaultKinds, defaultRules, defineFieldType, defineRule, fileRules, formatPath, formatRules, lengthRules, normalizeRules, numberRules, textUnitRules };
|
|
123
883
|
|
|
124
884
|
//# sourceMappingURL=index.js.map
|