@atproto/lex-client 0.0.15 → 0.0.17

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/dist/errors.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { LexError, LexErrorCode, LexErrorData } from '@atproto/lex-data';
2
2
  import { InferMethodError, LexValidationError, Procedure, Query, ResultFailure } from '@atproto/lex-schema';
3
- import { XrpcResponsePayload } from './util.js';
3
+ import { XrpcUnknownResponsePayload } from './types.js';
4
4
  import { WWWAuthenticate } from './www-authenticate.js';
5
+ export type { XrpcUnknownResponsePayload };
5
6
  export type DownstreamError<N extends LexErrorCode = LexErrorCode> = {
6
7
  status: number;
7
8
  headers?: Headers;
@@ -48,7 +49,7 @@ export type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> = {
48
49
  *
49
50
  * This function checks whether a given payload matches this schema.
50
51
  */
51
- export declare function isXrpcErrorPayload(payload: XrpcResponsePayload | null | undefined): payload is XrpcErrorPayload;
52
+ export declare function isXrpcErrorPayload(payload: XrpcUnknownResponsePayload | null | undefined): payload is XrpcErrorPayload;
52
53
  /**
53
54
  * Abstract base class for all XRPC errors.
54
55
  *
@@ -174,9 +175,9 @@ export declare class XrpcAuthenticationError<M extends Procedure | Query = Proce
174
175
  */
175
176
  export declare class XrpcUpstreamError<M extends Procedure | Query = Procedure | Query> extends XrpcError<M, 'UpstreamFailure', XrpcUpstreamError<M>> {
176
177
  readonly response: Response;
177
- readonly payload: XrpcResponsePayload | null;
178
+ readonly payload: XrpcUnknownResponsePayload | null;
178
179
  name: string;
179
- constructor(method: M, response: Response, payload?: XrpcResponsePayload | null, message?: string, options?: ErrorOptions);
180
+ constructor(method: M, response: Response, payload?: XrpcUnknownResponsePayload | null, message?: string, options?: ErrorOptions);
180
181
  get reason(): this;
181
182
  shouldRetry(): boolean;
182
183
  toDownstreamError(): DownstreamError;
@@ -195,21 +196,15 @@ export declare class XrpcUpstreamError<M extends Procedure | Query = Procedure |
195
196
  export declare class XrpcInvalidResponseError<M extends Procedure | Query = Procedure | Query> extends XrpcUpstreamError<M> {
196
197
  readonly cause: LexValidationError;
197
198
  name: string;
198
- constructor(method: M, response: Response, payload: XrpcResponsePayload, cause: LexValidationError);
199
+ constructor(method: M, response: Response, payload: XrpcUnknownResponsePayload, cause: LexValidationError);
199
200
  toDownstreamError(): DownstreamError;
200
201
  }
201
202
  /**
202
- * Error class for internal/client-side errors during XRPC requests.
203
+ * Error class for unexpected internal/client-side errors during XRPC requests.
203
204
  *
204
- * This represents errors that occur before or during the request that are not
205
- * server responses, such as:
206
- * - Network errors (connection refused, DNS failure)
207
- * - Request timeouts
208
- * - Request aborted via AbortSignal
209
- * - Invalid request construction
210
- *
211
- * The error code is always 'InternalServerError' and these errors are
212
- * optimistically considered retryable.
205
+ * The error code is always 'InternalServerError' and these errors not
206
+ * considered retryable as they stem from unforeseen issues in the
207
+ * implementation.
213
208
  *
214
209
  * @typeParam M - The XRPC method type
215
210
  */
@@ -217,8 +212,25 @@ export declare class XrpcInternalError<M extends Procedure | Query = Procedure |
217
212
  name: string;
218
213
  constructor(method: M, message?: string, options?: ErrorOptions);
219
214
  get reason(): this;
220
- shouldRetry(): true;
221
- toJSON(): LexErrorData<'InternalServerError'>;
215
+ shouldRetry(): boolean;
216
+ toJSON(): LexErrorData;
217
+ toDownstreamError(): DownstreamError;
218
+ }
219
+ /**
220
+ * Special case of XrpcInternalError that specifically represents errors thrown
221
+ * by {@link Agent.fetchHandler} during the XRPC request. This includes:
222
+ * - Network errors (connection refused, DNS failure)
223
+ * - Request timeouts
224
+ * - Request aborted via AbortSignal
225
+ *
226
+ * These errors are optimistically considered retryable, as many fetch errors
227
+ * are transient and may succeed on retry.
228
+ */
229
+ export declare class XrpcFetchError<M extends Procedure | Query = Procedure | Query> extends XrpcInternalError<M> {
230
+ name: string;
231
+ constructor(method: M, cause: unknown);
232
+ shouldRetry(): boolean;
233
+ toJSON(): LexErrorData;
222
234
  toDownstreamError(): DownstreamError;
223
235
  }
224
236
  /**
@@ -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,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.XrpcInternalError = exports.XrpcInvalidResponseError = exports.XrpcUpstreamError = exports.XrpcAuthenticationError = exports.XrpcResponseError = exports.XrpcError = exports.LexError = exports.RETRYABLE_HTTP_STATUS_CODES = void 0;
3
+ exports.XrpcFetchError = exports.XrpcInternalError = exports.XrpcInvalidResponseError = exports.XrpcUpstreamError = exports.XrpcAuthenticationError = exports.XrpcResponseError = exports.XrpcError = exports.LexError = exports.RETRYABLE_HTTP_STATUS_CODES = void 0;
4
4
  exports.isXrpcErrorPayload = isXrpcErrorPayload;
5
5
  exports.asXrpcFailure = asXrpcFailure;
6
6
  const lex_data_1 = require("@atproto/lex-data");
@@ -251,17 +251,11 @@ class XrpcInvalidResponseError extends XrpcUpstreamError {
251
251
  }
252
252
  exports.XrpcInvalidResponseError = XrpcInvalidResponseError;
253
253
  /**
254
- * Error class for internal/client-side errors during XRPC requests.
254
+ * Error class for unexpected internal/client-side errors during XRPC requests.
255
255
  *
256
- * This represents errors that occur before or during the request that are not
257
- * server responses, such as:
258
- * - Network errors (connection refused, DNS failure)
259
- * - Request timeouts
260
- * - Request aborted via AbortSignal
261
- * - Invalid request construction
262
- *
263
- * The error code is always 'InternalServerError' and these errors are
264
- * optimistically considered retryable.
256
+ * The error code is always 'InternalServerError' and these errors not
257
+ * considered retryable as they stem from unforeseen issues in the
258
+ * implementation.
265
259
  *
266
260
  * @typeParam M - The XRPC method type
267
261
  */
@@ -274,11 +268,7 @@ class XrpcInternalError extends XrpcError {
274
268
  return this;
275
269
  }
276
270
  shouldRetry() {
277
- // Ideally, we would inspect the reason to determine if it's retryable
278
- // (by detecting network errors, timeouts, etc.). Since these cases are
279
- // highly platform-dependent, we optimistically assume all internal
280
- // errors are retryable.
281
- return true;
271
+ return false;
282
272
  }
283
273
  toJSON() {
284
274
  // @NOTE Do not expose internal error details to downstream clients
@@ -289,6 +279,43 @@ class XrpcInternalError extends XrpcError {
289
279
  }
290
280
  }
291
281
  exports.XrpcInternalError = XrpcInternalError;
282
+ /**
283
+ * Special case of XrpcInternalError that specifically represents errors thrown
284
+ * by {@link Agent.fetchHandler} during the XRPC request. This includes:
285
+ * - Network errors (connection refused, DNS failure)
286
+ * - Request timeouts
287
+ * - Request aborted via AbortSignal
288
+ *
289
+ * These errors are optimistically considered retryable, as many fetch errors
290
+ * are transient and may succeed on retry.
291
+ */
292
+ class XrpcFetchError extends XrpcInternalError {
293
+ name = 'XrpcFetchError';
294
+ constructor(method, cause) {
295
+ const message = cause instanceof Error ? cause.message : String(cause);
296
+ super(method, `Unexpected fetchHandler() error: ${message}`, { cause });
297
+ }
298
+ shouldRetry() {
299
+ // Ideally, we would inspect the reason to determine if it's retryable (by
300
+ // detecting network errors, timeouts, etc.). Since these cases are highly
301
+ // platform-dependent, we optimistically assume all fetch errors are
302
+ // transient and retryable.
303
+ return true;
304
+ }
305
+ toJSON() {
306
+ // @NOTE Do not expose internal error details to downstream clients
307
+ return { error: this.error, message: 'Failed to perform upstream request' };
308
+ }
309
+ toDownstreamError() {
310
+ // While it might technically be a 500 error, we use 502 Bad Gateway here to
311
+ // indicate that the error occurred while communicating with the upstream
312
+ // server, allowing downstream clients to distinguish between errors in our
313
+ // internal processing (500) and errors in the upstream server or network
314
+ // (502).
315
+ return { status: 502, body: this.toJSON() };
316
+ }
317
+ }
318
+ exports.XrpcFetchError = XrpcFetchError;
292
319
  /**
293
320
  * Converts an unknown error into an appropriate {@link XrpcFailure} type.
294
321
  *
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";;;AAmEA,gDAQC;AAmXD,sCAaC;AA3cD,gDAAwE;AAwC/D,yFAxCA,mBAAQ,OAwCA;AAvCjB,oDAO4B;AAE5B,+DAG8B;AAS9B;;;;;;;;;;;;;GAaG;AACU,QAAA,2BAA2B,GAAwB,IAAI,GAAG,CAAC;IACtE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;CAC5C,CAAC,CAAA;AAiBF;;;;;;;;;;;GAWG;AACH,SAAgB,kBAAkB,CAChC,OAA+C;IAE/C,OAAO,CACL,OAAO,IAAI,IAAI;QACf,OAAO,CAAC,QAAQ,KAAK,kBAAkB;QACvC,+BAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CACzC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAsB,SAKpB,SAAQ,mBAAW;IAMR;IAHX,IAAI,GAAG,WAAW,CAAA;IAElB,YACW,MAAS,EAClB,KAAQ,EACR,UAAkB,GAAG,KAAK,oBAAoB,EAC9C,OAAsB;QAEtB,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QALrB,WAAM,GAAN,MAAM,CAAG;IAMpB,CAAC;IAED;;OAEG;IACM,OAAO,GAAG,KAAc,CAAA;IAcjC,mBAAmB;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAA;IAC1D,CAAC;CACF;AAvCD,8BAuCC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAa,iBAGX,SAAQ,SAAwC;IAKrC;IACA;IALX,IAAI,GAAG,mBAAmB,CAAA;IAE1B,YACE,MAAS,EACA,QAAkB,EAClB,OAA4B,EACrC,OAAsB;QAEtB,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAA;QACvC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QAL7B,aAAQ,GAAR,QAAQ,CAAU;QAClB,YAAO,GAAP,OAAO,CAAqB;IAKvC,CAAC;IAED,IAAa,MAAM;QACjB,OAAO,IAAI,CAAA;IACb,CAAC;IAEQ,WAAW;QAClB,OAAO,mCAA2B,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9D,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAA;IAC1B,CAAC;IAEQ,iBAAiB;QACxB,2EAA2E;QAC3E,2EAA2E;QAC3E,0EAA0E;QAC1E,wEAAwE;QACxE,wDAAwD;QACxD,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;YACxD,OAAO,EAAE,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC;YAC3C,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;SACpB,CAAA;IACH,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAA;IAC7B,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAA;IAC9B,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAA;IAC1B,CAAC;CACF;AApDD,8CAoDC;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAa,uBAGX,SAAQ,iBAAuB;IAC/B,IAAI,GAAG,yBAAyB,CAAA;IAEvB,WAAW;QAClB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,sBAAsB,CAAkB;IACxC;;;OAGG;IACH,IAAI,eAAe;QACjB,OAAO,CAAC,IAAI,CAAC,sBAAsB;YACjC,IAAA,gDAA0B,EACxB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAC9C,IAAI,EAAE,CAAC,CAAA;IACZ,CAAC;IAEQ,iBAAiB;QACxB,OAAO;YACL,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC;YAC3C,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE;SACpB,CAAA;IACH,CAAC;CACF;AA7BD,0DA6BC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAa,iBAEX,SAAQ,SAAqD;IAKlD;IACA;IALX,IAAI,GAAG,mBAAmB,CAAA;IAE1B,YACE,MAAS,EACA,QAAkB,EAClB,UAAsC,IAAI,EACnD,UAAkB,mCAAmC,EACrD,OAAsB;QAEtB,KAAK,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QALzC,aAAQ,GAAR,QAAQ,CAAU;QAClB,YAAO,GAAP,OAAO,CAAmC;IAKrD,CAAC;IAED,IAAa,MAAM;QACjB,OAAO,IAAI,CAAA;IACb,CAAC;IAEQ,WAAW;QAClB,OAAO,mCAA2B,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9D,CAAC;IAEQ,iBAAiB;QACxB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAA;IAC7C,CAAC;CACF;AA1BD,8CA0BC;AAED;;;;;;;;;;GAUG;AACH,MAAa,wBAEX,SAAQ,iBAAoB;IAOjB;IANX,IAAI,GAAG,0BAA0B,CAAA;IAEjC,YACE,MAAS,EACT,QAAkB,EAClB,OAA4B,EACnB,KAAyB;QAElC,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,qBAAqB,KAAK,CAAC,OAAO,EAAE,EAAE;YACrE,KAAK;SACN,CAAC,CAAA;QAJO,UAAK,GAAL,KAAK,CAAoB;IAKpC,CAAC;IAEQ,iBAAiB;QACxB,0EAA0E;QAC1E,2EAA2E;QAC3E,uEAAuE;QACvE,sEAAsE;QACtE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAA;IAC7C,CAAC;CACF;AAvBD,4DAuBC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAa,iBAEX,SAAQ,SAAyD;IACjE,IAAI,GAAG,mBAAmB,CAAA;IAE1B,YAAY,MAAS,EAAE,OAAgB,EAAE,OAAsB;QAC7D,KAAK,CACH,MAAM,EACN,qBAAqB,EACrB,OAAO,IAAI,gCAAgC,EAC3C,OAAO,CACR,CAAA;IACH,CAAC;IAED,IAAa,MAAM;QACjB,OAAO,IAAI,CAAA;IACb,CAAC;IAEQ,WAAW;QAClB,sEAAsE;QACtE,uEAAuE;QACvE,mEAAmE;QACnE,wBAAwB;QACxB,OAAO,IAAI,CAAA;IACb,CAAC;IAEQ,MAAM;QACb,mEAAmE;QACnE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAA;IAChE,CAAC;IAEQ,iBAAiB;QACxB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAA;IAC7C,CAAC;CACF;AAlCD,8CAkCC;AA6BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAgB,aAAa,CAC3B,MAAS,EACT,KAAc;IAEd,IACE,KAAK,YAAY,iBAAiB;QAClC,KAAK,YAAY,iBAAiB;QAClC,KAAK,YAAY,iBAAiB,EAClC,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,KAAK,CAAA;IAC3C,CAAC;IAED,OAAO,IAAI,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;AAC5D,CAAC;AAED,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,YAAY;IACZ,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,IAAI;IACJ,SAAS;IACT,mBAAmB;IACnB,SAAS;CACV,CAAC,CAAA;AAEF,SAAS,oBAAoB,CAAC,OAAgB;IAC5C,MAAM,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAEnC,6CAA6C;IAC7C,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC;IAED,mDAAmD;IACnD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IAC5C,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;QAC5B,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,wEAAwE;IACxE,sCAAsC;IACtC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAC/B,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;IAEjC,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import { LexError, LexErrorCode, LexErrorData } from '@atproto/lex-data'\nimport {\n InferMethodError,\n LexValidationError,\n Procedure,\n Query,\n ResultFailure,\n lexErrorDataSchema,\n} from '@atproto/lex-schema'\nimport { XrpcResponsePayload } from './util.js'\nimport {\n WWWAuthenticate,\n parseWWWAuthenticateHeader,\n} from './www-authenticate.js'\n\nexport type DownstreamError<N extends LexErrorCode = LexErrorCode> = {\n status: number\n headers?: Headers\n encoding?: 'application/json'\n body: LexErrorData<N>\n}\n\n/**\n * HTTP status codes that indicate a transient error that may succeed on retry.\n *\n * Includes:\n * - 408 Request Timeout\n * - 425 Too Early\n * - 429 Too Many Requests (rate limited)\n * - 500 Internal Server Error\n * - 502 Bad Gateway\n * - 503 Service Unavailable\n * - 504 Gateway Timeout\n * - 522 Connection Timed Out (Cloudflare)\n * - 524 A Timeout Occurred (Cloudflare)\n */\nexport const RETRYABLE_HTTP_STATUS_CODES: ReadonlySet<number> = new Set([\n 408, 425, 429, 500, 502, 503, 504, 522, 524,\n])\n\nexport { LexError }\nexport type { LexErrorCode, LexErrorData }\n\n/**\n * The payload structure for XRPC error responses.\n *\n * All XRPC errors return JSON with an `error` code and optional `message`.\n *\n * @typeParam N - The specific error code type\n */\nexport type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> = {\n body: LexErrorData<N>\n encoding: 'application/json'\n}\n\n/**\n * All unsuccessful responses should follow a standard error response\n * schema. The Content-Type should be application/json, and the payload\n * should be a JSON object with the following fields:\n *\n * - `error` (string, required): type name of the error (generic ASCII\n * constant, no whitespace)\n * - `message` (string, optional): description of the error, appropriate for\n * display to humans\n *\n * This function checks whether a given payload matches this schema.\n */\nexport function isXrpcErrorPayload(\n payload: XrpcResponsePayload | null | undefined,\n): payload is XrpcErrorPayload {\n return (\n payload != null &&\n payload.encoding === 'application/json' &&\n lexErrorDataSchema.matches(payload.body)\n )\n}\n\n/**\n * Abstract base class for all XRPC errors.\n *\n * Extends {@link LexError} and implements {@link ResultFailure} for use with\n * safe/result-based error handling patterns.\n *\n * @typeParam M - The XRPC method type (Procedure or Query)\n * @typeParam N - The error code type\n * @typeParam TReason - The reason type for ResultFailure\n *\n * @see {@link XrpcResponseError} - For valid XRPC error responses\n * @see {@link XrpcUpstreamError} - For invalid/unexpected responses\n * @see {@link XrpcInternalError} - For network/internal errors\n */\nexport abstract class XrpcError<\n M extends Procedure | Query = Procedure | Query,\n N extends LexErrorCode = LexErrorCode,\n TReason = unknown,\n >\n extends LexError<N>\n implements ResultFailure<TReason>\n{\n name = 'XrpcError'\n\n constructor(\n readonly method: M,\n error: N,\n message: string = `${error} Lexicon RPC error`,\n options?: ErrorOptions,\n ) {\n super(error, message, options)\n }\n\n /**\n * @see {@link ResultFailure.success}\n */\n readonly success = false as const\n\n /**\n * @see {@link ResultFailure.reason}\n */\n abstract readonly reason: TReason\n\n /**\n * Indicates whether the error is transient and can be retried.\n */\n abstract shouldRetry(): boolean\n\n abstract toDownstreamError(): DownstreamError\n\n matchesSchemaErrors(): this is XrpcError<M, InferMethodError<M>> {\n return this.method.errors?.includes(this.error) ?? false\n }\n}\n\n/**\n * Error class for valid XRPC error responses from the server.\n *\n * This represents a properly formatted XRPC error where the server returned\n * a non-2xx status with a valid JSON error payload containing `error` and\n * optional `message` fields.\n *\n * Use {@link matchesSchemaErrors} to check if the error matches the method's declared\n * error types for type-safe error handling.\n *\n * @typeParam M - The XRPC method type\n * @typeParam N - The error code type (inferred from method or generic)\n *\n * @example Handling specific errors\n * ```typescript\n * try {\n * await client.xrpc(someMethod, options)\n * } catch (err) {\n * if (err instanceof XrpcResponseError && err.error === 'RecordNotFound') {\n * // Handle not found case\n * }\n * }\n * ```\n */\nexport class XrpcResponseError<\n M extends Procedure | Query = Procedure | Query,\n N extends LexErrorCode = InferMethodError<M> | LexErrorCode,\n> extends XrpcError<M, N, XrpcResponseError<M, N>> {\n name = 'XrpcResponseError'\n\n constructor(\n method: M,\n readonly response: Response,\n readonly payload: XrpcErrorPayload<N>,\n options?: ErrorOptions,\n ) {\n const { error, message } = payload.body\n super(method, error, message, options)\n }\n\n override get reason(): this {\n return this\n }\n\n override shouldRetry(): boolean {\n return RETRYABLE_HTTP_STATUS_CODES.has(this.response.status)\n }\n\n override toJSON(): LexErrorData<N> {\n return this.payload.body\n }\n\n override toDownstreamError(): DownstreamError {\n // If the upstream server returned a 5xx error, we want to return a 502 Bad\n // Gateway to downstream clients, as the issue is with the upstream server,\n // not us. We still return the original error code and message in the body\n // for transparency, but we do not want to expose internal server errors\n // from the upstream server as-is to downstream clients.\n return {\n status: this.response.status === 500 ? 502 : this.status,\n headers: stripHopByHopHeaders(this.headers),\n body: this.toJSON(),\n }\n }\n\n get status(): number {\n return this.response.status\n }\n\n get headers(): Headers {\n return this.response.headers\n }\n\n get body(): LexErrorData<N> {\n return this.payload.body\n }\n}\n\nexport type { WWWAuthenticate }\n\n/**\n * Error class for 401 Unauthorized XRPC responses.\n *\n * Extends {@link XrpcResponseError} with access to parsed WWW-Authenticate header\n * information, useful for implementing authentication flows.\n *\n * Authentication errors are never retryable as they require user intervention\n * (e.g., re-authentication, token refresh).\n *\n * @typeParam M - The XRPC method type\n * @typeParam N - The error code type\n *\n * @example Handling authentication errors\n * ```typescript\n * try {\n * await client.xrpc(someMethod, options)\n * } catch (err) {\n * if (err instanceof XrpcAuthenticationError) {\n * const { DPoP } = err.wwwAuthenticate\n * if (DPoP?.error === 'use_dpop_nonce') {\n * // Handle DPoP nonce requirement\n * }\n * }\n * }\n * ```\n */\nexport class XrpcAuthenticationError<\n M extends Procedure | Query = Procedure | Query,\n N extends LexErrorCode = LexErrorCode,\n> extends XrpcResponseError<M, N> {\n name = 'XrpcAuthenticationError'\n\n override shouldRetry(): boolean {\n return false\n }\n\n #wwwAuthenticateCached?: WWWAuthenticate\n /**\n * Parsed WWW-Authenticate header from the response.\n * Contains authentication scheme parameters (e.g., Bearer realm, DPoP nonce).\n */\n get wwwAuthenticate(): WWWAuthenticate {\n return (this.#wwwAuthenticateCached ??=\n parseWWWAuthenticateHeader(\n this.response.headers.get('www-authenticate'),\n ) ?? {})\n }\n\n override toDownstreamError(): DownstreamError {\n return {\n status: 401,\n headers: stripHopByHopHeaders(this.headers),\n body: this.toJSON(),\n }\n }\n}\n\n/**\n * Error class for invalid or unprocessable XRPC responses from upstream servers.\n *\n * This occurs when the server returns a response that doesn't conform to the\n * XRPC protocol, such as:\n * - Missing or invalid Content-Type header\n * - Response body that doesn't match the method's output schema\n * - Non-JSON error responses\n * - Responses from non-XRPC endpoints\n *\n * The error code is always 'UpstreamFailure' and maps to HTTP 502 Bad Gateway\n * when converted to a response.\n *\n * @typeParam M - The XRPC method type\n */\nexport class XrpcUpstreamError<\n M extends Procedure | Query = Procedure | Query,\n> extends XrpcError<M, 'UpstreamFailure', XrpcUpstreamError<M>> {\n name = 'XrpcUpstreamError'\n\n constructor(\n method: M,\n readonly response: Response,\n readonly payload: XrpcResponsePayload | null = null,\n message: string = `Unexpected upstream XRPC response`,\n options?: ErrorOptions,\n ) {\n super(method, 'UpstreamFailure', message, options)\n }\n\n override get reason(): this {\n return this\n }\n\n override shouldRetry(): boolean {\n return RETRYABLE_HTTP_STATUS_CODES.has(this.response.status)\n }\n\n override toDownstreamError(): DownstreamError {\n return { status: 502, body: this.toJSON() }\n }\n}\n\n/**\n * Error class for invalid XRPC responses that fail schema validation.\n *\n * This is a specific type of {@link XrpcUpstreamError} that indicates the\n * upstream server returned a response that was structurally valid but did not\n * conform to the expected schema for the method. This likely indicates a\n * mismatch between client and server versions or an issue with the server's\n * XRPC implementation.\n *\n * @typeParam M - The XRPC method type\n */\nexport class XrpcInvalidResponseError<\n M extends Procedure | Query = Procedure | Query,\n> extends XrpcUpstreamError<M> {\n name = 'XrpcInvalidResponseError'\n\n constructor(\n method: M,\n response: Response,\n payload: XrpcResponsePayload,\n readonly cause: LexValidationError,\n ) {\n super(method, response, payload, `Invalid response: ${cause.message}`, {\n cause,\n })\n }\n\n override toDownstreamError(): DownstreamError {\n // @NOTE This could be reflected as both a 500 (\"we\" are at fault) and 502\n // (\"they\" are at fault). We are using 502 here to allow downstream clients\n // to determine that the issue lies at the interface between us and the\n // upstream server, rather than an issue with our internal processing.\n return { status: 502, body: this.toJSON() }\n }\n}\n\n/**\n * Error class for internal/client-side errors during XRPC requests.\n *\n * This represents errors that occur before or during the request that are not\n * server responses, such as:\n * - Network errors (connection refused, DNS failure)\n * - Request timeouts\n * - Request aborted via AbortSignal\n * - Invalid request construction\n *\n * The error code is always 'InternalServerError' and these errors are\n * optimistically considered retryable.\n *\n * @typeParam M - The XRPC method type\n */\nexport class XrpcInternalError<\n M extends Procedure | Query = Procedure | Query,\n> extends XrpcError<M, 'InternalServerError', XrpcInternalError<M>> {\n name = 'XrpcInternalError'\n\n constructor(method: M, message?: string, options?: ErrorOptions) {\n super(\n method,\n 'InternalServerError',\n message ?? 'Unable to fulfill XRPC request',\n options,\n )\n }\n\n override get reason(): this {\n return this\n }\n\n override shouldRetry(): true {\n // Ideally, we would inspect the reason to determine if it's retryable\n // (by detecting network errors, timeouts, etc.). Since these cases are\n // highly platform-dependent, we optimistically assume all internal\n // errors are retryable.\n return true\n }\n\n override toJSON(): LexErrorData<'InternalServerError'> {\n // @NOTE Do not expose internal error details to downstream clients\n return { error: this.error, message: 'Internal Server Error' }\n }\n\n override toDownstreamError(): DownstreamError {\n return { status: 500, body: this.toJSON() }\n }\n}\n\n/**\n * Union type of all possible XRPC failure types.\n *\n * Used as the return type for safe/non-throwing XRPC methods. Check the\n * `success` property to distinguish between success and failure:\n *\n * @typeParam M - The XRPC method type\n *\n * @example\n * ```typescript\n * const result = await client.xrpcSafe(someMethod, options)\n * if (result.success) {\n * console.log(result.body) // XrpcResponse\n * } else {\n * // result is XrpcFailure (XrpcResponseError | XrpcUpstreamError | XrpcInternalError)\n * console.error(result.error, result.message)\n * }\n * ```\n */\nexport type XrpcFailure<M extends Procedure | Query = Procedure | Query> =\n // The server returned a valid XRPC error response\n | XrpcResponseError<M>\n // The response was not a valid XRPC response, or it does not match the schema\n | XrpcUpstreamError<M>\n // Something went wrong (network error, etc.)\n | XrpcInternalError<M>\n\n/**\n * Converts an unknown error into an appropriate {@link XrpcFailure} type.\n *\n * If the error is already an XrpcFailure for the given method, returns it as-is.\n * Otherwise, wraps it in an {@link XrpcInternalError}.\n *\n * @param method - The XRPC method that was called\n * @param cause - The error to convert\n * @returns An XrpcFailure instance\n *\n * @example\n * ```typescript\n * try {\n * const response = await fetch(...)\n * // ... process response\n * } catch (err) {\n * return asXrpcFailure(method, err)\n * }\n * ```\n */\nexport function asXrpcFailure<M extends Procedure | Query>(\n method: M,\n cause: unknown,\n): XrpcFailure<M> {\n if (\n cause instanceof XrpcResponseError ||\n cause instanceof XrpcUpstreamError ||\n cause instanceof XrpcInternalError\n ) {\n if (cause.method === method) return cause\n }\n\n return new XrpcInternalError(method, undefined, { cause })\n}\n\nconst HOP_BY_HOP_HEADERS = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n])\n\nfunction stripHopByHopHeaders(headers: Headers): Headers {\n const result = new Headers(headers)\n\n // Remove statically known hop-by-hop headers\n for (const name of HOP_BY_HOP_HEADERS) {\n result.delete(name)\n }\n\n // Remove headers listed in the \"Connection\" header\n const connection = headers.get('connection')\n if (connection) {\n for (const name of connection.split(',')) {\n result.delete(name.trim())\n }\n }\n\n // These are not actually hop-by-hop headers, but we remove them because the\n // upstream payload gets parsed and re-serialized, so content length and\n // encoding may no longer be accurate.\n result.delete('content-length')\n result.delete('content-encoding')\n\n return result\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["import { LexError, LexErrorCode, LexErrorData } from '@atproto/lex-data'\nimport {\n InferMethodError,\n LexValidationError,\n Procedure,\n Query,\n ResultFailure,\n lexErrorDataSchema,\n} from '@atproto/lex-schema'\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport { Agent } from './agent.js'\nimport { XrpcUnknownResponsePayload } from './types.js'\nimport {\n WWWAuthenticate,\n parseWWWAuthenticateHeader,\n} from './www-authenticate.js'\n\nexport type { XrpcUnknownResponsePayload }\n\nexport type DownstreamError<N extends LexErrorCode = LexErrorCode> = {\n status: number\n headers?: Headers\n encoding?: 'application/json'\n body: LexErrorData<N>\n}\n\n/**\n * HTTP status codes that indicate a transient error that may succeed on retry.\n *\n * Includes:\n * - 408 Request Timeout\n * - 425 Too Early\n * - 429 Too Many Requests (rate limited)\n * - 500 Internal Server Error\n * - 502 Bad Gateway\n * - 503 Service Unavailable\n * - 504 Gateway Timeout\n * - 522 Connection Timed Out (Cloudflare)\n * - 524 A Timeout Occurred (Cloudflare)\n */\nexport const RETRYABLE_HTTP_STATUS_CODES: ReadonlySet<number> = new Set([\n 408, 425, 429, 500, 502, 503, 504, 522, 524,\n])\n\nexport { LexError }\nexport type { LexErrorCode, LexErrorData }\n\n/**\n * The payload structure for XRPC error responses.\n *\n * All XRPC errors return JSON with an `error` code and optional `message`.\n *\n * @typeParam N - The specific error code type\n */\nexport type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> = {\n body: LexErrorData<N>\n encoding: 'application/json'\n}\n\n/**\n * All unsuccessful responses should follow a standard error response\n * schema. The Content-Type should be application/json, and the payload\n * should be a JSON object with the following fields:\n *\n * - `error` (string, required): type name of the error (generic ASCII\n * constant, no whitespace)\n * - `message` (string, optional): description of the error, appropriate for\n * display to humans\n *\n * This function checks whether a given payload matches this schema.\n */\nexport function isXrpcErrorPayload(\n payload: XrpcUnknownResponsePayload | null | undefined,\n): payload is XrpcErrorPayload {\n return (\n payload != null &&\n payload.encoding === 'application/json' &&\n lexErrorDataSchema.matches(payload.body)\n )\n}\n\n/**\n * Abstract base class for all XRPC errors.\n *\n * Extends {@link LexError} and implements {@link ResultFailure} for use with\n * safe/result-based error handling patterns.\n *\n * @typeParam M - The XRPC method type (Procedure or Query)\n * @typeParam N - The error code type\n * @typeParam TReason - The reason type for ResultFailure\n *\n * @see {@link XrpcResponseError} - For valid XRPC error responses\n * @see {@link XrpcUpstreamError} - For invalid/unexpected responses\n * @see {@link XrpcInternalError} - For network/internal errors\n */\nexport abstract class XrpcError<\n M extends Procedure | Query = Procedure | Query,\n N extends LexErrorCode = LexErrorCode,\n TReason = unknown,\n >\n extends LexError<N>\n implements ResultFailure<TReason>\n{\n name = 'XrpcError'\n\n constructor(\n readonly method: M,\n error: N,\n message: string = `${error} Lexicon RPC error`,\n options?: ErrorOptions,\n ) {\n super(error, message, options)\n }\n\n /**\n * @see {@link ResultFailure.success}\n */\n readonly success = false as const\n\n /**\n * @see {@link ResultFailure.reason}\n */\n abstract readonly reason: TReason\n\n /**\n * Indicates whether the error is transient and can be retried.\n */\n abstract shouldRetry(): boolean\n\n abstract toDownstreamError(): DownstreamError\n\n matchesSchemaErrors(): this is XrpcError<M, InferMethodError<M>> {\n return this.method.errors?.includes(this.error) ?? false\n }\n}\n\n/**\n * Error class for valid XRPC error responses from the server.\n *\n * This represents a properly formatted XRPC error where the server returned\n * a non-2xx status with a valid JSON error payload containing `error` and\n * optional `message` fields.\n *\n * Use {@link matchesSchemaErrors} to check if the error matches the method's declared\n * error types for type-safe error handling.\n *\n * @typeParam M - The XRPC method type\n * @typeParam N - The error code type (inferred from method or generic)\n *\n * @example Handling specific errors\n * ```typescript\n * try {\n * await client.xrpc(someMethod, options)\n * } catch (err) {\n * if (err instanceof XrpcResponseError && err.error === 'RecordNotFound') {\n * // Handle not found case\n * }\n * }\n * ```\n */\nexport class XrpcResponseError<\n M extends Procedure | Query = Procedure | Query,\n N extends LexErrorCode = InferMethodError<M> | LexErrorCode,\n> extends XrpcError<M, N, XrpcResponseError<M, N>> {\n name = 'XrpcResponseError'\n\n constructor(\n method: M,\n readonly response: Response,\n readonly payload: XrpcErrorPayload<N>,\n options?: ErrorOptions,\n ) {\n const { error, message } = payload.body\n super(method, error, message, options)\n }\n\n override get reason(): this {\n return this\n }\n\n override shouldRetry(): boolean {\n return RETRYABLE_HTTP_STATUS_CODES.has(this.response.status)\n }\n\n override toJSON(): LexErrorData<N> {\n return this.payload.body\n }\n\n override toDownstreamError(): DownstreamError {\n // If the upstream server returned a 5xx error, we want to return a 502 Bad\n // Gateway to downstream clients, as the issue is with the upstream server,\n // not us. We still return the original error code and message in the body\n // for transparency, but we do not want to expose internal server errors\n // from the upstream server as-is to downstream clients.\n return {\n status: this.response.status === 500 ? 502 : this.status,\n headers: stripHopByHopHeaders(this.headers),\n body: this.toJSON(),\n }\n }\n\n get status(): number {\n return this.response.status\n }\n\n get headers(): Headers {\n return this.response.headers\n }\n\n get body(): LexErrorData<N> {\n return this.payload.body\n }\n}\n\nexport type { WWWAuthenticate }\n\n/**\n * Error class for 401 Unauthorized XRPC responses.\n *\n * Extends {@link XrpcResponseError} with access to parsed WWW-Authenticate header\n * information, useful for implementing authentication flows.\n *\n * Authentication errors are never retryable as they require user intervention\n * (e.g., re-authentication, token refresh).\n *\n * @typeParam M - The XRPC method type\n * @typeParam N - The error code type\n *\n * @example Handling authentication errors\n * ```typescript\n * try {\n * await client.xrpc(someMethod, options)\n * } catch (err) {\n * if (err instanceof XrpcAuthenticationError) {\n * const { DPoP } = err.wwwAuthenticate\n * if (DPoP?.error === 'use_dpop_nonce') {\n * // Handle DPoP nonce requirement\n * }\n * }\n * }\n * ```\n */\nexport class XrpcAuthenticationError<\n M extends Procedure | Query = Procedure | Query,\n N extends LexErrorCode = LexErrorCode,\n> extends XrpcResponseError<M, N> {\n name = 'XrpcAuthenticationError'\n\n override shouldRetry(): boolean {\n return false\n }\n\n #wwwAuthenticateCached?: WWWAuthenticate\n /**\n * Parsed WWW-Authenticate header from the response.\n * Contains authentication scheme parameters (e.g., Bearer realm, DPoP nonce).\n */\n get wwwAuthenticate(): WWWAuthenticate {\n return (this.#wwwAuthenticateCached ??=\n parseWWWAuthenticateHeader(\n this.response.headers.get('www-authenticate'),\n ) ?? {})\n }\n\n override toDownstreamError(): DownstreamError {\n return {\n status: 401,\n headers: stripHopByHopHeaders(this.headers),\n body: this.toJSON(),\n }\n }\n}\n\n/**\n * Error class for invalid or unprocessable XRPC responses from upstream servers.\n *\n * This occurs when the server returns a response that doesn't conform to the\n * XRPC protocol, such as:\n * - Missing or invalid Content-Type header\n * - Response body that doesn't match the method's output schema\n * - Non-JSON error responses\n * - Responses from non-XRPC endpoints\n *\n * The error code is always 'UpstreamFailure' and maps to HTTP 502 Bad Gateway\n * when converted to a response.\n *\n * @typeParam M - The XRPC method type\n */\nexport class XrpcUpstreamError<\n M extends Procedure | Query = Procedure | Query,\n> extends XrpcError<M, 'UpstreamFailure', XrpcUpstreamError<M>> {\n name = 'XrpcUpstreamError'\n\n constructor(\n method: M,\n readonly response: Response,\n readonly payload: XrpcUnknownResponsePayload | null = null,\n message: string = `Unexpected upstream XRPC response`,\n options?: ErrorOptions,\n ) {\n super(method, 'UpstreamFailure', message, options)\n }\n\n override get reason(): this {\n return this\n }\n\n override shouldRetry(): boolean {\n return RETRYABLE_HTTP_STATUS_CODES.has(this.response.status)\n }\n\n override toDownstreamError(): DownstreamError {\n return { status: 502, body: this.toJSON() }\n }\n}\n\n/**\n * Error class for invalid XRPC responses that fail schema validation.\n *\n * This is a specific type of {@link XrpcUpstreamError} that indicates the\n * upstream server returned a response that was structurally valid but did not\n * conform to the expected schema for the method. This likely indicates a\n * mismatch between client and server versions or an issue with the server's\n * XRPC implementation.\n *\n * @typeParam M - The XRPC method type\n */\nexport class XrpcInvalidResponseError<\n M extends Procedure | Query = Procedure | Query,\n> extends XrpcUpstreamError<M> {\n name = 'XrpcInvalidResponseError'\n\n constructor(\n method: M,\n response: Response,\n payload: XrpcUnknownResponsePayload,\n readonly cause: LexValidationError,\n ) {\n super(method, response, payload, `Invalid response: ${cause.message}`, {\n cause,\n })\n }\n\n override toDownstreamError(): DownstreamError {\n // @NOTE This could be reflected as both a 500 (\"we\" are at fault) and 502\n // (\"they\" are at fault). We are using 502 here to allow downstream clients\n // to determine that the issue lies at the interface between us and the\n // upstream server, rather than an issue with our internal processing.\n return { status: 502, body: this.toJSON() }\n }\n}\n\n/**\n * Error class for unexpected internal/client-side errors during XRPC requests.\n *\n * The error code is always 'InternalServerError' and these errors not\n * considered retryable as they stem from unforeseen issues in the\n * implementation.\n *\n * @typeParam M - The XRPC method type\n */\nexport class XrpcInternalError<\n M extends Procedure | Query = Procedure | Query,\n> extends XrpcError<M, 'InternalServerError', XrpcInternalError<M>> {\n name = 'XrpcInternalError'\n\n constructor(method: M, message?: string, options?: ErrorOptions) {\n super(\n method,\n 'InternalServerError',\n message ?? 'Unable to fulfill XRPC request',\n options,\n )\n }\n\n override get reason(): this {\n return this\n }\n\n override shouldRetry(): boolean {\n return false\n }\n\n override toJSON(): LexErrorData {\n // @NOTE Do not expose internal error details to downstream clients\n return { error: this.error, message: 'Internal Server Error' }\n }\n\n override toDownstreamError(): DownstreamError {\n return { status: 500, body: this.toJSON() }\n }\n}\n\n/**\n * Special case of XrpcInternalError that specifically represents errors thrown\n * by {@link Agent.fetchHandler} during the XRPC request. This includes:\n * - Network errors (connection refused, DNS failure)\n * - Request timeouts\n * - Request aborted via AbortSignal\n *\n * These errors are optimistically considered retryable, as many fetch errors\n * are transient and may succeed on retry.\n */\nexport class XrpcFetchError<\n M extends Procedure | Query = Procedure | Query,\n> extends XrpcInternalError<M> {\n name = 'XrpcFetchError'\n\n constructor(method: M, cause: unknown) {\n const message = cause instanceof Error ? cause.message : String(cause)\n super(method, `Unexpected fetchHandler() error: ${message}`, { cause })\n }\n\n override shouldRetry(): boolean {\n // Ideally, we would inspect the reason to determine if it's retryable (by\n // detecting network errors, timeouts, etc.). Since these cases are highly\n // platform-dependent, we optimistically assume all fetch errors are\n // transient and retryable.\n return true\n }\n\n override toJSON(): LexErrorData {\n // @NOTE Do not expose internal error details to downstream clients\n return { error: this.error, message: 'Failed to perform upstream request' }\n }\n\n override toDownstreamError(): DownstreamError {\n // While it might technically be a 500 error, we use 502 Bad Gateway here to\n // indicate that the error occurred while communicating with the upstream\n // server, allowing downstream clients to distinguish between errors in our\n // internal processing (500) and errors in the upstream server or network\n // (502).\n return { status: 502, body: this.toJSON() }\n }\n}\n\n/**\n * Union type of all possible XRPC failure types.\n *\n * Used as the return type for safe/non-throwing XRPC methods. Check the\n * `success` property to distinguish between success and failure:\n *\n * @typeParam M - The XRPC method type\n *\n * @example\n * ```typescript\n * const result = await client.xrpcSafe(someMethod, options)\n * if (result.success) {\n * console.log(result.body) // XrpcResponse\n * } else {\n * // result is XrpcFailure (XrpcResponseError | XrpcUpstreamError | XrpcInternalError)\n * console.error(result.error, result.message)\n * }\n * ```\n */\nexport type XrpcFailure<M extends Procedure | Query = Procedure | Query> =\n // The server returned a valid XRPC error response\n | XrpcResponseError<M>\n // The response was not a valid XRPC response, or it does not match the schema\n | XrpcUpstreamError<M>\n // Something went wrong (network error, etc.)\n | XrpcInternalError<M>\n\n/**\n * Converts an unknown error into an appropriate {@link XrpcFailure} type.\n *\n * If the error is already an XrpcFailure for the given method, returns it as-is.\n * Otherwise, wraps it in an {@link XrpcInternalError}.\n *\n * @param method - The XRPC method that was called\n * @param cause - The error to convert\n * @returns An XrpcFailure instance\n *\n * @example\n * ```typescript\n * try {\n * const response = await fetch(...)\n * // ... process response\n * } catch (err) {\n * return asXrpcFailure(method, err)\n * }\n * ```\n */\nexport function asXrpcFailure<M extends Procedure | Query>(\n method: M,\n cause: unknown,\n): XrpcFailure<M> {\n if (\n cause instanceof XrpcResponseError ||\n cause instanceof XrpcUpstreamError ||\n cause instanceof XrpcInternalError\n ) {\n if (cause.method === method) return cause\n }\n\n return new XrpcInternalError(method, undefined, { cause })\n}\n\nconst HOP_BY_HOP_HEADERS = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n])\n\nfunction stripHopByHopHeaders(headers: Headers): Headers {\n const result = new Headers(headers)\n\n // Remove statically known hop-by-hop headers\n for (const name of HOP_BY_HOP_HEADERS) {\n result.delete(name)\n }\n\n // Remove headers listed in the \"Connection\" header\n const connection = headers.get('connection')\n if (connection) {\n for (const name of connection.split(',')) {\n result.delete(name.trim())\n }\n }\n\n // These are not actually hop-by-hop headers, but we remove them because the\n // upstream payload gets parsed and re-serialized, so content length and\n // encoding may no longer be accurate.\n result.delete('content-length')\n result.delete('content-encoding')\n\n return result\n}\n"]}
@@ -1,6 +1,62 @@
1
- import { InferMethodOutputEncoding, Procedure, Query, ResultSuccess } from '@atproto/lex-schema';
2
- import { XrpcResponseBody, XrpcResponsePayload } from './util.js';
3
- export type { XrpcResponseBody, XrpcResponsePayload };
1
+ import { InferMethodOutputEncoding, InferOutput, LexValue, Payload, Procedure, Query, ResultSuccess, Validator } from '@atproto/lex-schema';
2
+ import { EncodingString } from './types.js';
3
+ type InferEncodingType<TEncoding extends string> = TEncoding extends '*/*' ? EncodingString : TEncoding extends `${infer T extends string}/*` ? `${T}/${string}` : TEncoding;
4
+ type InferBodyType<TEncoding extends string, TSchema> = TSchema extends Validator ? InferOutput<TSchema> : TEncoding extends `application/json` ? LexValue : Uint8Array;
5
+ /**
6
+ * The body type of an XRPC response, inferred from the method's output schema.
7
+ *
8
+ * For JSON responses, this is the parsed LexValue. For binary responses,
9
+ * this is a Uint8Array.
10
+ *
11
+ * @typeParam M - The XRPC method type (Procedure or Query)
12
+ */
13
+ export type XrpcResponseBody<M extends Procedure | Query> = M['output'] extends Payload<infer TEncoding, infer TSchema> ? TEncoding extends string ? InferBodyType<TEncoding, TSchema> : undefined : never;
14
+ /**
15
+ * The full payload type of an XRPC response, including body and encoding.
16
+ *
17
+ * Returns `null` for methods that have no output.
18
+ *
19
+ * @typeParam M - The XRPC method type (Procedure or Query)
20
+ */
21
+ export type XrpcResponsePayload<M extends Procedure | Query> = M['output'] extends Payload<infer TEncoding, infer TSchema> ? TEncoding extends string ? {
22
+ encoding: InferEncodingType<TEncoding>;
23
+ body: InferBodyType<TEncoding, TSchema>;
24
+ } : undefined : never;
25
+ export type XrpcResponseOptions = {
26
+ /**
27
+ * Whether to validate the response against the method's output schema.
28
+ * Disabling this can improve performance but may lead to runtime errors if
29
+ * the response does not conform to the expected schema. Only set this to
30
+ * `false` if you are certain that the upstream service will always return
31
+ * valid responses.
32
+ *
33
+ * @default true
34
+ */
35
+ validateResponse?: boolean;
36
+ /**
37
+ * Whether to strictly process response payloads according to Lex encoding
38
+ * rules. By default, the client will reject responses with invalid Lex data
39
+ * (floats and invalid $bytes / $link objects).
40
+ *
41
+ * Setting this option to `false` will allow the client to accept such
42
+ * responses in a non-strict mode, where invalid Lex data will be returned
43
+ * as-is (e.g., floats will not be rejected, and invalid $bytes / $link
44
+ * objects will not be converted to Uint8Array / Cid). When in non-strict
45
+ * mode, the validation will also be relaxed when validating the response
46
+ * against the method's output schema, allowing values that do not strictly
47
+ * conform to the schema (e.g. datetime strings that are not valid RFC3339
48
+ * format, blobs that are not of the right size/mime-type, etc.) to be
49
+ * accepted as long as their basic structure is correct.
50
+ *
51
+ * When validation is enabled (the default), the values defined through the
52
+ * method schema will be enforced, ensuring that the client can still process
53
+ * the response even if the server returns invalid Lex data.
54
+ *
55
+ * @default true
56
+ * @see {@link LexParseOptions.strict}
57
+ */
58
+ strictResponseProcessing?: boolean;
59
+ };
4
60
  /**
5
61
  * Small container for XRPC response data.
6
62
  *
@@ -42,8 +98,7 @@ export declare class XrpcResponse<M extends Procedure | Query> implements Result
42
98
  * @throws {XrpcUpstreamError} when the response is not a valid XRPC
43
99
  * response, or if the response does not conform to the method's schema.
44
100
  */
45
- static fromFetchResponse<const M extends Procedure | Query>(method: M, response: Response, options?: {
46
- validateResponse?: boolean;
47
- }): Promise<XrpcResponse<M>>;
101
+ static fromFetchResponse<const M extends Procedure | Query>(method: M, response: Response, options?: XrpcResponseOptions): Promise<XrpcResponse<M>>;
48
102
  }
103
+ export {};
49
104
  //# sourceMappingURL=response.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AACA,OAAO,EACL,yBAAyB,EACzB,SAAS,EACT,KAAK,EACL,aAAa,EACd,MAAM,qBAAqB,CAAA;AAQ5B,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAKjE,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAA;AAErD;;;;GAIG;AACH,qBAAa,YAAY,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,CACnD,YAAW,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAWvC,QAAQ,CAAC,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO;IACzB,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAZ1C,yCAAyC;IACzC,QAAQ,CAAC,OAAO,EAAG,IAAI,CAAS;IAEhC,uCAAuC;IACvC,IAAI,KAAK,IAAI,IAAI,CAEhB;gBAGU,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAG1C;;;OAGG;IACH,IAAI,QAAQ,YAEX;IAED;;;OAGG;IACH,IAAI,QAAQ,IACuB,yBAAyB,CAAC,CAAC,CAAC,CAC9D;IAED;;;;;;OAMG;IACH,IAAI,IAAI,IACuB,gBAAgB,CAAC,CAAC,CAAC,CACjD;IAED;;;;;;;OAOG;WACU,iBAAiB,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,EAC9D,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAAE,GACvC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;CAmG5B"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AACA,OAAO,EACL,yBAAyB,EACzB,WAAW,EACX,QAAQ,EACR,OAAO,EACP,SAAS,EACT,KAAK,EACL,aAAa,EACb,SAAS,EACV,MAAM,qBAAqB,CAAA;AAQ5B,OAAO,EACL,cAAc,EAGf,MAAM,YAAY,CAAA;AAWnB,KAAK,iBAAiB,CAAC,SAAS,SAAS,MAAM,IAAI,SAAS,SAAS,KAAK,GACtE,cAAc,GACd,SAAS,SAAS,GAAG,MAAM,CAAC,SAAS,MAAM,IAAI,GAC7C,GAAG,CAAC,IAAI,MAAM,EAAE,GAChB,SAAS,CAAA;AAEf,KAAK,aAAa,CAChB,SAAS,SAAS,MAAM,EACxB,OAAO,IACL,OAAO,SAAS,SAAS,GACzB,WAAW,CAAC,OAAO,CAAC,GACpB,SAAS,SAAS,kBAAkB,GAClC,QAAQ,GACR,UAAU,CAAA;AAEhB;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,IACtD,CAAC,CAAC,QAAQ,CAAC,SAAS,OAAO,CAAC,MAAM,SAAS,EAAE,MAAM,OAAO,CAAC,GACvD,SAAS,SAAS,MAAM,GACtB,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,GACjC,SAAS,GACX,KAAK,CAAA;AAEX;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,IACzD,CAAC,CAAC,QAAQ,CAAC,SAAS,OAAO,CAAC,MAAM,SAAS,EAAE,MAAM,OAAO,CAAC,GACvD,SAAS,SAAS,MAAM,GACtB;IACE,QAAQ,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAA;IACtC,IAAI,EAAE,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;CACxC,GACD,SAAS,GACX,KAAK,CAAA;AAEX,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAE1B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAA;CACnC,CAAA;AAED;;;;GAIG;AACH,qBAAa,YAAY,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,CACnD,YAAW,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAWvC,QAAQ,CAAC,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO;IACzB,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAZ1C,yCAAyC;IACzC,QAAQ,CAAC,OAAO,EAAG,IAAI,CAAS;IAEhC,uCAAuC;IACvC,IAAI,KAAK,IAAI,IAAI,CAEhB;gBAGU,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAG1C;;;OAGG;IACH,IAAI,QAAQ,YAEX;IAED;;;OAGG;IACH,IAAI,QAAQ,IACuB,yBAAyB,CAAC,CAAC,CAAC,CAC9D;IAED;;;;;;OAMG;IACH,IAAI,IAAI,IACuB,gBAAgB,CAAC,CAAC,CAAC,CACjD;IAED;;;;;;;OAOG;WACU,iBAAiB,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,EAC9D,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;CAmG5B"}
package/dist/response.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.XrpcResponse = void 0;
4
4
  const lex_json_1 = require("@atproto/lex-json");
5
5
  const errors_js_1 = require("./errors.js");
6
+ const types_js_1 = require("./types.js");
6
7
  const CONTENT_TYPE_BINARY = 'application/octet-stream';
7
8
  const CONTENT_TYPE_JSON = 'application/json';
8
9
  /**
@@ -66,8 +67,8 @@ class XrpcResponse {
66
67
  // @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here
67
68
  if (response.status < 200 || response.status >= 300) {
68
69
  // Always parse json for error responses
69
- const payload = await readPayload(response, { parse: true }).catch((cause) => {
70
- throw new errors_js_1.XrpcUpstreamError(method, response, null, 'Unable to parse response payload', { cause });
70
+ const payload = await readPayload(method, response, {
71
+ parse: { strict: options?.strictResponseProcessing ?? true },
71
72
  });
72
73
  // Properly formatted XRPC error response ?
73
74
  if (response.status >= 400 && (0, errors_js_1.isXrpcErrorPayload)(payload)) {
@@ -82,11 +83,11 @@ class XrpcResponse {
82
83
  ? 'Invalid response payload'
83
84
  : 'Invalid response status code');
84
85
  }
85
- // Only parse json if the schema expects it
86
- const payload = await readPayload(response, {
87
- parse: method.output.encoding === CONTENT_TYPE_JSON,
88
- }).catch((cause) => {
89
- throw new errors_js_1.XrpcUpstreamError(method, response, null, 'Unable to parse response payload', { cause });
86
+ const payload = await readPayload(method, response, {
87
+ // Only parse json if the schema expects it
88
+ parse: method.output.encoding === CONTENT_TYPE_JSON && {
89
+ strict: options?.strictResponseProcessing ?? true,
90
+ },
90
91
  });
91
92
  // Response is successful (2xx). Validate payload (data and encoding) against schema.
92
93
  if (method.output.encoding == null) {
@@ -104,10 +105,17 @@ class XrpcResponse {
104
105
  }
105
106
  // Assert valid response body.
106
107
  if (method.output.schema && options?.validateResponse !== false) {
107
- const result = method.output.schema.safeParse(payload.body);
108
+ const result = method.output.schema.safeParse(payload.body, {
109
+ strict: options?.strictResponseProcessing ?? true,
110
+ });
108
111
  if (!result.success) {
109
112
  throw new errors_js_1.XrpcInvalidResponseError(method, response, payload, result.reason);
110
113
  }
114
+ const parsedPayload = {
115
+ body: result.value,
116
+ encoding: payload.encoding,
117
+ };
118
+ return new XrpcResponse(method, response.status, response.headers, parsedPayload);
111
119
  }
112
120
  }
113
121
  return new XrpcResponse(method, response.status, response.headers, payload);
@@ -117,39 +125,50 @@ exports.XrpcResponse = XrpcResponse;
117
125
  /**
118
126
  * @note this function always consumes the response body
119
127
  */
120
- async function readPayload(response, options) {
121
- // @TODO Should we limit the maximum response size here (this could also be
122
- // done by the FetchHandler)?
123
- const encoding = response.headers
124
- .get('content-type')
125
- ?.split(';')[0]
126
- .trim()
127
- .toLowerCase();
128
- // Response content-type is undefined
129
- if (!encoding) {
130
- // If the body is empty, return undefined (= no payload)
131
- const body = await response.arrayBuffer();
132
- if (body.byteLength === 0)
133
- return undefined;
134
- // If we got data despite no content-type, treat it as binary
135
- return {
136
- encoding: CONTENT_TYPE_BINARY,
137
- body: new Uint8Array(body),
138
- };
128
+ async function readPayload(method, response, options) {
129
+ try {
130
+ // @TODO Should we limit the maximum response size here (this could also be
131
+ // done by the FetchHandler)?
132
+ const encoding = response.headers
133
+ .get('content-type')
134
+ ?.split(';')[0]
135
+ .trim()
136
+ .toLowerCase();
137
+ // Response content-type is undefined
138
+ if (!encoding) {
139
+ // If the body is empty, return undefined (= no payload)
140
+ const arrayBuffer = await response.arrayBuffer();
141
+ if (arrayBuffer.byteLength === 0)
142
+ return undefined;
143
+ // If we got data despite no content-type, treat it as binary
144
+ return {
145
+ encoding: CONTENT_TYPE_BINARY,
146
+ body: new Uint8Array(arrayBuffer),
147
+ };
148
+ }
149
+ if (!(0, types_js_1.isEncodingString)(encoding)) {
150
+ throw new TypeError(`Invalid content-type "${encoding}" in response`);
151
+ }
152
+ if (options?.parse && encoding === CONTENT_TYPE_JSON) {
153
+ // @NOTE It might be worth returning the raw bytes here (Uint8Array) and
154
+ // perform the lex parsing using cborg/json, allowing to do
155
+ // bytes->LexValue in one step instead of bytes->text->JSON->LexValue.
156
+ // This would require adding encode/decode utilities to lex-json (similar
157
+ // to @ipld/dag-json)
158
+ const text = await response.text();
159
+ // @NOTE Using `lexParse(text)` (instead of `jsonToLex(json)`) here as
160
+ // using a reviver function during JSON.parse should be faster than
161
+ // parsing to JSON then converting to Lex (?)
162
+ // @TODO verify statement above
163
+ return { encoding, body: (0, lex_json_1.lexParse)(text, options.parse) };
164
+ }
165
+ const arrayBuffer = await response.arrayBuffer();
166
+ return { encoding, body: new Uint8Array(arrayBuffer) };
139
167
  }
140
- if (options?.parse && encoding === CONTENT_TYPE_JSON) {
141
- // @NOTE It might be worth returning the raw bytes here (Uint8Array) and
142
- // perform the lex parsing using cborg/json, allowing to do
143
- // bytes->LexValue in one step instead of bytes->text->JSON->LexValue.
144
- // This would require adding encode/decode utilities to lex-json (similar
145
- // to @ipld/dag-json)
146
- const text = await response.text();
147
- // @NOTE Using `lexParse(text)` (instead of `jsonToLex(json)`) here as
148
- // using a reviver function during JSON.parse should be faster than
149
- // parsing to JSON then converting to Lex (?)
150
- // @TODO verify statement above
151
- return { encoding, body: (0, lex_json_1.lexParse)(text) };
168
+ catch (cause) {
169
+ const message = 'Unable to parse response payload';
170
+ const messageDetail = cause instanceof TypeError ? cause.message : undefined;
171
+ throw new errors_js_1.XrpcUpstreamError(method, response, null, messageDetail ? `${message}: ${messageDetail}` : message, { cause });
152
172
  }
153
- return { encoding, body: new Uint8Array(await response.arrayBuffer()) };
154
173
  }
155
174
  //# sourceMappingURL=response.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"response.js","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"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","sourcesContent":["import { lexParse } from '@atproto/lex-json'\nimport {\n InferMethodOutputEncoding,\n Procedure,\n Query,\n ResultSuccess,\n} from '@atproto/lex-schema'\nimport {\n XrpcAuthenticationError,\n XrpcInvalidResponseError,\n XrpcResponseError,\n XrpcUpstreamError,\n isXrpcErrorPayload,\n} from './errors.js'\nimport { XrpcResponseBody, XrpcResponsePayload } from './util.js'\n\nconst CONTENT_TYPE_BINARY = 'application/octet-stream'\nconst CONTENT_TYPE_JSON = 'application/json'\n\nexport type { XrpcResponseBody, XrpcResponsePayload }\n\n/**\n * Small container for XRPC response data.\n *\n * @implements {ResultSuccess<XrpcResponse<M>>} for convenience in result handling contexts.\n */\nexport class XrpcResponse<M extends Procedure | Query>\n implements ResultSuccess<XrpcResponse<M>>\n{\n /** @see {@link ResultSuccess.success} */\n readonly success = true as const\n\n /** @see {@link ResultSuccess.value} */\n get value(): this {\n return this\n }\n\n constructor(\n readonly method: M,\n readonly status: number,\n readonly headers: Headers,\n readonly payload: XrpcResponsePayload<M>,\n ) {}\n\n /**\n * Whether the response payload was parsed as {@link LexValue} (`true`) or is\n * in binary form {@link Uint8Array} (`false`).\n */\n get isParsed() {\n return this.method.output.encoding === CONTENT_TYPE_JSON\n }\n\n /**\n * The Content-Type encoding of the response (e.g., 'application/json').\n * Returns `undefined` if the response has no body.\n */\n get encoding() {\n return this.payload?.encoding as InferMethodOutputEncoding<M>\n }\n\n /**\n * The parsed response body.\n *\n * For 'application/json' responses, this is the parsed and validated LexValue.\n * For binary responses, this is a Uint8Array.\n * Returns `undefined` if the response has no body.\n */\n get body() {\n return this.payload?.body as XrpcResponseBody<M>\n }\n\n /**\n * @throws {XrpcResponseError} in case of (valid) XRPC error responses. Use\n * {@link XrpcResponseError.matchesSchemaErrors} to narrow the error type based on\n * the method's declared error schema. This can be narrowed further as a\n * {@link XrpcAuthenticationError} if the error is an authentication error.\n * @throws {XrpcUpstreamError} when the response is not a valid XRPC\n * response, or if the response does not conform to the method's schema.\n */\n static async fromFetchResponse<const M extends Procedure | Query>(\n method: M,\n response: Response,\n options?: { validateResponse?: boolean },\n ): Promise<XrpcResponse<M>> {\n // @NOTE The body MUST either be read or canceled to avoid resource leaks.\n // Since nothing should cause an exception before \"readPayload\" is\n // called, we can safely not use a try/finally here.\n\n // @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here\n if (response.status < 200 || response.status >= 300) {\n // Always parse json for error responses\n const payload = await readPayload(response, { parse: true }).catch(\n (cause) => {\n throw new XrpcUpstreamError(\n method,\n response,\n null,\n 'Unable to parse response payload',\n { cause },\n )\n },\n )\n\n // Properly formatted XRPC error response ?\n if (response.status >= 400 && isXrpcErrorPayload(payload)) {\n throw response.status === 401\n ? new XrpcAuthenticationError<M>(method, response, payload)\n : new XrpcResponseError<M>(method, response, payload)\n }\n\n // Invalid XRPC response (we probably did not hit an XRPC implementation)\n throw new XrpcUpstreamError(\n method,\n response,\n payload,\n response.status >= 500\n ? 'Upstream server encountered an error'\n : response.status >= 400\n ? 'Invalid response payload'\n : 'Invalid response status code',\n )\n }\n\n // Only parse json if the schema expects it\n const payload = await readPayload(response, {\n parse: method.output.encoding === CONTENT_TYPE_JSON,\n }).catch((cause) => {\n throw new XrpcUpstreamError(\n method,\n response,\n null,\n 'Unable to parse response payload',\n { cause },\n )\n })\n\n // Response is successful (2xx). Validate payload (data and encoding) against schema.\n if (method.output.encoding == null) {\n // Schema expects no payload\n if (payload) {\n throw new XrpcUpstreamError(\n method,\n response,\n payload,\n `Expected response with no body, got ${payload.encoding}`,\n )\n }\n } else {\n // Schema expects a payload\n if (!payload || !method.output.matchesEncoding(payload.encoding)) {\n throw new XrpcUpstreamError(\n method,\n response,\n payload,\n payload\n ? `Expected ${method.output.encoding} response, got ${payload.encoding}`\n : `Expected non-empty response with content-type ${method.output.encoding}`,\n )\n }\n\n // Assert valid response body.\n if (method.output.schema && options?.validateResponse !== false) {\n const result = method.output.schema.safeParse(payload.body)\n\n if (!result.success) {\n throw new XrpcInvalidResponseError(\n method,\n response,\n payload,\n result.reason,\n )\n }\n }\n }\n\n return new XrpcResponse<M>(\n method,\n response.status,\n response.headers,\n payload as XrpcResponsePayload<M>,\n )\n }\n}\n\n/**\n * @note this function always consumes the response body\n */\nasync function readPayload(\n response: Response,\n options?: { parse?: boolean },\n): Promise<XrpcResponsePayload> {\n // @TODO Should we limit the maximum response size here (this could also be\n // done by the FetchHandler)?\n\n const encoding = response.headers\n .get('content-type')\n ?.split(';')[0]\n .trim()\n .toLowerCase()\n\n // Response content-type is undefined\n if (!encoding) {\n // If the body is empty, return undefined (= no payload)\n const body = await response.arrayBuffer()\n if (body.byteLength === 0) return undefined\n\n // If we got data despite no content-type, treat it as binary\n return {\n encoding: CONTENT_TYPE_BINARY,\n body: new Uint8Array(body),\n }\n }\n\n if (options?.parse && encoding === CONTENT_TYPE_JSON) {\n // @NOTE It might be worth returning the raw bytes here (Uint8Array) and\n // perform the lex parsing using cborg/json, allowing to do\n // bytes->LexValue in one step instead of bytes->text->JSON->LexValue.\n // This would require adding encode/decode utilities to lex-json (similar\n // to @ipld/dag-json)\n const text = await response.text()\n\n // @NOTE Using `lexParse(text)` (instead of `jsonToLex(json)`) here as\n // using a reviver function during JSON.parse should be faster than\n // parsing to JSON then converting to Lex (?)\n\n // @TODO verify statement above\n return { encoding, body: lexParse(text) }\n }\n\n return { encoding, body: new Uint8Array(await response.arrayBuffer()) }\n}\n"]}
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"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","sourcesContent":["import { LexParseOptions, lexParse } from '@atproto/lex-json'\nimport {\n InferMethodOutputEncoding,\n InferOutput,\n LexValue,\n Payload,\n Procedure,\n Query,\n ResultSuccess,\n Validator,\n} from '@atproto/lex-schema'\nimport {\n XrpcAuthenticationError,\n XrpcInvalidResponseError,\n XrpcResponseError,\n XrpcUpstreamError,\n isXrpcErrorPayload,\n} from './errors.js'\nimport {\n EncodingString,\n XrpcUnknownResponsePayload,\n isEncodingString,\n} from './types.js'\n\nconst CONTENT_TYPE_BINARY = 'application/octet-stream'\nconst CONTENT_TYPE_JSON = 'application/json'\n\n// @NOTE the output schema is used in \"parse\" mode (safeParse), which means that\n// defaults will be applied and coercions will be performed, so we need to use\n// InferOutput here to get the final parsed type, not Infer/InferInput. For this\n// reason, we cannot use InferMethodOutputBody and InferMethodOutput from\n// lex-schema here.\n\ntype InferEncodingType<TEncoding extends string> = TEncoding extends '*/*'\n ? EncodingString\n : TEncoding extends `${infer T extends string}/*`\n ? `${T}/${string}`\n : TEncoding\n\ntype InferBodyType<\n TEncoding extends string,\n TSchema,\n> = TSchema extends Validator\n ? InferOutput<TSchema>\n : TEncoding extends `application/json`\n ? LexValue\n : Uint8Array\n\n/**\n * The body type of an XRPC response, inferred from the method's output schema.\n *\n * For JSON responses, this is the parsed LexValue. For binary responses,\n * this is a Uint8Array.\n *\n * @typeParam M - The XRPC method type (Procedure or Query)\n */\nexport type XrpcResponseBody<M extends Procedure | Query> =\n M['output'] extends Payload<infer TEncoding, infer TSchema>\n ? TEncoding extends string\n ? InferBodyType<TEncoding, TSchema>\n : undefined\n : never\n\n/**\n * The full payload type of an XRPC response, including body and encoding.\n *\n * Returns `null` for methods that have no output.\n *\n * @typeParam M - The XRPC method type (Procedure or Query)\n */\nexport type XrpcResponsePayload<M extends Procedure | Query> =\n M['output'] extends Payload<infer TEncoding, infer TSchema>\n ? TEncoding extends string\n ? {\n encoding: InferEncodingType<TEncoding>\n body: InferBodyType<TEncoding, TSchema>\n }\n : undefined\n : never\n\nexport type XrpcResponseOptions = {\n /**\n * Whether to validate the response against the method's output schema.\n * Disabling this can improve performance but may lead to runtime errors if\n * the response does not conform to the expected schema. Only set this to\n * `false` if you are certain that the upstream service will always return\n * valid responses.\n *\n * @default true\n */\n validateResponse?: boolean\n\n /**\n * Whether to strictly process response payloads according to Lex encoding\n * rules. By default, the client will reject responses with invalid Lex data\n * (floats and invalid $bytes / $link objects).\n *\n * Setting this option to `false` will allow the client to accept such\n * responses in a non-strict mode, where invalid Lex data will be returned\n * as-is (e.g., floats will not be rejected, and invalid $bytes / $link\n * objects will not be converted to Uint8Array / Cid). When in non-strict\n * mode, the validation will also be relaxed when validating the response\n * against the method's output schema, allowing values that do not strictly\n * conform to the schema (e.g. datetime strings that are not valid RFC3339\n * format, blobs that are not of the right size/mime-type, etc.) to be\n * accepted as long as their basic structure is correct.\n *\n * When validation is enabled (the default), the values defined through the\n * method schema will be enforced, ensuring that the client can still process\n * the response even if the server returns invalid Lex data.\n *\n * @default true\n * @see {@link LexParseOptions.strict}\n */\n strictResponseProcessing?: boolean\n}\n\n/**\n * Small container for XRPC response data.\n *\n * @implements {ResultSuccess<XrpcResponse<M>>} for convenience in result handling contexts.\n */\nexport class XrpcResponse<M extends Procedure | Query>\n implements ResultSuccess<XrpcResponse<M>>\n{\n /** @see {@link ResultSuccess.success} */\n readonly success = true as const\n\n /** @see {@link ResultSuccess.value} */\n get value(): this {\n return this\n }\n\n constructor(\n readonly method: M,\n readonly status: number,\n readonly headers: Headers,\n readonly payload: XrpcResponsePayload<M>,\n ) {}\n\n /**\n * Whether the response payload was parsed as {@link LexValue} (`true`) or is\n * in binary form {@link Uint8Array} (`false`).\n */\n get isParsed() {\n return this.method.output.encoding === CONTENT_TYPE_JSON\n }\n\n /**\n * The Content-Type encoding of the response (e.g., 'application/json').\n * Returns `undefined` if the response has no body.\n */\n get encoding() {\n return this.payload?.encoding as InferMethodOutputEncoding<M>\n }\n\n /**\n * The parsed response body.\n *\n * For 'application/json' responses, this is the parsed and validated LexValue.\n * For binary responses, this is a Uint8Array.\n * Returns `undefined` if the response has no body.\n */\n get body() {\n return this.payload?.body as XrpcResponseBody<M>\n }\n\n /**\n * @throws {XrpcResponseError} in case of (valid) XRPC error responses. Use\n * {@link XrpcResponseError.matchesSchemaErrors} to narrow the error type based on\n * the method's declared error schema. This can be narrowed further as a\n * {@link XrpcAuthenticationError} if the error is an authentication error.\n * @throws {XrpcUpstreamError} when the response is not a valid XRPC\n * response, or if the response does not conform to the method's schema.\n */\n static async fromFetchResponse<const M extends Procedure | Query>(\n method: M,\n response: Response,\n options?: XrpcResponseOptions,\n ): Promise<XrpcResponse<M>> {\n // @NOTE The body MUST either be read or canceled to avoid resource leaks.\n // Since nothing should cause an exception before \"readPayload\" is\n // called, we can safely not use a try/finally here.\n\n // @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here\n if (response.status < 200 || response.status >= 300) {\n // Always parse json for error responses\n const payload = await readPayload(method, response, {\n parse: { strict: options?.strictResponseProcessing ?? true },\n })\n\n // Properly formatted XRPC error response ?\n if (response.status >= 400 && isXrpcErrorPayload(payload)) {\n throw response.status === 401\n ? new XrpcAuthenticationError<M>(method, response, payload)\n : new XrpcResponseError<M>(method, response, payload)\n }\n\n // Invalid XRPC response (we probably did not hit an XRPC implementation)\n throw new XrpcUpstreamError(\n method,\n response,\n payload,\n response.status >= 500\n ? 'Upstream server encountered an error'\n : response.status >= 400\n ? 'Invalid response payload'\n : 'Invalid response status code',\n )\n }\n\n const payload = await readPayload(method, response, {\n // Only parse json if the schema expects it\n parse: method.output.encoding === CONTENT_TYPE_JSON && {\n strict: options?.strictResponseProcessing ?? true,\n },\n })\n\n // Response is successful (2xx). Validate payload (data and encoding) against schema.\n if (method.output.encoding == null) {\n // Schema expects no payload\n if (payload) {\n throw new XrpcUpstreamError(\n method,\n response,\n payload,\n `Expected response with no body, got ${payload.encoding}`,\n )\n }\n } else {\n // Schema expects a payload\n if (!payload || !method.output.matchesEncoding(payload.encoding)) {\n throw new XrpcUpstreamError(\n method,\n response,\n payload,\n payload\n ? `Expected ${method.output.encoding} response, got ${payload.encoding}`\n : `Expected non-empty response with content-type ${method.output.encoding}`,\n )\n }\n\n // Assert valid response body.\n if (method.output.schema && options?.validateResponse !== false) {\n const result = method.output.schema.safeParse(payload.body, {\n strict: options?.strictResponseProcessing ?? true,\n })\n\n if (!result.success) {\n throw new XrpcInvalidResponseError(\n method,\n response,\n payload,\n result.reason,\n )\n }\n\n const parsedPayload = {\n body: result.value,\n encoding: payload.encoding,\n } as XrpcResponsePayload<M>\n\n return new XrpcResponse<M>(\n method,\n response.status,\n response.headers,\n parsedPayload,\n )\n }\n }\n\n return new XrpcResponse<M>(\n method,\n response.status,\n response.headers,\n payload as XrpcResponsePayload<M>,\n )\n }\n}\n\ntype ReadPayloadOptions = {\n /**\n * Whether to parse the response body as JSON and convert it to LexValue.\n *\n * @default false\n */\n parse?: false | LexParseOptions\n}\n\n/**\n * @note this function always consumes the response body\n */\nasync function readPayload(\n method: Query | Procedure,\n response: Response,\n options?: ReadPayloadOptions,\n): Promise<undefined | XrpcUnknownResponsePayload> {\n try {\n // @TODO Should we limit the maximum response size here (this could also be\n // done by the FetchHandler)?\n\n const encoding = response.headers\n .get('content-type')\n ?.split(';')[0]\n .trim()\n .toLowerCase()\n\n // Response content-type is undefined\n if (!encoding) {\n // If the body is empty, return undefined (= no payload)\n const arrayBuffer = await response.arrayBuffer()\n if (arrayBuffer.byteLength === 0) return undefined\n\n // If we got data despite no content-type, treat it as binary\n return {\n encoding: CONTENT_TYPE_BINARY,\n body: new Uint8Array(arrayBuffer),\n }\n }\n\n if (!isEncodingString(encoding)) {\n throw new TypeError(`Invalid content-type \"${encoding}\" in response`)\n }\n\n if (options?.parse && encoding === CONTENT_TYPE_JSON) {\n // @NOTE It might be worth returning the raw bytes here (Uint8Array) and\n // perform the lex parsing using cborg/json, allowing to do\n // bytes->LexValue in one step instead of bytes->text->JSON->LexValue.\n // This would require adding encode/decode utilities to lex-json (similar\n // to @ipld/dag-json)\n const text = await response.text()\n\n // @NOTE Using `lexParse(text)` (instead of `jsonToLex(json)`) here as\n // using a reviver function during JSON.parse should be faster than\n // parsing to JSON then converting to Lex (?)\n\n // @TODO verify statement above\n return { encoding, body: lexParse(text, options.parse) }\n }\n\n const arrayBuffer = await response.arrayBuffer()\n return { encoding, body: new Uint8Array(arrayBuffer) }\n } catch (cause) {\n const message = 'Unable to parse response payload'\n const messageDetail = cause instanceof TypeError ? cause.message : undefined\n throw new XrpcUpstreamError(\n method,\n response,\n null,\n messageDetail ? `${message}: ${messageDetail}` : message,\n { cause },\n )\n }\n}\n"]}