@atproto/lex-schema 0.1.1 → 0.1.3

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 (65) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/core/record-key.d.ts +10 -0
  3. package/dist/core/record-key.d.ts.map +1 -1
  4. package/dist/core/record-key.js.map +1 -1
  5. package/dist/core/result.d.ts +1 -125
  6. package/dist/core/result.d.ts.map +1 -1
  7. package/dist/core/result.js +1 -128
  8. package/dist/core/result.js.map +1 -1
  9. package/dist/core/validation-error.d.ts +0 -18
  10. package/dist/core/validation-error.d.ts.map +1 -1
  11. package/dist/core/validation-error.js +0 -30
  12. package/dist/core/validation-error.js.map +1 -1
  13. package/dist/core/validator.d.ts +8 -15
  14. package/dist/core/validator.d.ts.map +1 -1
  15. package/dist/core/validator.js +3 -14
  16. package/dist/core/validator.js.map +1 -1
  17. package/dist/helpers.d.ts +35 -2
  18. package/dist/helpers.d.ts.map +1 -1
  19. package/dist/helpers.js +33 -0
  20. package/dist/helpers.js.map +1 -1
  21. package/dist/schema/array.d.ts +1 -1
  22. package/dist/schema/blob.d.ts +1 -1
  23. package/dist/schema/boolean.d.ts +1 -1
  24. package/dist/schema/bytes.d.ts +1 -1
  25. package/dist/schema/cid.d.ts +1 -1
  26. package/dist/schema/custom.d.ts +1 -1
  27. package/dist/schema/dict.d.ts +1 -1
  28. package/dist/schema/enum.d.ts +1 -1
  29. package/dist/schema/integer.d.ts +1 -1
  30. package/dist/schema/intersection.d.ts +1 -1
  31. package/dist/schema/lex-map.d.ts +1 -1
  32. package/dist/schema/lex-value.d.ts +1 -1
  33. package/dist/schema/literal.d.ts +1 -1
  34. package/dist/schema/null.d.ts +1 -1
  35. package/dist/schema/nullable.d.ts +1 -1
  36. package/dist/schema/object.d.ts +1 -1
  37. package/dist/schema/optional.d.ts +1 -1
  38. package/dist/schema/params.d.ts +2 -2
  39. package/dist/schema/params.d.ts.map +1 -1
  40. package/dist/schema/record.d.ts +4 -4
  41. package/dist/schema/record.d.ts.map +1 -1
  42. package/dist/schema/record.js +4 -3
  43. package/dist/schema/record.js.map +1 -1
  44. package/dist/schema/regexp.d.ts +1 -1
  45. package/dist/schema/string.d.ts +1 -1
  46. package/dist/schema/token.d.ts +2 -1
  47. package/dist/schema/token.d.ts.map +1 -1
  48. package/dist/schema/token.js +6 -1
  49. package/dist/schema/token.js.map +1 -1
  50. package/dist/schema/typed-union.d.ts +1 -1
  51. package/dist/schema/union.d.ts.map +1 -1
  52. package/dist/schema/union.js +3 -3
  53. package/dist/schema/union.js.map +1 -1
  54. package/dist/schema/unknown.d.ts +1 -1
  55. package/package.json +2 -2
  56. package/src/core/record-key.ts +20 -1
  57. package/src/core/result.ts +8 -155
  58. package/src/core/validation-error.ts +1 -33
  59. package/src/core/validator.ts +10 -21
  60. package/src/helpers.test.ts +126 -1
  61. package/src/helpers.ts +108 -1
  62. package/src/schema/record.test.ts +9 -30
  63. package/src/schema/record.ts +9 -16
  64. package/src/schema/token.ts +9 -1
  65. package/src/schema/union.ts +4 -4
@@ -1,4 +1,4 @@
1
- import { describe, test } from 'vitest'
1
+ import { describe, expect, expectTypeOf, it, test } from 'vitest'
2
2
  import * as l from './external.js'
3
3
 
4
4
  class BinaryValue {
@@ -567,3 +567,128 @@ describe('InferMethodMessage', () => {
567
567
  })
568
568
  })
569
569
  })
