decoders 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +98 -84
- package/dist/index.d.cts +44 -33
- package/dist/index.d.ts +44 -33
- package/dist/index.js +98 -84
- package/package.json +21 -19
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/decoders)
|
|
4
4
|
[](https://github.com/nvie/decoders/actions)
|
|
5
|
-
[](https://pkg-size.dev/decoders)
|
|
6
6
|
|
|
7
7
|
Elegant and battle-tested validation library for type-safe input data for
|
|
8
8
|
[TypeScript](https://www.typescriptlang.org/).
|
package/dist/index.cjs
CHANGED
|
@@ -29,18 +29,12 @@ function makeObjectAnn(fields, text) {
|
|
|
29
29
|
function makeArrayAnn(items, text) {
|
|
30
30
|
return brand({ type: "array", items, text });
|
|
31
31
|
}
|
|
32
|
-
function
|
|
33
|
-
return brand({ type: "
|
|
34
|
-
}
|
|
35
|
-
function makeUnknownAnn(value, text) {
|
|
36
|
-
return brand({ type: "unknown", value, text });
|
|
32
|
+
function makeOpaqueAnn(value, text) {
|
|
33
|
+
return brand({ type: "opaque", value, text });
|
|
37
34
|
}
|
|
38
35
|
function makeScalarAnn(value, text) {
|
|
39
36
|
return brand({ type: "scalar", value, text });
|
|
40
37
|
}
|
|
41
|
-
function makeCircularRefAnn(text) {
|
|
42
|
-
return brand({ type: "circular-ref", text });
|
|
43
|
-
}
|
|
44
38
|
function updateText(annotation, text) {
|
|
45
39
|
if (text !== void 0) {
|
|
46
40
|
return brand({ ...annotation, text });
|
|
@@ -66,7 +60,8 @@ function annotateArray(arr, text, seen) {
|
|
|
66
60
|
function annotateObject(obj, text, seen) {
|
|
67
61
|
seen.add(obj);
|
|
68
62
|
const fields = /* @__PURE__ */ new Map();
|
|
69
|
-
for (const
|
|
63
|
+
for (const key of Object.keys(obj)) {
|
|
64
|
+
const value = obj[key];
|
|
70
65
|
fields.set(key, annotate(value, void 0, seen));
|
|
71
66
|
}
|
|
72
67
|
return makeObjectAnn(fields, text);
|
|
@@ -80,22 +75,22 @@ function annotate(value, text, seen) {
|
|
|
80
75
|
}
|
|
81
76
|
if (Array.isArray(value)) {
|
|
82
77
|
if (seen.has(value)) {
|
|
83
|
-
return
|
|
78
|
+
return makeOpaqueAnn("<circular ref>", text);
|
|
84
79
|
} else {
|
|
85
80
|
return annotateArray(value, text, seen);
|
|
86
81
|
}
|
|
87
82
|
}
|
|
88
83
|
if (isPojo(value)) {
|
|
89
84
|
if (seen.has(value)) {
|
|
90
|
-
return
|
|
85
|
+
return makeOpaqueAnn("<circular ref>", text);
|
|
91
86
|
} else {
|
|
92
87
|
return annotateObject(value, text, seen);
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
90
|
if (typeof value === "function") {
|
|
96
|
-
return
|
|
91
|
+
return makeOpaqueAnn("<function>", text);
|
|
97
92
|
}
|
|
98
|
-
return
|
|
93
|
+
return makeOpaqueAnn("???", text);
|
|
99
94
|
}
|
|
100
95
|
function public_annotate(value, text) {
|
|
101
96
|
return annotate(value, text, /* @__PURE__ */ new WeakSet());
|
|
@@ -116,6 +111,10 @@ function indent(s, prefix = INDENT) {
|
|
|
116
111
|
return `${prefix}${s}`;
|
|
117
112
|
}
|
|
118
113
|
}
|
|
114
|
+
var quotePattern = /'/g;
|
|
115
|
+
function quote(value) {
|
|
116
|
+
return typeof value === "string" ? "'" + value.replace(quotePattern, "\\'") + "'" : JSON.stringify(value);
|
|
117
|
+
}
|
|
119
118
|
|
|
120
119
|
// src/core/format.ts
|
|
121
120
|
function summarize(ann, keypath = []) {
|
|
@@ -144,9 +143,9 @@ function summarize(ann, keypath = []) {
|
|
|
144
143
|
if (keypath.length === 0) {
|
|
145
144
|
prefix = "";
|
|
146
145
|
} else if (keypath.length === 1) {
|
|
147
|
-
prefix = typeof keypath[0] === "number" ? `Value at index ${keypath[0]}: ` : `Value at key ${
|
|
146
|
+
prefix = typeof keypath[0] === "number" ? `Value at index ${keypath[0]}: ` : `Value at key ${quote(keypath[0])}: `;
|
|
148
147
|
} else {
|
|
149
|
-
prefix = `Value at keypath ${keypath.map(String).join(".")}: `;
|
|
148
|
+
prefix = `Value at keypath ${quote(keypath.map(String).join("."))}: `;
|
|
150
149
|
}
|
|
151
150
|
return [...result, `${prefix}${text}`];
|
|
152
151
|
}
|
|
@@ -202,7 +201,7 @@ function serializeValue(value) {
|
|
|
202
201
|
return "undefined";
|
|
203
202
|
} else {
|
|
204
203
|
if (isDate(value)) {
|
|
205
|
-
return `new Date(${
|
|
204
|
+
return `new Date(${quote(value.toISOString())})`;
|
|
206
205
|
} else if (value instanceof Date) {
|
|
207
206
|
return "(Invalid Date)";
|
|
208
207
|
} else {
|
|
@@ -216,14 +215,12 @@ function serializeAnnotation(ann, prefix = "") {
|
|
|
216
215
|
serialized = serializeArray(ann, prefix);
|
|
217
216
|
} else if (ann.type === "object") {
|
|
218
217
|
serialized = serializeObject(ann, prefix);
|
|
219
|
-
} else if (ann.type === "
|
|
220
|
-
serialized = "<function>";
|
|
221
|
-
} else if (ann.type === "circular-ref") {
|
|
222
|
-
serialized = "<circular ref>";
|
|
223
|
-
} else if (ann.type === "unknown") {
|
|
224
|
-
serialized = "???";
|
|
225
|
-
} else {
|
|
218
|
+
} else if (ann.type === "scalar") {
|
|
226
219
|
serialized = serializeValue(ann.value);
|
|
220
|
+
} else {
|
|
221
|
+
/* @__PURE__ */ ((_) => {
|
|
222
|
+
})(ann);
|
|
223
|
+
serialized = ann.value;
|
|
227
224
|
}
|
|
228
225
|
const text = ann.text;
|
|
229
226
|
if (text !== void 0) {
|
|
@@ -311,10 +308,15 @@ function define(fn) {
|
|
|
311
308
|
}
|
|
312
309
|
function then(next) {
|
|
313
310
|
return define((blob, ok2, err2) => {
|
|
314
|
-
const
|
|
315
|
-
|
|
311
|
+
const r1 = decode(blob);
|
|
312
|
+
if (!r1.ok) return r1;
|
|
313
|
+
const r2 = isDecoder(next) ? next : next(r1.value, ok2, err2);
|
|
314
|
+
return isDecoder(r2) ? r2.decode(r1.value) : r2;
|
|
316
315
|
});
|
|
317
316
|
}
|
|
317
|
+
function pipe(next) {
|
|
318
|
+
return then(next);
|
|
319
|
+
}
|
|
318
320
|
function reject(rejectFn) {
|
|
319
321
|
return then((blob, ok2, err2) => {
|
|
320
322
|
const errmsg = rejectFn(blob);
|
|
@@ -331,13 +333,7 @@ function define(fn) {
|
|
|
331
333
|
}
|
|
332
334
|
});
|
|
333
335
|
}
|
|
334
|
-
|
|
335
|
-
return define((blob, ok2, err2) => {
|
|
336
|
-
const result = decode(blob);
|
|
337
|
-
return result.ok ? next([blob, result.value], ok2, err2) : result;
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
return Object.freeze({
|
|
336
|
+
return brand2({
|
|
341
337
|
verify,
|
|
342
338
|
value,
|
|
343
339
|
decode,
|
|
@@ -346,9 +342,17 @@ function define(fn) {
|
|
|
346
342
|
reject,
|
|
347
343
|
describe,
|
|
348
344
|
then,
|
|
349
|
-
|
|
345
|
+
pipe
|
|
350
346
|
});
|
|
351
347
|
}
|
|
348
|
+
var _register2 = /* @__PURE__ */ new WeakSet();
|
|
349
|
+
function brand2(decoder) {
|
|
350
|
+
_register2.add(decoder);
|
|
351
|
+
return decoder;
|
|
352
|
+
}
|
|
353
|
+
function isDecoder(thing) {
|
|
354
|
+
return _register2.has(thing);
|
|
355
|
+
}
|
|
352
356
|
|
|
353
357
|
// src/arrays.ts
|
|
354
358
|
var poja = define((blob, ok2, err2) => {
|
|
@@ -489,7 +493,7 @@ function object(decoders) {
|
|
|
489
493
|
objAnn = merge(objAnn, errors);
|
|
490
494
|
}
|
|
491
495
|
if (missingKeys.size > 0) {
|
|
492
|
-
const errMsg = Array.from(missingKeys).map(
|
|
496
|
+
const errMsg = Array.from(missingKeys).map(quote).join(", ");
|
|
493
497
|
const pluralized = missingKeys.size > 1 ? "keys" : "key";
|
|
494
498
|
objAnn = updateText(objAnn, `Missing ${pluralized}: ${errMsg}`);
|
|
495
499
|
}
|
|
@@ -503,20 +507,16 @@ function exact(decoders) {
|
|
|
503
507
|
const checked = pojo.reject((plainObj) => {
|
|
504
508
|
const actualKeys = new Set(Object.keys(plainObj));
|
|
505
509
|
const extraKeys = difference(actualKeys, allowedKeys);
|
|
506
|
-
return extraKeys.size > 0 ? `Unexpected extra keys: ${Array.from(extraKeys).join(", ")}` :
|
|
507
|
-
// Don't reject
|
|
508
|
-
null
|
|
509
|
-
);
|
|
510
|
+
return extraKeys.size > 0 ? `Unexpected extra keys: ${Array.from(extraKeys).map(quote).join(", ")}` : null;
|
|
510
511
|
});
|
|
511
|
-
return checked.
|
|
512
|
+
return checked.pipe(object(decoders));
|
|
512
513
|
}
|
|
513
514
|
function inexact(decoders) {
|
|
514
|
-
return pojo.
|
|
515
|
+
return pojo.pipe((plainObj) => {
|
|
515
516
|
const allkeys = new Set(Object.keys(plainObj));
|
|
516
|
-
|
|
517
|
+
return object(decoders).transform((safepart) => {
|
|
517
518
|
const safekeys = new Set(Object.keys(decoders));
|
|
518
|
-
for (const k of safekeys)
|
|
519
|
-
allkeys.add(k);
|
|
519
|
+
for (const k of safekeys) allkeys.add(k);
|
|
520
520
|
const rv = {};
|
|
521
521
|
for (const k of allkeys) {
|
|
522
522
|
if (safekeys.has(k)) {
|
|
@@ -530,7 +530,6 @@ function inexact(decoders) {
|
|
|
530
530
|
}
|
|
531
531
|
return rv;
|
|
532
532
|
});
|
|
533
|
-
return decoder.decode(plainObj);
|
|
534
533
|
});
|
|
535
534
|
}
|
|
536
535
|
|
|
@@ -566,9 +565,7 @@ function oneOf(constants) {
|
|
|
566
565
|
if (winner !== void 0) {
|
|
567
566
|
return ok2(winner);
|
|
568
567
|
}
|
|
569
|
-
return err2(
|
|
570
|
-
`Must be one of ${constants.map((value) => JSON.stringify(value)).join(", ")}`
|
|
571
|
-
);
|
|
568
|
+
return err2(`Must be one of ${constants.map((value) => quote(value)).join(", ")}`);
|
|
572
569
|
});
|
|
573
570
|
}
|
|
574
571
|
function enum_(enumObj) {
|
|
@@ -594,9 +591,9 @@ function taggedUnion(field, mapping2) {
|
|
|
594
591
|
);
|
|
595
592
|
}
|
|
596
593
|
function select(scout, selectFn) {
|
|
597
|
-
return
|
|
598
|
-
const
|
|
599
|
-
return
|
|
594
|
+
return define((blob) => {
|
|
595
|
+
const result = scout.decode(blob);
|
|
596
|
+
return result.ok ? selectFn(result.value).decode(blob) : result;
|
|
600
597
|
});
|
|
601
598
|
}
|
|
602
599
|
|
|
@@ -627,9 +624,7 @@ function nullish(decoder, defaultValue) {
|
|
|
627
624
|
}
|
|
628
625
|
function constant(value) {
|
|
629
626
|
return define(
|
|
630
|
-
(blob, ok2, err2) => blob === value ? ok2(value) : err2(
|
|
631
|
-
`Must be ${typeof value === "symbol" ? String(value) : JSON.stringify(value)}`
|
|
632
|
-
)
|
|
627
|
+
(blob, ok2, err2) => blob === value ? ok2(value) : err2(`Must be ${typeof value === "symbol" ? String(value) : quote(value)}`)
|
|
633
628
|
);
|
|
634
629
|
}
|
|
635
630
|
function always(value) {
|
|
@@ -645,36 +640,11 @@ var hardcoded = always;
|
|
|
645
640
|
var unknown = define((blob, ok2, _) => ok2(blob));
|
|
646
641
|
var mixed = unknown;
|
|
647
642
|
|
|
648
|
-
// src/numbers.ts
|
|
649
|
-
var anyNumber = define(
|
|
650
|
-
(blob, ok2, err2) => isNumber(blob) ? ok2(blob) : err2("Must be number")
|
|
651
|
-
);
|
|
652
|
-
var number = anyNumber.refine(
|
|
653
|
-
(n) => Number.isFinite(n),
|
|
654
|
-
"Number must be finite"
|
|
655
|
-
);
|
|
656
|
-
var integer = number.refine(
|
|
657
|
-
(n) => Number.isInteger(n),
|
|
658
|
-
"Number must be an integer"
|
|
659
|
-
);
|
|
660
|
-
var positiveNumber = number.refine(
|
|
661
|
-
(n) => n >= 0 && !Object.is(n, -0),
|
|
662
|
-
"Number must be positive"
|
|
663
|
-
);
|
|
664
|
-
var positiveInteger = integer.refine(
|
|
665
|
-
(n) => n >= 0 && !Object.is(n, -0),
|
|
666
|
-
"Number must be positive"
|
|
667
|
-
);
|
|
668
|
-
var bigint = define(
|
|
669
|
-
(blob, ok2, err2) => isBigInt(blob) ? ok2(blob) : err2("Must be bigint")
|
|
670
|
-
);
|
|
671
|
-
|
|
672
643
|
// src/booleans.ts
|
|
673
644
|
var boolean = define((blob, ok2, err2) => {
|
|
674
645
|
return typeof blob === "boolean" ? ok2(blob) : err2("Must be boolean");
|
|
675
646
|
});
|
|
676
647
|
var truthy = define((blob, ok2, _) => ok2(!!blob));
|
|
677
|
-
var numericBoolean = number.transform((n) => !!n);
|
|
678
648
|
|
|
679
649
|
// src/collections.ts
|
|
680
650
|
function record(fst, snd) {
|
|
@@ -683,14 +653,12 @@ function record(fst, snd) {
|
|
|
683
653
|
return pojo.then((input, ok2, err2) => {
|
|
684
654
|
let rv = {};
|
|
685
655
|
const errors = /* @__PURE__ */ new Map();
|
|
686
|
-
for (const
|
|
656
|
+
for (const key of Object.keys(input)) {
|
|
657
|
+
const value = input[key];
|
|
687
658
|
const keyResult = _optionalChain([keyDecoder, 'optionalAccess', _2 => _2.decode, 'call', _3 => _3(key)]);
|
|
688
659
|
if (_optionalChain([keyResult, 'optionalAccess', _4 => _4.ok]) === false) {
|
|
689
660
|
return err2(
|
|
690
|
-
public_annotate(
|
|
691
|
-
input,
|
|
692
|
-
`Invalid key ${JSON.stringify(key)}: ${formatShort(keyResult.error)}`
|
|
693
|
-
)
|
|
661
|
+
public_annotate(input, `Invalid key ${quote(key)}: ${formatShort(keyResult.error)}`)
|
|
694
662
|
);
|
|
695
663
|
}
|
|
696
664
|
const k = _nullishCoalesce(_optionalChain([keyResult, 'optionalAccess', _5 => _5.value]), () => ( key));
|
|
@@ -720,6 +688,18 @@ function mapping(decoder) {
|
|
|
720
688
|
return record(decoder).transform((obj) => new Map(Object.entries(obj)));
|
|
721
689
|
}
|
|
722
690
|
|
|
691
|
+
// src/lib/size-options.ts
|
|
692
|
+
function bySizeOptions(options) {
|
|
693
|
+
const size = _optionalChain([options, 'optionalAccess', _6 => _6.size]);
|
|
694
|
+
const min = _nullishCoalesce(size, () => ( _optionalChain([options, 'optionalAccess', _7 => _7.min])));
|
|
695
|
+
const max = _nullishCoalesce(size, () => ( _optionalChain([options, 'optionalAccess', _8 => _8.max])));
|
|
696
|
+
const atLeast = min === max ? "" : "at least ";
|
|
697
|
+
const atMost = min === max ? "" : "at most ";
|
|
698
|
+
const tooShort = min !== void 0 && `Too short, must be ${atLeast}${min} chars`;
|
|
699
|
+
const tooLong = max !== void 0 && `Too long, must be ${atMost}${max} chars`;
|
|
700
|
+
return tooShort && tooLong ? (s) => s.length < min ? tooShort : s.length > max ? tooLong : null : tooShort ? (s) => s.length < min ? tooShort : null : tooLong ? (s) => s.length > max ? tooLong : null : () => null;
|
|
701
|
+
}
|
|
702
|
+
|
|
723
703
|
// src/strings.ts
|
|
724
704
|
var url_re = /^([A-Za-z]{3,9}(?:[+][A-Za-z]{3,9})?):\/\/(?:([-;:&=+$,\w]+)@)?(?:([A-Za-z0-9.-]+)(?::([0-9]{2,5}))?)(\/(?:[-+~%/.,\w]*)?(?:\?[-+=&;%@.,/\w]*)?(?:#[.,!/\w]*)?)?$/;
|
|
725
705
|
var string = define(
|
|
@@ -742,6 +722,15 @@ var httpsUrl = url.refine(
|
|
|
742
722
|
(value) => value.protocol === "https:",
|
|
743
723
|
"Must be an HTTPS URL"
|
|
744
724
|
);
|
|
725
|
+
var identifier = regex(
|
|
726
|
+
/^[a-z_][a-z0-9_]*$/i,
|
|
727
|
+
"Must be valid identifier"
|
|
728
|
+
);
|
|
729
|
+
function nanoid(options) {
|
|
730
|
+
return regex(/^[a-z0-9_-]+$/i, "Must be nano ID").reject(
|
|
731
|
+
bySizeOptions(_nullishCoalesce(options, () => ( { size: 21 })))
|
|
732
|
+
);
|
|
733
|
+
}
|
|
745
734
|
var uuid = regex(
|
|
746
735
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
747
736
|
"Must be uuid"
|
|
@@ -781,6 +770,30 @@ var iso8601 = (
|
|
|
781
770
|
);
|
|
782
771
|
var datelike = either(date, iso8601).describe("Must be a Date or date string");
|
|
783
772
|
|
|
773
|
+
// src/numbers.ts
|
|
774
|
+
var anyNumber = define(
|
|
775
|
+
(blob, ok2, err2) => isNumber(blob) ? ok2(blob) : err2("Must be number")
|
|
776
|
+
);
|
|
777
|
+
var number = anyNumber.refine(
|
|
778
|
+
(n) => Number.isFinite(n),
|
|
779
|
+
"Number must be finite"
|
|
780
|
+
);
|
|
781
|
+
var integer = number.refine(
|
|
782
|
+
(n) => Number.isInteger(n),
|
|
783
|
+
"Number must be an integer"
|
|
784
|
+
);
|
|
785
|
+
var positiveNumber = number.refine(
|
|
786
|
+
(n) => n >= 0 && !Object.is(n, -0),
|
|
787
|
+
"Number must be positive"
|
|
788
|
+
);
|
|
789
|
+
var positiveInteger = integer.refine(
|
|
790
|
+
(n) => n >= 0 && !Object.is(n, -0),
|
|
791
|
+
"Number must be positive"
|
|
792
|
+
);
|
|
793
|
+
var bigint = define(
|
|
794
|
+
(blob, ok2, err2) => isBigInt(blob) ? ok2(blob) : err2("Must be bigint")
|
|
795
|
+
);
|
|
796
|
+
|
|
784
797
|
// src/json.ts
|
|
785
798
|
var jsonObject = lazy(() => record(json));
|
|
786
799
|
var jsonArray = lazy(() => array(json));
|
|
@@ -859,4 +872,5 @@ var json = either(
|
|
|
859
872
|
|
|
860
873
|
|
|
861
874
|
|
|
862
|
-
|
|
875
|
+
|
|
876
|
+
exports.always = always; exports.anyNumber = anyNumber; exports.array = array; exports.bigint = bigint; exports.boolean = boolean; exports.constant = constant; exports.date = date; exports.datelike = datelike; exports.decimal = decimal; exports.define = define; exports.dict = dict; exports.either = either; exports.email = email; exports.enum_ = enum_; exports.err = err; exports.exact = exact; exports.fail = fail; exports.formatInline = formatInline; exports.formatShort = formatShort; exports.hardcoded = hardcoded; exports.hexadecimal = hexadecimal; exports.httpsUrl = httpsUrl; exports.identifier = identifier; exports.inexact = inexact; exports.instanceOf = instanceOf; exports.integer = integer; exports.iso8601 = iso8601; exports.json = json; exports.jsonArray = jsonArray; exports.jsonObject = jsonObject; exports.lazy = lazy; exports.mapping = mapping; exports.maybe = maybe; exports.mixed = mixed; exports.nanoid = nanoid; exports.never = never; exports.nonEmptyArray = nonEmptyArray; exports.nonEmptyString = nonEmptyString; exports.null_ = null_; exports.nullable = nullable; exports.nullish = nullish; exports.number = number; exports.numeric = numeric; exports.object = object; exports.ok = ok; exports.oneOf = oneOf; exports.optional = optional; exports.poja = poja; exports.pojo = pojo; exports.positiveInteger = positiveInteger; exports.positiveNumber = positiveNumber; exports.prep = prep; exports.record = record; exports.regex = regex; exports.select = select; exports.set = set; exports.setFromArray = setFromArray; exports.string = string; exports.taggedUnion = taggedUnion; exports.truthy = truthy; exports.tuple = tuple; exports.undefined_ = undefined_; exports.unknown = unknown; exports.url = url; exports.uuid = uuid; exports.uuidv1 = uuidv1; exports.uuidv4 = uuidv4;
|
package/dist/index.d.cts
CHANGED
|
@@ -13,20 +13,12 @@ interface ScalarAnnotation {
|
|
|
13
13
|
readonly value: unknown;
|
|
14
14
|
readonly text?: string;
|
|
15
15
|
}
|
|
16
|
-
interface
|
|
17
|
-
readonly type: '
|
|
16
|
+
interface OpaqueAnnotation {
|
|
17
|
+
readonly type: 'opaque';
|
|
18
|
+
readonly value: string;
|
|
18
19
|
readonly text?: string;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
readonly type: 'circular-ref';
|
|
22
|
-
readonly text?: string;
|
|
23
|
-
}
|
|
24
|
-
interface UnknownAnnotation {
|
|
25
|
-
readonly type: 'unknown';
|
|
26
|
-
readonly value: unknown;
|
|
27
|
-
readonly text?: string;
|
|
28
|
-
}
|
|
29
|
-
type Annotation = ObjectAnnotation | ArrayAnnotation | ScalarAnnotation | FunctionAnnotation | CircularRefAnnotation | UnknownAnnotation;
|
|
21
|
+
type Annotation = ObjectAnnotation | ArrayAnnotation | ScalarAnnotation | OpaqueAnnotation;
|
|
30
22
|
|
|
31
23
|
/**
|
|
32
24
|
* Result <value> <error>
|
|
@@ -59,7 +51,8 @@ type DecodeResult<T> = Result<T, Annotation>;
|
|
|
59
51
|
* `ok()` and `err()` constructor functions are provided as the 2nd and 3rd
|
|
60
52
|
* param. One of these should be called and its value returned.
|
|
61
53
|
*/
|
|
62
|
-
type AcceptanceFn<
|
|
54
|
+
type AcceptanceFn<O, I = unknown> = (blob: I, ok: (value: O) => DecodeResult<O>, err: (msg: string | Annotation) => DecodeResult<O>) => DecodeResult<O>;
|
|
55
|
+
type Next<O, I = unknown> = Decoder<O> | ((blob: I, ok: (value: O) => DecodeResult<O>, err: (msg: string | Annotation) => DecodeResult<O>) => DecodeResult<O> | Decoder<O>);
|
|
63
56
|
interface Decoder<T> {
|
|
64
57
|
/**
|
|
65
58
|
* Verifies untrusted input. Either returns a value, or throws a decoding
|
|
@@ -96,23 +89,31 @@ interface Decoder<T> {
|
|
|
96
89
|
*/
|
|
97
90
|
describe(message: string): Decoder<T>;
|
|
98
91
|
/**
|
|
99
|
-
* Send the output of the current decoder into another acceptance
|
|
100
|
-
* The given acceptance function will receive the output of the
|
|
101
|
-
* decoder as its input
|
|
102
|
-
*
|
|
103
|
-
* This works similar to how you would `define()` a new decoder, except
|
|
104
|
-
* that the ``blob`` param will now be ``T`` (a known type), rather than
|
|
105
|
-
* ``unknown``. This will allow the function to make a stronger assumption
|
|
106
|
-
* about its input and avoid re-refining inputs.
|
|
92
|
+
* Send the output of the current decoder into another decoder or acceptance
|
|
93
|
+
* function. The given acceptance function will receive the output of the
|
|
94
|
+
* current decoder as its input.
|
|
107
95
|
*
|
|
108
96
|
* > _**NOTE:** This is an advanced, low-level, API. It's not recommended
|
|
109
97
|
* > to reach for this construct unless there is no other way. Most cases can
|
|
110
|
-
* > be covered more elegantly by `.transform()
|
|
98
|
+
* > be covered more elegantly by `.transform()`, `.refine()`, or `.pipe()`
|
|
99
|
+
* > instead._
|
|
100
|
+
*/
|
|
101
|
+
then<V>(next: Next<V, T>): Decoder<V>;
|
|
102
|
+
/**
|
|
103
|
+
* Send the output of this decoder as input to another decoder.
|
|
104
|
+
*
|
|
105
|
+
* This can be useful to validate the results of a transform, i.e.:
|
|
106
|
+
*
|
|
107
|
+
* string
|
|
108
|
+
* .transform((s) => s.split(','))
|
|
109
|
+
* .pipe(array(nonEmptyString))
|
|
111
110
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
111
|
+
* You can also conditionally pipe:
|
|
112
|
+
*
|
|
113
|
+
* string.pipe((s) => s.startsWith('@') ? username : email)
|
|
114
114
|
*/
|
|
115
|
-
|
|
115
|
+
pipe<V, D extends Decoder<V>>(next: D): Decoder<DecoderType<D>>;
|
|
116
|
+
pipe<V, D extends Decoder<V>>(next: (blob: T) => D): Decoder<DecoderType<D>>;
|
|
116
117
|
}
|
|
117
118
|
/**
|
|
118
119
|
* Helper type to return the output type of a Decoder.
|
|
@@ -264,13 +265,6 @@ declare const boolean: Decoder<boolean>;
|
|
|
264
265
|
* Accepts anything and will return its "truth" value. Will never reject.
|
|
265
266
|
*/
|
|
266
267
|
declare const truthy: Decoder<boolean>;
|
|
267
|
-
/**
|
|
268
|
-
* Accepts numbers, but return their boolean representation.
|
|
269
|
-
*
|
|
270
|
-
* @deprecated This decoder will be removed in a future version. You can use
|
|
271
|
-
* `truthy` to get almost the same effect.
|
|
272
|
-
*/
|
|
273
|
-
declare const numericBoolean: Decoder<boolean>;
|
|
274
268
|
|
|
275
269
|
/**
|
|
276
270
|
* Accepts objects where all values match the given decoder, and returns the
|
|
@@ -358,6 +352,12 @@ declare const jsonArray: Decoder<JSONArray>;
|
|
|
358
352
|
*/
|
|
359
353
|
declare const json: Decoder<JSONValue>;
|
|
360
354
|
|
|
355
|
+
type SizeOptions = {
|
|
356
|
+
min?: number;
|
|
357
|
+
max?: number;
|
|
358
|
+
size?: number;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
361
|
interface Klass<T> extends Function {
|
|
362
362
|
new (...args: readonly any[]): T;
|
|
363
363
|
}
|
|
@@ -490,6 +490,17 @@ declare const url: Decoder<URL>;
|
|
|
490
490
|
* as a URL instance.
|
|
491
491
|
*/
|
|
492
492
|
declare const httpsUrl: Decoder<URL>;
|
|
493
|
+
/**
|
|
494
|
+
* Accepts and returns strings that are valid identifiers in most programming
|
|
495
|
+
* languages.
|
|
496
|
+
*/
|
|
497
|
+
declare const identifier: Decoder<string>;
|
|
498
|
+
/**
|
|
499
|
+
* Accepts and returns [nanoid](https://zelark.github.io/nano-id-cc) string
|
|
500
|
+
* values. It assumes the default nanoid alphabet. If you're using a custom
|
|
501
|
+
* alphabet, use `regex()` instead.
|
|
502
|
+
*/
|
|
503
|
+
declare function nanoid(options?: SizeOptions): Decoder<string>;
|
|
493
504
|
/**
|
|
494
505
|
* Accepts strings that are valid
|
|
495
506
|
* [UUIDs](https://en.wikipedia.org/wiki/universally_unique_identifier)
|
|
@@ -581,4 +592,4 @@ declare function taggedUnion<O extends Record<string, Decoder<unknown>>, T = Dec
|
|
|
581
592
|
*/
|
|
582
593
|
declare function select<T, D extends Decoder<unknown>>(scout: Decoder<T>, selectFn: (result: T) => D): Decoder<DecoderType<D>>;
|
|
583
594
|
|
|
584
|
-
export { type DecodeResult, type Decoder, type DecoderType, type Err, type Formatter, type JSONArray, type JSONObject, type JSONValue, type Ok, type Result, type Scalar, always, anyNumber, array, bigint, boolean, constant, date, datelike, decimal, define, dict, either, email, enum_, err, exact, fail, formatInline, formatShort, hardcoded, hexadecimal, httpsUrl, inexact, instanceOf, integer, iso8601, json, jsonArray, jsonObject, lazy, mapping, maybe, mixed, never, nonEmptyArray, nonEmptyString, null_, nullable, nullish, number, numeric,
|
|
595
|
+
export { type DecodeResult, type Decoder, type DecoderType, type Err, type Formatter, type JSONArray, type JSONObject, type JSONValue, type Ok, type Result, type Scalar, type SizeOptions, always, anyNumber, array, bigint, boolean, constant, date, datelike, decimal, define, dict, either, email, enum_, err, exact, fail, formatInline, formatShort, hardcoded, hexadecimal, httpsUrl, identifier, inexact, instanceOf, integer, iso8601, json, jsonArray, jsonObject, lazy, mapping, maybe, mixed, nanoid, never, nonEmptyArray, nonEmptyString, null_, nullable, nullish, number, numeric, object, ok, oneOf, optional, poja, pojo, positiveInteger, positiveNumber, prep, record, regex, select, set, setFromArray, string, taggedUnion, truthy, tuple, undefined_, unknown, url, uuid, uuidv1, uuidv4 };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,20 +13,12 @@ interface ScalarAnnotation {
|
|
|
13
13
|
readonly value: unknown;
|
|
14
14
|
readonly text?: string;
|
|
15
15
|
}
|
|
16
|
-
interface
|
|
17
|
-
readonly type: '
|
|
16
|
+
interface OpaqueAnnotation {
|
|
17
|
+
readonly type: 'opaque';
|
|
18
|
+
readonly value: string;
|
|
18
19
|
readonly text?: string;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
readonly type: 'circular-ref';
|
|
22
|
-
readonly text?: string;
|
|
23
|
-
}
|
|
24
|
-
interface UnknownAnnotation {
|
|
25
|
-
readonly type: 'unknown';
|
|
26
|
-
readonly value: unknown;
|
|
27
|
-
readonly text?: string;
|
|
28
|
-
}
|
|
29
|
-
type Annotation = ObjectAnnotation | ArrayAnnotation | ScalarAnnotation | FunctionAnnotation | CircularRefAnnotation | UnknownAnnotation;
|
|
21
|
+
type Annotation = ObjectAnnotation | ArrayAnnotation | ScalarAnnotation | OpaqueAnnotation;
|
|
30
22
|
|
|
31
23
|
/**
|
|
32
24
|
* Result <value> <error>
|
|
@@ -59,7 +51,8 @@ type DecodeResult<T> = Result<T, Annotation>;
|
|
|
59
51
|
* `ok()` and `err()` constructor functions are provided as the 2nd and 3rd
|
|
60
52
|
* param. One of these should be called and its value returned.
|
|
61
53
|
*/
|
|
62
|
-
type AcceptanceFn<
|
|
54
|
+
type AcceptanceFn<O, I = unknown> = (blob: I, ok: (value: O) => DecodeResult<O>, err: (msg: string | Annotation) => DecodeResult<O>) => DecodeResult<O>;
|
|
55
|
+
type Next<O, I = unknown> = Decoder<O> | ((blob: I, ok: (value: O) => DecodeResult<O>, err: (msg: string | Annotation) => DecodeResult<O>) => DecodeResult<O> | Decoder<O>);
|
|
63
56
|
interface Decoder<T> {
|
|
64
57
|
/**
|
|
65
58
|
* Verifies untrusted input. Either returns a value, or throws a decoding
|
|
@@ -96,23 +89,31 @@ interface Decoder<T> {
|
|
|
96
89
|
*/
|
|
97
90
|
describe(message: string): Decoder<T>;
|
|
98
91
|
/**
|
|
99
|
-
* Send the output of the current decoder into another acceptance
|
|
100
|
-
* The given acceptance function will receive the output of the
|
|
101
|
-
* decoder as its input
|
|
102
|
-
*
|
|
103
|
-
* This works similar to how you would `define()` a new decoder, except
|
|
104
|
-
* that the ``blob`` param will now be ``T`` (a known type), rather than
|
|
105
|
-
* ``unknown``. This will allow the function to make a stronger assumption
|
|
106
|
-
* about its input and avoid re-refining inputs.
|
|
92
|
+
* Send the output of the current decoder into another decoder or acceptance
|
|
93
|
+
* function. The given acceptance function will receive the output of the
|
|
94
|
+
* current decoder as its input.
|
|
107
95
|
*
|
|
108
96
|
* > _**NOTE:** This is an advanced, low-level, API. It's not recommended
|
|
109
97
|
* > to reach for this construct unless there is no other way. Most cases can
|
|
110
|
-
* > be covered more elegantly by `.transform()
|
|
98
|
+
* > be covered more elegantly by `.transform()`, `.refine()`, or `.pipe()`
|
|
99
|
+
* > instead._
|
|
100
|
+
*/
|
|
101
|
+
then<V>(next: Next<V, T>): Decoder<V>;
|
|
102
|
+
/**
|
|
103
|
+
* Send the output of this decoder as input to another decoder.
|
|
104
|
+
*
|
|
105
|
+
* This can be useful to validate the results of a transform, i.e.:
|
|
106
|
+
*
|
|
107
|
+
* string
|
|
108
|
+
* .transform((s) => s.split(','))
|
|
109
|
+
* .pipe(array(nonEmptyString))
|
|
111
110
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
111
|
+
* You can also conditionally pipe:
|
|
112
|
+
*
|
|
113
|
+
* string.pipe((s) => s.startsWith('@') ? username : email)
|
|
114
114
|
*/
|
|
115
|
-
|
|
115
|
+
pipe<V, D extends Decoder<V>>(next: D): Decoder<DecoderType<D>>;
|
|
116
|
+
pipe<V, D extends Decoder<V>>(next: (blob: T) => D): Decoder<DecoderType<D>>;
|
|
116
117
|
}
|
|
117
118
|
/**
|
|
118
119
|
* Helper type to return the output type of a Decoder.
|
|
@@ -264,13 +265,6 @@ declare const boolean: Decoder<boolean>;
|
|
|
264
265
|
* Accepts anything and will return its "truth" value. Will never reject.
|
|
265
266
|
*/
|
|
266
267
|
declare const truthy: Decoder<boolean>;
|
|
267
|
-
/**
|
|
268
|
-
* Accepts numbers, but return their boolean representation.
|
|
269
|
-
*
|
|
270
|
-
* @deprecated This decoder will be removed in a future version. You can use
|
|
271
|
-
* `truthy` to get almost the same effect.
|
|
272
|
-
*/
|
|
273
|
-
declare const numericBoolean: Decoder<boolean>;
|
|
274
268
|
|
|
275
269
|
/**
|
|
276
270
|
* Accepts objects where all values match the given decoder, and returns the
|
|
@@ -358,6 +352,12 @@ declare const jsonArray: Decoder<JSONArray>;
|
|
|
358
352
|
*/
|
|
359
353
|
declare const json: Decoder<JSONValue>;
|
|
360
354
|
|
|
355
|
+
type SizeOptions = {
|
|
356
|
+
min?: number;
|
|
357
|
+
max?: number;
|
|
358
|
+
size?: number;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
361
|
interface Klass<T> extends Function {
|
|
362
362
|
new (...args: readonly any[]): T;
|
|
363
363
|
}
|
|
@@ -490,6 +490,17 @@ declare const url: Decoder<URL>;
|
|
|
490
490
|
* as a URL instance.
|
|
491
491
|
*/
|
|
492
492
|
declare const httpsUrl: Decoder<URL>;
|
|
493
|
+
/**
|
|
494
|
+
* Accepts and returns strings that are valid identifiers in most programming
|
|
495
|
+
* languages.
|
|
496
|
+
*/
|
|
497
|
+
declare const identifier: Decoder<string>;
|
|
498
|
+
/**
|
|
499
|
+
* Accepts and returns [nanoid](https://zelark.github.io/nano-id-cc) string
|
|
500
|
+
* values. It assumes the default nanoid alphabet. If you're using a custom
|
|
501
|
+
* alphabet, use `regex()` instead.
|
|
502
|
+
*/
|
|
503
|
+
declare function nanoid(options?: SizeOptions): Decoder<string>;
|
|
493
504
|
/**
|
|
494
505
|
* Accepts strings that are valid
|
|
495
506
|
* [UUIDs](https://en.wikipedia.org/wiki/universally_unique_identifier)
|
|
@@ -581,4 +592,4 @@ declare function taggedUnion<O extends Record<string, Decoder<unknown>>, T = Dec
|
|
|
581
592
|
*/
|
|
582
593
|
declare function select<T, D extends Decoder<unknown>>(scout: Decoder<T>, selectFn: (result: T) => D): Decoder<DecoderType<D>>;
|
|
583
594
|
|
|
584
|
-
export { type DecodeResult, type Decoder, type DecoderType, type Err, type Formatter, type JSONArray, type JSONObject, type JSONValue, type Ok, type Result, type Scalar, always, anyNumber, array, bigint, boolean, constant, date, datelike, decimal, define, dict, either, email, enum_, err, exact, fail, formatInline, formatShort, hardcoded, hexadecimal, httpsUrl, inexact, instanceOf, integer, iso8601, json, jsonArray, jsonObject, lazy, mapping, maybe, mixed, never, nonEmptyArray, nonEmptyString, null_, nullable, nullish, number, numeric,
|
|
595
|
+
export { type DecodeResult, type Decoder, type DecoderType, type Err, type Formatter, type JSONArray, type JSONObject, type JSONValue, type Ok, type Result, type Scalar, type SizeOptions, always, anyNumber, array, bigint, boolean, constant, date, datelike, decimal, define, dict, either, email, enum_, err, exact, fail, formatInline, formatShort, hardcoded, hexadecimal, httpsUrl, identifier, inexact, instanceOf, integer, iso8601, json, jsonArray, jsonObject, lazy, mapping, maybe, mixed, nanoid, never, nonEmptyArray, nonEmptyString, null_, nullable, nullish, number, numeric, object, ok, oneOf, optional, poja, pojo, positiveInteger, positiveNumber, prep, record, regex, select, set, setFromArray, string, taggedUnion, truthy, tuple, undefined_, unknown, url, uuid, uuidv1, uuidv4 };
|
package/dist/index.js
CHANGED
|
@@ -29,18 +29,12 @@ function makeObjectAnn(fields, text) {
|
|
|
29
29
|
function makeArrayAnn(items, text) {
|
|
30
30
|
return brand({ type: "array", items, text });
|
|
31
31
|
}
|
|
32
|
-
function
|
|
33
|
-
return brand({ type: "
|
|
34
|
-
}
|
|
35
|
-
function makeUnknownAnn(value, text) {
|
|
36
|
-
return brand({ type: "unknown", value, text });
|
|
32
|
+
function makeOpaqueAnn(value, text) {
|
|
33
|
+
return brand({ type: "opaque", value, text });
|
|
37
34
|
}
|
|
38
35
|
function makeScalarAnn(value, text) {
|
|
39
36
|
return brand({ type: "scalar", value, text });
|
|
40
37
|
}
|
|
41
|
-
function makeCircularRefAnn(text) {
|
|
42
|
-
return brand({ type: "circular-ref", text });
|
|
43
|
-
}
|
|
44
38
|
function updateText(annotation, text) {
|
|
45
39
|
if (text !== void 0) {
|
|
46
40
|
return brand({ ...annotation, text });
|
|
@@ -66,7 +60,8 @@ function annotateArray(arr, text, seen) {
|
|
|
66
60
|
function annotateObject(obj, text, seen) {
|
|
67
61
|
seen.add(obj);
|
|
68
62
|
const fields = /* @__PURE__ */ new Map();
|
|
69
|
-
for (const
|
|
63
|
+
for (const key of Object.keys(obj)) {
|
|
64
|
+
const value = obj[key];
|
|
70
65
|
fields.set(key, annotate(value, void 0, seen));
|
|
71
66
|
}
|
|
72
67
|
return makeObjectAnn(fields, text);
|
|
@@ -80,22 +75,22 @@ function annotate(value, text, seen) {
|
|
|
80
75
|
}
|
|
81
76
|
if (Array.isArray(value)) {
|
|
82
77
|
if (seen.has(value)) {
|
|
83
|
-
return
|
|
78
|
+
return makeOpaqueAnn("<circular ref>", text);
|
|
84
79
|
} else {
|
|
85
80
|
return annotateArray(value, text, seen);
|
|
86
81
|
}
|
|
87
82
|
}
|
|
88
83
|
if (isPojo(value)) {
|
|
89
84
|
if (seen.has(value)) {
|
|
90
|
-
return
|
|
85
|
+
return makeOpaqueAnn("<circular ref>", text);
|
|
91
86
|
} else {
|
|
92
87
|
return annotateObject(value, text, seen);
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
90
|
if (typeof value === "function") {
|
|
96
|
-
return
|
|
91
|
+
return makeOpaqueAnn("<function>", text);
|
|
97
92
|
}
|
|
98
|
-
return
|
|
93
|
+
return makeOpaqueAnn("???", text);
|
|
99
94
|
}
|
|
100
95
|
function public_annotate(value, text) {
|
|
101
96
|
return annotate(value, text, /* @__PURE__ */ new WeakSet());
|
|
@@ -116,6 +111,10 @@ function indent(s, prefix = INDENT) {
|
|
|
116
111
|
return `${prefix}${s}`;
|
|
117
112
|
}
|
|
118
113
|
}
|
|
114
|
+
var quotePattern = /'/g;
|
|
115
|
+
function quote(value) {
|
|
116
|
+
return typeof value === "string" ? "'" + value.replace(quotePattern, "\\'") + "'" : JSON.stringify(value);
|
|
117
|
+
}
|
|
119
118
|
|
|
120
119
|
// src/core/format.ts
|
|
121
120
|
function summarize(ann, keypath = []) {
|
|
@@ -144,9 +143,9 @@ function summarize(ann, keypath = []) {
|
|
|
144
143
|
if (keypath.length === 0) {
|
|
145
144
|
prefix = "";
|
|
146
145
|
} else if (keypath.length === 1) {
|
|
147
|
-
prefix = typeof keypath[0] === "number" ? `Value at index ${keypath[0]}: ` : `Value at key ${
|
|
146
|
+
prefix = typeof keypath[0] === "number" ? `Value at index ${keypath[0]}: ` : `Value at key ${quote(keypath[0])}: `;
|
|
148
147
|
} else {
|
|
149
|
-
prefix = `Value at keypath ${keypath.map(String).join(".")}: `;
|
|
148
|
+
prefix = `Value at keypath ${quote(keypath.map(String).join("."))}: `;
|
|
150
149
|
}
|
|
151
150
|
return [...result, `${prefix}${text}`];
|
|
152
151
|
}
|
|
@@ -202,7 +201,7 @@ function serializeValue(value) {
|
|
|
202
201
|
return "undefined";
|
|
203
202
|
} else {
|
|
204
203
|
if (isDate(value)) {
|
|
205
|
-
return `new Date(${
|
|
204
|
+
return `new Date(${quote(value.toISOString())})`;
|
|
206
205
|
} else if (value instanceof Date) {
|
|
207
206
|
return "(Invalid Date)";
|
|
208
207
|
} else {
|
|
@@ -216,14 +215,12 @@ function serializeAnnotation(ann, prefix = "") {
|
|
|
216
215
|
serialized = serializeArray(ann, prefix);
|
|
217
216
|
} else if (ann.type === "object") {
|
|
218
217
|
serialized = serializeObject(ann, prefix);
|
|
219
|
-
} else if (ann.type === "
|
|
220
|
-
serialized = "<function>";
|
|
221
|
-
} else if (ann.type === "circular-ref") {
|
|
222
|
-
serialized = "<circular ref>";
|
|
223
|
-
} else if (ann.type === "unknown") {
|
|
224
|
-
serialized = "???";
|
|
225
|
-
} else {
|
|
218
|
+
} else if (ann.type === "scalar") {
|
|
226
219
|
serialized = serializeValue(ann.value);
|
|
220
|
+
} else {
|
|
221
|
+
/* @__PURE__ */ ((_) => {
|
|
222
|
+
})(ann);
|
|
223
|
+
serialized = ann.value;
|
|
227
224
|
}
|
|
228
225
|
const text = ann.text;
|
|
229
226
|
if (text !== void 0) {
|
|
@@ -311,10 +308,15 @@ function define(fn) {
|
|
|
311
308
|
}
|
|
312
309
|
function then(next) {
|
|
313
310
|
return define((blob, ok2, err2) => {
|
|
314
|
-
const
|
|
315
|
-
|
|
311
|
+
const r1 = decode(blob);
|
|
312
|
+
if (!r1.ok) return r1;
|
|
313
|
+
const r2 = isDecoder(next) ? next : next(r1.value, ok2, err2);
|
|
314
|
+
return isDecoder(r2) ? r2.decode(r1.value) : r2;
|
|
316
315
|
});
|
|
317
316
|
}
|
|
317
|
+
function pipe(next) {
|
|
318
|
+
return then(next);
|
|
319
|
+
}
|
|
318
320
|
function reject(rejectFn) {
|
|
319
321
|
return then((blob, ok2, err2) => {
|
|
320
322
|
const errmsg = rejectFn(blob);
|
|
@@ -331,13 +333,7 @@ function define(fn) {
|
|
|
331
333
|
}
|
|
332
334
|
});
|
|
333
335
|
}
|
|
334
|
-
|
|
335
|
-
return define((blob, ok2, err2) => {
|
|
336
|
-
const result = decode(blob);
|
|
337
|
-
return result.ok ? next([blob, result.value], ok2, err2) : result;
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
return Object.freeze({
|
|
336
|
+
return brand2({
|
|
341
337
|
verify,
|
|
342
338
|
value,
|
|
343
339
|
decode,
|
|
@@ -346,9 +342,17 @@ function define(fn) {
|
|
|
346
342
|
reject,
|
|
347
343
|
describe,
|
|
348
344
|
then,
|
|
349
|
-
|
|
345
|
+
pipe
|
|
350
346
|
});
|
|
351
347
|
}
|
|
348
|
+
var _register2 = /* @__PURE__ */ new WeakSet();
|
|
349
|
+
function brand2(decoder) {
|
|
350
|
+
_register2.add(decoder);
|
|
351
|
+
return decoder;
|
|
352
|
+
}
|
|
353
|
+
function isDecoder(thing) {
|
|
354
|
+
return _register2.has(thing);
|
|
355
|
+
}
|
|
352
356
|
|
|
353
357
|
// src/arrays.ts
|
|
354
358
|
var poja = define((blob, ok2, err2) => {
|
|
@@ -489,7 +493,7 @@ function object(decoders) {
|
|
|
489
493
|
objAnn = merge(objAnn, errors);
|
|
490
494
|
}
|
|
491
495
|
if (missingKeys.size > 0) {
|
|
492
|
-
const errMsg = Array.from(missingKeys).map(
|
|
496
|
+
const errMsg = Array.from(missingKeys).map(quote).join(", ");
|
|
493
497
|
const pluralized = missingKeys.size > 1 ? "keys" : "key";
|
|
494
498
|
objAnn = updateText(objAnn, `Missing ${pluralized}: ${errMsg}`);
|
|
495
499
|
}
|
|
@@ -503,20 +507,16 @@ function exact(decoders) {
|
|
|
503
507
|
const checked = pojo.reject((plainObj) => {
|
|
504
508
|
const actualKeys = new Set(Object.keys(plainObj));
|
|
505
509
|
const extraKeys = difference(actualKeys, allowedKeys);
|
|
506
|
-
return extraKeys.size > 0 ? `Unexpected extra keys: ${Array.from(extraKeys).join(", ")}` :
|
|
507
|
-
// Don't reject
|
|
508
|
-
null
|
|
509
|
-
);
|
|
510
|
+
return extraKeys.size > 0 ? `Unexpected extra keys: ${Array.from(extraKeys).map(quote).join(", ")}` : null;
|
|
510
511
|
});
|
|
511
|
-
return checked.
|
|
512
|
+
return checked.pipe(object(decoders));
|
|
512
513
|
}
|
|
513
514
|
function inexact(decoders) {
|
|
514
|
-
return pojo.
|
|
515
|
+
return pojo.pipe((plainObj) => {
|
|
515
516
|
const allkeys = new Set(Object.keys(plainObj));
|
|
516
|
-
|
|
517
|
+
return object(decoders).transform((safepart) => {
|
|
517
518
|
const safekeys = new Set(Object.keys(decoders));
|
|
518
|
-
for (const k of safekeys)
|
|
519
|
-
allkeys.add(k);
|
|
519
|
+
for (const k of safekeys) allkeys.add(k);
|
|
520
520
|
const rv = {};
|
|
521
521
|
for (const k of allkeys) {
|
|
522
522
|
if (safekeys.has(k)) {
|
|
@@ -530,7 +530,6 @@ function inexact(decoders) {
|
|
|
530
530
|
}
|
|
531
531
|
return rv;
|
|
532
532
|
});
|
|
533
|
-
return decoder.decode(plainObj);
|
|
534
533
|
});
|
|
535
534
|
}
|
|
536
535
|
|
|
@@ -566,9 +565,7 @@ function oneOf(constants) {
|
|
|
566
565
|
if (winner !== void 0) {
|
|
567
566
|
return ok2(winner);
|
|
568
567
|
}
|
|
569
|
-
return err2(
|
|
570
|
-
`Must be one of ${constants.map((value) => JSON.stringify(value)).join(", ")}`
|
|
571
|
-
);
|
|
568
|
+
return err2(`Must be one of ${constants.map((value) => quote(value)).join(", ")}`);
|
|
572
569
|
});
|
|
573
570
|
}
|
|
574
571
|
function enum_(enumObj) {
|
|
@@ -594,9 +591,9 @@ function taggedUnion(field, mapping2) {
|
|
|
594
591
|
);
|
|
595
592
|
}
|
|
596
593
|
function select(scout, selectFn) {
|
|
597
|
-
return
|
|
598
|
-
const
|
|
599
|
-
return
|
|
594
|
+
return define((blob) => {
|
|
595
|
+
const result = scout.decode(blob);
|
|
596
|
+
return result.ok ? selectFn(result.value).decode(blob) : result;
|
|
600
597
|
});
|
|
601
598
|
}
|
|
602
599
|
|
|
@@ -627,9 +624,7 @@ function nullish(decoder, defaultValue) {
|
|
|
627
624
|
}
|
|
628
625
|
function constant(value) {
|
|
629
626
|
return define(
|
|
630
|
-
(blob, ok2, err2) => blob === value ? ok2(value) : err2(
|
|
631
|
-
`Must be ${typeof value === "symbol" ? String(value) : JSON.stringify(value)}`
|
|
632
|
-
)
|
|
627
|
+
(blob, ok2, err2) => blob === value ? ok2(value) : err2(`Must be ${typeof value === "symbol" ? String(value) : quote(value)}`)
|
|
633
628
|
);
|
|
634
629
|
}
|
|
635
630
|
function always(value) {
|
|
@@ -645,36 +640,11 @@ var hardcoded = always;
|
|
|
645
640
|
var unknown = define((blob, ok2, _) => ok2(blob));
|
|
646
641
|
var mixed = unknown;
|
|
647
642
|
|
|
648
|
-
// src/numbers.ts
|
|
649
|
-
var anyNumber = define(
|
|
650
|
-
(blob, ok2, err2) => isNumber(blob) ? ok2(blob) : err2("Must be number")
|
|
651
|
-
);
|
|
652
|
-
var number = anyNumber.refine(
|
|
653
|
-
(n) => Number.isFinite(n),
|
|
654
|
-
"Number must be finite"
|
|
655
|
-
);
|
|
656
|
-
var integer = number.refine(
|
|
657
|
-
(n) => Number.isInteger(n),
|
|
658
|
-
"Number must be an integer"
|
|
659
|
-
);
|
|
660
|
-
var positiveNumber = number.refine(
|
|
661
|
-
(n) => n >= 0 && !Object.is(n, -0),
|
|
662
|
-
"Number must be positive"
|
|
663
|
-
);
|
|
664
|
-
var positiveInteger = integer.refine(
|
|
665
|
-
(n) => n >= 0 && !Object.is(n, -0),
|
|
666
|
-
"Number must be positive"
|
|
667
|
-
);
|
|
668
|
-
var bigint = define(
|
|
669
|
-
(blob, ok2, err2) => isBigInt(blob) ? ok2(blob) : err2("Must be bigint")
|
|
670
|
-
);
|
|
671
|
-
|
|
672
643
|
// src/booleans.ts
|
|
673
644
|
var boolean = define((blob, ok2, err2) => {
|
|
674
645
|
return typeof blob === "boolean" ? ok2(blob) : err2("Must be boolean");
|
|
675
646
|
});
|
|
676
647
|
var truthy = define((blob, ok2, _) => ok2(!!blob));
|
|
677
|
-
var numericBoolean = number.transform((n) => !!n);
|
|
678
648
|
|
|
679
649
|
// src/collections.ts
|
|
680
650
|
function record(fst, snd) {
|
|
@@ -683,14 +653,12 @@ function record(fst, snd) {
|
|
|
683
653
|
return pojo.then((input, ok2, err2) => {
|
|
684
654
|
let rv = {};
|
|
685
655
|
const errors = /* @__PURE__ */ new Map();
|
|
686
|
-
for (const
|
|
656
|
+
for (const key of Object.keys(input)) {
|
|
657
|
+
const value = input[key];
|
|
687
658
|
const keyResult = keyDecoder?.decode(key);
|
|
688
659
|
if (keyResult?.ok === false) {
|
|
689
660
|
return err2(
|
|
690
|
-
public_annotate(
|
|
691
|
-
input,
|
|
692
|
-
`Invalid key ${JSON.stringify(key)}: ${formatShort(keyResult.error)}`
|
|
693
|
-
)
|
|
661
|
+
public_annotate(input, `Invalid key ${quote(key)}: ${formatShort(keyResult.error)}`)
|
|
694
662
|
);
|
|
695
663
|
}
|
|
696
664
|
const k = keyResult?.value ?? key;
|
|
@@ -720,6 +688,18 @@ function mapping(decoder) {
|
|
|
720
688
|
return record(decoder).transform((obj) => new Map(Object.entries(obj)));
|
|
721
689
|
}
|
|
722
690
|
|
|
691
|
+
// src/lib/size-options.ts
|
|
692
|
+
function bySizeOptions(options) {
|
|
693
|
+
const size = options?.size;
|
|
694
|
+
const min = size ?? options?.min;
|
|
695
|
+
const max = size ?? options?.max;
|
|
696
|
+
const atLeast = min === max ? "" : "at least ";
|
|
697
|
+
const atMost = min === max ? "" : "at most ";
|
|
698
|
+
const tooShort = min !== void 0 && `Too short, must be ${atLeast}${min} chars`;
|
|
699
|
+
const tooLong = max !== void 0 && `Too long, must be ${atMost}${max} chars`;
|
|
700
|
+
return tooShort && tooLong ? (s) => s.length < min ? tooShort : s.length > max ? tooLong : null : tooShort ? (s) => s.length < min ? tooShort : null : tooLong ? (s) => s.length > max ? tooLong : null : () => null;
|
|
701
|
+
}
|
|
702
|
+
|
|
723
703
|
// src/strings.ts
|
|
724
704
|
var url_re = /^([A-Za-z]{3,9}(?:[+][A-Za-z]{3,9})?):\/\/(?:([-;:&=+$,\w]+)@)?(?:([A-Za-z0-9.-]+)(?::([0-9]{2,5}))?)(\/(?:[-+~%/.,\w]*)?(?:\?[-+=&;%@.,/\w]*)?(?:#[.,!/\w]*)?)?$/;
|
|
725
705
|
var string = define(
|
|
@@ -742,6 +722,15 @@ var httpsUrl = url.refine(
|
|
|
742
722
|
(value) => value.protocol === "https:",
|
|
743
723
|
"Must be an HTTPS URL"
|
|
744
724
|
);
|
|
725
|
+
var identifier = regex(
|
|
726
|
+
/^[a-z_][a-z0-9_]*$/i,
|
|
727
|
+
"Must be valid identifier"
|
|
728
|
+
);
|
|
729
|
+
function nanoid(options) {
|
|
730
|
+
return regex(/^[a-z0-9_-]+$/i, "Must be nano ID").reject(
|
|
731
|
+
bySizeOptions(options ?? { size: 21 })
|
|
732
|
+
);
|
|
733
|
+
}
|
|
745
734
|
var uuid = regex(
|
|
746
735
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
747
736
|
"Must be uuid"
|
|
@@ -781,6 +770,30 @@ var iso8601 = (
|
|
|
781
770
|
);
|
|
782
771
|
var datelike = either(date, iso8601).describe("Must be a Date or date string");
|
|
783
772
|
|
|
773
|
+
// src/numbers.ts
|
|
774
|
+
var anyNumber = define(
|
|
775
|
+
(blob, ok2, err2) => isNumber(blob) ? ok2(blob) : err2("Must be number")
|
|
776
|
+
);
|
|
777
|
+
var number = anyNumber.refine(
|
|
778
|
+
(n) => Number.isFinite(n),
|
|
779
|
+
"Number must be finite"
|
|
780
|
+
);
|
|
781
|
+
var integer = number.refine(
|
|
782
|
+
(n) => Number.isInteger(n),
|
|
783
|
+
"Number must be an integer"
|
|
784
|
+
);
|
|
785
|
+
var positiveNumber = number.refine(
|
|
786
|
+
(n) => n >= 0 && !Object.is(n, -0),
|
|
787
|
+
"Number must be positive"
|
|
788
|
+
);
|
|
789
|
+
var positiveInteger = integer.refine(
|
|
790
|
+
(n) => n >= 0 && !Object.is(n, -0),
|
|
791
|
+
"Number must be positive"
|
|
792
|
+
);
|
|
793
|
+
var bigint = define(
|
|
794
|
+
(blob, ok2, err2) => isBigInt(blob) ? ok2(blob) : err2("Must be bigint")
|
|
795
|
+
);
|
|
796
|
+
|
|
784
797
|
// src/json.ts
|
|
785
798
|
var jsonObject = lazy(() => record(json));
|
|
786
799
|
var jsonArray = lazy(() => array(json));
|
|
@@ -815,6 +828,7 @@ export {
|
|
|
815
828
|
hardcoded,
|
|
816
829
|
hexadecimal,
|
|
817
830
|
httpsUrl,
|
|
831
|
+
identifier,
|
|
818
832
|
inexact,
|
|
819
833
|
instanceOf,
|
|
820
834
|
integer,
|
|
@@ -826,6 +840,7 @@ export {
|
|
|
826
840
|
mapping,
|
|
827
841
|
maybe,
|
|
828
842
|
mixed,
|
|
843
|
+
nanoid,
|
|
829
844
|
never,
|
|
830
845
|
nonEmptyArray,
|
|
831
846
|
nonEmptyString,
|
|
@@ -834,7 +849,6 @@ export {
|
|
|
834
849
|
nullish,
|
|
835
850
|
number,
|
|
836
851
|
numeric,
|
|
837
|
-
numericBoolean,
|
|
838
852
|
object,
|
|
839
853
|
ok,
|
|
840
854
|
oneOf,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "decoders",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Elegant and battle-tested validation library for type-safe input data for TypeScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"lint": "eslint --color --report-unused-disable-directives src/ test/ && prettier --list-different src/ test/ test-d/",
|
|
42
42
|
"lint:docs": "cog -c --check docs/*.md || (npm run docs; git diff; echo 'Error: docs not up-to-date, please re-run \"npm docs\" to update them.' && exit 1)",
|
|
43
43
|
"lint:package": "publint --strict && attw --pack",
|
|
44
|
-
"format": "eslint --color --report-unused-disable-directives --fix src/ test/
|
|
44
|
+
"format": "eslint --color --report-unused-disable-directives --fix src/ test/ ; prettier --write src/ test/ test-d/",
|
|
45
45
|
"test": "vitest run --coverage",
|
|
46
46
|
"test:completeness": "./bin/check.sh",
|
|
47
47
|
"test:typescript": "tsc --noEmit",
|
|
@@ -61,25 +61,27 @@
|
|
|
61
61
|
"verify"
|
|
62
62
|
],
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@arethetypeswrong/cli": "^0.
|
|
64
|
+
"@arethetypeswrong/cli": "^0.15.3",
|
|
65
65
|
"@release-it/keep-a-changelog": "^5.0.0",
|
|
66
|
-
"@
|
|
67
|
-
"@typescript-eslint/
|
|
68
|
-
"@
|
|
69
|
-
"
|
|
66
|
+
"@types/eslint": "^8.56.10",
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
|
68
|
+
"@typescript-eslint/parser": "^7.14.1",
|
|
69
|
+
"@vitest/coverage-istanbul": "^1.6.0",
|
|
70
|
+
"eslint": "^8.57.0",
|
|
70
71
|
"eslint-plugin-import": "^2.29.1",
|
|
71
|
-
"eslint-plugin-simple-import-sort": "^
|
|
72
|
-
"fast-check": "^3.
|
|
73
|
-
"itertools": "^2.2
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
72
|
+
"eslint-plugin-simple-import-sort": "^12.1.0",
|
|
73
|
+
"fast-check": "^3.19.0",
|
|
74
|
+
"itertools": "^2.3.2",
|
|
75
|
+
"pkg-pr-new": "^0.0.9",
|
|
76
|
+
"prettier": "^3.3.2",
|
|
77
|
+
"publint": "^0.2.8",
|
|
78
|
+
"release-it": "^17.4.0",
|
|
79
|
+
"ts-morph": "^23.0.0",
|
|
80
|
+
"tsd": "^0.31.1",
|
|
81
|
+
"tsup": "^8.1.0",
|
|
82
|
+
"typescript": "^5.5.2",
|
|
83
|
+
"vite-tsconfig-paths": "^4.3.2",
|
|
84
|
+
"vitest": "^1.6.0"
|
|
83
85
|
},
|
|
84
86
|
"githubUrl": "https://github.com/nvie/decoders",
|
|
85
87
|
"sideEffects": false
|