decoders 2.6.0 → 2.7.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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <img alt="Decoders logo" src="./docs/assets/logo@2x.png" style="width: 100%; max-width: 830px; max-height: 248px" width="830" /><br />
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/decoders.svg)](https://www.npmjs.com/package/decoders)
4
- [![Build Status](https://github.com/nvie/decoders/workflows/test/badge.svg)](https://github.com/nvie/decoders/actions)
4
+ [![Test Status](https://github.com/nvie/decoders/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/nvie/decoders/actions)
5
5
  [![Bundle size for decoders](https://pkg-size.dev/badge/bundle/4200)](https://pkg-size.dev/decoders)
6
6
 
7
7
  Elegant and battle-tested validation library for type-safe input data for
package/dist/index.cjs CHANGED
@@ -18,7 +18,8 @@ function isPojo(value) {
18
18
  }
19
19
 
20
20
  // src/core/annotate.ts
21
- var _register = /* @__PURE__ */ new WeakSet();
21
+ var kAnnotationRegistry = Symbol.for("decoders.kAnnotationRegistry");
22
+ var _register = globalThis[kAnnotationRegistry] ??= /* @__PURE__ */ new WeakSet();
22
23
  function brand(ann) {
23
24
  _register.add(ann);
24
25
  return ann;
@@ -102,7 +103,7 @@ function public_annotateObject(obj, text) {
102
103
  // src/lib/text.ts
103
104
  var INDENT = " ";
104
105
  function isMultiline(s) {
105
- return s.indexOf("\n") >= 0;
106
+ return s.includes("\n");
106
107
  }
107
108
  function indent(s, prefix = INDENT) {
108
109
  if (isMultiline(s)) {
@@ -166,7 +167,7 @@ function serializeArray(annotation, prefix) {
166
167
  const result = [];
167
168
  for (const item of items) {
168
169
  const [ser, ann] = serializeAnnotation(item, `${prefix}${INDENT}`);
169
- result.push(`${prefix}${INDENT}${ser}${","}`);
170
+ result.push(`${prefix}${INDENT}${ser},`);
170
171
  if (ann !== void 0) {
171
172
  result.push(indent(ann, `${prefix}${INDENT}`));
172
173
  }
@@ -310,11 +311,8 @@ ${formatted}`);
310
311
  }
311
312
  function define(fn) {
312
313
  function decode(blob) {
313
- return fn(
314
- blob,
315
- ok,
316
- (msg) => err(isAnnotation(msg) ? msg : public_annotate(blob, msg))
317
- );
314
+ const makeFlexErr = (msg) => err(isAnnotation(msg) ? msg : public_annotate(blob, msg));
315
+ return fn(blob, ok, makeFlexErr);
318
316
  }
319
317
  function verify(blob, formatter = formatInline) {
320
318
  const result = decode(blob);
@@ -341,6 +339,9 @@ function define(fn) {
341
339
  )
342
340
  );
343
341
  }
342
+ function refineType() {
343
+ return self;
344
+ }
344
345
  function then(next) {
345
346
  return define((blob, ok2, err2) => {
346
347
  const r1 = decode(blob);
@@ -368,12 +369,13 @@ function define(fn) {
368
369
  }
369
370
  });
370
371
  }
371
- return brand2({
372
+ const unregistered = {
372
373
  verify,
373
374
  value,
374
375
  decode,
375
376
  transform,
376
377
  refine,
378
+ refineType,
377
379
  reject,
378
380
  describe,
379
381
  then,
@@ -391,9 +393,12 @@ function define(fn) {
391
393
  }
392
394
  }
393
395
  }
394
- });
396
+ };
397
+ const self = brand2(unregistered);
398
+ return self;
395
399
  }
396
- var _register2 = /* @__PURE__ */ new WeakSet();
400
+ var kDecoderRegistry = Symbol.for("decoders.kDecoderRegistry");
401
+ var _register2 = globalThis[kDecoderRegistry] ??= /* @__PURE__ */ new WeakSet();
397
402
  function brand2(decoder) {
398
403
  _register2.add(decoder);
399
404
  return decoder;
@@ -409,30 +414,28 @@ var poja = define((blob, ok2, err2) => {
409
414
  }
410
415
  return ok2(blob);
411
416
  });
412
- function all(items, blobs, ok2, err2) {
413
- const results = [];
414
- for (let index = 0; index < items.length; ++index) {
415
- const result = items[index];
416
- if (result.ok) {
417
- results.push(result.value);
418
- } else {
419
- const ann = result.error;
420
- const clone = blobs.slice();
421
- clone.splice(
422
- index,
423
- 1,
424
- public_annotate(ann, ann.text ? `${ann.text} (at index ${index})` : `index ${index}`)
425
- );
426
- return err2(public_annotate(clone));
427
- }
428
- }
429
- return ok2(results);
430
- }
431
417
  function array(decoder) {
432
418
  const decodeFn = decoder.decode;
433
- return poja.then((blobs, ok2, err2) => {
434
- const results = blobs.map(decodeFn);
435
- return all(results, blobs, ok2, err2);
419
+ return poja.then((inputs, ok2, err2) => {
420
+ const results = [];
421
+ for (let i = 0; i < inputs.length; ++i) {
422
+ const blob = inputs[i];
423
+ const result = decodeFn(blob);
424
+ if (result.ok) {
425
+ results.push(result.value);
426
+ } else {
427
+ results.length = 0;
428
+ const ann = result.error;
429
+ const clone = inputs.slice();
430
+ clone.splice(
431
+ i,
432
+ 1,
433
+ public_annotate(ann, ann.text ? `${ann.text} (at index ${i})` : `index ${i}`)
434
+ );
435
+ return err2(public_annotate(clone));
436
+ }
437
+ }
438
+ return ok2(results);
436
439
  });
437
440
  }
438
441
  function isNonEmpty(arr) {
@@ -466,7 +469,10 @@ function tuple(...decoders) {
466
469
  // src/misc.ts
467
470
  function instanceOf(klass) {
468
471
  return define(
469
- (blob, ok2, err2) => blob instanceof klass ? ok2(blob) : err2(`Must be ${klass.name} instance`)
472
+ (blob, ok2, err2) => (
473
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
474
+ blob instanceof klass ? ok2(blob) : err2(`Must be ${klass.name} instance`)
475
+ )
470
476
  );
471
477
  }
472
478
  function lazy(decoderFn) {
@@ -528,9 +534,7 @@ function object(decoders) {
528
534
  if (rawValue === void 0) {
529
535
  missingKeys.add(key);
530
536
  } else {
531
- if (errors === null) {
532
- errors = /* @__PURE__ */ new Map();
533
- }
537
+ errors ??= /* @__PURE__ */ new Map();
534
538
  errors.set(key, ann);
535
539
  }
536
540
  }
@@ -595,8 +599,8 @@ function either(...decoders) {
595
599
  }
596
600
  return define((blob, _, err2) => {
597
601
  const errors = [];
598
- for (let i = 0; i < decoders.length; i++) {
599
- const result = decoders[i].decode(blob);
602
+ for (const decoder of decoders) {
603
+ const result = decoder.decode(blob);
600
604
  if (result.ok) {
601
605
  return result;
602
606
  } else {
@@ -697,7 +701,7 @@ var truthy = define((blob, ok2, _) => ok2(!!blob));
697
701
  // src/collections.ts
698
702
  function record(fst, snd) {
699
703
  const keyDecoder = snd !== void 0 ? fst : void 0;
700
- const valueDecoder = snd !== void 0 ? snd : fst;
704
+ const valueDecoder = _nullishCoalesce(snd, () => ( fst));
701
705
  return pojo.then((input, ok2, err2) => {
702
706
  let rv = {};
703
707
  const errors = /* @__PURE__ */ new Map();
@@ -738,9 +742,9 @@ function mapping(decoder) {
738
742
 
739
743
  // src/lib/size-options.ts
740
744
  function bySizeOptions(options) {
741
- const size = _optionalChain([options, 'optionalAccess', _6 => _6.size]);
742
- const min = _nullishCoalesce(size, () => ( _optionalChain([options, 'optionalAccess', _7 => _7.min])));
743
- const max = _nullishCoalesce(size, () => ( _optionalChain([options, 'optionalAccess', _8 => _8.max])));
745
+ const size = options.size;
746
+ const min = _nullishCoalesce(size, () => ( options.min));
747
+ const max = _nullishCoalesce(size, () => ( options.max));
744
748
  const atLeast = min === max ? "" : "at least ";
745
749
  const atMost = min === max ? "" : "at most ";
746
750
  const tooShort = min !== void 0 && `Too short, must be ${atLeast}${min} chars`;
@@ -815,19 +819,14 @@ var iso8601_re = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[.]\d+)?(?:Z|[+-]\d{2}:
815
819
  var date = define((blob, ok2, err2) => {
816
820
  return isDate(blob) ? ok2(blob) : err2("Must be a Date");
817
821
  });
818
- var iso8601 = (
819
- // Input itself needs to match the ISO8601 regex...
820
- regex(iso8601_re, "Must be ISO8601 format").transform(
821
- // Make sure it is a _valid_ date
822
- (value) => {
823
- const date2 = new Date(value);
824
- if (isNaN(date2.getTime())) {
825
- throw new Error("Must be valid date/time value");
826
- }
827
- return date2;
828
- }
829
- )
822
+ var dateString = regex(
823
+ iso8601_re,
824
+ "Must be ISO8601 format"
825
+ ).refine(
826
+ (value) => !isNaN(new Date(value).getTime()),
827
+ "Must be valid date/time value"
830
828
  );
829
+ var iso8601 = dateString.transform((value) => new Date(value));
831
830
  var datelike = either(date, iso8601).describe("Must be a Date or date string");
832
831
 
833
832
  // src/numbers.ts
package/dist/index.d.cts CHANGED
@@ -130,6 +130,11 @@ interface Decoder<T> {
130
130
  */
131
131
  refine<N extends T>(predicate: (value: T) => value is N, msg: string): Decoder<N>;
132
132
  refine(predicate: (value: T) => boolean, msg: string): Decoder<T>;
133
+ /**
134
+ * Cast the return type of this read-only decoder to a narrower type. This is
135
+ * useful to return "branded" types. This method has no runtime effect.
136
+ */
137
+ refineType<SubT extends T>(): Decoder<SubT>;
133
138
  /**
134
139
  * Build a new decoder from the current one, with an extra rejection
135
140
  * criterium.
@@ -168,8 +173,7 @@ interface Decoder<T> {
168
173
  *
169
174
  * string.pipe((s) => s.startsWith('@') ? username : email)
170
175
  */
171
- pipe<V, D extends Decoder<V>>(next: D): Decoder<DecoderType<D>>;
172
- pipe<V, D extends Decoder<V>>(next: (blob: T) => D): Decoder<DecoderType<D>>;
176
+ pipe<V, D extends Decoder<V>>(next: D | ((blob: T) => D)): Decoder<DecoderType<D>>;
173
177
  /**
174
178
  * The Standard Schema interface for this decoder.
175
179
  */
package/dist/index.d.ts CHANGED
@@ -130,6 +130,11 @@ interface Decoder<T> {
130
130
  */
131
131
  refine<N extends T>(predicate: (value: T) => value is N, msg: string): Decoder<N>;
132
132
  refine(predicate: (value: T) => boolean, msg: string): Decoder<T>;
133
+ /**
134
+ * Cast the return type of this read-only decoder to a narrower type. This is
135
+ * useful to return "branded" types. This method has no runtime effect.
136
+ */
137
+ refineType<SubT extends T>(): Decoder<SubT>;
133
138
  /**
134
139
  * Build a new decoder from the current one, with an extra rejection
135
140
  * criterium.
@@ -168,8 +173,7 @@ interface Decoder<T> {
168
173
  *
169
174
  * string.pipe((s) => s.startsWith('@') ? username : email)
170
175
  */
171
- pipe<V, D extends Decoder<V>>(next: D): Decoder<DecoderType<D>>;
172
- pipe<V, D extends Decoder<V>>(next: (blob: T) => D): Decoder<DecoderType<D>>;
176
+ pipe<V, D extends Decoder<V>>(next: D | ((blob: T) => D)): Decoder<DecoderType<D>>;
173
177
  /**
174
178
  * The Standard Schema interface for this decoder.
175
179
  */
package/dist/index.js CHANGED
@@ -18,7 +18,8 @@ function isPojo(value) {
18
18
  }
19
19
 
20
20
  // src/core/annotate.ts
21
- var _register = /* @__PURE__ */ new WeakSet();
21
+ var kAnnotationRegistry = Symbol.for("decoders.kAnnotationRegistry");
22
+ var _register = globalThis[kAnnotationRegistry] ??= /* @__PURE__ */ new WeakSet();
22
23
  function brand(ann) {
23
24
  _register.add(ann);
24
25
  return ann;
@@ -102,7 +103,7 @@ function public_annotateObject(obj, text) {
102
103
  // src/lib/text.ts
103
104
  var INDENT = " ";
104
105
  function isMultiline(s) {
105
- return s.indexOf("\n") >= 0;
106
+ return s.includes("\n");
106
107
  }
107
108
  function indent(s, prefix = INDENT) {
108
109
  if (isMultiline(s)) {
@@ -166,7 +167,7 @@ function serializeArray(annotation, prefix) {
166
167
  const result = [];
167
168
  for (const item of items) {
168
169
  const [ser, ann] = serializeAnnotation(item, `${prefix}${INDENT}`);
169
- result.push(`${prefix}${INDENT}${ser}${","}`);
170
+ result.push(`${prefix}${INDENT}${ser},`);
170
171
  if (ann !== void 0) {
171
172
  result.push(indent(ann, `${prefix}${INDENT}`));
172
173
  }
@@ -310,11 +311,8 @@ ${formatted}`);
310
311
  }
311
312
  function define(fn) {
312
313
  function decode(blob) {
313
- return fn(
314
- blob,
315
- ok,
316
- (msg) => err(isAnnotation(msg) ? msg : public_annotate(blob, msg))
317
- );
314
+ const makeFlexErr = (msg) => err(isAnnotation(msg) ? msg : public_annotate(blob, msg));
315
+ return fn(blob, ok, makeFlexErr);
318
316
  }
319
317
  function verify(blob, formatter = formatInline) {
320
318
  const result = decode(blob);
@@ -341,6 +339,9 @@ function define(fn) {
341
339
  )
342
340
  );
343
341
  }
342
+ function refineType() {
343
+ return self;
344
+ }
344
345
  function then(next) {
345
346
  return define((blob, ok2, err2) => {
346
347
  const r1 = decode(blob);
@@ -368,12 +369,13 @@ function define(fn) {
368
369
  }
369
370
  });
370
371
  }
371
- return brand2({
372
+ const unregistered = {
372
373
  verify,
373
374
  value,
374
375
  decode,
375
376
  transform,
376
377
  refine,
378
+ refineType,
377
379
  reject,
378
380
  describe,
379
381
  then,
@@ -391,9 +393,12 @@ function define(fn) {
391
393
  }
392
394
  }
393
395
  }
394
- });
396
+ };
397
+ const self = brand2(unregistered);
398
+ return self;
395
399
  }
396
- var _register2 = /* @__PURE__ */ new WeakSet();
400
+ var kDecoderRegistry = Symbol.for("decoders.kDecoderRegistry");
401
+ var _register2 = globalThis[kDecoderRegistry] ??= /* @__PURE__ */ new WeakSet();
397
402
  function brand2(decoder) {
398
403
  _register2.add(decoder);
399
404
  return decoder;
@@ -409,30 +414,28 @@ var poja = define((blob, ok2, err2) => {
409
414
  }
410
415
  return ok2(blob);
411
416
  });
412
- function all(items, blobs, ok2, err2) {
413
- const results = [];
414
- for (let index = 0; index < items.length; ++index) {
415
- const result = items[index];
416
- if (result.ok) {
417
- results.push(result.value);
418
- } else {
419
- const ann = result.error;
420
- const clone = blobs.slice();
421
- clone.splice(
422
- index,
423
- 1,
424
- public_annotate(ann, ann.text ? `${ann.text} (at index ${index})` : `index ${index}`)
425
- );
426
- return err2(public_annotate(clone));
427
- }
428
- }
429
- return ok2(results);
430
- }
431
417
  function array(decoder) {
432
418
  const decodeFn = decoder.decode;
433
- return poja.then((blobs, ok2, err2) => {
434
- const results = blobs.map(decodeFn);
435
- return all(results, blobs, ok2, err2);
419
+ return poja.then((inputs, ok2, err2) => {
420
+ const results = [];
421
+ for (let i = 0; i < inputs.length; ++i) {
422
+ const blob = inputs[i];
423
+ const result = decodeFn(blob);
424
+ if (result.ok) {
425
+ results.push(result.value);
426
+ } else {
427
+ results.length = 0;
428
+ const ann = result.error;
429
+ const clone = inputs.slice();
430
+ clone.splice(
431
+ i,
432
+ 1,
433
+ public_annotate(ann, ann.text ? `${ann.text} (at index ${i})` : `index ${i}`)
434
+ );
435
+ return err2(public_annotate(clone));
436
+ }
437
+ }
438
+ return ok2(results);
436
439
  });
437
440
  }
438
441
  function isNonEmpty(arr) {
@@ -466,7 +469,10 @@ function tuple(...decoders) {
466
469
  // src/misc.ts
467
470
  function instanceOf(klass) {
468
471
  return define(
469
- (blob, ok2, err2) => blob instanceof klass ? ok2(blob) : err2(`Must be ${klass.name} instance`)
472
+ (blob, ok2, err2) => (
473
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
474
+ blob instanceof klass ? ok2(blob) : err2(`Must be ${klass.name} instance`)
475
+ )
470
476
  );
471
477
  }
472
478
  function lazy(decoderFn) {
@@ -528,9 +534,7 @@ function object(decoders) {
528
534
  if (rawValue === void 0) {
529
535
  missingKeys.add(key);
530
536
  } else {
531
- if (errors === null) {
532
- errors = /* @__PURE__ */ new Map();
533
- }
537
+ errors ??= /* @__PURE__ */ new Map();
534
538
  errors.set(key, ann);
535
539
  }
536
540
  }
@@ -595,8 +599,8 @@ function either(...decoders) {
595
599
  }
596
600
  return define((blob, _, err2) => {
597
601
  const errors = [];
598
- for (let i = 0; i < decoders.length; i++) {
599
- const result = decoders[i].decode(blob);
602
+ for (const decoder of decoders) {
603
+ const result = decoder.decode(blob);
600
604
  if (result.ok) {
601
605
  return result;
602
606
  } else {
@@ -697,7 +701,7 @@ var truthy = define((blob, ok2, _) => ok2(!!blob));
697
701
  // src/collections.ts
698
702
  function record(fst, snd) {
699
703
  const keyDecoder = snd !== void 0 ? fst : void 0;
700
- const valueDecoder = snd !== void 0 ? snd : fst;
704
+ const valueDecoder = snd ?? fst;
701
705
  return pojo.then((input, ok2, err2) => {
702
706
  let rv = {};
703
707
  const errors = /* @__PURE__ */ new Map();
@@ -738,9 +742,9 @@ function mapping(decoder) {
738
742
 
739
743
  // src/lib/size-options.ts
740
744
  function bySizeOptions(options) {
741
- const size = options?.size;
742
- const min = size ?? options?.min;
743
- const max = size ?? options?.max;
745
+ const size = options.size;
746
+ const min = size ?? options.min;
747
+ const max = size ?? options.max;
744
748
  const atLeast = min === max ? "" : "at least ";
745
749
  const atMost = min === max ? "" : "at most ";
746
750
  const tooShort = min !== void 0 && `Too short, must be ${atLeast}${min} chars`;
@@ -815,19 +819,14 @@ var iso8601_re = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:[.]\d+)?(?:Z|[+-]\d{2}:
815
819
  var date = define((blob, ok2, err2) => {
816
820
  return isDate(blob) ? ok2(blob) : err2("Must be a Date");
817
821
  });
818
- var iso8601 = (
819
- // Input itself needs to match the ISO8601 regex...
820
- regex(iso8601_re, "Must be ISO8601 format").transform(
821
- // Make sure it is a _valid_ date
822
- (value) => {
823
- const date2 = new Date(value);
824
- if (isNaN(date2.getTime())) {
825
- throw new Error("Must be valid date/time value");
826
- }
827
- return date2;
828
- }
829
- )
822
+ var dateString = regex(
823
+ iso8601_re,
824
+ "Must be ISO8601 format"
825
+ ).refine(
826
+ (value) => !isNaN(new Date(value).getTime()),
827
+ "Must be valid date/time value"
830
828
  );
829
+ var iso8601 = dateString.transform((value) => new Date(value));
831
830
  var datelike = either(date, iso8601).describe("Must be a Date or date string");
832
831
 
833
832
  // src/numbers.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decoders",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "Elegant and battle-tested validation library for type-safe input data for TypeScript",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,7 +14,6 @@
14
14
  },
15
15
  "type": "module",
16
16
  "main": "./dist/index.cjs",
17
- "module": "./dist/index.js",
18
17
  "types": "./dist/index.d.cts",
19
18
  "exports": {
20
19
  ".": {
@@ -61,28 +60,27 @@
61
60
  "verify"
62
61
  ],
63
62
  "devDependencies": {
64
- "@arethetypeswrong/cli": "^0.17.3",
63
+ "@arethetypeswrong/cli": "^0.17.4",
64
+ "@eslint/js": "^9.23.0",
65
65
  "@release-it/keep-a-changelog": "^6.0.0",
66
66
  "@standard-schema/spec": "^1.0.0",
67
- "@types/eslint": "^8.56.10",
68
- "@typescript-eslint/eslint-plugin": "^7.14.1",
69
- "@typescript-eslint/parser": "^7.14.1",
70
- "@vitest/coverage-istanbul": "^3.0.5",
71
- "eslint": "^8.57.0",
72
- "eslint-plugin-import": "^2.29.1",
73
- "eslint-plugin-simple-import-sort": "^12.1.0",
74
- "fast-check": "^3.23.2",
75
- "itertools": "^2.3.2",
76
- "pkg-pr-new": "^0.0.39",
77
- "prettier": "^3.4.2",
78
- "publint": "^0.3.2",
67
+ "@vitest/coverage-istanbul": "^3.0.9",
68
+ "eslint": "^9.23.0",
69
+ "eslint-plugin-import": "^2.31.0",
70
+ "eslint-plugin-simple-import-sort": "^12.1.1",
71
+ "fast-check": "^4.0.0",
72
+ "itertools": "^2.4.1",
73
+ "pkg-pr-new": "^0.0.41",
74
+ "prettier": "^3.5.3",
75
+ "publint": "^0.3.9",
79
76
  "release-it": "^18.1.2",
80
- "ts-morph": "^25.0.0",
77
+ "ts-morph": "^25.0.1",
81
78
  "tsd": "^0.31.2",
82
- "tsup": "^8.3.6",
83
- "typescript": "^5.7.3",
79
+ "tsup": "^8.4.0",
80
+ "typescript": "^5.8.2",
81
+ "typescript-eslint": "^8.28.0",
84
82
  "vite-tsconfig-paths": "^5.1.4",
85
- "vitest": "^3.0.5"
83
+ "vitest": "^3.0.9"
86
84
  },
87
85
  "githubUrl": "https://github.com/nvie/decoders",
88
86
  "sideEffects": false