@atproto/lex-client 0.0.10 → 0.0.12
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/agent.d.ts +72 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +46 -1
- package/dist/agent.js.map +1 -1
- package/dist/client.d.ts +442 -46
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +145 -1
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +202 -48
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +208 -65
- package/dist/errors.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts +20 -20
- package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts +12 -12
- package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts +6 -6
- package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts +6 -6
- package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts +22 -22
- package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts +2 -2
- package/dist/response.d.ts +17 -6
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +45 -32
- package/dist/response.js.map +1 -1
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +40 -5
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +22 -0
- package/dist/util.js.map +1 -1
- package/dist/www-authenticate.d.ts +35 -0
- package/dist/www-authenticate.d.ts.map +1 -0
- package/dist/www-authenticate.js +57 -0
- package/dist/www-authenticate.js.map +1 -0
- package/dist/xrpc.d.ts +82 -10
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +15 -28
- package/dist/xrpc.js.map +1 -1
- package/package.json +7 -7
- package/src/agent.ts +101 -1
- package/src/client.ts +428 -15
- package/src/errors.ts +308 -120
- package/src/response.ts +68 -63
- package/src/types.ts +52 -0
- package/src/util.ts +50 -5
- package/src/www-authenticate.test.ts +227 -0
- package/src/www-authenticate.ts +101 -0
- package/src/xrpc.ts +100 -53
package/dist/errors.d.ts
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
import { LexError, LexErrorCode, LexErrorData } from '@atproto/lex-data';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { InferMethodError, Procedure, Query, ResultFailure } from '@atproto/lex-schema';
|
|
3
|
+
import { XrpcResponsePayload } from './util.js';
|
|
4
|
+
import { WWWAuthenticate } from './www-authenticate.js';
|
|
5
|
+
/**
|
|
6
|
+
* HTTP status codes that indicate a transient error that may succeed on retry.
|
|
7
|
+
*
|
|
8
|
+
* Includes:
|
|
9
|
+
* - 408 Request Timeout
|
|
10
|
+
* - 425 Too Early
|
|
11
|
+
* - 429 Too Many Requests (rate limited)
|
|
12
|
+
* - 500 Internal Server Error
|
|
13
|
+
* - 502 Bad Gateway
|
|
14
|
+
* - 503 Service Unavailable
|
|
15
|
+
* - 504 Gateway Timeout
|
|
16
|
+
* - 522 Connection Timed Out (Cloudflare)
|
|
17
|
+
* - 524 A Timeout Occurred (Cloudflare)
|
|
18
|
+
*/
|
|
19
|
+
export declare const RETRYABLE_HTTP_STATUS_CODES: ReadonlySet<number>;
|
|
4
20
|
export { LexError };
|
|
5
21
|
export type { LexErrorCode, LexErrorData };
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The payload structure for XRPC error responses.
|
|
24
|
+
*
|
|
25
|
+
* All XRPC errors return JSON with an `error` code and optional `message`.
|
|
26
|
+
*
|
|
27
|
+
* @typeParam N - The specific error code type
|
|
28
|
+
*/
|
|
29
|
+
export type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> = {
|
|
30
|
+
body: LexErrorData<N>;
|
|
31
|
+
encoding: 'application/json';
|
|
32
|
+
};
|
|
11
33
|
/**
|
|
12
34
|
* All unsuccessful responses should follow a standard error response
|
|
13
35
|
* schema. The Content-Type should be application/json, and the payload
|
|
@@ -20,64 +42,196 @@ export declare class XrpcError<N extends LexErrorCode = LexErrorCode> extends Le
|
|
|
20
42
|
*
|
|
21
43
|
* This function checks whether a given payload matches this schema.
|
|
22
44
|
*/
|
|
23
|
-
export declare function isXrpcErrorPayload(payload:
|
|
45
|
+
export declare function isXrpcErrorPayload(payload: XrpcResponsePayload | null | undefined): payload is XrpcErrorPayload;
|
|
24
46
|
/**
|
|
25
|
-
*
|
|
47
|
+
* Abstract base class for all XRPC errors.
|
|
48
|
+
*
|
|
49
|
+
* Extends {@link LexError} and implements {@link ResultFailure} for use with
|
|
50
|
+
* safe/result-based error handling patterns.
|
|
51
|
+
*
|
|
52
|
+
* @typeParam M - The XRPC method type (Procedure or Query)
|
|
53
|
+
* @typeParam N - The error code type
|
|
54
|
+
* @typeParam TReason - The reason type for ResultFailure
|
|
55
|
+
*
|
|
56
|
+
* @see {@link XrpcResponseError} - For valid XRPC error responses
|
|
57
|
+
* @see {@link XrpcUpstreamError} - For invalid/unexpected responses
|
|
58
|
+
* @see {@link XrpcInternalError} - For network/internal errors
|
|
26
59
|
*/
|
|
27
|
-
|
|
28
|
-
readonly
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
60
|
+
export declare abstract class XrpcError<M extends Procedure | Query = Procedure | Query, N extends LexErrorCode = LexErrorCode, TReason = unknown> extends LexError<N> implements ResultFailure<TReason> {
|
|
61
|
+
readonly method: M;
|
|
62
|
+
name: string;
|
|
63
|
+
constructor(method: M, error: N, message?: string, options?: ErrorOptions);
|
|
64
|
+
/**
|
|
65
|
+
* @see {@link ResultFailure.success}
|
|
66
|
+
*/
|
|
67
|
+
readonly success: false;
|
|
68
|
+
/**
|
|
69
|
+
* @see {@link ResultFailure.reason}
|
|
70
|
+
*/
|
|
71
|
+
abstract readonly reason: TReason;
|
|
72
|
+
/**
|
|
73
|
+
* Indicates whether the error is transient and can be retried.
|
|
74
|
+
*/
|
|
75
|
+
abstract shouldRetry(): boolean;
|
|
76
|
+
matchesSchema(): this is XrpcError<M, InferMethodError<M>>;
|
|
77
|
+
}
|
|
32
78
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
79
|
+
* Error class for valid XRPC error responses from the server.
|
|
80
|
+
*
|
|
81
|
+
* This represents a properly formatted XRPC error where the server returned
|
|
82
|
+
* a non-2xx status with a valid JSON error payload containing `error` and
|
|
83
|
+
* optional `message` fields.
|
|
84
|
+
*
|
|
85
|
+
* Use {@link matchesSchema} to check if the error matches the method's declared
|
|
86
|
+
* error types for type-safe error handling.
|
|
87
|
+
*
|
|
88
|
+
* @typeParam M - The XRPC method type
|
|
89
|
+
* @typeParam N - The error code type (inferred from method or generic)
|
|
90
|
+
*
|
|
91
|
+
* @example Handling specific errors
|
|
92
|
+
* ```typescript
|
|
93
|
+
* try {
|
|
94
|
+
* await client.xrpc(someMethod, options)
|
|
95
|
+
* } catch (err) {
|
|
96
|
+
* if (err instanceof XrpcResponseError && err.error === 'RecordNotFound') {
|
|
97
|
+
* // Handle not found case
|
|
98
|
+
* }
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
35
101
|
*/
|
|
36
|
-
export declare class XrpcResponseError<M extends
|
|
37
|
-
readonly
|
|
38
|
-
readonly status: number;
|
|
39
|
-
readonly headers: Headers;
|
|
102
|
+
export declare class XrpcResponseError<M extends Procedure | Query = Procedure | Query, N extends LexErrorCode = InferMethodError<M> | LexErrorCode> extends XrpcError<M, N, XrpcResponseError<M, N>> {
|
|
103
|
+
readonly response: Response;
|
|
40
104
|
readonly payload: XrpcErrorPayload<N>;
|
|
41
105
|
name: string;
|
|
42
|
-
constructor(method: M,
|
|
43
|
-
readonly success = false;
|
|
106
|
+
constructor(method: M, response: Response, payload: XrpcErrorPayload<N>, options?: ErrorOptions);
|
|
44
107
|
get reason(): this;
|
|
45
|
-
get body(): LexErrorData;
|
|
46
|
-
matchesSchema(): this is M extends {
|
|
47
|
-
errors: readonly (infer E extends string)[];
|
|
48
|
-
} ? XrpcResponseError<M, E> : never;
|
|
49
108
|
shouldRetry(): boolean;
|
|
50
109
|
toJSON(): LexErrorData<N>;
|
|
51
110
|
toResponse(): Response;
|
|
111
|
+
get body(): LexErrorData;
|
|
112
|
+
}
|
|
113
|
+
export type { WWWAuthenticate };
|
|
114
|
+
/**
|
|
115
|
+
* Error class for 401 Unauthorized XRPC responses.
|
|
116
|
+
*
|
|
117
|
+
* Extends {@link XrpcResponseError} with access to parsed WWW-Authenticate header
|
|
118
|
+
* information, useful for implementing authentication flows.
|
|
119
|
+
*
|
|
120
|
+
* Authentication errors are never retryable as they require user intervention
|
|
121
|
+
* (e.g., re-authentication, token refresh).
|
|
122
|
+
*
|
|
123
|
+
* @typeParam M - The XRPC method type
|
|
124
|
+
* @typeParam N - The error code type
|
|
125
|
+
*
|
|
126
|
+
* @example Handling authentication errors
|
|
127
|
+
* ```typescript
|
|
128
|
+
* try {
|
|
129
|
+
* await client.xrpc(someMethod, options)
|
|
130
|
+
* } catch (err) {
|
|
131
|
+
* if (err instanceof XrpcAuthenticationError) {
|
|
132
|
+
* const { DPoP } = err.wwwAuthenticate
|
|
133
|
+
* if (DPoP?.error === 'use_dpop_nonce') {
|
|
134
|
+
* // Handle DPoP nonce requirement
|
|
135
|
+
* }
|
|
136
|
+
* }
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare class XrpcAuthenticationError<M extends Procedure | Query = Procedure | Query, N extends LexErrorCode = LexErrorCode> extends XrpcResponseError<M, N> {
|
|
141
|
+
#private;
|
|
142
|
+
name: string;
|
|
143
|
+
shouldRetry(): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Parsed WWW-Authenticate header from the response.
|
|
146
|
+
* Contains authentication scheme parameters (e.g., Bearer realm, DPoP nonce).
|
|
147
|
+
*/
|
|
148
|
+
get wwwAuthenticate(): WWWAuthenticate;
|
|
52
149
|
}
|
|
53
150
|
/**
|
|
54
|
-
*
|
|
151
|
+
* Error class for invalid or unprocessable XRPC responses from upstream servers.
|
|
152
|
+
*
|
|
153
|
+
* This occurs when the server returns a response that doesn't conform to the
|
|
154
|
+
* XRPC protocol, such as:
|
|
155
|
+
* - Missing or invalid Content-Type header
|
|
156
|
+
* - Response body that doesn't match the method's output schema
|
|
157
|
+
* - Non-JSON error responses
|
|
158
|
+
* - Responses from non-XRPC endpoints
|
|
159
|
+
*
|
|
160
|
+
* The error code is always 'UpstreamFailure' and maps to HTTP 502 Bad Gateway
|
|
161
|
+
* when converted to a response.
|
|
162
|
+
*
|
|
163
|
+
* @typeParam M - The XRPC method type
|
|
55
164
|
*/
|
|
56
|
-
export declare class XrpcUpstreamError<
|
|
57
|
-
|
|
58
|
-
readonly
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
payload: XrpcPayload | null;
|
|
62
|
-
};
|
|
63
|
-
constructor(error: N, message: string, response: {
|
|
64
|
-
status: number;
|
|
65
|
-
headers: Headers;
|
|
66
|
-
}, payload: XrpcPayload | null, options?: ErrorOptions);
|
|
67
|
-
readonly success: false;
|
|
165
|
+
export declare class XrpcUpstreamError<M extends Procedure | Query = Procedure | Query> extends XrpcError<M, 'UpstreamFailure', XrpcUpstreamError<M>> {
|
|
166
|
+
readonly response: Response;
|
|
167
|
+
readonly payload: XrpcResponsePayload | null;
|
|
168
|
+
name: string;
|
|
169
|
+
constructor(method: M, response: Response, payload?: XrpcResponsePayload | null, message?: string, options?: ErrorOptions);
|
|
68
170
|
get reason(): this;
|
|
69
|
-
matchesSchema(): false;
|
|
70
171
|
shouldRetry(): boolean;
|
|
71
172
|
toResponse(): Response;
|
|
72
173
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Error class for internal/client-side errors during XRPC requests.
|
|
176
|
+
*
|
|
177
|
+
* This represents errors that occur before or during the request that are not
|
|
178
|
+
* server responses, such as:
|
|
179
|
+
* - Network errors (connection refused, DNS failure)
|
|
180
|
+
* - Request timeouts
|
|
181
|
+
* - Request aborted via AbortSignal
|
|
182
|
+
* - Invalid request construction
|
|
183
|
+
*
|
|
184
|
+
* The error code is always 'InternalServerError' and these errors are
|
|
185
|
+
* optimistically considered retryable.
|
|
186
|
+
*
|
|
187
|
+
* @typeParam M - The XRPC method type
|
|
188
|
+
*/
|
|
189
|
+
export declare class XrpcInternalError<M extends Procedure | Query = Procedure | Query> extends XrpcError<M, 'InternalServerError', XrpcInternalError<M>> {
|
|
190
|
+
name: string;
|
|
191
|
+
constructor(method: M, message?: string, options?: ErrorOptions);
|
|
192
|
+
get reason(): this;
|
|
193
|
+
shouldRetry(): true;
|
|
80
194
|
toResponse(): Response;
|
|
81
|
-
static from(cause: unknown, message?: string): XrpcUnexpectedError;
|
|
82
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Union type of all possible XRPC failure types.
|
|
198
|
+
*
|
|
199
|
+
* Used as the return type for safe/non-throwing XRPC methods. Check the
|
|
200
|
+
* `success` property to distinguish between success and failure:
|
|
201
|
+
*
|
|
202
|
+
* @typeParam M - The XRPC method type
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* const result = await client.xrpcSafe(someMethod, options)
|
|
207
|
+
* if (result.success) {
|
|
208
|
+
* console.log(result.body) // XrpcResponse
|
|
209
|
+
* } else {
|
|
210
|
+
* // result is XrpcFailure (XrpcResponseError | XrpcUpstreamError | XrpcInternalError)
|
|
211
|
+
* console.error(result.error, result.message)
|
|
212
|
+
* }
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export type XrpcFailure<M extends Procedure | Query = Procedure | Query> = XrpcResponseError<M> | XrpcUpstreamError<M> | XrpcInternalError<M>;
|
|
216
|
+
/**
|
|
217
|
+
* Converts an unknown error into an appropriate {@link XrpcFailure} type.
|
|
218
|
+
*
|
|
219
|
+
* If the error is already an XrpcFailure for the given method, returns it as-is.
|
|
220
|
+
* Otherwise, wraps it in an {@link XrpcInternalError}.
|
|
221
|
+
*
|
|
222
|
+
* @param method - The XRPC method that was called
|
|
223
|
+
* @param cause - The error to convert
|
|
224
|
+
* @returns An XrpcFailure instance
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* try {
|
|
229
|
+
* const response = await fetch(...)
|
|
230
|
+
* // ... process response
|
|
231
|
+
* } catch (err) {
|
|
232
|
+
* return asXrpcFailure(method, err)
|
|
233
|
+
* }
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
export declare function asXrpcFailure<M extends Procedure | Query>(method: M, cause: unknown): XrpcFailure<M>;
|
|
83
237
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.js
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.XrpcInternalError = exports.XrpcUpstreamError = exports.XrpcAuthenticationError = exports.XrpcResponseError = exports.XrpcError = exports.LexError = exports.RETRYABLE_HTTP_STATUS_CODES = void 0;
|
|
4
4
|
exports.isXrpcErrorPayload = isXrpcErrorPayload;
|
|
5
|
+
exports.asXrpcFailure = asXrpcFailure;
|
|
5
6
|
const lex_data_1 = require("@atproto/lex-data");
|
|
6
7
|
Object.defineProperty(exports, "LexError", { enumerable: true, get: function () { return lex_data_1.LexError; } });
|
|
7
8
|
const lex_schema_1 = require("@atproto/lex-schema");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const www_authenticate_js_1 = require("./www-authenticate.js");
|
|
10
|
+
/**
|
|
11
|
+
* HTTP status codes that indicate a transient error that may succeed on retry.
|
|
12
|
+
*
|
|
13
|
+
* Includes:
|
|
14
|
+
* - 408 Request Timeout
|
|
15
|
+
* - 425 Too Early
|
|
16
|
+
* - 429 Too Many Requests (rate limited)
|
|
17
|
+
* - 500 Internal Server Error
|
|
18
|
+
* - 502 Bad Gateway
|
|
19
|
+
* - 503 Service Unavailable
|
|
20
|
+
* - 504 Gateway Timeout
|
|
21
|
+
* - 522 Connection Timed Out (Cloudflare)
|
|
22
|
+
* - 524 A Timeout Occurred (Cloudflare)
|
|
23
|
+
*/
|
|
24
|
+
exports.RETRYABLE_HTTP_STATUS_CODES = new Set([
|
|
25
|
+
408, 425, 429, 500, 502, 503, 504, 522, 524,
|
|
26
|
+
]);
|
|
15
27
|
/**
|
|
16
28
|
* All unsuccessful responses should follow a standard error response
|
|
17
29
|
* schema. The Content-Type should be application/json, and the payload
|
|
@@ -25,109 +37,240 @@ exports.XrpcError = XrpcError;
|
|
|
25
37
|
* This function checks whether a given payload matches this schema.
|
|
26
38
|
*/
|
|
27
39
|
function isXrpcErrorPayload(payload) {
|
|
28
|
-
return (payload
|
|
40
|
+
return (payload != null &&
|
|
29
41
|
payload.encoding === 'application/json' &&
|
|
30
|
-
lex_schema_1.
|
|
42
|
+
lex_schema_1.lexErrorDataSchema.matches(payload.body));
|
|
31
43
|
}
|
|
32
44
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
45
|
+
* Abstract base class for all XRPC errors.
|
|
46
|
+
*
|
|
47
|
+
* Extends {@link LexError} and implements {@link ResultFailure} for use with
|
|
48
|
+
* safe/result-based error handling patterns.
|
|
49
|
+
*
|
|
50
|
+
* @typeParam M - The XRPC method type (Procedure or Query)
|
|
51
|
+
* @typeParam N - The error code type
|
|
52
|
+
* @typeParam TReason - The reason type for ResultFailure
|
|
53
|
+
*
|
|
54
|
+
* @see {@link XrpcResponseError} - For valid XRPC error responses
|
|
55
|
+
* @see {@link XrpcUpstreamError} - For invalid/unexpected responses
|
|
56
|
+
* @see {@link XrpcInternalError} - For network/internal errors
|
|
35
57
|
*/
|
|
36
|
-
class
|
|
58
|
+
class XrpcError extends lex_data_1.LexError {
|
|
37
59
|
method;
|
|
38
|
-
|
|
39
|
-
|
|
60
|
+
name = 'XrpcError';
|
|
61
|
+
constructor(method, error, message = `${error} Lexicon RPC error`, options) {
|
|
62
|
+
super(error, message, options);
|
|
63
|
+
this.method = method;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* @see {@link ResultFailure.success}
|
|
67
|
+
*/
|
|
68
|
+
success = false;
|
|
69
|
+
matchesSchema() {
|
|
70
|
+
return this.method.errors?.includes(this.error) ?? false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.XrpcError = XrpcError;
|
|
74
|
+
/**
|
|
75
|
+
* Error class for valid XRPC error responses from the server.
|
|
76
|
+
*
|
|
77
|
+
* This represents a properly formatted XRPC error where the server returned
|
|
78
|
+
* a non-2xx status with a valid JSON error payload containing `error` and
|
|
79
|
+
* optional `message` fields.
|
|
80
|
+
*
|
|
81
|
+
* Use {@link matchesSchema} to check if the error matches the method's declared
|
|
82
|
+
* error types for type-safe error handling.
|
|
83
|
+
*
|
|
84
|
+
* @typeParam M - The XRPC method type
|
|
85
|
+
* @typeParam N - The error code type (inferred from method or generic)
|
|
86
|
+
*
|
|
87
|
+
* @example Handling specific errors
|
|
88
|
+
* ```typescript
|
|
89
|
+
* try {
|
|
90
|
+
* await client.xrpc(someMethod, options)
|
|
91
|
+
* } catch (err) {
|
|
92
|
+
* if (err instanceof XrpcResponseError && err.error === 'RecordNotFound') {
|
|
93
|
+
* // Handle not found case
|
|
94
|
+
* }
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
class XrpcResponseError extends XrpcError {
|
|
99
|
+
response;
|
|
40
100
|
payload;
|
|
41
101
|
name = 'XrpcResponseError';
|
|
42
|
-
constructor(method,
|
|
102
|
+
constructor(method, response, payload, options) {
|
|
43
103
|
const { error, message } = payload.body;
|
|
44
|
-
super(error, message, options);
|
|
45
|
-
this.
|
|
46
|
-
this.status = status;
|
|
47
|
-
this.headers = headers;
|
|
104
|
+
super(method, error, message, options);
|
|
105
|
+
this.response = response;
|
|
48
106
|
this.payload = payload;
|
|
49
107
|
}
|
|
50
|
-
success = false;
|
|
51
108
|
get reason() {
|
|
52
109
|
return this;
|
|
53
110
|
}
|
|
54
|
-
get body() {
|
|
55
|
-
return this.payload.body;
|
|
56
|
-
}
|
|
57
|
-
matchesSchema() {
|
|
58
|
-
return this.method.errors?.includes(this.error) ?? false;
|
|
59
|
-
}
|
|
60
111
|
shouldRetry() {
|
|
61
|
-
|
|
62
|
-
if (this.status < 500)
|
|
63
|
-
return false;
|
|
64
|
-
return true;
|
|
112
|
+
return exports.RETRYABLE_HTTP_STATUS_CODES.has(this.response.status);
|
|
65
113
|
}
|
|
66
114
|
toJSON() {
|
|
67
115
|
return this.payload.body;
|
|
68
116
|
}
|
|
69
117
|
toResponse() {
|
|
70
|
-
|
|
71
|
-
|
|
118
|
+
// Re-expose schema-valid errors as-is to downstream clients
|
|
119
|
+
if (this.matchesSchema()) {
|
|
120
|
+
const status = this.response.status >= 500 ? 502 : this.response.status;
|
|
121
|
+
return Response.json(this.toJSON(), { status });
|
|
122
|
+
}
|
|
123
|
+
return this.response.status >= 500
|
|
124
|
+
? // The upstream server had an error, return a generic upstream failure
|
|
125
|
+
Response.json({ error: 'UpstreamFailure' }, { status: 502 })
|
|
126
|
+
: // If the error is on our side, return a generic internal server error
|
|
127
|
+
Response.json({ error: 'InternalServerError' }, { status: 500 });
|
|
128
|
+
}
|
|
129
|
+
get body() {
|
|
130
|
+
return this.payload.body;
|
|
72
131
|
}
|
|
73
132
|
}
|
|
74
133
|
exports.XrpcResponseError = XrpcResponseError;
|
|
75
134
|
/**
|
|
76
|
-
*
|
|
135
|
+
* Error class for 401 Unauthorized XRPC responses.
|
|
136
|
+
*
|
|
137
|
+
* Extends {@link XrpcResponseError} with access to parsed WWW-Authenticate header
|
|
138
|
+
* information, useful for implementing authentication flows.
|
|
139
|
+
*
|
|
140
|
+
* Authentication errors are never retryable as they require user intervention
|
|
141
|
+
* (e.g., re-authentication, token refresh).
|
|
142
|
+
*
|
|
143
|
+
* @typeParam M - The XRPC method type
|
|
144
|
+
* @typeParam N - The error code type
|
|
145
|
+
*
|
|
146
|
+
* @example Handling authentication errors
|
|
147
|
+
* ```typescript
|
|
148
|
+
* try {
|
|
149
|
+
* await client.xrpc(someMethod, options)
|
|
150
|
+
* } catch (err) {
|
|
151
|
+
* if (err instanceof XrpcAuthenticationError) {
|
|
152
|
+
* const { DPoP } = err.wwwAuthenticate
|
|
153
|
+
* if (DPoP?.error === 'use_dpop_nonce') {
|
|
154
|
+
* // Handle DPoP nonce requirement
|
|
155
|
+
* }
|
|
156
|
+
* }
|
|
157
|
+
* }
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
class XrpcAuthenticationError extends XrpcResponseError {
|
|
161
|
+
name = 'XrpcAuthenticationError';
|
|
162
|
+
shouldRetry() {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
#wwwAuthenticateCached;
|
|
166
|
+
/**
|
|
167
|
+
* Parsed WWW-Authenticate header from the response.
|
|
168
|
+
* Contains authentication scheme parameters (e.g., Bearer realm, DPoP nonce).
|
|
169
|
+
*/
|
|
170
|
+
get wwwAuthenticate() {
|
|
171
|
+
return (this.#wwwAuthenticateCached ??=
|
|
172
|
+
(0, www_authenticate_js_1.parseWWWAuthenticateHeader)(this.response.headers.get('www-authenticate')) ?? {});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
exports.XrpcAuthenticationError = XrpcAuthenticationError;
|
|
176
|
+
/**
|
|
177
|
+
* Error class for invalid or unprocessable XRPC responses from upstream servers.
|
|
178
|
+
*
|
|
179
|
+
* This occurs when the server returns a response that doesn't conform to the
|
|
180
|
+
* XRPC protocol, such as:
|
|
181
|
+
* - Missing or invalid Content-Type header
|
|
182
|
+
* - Response body that doesn't match the method's output schema
|
|
183
|
+
* - Non-JSON error responses
|
|
184
|
+
* - Responses from non-XRPC endpoints
|
|
185
|
+
*
|
|
186
|
+
* The error code is always 'UpstreamFailure' and maps to HTTP 502 Bad Gateway
|
|
187
|
+
* when converted to a response.
|
|
188
|
+
*
|
|
189
|
+
* @typeParam M - The XRPC method type
|
|
77
190
|
*/
|
|
78
191
|
class XrpcUpstreamError extends XrpcError {
|
|
79
|
-
name = 'XrpcUpstreamError';
|
|
80
|
-
// For debugging purposes, we keep the response details here
|
|
81
192
|
response;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
193
|
+
payload;
|
|
194
|
+
name = 'XrpcUpstreamError';
|
|
195
|
+
constructor(method, response, payload = null, message = `Unexpected upstream XRPC response`, options) {
|
|
196
|
+
super(method, 'UpstreamFailure', message, options);
|
|
197
|
+
this.response = response;
|
|
198
|
+
this.payload = payload;
|
|
89
199
|
}
|
|
90
|
-
success = false;
|
|
91
200
|
get reason() {
|
|
92
201
|
return this;
|
|
93
202
|
}
|
|
94
|
-
matchesSchema() {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
203
|
shouldRetry() {
|
|
98
|
-
|
|
99
|
-
return this.response.status >= 500;
|
|
204
|
+
return exports.RETRYABLE_HTTP_STATUS_CODES.has(this.response.status);
|
|
100
205
|
}
|
|
101
206
|
toResponse() {
|
|
102
207
|
return Response.json(this.toJSON(), { status: 502 });
|
|
103
208
|
}
|
|
104
209
|
}
|
|
105
210
|
exports.XrpcUpstreamError = XrpcUpstreamError;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Error class for internal/client-side errors during XRPC requests.
|
|
213
|
+
*
|
|
214
|
+
* This represents errors that occur before or during the request that are not
|
|
215
|
+
* server responses, such as:
|
|
216
|
+
* - Network errors (connection refused, DNS failure)
|
|
217
|
+
* - Request timeouts
|
|
218
|
+
* - Request aborted via AbortSignal
|
|
219
|
+
* - Invalid request construction
|
|
220
|
+
*
|
|
221
|
+
* The error code is always 'InternalServerError' and these errors are
|
|
222
|
+
* optimistically considered retryable.
|
|
223
|
+
*
|
|
224
|
+
* @typeParam M - The XRPC method type
|
|
225
|
+
*/
|
|
226
|
+
class XrpcInternalError extends XrpcError {
|
|
227
|
+
name = 'XrpcInternalError';
|
|
228
|
+
constructor(method, message, options) {
|
|
229
|
+
super(method, 'InternalServerError', message ?? 'Unable to fulfill XRPC request', options);
|
|
110
230
|
}
|
|
111
|
-
success = false;
|
|
112
231
|
get reason() {
|
|
113
|
-
return this
|
|
114
|
-
}
|
|
115
|
-
matchesSchema() {
|
|
116
|
-
return false;
|
|
232
|
+
return this;
|
|
117
233
|
}
|
|
118
234
|
shouldRetry() {
|
|
235
|
+
// Ideally, we would inspect the reason to determine if it's retryable
|
|
236
|
+
// (by detecting network errors, timeouts, etc.). Since these cases are
|
|
237
|
+
// highly platform-dependent, we optimistically assume all internal
|
|
238
|
+
// errors are retryable.
|
|
119
239
|
return true;
|
|
120
240
|
}
|
|
121
241
|
toResponse() {
|
|
122
|
-
|
|
242
|
+
// Do not expose internal error details to downstream clients
|
|
243
|
+
return Response.json({ error: this.error }, { status: 500 });
|
|
123
244
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
245
|
+
}
|
|
246
|
+
exports.XrpcInternalError = XrpcInternalError;
|
|
247
|
+
/**
|
|
248
|
+
* Converts an unknown error into an appropriate {@link XrpcFailure} type.
|
|
249
|
+
*
|
|
250
|
+
* If the error is already an XrpcFailure for the given method, returns it as-is.
|
|
251
|
+
* Otherwise, wraps it in an {@link XrpcInternalError}.
|
|
252
|
+
*
|
|
253
|
+
* @param method - The XRPC method that was called
|
|
254
|
+
* @param cause - The error to convert
|
|
255
|
+
* @returns An XrpcFailure instance
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* try {
|
|
260
|
+
* const response = await fetch(...)
|
|
261
|
+
* // ... process response
|
|
262
|
+
* } catch (err) {
|
|
263
|
+
* return asXrpcFailure(method, err)
|
|
264
|
+
* }
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
function asXrpcFailure(method, cause) {
|
|
268
|
+
if (cause instanceof XrpcResponseError ||
|
|
269
|
+
cause instanceof XrpcUpstreamError ||
|
|
270
|
+
cause instanceof XrpcInternalError) {
|
|
271
|
+
if (cause.method === method)
|
|
128
272
|
return cause;
|
|
129
|
-
return new XrpcUnexpectedError(message, { cause });
|
|
130
273
|
}
|
|
274
|
+
return new XrpcInternalError(method, undefined, { cause });
|
|
131
275
|
}
|
|
132
|
-
exports.XrpcUnexpectedError = XrpcUnexpectedError;
|
|
133
276
|
//# sourceMappingURL=errors.js.map
|