decoders 2.3.0-beta.1 → 2.3.0-beta.3

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
  }
@@ -380,9 +389,6 @@ function isNonEmpty(arr) {
380
389
  function nonEmptyArray(decoder) {
381
390
  return array(decoder).refine(isNonEmpty, "Must be non-empty array");
382
391
  }
383
- function set(decoder) {
384
- return array(decoder).transform((items) => new Set(items));
385
- }
386
392
  var ntuple = (n) => poja.refine((arr) => arr.length === n, `Must be a ${n}-tuple`);
387
393
  function tuple(...decoders) {
388
394
  return ntuple(decoders.length).then((blobs, ok2, err2) => {
@@ -453,7 +459,7 @@ function object(decoders) {
453
459
  return pojo.then((plainObj, ok2, err2) => {
454
460
  const actualKeys = new Set(Object.keys(plainObj));
455
461
  const missingKeys = difference(knownKeys, actualKeys);
456
- const record = {};
462
+ const record2 = {};
457
463
  let errors = null;
458
464
  for (const key of Object.keys(decoders)) {
459
465
  const decoder = decoders[key];
@@ -462,7 +468,7 @@ function object(decoders) {
462
468
  if (result.ok) {
463
469
  const value = result.value;
464
470
  if (value !== void 0) {
465
- record[key] = value;
471
+ record2[key] = value;
466
472
  }
467
473
  missingKeys.delete(key);
468
474
  } else {
@@ -489,7 +495,7 @@ function object(decoders) {
489
495
  }
490
496
  return err2(objAnn);
491
497
  }
492
- return ok2(record);
498
+ return ok2(record2);
493
499
  });
494
500
  }
495
501
  function exact(decoders) {
@@ -527,41 +533,6 @@ function inexact(decoders) {
527
533
  return decoder.decode(plainObj);
528
534
  });
529
535
  }
530
- function dict(decoder) {
531
- return pojo.then((plainObj, ok2, err2) => {
532
- 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);
537
- if (result.ok) {
538
- if (errors === null) {
539
- rv[key] = result.value;
540
- }
541
- } else {
542
- rv = {};
543
- if (errors === null) {
544
- errors = /* @__PURE__ */ new Map();
545
- }
546
- errors.set(key, result.error);
547
- }
548
- }
549
- if (errors !== null) {
550
- return err2(merge(public_annotateObject(plainObj), errors));
551
- } else {
552
- return ok2(rv);
553
- }
554
- });
555
- }
556
- 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
- );
564
- }
565
536
 
566
537
  // src/unions.ts
567
538
  var EITHER_PREFIX = "Either:\n";
@@ -600,6 +571,17 @@ function oneOf(constants) {
600
571
  );
601
572
  });
602
573
  }
