@atcute/lexicons 1.1.1 → 1.2.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.
@@ -1,3 +1,4 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
1
2
  import * as syntax from '../syntax/index.js';
2
3
  import * as interfaces from '../interfaces/index.js';
3
4
  import type { $type } from '../types/brand.js';
@@ -22,51 +23,45 @@ type StringFormatMap = {
22
23
  export type StringFormat = keyof StringFormatMap;
23
24
  type Literal = string | number | boolean;
24
25
  type Key = string | number;
26
+ type IssueFormatter = () => string;
25
27
  export type IssueLeaf = {
26
28
  ok: false;
29
+ msg: IssueFormatter;
30
+ } & ({
27
31
  code: 'missing_value';
28
32
  } | {
29
- ok: false;
30
33
  code: 'invalid_literal';
31
34
  expected: readonly Literal[];
32
35
  } | {
33
- ok: false;
34
36
  code: 'invalid_type';
35
37
  expected: InputType;
36
38
  } | {
37
- ok: false;
38
39
  code: 'invalid_variant';
39
40
  expected: string[];
40
41
  } | {
41
- ok: false;
42
42
  code: 'invalid_integer_range';
43
43
  min: number;
44
44
  max: number;
45
45
  } | {
46
- ok: false;
47
46
  code: 'invalid_string_format';
48
47
  expected: StringFormat;
49
48
  } | {
50
- ok: false;
51
49
  code: 'invalid_string_graphemes';
52
50
  minGraphemes: number;
53
51
  maxGraphemes: number;
54
52
  } | {
55
- ok: false;
56
53
  code: 'invalid_string_length';
57
54
  minLength: number;
58
55
  maxLength: number;
59
56
  } | {
60
- ok: false;
61
57
  code: 'invalid_array_length';
62
58
  minLength: number;
63
59
  maxLength: number;
64
60
  } | {
65
- ok: false;
66
61
  code: 'invalid_bytes_size';
67
62
  minSize: number;
68
63
  maxSize: number;
69
- };
64
+ });
70
65
  export type IssueTree = IssueLeaf | {
71
66
  ok: false;
72
67
  code: 'prepend';
@@ -134,6 +129,7 @@ export type Err = {
134
129
  throw(): never;
135
130
  };
136
131
  export type ValidationResult<T> = Ok<T> | Err;
132
+ export declare const ok: <T>(value: T) => Ok<T>;
137
133
  declare const kType: unique symbol;
138
134
  type kType = typeof kType;
139
135
  declare const kObjectType: unique symbol;
@@ -142,10 +138,12 @@ export declare const FLAG_EMPTY = 0;
142
138
  export declare const FLAG_ABORT_EARLY: number;
143
139
  type MatcherResult = undefined | Ok<unknown> | IssueTree;
144
140
  type Matcher = (input: unknown, flags: number) => MatcherResult;
141
+ type LexStandardSchema<T extends BaseSchema> = StandardSchemaV1.Props<InferInput<T>, InferOutput<T>>;
145
142
  export interface BaseSchema<TInput = unknown, TOutput = TInput> {
146
143
  readonly kind: 'schema';
147
144
  readonly type: string;
148
145
  readonly '~run': Matcher;
146
+ readonly '~standard': LexStandardSchema<this>;
149
147
  readonly [kType]?: {
150
148
  in: TInput;
151
149
  out: TOutput;
@@ -11,13 +11,17 @@ const joinIssues = (left, right) => {
11
11
  const prependPath = (key, tree) => {
12
12
  return { ok: false, code: 'prepend', key, tree };
13
13
  };
14
+ // #__NO_SIDE_EFFECTS__
15
+ export const ok = (value) => {
16
+ return { ok: true, value };
17
+ };
14
18
  // None set
15
19
  export const FLAG_EMPTY = 0;
16
20
  // Don't continue validation if an error is encountered
17
21
  export const FLAG_ABORT_EARLY = 1 << 0;
18
22
  // #region Schema runner
19
23
  const cloneIssueWithPath = (issue, path) => {
20
- const { ok: _ok, ...clone } = issue;
24
+ const { ok: _ok, msg: _fmt, ...clone } = issue;
21
25
  return { ...clone, path };
22
26
  };
23
27
  const collectIssues = (tree, path = [], issues = []) => {
@@ -112,65 +116,7 @@ const formatIssueTree = (tree) => {
112
116
  }
113
117
  break;
114
118
  }
115
- let message;
116
- switch (tree.code) {
117
- case 'missing_value': {
118
- message = `missing value`;
119
- break;
120
- }
121
- case 'invalid_literal': {
122
- message = `expected ${separatedList(tree.expected.map(formatLiteral), 'or')}`;
123
- break;
124
- }
125
- case 'invalid_type': {
126
- message = `expected ${tree.expected}`;
127
- break;
128
- }
129
- case 'invalid_variant': {
130
- message = `expected ${separatedList(tree.expected, 'or')}`;
131
- break;
132
- }
133
- case 'invalid_integer_range': {
134
- const min = tree.min;
135
- const max = tree.max;
136
- message = `expected an integer `;
137
- if (min > 0) {
138
- if (max === min) {
139
- message += `of exactly ${min}`;
140
- }
141
- else if (max !== Infinity) {
142
- message += `between ${min} and ${max}`;
143
- }
144
- else {
145
- message += `of at least ${min}`;
146
- }
147
- }
148
- else {
149
- message += `of at most ${max}`;
150
- }
151
- break;
152
- }
153
- case 'invalid_string_format': {
154
- message = `expected a ${tree.expected} formatted string`;
155
- break;
156
- }
157
- case 'invalid_string_graphemes': {
158
- message = formatRangeMessage('a string', 'grapheme', tree.minGraphemes, tree.maxGraphemes);
159
- break;
160
- }
161
- case 'invalid_string_length': {
162
- message = formatRangeMessage('a string', 'character', tree.minLength, tree.maxLength);
163
- break;
164
- }
165
- case 'invalid_array_length': {
166
- message = formatRangeMessage('an array', 'item', tree.minLength, tree.maxLength);
167
- break;
168
- }
169
- case 'invalid_bytes_size': {
170
- message = formatRangeMessage('a byte array', 'byte', tree.minSize, tree.maxSize);
171
- break;
172
- }
173
- }
119
+ const message = tree.msg();
174
120
  let msg = `${tree.code} at ${path || '.'} (${message})`;
175
121
  if (count > 0) {
176
122
  msg += ` (+${count} other issue(s))`;
@@ -216,7 +162,7 @@ export const is = (schema, input) => {
216
162
  export const safeParse = (schema, input) => {
217
163
  const r = schema['~run'](input, FLAG_EMPTY);
218
164
  if (r === undefined) {
219
- return { ok: true, value: input };
165
+ return ok(input);
220
166
  }
221
167
  if (r.ok) {
222
168
  return r;
@@ -233,6 +179,43 @@ export const parse = (schema, input) => {
233
179
  }
234
180
  throw new ValidationError(r);
235
181
  };
182
+ // #region Standard Schema support
183
+ const collectStandardIssues = (tree, path = [], issues = []) => {
184
+ for (;;) {
185
+ switch (tree.code) {
186
+ case 'join': {
187
+ collectStandardIssues(tree.left, path.slice(), issues);
188
+ tree = tree.right;
189
+ continue;
190
+ }
191
+ case 'prepend': {
192
+ path.push(tree.key);
193
+ tree = tree.tree;
194
+ continue;
195
+ }
196
+ default: {
197
+ issues.push({ message: tree.msg(), path: path.length > 0 ? path : undefined });
198
+ return issues;
199
+ }
200
+ }
201
+ }
202
+ };
203
+ const toStandardSchema = (schema) => {
204
+ return {
205
+ version: 1,
206
+ vendor: '@atcute/lexicons',
207
+ validate(value) {
208
+ const r = schema['~run'](value, FLAG_EMPTY);
209
+ if (r === undefined) {
210
+ return { value: value };
211
+ }
212
+ if (r.ok) {
213
+ return { value: r.value };
214
+ }
215
+ return { issues: collectStandardIssues(r) };
216
+ },
217
+ };
218
+ };
236
219
  // #__NO_SIDE_EFFECTS__
237
220
  export const constrain = (base, constraints) => {
238
221
  const len = constraints.length;
@@ -283,6 +266,9 @@ export const literal = (value) => {
283
266
  ok: false,
284
267
  code: 'invalid_literal',
285
268
  expected: [value],
269
+ msg() {
270
+ return `expected ${formatLiteral(value)}`;
271
+ },
286
272
  };
287
273
  return {
288
274
  kind: 'schema',
@@ -294,6 +280,9 @@ export const literal = (value) => {
294
280
  }
295
281
  return undefined;
296
282
  },
283
+ get '~standard'() {
284
+ return lazyProperty(this, '~standard', toStandardSchema(this));
285
+ },
297
286
  };
298
287
  };
299
288
  // #__NO_SIDE_EFFECTS__
@@ -302,6 +291,9 @@ export const literalEnum = (values) => {
302
291
  ok: false,
303
292
  code: 'invalid_literal',
304
293
  expected: values,
294
+ msg() {
295
+ return `expected ${separatedList(values.map(formatLiteral), 'or')}`;
296
+ },
305
297
  };
306
298
  return {
307
299
  kind: 'schema',
@@ -313,12 +305,18 @@ export const literalEnum = (values) => {
313
305
  }
314
306
  return undefined;
315
307
  },
308
+ get '~standard'() {
309
+ return lazyProperty(this, '~standard', toStandardSchema(this));
310
+ },
316
311
  };
317
312
  };
318
313
  const ISSUE_TYPE_BOOLEAN = {
319
314
  ok: false,
320
315
  code: 'invalid_type',
321
316
  expected: 'boolean',
317
+ msg() {
318
+ return `expected boolean`;
319
+ },
322
320
  };
323
321
  const BOOLEAN_SCHEMA = {
324
322
  kind: 'schema',
@@ -329,6 +327,9 @@ const BOOLEAN_SCHEMA = {
329
327
  }
330
328
  return undefined;
331
329
  },
330
+ get '~standard'() {
331
+ return lazyProperty(this, '~standard', toStandardSchema(this));
332
+ },
332
333
  };
333
334
  // #__NO_SIDE_EFFECTS__
334
335
  export const boolean = () => {
@@ -338,6 +339,9 @@ const ISSUE_TYPE_INTEGER = {
338
339
  ok: false,
339
340
  code: 'invalid_type',
340
341
  expected: 'integer',
342
+ msg() {
343
+ return `expected integer`;
344
+ },
341
345
  };
342
346
  const INTEGER_SCHEMA = {
343
347
  kind: 'schema',
@@ -351,6 +355,9 @@ const INTEGER_SCHEMA = {
351
355
  }
352
356
  return undefined;
353
357
  },
358
+ get '~standard'() {
359
+ return lazyProperty(this, '~standard', toStandardSchema(this));
360
+ },
354
361
  };
355
362
  // #__NO_SIDE_EFFECTS__
356
363
  export const integer = () => {
@@ -363,6 +370,24 @@ export const integerRange = (min, max = Infinity) => {
363
370
  code: 'invalid_integer_range',
364
371
  min: min,
365
372
  max: max,
373
+ msg() {
374
+ let message = `expected an integer `;
375
+ if (min > 0) {
376
+ if (max === min) {
377
+ message += `of exactly ${min}`;
378
+ }
379
+ else if (max !== Infinity) {
380
+ message += `between ${min} and ${max}`;
381
+ }
382
+ else {
383
+ message += `of at least ${min}`;
384
+ }
385
+ }
386
+ else {
387
+ message += `of at most ${max}`;
388
+ }
389
+ return message;
390
+ },
366
391
  };
367
392
  return {
368
393
  kind: 'constraint',
@@ -384,6 +409,9 @@ const ISSUE_TYPE_STRING = {
384
409
  ok: false,
385
410
  code: 'invalid_type',
386
411
  expected: 'string',
412
+ msg() {
413
+ return `expected string`;
414
+ },
387
415
  };
388
416
  const STRING_SINGLETON = {
389
417
  kind: 'schema',
@@ -395,6 +423,9 @@ const STRING_SINGLETON = {
395
423
  }
396
424
  return undefined;
397
425
  },
426
+ get '~standard'() {
427
+ return lazyProperty(this, '~standard', toStandardSchema(this));
428
+ },
398
429
  };
399
430
  // #__NO_SIDE_EFFECTS__
400
431
  export const string = () => {
@@ -406,6 +437,9 @@ const _formattedString = (format, validate) => {
406
437
  ok: false,
407
438
  code: 'invalid_string_format',
408
439
  expected: format,
440
+ msg() {
441
+ return `expected a ${format} formatted string`;
442
+ },
409
443
  };
410
444
  const schema = {
411
445
  kind: 'schema',
@@ -420,6 +454,9 @@ const _formattedString = (format, validate) => {
420
454
  }
421
455
  return undefined;
422
456
  },
457
+ get '~standard'() {
458
+ return lazyProperty(this, '~standard', toStandardSchema(this));
459
+ },
423
460
  };
424
461
  return () => schema;
425
462
  };
@@ -442,6 +479,9 @@ export const stringLength = (minLength, maxLength = Infinity) => {
442
479
  code: 'invalid_string_length',
443
480
  minLength: minLength,
444
481
  maxLength: maxLength,
482
+ msg() {
483
+ return formatRangeMessage('a string', 'character', minLength, maxLength);
484
+ },
445
485
  };
446
486
  return {
447
487
  kind: 'constraint',
@@ -479,6 +519,9 @@ export const stringGraphemes = (minGraphemes, maxGraphemes = Infinity) => {
479
519
  code: 'invalid_string_graphemes',
480
520
  minGraphemes: minGraphemes,
481
521
  maxGraphemes: maxGraphemes,
522
+ msg() {
523
+ return formatRangeMessage('a string', 'grapheme', minGraphemes, maxGraphemes);
524
+ },
482
525
  };
483
526
  return {
484
527
  kind: 'constraint',
@@ -513,6 +556,9 @@ const ISSUE_EXPECTED_BLOB = {
513
556
  ok: false,
514
557
  code: 'invalid_type',
515
558
  expected: 'blob',
559
+ msg() {
560
+ return `expected blob`;
561
+ },
516
562
  };
517
563
  const BLOB_SCHEMA = {
518
564
  kind: 'schema',
@@ -531,10 +577,13 @@ const BLOB_SCHEMA = {
531
577
  ref: { $link: input.cid },
532
578
  size: -1,
533
579
  };
534
- return { ok: true, value: blob };
580
+ return ok(blob);
535
581
  }
536
582
  return ISSUE_EXPECTED_BLOB;
537
583
  },
584
+ get '~standard'() {
585
+ return lazyProperty(this, '~standard', toStandardSchema(this));
586
+ },
538
587
  };
539
588
  // #__NO_SIDE_EFFECTS__
540
589
  export const blob = () => {
@@ -544,6 +593,9 @@ const ISSUE_EXPECTED_BYTES = {
544
593
  ok: false,
545
594
  code: 'invalid_type',
546
595
  expected: 'bytes',
596
+ msg() {
597
+ return `expected bytes`;
598
+ },
547
599
  };
548
600
  const BYTES_SCHEMA = {
549
601
  kind: 'schema',
@@ -554,6 +606,9 @@ const BYTES_SCHEMA = {
554
606
  }
555
607
  return undefined;
556
608
  },
609
+ get '~standard'() {
610
+ return lazyProperty(this, '~standard', toStandardSchema(this));
611
+ },
557
612
  };
558
613
  // #__NO_SIDE_EFFECTS__
559
614
  export const bytes = () => {
@@ -566,6 +621,9 @@ export const bytesSize = (minSize, maxSize = Infinity) => {
566
621
  code: 'invalid_bytes_size',
567
622
  minSize: minSize,
568
623
  maxSize: maxSize,
624
+ msg() {
625
+ return formatRangeMessage('a byte array', 'byte', minSize, maxSize);
626
+ },
569
627
  };
570
628
  return {
571
629
  kind: 'constraint',
@@ -602,6 +660,9 @@ const ISSUE_EXPECTED_CID_LINK = {
602
660
  ok: false,
603
661
  code: 'invalid_type',
604
662
  expected: 'cid-link',
663
+ msg() {
664
+ return `expected cid-link`;
665
+ },
605
666
  };
606
667
  const CID_LINK_SCHEMA = {
607
668
  kind: 'schema',
@@ -612,6 +673,9 @@ const CID_LINK_SCHEMA = {
612
673
  }
613
674
  return undefined;
614
675
  },
676
+ get '~standard'() {
677
+ return lazyProperty(this, '~standard', toStandardSchema(this));
678
+ },
615
679
  };
616
680
  // #__NO_SIDE_EFFECTS__
617
681
  export const cidLink = () => {
@@ -629,6 +693,9 @@ export const nullable = (wrapped) => {
629
693
  }
630
694
  return wrapped['~run'](input, flags);
631
695
  },
696
+ get '~standard'() {
697
+ return lazyProperty(this, '~standard', toStandardSchema(this));
698
+ },
632
699
  };
633
700
  };
634
701
  // #__NO_SIDE_EFFECTS__
@@ -644,10 +711,13 @@ export const optional = (wrapped, defaultValue) => {
644
711
  return undefined;
645
712
  }
646
713
  const value = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
647
- return { ok: true, value };
714
+ return ok(value);
648
715
  }
649
716
  return wrapped['~run'](input, flags);
650
717
  },
