@raoh/core 0.9.0-dev.15.20261004151401.ge5893e0cc07d

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.
@@ -0,0 +1,140 @@
1
+ import { Decimal } from "./decimal.ts";
2
+ import { Chain } from "./decoder.ts";
3
+ /** A decoder of strings, with the operations on strings. */
4
+ export declare class StringDecoder extends Chain<string> {
5
+ #private;
6
+ /** Gives `too_short` where the string has fewer code points than `min`. */
7
+ minLength(min: number, message?: string): this;
8
+ /** Gives `too_long` where the string has more code points than `max`. */
9
+ maxLength(max: number, message?: string): this;
10
+ /** Gives `invalid_length` where the string does not have exactly `length` code points. */
11
+ fixedLength(length: number, message?: string): this;
12
+ /** Gives `not_allowed`, the allowed strings in code point order, where the string is none of them. */
13
+ oneOf(allowed: readonly string[], message?: string): this;
14
+ /** Gives `invalid_format.starts_with` unless the string starts with `prefix`. */
15
+ startsWith(prefix: string, message?: string): this;
16
+ /** Gives `invalid_format.ends_with` unless the string ends with `suffix`. */
17
+ endsWith(suffix: string, message?: string): this;
18
+ /** Gives `invalid_format.includes` unless the string includes `substring`. */
19
+ includes(substring: string, message?: string): this;
20
+ /** Gives `invalid_format.email` unless the string is an e-mail address of RFC 5321's ASCII profile. */
21
+ email(message?: string): this;
22
+ /** Gives `invalid_format.ipv4` unless the string is an IPv4 address. */
23
+ ipv4(message?: string): this;
24
+ /** Gives `invalid_format.ipv6` unless the string is an IPv6 address, with a zone only where one is allowed. */
25
+ ipv6(message?: string): this;
26
+ /** Gives `invalid_format.ip` unless the string is an IPv4 or an IPv6 address. */
27
+ ip(message?: string): this;
28
+ /** Gives `invalid_format.ulid` unless the string is a ULID; the string is given as it is. */
29
+ ulid(message?: string): this;
30
+ /** Gives `invalid_format.cuid` unless the string is a CUID of version 1. */
31
+ cuid(message?: string): this;
32
+ /** Reads a UUID, in either case, and gives it in lower case; anything else gives `invalid_format.uuid`. */
33
+ uuid(message?: string): Chain<string>;
34
+ /** Reads an RFC 3986 URI, as written; anything else gives `invalid_format.uri`. */
35
+ uri(message?: string): Chain<string>;
36
+ /** Reads an http or https URI with a non-empty host; anything else gives `invalid_format.url`. */
37
+ url(message?: string): Chain<string>;
38
+ /** Reads an int32 written with an optional sign and ASCII digits. */
39
+ toInt(message?: string): IntDecoder;
40
+ /** Reads an int64 written with an optional sign and ASCII digits. */
41
+ toLong(message?: string): LongDecoder;
42
+ /** Reads a decimal, keeping the scale it is written with. */
43
+ toDecimal(message?: string): DecimalDecoder;
44
+ /** Reads true, 1, yes or on as true and false, 0, no or off as false, in either case of ASCII. */
45
+ toBool(message?: string): BoolDecoder;
46
+ }
47
+ /** How the values a numeric decoder reads are ordered and held in an issue's metadata. */
48
+ interface Arithmetic<T> {
49
+ compare(a: T, b: T): number;
50
+ meta(value: T): unknown;
51
+ /** The least value `positive` allows, and the greatest `negative` allows, as metadata. */
52
+ readonly positiveMin: unknown;
53
+ readonly negativeMax: unknown;
54
+ readonly zero: T;
55
+ }
56
+ /** A decoder of numbers, with the operations that bound them. */
57
+ export declare abstract class BoundedDecoder<T> extends Chain<T> {
58
+ protected abstract get arithmetic(): Arithmetic<T>;
59
+ metaValue(value: T): unknown;
60
+ /** Gives `out_of_range.minimum` where the value is less than `min`. */
61
+ min(min: T, message?: string): this;
62
+ /** Gives `out_of_range.maximum` where the value is greater than `max`. */
63
+ max(max: T, message?: string): this;
64
+ /** Gives `out_of_range.range` where the value is outside [min, max]. */
65
+ range(min: T, max: T, message?: string): this;
66
+ /** Gives `out_of_range.positive` unless the value is greater than zero. */
67
+ positive(message?: string): this;
68
+ /** Gives `out_of_range.negative` unless the value is less than zero; -0 is. */
69
+ negative(message?: string): this;
70
+ /** Gives `out_of_range.non_negative` where the value is less than zero; -0 is. */
71
+ nonNegative(message?: string): this;
72
+ /** Gives `out_of_range.non_positive` where the value is greater than zero. */
73
+ nonPositive(message?: string): this;
74
+ /** `oneOf` for the numeric decoders that have it. */
75
+ protected allowing(allowed: readonly T[], message: string | undefined): this;
76
+ /** `multipleOf` for the numeric decoders that have it. */
77
+ protected multiples(divisor: T, isMultiple: (value: T) => boolean, message: string | undefined): this;
78
+ }
79
+ /** A decoder of int32 values, as JavaScript numbers. */
80
+ export declare class IntDecoder extends BoundedDecoder<number> {
81
+ protected get arithmetic(): Arithmetic<number>;
82
+ /** Gives `not_allowed`, the allowed values in ascending order, where the value is none of them. */
83
+ oneOf(allowed: readonly number[], message?: string): this;
84
+ /** Gives `not_multiple_of` where the value is not a multiple of `divisor`. */
85
+ multipleOf(divisor: number, message?: string): this;
86
+ }
87
+ /** A decoder of int64 values, as bigints. */
88
+ export declare class LongDecoder extends BoundedDecoder<bigint> {
89
+ protected get arithmetic(): Arithmetic<bigint>;
90
+ /** Gives `not_allowed`, the allowed values in ascending order, where the value is none of them. */
91
+ oneOf(allowed: readonly bigint[], message?: string): this;
92
+ /** Gives `not_multiple_of` where the value is not a multiple of `divisor`. */
93
+ multipleOf(divisor: bigint, message?: string): this;
94
+ }
95
+ /**
96
+ * A decoder of float32 values, as JavaScript numbers holding a float32 value. Bounds are compared
97
+ * in the float order of the value model, in which -0 is less than +0 and NaN is the greatest.
98
+ */
99
+ export declare class FloatDecoder extends BoundedDecoder<number> {
100
+ protected get arithmetic(): Arithmetic<number>;
101
+ min(min: number, message?: string): this;
102
+ max(max: number, message?: string): this;
103
+ range(min: number, max: number, message?: string): this;
104
+ /** Gives `not_allowed`, the allowed values in the float order, where the value is none of them. */
105
+ oneOf(allowed: readonly number[], message?: string): this;
106
+ }
107
+ /** A decoder of float64 values. Bounds are compared in the float order of the value model. */
108
+ export declare class DoubleDecoder extends BoundedDecoder<number> {
109
+ protected get arithmetic(): Arithmetic<number>;
110
+ /** Gives `not_allowed`, the allowed values in the float order, where the value is none of them. */
111
+ oneOf(allowed: readonly number[], message?: string): this;
112
+ }
113
+ /** A decoder of decimals, which keep the scale they are written with; bounds compare them by value. */
114
+ export declare class DecimalDecoder extends BoundedDecoder<Decimal> {
115
+ protected get arithmetic(): Arithmetic<Decimal>;
116
+ /** Gives `not_multiple_of` where the value is not an integer multiple of `divisor`. */
117
+ multipleOf(divisor: Decimal, message?: string): this;
118
+ /** Gives `invalid_scale` where the decimal's scale is greater than `max`. */
119
+ scale(max: number, message?: string): this;
120
+ }
121
+ /** A decoder of booleans. */
122
+ export declare class BoolDecoder extends Chain<boolean> {
123
+ /** Gives `invalid_value`, expected true, for false. */
124
+ isTrue(message?: string): this;
125
+ }
126
+ /** A decoder of strings: a JSON string. */
127
+ export declare function string(): StringDecoder;
128
+ /** A decoder of int32 values: a JSON number written as an integer, within the int32 range. */
129
+ export declare function int(): IntDecoder;
130
+ /** A decoder of int64 values: a JSON number written as an integer, within the int64 range, as a bigint. */
131
+ export declare function long(): LongDecoder;
132
+ /** A decoder of float32 values: any JSON number, rounded once to the nearest float32. */
133
+ export declare function float(): FloatDecoder;
134
+ /** A decoder of float64 values: any JSON number, rounded once to the nearest float64. */
135
+ export declare function double(): DoubleDecoder;
136
+ /** A decoder of decimals: any JSON number, at the scale its lexeme gives. */
137
+ export declare function decimal(): DecimalDecoder;
138
+ /** A decoder of booleans: a JSON boolean. */
139
+ export declare function bool(): BoolDecoder;
140
+ export {};
@@ -0,0 +1,427 @@
1
+ // The decoders of scalars, and the operations each kind of scalar has.
2
+ import { Decimal } from "./decimal.js";
3
+ import { Chain } from "./decoder.js";
4
+ import { Float, nearestFloat } from "./float.js";
5
+ import { kindOf, lexemeOf } from "./input.js";
6
+ import { Issue, failed, ok } from "./issue.js";
7
+ import { compareValues, includesSame } from "./meta.js";
8
+ import { isCuid, isEmail, isIpv4, isIpv6, isUlid, isUuid, uriParts } from "./text.js";
9
+ const REQUIRED = new Issue("required");
10
+ function typeMismatch(expected, input) {
11
+ return new Issue("type_mismatch", { meta: { expected, actual: kindOf(input) } });
12
+ }
13
+ function numericRange(expected) {
14
+ return new Issue("type_mismatch", { messageKey: "type_mismatch.numeric_range", meta: { expected } });
15
+ }
16
+ /**
17
+ * A scalar decoder's first step: absent or null gives `required`, a value of another kind
18
+ * `type_mismatch`, and a value of the kind what `read` makes of it.
19
+ */
20
+ function scalar(expected, kind, read) {
21
+ return (input, path) => {
22
+ if (input === undefined || input === null) {
23
+ return failed(REQUIRED.under(path));
24
+ }
25
+ if (kindOf(input) !== kind) {
26
+ return failed(typeMismatch(expected, input).under(path));
27
+ }
28
+ const made = read(input);
29
+ return made.issues === undefined ? made : failed(made.issues.under(path));
30
+ };
31
+ }
32
+ const INTEGER = /^-?(?:0|[1-9][0-9]*)$/;
33
+ const INT32_MIN = -(2n ** 31n);
34
+ const INT32_MAX = 2n ** 31n - 1n;
35
+ const INT64_MIN = -(2n ** 63n);
36
+ const INT64_MAX = 2n ** 63n - 1n;
37
+ /** An integer of the input model within [min, max], or the issue that says why it is not one. */
38
+ function integer(input, expected, min, max) {
39
+ const lexeme = lexemeOf(input);
40
+ if (lexeme === undefined || !INTEGER.test(lexeme)) {
41
+ return failed(typeMismatch(expected, input));
42
+ }
43
+ const value = BigInt(lexeme);
44
+ return value < min || value > max ? failed(numericRange(expected)) : ok(value);
45
+ }
46
+ /** The parts of a JSON number's lexeme: its sign, its digits as one integer, and the power of ten they are scaled by. */
47
+ function numberParts(lexeme) {
48
+ const decimal = Decimal.parse(lexeme);
49
+ if (decimal === undefined) {
50
+ return undefined;
51
+ }
52
+ const negative = lexeme.startsWith("-");
53
+ return { negative, coefficient: negative ? -decimal.coefficient : decimal.coefficient, exponent: -decimal.scale };
54
+ }
55
+ function floatOf(input, expected, width) {
56
+ // A JavaScript number is already a float64, so it is the value, or rounded once to a float32.
57
+ if (typeof input === "number") {
58
+ if (!Number.isFinite(input)) {
59
+ return failed(typeMismatch(expected, input));
60
+ }
61
+ const value = width === 32 ? Math.fround(input) : input;
62
+ return Number.isFinite(value) ? ok(value) : failed(numericRange(expected));
63
+ }
64
+ const lexeme = lexemeOf(input);
65
+ const parts = lexeme === undefined ? undefined : numberParts(lexeme);
66
+ if (parts === undefined) {
67
+ return failed(typeMismatch(expected, input));
68
+ }
69
+ const value = nearestFloat(parts.negative, parts.coefficient, parts.exponent, width);
70
+ return Number.isFinite(value) ? ok(value) : failed(numericRange(expected));
71
+ }
72
+ /** The number of Unicode code points in `text`. */
73
+ function codePoints(text) {
74
+ let count = 0;
75
+ for (const _ of text) {
76
+ count += 1;
77
+ }
78
+ return count;
79
+ }
80
+ /** A decoder of strings, with the operations on strings. */
81
+ export class StringDecoder extends Chain {
82
+ /** Gives `too_short` where the string has fewer code points than `min`. */
83
+ minLength(min, message) {
84
+ return this.check((s) => {
85
+ const actual = codePoints(s);
86
+ return actual < min ? new Issue("too_short", { meta: { min, actual } }) : undefined;
87
+ }, message);
88
+ }
89
+ /** Gives `too_long` where the string has more code points than `max`. */
90
+ maxLength(max, message) {
91
+ return this.check((s) => {
92
+ const actual = codePoints(s);
93
+ return actual > max ? new Issue("too_long", { meta: { max, actual } }) : undefined;
94
+ }, message);
95
+ }
96
+ /** Gives `invalid_length` where the string does not have exactly `length` code points. */
97
+ fixedLength(length, message) {
98
+ return this.check((s) => {
99
+ const actual = codePoints(s);
100
+ return actual !== length ? new Issue("invalid_length", { meta: { expected: length, actual } }) : undefined;
101
+ }, message);
102
+ }
103
+ /** Gives `not_allowed`, the allowed strings in code point order, where the string is none of them. */
104
+ oneOf(allowed, message) {
105
+ distinct(allowed);
106
+ const sorted = [...allowed].sort(compareValues);
107
+ return this.check((s) => (allowed.includes(s) ? undefined : new Issue("not_allowed", { meta: { allowed: sorted, actual: s } })), message);
108
+ }
109
+ /** Gives `invalid_format.starts_with` unless the string starts with `prefix`. */
110
+ startsWith(prefix, message) {
111
+ return this.#format((s) => s.startsWith(prefix), "starts_with", { prefix }, message);
112
+ }
113
+ /** Gives `invalid_format.ends_with` unless the string ends with `suffix`. */
114
+ endsWith(suffix, message) {
115
+ return this.#format((s) => s.endsWith(suffix), "ends_with", { suffix }, message);
116
+ }
117
+ /** Gives `invalid_format.includes` unless the string includes `substring`. */
118
+ includes(substring, message) {
119
+ return this.#format((s) => s.includes(substring), "includes", { substring }, message);
120
+ }
121
+ /** Gives `invalid_format.email` unless the string is an e-mail address of RFC 5321's ASCII profile. */
122
+ email(message) {
123
+ return this.#format(isEmail, "email", {}, message);
124
+ }
125
+ /** Gives `invalid_format.ipv4` unless the string is an IPv4 address. */
126
+ ipv4(message) {
127
+ return this.#format(isIpv4, "ipv4", {}, message);
128
+ }
129
+ /** Gives `invalid_format.ipv6` unless the string is an IPv6 address, with a zone only where one is allowed. */
130
+ ipv6(message) {
131
+ return this.#format(isIpv6, "ipv6", {}, message);
132
+ }
133
+ /** Gives `invalid_format.ip` unless the string is an IPv4 or an IPv6 address. */
134
+ ip(message) {
135
+ return this.#format((s) => isIpv4(s) || isIpv6(s), "ip", {}, message);
136
+ }
137
+ /** Gives `invalid_format.ulid` unless the string is a ULID; the string is given as it is. */
138
+ ulid(message) {
139
+ return this.#format(isUlid, "ulid", {}, message);
140
+ }
141
+ /** Gives `invalid_format.cuid` unless the string is a CUID of version 1. */
142
+ cuid(message) {
143
+ return this.#format(isCuid, "cuid", {}, message);
144
+ }
145
+ /** Reads a UUID, in either case, and gives it in lower case; anything else gives `invalid_format.uuid`. */
146
+ uuid(message) {
147
+ return new Chain(this.convert((s) => (isUuid(s) ? ok(s.toLowerCase()) : failed(format("uuid"))), message));
148
+ }
149
+ /** Reads an RFC 3986 URI, as written; anything else gives `invalid_format.uri`. */
150
+ uri(message) {
151
+ return new Chain(this.convert((s) => (uriParts(s) !== undefined ? ok(s) : failed(format("uri"))), message));
152
+ }
153
+ /** Reads an http or https URI with a non-empty host; anything else gives `invalid_format.url`. */
154
+ url(message) {
155
+ return new Chain(this.convert((s) => {
156
+ const parts = uriParts(s);
157
+ const scheme = parts?.scheme.toLowerCase();
158
+ const web = (scheme === "http" || scheme === "https") && parts?.host !== undefined && parts.host !== "";
159
+ return web ? ok(s) : failed(format("url"));
160
+ }, message));
161
+ }
162
+ /** Reads an int32 written with an optional sign and ASCII digits. */
163
+ toInt(message) {
164
+ return new IntDecoder(this.convert((s) => textInteger(s, "integer", INT32_MIN, INT32_MAX, Number), message));
165
+ }
166
+ /** Reads an int64 written with an optional sign and ASCII digits. */
167
+ toLong(message) {
168
+ return new LongDecoder(this.convert((s) => textInteger(s, "long", INT64_MIN, INT64_MAX, (n) => n), message));
169
+ }
170
+ /** Reads a decimal, keeping the scale it is written with. */
171
+ toDecimal(message) {
172
+ return new DecimalDecoder(this.convert((s) => {
173
+ const decimal = Decimal.parse(s);
174
+ return decimal === undefined ? failed(textMismatch("decimal")) : ok(decimal);
175
+ }, message));
176
+ }
177
+ /** Reads true, 1, yes or on as true and false, 0, no or off as false, in either case of ASCII. */
178
+ toBool(message) {
179
+ return new BoolDecoder(this.convert((s) => {
180
+ const word = /^[A-Za-z01]{1,5}$/.test(s) ? s.toLowerCase() : "";
181
+ if (word === "true" || word === "1" || word === "yes" || word === "on") {
182
+ return ok(true);
183
+ }
184
+ if (word === "false" || word === "0" || word === "no" || word === "off") {
185
+ return ok(false);
186
+ }
187
+ return failed(textMismatch("boolean"));
188
+ }, message));
189
+ }
190
+ #format(test, kind, meta, message) {
191
+ return this.check((s) => (test(s) ? undefined : format(kind, meta)), message);
192
+ }
193
+ }
194
+ function format(kind, meta = {}) {
195
+ return new Issue("invalid_format", { messageKey: `invalid_format.${kind}`, meta });
196
+ }
197
+ /** The `type_mismatch` of a string that does not read as the kind expected, which has no `actual`: the string was the kind expected. */
198
+ function textMismatch(expected) {
199
+ return new Issue("type_mismatch", { meta: { expected } });
200
+ }
201
+ function textInteger(s, expected, min, max, as) {
202
+ if (!/^[+-]?[0-9]+$/.test(s)) {
203
+ return failed(textMismatch(expected));
204
+ }
205
+ const value = BigInt(s);
206
+ return value < min || value > max ? failed(numericRange(expected)) : ok(as(value));
207
+ }
208
+ /** A decoder of numbers, with the operations that bound them. */
209
+ export class BoundedDecoder extends Chain {
210
+ metaValue(value) {
211
+ return this.arithmetic.meta(value);
212
+ }
213
+ /** Gives `out_of_range.minimum` where the value is less than `min`. */
214
+ min(min, message) {
215
+ const a = this.arithmetic;
216
+ return this.check((v) => (a.compare(v, min) < 0 ? outOfRange("minimum", { min: a.meta(min), actual: a.meta(v) }) : undefined), message);
217
+ }
218
+ /** Gives `out_of_range.maximum` where the value is greater than `max`. */
219
+ max(max, message) {
220
+ const a = this.arithmetic;
221
+ return this.check((v) => (a.compare(v, max) > 0 ? outOfRange("maximum", { max: a.meta(max), actual: a.meta(v) }) : undefined), message);
222
+ }
223
+ /** Gives `out_of_range.range` where the value is outside [min, max]. */
224
+ range(min, max, message) {
225
+ const a = this.arithmetic;
226
+ if (a.compare(min, max) > 0) {
227
+ throw new RangeError("the lower bound of a range is not greater than the upper");
228
+ }
229
+ return this.check((v) => a.compare(v, min) < 0 || a.compare(v, max) > 0
230
+ ? outOfRange("range", { min: a.meta(min), max: a.meta(max), actual: a.meta(v) })
231
+ : undefined, message);
232
+ }
233
+ /** Gives `out_of_range.positive` unless the value is greater than zero. */
234
+ positive(message) {
235
+ const a = this.arithmetic;
236
+ return this.check((v) => (a.compare(v, a.zero) > 0 ? undefined : outOfRange("positive", { min: a.positiveMin, actual: a.meta(v) })), message);
237
+ }
238
+ /** Gives `out_of_range.negative` unless the value is less than zero; -0 is. */
239
+ negative(message) {
240
+ const a = this.arithmetic;
241
+ return this.check((v) => (a.compare(v, a.zero) < 0 ? undefined : outOfRange("negative", { max: a.negativeMax, actual: a.meta(v) })), message);
242
+ }
243
+ /** Gives `out_of_range.non_negative` where the value is less than zero; -0 is. */
244
+ nonNegative(message) {
245
+ const a = this.arithmetic;
246
+ return this.check((v) => (a.compare(v, a.zero) < 0 ? outOfRange("non_negative", { min: a.meta(a.zero), actual: a.meta(v) }) : undefined), message);
247
+ }
248
+ /** Gives `out_of_range.non_positive` where the value is greater than zero. */
249
+ nonPositive(message) {
250
+ const a = this.arithmetic;
251
+ return this.check((v) => (a.compare(v, a.zero) > 0 ? outOfRange("non_positive", { max: a.meta(a.zero), actual: a.meta(v) }) : undefined), message);
252
+ }
253
+ /** `oneOf` for the numeric decoders that have it. */
254
+ allowing(allowed, message) {
255
+ const a = this.arithmetic;
256
+ const held = allowed.map((v) => a.meta(v));
257
+ distinct(held);
258
+ const sorted = [...allowed].sort((x, y) => a.compare(x, y)).map((v) => a.meta(v));
259
+ return this.check((v) => (includesSame(held, a.meta(v)) ? undefined : new Issue("not_allowed", { meta: { allowed: sorted, actual: a.meta(v) } })), message);
260
+ }
261
+ /** `multipleOf` for the numeric decoders that have it. */
262
+ multiples(divisor, isMultiple, message) {
263
+ const a = this.arithmetic;
264
+ if (a.compare(divisor, a.zero) === 0) {
265
+ throw new RangeError("the divisor of multipleOf is not zero");
266
+ }
267
+ return this.check((v) => (isMultiple(v) ? undefined : new Issue("not_multiple_of", { meta: { divisor: a.meta(divisor), actual: a.meta(v) } })), message);
268
+ }
269
+ }
270
+ function outOfRange(kind, meta) {
271
+ return new Issue("out_of_range", { messageKey: `out_of_range.${kind}`, meta });
272
+ }
273
+ function distinct(values) {
274
+ values.forEach((value, i) => {
275
+ if (includesSame(values.slice(0, i), value)) {
276
+ throw new RangeError(`${String(value)} is allowed twice`);
277
+ }
278
+ });
279
+ }
280
+ const INT32 = {
281
+ compare: (a, b) => a - b,
282
+ meta: (v) => v,
283
+ positiveMin: 1,
284
+ negativeMax: -1,
285
+ zero: 0,
286
+ };
287
+ /** A decoder of int32 values, as JavaScript numbers. */
288
+ export class IntDecoder extends BoundedDecoder {
289
+ get arithmetic() {
290
+ return INT32;
291
+ }
292
+ /** Gives `not_allowed`, the allowed values in ascending order, where the value is none of them. */
293
+ oneOf(allowed, message) {
294
+ return this.allowing(allowed, message);
295
+ }
296
+ /** Gives `not_multiple_of` where the value is not a multiple of `divisor`. */
297
+ multipleOf(divisor, message) {
298
+ return this.multiples(divisor, (v) => v % divisor === 0, message);
299
+ }
300
+ }
301
+ const INT64 = {
302
+ compare: (a, b) => (a < b ? -1 : a > b ? 1 : 0),
303
+ meta: (v) => v,
304
+ positiveMin: 1n,
305
+ negativeMax: -1n,
306
+ zero: 0n,
307
+ };
308
+ /** A decoder of int64 values, as bigints. */
309
+ export class LongDecoder extends BoundedDecoder {
310
+ get arithmetic() {
311
+ return INT64;
312
+ }
313
+ /** Gives `not_allowed`, the allowed values in ascending order, where the value is none of them. */
314
+ oneOf(allowed, message) {
315
+ return this.allowing(allowed, message);
316
+ }
317
+ /** Gives `not_multiple_of` where the value is not a multiple of `divisor`. */
318
+ multipleOf(divisor, message) {
319
+ return this.multiples(divisor, (v) => v % divisor === 0n, message);
320
+ }
321
+ }
322
+ function floatArithmetic(width) {
323
+ return {
324
+ compare: (a, b) => compareValues(a, b),
325
+ meta: (v) => new Float(v, width),
326
+ positiveMin: new Float(0, width),
327
+ negativeMax: new Float(0, width),
328
+ zero: 0,
329
+ };
330
+ }
331
+ const FLOAT32 = floatArithmetic(32);
332
+ const FLOAT64 = floatArithmetic(64);
333
+ /**
334
+ * A decoder of float32 values, as JavaScript numbers holding a float32 value. Bounds are compared
335
+ * in the float order of the value model, in which -0 is less than +0 and NaN is the greatest.
336
+ */
337
+ export class FloatDecoder extends BoundedDecoder {
338
+ get arithmetic() {
339
+ return FLOAT32;
340
+ }
341
+ min(min, message) {
342
+ return super.min(Math.fround(min), message);
343
+ }
344
+ max(max, message) {
345
+ return super.max(Math.fround(max), message);
346
+ }
347
+ range(min, max, message) {
348
+ return super.range(Math.fround(min), Math.fround(max), message);
349
+ }
350
+ /** Gives `not_allowed`, the allowed values in the float order, where the value is none of them. */
351
+ oneOf(allowed, message) {
352
+ return this.allowing(allowed.map(Math.fround), message);
353
+ }
354
+ }
355
+ /** A decoder of float64 values. Bounds are compared in the float order of the value model. */
356
+ export class DoubleDecoder extends BoundedDecoder {
357
+ get arithmetic() {
358
+ return FLOAT64;
359
+ }
360
+ /** Gives `not_allowed`, the allowed values in the float order, where the value is none of them. */
361
+ oneOf(allowed, message) {
362
+ return this.allowing(allowed, message);
363
+ }
364
+ }
365
+ const DECIMAL = {
366
+ compare: (a, b) => a.compare(b),
367
+ meta: (v) => v,
368
+ positiveMin: Decimal.of(0),
369
+ negativeMax: Decimal.of(0),
370
+ zero: Decimal.of(0),
371
+ };
372
+ /** A decoder of decimals, which keep the scale they are written with; bounds compare them by value. */
373
+ export class DecimalDecoder extends BoundedDecoder {
374
+ get arithmetic() {
375
+ return DECIMAL;
376
+ }
377
+ /** Gives `not_multiple_of` where the value is not an integer multiple of `divisor`. */
378
+ multipleOf(divisor, message) {
379
+ return this.multiples(divisor, (v) => v.isMultipleOf(divisor), message);
380
+ }
381
+ /** Gives `invalid_scale` where the decimal's scale is greater than `max`. */
382
+ scale(max, message) {
383
+ return this.check((v) => (v.scale > max ? new Issue("invalid_scale", { meta: { maxScale: max, actualScale: v.scale } }) : undefined), message);
384
+ }
385
+ }
386
+ /** A decoder of booleans. */
387
+ export class BoolDecoder extends Chain {
388
+ /** Gives `invalid_value`, expected true, for false. */
389
+ isTrue(message) {
390
+ return this.check((v) => (v ? undefined : new Issue("invalid_value", { meta: { expected: true, actual: false } })), message);
391
+ }
392
+ }
393
+ /** A decoder of strings: a JSON string. */
394
+ export function string() {
395
+ return new StringDecoder(scalar("string", "string", (input) => ok(input)));
396
+ }
397
+ /** A decoder of int32 values: a JSON number written as an integer, within the int32 range. */
398
+ export function int() {
399
+ return new IntDecoder(scalar("integer", "number", (input) => {
400
+ const read = integer(input, "integer", INT32_MIN, INT32_MAX);
401
+ return read.issues === undefined ? ok(Number(read.value)) : read;
402
+ }));
403
+ }
404
+ /** A decoder of int64 values: a JSON number written as an integer, within the int64 range, as a bigint. */
405
+ export function long() {
406
+ return new LongDecoder(scalar("long", "number", (input) => integer(input, "long", INT64_MIN, INT64_MAX)));
407
+ }
408
+ /** A decoder of float32 values: any JSON number, rounded once to the nearest float32. */
409
+ export function float() {
410
+ return new FloatDecoder(scalar("float", "number", (input) => floatOf(input, "float", 32)));
411
+ }
412
+ /** A decoder of float64 values: any JSON number, rounded once to the nearest float64. */
413
+ export function double() {
414
+ return new DoubleDecoder(scalar("double", "number", (input) => floatOf(input, "double", 64)));
415
+ }
416
+ /** A decoder of decimals: any JSON number, at the scale its lexeme gives. */
417
+ export function decimal() {
418
+ return new DecimalDecoder(scalar("number", "number", (input) => {
419
+ const lexeme = lexemeOf(input);
420
+ const value = lexeme === undefined ? undefined : Decimal.parse(lexeme);
421
+ return value === undefined ? failed(typeMismatch("number", input)) : ok(value);
422
+ }));
423
+ }
424
+ /** A decoder of booleans: a JSON boolean. */
425
+ export function bool() {
426
+ return new BoolDecoder(scalar("boolean", "boolean", (input) => ok(input)));
427
+ }
package/dist/set.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A finite set whose members are told apart as the value model tells values apart: +0 and -0 are
3
+ * two members, every NaN is one, and decimals, lists and other structured values are members by
4
+ * what they hold, not by identity. A JavaScript `Set` compares by SameValueZero and identity, and
5
+ * so holds neither.
6
+ */
7
+ export declare class ValueSet<E> implements Iterable<E> {
8
+ #private;
9
+ private constructor();
10
+ /** The set of the values given, each kept the first time it occurs. */
11
+ static of<E>(values: Iterable<E>): ValueSet<E>;
12
+ get [Symbol.toStringTag](): string;
13
+ get size(): number;
14
+ /** Whether `value` is a member. */
15
+ has(value: E): boolean;
16
+ [Symbol.iterator](): Iterator<E>;
17
+ /** The members, in the order they were first given. */
18
+ values(): readonly E[];
19
+ /** Whether the two sets have the same members. */
20
+ equals(other: unknown): boolean;
21
+ toJSON(): readonly E[];
22
+ }
package/dist/set.js ADDED
@@ -0,0 +1,61 @@
1
+ // A set of values compared as the value model compares them.
2
+ import { keyOf, same } from "./meta.js";
3
+ import { ofThisCopy, tagOf } from "./copy.js";
4
+ /**
5
+ * A finite set whose members are told apart as the value model tells values apart: +0 and -0 are
6
+ * two members, every NaN is one, and decimals, lists and other structured values are members by
7
+ * what they hold, not by identity. A JavaScript `Set` compares by SameValueZero and identity, and
8
+ * so holds neither.
9
+ */
10
+ export class ValueSet {
11
+ #members;
12
+ #keys;
13
+ constructor(members, keys) {
14
+ this.#members = members;
15
+ this.#keys = keys;
16
+ }
17
+ /** The set of the values given, each kept the first time it occurs. */
18
+ static of(values) {
19
+ const members = [];
20
+ const keys = new Set();
21
+ for (const value of values) {
22
+ const key = keyOf(value);
23
+ if (key !== undefined) {
24
+ if (keys.has(key)) {
25
+ continue;
26
+ }
27
+ keys.add(key);
28
+ }
29
+ else if (members.some((member) => same(member, value))) {
30
+ continue;
31
+ }
32
+ members.push(value);
33
+ }
34
+ return new ValueSet(Object.freeze(members), keys);
35
+ }
36
+ get [Symbol.toStringTag]() {
37
+ return tagOf("ValueSet");
38
+ }
39
+ get size() {
40
+ return this.#members.length;
41
+ }
42
+ /** Whether `value` is a member. */
43
+ has(value) {
44
+ const key = keyOf(value);
45
+ return key !== undefined ? this.#keys.has(key) : this.#members.some((member) => same(member, value));
46
+ }
47
+ [Symbol.iterator]() {
48
+ return this.#members[Symbol.iterator]();
49
+ }
50
+ /** The members, in the order they were first given. */
51
+ values() {
52
+ return this.#members;
53
+ }
54
+ /** Whether the two sets have the same members. */
55
+ equals(other) {
56
+ return ofThisCopy(other, ValueSet, "ValueSet") && other.size === this.size && this.#members.every((member) => other.has(member));
57
+ }
58
+ toJSON() {
59
+ return this.#members;
60
+ }
61
+ }