570
+
571
+ describe(l.atUri, () => {
572
+ describe('string collection', () => {
573
+ const did = 'did:example:alice'
574
+
575
+ it('builds a valid record URI', () => {
576
+ const uri = l.atUri(did, 'com.ex.foo', 'bar')
577
+ expect(uri).toBe('at://did:example:alice/com.ex.foo/bar')
578
+ expectTypeOf(uri).toEqualTypeOf<`at://did:example:alice/com.ex.foo/bar`>()
579
+ })
580
+
581
+ it('validates record key values', () => {
582
+ expect(() => {
583
+ // @ts-expect-error
584
+ l.atUri(did, 'com.ex.foo', '.')
585
+ }).toThrow()
586
+ expect(() => {
587
+ // @ts-expect-error
588
+ l.atUri(did, 'com.ex.foo', '..')
589
+ }).toThrow()
590
+ })
591
+
592
+ it('expects a second argument', () => {
593
+ expect(() => {
594
+ // @ts-expect-error
595
+ l.atUri(did, 'com.ex.foo')
596
+ }).toThrow()
597
+ })
598
+ })
599
+
600
+ describe('literal', () => {
601
+ const schema = l.record('literal:bar', 'com.ex.foo', l.object({}))
602
+ const did = 'did:example:alice'
603
+
604
+ it('allows omitting the record key for literal keys', () => {
605
+ const uri = l.atUri(did, schema)
606
+ expect(uri).toBe('at://did:example:alice/com.ex.foo/bar')
607
+ expectTypeOf(uri).toEqualTypeOf<`at://did:example:alice/com.ex.foo/bar`>()
608
+ })
609
+
610
+ it('still allows providing the record key for literal keys', () => {
611
+ expectTypeOf(
612
+ l.atUri(did, schema, 'bar'),
613
+ ).toEqualTypeOf<`at://did:example:alice/com.ex.foo/bar`>()
614
+ })
615
+
616
+ it('validates invalid record keys', () => {
617
+ expect(() => {
618
+ // @ts-expect-error
619
+ l.atUri(did, schema, 'wrong')
620
+ }).toThrow()
621
+ })
622
+ })
623
+
624
+ describe('tid', () => {
625
+ const schema = l.record('tid', 'com.ex.foo', l.object({}))
626
+ const did = 'did:example:alice'
627
+
628
+ it('builds a valid record URI', () => {
629
+ const uri = l.atUri(did, schema, '3jzfcijpj2z2a')
630
+ expectTypeOf(
631
+ uri,
632
+ ).toEqualTypeOf<`at://did:example:alice/com.ex.foo/3jzfcijpj2z2a`>()
633
+ expect(uri).toBe('at://did:example:alice/com.ex.foo/3jzfcijpj2z2a')
634
+ })
635
+
636
+ it('expects a second argument for non-literal keys', () => {
637
+ expect(() => {
638
+ // @ts-expect-error
639
+ l.atUri(did, schema)
640
+ }).toThrow()
641
+ })
642
+
643
+ it('validates the record key value', () => {
644
+ expect(() => {
645
+ l.atUri(did, schema, 'invalid-tid')
646
+ }).toThrow()
647
+ })
648
+ })
649
+
650
+ describe('any', () => {
651
+ const schema = l.record('any', 'com.ex.foo', l.object({}))
652
+ const did = 'did:example:alice'
653
+
654
+ it('builds a valid record URI valid keys', () => {
655
+ const uri = l.atUri(did, schema, 'customKey')
656
+ expect(uri).toBe('at://did:example:alice/com.ex.foo/customKey')
657
+ expectTypeOf(
658
+ uri,
659
+ ).toEqualTypeOf<`at://did:example:alice/com.ex.foo/customKey`>()
660
+ })
661
+
662
+ it('rejects invalid record key values', () => {
663
+ expect(() => {
664
+ // @ts-expect-error
665
+ l.atUri(did, schema, '.')
666
+ }).toThrow()
667
+ expect(() => {
668
+ // @ts-expect-error
669
+ l.atUri(did, schema, '..')
670
+ }).toThrow()
671
+ })
672
+
673
+ it('expects a second argument', () => {
674
+ expect(() => {
675
+ // @ts-expect-error
676
+ l.atUri(did, schema)
677
+ }).toThrow()
678
+ })
679
+
680
+ describe('edge cases', () => {
681
+ it('limits the record key to 512 characters', () => {
682
+ const uri = l.atUri(did, schema, 'a'.repeat(512))
683
+
684
+ expect(uri).toBe(`at://did:example:alice/com.ex.foo/${'a'.repeat(512)}`)
685
+ expectTypeOf(
686
+ uri,
687
+ ).toEqualTypeOf<`at://did:example:alice/com.ex.foo/${string}`>()
688
+ expect(() => {
689
+ l.atUri(did, schema, 'a'.repeat(513))
690
+ }).toThrow()
691
+ })
692
+ })
693
+ })
694
+ })
package/src/helpers.ts CHANGED
@@ -1,11 +1,22 @@
1
1
  import { LexErrorData } from '@atproto/lex-data'
