decoders 2.3.0-beta.1 → 2.3.0-beta.2

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
@@ -1,4 +1,13 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/lib/utils.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/lib/utils.ts
2
+ function isNumber(value) {
3
+ return typeof value === "number";
4
+ }
5
+ function isString(value) {
6
+ return typeof value === "string";
7
+ }
8
+ function isBigInt(value) {
9
+ return typeof value === "bigint";
10
+ }
2
11
  function isDate(value) {
3
12
  return !!value && Object.prototype.toString.call(value) === "[object Date]" && !isNaN(value);
4
13
  }
@@ -453,7 +462,7 @@ function object(decoders) {
453
462
  return pojo.then((plainObj, ok2, err2) => {
454
463
  const actualKeys = new Set(Object.keys(plainObj));
455
464
  const missingKeys = difference(knownKeys, actualKeys);
456
- const record = {};
465
+ const record2 = {};
457
466
  let errors = null;
458
467
  for (const key of Object.keys(decoders)) {
459
468
  const decoder = decoders[key];
@@ -462,7 +471,7 @@ function object(decoders) {
462
471
  if (result.ok) {
463
472
  const value = result.value;
464
473
  if (value !== void 0) {
465
- record[key] = value;
474
+ record2[key] = value;
466
475
  }
467
476
  missingKeys.delete(key);
468
477
  } else {
@@ -489,7 +498,7 @@ function object(decoders) {
489
498
  }
490
499
  return err2(objAnn);
491
500
  }
492
- return ok2(record);
501
+ return ok2(record2);
493
502
  });
494
503
  }
495
504
  function exact(decoders) {
@@ -527,40 +536,43 @@ function inexact(decoders) {
527
536
  return decoder.decode(plainObj);
528
537
  });
529
538
  }
530
- function dict(decoder) {
531
- return pojo.then((plainObj, ok2, err2) => {
539
+ function record(fst, snd) {
540
+ const keyDecoder = snd !== void 0 ? fst : void 0;
541
+ const valueDecoder = snd !== void 0 ? snd : fst;
542
+ return pojo.then((rec, ok2, err2) => {
532
543
  let rv = {};
533
- let errors = null;
534
- for (const key of Object.keys(plainObj)) {
535
- const value = plainObj[key];
536
- const result = decoder.decode(value);
544
+ const errors = /* @__PURE__ */ new Map();
545
+ for (const [key, value] of Object.entries(rec)) {
546
+ const keyResult = _optionalChain([keyDecoder, 'optionalAccess', _2 => _2.decode, 'call', _3 => _3(key)]);
547
+ if (_optionalChain([keyResult, 'optionalAccess', _4 => _4.ok]) === false) {
548
+ return err2(
549
+ public_annotate(
550
+ rec,
551
+ `Invalid key ${JSON.stringify(key)}: ${formatShort(keyResult.error)}`
552
+ )
553
+ );
554
+ }
555
+ const k = _nullishCoalesce(_optionalChain([keyResult, 'optionalAccess', _5 => _5.value]), () => ( key));
556
+ const result = valueDecoder.decode(value);
537
557
  if (result.ok) {
538
- if (errors === null) {
539
- rv[key] = result.value;
558
+ if (errors.size === 0) {
559
+ rv[k] = result.value;
540
560
  }
541
561
  } else {
542
- rv = {};
543
- if (errors === null) {
544
- errors = /* @__PURE__ */ new Map();
545
- }
546
562
  errors.set(key, result.error);
563
+ rv = {};
547
564
  }
548
565
  }
549
- if (errors !== null) {
550
- return err2(merge(public_annotateObject(plainObj), errors));
566
+ if (errors.size > 0) {
567
+ return err2(merge(public_annotateObject(rec), errors));
551
568
  } else {
552
569
  return ok2(rv);
553
570
  }
554
571
  });
555
572
  }
573
+ var dict = record;
556
574
  function mapping(decoder) {
557
- return dict(decoder).transform(
558
- (obj) => new Map(
559
- // This is effectively Object.entries(obj), but in a way that Flow
560
- // will know the types are okay
561
- Object.keys(obj).map((key) => [key, obj[key]])
562
- )
563
- );
575
+ return record(decoder).transform((obj) => new Map(Object.entries(obj)));
564
576
  }
565
577
 
566
578
  // src/unions.ts
@@ -600,6 +612,17 @@ function oneOf(constants) {
600
612
  );
601
613
  });
602
614
  }
615
+ function enum_(enumObj) {
616
+ const values = Object.values(enumObj);
617
+ if (!values.some(isNumber)) {
618
+ return oneOf(values);
619
+ } else {
620
+ const nums = values.filter(isNumber);
621
+ const ignore = new Set(nums.map((val) => enumObj[val]));
622
+ const strings = values.filter(isString).filter((val) => !ignore.has(val));
623
+ return oneOf([...nums, ...strings]);
624
+ }
625
+ }
603
626
  function taggedUnion(field, mapping2) {
604
627
  const scout = object({
605
628
  [field]: prep(String, oneOf(Object.keys(mapping2)))
@@ -625,9 +648,9 @@ function lazyval(value) {
625
648
  var null_ = constant(null);
626
649
  var undefined_ = constant(void 0);
627
650
  var nullish_ = define(
628
- (blob, ok2, err2) => blob == null ? ok2(blob) : (
629
- // Combine error message into a single line for readability
630
- err2("Must be undefined or null")
651
+ (blob, ok2, err2) => (
652
+ // Equiv to either(undefined_, null_), but combined for better error message
653
+ blob == null ? ok2(blob) : err2("Must be undefined or null")
631
654
  )
632
655
  );
633
656
  function optional(decoder, defaultValue) {
@@ -665,7 +688,7 @@ var mixed = unknown;
665
688
 
666
689
  // src/numbers.ts
667
690
  var anyNumber = define(
668
- (blob, ok2, err2) => typeof blob === "number" ? ok2(blob) : err2("Must be number")
691
+ (blob, ok2, err2) => isNumber(blob) ? ok2(blob) : err2("Must be number")
669
692
  );
670
693
  var number = anyNumber.refine(
671
694
  (n) => Number.isFinite(n),
@@ -678,7 +701,7 @@ var integer = number.refine(
678
701
  var positiveNumber = number.refine((n) => n >= 0, "Number must be positive").transform(Math.abs);
679
702
  var positiveInteger = integer.refine((n) => n >= 0, "Number must be positive").transform(Math.abs);
680
703
  var bigint = define(
681
- (blob, ok2, err2) => typeof blob === "bigint" ? ok2(blob) : err2("Must be bigint")
704
+ (blob, ok2, err2) => isBigInt(blob) ? ok2(blob) : err2("Must be bigint")
682
705
  );
683
706
 
684
707
  // src/booleans.ts
@@ -691,7 +714,7 @@ var numericBoolean = number.transform((n) => !!n);
691
714
  // src/strings.ts
692
715
  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]*)?)?$/;
693
716
  var string = define(
694
- (blob, ok2, err2) => typeof blob === "string" ? ok2(blob) : err2("Must be string")
717
+ (blob, ok2, err2) => isString(blob) ? ok2(blob) : err2("Must be string")
695
718
  );
696
719
  var nonEmptyString = regex(/\S/, "Must be non-empty string");
697
720
  function regex(regex2, msg) {
@@ -722,6 +745,12 @@ var uuidv4 = (
722
745
  // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)
723
746
  uuid.refine((value) => value[14] === "4", "Must be uuidv4")
724
747
  );
748
+ var decimal = regex(/^[0-9]+$/, "Must only contain digits");
749
+ var hexadecimal = regex(
750
+ /^[0-9a-f]+$/i,
751
+ "Must only contain hexadecimal digits"
752
+ );
753
+ var numeric = decimal.transform(Number);
725
754
 
726
755
  // src/dates.ts
727
756
  var iso8601_re = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[.]\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
@@ -743,7 +772,7 @@ var iso8601 = (
743
772
  );
744
773
 
745
774
  // src/json.ts
746
- var jsonObject = lazy(() => dict(json));
775
+ var jsonObject = lazy(() => record(json));
747
776
  var jsonArray = lazy(() => array(json));
748
777
  var json = either(
749
778
  null_,
@@ -813,4 +842,9 @@ var json = either(
813
842
 
814
843
 
815
844
 
816
- exports.always = always; exports.anyNumber = anyNumber; exports.array = array; exports.bigint = bigint; exports.boolean = boolean; exports.constant = constant; exports.date = date; exports.define = define; exports.dict = dict; exports.either = either; exports.email = email; exports.err = err; exports.exact = exact; exports.fail = fail; exports.formatInline = formatInline; exports.formatShort = formatShort; exports.hardcoded = hardcoded; exports.httpsUrl = httpsUrl; 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.never = never; exports.nonEmptyArray = nonEmptyArray; exports.nonEmptyString = nonEmptyString; exports.null_ = null_; exports.nullable = nullable; exports.nullish = nullish; exports.number = number; exports.numericBoolean = numericBoolean; 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.regex = regex; exports.select = select; exports.set = set; 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;
845
+
846
+
847
+
848
+
849
+
850
+ exports.always = always; exports.anyNumber = anyNumber; exports.array = array; exports.bigint = bigint; exports.boolean = boolean; exports.constant = constant; exports.date = date; 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.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.never = never; exports.nonEmptyArray = nonEmptyArray; exports.nonEmptyString = nonEmptyString; exports.null_ = null_; exports.nullable = nullable; exports.nullish = nullish; exports.number = number; exports.numeric = numeric; exports.numericBoolean = numericBoolean; 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.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
@@ -206,7 +206,7 @@ declare function nullable<T, C extends Scalar>(decoder: Decoder<T>, defaultValue
206
206
  declare function nullable<T, V>(decoder: Decoder<T>, defaultValue: (() => V) | V): Decoder<NonNullable<T> | V>;
207
207
  /**
208
208
  * @deprecated
209
- * Alias of nullish().
209
+ * Alias of `nullish()`.
210
210
  */
211
211
  declare const maybe: typeof nullish;
212
212
  /**
@@ -236,12 +236,12 @@ declare function always<T>(value: (() => T) | T): Decoder<T>;
236
236
  */
237
237
  declare function never(msg: string): Decoder<never>;
238
238
  /**
239
- * Alias of never().
239
+ * Alias of `never()`.
240
240
  */
241
241
  declare const fail: typeof never;
242
242
  /**
243
243
  * @deprecated
244
- * Alias of always.
244
+ * Alias of `always()`.
245
245
  */
246
246
  declare const hardcoded: typeof always;
247
247
  /**
@@ -254,7 +254,7 @@ declare const hardcoded: typeof always;
254
254
  declare const unknown: Decoder<unknown>;
255
255
  /**
256
256
  * @deprecated
257
- * Alias of unknown.
257
+ * Alias of `unknown`.
258
258
  */
259
259
  declare const mixed: Decoder<unknown>;
260
260
 
@@ -268,6 +268,9 @@ declare const boolean: Decoder<boolean>;
268
268
  declare const truthy: Decoder<boolean>;
269
269
  /**
270
270
  * Accepts numbers, but return their boolean representation.
271
+ *
272
+ * @deprecated This decoder will be removed in a future version. You can use
273
+ * `truthy` to get almost the same effect.
271
274
  */
272
275
  declare const numericBoolean: Decoder<boolean>;
273
276
 
@@ -422,17 +425,22 @@ declare function inexact(decoders: Record<any, never>): Decoder<Record<string, u
422
425
  declare function inexact<Ds extends Record<string, Decoder<unknown>>>(decoders: Ds): Decoder<ObjectDecoderType<Ds> & Record<string, unknown>>;
423
426
  /**
424
427
  * Accepts objects where all values match the given decoder, and returns the
425
- * result as a `Record<string, T>`.
426
- *
427
- * The main difference between `object()` and `dict()` is that you'd typically
428
- * use `object()` if this is a record-like object, where all field names are
429
- * known and the values are heterogeneous. Whereas with `dict()` the keys are
430
- * typically dynamic and the values homogeneous, like in a dictionary,
431
- * a lookup table, or a cache.
428
+ * result as a `Record<string, V>`.
429
+ */
430
+ declare function record<V>(valueDecoder: Decoder<V>): Decoder<Record<string, V>>;
431
+ /**
432
+ * Accepts objects where all keys and values match the given decoders, and
433
+ * returns the result as a `Record<K, V>`. The given key decoder must return
434
+ * strings.
435
+ */
436
+ declare function record<K extends string, V>(keyDecoder: Decoder<K>, valueDecoder: Decoder<V>): Decoder<Record<K, V>>;
437
+ /**
438
+ * @deprecated
439
+ * Alias of `record()`.
432
440
  */
433
- declare function dict<T>(decoder: Decoder<T>): Decoder<Record<string, T>>;
441
+ declare const dict: typeof record;
434
442
  /**
435
- * Similar to `dict()`, but returns the result as a `Map<string, T>` (an [ES6
443
+ * Similar to `record()`, but returns the result as a `Map<string, T>` (an [ES6
436
444
  * Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map))
437
445
  * instead.
438
446
  */
@@ -482,6 +490,21 @@ declare const uuidv1: Decoder<string>;
482
490
  * strings.
483
491
  */
484
492
  declare const uuidv4: Decoder<string>;
493
+ /**
494
+ * Accepts and returns strings with decimal digits only (base-10).
495
+ * To convert these to numbers, use the `numeric` decoder.
496
+ */
497
+ declare const decimal: Decoder<string>;
498
+ /**
499
+ * Accepts and returns strings with hexadecimal digits only (base-16).
500
+ */
501
+ declare const hexadecimal: Decoder<string>;
502
+ /**
503
+ * Accepts valid numerical strings (in base-10) and returns them as a number.
504
+ * To only accept numerical strings and keep them as string values, use the
505
+ * `decimal` decoder.
506
+ */
507
+ declare const numeric: Decoder<number>;
485
508
 
486
509
  /**
487
510
  * Accepts values accepted by any of the given decoders.
@@ -496,6 +519,10 @@ declare function either<TDecoders extends readonly Decoder<unknown>[], T = Decod
496
519
  * specified values.
497
520
  */
498
521
  declare function oneOf<C extends Scalar>(constants: readonly C[]): Decoder<C>;
522
+ /**
523
+ * Accepts and return an enum value.
524
+ */
525
+ declare function enum_<TEnum extends Record<string, string | number>>(enumObj: TEnum): Decoder<TEnum[keyof TEnum]>;
499
526
  /**
500
527
  * If you are decoding tagged unions you may want to use the `taggedUnion()`
501
528
  * decoder instead of the general purpose `either()` decoder to get better
@@ -536,4 +563,4 @@ declare function taggedUnion<O extends Record<string, Decoder<unknown>>, T = Dec
536
563
  */
537
564
  declare function select<T, D extends Decoder<unknown>>(scout: Decoder<T>, selectFn: (result: T) => D): Decoder<DecoderType<D>>;
538
565
 
539
- 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, define, dict, either, email, err, exact, fail, formatInline, formatShort, hardcoded, httpsUrl, inexact, instanceOf, integer, iso8601, json, jsonArray, jsonObject, lazy, mapping, maybe, mixed, never, nonEmptyArray, nonEmptyString, null_, nullable, nullish, number, numericBoolean, object, ok, oneOf, optional, poja, pojo, positiveInteger, positiveNumber, prep, regex, select, set, string, taggedUnion, truthy, tuple, undefined_, unknown, url, uuid, uuidv1, uuidv4 };
566
+ 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, 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, numericBoolean, object, ok, oneOf, optional, poja, pojo, positiveInteger, positiveNumber, prep, record, regex, select, set, string, taggedUnion, truthy, tuple, undefined_, unknown, url, uuid, uuidv1, uuidv4 };
package/dist/index.d.ts CHANGED
@@ -206,7 +206,7 @@ declare function nullable<T, C extends Scalar>(decoder: Decoder<T>, defaultValue
206
206
  declare function nullable<T, V>(decoder: Decoder<T>, defaultValue: (() => V) | V): Decoder<NonNullable<T> | V>;
207
207
  /**
208
208
  * @deprecated
209
- * Alias of nullish().
209
+ * Alias of `nullish()`.
210
210
  */
211
211
  declare const maybe: typeof nullish;
212
212
  /**
@@ -236,12 +236,12 @@ declare function always<T>(value: (() => T) | T): Decoder<T>;
236
236
  */
237
237
  declare function never(msg: string): Decoder<never>;
238
238
  /**
239
- * Alias of never().
239
+ * Alias of `never()`.
240
240
  */
241
241
  declare const fail: typeof never;
242
242
  /**
243
243
  * @deprecated
244
- * Alias of always.
244
+ * Alias of `always()`.
245
245
  */
246
246
  declare const hardcoded: typeof always;
247
247
  /**
@@ -254,7 +254,7 @@ declare const hardcoded: typeof always;
254
254
  declare const unknown: Decoder<unknown>;
255
255
  /**
256
256
  * @deprecated
257
- * Alias of unknown.
257
+ * Alias of `unknown`.
258
258
  */
259
259
  declare const mixed: Decoder<unknown>;
260
260
 
@@ -268,6 +268,9 @@ declare const boolean: Decoder<boolean>;
268
268
  declare const truthy: Decoder<boolean>;
269
269
  /**
270
270
  * Accepts numbers, but return their boolean representation.
271
+ *
272
+ * @deprecated This decoder will be removed in a future version. You can use
273
+ * `truthy` to get almost the same effect.
271
274
  */
272
275
  declare const numericBoolean: Decoder<boolean>;
273
276
 
@@ -422,17 +425,22 @@ declare function inexact(decoders: Record<any, never>): Decoder<Record<string, u
422
425
  declare function inexact<Ds extends Record<string, Decoder<unknown>>>(decoders: Ds): Decoder<ObjectDecoderType<Ds> & Record<string, unknown>>;
423
426
  /**
424
427
  * Accepts objects where all values match the given decoder, and returns the
425
- * result as a `Record<string, T>`.
426
- *
427
- * The main difference between `object()` and `dict()` is that you'd typically
428
- * use `object()` if this is a record-like object, where all field names are
429
- * known and the values are heterogeneous. Whereas with `dict()` the keys are
430
- * typically dynamic and the values homogeneous, like in a dictionary,
431
- * a lookup table, or a cache.
428
+ * result as a `Record<string, V>`.
429
+ */
430
+ declare function record<V>(valueDecoder: Decoder<V>): Decoder<Record<string, V>>;
431
+ /**
432
+ * Accepts objects where all keys and values match the given decoders, and
433
+ * returns the result as a `Record<K, V>`. The given key decoder must return
434
+ * strings.
435
+ */
436
+ declare function record<K extends string, V>(keyDecoder: Decoder<K>, valueDecoder: Decoder<V>): Decoder<Record<K, V>>;
437
+ /**
438
+ * @deprecated
439
+ * Alias of `record()`.
432
440
  */
433
- declare function dict<T>(decoder: Decoder<T>): Decoder<Record<string, T>>;
441
+ declare const dict: typeof record;
434
442
  /**
435
- * Similar to `dict()`, but returns the result as a `Map<string, T>` (an [ES6
443
+ * Similar to `record()`, but returns the result as a `Map<string, T>` (an [ES6
436
444
  * Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map))
437
445
  * instead.
438
446
  */
@@ -482,6 +490,21 @@ declare const uuidv1: Decoder<string>;
482
490
  * strings.
483
491
  */
484
492
  declare const uuidv4: Decoder<string>;
493
+ /**
494
+ * Accepts and returns strings with decimal digits only (base-10).
495
+ * To convert these to numbers, use the `numeric` decoder.
496
+ */
497
+ declare const decimal: Decoder<string>;
498
+ /**
499
+ * Accepts and returns strings with hexadecimal digits only (base-16).
500
+ */
501
+ declare const hexadecimal: Decoder<string>;
502
+ /**
503
+ * Accepts valid numerical strings (in base-10) and returns them as a number.
504
+ * To only accept numerical strings and keep them as string values, use the
505
+ * `decimal` decoder.
506
+ */
507
+ declare const numeric: Decoder<number>;
485
508
 
486
509
  /**
487
510
  * Accepts values accepted by any of the given decoders.
@@ -496,6 +519,10 @@ declare function either<TDecoders extends readonly Decoder<unknown>[], T = Decod
496
519
  * specified values.
497
520
  */
498
521
  declare function oneOf<C extends Scalar>(constants: readonly C[]): Decoder<C>;
522
+ /**
523
+ * Accepts and return an enum value.
524
+ */
525
+ declare function enum_<TEnum extends Record<string, string | number>>(enumObj: TEnum): Decoder<TEnum[keyof TEnum]>;
499
526
  /**
500
527
  * If you are decoding tagged unions you may want to use the `taggedUnion()`
501
528
  * decoder instead of the general purpose `either()` decoder to get better
@@ -536,4 +563,4 @@ declare function taggedUnion<O extends Record<string, Decoder<unknown>>, T = Dec
536
563
  */
537
564
  declare function select<T, D extends Decoder<unknown>>(scout: Decoder<T>, selectFn: (result: T) => D): Decoder<DecoderType<D>>;
538
565
 
539
- 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, define, dict, either, email, err, exact, fail, formatInline, formatShort, hardcoded, httpsUrl, inexact, instanceOf, integer, iso8601, json, jsonArray, jsonObject, lazy, mapping, maybe, mixed, never, nonEmptyArray, nonEmptyString, null_, nullable, nullish, number, numericBoolean, object, ok, oneOf, optional, poja, pojo, positiveInteger, positiveNumber, prep, regex, select, set, string, taggedUnion, truthy, tuple, undefined_, unknown, url, uuid, uuidv1, uuidv4 };
566
+ 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, 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, numericBoolean, object, ok, oneOf, optional, poja, pojo, positiveInteger, positiveNumber, prep, record, regex, select, set, string, taggedUnion, truthy, tuple, undefined_, unknown, url, uuid, uuidv1, uuidv4 };
package/dist/index.js CHANGED
@@ -1,4 +1,13 @@
1
1
  // src/lib/utils.ts
2
+ function isNumber(value) {
3
+ return typeof value === "number";
4
+ }
5
+ function isString(value) {
6
+ return typeof value === "string";
7
+ }
8
+ function isBigInt(value) {
9
+ return typeof value === "bigint";
10
+ }
2
11
  function isDate(value) {
3
12
  return !!value && Object.prototype.toString.call(value) === "[object Date]" && !isNaN(value);
4
13
  }
@@ -453,7 +462,7 @@ function object(decoders) {
453
462
  return pojo.then((plainObj, ok2, err2) => {
454
463
  const actualKeys = new Set(Object.keys(plainObj));
455
464
  const missingKeys = difference(knownKeys, actualKeys);
456
- const record = {};
465
+ const record2 = {};
457
466
  let errors = null;
458
467
  for (const key of Object.keys(decoders)) {
459
468
  const decoder = decoders[key];
@@ -462,7 +471,7 @@ function object(decoders) {
462
471
  if (result.ok) {
463
472
  const value = result.value;
464
473
  if (value !== void 0) {
465
- record[key] = value;
474
+ record2[key] = value;
466
475
  }
467
476
  missingKeys.delete(key);
468
477
  } else {
@@ -489,7 +498,7 @@ function object(decoders) {
489
498
  }
490
499
  return err2(objAnn);
491
500
  }
492
- return ok2(record);
501
+ return ok2(record2);
493
502
  });
494
503
  }
495
504
  function exact(decoders) {
@@ -527,40 +536,43 @@ function inexact(decoders) {
527
536
  return decoder.decode(plainObj);
528
537
  });
529
538
  }
530
- function dict(decoder) {
531
- return pojo.then((plainObj, ok2, err2) => {
539
+ function record(fst, snd) {
540
+ const keyDecoder = snd !== void 0 ? fst : void 0;
541
+ const valueDecoder = snd !== void 0 ? snd : fst;
542
+ return pojo.then((rec, ok2, err2) => {
532
543
  let rv = {};
533
- let errors = null;
534
- for (const key of Object.keys(plainObj)) {
535
- const value = plainObj[key];
536
- const result = decoder.decode(value);
544
+ const errors = /* @__PURE__ */ new Map();
545
+ for (const [key, value] of Object.entries(rec)) {
546
+ const keyResult = keyDecoder?.decode(key);
547
+ if (keyResult?.ok === false) {
548
+ return err2(
549
+ public_annotate(
550
+ rec,
551
+ `Invalid key ${JSON.stringify(key)}: ${formatShort(keyResult.error)}`
552
+ )
553
+ );
554
+ }
555
+ const k = keyResult?.value ?? key;
556
+ const result = valueDecoder.decode(value);
537
557
  if (result.ok) {
538
- if (errors === null) {
539
- rv[key] = result.value;
558
+ if (errors.size === 0) {
559
+ rv[k] = result.value;
540
560
  }
541
561
  } else {
542
- rv = {};
543
- if (errors === null) {
544
- errors = /* @__PURE__ */ new Map();
545
- }
546
562
  errors.set(key, result.error);
563
+ rv = {};
547
564
  }
548
565
  }
549
- if (errors !== null) {
550
- return err2(merge(public_annotateObject(plainObj), errors));
566
+ if (errors.size > 0) {
567
+ return err2(merge(public_annotateObject(rec), errors));
551
568
  } else {
552
569
  return ok2(rv);
553
570
  }
554
571
  });
555
572
  }
573
+ var dict = record;
556
574
  function mapping(decoder) {
557
- return dict(decoder).transform(
558
- (obj) => new Map(
559
- // This is effectively Object.entries(obj), but in a way that Flow
560
- // will know the types are okay
561
- Object.keys(obj).map((key) => [key, obj[key]])
562
- )
563
- );
575
+ return record(decoder).transform((obj) => new Map(Object.entries(obj)));
564
576
  }
565
577
 
566
578
  // src/unions.ts
@@ -600,6 +612,17 @@ function oneOf(constants) {
600
612
  );
601
613
  });
602
614
  }
615
+ function enum_(enumObj) {
616
+ const values = Object.values(enumObj);
617
+ if (!values.some(isNumber)) {
618
+ return oneOf(values);
619
+ } else {
620
+ const nums = values.filter(isNumber);
621
+ const ignore = new Set(nums.map((val) => enumObj[val]));
622
+ const strings = values.filter(isString).filter((val) => !ignore.has(val));
623
+ return oneOf([...nums, ...strings]);
624
+ }
625
+ }
603
626
  function taggedUnion(field, mapping2) {
604
627
  const scout = object({
605
628
  [field]: prep(String, oneOf(Object.keys(mapping2)))
@@ -625,9 +648,9 @@ function lazyval(value) {
625
648
  var null_ = constant(null);
626
649
  var undefined_ = constant(void 0);
627
650
  var nullish_ = define(
628
- (blob, ok2, err2) => blob == null ? ok2(blob) : (
629
- // Combine error message into a single line for readability
630
- err2("Must be undefined or null")
651
+ (blob, ok2, err2) => (
652
+ // Equiv to either(undefined_, null_), but combined for better error message
653
+ blob == null ? ok2(blob) : err2("Must be undefined or null")
631
654
  )
632
655
  );
633
656
  function optional(decoder, defaultValue) {
@@ -665,7 +688,7 @@ var mixed = unknown;
665
688
 
666
689
  // src/numbers.ts
667
690
  var anyNumber = define(
668
- (blob, ok2, err2) => typeof blob === "number" ? ok2(blob) : err2("Must be number")
691
+ (blob, ok2, err2) => isNumber(blob) ? ok2(blob) : err2("Must be number")
669
692
  );
670
693
  var number = anyNumber.refine(
671
694
  (n) => Number.isFinite(n),
@@ -678,7 +701,7 @@ var integer = number.refine(
678
701
  var positiveNumber = number.refine((n) => n >= 0, "Number must be positive").transform(Math.abs);
679
702
  var positiveInteger = integer.refine((n) => n >= 0, "Number must be positive").transform(Math.abs);
680
703
  var bigint = define(
681
- (blob, ok2, err2) => typeof blob === "bigint" ? ok2(blob) : err2("Must be bigint")
704
+ (blob, ok2, err2) => isBigInt(blob) ? ok2(blob) : err2("Must be bigint")
682
705
  );
683
706
 
684
707
  // src/booleans.ts
@@ -691,7 +714,7 @@ var numericBoolean = number.transform((n) => !!n);
691
714
  // src/strings.ts
692
715
  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]*)?)?$/;
693
716
  var string = define(
694
- (blob, ok2, err2) => typeof blob === "string" ? ok2(blob) : err2("Must be string")
717
+ (blob, ok2, err2) => isString(blob) ? ok2(blob) : err2("Must be string")
695
718
  );
696
719
  var nonEmptyString = regex(/\S/, "Must be non-empty string");
697
720
  function regex(regex2, msg) {
@@ -722,6 +745,12 @@ var uuidv4 = (
722
745
  // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)
723
746
  uuid.refine((value) => value[14] === "4", "Must be uuidv4")
724
747
  );
748
+ var decimal = regex(/^[0-9]+$/, "Must only contain digits");
749
+ var hexadecimal = regex(
750
+ /^[0-9a-f]+$/i,
751
+ "Must only contain hexadecimal digits"
752
+ );
753
+ var numeric = decimal.transform(Number);
725
754
 
726
755
  // src/dates.ts
727
756
  var iso8601_re = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[.]\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
@@ -743,7 +772,7 @@ var iso8601 = (
743
772
  );
744
773
 
745
774
  // src/json.ts
746
- var jsonObject = lazy(() => dict(json));
775
+ var jsonObject = lazy(() => record(json));
747
776
  var jsonArray = lazy(() => array(json));
748
777
  var json = either(
749
778
  null_,
@@ -761,16 +790,19 @@ export {
761
790
  boolean,
762
791
  constant,
763
792
  date,
793
+ decimal,
764
794
  define,
765
795
  dict,
766
796
  either,
767
797
  email,
798
+ enum_,
768
799
  err,
769
800
  exact,
770
801
  fail,
771
802
  formatInline,
772
803
  formatShort,
773
804
  hardcoded,
805
+ hexadecimal,
774
806
  httpsUrl,
775
807
  inexact,
776
808
  instanceOf,
@@ -790,6 +822,7 @@ export {
790
822
  nullable,
791
823
  nullish,
792
824
  number,
825
+ numeric,
793
826
  numericBoolean,
794
827
  object,
795
828
  ok,
@@ -800,6 +833,7 @@ export {
800
833
  positiveInteger,
801
834
  positiveNumber,
802
835
  prep,
836
+ record,
803
837
  regex,
804
838
  select,
805
839
  set,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decoders",
3
- "version": "2.3.0-beta.1",
3
+ "version": "2.3.0-beta.2",
4
4
  "description": "Elegant and battle-tested validation library for type-safe input data for TypeScript",
5
5
  "license": "MIT",
6
6
  "repository": {