@atproto/lex-schema 0.0.3 → 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.
- package/CHANGELOG.md +44 -0
- package/dist/core/$type.d.ts +7 -0
- package/dist/core/$type.d.ts.map +1 -1
- package/dist/core/$type.js.map +1 -1
- package/dist/core/result.d.ts +7 -6
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +9 -8
- package/dist/core/result.js.map +1 -1
- package/dist/core/string-format.d.ts +37 -26
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +66 -59
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/external.d.ts +7 -6
- package/dist/external.d.ts.map +1 -1
- package/dist/external.js +1 -0
- package/dist/external.js.map +1 -1
- package/dist/helpers.d.ts +36 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +3 -0
- package/dist/helpers.js.map +1 -0
- package/dist/schema/blob.d.ts +1 -0
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +32 -18
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/custom.js +1 -1
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/integer.js +1 -1
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/params.d.ts +0 -1
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts +17 -15
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js +28 -0
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/procedure.d.ts +3 -6
- package/dist/schema/procedure.d.ts.map +1 -1
- package/dist/schema/procedure.js +1 -0
- package/dist/schema/procedure.js.map +1 -1
- package/dist/schema/query.d.ts +3 -5
- package/dist/schema/query.d.ts.map +1 -1
- package/dist/schema/query.js +1 -0
- package/dist/schema/query.js.map +1 -1
- package/dist/schema/record.d.ts +13 -12
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/refine.js +1 -1
- package/dist/schema/refine.js.map +1 -1
- package/dist/schema/subscription.d.ts +4 -7
- package/dist/schema/subscription.d.ts.map +1 -1
- package/dist/schema/subscription.js.map +1 -1
- package/dist/schema/typed-object.d.ts +7 -6
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +1 -4
- package/dist/schema/union.js.map +1 -1
- package/dist/util/assertion-util.d.ts +8 -0
- package/dist/util/assertion-util.d.ts.map +1 -0
- package/dist/util/assertion-util.js +31 -0
- package/dist/util/assertion-util.js.map +1 -0
- package/dist/validation/schema.d.ts +21 -2
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/schema.js +25 -2
- package/dist/validation/schema.js.map +1 -1
- package/dist/validation/validation-error.d.ts.map +1 -1
- package/dist/validation/validation-error.js +3 -3
- package/dist/validation/validation-error.js.map +1 -1
- package/dist/validation/validation-issue.js +10 -2
- package/dist/validation/validation-issue.js.map +1 -1
- package/dist/validation/validator.d.ts +4 -3
- package/dist/validation/validator.d.ts.map +1 -1
- package/dist/validation/validator.js +13 -10
- package/dist/validation/validator.js.map +1 -1
- package/package.json +2 -2
- package/src/core/$type.ts +4 -0
- package/src/core/result.ts +9 -8
- package/src/core/string-format.ts +88 -68
- package/src/core/types.ts +4 -0
- package/src/external.ts +9 -8
- package/src/helpers.test.ts +486 -0
- package/src/helpers.ts +61 -0
- package/src/schema/blob.test.ts +2 -4
- package/src/schema/blob.ts +31 -23
- package/src/schema/custom.test.ts +5 -5
- package/src/schema/custom.ts +1 -1
- package/src/schema/integer.ts +1 -1
- package/src/schema/params.ts +0 -7
- package/src/schema/payload.ts +67 -34
- package/src/schema/permission-set.test.ts +36 -36
- package/src/schema/procedure.test.ts +1 -62
- package/src/schema/procedure.ts +8 -20
- package/src/schema/query.test.ts +22 -69
- package/src/schema/query.ts +7 -14
- package/src/schema/record.ts +8 -4
- package/src/schema/refine.ts +1 -1
- package/src/schema/subscription.test.ts +30 -93
- package/src/schema/subscription.ts +11 -24
- package/src/schema/typed-object.ts +7 -3
- package/src/schema/union.ts +1 -4
- package/src/util/assertion-util.ts +40 -0
- package/src/validation/schema.ts +29 -4
- package/src/validation/validation-error.ts +4 -4
- package/src/validation/validation-issue.ts +12 -2
- package/src/validation/validator.ts +16 -12
- 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
|
package/src/schema/blob.test.ts
CHANGED
|
@@ -353,8 +353,7 @@ describe('BlobSchema', () => {
|
|
|
353
353
|
mimeType: 'image/gif',
|
|
354
354
|
size: 10000,
|
|
355
355
|
})
|
|
356
|
-
|
|
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
|
-
|
|
369
|
-
expect(result.success).toBe(true)
|
|
367
|
+
expect(result.success).toBe(false)
|
|
370
368
|
})
|
|
371
369
|
|
|
372
370
|
it('accepts blob matching accept constraint', () => {
|
package/src/schema/blob.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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(
|
|
69
|
+
return ctx.success(blob as BlobSchemaOutput<O>)
|
|
64
70
|
}
|
|
65
|
-
}
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
161
|
-
expect(result.
|
|
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
|
-
|
|
395
|
+
reason: {
|
|
396
396
|
issues: [
|
|
397
397
|
{ message: 'This is a custom issue' },
|
|
398
398
|
{ message: 'Must be a string' },
|
package/src/schema/custom.ts
CHANGED
|
@@ -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.
|
|
36
|
+
return ctx.issue(new IssueCustom(path, input, this.message))
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/schema/integer.ts
CHANGED
|
@@ -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
|
}
|