2
- import { InferOutput, Restricted, Schema } from './core.js'
2
+ import {
3
+ AtIdentifierString,
4
+ InferOutput,
5
+ NsidString,
6
+ RecordKeyValue,
7
+ Restricted,
8
+ Schema,
9
+ assertAtIdentifierString,
10
+ assertStringFormat,
11
+ } from './core.js'
3
12
  import {
4
13
  InferPayload,
5
14
  InferPayloadBody,
6
15
  InferPayloadEncoding,
16
+ InferRecordKey,
7
17
  Procedure,
8
18
  Query,
19
+ RecordSchema,
9
20
  Subscription,
10
21
  object,
11
22
  optional,
@@ -113,3 +124,99 @@ export const lexErrorDataSchema = object({
113
124
  // description of the error, appropriate for display to humans
114
125
  message: optional(string()),
115
126
  }) satisfies Schema<LexErrorData>
127
+
128
+ /**
129
+ * Helper function to construct AT Protocol URIs with compile-time & runtime
130
+ * validation of their components. This function supports different use cases,
131
+ * including constructing URIs from raw strings or from RecordSchema instances,
132
+ * ensuring that the resulting URI adheres to the expected format.
133
+ *
134
+ * @throws {TypeError} If the arguments do not match the interface
135
+ * @throws {Error} If AT-URI components are invalid
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * import { atUri } from '@atproto/lex'
140
+ * import { app } from '#/lexicons/index.js'
141
+ *
142
+ * // Constructing a URI from raw components
143
+ * const uri1 = atUri('did:example:123', 'app.bsky.feed.post', 'my-post')
144
+ *
145
+ * // Constructing a URI from a RecordSchema instance
146
+ * const uri2 = atUri('did:example:123', app.bsky.feed.post, 'my-post')
147
+ *
148
+ * // Literal rkey can be omitted
149
+ * const uri3 = atUri('did:example:123', app.bsky.actor.profile) // rkey 'self' is implied
150
+ *
151
+ * // Invalid URIs will throw errors
152
+ * atUri('invalid authority', 'app.bsky.feed.post', 'my-post') // throws
153
+ * atUri('did:example:123', 'invalid collection', 'my-post') // throws
154
+ * atUri('did:example:123', 'app.bsky.feed.post', '..') // throws
155
+ * ```
156
+ */
157
+ export function atUri<const TAuthority extends AtIdentifierString>(
158
+ authority: TAuthority,
159
+ ): `at://${TAuthority}`
160
+ export function atUri<
161
+ const TAuthority extends AtIdentifierString,
162
+ const TCollection extends NsidString,
163
+ const TRecordKey extends RecordKeyValue,
164
+ >(
165
+ authority: TAuthority,
166
+ nsid: TCollection,
167
+ rkey: TRecordKey extends '..' | '.' ? never : TRecordKey,
168
+ ): `at://${TAuthority}/${TCollection}/${TRecordKey}`
169
+ export function atUri<
170
+ const TAuthority extends AtIdentifierString,
171
+ const TRecord extends RecordSchema,
172
+ >(
173
+ authority: TAuthority,
174
+ record: TRecord['key'] extends `literal:${string}` ? Main<TRecord> : never,
175
+ ): `at://${TAuthority}/${TRecord['$type']}/${InferRecordKey<TRecord>}`
176
+ export function atUri<
177
+ const TAuthority extends AtIdentifierString,
178
+ const TRecord extends RecordSchema,
179
+ const TRecordKey extends InferRecordKey<TRecord>,
180
+ >(
181
+ authority: TAuthority,
182
+ record: Main<TRecord>,
183
+ rkey: TRecordKey extends '..' | '.' ? never : TRecordKey,
184
+ ): `at://${TAuthority}/${TRecord['$type']}/${TRecordKey}`
185
+ export function atUri(
186
+ authority: AtIdentifierString,
187
+ record?: string | Main<RecordSchema>,
188
+ rkey?: string,
189
+ ) {
190
+ /**
191
+ * @NOTE because we are encoding potentially untrusted input into a URI, we
192
+ * validate the input against the AT Protocol constraints, ensuring that no
193
+ * invalid URIs can be generated.
194
+ */
195
+ switch (typeof record) {
196
+ case 'undefined': {
197
+ assertAtIdentifierString(authority)
198
+ return `at://${authority}`
199
+ }
200
+
201
+ case 'string': {
202
+ if (!rkey) {
203
+ throw new TypeError('Record key is required when record is a string')
204
+ }
205
+ assertAtIdentifierString(authority)
206
+ assertStringFormat(record, 'nsid')
207
+ assertStringFormat(rkey, 'record-key')
208
+ return `at://${authority}/${record}/${rkey}`
209
+ }
210
+
211
+ default: {
212
+ // @NOTE The use of a schema assumes that the collection ($type) is a
213
+ // valid NSID that can safely be included in the URI without additional
214
+ // checks.
215
+ assertAtIdentifierString(authority)
216
+ const schema = getMain(record)
217
+ // @NOTE parsing will apply defaults, so that literal keys will be
218
+ // properly validated and included in the URI.
219
+ return `at://${authority}/${schema.$type}/${schema.keySchema.parse(rkey)}`
220
+ }
221
+ }
222
+ }
@@ -12,7 +12,6 @@ describe('RecordSchema', () => {
12
12
  'any',
13
13
  'app.bsky.feed.post',
14
14
  object({
15
- $type: string(),
16
15
  text: string(),
17
16
  }),
18
17
  )
@@ -284,7 +283,6 @@ describe('RecordSchema', () => {
284
283
  'any',
285
284
  'app.bsky.feed.post',
286
285
  object({
287
- $type: string(),
288
286
  text: string(),
289
287
  }),
290
288
  )
@@ -320,7 +318,6 @@ describe('RecordSchema', () => {
320
318
  'tid',
321
319
  'app.bsky.feed.post',
322
320
  object({
323
- $type: string(),
324
321
  text: string(),
325
322
  }),
326
323
  )
@@ -356,7 +353,6 @@ describe('RecordSchema', () => {
356
353
  'nsid',
357
354
  'app.bsky.feed.post',
358
355
  object({
359
- $type: string(),
360
356
  text: string(),
361
357
  }),
362
358
  )
@@ -395,7 +391,6 @@ describe('RecordSchema', () => {
395
391
  'literal:self',
396
392
  'app.bsky.feed.post',
397
393
  object({
398
- $type: string(),
399
394
  text: string(),
400
395
  }),
401
396
  )
@@ -426,7 +421,6 @@ describe('RecordSchema', () => {
426
421
  'literal:customKey',
427
422
  'app.bsky.feed.post',
428
423
  object({
429
- $type: string(),
430
424
  text: string(),
431
425
  }),
432
426
  )
@@ -453,7 +447,6 @@ describe('RecordSchema', () => {
453
447
  'any',
454
448
  'app.bsky.feed.post#main',
455
449
  object({
456
- $type: string(),
457
450
  text: string(),
458
451
  }),
459
452
  )
@@ -488,7 +481,6 @@ describe('RecordSchema', () => {
488
481
  'any',
489
482
  'app.bsky.feed.post',
490
483
  object({
491
- $type: string(),
492
484
  text: string({ maxLength: 300 }),
493
485
  createdAt: string({ format: 'datetime' }),
494
486
  }),
@@ -527,7 +519,6 @@ describe('RecordSchema', () => {
527
519
  'any',
528
520
  'app.bsky.feed.post',
529
521
  object({
530
- $type: string(),
531
522
  text: string(),
532
523
  }),
533
524
  )
@@ -592,7 +583,6 @@ describe('RecordSchema', () => {
592
583
  'any',
593
584
  'app.bsky.complex',
594
585
  object({
595
- $type: string(),
596
586
  nested: object({
597
587
  deep: object({
598
588
  value: string(),
@@ -618,7 +608,6 @@ describe('RecordSchema', () => {
618
608
  'any',
619
609
  'app.bsky.feed.post',
620
610
  object({
621
- $type: string(),
622
611
  text: string(),
623
612
  }),
624
613
  )
@@ -639,7 +628,6 @@ describe('RecordSchema', () => {
639
628
  'any',
640
629
  'app.bsky.feed.post',
641
630
  object({
642
- $type: string(),
643
631
  text: string(),
644
632
  }),
645
633
  )
@@ -656,7 +644,6 @@ describe('RecordSchema', () => {
656
644
  'any',
657
645
  'app.bsky.feed.post',
658
646
  object({
659
- $type: string(),
660
647
  text: string(),
661
648
  author: string(),
662
649
  }),
@@ -682,35 +669,32 @@ describe('RecordSchema', () => {
682
669
 
683
670
  describe('different record key types', () => {
684
671
  it('constructs with key type "any"', () => {
685
- const schema = record('any', 'app.bsky.test', object({ $type: string() }))
672
+ const schema = record('any', 'app.bsky.test', object({}))
686
673
  expect(schema.key).toBe('any')
687
674
  expect(schema.keySchema).toBeDefined()
688
675
  })
689
676
 
690
677
  it('constructs with key type "tid"', () => {
691
- const schema = record('tid', 'app.bsky.test', object({ $type: string() }))
678
+ const schema = record('tid', 'app.bsky.test', object({}))
692
679
  expect(schema.key).toBe('tid')
693
680
  expect(schema.keySchema).toBeDefined()
694
681
  })
695
682
 
696
683
  it('constructs with key type "nsid"', () => {
697
- const schema = record(
698
- 'nsid',
699
- 'app.bsky.test',
700
- object({ $type: string() }),
701
- )
684
+ const schema = record('nsid', 'app.bsky.test', object({}))
702
685
  expect(schema.key).toBe('nsid')
703
686
  expect(schema.keySchema).toBeDefined()
687
+ expect(schema.keySchema.safeParse('app.bsky.post').success).toBe(true)
688
+ expect(schema.keySchema.safeParse('invalid-nsid').success).toBe(false)
704
689
  })
705
690
 
706
691
  it('constructs with literal key type', () => {
707
- const schema = record(
708
- 'literal:custom',
709
- 'app.bsky.test',
710
- object({ $type: string() }),
711
- )
692
+ const schema = record('literal:custom', 'app.bsky.test', object({}))
712
693
  expect(schema.key).toBe('literal:custom')
713
694
  expect(schema.keySchema).toBeDefined()
695
+ // Applies default value in parse mode
696
+ expect(schema.keySchema.parse(undefined)).toBe('custom')
697
+ expect(schema.keySchema.safeParse('not-custom').success).toBe(false)
714
698
  })
715
699
  })
716
700
 
@@ -719,7 +703,6 @@ describe('RecordSchema', () => {
719
703
  'any',
720
704
  'app.bsky.feed.post',
721
705
  object({
722
- $type: string(),
723
706
  text: string(),
724
707
  }),
725
708
  )
@@ -755,7 +738,6 @@ describe('RecordSchema', () => {
755
738
  'any',
756
739
  'app.bsky.feed.post',
757
740
  object({
758
- $type: string(),
759
741
  text: string(),
760
742
  }),
761
743
  )
@@ -774,7 +756,6 @@ describe('RecordSchema', () => {
774
756
  'any',
775
757
  'app.bsky.feed.post',
776
758
  object({
777
- $type: string(),
778
759
  text: string(),
779
760
  }),
780
761
  )
@@ -791,7 +772,6 @@ describe('RecordSchema', () => {
791
772
  'any',
792
773
  'app.bsky.feed.post#reply123',
793
774
  object({
794
- $type: string(),
795
775
  text: string(),
796
776
  }),
797
777
  )
@@ -809,7 +789,6 @@ describe('RecordSchema', () => {
809
789
  'any',
810
790
  'app.bsky.feed.post',
811
791
  object({
812
- $type: string(),
813
792
  text: string(),
814
793
  }),
815
794
  )
@@ -6,8 +6,8 @@ import {
6
6
  InferOutput,
7
7
  LexiconRecordKey,
8
8
  NsidString,
9
+ RecordKeyValue,
9
10
  Schema,
10
- TidString,
11
11
  Unknown$TypedObject,
12
12
  ValidationContext,
13
13
  Validator,
@@ -15,6 +15,7 @@ import {
15
15
  import { lazyProperty } from '../util/lazy-property.js'
16
16
  import { literal } from './literal.js'
17
17
  import { string } from './string.js'
18
+ import { withDefault } from './with-default.js'
18
19
 
19
20
  /**
20
21
  * Infers the record key type from a RecordSchema.
@@ -22,7 +23,7 @@ import { string } from './string.js'
22
23
  * @template R - The RecordSchema type
23
24
  */
24
25
  export type InferRecordKey<R extends RecordSchema> =
25
- R extends RecordSchema<infer TKey> ? RecordKeySchemaOutput<TKey> : never
26
+ R extends RecordSchema<infer TKey> ? RecordKeyValue<TKey> : never
26
27
 
27
28
  export type TypedRecord<
28
29
  TType extends NsidString,
@@ -120,24 +121,16 @@ export class RecordSchema<
120
121
  }
121
122
 
122
123
  export type RecordKeySchemaOutput<Key extends LexiconRecordKey> =
123
- Key extends 'any'
124
- ? string
125
- : Key extends 'tid'
126
- ? TidString
127
- : Key extends 'nsid'
128
- ? NsidString
129
- : Key extends `literal:${infer L extends string}`
130
- ? L
131
- : never
124
+ RecordKeyValue<Key>
132
125
 
133
126
  export type RecordKeySchema<Key extends LexiconRecordKey> = Schema<
134
- RecordKeySchemaOutput<Key>
127
+ RecordKeyValue<Key>
135
128
  >
136
129
 
137
- const keySchema = string({ minLength: 1 })
130
+ const keySchema = string({ format: 'record-key' })
138
131
  const tidSchema = string({ format: 'tid' })
139
132
  const nsidSchema = string({ format: 'nsid' })
140
- const selfLiteralSchema = literal('self')
133
+ const selfLiteralSchema = withDefault(literal('self'), 'self')
141
134
 
142
135
  function recordKey<Key extends LexiconRecordKey>(
143
136
  key: Key,
@@ -147,9 +140,9 @@ function recordKey<Key extends LexiconRecordKey>(
147
140
  if (key === 'tid') return tidSchema as any
148
141
  if (key === 'nsid') return nsidSchema as any
149
142
  if (key.startsWith('literal:')) {
150
- const value = key.slice(8) as RecordKeySchemaOutput<Key>
143
+ const value = key.slice(8) as RecordKeyValue<Key>
151
144
  if (value === 'self') return selfLiteralSchema as any
152
- return literal(value)
145
+ return withDefault(literal(value), value)
153
146
  }
154
147
 
155
148
  throw new Error(`Unsupported record key type: ${key}`)
@@ -24,6 +24,10 @@ export class TokenSchema<
24
24
  super()
25
25
  }
26
26
 
27
+ get $token(): TValue {
28
+ return this.value
29
+ }
30
+
27
31
  validateInContext(input: unknown, ctx: ValidationContext) {
28
32
  if (input === this.value) {
29
33
  return ctx.success(this.value)
@@ -31,7 +35,11 @@ export class TokenSchema<
31
35
 
32
36
  // @NOTE: allow using the token instance itself (but convert to the actual
33
37
  // token value)
34
- if (input instanceof TokenSchema && input.value === this.value) {
38
+ if (
39
+ ctx.options.mode === 'parse' &&
40
+ input instanceof TokenSchema &&
41
+ input.value === this.value
42
+ ) {
35
43
  return ctx.success(this.value)
36
44
  }
37
45
 
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  InferInput,
3
3
  InferOutput,
4
+ Issue,
4
5
  LexValidationError,
5
6
  Schema,
6
7
  ValidationContext,
7
- ValidationFailure,
8
8
  Validator,
9
9
  } from '../core.js'
10
10
 
@@ -44,16 +44,16 @@ export class UnionSchema<
44
44
  }
45
45
 
46
46
  validateInContext(input: unknown, ctx: ValidationContext) {
47
- const failures: ValidationFailure[] = []
47
+ const issues: Issue[] = []
48
48
 
49
49
  for (const validator of this.validators) {
50
50
  const result = ctx.validate(input, validator)
51
51
  if (result.success) return result
52
52
 
53
- failures.push(result)
53
+ issues.push(...result.issues)
54
54
  }
55
55
 
56
- return ctx.failure(LexValidationError.fromFailures(failures))
56
+ return new LexValidationError(issues)
57
57
  }
58
58
  }
59
59