decoders 2.10.1 → 2.12.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/dist/index.cjs CHANGED
@@ -29,56 +29,87 @@ function isPlainObject(value) {
29
29
  // something is a pojo... ¯\_(ツ)_/¯
30
30
  Object.prototype.toString.call(value) === "[object Object]";
31
31
  }
32
+ function assertNever(_value, msg = "Unhandled case") {
33
+ throw new Error(msg);
34
+ }
32
35
 
33
36
  // src/core/annotate.ts
34
37
  var kAnnotationRegistry = /* @__PURE__ */ Symbol.for("decoders.kAnnotationRegistry");
35
- var _register = globalThis[kAnnotationRegistry] ??= /* @__PURE__ */ new WeakSet();
36
- function brand(ann) {
37
- _register.add(ann);
38
+ var _stamped = globalThis[kAnnotationRegistry] ??= /* @__PURE__ */ new WeakSet();
39
+ function stamp(ann) {
40
+ _stamped.add(ann);
38
41
  return ann;
39
42
  }
40
- function makeObjectAnn(fields, text) {
41
- return brand({ type: "object", fields, text });
43
+ function makeObjectAnn(getFields, text) {
44
+ let fields;
45
+ return stamp({
46
+ type: "object",
47
+ get fields() {
48
+ return fields ??= getFields();
49
+ },
50
+ text
51
+ });
42
52
  }
43
- function makeArrayAnn(items, text) {
44
- return brand({ type: "array", items, text });
53
+ function makeArrayAnn(getItems, text) {
54
+ let items;
55
+ return stamp({
56
+ type: "array",
57
+ get items() {
58
+ return items ??= getItems();
59
+ },
60
+ text
61
+ });
45
62
  }
46
63
  function makeOpaqueAnn(value, text) {
47
- return brand({ type: "opaque", value, text });
64
+ return stamp({ type: "opaque", value, text });
48
65
  }
49
66
  function makeScalarAnn(value, text) {
50
- return brand({ type: "scalar", value, text });
67
+ return stamp({ type: "scalar", value, text });
51
68
  }
52
69
  function updateText(annotation, text) {
53
- if (text !== void 0) {
54
- return brand({ ...annotation, text });
55
- } else {
70
+ if (text === void 0) {
56
71
  return annotation;
57
72
  }
73
+ switch (annotation.type) {
74
+ case "object":
75
+ return makeObjectAnn(() => annotation.fields, text);
76
+ case "array":
77
+ return makeArrayAnn(() => annotation.items, text);
78
+ case "scalar":
79
+ return makeScalarAnn(annotation.value, text);
80
+ case "opaque":
81
+ return makeOpaqueAnn(annotation.value, text);
82
+ // istanbul ignore next -- @preserve
83
+ default:
84
+ return assertNever(annotation, "Unknown annotation type");
85
+ }
58
86
  }
59
87
  function merge(objAnnotation, fields) {
60
- const newFields = new Map([...objAnnotation.fields, ...fields]);
61
- return makeObjectAnn(newFields, objAnnotation.text);
88
+ return makeObjectAnn(
89
+ () => new Map([...objAnnotation.fields, ...fields]),
90
+ objAnnotation.text
91
+ );
62
92
  }
63
93
  function isAnnotation(thing) {
64
- return _register.has(thing);
94
+ return _stamped.has(thing);
65
95
  }
66
96
  function annotateArray(arr, text, seen) {
67
97
  seen.add(arr);
68
- const items = [];
69
- for (const value of arr) {
70
- items.push(__annotate(value, void 0, seen));
71
- }
72
- return makeArrayAnn(items, text);
98
+ return makeArrayAnn(
99
+ () => Array.from(arr, (value) => __annotate(value, void 0, seen)),
100
+ text
101
+ );
73
102
  }
74
103
  function annotateObject(obj, text, seen) {
75
104
  seen.add(obj);
76
- const fields = /* @__PURE__ */ new Map();
77
- for (const key of Object.keys(obj)) {
78
- const value = obj[key];
79
- fields.set(key, __annotate(value, void 0, seen));
80
- }
81
- return makeObjectAnn(fields, text);
105
+ return makeObjectAnn(() => {
106
+ const fields = /* @__PURE__ */ new Map();
107
+ for (const key of Object.keys(obj)) {
108
+ const value = obj[key];
109
+ fields.set(key, __annotate(value, void 0, seen));
110
+ }
111
+ return fields;
112
+ }, text);
82
113
  }
83
114
  function __annotate(value, text, seen) {
84
115
  if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "symbol" || typeof value === "bigint" || typeof value.getMonth === "function") {
@@ -299,6 +330,9 @@ function* iterAnnotation(ann, stack) {
299
330
  case "opaque": {
300
331
  break;
301
332
  }
333
+ // istanbul ignore next -- @preserve
334
+ default:
335
+ assertNever(ann, "Unknown annotation type");
302
336
  }
303
337
  }
304
338
  function formatAsIssues(ann) {
@@ -337,29 +371,60 @@ ${formatted}`);
337
371
  return formatted;
338
372
  }
339
373
  }
340
- // @__NO_SIDE_EFFECTS__
341
- function define(fn) {
342
- function decode(blob) {
343
- const makeFlexErr = (msg) => err(isAnnotation(msg) ? msg : public_annotate(blob, msg));
344
- return fn(blob, ok, makeFlexErr);
345
- }
346
- function verify(blob, formatter = formatInline) {
347
- const result = decode(blob);
348
- if (result.ok) {
349
- return result.value;
350
- } else {
351
- throw format(result.error, formatter);
352
- }
353
- }
354
- function value(blob) {
355
- return decode(blob).value;
374
+ var _standard = /* @__PURE__ */ new WeakMap();
375
+ var DecoderImpl = class {
376
+ /**
377
+ * Verifies the untrusted/unknown input and either accepts or rejects it.
378
+ *
379
+ * Contrasted with `.verify()`, calls to `.decode()` will never fail and
380
+ * instead return a result type.
381
+ */
382
+
383
+ /**
384
+ * Verifies the untrusted/unknown input and either accepts or rejects it.
385
+ * When accepted, returns a value of type `T`. Otherwise fail with
386
+ * a runtime error.
387
+ */
388
+
389
+ /**
390
+ * Verifies the untrusted/unknown input and either accepts or rejects it.
391
+ * When accepted, returns the decoded `T` value directly. Otherwise returns
392
+ * `undefined`.
393
+ *
394
+ * Use this when you're not interested in programmatically handling the
395
+ * error message.
396
+ */
397
+
398
+ constructor(fn) {
399
+ const decode = (blob) => {
400
+ const makeFlexErr = (msg) => err(isAnnotation(msg) ? msg : public_annotate(blob, msg));
401
+ return fn(blob, ok, makeFlexErr);
402
+ };
403
+ const verify = (blob, formatter = formatInline) => {
404
+ const result = decode(blob);
405
+ if (result.ok) {
406
+ return result.value;
407
+ } else {
408
+ throw format(result.error, formatter);
409
+ }
410
+ };
411
+ const value = (blob) => decode(blob).value;
412
+ this.decode = decode;
413
+ this.verify = verify;
414
+ this.value = value;
356
415
  }
357
- function transform(transformFn) {
358
- return chain(noThrow(transformFn));
416
+ /**
417
+ * Accepts any value the given decoder accepts, and on success, will call
418
+ * the given function **on the decoded result**. If the transformation
419
+ * function throws an error, the whole decoder will fail using the error
420
+ * message as the failure reason.
421
+ */
422
+ transform(transformFn) {
423
+ return this.chain(noThrow(transformFn));
359
424
  }
360
- function refine(predicateFn, errmsg) {
361
- return reject(
362
- (value2) => predicateFn(value2) ? (
425
+ refine(predicateFn, errmsg) {
426
+ return this.reject(
427
+ (value) => predicateFn(value) ? (
363
428
  // Don't reject
364
429
  null
365
430
  ) : (
@@ -368,10 +433,25 @@ function define(fn) {
368
433
  )
369
434
  );
370
435
  }
371
- function refineType() {
372
- return self;
436
+ /**
437
+ * Cast the return type of this read-only decoder to a narrower type. This is
438
+ * useful to return "branded" types. This method has no runtime effect.
439
+ */
440
+ refineType() {
441
+ return this;
373
442
  }
374
- function chain(next) {
443
+ /**
444
+ * Send the output of the current decoder into another decoder or acceptance
445
+ * function. The given acceptance function will receive the output of the
446
+ * current decoder as its input.
447
+ *
448
+ * > _**NOTE:** This is an advanced, low-level, API. It's not recommended
449
+ * > to reach for this construct unless there is no other way. Most cases can
450
+ * > be covered more elegantly by `.transform()`, `.refine()`, or `.pipe()`
451
+ * > instead._
452
+ */
453
+ chain(next) {
454
+ const decode = this.decode;
375
455
  return /* @__PURE__ */ define((blob, ok2, err2) => {
376
456
  const r1 = decode(blob);
377
457
  if (!r1.ok) return r1;
@@ -379,16 +459,46 @@ function define(fn) {
379
459
  return /* @__PURE__ */ isDecoder(r2) ? r2.decode(r1.value) : r2;
380
460
  });
381
461
  }
382
- function pipe(next) {
383
- return chain(next);
462
+ /**
463
+ * Send the output of this decoder as input to another decoder.
464
+ *
465
+ * This can be useful to validate the results of a transform, i.e.:
466
+ *
467
+ * string
468
+ * .transform((s) => s.split(','))
469
+ * .pipe(array(nonEmptyString))
470
+ *
471
+ * You can also conditionally pipe:
472
+ *
473
+ * string.pipe((s) => s.startsWith('@') ? username : email)
474
+ */
475
+ pipe(next) {
476
+ return this.chain(next);
384
477
  }
385
- function reject(rejectFn) {
386
- return chain((blob, ok2, err2) => {
478
+ /**
479
+ * Adds an extra predicate to a decoder. The new decoder is like the
480
+ * original decoder, but only accepts values that aren't rejected by the
481
+ * given function.
482
+ *
483
+ * The given function can return `null` to accept the decoded value, or
484
+ * return a specific error message to reject.
485
+ *
486
+ * Unlike `.refine()`, you can use this function to return a dynamic error
487
+ * message.
488
+ */
489
+ reject(rejectFn) {
490
+ return this.chain((blob, ok2, err2) => {
387
491
  const errmsg = rejectFn(blob);
388
492
  return errmsg === null ? ok2(blob) : err2(typeof errmsg === "string" ? public_annotate(blob, errmsg) : errmsg);
389
493
  });
390
494
  }
391
- function describe(message) {
495
+ /**
496
+ * Uses the given decoder, but will use an alternative error message in
497
+ * case it rejects. This can be used to simplify or shorten otherwise
498
+ * long or low-level/technical errors.
499
+ */
500
+ describe(message) {
501
+ const decode = this.decode;
392
502
  return /* @__PURE__ */ define((blob, _, err2) => {
393
503
  const result = decode(blob);
394
504
  if (result.ok) {
@@ -398,18 +508,14 @@ function define(fn) {
398
508
  }
399
509
  });
400
510
  }
401
- const unregistered = {
402
- verify,
403
- value,
404
- decode,
405
- transform,
406
- refine,
407
- refineType,
408
- reject,
409
- describe,
410
- chain,
411
- pipe,
412
- "~standard": {
511
+ /**
512
+ * The Standard Schema interface for this decoder.
513
+ */
514
+ get "~standard"() {
515
+ const memo = _standard.get(this);
516
+ if (memo !== void 0) return memo;
517
+ const decode = this.decode;
518
+ const props = {
413
519
  version: 1,
414
520
  vendor: "decoders",
415
521
  validate: (blob) => {
@@ -421,82 +527,26 @@ function define(fn) {
421
527
  return { issues };
422
528
  }
423
529
  }
424
- }
425
- };
426
- const self = brand2(unregistered);
427
- return self;
530
+ };
531
+ _standard.set(this, props);
532
+ return props;
533
+ }
534
+ };
535
+ Object.defineProperty(DecoderImpl, "name", { value: "Decoder" });
536
+ // @__NO_SIDE_EFFECTS__
537
+ function define(fn) {
538
+ const decoder = new DecoderImpl(fn);
539
+ return stamp2(decoder);
428
540
  }
429
541
  var kDecoderRegistry = /* @__PURE__ */ Symbol.for("decoders.kDecoderRegistry");
430
- var _register2 = globalThis[kDecoderRegistry] ??= /* @__PURE__ */ new WeakSet();
431
- function brand2(decoder) {
432
- _register2.add(decoder);
542
+ var _stamped2 = globalThis[kDecoderRegistry] ??= /* @__PURE__ */ new WeakSet();
543
+ function stamp2(decoder) {
544
+ _stamped2.add(decoder);
433
545
  return decoder;
434
546
  }
435
547
  // @__NO_SIDE_EFFECTS__
436
548
  function isDecoder(value) {
437
- return _register2.has(value);
438
- }
439
-
440
- // src/arrays.ts
441
- var poja = define((blob, ok2, err2) => {
442
- if (!Array.isArray(blob)) {
443
- return err2("Must be an array");
444
- }
445
- return ok2(blob);
446
- });
447
- // @__NO_SIDE_EFFECTS__
448
- function array(decoder) {
449
- const decodeFn = decoder.decode;
450
- return poja.chain((inputs, ok2, err2) => {
451
- const results = [];
452
- for (let i = 0; i < inputs.length; ++i) {
453
- const blob = inputs[i];
454
- const result = decodeFn(blob);
455
- if (result.ok) {
456
- results.push(result.value);
457
- } else {
458
- results.length = 0;
459
- const ann = result.error;
460
- const clone = inputs.slice();
461
- clone.splice(
462
- i,
463
- 1,
464
- public_annotate(ann, ann.text ? `${ann.text} (at index ${i})` : `index ${i}`)
465
- );
466
- return err2(public_annotate(clone));
467
- }
468
- }
469
- return ok2(results);
470
- });
471
- }
472
- function isNonEmpty(arr) {
473
- return arr.length > 0;
474
- }
475
- // @__NO_SIDE_EFFECTS__
476
- function nonEmptyArray(decoder) {
477
- return (/* @__PURE__ */ array(decoder)).refine(isNonEmpty, "Must have at least 1 item");
478
- }
479
- var ntuple = /* @__NO_SIDE_EFFECTS__ */ (n) => poja.refine((arr) => arr.length === n, `Must be a ${n}-tuple`);
480
- // @__NO_SIDE_EFFECTS__
481
- function tuple(...decoders) {
482
- return (/* @__PURE__ */ ntuple(decoders.length)).chain((blobs, ok2, err2) => {
483
- let allOk = true;
484
- const rvs = decoders.map((decoder, i) => {
485
- const blob = blobs[i];
486
- const result = decoder.decode(blob);
487
- if (result.ok) {
488
- return result.value;
489
- } else {
490
- allOk = false;
491
- return result.error;
492
- }
493
- });
494
- if (allOk) {
495
- return ok2(rvs);
496
- } else {
497
- return err2(public_annotate(rvs));
498
- }
499
- });
549
+ return _stamped2.has(value);
500
550
  }
501
551
 
502
552
  // src/lib/size-options.ts
@@ -561,6 +611,66 @@ function prep(mapperFn, decoder) {
561
611
  });
562
612
  }
563
613
 
614
+ // src/arrays.ts
615
+ var poja = define((blob, ok2, err2) => {
616
+ if (!Array.isArray(blob)) {
617
+ return err2("Must be an array");
618
+ }
619
+ return ok2(blob);
620
+ });
621
+ // @__NO_SIDE_EFFECTS__
622
+ function array(decoder, options) {
623
+ const decodeFn = decoder.decode;
624
+ const base = options !== void 0 ? sized(poja, options) : poja;
625
+ return base.chain((inputs, ok2, err2) => {
626
+ const results = [];
627
+ for (let i = 0; i < inputs.length; ++i) {
628
+ const blob = inputs[i];
629
+ const result = decodeFn(blob);
630
+ if (result.ok) {
631
+ results.push(result.value);
632
+ } else {
633
+ results.length = 0;
634
+ const ann = result.error;
635
+ const clone = inputs.slice();
636
+ clone.splice(
637
+ i,
638
+ 1,
639
+ public_annotate(ann, ann.text ? `${ann.text} (at index ${i})` : `index ${i}`)
640
+ );
641
+ return err2(public_annotate(clone));
642
+ }
643
+ }
644
+ return ok2(results);
645
+ });
646
+ }
647
+ // @__NO_SIDE_EFFECTS__
648
+ function nonEmptyArray(decoder) {
649
+ return (/* @__PURE__ */ array(decoder, { min: 1 })).refineType();
650
+ }
651
+ var ntuple = /* @__NO_SIDE_EFFECTS__ */ (n) => poja.refine((arr) => arr.length === n, `Must be a ${n}-tuple`);
652
+ // @__NO_SIDE_EFFECTS__
653
+ function tuple(...decoders) {
654
+ return (/* @__PURE__ */ ntuple(decoders.length)).chain((blobs, ok2, err2) => {
655
+ let allOk = true;
656
+ const rvs = decoders.map((decoder, i) => {
657
+ const blob = blobs[i];
658
+ const result = decoder.decode(blob);
659
+ if (result.ok) {
660
+ return result.value;
661
+ } else {
662
+ allOk = false;
663
+ return result.error;
664
+ }
665
+ });
666
+ if (allOk) {
667
+ return ok2(rvs);
668
+ } else {
669
+ return err2(public_annotate(rvs));
670
+ }
671
+ });
672
+ }
673
+
564
674
  // src/lib/set-methods.ts
565
675
  // @__NO_SIDE_EFFECTS__
566
676
  function difference(xs, ys) {
@@ -955,14 +1065,16 @@ var integer = /* @__PURE__ */ number.refine(
955
1065
  (n) => Number.isInteger(n),
956
1066
  "Number must be an integer"
957
1067
  );
958
- var positiveNumber = /* @__PURE__ */ number.refine(
1068
+ var nonNegativeNumber = /* @__PURE__ */ number.refine(
959
1069
  (n) => n >= 0 && !Object.is(n, -0),
960
1070
  "Number must be positive"
961
1071
  );
962
- var positiveInteger = /* @__PURE__ */ integer.refine(
1072
+ var natural = /* @__PURE__ */ integer.refine(
963
1073
  (n) => n >= 0 && !Object.is(n, -0),
964
1074
  "Number must be positive"
965
1075
  );
1076
+ var positiveNumber = nonNegativeNumber;
1077
+ var positiveInteger = natural;
966
1078
  // @__NO_SIDE_EFFECTS__
967
1079
  function min(min2, decoder = number) {
968
1080
  return decoder.reject(
@@ -1076,5 +1188,8 @@ var json = /* @__PURE__ */ either(
1076
1188
 
1077
1189
 
1078
1190
 
1079
- exports._annotate = public_annotate; exports.always = always; exports.anyNumber = anyNumber; exports.anything = anything; exports.array = array; exports.between = between; exports.bigint = bigint; exports.boolean = boolean; exports.constant = constant; exports.date = date; exports.dateString = dateString; exports.datelike = datelike; exports.decimal = decimal; exports.define = define; exports.either = either; exports.email = email; exports.endsWith = endsWith; exports.enum_ = enum_; exports.err = err; exports.exact = exact; exports.fail = fail; exports.flexDate = flexDate; exports.formatInline = formatInline; exports.formatShort = formatShort; exports.hexadecimal = hexadecimal; exports.httpsUrl = httpsUrl; exports.identifier = identifier; exports.inexact = inexact; exports.instanceOf = instanceOf; exports.integer = integer; exports.isDate = isDate; exports.isDecoder = isDecoder; exports.isPlainObject = isPlainObject; exports.isPromiseLike = isPromiseLike; exports.iso8601 = iso8601; exports.isoDate = isoDate; exports.isoDateString = isoDateString; exports.json = json; exports.jsonArray = jsonArray; exports.jsonObject = jsonObject; exports.lazy = lazy; exports.mapping = mapping; exports.max = max; exports.min = min; 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.setFromArray = setFromArray; exports.sized = sized; exports.startsWith = startsWith; exports.string = string; exports.taggedUnion = taggedUnion; exports.truthy = truthy; exports.tuple = tuple; exports.undefined_ = undefined_; exports.unknown = unknown; exports.url = url; exports.urlString = urlString; exports.uuid = uuid; exports.uuidv1 = uuidv1; exports.uuidv4 = uuidv4;
1191
+
1192
+
1193
+ exports._annotate = public_annotate; exports.always = always; exports.anyNumber = anyNumber; exports.anything = anything; exports.array = array; exports.between = between; exports.bigint = bigint; exports.boolean = boolean; exports.constant = constant; exports.date = date; exports.dateString = dateString; exports.datelike = datelike; exports.decimal = decimal; exports.define = define; exports.either = either; exports.email = email; exports.endsWith = endsWith; exports.enum_ = enum_; exports.err = err; exports.exact = exact; exports.fail = fail; exports.flexDate = flexDate; exports.formatInline = formatInline; exports.formatShort = formatShort; exports.hexadecimal = hexadecimal; exports.httpsUrl = httpsUrl; exports.identifier = identifier; exports.inexact = inexact; exports.instanceOf = instanceOf; exports.integer = integer; exports.isDate = isDate; exports.isDecoder = isDecoder; exports.isPlainObject = isPlainObject; exports.isPromiseLike = isPromiseLike; exports.iso8601 = iso8601; exports.isoDate = isoDate; exports.isoDateString = isoDateString; exports.json = json; exports.jsonArray = jsonArray; exports.jsonObject = jsonObject; exports.lazy = lazy; exports.mapping = mapping; exports.max = max; exports.min = min; exports.nanoid = nanoid; exports.natural = natural; exports.never = never; exports.nonEmptyArray = nonEmptyArray; exports.nonEmptyString = nonEmptyString; exports.nonNegativeNumber = nonNegativeNumber; 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.setFromArray = setFromArray; exports.sized = sized; exports.startsWith = startsWith; exports.string = string; exports.taggedUnion = taggedUnion; exports.truthy = truthy; exports.tuple = tuple; exports.undefined_ = undefined_; exports.unknown = unknown; exports.url = url; exports.urlString = urlString; exports.uuid = uuid; exports.uuidv1 = uuidv1; exports.uuidv4 = uuidv4;
1194
+ // istanbul ignore next -- @preserve
1080
1195
  // istanbul ignore else -- @preserve