@atcute/lexicons 1.1.0 → 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,44 @@ 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
+ };
219
+ // #__NO_SIDE_EFFECTS__
236
220
  export const constrain = (base, constraints) => {
237
221
  const len = constraints.length;
238
222
  return {
@@ -282,6 +266,9 @@ export const literal = (value) => {
282
266
  ok: false,
283
267
  code: 'invalid_literal',
284
268
  expected: [value],
269
+ msg() {
270
+ return `expected ${formatLiteral(value)}`;
271
+ },
285
272
  };
286
273
  return {
287
274
  kind: 'schema',
@@ -293,6 +280,9 @@ export const literal = (value) => {
293
280
  }
294
281
  return undefined;
295
282
  },
283
+ get '~standard'() {
284
+ return lazyProperty(this, '~standard', toStandardSchema(this));
285
+ },
296
286
  };
297
287
  };
298
288
  // #__NO_SIDE_EFFECTS__
@@ -301,6 +291,9 @@ export const literalEnum = (values) => {
301
291
  ok: false,
302
292
  code: 'invalid_literal',
303
293
  expected: values,
294
+ msg() {
295
+ return `expected ${separatedList(values.map(formatLiteral), 'or')}`;
296
+ },
304
297
  };
305
298
  return {
306
299
  kind: 'schema',
@@ -312,12 +305,18 @@ export const literalEnum = (values) => {
312
305
  }
313
306
  return undefined;
314
307
  },
308
+ get '~standard'() {
309
+ return lazyProperty(this, '~standard', toStandardSchema(this));
310
+ },
315
311
  };
316
312
  };
317
313
  const ISSUE_TYPE_BOOLEAN = {
318
314
  ok: false,
319
315
  code: 'invalid_type',
320
316
  expected: 'boolean',
317
+ msg() {
318
+ return `expected boolean`;
319
+ },
321
320
  };
322
321
  const BOOLEAN_SCHEMA = {
323
322
  kind: 'schema',
@@ -328,6 +327,9 @@ const BOOLEAN_SCHEMA = {
328
327
  }
329
328
  return undefined;
330
329
  },
330
+ get '~standard'() {
331
+ return lazyProperty(this, '~standard', toStandardSchema(this));
332
+ },
331
333
  };
332
334
  // #__NO_SIDE_EFFECTS__
333
335
  export const boolean = () => {
@@ -337,6 +339,9 @@ const ISSUE_TYPE_INTEGER = {
337
339
  ok: false,
338
340
  code: 'invalid_type',
339
341
  expected: 'integer',
342
+ msg() {
343
+ return `expected integer`;
344
+ },
340
345
  };
341
346
  const INTEGER_SCHEMA = {
342
347
  kind: 'schema',
@@ -350,6 +355,9 @@ const INTEGER_SCHEMA = {
350
355
  }
351
356
  return undefined;
352
357
  },
358
+ get '~standard'() {
359
+ return lazyProperty(this, '~standard', toStandardSchema(this));
360
+ },
353
361
  };
354
362
  // #__NO_SIDE_EFFECTS__
355
363
  export const integer = () => {
@@ -362,6 +370,24 @@ export const integerRange = (min, max = Infinity) => {
362
370
  code: 'invalid_integer_range',
363
371
  min: min,
364
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
+ },
365
391
  };
366
392
  return {
367
393
  kind: 'constraint',
@@ -383,6 +409,9 @@ const ISSUE_TYPE_STRING = {
383
409
  ok: false,
384
410
  code: 'invalid_type',
385
411
  expected: 'string',
412
+ msg() {
413
+ return `expected string`;
414
+ },
386
415
  };
387
416
  const STRING_SINGLETON = {
388
417
  kind: 'schema',
@@ -394,6 +423,9 @@ const STRING_SINGLETON = {
394
423
  }
395
424
  return undefined;
396
425
  },
426
+ get '~standard'() {
427
+ return lazyProperty(this, '~standard', toStandardSchema(this));
428
+ },
397
429
  };
