@atproto/lex-client 0.0.17 → 0.0.18

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/src/response.ts CHANGED
@@ -13,8 +13,7 @@ import {
13
13
  XrpcAuthenticationError,
14
14
  XrpcInvalidResponseError,
15
15
  XrpcResponseError,
16
- XrpcUpstreamError,
17
- isXrpcErrorPayload,
16
+ XrpcResponseValidationError,
18
17
  } from './errors.js'
19
18
  import {
20
19
  EncodingString,
@@ -58,7 +57,7 @@ export type XrpcResponseBody<M extends Procedure | Query> =
58
57
  M['output'] extends Payload<infer TEncoding, infer TSchema>
59
58
  ? TEncoding extends string
60
59
  ? InferBodyType<TEncoding, TSchema>
61
- : undefined
60
+ : undefined | LexValue | Uint8Array
62
61
  : never
63
62
 
64
63
  /**
@@ -75,7 +74,9 @@ export type XrpcResponsePayload<M extends Procedure | Query> =
75
74
  encoding: InferEncodingType<TEncoding>
76
75
  body: InferBodyType<TEncoding, TSchema>
77
76
  }
78
- : undefined
77
+ : // If the schema does not specify an output encoding, anything could be
78
+ // returned, including no payload at all (undefined).
79
+ undefined | { body: LexValue | Uint8Array; encoding: string }
79
80
  : never
80
81
 
81
82
  export type XrpcResponseOptions = {
@@ -170,7 +171,7 @@ export class XrpcResponse<M extends Procedure | Query>
170
171
  * {@link XrpcResponseError.matchesSchemaErrors} to narrow the error type based on
171
172
  * the method's declared error schema. This can be narrowed further as a
172
173
  * {@link XrpcAuthenticationError} if the error is an authentication error.
173
- * @throws {XrpcUpstreamError} when the response is not a valid XRPC
174
+ * @throws {XrpcInvalidResponseError} when the response is not a valid XRPC
174
175
  * response, or if the response does not conform to the method's schema.
175
176
  */
176
177
  static async fromFetchResponse<const M extends Procedure | Query>(
@@ -182,63 +183,62 @@ export class XrpcResponse<M extends Procedure | Query>
182
183
  // Since nothing should cause an exception before "readPayload" is
183
184
  // called, we can safely not use a try/finally here.
184
185
 
185
- // @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here
186
- if (response.status < 200 || response.status >= 300) {
187
- // Always parse json for error responses
186
+ // Always turn 4xx/5xx responses into XrpcResponseError
187
+ if (response.status >= 400) {
188
188
  const payload = await readPayload(method, response, {
189
- parse: { strict: options?.strictResponseProcessing ?? true },
189
+ // Always parse errors in non-strict mode
190
+ parse: { strict: false },
190
191
  })
191
192
 
192
- // Properly formatted XRPC error response ?
193
- if (response.status >= 400 && isXrpcErrorPayload(payload)) {
194
- throw response.status === 401
195
- ? new XrpcAuthenticationError<M>(method, response, payload)
196
- : new XrpcResponseError<M>(method, response, payload)
193
+ if (response.status === 401) {
194
+ throw new XrpcAuthenticationError<M>(method, response, payload)
197
195
  }
198
196
 
199
- // Invalid XRPC response (we probably did not hit an XRPC implementation)
200
- throw new XrpcUpstreamError(
197
+ throw new XrpcResponseError<M>(method, response, payload)
198
+ }
199
+
200
+ // @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here
201
+ if (response.status < 200 || response.status >= 300) {
202
+ await response.body?.cancel()
203
+
204
+ throw new XrpcInvalidResponseError(
201
205
  method,
202
206
  response,
203
- payload,
204
- response.status >= 500
205
- ? 'Upstream server encountered an error'
206
- : response.status >= 400
207
- ? 'Invalid response payload'
208
- : 'Invalid response status code',
207
+ undefined,
208
+ `Unexpected status code ${response.status}`,
209
209
  )
210
210
  }
211
211
 
212
212
  const payload = await readPayload(method, response, {
213
- // Only parse json if the schema expects it
214
- parse: method.output.encoding === CONTENT_TYPE_JSON && {
215
- strict: options?.strictResponseProcessing ?? true,
216
- },
213
+ // Parse response if there is a schema, or if the encoding is
214
+ // "application/json"
215
+ parse:
216
+ method.output.schema || method.output.encoding === CONTENT_TYPE_JSON
217
+ ? { strict: options?.strictResponseProcessing ?? true }
218
+ : // If there is no declared output encoding, we'll parse the output (in loose mode)
219
+ method.output.encoding == null
220
+ ? { strict: false }
221
+ : false,
217
222
  })
218
223
 
224
+ if (!method.output.matchesEncoding(payload?.encoding)) {
225
+ throw new XrpcInvalidResponseError(
226
+ method,
227
+ response,
228
+ payload,
229
+ `Expected ${stringifyEncoding(method.output.encoding)} response (got ${stringifyEncoding(payload?.encoding)})`,
230
+ )
231
+ }
232
+
219
233
  // Response is successful (2xx). Validate payload (data and encoding) against schema.
220
- if (method.output.encoding == null) {
221
- // Schema expects no payload
222
- if (payload) {
223
- throw new XrpcUpstreamError(
224
- method,
225
- response,
226
- payload,
227
- `Expected response with no body, got ${payload.encoding}`,
228
- )
229
- }
230
- } else {
231
- // Schema expects a payload
232
- if (!payload || !method.output.matchesEncoding(payload.encoding)) {
233
- throw new XrpcUpstreamError(
234
- method,
235
- response,
236
- payload,
237
- payload
238
- ? `Expected ${method.output.encoding} response, got ${payload.encoding}`
239
- : `Expected non-empty response with content-type ${method.output.encoding}`,
240
- )
241
- }
234
+ if (method.output.encoding != null) {
235
+ // If the schema specifies an output, verify that the response properly
236
+ // matches the expected format (encoding and schema, if present). If no
237
+ // output is specified, any payload could be returned.
238
+
239
+ // Needed for type safety. Should never happen since matchesEncoding()
240
+ // should return not succeed if there is a schema encoding but no payload.
241
+ if (!payload) throw new Error('Expected payload')
242
242
 
243
243
  // Assert valid response body.
244
244
  if (method.output.schema && options?.validateResponse !== false) {
@@ -247,7 +247,7 @@ export class XrpcResponse<M extends Procedure | Query>
247
247
  })
248
248
 
249
249
  if (!result.success) {
250
- throw new XrpcInvalidResponseError(
250
+ throw new XrpcResponseValidationError(
251
251
  method,
252
252
  response,
253
253
  payload,
@@ -343,12 +343,16 @@ async function readPayload(
343
343
  } catch (cause) {
344
344
  const message = 'Unable to parse response payload'
345
345
  const messageDetail = cause instanceof TypeError ? cause.message : undefined
346
- throw new XrpcUpstreamError(
346
+ throw new XrpcInvalidResponseError(
347
347
  method,
348
348
  response,
349
- null,
349
+ undefined,
350
350
  messageDetail ? `${message}: ${messageDetail}` : message,
351
351
  { cause },
352
352
  )
353
353
  }
354
354
  }
355
+
356
+ function stringifyEncoding(encoding: string | undefined) {
357
+ return encoding ? `"${encoding}"` : 'no payload'
358
+ }
package/src/xrpc.test.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  XrpcInternalError,
10
10
  XrpcInvalidResponseError,
11
11
  XrpcResponseError,
12
- XrpcUpstreamError,
12
+ XrpcResponseValidationError,
13
13
  } from './errors.js'
14
14
  import { XrpcResponse } from './response.js'
15
15
  import { xrpc, xrpcSafe } from './xrpc.js'
@@ -221,6 +221,17 @@ describe(xrpc, () => {
221
221
  expect(response.success).toBe(true)
222
222
  expect(response.body).toEqual({ value: 'ok' })
223
223
  })
224
+
225
+ it('ignores output for no-output queries', async () => {
226
+ const fetchHandler = vi.fn<FetchHandler>(async () => {
227
+ return Response.json({ unexpected: 'data' })
228
+ })
229
+
230
+ const response = await xrpc(fetchHandler, testNoOutputQuery)
231
+
232
+ expect(response.success).toBe(true)
233
+ expect(response.body).toStrictEqual({ unexpected: 'data' })
234
+ })
224
235
  })
225
236
 
226
237
  describe('error handling', () => {
@@ -271,7 +282,7 @@ describe(xrpc, () => {
271
282
  ).rejects.toSatisfy((err) => {
272
283
  assert(err instanceof XrpcResponseError)
273
284
  expect(err.status).toBe(400)
274
- expect(err.body).toEqual({
285
+ expect(err.toJSON()).toEqual({
275
286
  error: 'TestError',
276
287
  message: 'bad request',
277
288
  })
@@ -297,7 +308,7 @@ describe(xrpc, () => {
297
308
  })
298
309
  })
299
310
 
300
- it('throws XrpcUpstreamError for non-XRPC error response', async () => {
311
+ it('throws XrpcResponseError for non-XRPC error response', async () => {
301
312
  const fetchHandler = vi.fn<FetchHandler>(async () => {
302
313
  return new Response('Not Found', {
303
314
  status: 404,
@@ -308,13 +319,13 @@ describe(xrpc, () => {
308
319
  await expect(
309
320
  xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
310
321
  ).rejects.toSatisfy((err) => {
311
- assert(err instanceof XrpcUpstreamError)
312
- expect(err.message).toBe('Invalid response payload')
322
+ assert(err instanceof XrpcResponseError)
323
+ expect(err.message).toBe('Upstream server responded with a 404 error')
313
324
  return true
314
325
  })
315
326
  })
316
327
 
317
- it('throws XrpcUpstreamError for 500 without valid error payload', async () => {
328
+ it('throws XrpcResponseError for 500 without valid error payload', async () => {
318
329
  const fetchHandler = vi.fn<FetchHandler>(async () => {
319
330
  return new Response('Internal Server Error', {
320
331
  status: 500,
@@ -325,8 +336,8 @@ describe(xrpc, () => {
325
336
  await expect(
326
337
  xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
327
338
  ).rejects.toSatisfy((err) => {
328
- assert(err instanceof XrpcUpstreamError)
329
- expect(err.message).toBe('Upstream server encountered an error')
339
+ assert(err instanceof XrpcResponseError)
340
+ expect(err.message).toBe('Upstream server responded with a 500 error')
330
341
  return true
331
342
  })
332
343
  })
@@ -344,7 +355,7 @@ describe(xrpc, () => {
344
355
  ).rejects.toSatisfy((err) => {
345
356
  assert(err instanceof XrpcResponseError)
346
357
  expect(err.status).toBe(502)
347
- expect(err.body).toEqual({
358
+ expect(err.toJSON()).toEqual({
348
359
  error: 'ServerError',
349
360
  message: 'Something went wrong',
350
361
  })
@@ -354,7 +365,7 @@ describe(xrpc, () => {
354
365
  })
355
366
 
356
367
  describe('invalid response errors', () => {
357
- it('throws XrpcInvalidResponseError when response body fails validation', async () => {
368
+ it('throws XrpcResponseValidationError when response body fails validation', async () => {
358
369
  // Schema expects { value: string } but we return { value: 123 }
359
370
  const fetchHandler = vi.fn<FetchHandler>(async () => {
360
371
  return Response.json({ value: 123 })
@@ -363,14 +374,14 @@ describe(xrpc, () => {
363
374
  await expect(
364
375
  xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
365
376
  ).rejects.toSatisfy((err) => {
366
- assert(err instanceof XrpcInvalidResponseError)
367
- expect(err).toBeInstanceOf(XrpcUpstreamError)
377
+ assert(err instanceof XrpcResponseValidationError)
378
+ expect(err).toBeInstanceOf(XrpcInvalidResponseError)
368
379
  expect(err.cause).toBeInstanceOf(Error)
369
380
  return true
370
381
  })
371
382
  })
372
383
 
373
- it('throws XrpcUpstreamError when response has wrong content-type', async () => {
384
+ it('throws XrpcInvalidResponseError when response has wrong content-type', async () => {
374
385
  const fetchHandler = vi.fn<FetchHandler>(async () => {
375
386
  return new Response('binary data', {
376
387
  status: 200,
@@ -381,7 +392,7 @@ describe(xrpc, () => {
381
392
  await expect(
382
393
  xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
383
394
  ).rejects.toSatisfy((err) => {
384
- assert(err instanceof XrpcUpstreamError)
395
+ assert(err instanceof XrpcInvalidResponseError)
385
396
  expect(err.message).toContain('application/json')
386
397
  return true
387
398
  })
@@ -408,7 +419,7 @@ describe(xrpc, () => {
408
419
  })
409
420
 
410
421
  describe('response payload parsing', () => {
411
- it('throws XrpcUpstreamError when error response body cannot be parsed', async () => {
422
+ it('throws XrpcInvalidResponseError when error response body cannot be parsed', async () => {
412
423
  const fetchHandler = vi.fn<FetchHandler>(async () => {
413
424
  return new Response('not valid json', {
414
425
  status: 400,
@@ -419,7 +430,7 @@ describe(xrpc, () => {
419
430
  await expect(
420
431
  xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
421
432
  ).rejects.toSatisfy((err) => {
422
- assert(err instanceof XrpcUpstreamError)
433
+ assert(err instanceof XrpcInvalidResponseError)
423
434
  expect(err.message).toMatch('Unable to parse response payload')
424
435
  assert(err.cause instanceof Error)
425
436
  expect(err.cause.message).toContain('Unexpected token')
@@ -427,7 +438,7 @@ describe(xrpc, () => {
427
438
  })
428
439
  })
429
440
 
430
- it('throws XrpcUpstreamError when success response body cannot be parsed', async () => {
441
+ it('throws XrpcInvalidResponseError when success response body cannot be parsed', async () => {
431
442
  const fetchHandler = vi.fn<FetchHandler>(async () => {
432
443
  return new Response('not valid json', {
433
444
  status: 200,
@@ -438,7 +449,7 @@ describe(xrpc, () => {
438
449
  await expect(
439
450
  xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
440
451
  ).rejects.toSatisfy((err) => {
441
- assert(err instanceof XrpcUpstreamError)
452
+ assert(err instanceof XrpcInvalidResponseError)
442
453
  expect(err.message).toMatch('Unable to parse response payload')
443
454
  assert(err.cause instanceof Error)
444
455
  expect(err.cause.message).toContain('Unexpected token')
@@ -446,21 +457,7 @@ describe(xrpc, () => {
446
457
  })
447
458
  })
448
459
 
449
- it('throws XrpcUpstreamError when schema expects no payload but got one', async () => {
450
- const fetchHandler = vi.fn<FetchHandler>(async () => {
451
- return Response.json({ unexpected: 'data' })
452
- })
453
-
454
- await expect(xrpc(fetchHandler, testNoOutputQuery)).rejects.toSatisfy(
455
- (err) => {
456
- assert(err instanceof XrpcUpstreamError)
457
- expect(err.message).toContain('no body')
458
- return true
459
- },
460
- )
461
- })
462
-
463
- it('throws XrpcUpstreamError when schema expects payload but response is empty', async () => {
460
+ it('throws XrpcInvalidResponseError when schema expects payload but response is empty', async () => {
464
461
  const fetchHandler = vi.fn<FetchHandler>(async () => {
465
462
  return new Response(null, { status: 200 })
466
463
  })
@@ -468,8 +465,8 @@ describe(xrpc, () => {
468
465
  await expect(
469
466
  xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
470
467
  ).rejects.toSatisfy((err) => {
471
- assert(err instanceof XrpcUpstreamError)
472
- expect(err.message).toContain('non-empty response')
468
+ assert(err instanceof XrpcInvalidResponseError)
469
+ expect(err.message).toContain('got no payload')
473
470
  return true
474
471
  })
475
472
  })
@@ -516,15 +513,15 @@ describe(xrpc, () => {
516
513
  })
517
514
 
518
515
  describe('non-2xx non-4xx/5xx responses', () => {
519
- it('throws XrpcUpstreamError for 3xx status codes', async () => {
516
+ it('throws XrpcInvalidResponseError for 3xx status codes', async () => {
520
517
  const fetchHandler: FetchHandler = async () =>
521
518
  Response.json({ value: 'redirect' }, { status: 302 })
522
519
 
523
520
  await expect(
524
521
  xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
525
522
  ).rejects.toSatisfy((err) => {
526
- assert(err instanceof XrpcUpstreamError)
527
- expect(err.message).toBe('Invalid response status code')
523
+ assert(err instanceof XrpcInvalidResponseError)
524
+ expect(err.message).toBe('Unexpected status code 302')
528
525
  return true
529
526
  })
530
527
  })
@@ -606,8 +603,8 @@ describe(xrpc, () => {
606
603
  await expect(
607
604
  xrpc(fetchHandler, testQuery, { params: { limit: 10 } }),
608
605
  ).rejects.toSatisfy((err) => {
609
- assert(err instanceof XrpcInvalidResponseError)
610
- expect(err).toBeInstanceOf(XrpcUpstreamError)
606
+ assert(err instanceof XrpcResponseValidationError)
607
+ expect(err).toBeInstanceOf(XrpcInvalidResponseError)
611
608
  return true
612
609
  })
613
610
  })
@@ -660,7 +657,7 @@ describe(xrpc, () => {
660
657
  await expect(
661
658
  xrpc(validWithFloatHandler, testQuery, { params: { limit: 10 } }),
662
659
  ).rejects.toSatisfy((err) => {
663
- assert(err instanceof XrpcUpstreamError)
660
+ assert(err instanceof XrpcInvalidResponseError)
664
661
  expect(err.message).toMatch('Unable to parse response payload')
665
662
  expect(err.cause).toBeInstanceOf(TypeError)
666
663
  return true
@@ -683,7 +680,7 @@ describe(xrpc, () => {
683
680
  strictResponseProcessing: true,
684
681
  }),
685
682
  ).rejects.toSatisfy((err) => {
686
- assert(err instanceof XrpcUpstreamError)
683
+ assert(err instanceof XrpcInvalidResponseError)
687
684
  expect(err.message).toMatch('Unable to parse response payload')
688
685
  expect(err.cause).toBeInstanceOf(TypeError)
689
686
  return true
@@ -693,8 +690,8 @@ describe(xrpc, () => {
693
690
  it('rejects error response with invalid lex data by default', async () => {
694
691
  await expect(xrpc(errorWithFloatHandler, testQuery)).rejects.toSatisfy(
695
692
  (err) => {
696
- assert(err instanceof XrpcUpstreamError)
697
- expect(err.message).toMatch('Unable to parse response payload')
693
+ assert(err instanceof XrpcResponseError)
694
+ expect(err.message).toBe('test-error-description')
698
695
  return true
699
696
  },
700
697
  )
@@ -710,7 +707,7 @@ describe(xrpc, () => {
710
707
  // Error response is still an error, but it should be parsed successfully
711
708
  assert(err instanceof XrpcResponseError)
712
709
  expect(err.status).toBe(400)
713
- expect(err.payload.body).toEqual({
710
+ expect(err.payload?.body).toEqual({
714
711
  error: 'TestError',
715
712
  message: 'test-error-description',
716
713
  extra: 1.5,
@@ -731,8 +728,8 @@ describe(xrpc, () => {
731
728
  validateResponse: true,
732
729
  }),
733
730
  ).rejects.toSatisfy((err) => {
734
- assert(err instanceof XrpcInvalidResponseError)
735
- expect(err).toBeInstanceOf(XrpcUpstreamError)
731
+ assert(err instanceof XrpcResponseValidationError)
732
+ expect(err).toBeInstanceOf(XrpcInvalidResponseError)
736
733
  return true
737
734
  })
738
735
  })
@@ -778,7 +775,7 @@ describe(xrpc, () => {
778
775
  validateResponse: false,
779
776
  }),
780
777
  ).rejects.toSatisfy((err) => {
781
- assert(err instanceof XrpcUpstreamError)
778
+ assert(err instanceof XrpcInvalidResponseError)
782
779
  expect(err.message).toMatch('Unable to parse response payload')
783
780
  return true
784
781
  })
@@ -925,8 +922,8 @@ describe(xrpcSafe, () => {
925
922
 
926
923
  assert(!result.success)
927
924
  assert(result instanceof XrpcResponseError)
928
- expect(result.status).toBe(400)
929
- expect(result.body).toEqual({
925
+ expect(result.response.status).toBe(400)
926
+ expect(result.toJSON()).toEqual({
930
927
  error: 'TestError',
931
928
  message: 'bad request',
932
929
  })
@@ -946,10 +943,10 @@ describe(xrpcSafe, () => {
946
943
  assert(!result.success)
947
944
  assert(result instanceof XrpcResponseError)
948
945
  expect(result).toBeInstanceOf(XrpcAuthenticationError)
949
- expect(result.status).toBe(401)
946
+ expect(result.response.status).toBe(401)
950
947
  })
951
948
 
952
- it('returns XrpcUpstreamError for non-XRPC error response', async () => {
949
+ it('returns XrpcResponseError for non-XRPC error response', async () => {
953
950
  const fetchHandler: FetchHandler = async () =>
954
951
  new Response('Not Found', {
955
952
  status: 404,
@@ -961,10 +958,10 @@ describe(xrpcSafe, () => {
961
958
  })
962
959
 
963
960
  assert(!result.success)
964
- expect(result).toBeInstanceOf(XrpcUpstreamError)
961
+ expect(result).toBeInstanceOf(XrpcResponseError)
965
962
  })
966
963
 
967
- it('returns XrpcUpstreamError for 500 without valid error payload', async () => {
964
+ it('returns XrpcResponseError for 500 without valid error payload', async () => {
968
965
  const fetchHandler: FetchHandler = async () =>
969
966
  new Response('Internal Server Error', {
970
967
  status: 500,
@@ -976,12 +973,16 @@ describe(xrpcSafe, () => {
976
973
  })
977
974
 
978
975
  assert(!result.success)
979
- expect(result).toBeInstanceOf(XrpcUpstreamError)
976
+ expect(result).toBeInstanceOf(XrpcResponseError)
977
+ expect(result.error).toBe('InternalServerError')
978
+ expect(result.message).toMatch(
979
+ 'Upstream server responded with a 500 error',
980
+ )
980
981
  })
981
982
  })
982
983
 
983
984
  describe('invalid response errors', () => {
984
- it('returns XrpcInvalidResponseError when response body fails validation', async () => {
985
+ it('returns XrpcResponseValidationError when response body fails validation', async () => {
985
986
  const fetchHandler: FetchHandler = async () =>
986
987
  Response.json({ value: 123 })
987
988
 
@@ -990,11 +991,11 @@ describe(xrpcSafe, () => {
990
991
  })
991
992
 
992
993
  assert(!result.success)
994
+ expect(result).toBeInstanceOf(XrpcResponseValidationError)
993
995
  expect(result).toBeInstanceOf(XrpcInvalidResponseError)
994
- expect(result).toBeInstanceOf(XrpcUpstreamError)
995
996
  })
996
997
 
997
- it('returns XrpcUpstreamError when response has wrong content-type', async () => {
998
+ it('returns XrpcInvalidResponseError when response has wrong content-type', async () => {
998
999
  const fetchHandler: FetchHandler = async () =>
999
1000
  new Response('binary data', {
1000
1001
  status: 200,
@@ -1006,7 +1007,7 @@ describe(xrpcSafe, () => {
1006
1007
  })
1007
1008
 
1008
1009
  assert(!result.success)
1009
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1010
+ expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1010
1011
  })
1011
1012
  })
1012
1013
 
@@ -1042,6 +1043,7 @@ describe(xrpcSafe, () => {
1042
1043
  assert(!result.success)
1043
1044
  expect(result).toBeInstanceOf(XrpcInternalError)
1044
1045
  expect(result).not.toBeInstanceOf(XrpcFetchError)
1046
+ expect(fetchHandler).not.toHaveBeenCalled()
1045
1047
  })
1046
1048
 
1047
1049
  it('returns XrpcInternalError for invalid body when enabled', async () => {
@@ -1088,7 +1090,7 @@ describe(xrpcSafe, () => {
1088
1090
  })
1089
1091
 
1090
1092
  describe('validateResponse', () => {
1091
- it('returns XrpcInvalidResponseError for invalid body by default', async () => {
1093
+ it('returns XrpcResponseValidationError for invalid body by default', async () => {
1092
1094
  const fetchHandler = vi.fn<FetchHandler>(async () => {
1093
1095
  return Response.json({ value: 123 })
1094
1096
  })
@@ -1098,8 +1100,8 @@ describe(xrpcSafe, () => {
1098
1100
  })
1099
1101
 
1100
1102
  assert(!result.success)
1103
+ expect(result).toBeInstanceOf(XrpcResponseValidationError)
1101
1104
  expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1102
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1103
1105
  })
1104
1106
 
1105
1107
  it('accepts invalid response body when disabled', async () => {
@@ -1164,7 +1166,7 @@ describe(xrpcSafe, () => {
1164
1166
 
1165
1167
  const result = await xrpcSafe(fetchHandler, testQueryGetBlobRef)
1166
1168
  assert(!result.success)
1167
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1169
+ expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1168
1170
  expect(result.message).toMatch('Unable to parse response payload')
1169
1171
  assert(result.cause instanceof TypeError)
1170
1172
  expect(result.cause.message).toBe('Invalid blob object')
@@ -1187,7 +1189,7 @@ describe(xrpcSafe, () => {
1187
1189
 
1188
1190
  const result = await xrpcSafe(fetchHandler, testQueryGetBlobRef)
1189
1191
  assert(!result.success)
1190
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1192
+ expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1191
1193
  expect(result.message).toMatch('Unable to parse response payload')
1192
1194
  assert(result.cause instanceof TypeError)
1193
1195
  expect(result.cause.message).toBe('Invalid blob object')
@@ -1209,9 +1211,9 @@ describe(xrpcSafe, () => {
1209
1211
 
1210
1212
  const result = await xrpcSafe(fetchHandler, testQueryGetBlobRef)
1211
1213
  assert(!result.success)
1212
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1214
+ expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1213
1215
  expect(result.message).toBe(
1214
- 'Invalid response: Expected "image/png" (got "invalid/mime") at $.blobRef.mimeType',
1216
+ 'Invalid response payload: Expected "image/png" (got "invalid/mime") at $.blobRef.mimeType',
1215
1217
  )
1216
1218
  })
1217
1219
 
@@ -1231,9 +1233,9 @@ describe(xrpcSafe, () => {
1231
1233
 
1232
1234
  const result = await xrpcSafe(fetchHandler, testQueryGetBlobRef)
1233
1235
  assert(!result.success)
1234
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1236
+ expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1235
1237
  expect(result.message).toBe(
1236
- 'Invalid response: blob too big (maximum 10, got 100) at $.blobRef',
1238
+ 'Invalid response payload: blob too big (maximum 10, got 100) at $.blobRef',
1237
1239
  )
1238
1240
  })
1239
1241
 
@@ -1342,7 +1344,7 @@ describe(xrpcSafe, () => {
1342
1344
  })
1343
1345
 
1344
1346
  assert(!result.success)
1345
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1347
+ expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1346
1348
  expect(result.message).toMatch('Unable to parse response payload')
1347
1349
  assert(result.cause instanceof TypeError)
1348
1350
  expect(result.cause.message).toBe('Invalid non-integer number: 1.5')
@@ -1369,7 +1371,7 @@ describe(xrpcSafe, () => {
1369
1371
  })
1370
1372
 
1371
1373
  assert(!result.success)
1372
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1374
+ expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1373
1375
  expect(result.message).toMatch('Unable to parse response payload')
1374
1376
  assert(result.cause instanceof TypeError)
1375
1377
  expect(result.cause.message).toBe('Invalid non-integer number: 1.5')
@@ -1383,10 +1385,8 @@ describe(xrpcSafe, () => {
1383
1385
  })
1384
1386
 
1385
1387
  assert(!result.success)
1386
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1387
- expect(result.message).toMatch('Unable to parse response payload')
1388
- assert(result.cause instanceof TypeError)
1389
- expect(result.cause.message).toBe('Invalid non-integer number: 1.5')
1388
+ expect(result).toBeInstanceOf(XrpcResponseError)
1389
+ expect(result.message).toBe('test-error-description')
1390
1390
  })
1391
1391
 
1392
1392
  it('parses error response with invalid lex data when strict processing is disabled', async () => {
@@ -1399,7 +1399,7 @@ describe(xrpcSafe, () => {
1399
1399
 
1400
1400
  assert(!result.success)
1401
1401
  assert(result instanceof XrpcResponseError)
1402
- expect(result.status).toBe(400)
1402
+ expect(result.response.status).toBe(400)
1403
1403
  expect(result.message).toBe('test-error-description')
1404
1404
  })
1405
1405
 
@@ -1415,8 +1415,8 @@ describe(xrpcSafe, () => {
1415
1415
  })
1416
1416
 
1417
1417
  assert(!result.success)
1418
+ expect(result).toBeInstanceOf(XrpcResponseValidationError)
1418
1419
  expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1419
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1420
1420
  })
1421
1421
 
1422
1422
  it('with strictResponseProcessing: false and validateResponse: false, schema validation is skipped', async () => {
@@ -1444,7 +1444,7 @@ describe(xrpcSafe, () => {
1444
1444
  })
1445
1445
 
1446
1446
  assert(!result.success)
1447
- expect(result).toBeInstanceOf(XrpcUpstreamError)
1447
+ expect(result).toBeInstanceOf(XrpcInvalidResponseError)
1448
1448
  expect(result.message).toMatch('Unable to parse response payload')
1449
1449
  assert(result.cause instanceof TypeError)
1450
1450
  expect(result.cause.message).toBe('Invalid non-integer number: 1.5')
package/src/xrpc.ts CHANGED
@@ -360,9 +360,7 @@ function buildPayload(
360
360
  ): null | { body: BodyInit; encoding: string } {
361
361
  if (schema.encoding === undefined) {
362
362
  if (body !== undefined) {
363
- throw new TypeError(
364
- `Cannot send a ${typeof body} body with undefined encoding`,
365
- )
363
+ throw new TypeError(`Endpoint expects no payload`)
366
364
  }
367
365
 
368
366
  return null