decoders 2.0.0-beta1 → 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.
Files changed (165) hide show
  1. package/CHANGELOG.md +53 -5
  2. package/Decoder.d.ts +94 -0
  3. package/Decoder.js +222 -0
  4. package/Decoder.js.flow +286 -0
  5. package/Decoder.mjs +215 -0
  6. package/NotSupportedTSVersion.d.ts +1 -0
  7. package/README.md +124 -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 +6 -0
  17. package/{cjs/format/inline.js → format.js} +7 -2
  18. package/{cjs/format/inline.js.flow → format.js.flow} +9 -3
  19. package/{es/format/inline.js → format.mjs} +5 -2
  20. package/index.d.ts +40 -0
  21. package/index.js +89 -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 +59 -0
  26. package/lib/arrays.js +139 -0
  27. package/lib/arrays.js.flow +138 -0
  28. package/lib/arrays.mjs +124 -0
  29. package/lib/basics.d.ts +93 -0
  30. package/lib/basics.js +144 -0
  31. package/lib/basics.js.flow +124 -0
  32. package/lib/basics.mjs +120 -0
  33. package/lib/booleans.d.ts +16 -0
  34. package/lib/booleans.js +35 -0
  35. package/lib/booleans.js.flow +22 -0
  36. package/lib/booleans.mjs +25 -0
  37. package/lib/dates.d.ts +15 -0
  38. package/lib/dates.js +44 -0
  39. package/lib/dates.js.flow +40 -0
  40. package/lib/dates.mjs +34 -0
  41. package/lib/json.d.ts +35 -0
  42. package/lib/json.js +55 -0
  43. package/lib/json.js.flow +50 -0
  44. package/lib/json.mjs +40 -0
  45. package/lib/numbers.d.ts +31 -0
  46. package/lib/numbers.js +51 -0
  47. package/lib/numbers.js.flow +48 -0
  48. package/lib/numbers.mjs +41 -0
  49. package/lib/objects.d.ts +75 -0
  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 +56 -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 +55 -0
  58. package/lib/unions.js +160 -0
  59. package/lib/unions.js.flow +155 -0
  60. package/lib/unions.mjs +146 -0
  61. package/lib/utilities.d.ts +34 -0
  62. package/lib/utilities.js +75 -0
  63. package/lib/utilities.js.flow +65 -0
  64. package/lib/utilities.mjs +60 -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/boolean.d.ts +0 -5
  147. package/ts/constants.d.ts +0 -11
  148. package/ts/date.d.ts +0 -4
  149. package/ts/describe.d.ts +0 -3
  150. package/ts/dispatch.d.ts +0 -8
  151. package/ts/either.d.ts +0 -61
  152. package/ts/fail.d.ts +0 -3
  153. package/ts/index.d.ts +0 -42
  154. package/ts/inline.d.ts +0 -3
  155. package/ts/instanceOf.d.ts +0 -3
  156. package/ts/json.d.ts +0 -11
  157. package/ts/lazy.d.ts +0 -3
  158. package/ts/mapping.d.ts +0 -4
  159. package/ts/number.d.ts +0 -6
  160. package/ts/object.d.ts +0 -33
  161. package/ts/optional.d.ts +0 -5
  162. package/ts/result.d.ts +0 -39
  163. package/ts/short.d.ts +0 -3
  164. package/ts/string.d.ts +0 -7
  165. package/ts/tuple.d.ts +0 -30
