@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/agent.d.ts +72 -0
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +46 -1
  5. package/dist/agent.js.map +1 -1
  6. package/dist/client.d.ts +442 -46
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +145 -1
  9. package/dist/client.js.map +1 -1
  10. package/dist/errors.d.ts +202 -48
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js +208 -65
  13. package/dist/errors.js.map +1 -1
  14. package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts +20 -20
  15. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts +12 -12
  16. package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts +6 -6
  17. package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts +6 -6
  18. package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts +22 -22
  19. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts +2 -2
  20. package/dist/response.d.ts +17 -6
  21. package/dist/response.d.ts.map +1 -1
  22. package/dist/response.js +45 -32
  23. package/dist/response.js.map +1 -1
  24. package/dist/types.d.ts +51 -0
  25. package/dist/types.d.ts.map +1 -1
  26. package/dist/types.js.map +1 -1
  27. package/dist/util.d.ts +40 -5
  28. package/dist/util.d.ts.map +1 -1
  29. package/dist/util.js +22 -0
  30. package/dist/util.js.map +1 -1
  31. package/dist/www-authenticate.d.ts +35 -0
  32. package/dist/www-authenticate.d.ts.map +1 -0
  33. package/dist/www-authenticate.js +57 -0
  34. package/dist/www-authenticate.js.map +1 -0
  35. package/dist/xrpc.d.ts +82 -10
  36. package/dist/xrpc.d.ts.map +1 -1
  37. package/dist/xrpc.js +15 -28
  38. package/dist/xrpc.js.map +1 -1
  39. package/package.json +7 -7
  40. package/src/agent.ts +101 -1
  41. package/src/client.ts +428 -15
  42. package/src/errors.ts +308 -120
  43. package/src/response.ts +68 -63
  44. package/src/types.ts +52 -0
  45. package/src/util.ts +50 -5
  46. package/src/www-authenticate.test.ts +227 -0
  47. package/src/www-authenticate.ts +101 -0
  48. 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 { l } from '@atproto/lex-schema';
3
- import { XrpcPayload } from './util.js';
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
- export type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> = XrpcPayload<LexErrorData<N>, 'application/json'>;
7
- export declare class XrpcError<N extends LexErrorCode = LexErrorCode> extends LexError<N> {
8
- name: string;
9
- constructor(error: N, message?: string, options?: ErrorOptions);
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: XrpcPayload | null): payload is XrpcErrorPayload;
45
+ export declare function isXrpcErrorPayload(payload: XrpcResponsePayload | null | undefined): payload is XrpcErrorPayload;
24
46
  /**
25
- * Interface representing a failed XRPC request result.
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
- type LexRpcFailureResult<N extends LexErrorCode, E> = l.ResultFailure<E> & {
28
- readonly error: N;
29
- shouldRetry(): boolean;
30
- matchesSchema(): boolean;
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
- * Class used to represent an HTTP request that resulted in an XRPC method error
34
- * That is, a non-2xx response with a valid XRPC error payload.
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 l.Procedure | l.Query = l.Procedure | l.Query, N extends LexErrorCode = LexErrorCode> extends XrpcError<N> implements LexRpcFailureResult<N, XrpcResponseError<M, N>> {
37
- readonly method: M;
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, status: number, headers: Headers, payload: XrpcErrorPayload<N>, options?: ErrorOptions);
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
- * This class represents an invalid XRPC response from the server.
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<N extends 'InvalidResponse' | 'UpstreamFailure' = 'InvalidResponse' | 'UpstreamFailure'> extends XrpcError<N> implements LexRpcFailureResult<N, XrpcUpstreamError<N>> {
57
- name: "XrpcUpstreamError";
58
- readonly response: {
59
- status: number;
60
- headers: Headers;
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
- export declare class XrpcUnexpectedError extends XrpcError<'InternalServerError'> implements LexRpcFailureResult<'InternalServerError', unknown> {
74
- name: "XrpcUnexpectedError";
75
- protected constructor(message: string, options: Required<ErrorOptions>);
76
- readonly success = false;
77
- get reason(): unknown;
78
- matchesSchema(): false;
79
- shouldRetry(): boolean;
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
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
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.XrpcUnexpectedError = exports.XrpcUpstreamError = exports.XrpcResponseError = exports.XrpcError = exports.LexError = void 0;
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
- class XrpcError extends lex_data_1.LexError {
9
- name = 'XrpcError';
10
- constructor(error, message = `${error} Lexicon RPC error`, options) {
11
- super(error, message, options);
12
- }
13
- }
14
- exports.XrpcError = XrpcError;
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 !== null &&
40
+ return (payload != null &&
29
41
  payload.encoding === 'application/json' &&
30
- lex_schema_1.l.lexErrorData.matches(payload.body));
42
+ lex_schema_1.lexErrorDataSchema.matches(payload.body));
31
43
  }
32
44
  /**
33
- * Class used to represent an HTTP request that resulted in an XRPC method error
34
- * That is, a non-2xx response with a valid XRPC error payload.
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 XrpcResponseError extends XrpcError {
58
+ class XrpcError extends lex_data_1.LexError {
37
59
  method;
38
- status;
39
- headers;
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, status, headers, payload, options) {
102
+ constructor(method, response, payload, options) {
43
103
  const { error, message } = payload.body;
44
- super(error, message, options);
45
- this.method = method;
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
- // Do not retry client errors
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
- const { status, headers } = this;
71
- return Response.json(this.toJSON(), { status, headers });
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
- * This class represents an invalid XRPC response from the server.
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
- constructor(error, message, response, payload, options) {
83
- super(error, message, { cause: options?.cause });
84
- this.response = {
85
- status: response.status,
86
- headers: response.headers,
87
- payload,
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
- // Do not retry client errors
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
- class XrpcUnexpectedError extends XrpcError {
107
- name = 'XrpcUnexpectedError';
108
- constructor(message, options) {
109
- super('InternalServerError', message, options);
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.cause;
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
- return Response.json(this.toJSON(), { status: 500 });
242
+ // Do not expose internal error details to downstream clients
243
+ return Response.json({ error: this.error }, { status: 500 });
123
244
  }
124
- static from(cause, message = cause instanceof lex_data_1.LexError
125
- ? cause.message
126
- : 'XRPC request failed') {
127
- if (cause instanceof XrpcUnexpectedError)
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