@atproto/lex-builder 0.0.4 → 0.0.5

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.
@@ -132,17 +132,18 @@ export class LexDefBuilder {
132
132
 
133
133
  private async addPermissionSet(hash: string, def: LexiconPermissionSet) {
134
134
  const permission = def.permissions.map((def) => {
135
- const options = stringifyOptionalOptions(def, ['resource', 'type'])
135
+ const options = stringifyOptions(def, undefined, ['resource', 'type'])
136
136
  return this.pure(
137
137
  `l.permission(${JSON.stringify(def.resource)}, ${options})`,
138
138
  )
139
139
  })
140
140
 
141
- const options = stringifyOptionalOptions(def, [
142
- 'type',
143
- 'description',
144
- 'permissions',
145
- ])
141
+ const options = stringifyOptions(def, [
142
+ 'title',
143
+ 'title:lang',
144
+ 'detail',
145
+ 'detail:lang',
146
+ ] satisfies (keyof l.PermissionSetOptions)[])
146
147
 
147
148
  await this.addSchema(hash, def, {
148
149
  schema: this.pure(
@@ -326,11 +327,10 @@ export class LexDefBuilder {
326
327
  // collisions.
327
328
 
328
329
  const itemSchema = await this.compileContainedSchema(def.items)
329
- const options = stringifyOptionalOptions(def, [
330
- 'type',
331
- 'description',
332
- 'items',
333
- ])
330
+ const options = stringifyOptions(def, [
331
+ 'minLength',
332
+ 'maxLength',
333
+ ] satisfies (keyof l.ArraySchemaOptions)[])
334
334
 
335
335
  await this.addSchema(hash, def, {
336
336
  type: `(${await this.compileContainedType(def.items)})[]`,
@@ -425,10 +425,10 @@ export class LexDefBuilder {
425
425
  if (hash === 'main' && validationUtils) {
426
426
  this.addUtils({
427
427
  $assert: markPure(`${ref.varName}.assert.bind(${ref.varName})`),
428
- $check: markPure(`${ref.varName}.check.bind(${ref.varName})`),
429
- $maybe: markPure(`${ref.varName}.maybe.bind(${ref.varName})`),
428
+ $ifMatches: markPure(`${ref.varName}.ifMatches.bind(${ref.varName})`),
429
+ $matches: markPure(`${ref.varName}.matches.bind(${ref.varName})`),
430
430
  $parse: markPure(`${ref.varName}.parse.bind(${ref.varName})`),
431
- $validate: markPure(`${ref.varName}.validate.bind(${ref.varName})`),
431
+ $safeParse: markPure(`${ref.varName}.safeParse.bind(${ref.varName})`),
432
432
  })
433
433
  }
434
434
 
@@ -459,12 +459,7 @@ export class LexDefBuilder {
459
459
  if (!def) return this.pure(`l.params({})`)
460
460
 
461
461
  const properties = await this.compilePropertiesSchemas(def)
462
- const options = stringifyOptionalOptions(def, [
463
- 'type',
464
- 'description',
465
- 'properties',
466
- ])
467
- return this.pure(`l.params({${properties.join(',')}}, ${options})`)
462
+ return this.pure(`l.params({${properties.join(',')}})`)
468
463
  }
469
464
 
470
465
  private async compileErrors(defs?: readonly LexiconError[]) {
@@ -474,29 +469,28 @@ export class LexDefBuilder {
474
469
 
475
470
  private async compileObjectSchema(def: LexiconObject): Promise<string> {
476
471
  const properties = await this.compilePropertiesSchemas(def)
477
- const options = stringifyOptionalOptions(def, [
478
- 'type',
479
- 'description',
480
- 'properties',
481
- ])
482
- return this.pure(`l.object({${properties.join(',')}}, ${options})`)
472
+ return this.pure(`l.object({${properties.join(',')}})`)
483
473
  }
484
474
 
485
475
  private async compilePropertiesSchemas(options: {
486
476
  properties: Record<string, LexiconArray | LexiconArrayItems>
487
477
  required?: readonly string[]
488
- }) {
489
- for (const prop of options.required || []) {
490
- if (!Object.hasOwn(options.properties, prop)) {
491
- throw new Error(`Required property "${prop}" not found in properties`)
478
+ nullable?: readonly string[]
479
+ }): Promise<string[]> {
480
+ for (const opt of ['required', 'nullable'] as const) {
481
+ if (options[opt]) {
482
+ for (const prop of options[opt]) {
483
+ if (!Object.hasOwn(options.properties, prop)) {
484
+ throw new Error(`No schema found for ${opt} property "${prop}"`)
485
+ }
486
+ }
492
487
  }
493
488
  }
494
489
 
495
490
  return Promise.all(
496
- Object.entries(options.properties).map(
497
- this.compilePropertyEntrySchema,
498
- this,
499
- ),
491
+ Object.entries(options.properties).map((entry) => {
492
+ return this.compilePropertyEntrySchema(entry, options)
493
+ }),
500
494
  )
501
495
  }
502
496
 
@@ -512,13 +506,27 @@ export class LexDefBuilder {
512
506
  )
513
507
  }
514
508
 
515
- private async compilePropertyEntrySchema([key, def]: [
516
- string,
517
- LexiconArray | LexiconArrayItems,
518
- ]) {
519
- const name = JSON.stringify(key)
520
- const schema = await this.compileContainedSchema(def)
521
- return `${name}:${schema}`
509
+ private async compilePropertyEntrySchema(
510
+ [key, def]: [string, LexiconArray | LexiconArrayItems],
511
+ options: {
512
+ required?: readonly string[]
513
+ nullable?: readonly string[]
514
+ },
515
+ ) {
516
+ const isNullable = options.nullable?.includes(key)
517
+ const isRequired = options.required?.includes(key)
518
+
519
+ let schema = await this.compileContainedSchema(def)
520
+
521
+ if (isNullable) {
522
+ schema = this.pure(`l.nullable(${schema})`)
523
+ }
524
+
525
+ if (!isRequired) {
526
+ schema = this.pure(`l.optional(${schema})`)
527
+ }
528
+
529
+ return `${JSON.stringify(key)}:${schema}`
522
530
  }
523
531
 
524
532
  private async compilePropertyEntryType(
@@ -603,11 +611,10 @@ export class LexDefBuilder {
603
611
 
604
612
  private async compileArraySchema(def: LexiconArray): Promise<string> {
605
613
  const itemSchema = await this.compileContainedSchema(def.items)
606
- const options = stringifyOptionalOptions(def, [
607
- 'type',
608
- 'description',
609
- 'items',
610
- ])
614
+ const options = stringifyOptions(def, [
615
+ 'minLength',
616
+ 'maxLength',
617
+ ] satisfies (keyof l.ArraySchemaOptions)[])
611
618
  return this.pure(`l.array(${itemSchema}, ${options})`)
612
619
  }
613
620
 
@@ -626,7 +633,9 @@ export class LexDefBuilder {
626
633
  private async compileBooleanSchema(def: LexiconBoolean): Promise<string> {
627
634
  if (hasConst(def)) return this.compileConstSchema(def)
628
635
 
629
- const options = stringifyOptionalOptions(def, ['type', 'description'])
636
+ const options = stringifyOptions(def, [
637
+ 'default',
638
+ ] satisfies (keyof l.BooleanSchemaOptions)[])
630
639
  return this.pure(`l.boolean(${options})`)
631
640
  }
632
641
 
@@ -637,24 +646,23 @@ export class LexDefBuilder {
637
646
 
638
647
  private async compileIntegerSchema(def: LexiconInteger): Promise<string> {
639
648
  if (hasConst(def)) {
640
- const schema = l.integer(def)
641
- assert(
642
- schema.check(def.const),
643
- `Integer const ${def.const} is out of bounds`,
644
- )
649
+ const schema: l.IntegerSchema = l.integer(def)
650
+ schema.assert(def.const)
645
651
  }
646
652
 
647
653
  if (hasEnum(def)) {
648
- const schema = l.integer(def)
649
- for (const val of def.enum) {
650
- assert(schema.check(val), `Integer enum value ${val} is out of bounds`)
651
- }
654
+ const schema: l.IntegerSchema = l.integer(def)
655
+ for (const val of def.enum) schema.assert(val)
652
656
  }
653
657
 
654
658
  if (hasConst(def)) return this.compileConstSchema(def)
655
659
  if (hasEnum(def)) return this.compileEnumSchema(def)
656
660
 
657
- const options = stringifyOptionalOptions(def, ['type', 'description'])
661
+ const options = stringifyOptions(def, [
662
+ 'default',
663
+ 'maximum',
664
+ 'minimum',
665
+ ] satisfies (keyof l.IntegerSchemaOptions)[])
658
666
  return this.pure(`l.integer(${options})`)
659
667
  }
660
668
 
@@ -667,25 +675,24 @@ export class LexDefBuilder {
667
675
 
668
676
  private async compileStringSchema(def: LexiconString): Promise<string> {
669
677
  if (hasConst(def)) {
670
- const schema = l.string(def)
671
- assert(
672
- schema.check(def.const),
673
- `String const "${def.const}" does not match format`,
674
- )
678
+ const schema: l.StringSchema = l.string(def)
679
+ schema.assert(def.const)
675
680
  } else if (hasEnum(def)) {
676
- const schema = l.string(def)
677
- for (const val of def.enum) {
678
- assert(
679
- schema.check(val),
680
- `String enum value "${val}" does not match format`,
681
- )
682
- }
681
+ const schema: l.StringSchema = l.string(def)
682
+ for (const val of def.enum) schema.assert(val)
683
683
  }
684
684
 
685
685
  if (hasConst(def)) return this.compileConstSchema(def)
686
686
  if (hasEnum(def)) return this.compileEnumSchema(def)
687
687
 
688
- const options = stringifyOptionalOptions(def, ['type', 'description'])
688
+ const options = stringifyOptions(def, [
689
+ 'default',
690
+ 'format',
691
+ 'maxGraphemes',
692
+ 'minGraphemes',
693
+ 'maxLength',
694
+ 'minLength',
695
+ ] satisfies (keyof l.StringSchemaOptions)[])
689
696
  return this.pure(`l.string(${options})`)
690
697
  }
691
698
 
@@ -694,20 +701,32 @@ export class LexDefBuilder {
694
701
  if (hasEnum(def)) return this.compileEnumType(def)
695
702
 
696
703
  switch (def.format) {
704
+ case undefined:
705
+ break
697
706
  case 'datetime':
698
- return 'l.Datetime'
707
+ return 'l.DatetimeString'
699
708
  case 'uri':
700
- return 'l.Uri'
709
+ return 'l.UriString'
701
710
  case 'at-uri':
702
- return 'l.AtUri'
711
+ return 'l.AtUriString'
703
712
  case 'did':
704
- return 'l.Did'
713
+ return 'l.DidString'
705
714
  case 'handle':
706
- return 'l.Handle'
715
+ return 'l.HandleString'
707
716
  case 'at-identifier':
708
- return 'l.AtIdentifier'
717
+ return 'l.AtIdentifierString'
709
718
  case 'nsid':
710
- return 'l.Nsid'
719
+ return 'l.NsidString'
720
+ case 'tid':
721
+ return 'l.TidString'
722
+ case 'cid':
723
+ return 'l.CidString'
724
+ case 'language':
725
+ return 'l.LanguageString'
726
+ case 'record-key':
727
+ return 'l.RecordKeyString'
728
+ default:
729
+ throw new Error(`Unknown string format: ${def.format}`)
711
730
  }
712
731
 
713
732
  if (def.knownValues?.length) {
@@ -721,7 +740,10 @@ export class LexDefBuilder {
721
740
  }
722
741
 
723
742
  private async compileBytesSchema(def: LexiconBytes): Promise<string> {
724
- const options = stringifyOptionalOptions(def, ['type', 'description'])
743
+ const options = stringifyOptions(def, [
744
+ 'minLength',
745
+ 'maxLength',
746
+ ] satisfies (keyof l.BytesSchemaOptions)[])
725
747
  return this.pure(`l.bytes(${options})`)
726
748
  }
727
749
 
@@ -730,10 +752,12 @@ export class LexDefBuilder {
730
752
  }
731
753
 
732
754
  private async compileBlobSchema(def: LexiconBlob): Promise<string> {
733
- const opts = this.options.allowLegacyBlobs
734
- ? { ...def, allowLegacy: true }
735
- : def
736
- const options = stringifyOptionalOptions(opts, ['type', 'description'])
755
+ const opts = { ...def, allowLegacy: this.options.allowLegacyBlobs === true }
756
+ const options = stringifyOptions(opts, [
757
+ 'maxSize',
758
+ 'accept',
759
+ 'allowLegacy',
760
+ ] satisfies (keyof l.BlobSchemaOptions)[])
737
761
  return this.pure(`l.blob(${options})`)
738
762
  }
739
763
 
@@ -743,13 +767,12 @@ export class LexDefBuilder {
743
767
  : 'l.BlobRef'
744
768
  }
745
769
 
746
- private async compileCidLinkSchema(def: LexiconCid): Promise<string> {
747
- const options = stringifyOptionalOptions(def, ['type', 'description'])
748
- return this.pure(`l.cidLink(${options})`)
770
+ private async compileCidLinkSchema(_def: LexiconCid): Promise<string> {
771
+ return this.pure(`l.cidLink()`)
749
772
  }
750
773
 
751
774
  private async compileCidLinkType(_def: LexiconCid): Promise<string> {
752
- return 'l.CID'
775
+ return 'l.Cid'
753
776
  }
754
777
 
755
778
  private async compileRefSchema(def: LexiconRef): Promise<string> {
@@ -796,12 +819,15 @@ export class LexDefBuilder {
796
819
 
797
820
  private async compileConstSchema<
798
821
  T extends null | number | string | boolean,
799
- >(def: { const: T; enum?: readonly T[] }): Promise<string> {
822
+ >(def: { const: T; enum?: readonly T[]; default?: T }): Promise<string> {
800
823
  if (hasEnum(def) && !def.enum.includes(def.const)) {
801
824
  return this.pure(`l.never()`)
802
825
  }
803
826
 
804
- return this.pure(`l.literal(${JSON.stringify(def.const)})`)
827
+ const options = stringifyOptions(def, [
828
+ 'default',
829
+ ] satisfies (keyof l.LiteralSchemaOptions<any>)[])
830
+ return this.pure(`l.literal(${JSON.stringify(def.const)}, ${options})`)
805
831
  }
806
832
 
807
833
  private async compileConstType<
@@ -815,14 +841,18 @@ export class LexDefBuilder {
815
841
 
816
842
  private async compileEnumSchema<T extends null | number | string>(def: {
817
843
  enum: readonly T[]
844
+ default?: T
818
845
  }): Promise<string> {
819
846
  if (def.enum.length === 0) {
820
847
  return this.pure(`l.never()`)
821
848
  }
822
- if (def.enum.length === 1) {
849
+ if (def.enum.length === 1 && def.default === undefined) {
823
850
  return this.pure(`l.literal(${JSON.stringify(def.enum[0])})`)
824
851
  }
825
- return this.pure(`l.enum(${JSON.stringify(def.enum)})`)
852
+ const options = stringifyOptions(def, [
853
+ 'default',
854
+ ] satisfies (keyof l.EnumSchemaOptions<any>)[])
855
+ return this.pure(`l.enum(${JSON.stringify(def.enum)}, ${options})`)
826
856
  }
827
857
 
828
858
  private async compileEnumType<T extends null | number | string>(def: {
@@ -888,11 +918,14 @@ function compileJsDoc(description: string) {
888
918
  }`
889
919
  }
890
920
 
891
- function stringifyOptionalOptions<O extends Record<string, unknown>>(
921
+ function stringifyOptions<O extends Record<string, unknown>>(
892
922
  obj: O,
893
- omit?: (keyof O)[],
923
+ include?: (keyof O)[],
924
+ exclude?: (keyof O)[],
894
925
  ) {
895
- const filtered = Object.entries(obj).filter(([k]) => !omit?.includes(k))
926
+ const filtered = Object.entries(obj).filter(
927
+ ([k]) => (!include || include.includes(k)) && !exclude?.includes(k),
928
+ )
896
929
  return filtered.length ? JSON.stringify(Object.fromEntries(filtered)) : ''
897
930
  }
898
931
 
@@ -16,7 +16,7 @@ export class LexiconDirectoryIndexer extends LexiconIterableIndexer {
16
16
 
17
17
  type ReadLexiconsOptions = {
18
18
  lexicons: string
19
- ignoreErrors?: boolean
19
+ ignoreInvalidLexicons?: boolean
20
20
  }
21
21
 
22
22
  async function* readLexicons(
@@ -29,7 +29,7 @@ async function* readLexicons(
29
29
  yield lexiconDocumentSchema.parse(JSON.parse(data))
30
30
  } catch (cause) {
31
31
  const message = `Error parsing lexicon document ${filePath}`
32
- if (options.ignoreErrors) console.error(`${message}:`, cause)
32
+ if (options.ignoreInvalidLexicons) console.error(`${message}:`, cause)
33
33
  else throw new Error(message, { cause })
34
34
  }
35
35
  }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": ["../../../tsconfig/node.json"],
3
+ "include": ["./src"],
4
+ "exclude": ["**/*.test.ts"],
5
+ "compilerOptions": {
6
+ "noImplicitAny": true,
7
+ "importHelpers": true,
8
+ "target": "ES2023",
9
+ "rootDir": "./src",
10
+ "outDir": "./dist",
11
+ "types": ["node"]
12
+ }
13
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "include": [],
3
+ "references": [
4
+ { "path": "./tsconfig.build.json" },
5
+ { "path": "./tsconfig.tests.json" }
6
+ ]
7
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../../tsconfig/tests.json",
3
+ "include": ["./tests", "./src/**.test.ts"],
4
+ "compilerOptions": {
5
+ "noImplicitAny": true,
6
+ "rootDir": "./",
7
+ "baseUrl": "./"
8
+ }
9
+ }