@@ -0,0 +1,246 @@
1
+ // @flow strict
2
+
3
+ import { annotateObject, merge, updateText } from '../annotate';
4
+ import { define } from '../Decoder';
5
+ import type { Annotation } from '../annotate';
6
+ import type { _Any as AnyDecoder } from '../_utils';
7
+ import type { Decoder, DecodeResult } from '../Decoder';
8
+
9
+ function subtract(xs: Set<string>, ys: Set<string>): Set<string> {
10
+ const result = new Set();
11
+ xs.forEach((x) => {
12
+ if (!ys.has(x)) {
13
+ result.add(x);
14
+ }
15
+ });
16
+ return result;
17
+ }
18
+
19
+ /**
20
+ * Accepts any "plain old JavaScript object", but doesn't validate its keys or
21
+ * values further.
22
+ */
23
+ export const pojo: Decoder<{| [string]: mixed |}> = define((blob, ok, err) =>
24
+ blob !== null &&
25
+ blob !== undefined &&
26
+ typeof blob === 'object' &&
27
+ // This still seems to be the only reliable way to determine whether
28
+ // something is a pojo... ¯\_(ツ)_/¯
29
+ // $FlowFixMe[method-unbinding]
30
+ Object.prototype.toString.call(blob) === '[object Object]'
31
+ ? ok(
32
+ // NOTE:
33
+ // Since Flow 0.98, typeof o === 'object' refines to
34
+ // {| +[string]: mixed |}
35
+ // instead of
36
+ // {| [string]: mixed |}
37
+ //
38
+ // For rationale, see https://github.com/facebook/flow/issues/7685.
39
+ // In this case, we don't want to output a read-only version of
40
+ // the object because it's up to the user of decoders to
41
+ // determine what they want to do with the decoded output. If they
42
+ // want to write items into the array, that's fine! The fastest
43
+ // way to turn a read-only Object to a writeable one in ES6 seems
44
+ // to be to use object-spread. (Going off this benchmark:
45
+ // https://thecodebarbarian.com/object-assign-vs-object-spread.html)
46
+ { ...blob },
47
+ )
48
+ : err('Must be an object'),
49
+ );
50
+
51
+ /**
52
+ * Accepts objects with fields matching the given decoders. Extra fields that
53
+ * exist on the input object are ignored and will not be returned.
54
+ */
55
+ export function object<O: { +[field: string]: AnyDecoder, ... }>(
56
+ decodersByKey: O,
57
+ ): Decoder<$ObjMap<O, <T>(Decoder<T>) => T>> {
58
+ // Compute this set at decoder definition time
59
+ const knownKeys = new Set(Object.keys(decodersByKey));
60
+
61
+ return pojo.then((plainObj, ok, err) => {
62
+ const actualKeys = new Set(Object.keys(plainObj));
63
+
64
+ // At this point, "missingKeys" will also include all fields that may
65
+ // validly be optional. We'll let the underlying decoder decide and
66
+ // remove the key from this missing set if the decoder accepts the
67
+ // value.
68
+ const missingKeys = subtract(knownKeys, actualKeys);
69
+
70
+ let record = {};
71
+ let errors: { [key: string]: Annotation } | null = null;
72
+
73
+ Object.keys(decodersByKey).forEach((key) => {
74
+ const decoder = decodersByKey[key];
75
+ const rawValue = plainObj[key];
76
+ const result: DecodeResult<mixed> = decoder.decode(rawValue);
77
+
78
+ if (result.ok) {
79
+ const value = result.value;
80
+ if (value !== undefined) {
81
+ record[key] = value;
82
+ }
83
+
84
+ // If this succeeded, remove the key from the missing keys
85
+ // tracker
86
+ missingKeys.delete(key);
87
+ } else {
88
+ const ann = result.error;
89
+
90
+ // Keep track of the annotation, but don't return just yet. We
91
+ // want to collect more error information.
92
+ if (rawValue === undefined) {
93
+ // Explicitly add it to the missing set if the value is
94
+ // undefined. This covers explicit undefineds to be
95
+ // treated the same as implicit undefineds (aka missing
96
+ // keys).
97
+ missingKeys.add(key);
98
+ } else {
99
+ if (errors === null) {
100
+ errors = {};
101
+ }
102
+ errors[key] = ann;
103
+ }
104
+ }
105
+ });
106
+
107
+ // Deal with errors now. There are two classes of errors we want to
108
+ // report. First of all, we want to report any inline errors in this
109
+ // object. Lastly, any fields that are missing should be annotated on
110
+ // the outer object itself.
111
+ if (errors || missingKeys.size > 0) {
112
+ let objAnn = annotateObject(plainObj);
113
+
114
+ if (errors) {
115
+ objAnn = merge(objAnn, errors);
116
+ }
117
+
118
+ if (missingKeys.size > 0) {
119
+ const errMsg = Array.from(missingKeys)
120
+ .map((key) => `"${key}"`)
121
+ .join(', ');
122
+ const pluralized = missingKeys.size > 1 ? 'keys' : 'key';
123
+ objAnn = updateText(objAnn, `Missing ${pluralized}: ${errMsg}`);
124
+ }
125
+
126
+ return err(objAnn);
127
+ }
128
+
129
+ return ok(record);
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Like `object()`, but will reject inputs that contain extra fields that are
135
+ * not specified explicitly.
136
+ */
137
+ export function exact<O: { +[field: string]: AnyDecoder, ... }>(
138
+ decodersByKey: O,
139
+ ): Decoder<$ObjMap<$Exact<O>, <T>(Decoder<T>) => T>> {
140
+ // Compute this set at decoder definition time
141
+ const allowedKeys = new Set(Object.keys(decodersByKey));
142
+
143
+ // Check the inputted object for any unexpected extra keys
144
+ const checked = pojo.reject((plainObj) => {
145
+ const actualKeys = new Set(Object.keys(plainObj));
146
+ const extraKeys = subtract(actualKeys, allowedKeys);
147
+ return extraKeys.size > 0
148
+ ? `Unexpected extra keys: ${Array.from(extraKeys).join(', ')}`
149
+ : // Don't reject
150
+ null;
151
+ });
152
+
153
+ // Defer to the "object" decoder for doing the real decoding work. Since
154
+ // we made sure there are no superfluous keys in this structure, it's now
155
+ // safe to force-cast it to an $Exact<> type.
156
+ return checked.then(object(decodersByKey).decode);
157
+ }
158
+
159
+ /**
160
+ * Like `object()`, but will pass through any extra fields on the input object
161
+ * unvalidated that will thus be of `unknown` type statically.
162
+ */
163
+ export function inexact<O: { +[field: string]: AnyDecoder }>(
164
+ decodersByKey: O,
165
+ ): Decoder<$ObjMap<O, <T>(Decoder<T>) => T> & { +[string]: mixed }> {
166
+ return pojo.then((plainObj) => {
167
+ const allkeys = new Set(Object.keys(plainObj));
168
+ const decoder = object(decodersByKey).transform(
169
+ (safepart: $ObjMap<O, <T>(Decoder<T>) => T>) => {
170
+ const safekeys = new Set(Object.keys(decodersByKey));
171
+
172
+ // To account for hard-coded keys that aren't part of the input
173
+ safekeys.forEach((k) => allkeys.add(k));
174
+
175
+ const rv = {};
176
+ allkeys.forEach((k) => {
177
+ if (safekeys.has(k)) {
178
+ const value = safepart[k];
179
+ if (value !== undefined) {
180
+ rv[k] = value;
181
+ }
182
+ } else {
183
+ rv[k] = plainObj[k];
184
+ }
185
+ });
186
+ return rv;
187
+ },
188
+ );
189
+ return decoder.decode(plainObj);
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Accepts objects where all values match the given decoder, and returns the
195
+ * result as a `{ [string]: T }`.
196
+ *
197
+ * The main difference between `object()` and `dict()` is that you'd typically
198
+ * use `object()` if this is a record-like object, where all field names are
199
+ * known and the values are heterogeneous. Whereas with `dict()` the keys are
200
+ * typically dynamic and the values homogeneous, like in a dictionary,
201
+ * a lookup table, or a cache.
202
+ */
203
+ export function dict<T>(decoder: Decoder<T>): Decoder<{ [string]: T }> {
204
+ return pojo.then((plainObj, ok, err) => {
205
+ let rv: { [key: string]: T } = {};
206
+ let errors: { [key: string]: Annotation } | null = null;
207
+
208
+ Object.keys(plainObj).forEach((key: string) => {
209
+ const value = plainObj[key];
210
+ const result = decoder.decode(value);
211
+ if (result.ok) {
212
+ if (errors === null) {
213
+ rv[key] = result.value;
214
+ }
215
+ } else {
216
+ rv = {}; // Clear the success value so it can get garbage collected early
217
+ if (errors === null) {
218
+ errors = {};
219
+ }
220
+ errors[key] = result.error;
221
+ }
222
+ });
223
+
224
+ if (errors !== null) {
225
+ return err(merge(annotateObject(plainObj), errors));
226
+ } else {
227
+ return ok(rv);
228
+ }
229
+ });
230
+ }
231
+
232
+ /**
233
+ * Similar to `dict()`, but returns the result as a `Map<string, T>` (an [ES6
234
+ * Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map))
235
+ * instead.
236
+ */
237
+ export function mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>> {
238
+ return dict(decoder).transform(
239
+ (obj) =>
240
+ new Map(
241
+ // This is effectively Object.entries(obj), but in a way that Flow
242
+ // will know the types are okay
243
+ Object.keys(obj).map((key) => [key, obj[key]]),
244
+ ),
245
+ );
246
+ }
@@ -0,0 +1,223 @@
1
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+
3
+ import { annotateObject, merge, updateText } from '../annotate.mjs';
4
+ import { define } from '../Decoder.mjs';
5
+
6
+ function subtract(xs, ys) {
7
+ var result = new Set();
8
+ xs.forEach(function (x) {
9
+ if (!ys.has(x)) {
10
+ result.add(x);
11
+ }
12
+ });
13
+ return result;
14
+ }
15
+ /**
16
+ * Accepts any "plain old JavaScript object", but doesn't validate its keys or
17
+ * values further.
18
+ */
19
+
20
+
21
+ export var pojo = define(function (blob, ok, err) {
22
+ return blob !== null && blob !== undefined && typeof blob === 'object' && // This still seems to be the only reliable way to determine whether
23
+ // something is a pojo... ¯\_(ツ)_/¯
24
+ // $FlowFixMe[method-unbinding]
25
+ Object.prototype.toString.call(blob) === '[object Object]' ? ok( // NOTE:
26
+ // Since Flow 0.98, typeof o === 'object' refines to
27
+ // {| +[string]: mixed |}
28
+ // instead of
29
+ // {| [string]: mixed |}
30
+ //
31
+ // For rationale, see https://github.com/facebook/flow/issues/7685.
32
+ // In this case, we don't want to output a read-only version of
33
+ // the object because it's up to the user of decoders to
34
+ // determine what they want to do with the decoded output. If they
35
+ // want to write items into the array, that's fine! The fastest
36
+ // way to turn a read-only Object to a writeable one in ES6 seems
37
+ // to be to use object-spread. (Going off this benchmark:
38
+ // https://thecodebarbarian.com/object-assign-vs-object-spread.html)
39
+ _extends({}, blob)) : err('Must be an object');
40
+ });
41
+ /**
42
+ * Accepts objects with fields matching the given decoders. Extra fields that
43
+ * exist on the input object are ignored and will not be returned.
44
+ */
45
+
46
+ export function object(decodersByKey) {
47
+ // Compute this set at decoder definition time
48
+ var knownKeys = new Set(Object.keys(decodersByKey));
49
+ return pojo.then(function (plainObj, ok, err) {
50
+ var actualKeys = new Set(Object.keys(plainObj)); // At this point, "missingKeys" will also include all fields that may
51
+ // validly be optional. We'll let the underlying decoder decide and
52
+ // remove the key from this missing set if the decoder accepts the
53
+ // value.
54
+
55
+ var missingKeys = subtract(knownKeys, actualKeys);
56
+ var record = {};
57
+ var errors = null;
58
+ Object.keys(decodersByKey).forEach(function (key) {
59
+ var decoder = decodersByKey[key];
60
+ var rawValue = plainObj[key];
61
+ var result = decoder.decode(rawValue);
62
+
63
+ if (result.ok) {
64
+ var value = result.value;
65
+
66
+ if (value !== undefined) {
67
+ record[key] = value;
68
+ } // If this succeeded, remove the key from the missing keys
69
+ // tracker
70
+
71
+
72
+ missingKeys["delete"](key);
73
+ } else {
74
+ var ann = result.error; // Keep track of the annotation, but don't return just yet. We
75
+ // want to collect more error information.
76
+
77
+ if (rawValue === undefined) {
78
+ // Explicitly add it to the missing set if the value is
79
+ // undefined. This covers explicit undefineds to be
80
+ // treated the same as implicit undefineds (aka missing
81
+ // keys).
82
+ missingKeys.add(key);
83
+ } else {
84
+ if (errors === null) {
85
+ errors = {};
86
+ }
87
+
88
+ errors[key] = ann;
89
+ }
90
+ }
91
+ }); // Deal with errors now. There are two classes of errors we want to
92
+ // report. First of all, we want to report any inline errors in this
93
+ // object. Lastly, any fields that are missing should be annotated on
94
+ // the outer object itself.
95
+
96
+ if (errors || missingKeys.size > 0) {
97
+ var objAnn = annotateObject(plainObj);
98
+
99
+ if (errors) {
100
+ objAnn = merge(objAnn, errors);
101
+ }
102
+
103
+ if (missingKeys.size > 0) {
104
+ var errMsg = Array.from(missingKeys).map(function (key) {
105
+ return "\"" + key + "\"";
106
+ }).join(', ');
107
+ var pluralized = missingKeys.size > 1 ? 'keys' : 'key';
108
+ objAnn = updateText(objAnn, "Missing " + pluralized + ": " + errMsg);
109
+ }
110
+
111
+ return err(objAnn);
112
+ }
113
+
114
+ return ok(record);
115
+ });
116
+ }
117
+ /**
118
+ * Like `object()`, but will reject inputs that contain extra fields that are
119
+ * not specified explicitly.
120
+ */
121
+
122
+ export function exact(decodersByKey) {
123
+ // Compute this set at decoder definition time
124
+ var allowedKeys = new Set(Object.keys(decodersByKey)); // Check the inputted object for any unexpected extra keys
125
+
126
+ var checked = pojo.reject(function (plainObj) {
127
+ var actualKeys = new Set(Object.keys(plainObj));
128
+ var extraKeys = subtract(actualKeys, allowedKeys);
129
+ return extraKeys.size > 0 ? "Unexpected extra keys: " + Array.from(extraKeys).join(', ') : // Don't reject
130
+ null;
131
+ }); // Defer to the "object" decoder for doing the real decoding work. Since
132
+ // we made sure there are no superfluous keys in this structure, it's now
133
+ // safe to force-cast it to an $Exact<> type.
134
+
135
+ return checked.then(object(decodersByKey).decode);
136
+ }
137
+ /**
138
+ * Like `object()`, but will pass through any extra fields on the input object
139
+ * unvalidated that will thus be of `unknown` type statically.
140
+ */
141
+
142
+ export function inexact(decodersByKey) {
143
+ return pojo.then(function (plainObj) {
144
+ var allkeys = new Set(Object.keys(plainObj));
145
+ var decoder = object(decodersByKey).transform(function (safepart) {
146
+ var safekeys = new Set(Object.keys(decodersByKey)); // To account for hard-coded keys that aren't part of the input
147
+
148
+ safekeys.forEach(function (k) {
149
+ return allkeys.add(k);
150
+ });
151
+ var rv = {};
152
+ allkeys.forEach(function (k) {
153
+ if (safekeys.has(k)) {
154
+ var value = safepart[k];
155
+
156
+ if (value !== undefined) {
157
+ rv[k] = value;
158
+ }
159
+ } else {
160
+ rv[k] = plainObj[k];
161
+ }
162
+ });
163
+ return rv;
164
+ });
165
+ return decoder.decode(plainObj);
166
+ });
167
+ }
168
+ /**
169
+ * Accepts objects where all values match the given decoder, and returns the
170
+ * result as a `{ [string]: T }`.
171
+ *
172
+ * The main difference between `object()` and `dict()` is that you'd typically
173
+ * use `object()` if this is a record-like object, where all field names are
174
+ * known and the values are heterogeneous. Whereas with `dict()` the keys are
175
+ * typically dynamic and the values homogeneous, like in a dictionary,
176
+ * a lookup table, or a cache.
177
+ */
178
+
179
+ export function dict(decoder) {
180
+ return pojo.then(function (plainObj, ok, err) {
181
+ var rv = {};
182
+ var errors = null;
183
+ Object.keys(plainObj).forEach(function (key) {
184
+ var value = plainObj[key];
185
+ var result = decoder.decode(value);
186
+
187
+ if (result.ok) {
188
+ if (errors === null) {
189
+ rv[key] = result.value;
190
+ }
191
+ } else {
192
+ rv = {}; // Clear the success value so it can get garbage collected early
193
+
194
+ if (errors === null) {
195
+ errors = {};
196
+ }
197
+
198
+ errors[key] = result.error;
199
+ }
200
+ });
201
+
202
+ if (errors !== null) {
203
+ return err(merge(annotateObject(plainObj), errors));
204
+ } else {
205
+ return ok(rv);
206
+ }
207
+ });
208
+ }
209
+ /**
210
+ * Similar to `dict()`, but returns the result as a `Map<string, T>` (an [ES6
211
+ * Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map))
212
+ * instead.
213
+ */
214
+
215
+ export function mapping(decoder) {
216
+ return dict(decoder).transform(function (obj) {
217
+ return new Map( // This is effectively Object.entries(obj), but in a way that Flow
218
+ // will know the types are okay
219
+ Object.keys(obj).map(function (key) {
220
+ return [key, obj[key]];
221
+ }));
222
+ });
223
+ }
@@ -0,0 +1,56 @@
1
+ /// <reference lib="dom" />
2
+
3
+ import { Decoder } from '../Decoder';
4
+
5
+ /**
6
+ * Accepts and returns strings.
7
+ */
8
+ export const string: Decoder<string>;
9
+
10
+ /**
11
+ * Like `string`, but will reject the empty string or strings containing only whitespace.
12
+ */
13
+ export const nonEmptyString: Decoder<string>;
14
+
15
+ /**
16
+ * Accepts and returns strings that match the given regular expression.
17
+ */
18
+ export function regex(regex: RegExp, msg: string): Decoder<string>;
19
+
20
+ /**
21
+ * Accepts and returns strings that are syntactically valid email addresses.
22
+ * (This will not mean that the email address actually exist.)
23
+ */
24
+ export const email: Decoder<string>;
25
+
26
+ /**
27
+ * Accepts strings that are valid URLs, returns the value as a URL instance.
28
+ */
29
+ export const url: Decoder<URL>;
30
+
31
+ /**
32
+ * Accepts strings that are valid URLs, but only HTTPS ones. Returns the value
33
+ * as a URL instance.
34
+ */
35
+ export const httpsUrl: Decoder<URL>;
36
+
37
+ /**
38
+ * Accepts strings that are valid
39
+ * [UUIDs](https://en.wikipedia.org/wiki/universally_unique_identifier)
40
+ * (universally unique identifier).
41
+ */
42
+ export const uuid: Decoder<string>;
43
+
44
+ /**
45
+ * Like `uuid`, but only accepts
46
+ * [UUIDv1](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_%28date-time_and_MAC_address%29)
47
+ * strings.
48
+ */
49
+ export const uuidv1: Decoder<string>;
50
+
51
+ /**
52
+ * Like `uuid`, but only accepts
53
+ * [UUIDv4](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_%28random%29)
54
+ * strings.
55
+ */
56
+ export const uuidv4: Decoder<string>;
package/lib/strings.js ADDED
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.nonEmptyString = exports.httpsUrl = exports.email = void 0;
5
+ exports.regex = regex;
6
+ exports.uuidv4 = exports.uuidv1 = exports.uuid = exports.url = exports.string = void 0;
7
+
8
+ var _Decoder = require("../Decoder");
9
+
10
+ var _unions = require("./unions");
11
+
12
+ var _utilities = require("./utilities");
13
+
14
+ /** Match groups in this regex:
15
+ * \1 - the scheme
16
+ * \2 - the username/password (optional)
17
+ * \3 - the host
18
+ * \4 - the port (optional)
19
+ * \5 - the path (optional)
20
+ */
21
+ var url_re = /^([A-Za-z]{3,9}(?:[+][A-Za-z]{3,9})?):\/\/(?:([-;:&=+$,\w]+)@)?(?:([A-Za-z0-9.-]+)(?::([0-9]{2,5}))?)(\/(?:[-+~%/.,\w]*)?(?:\?[-+=&;%@.,\w]*)?(?:#[.,!/\w]*)?)?$/;
22
+ /**
23
+ * Accepts and returns strings.
24
+ */
25
+
26
+ var string = (0, _Decoder.define)(function (blob, ok, err) {
27
+ return typeof blob === 'string' ? ok(blob) : err('Must be string');
28
+ });
29
+ /**
30
+ * Like `string`, but will reject the empty string or strings containing only whitespace.
31
+ */
32
+
33
+ exports.string = string;
34
+ var nonEmptyString = regex(/\S/, 'Must be non-empty string');
35
+ /**
36
+ * Accepts and returns strings that match the given regular expression.
37
+ */
38
+
39
+ exports.nonEmptyString = nonEmptyString;
40
+
41
+ function regex(regex, msg) {
42
+ return string.refine(function (s) {
43
+ return regex.test(s);
44
+ }, msg);
45
+ }
46
+ /**
47
+ * Accepts and returns strings that are syntactically valid email addresses.
48
+ * (This will not mean that the email address actually exist.)
49
+ */
50
+
51
+
52
+ var email = regex( // The almost perfect email regex, taken from https://emailregex.com/
53
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 'Must be email');
54
+ /**
55
+ * Accepts strings that are valid URLs, returns the value as a URL instance.
56
+ */
57
+
58
+ exports.email = email;
59
+ var url = (0, _unions.either)(regex(url_re, 'Must be URL').transform(function (value) {
60
+ return new URL(value);
61
+ }), (0, _utilities.instanceOf)(URL));
62
+ /**
63
+ * Accepts strings that are valid URLs, but only HTTPS ones. Returns the value
64
+ * as a URL instance.
65
+ */
66
+
67
+ exports.url = url;
68
+ var httpsUrl = url.refine(function (value) {
69
+ return value.protocol === 'https:';
70
+ }, 'Must be an HTTPS URL');
71
+ /**
72
+ * Accepts strings that are valid
73
+ * [UUIDs](https://en.wikipedia.org/wiki/universally_unique_identifier)
74
+ * (universally unique identifier).
75
+ */
76
+
77
+ exports.httpsUrl = httpsUrl;
78
+ var uuid = regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, 'Must be uuid');
79
+ /**
80
+ * Like `uuid`, but only accepts
81
+ * [UUIDv1](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_%28date-time_and_MAC_address%29)
82
+ * strings.
83
+ */
84
+
85
+ exports.uuid = uuid;
86
+ var uuidv1 = // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address)
87
+ uuid.refine(function (value) {
88
+ return value[14] === '1';
89
+ }, 'Must be uuidv1');
90
+ /**
91
+ * Like `uuid`, but only accepts
92
+ * [UUIDv4](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_%28random%29)
93
+ * strings.
94
+ */
95
+
96
+ exports.uuidv1 = uuidv1;
97
+ var uuidv4 = // https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)
98
+ uuid.refine(function (value) {
99
+ return value[14] === '4';
100
+ }, 'Must be uuidv4');
101
+ exports.uuidv4 = uuidv4;