718
+ get '~standard'() {
719
+ return lazyProperty(this, '~standard', toStandardSchema(this));
720
+ },
651
721
  };
652
722
  };
653
723
  const isOptionalSchema = (schema) => {
@@ -657,6 +727,9 @@ const ISSUE_TYPE_ARRAY = {
657
727
  ok: false,
658
728
  code: 'invalid_type',
659
729
  expected: 'array',
730
+ msg() {
731
+ return `expected array`;
732
+ },
660
733
  };
661
734
  // #__NO_SIDE_EFFECTS__
662
735
  export const array = (item) => {
@@ -699,12 +772,15 @@ export const array = (item) => {
699
772
  return issues;
700
773
  }
701
774
  if (output !== undefined) {
702
- return { ok: true, value: output };
775
+ return ok(output);
703
776
  }
704
777
  return undefined;
705
778
  };
706
779
  return lazyProperty(this, '~run', matcher);
707
780
  },
781
+ get '~standard'() {
782
+ return lazyProperty(this, '~standard', toStandardSchema(this));
783
+ },
708
784
  };
709
785
  };
710
786
  // #__NO_SIDE_EFFECTS__
@@ -714,6 +790,9 @@ export const arrayLength = (minLength, maxLength = Infinity) => {
714
790
  code: 'invalid_array_length',
715
791
  minLength: minLength,
716
792
  maxLength: maxLength,
793
+ msg() {
794
+ return formatRangeMessage('an array', 'item', minLength, maxLength);
795
+ },
717
796
  };