398
430
  // #__NO_SIDE_EFFECTS__
399
431
  export const string = () => {
@@ -405,6 +437,9 @@ const _formattedString = (format, validate) => {
405
437
  ok: false,
406
438
  code: 'invalid_string_format',
407
439
  expected: format,
440
+ msg() {
441
+ return `expected a ${format} formatted string`;
442
+ },
408
443
  };
409
444
  const schema = {
410
445
  kind: 'schema',
@@ -419,6 +454,9 @@ const _formattedString = (format, validate) => {
419
454
  }
420
455
  return undefined;
421
456
  },
457
+ get '~standard'() {
458
+ return lazyProperty(this, '~standard', toStandardSchema(this));
459
+ },
422
460
  };
423
461
  return () => schema;
424
462
  };
@@ -441,6 +479,9 @@ export const stringLength = (minLength, maxLength = Infinity) => {
441
479
  code: 'invalid_string_length',
442
480
  minLength: minLength,
443
481
  maxLength: maxLength,
482
+ msg() {
483
+ return formatRangeMessage('a string', 'character', minLength, maxLength);
484
+ },
444
485
  };
445
486
  return {
446
487
  kind: 'constraint',
@@ -471,12 +512,16 @@ export const stringLength = (minLength, maxLength = Infinity) => {
471
512
  },
472
513
  };
473
514
  };
515
+ // #__NO_SIDE_EFFECTS__
474
516
  export const stringGraphemes = (minGraphemes, maxGraphemes = Infinity) => {
475
517
  const issue = {
476
518
  ok: false,
477
519
  code: 'invalid_string_graphemes',
478
520
  minGraphemes: minGraphemes,
479
521
  maxGraphemes: maxGraphemes,
522
+ msg() {
523
+ return formatRangeMessage('a string', 'grapheme', minGraphemes, maxGraphemes);
524
+ },
480
525
  };
481
526
  return {
482
527
  kind: 'constraint',
@@ -511,6 +556,9 @@ const ISSUE_EXPECTED_BLOB = {
511
556
  ok: false,
512
557
  code: 'invalid_type',
513
558
  expected: 'blob',
559
+ msg() {
560
+ return `expected blob`;
561
+ },
514
562
  };
515
563
  const BLOB_SCHEMA = {
516
564
  kind: 'schema',
@@ -529,10 +577,13 @@ const BLOB_SCHEMA = {
529
577
  ref: { $link: input.cid },
530
578
  size: -1,
531
579
  };
532
- return { ok: true, value: blob };
580
+ return ok(blob);
533
581
  }
534
582
  return ISSUE_EXPECTED_BLOB;
535
583
  },
584
+ get '~standard'() {
585
+ return lazyProperty(this, '~standard', toStandardSchema(this));
586
+ },
536
587
  };
537
588
  // #__NO_SIDE_EFFECTS__
538
589
  export const blob = () => {
@@ -542,6 +593,9 @@ const ISSUE_EXPECTED_BYTES = {
542
593
  ok: false,
543
594
  code: 'invalid_type',
544
595
  expected: 'bytes',
596
+ msg() {
597
+ return `expected bytes`;
598
+ },
545
599
  };
546
600
  const BYTES_SCHEMA = {
547
601
  kind: 'schema',
@@ -552,6 +606,9 @@ const BYTES_SCHEMA = {
552
606
  }
553
607
  return undefined;
554
608
  },
609
+ get '~standard'() {
610
+ return lazyProperty(this, '~standard', toStandardSchema(this));
611
+ },
555
612
  };
556
613
  // #__NO_SIDE_EFFECTS__
