@atcute/lexicons 1.0.0

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 (91) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +11 -0
  3. package/dist/ambient.d.ts +8 -0
  4. package/dist/ambient.js +2 -0
  5. package/dist/ambient.js.map +1 -0
  6. package/dist/index.d.ts +16 -0
  7. package/dist/index.js +3 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/interfaces/blob.d.ts +20 -0
  10. package/dist/interfaces/blob.js +20 -0
  11. package/dist/interfaces/blob.js.map +1 -0
  12. package/dist/interfaces/bytes.d.ts +23 -0
  13. package/dist/interfaces/bytes.js +21 -0
  14. package/dist/interfaces/bytes.js.map +1 -0
  15. package/dist/interfaces/cid-link.d.ts +24 -0
  16. package/dist/interfaces/cid-link.js +13 -0
  17. package/dist/interfaces/cid-link.js.map +1 -0
  18. package/dist/interfaces/index.d.ts +3 -0
  19. package/dist/interfaces/index.js +4 -0
  20. package/dist/interfaces/index.js.map +1 -0
  21. package/dist/syntax/at-identifier.d.ts +8 -0
  22. package/dist/syntax/at-identifier.js +7 -0
  23. package/dist/syntax/at-identifier.js.map +1 -0
  24. package/dist/syntax/at-uri.d.ts +46 -0
  25. package/dist/syntax/at-uri.js +64 -0
  26. package/dist/syntax/at-uri.js.map +1 -0
  27. package/dist/syntax/cid.d.ts +5 -0
  28. package/dist/syntax/cid.js +10 -0
  29. package/dist/syntax/cid.js.map +1 -0
  30. package/dist/syntax/datetime.d.ts +2 -0
  31. package/dist/syntax/datetime.js +6 -0
  32. package/dist/syntax/datetime.js.map +1 -0
  33. package/dist/syntax/did.d.ts +9 -0
  34. package/dist/syntax/did.js +6 -0
  35. package/dist/syntax/did.js.map +1 -0
  36. package/dist/syntax/handle.d.ts +6 -0
  37. package/dist/syntax/handle.js +6 -0
  38. package/dist/syntax/handle.js.map +1 -0
  39. package/dist/syntax/index.d.ts +11 -0
  40. package/dist/syntax/index.js +12 -0
  41. package/dist/syntax/index.js.map +1 -0
  42. package/dist/syntax/language.d.ts +2 -0
  43. package/dist/syntax/language.js +6 -0
  44. package/dist/syntax/language.js.map +1 -0
  45. package/dist/syntax/nsid.d.ts +5 -0
  46. package/dist/syntax/nsid.js +6 -0
  47. package/dist/syntax/nsid.js.map +1 -0
  48. package/dist/syntax/record-key.d.ts +5 -0
  49. package/dist/syntax/record-key.js +6 -0
  50. package/dist/syntax/record-key.js.map +1 -0
  51. package/dist/syntax/tid.d.ts +5 -0
  52. package/dist/syntax/tid.js +6 -0
  53. package/dist/syntax/tid.js.map +1 -0
  54. package/dist/syntax/uri.d.ts +5 -0
  55. package/dist/syntax/uri.js +6 -0
  56. package/dist/syntax/uri.js.map +1 -0
  57. package/dist/types/brand.d.ts +13 -0
  58. package/dist/types/brand.js +2 -0
  59. package/dist/types/brand.js.map +1 -0
  60. package/dist/utils.d.ts +11 -0
  61. package/dist/utils.js +12 -0
  62. package/dist/utils.js.map +1 -0
  63. package/dist/validations/index.d.ts +409 -0
  64. package/dist/validations/index.js +1003 -0
  65. package/dist/validations/index.js.map +1 -0
  66. package/dist/validations/utils.d.ts +8 -0
  67. package/dist/validations/utils.js +54 -0
  68. package/dist/validations/utils.js.map +1 -0
  69. package/lib/ambient.ts +7 -0
  70. package/lib/index.ts +38 -0
  71. package/lib/interfaces/blob.ts +47 -0
  72. package/lib/interfaces/bytes.ts +45 -0
  73. package/lib/interfaces/cid-link.ts +36 -0
  74. package/lib/interfaces/index.ts +3 -0
  75. package/lib/syntax/at-identifier.ts +13 -0
  76. package/lib/syntax/at-uri.ts +117 -0
  77. package/lib/syntax/cid.ts +16 -0
  78. package/lib/syntax/datetime.ts +9 -0
  79. package/lib/syntax/did.ts +16 -0
  80. package/lib/syntax/handle.ts +13 -0
  81. package/lib/syntax/index.ts +11 -0
  82. package/lib/syntax/language.ts +9 -0
  83. package/lib/syntax/nsid.ts +12 -0
  84. package/lib/syntax/record-key.ts +11 -0
  85. package/lib/syntax/tid.ts +11 -0
  86. package/lib/syntax/uri.ts +11 -0
  87. package/lib/types/brand.ts +11 -0
  88. package/lib/utils.ts +15 -0
  89. package/lib/validations/index.ts +1749 -0
  90. package/lib/validations/utils.ts +62 -0
  91. package/package.json +35 -0
