@atproto/lex-schema 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.
Files changed (109) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/core/$type.d.ts +7 -0
  3. package/dist/core/$type.d.ts.map +1 -1
  4. package/dist/core/$type.js.map +1 -1
  5. package/dist/core/result.d.ts +7 -6
  6. package/dist/core/result.d.ts.map +1 -1
  7. package/dist/core/result.js +9 -8
  8. package/dist/core/result.js.map +1 -1
  9. package/dist/core/string-format.d.ts +37 -26
  10. package/dist/core/string-format.d.ts.map +1 -1
  11. package/dist/core/string-format.js +66 -59
  12. package/dist/core/string-format.js.map +1 -1
  13. package/dist/core/types.d.ts +3 -0
  14. package/dist/core/types.d.ts.map +1 -1
  15. package/dist/core/types.js.map +1 -1
  16. package/dist/external.d.ts +7 -6
  17. package/dist/external.d.ts.map +1 -1
  18. package/dist/external.js +1 -0
  19. package/dist/external.js.map +1 -1
  20. package/dist/helpers.d.ts +36 -0
  21. package/dist/helpers.d.ts.map +1 -0
  22. package/dist/helpers.js +3 -0
  23. package/dist/helpers.js.map +1 -0
  24. package/dist/schema/blob.d.ts +1 -0
  25. package/dist/schema/blob.d.ts.map +1 -1
  26. package/dist/schema/blob.js +32 -18
  27. package/dist/schema/blob.js.map +1 -1
  28. package/dist/schema/custom.js +1 -1
  29. package/dist/schema/custom.js.map +1 -1
  30. package/dist/schema/integer.js +1 -1
  31. package/dist/schema/integer.js.map +1 -1
  32. package/dist/schema/params.d.ts +0 -1
  33. package/dist/schema/params.d.ts.map +1 -1
  34. package/dist/schema/params.js.map +1 -1
  35. package/dist/schema/payload.d.ts +17 -15
  36. package/dist/schema/payload.d.ts.map +1 -1
  37. package/dist/schema/payload.js +28 -0
  38. package/dist/schema/payload.js.map +1 -1
  39. package/dist/schema/procedure.d.ts +3 -6
  40. package/dist/schema/procedure.d.ts.map +1 -1
  41. package/dist/schema/procedure.js +1 -0
  42. package/dist/schema/procedure.js.map +1 -1
  43. package/dist/schema/query.d.ts +3 -5
  44. package/dist/schema/query.d.ts.map +1 -1
  45. package/dist/schema/query.js +1 -0
  46. package/dist/schema/query.js.map +1 -1
  47. package/dist/schema/record.d.ts +13 -12
  48. package/dist/schema/record.d.ts.map +1 -1
  49. package/dist/schema/record.js.map +1 -1
  50. package/dist/schema/refine.js +1 -1
  51. package/dist/schema/refine.js.map +1 -1
  52. package/dist/schema/subscription.d.ts +4 -7
  53. package/dist/schema/subscription.d.ts.map +1 -1
  54. package/dist/schema/subscription.js.map +1 -1
  55. package/dist/schema/typed-object.d.ts +7 -6
  56. package/dist/schema/typed-object.d.ts.map +1 -1
  57. package/dist/schema/typed-object.js.map +1 -1
  58. package/dist/schema/union.d.ts.map +1 -1
  59. package/dist/schema/union.js +1 -4
  60. package/dist/schema/union.js.map +1 -1
  61. package/dist/util/assertion-util.d.ts +8 -0
  62. package/dist/util/assertion-util.d.ts.map +1 -0
  63. package/dist/util/assertion-util.js +31 -0
  64. package/dist/util/assertion-util.js.map +1 -0
  65. package/dist/validation/schema.d.ts +21 -2
  66. package/dist/validation/schema.d.ts.map +1 -1
  67. package/dist/validation/schema.js +25 -2
  68. package/dist/validation/schema.js.map +1 -1
  69. package/dist/validation/validation-error.d.ts.map +1 -1
  70. package/dist/validation/validation-error.js +3 -3
  71. package/dist/validation/validation-error.js.map +1 -1
  72. package/dist/validation/validation-issue.js +10 -2
  73. package/dist/validation/validation-issue.js.map +1 -1
  74. package/dist/validation/validator.d.ts +4 -3
  75. package/dist/validation/validator.d.ts.map +1 -1
  76. package/dist/validation/validator.js +13 -10
  77. package/dist/validation/validator.js.map +1 -1
  78. package/package.json +2 -2
  79. package/src/core/$type.ts +4 -0
  80. package/src/core/result.ts +9 -8
  81. package/src/core/string-format.ts +88 -68
  82. package/src/core/types.ts +4 -0
  83. package/src/external.ts +9 -8
  84. package/src/helpers.test.ts +486 -0
  85. package/src/helpers.ts +61 -0
  86. package/src/schema/blob.test.ts +2 -4
  87. package/src/schema/blob.ts +31 -23
  88. package/src/schema/custom.test.ts +5 -5
  89. package/src/schema/custom.ts +1 -1
  90. package/src/schema/integer.ts +1 -1
  91. package/src/schema/params.ts +0 -7
  92. package/src/schema/payload.ts +67 -34
  93. package/src/schema/permission-set.test.ts +36 -36
  94. package/src/schema/procedure.test.ts +1 -62
  95. package/src/schema/procedure.ts +8 -20
  96. package/src/schema/query.test.ts +22 -69
  97. package/src/schema/query.ts +7 -14
  98. package/src/schema/record.ts +8 -4
  99. package/src/schema/refine.ts +1 -1
  100. package/src/schema/subscription.test.ts +30 -93
  101. package/src/schema/subscription.ts +11 -24
  102. package/src/schema/typed-object.ts +7 -3
  103. package/src/schema/union.ts +1 -4
  104. package/src/util/assertion-util.ts +40 -0
  105. package/src/validation/schema.ts +29 -4
  106. package/src/validation/validation-error.ts +4 -4
  107. package/src/validation/validation-issue.ts +12 -2
  108. package/src/validation/validator.ts +16 -12
  109. package/tsconfig.tests.json +1 -1
