decoders 2.0.0-beta12 → 2.0.0-beta13

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/CHANGELOG.md CHANGED
@@ -1,47 +1,65 @@
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.
3
+ This is a breaking change, which brings numerous benefits:
6
4
 
7
- Potentially breaking changes:
5
+ - A **simpler API** 😇
6
+ - Smaller **bundle size** (67% reduction 😱)
7
+ - **Tree-shaking** support 🍃
8
+ - Runtime **speed** 🏎️
9
+ - Better documentation 📚
10
+ - Better support for writing your own decoders 🛠️
11
+
12
+ Please see the [migration guide](./MIGRATING-v2.md) for precise instructions on how to
13
+ adjust your v1 code.
14
+
15
+ The main change is the brand new `Decoder<T>` API! The **tl;dr** is:
16
+
17
+ | Replace this v1 pattern... | | ...with this v2 API | Notes |
18
+ | :----------------------------------- | --- | :----------------------------------------- | :--------------------------------------------------------------------------- |
19
+ | `mydecoder(input)` | → | `mydecoder.decode(input)` | [migration instructions](./MIGRATING-v2.md#stop-calling-decoders) |
20
+ | `guard(mydecoder)(input)` | → | `mydecoder.verify(input)` | [migration instructions](./MIGRATING-v2.md#guards-are-no-longer-a-thing) |
21
+ | `map(mydecoder, ...)` | → | `mydecoder.transform(...)` | [migration instructions](./MIGRATING-v2.md#map-is-now-transform) |
22
+ | `compose(mydecoder, predicate(...))` | → | `mydecoder.refine(...)` | [migration instructions](./MIGRATING-v2.md#compose--predicate-is-now-refine) |
23
+ | `describe(mydecoder, ...)` | → | `mydecoder.describe(...)` | |
24
+ | `mydecoder(input).value()` | → | `mydecoder.value(input)` | |
25
+ | `either`, `either3`, ..., `either9` | → | `either` | [migration instructions](./MIGRATING-v2.md#eitherN-is-now-simply-either) |
26
+ | `tuple1`, `tuple2`, ... `tuple6` | → | `tuple` | [migration instructions](./MIGRATING-v2.md#tupleN-is-now-simply-tuple) |
27
+ | `dispatch` | → | `taggedUnion` | [migration instructions](./MIGRATING-v2.md#dispatch-is-now-taggedUnion) |
28
+ | `url(...)` | → | `httpsUrl` / `url` (signature has changed) | [migration instructions](./MIGRATING-v2.md#signature-of-url-has-changed) |
29
+
30
+ The full documentation is available on [**decoders.cc**](https://decoders.cc).
31
+
32
+ Other features:
33
+
34
+ - Include ES modules in published NPM builds (yay tree-shaking! 🍃)
35
+ - Much smaller total bundle size (**67% smaller** compared to v1 😱)
36
+
37
+ Other potentially breaking changes:
8
38
 
9
39
  - Drop support for all Node versions below 12.x
10
- - Drop support for Flow versions below 0.142.0
11
40
  - Drop support for TypeScript versions below 4.1.0
41
+ - Drop support for Flow versions below 0.142.0
12
42
  - 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:
43
+ - Direct reliance on `lemons` has been removed
32
44
 
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)
45
+ New decoders:
46
+
47
+ - [`always`](https://decoders.cc/api.html#always)
48
+ - [`anyNumber`](https://decoders.cc/api.html#anyNumber)
49
+ - [`never`](https://decoders.cc/api.html#never)
50
+ - [`prep()`](https://decoders.cc/api.html#prep)
51
+ - [`set()`](https://decoders.cc/api.html#set)
52
+ - [`uuid`](https://decoders.cc/api.html#uuid)
53
+ - [`uuidv1`](https://decoders.cc/api.html#uuidv1)
54
+ - [`uuidv4`](https://decoders.cc/api.html#uuidv4)
55
+
56
+ Other improvements:
57
+
58
+ - [`optional()`](https://decoders.cc/api.html#optional),
59
+ [`nullable()`](https://decoders.cc/api.html#nullable), and
60
+ [`maybe()`](https://decoders.cc/api.html#maybe) now each take an optional 2nd param to
61
+ specify a default value
43
62
  - Better error messages for nested `either`s
44
- - Guard API now has a simpler way to specify formatters
45
63
 
46
64
  Implementation changes:
47
65
 
package/Decoder.d.ts CHANGED
@@ -4,25 +4,91 @@ import { Result } from './result';
4
4
  export type Scalar = string | number | boolean | symbol | undefined | null;
5
5
 
6
6
  export type DecodeResult<T> = Result<T, Annotation>;
7
- export type DecodeFn<T, I = unknown> = (
8
- blob: I,
7
+
8
+ export type AcceptanceFn<T, InputT = unknown> = (
9
+ blob: InputT,
9
10
  ok: (value: T) => DecodeResult<T>,
10
11
  err: (msg: string | Annotation) => DecodeResult<T>,
11
12
  ) => DecodeResult<T>;
12
13
 
13
14
  export interface Decoder<T> {
15
+ /**
16
+ * Verifies untrusted input. Either returns a value, or throws a decoding
17
+ * error.
18
+ */
14
19
  verify(blob: unknown, formatterFn?: (ann: Annotation) => string | Error): T;
20
+
21
+ /**
22
+ * Verifies untrusted input. Either returns a value, or returns undefined.
23
+ */
15
24
  value(blob: unknown): T | undefined;
25
+
26
+ /**
27
+ * Verifies untrusted input. Always returns a DecodeResult, which is either
28
+ * an "ok" value or an "error" annotation.
29
+ */
16
30
  decode(blob: unknown): DecodeResult<T>;
31
+
32
+ /**
33
+ * Build a new decoder from the the current one, with an extra acceptance
34
+ * criterium.
35
+ */
17
36
  refine<N extends T>(predicate: (value: T) => value is N, msg: string): Decoder<N>;
18
37
  refine(predicate: (value: T) => boolean, msg: string): Decoder<T>;
38
+
39
+ /**
40
+ * Build a new decoder from the current one, with an extra rejection
41
+ * criterium.
42
+ */
19
43
  reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T>;
44
+
45
+ /**
46
+ * Build a new decoder from the current one, modifying its outputted value.
47
+ */
20
48
  transform<V>(transformFn: (value: T) => V): Decoder<V>;
49
+
50
+ /**
51
+ * Build a new decoder from the current one, with a mutated error message
52
+ * in case of a rejection.
53
+ */
21
54
  describe(message: string): Decoder<T>;
22
- then<V>(nextDecodeFn: DecodeFn<V, T>): Decoder<V>;
23
- peek<V>(nextDecodeFn: DecodeFn<V, [unknown, T]>): Decoder<V>;
55
+
56
+ /**
57
+ * Chain together the current decoder with another acceptance function.
58
+ */
59
+ then<V>(next: AcceptanceFn<V, T>): Decoder<V>;
60
+
61
+ // Experimental APIs (please don't rely on these yet)
62
+ peek_UNSTABLE<V>(next: AcceptanceFn<V, [unknown, T]>): Decoder<V>;
24
63
  }
25
64
 
65
+ /**
66
+ * Helper type to return the "type" of a Decoder.
67
+ *
68
+ * You can use it on types:
69
+ *
70
+ * DecoderType<Decoder<string>> // string
71
+ * DecoderType<Decoder<number[]>> // number[]
72
+ *
73
+ * Or on "values", by using the `typeof` keyword:
74
+ *
75
+ * DecoderType<typeof string> // string
76
+ * DecoderType<typeof truthy> // boolean
77
+ *
78
+ */
26
79
  export type DecoderType<T> = T extends Decoder<infer V> ? V : never;
27
80
 
28
- export function define<T>(fn: DecodeFn<T>): Decoder<T>;
81
+ /**
82
+ * Defines a new `Decoder<T>`, by implementing a custom acceptance function.
83
+ * The function receives three arguments:
84
+ *
85
+ * 1. `blob` - the raw/unknown input (aka your external data)
86
+ * 2. `ok` - Call `ok(value)` to accept the input and return ``value``
87
+ * 3. `err` - Call `err(message)` to reject the input with error ``message``
88
+ *
89
+ * The expected return value should be a `DecodeResult<T>`, which can be
90
+ * obtained by returning the result of calling the provided `ok` or `err`
91
+ * helper functions. Please note that `ok()` and `err()` don't perform side
92
+ * effects! You'll need to _return_ those values.
93
+ */
94
+ export function define<T>(fn: AcceptanceFn<T>): Decoder<T>;
package/Decoder.js CHANGED
@@ -19,38 +19,52 @@ function noThrow(fn) {
19
19
  }
20
20
  };
21
21
  }
22
+
23
+ function format(err, formatter) {
24
+ var formatted = formatter(err); // Formatter functions may return a string or an error for convenience of
25
+ // writing them. If it already returns an Error, return it unmodified. If
26
+ // it returns a string, wrap it in a "Decoding error" instance.
27
+
28
+ if (typeof formatted === 'string') {
29
+ var _err = new Error('\n' + formatted);
30
+
31
+ _err.name = 'Decoding error';
32
+ return _err;
33
+ } else {
34
+ return formatted;
35
+ }
36
+ }
22
37
  /**
23
38
  * Defines a new `Decoder<T>`, by implementing a custom acceptance function.
24
39
  * The function receives three arguments:
25
40
  *
26
41
  * 1. `blob` - the raw/unknown input (aka your external data)
27
42
  * 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
43
+ * 3. `err` - Call `err(message)` to reject the input with error ``message``
30
44
  *
31
45
  * 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.
46
+ * obtained by returning the result of calling the provided `ok` or `err`
47
+ * helper functions. Please note that `ok()` and `err()` don't perform side
48
+ * effects! You'll need to _return_ those values.
34
49
  */
35
50
 
36
51
 
37
- function define(decodeFn) {
52
+ function define(fn) {
38
53
  /**
39
- * Validates the raw/untrusted/unknown input and either accepts or rejects
40
- * it.
54
+ * Verifies the untrusted/unknown input and either accepts or rejects it.
41
55
  *
42
56
  * Contrasted with `.verify()`, calls to `.decode()` will never fail and
43
57
  * instead return a result type.
44
58
  */
45
59
  function decode(blob) {
46
- return decodeFn(blob, _result.ok, function (msg) {
60
+ return fn(blob, _result.ok, function (msg) {
47
61
  return (0, _result.err)(typeof msg === 'string' ? (0, _annotate.annotate)(blob, msg) : msg);
48
62
  });
49
63
  }
50
64
  /**
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.
65
+ * Verifies the untrusted/unknown input and either accepts or rejects it.
66
+ * When accepted, returns a value of type `T`. Otherwise fail with
67
+ * a runtime error.
54
68
  */
55
69
 
56
70
 
@@ -64,26 +78,13 @@ function define(decodeFn) {
64
78
  if (result.ok) {
65
79
  return result.value;
66
80
  } 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
+ throw format(result.error, formatter);
81
82
  }
82
83
  }
83
84
  /**
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`.
85
+ * Verifies the untrusted/unknown input and either accepts or rejects it.
86
+ * When accepted, returns the decoded `T` value directly. Otherwise returns
87
+ * `undefined`.
87
88
  *
88
89
  * Use this when you're not interested in programmatically handling the
89
90
  * error message.
@@ -121,23 +122,21 @@ function define(decodeFn) {
121
122
  /**
122
123
  * Chain together the current decoder with another.
123
124
  *
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.
125
+ * > _**NOTE:** This is an advanced, low-level, API. It's not recommended
126
+ * > to reach for this construct unless there is no other way. Most cases can
127
+ * > be covered more elegantly by `.transform()` or `.refine()` instead._
127
128
  *
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.
129
+ * If the current decoder accepts an input, the resulting ``T`` value will
130
+ * get passed into the given ``next`` acceptance function to further decide
131
+ * whether or not the value should get accepted or rejected.
134
132
  *
135
- * If it helps, you can think of `define(nextFn)` as equivalent to
136
- * `unknown.then(nextFn)`.
133
+ * This works similar to how you would `define()` a new decoder, except
134
+ * that the ``blob`` param will now be ``T`` (a known type), rather than
135
+ * ``unknown``. This will allow the function to make a stronger assumption
136
+ * about its input and avoid re-refining inputs.
137
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()`.
138
+ * If it helps, you can think of `define(...)` as equivalent to
139
+ * `unknown.then(...)`.
141
140
  */
142
141
 
143
142
 
package/Decoder.js.flow CHANGED
@@ -4,47 +4,85 @@ import { annotate } from './annotate';
4
4
  import { formatInline } from './format';
5
5
  import { err as makeErr, ok as makeOk } from './result';
6
6
  import type { Annotation } from './annotate';
7
+ import type { Formatter } from './format';
7
8
  import type { Result } from './result';
8
9
 
9
10
  export type Scalar = string | number | boolean | symbol | void | null;
10
11
 
11
12
  export type DecodeResult<T> = Result<T, Annotation>;
12
- export type DecodeFn<T, I = mixed> = (
13
- blob: I,
13
+
14
+ export type AcceptanceFn<T, InputT = mixed> = (
15
+ blob: InputT,
14
16
  ok: (value: T) => DecodeResult<T>,
15
17
  err: (msg: string | Annotation) => DecodeResult<T>,
16
18
  ) => DecodeResult<T>;
17
19
 
20
+ export type Decoder<T> = {|
21
+ /**
22
+ * Verifies untrusted input. Either returns a value, or throws a decoding
23
+ * error.
24
+ */
25
+ verify(blob: mixed, formatter?: Formatter): T,
26
+
27
+ /**
28
+ * Verifies untrusted input. Either returns a value, or returns undefined.
29
+ */
30
+ value(blob: mixed): T | void,
31
+
32
+ /**
33
+ * Verifies untrusted input. Always returns a DecodeResult, which is either
34
+ * an "ok" value or an "error" annotation.
35
+ */
36
+ decode(blob: mixed): DecodeResult<T>,
37
+
38
+ /**
39
+ * Build a new decoder from the the current one, with an extra acceptance
40
+ * criterium.
41
+ */
42
+ refine(predicateFn: (value: T) => boolean, errmsg: string): Decoder<T>,
43
+
44
+ /**
45
+ * Build a new decoder from the current one, with an extra rejection
46
+ * criterium.
47
+ */
48
+ reject(rejectFn: (value: T) => string | Annotation | null): Decoder<T>,
49
+
50
+ /**
51
+ * Build a new decoder from the current one, modifying its outputted value.
52
+ */
53
+ transform<V>(transformFn: (value: T) => V): Decoder<V>,
54
+
55
+ /**
56
+ * Build a new decoder from the current one, with a mutated error message
57
+ * in case of a rejection.
58
+ */
59
+ describe(message: string): Decoder<T>,
60
+
61
+ /**
62
+ * Chain together the current decoder with another acceptance function.
63
+ */
64
+ then<V>(next: AcceptanceFn<V, T>): Decoder<V>,
65
+
66
+ // Experimental APIs (please don't rely on these yet)
67
+ peek_UNSTABLE<V>(next: AcceptanceFn<V, [mixed, T]>): Decoder<V>,
68
+ |};
69
+
18
70
  /**
19
71
  * Helper type to return the "type" of a Decoder.
20
72
  *
21
73
  * You can use it on types:
22
74
  *
23
- * DecoderType<Decoder<string>> // => string
24
- * DecoderType<Decoder<number[]>> // => number[]
75
+ * DecoderType<Decoder<string>> // string
76
+ * DecoderType<Decoder<number[]>> // number[]
25
77
  *
26
78
  * Or on "values", by using the `typeof` keyword:
27
79
  *
28
- * DecoderType<typeof array(string)> // => string[]
29
- * DecoderType<typeof truthy> // => boolean
80
+ * DecoderType<typeof string> // string
81
+ * DecoderType<typeof truthy> // boolean
30
82
  *
31
83
  */
32
84
  export type DecoderType<D> = $Call<<T>(Decoder<T>) => T, D>;
33
85
 
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
86
  function noThrow<T, V>(fn: (value: T) => V): (T) => DecodeResult<V> {
49
87
  return (t) => {
50
88
  try {
@@ -56,65 +94,65 @@ function noThrow<T, V>(fn: (value: T) => V): (T) => DecodeResult<V> {
56
94
  };
57
95
  }
58
96
 
97
+ function format(err: Annotation, formatter: Formatter): Error {
98
+ const formatted = formatter(err);
99
+
100
+ // Formatter functions may return a string or an error for convenience of
101
+ // writing them. If it already returns an Error, return it unmodified. If
102
+ // it returns a string, wrap it in a "Decoding error" instance.
103
+ if (typeof formatted === 'string') {
104
+ const err = new Error('\n' + formatted);
105
+ err.name = 'Decoding error';
106
+ return err;
107
+ } else {
108
+ return formatted;
109
+ }
110
+ }
111
+
59
112
  /**
60
113
  * Defines a new `Decoder<T>`, by implementing a custom acceptance function.
61
114
  * The function receives three arguments:
62
115
  *
63
116
  * 1. `blob` - the raw/unknown input (aka your external data)
64
117
  * 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
118
+ * 3. `err` - Call `err(message)` to reject the input with error ``message``
67
119
  *
68
120
  * 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.
121
+ * obtained by returning the result of calling the provided `ok` or `err`
122
+ * helper functions. Please note that `ok()` and `err()` don't perform side
123
+ * effects! You'll need to _return_ those values.
71
124
  */
72
- export function define<T>(decodeFn: DecodeFn<T>): Decoder<T> {
125
+ export function define<T>(fn: AcceptanceFn<T>): Decoder<T> {
73
126
  /**
74
- * Validates the raw/untrusted/unknown input and either accepts or rejects
75
- * it.
127
+ * Verifies the untrusted/unknown input and either accepts or rejects it.
76
128
  *
77
129
  * Contrasted with `.verify()`, calls to `.decode()` will never fail and
78
130
  * instead return a result type.
79
131
  */
80
132
  function decode(blob: mixed): DecodeResult<T> {
81
- return decodeFn(blob, makeOk, (msg: Annotation | string) =>
133
+ return fn(blob, makeOk, (msg: Annotation | string) =>
82
134
  makeErr(typeof msg === 'string' ? annotate(blob, msg) : msg),
83
135
  );
84
136
  }
85
137
 
86
138
  /**
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.
139
+ * Verifies the untrusted/unknown input and either accepts or rejects it.
140
+ * When accepted, returns a value of type `T`. Otherwise fail with
141
+ * a runtime error.
90
142
  */
91
- function verify(
92
- blob: mixed,
93
- formatter: (Annotation) => string | Error = formatInline,
94
- ): T {
143
+ function verify(blob: mixed, formatter: Formatter = formatInline): T {
95
144
  const result = decode(blob);
96
145
  if (result.ok) {
97
146
  return result.value;
98
147
  } 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
- }
148
+ throw format(result.error, formatter);
111
149
  }
112
150
  }
113
151
 
114
152
  /**
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`.
153
+ * Verifies the untrusted/unknown input and either accepts or rejects it.
154
+ * When accepted, returns the decoded `T` value directly. Otherwise returns
155
+ * `undefined`.
118
156
  *
119
157
  * Use this when you're not interested in programmatically handling the
120
158
  * error message.
@@ -151,25 +189,23 @@ export function define<T>(decodeFn: DecodeFn<T>): Decoder<T> {
151
189
  /**
152
190
  * Chain together the current decoder with another.
153
191
  *
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.
192
+ * > _**NOTE:** This is an advanced, low-level, API. It's not recommended
193
+ * > to reach for this construct unless there is no other way. Most cases can
194
+ * > be covered more elegantly by `.transform()` or `.refine()` instead._
157
195
  *
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.
196
+ * If the current decoder accepts an input, the resulting ``T`` value will
197
+ * get passed into the given ``next`` acceptance function to further decide
198
+ * whether or not the value should get accepted or rejected.
164
199
  *
165
- * If it helps, you can think of `define(nextFn)` as equivalent to
166
- * `unknown.then(nextFn)`.
200
+ * This works similar to how you would `define()` a new decoder, except
201
+ * that the ``blob`` param will now be ``T`` (a known type), rather than
202
+ * ``unknown``. This will allow the function to make a stronger assumption
203
+ * about its input and avoid re-refining inputs.
167
204
  *
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()`.
205
+ * If it helps, you can think of `define(...)` as equivalent to
206
+ * `unknown.then(...)`.
171
207
  */
172
- function then<V>(next: DecodeFn<V, T>): Decoder<V> {
208
+ function then<V>(next: AcceptanceFn<V, T>): Decoder<V> {
173
209
  return define((blob, ok, err) => {
174
210
  const result = decode(blob);
175
211
  return result.ok ? next(result.value, ok, err) : result;
@@ -227,7 +263,7 @@ export function define<T>(decodeFn: DecodeFn<T>): Decoder<T> {
227
263
  *
228
264
  * This is an advanced, low-level, decoder.
229
265
  */
230
- function peek_UNSTABLE<V>(next: DecodeFn<V, [mixed, T]>): Decoder<V> {
266
+ function peek_UNSTABLE<V>(next: AcceptanceFn<V, [mixed, T]>): Decoder<V> {
231
267
  return define((blob, ok, err) => {
232
268
  const result = decode(blob);
233
269
  return result.ok ? next([blob, result.value], ok, err) : result;