decoders 1.26.0-test1 → 2.0.0-beta12

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.
Files changed (128) hide show
  1. package/CHANGELOG.md +53 -3
  2. package/Decoder.d.ts +28 -0
  3. package/Decoder.js +223 -0
  4. package/Decoder.js.flow +250 -0
  5. package/Decoder.mjs +216 -0
  6. package/NotSupportedTSVersion.d.ts +1 -0
  7. package/README.md +105 -961
  8. package/_utils.d.ts +9 -0
  9. package/_utils.js +102 -0
  10. package/_utils.js.flow +93 -0
  11. package/_utils.mjs +89 -0
  12. package/annotate.d.ts +62 -0
  13. package/annotate.js +161 -0
  14. package/annotate.js.flow +218 -0
  15. package/annotate.mjs +144 -0
  16. package/format.d.ts +4 -0
  17. package/format.js +151 -0
  18. package/format.js.flow +126 -0
  19. package/format.mjs +140 -0
  20. package/index.d.ts +40 -0
  21. package/index.js +90 -0
  22. package/index.js.flow +44 -0
  23. package/index.mjs +11 -0
  24. package/{ts/helpers.d.ts → lib/_helpers.d.ts} +0 -0
  25. package/lib/arrays.d.ts +37 -0
  26. package/lib/arrays.js +138 -0
  27. package/lib/arrays.js.flow +138 -0
  28. package/lib/arrays.mjs +123 -0
  29. package/lib/basics.d.ts +38 -0
  30. package/lib/basics.js +134 -0
  31. package/lib/basics.js.flow +113 -0
  32. package/lib/basics.mjs +110 -0
  33. package/{ts/boolean.d.ts → lib/booleans.d.ts} +1 -1
  34. package/lib/booleans.js +35 -0
  35. package/lib/booleans.js.flow +22 -0
  36. package/lib/booleans.mjs +25 -0
  37. package/{ts/date.d.ts → lib/dates.d.ts} +1 -1
  38. package/lib/dates.js +44 -0
  39. package/lib/dates.js.flow +40 -0
  40. package/lib/dates.mjs +34 -0
  41. package/{ts → lib}/json.d.ts +1 -1
  42. package/lib/json.js +55 -0
  43. package/lib/json.js.flow +50 -0
  44. package/lib/json.mjs +40 -0
  45. package/{ts/number.d.ts → lib/numbers.d.ts} +2 -1
  46. package/lib/numbers.js +52 -0
  47. package/lib/numbers.js.flow +49 -0
  48. package/lib/numbers.mjs +42 -0
  49. package/{ts/object.d.ts → lib/objects.d.ts} +11 -6
  50. package/lib/objects.js +240 -0
  51. package/lib/objects.js.flow +246 -0
  52. package/lib/objects.mjs +223 -0
  53. package/lib/strings.d.ts +13 -0
  54. package/lib/strings.js +101 -0
  55. package/lib/strings.js.flow +90 -0
  56. package/lib/strings.mjs +82 -0
  57. package/lib/unions.d.ts +78 -0
  58. package/lib/unions.js +161 -0
  59. package/lib/unions.js.flow +158 -0
  60. package/lib/unions.mjs +145 -0
  61. package/lib/utilities.d.ts +10 -0
  62. package/lib/utilities.js +94 -0
  63. package/lib/utilities.js.flow +84 -0
  64. package/lib/utilities.mjs +79 -0
  65. package/package.json +79 -25
  66. package/result.d.ts +16 -0
  67. package/result.js +34 -0
  68. package/result.js.flow +26 -0
  69. package/result.mjs +27 -0
  70. package/cjs/array.js +0 -133
  71. package/cjs/array.js.flow +0 -106
  72. package/cjs/boolean.js +0 -42
  73. package/cjs/boolean.js.flow +0 -28
  74. package/cjs/constants.js +0 -67
  75. package/cjs/constants.js.flow +0 -45
  76. package/cjs/date.js +0 -42
  77. package/cjs/date.js.flow +0 -38
  78. package/cjs/describe.js +0 -22
  79. package/cjs/describe.js.flow +0 -17
  80. package/cjs/dispatch.js +0 -58
  81. package/cjs/dispatch.js.flow +0 -57
  82. package/cjs/either.js +0 -85
  83. package/cjs/either.js.flow +0 -131
  84. package/cjs/fail.js +0 -19
  85. package/cjs/fail.js.flow +0 -13
  86. package/cjs/guard.js +0 -30
  87. package/cjs/guard.js.flow +0 -36
  88. package/cjs/index.js +0 -397
  89. package/cjs/index.js.flow +0 -56
  90. package/cjs/instanceOf.js +0 -17
  91. package/cjs/instanceOf.js.flow +0 -21
  92. package/cjs/json.js +0 -33
  93. package/cjs/json.js.flow +0 -28
  94. package/cjs/lazy.js +0 -18
  95. package/cjs/lazy.js.flow +0 -15
  96. package/cjs/mapping.js +0 -113
  97. package/cjs/mapping.js.flow +0 -71
  98. package/cjs/number.js +0 -38
  99. package/cjs/number.js.flow +0 -35
  100. package/cjs/object.js +0 -254
  101. package/cjs/object.js.flow +0 -211
  102. package/cjs/optional.js +0 -52
  103. package/cjs/optional.js.flow +0 -42
  104. package/cjs/string.js +0 -93
  105. package/cjs/string.js.flow +0 -81
  106. package/cjs/tuple.js +0 -199
  107. package/cjs/tuple.js.flow +0 -221
  108. package/cjs/types.js +0 -5
  109. package/cjs/types.js.flow +0 -26
  110. package/cjs/utils.js +0 -70
  111. package/cjs/utils.js.flow +0 -58
  112. package/es/index.js +0 -1212
  113. package/ts/array.d.ts +0 -5
  114. package/ts/constants.d.ts +0 -11
  115. package/ts/describe.d.ts +0 -3
  116. package/ts/dispatch.d.ts +0 -8
  117. package/ts/either.d.ts +0 -61
  118. package/ts/fail.d.ts +0 -3
  119. package/ts/guard.d.ts +0 -7
  120. package/ts/index.d.ts +0 -38
  121. package/ts/instanceOf.d.ts +0 -3
  122. package/ts/lazy.d.ts +0 -3
  123. package/ts/mapping.d.ts +0 -4
  124. package/ts/optional.d.ts +0 -5
  125. package/ts/string.d.ts +0 -7
  126. package/ts/tuple.d.ts +0 -30
  127. package/ts/types.d.ts +0 -18
  128. package/ts/utils.d.ts +0 -13