557
614
  export const bytes = () => {
@@ -564,6 +621,9 @@ export const bytesSize = (minSize, maxSize = Infinity) => {
564
621
  code: 'invalid_bytes_size',
565
622
  minSize: minSize,
566
623
  maxSize: maxSize,
624
+ msg() {
625
+ return formatRangeMessage('a byte array', 'byte', minSize, maxSize);
626
+ },
567
627
  };
568
628
  return {
569
629
  kind: 'constraint',
@@ -600,6 +660,9 @@ const ISSUE_EXPECTED_CID_LINK = {
600
660
  ok: false,
601
661
  code: 'invalid_type',
602
662
  expected: 'cid-link',
663
+ msg() {
664
+ return `expected cid-link`;
665
+ },
603
666
  };
604
667
  const CID_LINK_SCHEMA = {
605
668
  kind: 'schema',
@@ -610,6 +673,9 @@ const CID_LINK_SCHEMA = {
610
673
  }
611
674
  return undefined;
612
675
  },
676
+ get '~standard'() {
677
+ return lazyProperty(this, '~standard', toStandardSchema(this));
678
+ },
613
679
  };
614
680
  // #__NO_SIDE_EFFECTS__
615
681
  export const cidLink = () => {
@@ -627,6 +693,9 @@ export const nullable = (wrapped) => {
627
693
  }
628
694
  return wrapped['~run'](input, flags);
629
695
  },
696
+ get '~standard'() {
697
+ return lazyProperty(this, '~standard', toStandardSchema(this));
698
+ },
630
699
  };
631
700
  };
632
701
  // #__NO_SIDE_EFFECTS__
@@ -642,10 +711,13 @@ export const optional = (wrapped, defaultValue) => {
642
711
  return undefined;
643
712
  }
644
713
  const value = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
645
- return { ok: true, value };
714
+ return ok(value);
646
715
  }
647
716
  return wrapped['~run'](input, flags);
648
717
  },
718
+ get '~standard'() {
719
+ return lazyProperty(this, '~standard', toStandardSchema(this));
720
+ },
649
721
  };
650
722
  };
651
723
  const isOptionalSchema = (schema) => {
@@ -655,6 +727,9 @@ const ISSUE_TYPE_ARRAY = {
655
727
  ok: false,
656
728
  code: 'invalid_type',
657
729
  expected: 'array',
730
+ msg() {
731
+ return `expected array`;
732
+ },
658
733
  };
659
734
  // #__NO_SIDE_EFFECTS__
660
735
  export const array = (item) => {
@@ -697,12 +772,15 @@ export const array = (item) => {
697
772
  return issues;
698
773
  }
699
774
  if (output !== undefined) {
700
- return { ok: true, value: output };
775
+ return ok(output);
701
776
  }
702
777
  return undefined;
703
778
  };
704
779
  return lazyProperty(this, '~run', matcher);
705
780
  },
781
+ get '~standard'() {
782
+ return lazyProperty(this, '~standard', toStandardSchema(this));
783
+ },
706
784
  };
707
785
  };
708
786
  // #__NO_SIDE_EFFECTS__
@@ -712,6 +790,9 @@ export const arrayLength = (minLength, maxLength = Infinity) => {
712
790
  code: 'invalid_array_length',
713
791
  minLength: minLength,
714
792
  maxLength: maxLength,
793
+ msg() {
794
+ return formatRangeMessage('an array', 'item', minLength, maxLength);
795
+ },
715
796
  };