718
797
  return {
719
798
  kind: 'constraint',
@@ -736,10 +815,16 @@ const ISSUE_TYPE_OBJECT = {
736
815
  ok: false,
737
816
  code: 'invalid_type',
738
817
  expected: 'object',
818
+ msg() {
819
+ return `expected object`;
820
+ },
739
821
  };
740
822
  const ISSUE_MISSING = {
741
823
  ok: false,
742
824
  code: 'missing_value',
825
+ msg() {
826
+ return `missing value`;
827
+ },
743
828
  };
744
829
  const set = (obj, key, value) => {
745
830
  if (key === '__proto__') {
@@ -782,6 +867,7 @@ export const object = (shape) => {
782
867
  const len = shape.length;
783
868
  const generateFastpass = () => {
784
869
  const fields = [
870
+ ['$ok', ok],
785
871
  ['$joinIssues', joinIssues],
786
872
  ['$prependPath', prependPath],
787
873
  ];
@@ -820,7 +906,7 @@ export const object = (shape) => {
820
906
  }
821
907
  doc += `}`;
822
908
  }
823
- doc += `if($iss!==undefined)return $iss;if($out!==undefined)return{ok:true,value:$out};`;
909
+ doc += `if($iss!==undefined)return $iss;if($out!==undefined)return $ok($out);`;
824
910
  const fn = new Function(`[${fields.map(([id]) => id).join(',')}]`, `return function matcher($in,$flags){${doc}}`);
825
911
  return fn(fields.map(([, field]) => field));
826
912
  };
@@ -871,12 +957,15 @@ export const object = (shape) => {
871
957
  return issues;
872
958
  }
873
959
  if (output !== undefined) {
874
- return { ok: true, value: output };
960
+ return ok(output);
875
961
  }
876
962
  return undefined;
877
963
  };
878
964
  return lazyProperty(this, '~run', matcher);
879
965
  },
966
+ get '~standard'() {
967
+ return lazyProperty(this, '~standard', toStandardSchema(this));
968
+ },
880
969
  };
