ultraenv 1.0.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/LICENSE +21 -0
- package/README.md +2058 -0
- package/bin/ultraenv.mjs +3 -0
- package/dist/chunk-2USZPWLZ.js +288 -0
- package/dist/chunk-3UV2QNJL.js +270 -0
- package/dist/chunk-3VYXPTYV.js +179 -0
- package/dist/chunk-4XUYMRK5.js +366 -0
- package/dist/chunk-5G2DU52U.js +189 -0
- package/dist/chunk-6KS56D6E.js +172 -0
- package/dist/chunk-AWN6ADV7.js +328 -0
- package/dist/chunk-CHVO6NWI.js +203 -0
- package/dist/chunk-CIFMBJ4H.js +3975 -0
- package/dist/chunk-GC7RXHLA.js +253 -0
- package/dist/chunk-HFXQGJY3.js +445 -0
- package/dist/chunk-IGFVP24Q.js +91 -0
- package/dist/chunk-IKPTKALB.js +78 -0
- package/dist/chunk-JB7RKV3C.js +66 -0
- package/dist/chunk-MNVFG7H4.js +611 -0
- package/dist/chunk-MSXMESFP.js +1910 -0
- package/dist/chunk-N5PAV4NM.js +127 -0
- package/dist/chunk-NBOABPHM.js +158 -0
- package/dist/chunk-OMAOROL4.js +49 -0
- package/dist/chunk-R7PZRSZ7.js +105 -0
- package/dist/chunk-TE7HPLA6.js +73 -0
- package/dist/chunk-TMT5KCO3.js +101 -0
- package/dist/chunk-UEWYFN6A.js +189 -0
- package/dist/chunk-WMHN5RW2.js +128 -0
- package/dist/chunk-XC65ORJ5.js +70 -0
- package/dist/chunk-YMMP4VQL.js +118 -0
- package/dist/chunk-YN2KGTCB.js +33 -0
- package/dist/chunk-YTICOB5M.js +65 -0
- package/dist/chunk-YVWLXFUT.js +107 -0
- package/dist/ci-check-sync-VBMSVWIV.js +48 -0
- package/dist/ci-scan-24MT5XGS.js +41 -0
- package/dist/ci-setup-C2NKEFRD.js +135 -0
- package/dist/ci-validate-7AW24LSQ.js +57 -0
- package/dist/cli/index.cjs +9217 -0
- package/dist/cli/index.d.cts +9 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +339 -0
- package/dist/comparator-RDKX3OI7.js +13 -0
- package/dist/completion-MW35C2XO.js +168 -0
- package/dist/config-O5YRQP5Z.js +13 -0
- package/dist/debug-PTPXAF3K.js +131 -0
- package/dist/declaration-LEME4AFZ.js +10 -0
- package/dist/doctor-FZAUPKHS.js +129 -0
- package/dist/envs-compare-5K3HESX5.js +49 -0
- package/dist/envs-create-2XXHXMGA.js +58 -0
- package/dist/envs-list-NQM5252B.js +59 -0
- package/dist/envs-switch-6L2AQYID.js +50 -0
- package/dist/envs-validate-FL73Q76T.js +89 -0
- package/dist/fs-VH7ATUS3.js +31 -0
- package/dist/generator-LFZBMZZS.js +14 -0
- package/dist/git-BZS4DPAI.js +30 -0
- package/dist/help-3XJBXEHE.js +121 -0
- package/dist/index.cjs +12907 -0
- package/dist/index.d.cts +2562 -0
- package/dist/index.d.ts +2562 -0
- package/dist/index.js +3212 -0
- package/dist/init-Y7JQ2KYJ.js +146 -0
- package/dist/install-hook-SKXIV6NV.js +111 -0
- package/dist/json-schema-I26YNQBH.js +10 -0
- package/dist/key-manager-O3G55WPU.js +25 -0
- package/dist/middleware/express.cjs +103 -0
- package/dist/middleware/express.d.cts +115 -0
- package/dist/middleware/express.d.ts +115 -0
- package/dist/middleware/express.js +8 -0
- package/dist/middleware/fastify.cjs +91 -0
- package/dist/middleware/fastify.d.cts +111 -0
- package/dist/middleware/fastify.d.ts +111 -0
- package/dist/middleware/fastify.js +8 -0
- package/dist/module-IDIZPP4M.js +10 -0
- package/dist/protect-NCWPM6VC.js +161 -0
- package/dist/scan-TRLY36TT.js +58 -0
- package/dist/schema/index.cjs +4074 -0
- package/dist/schema/index.d.cts +1244 -0
- package/dist/schema/index.d.ts +1244 -0
- package/dist/schema/index.js +152 -0
- package/dist/sync-TMHMTLH2.js +186 -0
- package/dist/typegen-SQOSXBWM.js +80 -0
- package/dist/validate-IOAM5HWS.js +100 -0
- package/dist/vault-decrypt-U6HJZNBV.js +111 -0
- package/dist/vault-diff-B3ZOQTWI.js +132 -0
- package/dist/vault-encrypt-GUSLCSKS.js +112 -0
- package/dist/vault-init-GUBOTOUL.js +106 -0
- package/dist/vault-rekey-DAHT7JCN.js +132 -0
- package/dist/vault-status-GDLRU2OK.js +90 -0
- package/dist/vault-verify-CD76FJSF.js +102 -0
- package/package.json +106 -0
|
@@ -0,0 +1,3975 @@
|
|
|
1
|
+
// src/schema/builder.ts
|
|
2
|
+
var SchemaBuilder = class _SchemaBuilder {
|
|
3
|
+
/** Internal parser function: string → ParseResult<TOutput> */
|
|
4
|
+
_parser;
|
|
5
|
+
/** Ordered list of validator functions: value → error message or null */
|
|
6
|
+
_validators;
|
|
7
|
+
/** Ordered list of transform functions applied after validation */
|
|
8
|
+
_transforms;
|
|
9
|
+
/** Schema metadata */
|
|
10
|
+
_meta;
|
|
11
|
+
constructor(parser, typeName) {
|
|
12
|
+
this._parser = parser;
|
|
13
|
+
this._validators = [];
|
|
14
|
+
this._transforms = [];
|
|
15
|
+
this._meta = {
|
|
16
|
+
typeName,
|
|
17
|
+
required: true,
|
|
18
|
+
hasDefault: false,
|
|
19
|
+
isSecret: false,
|
|
20
|
+
isDeprecated: false,
|
|
21
|
+
aliases: []
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// -------------------------------------------------------------------------
|
|
25
|
+
// Core Parsing & Validation
|
|
26
|
+
// -------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Parse a raw string value into the output type.
|
|
29
|
+
* Runs the parser, then all validators and transforms.
|
|
30
|
+
*/
|
|
31
|
+
_parse(raw) {
|
|
32
|
+
const parsed = this._parser(raw);
|
|
33
|
+
if (!parsed.success) {
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
const validationError = this._validate(parsed.value);
|
|
37
|
+
if (validationError !== null) {
|
|
38
|
+
return { success: false, error: validationError };
|
|
39
|
+
}
|
|
40
|
+
const transformed = this._applyTransforms(parsed.value);
|
|
41
|
+
return { success: true, value: transformed };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate an already-parsed value against all registered validators.
|
|
45
|
+
* Returns an error message string, or null if validation passes.
|
|
46
|
+
*/
|
|
47
|
+
_validate(value) {
|
|
48
|
+
for (const validator of this._validators) {
|
|
49
|
+
const error = validator(value);
|
|
50
|
+
if (error !== null) {
|
|
51
|
+
return error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
// -------------------------------------------------------------------------
|
|
57
|
+
// Metadata Accessors
|
|
58
|
+
// -------------------------------------------------------------------------
|
|
59
|
+
/** Get a readonly view of the schema metadata */
|
|
60
|
+
get meta() {
|
|
61
|
+
return this._meta;
|
|
62
|
+
}
|
|
63
|
+
/** Whether this schema is effectively optional (marked optional or has default) */
|
|
64
|
+
get isOptional() {
|
|
65
|
+
return !this._meta.required || this._meta.hasDefault;
|
|
66
|
+
}
|
|
67
|
+
/** The runtime type name string */
|
|
68
|
+
get typeName() {
|
|
69
|
+
return this._meta.typeName;
|
|
70
|
+
}
|
|
71
|
+
/** Get the default value, parsing it if a raw default exists */
|
|
72
|
+
getDefaultValue() {
|
|
73
|
+
if (!this._meta.hasDefault || this._meta.rawDefaultValue === void 0) {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
const result = this._parse(this._meta.rawDefaultValue);
|
|
77
|
+
return result.success ? result.value : void 0;
|
|
78
|
+
}
|
|
79
|
+
/** Return a composed reference to this builder for chaining */
|
|
80
|
+
get chain() {
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
// -------------------------------------------------------------------------
|
|
84
|
+
// Modifiers — Description & Example
|
|
85
|
+
// -------------------------------------------------------------------------
|
|
86
|
+
/** Add a human-readable description */
|
|
87
|
+
description(desc) {
|
|
88
|
+
this._meta = { ...this._meta, description: desc };
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
/** Set an example value for documentation */
|
|
92
|
+
example(ex) {
|
|
93
|
+
this._meta = { ...this._meta, example: String(ex) };
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
// -------------------------------------------------------------------------
|
|
97
|
+
// Modifiers — Required / Optional / Default
|
|
98
|
+
// -------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Mark this field as optional. The output type becomes `TOutput | undefined`.
|
|
101
|
+
*/
|
|
102
|
+
optional() {
|
|
103
|
+
this._meta = { ...this._meta, required: false };
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Mark this field as required.
|
|
108
|
+
*/
|
|
109
|
+
required() {
|
|
110
|
+
this._meta = { ...this._meta, required: true };
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Set a default value. The field becomes effectively optional.
|
|
115
|
+
* The output type remains `TOutput` (guaranteed by the default).
|
|
116
|
+
*/
|
|
117
|
+
default(value) {
|
|
118
|
+
this._meta = {
|
|
119
|
+
...this._meta,
|
|
120
|
+
hasDefault: true,
|
|
121
|
+
required: false,
|
|
122
|
+
rawDefaultValue: String(value)
|
|
123
|
+
};
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
// -------------------------------------------------------------------------
|
|
127
|
+
// Modifiers — Secret & Deprecated
|
|
128
|
+
// -------------------------------------------------------------------------
|
|
129
|
+
/** Mark this field as a secret (masks in logs) */
|
|
130
|
+
secret() {
|
|
131
|
+
this._meta = { ...this._meta, isSecret: true };
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
/** Mark this field as deprecated with a message */
|
|
135
|
+
deprecated(message) {
|
|
136
|
+
this._meta = { ...this._meta, isDeprecated: true, deprecationMessage: message };
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
// -------------------------------------------------------------------------
|
|
140
|
+
// Modifiers — Alias
|
|
141
|
+
// -------------------------------------------------------------------------
|
|
142
|
+
/** Add alternative variable names */
|
|
143
|
+
alias(...names) {
|
|
144
|
+
this._meta = { ...this._meta, aliases: [...this._meta.aliases, ...names] };
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
// -------------------------------------------------------------------------
|
|
148
|
+
// Modifiers — Transform & Custom Validation
|
|
149
|
+
// -------------------------------------------------------------------------
|
|
150
|
+
/** Add a transform function applied after validation */
|
|
151
|
+
transform(fn) {
|
|
152
|
+
this._transforms = [...this._transforms, fn];
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
/** Add a custom validation function */
|
|
156
|
+
custom(fn) {
|
|
157
|
+
this._validators = [...this._validators, fn];
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
// -------------------------------------------------------------------------
|
|
161
|
+
// Modifiers — Conditional
|
|
162
|
+
// -------------------------------------------------------------------------
|
|
163
|
+
/**
|
|
164
|
+
* Apply this schema only when a condition is met.
|
|
165
|
+
* The condition receives the current env values.
|
|
166
|
+
*/
|
|
167
|
+
when(check2) {
|
|
168
|
+
this._meta = { ...this._meta, conditional: { check: check2 } };
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
// Internal Methods
|
|
173
|
+
// -------------------------------------------------------------------------
|
|
174
|
+
/** Add a validator function (used by type-specific subclasses) */
|
|
175
|
+
_addValidator(validator) {
|
|
176
|
+
this._validators = [...this._validators, validator];
|
|
177
|
+
}
|
|
178
|
+
/** Add a transform function (used by type-specific subclasses) */
|
|
179
|
+
_addTransform(fn) {
|
|
180
|
+
this._transforms = [...this._transforms, fn];
|
|
181
|
+
}
|
|
182
|
+
/** Replace the parser function (used for type narrowing) */
|
|
183
|
+
_setParser(parser) {
|
|
184
|
+
this._parser = parser;
|
|
185
|
+
}
|
|
186
|
+
/** Apply all transforms to a value */
|
|
187
|
+
_applyTransforms(value) {
|
|
188
|
+
let result = value;
|
|
189
|
+
for (const transform of this._transforms) {
|
|
190
|
+
result = transform(result);
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
/** Clone this builder (for composition) */
|
|
195
|
+
clone() {
|
|
196
|
+
const cloned = new _SchemaBuilder(this._parser, this._meta.typeName);
|
|
197
|
+
cloned._validators = [...this._validators];
|
|
198
|
+
cloned._transforms = [...this._transforms];
|
|
199
|
+
cloned._meta = { ...this._meta, aliases: [...this._meta.aliases] };
|
|
200
|
+
return cloned;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/schema/engine.ts
|
|
205
|
+
function validate(vars, schema, options) {
|
|
206
|
+
const strict = options?.strict ?? false;
|
|
207
|
+
const abortEarly = options?.abortEarly ?? false;
|
|
208
|
+
const deprecationWarnings = options?.deprecationWarnings ?? true;
|
|
209
|
+
const errors = [];
|
|
210
|
+
const warnings = [];
|
|
211
|
+
const values = {};
|
|
212
|
+
const unknown = [];
|
|
213
|
+
const aliasMap = /* @__PURE__ */ new Map();
|
|
214
|
+
for (const [key, builder] of Object.entries(schema)) {
|
|
215
|
+
for (const alias of builder.meta.aliases) {
|
|
216
|
+
aliasMap.set(alias, key);
|
|
217
|
+
aliasMap.set(alias.toLowerCase(), key);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const resolvedVars = resolveVariables(vars, schema, aliasMap);
|
|
221
|
+
for (const [key, builder] of Object.entries(schema)) {
|
|
222
|
+
const conditional = builder.meta.conditional;
|
|
223
|
+
if (conditional !== void 0) {
|
|
224
|
+
try {
|
|
225
|
+
if (!conditional.check(values)) {
|
|
226
|
+
if (builder.meta.hasDefault) {
|
|
227
|
+
const defaultVal = builder.getDefaultValue();
|
|
228
|
+
if (defaultVal !== void 0) {
|
|
229
|
+
values[key] = defaultVal;
|
|
230
|
+
}
|
|
231
|
+
} else if (!builder.meta.required) {
|
|
232
|
+
values[key] = void 0;
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (deprecationWarnings && builder.meta.isDeprecated) {
|
|
240
|
+
const rawValue2 = resolvedVars[key] ?? "";
|
|
241
|
+
if (rawValue2 !== "") {
|
|
242
|
+
warnings.push({
|
|
243
|
+
field: key,
|
|
244
|
+
rawValue: rawValue2,
|
|
245
|
+
message: builder.meta.deprecationMessage ?? `Variable "${key}" is deprecated`,
|
|
246
|
+
code: "DEPRECATED"
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const rawValue = resolvedVars[key];
|
|
251
|
+
if (rawValue === void 0) {
|
|
252
|
+
if (builder.meta.hasDefault) {
|
|
253
|
+
const defaultVal = builder.getDefaultValue();
|
|
254
|
+
if (defaultVal !== void 0) {
|
|
255
|
+
values[key] = defaultVal;
|
|
256
|
+
} else {
|
|
257
|
+
errors.push({
|
|
258
|
+
field: key,
|
|
259
|
+
rawValue: builder.meta.rawDefaultValue ?? "",
|
|
260
|
+
message: `Default value "${builder.meta.rawDefaultValue ?? ""}" could not be parsed as ${builder.meta.typeName}`,
|
|
261
|
+
expected: builder.meta.typeName,
|
|
262
|
+
meta: builder.meta
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
} else if (builder.meta.required) {
|
|
266
|
+
errors.push({
|
|
267
|
+
field: key,
|
|
268
|
+
rawValue: "",
|
|
269
|
+
message: `Required variable "${key}" is missing`,
|
|
270
|
+
expected: builder.meta.typeName,
|
|
271
|
+
meta: builder.meta
|
|
272
|
+
});
|
|
273
|
+
} else {
|
|
274
|
+
values[key] = void 0;
|
|
275
|
+
}
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const parseResult = builder._parse(rawValue);
|
|
279
|
+
if (!parseResult.success) {
|
|
280
|
+
errors.push({
|
|
281
|
+
field: key,
|
|
282
|
+
rawValue,
|
|
283
|
+
message: parseResult.error,
|
|
284
|
+
expected: builder.meta.typeName,
|
|
285
|
+
meta: builder.meta
|
|
286
|
+
});
|
|
287
|
+
if (abortEarly) {
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
values[key] = parseResult.value;
|
|
293
|
+
}
|
|
294
|
+
if (strict) {
|
|
295
|
+
const schemaKeys = new Set(Object.keys(schema));
|
|
296
|
+
for (const alias of aliasMap.keys()) {
|
|
297
|
+
schemaKeys.add(alias);
|
|
298
|
+
}
|
|
299
|
+
for (const key of Object.keys(vars)) {
|
|
300
|
+
if (!schemaKeys.has(key) && !aliasMap.has(key)) {
|
|
301
|
+
unknown.push(key);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
valid: errors.length === 0,
|
|
307
|
+
errors,
|
|
308
|
+
warnings,
|
|
309
|
+
values,
|
|
310
|
+
unknown
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function resolveVariables(vars, schema, aliasMap) {
|
|
314
|
+
const result = {};
|
|
315
|
+
for (const key of Object.keys(schema)) {
|
|
316
|
+
if (key in vars && vars[key] !== void 0) {
|
|
317
|
+
result[key] = vars[key];
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const caseInsensitiveKey = Object.keys(vars).find(
|
|
321
|
+
(k) => k.toLowerCase() === key.toLowerCase()
|
|
322
|
+
);
|
|
323
|
+
if (caseInsensitiveKey !== void 0 && vars[caseInsensitiveKey] !== void 0) {
|
|
324
|
+
result[key] = vars[caseInsensitiveKey];
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
let foundAlias = false;
|
|
328
|
+
for (const [alias, schemaKey] of aliasMap.entries()) {
|
|
329
|
+
if (schemaKey === key) {
|
|
330
|
+
if (alias in vars && vars[alias] !== void 0) {
|
|
331
|
+
result[key] = vars[alias];
|
|
332
|
+
foundAlias = true;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
const ciAlias = Object.keys(vars).find(
|
|
336
|
+
(k) => k.toLowerCase() === alias.toLowerCase()
|
|
337
|
+
);
|
|
338
|
+
if (ciAlias !== void 0 && vars[ciAlias] !== void 0) {
|
|
339
|
+
result[key] = vars[ciAlias];
|
|
340
|
+
foundAlias = true;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (!foundAlias) {
|
|
346
|
+
result[key] = void 0;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
function validateValue(rawValue, builder) {
|
|
352
|
+
if (rawValue === void 0) {
|
|
353
|
+
if (builder.meta.hasDefault) {
|
|
354
|
+
const defaultVal = builder.getDefaultValue();
|
|
355
|
+
if (defaultVal !== void 0) {
|
|
356
|
+
return { success: true, value: defaultVal };
|
|
357
|
+
}
|
|
358
|
+
return { success: false, error: `Value is required but has no valid default` };
|
|
359
|
+
}
|
|
360
|
+
if (builder.meta.required) {
|
|
361
|
+
return { success: false, error: "Value is required" };
|
|
362
|
+
}
|
|
363
|
+
return { success: false, error: "Value is undefined and optional (use defineEnv for proper handling)" };
|
|
364
|
+
}
|
|
365
|
+
return builder._parse(rawValue);
|
|
366
|
+
}
|
|
367
|
+
function formatValidationResult(result) {
|
|
368
|
+
const lines = [];
|
|
369
|
+
if (result.valid) {
|
|
370
|
+
lines.push("\u2713 All validations passed");
|
|
371
|
+
if (result.warnings.length > 0) {
|
|
372
|
+
lines.push("");
|
|
373
|
+
lines.push(`\u26A0 ${result.warnings.length} warning(s):`);
|
|
374
|
+
for (const warning of result.warnings) {
|
|
375
|
+
lines.push(` - ${warning.field}: ${warning.message}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} else {
|
|
379
|
+
lines.push(`\u2717 Validation failed with ${result.errors.length} error(s):`);
|
|
380
|
+
for (const error of result.errors) {
|
|
381
|
+
lines.push(` - ${error.field}: ${error.message}`);
|
|
382
|
+
if (error.meta.description) {
|
|
383
|
+
lines.push(` ${error.meta.description}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (result.warnings.length > 0) {
|
|
387
|
+
lines.push("");
|
|
388
|
+
lines.push(`\u26A0 ${result.warnings.length} warning(s):`);
|
|
389
|
+
for (const warning of result.warnings) {
|
|
390
|
+
lines.push(` - ${warning.field}: ${warning.message}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (result.unknown.length > 0) {
|
|
395
|
+
lines.push("");
|
|
396
|
+
lines.push(`? Unknown variables: ${result.unknown.join(", ")}`);
|
|
397
|
+
}
|
|
398
|
+
return lines.join("\n");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/schema/validators/string.ts
|
|
402
|
+
var EMAIL_REGEX = /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/;
|
|
403
|
+
var UUID_V4_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
|
|
404
|
+
var UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
405
|
+
var HEX_REGEX = /^(0x)?[0-9a-fA-F]+$/;
|
|
406
|
+
var BASE64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
407
|
+
var ALPHANUMERIC_REGEX = /^[a-zA-Z0-9]+$/;
|
|
408
|
+
function validateEmail(value) {
|
|
409
|
+
if (!EMAIL_REGEX.test(value)) {
|
|
410
|
+
return `"${value}" is not a valid email address`;
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
function validateUrl(value, opts) {
|
|
415
|
+
try {
|
|
416
|
+
const url = new URL(value);
|
|
417
|
+
const protocols = opts.protocols ?? ["http", "https"];
|
|
418
|
+
const allowed = protocols.map((p) => p.toLowerCase());
|
|
419
|
+
if (!allowed.includes(url.protocol.replace(":", ""))) {
|
|
420
|
+
return `URL protocol must be one of: ${allowed.join(", ")}. Got "${url.protocol}"`;
|
|
421
|
+
}
|
|
422
|
+
return null;
|
|
423
|
+
} catch {
|
|
424
|
+
return `"${value}" is not a valid URL`;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function validateUuid(value, opts) {
|
|
428
|
+
if (opts.version === 4) {
|
|
429
|
+
if (!UUID_V4_REGEX.test(value)) {
|
|
430
|
+
return `"${value}" is not a valid UUID v4`;
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
if (!UUID_REGEX.test(value)) {
|
|
435
|
+
return `"${value}" is not a valid UUID`;
|
|
436
|
+
}
|
|
437
|
+
if (opts.version !== void 0) {
|
|
438
|
+
const versionDigit = value[14];
|
|
439
|
+
if (versionDigit !== String(opts.version)) {
|
|
440
|
+
return `"${value}" is not a valid UUID v${opts.version}`;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
function validateHex(value) {
|
|
446
|
+
if (!HEX_REGEX.test(value)) {
|
|
447
|
+
return `"${value}" is not a valid hex string`;
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
function validateBase64(value) {
|
|
452
|
+
if (value.length === 0 || !BASE64_REGEX.test(value)) {
|
|
453
|
+
return `"${value}" is not a valid base64 string`;
|
|
454
|
+
}
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
function validateAlphanumeric(value) {
|
|
458
|
+
if (!ALPHANUMERIC_REGEX.test(value)) {
|
|
459
|
+
return `"${value}" must contain only letters and numbers (alphanumeric)`;
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
var StringSchemaBuilder = class extends SchemaBuilder {
|
|
464
|
+
constructor() {
|
|
465
|
+
const parser = (raw) => ({ success: true, value: raw });
|
|
466
|
+
super(parser, "string");
|
|
467
|
+
}
|
|
468
|
+
// -------------------------------------------------------------------------
|
|
469
|
+
// Format Validators
|
|
470
|
+
// -------------------------------------------------------------------------
|
|
471
|
+
/** Validate as an email address */
|
|
472
|
+
email() {
|
|
473
|
+
this._addValidator((value) => validateEmail(value));
|
|
474
|
+
return this;
|
|
475
|
+
}
|
|
476
|
+
/** Validate as a URL with optional protocol restriction */
|
|
477
|
+
url(opts) {
|
|
478
|
+
this._addValidator((value) => validateUrl(value, opts ?? {}));
|
|
479
|
+
return this;
|
|
480
|
+
}
|
|
481
|
+
/** Validate as a UUID (optionally restrict to a specific version 1-7) */
|
|
482
|
+
uuid(version) {
|
|
483
|
+
this._addValidator((value) => validateUuid(value, { version }));
|
|
484
|
+
return this;
|
|
485
|
+
}
|
|
486
|
+
/** Validate as a hex string (with optional 0x prefix) */
|
|
487
|
+
hex() {
|
|
488
|
+
this._addValidator((value) => validateHex(value));
|
|
489
|
+
return this;
|
|
490
|
+
}
|
|
491
|
+
/** Validate as a base64-encoded string */
|
|
492
|
+
base64() {
|
|
493
|
+
this._addValidator((value) => validateBase64(value));
|
|
494
|
+
return this;
|
|
495
|
+
}
|
|
496
|
+
/** Validate that the string contains only letters and numbers */
|
|
497
|
+
alphanumeric() {
|
|
498
|
+
this._addValidator((value) => validateAlphanumeric(value));
|
|
499
|
+
return this;
|
|
500
|
+
}
|
|
501
|
+
// -------------------------------------------------------------------------
|
|
502
|
+
// Case & Whitespace
|
|
503
|
+
// -------------------------------------------------------------------------
|
|
504
|
+
/** Convert to lowercase */
|
|
505
|
+
lowercase() {
|
|
506
|
+
this._addTransform((value) => value.toLowerCase());
|
|
507
|
+
this._addValidator((value) => {
|
|
508
|
+
if (value !== value.toLowerCase()) {
|
|
509
|
+
return `"${String(value)}" must be lowercase`;
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
512
|
+
});
|
|
513
|
+
return this;
|
|
514
|
+
}
|
|
515
|
+
/** Convert to uppercase */
|
|
516
|
+
uppercase() {
|
|
517
|
+
this._addTransform((value) => value.toUpperCase());
|
|
518
|
+
this._addValidator((value) => {
|
|
519
|
+
if (value !== value.toUpperCase()) {
|
|
520
|
+
return `"${String(value)}" must be uppercase`;
|
|
521
|
+
}
|
|
522
|
+
return null;
|
|
523
|
+
});
|
|
524
|
+
return this;
|
|
525
|
+
}
|
|
526
|
+
/** Trim whitespace from both ends */
|
|
527
|
+
trim() {
|
|
528
|
+
this._addTransform((value) => value.trim());
|
|
529
|
+
return this;
|
|
530
|
+
}
|
|
531
|
+
// -------------------------------------------------------------------------
|
|
532
|
+
// Length Constraints
|
|
533
|
+
// -------------------------------------------------------------------------
|
|
534
|
+
/** Minimum string length */
|
|
535
|
+
minLength(n) {
|
|
536
|
+
this._addValidator((value) => {
|
|
537
|
+
if (value.length < n) {
|
|
538
|
+
return `String must be at least ${n} characters long, got ${value.length}`;
|
|
539
|
+
}
|
|
540
|
+
return null;
|
|
541
|
+
});
|
|
542
|
+
return this;
|
|
543
|
+
}
|
|
544
|
+
/** Maximum string length */
|
|
545
|
+
maxLength(n) {
|
|
546
|
+
this._addValidator((value) => {
|
|
547
|
+
if (value.length > n) {
|
|
548
|
+
return `String must be at most ${n} characters long, got ${value.length}`;
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
});
|
|
552
|
+
return this;
|
|
553
|
+
}
|
|
554
|
+
/** Exact string length */
|
|
555
|
+
length(n) {
|
|
556
|
+
this._addValidator((value) => {
|
|
557
|
+
if (value.length !== n) {
|
|
558
|
+
return `String must be exactly ${n} characters long, got ${value.length}`;
|
|
559
|
+
}
|
|
560
|
+
return null;
|
|
561
|
+
});
|
|
562
|
+
return this;
|
|
563
|
+
}
|
|
564
|
+
// -------------------------------------------------------------------------
|
|
565
|
+
// Pattern Matching
|
|
566
|
+
// -------------------------------------------------------------------------
|
|
567
|
+
/** Match a regular expression pattern */
|
|
568
|
+
pattern(regex) {
|
|
569
|
+
this._addValidator((value) => {
|
|
570
|
+
if (!regex.test(value)) {
|
|
571
|
+
return `String must match pattern ${regex.toString()}`;
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
});
|
|
575
|
+
return this;
|
|
576
|
+
}
|
|
577
|
+
/** Must start with a specific prefix */
|
|
578
|
+
startsWith(prefix) {
|
|
579
|
+
this._addValidator((value) => {
|
|
580
|
+
if (!value.startsWith(prefix)) {
|
|
581
|
+
return `String must start with "${prefix}"`;
|
|
582
|
+
}
|
|
583
|
+
return null;
|
|
584
|
+
});
|
|
585
|
+
return this;
|
|
586
|
+
}
|
|
587
|
+
/** Must end with a specific suffix */
|
|
588
|
+
endsWith(suffix) {
|
|
589
|
+
this._addValidator((value) => {
|
|
590
|
+
if (!value.endsWith(suffix)) {
|
|
591
|
+
return `String must end with "${suffix}"`;
|
|
592
|
+
}
|
|
593
|
+
return null;
|
|
594
|
+
});
|
|
595
|
+
return this;
|
|
596
|
+
}
|
|
597
|
+
/** Must contain a specific substring */
|
|
598
|
+
includes(substring) {
|
|
599
|
+
this._addValidator((value) => {
|
|
600
|
+
if (!value.includes(substring)) {
|
|
601
|
+
return `String must contain "${substring}"`;
|
|
602
|
+
}
|
|
603
|
+
return null;
|
|
604
|
+
});
|
|
605
|
+
return this;
|
|
606
|
+
}
|
|
607
|
+
// -------------------------------------------------------------------------
|
|
608
|
+
// Enum / OneOf — Type Narrowing
|
|
609
|
+
// -------------------------------------------------------------------------
|
|
610
|
+
/** Restrict to one of the specified values (alias for enum) */
|
|
611
|
+
oneOf(values) {
|
|
612
|
+
const set = new Set(values);
|
|
613
|
+
this._addValidator((value) => {
|
|
614
|
+
if (!set.has(value)) {
|
|
615
|
+
return `Value must be one of: ${Array.from(set).join(", ")}. Got "${value}"`;
|
|
616
|
+
}
|
|
617
|
+
return null;
|
|
618
|
+
});
|
|
619
|
+
return this;
|
|
620
|
+
}
|
|
621
|
+
/** Restrict to an enum of literal values — narrows the output type */
|
|
622
|
+
enum(values) {
|
|
623
|
+
return this.oneOf(values);
|
|
624
|
+
}
|
|
625
|
+
/** Non-empty string */
|
|
626
|
+
nonempty() {
|
|
627
|
+
this._addValidator((value) => {
|
|
628
|
+
if (value.length === 0) {
|
|
629
|
+
return "String must not be empty";
|
|
630
|
+
}
|
|
631
|
+
return null;
|
|
632
|
+
});
|
|
633
|
+
return this;
|
|
634
|
+
}
|
|
635
|
+
/** Must match a valid JSON string (checks parseability) */
|
|
636
|
+
json() {
|
|
637
|
+
this._addValidator((value) => {
|
|
638
|
+
try {
|
|
639
|
+
JSON.parse(value);
|
|
640
|
+
return null;
|
|
641
|
+
} catch {
|
|
642
|
+
return `"${String(value).slice(0, 50)}" is not valid JSON`;
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
return this;
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
function createStringSchema() {
|
|
649
|
+
return new StringSchemaBuilder();
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/schema/validators/number.ts
|
|
653
|
+
function parseNumber(raw) {
|
|
654
|
+
const trimmed = raw.trim();
|
|
655
|
+
if (trimmed === "") {
|
|
656
|
+
return { success: false, error: "Value is empty, expected a number" };
|
|
657
|
+
}
|
|
658
|
+
const num = Number(trimmed);
|
|
659
|
+
if (Number.isNaN(num)) {
|
|
660
|
+
return { success: false, error: `"${trimmed}" is not a valid number` };
|
|
661
|
+
}
|
|
662
|
+
return { success: true, value: num };
|
|
663
|
+
}
|
|
664
|
+
function validateInteger(value) {
|
|
665
|
+
if (!Number.isInteger(value)) {
|
|
666
|
+
return `Expected an integer, got ${value}`;
|
|
667
|
+
}
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
function validateFloat(value) {
|
|
671
|
+
void value;
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
function validateMin(n) {
|
|
675
|
+
return (value) => {
|
|
676
|
+
if (value < n) {
|
|
677
|
+
return `Number must be at least ${n}, got ${value}`;
|
|
678
|
+
}
|
|
679
|
+
return null;
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function validateMax(n) {
|
|
683
|
+
return (value) => {
|
|
684
|
+
if (value > n) {
|
|
685
|
+
return `Number must be at most ${n}, got ${value}`;
|
|
686
|
+
}
|
|
687
|
+
return null;
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function validatePositive(value) {
|
|
691
|
+
if (value <= 0) {
|
|
692
|
+
return `Number must be positive (> 0), got ${value}`;
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
function validateNegative(value) {
|
|
697
|
+
if (value >= 0) {
|
|
698
|
+
return `Number must be negative (< 0), got ${value}`;
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
function validatePort(value) {
|
|
703
|
+
if (!Number.isInteger(value)) {
|
|
704
|
+
return `Port must be an integer, got ${value}`;
|
|
705
|
+
}
|
|
706
|
+
if (value < 1 || value > 65535) {
|
|
707
|
+
return `Port must be between 1 and 65535, got ${value}`;
|
|
708
|
+
}
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
function validatePercentage(value) {
|
|
712
|
+
if (value < 0 || value > 100) {
|
|
713
|
+
return `Percentage must be between 0 and 100, got ${value}`;
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
function validateFinite(value) {
|
|
718
|
+
if (!Number.isFinite(value)) {
|
|
719
|
+
return `Number must be finite, got ${value}`;
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
function validateNonNegative(value) {
|
|
724
|
+
if (value < 0) {
|
|
725
|
+
return `Number must be non-negative (>= 0), got ${value}`;
|
|
726
|
+
}
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
var NumberSchemaBuilder = class extends SchemaBuilder {
|
|
730
|
+
constructor() {
|
|
731
|
+
const parser = (raw) => parseNumber(raw);
|
|
732
|
+
super(parser, "number");
|
|
733
|
+
}
|
|
734
|
+
// -------------------------------------------------------------------------
|
|
735
|
+
// Type Constraints
|
|
736
|
+
// -------------------------------------------------------------------------
|
|
737
|
+
/** Must be an integer (no decimals) */
|
|
738
|
+
integer() {
|
|
739
|
+
this._addValidator((value) => validateInteger(value));
|
|
740
|
+
return this;
|
|
741
|
+
}
|
|
742
|
+
/** Must be a float (semantic marker — all JS numbers are doubles) */
|
|
743
|
+
float() {
|
|
744
|
+
this._addValidator((value) => validateFloat(value));
|
|
745
|
+
return this;
|
|
746
|
+
}
|
|
747
|
+
// -------------------------------------------------------------------------
|
|
748
|
+
// Range Constraints
|
|
749
|
+
// -------------------------------------------------------------------------
|
|
750
|
+
/** Minimum value (inclusive) */
|
|
751
|
+
min(n) {
|
|
752
|
+
this._addValidator(validateMin(n));
|
|
753
|
+
return this;
|
|
754
|
+
}
|
|
755
|
+
/** Maximum value (inclusive) */
|
|
756
|
+
max(n) {
|
|
757
|
+
this._addValidator(validateMax(n));
|
|
758
|
+
return this;
|
|
759
|
+
}
|
|
760
|
+
/** Must be positive (> 0) */
|
|
761
|
+
positive() {
|
|
762
|
+
this._addValidator((value) => validatePositive(value));
|
|
763
|
+
return this;
|
|
764
|
+
}
|
|
765
|
+
/** Must be negative (< 0) */
|
|
766
|
+
negative() {
|
|
767
|
+
this._addValidator((value) => validateNegative(value));
|
|
768
|
+
return this;
|
|
769
|
+
}
|
|
770
|
+
/** Must be non-negative (>= 0) */
|
|
771
|
+
nonNegative() {
|
|
772
|
+
this._addValidator((value) => validateNonNegative(value));
|
|
773
|
+
return this;
|
|
774
|
+
}
|
|
775
|
+
// -------------------------------------------------------------------------
|
|
776
|
+
// Specialized Validators
|
|
777
|
+
// -------------------------------------------------------------------------
|
|
778
|
+
/** Must be a valid network port (1-65535) */
|
|
779
|
+
port() {
|
|
780
|
+
this._addValidator((value) => validatePort(value));
|
|
781
|
+
return this;
|
|
782
|
+
}
|
|
783
|
+
/** Must be a percentage (0-100) */
|
|
784
|
+
percentage() {
|
|
785
|
+
this._addValidator((value) => validatePercentage(value));
|
|
786
|
+
return this;
|
|
787
|
+
}
|
|
788
|
+
/** Must be finite (no Infinity or NaN) */
|
|
789
|
+
finite() {
|
|
790
|
+
this._addValidator((value) => validateFinite(value));
|
|
791
|
+
return this;
|
|
792
|
+
}
|
|
793
|
+
/** Must be one of the specified values — narrows the output type */
|
|
794
|
+
oneOf(values) {
|
|
795
|
+
const set = new Set(values);
|
|
796
|
+
this._addValidator((value) => {
|
|
797
|
+
if (!set.has(value)) {
|
|
798
|
+
return `Value must be one of: ${Array.from(set).join(", ")}. Got ${value}`;
|
|
799
|
+
}
|
|
800
|
+
return null;
|
|
801
|
+
});
|
|
802
|
+
return this;
|
|
803
|
+
}
|
|
804
|
+
/** Must be a multiple of the given number */
|
|
805
|
+
step(n) {
|
|
806
|
+
this._addValidator((value) => {
|
|
807
|
+
const v = value;
|
|
808
|
+
if (n !== 0 && !Number.isFinite(v / n) || v % n !== 0) {
|
|
809
|
+
return `Value must be a multiple of ${n}, got ${v}`;
|
|
810
|
+
}
|
|
811
|
+
return null;
|
|
812
|
+
});
|
|
813
|
+
return this;
|
|
814
|
+
}
|
|
815
|
+
/** Must be a safe integer (-(2^53 - 1) to 2^53 - 1) */
|
|
816
|
+
safeInt() {
|
|
817
|
+
this._addValidator((value) => {
|
|
818
|
+
if (!Number.isSafeInteger(value)) {
|
|
819
|
+
return `Value must be a safe integer, got ${value}`;
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
});
|
|
823
|
+
return this;
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
function createNumberSchema() {
|
|
827
|
+
return new NumberSchemaBuilder();
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/schema/validators/boolean.ts
|
|
831
|
+
var TRUTHY_SET = /* @__PURE__ */ new Set(["true", "1", "yes", "on"]);
|
|
832
|
+
var FALSY_SET = /* @__PURE__ */ new Set(["false", "0", "no", "off", ""]);
|
|
833
|
+
function parseBooleanFull(raw) {
|
|
834
|
+
const lower = raw.trim().toLowerCase();
|
|
835
|
+
if (TRUTHY_SET.has(lower)) {
|
|
836
|
+
return { success: true, value: true };
|
|
837
|
+
}
|
|
838
|
+
if (FALSY_SET.has(lower)) {
|
|
839
|
+
return { success: true, value: false };
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
success: false,
|
|
843
|
+
error: `"${raw}" is not a valid boolean. Use true/false, 1/0, yes/no, or on/off`
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
var BooleanSchemaBuilder = class extends SchemaBuilder {
|
|
847
|
+
_truthyValues;
|
|
848
|
+
_falsyValues;
|
|
849
|
+
constructor() {
|
|
850
|
+
super(parseBooleanFull, "boolean");
|
|
851
|
+
this._truthyValues = TRUTHY_SET;
|
|
852
|
+
this._falsyValues = FALSY_SET;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Override the set of truthy values (case-insensitive).
|
|
856
|
+
* Common defaults: true, 1, yes, on
|
|
857
|
+
*/
|
|
858
|
+
truthy(values) {
|
|
859
|
+
this._truthyValues = new Set(values.map((v) => v.toLowerCase()));
|
|
860
|
+
this._rebuildParser();
|
|
861
|
+
return this;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Override the set of falsy values (case-insensitive).
|
|
865
|
+
* Common defaults: false, 0, no, off, ''
|
|
866
|
+
*/
|
|
867
|
+
falsy(values) {
|
|
868
|
+
this._falsyValues = new Set(values.map((v) => v.toLowerCase()));
|
|
869
|
+
this._rebuildParser();
|
|
870
|
+
return this;
|
|
871
|
+
}
|
|
872
|
+
/** Rebuild the parser function with current truthy/falsy sets */
|
|
873
|
+
_rebuildParser() {
|
|
874
|
+
const truthy = this._truthyValues;
|
|
875
|
+
const falsy = this._falsyValues;
|
|
876
|
+
const parser = (raw) => {
|
|
877
|
+
const lower = raw.trim().toLowerCase();
|
|
878
|
+
if (truthy.has(lower)) {
|
|
879
|
+
return { success: true, value: true };
|
|
880
|
+
}
|
|
881
|
+
if (falsy.has(lower)) {
|
|
882
|
+
return { success: true, value: false };
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
success: false,
|
|
886
|
+
error: `"${raw}" is not a valid boolean. Accepted truthy: [${Array.from(truthy).join(", ")}]. Accepted falsy: [${Array.from(falsy).join(", ")}]`
|
|
887
|
+
};
|
|
888
|
+
};
|
|
889
|
+
this._setParser(parser);
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
function createBooleanSchema() {
|
|
893
|
+
return new BooleanSchemaBuilder();
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/schema/validators/enum.ts
|
|
897
|
+
var EnumSchemaBuilder = class extends SchemaBuilder {
|
|
898
|
+
_values;
|
|
899
|
+
_valuesArray;
|
|
900
|
+
_caseInsensitive;
|
|
901
|
+
constructor(values) {
|
|
902
|
+
const valueSet = new Set(values);
|
|
903
|
+
const parser = (raw) => {
|
|
904
|
+
const trimmed = raw.trim();
|
|
905
|
+
if (valueSet.has(trimmed)) {
|
|
906
|
+
return { success: true, value: trimmed };
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
909
|
+
success: false,
|
|
910
|
+
error: `"${trimmed}" is not a valid enum value. Expected one of: ${Array.from(valueSet).join(", ")}`
|
|
911
|
+
};
|
|
912
|
+
};
|
|
913
|
+
super(parser, "enum");
|
|
914
|
+
this._values = valueSet;
|
|
915
|
+
this._valuesArray = values;
|
|
916
|
+
this._caseInsensitive = false;
|
|
917
|
+
}
|
|
918
|
+
/** Enable or disable case-insensitive matching */
|
|
919
|
+
caseInsensitive(enabled) {
|
|
920
|
+
this._caseInsensitive = enabled ?? true;
|
|
921
|
+
this._rebuildParser();
|
|
922
|
+
return this;
|
|
923
|
+
}
|
|
924
|
+
/** Get the list of allowed values */
|
|
925
|
+
get values() {
|
|
926
|
+
return this._valuesArray;
|
|
927
|
+
}
|
|
928
|
+
_rebuildParser() {
|
|
929
|
+
const values = this._values;
|
|
930
|
+
const caseInsensitive = this._caseInsensitive;
|
|
931
|
+
const parser = (raw) => {
|
|
932
|
+
const trimmed = raw.trim();
|
|
933
|
+
const lookup = caseInsensitive ? trimmed.toLowerCase() : trimmed;
|
|
934
|
+
if (caseInsensitive) {
|
|
935
|
+
for (const val of values) {
|
|
936
|
+
if (val.toLowerCase() === lookup) {
|
|
937
|
+
return { success: true, value: val };
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
} else {
|
|
941
|
+
if (values.has(trimmed)) {
|
|
942
|
+
return { success: true, value: trimmed };
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return {
|
|
946
|
+
success: false,
|
|
947
|
+
error: `"${trimmed}" is not a valid enum value. Expected one of: ${Array.from(values).join(", ")}`
|
|
948
|
+
};
|
|
949
|
+
};
|
|
950
|
+
this._setParser(parser);
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
function createEnumSchema(values) {
|
|
954
|
+
return new EnumSchemaBuilder(values);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// src/schema/validators/array.ts
|
|
958
|
+
var ArraySchemaBuilder = class extends SchemaBuilder {
|
|
959
|
+
_separator;
|
|
960
|
+
_trimItems;
|
|
961
|
+
_filterEmpty;
|
|
962
|
+
constructor() {
|
|
963
|
+
const parser = (raw) => {
|
|
964
|
+
return { success: true, value: raw.split(",") };
|
|
965
|
+
};
|
|
966
|
+
super(parser, "array");
|
|
967
|
+
this._separator = ",";
|
|
968
|
+
this._trimItems = false;
|
|
969
|
+
this._filterEmpty = false;
|
|
970
|
+
this._rebuildParser();
|
|
971
|
+
}
|
|
972
|
+
/** Set the separator character (default: ',') */
|
|
973
|
+
separator(char) {
|
|
974
|
+
this._separator = char;
|
|
975
|
+
this._rebuildParser();
|
|
976
|
+
return this;
|
|
977
|
+
}
|
|
978
|
+
/** Trim whitespace from each item after splitting */
|
|
979
|
+
trimItems(enabled) {
|
|
980
|
+
this._trimItems = enabled ?? true;
|
|
981
|
+
this._rebuildParser();
|
|
982
|
+
return this;
|
|
983
|
+
}
|
|
984
|
+
/** Remove empty strings from the result */
|
|
985
|
+
filterEmpty(enabled) {
|
|
986
|
+
this._filterEmpty = enabled ?? true;
|
|
987
|
+
this._rebuildParser();
|
|
988
|
+
return this;
|
|
989
|
+
}
|
|
990
|
+
/** Require unique items (no duplicates) */
|
|
991
|
+
unique() {
|
|
992
|
+
this._addValidator((value) => {
|
|
993
|
+
const seen = /* @__PURE__ */ new Set();
|
|
994
|
+
for (const item of value) {
|
|
995
|
+
if (seen.has(item)) {
|
|
996
|
+
return `Array contains duplicate value: "${item}"`;
|
|
997
|
+
}
|
|
998
|
+
seen.add(item);
|
|
999
|
+
}
|
|
1000
|
+
return null;
|
|
1001
|
+
});
|
|
1002
|
+
return this;
|
|
1003
|
+
}
|
|
1004
|
+
/** Minimum number of items */
|
|
1005
|
+
minItems(n) {
|
|
1006
|
+
this._addValidator((value) => {
|
|
1007
|
+
if (value.length < n) {
|
|
1008
|
+
return `Array must have at least ${n} item(s), got ${value.length}`;
|
|
1009
|
+
}
|
|
1010
|
+
return null;
|
|
1011
|
+
});
|
|
1012
|
+
return this;
|
|
1013
|
+
}
|
|
1014
|
+
/** Maximum number of items */
|
|
1015
|
+
maxItems(n) {
|
|
1016
|
+
this._addValidator((value) => {
|
|
1017
|
+
if (value.length > n) {
|
|
1018
|
+
return `Array must have at most ${n} item(s), got ${value.length}`;
|
|
1019
|
+
}
|
|
1020
|
+
return null;
|
|
1021
|
+
});
|
|
1022
|
+
return this;
|
|
1023
|
+
}
|
|
1024
|
+
/** Exact number of items */
|
|
1025
|
+
items(n) {
|
|
1026
|
+
this._addValidator((value) => {
|
|
1027
|
+
if (value.length !== n) {
|
|
1028
|
+
return `Array must have exactly ${n} item(s), got ${value.length}`;
|
|
1029
|
+
}
|
|
1030
|
+
return null;
|
|
1031
|
+
});
|
|
1032
|
+
return this;
|
|
1033
|
+
}
|
|
1034
|
+
/** Non-empty array */
|
|
1035
|
+
nonempty() {
|
|
1036
|
+
this._addValidator((value) => {
|
|
1037
|
+
if (value.length === 0) {
|
|
1038
|
+
return "Array must not be empty";
|
|
1039
|
+
}
|
|
1040
|
+
return null;
|
|
1041
|
+
});
|
|
1042
|
+
return this;
|
|
1043
|
+
}
|
|
1044
|
+
/** Validate each item against a custom function */
|
|
1045
|
+
itemValidate(fn) {
|
|
1046
|
+
this._addValidator((value) => {
|
|
1047
|
+
for (let i = 0; i < value.length; i++) {
|
|
1048
|
+
const error = fn(value[i] ?? "", i);
|
|
1049
|
+
if (error !== null) {
|
|
1050
|
+
return `Item at index ${i}: ${error}`;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return null;
|
|
1054
|
+
});
|
|
1055
|
+
return this;
|
|
1056
|
+
}
|
|
1057
|
+
_rebuildParser() {
|
|
1058
|
+
const sep = this._separator;
|
|
1059
|
+
const trim = this._trimItems;
|
|
1060
|
+
const filterEmpty = this._filterEmpty;
|
|
1061
|
+
const parser = (raw) => {
|
|
1062
|
+
let items = raw.split(sep);
|
|
1063
|
+
if (trim) {
|
|
1064
|
+
items = items.map((item) => item.trim());
|
|
1065
|
+
}
|
|
1066
|
+
if (filterEmpty) {
|
|
1067
|
+
items = items.filter((item) => item.length > 0);
|
|
1068
|
+
}
|
|
1069
|
+
return { success: true, value: items };
|
|
1070
|
+
};
|
|
1071
|
+
this._setParser(parser);
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
function createArraySchema() {
|
|
1075
|
+
return new ArraySchemaBuilder();
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/schema/validators/json.ts
|
|
1079
|
+
var JsonSchemaBuilder = class extends SchemaBuilder {
|
|
1080
|
+
constructor() {
|
|
1081
|
+
const parser = (raw) => {
|
|
1082
|
+
try {
|
|
1083
|
+
const parsed = JSON.parse(raw);
|
|
1084
|
+
return { success: true, value: parsed };
|
|
1085
|
+
} catch (e) {
|
|
1086
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1087
|
+
return { success: false, error: `Invalid JSON: ${message}` };
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
super(parser, "json");
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Add a custom schema validation function for the parsed object.
|
|
1094
|
+
* Return an error message string, or null if valid.
|
|
1095
|
+
*/
|
|
1096
|
+
schema(fn) {
|
|
1097
|
+
this._addValidator(fn);
|
|
1098
|
+
return this;
|
|
1099
|
+
}
|
|
1100
|
+
/** Require the parsed JSON to be an object (not null, array, or primitive) */
|
|
1101
|
+
object() {
|
|
1102
|
+
this._addValidator((value) => {
|
|
1103
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1104
|
+
return "JSON must be an object";
|
|
1105
|
+
}
|
|
1106
|
+
return null;
|
|
1107
|
+
});
|
|
1108
|
+
return this;
|
|
1109
|
+
}
|
|
1110
|
+
/** Require the parsed JSON to be an array */
|
|
1111
|
+
array() {
|
|
1112
|
+
this._addValidator((value) => {
|
|
1113
|
+
if (!Array.isArray(value)) {
|
|
1114
|
+
return "JSON must be an array";
|
|
1115
|
+
}
|
|
1116
|
+
return null;
|
|
1117
|
+
});
|
|
1118
|
+
return this;
|
|
1119
|
+
}
|
|
1120
|
+
/** Require the parsed JSON to be a string primitive */
|
|
1121
|
+
string() {
|
|
1122
|
+
this._addValidator((value) => {
|
|
1123
|
+
if (typeof value !== "string") {
|
|
1124
|
+
return "JSON must be a string";
|
|
1125
|
+
}
|
|
1126
|
+
return null;
|
|
1127
|
+
});
|
|
1128
|
+
return this;
|
|
1129
|
+
}
|
|
1130
|
+
/** Require the parsed JSON to be a number primitive */
|
|
1131
|
+
number() {
|
|
1132
|
+
this._addValidator((value) => {
|
|
1133
|
+
if (typeof value !== "number") {
|
|
1134
|
+
return "JSON must be a number";
|
|
1135
|
+
}
|
|
1136
|
+
return null;
|
|
1137
|
+
});
|
|
1138
|
+
return this;
|
|
1139
|
+
}
|
|
1140
|
+
/** Require the parsed JSON to be a boolean primitive */
|
|
1141
|
+
boolean() {
|
|
1142
|
+
this._addValidator((value) => {
|
|
1143
|
+
if (typeof value !== "boolean") {
|
|
1144
|
+
return "JSON must be a boolean";
|
|
1145
|
+
}
|
|
1146
|
+
return null;
|
|
1147
|
+
});
|
|
1148
|
+
return this;
|
|
1149
|
+
}
|
|
1150
|
+
/** Require a specific top-level key to exist */
|
|
1151
|
+
hasKey(key) {
|
|
1152
|
+
this._addValidator((value) => {
|
|
1153
|
+
if (typeof value !== "object" || value === null || !(key in value)) {
|
|
1154
|
+
return `JSON object must have key "${key}"`;
|
|
1155
|
+
}
|
|
1156
|
+
return null;
|
|
1157
|
+
});
|
|
1158
|
+
return this;
|
|
1159
|
+
}
|
|
1160
|
+
/** Use a custom JSON reviver during parsing */
|
|
1161
|
+
reviver(reviver) {
|
|
1162
|
+
const parser = (raw) => {
|
|
1163
|
+
try {
|
|
1164
|
+
const parsed = JSON.parse(raw, reviver);
|
|
1165
|
+
return { success: true, value: parsed };
|
|
1166
|
+
} catch (e) {
|
|
1167
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1168
|
+
return { success: false, error: `Invalid JSON: ${message}` };
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
this._setParser(parser);
|
|
1172
|
+
return this;
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
function createJsonSchema() {
|
|
1176
|
+
return new JsonSchemaBuilder();
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// src/schema/validators/date.ts
|
|
1180
|
+
function parseDate(raw) {
|
|
1181
|
+
const trimmed = raw.trim();
|
|
1182
|
+
const timestamp = Date.parse(trimmed);
|
|
1183
|
+
if (!Number.isNaN(timestamp)) {
|
|
1184
|
+
return { success: true, value: new Date(timestamp) };
|
|
1185
|
+
}
|
|
1186
|
+
if (/^\d+$/.test(trimmed)) {
|
|
1187
|
+
const numericDate = new Date(Number(trimmed));
|
|
1188
|
+
if (!Number.isNaN(numericDate.getTime())) {
|
|
1189
|
+
return { success: true, value: numericDate };
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return {
|
|
1193
|
+
success: false,
|
|
1194
|
+
error: `"${trimmed}" is not a valid date. Supported formats: ISO 8601, RFC 2822, or numeric timestamp`
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
function validatePast(value) {
|
|
1198
|
+
if (value.getTime() >= Date.now()) {
|
|
1199
|
+
return `Date must be in the past, got ${value.toISOString()}`;
|
|
1200
|
+
}
|
|
1201
|
+
return null;
|
|
1202
|
+
}
|
|
1203
|
+
function validateFuture(value) {
|
|
1204
|
+
if (value.getTime() <= Date.now()) {
|
|
1205
|
+
return `Date must be in the future, got ${value.toISOString()}`;
|
|
1206
|
+
}
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
function validateAfter(ref) {
|
|
1210
|
+
return (value) => {
|
|
1211
|
+
if (value.getTime() <= ref.getTime()) {
|
|
1212
|
+
return `Date must be after ${ref.toISOString()}, got ${value.toISOString()}`;
|
|
1213
|
+
}
|
|
1214
|
+
return null;
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
function validateBefore(ref) {
|
|
1218
|
+
return (value) => {
|
|
1219
|
+
if (value.getTime() >= ref.getTime()) {
|
|
1220
|
+
return `Date must be before ${ref.toISOString()}, got ${value.toISOString()}`;
|
|
1221
|
+
}
|
|
1222
|
+
return null;
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
var DateSchemaBuilder = class extends SchemaBuilder {
|
|
1226
|
+
constructor() {
|
|
1227
|
+
super(parseDate, "date");
|
|
1228
|
+
}
|
|
1229
|
+
/** Date must be in the past */
|
|
1230
|
+
past() {
|
|
1231
|
+
this._addValidator(validatePast);
|
|
1232
|
+
return this;
|
|
1233
|
+
}
|
|
1234
|
+
/** Date must be in the future */
|
|
1235
|
+
future() {
|
|
1236
|
+
this._addValidator(validateFuture);
|
|
1237
|
+
return this;
|
|
1238
|
+
}
|
|
1239
|
+
/** Date must be after the given reference date */
|
|
1240
|
+
after(ref) {
|
|
1241
|
+
this._addValidator(validateAfter(ref));
|
|
1242
|
+
return this;
|
|
1243
|
+
}
|
|
1244
|
+
/** Date must be before the given reference date */
|
|
1245
|
+
before(ref) {
|
|
1246
|
+
this._addValidator(validateBefore(ref));
|
|
1247
|
+
return this;
|
|
1248
|
+
}
|
|
1249
|
+
/** Date must be a valid date (re-checks, useful after transforms) */
|
|
1250
|
+
valid() {
|
|
1251
|
+
this._addValidator((value) => {
|
|
1252
|
+
if (Number.isNaN(value.getTime())) {
|
|
1253
|
+
return `"${value.toISOString()}" is not a valid date`;
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
});
|
|
1257
|
+
return this;
|
|
1258
|
+
}
|
|
1259
|
+
/** Use a custom date parser */
|
|
1260
|
+
parse(fn) {
|
|
1261
|
+
const parser = (raw) => {
|
|
1262
|
+
try {
|
|
1263
|
+
const date = fn(raw);
|
|
1264
|
+
if (Number.isNaN(date.getTime())) {
|
|
1265
|
+
return { success: false, error: `"${raw}" is not a valid date` };
|
|
1266
|
+
}
|
|
1267
|
+
return { success: true, value: date };
|
|
1268
|
+
} catch (e) {
|
|
1269
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1270
|
+
return { success: false, error: `Failed to parse date: ${message}` };
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
this._setParser(parser);
|
|
1274
|
+
return this;
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
function createDateSchema() {
|
|
1278
|
+
return new DateSchemaBuilder();
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// src/schema/validators/regex.ts
|
|
1282
|
+
function parseRegex(raw) {
|
|
1283
|
+
const trimmed = raw.trim();
|
|
1284
|
+
const regexMatch = trimmed.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
1285
|
+
if (regexMatch) {
|
|
1286
|
+
try {
|
|
1287
|
+
const pattern = regexMatch[1] ?? "";
|
|
1288
|
+
const flags = regexMatch[2] ?? "";
|
|
1289
|
+
return { success: true, value: new RegExp(pattern, flags) };
|
|
1290
|
+
} catch (e) {
|
|
1291
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1292
|
+
return { success: false, error: `Invalid regex: ${message}` };
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
try {
|
|
1296
|
+
return { success: true, value: new RegExp(trimmed) };
|
|
1297
|
+
} catch (e) {
|
|
1298
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1299
|
+
return { success: false, error: `Invalid regex: ${message}` };
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
var RegexSchemaBuilder = class extends SchemaBuilder {
|
|
1303
|
+
constructor() {
|
|
1304
|
+
super(parseRegex, "regex");
|
|
1305
|
+
}
|
|
1306
|
+
/** Require the regex to have a specific flag */
|
|
1307
|
+
hasFlag(flag) {
|
|
1308
|
+
this._addValidator((value) => {
|
|
1309
|
+
if (!value.flags.includes(flag)) {
|
|
1310
|
+
return `Regex must have flag "${flag}"`;
|
|
1311
|
+
}
|
|
1312
|
+
return null;
|
|
1313
|
+
});
|
|
1314
|
+
return this;
|
|
1315
|
+
}
|
|
1316
|
+
/** Require the regex to NOT have a specific flag */
|
|
1317
|
+
noFlag(flag) {
|
|
1318
|
+
this._addValidator((value) => {
|
|
1319
|
+
if (value.flags.includes(flag)) {
|
|
1320
|
+
return `Regex must not have flag "${flag}"`;
|
|
1321
|
+
}
|
|
1322
|
+
return null;
|
|
1323
|
+
});
|
|
1324
|
+
return this;
|
|
1325
|
+
}
|
|
1326
|
+
/** Require the regex to be case-insensitive */
|
|
1327
|
+
caseInsensitive() {
|
|
1328
|
+
return this.hasFlag("i");
|
|
1329
|
+
}
|
|
1330
|
+
/** Require the regex to be global */
|
|
1331
|
+
global() {
|
|
1332
|
+
return this.hasFlag("g");
|
|
1333
|
+
}
|
|
1334
|
+
/** Require the regex to be multiline */
|
|
1335
|
+
multiline() {
|
|
1336
|
+
return this.hasFlag("m");
|
|
1337
|
+
}
|
|
1338
|
+
/** Test the regex against a string (validation-only, doesn't change the value) */
|
|
1339
|
+
test(testString, expectMatch) {
|
|
1340
|
+
this._addValidator((value) => {
|
|
1341
|
+
const matches = value.test(testString);
|
|
1342
|
+
if (expectMatch && !matches) {
|
|
1343
|
+
return `Regex must match "${testString}"`;
|
|
1344
|
+
}
|
|
1345
|
+
if (!expectMatch && matches) {
|
|
1346
|
+
return `Regex must not match "${testString}"`;
|
|
1347
|
+
}
|
|
1348
|
+
return null;
|
|
1349
|
+
});
|
|
1350
|
+
return this;
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
function createRegexSchema() {
|
|
1354
|
+
return new RegexSchemaBuilder();
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/schema/validators/bigint.ts
|
|
1358
|
+
function parseBigIntProper(raw, radix) {
|
|
1359
|
+
const trimmed = raw.trim();
|
|
1360
|
+
try {
|
|
1361
|
+
const cleaned = trimmed.replace(/^0x/i, "");
|
|
1362
|
+
const value = radix === 16 && trimmed.startsWith("0x") ? BigInt("0x" + cleaned) : BigInt(cleaned);
|
|
1363
|
+
return { success: true, value };
|
|
1364
|
+
} catch {
|
|
1365
|
+
return { success: false, error: `"${trimmed}" is not a valid BigInt (radix: ${radix})` };
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function validateMin2(n) {
|
|
1369
|
+
return (value) => {
|
|
1370
|
+
if (value < n) {
|
|
1371
|
+
return `BigInt must be at least ${n}, got ${value}`;
|
|
1372
|
+
}
|
|
1373
|
+
return null;
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
function validateMax2(n) {
|
|
1377
|
+
return (value) => {
|
|
1378
|
+
if (value > n) {
|
|
1379
|
+
return `BigInt must be at most ${n}, got ${value}`;
|
|
1380
|
+
}
|
|
1381
|
+
return null;
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function validatePositive2(value) {
|
|
1385
|
+
if (value <= 0n) {
|
|
1386
|
+
return `BigInt must be positive (> 0), got ${value}`;
|
|
1387
|
+
}
|
|
1388
|
+
return null;
|
|
1389
|
+
}
|
|
1390
|
+
function validateNegative2(value) {
|
|
1391
|
+
if (value >= 0n) {
|
|
1392
|
+
return `BigInt must be negative (< 0), got ${value}`;
|
|
1393
|
+
}
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
1396
|
+
function validateNonNegative2(value) {
|
|
1397
|
+
if (value < 0n) {
|
|
1398
|
+
return `BigInt must be non-negative (>= 0), got ${value}`;
|
|
1399
|
+
}
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
var BigIntSchemaBuilder = class extends SchemaBuilder {
|
|
1403
|
+
_radix;
|
|
1404
|
+
constructor() {
|
|
1405
|
+
const parser = (raw) => parseBigIntProper(raw, 10);
|
|
1406
|
+
super(parser, "bigint");
|
|
1407
|
+
this._radix = 10;
|
|
1408
|
+
}
|
|
1409
|
+
/** Set the radix for parsing (default: 10). Use 16 for hex. */
|
|
1410
|
+
radix(r) {
|
|
1411
|
+
this._radix = r;
|
|
1412
|
+
this._rebuildParser();
|
|
1413
|
+
return this;
|
|
1414
|
+
}
|
|
1415
|
+
/** Minimum value (inclusive) */
|
|
1416
|
+
min(n) {
|
|
1417
|
+
this._addValidator(validateMin2(n));
|
|
1418
|
+
return this;
|
|
1419
|
+
}
|
|
1420
|
+
/** Maximum value (inclusive) */
|
|
1421
|
+
max(n) {
|
|
1422
|
+
this._addValidator(validateMax2(n));
|
|
1423
|
+
return this;
|
|
1424
|
+
}
|
|
1425
|
+
/** Must be positive (> 0) */
|
|
1426
|
+
positive() {
|
|
1427
|
+
this._addValidator(validatePositive2);
|
|
1428
|
+
return this;
|
|
1429
|
+
}
|
|
1430
|
+
/** Must be negative (< 0) */
|
|
1431
|
+
negative() {
|
|
1432
|
+
this._addValidator(validateNegative2);
|
|
1433
|
+
return this;
|
|
1434
|
+
}
|
|
1435
|
+
/** Must be non-negative (>= 0) */
|
|
1436
|
+
nonNegative() {
|
|
1437
|
+
this._addValidator(validateNonNegative2);
|
|
1438
|
+
return this;
|
|
1439
|
+
}
|
|
1440
|
+
/** Must be one of the specified values */
|
|
1441
|
+
oneOf(values) {
|
|
1442
|
+
const set = new Set(values);
|
|
1443
|
+
this._addValidator((value) => {
|
|
1444
|
+
if (!set.has(value)) {
|
|
1445
|
+
return `Value must be one of: ${Array.from(set).map(String).join(", ")}. Got ${value}`;
|
|
1446
|
+
}
|
|
1447
|
+
return null;
|
|
1448
|
+
});
|
|
1449
|
+
return this;
|
|
1450
|
+
}
|
|
1451
|
+
/** Use a custom parse function */
|
|
1452
|
+
parse(fn) {
|
|
1453
|
+
const parser = (raw) => {
|
|
1454
|
+
try {
|
|
1455
|
+
const value = fn(raw);
|
|
1456
|
+
return { success: true, value };
|
|
1457
|
+
} catch (e) {
|
|
1458
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1459
|
+
return { success: false, error: `Failed to parse BigInt: ${message}` };
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
this._setParser(parser);
|
|
1463
|
+
return this;
|
|
1464
|
+
}
|
|
1465
|
+
_rebuildParser() {
|
|
1466
|
+
const r = this._radix;
|
|
1467
|
+
const parser = (raw) => parseBigIntProper(raw, r);
|
|
1468
|
+
this._setParser(parser);
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
function createBigIntSchema() {
|
|
1472
|
+
return new BigIntSchemaBuilder();
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// src/schema/validators/url.ts
|
|
1476
|
+
function parseAndValidateUrl(raw, opts) {
|
|
1477
|
+
const trimmed = raw.trim();
|
|
1478
|
+
try {
|
|
1479
|
+
const url = new URL(trimmed);
|
|
1480
|
+
const protocols = (opts.protocols ?? ["http", "https"]).map((p) => p.toLowerCase());
|
|
1481
|
+
if (!protocols.includes(url.protocol.replace(":", ""))) {
|
|
1482
|
+
return {
|
|
1483
|
+
success: false,
|
|
1484
|
+
error: `URL protocol must be one of: ${protocols.join(", ")}. Got "${url.protocol}"`
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
if (opts.hostname !== void 0 && url.hostname !== opts.hostname) {
|
|
1488
|
+
return {
|
|
1489
|
+
success: false,
|
|
1490
|
+
error: `URL hostname must be "${opts.hostname}", got "${url.hostname}"`
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
if (opts.allowedPorts !== void 0) {
|
|
1494
|
+
const port = url.port !== "" ? Number(url.port) : url.protocol === "https:" ? 443 : 80;
|
|
1495
|
+
if (!opts.allowedPorts.includes(port)) {
|
|
1496
|
+
return {
|
|
1497
|
+
success: false,
|
|
1498
|
+
error: `URL port must be one of: ${opts.allowedPorts.join(", ")}. Got ${port}`
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
if (opts.allowQuery === false && url.search !== "") {
|
|
1503
|
+
return { success: false, error: "URL must not contain a query string" };
|
|
1504
|
+
}
|
|
1505
|
+
if (opts.allowFragment === false && url.hash !== "") {
|
|
1506
|
+
return { success: false, error: "URL must not contain a fragment" };
|
|
1507
|
+
}
|
|
1508
|
+
const maxLen = opts.maxLength ?? 2048;
|
|
1509
|
+
if (trimmed.length > maxLen) {
|
|
1510
|
+
return {
|
|
1511
|
+
success: false,
|
|
1512
|
+
error: `URL must be at most ${maxLen} characters, got ${trimmed.length}`
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
return { success: true, value: url };
|
|
1516
|
+
} catch {
|
|
1517
|
+
return { success: false, error: `"${trimmed}" is not a valid URL` };
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
function createUrlSchema(opts) {
|
|
1521
|
+
const options = opts ?? {};
|
|
1522
|
+
const parser = (raw) => parseAndValidateUrl(raw, options);
|
|
1523
|
+
return new SchemaBuilder(parser, "url");
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// src/schema/validators/email.ts
|
|
1527
|
+
var EMAIL_REGEX2 = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
1528
|
+
function parseAndValidateEmail(raw, opts) {
|
|
1529
|
+
const trimmed = raw.trim();
|
|
1530
|
+
const maxLen = opts.maxLength ?? 254;
|
|
1531
|
+
const maxLocal = opts.maxLocalLength ?? 64;
|
|
1532
|
+
if (trimmed.length === 0) {
|
|
1533
|
+
return { success: false, error: "Email must not be empty" };
|
|
1534
|
+
}
|
|
1535
|
+
if (trimmed.length > maxLen) {
|
|
1536
|
+
return { success: false, error: `Email must be at most ${maxLen} characters, got ${trimmed.length}` };
|
|
1537
|
+
}
|
|
1538
|
+
if (!EMAIL_REGEX2.test(trimmed)) {
|
|
1539
|
+
return { success: false, error: `"${trimmed}" is not a valid email address` };
|
|
1540
|
+
}
|
|
1541
|
+
const atIndex = trimmed.lastIndexOf("@");
|
|
1542
|
+
const localPart = trimmed.slice(0, atIndex);
|
|
1543
|
+
const domain = trimmed.slice(atIndex + 1);
|
|
1544
|
+
if (localPart.length > maxLocal) {
|
|
1545
|
+
return { success: false, error: `Email local part must be at most ${maxLocal} characters, got ${localPart.length}` };
|
|
1546
|
+
}
|
|
1547
|
+
if (opts.allowPlusAddressing === false && localPart.includes("+")) {
|
|
1548
|
+
return { success: false, error: "Plus addressing (+) is not allowed" };
|
|
1549
|
+
}
|
|
1550
|
+
if (opts.allowedTlds !== void 0 && opts.allowedTlds.length > 0) {
|
|
1551
|
+
const tld = domain.split(".").pop() ?? "";
|
|
1552
|
+
const allowed = opts.allowedTlds.map((t2) => t2.toLowerCase());
|
|
1553
|
+
if (!allowed.includes(tld.toLowerCase())) {
|
|
1554
|
+
return { success: false, error: `Email TLD must be one of: ${allowed.join(", ")}. Got ".${tld}"` };
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
if (opts.blockedDomains !== void 0) {
|
|
1558
|
+
const blocked = opts.blockedDomains.map((d) => d.toLowerCase());
|
|
1559
|
+
if (blocked.includes(domain.toLowerCase())) {
|
|
1560
|
+
return { success: false, error: `Email domain "${domain}" is not allowed` };
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return { success: true, value: trimmed };
|
|
1564
|
+
}
|
|
1565
|
+
function createEmailSchema(opts) {
|
|
1566
|
+
const options = opts ?? {};
|
|
1567
|
+
const parser = (raw) => parseAndValidateEmail(raw, options);
|
|
1568
|
+
return new SchemaBuilder(parser, "email");
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// src/schema/validators/ip.ts
|
|
1572
|
+
var IPV4_REGEX = /^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$/;
|
|
1573
|
+
var IPV6_REGEX = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}$|^[0-9a-fA-F]{1,4}::(?:[0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,2}:(?:[0-9a-fA-F]{1,4}:){0,4}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,3}:(?:[0-9a-fA-F]{1,4}:){0,3}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,4}:(?:[0-9a-fA-F]{1,4}:){0,2}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,5}:(?:[0-9a-fA-F]{1,4}:)?[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|^::$/;
|
|
1574
|
+
var IPV4_CIDR_REGEX = /^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\/(?:3[0-2]|[12]?\d)$/;
|
|
1575
|
+
var IPV6_MAPPED_V4_REGEX = /^::ffff:(?:\d{1,3}\.){3}\d{1,3}$/i;
|
|
1576
|
+
function parseAndValidateIp(raw, opts) {
|
|
1577
|
+
const trimmed = raw.trim();
|
|
1578
|
+
const version = opts.version ?? "both";
|
|
1579
|
+
const allowCidr = opts.allowCidr ?? false;
|
|
1580
|
+
const isV4 = IPV4_REGEX.test(trimmed);
|
|
1581
|
+
const isV6 = IPV6_REGEX.test(trimmed);
|
|
1582
|
+
const isV4Cidr = allowCidr && IPV4_CIDR_REGEX.test(trimmed);
|
|
1583
|
+
const isMappedV4 = IPV6_MAPPED_V4_REGEX.test(trimmed);
|
|
1584
|
+
if (version === "4" && !isV4 && !isV4Cidr) {
|
|
1585
|
+
return { success: false, error: `"${trimmed}" is not a valid IPv4 address` };
|
|
1586
|
+
}
|
|
1587
|
+
if (version === "6" && !isV6) {
|
|
1588
|
+
if (opts.allowMappedV4 === false && isMappedV4) {
|
|
1589
|
+
return { success: false, error: `"${trimmed}" is an IPv4-mapped IPv6 address, which is not allowed` };
|
|
1590
|
+
}
|
|
1591
|
+
return { success: false, error: `"${trimmed}" is not a valid IPv6 address` };
|
|
1592
|
+
}
|
|
1593
|
+
if (version === "both" && !isV4 && !isV6 && !isV4Cidr) {
|
|
1594
|
+
return { success: false, error: `"${trimmed}" is not a valid IP address` };
|
|
1595
|
+
}
|
|
1596
|
+
if (version === "6" && isMappedV4 && opts.allowMappedV4 === false) {
|
|
1597
|
+
return { success: false, error: `"${trimmed}" is an IPv4-mapped IPv6 address, which is not allowed` };
|
|
1598
|
+
}
|
|
1599
|
+
return { success: true, value: trimmed };
|
|
1600
|
+
}
|
|
1601
|
+
function createIpSchema(opts) {
|
|
1602
|
+
const options = opts ?? {};
|
|
1603
|
+
const parser = (raw) => parseAndValidateIp(raw, options);
|
|
1604
|
+
return new SchemaBuilder(parser, "ip");
|
|
1605
|
+
}
|
|
1606
|
+
function createIpv4Schema(opts) {
|
|
1607
|
+
return createIpSchema({ ...opts, version: "4" });
|
|
1608
|
+
}
|
|
1609
|
+
function createIpv6Schema(opts) {
|
|
1610
|
+
return createIpSchema({ ...opts, version: "6" });
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// src/schema/validators/hostname.ts
|
|
1614
|
+
var HOSTNAME_LABEL_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
|
|
1615
|
+
var HOSTNAME_LABEL_REGEX_UNDERSCORE = /^[a-zA-Z0-9_]([a-zA-Z0-9_-]*[a-zA-Z0-9_])?$/;
|
|
1616
|
+
function parseAndValidateHostname(raw, opts) {
|
|
1617
|
+
const trimmed = raw.trim();
|
|
1618
|
+
const maxLen = opts.maxLength ?? 253;
|
|
1619
|
+
const maxLabel = opts.maxLabelLength ?? 63;
|
|
1620
|
+
const allowTrailingDot = opts.allowTrailingDot ?? false;
|
|
1621
|
+
const allowIdn = opts.allowIdn ?? true;
|
|
1622
|
+
const allowUnderscore = opts.allowUnderscore ?? false;
|
|
1623
|
+
if (trimmed.length === 0) {
|
|
1624
|
+
return { success: false, error: "Hostname must not be empty" };
|
|
1625
|
+
}
|
|
1626
|
+
if (trimmed.length > maxLen + (allowTrailingDot ? 1 : 0)) {
|
|
1627
|
+
return {
|
|
1628
|
+
success: false,
|
|
1629
|
+
error: `Hostname must be at most ${maxLen} characters, got ${trimmed.length}`
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
const forValidation = allowTrailingDot && trimmed.endsWith(".") ? trimmed.slice(0, -1) : trimmed;
|
|
1633
|
+
if (forValidation.length === 0) {
|
|
1634
|
+
return { success: false, error: "Hostname must not be just a dot" };
|
|
1635
|
+
}
|
|
1636
|
+
if (!allowIdn && /[^\x00-\x7F]/.test(trimmed)) {
|
|
1637
|
+
return { success: false, error: "Internationalized domain names are not allowed" };
|
|
1638
|
+
}
|
|
1639
|
+
const labels = forValidation.split(".");
|
|
1640
|
+
if (labels.length < 1) {
|
|
1641
|
+
return { success: false, error: "Hostname must have at least one label" };
|
|
1642
|
+
}
|
|
1643
|
+
const labelRegex = allowUnderscore ? HOSTNAME_LABEL_REGEX_UNDERSCORE : HOSTNAME_LABEL_REGEX;
|
|
1644
|
+
for (const label of labels) {
|
|
1645
|
+
if (label.length === 0) {
|
|
1646
|
+
return { success: false, error: "Hostname contains an empty label" };
|
|
1647
|
+
}
|
|
1648
|
+
if (label.length > maxLabel) {
|
|
1649
|
+
return {
|
|
1650
|
+
success: false,
|
|
1651
|
+
error: `Hostname label "${label}" exceeds maximum length of ${maxLabel}`
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
if (!labelRegex.test(label)) {
|
|
1655
|
+
return { success: false, error: `Hostname label "${label}" contains invalid characters` };
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return { success: true, value: trimmed };
|
|
1659
|
+
}
|
|
1660
|
+
function createHostnameSchema(opts) {
|
|
1661
|
+
const options = opts ?? {};
|
|
1662
|
+
const parser = (raw) => parseAndValidateHostname(raw, options);
|
|
1663
|
+
return new SchemaBuilder(parser, "hostname");
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// src/schema/validators/port.ts
|
|
1667
|
+
function parseAndValidatePort(raw, opts) {
|
|
1668
|
+
const trimmed = raw.trim();
|
|
1669
|
+
const num = Number(trimmed);
|
|
1670
|
+
if (!Number.isInteger(num)) {
|
|
1671
|
+
return { success: false, error: `"${trimmed}" is not a valid integer port number` };
|
|
1672
|
+
}
|
|
1673
|
+
if (opts.allowedPorts !== void 0) {
|
|
1674
|
+
if (!opts.allowedPorts.includes(num)) {
|
|
1675
|
+
return {
|
|
1676
|
+
success: false,
|
|
1677
|
+
error: `Port must be one of: ${opts.allowedPorts.join(", ")}. Got ${num}`
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
return { success: true, value: num };
|
|
1681
|
+
}
|
|
1682
|
+
if (opts.allowZero !== true && num === 0) {
|
|
1683
|
+
return { success: false, error: "Port 0 is not allowed" };
|
|
1684
|
+
}
|
|
1685
|
+
if (num < 1 || num > 65535) {
|
|
1686
|
+
return { success: false, error: `Port must be between 1 and 65535, got ${num}` };
|
|
1687
|
+
}
|
|
1688
|
+
const allowWellKnown = opts.allowWellKnown ?? true;
|
|
1689
|
+
const allowRegistered = opts.allowRegistered ?? true;
|
|
1690
|
+
const allowDynamic = opts.allowDynamic ?? true;
|
|
1691
|
+
if (num <= 1023 && !allowWellKnown) {
|
|
1692
|
+
return { success: false, error: `Well-known port ${num} is not allowed` };
|
|
1693
|
+
}
|
|
1694
|
+
if (num >= 1024 && num <= 49151 && !allowRegistered) {
|
|
1695
|
+
return { success: false, error: `Registered port ${num} is not allowed` };
|
|
1696
|
+
}
|
|
1697
|
+
if (num >= 49152 && !allowDynamic) {
|
|
1698
|
+
return { success: false, error: `Dynamic port ${num} is not allowed` };
|
|
1699
|
+
}
|
|
1700
|
+
return { success: true, value: num };
|
|
1701
|
+
}
|
|
1702
|
+
function createPortSchema(opts) {
|
|
1703
|
+
const options = opts ?? {};
|
|
1704
|
+
const parser = (raw) => parseAndValidatePort(raw, options);
|
|
1705
|
+
return new SchemaBuilder(parser, "port");
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// src/schema/validators/path.ts
|
|
1709
|
+
function parseAndValidatePath(raw, opts) {
|
|
1710
|
+
const trimmed = raw.trim();
|
|
1711
|
+
if (trimmed.length === 0) {
|
|
1712
|
+
return { success: false, error: "Path must not be empty" };
|
|
1713
|
+
}
|
|
1714
|
+
if (opts.absolute === true && !trimmed.startsWith("/")) {
|
|
1715
|
+
return { success: false, error: 'Path must be absolute (start with "/")' };
|
|
1716
|
+
}
|
|
1717
|
+
if (opts.relative === true && trimmed.startsWith("/")) {
|
|
1718
|
+
return { success: false, error: 'Path must be relative (not start with "/")' };
|
|
1719
|
+
}
|
|
1720
|
+
if (opts.allowHomeDir !== true && trimmed.includes("~")) {
|
|
1721
|
+
return { success: false, error: 'Path must not contain "~" (home directory)' };
|
|
1722
|
+
}
|
|
1723
|
+
if (opts.allowParentTraversal !== true) {
|
|
1724
|
+
const segments = trimmed.replace(/^\//, "").split("/");
|
|
1725
|
+
for (const segment of segments) {
|
|
1726
|
+
if (segment === "..") {
|
|
1727
|
+
return { success: false, error: 'Path must not contain ".." (parent directory traversal)' };
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
if (opts.maxDepth !== void 0) {
|
|
1732
|
+
const segments = trimmed.replace(/^\//, "").split("/").filter((s) => s.length > 0);
|
|
1733
|
+
if (segments.length > opts.maxDepth) {
|
|
1734
|
+
return {
|
|
1735
|
+
success: false,
|
|
1736
|
+
error: `Path depth must be at most ${opts.maxDepth}, got ${segments.length}`
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
if (opts.extensions !== void 0 && opts.extensions.length > 0) {
|
|
1741
|
+
const validExts = opts.extensions.map((e) => e.toLowerCase());
|
|
1742
|
+
const lastDot = trimmed.lastIndexOf(".");
|
|
1743
|
+
const ext = lastDot >= 0 ? trimmed.slice(lastDot).toLowerCase() : "";
|
|
1744
|
+
if (!validExts.includes(ext)) {
|
|
1745
|
+
return {
|
|
1746
|
+
success: false,
|
|
1747
|
+
error: `Path extension must be one of: ${validExts.join(", ")}. Got "${ext || "(none)"}"`
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
return { success: true, value: trimmed };
|
|
1752
|
+
}
|
|
1753
|
+
function createPathSchema(opts) {
|
|
1754
|
+
const options = opts ?? {};
|
|
1755
|
+
const parser = (raw) => parseAndValidatePath(raw, options);
|
|
1756
|
+
return new SchemaBuilder(parser, "path");
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// src/schema/validators/uuid.ts
|
|
1760
|
+
var UUID_REGEX2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1761
|
+
var NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
1762
|
+
function parseAndValidateUuid(raw, opts) {
|
|
1763
|
+
const trimmed = raw.trim().toLowerCase();
|
|
1764
|
+
if (!UUID_REGEX2.test(trimmed)) {
|
|
1765
|
+
return { success: false, error: `"${trimmed}" is not a valid UUID` };
|
|
1766
|
+
}
|
|
1767
|
+
if (trimmed === NIL_UUID && opts.allowNil !== true) {
|
|
1768
|
+
return { success: false, error: "Nil UUID (all zeros) is not allowed" };
|
|
1769
|
+
}
|
|
1770
|
+
if (opts.version !== void 0) {
|
|
1771
|
+
const versionDigit = trimmed[14] ?? "0";
|
|
1772
|
+
if (versionDigit !== String(opts.version)) {
|
|
1773
|
+
return {
|
|
1774
|
+
success: false,
|
|
1775
|
+
error: `Expected UUID v${opts.version}, but got v${versionDigit}`
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
if (opts.strictVariant !== false) {
|
|
1780
|
+
const variantNibble = trimmed[19] ?? "0";
|
|
1781
|
+
const variantNum = parseInt(variantNibble, 16);
|
|
1782
|
+
if (variantNum < 8 || variantNum > 11) {
|
|
1783
|
+
return {
|
|
1784
|
+
success: false,
|
|
1785
|
+
error: `UUID variant nibble must be 8, 9, a, or b (RFC 4122). Got "${variantNibble}"`
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
const versionNibble = trimmed[14] ?? "0";
|
|
1789
|
+
const versionNum = parseInt(versionNibble, 16);
|
|
1790
|
+
if (versionNum < 1 || versionNum > 8) {
|
|
1791
|
+
return {
|
|
1792
|
+
success: false,
|
|
1793
|
+
error: `UUID version nibble must be 1-8. Got "${versionNibble}"`
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return { success: true, value: trimmed };
|
|
1798
|
+
}
|
|
1799
|
+
function createUuidSchema(opts) {
|
|
1800
|
+
const options = opts ?? {};
|
|
1801
|
+
const parser = (raw) => parseAndValidateUuid(raw, options);
|
|
1802
|
+
return new SchemaBuilder(parser, "uuid");
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
// src/schema/validators/hex.ts
|
|
1806
|
+
var HEX_CHARS = new Set("0123456789abcdefABCDEF");
|
|
1807
|
+
function parseAndValidateHex(raw, opts) {
|
|
1808
|
+
const trimmed = raw.trim();
|
|
1809
|
+
let content = trimmed;
|
|
1810
|
+
const allowPrefix = opts.allowPrefix ?? false;
|
|
1811
|
+
const requirePrefix = opts.requirePrefix ?? false;
|
|
1812
|
+
if (requirePrefix && !content.startsWith("0x") && !content.startsWith("0X")) {
|
|
1813
|
+
return { success: false, error: 'Hex string must start with "0x" prefix' };
|
|
1814
|
+
}
|
|
1815
|
+
if (content.startsWith("0x") || content.startsWith("0X")) {
|
|
1816
|
+
if (!allowPrefix && !requirePrefix) {
|
|
1817
|
+
return { success: false, error: 'Hex string must not have a "0x" prefix' };
|
|
1818
|
+
}
|
|
1819
|
+
content = content.slice(2);
|
|
1820
|
+
}
|
|
1821
|
+
if (content.length === 0) {
|
|
1822
|
+
return { success: false, error: "Hex string must not be empty" };
|
|
1823
|
+
}
|
|
1824
|
+
for (let i = 0; i < content.length; i++) {
|
|
1825
|
+
if (!HEX_CHARS.has(content[i] ?? "")) {
|
|
1826
|
+
return {
|
|
1827
|
+
success: false,
|
|
1828
|
+
error: `Invalid hex character "${content[i]}" at position ${i}`
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
const caseSensitive = opts.caseSensitive ?? "mixed";
|
|
1833
|
+
if (caseSensitive === "lower" && content !== content.toLowerCase()) {
|
|
1834
|
+
return { success: false, error: "Hex string must be lowercase" };
|
|
1835
|
+
}
|
|
1836
|
+
if (caseSensitive === "upper" && content !== content.toUpperCase()) {
|
|
1837
|
+
return { success: false, error: "Hex string must be uppercase" };
|
|
1838
|
+
}
|
|
1839
|
+
if (opts.byteLength !== void 0) {
|
|
1840
|
+
if (content.length !== opts.byteLength * 2) {
|
|
1841
|
+
return {
|
|
1842
|
+
success: false,
|
|
1843
|
+
error: `Hex string must be ${opts.byteLength * 2} characters (${opts.byteLength} bytes), got ${content.length}`
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
if (opts.byteLength === void 0 && content.length % 2 !== 0) {
|
|
1848
|
+
return {
|
|
1849
|
+
success: false,
|
|
1850
|
+
error: "Hex string must have an even number of characters"
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
return { success: true, value: trimmed };
|
|
1854
|
+
}
|
|
1855
|
+
function createHexSchema(opts) {
|
|
1856
|
+
const options = opts ?? {};
|
|
1857
|
+
const parser = (raw) => parseAndValidateHex(raw, options);
|
|
1858
|
+
return new SchemaBuilder(parser, "hex");
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// src/schema/validators/base64.ts
|
|
1862
|
+
var BASE64_CHARS_STANDARD = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
1863
|
+
var BASE64_CHARS_URL_SAFE = /^[A-Za-z0-9\-_]*={0,2}$/;
|
|
1864
|
+
function parseAndValidateBase64(raw, opts) {
|
|
1865
|
+
const trimmed = raw.trim();
|
|
1866
|
+
const urlSafe = opts.urlSafe ?? false;
|
|
1867
|
+
const requirePadding = opts.requirePadding ?? false;
|
|
1868
|
+
const noWhitespace = opts.noWhitespace ?? true;
|
|
1869
|
+
const allowEmpty = opts.allowEmpty ?? false;
|
|
1870
|
+
if (trimmed.length === 0) {
|
|
1871
|
+
if (allowEmpty) {
|
|
1872
|
+
return { success: true, value: trimmed };
|
|
1873
|
+
}
|
|
1874
|
+
return { success: false, error: "Base64 string must not be empty" };
|
|
1875
|
+
}
|
|
1876
|
+
if (noWhitespace && /\s/.test(trimmed)) {
|
|
1877
|
+
return { success: false, error: "Base64 string must not contain whitespace" };
|
|
1878
|
+
}
|
|
1879
|
+
const regex = urlSafe ? BASE64_CHARS_URL_SAFE : BASE64_CHARS_STANDARD;
|
|
1880
|
+
if (!regex.test(trimmed)) {
|
|
1881
|
+
const validChars = urlSafe ? "A-Za-z0-9-_" : "A-Za-z0-9+/";
|
|
1882
|
+
return {
|
|
1883
|
+
success: false,
|
|
1884
|
+
error: `Base64 string contains invalid characters. Allowed: ${validChars}=`
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
if (requirePadding) {
|
|
1888
|
+
const mod = trimmed.replace(/=/g, "").length % 4;
|
|
1889
|
+
const expectedPadding = mod === 0 ? 0 : 4 - mod;
|
|
1890
|
+
const actualPadding = trimmed.length - trimmed.replace(/=/g, "").length;
|
|
1891
|
+
if (actualPadding !== expectedPadding) {
|
|
1892
|
+
return {
|
|
1893
|
+
success: false,
|
|
1894
|
+
error: `Base64 padding is invalid. Expected ${expectedPadding} padding characters, got ${actualPadding}`
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
const paddingIndex = trimmed.indexOf("=");
|
|
1899
|
+
if (paddingIndex !== -1) {
|
|
1900
|
+
const afterPadding = trimmed.slice(paddingIndex);
|
|
1901
|
+
if (/[^=]/.test(afterPadding)) {
|
|
1902
|
+
return { success: false, error: "Base64 padding (=) must only appear at the end" };
|
|
1903
|
+
}
|
|
1904
|
+
if (afterPadding.length > 2) {
|
|
1905
|
+
return { success: false, error: "Base64 string has too many padding characters" };
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
const decoded = Buffer.from(trimmed, "base64");
|
|
1909
|
+
const reEncoded = decoded.toString("base64");
|
|
1910
|
+
const normalizedInput = trimmed.replace(/=+$/, "");
|
|
1911
|
+
const normalizedReEncoded = reEncoded.replace(/=+$/, "");
|
|
1912
|
+
if (urlSafe) {
|
|
1913
|
+
const urlSafeReEncoded = normalizedReEncoded.replace(/\+/g, "-").replace(/\//g, "_");
|
|
1914
|
+
if (normalizedInput !== urlSafeReEncoded) {
|
|
1915
|
+
return { success: false, error: "Base64 string contains invalid characters" };
|
|
1916
|
+
}
|
|
1917
|
+
} else {
|
|
1918
|
+
if (normalizedInput !== normalizedReEncoded) {
|
|
1919
|
+
return { success: false, error: "Base64 string contains invalid characters" };
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
if (opts.minBytes !== void 0 && decoded.length < opts.minBytes) {
|
|
1923
|
+
return {
|
|
1924
|
+
success: false,
|
|
1925
|
+
error: `Base64 decoded length must be at least ${opts.minBytes} bytes, got ${decoded.length}`
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
if (opts.maxBytes !== void 0 && decoded.length > opts.maxBytes) {
|
|
1929
|
+
return {
|
|
1930
|
+
success: false,
|
|
1931
|
+
error: `Base64 decoded length must be at most ${opts.maxBytes} bytes, got ${decoded.length}`
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
return { success: true, value: trimmed };
|
|
1935
|
+
}
|
|
1936
|
+
function createBase64Schema(opts) {
|
|
1937
|
+
const options = opts ?? {};
|
|
1938
|
+
const parser = (raw) => parseAndValidateBase64(raw, options);
|
|
1939
|
+
return new SchemaBuilder(parser, "base64");
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/schema/validators/semver.ts
|
|
1943
|
+
var SEMVER_CORE = "(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)";
|
|
1944
|
+
var SEMVER_PRERELEASE = "(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))";
|
|
1945
|
+
var SEMVER_BUILD = "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))";
|
|
1946
|
+
var STRICT_SEMVER_REGEX = new RegExp(`^${SEMVER_CORE}(?:${SEMVER_PRERELEASE})?(?:${SEMVER_BUILD})?$`);
|
|
1947
|
+
var LOOSE_SEMVER_REGEX = /^v?(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
1948
|
+
function parseAndValidateSemver(raw, opts) {
|
|
1949
|
+
const trimmed = raw.trim();
|
|
1950
|
+
let content = trimmed;
|
|
1951
|
+
const allowVPrefix = opts.allowVPrefix ?? true;
|
|
1952
|
+
if (content.startsWith("v") || content.startsWith("V")) {
|
|
1953
|
+
if (!allowVPrefix) {
|
|
1954
|
+
return { success: false, error: 'Semver string must not start with "v" prefix' };
|
|
1955
|
+
}
|
|
1956
|
+
content = content.slice(1);
|
|
1957
|
+
}
|
|
1958
|
+
const loose = opts.loose ?? false;
|
|
1959
|
+
const regex = loose ? LOOSE_SEMVER_REGEX : STRICT_SEMVER_REGEX;
|
|
1960
|
+
if (!regex.test(content)) {
|
|
1961
|
+
const mode = loose ? "loose" : "strict";
|
|
1962
|
+
return {
|
|
1963
|
+
success: false,
|
|
1964
|
+
error: `"${trimmed}" is not a valid semver string (${mode} mode)`
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
if (!loose) {
|
|
1968
|
+
const match = content.match(new RegExp(`^${SEMVER_CORE}(?:${SEMVER_PRERELEASE})?(?:${SEMVER_BUILD})?$`));
|
|
1969
|
+
if (match) {
|
|
1970
|
+
const prerelease = match[4];
|
|
1971
|
+
const build = match[5];
|
|
1972
|
+
if (prerelease !== void 0 && opts.allowPrerelease === false) {
|
|
1973
|
+
return {
|
|
1974
|
+
success: false,
|
|
1975
|
+
error: "Semver string must not have a prerelease suffix"
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
if (build !== void 0 && opts.allowBuildMetadata === false) {
|
|
1979
|
+
return {
|
|
1980
|
+
success: false,
|
|
1981
|
+
error: "Semver string must not have build metadata"
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
return { success: true, value: trimmed };
|
|
1987
|
+
}
|
|
1988
|
+
function createSemverSchema(opts) {
|
|
1989
|
+
const options = opts ?? {};
|
|
1990
|
+
const parser = (raw) => parseAndValidateSemver(raw, options);
|
|
1991
|
+
return new SchemaBuilder(parser, "semver");
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// src/schema/validators/cron.ts
|
|
1995
|
+
var PREDEFINED_CRON = /* @__PURE__ */ new Set([
|
|
1996
|
+
"@yearly",
|
|
1997
|
+
"@annually",
|
|
1998
|
+
"@monthly",
|
|
1999
|
+
"@weekly",
|
|
2000
|
+
"@daily",
|
|
2001
|
+
"@midnight",
|
|
2002
|
+
"@hourly"
|
|
2003
|
+
]);
|
|
2004
|
+
var CRON_FIELD_REGEX = /^(?:\*|[0-9]+(?:-[0-9]+)?(?:,[0-9]+(?:-[0-9]+)?)*)(?:\/[0-9]+)?$/;
|
|
2005
|
+
var MONTH_NAMES = {
|
|
2006
|
+
jan: "1",
|
|
2007
|
+
feb: "2",
|
|
2008
|
+
mar: "3",
|
|
2009
|
+
apr: "4",
|
|
2010
|
+
may: "5",
|
|
2011
|
+
jun: "6",
|
|
2012
|
+
jul: "7",
|
|
2013
|
+
aug: "8",
|
|
2014
|
+
sep: "9",
|
|
2015
|
+
oct: "10",
|
|
2016
|
+
nov: "11",
|
|
2017
|
+
dec: "12"
|
|
2018
|
+
};
|
|
2019
|
+
var DAY_NAMES = {
|
|
2020
|
+
sun: "0",
|
|
2021
|
+
mon: "1",
|
|
2022
|
+
tue: "2",
|
|
2023
|
+
wed: "3",
|
|
2024
|
+
thu: "4",
|
|
2025
|
+
fri: "5",
|
|
2026
|
+
sat: "6"
|
|
2027
|
+
};
|
|
2028
|
+
function replaceNames(field, names) {
|
|
2029
|
+
let result = field.toLowerCase();
|
|
2030
|
+
for (const [name, num] of Object.entries(names)) {
|
|
2031
|
+
result = result.replace(new RegExp(name, "gi"), num);
|
|
2032
|
+
}
|
|
2033
|
+
return result;
|
|
2034
|
+
}
|
|
2035
|
+
function validateCronField(field, min, max) {
|
|
2036
|
+
const normalized = replaceNames(field, { ...MONTH_NAMES, ...DAY_NAMES });
|
|
2037
|
+
const parts = normalized.split(",");
|
|
2038
|
+
for (const part of parts) {
|
|
2039
|
+
const trimmed = part.trim();
|
|
2040
|
+
if (!CRON_FIELD_REGEX.test(trimmed)) {
|
|
2041
|
+
return `Invalid cron field: "${field}"`;
|
|
2042
|
+
}
|
|
2043
|
+
const rangeMatch = trimmed.match(/^(\*|\d+)(?:-(\d+))?(?:\/(\d+))?$/);
|
|
2044
|
+
if (!rangeMatch) {
|
|
2045
|
+
return `Invalid cron field: "${field}"`;
|
|
2046
|
+
}
|
|
2047
|
+
const start = rangeMatch[1] === "*" ? min : Number(rangeMatch[1]);
|
|
2048
|
+
const end = rangeMatch[2] !== void 0 ? Number(rangeMatch[2]) : start;
|
|
2049
|
+
const step = rangeMatch[3] !== void 0 ? Number(rangeMatch[3]) : 1;
|
|
2050
|
+
if (start < min || start > max) {
|
|
2051
|
+
return `Cron field value ${start} is out of range [${min}, ${max}]`;
|
|
2052
|
+
}
|
|
2053
|
+
if (end < min || end > max) {
|
|
2054
|
+
return `Cron field value ${end} is out of range [${min}, ${max}]`;
|
|
2055
|
+
}
|
|
2056
|
+
if (step < 1) {
|
|
2057
|
+
return `Cron step must be positive, got ${step}`;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
return null;
|
|
2061
|
+
}
|
|
2062
|
+
function parseAndValidateCron(raw, opts) {
|
|
2063
|
+
const trimmed = raw.trim();
|
|
2064
|
+
if (trimmed.startsWith("@")) {
|
|
2065
|
+
if (opts.allowAliases !== false && PREDEFINED_CRON.has(trimmed.toLowerCase())) {
|
|
2066
|
+
return { success: true, value: trimmed };
|
|
2067
|
+
}
|
|
2068
|
+
return { success: false, error: `Invalid predefined cron expression: "${trimmed}"` };
|
|
2069
|
+
}
|
|
2070
|
+
const fields = trimmed.split(/\s+/);
|
|
2071
|
+
if (opts.allowSeconds === true && fields.length === 6) {
|
|
2072
|
+
const ranges = [[0, 59], [0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
|
|
2073
|
+
for (let i = 0; i < fields.length; i++) {
|
|
2074
|
+
const range = ranges[i] ?? [0, 59];
|
|
2075
|
+
const error = validateCronField(fields[i] ?? "", range[0] ?? 0, range[1] ?? 0);
|
|
2076
|
+
if (error) return { success: false, error };
|
|
2077
|
+
}
|
|
2078
|
+
return { success: true, value: trimmed };
|
|
2079
|
+
}
|
|
2080
|
+
if (opts.allowYear === true && fields.length === 6) {
|
|
2081
|
+
const ranges = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6], [1970, 2099]];
|
|
2082
|
+
for (let i = 0; i < fields.length; i++) {
|
|
2083
|
+
const range = ranges[i] ?? [0, 59];
|
|
2084
|
+
const error = validateCronField(fields[i] ?? "", range[0] ?? 0, range[1] ?? 0);
|
|
2085
|
+
if (error) return { success: false, error };
|
|
2086
|
+
}
|
|
2087
|
+
return { success: true, value: trimmed };
|
|
2088
|
+
}
|
|
2089
|
+
if (fields.length === 5) {
|
|
2090
|
+
const ranges = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
|
|
2091
|
+
for (let i = 0; i < fields.length; i++) {
|
|
2092
|
+
const range = ranges[i] ?? [0, 59];
|
|
2093
|
+
const error = validateCronField(fields[i] ?? "", range[0] ?? 0, range[1] ?? 0);
|
|
2094
|
+
if (error) return { success: false, error };
|
|
2095
|
+
}
|
|
2096
|
+
return { success: true, value: trimmed };
|
|
2097
|
+
}
|
|
2098
|
+
return {
|
|
2099
|
+
success: false,
|
|
2100
|
+
error: `Cron expression must have 5 fields (got ${fields.length}). Format: "min hour day month weekday"`
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
function createCronSchema(opts) {
|
|
2104
|
+
const options = opts ?? {};
|
|
2105
|
+
const parser = (raw) => parseAndValidateCron(raw, options);
|
|
2106
|
+
return new SchemaBuilder(parser, "cron");
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/schema/validators/duration.ts
|
|
2110
|
+
var DURATION_UNITS = {
|
|
2111
|
+
ms: 1,
|
|
2112
|
+
s: 1e3,
|
|
2113
|
+
sec: 1e3,
|
|
2114
|
+
m: 60 * 1e3,
|
|
2115
|
+
min: 60 * 1e3,
|
|
2116
|
+
h: 60 * 60 * 1e3,
|
|
2117
|
+
hr: 60 * 60 * 1e3,
|
|
2118
|
+
d: 24 * 60 * 60 * 1e3,
|
|
2119
|
+
day: 24 * 60 * 60 * 1e3,
|
|
2120
|
+
w: 7 * 24 * 60 * 60 * 1e3,
|
|
2121
|
+
wk: 7 * 24 * 60 * 60 * 1e3,
|
|
2122
|
+
y: 365 * 24 * 60 * 60 * 1e3,
|
|
2123
|
+
yr: 365 * 24 * 60 * 60 * 1e3
|
|
2124
|
+
};
|
|
2125
|
+
var DURATION_REGEX = /^(-)?(\d+(?:\.\d+)?)(ms|s|sec|m|min|h|hr|d|day|w|wk|y|yr)$/;
|
|
2126
|
+
function parseAndValidateDuration(raw, opts) {
|
|
2127
|
+
const trimmed = raw.trim();
|
|
2128
|
+
const allowNegative = opts.allowNegative ?? false;
|
|
2129
|
+
if (trimmed.length === 0) {
|
|
2130
|
+
return { success: false, error: "Duration string must not be empty" };
|
|
2131
|
+
}
|
|
2132
|
+
const match = trimmed.match(DURATION_REGEX);
|
|
2133
|
+
if (!match) {
|
|
2134
|
+
return {
|
|
2135
|
+
success: false,
|
|
2136
|
+
error: `Invalid duration format: "${trimmed}". Expected format: "<number><unit>" (e.g., "30s", "5m", "2h", "1d")`
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
const negative = match[1] === "-";
|
|
2140
|
+
const numStr = match[2] ?? "";
|
|
2141
|
+
const unit = match[3] ?? "ms";
|
|
2142
|
+
if (negative && !allowNegative) {
|
|
2143
|
+
return { success: false, error: "Negative durations are not allowed" };
|
|
2144
|
+
}
|
|
2145
|
+
const num = Number(numStr);
|
|
2146
|
+
if (!Number.isFinite(num)) {
|
|
2147
|
+
return { success: false, error: `Invalid duration number: "${numStr}"` };
|
|
2148
|
+
}
|
|
2149
|
+
const durationUnit = unit;
|
|
2150
|
+
const multiplier = DURATION_UNITS[durationUnit] ?? 1;
|
|
2151
|
+
let ms = num * multiplier;
|
|
2152
|
+
if (negative) {
|
|
2153
|
+
ms = -ms;
|
|
2154
|
+
}
|
|
2155
|
+
const outputUnit = opts.unit ?? "ms";
|
|
2156
|
+
let result;
|
|
2157
|
+
switch (outputUnit) {
|
|
2158
|
+
case "ms":
|
|
2159
|
+
result = ms;
|
|
2160
|
+
break;
|
|
2161
|
+
case "s":
|
|
2162
|
+
result = ms / 1e3;
|
|
2163
|
+
break;
|
|
2164
|
+
case "m":
|
|
2165
|
+
result = ms / (60 * 1e3);
|
|
2166
|
+
break;
|
|
2167
|
+
case "h":
|
|
2168
|
+
result = ms / (60 * 60 * 1e3);
|
|
2169
|
+
break;
|
|
2170
|
+
}
|
|
2171
|
+
if (opts.min !== void 0 && ms < opts.min) {
|
|
2172
|
+
return {
|
|
2173
|
+
success: false,
|
|
2174
|
+
error: `Duration must be at least ${opts.min}ms, got ${Math.round(ms)}ms`
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
if (opts.max !== void 0 && ms > opts.max) {
|
|
2178
|
+
return {
|
|
2179
|
+
success: false,
|
|
2180
|
+
error: `Duration must be at most ${opts.max}ms, got ${Math.round(ms)}ms`
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
return { success: true, value: result };
|
|
2184
|
+
}
|
|
2185
|
+
function createDurationSchema(opts) {
|
|
2186
|
+
const options = opts ?? {};
|
|
2187
|
+
const parser = (raw) => parseAndValidateDuration(raw, options);
|
|
2188
|
+
return new SchemaBuilder(parser, "duration");
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
// src/schema/validators/bytes.ts
|
|
2192
|
+
var BINARY_UNITS = {
|
|
2193
|
+
b: 1,
|
|
2194
|
+
byte: 1,
|
|
2195
|
+
bytes: 1,
|
|
2196
|
+
kb: 1024,
|
|
2197
|
+
kib: 1024,
|
|
2198
|
+
mb: 1024 * 1024,
|
|
2199
|
+
mib: 1024 * 1024,
|
|
2200
|
+
gb: 1024 * 1024 * 1024,
|
|
2201
|
+
gib: 1024 * 1024 * 1024,
|
|
2202
|
+
tb: 1024 * 1024 * 1024 * 1024,
|
|
2203
|
+
tib: 1024 * 1024 * 1024 * 1024,
|
|
2204
|
+
pb: 1024 * 1024 * 1024 * 1024 * 1024,
|
|
2205
|
+
pib: 1024 * 1024 * 1024 * 1024 * 1024
|
|
2206
|
+
};
|
|
2207
|
+
var DECIMAL_UNITS = {
|
|
2208
|
+
b: 1,
|
|
2209
|
+
byte: 1,
|
|
2210
|
+
bytes: 1,
|
|
2211
|
+
kb: 1e3,
|
|
2212
|
+
mb: 1e3 * 1e3,
|
|
2213
|
+
gb: 1e3 * 1e3 * 1e3,
|
|
2214
|
+
tb: 1e3 * 1e3 * 1e3 * 1e3,
|
|
2215
|
+
pb: 1e3 * 1e3 * 1e3 * 1e3 * 1e3
|
|
2216
|
+
};
|
|
2217
|
+
var BYTES_REGEX = /^(\d+(?:\.\d+)?)\s*([a-zA-Z]+)$/;
|
|
2218
|
+
function parseAndValidateBytes(raw, opts) {
|
|
2219
|
+
const trimmed = raw.trim();
|
|
2220
|
+
if (trimmed.length === 0) {
|
|
2221
|
+
return { success: false, error: "Bytes string must not be empty" };
|
|
2222
|
+
}
|
|
2223
|
+
const match = trimmed.match(BYTES_REGEX);
|
|
2224
|
+
if (!match) {
|
|
2225
|
+
return {
|
|
2226
|
+
success: false,
|
|
2227
|
+
error: `Invalid bytes format: "${trimmed}". Expected format: "<number><unit>" (e.g., "1KB", "5MB", "2GB")`
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
const numStr = match[1];
|
|
2231
|
+
const unitStr = (match[2] ?? "").toLowerCase();
|
|
2232
|
+
const num = Number(numStr);
|
|
2233
|
+
if (!Number.isFinite(num) || num < 0) {
|
|
2234
|
+
return { success: false, error: `Invalid bytes number: "${numStr}"` };
|
|
2235
|
+
}
|
|
2236
|
+
const isBinary = opts.binary ?? true;
|
|
2237
|
+
const units = isBinary ? BINARY_UNITS : DECIMAL_UNITS;
|
|
2238
|
+
const multiplier = units[unitStr];
|
|
2239
|
+
if (multiplier === void 0) {
|
|
2240
|
+
return {
|
|
2241
|
+
success: false,
|
|
2242
|
+
error: `Unknown bytes unit: "${match[2]}". Supported: ${Object.keys(units).join(", ")}`
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2245
|
+
let bytes = num * multiplier;
|
|
2246
|
+
bytes = Math.round(bytes);
|
|
2247
|
+
const outputUnit = opts.unit ?? "bytes";
|
|
2248
|
+
let result;
|
|
2249
|
+
switch (outputUnit) {
|
|
2250
|
+
case "bytes":
|
|
2251
|
+
result = bytes;
|
|
2252
|
+
break;
|
|
2253
|
+
case "kb":
|
|
2254
|
+
result = bytes / (isBinary ? 1024 : 1e3);
|
|
2255
|
+
break;
|
|
2256
|
+
case "mb":
|
|
2257
|
+
result = bytes / (isBinary ? 1024 : 1e3) ** 2;
|
|
2258
|
+
break;
|
|
2259
|
+
case "gb":
|
|
2260
|
+
result = bytes / (isBinary ? 1024 : 1e3) ** 3;
|
|
2261
|
+
break;
|
|
2262
|
+
case "tb":
|
|
2263
|
+
result = bytes / (isBinary ? 1024 : 1e3) ** 4;
|
|
2264
|
+
break;
|
|
2265
|
+
}
|
|
2266
|
+
if (opts.min !== void 0 && bytes < opts.min) {
|
|
2267
|
+
return {
|
|
2268
|
+
success: false,
|
|
2269
|
+
error: `Bytes must be at least ${opts.min}, got ${bytes}`
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
if (opts.max !== void 0 && bytes > opts.max) {
|
|
2273
|
+
return {
|
|
2274
|
+
success: false,
|
|
2275
|
+
error: `Bytes must be at most ${opts.max}, got ${bytes}`
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
return { success: true, value: result };
|
|
2279
|
+
}
|
|
2280
|
+
function createBytesSchema(opts) {
|
|
2281
|
+
const options = opts ?? {};
|
|
2282
|
+
const parser = (raw) => parseAndValidateBytes(raw, options);
|
|
2283
|
+
return new SchemaBuilder(parser, "bytes");
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// src/schema/validators/color.ts
|
|
2287
|
+
var NAMED_COLORS = /* @__PURE__ */ new Set([
|
|
2288
|
+
"aliceblue",
|
|
2289
|
+
"antiquewhite",
|
|
2290
|
+
"aqua",
|
|
2291
|
+
"aquamarine",
|
|
2292
|
+
"azure",
|
|
2293
|
+
"beige",
|
|
2294
|
+
"bisque",
|
|
2295
|
+
"black",
|
|
2296
|
+
"blanchedalmond",
|
|
2297
|
+
"blue",
|
|
2298
|
+
"blueviolet",
|
|
2299
|
+
"brown",
|
|
2300
|
+
"burlywood",
|
|
2301
|
+
"cadetblue",
|
|
2302
|
+
"chartreuse",
|
|
2303
|
+
"chocolate",
|
|
2304
|
+
"coral",
|
|
2305
|
+
"cornflowerblue",
|
|
2306
|
+
"cornsilk",
|
|
2307
|
+
"crimson",
|
|
2308
|
+
"cyan",
|
|
2309
|
+
"darkblue",
|
|
2310
|
+
"darkcyan",
|
|
2311
|
+
"darkgoldenrod",
|
|
2312
|
+
"darkgray",
|
|
2313
|
+
"darkgreen",
|
|
2314
|
+
"darkkhaki",
|
|
2315
|
+
"darkmagenta",
|
|
2316
|
+
"darkolivegreen",
|
|
2317
|
+
"darkorange",
|
|
2318
|
+
"darkorchid",
|
|
2319
|
+
"darkred",
|
|
2320
|
+
"darksalmon",
|
|
2321
|
+
"darkseagreen",
|
|
2322
|
+
"darkslateblue",
|
|
2323
|
+
"darkslategray",
|
|
2324
|
+
"darkturquoise",
|
|
2325
|
+
"darkviolet",
|
|
2326
|
+
"deeppink",
|
|
2327
|
+
"deepskyblue",
|
|
2328
|
+
"dimgray",
|
|
2329
|
+
"dodgerblue",
|
|
2330
|
+
"firebrick",
|
|
2331
|
+
"floralwhite",
|
|
2332
|
+
"forestgreen",
|
|
2333
|
+
"fuchsia",
|
|
2334
|
+
"gainsboro",
|
|
2335
|
+
"ghostwhite",
|
|
2336
|
+
"gold",
|
|
2337
|
+
"goldenrod",
|
|
2338
|
+
"gray",
|
|
2339
|
+
"green",
|
|
2340
|
+
"greenyellow",
|
|
2341
|
+
"honeydew",
|
|
2342
|
+
"hotpink",
|
|
2343
|
+
"indianred",
|
|
2344
|
+
"indigo",
|
|
2345
|
+
"ivory",
|
|
2346
|
+
"khaki",
|
|
2347
|
+
"lavender",
|
|
2348
|
+
"lavenderblush",
|
|
2349
|
+
"lawngreen",
|
|
2350
|
+
"lemonchiffon",
|
|
2351
|
+
"lightblue",
|
|
2352
|
+
"lightcoral",
|
|
2353
|
+
"lightcyan",
|
|
2354
|
+
"lightgoldenrodyellow",
|
|
2355
|
+
"lightgray",
|
|
2356
|
+
"lightgreen",
|
|
2357
|
+
"lightpink",
|
|
2358
|
+
"lightsalmon",
|
|
2359
|
+
"lightseagreen",
|
|
2360
|
+
"lightskyblue",
|
|
2361
|
+
"lightslategray",
|
|
2362
|
+
"lightsteelblue",
|
|
2363
|
+
"lightyellow",
|
|
2364
|
+
"lime",
|
|
2365
|
+
"limegreen",
|
|
2366
|
+
"linen",
|
|
2367
|
+
"magenta",
|
|
2368
|
+
"maroon",
|
|
2369
|
+
"mediumaquamarine",
|
|
2370
|
+
"mediumblue",
|
|
2371
|
+
"mediumorchid",
|
|
2372
|
+
"mediumpurple",
|
|
2373
|
+
"mediumseagreen",
|
|
2374
|
+
"mediumslateblue",
|
|
2375
|
+
"mediumspringgreen",
|
|
2376
|
+
"mediumturquoise",
|
|
2377
|
+
"mediumvioletred",
|
|
2378
|
+
"midnightblue",
|
|
2379
|
+
"mintcream",
|
|
2380
|
+
"mistyrose",
|
|
2381
|
+
"moccasin",
|
|
2382
|
+
"navajowhite",
|
|
2383
|
+
"navy",
|
|
2384
|
+
"oldlace",
|
|
2385
|
+
"olive",
|
|
2386
|
+
"olivedrab",
|
|
2387
|
+
"orange",
|
|
2388
|
+
"orangered",
|
|
2389
|
+
"orchid",
|
|
2390
|
+
"palegoldenrod",
|
|
2391
|
+
"palegreen",
|
|
2392
|
+
"paleturquoise",
|
|
2393
|
+
"palevioletred",
|
|
2394
|
+
"papayawhip",
|
|
2395
|
+
"peachpuff",
|
|
2396
|
+
"peru",
|
|
2397
|
+
"pink",
|
|
2398
|
+
"plum",
|
|
2399
|
+
"powderblue",
|
|
2400
|
+
"purple",
|
|
2401
|
+
"rebeccapurple",
|
|
2402
|
+
"red",
|
|
2403
|
+
"rosybrown",
|
|
2404
|
+
"royalblue",
|
|
2405
|
+
"saddlebrown",
|
|
2406
|
+
"salmon",
|
|
2407
|
+
"sandybrown",
|
|
2408
|
+
"seagreen",
|
|
2409
|
+
"seashell",
|
|
2410
|
+
"sienna",
|
|
2411
|
+
"silver",
|
|
2412
|
+
"skyblue",
|
|
2413
|
+
"slateblue",
|
|
2414
|
+
"slategray",
|
|
2415
|
+
"snow",
|
|
2416
|
+
"springgreen",
|
|
2417
|
+
"steelblue",
|
|
2418
|
+
"tan",
|
|
2419
|
+
"teal",
|
|
2420
|
+
"thistle",
|
|
2421
|
+
"tomato",
|
|
2422
|
+
"turquoise",
|
|
2423
|
+
"violet",
|
|
2424
|
+
"wheat",
|
|
2425
|
+
"white",
|
|
2426
|
+
"whitesmoke",
|
|
2427
|
+
"yellow",
|
|
2428
|
+
"yellowgreen"
|
|
2429
|
+
]);
|
|
2430
|
+
var HEX_3_REGEX = /^#([0-9a-fA-F]{3})$/;
|
|
2431
|
+
var HEX_6_REGEX = /^#([0-9a-fA-F]{6})$/;
|
|
2432
|
+
var HEX_8_REGEX = /^#([0-9a-fA-F]{8})$/;
|
|
2433
|
+
var RGB_REGEX = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*([0-9.]+)\s*)?\)$/;
|
|
2434
|
+
var HSL_REGEX = /^hsla?\(\s*(\d{1,3})\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*(?:,\s*([0-9.]+)\s*)?\)$/;
|
|
2435
|
+
function parseAndValidateColor(raw, opts) {
|
|
2436
|
+
const trimmed = raw.trim();
|
|
2437
|
+
const formats = opts.formats;
|
|
2438
|
+
const allowShortHex = opts.allowShortHex ?? true;
|
|
2439
|
+
const allowAlpha = opts.allowAlpha ?? true;
|
|
2440
|
+
const allowHex = formats === void 0 || formats.includes("hex");
|
|
2441
|
+
const allowRgb = formats === void 0 || formats.includes("rgb") || formats.includes("rgba");
|
|
2442
|
+
const allowHsl = formats === void 0 || formats.includes("hsl") || formats.includes("hsla");
|
|
2443
|
+
const allowNamed = formats === void 0 || formats.includes("named");
|
|
2444
|
+
if (allowHex) {
|
|
2445
|
+
if (allowAlpha && HEX_8_REGEX.test(trimmed)) {
|
|
2446
|
+
return { success: true, value: trimmed };
|
|
2447
|
+
}
|
|
2448
|
+
if (HEX_6_REGEX.test(trimmed)) {
|
|
2449
|
+
return { success: true, value: trimmed };
|
|
2450
|
+
}
|
|
2451
|
+
if (allowShortHex && HEX_3_REGEX.test(trimmed)) {
|
|
2452
|
+
return { success: true, value: trimmed };
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
if (allowRgb && RGB_REGEX.test(trimmed)) {
|
|
2456
|
+
const match = trimmed.match(RGB_REGEX);
|
|
2457
|
+
if (match) {
|
|
2458
|
+
const r = Number(match[1]);
|
|
2459
|
+
const g = Number(match[2]);
|
|
2460
|
+
const b = Number(match[3]);
|
|
2461
|
+
const a = match[4] !== void 0 ? Number(match[4]) : 1;
|
|
2462
|
+
if (r > 255 || g > 255 || b > 255) {
|
|
2463
|
+
return { success: false, error: `RGB values must be 0-255` };
|
|
2464
|
+
}
|
|
2465
|
+
if (a < 0 || a > 1) {
|
|
2466
|
+
return { success: false, error: `RGBA alpha must be between 0 and 1` };
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
return { success: true, value: trimmed };
|
|
2470
|
+
}
|
|
2471
|
+
if (allowHsl && HSL_REGEX.test(trimmed)) {
|
|
2472
|
+
const match = trimmed.match(HSL_REGEX);
|
|
2473
|
+
if (match) {
|
|
2474
|
+
const h = Number(match[1]);
|
|
2475
|
+
const s = Number(match[2]);
|
|
2476
|
+
const l = Number(match[3]);
|
|
2477
|
+
const a = match[4] !== void 0 ? Number(match[4]) : 1;
|
|
2478
|
+
if (h > 360) {
|
|
2479
|
+
return { success: false, error: `HSL hue must be 0-360` };
|
|
2480
|
+
}
|
|
2481
|
+
if (s > 100 || l > 100) {
|
|
2482
|
+
return { success: false, error: `HSL saturation/lightness must be 0-100%` };
|
|
2483
|
+
}
|
|
2484
|
+
if (a < 0 || a > 1) {
|
|
2485
|
+
return { success: false, error: `HSLA alpha must be between 0 and 1` };
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
return { success: true, value: trimmed };
|
|
2489
|
+
}
|
|
2490
|
+
if (allowNamed && NAMED_COLORS.has(trimmed.toLowerCase())) {
|
|
2491
|
+
return { success: true, value: trimmed };
|
|
2492
|
+
}
|
|
2493
|
+
return {
|
|
2494
|
+
success: false,
|
|
2495
|
+
error: `"${trimmed}" is not a valid color. Supported formats: hex (#RGB, #RRGGBB), rgb(), rgba(), hsl(), hsla(), or named colors`
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
function createColorSchema(opts) {
|
|
2499
|
+
const options = opts ?? {};
|
|
2500
|
+
const parser = (raw) => parseAndValidateColor(raw, options);
|
|
2501
|
+
return new SchemaBuilder(parser, "color");
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
// src/schema/validators/locale.ts
|
|
2505
|
+
var LANGUAGE_REGION_REGEX = /^([a-zA-Z]{2,3})(?:-([a-zA-Z]{4}))?(?:-([a-zA-Z]{2}|[0-9]{3}))?/;
|
|
2506
|
+
var COMMON_LANGUAGES = /* @__PURE__ */ new Set([
|
|
2507
|
+
"aa",
|
|
2508
|
+
"ab",
|
|
2509
|
+
"ae",
|
|
2510
|
+
"af",
|
|
2511
|
+
"ak",
|
|
2512
|
+
"am",
|
|
2513
|
+
"an",
|
|
2514
|
+
"ar",
|
|
2515
|
+
"as",
|
|
2516
|
+
"av",
|
|
2517
|
+
"ay",
|
|
2518
|
+
"az",
|
|
2519
|
+
"ba",
|
|
2520
|
+
"be",
|
|
2521
|
+
"bg",
|
|
2522
|
+
"bh",
|
|
2523
|
+
"bi",
|
|
2524
|
+
"bm",
|
|
2525
|
+
"bn",
|
|
2526
|
+
"bo",
|
|
2527
|
+
"br",
|
|
2528
|
+
"bs",
|
|
2529
|
+
"ca",
|
|
2530
|
+
"ce",
|
|
2531
|
+
"ch",
|
|
2532
|
+
"co",
|
|
2533
|
+
"cr",
|
|
2534
|
+
"cs",
|
|
2535
|
+
"cu",
|
|
2536
|
+
"cv",
|
|
2537
|
+
"cy",
|
|
2538
|
+
"da",
|
|
2539
|
+
"de",
|
|
2540
|
+
"dv",
|
|
2541
|
+
"dz",
|
|
2542
|
+
"ee",
|
|
2543
|
+
"el",
|
|
2544
|
+
"en",
|
|
2545
|
+
"eo",
|
|
2546
|
+
"es",
|
|
2547
|
+
"et",
|
|
2548
|
+
"eu",
|
|
2549
|
+
"fa",
|
|
2550
|
+
"ff",
|
|
2551
|
+
"fi",
|
|
2552
|
+
"fj",
|
|
2553
|
+
"fo",
|
|
2554
|
+
"fr",
|
|
2555
|
+
"fy",
|
|
2556
|
+
"ga",
|
|
2557
|
+
"gd",
|
|
2558
|
+
"gl",
|
|
2559
|
+
"gn",
|
|
2560
|
+
"gu",
|
|
2561
|
+
"gv",
|
|
2562
|
+
"ha",
|
|
2563
|
+
"he",
|
|
2564
|
+
"hi",
|
|
2565
|
+
"ho",
|
|
2566
|
+
"hr",
|
|
2567
|
+
"ht",
|
|
2568
|
+
"hu",
|
|
2569
|
+
"hy",
|
|
2570
|
+
"hz",
|
|
2571
|
+
"ia",
|
|
2572
|
+
"id",
|
|
2573
|
+
"ie",
|
|
2574
|
+
"ig",
|
|
2575
|
+
"ii",
|
|
2576
|
+
"ik",
|
|
2577
|
+
"io",
|
|
2578
|
+
"is",
|
|
2579
|
+
"it",
|
|
2580
|
+
"iu",
|
|
2581
|
+
"ja",
|
|
2582
|
+
"jv",
|
|
2583
|
+
"ka",
|
|
2584
|
+
"kg",
|
|
2585
|
+
"ki",
|
|
2586
|
+
"kj",
|
|
2587
|
+
"kk",
|
|
2588
|
+
"kl",
|
|
2589
|
+
"km",
|
|
2590
|
+
"kn",
|
|
2591
|
+
"ko",
|
|
2592
|
+
"kr",
|
|
2593
|
+
"ks",
|
|
2594
|
+
"ku",
|
|
2595
|
+
"kv",
|
|
2596
|
+
"kw",
|
|
2597
|
+
"ky",
|
|
2598
|
+
"la",
|
|
2599
|
+
"lb",
|
|
2600
|
+
"lg",
|
|
2601
|
+
"li",
|
|
2602
|
+
"ln",
|
|
2603
|
+
"lo",
|
|
2604
|
+
"lt",
|
|
2605
|
+
"lu",
|
|
2606
|
+
"lv",
|
|
2607
|
+
"mg",
|
|
2608
|
+
"mh",
|
|
2609
|
+
"mi",
|
|
2610
|
+
"mk",
|
|
2611
|
+
"ml",
|
|
2612
|
+
"mn",
|
|
2613
|
+
"mr",
|
|
2614
|
+
"ms",
|
|
2615
|
+
"mt",
|
|
2616
|
+
"my",
|
|
2617
|
+
"na",
|
|
2618
|
+
"nb",
|
|
2619
|
+
"nd",
|
|
2620
|
+
"ne",
|
|
2621
|
+
"ng",
|
|
2622
|
+
"nl",
|
|
2623
|
+
"nn",
|
|
2624
|
+
"no",
|
|
2625
|
+
"nr",
|
|
2626
|
+
"nv",
|
|
2627
|
+
"ny",
|
|
2628
|
+
"oc",
|
|
2629
|
+
"oj",
|
|
2630
|
+
"om",
|
|
2631
|
+
"or",
|
|
2632
|
+
"os",
|
|
2633
|
+
"pa",
|
|
2634
|
+
"pi",
|
|
2635
|
+
"pl",
|
|
2636
|
+
"ps",
|
|
2637
|
+
"pt",
|
|
2638
|
+
"qu",
|
|
2639
|
+
"rm",
|
|
2640
|
+
"rn",
|
|
2641
|
+
"ro",
|
|
2642
|
+
"ru",
|
|
2643
|
+
"rw",
|
|
2644
|
+
"sa",
|
|
2645
|
+
"sc",
|
|
2646
|
+
"sd",
|
|
2647
|
+
"se",
|
|
2648
|
+
"sg",
|
|
2649
|
+
"si",
|
|
2650
|
+
"sk",
|
|
2651
|
+
"sl",
|
|
2652
|
+
"sm",
|
|
2653
|
+
"sn",
|
|
2654
|
+
"so",
|
|
2655
|
+
"sq",
|
|
2656
|
+
"sr",
|
|
2657
|
+
"ss",
|
|
2658
|
+
"st",
|
|
2659
|
+
"su",
|
|
2660
|
+
"sv",
|
|
2661
|
+
"sw",
|
|
2662
|
+
"ta",
|
|
2663
|
+
"te",
|
|
2664
|
+
"tg",
|
|
2665
|
+
"th",
|
|
2666
|
+
"ti",
|
|
2667
|
+
"tk",
|
|
2668
|
+
"tl",
|
|
2669
|
+
"tn",
|
|
2670
|
+
"to",
|
|
2671
|
+
"tr",
|
|
2672
|
+
"ts",
|
|
2673
|
+
"tt",
|
|
2674
|
+
"tw",
|
|
2675
|
+
"ty",
|
|
2676
|
+
"ug",
|
|
2677
|
+
"uk",
|
|
2678
|
+
"ur",
|
|
2679
|
+
"uz",
|
|
2680
|
+
"ve",
|
|
2681
|
+
"vi",
|
|
2682
|
+
"vo",
|
|
2683
|
+
"wa",
|
|
2684
|
+
"wo",
|
|
2685
|
+
"xh",
|
|
2686
|
+
"yi",
|
|
2687
|
+
"yo",
|
|
2688
|
+
"za",
|
|
2689
|
+
"zh",
|
|
2690
|
+
"zu"
|
|
2691
|
+
]);
|
|
2692
|
+
function parseAndValidateLocale(raw, opts) {
|
|
2693
|
+
const trimmed = raw.trim().toLowerCase();
|
|
2694
|
+
if (trimmed.length === 0) {
|
|
2695
|
+
return { success: false, error: "Locale must not be empty" };
|
|
2696
|
+
}
|
|
2697
|
+
if (opts.allowExtensions !== true && /-[uU]-/.test(trimmed)) {
|
|
2698
|
+
return { success: false, error: "Unicode locale extensions are not allowed" };
|
|
2699
|
+
}
|
|
2700
|
+
const baseLocale = trimmed.split("-u-")[0] ?? trimmed;
|
|
2701
|
+
const match = baseLocale.match(LANGUAGE_REGION_REGEX);
|
|
2702
|
+
if (!match) {
|
|
2703
|
+
return {
|
|
2704
|
+
success: false,
|
|
2705
|
+
error: `"${trimmed}" is not a valid BCP 47 locale tag`
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
const language = match[1];
|
|
2709
|
+
const script = match[2];
|
|
2710
|
+
const region = match[3];
|
|
2711
|
+
if (language === void 0) {
|
|
2712
|
+
return { success: false, error: "Locale must include a language subtag" };
|
|
2713
|
+
}
|
|
2714
|
+
if (opts.allowedLanguages !== void 0) {
|
|
2715
|
+
const allowed = opts.allowedLanguages.map((l) => l.toLowerCase());
|
|
2716
|
+
if (!allowed.includes(language)) {
|
|
2717
|
+
return {
|
|
2718
|
+
success: false,
|
|
2719
|
+
error: `Language "${language}" is not allowed. Allowed: ${allowed.join(", ")}`
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
if (!COMMON_LANGUAGES.has(language) && opts.allowedLanguages === void 0) {
|
|
2724
|
+
}
|
|
2725
|
+
if (opts.requireRegion === true && region === void 0 && script === void 0) {
|
|
2726
|
+
return {
|
|
2727
|
+
success: false,
|
|
2728
|
+
error: 'Locale must include a region subtag (e.g., "en-US")'
|
|
2729
|
+
};
|
|
2730
|
+
}
|
|
2731
|
+
void script;
|
|
2732
|
+
return { success: true, value: trimmed };
|
|
2733
|
+
}
|
|
2734
|
+
function createLocaleSchema(opts) {
|
|
2735
|
+
const options = opts ?? {};
|
|
2736
|
+
const parser = (raw) => parseAndValidateLocale(raw, options);
|
|
2737
|
+
return new SchemaBuilder(parser, "locale");
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// src/schema/validators/timezone.ts
|
|
2741
|
+
var IANA_TZ_REGEX = /^(?:Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific|Etc|UTC)\/[a-zA-Z0-9_+-]+(?:\/[a-zA-Z0-9_+-]+)*$/;
|
|
2742
|
+
var UTC_OFFSET_REGEX = /^(?:UTC|GMT)(?:[+-](?:[01]?\d|2[0-3])(?::(?:[0-5]\d))?)?$/;
|
|
2743
|
+
var SHORT_TIMEZONES = /* @__PURE__ */ new Set([
|
|
2744
|
+
"UTC",
|
|
2745
|
+
"GMT",
|
|
2746
|
+
"EST",
|
|
2747
|
+
"EDT",
|
|
2748
|
+
"CST",
|
|
2749
|
+
"CDT",
|
|
2750
|
+
"MST",
|
|
2751
|
+
"MDT",
|
|
2752
|
+
"PST",
|
|
2753
|
+
"PDT",
|
|
2754
|
+
"AKST",
|
|
2755
|
+
"AKDT",
|
|
2756
|
+
"HST",
|
|
2757
|
+
"HDT",
|
|
2758
|
+
"SST",
|
|
2759
|
+
"SDT",
|
|
2760
|
+
"CHST",
|
|
2761
|
+
"WET",
|
|
2762
|
+
"WEST",
|
|
2763
|
+
"CET",
|
|
2764
|
+
"CEST",
|
|
2765
|
+
"EET",
|
|
2766
|
+
"EEST",
|
|
2767
|
+
"MSK",
|
|
2768
|
+
"MSD",
|
|
2769
|
+
"AST",
|
|
2770
|
+
"ADT",
|
|
2771
|
+
"NST",
|
|
2772
|
+
"NDT",
|
|
2773
|
+
"GST",
|
|
2774
|
+
"AZT",
|
|
2775
|
+
"AFT",
|
|
2776
|
+
"PKT",
|
|
2777
|
+
"IST",
|
|
2778
|
+
"NPT",
|
|
2779
|
+
"BST",
|
|
2780
|
+
"ICT",
|
|
2781
|
+
"WIB",
|
|
2782
|
+
"PHT",
|
|
2783
|
+
"JST",
|
|
2784
|
+
"KST",
|
|
2785
|
+
"CST"
|
|
2786
|
+
]);
|
|
2787
|
+
function parseAndValidateTimezone(raw, opts) {
|
|
2788
|
+
const trimmed = raw.trim();
|
|
2789
|
+
if (trimmed.length === 0) {
|
|
2790
|
+
return { success: false, error: "Timezone must not be empty" };
|
|
2791
|
+
}
|
|
2792
|
+
if (trimmed === "Z") {
|
|
2793
|
+
if (opts.allowZ !== false) {
|
|
2794
|
+
return { success: true, value: trimmed };
|
|
2795
|
+
}
|
|
2796
|
+
return { success: false, error: '"Z" timezone identifier is not allowed' };
|
|
2797
|
+
}
|
|
2798
|
+
if (IANA_TZ_REGEX.test(trimmed)) {
|
|
2799
|
+
return { success: true, value: trimmed };
|
|
2800
|
+
}
|
|
2801
|
+
if (opts.allowOffset !== false && UTC_OFFSET_REGEX.test(trimmed)) {
|
|
2802
|
+
return { success: true, value: trimmed };
|
|
2803
|
+
}
|
|
2804
|
+
if (opts.allowShort === true && SHORT_TIMEZONES.has(trimmed.toUpperCase())) {
|
|
2805
|
+
return { success: true, value: trimmed.toUpperCase() };
|
|
2806
|
+
}
|
|
2807
|
+
return {
|
|
2808
|
+
success: false,
|
|
2809
|
+
error: `"${trimmed}" is not a valid IANA timezone identifier. Expected format: "Area/Location" (e.g., "America/New_York", "Europe/London")`
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
function createTimezoneSchema(opts) {
|
|
2813
|
+
const options = opts ?? {};
|
|
2814
|
+
const parser = (raw) => parseAndValidateTimezone(raw, options);
|
|
2815
|
+
return new SchemaBuilder(parser, "timezone");
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
// src/schema/validators/country.ts
|
|
2819
|
+
var ALPHA2_CODES = /* @__PURE__ */ new Set([
|
|
2820
|
+
"AF",
|
|
2821
|
+
"AX",
|
|
2822
|
+
"AL",
|
|
2823
|
+
"DZ",
|
|
2824
|
+
"AS",
|
|
2825
|
+
"AD",
|
|
2826
|
+
"AO",
|
|
2827
|
+
"AI",
|
|
2828
|
+
"AQ",
|
|
2829
|
+
"AG",
|
|
2830
|
+
"AR",
|
|
2831
|
+
"AM",
|
|
2832
|
+
"AW",
|
|
2833
|
+
"AU",
|
|
2834
|
+
"AT",
|
|
2835
|
+
"AZ",
|
|
2836
|
+
"BS",
|
|
2837
|
+
"BH",
|
|
2838
|
+
"BD",
|
|
2839
|
+
"BB",
|
|
2840
|
+
"BY",
|
|
2841
|
+
"BE",
|
|
2842
|
+
"BZ",
|
|
2843
|
+
"BJ",
|
|
2844
|
+
"BM",
|
|
2845
|
+
"BT",
|
|
2846
|
+
"BO",
|
|
2847
|
+
"BQ",
|
|
2848
|
+
"BA",
|
|
2849
|
+
"BW",
|
|
2850
|
+
"BV",
|
|
2851
|
+
"BR",
|
|
2852
|
+
"IO",
|
|
2853
|
+
"BN",
|
|
2854
|
+
"BG",
|
|
2855
|
+
"BF",
|
|
2856
|
+
"BI",
|
|
2857
|
+
"CV",
|
|
2858
|
+
"KH",
|
|
2859
|
+
"CM",
|
|
2860
|
+
"CA",
|
|
2861
|
+
"KY",
|
|
2862
|
+
"CF",
|
|
2863
|
+
"TD",
|
|
2864
|
+
"CL",
|
|
2865
|
+
"CN",
|
|
2866
|
+
"CX",
|
|
2867
|
+
"CC",
|
|
2868
|
+
"CO",
|
|
2869
|
+
"KM",
|
|
2870
|
+
"CG",
|
|
2871
|
+
"CD",
|
|
2872
|
+
"CK",
|
|
2873
|
+
"CR",
|
|
2874
|
+
"CI",
|
|
2875
|
+
"HR",
|
|
2876
|
+
"CU",
|
|
2877
|
+
"CW",
|
|
2878
|
+
"CY",
|
|
2879
|
+
"CZ",
|
|
2880
|
+
"DK",
|
|
2881
|
+
"DJ",
|
|
2882
|
+
"DM",
|
|
2883
|
+
"DO",
|
|
2884
|
+
"EC",
|
|
2885
|
+
"EG",
|
|
2886
|
+
"SV",
|
|
2887
|
+
"GQ",
|
|
2888
|
+
"ER",
|
|
2889
|
+
"EE",
|
|
2890
|
+
"SZ",
|
|
2891
|
+
"ET",
|
|
2892
|
+
"FK",
|
|
2893
|
+
"FO",
|
|
2894
|
+
"FJ",
|
|
2895
|
+
"FI",
|
|
2896
|
+
"FR",
|
|
2897
|
+
"GF",
|
|
2898
|
+
"PF",
|
|
2899
|
+
"TF",
|
|
2900
|
+
"GA",
|
|
2901
|
+
"GM",
|
|
2902
|
+
"GE",
|
|
2903
|
+
"DE",
|
|
2904
|
+
"GH",
|
|
2905
|
+
"GI",
|
|
2906
|
+
"GR",
|
|
2907
|
+
"GL",
|
|
2908
|
+
"GD",
|
|
2909
|
+
"GP",
|
|
2910
|
+
"GU",
|
|
2911
|
+
"GT",
|
|
2912
|
+
"GG",
|
|
2913
|
+
"GN",
|
|
2914
|
+
"GW",
|
|
2915
|
+
"GY",
|
|
2916
|
+
"HT",
|
|
2917
|
+
"HM",
|
|
2918
|
+
"VA",
|
|
2919
|
+
"HN",
|
|
2920
|
+
"HK",
|
|
2921
|
+
"HU",
|
|
2922
|
+
"IS",
|
|
2923
|
+
"IN",
|
|
2924
|
+
"ID",
|
|
2925
|
+
"IR",
|
|
2926
|
+
"IQ",
|
|
2927
|
+
"IE",
|
|
2928
|
+
"IM",
|
|
2929
|
+
"IL",
|
|
2930
|
+
"IT",
|
|
2931
|
+
"JM",
|
|
2932
|
+
"JP",
|
|
2933
|
+
"JE",
|
|
2934
|
+
"JO",
|
|
2935
|
+
"KZ",
|
|
2936
|
+
"KE",
|
|
2937
|
+
"KI",
|
|
2938
|
+
"KP",
|
|
2939
|
+
"KR",
|
|
2940
|
+
"KW",
|
|
2941
|
+
"KG",
|
|
2942
|
+
"LA",
|
|
2943
|
+
"LV",
|
|
2944
|
+
"LB",
|
|
2945
|
+
"LS",
|
|
2946
|
+
"LR",
|
|
2947
|
+
"LY",
|
|
2948
|
+
"LI",
|
|
2949
|
+
"LT",
|
|
2950
|
+
"LU",
|
|
2951
|
+
"MO",
|
|
2952
|
+
"MG",
|
|
2953
|
+
"MW",
|
|
2954
|
+
"MY",
|
|
2955
|
+
"MV",
|
|
2956
|
+
"ML",
|
|
2957
|
+
"MT",
|
|
2958
|
+
"MH",
|
|
2959
|
+
"MQ",
|
|
2960
|
+
"MR",
|
|
2961
|
+
"MU",
|
|
2962
|
+
"YT",
|
|
2963
|
+
"MX",
|
|
2964
|
+
"FM",
|
|
2965
|
+
"MD",
|
|
2966
|
+
"MC",
|
|
2967
|
+
"MN",
|
|
2968
|
+
"ME",
|
|
2969
|
+
"MS",
|
|
2970
|
+
"MA",
|
|
2971
|
+
"MZ",
|
|
2972
|
+
"MM",
|
|
2973
|
+
"NA",
|
|
2974
|
+
"NR",
|
|
2975
|
+
"NP",
|
|
2976
|
+
"NL",
|
|
2977
|
+
"NC",
|
|
2978
|
+
"NZ",
|
|
2979
|
+
"NI",
|
|
2980
|
+
"NE",
|
|
2981
|
+
"NG",
|
|
2982
|
+
"NU",
|
|
2983
|
+
"NF",
|
|
2984
|
+
"MP",
|
|
2985
|
+
"NO",
|
|
2986
|
+
"OM",
|
|
2987
|
+
"PK",
|
|
2988
|
+
"PW",
|
|
2989
|
+
"PS",
|
|
2990
|
+
"PA",
|
|
2991
|
+
"PG",
|
|
2992
|
+
"PY",
|
|
2993
|
+
"PE",
|
|
2994
|
+
"PH",
|
|
2995
|
+
"PN",
|
|
2996
|
+
"PL",
|
|
2997
|
+
"PT",
|
|
2998
|
+
"PR",
|
|
2999
|
+
"QA",
|
|
3000
|
+
"RE",
|
|
3001
|
+
"RO",
|
|
3002
|
+
"RU",
|
|
3003
|
+
"RW",
|
|
3004
|
+
"BL",
|
|
3005
|
+
"SH",
|
|
3006
|
+
"KN",
|
|
3007
|
+
"LC",
|
|
3008
|
+
"MF",
|
|
3009
|
+
"PM",
|
|
3010
|
+
"VC",
|
|
3011
|
+
"WS",
|
|
3012
|
+
"SM",
|
|
3013
|
+
"ST",
|
|
3014
|
+
"SA",
|
|
3015
|
+
"SN",
|
|
3016
|
+
"RS",
|
|
3017
|
+
"SC",
|
|
3018
|
+
"SL",
|
|
3019
|
+
"SG",
|
|
3020
|
+
"SX",
|
|
3021
|
+
"SK",
|
|
3022
|
+
"SI",
|
|
3023
|
+
"SB",
|
|
3024
|
+
"SO",
|
|
3025
|
+
"ZA",
|
|
3026
|
+
"GS",
|
|
3027
|
+
"SS",
|
|
3028
|
+
"ES",
|
|
3029
|
+
"LK",
|
|
3030
|
+
"SD",
|
|
3031
|
+
"SR",
|
|
3032
|
+
"SJ",
|
|
3033
|
+
"SE",
|
|
3034
|
+
"CH",
|
|
3035
|
+
"SY",
|
|
3036
|
+
"TW",
|
|
3037
|
+
"TJ",
|
|
3038
|
+
"TZ",
|
|
3039
|
+
"TH",
|
|
3040
|
+
"TL",
|
|
3041
|
+
"TG",
|
|
3042
|
+
"TK",
|
|
3043
|
+
"TO",
|
|
3044
|
+
"TT",
|
|
3045
|
+
"TN",
|
|
3046
|
+
"TR",
|
|
3047
|
+
"TM",
|
|
3048
|
+
"TC",
|
|
3049
|
+
"TV",
|
|
3050
|
+
"UG",
|
|
3051
|
+
"UA",
|
|
3052
|
+
"AE",
|
|
3053
|
+
"GB",
|
|
3054
|
+
"US",
|
|
3055
|
+
"UM",
|
|
3056
|
+
"UY",
|
|
3057
|
+
"UZ",
|
|
3058
|
+
"VU",
|
|
3059
|
+
"VE",
|
|
3060
|
+
"VN",
|
|
3061
|
+
"VG",
|
|
3062
|
+
"VI",
|
|
3063
|
+
"WF",
|
|
3064
|
+
"EH",
|
|
3065
|
+
"YE",
|
|
3066
|
+
"ZM",
|
|
3067
|
+
"ZW"
|
|
3068
|
+
]);
|
|
3069
|
+
var ALPHA3_CODES = /* @__PURE__ */ new Set([
|
|
3070
|
+
"AFG",
|
|
3071
|
+
"ALA",
|
|
3072
|
+
"ALB",
|
|
3073
|
+
"DZA",
|
|
3074
|
+
"ASM",
|
|
3075
|
+
"AND",
|
|
3076
|
+
"AGO",
|
|
3077
|
+
"AIA",
|
|
3078
|
+
"ATA",
|
|
3079
|
+
"ATG",
|
|
3080
|
+
"ARG",
|
|
3081
|
+
"ARM",
|
|
3082
|
+
"ABW",
|
|
3083
|
+
"AUS",
|
|
3084
|
+
"AUT",
|
|
3085
|
+
"AZE",
|
|
3086
|
+
"BHS",
|
|
3087
|
+
"BHR",
|
|
3088
|
+
"BGD",
|
|
3089
|
+
"BRB",
|
|
3090
|
+
"BLR",
|
|
3091
|
+
"BEL",
|
|
3092
|
+
"BLZ",
|
|
3093
|
+
"BEN",
|
|
3094
|
+
"BMU",
|
|
3095
|
+
"BTN",
|
|
3096
|
+
"BOL",
|
|
3097
|
+
"BES",
|
|
3098
|
+
"BIH",
|
|
3099
|
+
"BWA",
|
|
3100
|
+
"BVT",
|
|
3101
|
+
"BRA",
|
|
3102
|
+
"IOT",
|
|
3103
|
+
"BRN",
|
|
3104
|
+
"BGR",
|
|
3105
|
+
"BFA",
|
|
3106
|
+
"BDI",
|
|
3107
|
+
"CPV",
|
|
3108
|
+
"KHM",
|
|
3109
|
+
"CMR",
|
|
3110
|
+
"CAN",
|
|
3111
|
+
"CYM",
|
|
3112
|
+
"CAF",
|
|
3113
|
+
"TCD",
|
|
3114
|
+
"CHL",
|
|
3115
|
+
"CHN",
|
|
3116
|
+
"CXR",
|
|
3117
|
+
"CCK",
|
|
3118
|
+
"COL",
|
|
3119
|
+
"COM",
|
|
3120
|
+
"COG",
|
|
3121
|
+
"COD",
|
|
3122
|
+
"COK",
|
|
3123
|
+
"CRI",
|
|
3124
|
+
"CIV",
|
|
3125
|
+
"HRV",
|
|
3126
|
+
"CUB",
|
|
3127
|
+
"CUW",
|
|
3128
|
+
"CYP",
|
|
3129
|
+
"CZE",
|
|
3130
|
+
"DNK",
|
|
3131
|
+
"DJI",
|
|
3132
|
+
"DMA",
|
|
3133
|
+
"DOM",
|
|
3134
|
+
"ECU",
|
|
3135
|
+
"EGY",
|
|
3136
|
+
"SLV",
|
|
3137
|
+
"GNQ",
|
|
3138
|
+
"ERI",
|
|
3139
|
+
"EST",
|
|
3140
|
+
"SWZ",
|
|
3141
|
+
"ETH",
|
|
3142
|
+
"FLK",
|
|
3143
|
+
"FRO",
|
|
3144
|
+
"FJI",
|
|
3145
|
+
"FIN",
|
|
3146
|
+
"FRA",
|
|
3147
|
+
"GUF",
|
|
3148
|
+
"PYF",
|
|
3149
|
+
"ATF",
|
|
3150
|
+
"GAB",
|
|
3151
|
+
"GMB",
|
|
3152
|
+
"GEO",
|
|
3153
|
+
"DEU",
|
|
3154
|
+
"GHA",
|
|
3155
|
+
"GIB",
|
|
3156
|
+
"GRC",
|
|
3157
|
+
"GRL",
|
|
3158
|
+
"GRD",
|
|
3159
|
+
"GLP",
|
|
3160
|
+
"GUM",
|
|
3161
|
+
"GTM",
|
|
3162
|
+
"GGY",
|
|
3163
|
+
"GIN",
|
|
3164
|
+
"GNB",
|
|
3165
|
+
"GUY",
|
|
3166
|
+
"HTI",
|
|
3167
|
+
"HMD",
|
|
3168
|
+
"VAT",
|
|
3169
|
+
"HND",
|
|
3170
|
+
"HKG",
|
|
3171
|
+
"HUN",
|
|
3172
|
+
"ISL",
|
|
3173
|
+
"IND",
|
|
3174
|
+
"IDN",
|
|
3175
|
+
"IRN",
|
|
3176
|
+
"IRQ",
|
|
3177
|
+
"IRL",
|
|
3178
|
+
"IMN",
|
|
3179
|
+
"ISR",
|
|
3180
|
+
"ITA",
|
|
3181
|
+
"JAM",
|
|
3182
|
+
"JPN",
|
|
3183
|
+
"JEY",
|
|
3184
|
+
"JOR",
|
|
3185
|
+
"KAZ",
|
|
3186
|
+
"KEN",
|
|
3187
|
+
"KIR",
|
|
3188
|
+
"PRK",
|
|
3189
|
+
"KOR",
|
|
3190
|
+
"KWT",
|
|
3191
|
+
"KGZ",
|
|
3192
|
+
"LAO",
|
|
3193
|
+
"LVA",
|
|
3194
|
+
"LBN",
|
|
3195
|
+
"L SO",
|
|
3196
|
+
"LBR",
|
|
3197
|
+
"LBY",
|
|
3198
|
+
"LIE",
|
|
3199
|
+
"LTU",
|
|
3200
|
+
"LUX",
|
|
3201
|
+
"MAC",
|
|
3202
|
+
"MDG",
|
|
3203
|
+
"MWI",
|
|
3204
|
+
"MYS",
|
|
3205
|
+
"MDV",
|
|
3206
|
+
"MLI",
|
|
3207
|
+
"MLT",
|
|
3208
|
+
"MHL",
|
|
3209
|
+
"MTQ",
|
|
3210
|
+
"MRT",
|
|
3211
|
+
"MUS",
|
|
3212
|
+
"MYT",
|
|
3213
|
+
"MEX",
|
|
3214
|
+
"FSM",
|
|
3215
|
+
"MDA",
|
|
3216
|
+
"MCO",
|
|
3217
|
+
"MNG",
|
|
3218
|
+
"MNE",
|
|
3219
|
+
"MSR",
|
|
3220
|
+
"MAR",
|
|
3221
|
+
"MOZ",
|
|
3222
|
+
"MMR",
|
|
3223
|
+
"NAM",
|
|
3224
|
+
"NRU",
|
|
3225
|
+
"NPL",
|
|
3226
|
+
"NLD",
|
|
3227
|
+
"NCL",
|
|
3228
|
+
"NZL",
|
|
3229
|
+
"NIC",
|
|
3230
|
+
"NER",
|
|
3231
|
+
"NGA",
|
|
3232
|
+
"NIU",
|
|
3233
|
+
"NFK",
|
|
3234
|
+
"MNP",
|
|
3235
|
+
"NOR",
|
|
3236
|
+
"OMN",
|
|
3237
|
+
"PAK",
|
|
3238
|
+
"PLW",
|
|
3239
|
+
"PSE",
|
|
3240
|
+
"PAN",
|
|
3241
|
+
"PNG",
|
|
3242
|
+
"PRY",
|
|
3243
|
+
"PER",
|
|
3244
|
+
"PHL",
|
|
3245
|
+
"PCN",
|
|
3246
|
+
"POL",
|
|
3247
|
+
"PRT",
|
|
3248
|
+
"PRI",
|
|
3249
|
+
"QAT",
|
|
3250
|
+
"REU",
|
|
3251
|
+
"ROU",
|
|
3252
|
+
"RUS",
|
|
3253
|
+
"RWA",
|
|
3254
|
+
"BLM",
|
|
3255
|
+
"SHN",
|
|
3256
|
+
"KNA",
|
|
3257
|
+
"LCA",
|
|
3258
|
+
"MAF",
|
|
3259
|
+
"SPM",
|
|
3260
|
+
"VCT",
|
|
3261
|
+
"WSM",
|
|
3262
|
+
"SMR",
|
|
3263
|
+
"STP",
|
|
3264
|
+
"SAU",
|
|
3265
|
+
"SEN",
|
|
3266
|
+
"SRB",
|
|
3267
|
+
"SYC",
|
|
3268
|
+
"SLE",
|
|
3269
|
+
"SGP",
|
|
3270
|
+
"SXM",
|
|
3271
|
+
"SVK",
|
|
3272
|
+
"SVN",
|
|
3273
|
+
"SLB",
|
|
3274
|
+
"SOM",
|
|
3275
|
+
"ZAF",
|
|
3276
|
+
"SGS",
|
|
3277
|
+
"SSD",
|
|
3278
|
+
"ESP",
|
|
3279
|
+
"LKA",
|
|
3280
|
+
"SDN",
|
|
3281
|
+
"SUR",
|
|
3282
|
+
"SJM",
|
|
3283
|
+
"SWE",
|
|
3284
|
+
"CHE",
|
|
3285
|
+
"SYR",
|
|
3286
|
+
"TWN",
|
|
3287
|
+
"TJK",
|
|
3288
|
+
"TZA",
|
|
3289
|
+
"THA",
|
|
3290
|
+
"TLS",
|
|
3291
|
+
"TGO",
|
|
3292
|
+
"TKL",
|
|
3293
|
+
"TON",
|
|
3294
|
+
"TTO",
|
|
3295
|
+
"TUN",
|
|
3296
|
+
"TUR",
|
|
3297
|
+
"TKM",
|
|
3298
|
+
"TCA",
|
|
3299
|
+
"TUV",
|
|
3300
|
+
"UGA",
|
|
3301
|
+
"UKR",
|
|
3302
|
+
"ARE",
|
|
3303
|
+
"GBR",
|
|
3304
|
+
"USA",
|
|
3305
|
+
"UMI",
|
|
3306
|
+
"URY",
|
|
3307
|
+
"UZB",
|
|
3308
|
+
"VUT",
|
|
3309
|
+
"VEN",
|
|
3310
|
+
"VNM",
|
|
3311
|
+
"VGB",
|
|
3312
|
+
"VIR",
|
|
3313
|
+
"WLF",
|
|
3314
|
+
"ESH",
|
|
3315
|
+
"YEM",
|
|
3316
|
+
"ZMB",
|
|
3317
|
+
"ZWE"
|
|
3318
|
+
]);
|
|
3319
|
+
var NUMERIC_REGEX = /^\d{3}$/;
|
|
3320
|
+
function parseAndValidateCountry(raw, opts) {
|
|
3321
|
+
const trimmed = raw.trim().toUpperCase();
|
|
3322
|
+
const format = opts.format ?? "both";
|
|
3323
|
+
if (trimmed.length === 0) {
|
|
3324
|
+
return { success: false, error: "Country code must not be empty" };
|
|
3325
|
+
}
|
|
3326
|
+
if (opts.allowNumeric === true && NUMERIC_REGEX.test(trimmed)) {
|
|
3327
|
+
return { success: true, value: trimmed };
|
|
3328
|
+
}
|
|
3329
|
+
if (trimmed.length === 2 && (format === "alpha2" || format === "both")) {
|
|
3330
|
+
if (ALPHA2_CODES.has(trimmed)) {
|
|
3331
|
+
return { success: true, value: trimmed };
|
|
3332
|
+
}
|
|
3333
|
+
return {
|
|
3334
|
+
success: false,
|
|
3335
|
+
error: `"${trimmed}" is not a valid ISO 3166-1 alpha-2 country code`
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
if (trimmed.length === 3 && (format === "alpha3" || format === "both")) {
|
|
3339
|
+
if (ALPHA3_CODES.has(trimmed)) {
|
|
3340
|
+
return { success: true, value: trimmed };
|
|
3341
|
+
}
|
|
3342
|
+
return {
|
|
3343
|
+
success: false,
|
|
3344
|
+
error: `"${trimmed}" is not a valid ISO 3166-1 alpha-3 country code`
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
return {
|
|
3348
|
+
success: false,
|
|
3349
|
+
error: `"${trimmed}" is not a valid country code. Expected ISO 3166-1 alpha-2 (e.g., "US") or alpha-3 (e.g., "USA")`
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
function createCountrySchema(opts) {
|
|
3353
|
+
const options = opts ?? {};
|
|
3354
|
+
const parser = (raw) => parseAndValidateCountry(raw, options);
|
|
3355
|
+
return new SchemaBuilder(parser, "country");
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
// src/schema/validators/currency.ts
|
|
3359
|
+
var CURRENCY_CODES = /* @__PURE__ */ new Set([
|
|
3360
|
+
"AED",
|
|
3361
|
+
"AFN",
|
|
3362
|
+
"ALL",
|
|
3363
|
+
"AMD",
|
|
3364
|
+
"ANG",
|
|
3365
|
+
"AOA",
|
|
3366
|
+
"ARS",
|
|
3367
|
+
"AUD",
|
|
3368
|
+
"AWG",
|
|
3369
|
+
"AZN",
|
|
3370
|
+
"BAM",
|
|
3371
|
+
"BBD",
|
|
3372
|
+
"BDT",
|
|
3373
|
+
"BGN",
|
|
3374
|
+
"BHD",
|
|
3375
|
+
"BIF",
|
|
3376
|
+
"BMD",
|
|
3377
|
+
"BND",
|
|
3378
|
+
"BOB",
|
|
3379
|
+
"BOV",
|
|
3380
|
+
"BRL",
|
|
3381
|
+
"BSD",
|
|
3382
|
+
"BTN",
|
|
3383
|
+
"BWP",
|
|
3384
|
+
"BYN",
|
|
3385
|
+
"BZD",
|
|
3386
|
+
"CAD",
|
|
3387
|
+
"CDF",
|
|
3388
|
+
"CHE",
|
|
3389
|
+
"CHF",
|
|
3390
|
+
"CHW",
|
|
3391
|
+
"CLF",
|
|
3392
|
+
"CLP",
|
|
3393
|
+
"CNY",
|
|
3394
|
+
"COP",
|
|
3395
|
+
"COU",
|
|
3396
|
+
"CRC",
|
|
3397
|
+
"CUC",
|
|
3398
|
+
"CUP",
|
|
3399
|
+
"CVE",
|
|
3400
|
+
"CZK",
|
|
3401
|
+
"DJF",
|
|
3402
|
+
"DKK",
|
|
3403
|
+
"DOP",
|
|
3404
|
+
"DZD",
|
|
3405
|
+
"EGP",
|
|
3406
|
+
"ERN",
|
|
3407
|
+
"ETB",
|
|
3408
|
+
"EUR",
|
|
3409
|
+
"FJD",
|
|
3410
|
+
"FKP",
|
|
3411
|
+
"GBP",
|
|
3412
|
+
"GEL",
|
|
3413
|
+
"GHS",
|
|
3414
|
+
"GIP",
|
|
3415
|
+
"GMD",
|
|
3416
|
+
"GNF",
|
|
3417
|
+
"GTQ",
|
|
3418
|
+
"GYD",
|
|
3419
|
+
"HKD",
|
|
3420
|
+
"HNL",
|
|
3421
|
+
"HRK",
|
|
3422
|
+
"HTG",
|
|
3423
|
+
"HUF",
|
|
3424
|
+
"IDR",
|
|
3425
|
+
"ILS",
|
|
3426
|
+
"INR",
|
|
3427
|
+
"IQD",
|
|
3428
|
+
"IRR",
|
|
3429
|
+
"ISK",
|
|
3430
|
+
"JMD",
|
|
3431
|
+
"JOD",
|
|
3432
|
+
"JPY",
|
|
3433
|
+
"KES",
|
|
3434
|
+
"KGS",
|
|
3435
|
+
"KHR",
|
|
3436
|
+
"KMF",
|
|
3437
|
+
"KPW",
|
|
3438
|
+
"KRW",
|
|
3439
|
+
"KWD",
|
|
3440
|
+
"KYD",
|
|
3441
|
+
"KZT",
|
|
3442
|
+
"LAK",
|
|
3443
|
+
"LBP",
|
|
3444
|
+
"LKR",
|
|
3445
|
+
"LRD",
|
|
3446
|
+
"LSL",
|
|
3447
|
+
"LYD",
|
|
3448
|
+
"MAD",
|
|
3449
|
+
"MDL",
|
|
3450
|
+
"MGA",
|
|
3451
|
+
"MKD",
|
|
3452
|
+
"MMK",
|
|
3453
|
+
"MNT",
|
|
3454
|
+
"MOP",
|
|
3455
|
+
"MRU",
|
|
3456
|
+
"MUR",
|
|
3457
|
+
"MVR",
|
|
3458
|
+
"MWK",
|
|
3459
|
+
"MXN",
|
|
3460
|
+
"MXV",
|
|
3461
|
+
"MYR",
|
|
3462
|
+
"MZN",
|
|
3463
|
+
"NAD",
|
|
3464
|
+
"NGN",
|
|
3465
|
+
"NIO",
|
|
3466
|
+
"NOK",
|
|
3467
|
+
"NPR",
|
|
3468
|
+
"NZD",
|
|
3469
|
+
"OMR",
|
|
3470
|
+
"PAB",
|
|
3471
|
+
"PEN",
|
|
3472
|
+
"PGK",
|
|
3473
|
+
"PHP",
|
|
3474
|
+
"PKR",
|
|
3475
|
+
"PLN",
|
|
3476
|
+
"PYG",
|
|
3477
|
+
"QAR",
|
|
3478
|
+
"RON",
|
|
3479
|
+
"RSD",
|
|
3480
|
+
"RUB",
|
|
3481
|
+
"RWF",
|
|
3482
|
+
"SAR",
|
|
3483
|
+
"SBD",
|
|
3484
|
+
"SCR",
|
|
3485
|
+
"SDG",
|
|
3486
|
+
"SEK",
|
|
3487
|
+
"SGD",
|
|
3488
|
+
"SHP",
|
|
3489
|
+
"SLE",
|
|
3490
|
+
"SLL",
|
|
3491
|
+
"SOS",
|
|
3492
|
+
"SRD",
|
|
3493
|
+
"SSP",
|
|
3494
|
+
"STN",
|
|
3495
|
+
"SVC",
|
|
3496
|
+
"SYP",
|
|
3497
|
+
"SZL",
|
|
3498
|
+
"THB",
|
|
3499
|
+
"TJS",
|
|
3500
|
+
"TMT",
|
|
3501
|
+
"TND",
|
|
3502
|
+
"TOP",
|
|
3503
|
+
"TRY",
|
|
3504
|
+
"TTD",
|
|
3505
|
+
"TWD",
|
|
3506
|
+
"TZS",
|
|
3507
|
+
"UAH",
|
|
3508
|
+
"UGX",
|
|
3509
|
+
"USD",
|
|
3510
|
+
"USN",
|
|
3511
|
+
"UYI",
|
|
3512
|
+
"UYU",
|
|
3513
|
+
"UYW",
|
|
3514
|
+
"UZS",
|
|
3515
|
+
"VED",
|
|
3516
|
+
"VES",
|
|
3517
|
+
"VND",
|
|
3518
|
+
"VUV",
|
|
3519
|
+
"WST",
|
|
3520
|
+
"XAF",
|
|
3521
|
+
"XAG",
|
|
3522
|
+
"XAU",
|
|
3523
|
+
"XBA",
|
|
3524
|
+
"XBB",
|
|
3525
|
+
"XBC",
|
|
3526
|
+
"XBD",
|
|
3527
|
+
"XCD",
|
|
3528
|
+
"XDR",
|
|
3529
|
+
"XOF",
|
|
3530
|
+
"XPD",
|
|
3531
|
+
"XPF",
|
|
3532
|
+
"XPT",
|
|
3533
|
+
"XSU",
|
|
3534
|
+
"XTS",
|
|
3535
|
+
"XUA",
|
|
3536
|
+
"XXX",
|
|
3537
|
+
"YER",
|
|
3538
|
+
"ZAR",
|
|
3539
|
+
"ZMW",
|
|
3540
|
+
"ZWL",
|
|
3541
|
+
// Historic / commonly used
|
|
3542
|
+
"BEF",
|
|
3543
|
+
"BYR",
|
|
3544
|
+
"CSD",
|
|
3545
|
+
"EEK",
|
|
3546
|
+
"FIM",
|
|
3547
|
+
"GRD",
|
|
3548
|
+
"IEP",
|
|
3549
|
+
"ITL",
|
|
3550
|
+
"LTL",
|
|
3551
|
+
"LVL",
|
|
3552
|
+
"MRO",
|
|
3553
|
+
"NLG",
|
|
3554
|
+
"PTE",
|
|
3555
|
+
"ROL",
|
|
3556
|
+
"RUR",
|
|
3557
|
+
"SKK",
|
|
3558
|
+
"SRG",
|
|
3559
|
+
"STD",
|
|
3560
|
+
"VEF",
|
|
3561
|
+
"VEB",
|
|
3562
|
+
"YUM"
|
|
3563
|
+
]);
|
|
3564
|
+
var CRYPTO_CODES = /* @__PURE__ */ new Set([
|
|
3565
|
+
"BTC",
|
|
3566
|
+
"ETH",
|
|
3567
|
+
"LTC",
|
|
3568
|
+
"BCH",
|
|
3569
|
+
"XRP",
|
|
3570
|
+
"DASH",
|
|
3571
|
+
"NEO",
|
|
3572
|
+
"EOS",
|
|
3573
|
+
"XLM",
|
|
3574
|
+
"ADA",
|
|
3575
|
+
"XTZ",
|
|
3576
|
+
"XMR",
|
|
3577
|
+
"LINK",
|
|
3578
|
+
"DOT",
|
|
3579
|
+
"YFI",
|
|
3580
|
+
"UNI",
|
|
3581
|
+
"SOL",
|
|
3582
|
+
"MATIC",
|
|
3583
|
+
"AVAX",
|
|
3584
|
+
"SHIB",
|
|
3585
|
+
"DOGE",
|
|
3586
|
+
"USDT",
|
|
3587
|
+
"USDC",
|
|
3588
|
+
"BNB",
|
|
3589
|
+
"FTM",
|
|
3590
|
+
"ATOM",
|
|
3591
|
+
"ALGO",
|
|
3592
|
+
"VET",
|
|
3593
|
+
"ICP",
|
|
3594
|
+
"FIL",
|
|
3595
|
+
"TRX",
|
|
3596
|
+
"ETC",
|
|
3597
|
+
"HBAR",
|
|
3598
|
+
"THETA",
|
|
3599
|
+
"AXS",
|
|
3600
|
+
"MANA",
|
|
3601
|
+
"SAND",
|
|
3602
|
+
"APE",
|
|
3603
|
+
"GRT",
|
|
3604
|
+
"FTT"
|
|
3605
|
+
]);
|
|
3606
|
+
var CURRENCY_REGEX = /^[A-Z]{3}$/;
|
|
3607
|
+
function parseAndValidateCurrency(raw, opts) {
|
|
3608
|
+
const trimmed = raw.trim().toUpperCase();
|
|
3609
|
+
if (trimmed.length === 0) {
|
|
3610
|
+
return { success: false, error: "Currency code must not be empty" };
|
|
3611
|
+
}
|
|
3612
|
+
if (!CURRENCY_REGEX.test(trimmed)) {
|
|
3613
|
+
return {
|
|
3614
|
+
success: false,
|
|
3615
|
+
error: `Currency code must be exactly 3 uppercase letters, got "${trimmed}"`
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
const allowCrypto = opts.allowCrypto ?? false;
|
|
3619
|
+
if (CURRENCY_CODES.has(trimmed)) {
|
|
3620
|
+
return { success: true, value: trimmed };
|
|
3621
|
+
}
|
|
3622
|
+
if (allowCrypto && CRYPTO_CODES.has(trimmed)) {
|
|
3623
|
+
return { success: true, value: trimmed };
|
|
3624
|
+
}
|
|
3625
|
+
return {
|
|
3626
|
+
success: false,
|
|
3627
|
+
error: `"${trimmed}" is not a valid ISO 4217 currency code`
|
|
3628
|
+
};
|
|
3629
|
+
}
|
|
3630
|
+
function createCurrencySchema(opts) {
|
|
3631
|
+
const options = opts ?? {};
|
|
3632
|
+
const parser = (raw) => parseAndValidateCurrency(raw, options);
|
|
3633
|
+
return new SchemaBuilder(parser, "currency");
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
// src/schema/modifiers/required.ts
|
|
3637
|
+
function applyRequired(builder) {
|
|
3638
|
+
void builder;
|
|
3639
|
+
return builder;
|
|
3640
|
+
}
|
|
3641
|
+
function isRequired(builder) {
|
|
3642
|
+
return !builder.meta.required ? false : builder.meta.required;
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
// src/schema/modifiers/optional.ts
|
|
3646
|
+
function applyOptional(builder) {
|
|
3647
|
+
void builder;
|
|
3648
|
+
return builder;
|
|
3649
|
+
}
|
|
3650
|
+
function isOptional(builder) {
|
|
3651
|
+
return builder.isOptional;
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
// src/schema/modifiers/default.ts
|
|
3655
|
+
function applyDefault(builder, defaultValue) {
|
|
3656
|
+
void builder;
|
|
3657
|
+
void defaultValue;
|
|
3658
|
+
return builder;
|
|
3659
|
+
}
|
|
3660
|
+
function hasDefault(builder) {
|
|
3661
|
+
return builder.meta.hasDefault;
|
|
3662
|
+
}
|
|
3663
|
+
function getDefaultValue(builder) {
|
|
3664
|
+
return builder.getDefaultValue();
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// src/schema/modifiers/secret.ts
|
|
3668
|
+
function applySecret(builder) {
|
|
3669
|
+
void builder;
|
|
3670
|
+
return builder;
|
|
3671
|
+
}
|
|
3672
|
+
function isSecret(builder) {
|
|
3673
|
+
return builder.meta.isSecret;
|
|
3674
|
+
}
|
|
3675
|
+
function maskValue(value, builder) {
|
|
3676
|
+
if (!builder.meta.isSecret) {
|
|
3677
|
+
return String(value);
|
|
3678
|
+
}
|
|
3679
|
+
const str = String(value);
|
|
3680
|
+
if (str.length <= 4) {
|
|
3681
|
+
return "****";
|
|
3682
|
+
}
|
|
3683
|
+
return str.slice(0, 4) + "*".repeat(Math.min(str.length - 4, 20));
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3686
|
+
// src/schema/modifiers/deprecated.ts
|
|
3687
|
+
function applyDeprecated(builder, message) {
|
|
3688
|
+
void builder;
|
|
3689
|
+
void message;
|
|
3690
|
+
return builder;
|
|
3691
|
+
}
|
|
3692
|
+
function isDeprecated(builder) {
|
|
3693
|
+
return builder.meta.isDeprecated;
|
|
3694
|
+
}
|
|
3695
|
+
function getDeprecationMessage(builder) {
|
|
3696
|
+
return builder.meta.deprecationMessage;
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3699
|
+
// src/schema/modifiers/alias.ts
|
|
3700
|
+
function applyAlias(builder, ...names) {
|
|
3701
|
+
void builder;
|
|
3702
|
+
void names;
|
|
3703
|
+
return builder;
|
|
3704
|
+
}
|
|
3705
|
+
function getAliases(builder) {
|
|
3706
|
+
return builder.meta.aliases;
|
|
3707
|
+
}
|
|
3708
|
+
function isAlias(builder, name) {
|
|
3709
|
+
return builder.meta.aliases.includes(name);
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
// src/schema/modifiers/transform.ts
|
|
3713
|
+
function applyTransform(builder, fn) {
|
|
3714
|
+
void builder;
|
|
3715
|
+
void fn;
|
|
3716
|
+
return builder;
|
|
3717
|
+
}
|
|
3718
|
+
function composeTransforms(...fns) {
|
|
3719
|
+
return (value) => {
|
|
3720
|
+
let result = value;
|
|
3721
|
+
for (const fn of fns) {
|
|
3722
|
+
result = fn(result);
|
|
3723
|
+
}
|
|
3724
|
+
return result;
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
// src/schema/modifiers/custom.ts
|
|
3729
|
+
function applyCustom(builder, fn) {
|
|
3730
|
+
void builder;
|
|
3731
|
+
void fn;
|
|
3732
|
+
return builder;
|
|
3733
|
+
}
|
|
3734
|
+
function minLength(n) {
|
|
3735
|
+
return (value) => {
|
|
3736
|
+
if (value.length < n) {
|
|
3737
|
+
return `Value must be at least ${n} characters, got ${value.length}`;
|
|
3738
|
+
}
|
|
3739
|
+
return null;
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
function maxLength(n) {
|
|
3743
|
+
return (value) => {
|
|
3744
|
+
if (value.length > n) {
|
|
3745
|
+
return `Value must be at most ${n} characters, got ${value.length}`;
|
|
3746
|
+
}
|
|
3747
|
+
return null;
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3750
|
+
function check(condition, message) {
|
|
3751
|
+
return (value) => {
|
|
3752
|
+
if (!condition(value)) {
|
|
3753
|
+
return message;
|
|
3754
|
+
}
|
|
3755
|
+
return null;
|
|
3756
|
+
};
|
|
3757
|
+
}
|
|
3758
|
+
function allOf(...validators) {
|
|
3759
|
+
return (value) => {
|
|
3760
|
+
for (const validator of validators) {
|
|
3761
|
+
const error = validator(value);
|
|
3762
|
+
if (error !== null) {
|
|
3763
|
+
return error;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
return null;
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
// src/schema/modifiers/conditional.ts
|
|
3771
|
+
function applyConditional(builder, check2) {
|
|
3772
|
+
void builder;
|
|
3773
|
+
void check2;
|
|
3774
|
+
return builder;
|
|
3775
|
+
}
|
|
3776
|
+
function hasConditional(builder) {
|
|
3777
|
+
return builder.meta.conditional !== void 0;
|
|
3778
|
+
}
|
|
3779
|
+
function shouldApply(builder, env) {
|
|
3780
|
+
const conditional = builder.meta.conditional;
|
|
3781
|
+
if (conditional === void 0) {
|
|
3782
|
+
return true;
|
|
3783
|
+
}
|
|
3784
|
+
try {
|
|
3785
|
+
return conditional.check(env);
|
|
3786
|
+
} catch {
|
|
3787
|
+
return true;
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
// src/schema/modifiers/description.ts
|
|
3792
|
+
function applyDescription(builder, desc) {
|
|
3793
|
+
void builder;
|
|
3794
|
+
void desc;
|
|
3795
|
+
return builder;
|
|
3796
|
+
}
|
|
3797
|
+
function getDescription(builder) {
|
|
3798
|
+
return builder.meta.description;
|
|
3799
|
+
}
|
|
3800
|
+
function formatSchemaDescription(builder, fieldName) {
|
|
3801
|
+
const lines = [];
|
|
3802
|
+
lines.push(`${fieldName} (${builder.typeName})`);
|
|
3803
|
+
if (builder.meta.description) {
|
|
3804
|
+
lines.push(` ${builder.meta.description}`);
|
|
3805
|
+
}
|
|
3806
|
+
if (builder.meta.isSecret) {
|
|
3807
|
+
lines.push(" [SECRET] - This value will be masked in logs");
|
|
3808
|
+
}
|
|
3809
|
+
if (builder.meta.isDeprecated) {
|
|
3810
|
+
const msg = builder.meta.deprecationMessage ?? "This variable is deprecated";
|
|
3811
|
+
lines.push(` [DEPRECATED] - ${msg}`);
|
|
3812
|
+
}
|
|
3813
|
+
if (builder.meta.aliases.length > 0) {
|
|
3814
|
+
lines.push(` Aliases: ${builder.meta.aliases.join(", ")}`);
|
|
3815
|
+
}
|
|
3816
|
+
if (builder.meta.hasDefault) {
|
|
3817
|
+
lines.push(` Default: ${builder.meta.rawDefaultValue ?? "(complex)"}`);
|
|
3818
|
+
}
|
|
3819
|
+
return lines.join("\n");
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
// src/schema/index.ts
|
|
3823
|
+
var t = {
|
|
3824
|
+
string: () => createStringSchema(),
|
|
3825
|
+
number: () => createNumberSchema(),
|
|
3826
|
+
boolean: () => createBooleanSchema(),
|
|
3827
|
+
enum: (values) => createEnumSchema(values),
|
|
3828
|
+
array: () => createArraySchema(),
|
|
3829
|
+
json: () => createJsonSchema(),
|
|
3830
|
+
date: () => createDateSchema(),
|
|
3831
|
+
regex: () => createRegexSchema(),
|
|
3832
|
+
bigint: () => createBigIntSchema(),
|
|
3833
|
+
url: (opts) => createUrlSchema(opts),
|
|
3834
|
+
email: (opts) => createEmailSchema(opts),
|
|
3835
|
+
ip: (opts) => createIpSchema(opts),
|
|
3836
|
+
ipv4: (opts) => createIpv4Schema(opts),
|
|
3837
|
+
ipv6: (opts) => createIpv6Schema(opts),
|
|
3838
|
+
hostname: (opts) => createHostnameSchema(opts),
|
|
3839
|
+
port: (opts) => createPortSchema(opts),
|
|
3840
|
+
path: (opts) => createPathSchema(opts),
|
|
3841
|
+
uuid: (opts) => createUuidSchema(opts),
|
|
3842
|
+
hex: (opts) => createHexSchema(opts),
|
|
3843
|
+
base64: (opts) => createBase64Schema(opts),
|
|
3844
|
+
semver: (opts) => createSemverSchema(opts),
|
|
3845
|
+
cron: (opts) => createCronSchema(opts),
|
|
3846
|
+
duration: (opts) => createDurationSchema(opts),
|
|
3847
|
+
bytes: (opts) => createBytesSchema(opts),
|
|
3848
|
+
color: (opts) => createColorSchema(opts),
|
|
3849
|
+
locale: (opts) => createLocaleSchema(opts),
|
|
3850
|
+
timezone: (opts) => createTimezoneSchema(opts),
|
|
3851
|
+
country: (opts) => createCountrySchema(opts),
|
|
3852
|
+
currency: (opts) => createCurrencySchema(opts)
|
|
3853
|
+
};
|
|
3854
|
+
function defineEnv(schema, vars, options) {
|
|
3855
|
+
const envVars = vars ?? getProcessEnv();
|
|
3856
|
+
const result = validate(envVars, schema, options);
|
|
3857
|
+
for (const warning of result.warnings) {
|
|
3858
|
+
process.stderr.write(`[ultraenv] Warning: ${warning.field}: ${warning.message}
|
|
3859
|
+
`);
|
|
3860
|
+
}
|
|
3861
|
+
if (!result.valid) {
|
|
3862
|
+
const messages = result.errors.map((e) => ` - ${e.field}: ${e.message}`).join("\n");
|
|
3863
|
+
const unknownMsg = result.unknown.length > 0 ? `
|
|
3864
|
+
Unknown variables: ${result.unknown.join(", ")}` : "";
|
|
3865
|
+
throw new Error(
|
|
3866
|
+
`Environment validation failed:
|
|
3867
|
+
${messages}${unknownMsg}`
|
|
3868
|
+
);
|
|
3869
|
+
}
|
|
3870
|
+
return result.values;
|
|
3871
|
+
}
|
|
3872
|
+
function tryDefineEnv(schema, vars, options) {
|
|
3873
|
+
const envVars = vars ?? getProcessEnv();
|
|
3874
|
+
const result = validate(envVars, schema, options);
|
|
3875
|
+
if (result.valid) {
|
|
3876
|
+
return {
|
|
3877
|
+
valid: true,
|
|
3878
|
+
values: result.values,
|
|
3879
|
+
warnings: result.warnings
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
return {
|
|
3883
|
+
valid: false,
|
|
3884
|
+
values: result.values,
|
|
3885
|
+
warnings: result.warnings,
|
|
3886
|
+
errors: result.errors,
|
|
3887
|
+
unknown: result.unknown
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
function getProcessEnv() {
|
|
3891
|
+
const result = {};
|
|
3892
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
3893
|
+
if (value !== void 0) {
|
|
3894
|
+
result[key] = value;
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
return result;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
export {
|
|
3901
|
+
SchemaBuilder,
|
|
3902
|
+
validate,
|
|
3903
|
+
validateValue,
|
|
3904
|
+
formatValidationResult,
|
|
3905
|
+
StringSchemaBuilder,
|
|
3906
|
+
createStringSchema,
|
|
3907
|
+
NumberSchemaBuilder,
|
|
3908
|
+
createNumberSchema,
|
|
3909
|
+
BooleanSchemaBuilder,
|
|
3910
|
+
createBooleanSchema,
|
|
3911
|
+
EnumSchemaBuilder,
|
|
3912
|
+
createEnumSchema,
|
|
3913
|
+
ArraySchemaBuilder,
|
|
3914
|
+
createArraySchema,
|
|
3915
|
+
JsonSchemaBuilder,
|
|
3916
|
+
createJsonSchema,
|
|
3917
|
+
DateSchemaBuilder,
|
|
3918
|
+
createDateSchema,
|
|
3919
|
+
RegexSchemaBuilder,
|
|
3920
|
+
createRegexSchema,
|
|
3921
|
+
BigIntSchemaBuilder,
|
|
3922
|
+
createBigIntSchema,
|
|
3923
|
+
createUrlSchema,
|
|
3924
|
+
createEmailSchema,
|
|
3925
|
+
createIpSchema,
|
|
3926
|
+
createIpv4Schema,
|
|
3927
|
+
createIpv6Schema,
|
|
3928
|
+
createHostnameSchema,
|
|
3929
|
+
createPortSchema,
|
|
3930
|
+
createPathSchema,
|
|
3931
|
+
createUuidSchema,
|
|
3932
|
+
createHexSchema,
|
|
3933
|
+
createBase64Schema,
|
|
3934
|
+
createSemverSchema,
|
|
3935
|
+
createCronSchema,
|
|
3936
|
+
createDurationSchema,
|
|
3937
|
+
createBytesSchema,
|
|
3938
|
+
createColorSchema,
|
|
3939
|
+
createLocaleSchema,
|
|
3940
|
+
createTimezoneSchema,
|
|
3941
|
+
createCountrySchema,
|
|
3942
|
+
createCurrencySchema,
|
|
3943
|
+
applyRequired,
|
|
3944
|
+
isRequired,
|
|
3945
|
+
applyOptional,
|
|
3946
|
+
isOptional,
|
|
3947
|
+
applyDefault,
|
|
3948
|
+
hasDefault,
|
|
3949
|
+
getDefaultValue,
|
|
3950
|
+
applySecret,
|
|
3951
|
+
isSecret,
|
|
3952
|
+
maskValue,
|
|
3953
|
+
applyDeprecated,
|
|
3954
|
+
isDeprecated,
|
|
3955
|
+
getDeprecationMessage,
|
|
3956
|
+
applyAlias,
|
|
3957
|
+
getAliases,
|
|
3958
|
+
isAlias,
|
|
3959
|
+
applyTransform,
|
|
3960
|
+
composeTransforms,
|
|
3961
|
+
applyCustom,
|
|
3962
|
+
minLength,
|
|
3963
|
+
maxLength,
|
|
3964
|
+
check,
|
|
3965
|
+
allOf,
|
|
3966
|
+
applyConditional,
|
|
3967
|
+
hasConditional,
|
|
3968
|
+
shouldApply,
|
|
3969
|
+
applyDescription,
|
|
3970
|
+
getDescription,
|
|
3971
|
+
formatSchemaDescription,
|
|
3972
|
+
t,
|
|
3973
|
+
defineEnv,
|
|
3974
|
+
tryDefineEnv
|
|
3975
|
+
};
|