@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.
- package/CHANGELOG.md +12 -0
- package/dist/core/record-key.d.ts +10 -0
- package/dist/core/record-key.d.ts.map +1 -1
- package/dist/core/record-key.js.map +1 -1
- package/dist/core/result.d.ts +1 -125
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +1 -128
- package/dist/core/result.js.map +1 -1
- package/dist/core/validation-error.d.ts +0 -18
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +0 -30
- package/dist/core/validation-error.js.map +1 -1
- package/dist/core/validator.d.ts +8 -15
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +3 -14
- package/dist/core/validator.js.map +1 -1
- package/dist/helpers.d.ts +35 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +33 -0
- package/dist/helpers.js.map +1 -1
- package/dist/schema/array.d.ts +1 -1
- package/dist/schema/blob.d.ts +1 -1
- package/dist/schema/boolean.d.ts +1 -1
- package/dist/schema/bytes.d.ts +1 -1
- package/dist/schema/cid.d.ts +1 -1
- package/dist/schema/custom.d.ts +1 -1
- package/dist/schema/dict.d.ts +1 -1
- package/dist/schema/enum.d.ts +1 -1
- package/dist/schema/integer.d.ts +1 -1
- package/dist/schema/intersection.d.ts +1 -1
- package/dist/schema/lex-map.d.ts +1 -1
- package/dist/schema/lex-value.d.ts +1 -1
- package/dist/schema/literal.d.ts +1 -1
- package/dist/schema/null.d.ts +1 -1
- package/dist/schema/nullable.d.ts +1 -1
- package/dist/schema/object.d.ts +1 -1
- package/dist/schema/optional.d.ts +1 -1
- package/dist/schema/params.d.ts +2 -2
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/record.d.ts +4 -4
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +4 -3
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/regexp.d.ts +1 -1
- package/dist/schema/string.d.ts +1 -1
- package/dist/schema/token.d.ts +2 -1
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +6 -1
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-union.d.ts +1 -1
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +3 -3
- package/dist/schema/union.js.map +1 -1
- package/dist/schema/unknown.d.ts +1 -1
- package/package.json +2 -2
- package/src/core/record-key.ts +20 -1
- package/src/core/result.ts +8 -155
- package/src/core/validation-error.ts +1 -33
- package/src/core/validator.ts +10 -21
- package/src/helpers.test.ts +126 -1
- package/src/helpers.ts +108 -1
- package/src/schema/record.test.ts +9 -30
- package/src/schema/record.ts +9 -16
- package/src/schema/token.ts +9 -1
- package/src/schema/union.ts +4 -4
package/src/helpers.test.ts
CHANGED
|
@@ -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 {
|
|
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({
|
|
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({
|
|
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
|
)
|
package/src/schema/record.ts
CHANGED
|
@@ -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> ?
|
|
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
|
|
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
|
-
|
|
127
|
+
RecordKeyValue<Key>
|
|
135
128
|
>
|
|
136
129
|
|
|
137
|
-
const keySchema = string({
|
|
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
|
|
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}`)
|
package/src/schema/token.ts
CHANGED
|
@@ -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 (
|
|
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
|
|
package/src/schema/union.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
53
|
+
issues.push(...result.issues)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
return
|
|
56
|
+
return new LexValidationError(issues)
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|