881
970
  };
882
971
  // #__NO_SIDE_EFFECTS__
@@ -901,17 +990,13 @@ export const record = (key, object) => {
901
990
  '~run'(input, flags) {
902
991
  return lazyProperty(this, '~run', validatedObject.value['~run'])(input, flags);
903
992
  },
993
+ get '~standard'() {
994
+ return lazyProperty(this, '~standard', toStandardSchema(this));
995
+ },
904
996
  };
905
997
  };
906
- const ISSUE_VARIANT_MISSING = /*#__PURE__*/ prependPath('$type', {
907
- ok: false,
908
- code: 'missing_value',
909
- });
910
- const ISSUE_VARIANT_TYPE = /*#__PURE__*/ prependPath('$type', {
911
- ok: false,
912
- code: 'invalid_type',
913
- expected: 'string',
914
- });
998
+ const ISSUE_VARIANT_MISSING = /*#__PURE__*/ prependPath('$type', ISSUE_MISSING);
999
+ const ISSUE_VARIANT_TYPE = /*#__PURE__*/ prependPath('$type', ISSUE_TYPE_STRING);
915
1000
  // #__NO_SIDE_EFFECTS__
916
1001
  export const variant = (members, closed = false) => {
917
1002
  return {
@@ -934,6 +1019,9 @@ export const variant = (members, closed = false) => {
934
1019
  ok: false,
935
1020
  code: 'invalid_variant',
936
1021
  expected: Object.keys(map),
1022
+ msg() {
1023
+ return `expected ${separatedList(Object.keys(map), 'or')}`;
1024
+ },
937
1025
  };
938
1026
  const matcher = (input, flags) => {
939
1027
  if (!isObject(input)) {
@@ -957,12 +1045,18 @@ export const variant = (members, closed = false) => {
957
1045
  };
958
1046
  return lazyProperty(this, '~run', matcher);
959
1047
  },
1048
+ get '~standard'() {
1049
+ return lazyProperty(this, '~standard', toStandardSchema(this));
1050
+ },
960
1051
  };
961
1052
  };
962
1053
  const ISSUE_TYPE_UNKNOWN = {
963
1054
  ok: false,
964
1055
  code: 'invalid_type',
965
1056
  expected: 'unknown',
1057
+ msg() {
1058
+ return `expected unknown`;
1059
+ },
966
1060
  };
967
1061
  const UNKNOWN_SCHEMA = {
968
1062
  kind: 'schema',
@@ -973,6 +1067,9 @@ const UNKNOWN_SCHEMA = {
973
1067
  }
974
1068
  return undefined;
975
1069
  },
1070
+ get '~standard'() {
1071
+ return lazyProperty(this, '~standard', toStandardSchema(this));
1072
+ },
976
1073
  };
977
1074
  // #__NO_SIDE_EFFECTS__
978
1075
  export const unknown = () => {