716
797
  return {
717
798
  kind: 'constraint',
@@ -734,10 +815,16 @@ const ISSUE_TYPE_OBJECT = {
734
815
  ok: false,
735
816
  code: 'invalid_type',
736
817
  expected: 'object',
818
+ msg() {
819
+ return `expected object`;
820
+ },
737
821
  };
738
822
  const ISSUE_MISSING = {
739
823
  ok: false,
740
824
  code: 'missing_value',
825
+ msg() {
826
+ return `missing value`;
827
+ },
741
828
  };
742
829
  const set = (obj, key, value) => {
743
830
  if (key === '__proto__') {
@@ -780,6 +867,7 @@ export const object = (shape) => {
780
867
  const len = shape.length;
781
868
  const generateFastpass = () => {
782
869
  const fields = [
870
+ ['$ok', ok],
783
871
  ['$joinIssues', joinIssues],
784
872
  ['$prependPath', prependPath],
785
873
  ];
@@ -818,7 +906,7 @@ export const object = (shape) => {
818
906
  }
819
907
  doc += `}`;
820
908
  }
821
- 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);`;
822
910
  const fn = new Function(`[${fields.map(([id]) => id).join(',')}]`, `return function matcher($in,$flags){${doc}}`);
823
911
  return fn(fields.map(([, field]) => field));
824
912
  };
@@ -869,12 +957,15 @@ export const object = (shape) => {
869
957
  return issues;
870
958
  }
871
959
  if (output !== undefined) {
872
- return { ok: true, value: output };
960
+ return ok(output);
873
961
  }
874
962
  return undefined;
875
963
  };
876
964
  return lazyProperty(this, '~run', matcher);
877
965
  },
966
+ get '~standard'() {
967
+ return lazyProperty(this, '~standard', toStandardSchema(this));
968
+ },
878
969
  };
879
970
  };
880
971
  // #__NO_SIDE_EFFECTS__
@@ -899,17 +990,13 @@ export const record = (key, object) => {
899
990
  '~run'(input, flags) {
900
991
  return lazyProperty(this, '~run', validatedObject.value['~run'])(input, flags);
901
992
  },
993
+ get '~standard'() {
994
+ return lazyProperty(this, '~standard', toStandardSchema(this));
995
+ },
902
996
  };
903
997
  };
904
- const ISSUE_VARIANT_MISSING = /*#__PURE__*/ prependPath('$type', {
905
- ok: false,
906
- code: 'missing_value',
907
- });
908
- const ISSUE_VARIANT_TYPE = /*#__PURE__*/ prependPath('$type', {
909
- ok: false,
910
- code: 'invalid_type',
911
- expected: 'string',
912
- });
998
+ const ISSUE_VARIANT_MISSING = /*#__PURE__*/ prependPath('$type', ISSUE_MISSING);
999
+ const ISSUE_VARIANT_TYPE = /*#__PURE__*/ prependPath('$type', ISSUE_TYPE_STRING);
913
1000
  // #__NO_SIDE_EFFECTS__
914
1001
  export const variant = (members, closed = false) => {
915
1002
  return {
@@ -932,6 +1019,9 @@ export const variant = (members, closed = false) => {
932
1019
  ok: false,
933
1020
  code: 'invalid_variant',
934
1021
  expected: Object.keys(map),
1022
+ msg() {
1023
+ return `expected ${separatedList(Object.keys(map), 'or')}`;
1024
+ },
935
1025
  };
936
1026
  const matcher = (input, flags) => {
937
1027
  if (!isObject(input)) {
@@ -955,12 +1045,18 @@ export const variant = (members, closed = false) => {
955
1045
  };
956
1046
  return lazyProperty(this, '~run', matcher);
957
1047
  },
1048
+ get '~standard'() {
1049
+ return lazyProperty(this, '~standard', toStandardSchema(this));
1050
+ },
958
1051
  };
959
1052
  };
960
1053
  const ISSUE_TYPE_UNKNOWN = {
961
1054
  ok: false,
962
1055
  code: 'invalid_type',
963
1056
  expected: 'unknown',
1057
+ msg() {
1058
+ return `expected unknown`;
1059
+ },
964
1060
  };
965
1061
  const UNKNOWN_SCHEMA = {
966
1062
  kind: 'schema',
@@ -971,6 +1067,9 @@ const UNKNOWN_SCHEMA = {
971
1067
  }
972
1068
  return undefined;
973
1069
  },
1070
+ get '~standard'() {
1071
+ return lazyProperty(this, '~standard', toStandardSchema(this));
1072
+ },
974
1073
  };
975
1074
  // #__NO_SIDE_EFFECTS__
976
1075
  export const unknown = () => {