package/CHANGELOG.md CHANGED
@@ -1,3 +1,53 @@
1
+ ## v2.0.0-beta
2
+
3
+ This is a breaking change, which brings numerous benefits including speed, bundle size,
4
+ and simplicity. Please see the [migration guide](./MIGRATING-v2.md) for instructions on
5
+ how to adjust your code.
6
+
7
+ Potentially breaking changes:
8
+
9
+ - Drop support for all Node versions below 12.x
10
+ - Drop support for Flow versions below 0.142.0
11
+ - Drop support for TypeScript versions below 4.1.0
12
+ - Drop all package dependencies
13
+ - Removed decoders:
14
+ - `guard()` no longer exists, see
15
+ [migration instructions](./MIGRATING-v2.md#guards-are-no-longer-a-thing)
16
+ - `eitherN()` (now simply `either()`, see
17
+ [migration instructions](./MIGRATING-v2.md#eitherN-is-now-simply-either))
18
+ - `tupleN()` (now simply `tuple()`, see
19
+ [migration instructions](./MIGRATING-v2.md#tupleN-is-now-simply-tuple))
20
+ - Renamed decoders:
21
+ - `map()` → `transform()` - see
22
+ [migration instructions](./MIGRATING-v2.md#map-is-now-transform)
23
+ - `dispatch()` → `taggedUnion()` - see
24
+ [migration instructions](./MIGRATING-v2.md#dispatch-is-now-taggedUnion)
25
+ - Decoders that have changed:
26
+ - API of `predicate()` has changed - see
27
+ [migration instructions](./MIGRATING-v2.md#predicate-is-now-a-first-class-citizen)
28
+ - API of `url` decoder has changed - see
29
+ [migration instructions](./MIGRATING-v2.md#url-decoder-has-changed)
30
+
31
+ New features:
32
+
33
+ - Include ES modules in published NPM builds (yay tree-shaking! 🍃)
34
+ - Much smaller total bundle size
35
+ - New decoders:
36
+ - [`always`](https://nvie.com/decoders/api#always)
37
+ - [`never`](https://nvie.com/decoders/api#never)
38
+ - [`prep()`](https://nvie.com/decoders/api#prep)
39
+ - [`set()`](https://nvie.com/decoders/api#set)
40
+ - [`uuidv1`](https://nvie.com/decoders/api#uuidv1)
41
+ - [`uuidv4`](https://nvie.com/decoders/api#uuidv4)
42
+ - [`uuid`](https://nvie.com/decoders/api#uuid)
43
+ - Better error messages for nested `either`s
44
+ - Guard API now has a simpler way to specify formatters
45
+
46
+ Implementation changes:
47
+
48
+ - Major reorganization of internal module structure
49
+ - Various simplification of internals
50
+
1
51
  ## v1.25.5
2
52
 
3
53
  - Fix compatibility issue with TypeScript projects configured with
@@ -322,7 +372,7 @@ to upgrade:
322
372
  ```javascript
323
373
  const mydecoder: Decoder<string> = predicate(
324
374
  (s) => s.startsWith('x'),
325
- 'Must start with "x"'
375
+ 'Must start with "x"',
326
376
  );
327
377
  ```
328
378
 
@@ -332,7 +382,7 @@ to upgrade:
332
382
  const mydecoder: Decoder<string, string> = predicate(
333
383
  // ^^^^^^ Provide the input type to predicate() decoders
334
384
  (s) => s.startsWith('x'),
335
- 'Must start with "x"'
385
+ 'Must start with "x"',
336
386
  );
337
387
  ```
338
388
 
@@ -556,6 +606,6 @@ to upgrade:
556
606
  object({
557
607
  name: string,
558
608
  age: number,
559
- })
609
+ }),
560
610
  );
561
611
  ```
package/Decoder.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { Annotation } from './annotate';
2
+ import { Result } from './result';
3
+
4
+ export type Scalar = string | number | boolean | symbol | undefined | null;
5
+
6
+ export type DecodeResult<T> = Result<T, Annotation>;
7
+ export type DecodeFn<T, I = unknown> = (
8
+ blob: I,
9
+ ok: (value: T) => DecodeResult<T>,
10
+ err: (msg: string | Annotation) => DecodeResult<T>,
11
+ ) => DecodeResult<T>;
12
+
13
+ export interface Decoder<T> {
14
+ verify(blob: unknown, formatterFn?: (ann: Annotation) => string | Error): T;
15
+ value(blob: unknown): T | undefined;
16
+ decode(blob: unknown): DecodeResult<T>;
17
+ refine<N extends T>(predicate: (value: T) => value is N, msg: string): Decoder<N>;
18
+ refine(predicate: (value: T) => boolean, msg: string): Decoder<T>;
19
+ reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T>;
20
+ transform<V>(transformFn: (value: T) => V): Decoder<V>;
21
+ describe(message: string): Decoder<T>;
22
+ then<V>(nextDecodeFn: DecodeFn<V, T>): Decoder<V>;
23
+ peek<V>(nextDecodeFn: DecodeFn<V, [unknown, T]>): Decoder<V>;
24
+ }
25
+
26
+ export type DecoderType<T> = T extends Decoder<infer V> ? V : never;
27
+
28
+ export function define<T>(fn: DecodeFn<T>): Decoder<T>;
package/Decoder.js ADDED
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.define = define;
5
+
6
+ var _annotate = require("./annotate");
7
+
8
+ var _format = require("./format");
9
+
10
+ var _result = require("./result");
11
+
12
+ function noThrow(fn) {
13
+ return function (t) {
14
+ try {
15
+ var v = fn(t);
16
+ return (0, _result.ok)(v);
17
+ } catch (e) {
18
+ return (0, _result.err)((0, _annotate.annotate)(t, e instanceof Error ? e.message : String(e)));
19
+ }
20
+ };
21
+ }
22
+ /**
23
+ * Defines a new `Decoder<T>`, by implementing a custom acceptance function.
24
+ * The function receives three arguments:
25
+ *
26
+ * 1. `blob` - the raw/unknown input (aka your external data)
27
+ * 2. `ok` - Call `ok(value)` to accept the input and return ``value``
28
+ * 3. `err` - Call `err(message)` to reject the input and use "message" in the
29
+ * annotation
30
+ *
31
+ * The expected return value should be a `DecodeResult<T>`, which can be
32
+ * obtained by returning the result from the provided `ok` or `err` helper
33
+ * functions.
34
+ */
35
+
36
+
37
+ function define(decodeFn) {
38
+ /**
39
+ * Validates the raw/untrusted/unknown input and either accepts or rejects
40
+ * it.
41
+ *
42
+ * Contrasted with `.verify()`, calls to `.decode()` will never fail and
43
+ * instead return a result type.
44
+ */
45
+ function decode(blob) {
46
+ return decodeFn(blob, _result.ok, function (msg) {
47
+ return (0, _result.err)(typeof msg === 'string' ? (0, _annotate.annotate)(blob, msg) : msg);
48
+ });
49
+ }
50
+ /**
51
+ * Verified the (raw/untrusted/unknown) input and either accepts or rejects
52
+ * it. When accepted, returns the decoded `T` value directly. Otherwise
53
+ * fail with a runtime error.
54
+ */
55
+
56
+
57
+ function verify(blob, formatter) {
58
+ if (formatter === void 0) {
59
+ formatter = _format.formatInline;
60
+ }
61
+
62
+ var result = decode(blob);
63
+
64
+ if (result.ok) {
65
+ return result.value;
66
+ } else {
67
+ // Formatters may return a string or an error for convenience of
68
+ // writing them. If it already returns an Error, throw it
69
+ // unmodified. If it returns a string, wrap it in a "Decoding
70
+ // error" instance from it and throw that.
71
+ var strOrErr = formatter(result.error);
72
+
73
+ if (typeof strOrErr === 'string') {
74
+ var _err = new Error('\n' + strOrErr);
75
+
76
+ _err.name = 'Decoding error';
77
+ throw _err;
78
+ } else {
79
+ throw strOrErr;
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * Verified the (raw/untrusted/unknown) input and either accepts or rejects
85
+ * it. When accepted, returns the decoded `T` value directly. Otherwise
86
+ * returns `undefined`.
87
+ *
88
+ * Use this when you're not interested in programmatically handling the
89
+ * error message.
90
+ */
91
+
92
+
93
+ function value(blob) {
94
+ return decode(blob).value;
95
+ }
96
+ /**
97
+ * Accepts any value the given decoder accepts, and on success, will call
98
+ * the given function **on the decoded result**. If the transformation
99
+ * function throws an error, the whole decoder will fail using the error
100
+ * message as the failure reason.
101
+ */
102
+
103
+
104
+ function transform(transformFn) {
105
+ return then(noThrow(transformFn));
106
+ }
107
+ /**
108
+ * Adds an extra predicate to a decoder. The new decoder is like the
109
+ * original decoder, but only accepts values that also meet the
110
+ * predicate.
111
+ */
112
+
113
+
114
+ function refine(predicateFn, errmsg) {
115
+ return reject(function (value) {
116
+ return predicateFn(value) ? // Don't reject
117
+ null : // Reject with the given error message
118
+ errmsg;
119
+ });
120
+ }
121
+ /**
122
+ * Chain together the current decoder with another.
123
+ *
124
+ * First, the current decoder must accept the input. If so, it will pass
125
+ * the successfully decoded result to the given ``next`` function to
126
+ * further decide whether or not the value should get accepted or rejected.
127
+ *
128
+ * The argument to `.then()` is a decoding function, just like one you
129
+ * would pass to `define()`. The key difference with `define()` is that
130
+ * `define()` must always assume an ``unknown`` input, whereas with
131
+ * a `.then()` call the provided ``next`` function will receive a ``T`` as
132
+ * its input. This will allow the function to make a stronger assumption
133
+ * about its input.
134
+ *
135
+ * If it helps, you can think of `define(nextFn)` as equivalent to
136
+ * `unknown.then(nextFn)`.
137
+ *
138
+ * This is an advanced, low-level, decoder. It's not recommended to reach
139
+ * for this low-level construct when implementing custom decoders. Most
140
+ * cases can be covered by `.transform()` or `.refine()`.
141
+ */
142
+
143
+
144
+ function then(next) {
145
+ return define(function (blob, ok, err) {
146
+ var result = decode(blob);
147
+ return result.ok ? next(result.value, ok, err) : result;
148
+ });
149
+ }
150
+ /**
151
+ * Adds an extra predicate to a decoder. The new decoder is like the
152
+ * original decoder, but only accepts values that aren't rejected by the
153
+ * given function.
154
+ *
155
+ * The given function can return `null` to accept the decoded value, or
156
+ * return a specific error message to reject.
157
+ *
158
+ * Unlike `.refine()`, you can use this function to return a dynamic error
159
+ * message.
160
+ */
161
+
162
+
163
+ function reject(rejectFn) {
164
+ return then(function (value, ok, err) {
165
+ var errmsg = rejectFn(value);
166
+ return errmsg === null ? ok(value) : err(typeof errmsg === 'string' ? (0, _annotate.annotate)(value, errmsg) : errmsg);
167
+ });
168
+ }
169
+ /**
170
+ * Uses the given decoder, but will use an alternative error message in
171
+ * case it rejects. This can be used to simplify or shorten otherwise
172
+ * long or low-level/technical errors.
173
+ */
174
+
175
+
176
+ function describe(message) {
177
+ return define(function (blob, _, err) {
178
+ // Decode using the given decoder...
179
+ var result = decode(blob);
180
+
181
+ if (result.ok) {
182
+ return result;
183
+ } else {
184
+ // ...but in case of error, annotate this with the custom given
185
+ // message instead
186
+ return err((0, _annotate.annotate)(result.error, message));
187
+ }
188
+ });
189
+ }
190
+ /**
191
+ * WARNING: This is an EXPERIMENTAL API that will likely change in the
192
+ * future. Please DO NOT rely on it.
193
+ *
194
+ * Chain together the current decoder with another, but also pass along
195
+ * the original input.
196
+ *
197
+ * This is like `.then()`, but instead of this function receiving just
198
+ * the decoded result ``T``, it also receives the original input.
199
+ *
200
+ * This is an advanced, low-level, decoder.
201
+ */
202
+
203
+
204
+ function peek_UNSTABLE(next) {
205
+ return define(function (blob, ok, err) {
206
+ var result = decode(blob);
207
+ return result.ok ? next([blob, result.value], ok, err) : result;
208
+ });
209
+ }
210
+
211
+ return Object.freeze({
212
+ verify: verify,
213
+ value: value,
214
+ decode: decode,
215
+ transform: transform,
216
+ refine: refine,
217
+ reject: reject,
218
+ describe: describe,
219
+ then: then,
220
+ // EXPERIMENTAL - please DO NOT rely on this method
221
+ peek_UNSTABLE: peek_UNSTABLE
222
+ });
223
+ }
@@ -0,0 +1,250 @@
1
+ // @flow strict
2
+
3
+ import { annotate } from './annotate';
4
+ import { formatInline } from './format';
5
+ import { err as makeErr, ok as makeOk } from './result';
6
+ import type { Annotation } from './annotate';
7
+ import type { Result } from './result';
8
+
9
+ export type Scalar = string | number | boolean | symbol | void | null;
10
+
11
+ export type DecodeResult<T> = Result<T, Annotation>;
12
+ export type DecodeFn<T, I = mixed> = (
13
+ blob: I,
14
+ ok: (value: T) => DecodeResult<T>,
15
+ err: (msg: string | Annotation) => DecodeResult<T>,
16
+ ) => DecodeResult<T>;
17
+
18
+ /**
19
+ * Helper type to return the "type" of a Decoder.
20
+ *
21
+ * You can use it on types:
22
+ *
23
+ * DecoderType<Decoder<string>> // => string
24
+ * DecoderType<Decoder<number[]>> // => number[]
25
+ *
26
+ * Or on "values", by using the `typeof` keyword:
27
+ *
28
+ * DecoderType<typeof array(string)> // => string[]
29
+ * DecoderType<typeof truthy> // => boolean
30
+ *
31
+ */
32
+ export type DecoderType<D> = $Call<<T>(Decoder<T>) => T, D>;
33
+
34
+ export type Decoder<T> = {|
35
+ verify(blob: mixed, formatterFn?: (Annotation) => string | Error): T,
36
+ value(blob: mixed): T | void,
37
+ decode(blob: mixed): DecodeResult<T>,
38
+ refine(predicateFn: (value: T) => boolean, errmsg: string): Decoder<T>,
39
+ reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T>,
40
+ transform<V>(transformFn: (value: T) => V): Decoder<V>,
41
+ describe(message: string): Decoder<T>,
42
+ then<V>(next: DecodeFn<V, T>): Decoder<V>,
43
+
44
+ // Experimental APIs (please don't rely on these yet)
45
+ peek_UNSTABLE<V>(next: DecodeFn<V, [mixed, T]>): Decoder<V>,
46
+ |};
47
+
48
+ function noThrow<T, V>(fn: (value: T) => V): (T) => DecodeResult<V> {
49
+ return (t) => {
50
+ try {
51
+ const v = fn(t);
52
+ return makeOk(v);
53
+ } catch (e) {
54
+ return makeErr(annotate(t, e instanceof Error ? e.message : String(e)));
55
+ }
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Defines a new `Decoder<T>`, by implementing a custom acceptance function.
61
+ * The function receives three arguments:
62
+ *
63
+ * 1. `blob` - the raw/unknown input (aka your external data)
64
+ * 2. `ok` - Call `ok(value)` to accept the input and return ``value``
65
+ * 3. `err` - Call `err(message)` to reject the input and use "message" in the
66
+ * annotation
67
+ *
68
+ * The expected return value should be a `DecodeResult<T>`, which can be
69
+ * obtained by returning the result from the provided `ok` or `err` helper
70
+ * functions.
71
+ */
72
+ export function define<T>(decodeFn: DecodeFn<T>): Decoder<T> {
73
+ /**
74
+ * Validates the raw/untrusted/unknown input and either accepts or rejects
75
+ * it.
76
+ *
77
+ * Contrasted with `.verify()`, calls to `.decode()` will never fail and
78
+ * instead return a result type.
79
+ */
80
+ function decode(blob: mixed): DecodeResult<T> {
81
+ return decodeFn(blob, makeOk, (msg: Annotation | string) =>
82
+ makeErr(typeof msg === 'string' ? annotate(blob, msg) : msg),
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Verified the (raw/untrusted/unknown) input and either accepts or rejects
88
+ * it. When accepted, returns the decoded `T` value directly. Otherwise
89
+ * fail with a runtime error.
90
+ */
91
+ function verify(
92
+ blob: mixed,
93
+ formatter: (Annotation) => string | Error = formatInline,
94
+ ): T {
95
+ const result = decode(blob);
96
+ if (result.ok) {
97
+ return result.value;
98
+ } else {
99
+ // Formatters may return a string or an error for convenience of
100
+ // writing them. If it already returns an Error, throw it
101
+ // unmodified. If it returns a string, wrap it in a "Decoding
102
+ // error" instance from it and throw that.
103
+ const strOrErr = formatter(result.error);
104
+ if (typeof strOrErr === 'string') {
105
+ const err = new Error('\n' + strOrErr);
106
+ err.name = 'Decoding error';
107
+ throw err;
108
+ } else {
109
+ throw strOrErr;
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Verified the (raw/untrusted/unknown) input and either accepts or rejects
116
+ * it. When accepted, returns the decoded `T` value directly. Otherwise
117
+ * returns `undefined`.
118
+ *
119
+ * Use this when you're not interested in programmatically handling the
120
+ * error message.
121
+ */
122
+ function value(blob: mixed): T | void {
123
+ return decode(blob).value;
124
+ }
125
+
126
+ /**
127
+ * Accepts any value the given decoder accepts, and on success, will call
128
+ * the given function **on the decoded result**. If the transformation
129
+ * function throws an error, the whole decoder will fail using the error
130
+ * message as the failure reason.
131
+ */
132
+ function transform<V>(transformFn: (T) => V): Decoder<V> {
133
+ return then(noThrow(transformFn));
134
+ }
135
+
136
+ /**
137
+ * Adds an extra predicate to a decoder. The new decoder is like the
138
+ * original decoder, but only accepts values that also meet the
139
+ * predicate.
140
+ */
141
+ function refine(predicateFn: (value: T) => boolean, errmsg: string): Decoder<T> {
142
+ return reject((value) =>
143
+ predicateFn(value)
144
+ ? // Don't reject
145
+ null
146
+ : // Reject with the given error message
147
+ errmsg,
148
+ );
149
+ }
150
+
151
+ /**
152
+ * Chain together the current decoder with another.
153
+ *
154
+ * First, the current decoder must accept the input. If so, it will pass
155
+ * the successfully decoded result to the given ``next`` function to
156
+ * further decide whether or not the value should get accepted or rejected.
157
+ *
158
+ * The argument to `.then()` is a decoding function, just like one you
159
+ * would pass to `define()`. The key difference with `define()` is that
160
+ * `define()` must always assume an ``unknown`` input, whereas with
161
+ * a `.then()` call the provided ``next`` function will receive a ``T`` as
162
+ * its input. This will allow the function to make a stronger assumption
163
+ * about its input.
164
+ *
165
+ * If it helps, you can think of `define(nextFn)` as equivalent to
166
+ * `unknown.then(nextFn)`.
167
+ *
168
+ * This is an advanced, low-level, decoder. It's not recommended to reach
169
+ * for this low-level construct when implementing custom decoders. Most
170
+ * cases can be covered by `.transform()` or `.refine()`.
171
+ */
172
+ function then<V>(next: DecodeFn<V, T>): Decoder<V> {
173
+ return define((blob, ok, err) => {
174
+ const result = decode(blob);
175
+ return result.ok ? next(result.value, ok, err) : result;
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Adds an extra predicate to a decoder. The new decoder is like the
181
+ * original decoder, but only accepts values that aren't rejected by the
182
+ * given function.
183
+ *
184
+ * The given function can return `null` to accept the decoded value, or
185
+ * return a specific error message to reject.
186
+ *
187
+ * Unlike `.refine()`, you can use this function to return a dynamic error
188
+ * message.
189
+ */
190
+ function reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T> {
191
+ return then((value, ok, err) => {
192
+ const errmsg = rejectFn(value);
193
+ return errmsg === null
194
+ ? ok(value)
195
+ : err(typeof errmsg === 'string' ? annotate(value, errmsg) : errmsg);
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Uses the given decoder, but will use an alternative error message in
201
+ * case it rejects. This can be used to simplify or shorten otherwise
202
+ * long or low-level/technical errors.
203
+ */
204
+ function describe(message: string): Decoder<T> {
205
+ return define((blob, _, err) => {
206
+ // Decode using the given decoder...
207
+ const result = decode(blob);
208
+ if (result.ok) {
209
+ return result;
210
+ } else {
211
+ // ...but in case of error, annotate this with the custom given
212
+ // message instead
213
+ return err(annotate(result.error, message));
214
+ }
215
+ });
216
+ }
217
+
218
+ /**
219
+ * WARNING: This is an EXPERIMENTAL API that will likely change in the
220
+ * future. Please DO NOT rely on it.
221
+ *
222
+ * Chain together the current decoder with another, but also pass along
223
+ * the original input.
224
+ *
225
+ * This is like `.then()`, but instead of this function receiving just
226
+ * the decoded result ``T``, it also receives the original input.
227
+ *
228
+ * This is an advanced, low-level, decoder.
229
+ */
230
+ function peek_UNSTABLE<V>(next: DecodeFn<V, [mixed, T]>): Decoder<V> {
231
+ return define((blob, ok, err) => {
232
+ const result = decode(blob);
233
+ return result.ok ? next([blob, result.value], ok, err) : result;
234
+ });
235
+ }
236
+
237
+ return Object.freeze({
238
+ verify,
239
+ value,
240
+ decode,
241
+ transform,
242
+ refine,
243
+ reject,
244
+ describe,
245
+ then,
246
+
247
+ // EXPERIMENTAL - please DO NOT rely on this method
248
+ peek_UNSTABLE,
249
+ });
250
+ }