decoders 2.0.0-beta1 → 2.0.0-beta10

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 (160) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/Decoder.d.ts +27 -0
  3. package/Decoder.js +199 -0
  4. package/Decoder.js.flow +224 -0
  5. package/Decoder.mjs +192 -0
  6. package/NotSupportedTSVersion.d.ts +1 -0
  7. package/README.md +105 -961
  8. package/_utils.d.ts +9 -0
  9. package/{cjs/_utils.js → _utils.js} +12 -18
  10. package/{cjs/_utils.js.flow → _utils.js.flow} +15 -19
  11. package/{es/_utils.js → _utils.mjs} +11 -15
  12. package/{ts/annotate.d.ts → annotate.d.ts} +25 -21
  13. package/{cjs/annotate.js → annotate.js} +0 -0
  14. package/{cjs/annotate.js.flow → annotate.js.flow} +0 -0
  15. package/{es/annotate.js → annotate.mjs} +0 -0
  16. package/format.d.ts +4 -0
  17. package/{cjs/format/inline.js → format.js} +7 -2
  18. package/{cjs/format/inline.js.flow → format.js.flow} +7 -3
  19. package/{es/format/inline.js → format.mjs} +5 -2
  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 → 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 +14 -0
  30. package/lib/basics.js +109 -0
  31. package/lib/basics.js.flow +85 -0
  32. package/lib/basics.mjs +85 -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} +9 -4
  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 -29
  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/_guard.js +0 -26
  71. package/cjs/_guard.js.flow +0 -20
  72. package/cjs/_types.js +0 -1
  73. package/cjs/_types.js.flow +0 -20
  74. package/cjs/format/index.js +0 -12
  75. package/cjs/format/index.js.flow +0 -4
  76. package/cjs/format/short.js +0 -10
  77. package/cjs/format/short.js.flow +0 -8
  78. package/cjs/index.js +0 -120
  79. package/cjs/index.js.flow +0 -63
  80. package/cjs/result.js +0 -172
  81. package/cjs/result.js.flow +0 -166
  82. package/cjs/stdlib/array.js +0 -108
  83. package/cjs/stdlib/array.js.flow +0 -103
  84. package/cjs/stdlib/boolean.js +0 -44
  85. package/cjs/stdlib/boolean.js.flow +0 -29
  86. package/cjs/stdlib/composition.js +0 -56
  87. package/cjs/stdlib/composition.js.flow +0 -43
  88. package/cjs/stdlib/constants.js +0 -69
  89. package/cjs/stdlib/constants.js.flow +0 -46
  90. package/cjs/stdlib/date.js +0 -46
  91. package/cjs/stdlib/date.js.flow +0 -40
  92. package/cjs/stdlib/describe.js +0 -26
  93. package/cjs/stdlib/describe.js.flow +0 -17
  94. package/cjs/stdlib/dispatch.js +0 -62
  95. package/cjs/stdlib/dispatch.js.flow +0 -58
  96. package/cjs/stdlib/either.js +0 -117
  97. package/cjs/stdlib/either.js.flow +0 -151
  98. package/cjs/stdlib/fail.js +0 -21
  99. package/cjs/stdlib/fail.js.flow +0 -12
  100. package/cjs/stdlib/instanceOf.js +0 -19
  101. package/cjs/stdlib/instanceOf.js.flow +0 -20
  102. package/cjs/stdlib/json.js +0 -31
  103. package/cjs/stdlib/json.js.flow +0 -28
  104. package/cjs/stdlib/lazy.js +0 -16
  105. package/cjs/stdlib/lazy.js.flow +0 -15
  106. package/cjs/stdlib/mapping.js +0 -67
  107. package/cjs/stdlib/mapping.js.flow +0 -54
  108. package/cjs/stdlib/number.js +0 -40
  109. package/cjs/stdlib/number.js.flow +0 -34
  110. package/cjs/stdlib/object.js +0 -194
  111. package/cjs/stdlib/object.js.flow +0 -203
  112. package/cjs/stdlib/optional.js +0 -54
  113. package/cjs/stdlib/optional.js.flow +0 -41
  114. package/cjs/stdlib/string.js +0 -98
  115. package/cjs/stdlib/string.js.flow +0 -82
  116. package/cjs/stdlib/tuple.js +0 -173
  117. package/cjs/stdlib/tuple.js.flow +0 -220
  118. package/es/_guard.js +0 -15
  119. package/es/_types.js +0 -0
  120. package/es/format/index.js +0 -2
  121. package/es/format/short.js +0 -4
  122. package/es/index.js +0 -37
  123. package/es/result.js +0 -139
  124. package/es/stdlib/array.js +0 -91
  125. package/es/stdlib/boolean.js +0 -28
  126. package/es/stdlib/composition.js +0 -42
  127. package/es/stdlib/constants.js +0 -46
  128. package/es/stdlib/date.js +0 -28
  129. package/es/stdlib/describe.js +0 -16
  130. package/es/stdlib/dispatch.js +0 -51
  131. package/es/stdlib/either.js +0 -90
  132. package/es/stdlib/fail.js +0 -11
  133. package/es/stdlib/instanceOf.js +0 -8
  134. package/es/stdlib/json.js +0 -15
  135. package/es/stdlib/lazy.js +0 -11
  136. package/es/stdlib/mapping.js +0 -54
  137. package/es/stdlib/number.js +0 -25
  138. package/es/stdlib/object.js +0 -175
  139. package/es/stdlib/optional.js +0 -38
  140. package/es/stdlib/string.js +0 -76
  141. package/es/stdlib/tuple.js +0 -155
  142. package/ts/_guard.d.ts +0 -7
  143. package/ts/_types.d.ts +0 -16
  144. package/ts/_utils.d.ts +0 -13
  145. package/ts/array.d.ts +0 -5
  146. package/ts/constants.d.ts +0 -11
  147. package/ts/describe.d.ts +0 -3
  148. package/ts/dispatch.d.ts +0 -8
  149. package/ts/either.d.ts +0 -61
  150. package/ts/fail.d.ts +0 -3
  151. package/ts/index.d.ts +0 -42
  152. package/ts/inline.d.ts +0 -3
  153. package/ts/instanceOf.d.ts +0 -3
  154. package/ts/lazy.d.ts +0 -3
  155. package/ts/mapping.d.ts +0 -4
  156. package/ts/optional.d.ts +0 -5
  157. package/ts/result.d.ts +0 -39
  158. package/ts/short.d.ts +0 -3
  159. package/ts/string.d.ts +0 -7
  160. package/ts/tuple.d.ts +0 -30