@@ -0,0 +1,486 @@
1
+ import * as l from './external.js'
2
+
3
+ class BinaryValue {
4
+ declare readonly ['__binaryValue']: BinaryValue
5
+ }
6
+
7
+ const expectType = <T>(_: T) => {}
8
+ const binaryValue = new BinaryValue()
9
+
10
+ describe('InferMethodParams', () => {
11
+ describe('query', () => {
12
+ test('infers parameter types', () => {
13
+ const query = l.query(
14
+ 'com.example.query',
15
+ l.params({
16
+ feed: l.string({ format: 'at-uri' }),
17
+ limit: l.optional(l.integer()),
18
+ }),
19
+ l.payload('application/json', undefined),
20
+ )
21
+
22
+ type Params = l.InferMethodParams<typeof query>
23
+
24
+ expectType<Params>({
25
+ feed: 'at://did:plc:abc123/app.bsky.feed.post/xyz',
26
+ limit: 50,
27
+ })
28
+ // @ts-expect-error
29
+ expectType<Params>({ feed: 123, limit: 50 })
30
+ // @ts-expect-error
31
+ expectType<Params>({ limit: 50 })
32
+ })
33
+ })
34
+
35
+ describe('procedure', () => {
36
+ test('infers parameter types', () => {
37
+ const procedure = l.procedure(
38
+ 'com.example.list',
39
+ l.params({ limit: l.string() }),
40
+ l.payload(undefined, undefined),
41
+ l.payload(undefined, undefined),
42
+ undefined,
43
+ )
44
+
45
+ type Params = l.InferMethodParams<typeof procedure>
46
+
47
+ expectType<Params>({ limit: '10' })
48
+ // @ts-expect-error
49
+ expectType<Params>({ limit: 10 })
50
+ // @ts-expect-error
51
+ expectType<Params>({ limit: binaryValue })
52
+ // @ts-expect-error
53
+ expectType<Params>(binaryValue)
54
+ })
55
+ })
56
+
57
+ describe('subscription', () => {
58
+ test('infers parameter types', () => {
59
+ const subscription = l.subscription(
60
+ 'com.example.subscribe',
61
+ l.params({
62
+ cursor: l.optional(l.integer()),
63
+ }),
64
+ l.unknown(),
65
+ )
66
+
67
+ type Params = l.InferMethodParams<typeof subscription>
68
+
69
+ expectType<Params>({ cursor: 100 })
70
+ // @ts-expect-error
71
+ expectType<Params>({ cursor: '100' })
72
+ // @ts-expect-error
73
+ expectType<Params>({ cursor: binaryValue })
74
+ })
75
+ })
76
+ })
77
+
78
+ describe('InferMethodInput', () => {
79
+ describe('procedure', () => {
80
+ test('with payload schema', () => {
81
+ const procedure = l.procedure(
82
+ 'com.example.create',
83
+ l.params({}),
84
+ l.payload('application/json', l.object({ text: l.string() })),
85
+ l.payload(undefined, undefined),
86
+ undefined,
87
+ )
88
+
89
+ type Input = l.InferMethodInput<typeof procedure, BinaryValue>
90
+
91
+ expectType<Input>({ encoding: 'application/json', body: { text: 'hi' } })
92
+ // @ts-expect-error
93
+ expectType<Input>({ encoding: 'application/json', body: { text: 123 } })
94
+ // @ts-expect-error
95
+ expectType<Input>({ encoding: 'text/plain', body: 'hello' })
96
+ // @ts-expect-error
97
+ expectType<Input>({ encoding: 'text/plain', body: new Uint8Array() })
98
+ // @ts-expect-error
99
+ expectType<Input>({ encoding: 'text/plain', body: binaryValue })
100
+ })
101
+
102
+ test('without payload schema', () => {
103
+ const procedure = l.procedure(
104
+ 'com.example.create',
105
+ l.params({}),
106
+ l.payload('*/*', undefined),
107
+ l.payload(undefined, undefined),
108
+ undefined,
109
+ )
110
+
111
+ type Input = l.InferMethodInput<typeof procedure, BinaryValue>
112
+
113
+ expectType<Input>({ encoding: 'image/png', body: binaryValue })
114
+ // @ts-expect-error
115
+ expectType<Input>({ encoding: 'image/png', body: new Uint8Array() })
116
+ // @ts-expect-error
117
+ expectType<Input>({ encoding: 'image/png', body: [] })
118
+ // @ts-expect-error
119
+ expectType<Input>({ encoding: 'image/png', body: 'foo' })
120
+ })
121
+ })
122
+ })
123
+
124
+ describe('InferMethodInputBody', () => {
125
+ describe('procedure', () => {
126
+ test('with payload schema', () => {
127
+ const procedure = l.procedure(
128
+ 'com.example.create',
129
+ l.params({}),
130
+ l.payload('application/json', l.object({ text: l.string() })),
131
+ l.payload(undefined, undefined),
132
+ undefined,
133
+ )
134
+
135
+ type InputBody = l.InferMethodInputBody<typeof procedure, BinaryValue>
136
+
137
+ expectType<InputBody>({ text: 'hello' })
138
+ // @ts-expect-error
139
+ expectType<InputBody>({ text: 123 })
140
+ // @ts-expect-error
141
+ expectType<InputBody>(binaryValue)
142
+ })
143
+
144
+ test('without payload schema', () => {
145
+ const procedure = l.procedure(
146
+ 'com.example.upload',
147
+ l.params({}),
148
+ l.payload('*/*', undefined),
149
+ l.payload(undefined, undefined),
150
+ undefined,
151
+ )
152
+
153
+ type InputBody = l.InferMethodInputBody<typeof procedure, BinaryValue>
154
+
155
+ expectType<InputBody>(binaryValue)
156
+ // @ts-expect-error
157
+ expectType<InputBody>(new Uint8Array())
158
+ // @ts-expect-error
159
+ expectType<InputBody>({ text: 'hello' })
160
+ })
161
+ })
162
+ })
163
+
164
+ describe('InferMethodInputEncoding', () => {
165
+ describe('procedure', () => {
166
+ test('with payload schema', () => {
167
+ const procedure = l.procedure(
168
+ 'com.example.create',
169
+ l.params({}),
170
+ l.payload('application/json', l.object({ text: l.string() })),
171
+ l.payload(undefined, undefined),
172
+ undefined,
173
+ )
174
+
175
+ type InputEncoding = l.InferMethodInputEncoding<typeof procedure>
176
+
177
+ expectType<InputEncoding>('application/json')
178
+ // @ts-expect-error
179
+ expectType<InputEncoding>('text/plain')
180
+ // @ts-expect-error
181
+ expectType<InputEncoding>(123)
182
+ })
183
+
184
+ test('without payload schema', () => {
185
+ const procedure = l.procedure(
186
+ 'com.example.upload',
187
+ l.params({}),
188
+ l.payload('*/*', undefined),
189
+ l.payload(undefined, undefined),
190
+ undefined,
191
+ )
192
+
193
+ type InputEncoding = l.InferMethodInputEncoding<typeof procedure>
194
+
195
+ expectType<InputEncoding>('image/png')
196
+ expectType<InputEncoding>('application/octet-stream')
197
+ // @ts-expect-error
198
+ expectType<InputEncoding>(123)
199
+ })
200
+ })
201
+ })
202
+
203
+ describe('InferMethodOutput', () => {
204
+ describe('query', () => {
205
+ test('with payload schema', () => {
206
+ const query = l.query(
207
+ 'com.example.query',
208
+ l.params({}),
209
+ l.payload('application/json', l.object({ items: l.array(l.string()) })),
210
+ )
211
+
212
+ type Output = l.InferMethodOutput<typeof query, BinaryValue>
213
+
214
+ expectType<Output>({ encoding: 'application/json', body: { items: [] } })
215
+ // @ts-expect-error
216
+ expectType<Output>({ encoding: 'application/json', body: { items: [1] } })
217
+ // @ts-expect-error
218
+ expectType<Output>({ encoding: 'text/plain', body: { items: [] } })
219
+ })
220
+
221
+ test('without payload schema', () => {
222
+ const query = l.query(
223
+ 'com.example.query',
224
+ l.params({}),
225
+ l.payload('*/*', undefined),
226
+ )
227
+
228
+ type Output = l.InferMethodOutput<typeof query, BinaryValue>
229
+
230
+ expectType<Output>({ encoding: 'image/png', body: binaryValue })
231
+ // @ts-expect-error
232
+ expectType<Output>({ encoding: 'image/png', body: new Uint8Array() })
233
+ // @ts-expect-error
234
+ expectType<Output>({ encoding: 'image/png', body: 'string' })
235
+ })
236
+ })
237
+
238
+ describe('procedure', () => {
239
+ test('with payload schema', () => {
240
+ const procedure = l.procedure(
241
+ 'com.example.create',
242
+ l.params({}),
243
+ l.payload(undefined, undefined),
244
+ l.payload(
245
+ 'application/json',
246
+ l.object({ uri: l.string({ format: 'at-uri' }) }),
247
+ ),
248
+ undefined,
249
+ )
250
+
251
+ type Output = l.InferMethodOutput<typeof procedure, BinaryValue>
252
+
253
+ expectType<Output>({
254
+ encoding: 'application/json',
255
+ body: { uri: 'at://did:plc:abc/post/123' },
256
+ })
257
+ // @ts-expect-error
258
+ expectType<Output>({ encoding: 'application/json', body: { uri: 123 } })
259
+ // @ts-expect-error
260
+ expectType<Output>({ encoding: 'text/plain', body: { uri: 'at://...' } })
261
+ // @ts-expect-error
262
+ expectType<Output>({ encoding: 'text/plain', body: 'Hello' })
263
+ })
264
+
265
+ test('without payload schema', () => {
266
+ const procedure = l.procedure(
267
+ 'com.example.export',
268
+ l.params({}),
269
+ l.payload(undefined, undefined),
270
+ l.payload('*/*', undefined),
271
+ undefined,
272
+ )
273
+
274
+ type Output = l.InferMethodOutput<typeof procedure, BinaryValue>
275
+
276
+ expectType<Output>({
277
+ encoding: 'application/zip',
278
+ body: binaryValue,
279
+ })
280
+ expectType<Output>({
281
+ encoding: 'application/zip',
282
+ // @ts-expect-error
283
+ body: new Uint8Array(),
284
+ })
285
+ // @ts-expect-error
286
+ expectType<Output>({ encoding: 'application/zip', body: 'string' })
287
+ })
288
+ })
289
+ })
290
+
291
+ describe('InferMethodOutputBody', () => {
292
+ describe('query', () => {
293
+ test('with payload schema', () => {
294
+ const query = l.query(
295
+ 'com.example.query',
296
+ l.params({}),
297
+ l.payload(
298
+ 'application/json',
299
+ l.object({
300
+ cursor: l.optional(l.string()),
301
+ feed: l.string({ format: 'at-uri' }),
302
+ }),
303
+ ),
304
+ )
305
+
306
+ type OutputBody = l.InferMethodOutputBody<typeof query, BinaryValue>
307
+
308
+ expectType<OutputBody>({
309
+ cursor: 'abc123',
310
+ feed: 'at://did:plc:abc123/app.bsky.feed.post/xyz',
311
+ })
312
+ // @ts-expect-error
313
+ expectType<OutputBody>({ cursor: 123, feed: 'at://...' })
314
+ // @ts-expect-error
315
+ expectType<OutputBody>({ feed: 123 })
316
+ })
317
+
318
+ test('without payload schema', () => {
319
+ const query = l.query(
320
+ 'com.example.query',
321
+ l.params({}),
322
+ l.payload('*/*', undefined),
323
+ )
324
+
325
+ type OutputBody = l.InferMethodOutputBody<typeof query, BinaryValue>
326
+
327
+ expectType<OutputBody>(binaryValue)
328
+ // @ts-expect-error
329
+ expectType<OutputBody>(new Uint8Array())
330
+ // @ts-expect-error
331
+ expectType<OutputBody>({ data: 'foo' })
332
+ })
333
+ })
334
+
335
+ describe('procedure', () => {
336
+ test('with payload schema', () => {
337
+ const procedure = l.procedure(
338
+ 'com.example.get',
339
+ l.params({}),
340
+ l.payload(undefined, undefined),
341
+ l.payload(
342
+ 'application/json',
343
+ l.object({ uri: l.string({ format: 'at-uri' }) }),
344
+ ),
345
+ undefined,
346
+ )
347
+
348
+ type OutputBody = l.InferMethodOutputBody<typeof procedure, BinaryValue>
349
+
350
+ expectType<OutputBody>({ uri: 'at://did:plc:abc/post/123' })
351
+ // @ts-expect-error
352
+ expectType<OutputBody>({ uri: 123 })
353
+ // @ts-expect-error
354
+ expectType<OutputBody>(binaryValue)
355
+ })
356
+
357
+ test('without payload schema', () => {
358
+ const procedure = l.procedure(
359
+ 'com.example.export',
360
+ l.params({}),
361
+ l.payload(undefined, undefined),
362
+ l.payload('*/*', undefined),
363
+ undefined,
364
+ )
365
+
366
+ type OutputBody = l.InferMethodOutputBody<typeof procedure, BinaryValue>
367
+
368
+ expectType<OutputBody>(binaryValue)
369
+ // @ts-expect-error
370
+ expectType<OutputBody>(new Uint8Array())
371
+ // @ts-expect-error
372
+ expectType<OutputBody>({ data: 'foo' })
373
+ })
374
+ })
375
+ })
376
+
377
+ describe('InferMethodOutputEncoding', () => {
378
+ describe('query', () => {
379
+ test('with payload schema', () => {
380
+ const query = l.query(
381
+ 'com.example.query',
382
+ l.params({}),
383
+ l.payload('application/json', l.object({ data: l.string() })),
384
+ )
385
+
386
+ type OutputEncoding = l.InferMethodOutputEncoding<typeof query>
387
+
388
+ expectType<OutputEncoding>('application/json')
389
+ // @ts-expect-error
390
+ expectType<OutputEncoding>('text/plain')
391
+ // @ts-expect-error
392
+ expectType<OutputEncoding>(123)
393
+ })
394
+
395
+ test('without payload schema', () => {
396
+ const query = l.query(
397
+ 'com.example.query',
398
+ l.params({}),
399
+ l.payload('*/*', undefined),
400
+ )
401
+
402
+ type OutputEncoding = l.InferMethodOutputEncoding<typeof query>
403
+
404
+ expectType<OutputEncoding>('image/png')
405
+ expectType<OutputEncoding>('application/octet-stream')
406
+ // @ts-expect-error
407
+ expectType<OutputEncoding>(123)
408
+ })
409
+ })
410
+
411
+ describe('procedure', () => {
412
+ test('with payload schema', () => {
413
+ const procedure = l.procedure(
414
+ 'com.example.create',
415
+ l.params({}),
416
+ l.payload(undefined, undefined),
417
+ l.payload('application/json', l.object({ id: l.string() })),
418
+ undefined,
419
+ )
420
+
421
+ type OutputEncoding = l.InferMethodOutputEncoding<typeof procedure>
422
+
423
+ expectType<OutputEncoding>('application/json')
424
+ // @ts-expect-error
425
+ expectType<OutputEncoding>('text/plain')
426
+ // @ts-expect-error
427
+ expectType<OutputEncoding>(123)
428
+ })
429
+
430
+ test('without payload schema', () => {
431
+ const procedure = l.procedure(
432
+ 'com.example.export',
433
+ l.params({}),
434
+ l.payload(undefined, undefined),
435
+ l.payload('*/*', undefined),
436
+ undefined,
437
+ )
438
+
439
+ type OutputEncoding = l.InferMethodOutputEncoding<typeof procedure>
440
+
441
+ expectType<OutputEncoding>('application/zip')
442
+ expectType<OutputEncoding>('application/octet-stream')
443
+ // @ts-expect-error
444
+ expectType<OutputEncoding>(123)
445
+ })
446
+ })
447
+ })
448
+
449
+ describe('InferMethodMessage', () => {
450
+ describe('subscription', () => {
451
+ test('with message schema', () => {
452
+ const subscription = l.subscription(
453
+ 'com.example.subscribe',
454
+ l.params({}),
455
+ l.object({
456
+ seq: l.integer(),
457
+ event: l.string(),
458
+ }),
459
+ )
460
+
461
+ type Message = l.InferMethodMessage<typeof subscription>
462
+
463
+ expectType<Message>({ seq: 1, event: 'create' })
464
+ // @ts-expect-error
465
+ expectType<Message>({ seq: '1', event: 'create' })
466
+ // @ts-expect-error
467
+ expectType<Message>({ seq: 1 })
468
+ // @ts-expect-error
469
+ expectType<Message>(binaryValue)
470
+ })
471
+
472
+ test('without message schema', () => {
473
+ const subscription = l.subscription(
474
+ 'com.example.subscribe',
475
+ l.params({}),
476
+ l.unknown(),
477
+ )
478
+
479
+ type Message = l.InferMethodMessage<typeof subscription>
480
+
481
+ expectType<Message>(undefined as unknown)
482
+ expectType<Message>({ any: 'value' })
483
+ expectType<Message>(123)
484
+ })
485
+ })
486
+ })
package/src/helpers.ts ADDED
@@ -0,0 +1,61 @@
1
+ import { Restricted } from './core.js'
2
+ import {
3
+ InferPayload,
4
+ InferPayloadBody,
5
+ InferPayloadEncoding,
6
+ Payload,
7
+ Procedure,
8
+ Query,
9
+ Subscription,
10
+ } from './schema.js'
11
+ import { Infer, Schema } from './validation.js'
12
+
13
+ /**
14
+ * Every XRPC implementation should translate `application/json` and `text/*`
15
+ * payloads into their native equivalent ({@link LexValue} or string). Binary
16
+ * data payloads, however, can be represented differently depending on the
17
+ * environment and use case (e.g. `Uint8Array`, `Blob`, `Buffer`,
18
+ * `ReadableStream`, etc.). This type is a placeholder to represent binary data
19
+ * when not explicitly provided.
20
+ */
21
+ type BinaryData = Restricted<'Binary data'>
22
+
23
+ export type InferMethodParams<
24
+ //
25
+ M extends Procedure | Query | Subscription,
26
+ > = Infer<M['parameters']>
27
+
28
+ export type InferMethodInput<
29
+ M extends Procedure | Query | Subscription,
30
+ B = BinaryData,
31
+ > = M extends { input: Payload } ? InferPayload<M['input'], B> : undefined
32
+
33
+ export type InferMethodInputBody<
34
+ M extends Procedure | Query | Subscription,
35
+ B = BinaryData,
36
+ > = M extends { input: Payload } ? InferPayloadBody<M['input'], B> : undefined
37
+
38
+ export type InferMethodInputEncoding<
39
+ M extends Procedure | Query | Subscription,
40
+ > = M extends { input: Payload } ? InferPayloadEncoding<M['input']> : undefined
41
+
42
+ export type InferMethodOutput<
43
+ M extends Procedure | Query | Subscription,
44
+ B = BinaryData,
45
+ > = M extends { output: Payload } ? InferPayload<M['output'], B> : undefined
46
+
47
+ export type InferMethodOutputBody<
48
+ M extends Procedure | Query | Subscription,
49
+ B = BinaryData,
50
+ > = M extends { output: Payload } ? InferPayloadBody<M['output'], B> : undefined
51
+
52
+ export type InferMethodOutputEncoding<
53
+ M extends Procedure | Query | Subscription,
54
+ > = M extends { output: Payload }
55
+ ? InferPayloadEncoding<M['output']>
56
+ : undefined
57
+
58
+ export type InferMethodMessage<
59
+ //
60
+ M extends Procedure | Query | Subscription,
61
+ > = M extends { message: Schema } ? Infer<M['message']> : undefined
@@ -353,8 +353,7 @@ describe('BlobSchema', () => {
353
353
  mimeType: 'image/gif',
354
354
  size: 10000,
355
355
  })
