@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.
package/dist/input.js ADDED
@@ -0,0 +1,465 @@
1
+ // The values a decoder reads.
2
+ //
3
+ // The input model is what a JSON text denotes, with every number kept as it is written. A decoder
4
+ // reads it from plain JavaScript values: `null`, a boolean, a string, an array, an object (a
5
+ // plain object or a `Map` of string keys), and a number as a `JsonNumber`, which `parse` gives and
6
+ // which keeps the lexeme. A JavaScript `number` or `bigint` is read as the number it is, which is
7
+ // what an adapter that has already converted the text gives: `1.50` read by `JSON.parse` is 1.5,
8
+ // and a decimal decoder sees the scale 1. `undefined` is an absent value, at the top as much
9
+ // as an object's member.
10
+ import { Decimal } from "./decimal.js";
11
+ import { ofThisCopy, tagOf } from "./copy.js";
12
+ /** A number of the input model: the text it is written with. */
13
+ export class JsonNumber {
14
+ lexeme;
15
+ constructor(lexeme) {
16
+ if (!LEXEME.test(lexeme)) {
17
+ throw new SyntaxError(`${JSON.stringify(lexeme)} is not a JSON number`);
18
+ }
19
+ this.lexeme = lexeme;
20
+ }
21
+ get [Symbol.toStringTag]() {
22
+ return tagOf("JsonNumber");
23
+ }
24
+ toString() {
25
+ return this.lexeme;
26
+ }
27
+ /**
28
+ * The number for `JSON.stringify` to write as this text. Where the engine has no
29
+ * `JSON.rawJSON`, a JavaScript number is given in its place only where the text it is written
30
+ * as denotes the same number; for one it would change, such as an integer beyond 2^53, this
31
+ * throws rather than write another number.
32
+ */
33
+ toJSON() {
34
+ const raw = JSON.rawJSON;
35
+ if (raw !== undefined) {
36
+ return raw(this.lexeme);
37
+ }
38
+ const number = Number(this.lexeme);
39
+ if (!Number.isFinite(number) || Object.is(number, -0) || !sameNumber(String(number), this.lexeme)) {
40
+ throw new RangeError(`${this.lexeme} cannot be written as a JavaScript number, and this engine has no JSON.rawJSON`);
41
+ }
42
+ return number;
43
+ }
44
+ }
45
+ /** Whether the two JSON number texts denote the same number. */
46
+ function sameNumber(a, b) {
47
+ const x = Decimal.parse(a);
48
+ const y = Decimal.parse(b);
49
+ return x !== undefined && y !== undefined && x.compare(y) === 0;
50
+ }
51
+ const LEXEME = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/;
52
+ /**
53
+ * The kind of `value`, as an issue's `actual` names it.
54
+ *
55
+ * This is where a JavaScript value is read as a value of the input model, for a decoder and for
56
+ * {@link stringify} alike. A string is one only where it is a sequence of Unicode scalar values;
57
+ * an object is a `Map` of string keys, or an object whose data are its own properties, plain or an
58
+ * instance of a class of the program's. A value that holds its data elsewhere — a `Date`, a `Set`,
59
+ * a `String` or `Number` object, a typed array, a function — is no value of the input model, and
60
+ * reading it as an object of no members, or of its characters, would read something it is not.
61
+ *
62
+ * @throws {TypeError} for a value that is no value of the input model
63
+ */
64
+ export function kindOf(value) {
65
+ if (value === undefined) {
66
+ return "missing";
67
+ }
68
+ if (value === null) {
69
+ return "null";
70
+ }
71
+ switch (typeof value) {
72
+ case "boolean":
73
+ return "boolean";
74
+ case "number":
75
+ case "bigint":
76
+ return "number";
77
+ case "string":
78
+ wellFormed(value);
79
+ return "string";
80
+ case "object":
81
+ if (ofThisCopy(value, JsonNumber, "JsonNumber") || ofThisCopy(value, Decimal, "Decimal")
82
+ || rawNumber(value) !== undefined) {
83
+ return "number";
84
+ }
85
+ if (Array.isArray(value)) {
86
+ return "array";
87
+ }
88
+ if (value instanceof Map || Object.prototype.toString.call(value) === "[object Object]") {
89
+ return "object";
90
+ }
91
+ throw new TypeError(`${Object.prototype.toString.call(value)} is no value of the input model`);
92
+ default:
93
+ throw new TypeError(`a ${typeof value} is no value of the input model`);
94
+ }
95
+ }
96
+ /** `text`, refused where it is not a sequence of Unicode scalar values, which no JSON text holds. */
97
+ function wellFormed(text) {
98
+ if (!text.isWellFormed()) {
99
+ throw new TypeError(`${JSON.stringify(text)} holds an unpaired surrogate, and is no string of the input model`);
100
+ }
101
+ return text;
102
+ }
103
+ /**
104
+ * The elements of an array of the input model, in order.
105
+ *
106
+ * @throws {TypeError} for an array with a hole, a place no value is at, which no JSON text writes
107
+ */
108
+ export function elementsOf(value) {
109
+ const out = [];
110
+ for (let i = 0; i < value.length; i += 1) {
111
+ if (!(i in value)) {
112
+ throw new TypeError(`an array with no element at ${i} is no value of the input model`);
113
+ }
114
+ out.push(value[i]);
115
+ }
116
+ return out;
117
+ }
118
+ const isRawJSON = JSON.isRawJSON;
119
+ /**
120
+ * The text of a number `JSON.rawJSON` made, which is how JavaScript itself carries a number as it
121
+ * is written; `undefined` for any other value.
122
+ *
123
+ * @throws {TypeError} for raw JSON that is not a number, which is no value of the input model
124
+ */
125
+ function rawNumber(value) {
126
+ if (isRawJSON === undefined || !isRawJSON(value)) {
127
+ return undefined;
128
+ }
129
+ const text = value.rawJSON;
130
+ if (!LEXEME.test(text)) {
131
+ throw new TypeError(`raw JSON ${text} is not a number, and only a number is read from raw JSON`);
132
+ }
133
+ return text;
134
+ }
135
+ /** Whether `value` is an object of the input model. */
136
+ export function isObject(value) {
137
+ return kindOf(value) === "object";
138
+ }
139
+ /**
140
+ * The lexeme of a number of the input model; `undefined` for a JavaScript number that no JSON
141
+ * text writes (NaN or an infinity). An integer is written in full, as `BigInt` writes it, so that
142
+ * 1e21 read by `JSON.parse` is still an integer to an integer decoder. A `Decimal` is written at its
143
+ * scale, so that `decimal()` reads back the decimal it is, trailing zeros and all.
144
+ */
145
+ export function lexemeOf(value) {
146
+ if (ofThisCopy(value, JsonNumber, "JsonNumber")) {
147
+ return value.lexeme;
148
+ }
149
+ if (ofThisCopy(value, Decimal, "Decimal")) {
150
+ return value.toString();
151
+ }
152
+ const raw = rawNumber(value);
153
+ if (raw !== undefined) {
154
+ return raw;
155
+ }
156
+ if (typeof value === "bigint") {
157
+ return value.toString();
158
+ }
159
+ if (typeof value === "number") {
160
+ if (!Number.isFinite(value)) {
161
+ return undefined;
162
+ }
163
+ if (Object.is(value, -0)) {
164
+ return "-0";
165
+ }
166
+ return Number.isInteger(value) ? BigInt(value).toString() : String(value);
167
+ }
168
+ return undefined;
169
+ }
170
+ /**
171
+ * The members of an object of the input model, in order; a member whose value is `undefined` is
172
+ * absent.
173
+ *
174
+ * @throws {TypeError} for a `Map` with a key that is not a string, and for a name that is not a
175
+ * sequence of Unicode scalar values: no JSON text names a member so
176
+ */
177
+ export function membersOf(value) {
178
+ const entries = value instanceof Map ? [...value.entries()] : Object.entries(value);
179
+ const out = [];
180
+ for (const [name, member] of entries) {
181
+ if (typeof name !== "string") {
182
+ throw new TypeError(`a Map with the key ${String(name)}, which is not a string, is no object of the input model`);
183
+ }
184
+ if (member !== undefined) {
185
+ out.push([wellFormed(name), member]);
186
+ }
187
+ }
188
+ return out;
189
+ }
190
+ /** The member of an object of the input model, or `undefined` where it is absent. */
191
+ export function memberOf(value, name) {
192
+ if (value instanceof Map) {
193
+ return value.get(name);
194
+ }
195
+ return Object.prototype.hasOwnProperty.call(value, name) ? value[name] : undefined;
196
+ }
197
+ /**
198
+ * Reads a JSON text (RFC 8259) into the input model: every number a {@link JsonNumber} holding its
199
+ * lexeme, and every object a `Map` holding its members in the order written.
200
+ *
201
+ * @throws {SyntaxError} where the text is not JSON, an object repeats a member name, or a string
202
+ * holds an unpaired surrogate, none of which is in the input model
203
+ */
204
+ export function parse(text) {
205
+ const reader = new Reader(text);
206
+ reader.space();
207
+ const value = reader.value(0);
208
+ reader.space();
209
+ if (reader.at < text.length) {
210
+ reader.fail("text after the value");
211
+ }
212
+ return value;
213
+ }
214
+ /**
215
+ * Writes a value of the input model as JSON text, as {@link parse} reads it back: every number as
216
+ * its lexeme, and every object's members in their order, a `Map`'s as it holds them. This is how
217
+ * a value is handed on to what reads JSON text and not JavaScript values, such as a module across
218
+ * a boundary, without a number rounded or a member moved: `JSON.stringify` writes a `Map` as `{}`
219
+ * and an object's integer-like member names before its others.
220
+ *
221
+ * What it writes, `parse` reads, as the value it was: a value is read as {@link kindOf} reads one,
222
+ * the same way a decoder reads it, so what the input model has no place for is refused here rather
223
+ * than written as text `parse` refuses or as some other value.
224
+ *
225
+ * @throws {TypeError} for `undefined` where a value has to be, a hole in an array, a number that is
226
+ * NaN or an infinity, a string or member name holding an unpaired surrogate, and any value that
227
+ * is no value of the input model
228
+ */
229
+ export function stringify(value) {
230
+ const out = [];
231
+ write(value, out, 0);
232
+ return out.join("");
233
+ }
234
+ function write(value, out, depth) {
235
+ if (depth > DEPTH) {
236
+ throw new TypeError(`nesting deeper than ${DEPTH}`);
237
+ }
238
+ switch (kindOf(value)) {
239
+ case "missing":
240
+ throw new TypeError("an absent value has no JSON text");
241
+ case "null":
242
+ out.push("null");
243
+ return;
244
+ case "boolean":
245
+ out.push(value ? "true" : "false");
246
+ return;
247
+ case "string":
248
+ out.push(JSON.stringify(value));
249
+ return;
250
+ case "number": {
251
+ const lexeme = lexemeOf(value);
252
+ if (lexeme === undefined) {
253
+ throw new TypeError(`${String(value)} is in no JSON text`);
254
+ }
255
+ out.push(lexeme);
256
+ return;
257
+ }
258
+ case "array": {
259
+ out.push("[");
260
+ elementsOf(value).forEach((item, i) => {
261
+ if (i > 0) {
262
+ out.push(",");
263
+ }
264
+ write(item, out, depth + 1);
265
+ });
266
+ out.push("]");
267
+ return;
268
+ }
269
+ case "object": {
270
+ out.push("{");
271
+ membersOf(value).forEach(([name, member], i) => {
272
+ if (i > 0) {
273
+ out.push(",");
274
+ }
275
+ out.push(JSON.stringify(name), ":");
276
+ write(member, out, depth + 1);
277
+ });
278
+ out.push("}");
279
+ }
280
+ }
281
+ }
282
+ /** How deep arrays and objects may nest before the text is refused rather than the stack overflowing. */
283
+ const DEPTH = 1000;
284
+ class Reader {
285
+ text;
286
+ at = 0;
287
+ constructor(text) {
288
+ this.text = text;
289
+ }
290
+ fail(what) {
291
+ throw new SyntaxError(`not JSON: ${what} at ${this.at}`);
292
+ }
293
+ space() {
294
+ while (this.at < this.text.length) {
295
+ const c = this.text.charCodeAt(this.at);
296
+ if (c !== 0x20 && c !== 0x09 && c !== 0x0a && c !== 0x0d) {
297
+ return;
298
+ }
299
+ this.at += 1;
300
+ }
301
+ }
302
+ value(depth) {
303
+ if (depth > DEPTH) {
304
+ this.fail(`nesting deeper than ${DEPTH}`);
305
+ }
306
+ const c = this.text[this.at];
307
+ switch (c) {
308
+ case "{":
309
+ return this.object(depth);
310
+ case "[":
311
+ return this.array(depth);
312
+ case '"':
313
+ return this.string();
314
+ case "t":
315
+ return this.word("true", true);
316
+ case "f":
317
+ return this.word("false", false);
318
+ case "n":
319
+ return this.word("null", null);
320
+ default:
321
+ if (c === "-" || (c !== undefined && c >= "0" && c <= "9")) {
322
+ return this.number();
323
+ }
324
+ return this.fail(c === undefined ? "the end of the text" : `${JSON.stringify(c)}`);
325
+ }
326
+ }
327
+ word(word, value) {
328
+ if (!this.text.startsWith(word, this.at)) {
329
+ this.fail("an unknown word");
330
+ }
331
+ this.at += word.length;
332
+ return value;
333
+ }
334
+ number() {
335
+ NUMBER_AT.lastIndex = this.at;
336
+ const read = NUMBER_AT.exec(this.text);
337
+ if (read === null) {
338
+ this.fail("a malformed number");
339
+ }
340
+ this.at += read[0].length;
341
+ return new JsonNumber(read[0]);
342
+ }
343
+ string() {
344
+ this.at += 1;
345
+ let out = "";
346
+ let from = this.at;
347
+ for (;;) {
348
+ const c = this.text.charCodeAt(this.at);
349
+ if (Number.isNaN(c)) {
350
+ this.fail("an unterminated string");
351
+ }
352
+ if (c === 0x22) {
353
+ out += this.text.slice(from, this.at);
354
+ this.at += 1;
355
+ break;
356
+ }
357
+ if (c < 0x20) {
358
+ this.fail("a control character in a string");
359
+ }
360
+ if (c === 0x5c) {
361
+ out += this.text.slice(from, this.at);
362
+ out += this.escape();
363
+ from = this.at;
364
+ continue;
365
+ }
366
+ this.at += 1;
367
+ }
368
+ if (!out.isWellFormed()) {
369
+ this.fail("a string holding an unpaired surrogate");
370
+ }
371
+ return out;
372
+ }
373
+ escape() {
374
+ const c = this.text[this.at + 1];
375
+ this.at += 2;
376
+ switch (c) {
377
+ case '"':
378
+ return '"';
379
+ case "\\":
380
+ return "\\";
381
+ case "/":
382
+ return "/";
383
+ case "b":
384
+ return "\b";
385
+ case "f":
386
+ return "\f";
387
+ case "n":
388
+ return "\n";
389
+ case "r":
390
+ return "\r";
391
+ case "t":
392
+ return "\t";
393
+ case "u": {
394
+ const hex = this.text.slice(this.at, this.at + 4);
395
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
396
+ this.fail("a malformed \\u escape");
397
+ }
398
+ this.at += 4;
399
+ return String.fromCharCode(Number.parseInt(hex, 16));
400
+ }
401
+ default:
402
+ return this.fail("an unknown escape");
403
+ }
404
+ }
405
+ array(depth) {
406
+ this.at += 1;
407
+ const out = [];
408
+ this.space();
409
+ if (this.text[this.at] === "]") {
410
+ this.at += 1;
411
+ return out;
412
+ }
413
+ for (;;) {
414
+ this.space();
415
+ out.push(this.value(depth + 1));
416
+ this.space();
417
+ const c = this.text[this.at];
418
+ this.at += 1;
419
+ if (c === "]") {
420
+ return out;
421
+ }
422
+ if (c !== ",") {
423
+ this.at -= 1;
424
+ this.fail("an array not closed");
425
+ }
426
+ }
427
+ }
428
+ object(depth) {
429
+ this.at += 1;
430
+ const out = new Map();
431
+ this.space();
432
+ if (this.text[this.at] === "}") {
433
+ this.at += 1;
434
+ return out;
435
+ }
436
+ for (;;) {
437
+ this.space();
438
+ if (this.text[this.at] !== '"') {
439
+ this.fail("a member without a name");
440
+ }
441
+ const name = this.string();
442
+ if (out.has(name)) {
443
+ this.fail(`the member ${JSON.stringify(name)} twice`);
444
+ }
445
+ this.space();
446
+ if (this.text[this.at] !== ":") {
447
+ this.fail("a member name without a colon");
448
+ }
449
+ this.at += 1;
450
+ this.space();
451
+ out.set(name, this.value(depth + 1));
452
+ this.space();
453
+ const c = this.text[this.at];
454
+ this.at += 1;
455
+ if (c === "}") {
456
+ return out;
457
+ }
458
+ if (c !== ",") {
459
+ this.at -= 1;
460
+ this.fail("an object not closed");
461
+ }
462
+ }
463
+ }
464
+ }
465
+ const NUMBER_AT = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
@@ -0,0 +1,85 @@
1
+ import { type MessageResolver } from "./messages.ts";
2
+ import { Path } from "./path.ts";
3
+ import { type IssueWire } from "./wire.ts";
4
+ /** What an issue is made with, besides its code. */
5
+ export interface IssueInit {
6
+ /** The kind of problem within the code's class; the code itself when not given. */
7
+ readonly messageKey?: string;
8
+ /** Named values that describe the problem. */
9
+ readonly meta?: Readonly<Record<string, unknown>>;
10
+ /** A sentence of the issue's own, which every catalogue leaves as it is. */
11
+ readonly message?: string;
12
+ /** Where the problem is, relative to where the issue is given. */
13
+ readonly path?: Path | readonly (string | number)[];
14
+ }
15
+ /**
16
+ * One thing the input did wrong: where it is, its code, the message key that says which kind of
17
+ * problem within the code it is, and the metadata that describes it.
18
+ *
19
+ * An issue carries no sentence unless whoever made it gave one. {@link message} writes one from a
20
+ * catalogue, the English one unless another is given, so the same issue reads in each person's
21
+ * language.
22
+ */
23
+ export declare class Issue {
24
+ #private;
25
+ readonly path: Path;
26
+ readonly code: string;
27
+ readonly messageKey: string;
28
+ readonly meta: Readonly<Record<string, unknown>>;
29
+ /** The sentence the issue was given, if it was given one. */
30
+ readonly givenMessage: string | undefined;
31
+ constructor(code: string, init?: IssueInit);
32
+ get [Symbol.toStringTag](): string;
33
+ /** The sentence for the issue: the one it was given, or the one `resolver` writes. */
34
+ message(resolver?: MessageResolver): string;
35
+ /** The same issue with `message` as its sentence. */
36
+ withMessage(message: string): Issue;
37
+ /** The same issue, its path read as starting at `base`. */
38
+ under(base: Path): Issue;
39
+ /**
40
+ * The issue as JSON, with its English sentence; {@link issueWire} writes it with another
41
+ * catalogue's. It takes no argument: `JSON.stringify` hands `toJSON` the member name.
42
+ */
43
+ toJSON(): IssueWire;
44
+ }
45
+ /** The issues a failed decode gives, in the order they were found; never empty. */
46
+ export declare class Issues implements Iterable<Issue> {
47
+ #private;
48
+ constructor(issues: readonly Issue[]);
49
+ get [Symbol.toStringTag](): string;
50
+ get length(): number;
51
+ /** The issues, in order. */
52
+ get list(): readonly Issue[];
53
+ [Symbol.iterator](): Iterator<Issue>;
54
+ /** The same issues, their paths read as starting at `base`. */
55
+ under(base: Path): Issues;
56
+ /**
57
+ * The issues at `path`, in order, and none where nothing was found there: what a form shows beside
58
+ * the field the path names. The path is a `Path` or its segments, a number naming an element by
59
+ * its index, so `at(["lines", 0, "sku"])` is the issues of the first line's `sku`. An issue at a
60
+ * path below or above it is not at it.
61
+ */
62
+ at(path: Path | readonly (string | number)[]): readonly Issue[];
63
+ /** The sentences for the issues, grouped by the JSON Pointer of their path. */
64
+ flatten(resolver?: MessageResolver): Record<string, string[]>;
65
+ /** The issues as JSON, in order, their sentences written by `resolver`. */
66
+ toWire(resolver?: MessageResolver): IssueWire[];
67
+ /** The issues as JSON, in order, with their English sentences. */
68
+ toJSON(): IssueWire[];
69
+ }
70
+ /** What decoding came to when it succeeded. */
71
+ export interface Ok<T> {
72
+ readonly value: T;
73
+ readonly issues?: undefined;
74
+ }
75
+ /** What decoding came to when it failed. */
76
+ export interface Failed {
77
+ readonly issues: Issues;
78
+ readonly value?: undefined;
79
+ }
80
+ /** What decoding comes to: the value, or the issues that kept it from being one. */
81
+ export type Result<T> = Ok<T> | Failed;
82
+ /** A success holding `value`. */
83
+ export declare function ok<T>(value: T): Ok<T>;
84
+ /** A failure with the issues given. */
85
+ export declare function failed(issues: Issues | Issue | readonly Issue[]): Failed;
package/dist/issue.js ADDED
@@ -0,0 +1,130 @@
1
+ // What a decoder that fails gives, and what decoding comes to.
2
+ var _a;
3
+ import { Messages } from "./messages.js";
4
+ import { Path } from "./path.js";
5
+ import { issueWire } from "./wire.js";
6
+ import { ofThisCopy, tagOf } from "./copy.js";
7
+ /**
8
+ * One thing the input did wrong: where it is, its code, the message key that says which kind of
9
+ * problem within the code it is, and the metadata that describes it.
10
+ *
11
+ * An issue carries no sentence unless whoever made it gave one. {@link message} writes one from a
12
+ * catalogue, the English one unless another is given, so the same issue reads in each person's
13
+ * language.
14
+ */
15
+ export class Issue {
16
+ path;
17
+ code;
18
+ messageKey;
19
+ meta;
20
+ /** The sentence the issue was given, if it was given one. */
21
+ givenMessage;
22
+ constructor(code, init = {}) {
23
+ this.code = code;
24
+ this.messageKey = init.messageKey ?? code;
25
+ this.meta = Object.freeze({ ...init.meta });
26
+ this.givenMessage = init.message;
27
+ this.path = init.path === undefined
28
+ ? Path.ROOT
29
+ : ofThisCopy(init.path, Path, "Path")
30
+ ? init.path
31
+ : Path.of(...init.path);
32
+ }
33
+ get [Symbol.toStringTag]() {
34
+ return tagOf("Issue");
35
+ }
36
+ /** The sentence for the issue: the one it was given, or the one `resolver` writes. */
37
+ message(resolver = Messages.english) {
38
+ return this.givenMessage ?? resolver.resolve(this);
39
+ }
40
+ /** The same issue with `message` as its sentence. */
41
+ withMessage(message) {
42
+ return this.#with({ message });
43
+ }
44
+ /** The same issue, its path read as starting at `base`. */
45
+ under(base) {
46
+ return base.isRoot ? this : this.#with({ path: base.concat(this.path) });
47
+ }
48
+ #with(changes) {
49
+ return new _a(this.code, {
50
+ messageKey: this.messageKey,
51
+ meta: this.meta,
52
+ message: this.givenMessage,
53
+ path: this.path,
54
+ ...changes,
55
+ });
56
+ }
57
+ /**
58
+ * The issue as JSON, with its English sentence; {@link issueWire} writes it with another
59
+ * catalogue's. It takes no argument: `JSON.stringify` hands `toJSON` the member name.
60
+ */
61
+ toJSON() {
62
+ return issueWire(this);
63
+ }
64
+ }
65
+ _a = Issue;
66
+ /** The issues a failed decode gives, in the order they were found; never empty. */
67
+ export class Issues {
68
+ #list;
69
+ constructor(issues) {
70
+ if (issues.length === 0) {
71
+ throw new RangeError("a failure has at least one issue");
72
+ }
73
+ this.#list = Object.freeze([...issues]);
74
+ }
75
+ get [Symbol.toStringTag]() {
76
+ return tagOf("Issues");
77
+ }
78
+ get length() {
79
+ return this.#list.length;
80
+ }
81
+ /** The issues, in order. */
82
+ get list() {
83
+ return this.#list;
84
+ }
85
+ [Symbol.iterator]() {
86
+ return this.#list[Symbol.iterator]();
87
+ }
88
+ /** The same issues, their paths read as starting at `base`. */
89
+ under(base) {
90
+ return base.isRoot ? this : new Issues(this.#list.map((issue) => issue.under(base)));
91
+ }
92
+ /**
93
+ * The issues at `path`, in order, and none where nothing was found there: what a form shows beside
94
+ * the field the path names. The path is a `Path` or its segments, a number naming an element by
95
+ * its index, so `at(["lines", 0, "sku"])` is the issues of the first line's `sku`. An issue at a
96
+ * path below or above it is not at it.
97
+ */
98
+ at(path) {
99
+ const wanted = ofThisCopy(path, Path, "Path") ? path : Path.of(...path);
100
+ return this.#list.filter((issue) => issue.path.equals(wanted));
101
+ }
102
+ /** The sentences for the issues, grouped by the JSON Pointer of their path. */
103
+ flatten(resolver = Messages.english) {
104
+ const out = {};
105
+ for (const issue of this.#list) {
106
+ const path = issue.path.toString();
107
+ (out[path] ??= []).push(issue.message(resolver));
108
+ }
109
+ return out;
110
+ }
111
+ /** The issues as JSON, in order, their sentences written by `resolver`. */
112
+ toWire(resolver = Messages.english) {
113
+ return this.#list.map((issue) => issueWire(issue, resolver));
114
+ }
115
+ /** The issues as JSON, in order, with their English sentences. */
116
+ toJSON() {
117
+ return this.toWire();
118
+ }
119
+ }
120
+ /** A success holding `value`. */
121
+ export function ok(value) {
122
+ return { value };
123
+ }
124
+ /** A failure with the issues given. */
125
+ export function failed(issues) {
126
+ if (ofThisCopy(issues, Issues, "Issues")) {
127
+ return { issues };
128
+ }
129
+ return { issues: new Issues(ofThisCopy(issues, Issue, "Issue") ? [issues] : issues) };
130
+ }