package/CHANGELOG.md CHANGED
@@ -1,15 +1,45 @@
1
1
  ## v2.0.0-beta
2
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
+
3
7
  Potentially breaking changes:
4
8
 
5
9
  - Drop support for all Node versions below 12.x
6
10
  - Drop support for Flow versions below 0.142.0
11
+ - Drop support for TypeScript versions below 4.1.0
7
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)
8
30
 
9
31
  New features:
10
32
 
11
33
  - Include ES modules in published NPM builds (yay tree-shaking! 🍃)
12
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)
13
43
  - Better error messages for nested `either`s
14
44
  - Guard API now has a simpler way to specify formatters
15
45
 
package/Decoder.d.ts ADDED
@@ -0,0 +1,27 @@
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
+ decode(blob: unknown): DecodeResult<T>;
15
+ verify(blob: unknown, formatterFn?: (ann: Annotation) => string): T;
16
+ refine<N extends T>(predicate: (value: T) => value is N, msg: string): Decoder<N>;
17
+ refine(predicate: (value: T) => boolean, msg: string): Decoder<T>;
18
+ reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T>;
19
+ transform<V>(transformFn: (value: T) => V): Decoder<V>;
20
+ describe(message: string): Decoder<T>;
21
+ then<V>(nextDecodeFn: DecodeFn<V, T>): Decoder<V>;
22
+ peek<V>(nextDecodeFn: DecodeFn<V, [unknown, T]>): Decoder<V>;
23
+ }
24
+
25
+ export type DecoderType<T> = T extends Decoder<infer V> ? V : never;
26
+
27
+ export function define<T>(fn: DecodeFn<T>): Decoder<T>;
package/Decoder.js ADDED
@@ -0,0 +1,199 @@
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
+ var _err = new Error('\n' + formatter(result.error));
68
+
69
+ _err.name = 'Decoding error';
70
+ throw _err;
71
+ }
72
+ }
73
+ /**
74
+ * Accepts any value the given decoder accepts, and on success, will call
75
+ * the given function **on the decoded result**. If the transformation
76
+ * function throws an error, the whole decoder will fail using the error
77
+ * message as the failure reason.
78
+ */
79
+
80
+
81
+ function transform(transformFn) {
82
+ return then(noThrow(transformFn));
83
+ }
84
+ /**
85
+ * Adds an extra predicate to a decoder. The new decoder is like the
86
+ * original decoder, but only accepts values that also meet the
87
+ * predicate.
88
+ */
89
+
90
+
91
+ function refine(predicateFn, errmsg) {
92
+ return reject(function (value) {
93
+ return predicateFn(value) ? // Don't reject
94
+ null : // Reject with the given error message
95
+ errmsg;
96
+ });
97
+ }
98
+ /**
99
+ * Chain together the current decoder with another.
100
+ *
101
+ * First, the current decoder must accept the input. If so, it will pass
102
+ * the successfully decoded result to the given ``next`` function to
103
+ * further decide whether or not the value should get accepted or rejected.
104
+ *
105
+ * The argument to `.then()` is a decoding function, just like one you
106
+ * would pass to `define()`. The key difference with `define()` is that
107
+ * `define()` must always assume an ``unknown`` input, whereas with
108
+ * a `.then()` call the provided ``next`` function will receive a ``T`` as
109
+ * its input. This will allow the function to make a stronger assumption
110
+ * about its input.
111
+ *
112
+ * If it helps, you can think of `define(nextFn)` as equivalent to
113
+ * `unknown.then(nextFn)`.
114
+ *
115
+ * This is an advanced, low-level, decoder. It's not recommended to reach
116
+ * for this low-level construct when implementing custom decoders. Most
117
+ * cases can be covered by `.transform()` or `.refine()`.
118
+ */
119
+
120
+
121
+ function then(next) {
122
+ return define(function (blob, ok, err) {
123
+ var result = decode(blob);
124
+ return result.ok ? next(result.value, ok, err) : result;
125
+ });
126
+ }
127
+ /**
128
+ * Adds an extra predicate to a decoder. The new decoder is like the
129
+ * original decoder, but only accepts values that aren't rejected by the
130
+ * given function.
131
+ *
132
+ * The given function can return `null` to accept the decoded value, or
133
+ * return a specific error message to reject.
134
+ *
135
+ * Unlike `.refine()`, you can use this function to return a dynamic error
136
+ * message.
137
+ */
138
+
139
+
140
+ function reject(rejectFn) {
141
+ return then(function (value, ok, err) {
142
+ var errmsg = rejectFn(value);
143
+ return errmsg === null ? ok(value) : err(typeof errmsg === 'string' ? (0, _annotate.annotate)(value, errmsg) : errmsg);
144
+ });
145
+ }
146
+ /**
147
+ * Uses the given decoder, but will use an alternative error message in
148
+ * case it rejects. This can be used to simplify or shorten otherwise
149
+ * long or low-level/technical errors.
150
+ */
151
+
152
+
153
+ function describe(message) {
154
+ return define(function (blob, _, err) {
155
+ // Decode using the given decoder...
156
+ var result = decode(blob);
157
+
158
+ if (result.ok) {
159
+ return result;
160
+ } else {
161
+ // ...but in case of error, annotate this with the custom given
162
+ // message instead
163
+ return err((0, _annotate.annotate)(result.error, message));
164
+ }
165
+ });
166
+ }
167
+ /**
168
+ * WARNING: This is an EXPERIMENTAL API that will likely change in the
169
+ * future. Please DO NOT rely on it.
170
+ *
171
+ * Chain together the current decoder with another, but also pass along
172
+ * the original input.
173
+ *
174
+ * This is like `.then()`, but instead of this function receiving just
175
+ * the decoded result ``T``, it also receives the original input.
176
+ *
177
+ * This is an advanced, low-level, decoder.
178
+ */
179
+
180
+
181
+ function peek_UNSTABLE(next) {
182
+ return define(function (blob, ok, err) {
183
+ var result = decode(blob);
184
+ return result.ok ? next([blob, result.value], ok, err) : result;
185
+ });
186
+ }
187
+
188
+ return Object.freeze({
189
+ verify: verify,
190
+ decode: decode,
191
+ transform: transform,
192
+ refine: refine,
193
+ reject: reject,
194
+ describe: describe,
195
+ then: then,
196
+ // EXPERIMENTAL - please DO NOT rely on this method
197
+ peek_UNSTABLE: peek_UNSTABLE
198
+ });
199
+ }
@@ -0,0 +1,224 @@
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
+ decode(blob: mixed): DecodeResult<T>,
36
+ verify(blob: mixed, formatterFn?: (Annotation) => string): T,
37
+ refine(predicateFn: (value: T) => boolean, errmsg: string): Decoder<T>,
38
+ reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T>,
39
+ transform<V>(transformFn: (value: T) => V): Decoder<V>,
40
+ describe(message: string): Decoder<T>,
41
+ then<V>(next: DecodeFn<V, T>): Decoder<V>,
42
+
43
+ // Experimental APIs (please don't rely on these yet)
44
+ peek_UNSTABLE<V>(next: DecodeFn<V, [mixed, T]>): Decoder<V>,
45
+ |};
46
+
47
+ function noThrow<T, V>(fn: (value: T) => V): (T) => DecodeResult<V> {
48
+ return (t) => {
49
+ try {
50
+ const v = fn(t);
51
+ return makeOk(v);
52
+ } catch (e) {
53
+ return makeErr(annotate(t, e instanceof Error ? e.message : String(e)));
54
+ }
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Defines a new `Decoder<T>`, by implementing a custom acceptance function.
60
+ * The function receives three arguments:
61
+ *
62
+ * 1. `blob` - the raw/unknown input (aka your external data)
63
+ * 2. `ok` - Call `ok(value)` to accept the input and return `value`
64
+ * 3. `err` - Call `err(message)` to reject the input and use "message" in the
65
+ * annotation
66
+ *
67
+ * The expected return value should be a `DecodeResult<T>`, which can be
68
+ * obtained by returning the result from the provided `ok` or `err` helper
69
+ * functions.
70
+ */
71
+ export function define<T>(decodeFn: DecodeFn<T>): Decoder<T> {
72
+ /**
73
+ * Validates the raw/untrusted/unknown input and either accepts or rejects
74
+ * it.
75
+ *
76
+ * Contrasted with `.verify()`, calls to `.decode()` will never fail and
77
+ * instead return a result type.
78
+ */
79
+ function decode(blob: mixed): DecodeResult<T> {
80
+ return decodeFn(blob, makeOk, (msg: Annotation | string) =>
81
+ makeErr(typeof msg === 'string' ? annotate(blob, msg) : msg),
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Verified the (raw/untrusted/unknown) input and either accepts or rejects
87
+ * it. When accepted, returns the decoded `T` value directly. Otherwise
88
+ * fail with a runtime error.
89
+ */
90
+ function verify(blob: mixed, formatter: (Annotation) => string = formatInline): T {
91
+ const result = decode(blob);
92
+ if (result.ok) {
93
+ return result.value;
94
+ } else {
95
+ const err = new Error('\n' + formatter(result.error));
96
+ err.name = 'Decoding error';
97
+ throw err;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Accepts any value the given decoder accepts, and on success, will call
103
+ * the given function **on the decoded result**. If the transformation
104
+ * function throws an error, the whole decoder will fail using the error
105
+ * message as the failure reason.
106
+ */
107
+ function transform<V>(transformFn: (T) => V): Decoder<V> {
108
+ return then(noThrow(transformFn));
109
+ }
110
+
111
+ /**
112
+ * Adds an extra predicate to a decoder. The new decoder is like the
113
+ * original decoder, but only accepts values that also meet the
114
+ * predicate.
115
+ */
116
+ function refine(predicateFn: (value: T) => boolean, errmsg: string): Decoder<T> {
117
+ return reject((value) =>
118
+ predicateFn(value)
119
+ ? // Don't reject
120
+ null
121
+ : // Reject with the given error message
122
+ errmsg,
123
+ );
124
+ }
125
+
126
+ /**
127
+ * Chain together the current decoder with another.
128
+ *
129
+ * First, the current decoder must accept the input. If so, it will pass
130
+ * the successfully decoded result to the given ``next`` function to
131
+ * further decide whether or not the value should get accepted or rejected.
132
+ *
133
+ * The argument to `.then()` is a decoding function, just like one you
134
+ * would pass to `define()`. The key difference with `define()` is that
135
+ * `define()` must always assume an ``unknown`` input, whereas with
136
+ * a `.then()` call the provided ``next`` function will receive a ``T`` as
137
+ * its input. This will allow the function to make a stronger assumption
138
+ * about its input.
139
+ *
140
+ * If it helps, you can think of `define(nextFn)` as equivalent to
141
+ * `unknown.then(nextFn)`.
142
+ *
143
+ * This is an advanced, low-level, decoder. It's not recommended to reach
144
+ * for this low-level construct when implementing custom decoders. Most
145
+ * cases can be covered by `.transform()` or `.refine()`.
146
+ */
147
+ function then<V>(next: DecodeFn<V, T>): Decoder<V> {
148
+ return define((blob, ok, err) => {
149
+ const result = decode(blob);
150
+ return result.ok ? next(result.value, ok, err) : result;
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Adds an extra predicate to a decoder. The new decoder is like the
156
+ * original decoder, but only accepts values that aren't rejected by the
157
+ * given function.
158
+ *
159
+ * The given function can return `null` to accept the decoded value, or
160
+ * return a specific error message to reject.
161
+ *
162
+ * Unlike `.refine()`, you can use this function to return a dynamic error
163
+ * message.
164
+ */
165
+ function reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T> {
166
+ return then((value, ok, err) => {
167
+ const errmsg = rejectFn(value);
168
+ return errmsg === null
169
+ ? ok(value)
170
+ : err(typeof errmsg === 'string' ? annotate(value, errmsg) : errmsg);
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Uses the given decoder, but will use an alternative error message in
176
+ * case it rejects. This can be used to simplify or shorten otherwise
177
+ * long or low-level/technical errors.
178
+ */
179
+ function describe(message: string): Decoder<T> {
180
+ return define((blob, _, err) => {
181
+ // Decode using the given decoder...
182
+ const result = decode(blob);
183
+ if (result.ok) {
184
+ return result;
185
+ } else {
186
+ // ...but in case of error, annotate this with the custom given
187
+ // message instead
188
+ return err(annotate(result.error, message));
189
+ }
190
+ });
191
+ }
192
+
193
+ /**
194
+ * WARNING: This is an EXPERIMENTAL API that will likely change in the
195
+ * future. Please DO NOT rely on it.
196
+ *
197
+ * Chain together the current decoder with another, but also pass along
198
+ * the original input.
199
+ *
200
+ * This is like `.then()`, but instead of this function receiving just
201
+ * the decoded result ``T``, it also receives the original input.
202
+ *
203
+ * This is an advanced, low-level, decoder.
204
+ */
205
+ function peek_UNSTABLE<V>(next: DecodeFn<V, [mixed, T]>): Decoder<V> {
206
+ return define((blob, ok, err) => {
207
+ const result = decode(blob);
208
+ return result.ok ? next([blob, result.value], ok, err) : result;
209
+ });
210
+ }
211
+
212
+ return Object.freeze({
213
+ verify,
214
+ decode,
215
+ transform,
216
+ refine,
217
+ reject,
218
+ describe,
219
+ then,
220
+
221
+ // EXPERIMENTAL - please DO NOT rely on this method
222
+ peek_UNSTABLE,
223
+ });
224
+ }
package/Decoder.mjs ADDED
@@ -0,0 +1,192 @@
1
+ import { annotate } from './annotate.mjs';
2
+ import { formatInline } from './format.mjs';
3
+ import { err as makeErr, ok as makeOk } from './result.mjs';
4
+
5
+ function noThrow(fn) {
6
+ return function (t) {
7
+ try {
8
+ var v = fn(t);
9
+ return makeOk(v);
10
+ } catch (e) {
11
+ return makeErr(annotate(t, e instanceof Error ? e.message : String(e)));
12
+ }
13
+ };
14
+ }
15
+ /**
16
+ * Defines a new `Decoder<T>`, by implementing a custom acceptance function.
17
+ * The function receives three arguments:
18
+ *
19
+ * 1. `blob` - the raw/unknown input (aka your external data)
20
+ * 2. `ok` - Call `ok(value)` to accept the input and return `value`
21
+ * 3. `err` - Call `err(message)` to reject the input and use "message" in the
22
+ * annotation
23
+ *
24
+ * The expected return value should be a `DecodeResult<T>`, which can be
25
+ * obtained by returning the result from the provided `ok` or `err` helper
26
+ * functions.
27
+ */
28
+
29
+
30
+ export function define(decodeFn) {
31
+ /**
32
+ * Validates the raw/untrusted/unknown input and either accepts or rejects
33
+ * it.
34
+ *
35
+ * Contrasted with `.verify()`, calls to `.decode()` will never fail and
36
+ * instead return a result type.
37
+ */
38
+ function decode(blob) {
39
+ return decodeFn(blob, makeOk, function (msg) {
40
+ return makeErr(typeof msg === 'string' ? annotate(blob, msg) : msg);
41
+ });
42
+ }
43
+ /**
44
+ * Verified the (raw/untrusted/unknown) input and either accepts or rejects
45
+ * it. When accepted, returns the decoded `T` value directly. Otherwise
46
+ * fail with a runtime error.
47
+ */
48
+
49
+
50
+ function verify(blob, formatter) {
51
+ if (formatter === void 0) {
52
+ formatter = formatInline;
53
+ }
54
+
55
+ var result = decode(blob);
56
+
57
+ if (result.ok) {
58
+ return result.value;
59
+ } else {
60
+ var _err = new Error('\n' + formatter(result.error));
61
+
62
+ _err.name = 'Decoding error';
63
+ throw _err;
64
+ }
65
+ }
66
+ /**
67
+ * Accepts any value the given decoder accepts, and on success, will call
68
+ * the given function **on the decoded result**. If the transformation
69
+ * function throws an error, the whole decoder will fail using the error
70
+ * message as the failure reason.
71
+ */
72
+
73
+
74
+ function transform(transformFn) {
75
+ return then(noThrow(transformFn));
76
+ }
77
+ /**
78
+ * Adds an extra predicate to a decoder. The new decoder is like the
79
+ * original decoder, but only accepts values that also meet the
80
+ * predicate.
81
+ */
82
+
83
+
84
+ function refine(predicateFn, errmsg) {
85
+ return reject(function (value) {
86
+ return predicateFn(value) ? // Don't reject
87
+ null : // Reject with the given error message
88
+ errmsg;
89
+ });
90
+ }
91
+ /**
92
+ * Chain together the current decoder with another.
93
+ *
94
+ * First, the current decoder must accept the input. If so, it will pass
95
+ * the successfully decoded result to the given ``next`` function to
96
+ * further decide whether or not the value should get accepted or rejected.
97
+ *
98
+ * The argument to `.then()` is a decoding function, just like one you
99
+ * would pass to `define()`. The key difference with `define()` is that
100
+ * `define()` must always assume an ``unknown`` input, whereas with
101
+ * a `.then()` call the provided ``next`` function will receive a ``T`` as
102
+ * its input. This will allow the function to make a stronger assumption
103
+ * about its input.
104
+ *
105
+ * If it helps, you can think of `define(nextFn)` as equivalent to
106
+ * `unknown.then(nextFn)`.
107
+ *
108
+ * This is an advanced, low-level, decoder. It's not recommended to reach
109
+ * for this low-level construct when implementing custom decoders. Most
110
+ * cases can be covered by `.transform()` or `.refine()`.
111
+ */
112
+
113
+
114
+ function then(next) {
115
+ return define(function (blob, ok, err) {
116
+ var result = decode(blob);
117
+ return result.ok ? next(result.value, ok, err) : result;
118
+ });
119
+ }
120
+ /**
121
+ * Adds an extra predicate to a decoder. The new decoder is like the
122
+ * original decoder, but only accepts values that aren't rejected by the
123
+ * given function.
124
+ *
125
+ * The given function can return `null` to accept the decoded value, or
126
+ * return a specific error message to reject.
127
+ *
128
+ * Unlike `.refine()`, you can use this function to return a dynamic error
129
+ * message.
130
+ */
131
+
132
+
133
+ function reject(rejectFn) {
134
+ return then(function (value, ok, err) {
135
+ var errmsg = rejectFn(value);
136
+ return errmsg === null ? ok(value) : err(typeof errmsg === 'string' ? annotate(value, errmsg) : errmsg);
137
+ });
138
+ }
139
+ /**
140
+ * Uses the given decoder, but will use an alternative error message in
141
+ * case it rejects. This can be used to simplify or shorten otherwise
142
+ * long or low-level/technical errors.
143
+ */
144
+
145
+
146
+ function describe(message) {
147
+ return define(function (blob, _, err) {
148
+ // Decode using the given decoder...
149
+ var result = decode(blob);
150
+
151
+ if (result.ok) {
152
+ return result;
153
+ } else {
154
+ // ...but in case of error, annotate this with the custom given
155
+ // message instead
156
+ return err(annotate(result.error, message));
157
+ }
158
+ });
159
+ }
160
+ /**
161
+ * WARNING: This is an EXPERIMENTAL API that will likely change in the
162
+ * future. Please DO NOT rely on it.
163
+ *
164
+ * Chain together the current decoder with another, but also pass along
165
+ * the original input.
166
+ *
167
+ * This is like `.then()`, but instead of this function receiving just
168
+ * the decoded result ``T``, it also receives the original input.
169
+ *
170
+ * This is an advanced, low-level, decoder.
171
+ */
172
+
173
+
174
+ function peek_UNSTABLE(next) {
175
+ return define(function (blob, ok, err) {
176
+ var result = decode(blob);
177
+ return result.ok ? next([blob, result.value], ok, err) : result;
178
+ });
179
+ }
180
+
181
+ return Object.freeze({
182
+ verify: verify,
183
+ decode: decode,
184
+ transform: transform,
185
+ refine: refine,
186
+ reject: reject,
187
+ describe: describe,
188
+ then: then,
189
+ // EXPERIMENTAL - please DO NOT rely on this method
190
+ peek_UNSTABLE: peek_UNSTABLE
191
+ });
192
+ }
@@ -0,0 +1 @@
1
+ "Package `decoders` requires TypeScript >= 4.1.0"