356
- // Accept constraints are not enforced (see comment in source)
357
- expect(result.success).toBe(true)
356
+ expect(result.success).toBe(false)
358
357
  })
359
358
 
360
359
  it('accepts blob with maxSize option (not enforced)', () => {
@@ -365,8 +364,7 @@ describe('BlobSchema', () => {
365
364
  mimeType: 'image/jpeg',
366
365
  size: 10000,
367
366
  })
368
- // MaxSize constraints are not enforced (see comment in source)
369
- expect(result.success).toBe(true)
367
+ expect(result.success).toBe(false)
370
368
  })
371
369
 
372
370
  it('accepts blob matching accept constraint', () => {
@@ -43,38 +43,46 @@ export class BlobSchema<O extends BlobSchemaOptions> extends Schema<
43
43
  input: unknown,
44
44
  ctx: ValidatorContext,
45
45
  ): ValidationResult<BlobSchemaOutput<O>> {
46
- if (!isBlob(input, this.options)) {
46
+ const blob: null | BlobRef | LegacyBlobRef =
47
+ (input as any)?.$type !== undefined
48
+ ? isBlobRef(input, this.options)
49
+ ? input
50
+ : null
51
+ : this.options.allowLegacy === true && isLegacyBlobRef(input)
52
+ ? input
53
+ : null
54
+
55
+ if (!blob) {
47
56
  return ctx.issueInvalidType(input, 'blob')
48
57
  }
49
58
 
50
- // @NOTE Historically, we did not enforce constraints on blob references
51
- // https://github.com/bluesky-social/atproto/blob/4c15fb47cec26060bff2e710e95869a90c9d7fdd/packages/lexicon/src/validators/blob.ts#L5-L19
52
-
53
- // const { accept } = this.options
54
- // if (accept && !accept.includes(input.mimeType)) {
55
- // return ctx.issueInvalidValue(input, accept)
56
- // }
59
+ const { accept } = this.options
60
+ if (accept && !matchesMime(blob.mimeType, accept)) {
61
+ return ctx.issueInvalidPropertyValue(blob, 'mimeType', accept)
62
+ }
57
63
 
58
- // const { maxSize } = this.options
59
- // if (maxSize != null && input.size != -1 && input.size > maxSize) {
60
- // return ctx.issueTooBig(input, 'blob', maxSize, input.size)
61
- // }
64
+ const { maxSize } = this.options
65
+ if (maxSize != null && 'size' in blob && blob.size > maxSize) {
66
+ return ctx.issueTooBig(blob, 'blob', maxSize, blob.size)
67
+ }
62
68
 
63
- return ctx.success(input)
69
+ return ctx.success(blob as BlobSchemaOutput<O>)
64
70
  }
65
- }
66
71
 
67
- function isBlob<O extends BlobSchemaOptions>(
68
- input: unknown,
69
- options: O,
70
- ): input is BlobSchemaOutput<O> {
71
- if ((input as any)?.$type !== undefined) {
72
- return isBlobRef(input, options)
72
+ matchesMime(mime: string): boolean {
73
+ const { accept } = this.options
74
+ if (!accept) return true
75
+ return matchesMime(mime, accept)
73
76
  }
77
+ }
74
78
 
75
- if (options.allowLegacy === true) {
76
- return isLegacyBlobRef(input)
79
+ function matchesMime(mime: string, accepted: string[]): boolean {
80
+ if (accepted.includes('*/*')) return true
81
+ if (accepted.includes(mime)) return true
82
+ for (const value of accepted) {
83
+ if (value.endsWith('/*') && mime.startsWith(value.slice(0, -1))) {
84
+ return true
85
+ }
77
86
  }
78
-
79
87
  return false
80
88
  }
@@ -32,7 +32,7 @@ describe('CustomSchema', () => {
32
32
  const result = schema.safeParse(123)
33
33
  expect(result.success).toBe(false)
34
34
  if (!result.success) {
35
- expect(result.error.message).toContain('Custom error message')
35
+ expect(result.reason.message).toContain('Custom error message')
36
36
  }
37
37
  })
38
38
  })
@@ -143,7 +143,7 @@ describe('CustomSchema', () => {
143
143
  const result = schema.safeParse(123)
144
144
  expect(result.success).toBe(false)
145
145
  if (!result.success) {
146
- expect(result.error.message).toContain('customField')
146
+ expect(result.reason.message).toContain('customField')
147
147
  }
148
148
  })
149
149
 
@@ -157,8 +157,8 @@ describe('CustomSchema', () => {
157
157
  const result = schema.safeParse(123)
158
158
  expect(result.success).toBe(false)
159
159
  if (!result.success) {
160
- expect(result.error.message).toContain('nested')
161
- expect(result.error.message).toContain('field')
160
+ expect(result.reason.message).toContain('nested')
161
+ expect(result.reason.message).toContain('field')
162
162
  }
163
163
  })
164
164
  })
@@ -392,7 +392,7 @@ describe('CustomSchema', () => {
392
392
 
393
393
  expect(schema.safeParse('test')).toMatchObject({
394
394
  success: false,
395
- error: {
395
+ reason: {
396
396
  issues: [
397
397
  { message: 'This is a custom issue' },
398
398
  { message: 'Must be a string' },
@@ -33,6 +33,6 @@ export class CustomSchema<T = unknown> extends Schema<T> {
33
33
  ): ValidationResult<T> {
34
34
  if (this.assertion.call(null, input, ctx)) return ctx.success(input as T)
35
35
  const path = ctx.concatPath(this.path)
36
- return ctx.failure(new IssueCustom(path, input, this.message))
36
+ return ctx.issue(new IssueCustom(path, input, this.message))
37
37
  }
38
38
  }
@@ -35,5 +35,5 @@ export class IntegerSchema extends Schema<number> {
35
35
  * Simple wrapper around {@link Number.isInteger} that acts as a type guard.
36
36
  */
37
37
  function isInteger(input: unknown): input is number {
38
- return Number.isInteger(input)
38
+ return Number.isInteger(input) && Number.isSafeInteger(input)
39
39
  }