574
+ function enum_(enumObj) {
575
+ const values = Object.values(enumObj);
576
+ if (!values.some(isNumber)) {
577
+ return oneOf(values);
578
+ } else {
579
+ const nums = values.filter(isNumber);
580
+ const ignore = new Set(nums.map((val) => enumObj[val]));
581
+ const strings = values.filter(isString).filter((val) => !ignore.has(val));
582
+ return oneOf([...nums, ...strings]);
583
+ }
584
+ }
603
585
  function taggedUnion(field, mapping2) {
604
586
  const scout = object({
605
587
  [field]: prep(String, oneOf(Object.keys(mapping2)))
@@ -625,9 +607,9 @@ function lazyval(value) {
625
607
  var null_ = constant(null);
626
608
  var undefined_ = constant(void 0);
627
609
  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")
610
+ (blob, ok2, err2) => (
611
+ // Equiv to either(undefined_, null_), but combined for better error message
612
+ blob == null ? ok2(blob) : err2("Must be undefined or null")
631
613
  )
632
614
  );
633
615
  function optional(decoder, defaultValue) {
@@ -665,7 +647,7 @@ var mixed = unknown;
665
647
 
666
648
  // src/numbers.ts
667
649
  var anyNumber = define(
668
- (blob, ok2, err2) => typeof blob === "number" ? ok2(blob) : err2("Must be number")
650
+ (blob, ok2, err2) => isNumber(blob) ? ok2(blob) : err2("Must be number")
669
651
  );
670
652
  var number = anyNumber.refine(
671
653
  (n) => Number.isFinite(n),
@@ -675,10 +657,16 @@ var integer = number.refine(
675
657
  (n) => Number.isInteger(n),
676
658
  "Number must be an integer"
677
659
  );
678
- var positiveNumber = number.refine((n) => n >= 0, "Number must be positive").transform(Math.abs);
679
- var positiveInteger = integer.refine((n) => n >= 0, "Number must be positive").transform(Math.abs);
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
+ );
680
668
  var bigint = define(
681
- (blob, ok2, err2) => typeof blob === "bigint" ? ok2(blob) : err2("Must be bigint")
669
+ (blob, ok2, err2) => isBigInt(blob) ? ok2(blob) : err2("Must be bigint")
682
670
  );
683
671
 
684
672
  // src/booleans.ts
@@ -688,10 +676,53 @@ var boolean = define((blob, ok2, err2) => {
688
676
  var truthy = define((blob, ok2, _) => ok2(!!blob));
689
677
  var numericBoolean = number.transform((n) => !!n);
690
678
 
679
+ // src/collections.ts
680
+ function record(fst, snd) {
681
+ const keyDecoder = snd !== void 0 ? fst : void 0;
682
+ const valueDecoder = snd !== void 0 ? snd : fst;
683
+ return pojo.then((input, ok2, err2) => {
684
+ let rv = {};
685
+ const errors = /* @__PURE__ */ new Map();
686
+ for (const [key, value] of Object.entries(input)) {
687
+ const keyResult = _optionalChain([keyDecoder, 'optionalAccess', _2 => _2.decode, 'call', _3 => _3(key)]);
688
+ if (_optionalChain([keyResult, 'optionalAccess', _4 => _4.ok]) === false) {
689
+ return err2(
690
+ public_annotate(
691
+ input,
692
+ `Invalid key ${JSON.stringify(key)}: ${formatShort(keyResult.error)}`
693
+ )
694
+ );
695
+ }
696
+ const k = _nullishCoalesce(_optionalChain([keyResult, 'optionalAccess', _5 => _5.value]), () => ( key));
697
+ const result = valueDecoder.decode(value);
698
+ if (result.ok) {
699
+ if (errors.size === 0) {
700
+ rv[k] = result.value;
701
+ }
702
+ } else {
703
+ errors.set(key, result.error);
704
+ rv = {};
705
+ }
706
+ }
707
+ if (errors.size > 0) {
708
+ return err2(merge(public_annotateObject(input), errors));
709
+ } else {
710
+ return ok2(rv);
711
+ }
712
+ });
713
+ }
714
+ var dict = record;
715
+ function set(decoder) {
716
+ return array(decoder).transform((items) => new Set(items));
717
+ }
718
+ function mapping(decoder) {
719
+ return record(decoder).transform((obj) => new Map(Object.entries(obj)));
720
+ }
721
+
691
722
  // src/strings.ts
692
723
  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
724
  var string = define(
694
- (blob, ok2, err2) => typeof blob === "string" ? ok2(blob) : err2("Must be string")
725
+ (blob, ok2, err2) => isString(blob) ? ok2(blob) : err2("Must be string")
695
726
  );
696
727
  var nonEmptyString = regex(/\S/, "Must be non-empty string");
697
728
  function regex(regex2, msg) {
@@ -722,6 +753,12 @@ var uuidv4 = (
722
753
  // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)
723
754
  uuid.refine((value) => value[14] === "4", "Must be uuidv4")
724
755
  );
756
+ var decimal = regex(/^[0-9]+$/, "Must only contain digits");
757
+ var hexadecimal = regex(
758
+ /^[0-9a-f]+$/i,
759
+ "Must only contain hexadecimal digits"
760
+ );
761
+ var numeric = decimal.transform(Number);
725
762
 
726
763
  // src/dates.ts
727
764
  var iso8601_re = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[.]\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
@@ -741,9 +778,10 @@ var iso8601 = (
741
778
  }
742
779
  )
743
780
  );
781
+ var datelike = either(date, iso8601).describe("Must be a Date or date string");
744
782
 
745
783
  // src/json.ts
746
- var jsonObject = lazy(() => dict(json));
784
+ var jsonObject = lazy(() => record(json));
747
785
  var jsonArray = lazy(() => array(json));
748
786
  var json = either(
749
787
  null_,
@@ -813,4 +851,10 @@ var json = either(
813
851
 
814
852
 
815
853
 
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;
854
+
855
+
856
+
857
+
858
+
859
+
860
+ 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.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
@@ -162,11 +162,6 @@ declare function array<T>(decoder: Decoder<T>): Decoder<T[]>;
162
162
  * Like `array()`, but will reject arrays with 0 elements.
163
163
  */
164
164
  declare function nonEmptyArray<T>(decoder: Decoder<T>): Decoder<[T, ...T[]]>;
165
- /**
166
- * Similar to `array()`, but returns the result as an [ES6
167
- * Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).
168
- */
169
- declare function set<T>(decoder: Decoder<T>): Decoder<Set<T>>;
170
165
  type TupleDecoderType<Ds extends readonly Decoder<unknown>[]> = {
171
166
  [K in keyof Ds]: DecoderType<Ds[K]>;
172
167
  };
@@ -205,8 +200,9 @@ declare function nullable<T>(decoder: Decoder<T>): Decoder<T | null>;
205
200
  declare function nullable<T, C extends Scalar>(decoder: Decoder<T>, defaultValue: (() => C) | C): Decoder<NonNullable<T> | C>;
206
201
  declare function nullable<T, V>(decoder: Decoder<T>, defaultValue: (() => V) | V): Decoder<NonNullable<T> | V>;
207
202
  /**
208
- * @deprecated
209
- * Alias of nullish().
203
+ * @deprecated Will get removed in a future version.
204
+ *
205
+ * Alias of `nullish()`.
210
206
  */
211
207
  declare const maybe: typeof nullish;
212
208
  /**
@@ -236,12 +232,13 @@ declare function always<T>(value: (() => T) | T): Decoder<T>;
236
232
  */
237
233
  declare function never(msg: string): Decoder<never>;
238
234
  /**
239
- * Alias of never().
235
+ * Alias of `never()`.
240
236
  */
241
237
  declare const fail: typeof never;
242
238
  /**
243
- * @deprecated
244
- * Alias of always.
239
+ * Alias of `always()`.
240
+ *
241
+ * @deprecated Will get removed in a future version.
245
242
  */
246
243
  declare const hardcoded: typeof always;
247
244
  /**
@@ -253,8 +250,9 @@ declare const hardcoded: typeof always;
253
250
  */
254
251
  declare const unknown: Decoder<unknown>;
255
252
  /**
256
- * @deprecated
257
- * Alias of unknown.
253
+ * Alias of `unknown`.
254
+ *
255
+ * @deprecated Will get removed in a future version.
258
256
  */
259
257
  declare const mixed: Decoder<unknown>;
260
258
 
@@ -268,9 +266,41 @@ declare const boolean: Decoder<boolean>;
268
266
  declare const truthy: Decoder<boolean>;
269
267
  /**
270
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.
271
272
  */
272
273
  declare const numericBoolean: Decoder<boolean>;
273
274
 
275
+ /**
276
+ * Accepts objects where all values match the given decoder, and returns the
277
+ * result as a `Record<string, V>`.
278
+ */
279
+ declare function record<V>(valueDecoder: Decoder<V>): Decoder<Record<string, V>>;
280
+ /**
281
+ * Accepts objects where all keys and values match the given decoders, and
282
+ * returns the result as a `Record<K, V>`. The given key decoder must return
283
+ * strings.
284
+ */
285
+ declare function record<K extends string, V>(keyDecoder: Decoder<K>, valueDecoder: Decoder<V>): Decoder<Record<K, V>>;
286
+ /**
287
+ * @deprecated Will get removed in a future version.
288
+ *
289
+ * Alias of `record()`.
290
+ */
291
+ declare const dict: typeof record;
292
+ /**
293
+ * Similar to `array()`, but returns the result as an [ES6
294
+ * Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).
295
+ */
296
+ declare function set<T>(decoder: Decoder<T>): Decoder<Set<T>>;
297
+ /**
298
+ * Similar to `record()`, but returns the result as a `Map<string, T>` (an [ES6
299
+ * Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map))
300
+ * instead.
301
+ */
302
+ declare function mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>>;
303
+
274
304
  /**
275
305
  * Accepts and returns `Date` instances.
276
306
  */
@@ -283,6 +313,12 @@ declare const date: Decoder<Date>;
283
313
  * `.toISOString()` when sending, decode them with `iso8601` when receiving.
284
314
  */
285
315
  declare const iso8601: Decoder<Date>;
316
+ /**
317
+ * Accepts either a Date, or an ISO date string, returns a Date instance.
318
+ * This is commonly useful to build decoders that can be reused to validate
319
+ * object with Date instances as well as objects coming from JSON payloads.
320
+ */
321
+ declare const datelike: Decoder<Date>;
286
322
 
287
323
  type JSONValue = null | string | number | boolean | JSONObject | JSONArray;
288
324
  type JSONObject = {
@@ -420,23 +456,6 @@ declare function exact<Ds extends Record<string, Decoder<unknown>>>(decoders: Ds
420
456
  */
421
457
  declare function inexact(decoders: Record<any, never>): Decoder<Record<string, unknown>>;
422
458
  declare function inexact<Ds extends Record<string, Decoder<unknown>>>(decoders: Ds): Decoder<ObjectDecoderType<Ds> & Record<string, unknown>>;
423
- /**
424
- * 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.
432
- */
433
- declare function dict<T>(decoder: Decoder<T>): Decoder<Record<string, T>>;
434
- /**
435
- * Similar to `dict()`, but returns the result as a `Map<string, T>` (an [ES6
436
- * Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map))
437
- * instead.
438
- */
439
- declare function mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>>;
440
459
 
441
460
  /**
442
461
  * Accepts and returns strings.
@@ -482,6 +501,21 @@ declare const uuidv1: Decoder<string>;
482
501
  * strings.
483
502
  */
484
503
  declare const uuidv4: Decoder<string>;
504
+ /**
505
+ * Accepts and returns strings with decimal digits only (base-10).
506
+ * To convert these to numbers, use the `numeric` decoder.
507
+ */
508
+ declare const decimal: Decoder<string>;
509
+ /**
510
+ * Accepts and returns strings with hexadecimal digits only (base-16).
511
+ */
512
+ declare const hexadecimal: Decoder<string>;
513
+ /**
514
+ * Accepts valid numerical strings (in base-10) and returns them as a number.
515
+ * To only accept numerical strings and keep them as string values, use the
516
+ * `decimal` decoder.
517
+ */
518
+ declare const numeric: Decoder<number>;
485
519
 
486
520
  /**
487
521
  * Accepts values accepted by any of the given decoders.
@@ -496,6 +530,10 @@ declare function either<TDecoders extends readonly Decoder<unknown>[], T = Decod
496
530
  * specified values.
497
531
  */
498
532
  declare function oneOf<C extends Scalar>(constants: readonly C[]): Decoder<C>;
533
+ /**
534
+ * Accepts and return an enum value.
535
+ */
536
+ declare function enum_<TEnum extends Record<string, string | number>>(enumObj: TEnum): Decoder<TEnum[keyof TEnum]>;
499
537
  /**
500
538
  * If you are decoding tagged unions you may want to use the `taggedUnion()`
501
539
  * decoder instead of the general purpose `either()` decoder to get better
@@ -536,4 +574,4 @@ declare function taggedUnion<O extends Record<string, Decoder<unknown>>, T = Dec
536
574
  */
537
575
  declare function select<T, D extends Decoder<unknown>>(scout: Decoder<T>, selectFn: (result: T) => D): Decoder<DecoderType<D>>;
538
576
 
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 };
577
+ 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, 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
@@ -162,11 +162,6 @@ declare function array<T>(decoder: Decoder<T>): Decoder<T[]>;
162
162
  * Like `array()`, but will reject arrays with 0 elements.
163
163
  */
164
164
  declare function nonEmptyArray<T>(decoder: Decoder<T>): Decoder<[T, ...T[]]>;
165
- /**
166
- * Similar to `array()`, but returns the result as an [ES6
167
- * Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).
168
- */
169
- declare function set<T>(decoder: Decoder<T>): Decoder<Set<T>>;
170
165
  type TupleDecoderType<Ds extends readonly Decoder<unknown>[]> = {
171
166
  [K in keyof Ds]: DecoderType<Ds[K]>;
172
167
  };
@@ -205,8 +200,9 @@ declare function nullable<T>(decoder: Decoder<T>): Decoder<T | null>;
205
200
  declare function nullable<T, C extends Scalar>(decoder: Decoder<T>, defaultValue: (() => C) | C): Decoder<NonNullable<T> | C>;
206
201
  declare function nullable<T, V>(decoder: Decoder<T>, defaultValue: (() => V) | V): Decoder<NonNullable<T> | V>;
207
202
  /**
208
- * @deprecated
209
- * Alias of nullish().
203
+ * @deprecated Will get removed in a future version.
204
+ *
205
+ * Alias of `nullish()`.
210
206
  */
211
207
  declare const maybe: typeof nullish;
212
208
  /**
@@ -236,12 +232,13 @@ declare function always<T>(value: (() => T) | T): Decoder<T>;
236
232
  */
237
233
  declare function never(msg: string): Decoder<never>;
238
234
  /**
239
- * Alias of never().
235
+ * Alias of `never()`.
240
236
  */
241
237
  declare const fail: typeof never;
242
238
  /**
243
- * @deprecated
244
- * Alias of always.
239
+ * Alias of `always()`.
240
+ *
241
+ * @deprecated Will get removed in a future version.
245
242
  */
246
243
  declare const hardcoded: typeof always;
247
244
  /**
@@ -253,8 +250,9 @@ declare const hardcoded: typeof always;
253
250
  */
254
251
  declare const unknown: Decoder<unknown>;
255
252
  /**
256
- * @deprecated
257
- * Alias of unknown.
253
+ * Alias of `unknown`.
254
+ *
255
+ * @deprecated Will get removed in a future version.
258
256
  */
259
257
  declare const mixed: Decoder<unknown>;
260
258
 
@@ -268,9 +266,41 @@ declare const boolean: Decoder<boolean>;
268
266
  declare const truthy: Decoder<boolean>;
269
267
  /**
270
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.
271
272
  */
272
273
  declare const numericBoolean: Decoder<boolean>;
273
274
 
275
+ /**
276
+ * Accepts objects where all values match the given decoder, and returns the
277
+ * result as a `Record<string, V>`.
278
+ */
279
+ declare function record<V>(valueDecoder: Decoder<V>): Decoder<Record<string, V>>;
280
+ /**
281
+ * Accepts objects where all keys and values match the given decoders, and
282
+ * returns the result as a `Record<K, V>`. The given key decoder must return
283
+ * strings.
284
+ */
285
+ declare function record<K extends string, V>(keyDecoder: Decoder<K>, valueDecoder: Decoder<V>): Decoder<Record<K, V>>;
286
+ /**
287
+ * @deprecated Will get removed in a future version.
288
+ *
289
+ * Alias of `record()`.
290
+ */
291
+ declare const dict: typeof record;
292
+ /**
293
+ * Similar to `array()`, but returns the result as an [ES6
294
+ * Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).
295
+ */
296
+ declare function set<T>(decoder: Decoder<T>): Decoder<Set<T>>;
297
+ /**
298
+ * Similar to `record()`, but returns the result as a `Map<string, T>` (an [ES6
299
+ * Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map))
300
+ * instead.
301
+ */
302
+ declare function mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>>;
303
+
274
304
  /**
275
305
  * Accepts and returns `Date` instances.
276
306
  */
@@ -283,6 +313,12 @@ declare const date: Decoder<Date>;
283
313
  * `.toISOString()` when sending, decode them with `iso8601` when receiving.
284
314
  */
285
315
  declare const iso8601: Decoder<Date>;
316
+ /**
317
+ * Accepts either a Date, or an ISO date string, returns a Date instance.
318
+ * This is commonly useful to build decoders that can be reused to validate
319
+ * object with Date instances as well as objects coming from JSON payloads.
320
+ */
321
+ declare const datelike: Decoder<Date>;
286
322
 
287
323
  type JSONValue = null | string | number | boolean | JSONObject | JSONArray;
288
324
  type JSONObject = {
@@ -420,23 +456,6 @@ declare function exact<Ds extends Record<string, Decoder<unknown>>>(decoders: Ds
420
456
  */
421
457
  declare function inexact(decoders: Record<any, never>): Decoder<Record<string, unknown>>;
422
458
  declare function inexact<Ds extends Record<string, Decoder<unknown>>>(decoders: Ds): Decoder<ObjectDecoderType<Ds> & Record<string, unknown>>;
423
- /**
424
- * 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.
432
- */
433
- declare function dict<T>(decoder: Decoder<T>): Decoder<Record<string, T>>;
434
- /**
435
- * Similar to `dict()`, but returns the result as a `Map<string, T>` (an [ES6
436
- * Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map))
437
- * instead.
438
- */
439
- declare function mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>>;
440
459
 
441
460
  /**
442
461
  * Accepts and returns strings.
@@ -482,6 +501,21 @@ declare const uuidv1: Decoder<string>;
482
501
  * strings.
483
502
  */
484
503
  declare const uuidv4: Decoder<string>;
504
+ /**
505
+ * Accepts and returns strings with decimal digits only (base-10).
506
+ * To convert these to numbers, use the `numeric` decoder.
507
+ */
508
+ declare const decimal: Decoder<string>;
509
+ /**
510
+ * Accepts and returns strings with hexadecimal digits only (base-16).
511
+ */
512
+ declare const hexadecimal: Decoder<string>;
513
+ /**
514
+ * Accepts valid numerical strings (in base-10) and returns them as a number.
515
+ * To only accept numerical strings and keep them as string values, use the
516
+ * `decimal` decoder.
517
+ */
518
+ declare const numeric: Decoder<number>;
485
519
 
486
520
  /**
487
521
  * Accepts values accepted by any of the given decoders.
@@ -496,6 +530,10 @@ declare function either<TDecoders extends readonly Decoder<unknown>[], T = Decod
496
530
  * specified values.
497
531
  */
498
532
  declare function oneOf<C extends Scalar>(constants: readonly C[]): Decoder<C>;
533
+ /**
534
+ * Accepts and return an enum value.
535
+ */
536
+ declare function enum_<TEnum extends Record<string, string | number>>(enumObj: TEnum): Decoder<TEnum[keyof TEnum]>;
499
537
  /**
500
538
  * If you are decoding tagged unions you may want to use the `taggedUnion()`
501
539
  * decoder instead of the general purpose `either()` decoder to get better
@@ -536,4 +574,4 @@ declare function taggedUnion<O extends Record<string, Decoder<unknown>>, T = Dec
536
574
  */
537
575
  declare function select<T, D extends Decoder<unknown>>(scout: Decoder<T>, selectFn: (result: T) => D): Decoder<DecoderType<D>>;
538
576
 
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 };
577
+ 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, 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
  }
@@ -380,9 +389,6 @@ function isNonEmpty(arr) {
380
389
  function nonEmptyArray(decoder) {
381
390
  return array(decoder).refine(isNonEmpty, "Must be non-empty array");
382
391
  }
383
- function set(decoder) {
384
- return array(decoder).transform((items) => new Set(items));
385
- }
386
392
  var ntuple = (n) => poja.refine((arr) => arr.length === n, `Must be a ${n}-tuple`);
387
393
  function tuple(...decoders) {
388
394
  return ntuple(decoders.length).then((blobs, ok2, err2) => {
@@ -453,7 +459,7 @@ function object(decoders) {
453
459
  return pojo.then((plainObj, ok2, err2) => {
454
460
  const actualKeys = new Set(Object.keys(plainObj));
455
461
  const missingKeys = difference(knownKeys, actualKeys);
456
- const record = {};
462
+ const record2 = {};
457
463
  let errors = null;
458
464
  for (const key of Object.keys(decoders)) {
459
465
  const decoder = decoders[key];
@@ -462,7 +468,7 @@ function object(decoders) {
462
468
  if (result.ok) {
463
469
  const value = result.value;
464
470
  if (value !== void 0) {
465
- record[key] = value;
471
+ record2[key] = value;
466
472
  }
467
473
  missingKeys.delete(key);
468
474
  } else {
@@ -489,7 +495,7 @@ function object(decoders) {
489
495
  }
490
496
  return err2(objAnn);
491
497
  }
492
- return ok2(record);
498
+ return ok2(record2);
493
499
  });
494
500
  }
495
501
  function exact(decoders) {
@@ -527,41 +533,6 @@ function inexact(decoders) {
527
533
  return decoder.decode(plainObj);
528
534
  });
529
535
  }
530
- function dict(decoder) {
531
- return pojo.then((plainObj, ok2, err2) => {
532
- 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);
537
- if (result.ok) {
538
- if (errors === null) {
539
- rv[key] = result.value;
540
- }
541
- } else {
542
- rv = {};
543
- if (errors === null) {
544
- errors = /* @__PURE__ */ new Map();
545
- }
546
- errors.set(key, result.error);
547
- }
548
- }
549
- if (errors !== null) {
550
- return err2(merge(public_annotateObject(plainObj), errors));
551
- } else {
552
- return ok2(rv);
553
- }
554
- });
555
- }
556
- 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
- );
564
- }
565
536
 
566
537
  // src/unions.ts
567
538
  var EITHER_PREFIX = "Either:\n";
@@ -600,6 +571,17 @@ function oneOf(constants) {
600
571
  );
601
572
  });
602
573
  }
574
+ function enum_(enumObj) {
575
+ const values = Object.values(enumObj);
576
+ if (!values.some(isNumber)) {
577
+ return oneOf(values);
578
+ } else {
579
+ const nums = values.filter(isNumber);
580
+ const ignore = new Set(nums.map((val) => enumObj[val]));
581
+ const strings = values.filter(isString).filter((val) => !ignore.has(val));
582
+ return oneOf([...nums, ...strings]);
583
+ }
584
+ }
603
585
  function taggedUnion(field, mapping2) {
604
586
  const scout = object({
605
587
  [field]: prep(String, oneOf(Object.keys(mapping2)))
@@ -625,9 +607,9 @@ function lazyval(value) {
625
607
  var null_ = constant(null);
626
608
  var undefined_ = constant(void 0);
627
609
  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")
610
+ (blob, ok2, err2) => (
611
+ // Equiv to either(undefined_, null_), but combined for better error message
612
+ blob == null ? ok2(blob) : err2("Must be undefined or null")
631
613
  )
632
614
  );
633
615
  function optional(decoder, defaultValue) {
@@ -665,7 +647,7 @@ var mixed = unknown;
665
647
 
666
648
  // src/numbers.ts
667
649
  var anyNumber = define(
668
- (blob, ok2, err2) => typeof blob === "number" ? ok2(blob) : err2("Must be number")
650
+ (blob, ok2, err2) => isNumber(blob) ? ok2(blob) : err2("Must be number")
669
651
  );
670
652
  var number = anyNumber.refine(
671
653
  (n) => Number.isFinite(n),
@@ -675,10 +657,16 @@ var integer = number.refine(
675
657
  (n) => Number.isInteger(n),
676
658
  "Number must be an integer"
677
659
  );
678
- var positiveNumber = number.refine((n) => n >= 0, "Number must be positive").transform(Math.abs);
679
- var positiveInteger = integer.refine((n) => n >= 0, "Number must be positive").transform(Math.abs);
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
+ );
680
668
  var bigint = define(
681
- (blob, ok2, err2) => typeof blob === "bigint" ? ok2(blob) : err2("Must be bigint")
669
+ (blob, ok2, err2) => isBigInt(blob) ? ok2(blob) : err2("Must be bigint")
682
670
  );
683
671
 
684
672
  // src/booleans.ts
@@ -688,10 +676,53 @@ var boolean = define((blob, ok2, err2) => {
688
676
  var truthy = define((blob, ok2, _) => ok2(!!blob));
689
677
  var numericBoolean = number.transform((n) => !!n);
690
678
 
679
+ // src/collections.ts
680
+ function record(fst, snd) {
681
+ const keyDecoder = snd !== void 0 ? fst : void 0;
682
+ const valueDecoder = snd !== void 0 ? snd : fst;
683
+ return pojo.then((input, ok2, err2) => {
684
+ let rv = {};
685
+ const errors = /* @__PURE__ */ new Map();
686
+ for (const [key, value] of Object.entries(input)) {
687
+ const keyResult = keyDecoder?.decode(key);
688
+ if (keyResult?.ok === false) {
689
+ return err2(
690
+ public_annotate(
691
+ input,
692
+ `Invalid key ${JSON.stringify(key)}: ${formatShort(keyResult.error)}`
693
+ )
694
+ );
695
+ }
696
+ const k = keyResult?.value ?? key;
697
+ const result = valueDecoder.decode(value);
698
+ if (result.ok) {
699
+ if (errors.size === 0) {
700
+ rv[k] = result.value;
701
+ }
702
+ } else {
703
+ errors.set(key, result.error);
704
+ rv = {};
705
+ }
706
+ }
707
+ if (errors.size > 0) {
708
+ return err2(merge(public_annotateObject(input), errors));
709
+ } else {
710
+ return ok2(rv);
711
+ }
712
+ });
713
+ }
714
+ var dict = record;
715
+ function set(decoder) {
716
+ return array(decoder).transform((items) => new Set(items));
717
+ }
718
+ function mapping(decoder) {
719
+ return record(decoder).transform((obj) => new Map(Object.entries(obj)));
720
+ }
721
+
691
722
  // src/strings.ts
692
723
  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
724
  var string = define(
694
- (blob, ok2, err2) => typeof blob === "string" ? ok2(blob) : err2("Must be string")
725
+ (blob, ok2, err2) => isString(blob) ? ok2(blob) : err2("Must be string")
695
726
  );
696
727
  var nonEmptyString = regex(/\S/, "Must be non-empty string");
697
728
  function regex(regex2, msg) {
@@ -722,6 +753,12 @@ var uuidv4 = (
722
753
  // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)
723
754
  uuid.refine((value) => value[14] === "4", "Must be uuidv4")
724
755
  );
756
+ var decimal = regex(/^[0-9]+$/, "Must only contain digits");
757
+ var hexadecimal = regex(
758
+ /^[0-9a-f]+$/i,
759
+ "Must only contain hexadecimal digits"
760
+ );
761
+ var numeric = decimal.transform(Number);
725
762
 
726
763
  // src/dates.ts
727
764
  var iso8601_re = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[.]\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
@@ -741,9 +778,10 @@ var iso8601 = (
741
778
  }
742
779
  )
743
780
  );
781
+ var datelike = either(date, iso8601).describe("Must be a Date or date string");
744
782
 
745
783
  // src/json.ts
746
- var jsonObject = lazy(() => dict(json));
784
+ var jsonObject = lazy(() => record(json));
747
785
  var jsonArray = lazy(() => array(json));
748
786
  var json = either(
749
787
  null_,
@@ -761,16 +799,20 @@ export {
761
799
  boolean,
762
800
  constant,
763
801
  date,
802
+ datelike,
803
+ decimal,
764
804
  define,
765
805
  dict,
766
806
  either,
767
807
  email,
808
+ enum_,
768
809
  err,
769
810
  exact,
770
811
  fail,
771
812
  formatInline,
772
813
  formatShort,
773
814
  hardcoded,
815
+ hexadecimal,
774
816
  httpsUrl,
775
817
  inexact,
776
818
  instanceOf,
@@ -790,6 +832,7 @@ export {
790
832
  nullable,
791
833
  nullish,
792
834
  number,
835
+ numeric,
793
836
  numericBoolean,
794
837
  object,
795
838
  ok,
@@ -800,6 +843,7 @@ export {
800
843
  positiveInteger,
801
844
  positiveNumber,
802
845
  prep,
846
+ record,
803
847
  regex,
804
848
  select,
805
849
  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.3",
4
4
  "description": "Elegant and battle-tested validation library for type-safe input data for TypeScript",
5
5
  "license": "MIT",
6
6
  "repository": {