@@ -0,0 +1,1749 @@
1
+ import * as syntax from '../syntax/index.js';
2
+
3
+ import { _isBytesWrapper } from '../interfaces/bytes.js';
4
+ import * as interfaces from '../interfaces/index.js';
5
+
6
+ import type { $type } from '../types/brand.js';
7
+
8
+ import { assert } from '../utils.js';
9
+
10
+ import { getGraphemeLength, getUtf8Length, isArray, isObject, lazy, lazyProperty } from './utils.js';
11
+
12
+ type Identity<T> = T;
13
+ type Flatten<T> = Identity<{ [K in keyof T]: T[K] }>;
14
+
15
+ type InputType =
16
+ | 'unknown'
17
+ | 'null'
18
+ | 'undefined'
19
+ | 'string'
20
+ | 'integer'
21
+ | 'boolean'
22
+ | 'blob'
23
+ | 'bytes'
24
+ | 'cid-link'
25
+ | 'object'
26
+ | 'array';
27
+
28
+ type StringFormatMap = {
29
+ 'at-identifier': syntax.ActorIdentifier;
30
+ 'at-uri': syntax.ResourceUri;
31
+ cid: syntax.Cid;
32
+ datetime: syntax.Datetime;
33
+ did: syntax.Did;
34
+ handle: syntax.Handle;
35
+ language: syntax.LanguageCode;
36
+ nsid: syntax.Nsid;
37
+ 'record-key': syntax.RecordKey;
38
+ tid: syntax.Tid;
39
+ uri: syntax.GenericUri;
40
+ };
41
+
42
+ export type StringFormat = keyof StringFormatMap;
43
+
44
+ type Literal = string | number | boolean;
45
+ type Key = string | number;
46
+
47
+ // #region Schema issue types
48
+ export type IssueLeaf =
49
+ | { ok: false; code: 'missing_value' }
50
+ | { ok: false; code: 'invalid_literal'; expected: readonly Literal[] }
51
+ | { ok: false; code: 'invalid_type'; expected: InputType }
52
+ | { ok: false; code: 'invalid_variant'; expected: string[] }
53
+ | { ok: false; code: 'invalid_integer_range'; min: number; max: number }
54
+ | { ok: false; code: 'invalid_string_format'; expected: StringFormat }
55
+ | { ok: false; code: 'invalid_string_graphemes'; minGraphemes: number; maxGraphemes: number }
56
+ | { ok: false; code: 'invalid_string_length'; minLength: number; maxLength: number }
57
+ | { ok: false; code: 'invalid_array_length'; minLength: number; maxLength: number }
58
+ | { ok: false; code: 'invalid_bytes_size'; minSize: number; maxSize: number };
59
+
60
+ export type IssueTree =
61
+ | IssueLeaf
62
+ | { ok: false; code: 'prepend'; key: Key; tree: IssueTree }
63
+ | { ok: false; code: 'join'; left: IssueTree; right: IssueTree };
64
+
65
+ export type Issue =
66
+ | { code: 'missing_value'; path: Key[] }
67
+ | { code: 'invalid_literal'; path: Key[]; expected: readonly Literal[] }
68
+ | { code: 'invalid_type'; path: Key[]; expected: InputType }
69
+ | { code: 'invalid_variant'; path: Key[]; expected: string[] }
70
+ | { code: 'invalid_integer_range'; path: Key[]; min: number; max: number }
71
+ | { code: 'invalid_string_format'; path: Key[]; expected: StringFormat }
72
+ | { code: 'invalid_string_graphemes'; path: Key[]; minGraphemes: number; maxGraphemes: number }
73
+ | { code: 'invalid_string_length'; path: Key[]; minLength: number; maxLength: number }
74
+ | { code: 'invalid_array_length'; path: Key[]; minLength: number; maxLength: number }
75
+ | { code: 'invalid_bytes_size'; path: Key[]; minSize: number; maxSize: number };
76
+
77
+ // #__NO_SIDE_EFFECTS__
78
+ const joinIssues = (left: IssueTree | undefined, right: IssueTree): IssueTree => {
79
+ return left ? { ok: false, code: 'join', left, right } : right;
80
+ };
81
+
82
+ // #__NO_SIDE_EFFECTS__
83
+ const prependPath = (key: Key, tree: IssueTree): IssueTree => {
84
+ return { ok: false, code: 'prepend', key, tree };
85
+ };
86
+
87
+ // #region Schema result types
88
+
89
+ export type Ok<T> = {
90
+ ok: true;
91
+ value: T;
92
+ };
93
+ export type Err = {
94
+ ok: false;
95
+ readonly message: string;
96
+ readonly issues: readonly Issue[];
97
+ throw(): never;
98
+ };
99
+
100
+ export type ValidationResult<T> = Ok<T> | Err;
101
+
102
+ // #region Base schema
103
+
104
+ // Private symbols meant to hold types
105
+ declare const kType: unique symbol;
106
+ type kType = typeof kType;
107
+
108
+ // We need a special symbol to hold the types for objects due to their
109
+ // recursive nature.
110
+ declare const kObjectType: unique symbol;
111
+ type kObjectType = typeof kObjectType;
112
+
113
+ // None set
114
+ export const FLAG_EMPTY = 0;
115
+ // Don't continue validation if an error is encountered
116
+ export const FLAG_ABORT_EARLY = 1 << 0;
117
+
118
+ type MatcherResult = undefined | Ok<unknown> | IssueTree;
119
+ type Matcher = (this: void, input: unknown, flags: number) => MatcherResult;
120
+
121
+ export interface BaseSchema<TInput = unknown, TOutput = TInput> {
122
+ readonly kind: 'schema';
123
+ readonly type: string;
124
+ readonly '~run': Matcher;
125
+
126
+ readonly [kType]?: { in: TInput; out: TOutput };
127
+ }
128
+
129
+ export type InferInput<T extends BaseSchema> = T extends { [kObjectType]?: any }
130
+ ? NonNullable<T[kObjectType]>['in']
131
+ : NonNullable<T[kType]>['in'];
132
+
133
+ export type InferOutput<T extends BaseSchema> = T extends { [kObjectType]?: any }
134
+ ? NonNullable<T[kObjectType]>['out']
135
+ : NonNullable<T[kType]>['out'];
136
+
137
+ // #region Schema runner
138
+ const cloneIssueWithPath = (issue: IssueLeaf, path: Key[]): Issue => {
139
+ const { ok: _ok, ...clone } = issue;
140
+
141
+ return { ...clone, path };
142
+ };
143
+
144
+ const collectIssues = (tree: IssueTree, path: Key[] = [], issues: Issue[] = []): Issue[] => {
145
+ for (;;) {
146
+ switch (tree.code) {
147
+ case 'join': {
148
+ collectIssues(tree.left, path.slice(), issues);
149
+ tree = tree.right;
150
+ continue;
151
+ }
152
+ case 'prepend': {
153
+ path.push(tree.key);
154
+ tree = tree.tree;
155
+ continue;
156
+ }
157
+ default: {
158
+ issues.push(cloneIssueWithPath(tree, path));
159
+ return issues;
160
+ }
161
+ }
162
+ }
163
+ };
164
+
165
+ const countIssues = (tree: IssueTree): number => {
166
+ let count = 0;
167
+ for (;;) {
168
+ switch (tree.code) {
169
+ case 'join': {
170
+ count += countIssues(tree.left);
171
+ tree = tree.right;
172
+ continue;
173
+ }
174
+ case 'prepend': {
175
+ tree = tree.tree;
176
+ continue;
177
+ }
178
+ default: {
179
+ return count + 1;
180
+ }
181
+ }
182
+ }
183
+ };
184
+
185
+ const separatedList = (list: string[], sep: 'or' | 'and'): string => {
186
+ switch (list.length) {
187
+ case 0: {
188
+ return `nothing`;
189
+ }
190
+ case 1: {
191
+ return list[0];
192
+ }
193
+ default: {
194
+ return `${list.slice(0, -1).join(', ')} ${sep} ${list[list.length - 1]}`;
195
+ }
196
+ }
197
+ };
198
+
199
+ const formatLiteral = (value: Literal): string => {
200
+ return JSON.stringify(value);
201
+ };
202
+
203
+ const formatRangeMessage = (
204
+ type: 'a string' | 'an array' | 'a byte array',
205
+ unit: 'character' | 'grapheme' | 'item' | 'byte',
206
+ min: number,
207
+ max: number,
208
+ ): string => {
209
+ let message = `${type} `;
210
+
211
+ if (min > 0) {
212
+ if (max === min) {
213
+ message += `${min}`;
214
+ } else if (max !== Infinity) {
215
+ message += `between ${min} and ${max}`;
216
+ } else {
217
+ message += `at least ${min}`;
218
+ }
219
+ } else {
220
+ message += `at most ${max}`;
221
+ }
222
+
223
+ message += ` ${unit}(s)`;
224
+ return message;
225
+ };
226
+
227
+ const formatIssueTree = (tree: IssueTree): string => {
228
+ let path = '';
229
+ let count = 0;
230
+ for (;;) {
231
+ switch (tree.code) {
232
+ case 'join': {
233
+ count += countIssues(tree.right);
234
+ tree = tree.left;
235
+ continue;
236
+ }
237
+ case 'prepend': {
238
+ path += `.${tree.key}`;
239
+ tree = tree.tree;
240
+ continue;
241
+ }
242
+ }
243
+
244
+ break;
245
+ }
246
+
247
+ let message: string;
248
+ switch (tree.code) {
249
+ case 'missing_value': {
250
+ message = `missing value`;
251
+ break;
252
+ }
253
+ case 'invalid_literal': {
254
+ message = `expected ${separatedList(tree.expected.map(formatLiteral), 'or')}`;
255
+ break;
256
+ }
257
+ case 'invalid_type': {
258
+ message = `expected ${tree.expected}`;
259
+ break;
260
+ }
261
+ case 'invalid_variant': {
262
+ message = `expected ${separatedList(tree.expected, 'or')}`;
263
+ break;
264
+ }
265
+ case 'invalid_integer_range': {
266
+ const min = tree.min;
267
+ const max = tree.max;
268
+
269
+ message = `expected an integer `;
270
+
271
+ if (min > 0) {
272
+ if (max === min) {
273
+ message += `of exactly ${min}`;
274
+ } else if (max !== Infinity) {
275
+ message += `between ${min} and ${max}`;
276
+ } else {
277
+ message += `of at least ${min}`;
278
+ }
279
+ } else {
280
+ message += `of at most ${max}`;
281
+ }
282
+
283
+ break;
284
+ }
285
+ case 'invalid_string_format': {
286
+ message = `expected a ${tree.expected} formatted string`;
287
+ break;
288
+ }
289
+ case 'invalid_string_graphemes': {
290
+ message = formatRangeMessage('a string', 'grapheme', tree.minGraphemes, tree.maxGraphemes);
291
+ break;
292
+ }
293
+ case 'invalid_string_length': {
294
+ message = formatRangeMessage('a string', 'character', tree.minLength, tree.maxLength);
295
+ break;
296
+ }
297
+ case 'invalid_array_length': {
298
+ message = formatRangeMessage('an array', 'item', tree.minLength, tree.maxLength);
299
+ break;
300
+ }
301
+ case 'invalid_bytes_size': {
302
+ message = formatRangeMessage('a byte array', 'byte', tree.minSize, tree.maxSize);
303
+ break;
304
+ }
305
+ }
306
+
307
+ let msg = `${tree.code} at ${path ?? '.'} (${message})`;
308
+ if (count > 0) {
309
+ msg += ` (+${count} other issue(s))`;
310
+ }
311
+
312
+ return msg;
313
+ };
314
+
315
+ export class ValidationError extends Error {
316
+ override readonly name = 'ValidationError';
317
+
318
+ #issueTree: IssueTree;
319
+
320
+ constructor(issueTree: IssueTree) {
321
+ super();
322
+
323
+ this.#issueTree = issueTree;
324
+ }
325
+
326
+ override get message(): string {
327
+ return formatIssueTree(this.#issueTree);
328
+ }
329
+
330
+ get issues(): readonly Issue[] {
331
+ return collectIssues(this.#issueTree);
332
+ }
333
+ }
334
+
335
+ class ErrImpl implements Err {
336
+ readonly ok = false;
337
+
338
+ #issueTree: IssueTree;
339
+
340
+ constructor(issueTree: IssueTree) {
341
+ this.#issueTree = issueTree;
342
+ }
343
+
344
+ get message(): string {
345
+ return formatIssueTree(this.#issueTree);
346
+ }
347
+
348
+ get issues(): readonly Issue[] {
349
+ return collectIssues(this.#issueTree);
350
+ }
351
+
352
+ throw(): never {
353
+ throw new ValidationError(this.#issueTree);
354
+ }
355
+ }
356
+
357
+ // #__NO_SIDE_EFFECTS__
358
+ export const is = <const TSchema extends BaseSchema>(
359
+ schema: TSchema,
360
+ input: unknown,
361
+ ): input is InferInput<TSchema> => {
362
+ const r = schema['~run'](input, FLAG_ABORT_EARLY);
363
+ return r === undefined || r.ok;
364
+ };
365
+
366
+ // #__NO_SIDE_EFFECTS__
367
+ export const safeParse = <const TSchema extends BaseSchema>(
368
+ schema: TSchema,
369
+ input: unknown,
370
+ ): ValidationResult<InferOutput<TSchema>> => {
371
+ const r = schema['~run'](input, FLAG_EMPTY);
372
+
373
+ if (r === undefined) {
374
+ return { ok: true, value: input as InferOutput<TSchema> };
375
+ }
376
+
377
+ if (r.ok) {
378
+ return r as Ok<InferOutput<TSchema>>;
379
+ }
380
+
381
+ return new ErrImpl(r);
382
+ };
383
+
384
+ export const parse = <const TSchema extends BaseSchema>(
385
+ schema: TSchema,
386
+ input: unknown,
387
+ ): InferOutput<TSchema> => {
388
+ const r = schema['~run'](input, FLAG_EMPTY);
389
+
390
+ if (r === undefined) {
391
+ return input as InferOutput<TSchema>;
392
+ }
393
+
394
+ if (r.ok) {
395
+ return r.value as InferOutput<TSchema>;
396
+ }
397
+
398
+ throw new ValidationError(r);
399
+ };
400
+
401
+ // #region Base constraint
402
+
403
+ export interface BaseConstraint<TType = unknown> {
404
+ readonly kind: 'constraint';
405
+ readonly type: string;
406
+ readonly '~run': (input: TType, flags: number) => MatcherResult;
407
+ }
408
+
409
+ type ConstraintTuple<T> = readonly [BaseConstraint<T>, ...BaseConstraint<T>[]];
410
+
411
+ export type SchemaWithConstraint<
412
+ TItem extends BaseSchema,
413
+ TConstraints extends ConstraintTuple<InferOutput<TItem>>,
414
+ > = TItem & {
415
+ readonly constraints: TConstraints;
416
+ };
417
+
418
+ export const constrain = <
419
+ TItem extends BaseSchema,
420
+ const TConstraints extends ConstraintTuple<InferOutput<TItem>>,
421
+ >(
422
+ base: TItem,
423
+ constraints: TConstraints,
424
+ ): SchemaWithConstraint<TItem, TConstraints> => {
425
+ const len = constraints.length;
426
+
427
+ return {
428
+ ...base,
429
+ constraints: constraints,
430
+ '~run'(input, flags) {
431
+ let result = base['~run'](input, flags);
432
+ let current: any;
433
+
434
+ if (result === undefined) {
435
+ current = input;
436
+ } else if (result.ok) {
437
+ current = result.value;
438
+ } else {
439
+ return result;
440
+ }
441
+
442
+ for (let idx = 0; idx < len; idx++) {
443
+ const r = constraints[idx]['~run'](current, flags);
444
+
445
+ if (r !== undefined) {
446
+ if (r.ok) {
447
+ current = r.value;
448
+
449
+ if (result === undefined || result.ok) {
450
+ result = r;
451
+ }
452
+ } else {
453
+ if (flags & FLAG_ABORT_EARLY) {
454
+ return r;
455
+ } else if (result === undefined || result.ok) {
456
+ result = r;
457
+ } else {
458
+ result = joinIssues(result, r);
459
+ }
460
+ }
461
+ }
462
+ }
463
+
464
+ return result;
465
+ },
466
+ };
467
+ };
468
+
469
+ // #region Base metadata
470
+
471
+ export interface BaseMetadata {
472
+ readonly kind: 'metadata';
473
+ readonly type: string;
474
+ }
475
+
476
+ // #region Literal schema
477
+
478
+ export interface LiteralSchema<T extends Literal = Literal> extends BaseSchema<T> {
479
+ readonly type: 'literal';
480
+ readonly expected: T;
481
+ }
482
+
483
+ // #__NO_SIDE_EFFECTS__
484
+ export const literal = <T extends Literal>(value: T): LiteralSchema<T> => {
485
+ const issue: IssueLeaf = {
486
+ ok: false,
487
+ code: 'invalid_literal',
488
+ expected: [value],
489
+ };
490
+
491
+ return {
492
+ kind: 'schema',
493
+ type: 'literal',
494
+ expected: value,
495
+ '~run'(input, _flags) {
496
+ if (input !== value) {
497
+ return issue;
498
+ }
499
+
500
+ return undefined;
501
+ },
502
+ };
503
+ };
504
+
505
+ export interface LiteralEnumSchema<TEnums extends readonly Literal[] = []>
506
+ extends BaseSchema<TEnums[number]> {
507
+ readonly type: 'literal_enum';
508
+ readonly expected: TEnums;
509
+ }
510
+
511
+ // #__NO_SIDE_EFFECTS__
512
+ export const literalEnum = <const TEnums extends readonly Literal[]>(
513
+ values: TEnums,
514
+ ): LiteralEnumSchema<TEnums> => {
515
+ const issue: IssueLeaf = {
516
+ ok: false,
517
+ code: 'invalid_literal',
518
+ expected: values,
519
+ };
520
+
521
+ return {
522
+ kind: 'schema',
523
+ type: 'literal_enum',
524
+ expected: values,
525
+ '~run'(input, _flags) {
526
+ if (!values.includes(input as any)) {
527
+ return issue;
528
+ }
529
+
530
+ return undefined;
531
+ },
532
+ };
533
+ };
534
+
535
+ // #region Boolean schema
536
+
537
+ export interface BooleanSchema extends BaseSchema<boolean> {
538
+ readonly type: 'boolean';
539
+ }
540
+
541
+ const ISSUE_TYPE_BOOLEAN: IssueLeaf = {
542
+ ok: false,
543
+ code: 'invalid_type',
544
+ expected: 'boolean',
545
+ };
546
+
547
+ const BOOLEAN_SCHEMA: BooleanSchema = {
548
+ kind: 'schema',
549
+ type: 'boolean',
550
+ '~run'(input, _flags) {
551
+ if (typeof input !== 'boolean') {
552
+ return ISSUE_TYPE_BOOLEAN;
553
+ }
554
+
555
+ return undefined;
556
+ },
557
+ };
558
+
559
+ // #__NO_SIDE_EFFECTS__
560
+ export const boolean = (): BooleanSchema => {
561
+ return BOOLEAN_SCHEMA;
562
+ };
563
+
564
+ // #region Integer schema
565
+
566
+ export interface IntegerSchema extends BaseSchema<number> {
567
+ readonly type: 'integer';
568
+ }
569
+
570
+ const ISSUE_TYPE_INTEGER: IssueLeaf = {
571
+ ok: false,
572
+ code: 'invalid_type',
573
+ expected: 'integer',
574
+ };
575
+
576
+ const INTEGER_SCHEMA: IntegerSchema = {
577
+ kind: 'schema',
578
+ type: 'integer',
579
+ '~run'(input, _flags) {
580
+ if (typeof input !== 'number') {
581
+ return ISSUE_TYPE_INTEGER;
582
+ }
583
+
584
+ if (input < 0 || !Number.isSafeInteger(input)) {
585
+ return ISSUE_TYPE_INTEGER;
586
+ }
587
+
588
+ return undefined;
589
+ },
590
+ };
591
+
592
+ // #__NO_SIDE_EFFECTS__
593
+ export const integer = (): IntegerSchema => {
594
+ return INTEGER_SCHEMA;
595
+ };
596
+
597
+ // #region Integer constraints
598
+
599
+ export interface IntegerRangeConstraint<TMin extends number = number, TMax extends number = number>
600
+ extends BaseConstraint<number> {
601
+ readonly type: 'integer_range';
602
+ readonly min: TMin;
603
+ readonly max: TMax;
604
+ }
605
+
606
+ // #__NO_SIDE_EFFECTS__
607
+ export const integerRange: {
608
+ <const TMin extends number>(min: TMin): IntegerRangeConstraint<TMin>;
609
+ <const TMin extends number, const TMax extends number>(
610
+ min: TMin,
611
+ max: TMax,
612
+ ): IntegerRangeConstraint<TMin, TMax>;
613
+ } = (min: number, max: number = Infinity): IntegerRangeConstraint => {
614
+ const issue: IssueLeaf = {
615
+ ok: false,
616
+ code: 'invalid_integer_range',
617
+ min: min,
618
+ max: max,
619
+ };
620
+
621
+ return {
622
+ kind: 'constraint',
623
+ type: 'integer_range',
624
+ min: min,
625
+ max: max,
626
+ '~run'(input, _flags) {
627
+ if (input < min) {
628
+ return issue;
629
+ }
630
+
631
+ if (input > max) {
632
+ return issue;
633
+ }
634
+
635
+ return undefined;
636
+ },
637
+ };
638
+ };
639
+
640
+ // #region String schema
641
+
642
+ export interface StringSchema<T extends string = string> extends BaseSchema<T> {
643
+ readonly type: 'string';
644
+ readonly format: null;
645
+ }
646
+
647
+ export interface FormattedStringSchema<TFormat extends keyof StringFormatMap = keyof StringFormatMap>
648
+ extends BaseSchema<StringFormatMap[TFormat]> {
649
+ readonly type: 'string';
650
+ readonly format: TFormat;
651
+ }
652
+
653
+ const ISSUE_TYPE_STRING: IssueLeaf = {
654
+ ok: false,
655
+ code: 'invalid_type',
656
+ expected: 'string',
657
+ };
658
+
659
+ const STRING_SINGLETON: StringSchema = {
660
+ kind: 'schema',
661
+ type: 'string',
662
+ format: null,
663
+ '~run'(input, _flags) {
664
+ if (typeof input !== 'string') {
665
+ return ISSUE_TYPE_STRING;
666
+ }
667
+
668
+ return undefined;
669
+ },
670
+ };
671
+
672
+ // #__NO_SIDE_EFFECTS__
673
+ export const string = <T extends string = string>(): StringSchema<T> => {
674
+ return STRING_SINGLETON as StringSchema<T>;
675
+ };
676
+
677
+ // #__NO_SIDE_EFFECTS__
678
+ const _formattedString = <TFormat extends keyof StringFormatMap>(
679
+ format: TFormat,
680
+ validate: (input: string) => boolean,
681
+ ) => {
682
+ const issue: IssueLeaf = {
683
+ ok: false,
684
+ code: 'invalid_string_format',
685
+ expected: format,
686
+ };
687
+
688
+ const schema: FormattedStringSchema<TFormat> = {
689
+ kind: 'schema',
690
+ type: 'string',
691
+ format: format,
692
+ '~run'(input, _flags) {
693
+ if (typeof input !== 'string') {
694
+ return ISSUE_TYPE_STRING;
695
+ }
696
+
697
+ if (!validate(input)) {
698
+ return issue;
699
+ }
700
+
701
+ return undefined;
702
+ },
703
+ };
704
+
705
+ return () => schema;
706
+ };
707
+
708
+ // prettier-ignore
709
+ export const actorIdentifierString = /*#__PURE__*/ _formattedString('at-identifier', syntax.isActorIdentifier);
710
+ export const resourceUriString = /*#__PURE__*/ _formattedString('at-uri', syntax.isResourceUri);
711
+ export const cidString = /*#__PURE__*/ _formattedString('cid', syntax.isCid);
712
+ export const datetimeString = /*#__PURE__*/ _formattedString('datetime', syntax.isDatetime);
713
+ export const didString = /*#__PURE__*/ _formattedString('did', syntax.isDid);
714
+ export const handleString = /*#__PURE__*/ _formattedString('handle', syntax.isHandle);
715
+ export const languageCodeString = /*#__PURE__*/ _formattedString('language', syntax.isLanguageCode);
716
+ export const nsidString = /*#__PURE__*/ _formattedString('nsid', syntax.isNsid);
717
+ export const recordKeyString = /*#__PURE__*/ _formattedString('record-key', syntax.isRecordKey);
718
+ export const tidString = /*#__PURE__*/ _formattedString('tid', syntax.isTid);
719
+ export const genericUriString = /*#__PURE__*/ _formattedString('uri', syntax.isGenericUri);
720
+
721
+ // #region String constraints
722
+
723
+ export interface StringLengthConstraint<
724
+ TMinLength extends number = number,
725
+ TMaxLength extends number = number,
726
+ > extends BaseConstraint<string> {
727
+ readonly type: 'string_length';
728
+ readonly minLength: TMinLength;
729
+ readonly maxLength: TMaxLength;
730
+ }
731
+
732
+ // #__NO_SIDE_EFFECTS__
733
+ export const stringLength: {
734
+ <const TMinLength extends number>(min: TMinLength): StringLengthConstraint<TMinLength>;
735
+ <const TMinLength extends number, const TMaxLength extends number>(
736
+ min: TMinLength,
737
+ max: TMaxLength,
738
+ ): StringLengthConstraint<TMinLength, TMaxLength>;
739
+ } = (minLength: number, maxLength: number = Infinity): StringLengthConstraint => {
740
+ const issue: IssueLeaf = {
741
+ ok: false,
742
+ code: 'invalid_string_length',
743
+ minLength: minLength,
744
+ maxLength: maxLength,
745
+ };
746
+
747
+ return {
748
+ kind: 'constraint',
749
+ type: 'string_length',
750
+ minLength: minLength,
751
+ maxLength: maxLength,
752
+ '~run'(input, _flags) {
753
+ // UTF-8 conversion can be expensive, so we're going to do some safe naive
754
+ // checks where we assume an upper-bound of the UTF-16 to UTF-8 conversion
755
+
756
+ const maybeUtf8Len = input.length * 3;
757
+
758
+ // fail early if we're still less than minimum length
759
+ if (maybeUtf8Len < minLength) {
760
+ return issue;
761
+ }
762
+
763
+ // skip if we're still within maximum length
764
+ if (maybeUtf8Len <= maxLength) {
765
+ return undefined;
766
+ }
767
+
768
+ const utf8Len = getUtf8Length(input);
769
+
770
+ if (utf8Len < minLength) {
771
+ return issue;
772
+ }
773
+
774
+ if (utf8Len > maxLength) {
775
+ return issue;
776
+ }
777
+
778
+ return undefined;
779
+ },
780
+ };
781
+ };
782
+
783
+ export interface StringGraphemesConstraint<
784
+ TMinGraphemes extends number = number,
785
+ TMaxGraphemes extends number = number,
786
+ > extends BaseConstraint<string> {
787
+ readonly type: 'string_graphemes';
788
+ readonly minGraphemes: TMinGraphemes;
789
+ readonly maxGraphemes: TMaxGraphemes;
790
+ }
791
+
792
+ export const stringGraphemes: {
793
+ <const TMinGraphemes extends number>(min: TMinGraphemes): StringGraphemesConstraint<TMinGraphemes>;
794
+ <const TMinGraphemes extends number, const TMaxGraphemes extends number>(
795
+ min: TMinGraphemes,
796
+ max: TMaxGraphemes,
797
+ ): StringGraphemesConstraint<TMinGraphemes, TMaxGraphemes>;
798
+ } = (minGraphemes: number, maxGraphemes: number = Infinity): StringGraphemesConstraint => {
799
+ const issue: IssueLeaf = {
800
+ ok: false,
801
+ code: 'invalid_string_graphemes',
802
+ minGraphemes: minGraphemes,
803
+ maxGraphemes: maxGraphemes,
804
+ };
805
+
806
+ return {
807
+ kind: 'constraint',
808
+ type: 'string_graphemes',
809
+ minGraphemes: minGraphemes,
810
+ maxGraphemes: maxGraphemes,
811
+ '~run'(input, _flags) {
812
+ // grapheme conversion is expensive, so we're going to do some safe naive
813
+ // checks where we assume 1 UTF-16 character = 1 grapheme.
814
+
815
+ const utf16Len = input.length;
816
+
817
+ // fail early if UTF-16 length is less than grapheme length
818
+ if (utf16Len < minGraphemes) {
819
+ return issue;
820
+ }
821
+
822
+ // skip if we're still within maximum constraint
823
+ if (utf16Len <= maxGraphemes) {
824
+ return undefined;
825
+ }
826
+
827
+ const graphemeLen = getGraphemeLength(input);
828
+
829
+ if (graphemeLen < minGraphemes) {
830
+ return issue;
831
+ }
832
+
833
+ if (graphemeLen > maxGraphemes) {
834
+ return issue;
835
+ }
836
+
837
+ return undefined;
838
+ },
839
+ };
840
+ };
841
+
842
+ // #region Blob schema
843
+
844
+ export interface BlobSchema extends BaseSchema<interfaces.Blob | interfaces.LegacyBlob, interfaces.Blob> {
845
+ readonly type: 'blob';
846
+ }
847
+
848
+ const ISSUE_EXPECTED_BLOB: IssueLeaf = {
849
+ ok: false,
850
+ code: 'invalid_type',
851
+ expected: 'blob',
852
+ };
853
+
854
+ const BLOB_SCHEMA: BlobSchema = {
855
+ kind: 'schema',
856
+ type: 'blob',
857
+ '~run'(input, _flags) {
858
+ if (typeof input !== 'object' || input === null) {
859
+ return ISSUE_EXPECTED_BLOB;
860
+ }
861
+
862
+ if (interfaces.isBlob(input)) {
863
+ return undefined;
864
+ }
865
+
866
+ if (interfaces.isLegacyBlob(input)) {
867
+ const blob: interfaces.Blob = {
868
+ $type: 'blob',
869
+ mimeType: input.mimeType,
870
+ ref: { $link: input.cid },
871
+ size: -1,
872
+ };
873
+
874
+ return { ok: true, value: blob };
875
+ }
876
+
877
+ return ISSUE_EXPECTED_BLOB;
878
+ },
879
+ };
880
+
881
+ // #__NO_SIDE_EFFECTS__
882
+ export const blob = (): BlobSchema => {
883
+ return BLOB_SCHEMA;
884
+ };
885
+
886
+ // #region IPLD bytes schema
887
+
888
+ export interface BytesSchema extends BaseSchema<interfaces.Bytes, interfaces.Bytes> {
889
+ readonly type: 'bytes';
890
+ }
891
+
892
+ const ISSUE_EXPECTED_BYTES: IssueLeaf = {
893
+ ok: false,
894
+ code: 'invalid_type',
895
+ expected: 'bytes',
896
+ };
897
+
898
+ const BYTES_SCHEMA: BytesSchema = {
899
+ kind: 'schema',
900
+ type: 'bytes',
901
+ '~run'(input, _flags) {
902
+ if (!interfaces.isBytes(input)) {
903
+ return ISSUE_EXPECTED_BYTES;
904
+ }
905
+
906
+ return undefined;
907
+ },
908
+ };
909
+
910
+ // #__NO_SIDE_EFFECTS__
911
+ export const bytes = (): BytesSchema => {
912
+ return BYTES_SCHEMA;
913
+ };
914
+
915
+ // #region IPLD bytes constraint
916
+ export interface BytesSizeConstraint<TMinLength extends number = number, TMaxLength extends number = number>
917
+ extends BaseConstraint<interfaces.Bytes> {
918
+ readonly type: 'bytes_size';
919
+ readonly minSize: TMinLength;
920
+ readonly maxSize: TMaxLength;
921
+ }
922
+
923
+ // #__NO_SIDE_EFFECTS__
924
+ export const bytesSize: {
925
+ <const TMinLength extends number>(min: TMinLength): BytesSizeConstraint<TMinLength>;
926
+ <const TMinLength extends number, const TMaxLength extends number>(
927
+ min: TMinLength,
928
+ max: TMaxLength,
929
+ ): BytesSizeConstraint<TMinLength, TMaxLength>;
930
+ } = (minSize: number, maxSize: number = Infinity): BytesSizeConstraint => {
931
+ const issue: IssueLeaf = {
932
+ ok: false,
933
+ code: 'invalid_bytes_size',
934
+ minSize: minSize,
935
+ maxSize: maxSize,
936
+ };
937
+
938
+ return {
939
+ kind: 'constraint',
940
+ type: 'bytes_size',
941
+ minSize: minSize,
942
+ maxSize: maxSize,
943
+ '~run'(input, _flags) {
944
+ let size: number;
945
+
946
+ if (_isBytesWrapper(input)) {
947
+ size = input.buf.length;
948
+ } else {
949
+ const str = input.$bytes;
950
+ let bytes = str.length;
951
+
952
+ if (str.charCodeAt(bytes - 1) === 0x3d) {
953
+ bytes--;
954
+ }
955
+ if (bytes > 1 && str.charCodeAt(bytes - 1) === 0x3d) {
956
+ bytes--;
957
+ }
958
+
959
+ size = (bytes * 3) >>> 2;
960
+ }
961
+
962
+ if (size < minSize) {
963
+ return issue;
964
+ }
965
+
966
+ if (size > maxSize) {
967
+ return issue;
968
+ }
969
+
970
+ return undefined;
971
+ },
972
+ };
973
+ };
974
+
975
+ // #region IPLD CID type schema
976
+
977
+ export interface CidLinkSchema extends BaseSchema<interfaces.CidLink, interfaces.CidLink> {
978
+ readonly type: 'cid_link';
979
+ }
980
+
981
+ const ISSUE_EXPECTED_CID_LINK: IssueLeaf = {
982
+ ok: false,
983
+ code: 'invalid_type',
984
+ expected: 'cid-link',
985
+ };
986
+
987
+ const CID_LINK_SCHEMA: CidLinkSchema = {
988
+ kind: 'schema',
989
+ type: 'cid_link',
990
+ '~run'(input, _flags) {
991
+ if (!interfaces.isCidLink(input)) {
992
+ return ISSUE_EXPECTED_CID_LINK;
993
+ }
994
+
995
+ return undefined;
996
+ },
997
+ };
998
+
999
+ // #__NO_SIDE_EFFECTS__
1000
+ export const cidLink = (): CidLinkSchema => {
1001
+ return CID_LINK_SCHEMA;
1002
+ };
1003
+
1004
+ // #region Nullable schema
1005
+
1006
+ export interface NullableSchema<TItem extends BaseSchema>
1007
+ extends BaseSchema<InferInput<TItem> | null, InferOutput<TItem> | null> {
1008
+ readonly type: 'nullable';
1009
+ readonly wrapped: TItem;
1010
+ }
1011
+
1012
+ // #__NO_SIDE_EFFECTS__
1013
+ export const nullable = <TItem extends BaseSchema>(wrapped: TItem): NullableSchema<TItem> => {
1014
+ return {
1015
+ kind: 'schema',
1016
+ type: 'nullable',
1017
+ wrapped: wrapped,
1018
+ '~run'(input, flags) {
1019
+ if (input === null) {
1020
+ return undefined;
1021
+ }
1022
+
1023
+ return wrapped['~run'](input, flags);
1024
+ },
1025
+ };
1026
+ };
1027
+
1028
+ // #region Optional schema
1029
+
1030
+ export type DefaultValue<TItem extends BaseSchema> =
1031
+ | InferOutput<TItem>
1032
+ | (() => InferOutput<TItem>)
1033
+ | undefined;
1034
+
1035
+ export type InferOptionalOutput<
1036
+ TItem extends BaseSchema,
1037
+ TDefault extends DefaultValue<TItem>,
1038
+ > = undefined extends TDefault ? InferOutput<TItem> | undefined : InferOutput<TItem>;
1039
+
1040
+ export interface OptionalSchema<TItem extends BaseSchema, TDefault extends DefaultValue<TItem>>
1041
+ extends BaseSchema<InferInput<TItem> | undefined, InferOptionalOutput<TItem, TDefault>> {
1042
+ readonly type: 'optional';
1043
+ readonly wrapped: TItem;
1044
+ readonly default: TDefault;
1045
+ }
1046
+
1047
+ type MaybeOptional<TItem extends BaseSchema> = TItem | OptionalSchema<TItem, undefined>;
1048
+
1049
+ // #__NO_SIDE_EFFECTS__
1050
+ export const optional: {
1051
+ <TItem extends BaseSchema>(wrapped: TItem): OptionalSchema<TItem, undefined>;
1052
+ <TItem extends BaseSchema, TDefault extends DefaultValue<TItem>>(
1053
+ wrapped: TItem,
1054
+ defaultValue: TDefault,
1055
+ ): OptionalSchema<TItem, TDefault>;
1056
+ } = (wrapped: BaseSchema, defaultValue?: any): OptionalSchema<any, any> => {
1057
+ return {
1058
+ kind: 'schema',
1059
+ type: 'optional',
1060
+ wrapped: wrapped,
1061
+ default: defaultValue,
1062
+ '~run'(input, flags) {
1063
+ if (input === undefined) {
1064
+ if (defaultValue === undefined) {
1065
+ return undefined;
1066
+ }
1067
+
1068
+ const value = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
1069
+
1070
+ return { ok: true, value };
1071
+ }
1072
+
1073
+ return wrapped['~run'](input, flags);
1074
+ },
1075
+ };
1076
+ };
1077
+
1078
+ const isOptionalSchema = (schema: BaseSchema): schema is OptionalSchema<any, unknown> => {
1079
+ return schema.type === 'optional';
1080
+ };
1081
+
1082
+ // #region Array schema
1083
+
1084
+ export interface ArraySchema<TItem extends BaseSchema> extends BaseSchema<unknown[], unknown[]> {
1085
+ readonly type: 'array';
1086
+ readonly item: TItem;
1087
+
1088
+ readonly [kObjectType]?: { in: InferInput<TItem>[]; out: InferOutput<TItem>[] };
1089
+ }
1090
+
1091
+ const ISSUE_TYPE_ARRAY: IssueLeaf = {
1092
+ ok: false,
1093
+ code: 'invalid_type',
1094
+ expected: 'array',
1095
+ };
1096
+
1097
+ // #__NO_SIDE_EFFECTS__
1098
+ export const array = <TItem extends BaseSchema>(item: TItem | (() => TItem)): ArraySchema<TItem> => {
1099
+ const resolvedShape = lazy(() => {
1100
+ return typeof item === 'function' ? item() : item;
1101
+ });
1102
+
1103
+ return {
1104
+ kind: 'schema',
1105
+ type: 'array',
1106
+ get item() {
1107
+ return lazyProperty(this, 'item', resolvedShape.value);
1108
+ },
1109
+ get '~run'() {
1110
+ const shape = resolvedShape.value;
1111
+
1112
+ const matcher: Matcher = (input, flags) => {
1113
+ if (!isArray(input)) {
1114
+ return ISSUE_TYPE_ARRAY;
1115
+ }
1116
+
1117
+ let issues: IssueTree | undefined;
1118
+ let output: any[] | undefined;
1119
+
1120
+ for (let idx = 0, len = input.length; idx < len; idx++) {
1121
+ const val = input[idx];
1122
+ const r = shape['~run'](val, flags);
1123
+
1124
+ if (r !== undefined) {
1125
+ if (r.ok) {
1126
+ if (output === undefined) {
1127
+ output = input.slice();
1128
+ }
1129
+
1130
+ output[idx] = r.value;
1131
+ } else {
1132
+ issues = joinIssues(issues, prependPath(idx, r));
1133
+
1134
+ if (flags & FLAG_ABORT_EARLY) {
1135
+ return issues;
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1140
+
1141
+ if (issues !== undefined) {
1142
+ return issues;
1143
+ }
1144
+
1145
+ if (output !== undefined) {
1146
+ return { ok: true, value: output };
1147
+ }
1148
+
1149
+ return undefined;
1150
+ };
1151
+
1152
+ return lazyProperty(this, '~run', matcher);
1153
+ },
1154
+ };
1155
+ };
1156
+
1157
+ // #region Array constraints
1158
+
1159
+ export interface ArrayLengthConstraint<TMinLength extends number = number, TMaxLength extends number = number>
1160
+ extends BaseConstraint<unknown[]> {
1161
+ readonly type: 'array_length';
1162
+ readonly minLength: TMinLength;
1163
+ readonly maxLength: TMaxLength;
1164
+ }
1165
+
1166
+ // #__NO_SIDE_EFFECTS__
1167
+ export const arrayLength: {
1168
+ <const TMinLength extends number>(min: TMinLength): ArrayLengthConstraint<TMinLength>;
1169
+ <const TMinLength extends number, const TMaxLength extends number>(
1170
+ min: TMinLength,
1171
+ max: TMaxLength,
1172
+ ): ArrayLengthConstraint<TMinLength, TMaxLength>;
1173
+ } = (minLength: number, maxLength: number = Infinity): ArrayLengthConstraint => {
1174
+ const issue: IssueLeaf = {
1175
+ ok: false,
1176
+ code: 'invalid_array_length',
1177
+ minLength: minLength,
1178
+ maxLength: maxLength,
1179
+ };
1180
+
1181
+ return {
1182
+ kind: 'constraint',
1183
+ type: 'array_length',
1184
+ minLength: minLength,
1185
+ maxLength: maxLength,
1186
+ '~run'(input, _flags) {
1187
+ const length = input.length;
1188
+
1189
+ if (length < minLength) {
1190
+ return issue;
1191
+ }
1192
+
1193
+ if (length > maxLength) {
1194
+ return issue;
1195
+ }
1196
+
1197
+ return undefined;
1198
+ },
1199
+ };
1200
+ };
1201
+
1202
+ // #region Object schema
1203
+
1204
+ export type LooseObjectShape = Record<string, any>;
1205
+ export type ObjectShape = Record<string, BaseSchema>;
1206
+
1207
+ export type OptionalObjectInputKeys<TShape extends ObjectShape> = {
1208
+ [Key in keyof TShape]: TShape[Key] extends OptionalSchema<any, any> ? Key : never;
1209
+ }[keyof TShape];
1210
+
1211
+ export type OptionalObjectOutputKeys<TShape extends ObjectShape> = {
1212
+ [Key in keyof TShape]: TShape[Key] extends OptionalSchema<any, infer Default>
1213
+ ? undefined extends Default
1214
+ ? Key
1215
+ : never
1216
+ : never;
1217
+ }[keyof TShape];
1218
+
1219
+ type InferObjectInput<TShape extends ObjectShape> = Flatten<
1220
+ {
1221
+ -readonly [Key in Exclude<keyof TShape, OptionalObjectInputKeys<TShape>>]: InferInput<TShape[Key]>;
1222
+ } & {
1223
+ -readonly [Key in OptionalObjectInputKeys<TShape>]?: InferInput<TShape[Key]>;
1224
+ }
1225
+ >;
1226
+
1227
+ type InferObjectOutput<TShape extends ObjectShape> = Flatten<
1228
+ {
1229
+ -readonly [Key in Exclude<keyof TShape, OptionalObjectOutputKeys<TShape>>]: InferOutput<TShape[Key]>;
1230
+ } & {
1231
+ -readonly [Key in OptionalObjectOutputKeys<TShape>]?: InferOutput<TShape[Key]>;
1232
+ }
1233
+ >;
1234
+
1235
+ export interface ObjectSchema<TShape extends LooseObjectShape = LooseObjectShape>
1236
+ extends BaseSchema<Record<string, unknown>> {
1237
+ readonly type: 'object';
1238
+ readonly shape: Readonly<TShape>;
1239
+
1240
+ readonly [kObjectType]?: { in: InferObjectInput<TShape>; out: InferObjectOutput<TShape> };
1241
+ }
1242
+
1243
+ interface ObjectEntry {
1244
+ key: string;
1245
+ schema: BaseSchema;
1246
+ optional: boolean;
1247
+ missing: IssueTree;
1248
+ }
1249
+
1250
+ const ISSUE_TYPE_OBJECT: IssueLeaf = {
1251
+ ok: false,
1252
+ code: 'invalid_type',
1253
+ expected: 'object',
1254
+ };
1255
+
1256
+ const ISSUE_MISSING: IssueLeaf = {
1257
+ ok: false,
1258
+ code: 'missing_value',
1259
+ };
1260
+
1261
+ const set = (obj: Record<string, unknown>, key: string, value: unknown): void => {
1262
+ if (key === '__proto__') {
1263
+ Object.defineProperty(obj, key, { value });
1264
+ } else {
1265
+ obj[key] = value;
1266
+ }
1267
+ };
1268
+
1269
+ // #__NO_SIDE_EFFECTS__
1270
+ export const object = <TShape extends LooseObjectShape>(shape: TShape): ObjectSchema<TShape> => {
1271
+ const resolvedEntries = lazy(() => {
1272
+ const resolved: ObjectEntry[] = [];
1273
+
1274
+ for (const key in shape) {
1275
+ const schema = shape[key];
1276
+
1277
+ resolved.push({
1278
+ key: key,
1279
+ schema: schema,
1280
+ optional: isOptionalSchema(schema),
1281
+ missing: prependPath(key, ISSUE_MISSING),
1282
+ });
1283
+ }
1284
+
1285
+ return resolved;
1286
+ });
1287
+
1288
+ return {
1289
+ kind: 'schema',
1290
+ type: 'object',
1291
+ get shape() {
1292
+ // if we just return the shape as is then it wouldn't be the same exact
1293
+ // shape when getters are present.
1294
+ const resolved = resolvedEntries.value;
1295
+ const obj: any = {};
1296
+
1297
+ for (const entry of resolved) {
1298
+ obj[entry.key] = entry.schema;
1299
+ }
1300
+
1301
+ return lazyProperty(this, 'shape', obj as TShape);
1302
+ },
1303
+ get '~run'() {
1304
+ const shape = resolvedEntries.value;
1305
+ const len = shape.length;
1306
+
1307
+ const matcher: Matcher = (input, flags) => {
1308
+ if (!isObject(input)) {
1309
+ return ISSUE_TYPE_OBJECT;
1310
+ }
1311
+
1312
+ let issues: IssueTree | undefined;
1313
+ let output: Record<string, unknown> | undefined;
1314
+
1315
+ for (let idx = 0; idx < len; idx++) {
1316
+ const entry = shape[idx];
1317
+
1318
+ const key = entry.key;
1319
+ const value = input[key];
1320
+
1321
+ if (value === undefined && !(key in input)) {
1322
+ if (!entry.optional) {
1323
+ issues = joinIssues(issues, entry.missing);
1324
+
1325
+ if (flags & FLAG_ABORT_EARLY) {
1326
+ return issues;
1327
+ }
1328
+
1329
+ continue;
1330
+ }
1331
+ }
1332
+
1333
+ const r = entry.schema['~run'](value, flags);
1334
+
1335
+ if (r === undefined) {
1336
+ if (output !== undefined) {
1337
+ /*#__INLINE__*/ set(output, key, value);
1338
+ }
1339
+ } else if (r.ok) {
1340
+ if (output === undefined) {
1341
+ output = { ...input };
1342
+ }
1343
+
1344
+ /*#__INLINE__*/ set(output, key, value);
1345
+ } else {
1346
+ issues = joinIssues(issues, prependPath(key, r));
1347
+
1348
+ if (flags & FLAG_ABORT_EARLY) {
1349
+ return issues;
1350
+ }
1351
+ }
1352
+ }
1353
+
1354
+ if (issues !== undefined) {
1355
+ return issues;
1356
+ }
1357
+
1358
+ if (output !== undefined) {
1359
+ return { ok: true, value: output };
1360
+ }
1361
+
1362
+ return undefined;
1363
+ };
1364
+
1365
+ return lazyProperty(this, '~run', matcher);
1366
+ },
1367
+ };
1368
+ };
1369
+
1370
+ // #region Record schema
1371
+ //
1372
+ // unfortunately, adapting for circular references has meant that we can't have
1373
+ // TypeScript check the object against a particular shape ($type field required)
1374
+
1375
+ export type RecordObjectShape = {
1376
+ $type: LiteralSchema<syntax.Nsid>;
1377
+ [key: string]: BaseSchema;
1378
+ };
1379
+
1380
+ export type RecordKeySchema = StringSchema | FormattedStringSchema | LiteralSchema<string>;
1381
+ export type RecordObjectSchema = ObjectSchema<RecordObjectShape>;
1382
+
1383
+ export interface RecordSchema<TObject extends ObjectSchema, TKey extends RecordKeySchema>
1384
+ extends BaseSchema<Record<string, unknown>> {
1385
+ readonly type: 'record';
1386
+ readonly key: TKey;
1387
+ readonly object: TObject;
1388
+
1389
+ readonly [kObjectType]?: { in: InferInput<TObject>; out: InferOutput<TObject> };
1390
+ }
1391
+
1392
+ // #__NO_SIDE_EFFECTS__
1393
+ export const record = <TKey extends RecordKeySchema, TObject extends ObjectSchema>(
1394
+ key: TKey,
1395
+ object: TObject,
1396
+ ): RecordSchema<TObject, TKey> => {
1397
+ const validatedObject = lazy((): TObject => {
1398
+ const shape = object.shape;
1399
+
1400
+ let t = shape.$type as MaybeOptional<LiteralSchema<syntax.Nsid>> | undefined;
1401
+
1402
+ assert(t !== undefined, `expected $type in record to be defined`);
1403
+ if (t.type === 'optional') {
1404
+ t = t.wrapped;
1405
+ }
1406
+
1407
+ assert(t.type === 'literal' && typeof t.expected === 'string', `expected $type to be a string literal`);
1408
+
1409
+ return object;
1410
+ });
1411
+
1412
+ return {
1413
+ kind: 'schema',
1414
+ type: 'record',
1415
+ key: key,
1416
+ get object() {
1417
+ return lazyProperty(this, 'object', validatedObject.value);
1418
+ },
1419
+ get '~run'() {
1420
+ const object = validatedObject.value;
1421
+
1422
+ const matcher: Matcher = (input, flags) => {
1423
+ return object['~run'](input, flags);
1424
+ };
1425
+
1426
+ return lazyProperty(this, '~run', matcher);
1427
+ },
1428
+ };
1429
+ };
1430
+
1431
+ // #region Variant schema
1432
+
1433
+ type VariantTuple = readonly ObjectSchema<any>[];
1434
+
1435
+ type InferVariantInput<TMembers extends VariantTuple> = $type.enforce<InferInput<TMembers[number]>>;
1436
+
1437
+ type InferVariantOutput<TMembers extends VariantTuple> = $type.enforce<InferOutput<TMembers[number]>>;
1438
+
1439
+ export interface VariantSchema<
1440
+ TMembers extends VariantTuple = VariantTuple,
1441
+ TClosed extends boolean = boolean,
1442
+ > extends BaseSchema<Record<string, unknown>> {
1443
+ readonly type: 'variant';
1444
+ readonly members: TMembers;
1445
+ readonly closed: TClosed;
1446
+
1447
+ readonly [kObjectType]?: { in: InferVariantInput<TMembers>; out: InferVariantOutput<TMembers> };
1448
+ }
1449
+
1450
+ const ISSUE_VARIANT_MISSING = /*#__PURE__*/ prependPath('$type', {
1451
+ ok: false,
1452
+ code: 'missing_value',
1453
+ });
1454
+
1455
+ const ISSUE_VARIANT_TYPE = /*#__PURE__*/ prependPath('$type', {
1456
+ ok: false,
1457
+ code: 'invalid_type',
1458
+ expected: 'string',
1459
+ });
1460
+
1461
+ // #__NO_SIDE_EFFECTS__
1462
+ export const variant: {
1463
+ <const TMembers extends VariantTuple>(members: TMembers): VariantSchema<TMembers>;
1464
+ <const TMembers extends VariantTuple, TClosed extends boolean>(
1465
+ members: TMembers,
1466
+ closed: TClosed,
1467
+ ): VariantSchema<TMembers, TClosed>;
1468
+ } = (members: ObjectSchema[], closed: boolean = false): VariantSchema<any, any> => {
1469
+ return {
1470
+ kind: 'schema',
1471
+ type: 'variant',
1472
+ members: members,
1473
+ closed: closed,
1474
+ get '~run'() {
1475
+ const map = Object.fromEntries(
1476
+ members.map((member, idx) => {
1477
+ const shape = member.shape;
1478
+
1479
+ let t = shape.$type as MaybeOptional<LiteralSchema<syntax.Nsid>> | undefined;
1480
+
1481
+ assert(t !== undefined, `expected $type in variant member #${idx} to be defined`);
1482
+ if (t.type === 'optional') {
1483
+ t = t.wrapped;
1484
+ }
1485
+
1486
+ assert(
1487
+ t.type === 'literal' && typeof t.expected === 'string',
1488
+ `expected $type in variant member #${idx} to be a string literal`,
1489
+ );
1490
+
1491
+ return [t.expected, member];
1492
+ }),
1493
+ );
1494
+
1495
+ const issue: IssueLeaf = {
1496
+ ok: false,
1497
+ code: 'invalid_variant',
1498
+ expected: Object.keys(map),
1499
+ };
1500
+
1501
+ const matcher: Matcher = (input, flags) => {
1502
+ if (!isObject(input)) {
1503
+ return ISSUE_TYPE_OBJECT;
1504
+ }
1505
+
1506
+ if (!('$type' in input)) {
1507
+ return ISSUE_VARIANT_MISSING;
1508
+ }
1509
+
1510
+ const type = input.$type;
1511
+ if (typeof type !== 'string') {
1512
+ return ISSUE_VARIANT_TYPE;
1513
+ }
1514
+
1515
+ if (!(type in map)) {
1516
+ if (closed) {
1517
+ return issue;
1518
+ }
1519
+
1520
+ return undefined;
1521
+ }
1522
+
1523
+ const schema = map[type];
1524
+
1525
+ return schema['~run'](input, flags);
1526
+ };
1527
+
1528
+ return lazyProperty(this, '~run', matcher);
1529
+ },
1530
+ };
1531
+ };
1532
+
1533
+ // #region Unknown schema
1534
+
1535
+ export interface UnknownSchema extends BaseSchema<Record<string, unknown>> {
1536
+ readonly type: 'unknown';
1537
+ }
1538
+
1539
+ const ISSUE_TYPE_UNKNOWN: IssueLeaf = {
1540
+ ok: false,
1541
+ code: 'invalid_type',
1542
+ expected: 'unknown',
1543
+ };
1544
+
1545
+ const UNKNOWN_SCHEMA: UnknownSchema = {
1546
+ kind: 'schema',
1547
+ type: 'unknown',
1548
+ '~run'(input, _flags) {
1549
+ if (typeof input !== 'object' || input === null) {
1550
+ return ISSUE_TYPE_UNKNOWN;
1551
+ }
1552
+
1553
+ return undefined;
1554
+ },
1555
+ };
1556
+
1557
+ // #__NO_SIDE_EFFECTS__
1558
+ export const unknown = (): UnknownSchema => {
1559
+ return UNKNOWN_SCHEMA;
1560
+ };
1561
+
1562
+ // #region XRPC types
1563
+
1564
+ export interface XRPCLexBodyParam<
1565
+ TSchema extends ObjectSchema | VariantSchema = ObjectSchema | VariantSchema,
1566
+ > {
1567
+ readonly type: 'lex';
1568
+ readonly schema: TSchema;
1569
+ }
1570
+
1571
+ export interface XRPCBlobBodyParam {
1572
+ readonly type: 'blob';
1573
+ }
1574
+
1575
+ export type XRPCBodyParam = XRPCLexBodyParam | XRPCBlobBodyParam | null;
1576
+
1577
+ export type InferXRPCBodyInput<T extends XRPCBodyParam> =
1578
+ T extends XRPCLexBodyParam<infer Schema>
1579
+ ? InferInput<Schema>
1580
+ : T extends XRPCBlobBodyParam
1581
+ ? Blob
1582
+ : T extends null
1583
+ ? void
1584
+ : never;
1585
+
1586
+ export type InferXRPCBodyOutput<T extends XRPCBodyParam> =
1587
+ T extends XRPCLexBodyParam<infer Schema>
1588
+ ? InferOutput<Schema>
1589
+ : T extends XRPCBlobBodyParam
1590
+ ? Blob
1591
+ : T extends null
1592
+ ? void
1593
+ : never;
1594
+
1595
+ // #region XRPC procedure metadata
1596
+
1597
+ export interface XRPCProcedureMetadata<
1598
+ TParams extends ObjectSchema | null,
1599
+ TInput extends XRPCBodyParam,
1600
+ TOutput extends XRPCBodyParam,
1601
+ TNsid extends syntax.Nsid,
1602
+ > extends BaseMetadata {
1603
+ readonly type: 'xrpc_procedure';
1604
+ readonly nsid: TNsid;
1605
+ readonly params: TParams;
1606
+ readonly input: TInput;
1607
+ readonly output: TOutput;
1608
+ }
1609
+
1610
+ // #__NO_SIDE_EFFECTS__
1611
+ export const procedure = <
1612
+ TNsid extends syntax.Nsid,
1613
+ TParams extends ObjectSchema | null,
1614
+ TInput extends XRPCBodyParam,
1615
+ TOutput extends XRPCBodyParam,
1616
+ >(
1617
+ nsid: TNsid,
1618
+ options: {
1619
+ params: TParams;
1620
+ input: TInput;
1621
+ output: TOutput;
1622
+ },
1623
+ ): XRPCProcedureMetadata<TParams, TInput, TOutput, TNsid> => {
1624
+ // `schema` can be a getter, and we'd have to resolve that getter.
1625
+
1626
+ return {
1627
+ kind: 'metadata',
1628
+ type: 'xrpc_procedure',
1629
+ nsid: nsid,
1630
+ params: options.params,
1631
+ get input() {
1632
+ let val = options.input;
1633
+
1634
+ switch (val?.type) {
1635
+ case 'lex': {
1636
+ val = {
1637
+ type: 'lex',
1638
+ schema: val.schema,
1639
+ } as TInput;
1640
+ break;
1641
+ }
1642
+ }
1643
+
1644
+ return lazyProperty(this, 'input', val);
1645
+ },
1646
+ get output() {
1647
+ let val = options.output;
1648
+
1649
+ switch (val?.type) {
1650
+ case 'lex': {
1651
+ val = {
1652
+ type: 'lex',
1653
+ schema: val.schema,
1654
+ } as TOutput;
1655
+ break;
1656
+ }
1657
+ }
1658
+
1659
+ return lazyProperty(this, 'output', val);
1660
+ },
1661
+ };
1662
+ };
1663
+
1664
+ // #region XRPC query metadata
1665
+
1666
+ export interface XRPCQueryMetadata<
1667
+ TParams extends ObjectSchema | null,
1668
+ TOutput extends XRPCBodyParam,
1669
+ TNsid extends syntax.Nsid,
1670
+ > extends BaseMetadata {
1671
+ readonly type: 'xrpc_query';
1672
+ readonly nsid: TNsid;
1673
+ readonly params: TParams;
1674
+ readonly output: TOutput;
1675
+ }
1676
+
1677
+ // #__NO_SIDE_EFFECTS__
1678
+ export const query = <
1679
+ TNsid extends syntax.Nsid,
1680
+ TParams extends ObjectSchema | null,
1681
+ TOutput extends XRPCBodyParam,
1682
+ >(
1683
+ nsid: TNsid,
1684
+ options: {
1685
+ params: TParams;
1686
+ output: TOutput;
1687
+ },
1688
+ ): XRPCQueryMetadata<TParams, TOutput, TNsid> => {
1689
+ // `schema` can be a getter, and we'd have to resolve that getter.
1690
+
1691
+ return {
1692
+ kind: 'metadata',
1693
+ type: 'xrpc_query',
1694
+ nsid: nsid,
1695
+ params: options.params,
1696
+ get output() {
1697
+ let val = options.output;
1698
+
1699
+ switch (val?.type) {
1700
+ case 'lex': {
1701
+ val = {
1702
+ type: 'lex',
1703
+ schema: val.schema,
1704
+ } as TOutput;
1705
+ }
1706
+ }
1707
+
1708
+ return lazyProperty(this, 'output', val);
1709
+ },
1710
+ };
1711
+ };
1712
+
1713
+ // #region XRPC subscription metadata
1714
+
1715
+ export interface XRPCSubscriptionMetadata<
1716
+ TParams extends ObjectSchema | null,
1717
+ TMessage extends ObjectSchema<any> | VariantSchema<any, any> | null,
1718
+ TNsid extends syntax.Nsid,
1719
+ > extends BaseMetadata {
1720
+ readonly type: 'xrpc_subscription';
1721
+ readonly nsid: TNsid;
1722
+ readonly params: TParams;
1723
+ readonly message: TMessage;
1724
+ }
1725
+
1726
+ // #__NO_SIDE_EFFECTS__
1727
+ export const subscription = <
1728
+ TNsid extends syntax.Nsid,
1729
+ TParams extends ObjectSchema | null,
1730
+ TMessage extends ObjectSchema<any> | VariantSchema<any, any> | null,
1731
+ >(
1732
+ nsid: TNsid,
1733
+ options: {
1734
+ params: TParams;
1735
+ readonly message: TMessage;
1736
+ },
1737
+ ): XRPCSubscriptionMetadata<TParams, TMessage, TNsid> => {
1738
+ // `message` can be a getter, and we'd have to resolve that getter.
1739
+
1740
+ return {
1741
+ kind: 'metadata',
1742
+ type: 'xrpc_subscription',
1743
+ nsid: nsid,
1744
+ params: options.params,
1745
+ get message() {
1746
+ return lazyProperty(this, 'message', options.message);
1747
+ },
1748
+ };
1749
+ };