@atproto/lex-client 0.0.16 → 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/CHANGELOG.md +32 -0
- package/dist/client.d.ts +24 -21
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -7
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +22 -22
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +62 -37
- package/dist/errors.js.map +1 -1
- package/dist/response.d.ts +66 -7
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +90 -69
- package/dist/response.js.map +1 -1
- package/dist/types.d.ts +8 -37
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +14 -27
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +15 -6
- package/dist/util.js.map +1 -1
- package/dist/xrpc.d.ts +40 -15
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +4 -2
- package/dist/xrpc.js.map +1 -1
- package/package.json +6 -6
- package/src/client.ts +83 -31
- package/src/errors.test.ts +243 -32
- package/src/errors.ts +91 -52
- package/src/response.ts +229 -102
- package/src/types.ts +17 -40
- package/src/util.test.ts +11 -11
- package/src/util.ts +33 -36
- package/src/xrpc.test.ts +691 -142
- package/src/xrpc.ts +73 -29
package/src/errors.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
LexError,
|
|
3
|
+
LexErrorCode,
|
|
4
|
+
LexErrorData,
|
|
5
|
+
LexValue,
|
|
6
|
+
} from '@atproto/lex-data'
|
|
2
7
|
import {
|
|
3
8
|
InferMethodError,
|
|
4
9
|
LexValidationError,
|
|
@@ -9,12 +14,36 @@ import {
|
|
|
9
14
|
} from '@atproto/lex-schema'
|
|
10
15
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
11
16
|
import { Agent } from './agent.js'
|
|
12
|
-
import {
|
|
17
|
+
import { XrpcUnknownResponsePayload } from './types.js'
|
|
13
18
|
import {
|
|
14
19
|
WWWAuthenticate,
|
|
15
20
|
parseWWWAuthenticateHeader,
|
|
16
21
|
} from './www-authenticate.js'
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Mapping that allows generating an XRPC error code from an HTTP status code
|
|
25
|
+
* when the response does not contain a valid XRPC error payload. This is used
|
|
26
|
+
* to convert non-XRPC error responses from upstream servers into a standardized
|
|
27
|
+
* XRPC error for downstream clients.
|
|
28
|
+
*/
|
|
29
|
+
const StatusErrorCodes = new Map<number, LexErrorCode>([
|
|
30
|
+
[400, 'InvalidRequest'],
|
|
31
|
+
[401, 'AuthenticationRequired'],
|
|
32
|
+
[403, 'Forbidden'],
|
|
33
|
+
[404, 'XRPCNotSupported'],
|
|
34
|
+
[406, 'NotAcceptable'],
|
|
35
|
+
[413, 'PayloadTooLarge'],
|
|
36
|
+
[415, 'UnsupportedMediaType'],
|
|
37
|
+
[429, 'RateLimitExceeded'],
|
|
38
|
+
[500, 'InternalServerError'],
|
|
39
|
+
[501, 'MethodNotImplemented'],
|
|
40
|
+
[502, 'UpstreamFailure'],
|
|
41
|
+
[503, 'NotEnoughResources'],
|
|
42
|
+
[504, 'UpstreamTimeout'],
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
export type { XrpcUnknownResponsePayload }
|
|
46
|
+
|
|
18
47
|
export type DownstreamError<N extends LexErrorCode = LexErrorCode> = {
|
|
19
48
|
status: number
|
|
20
49
|
headers?: Headers
|
|
@@ -68,7 +97,7 @@ export type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> = {
|
|
|
68
97
|
* This function checks whether a given payload matches this schema.
|
|
69
98
|
*/
|
|
70
99
|
export function isXrpcErrorPayload(
|
|
71
|
-
payload:
|
|
100
|
+
payload: XrpcUnknownResponsePayload | null | undefined,
|
|
72
101
|
): payload is XrpcErrorPayload {
|
|
73
102
|
return (
|
|
74
103
|
payload != null &&
|
|
@@ -88,7 +117,7 @@ export function isXrpcErrorPayload(
|
|
|
88
117
|
* @typeParam TReason - The reason type for ResultFailure
|
|
89
118
|
*
|
|
90
119
|
* @see {@link XrpcResponseError} - For valid XRPC error responses
|
|
91
|
-
* @see {@link
|
|
120
|
+
* @see {@link XrpcInvalidResponseError} - For invalid/unexpected responses
|
|
92
121
|
* @see {@link XrpcInternalError} - For network/internal errors
|
|
93
122
|
*/
|
|
94
123
|
export abstract class XrpcError<
|
|
@@ -158,17 +187,23 @@ export abstract class XrpcError<
|
|
|
158
187
|
*/
|
|
159
188
|
export class XrpcResponseError<
|
|
160
189
|
M extends Procedure | Query = Procedure | Query,
|
|
161
|
-
|
|
162
|
-
> extends XrpcError<M, N, XrpcResponseError<M, N>> {
|
|
190
|
+
> extends XrpcError<M, LexErrorCode, XrpcResponseError<M>> {
|
|
163
191
|
name = 'XrpcResponseError'
|
|
164
192
|
|
|
165
193
|
constructor(
|
|
166
194
|
method: M,
|
|
167
195
|
readonly response: Response,
|
|
168
|
-
readonly payload
|
|
196
|
+
readonly payload?: XrpcUnknownResponsePayload,
|
|
169
197
|
options?: ErrorOptions,
|
|
170
198
|
) {
|
|
171
|
-
const { error, message } = payload
|
|
199
|
+
const { error, message } = isXrpcErrorPayload(payload)
|
|
200
|
+
? payload.body
|
|
201
|
+
: {
|
|
202
|
+
error:
|
|
203
|
+
StatusErrorCodes.get(response.status) ??
|
|
204
|
+
(response.status >= 500 ? 'UpstreamFailure' : 'InvalidRequest'),
|
|
205
|
+
message: buildResponseOverviewMessage(response),
|
|
206
|
+
}
|
|
172
207
|
super(method, error, message, options)
|
|
173
208
|
}
|
|
174
209
|
|
|
@@ -180,19 +215,27 @@ export class XrpcResponseError<
|
|
|
180
215
|
return RETRYABLE_HTTP_STATUS_CODES.has(this.response.status)
|
|
181
216
|
}
|
|
182
217
|
|
|
183
|
-
override toJSON(): LexErrorData
|
|
184
|
-
|
|
218
|
+
override toJSON(): LexErrorData {
|
|
219
|
+
// Return the original error payload if it's a valid XRPC error, otherwise
|
|
220
|
+
// convert to an XRPC error format.
|
|
221
|
+
const { payload } = this
|
|
222
|
+
if (isXrpcErrorPayload(payload)) {
|
|
223
|
+
return payload.body
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return super.toJSON()
|
|
185
227
|
}
|
|
186
228
|
|
|
187
229
|
override toDownstreamError(): DownstreamError {
|
|
188
|
-
|
|
230
|
+
const { status, headers } = this.response
|
|
231
|
+
// If the upstream server returned a 500 error, we want to return a 502 Bad
|
|
189
232
|
// Gateway to downstream clients, as the issue is with the upstream server,
|
|
190
233
|
// not us. We still return the original error code and message in the body
|
|
191
234
|
// for transparency, but we do not want to expose internal server errors
|
|
192
235
|
// from the upstream server as-is to downstream clients.
|
|
193
236
|
return {
|
|
194
|
-
status:
|
|
195
|
-
headers: stripHopByHopHeaders(
|
|
237
|
+
status: status === 500 ? 502 : status,
|
|
238
|
+
headers: stripHopByHopHeaders(headers),
|
|
196
239
|
body: this.toJSON(),
|
|
197
240
|
}
|
|
198
241
|
}
|
|
@@ -205,8 +248,8 @@ export class XrpcResponseError<
|
|
|
205
248
|
return this.response.headers
|
|
206
249
|
}
|
|
207
250
|
|
|
208
|
-
get body():
|
|
209
|
-
return this.payload
|
|
251
|
+
get body(): undefined | Uint8Array | LexValue {
|
|
252
|
+
return this.payload?.body
|
|
210
253
|
}
|
|
211
254
|
}
|
|
212
255
|
|
|
@@ -240,8 +283,7 @@ export type { WWWAuthenticate }
|
|
|
240
283
|
*/
|
|
241
284
|
export class XrpcAuthenticationError<
|
|
242
285
|
M extends Procedure | Query = Procedure | Query,
|
|
243
|
-
|
|
244
|
-
> extends XrpcResponseError<M, N> {
|
|
286
|
+
> extends XrpcResponseError<M> {
|
|
245
287
|
name = 'XrpcAuthenticationError'
|
|
246
288
|
|
|
247
289
|
override shouldRetry(): boolean {
|
|
@@ -259,14 +301,6 @@ export class XrpcAuthenticationError<
|
|
|
259
301
|
this.response.headers.get('www-authenticate'),
|
|
260
302
|
) ?? {})
|
|
261
303
|
}
|
|
262
|
-
|
|
263
|
-
override toDownstreamError(): DownstreamError {
|
|
264
|
-
return {
|
|
265
|
-
status: 401,
|
|
266
|
-
headers: stripHopByHopHeaders(this.headers),
|
|
267
|
-
body: this.toJSON(),
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
304
|
}
|
|
271
305
|
|
|
272
306
|
/**
|
|
@@ -279,24 +313,25 @@ export class XrpcAuthenticationError<
|
|
|
279
313
|
* - Non-JSON error responses
|
|
280
314
|
* - Responses from non-XRPC endpoints
|
|
281
315
|
*
|
|
282
|
-
* The error code is always '
|
|
283
|
-
* when converted to a response.
|
|
316
|
+
* The error code is always 'InvalidResponse' and maps to HTTP 502 Bad Gateway
|
|
317
|
+
* when converted to a response. This should allow downstream clients to
|
|
318
|
+
* determine at which boundary the error occurred.
|
|
284
319
|
*
|
|
285
320
|
* @typeParam M - The XRPC method type
|
|
286
321
|
*/
|
|
287
|
-
export class
|
|
322
|
+
export class XrpcInvalidResponseError<
|
|
288
323
|
M extends Procedure | Query = Procedure | Query,
|
|
289
|
-
> extends XrpcError<M, '
|
|
290
|
-
name = '
|
|
324
|
+
> extends XrpcError<M, 'InvalidResponse', XrpcInvalidResponseError<M>> {
|
|
325
|
+
name = 'XrpcInvalidResponseError'
|
|
291
326
|
|
|
292
327
|
constructor(
|
|
293
328
|
method: M,
|
|
294
329
|
readonly response: Response,
|
|
295
|
-
readonly payload
|
|
296
|
-
message: string =
|
|
330
|
+
readonly payload?: XrpcUnknownResponsePayload,
|
|
331
|
+
message: string = buildResponseOverviewMessage(response),
|
|
297
332
|
options?: ErrorOptions,
|
|
298
333
|
) {
|
|
299
|
-
super(method, '
|
|
334
|
+
super(method, 'InvalidResponse', message, options)
|
|
300
335
|
}
|
|
301
336
|
|
|
302
337
|
override get reason(): this {
|
|
@@ -315,7 +350,7 @@ export class XrpcUpstreamError<
|
|
|
315
350
|
/**
|
|
316
351
|
* Error class for invalid XRPC responses that fail schema validation.
|
|
317
352
|
*
|
|
318
|
-
* This is a specific type of {@link
|
|
353
|
+
* This is a specific type of {@link XrpcInvalidResponseError} that indicates the
|
|
319
354
|
* upstream server returned a response that was structurally valid but did not
|
|
320
355
|
* conform to the expected schema for the method. This likely indicates a
|
|
321
356
|
* mismatch between client and server versions or an issue with the server's
|
|
@@ -323,28 +358,24 @@ export class XrpcUpstreamError<
|
|
|
323
358
|
*
|
|
324
359
|
* @typeParam M - The XRPC method type
|
|
325
360
|
*/
|
|
326
|
-
export class
|
|
361
|
+
export class XrpcResponseValidationError<
|
|
327
362
|
M extends Procedure | Query = Procedure | Query,
|
|
328
|
-
> extends
|
|
329
|
-
name = '
|
|
363
|
+
> extends XrpcInvalidResponseError<M> {
|
|
364
|
+
name = 'XrpcResponseValidationError'
|
|
330
365
|
|
|
331
366
|
constructor(
|
|
332
367
|
method: M,
|
|
333
368
|
response: Response,
|
|
334
|
-
payload:
|
|
369
|
+
payload: XrpcUnknownResponsePayload,
|
|
335
370
|
readonly cause: LexValidationError,
|
|
336
371
|
) {
|
|
337
|
-
super(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
// ("they" are at fault). We are using 502 here to allow downstream clients
|
|
345
|
-
// to determine that the issue lies at the interface between us and the
|
|
346
|
-
// upstream server, rather than an issue with our internal processing.
|
|
347
|
-
return { status: 502, body: this.toJSON() }
|
|
372
|
+
super(
|
|
373
|
+
method,
|
|
374
|
+
response,
|
|
375
|
+
payload,
|
|
376
|
+
`Invalid response payload: ${cause.message}`,
|
|
377
|
+
{ cause },
|
|
378
|
+
)
|
|
348
379
|
}
|
|
349
380
|
}
|
|
350
381
|
|
|
@@ -446,7 +477,7 @@ export class XrpcFetchError<
|
|
|
446
477
|
* if (result.success) {
|
|
447
478
|
* console.log(result.body) // XrpcResponse
|
|
448
479
|
* } else {
|
|
449
|
-
* // result is XrpcFailure (XrpcResponseError |
|
|
480
|
+
* // result is XrpcFailure (XrpcResponseError | XrpcInvalidResponseError | XrpcInternalError)
|
|
450
481
|
* console.error(result.error, result.message)
|
|
451
482
|
* }
|
|
452
483
|
* ```
|
|
@@ -455,7 +486,7 @@ export type XrpcFailure<M extends Procedure | Query = Procedure | Query> =
|
|
|
455
486
|
// The server returned a valid XRPC error response
|
|
456
487
|
| XrpcResponseError<M>
|
|
457
488
|
// The response was not a valid XRPC response, or it does not match the schema
|
|
458
|
-
|
|
|
489
|
+
| XrpcInvalidResponseError<M>
|
|
459
490
|
// Something went wrong (network error, etc.)
|
|
460
491
|
| XrpcInternalError<M>
|
|
461
492
|
|
|
@@ -485,7 +516,7 @@ export function asXrpcFailure<M extends Procedure | Query>(
|
|
|
485
516
|
): XrpcFailure<M> {
|
|
486
517
|
if (
|
|
487
518
|
cause instanceof XrpcResponseError ||
|
|
488
|
-
cause instanceof
|
|
519
|
+
cause instanceof XrpcInvalidResponseError ||
|
|
489
520
|
cause instanceof XrpcInternalError
|
|
490
521
|
) {
|
|
491
522
|
if (cause.method === method) return cause
|
|
@@ -529,3 +560,11 @@ function stripHopByHopHeaders(headers: Headers): Headers {
|
|
|
529
560
|
|
|
530
561
|
return result
|
|
531
562
|
}
|
|
563
|
+
|
|
564
|
+
function buildResponseOverviewMessage(response: Response): string {
|
|
565
|
+
if (response.status < 400) {
|
|
566
|
+
return `Upstream server responded with an invalid status code (${response.status})`
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return `Upstream server responded with a ${response.status} error`
|
|
570
|
+
}
|