@atproto/lex-client 0.0.10 → 0.0.11

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.js CHANGED
@@ -1,17 +1,15 @@
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
+ exports.RETRYABLE_HTTP_STATUS_CODES = new Set([
11
+ 408, 425, 429, 500, 502, 503, 504, 522, 524,
12
+ ]);
15
13
  /**
16
14
  * All unsuccessful responses should follow a standard error response
17
15
  * schema. The Content-Type should be application/json, and the payload
@@ -27,107 +25,128 @@ exports.XrpcError = XrpcError;
27
25
  function isXrpcErrorPayload(payload) {
28
26
  return (payload !== null &&
29
27
  payload.encoding === 'application/json' &&
30
- lex_schema_1.l.lexErrorData.matches(payload.body));
28
+ lex_schema_1.lexErrorDataSchema.matches(payload.body));
29
+ }
30
+ class XrpcError extends lex_data_1.LexError {
31
+ method;
32
+ name = 'XrpcError';
33
+ constructor(method, error, message = `${error} Lexicon RPC error`, options) {
34
+ super(error, message, options);
35
+ this.method = method;
36
+ }
37
+ /**
38
+ * @see {@link ResultFailure.success}
39
+ */
40
+ success = false;
41
+ matchesSchema() {
42
+ return this.method.errors?.includes(this.error) ?? false;
43
+ }
31
44
  }
45
+ exports.XrpcError = XrpcError;
32
46
  /**
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.
47
+ * Class used to represent an HTTP request that resulted in an XRPC method
48
+ * error. That is, a non-2xx response with a valid XRPC error payload.
35
49
  */
36
50
  class XrpcResponseError extends XrpcError {
37
- method;
38
- status;
39
- headers;
51
+ response;
40
52
  payload;
41
53
  name = 'XrpcResponseError';
42
- constructor(method, status, headers, payload, options) {
54
+ constructor(method, response, payload, options) {
43
55
  const { error, message } = payload.body;
44
- super(error, message, options);
45
- this.method = method;
46
- this.status = status;
47
- this.headers = headers;
56
+ super(method, error, message, options);
57
+ this.response = response;
48
58
  this.payload = payload;
49
59
  }
50
- success = false;
51
60
  get reason() {
52
61
  return this;
53
62
  }
54
- get body() {
55
- return this.payload.body;
56
- }
57
- matchesSchema() {
58
- return this.method.errors?.includes(this.error) ?? false;
59
- }
60
63
  shouldRetry() {
61
- // Do not retry client errors
62
- if (this.status < 500)
63
- return false;
64
- return true;
64
+ return exports.RETRYABLE_HTTP_STATUS_CODES.has(this.response.status);
65
65
  }
66
66
  toJSON() {
67
67
  return this.payload.body;
68
68
  }
69
69
  toResponse() {
70
- const { status, headers } = this;
71
- return Response.json(this.toJSON(), { status, headers });
70
+ // Re-expose schema-valid errors as-is to downstream clients
71
+ if (this.matchesSchema()) {
72
+ const status = this.response.status >= 500 ? 502 : this.response.status;
73
+ return Response.json(this.toJSON(), { status });
74
+ }
75
+ return this.response.status >= 500
76
+ ? // The upstream server had an error, return a generic upstream failure
77
+ Response.json({ error: 'UpstreamFailure' }, { status: 502 })
78
+ : // If the error is on our side, return a generic internal server error
79
+ Response.json({ error: 'InternalServerError' }, { status: 500 });
80
+ }
81
+ get body() {
82
+ return this.payload.body;
72
83
  }
73
84
  }
74
85
  exports.XrpcResponseError = XrpcResponseError;
86
+ class XrpcAuthenticationError extends XrpcResponseError {
87
+ name = 'XrpcAuthenticationError';
88
+ shouldRetry() {
89
+ return false;
90
+ }
91
+ #wwwAuthenticate;
92
+ get wwwAuthenticate() {
93
+ return (this.#wwwAuthenticate ??=
94
+ (0, www_authenticate_js_1.parseWWWAuthenticateHeader)(this.response.headers.get('www-authenticate')) ?? {});
95
+ }
96
+ }
97
+ exports.XrpcAuthenticationError = XrpcAuthenticationError;
75
98
  /**
76
- * This class represents an invalid XRPC response from the server.
99
+ * This class represents invalid or unprocessable XRPC response from the
100
+ * upstream server.
77
101
  */
78
102
  class XrpcUpstreamError extends XrpcError {
79
- name = 'XrpcUpstreamError';
80
- // For debugging purposes, we keep the response details here
81
103
  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
- };
104
+ payload;
105
+ name = 'XrpcUpstreamError';
106
+ constructor(method, response, payload, message = `Unexpected upstream XRPC response`, options) {
107
+ super(method, 'UpstreamFailure', message, options);
108
+ this.response = response;
109
+ this.payload = payload;
89
110
  }
90
- success = false;
91
111
  get reason() {
92
112
  return this;
93
113
  }
94
- matchesSchema() {
95
- return false;
96
- }
97
114
  shouldRetry() {
98
- // Do not retry client errors
99
- return this.response.status >= 500;
115
+ return exports.RETRYABLE_HTTP_STATUS_CODES.has(this.response.status);
100
116
  }
101
117
  toResponse() {
102
118
  return Response.json(this.toJSON(), { status: 502 });
103
119
  }
104
120
  }
105
121
  exports.XrpcUpstreamError = XrpcUpstreamError;
106
- class XrpcUnexpectedError extends XrpcError {
107
- name = 'XrpcUnexpectedError';
108
- constructor(message, options) {
109
- super('InternalServerError', message, options);
122
+ class XrpcInternalError extends XrpcError {
123
+ name = 'XrpcInternalError';
124
+ constructor(method, message, options) {
125
+ super(method, 'InternalServerError', message ?? 'Unable to fulfill XRPC request', options);
110
126
  }
111
- success = false;
112
127
  get reason() {
113
- return this.cause;
114
- }
115
- matchesSchema() {
116
- return false;
128
+ return this;
117
129
  }
118
130
  shouldRetry() {
131
+ // Ideally, we would inspect the reason to determine if it's retryable
132
+ // (by detecting network errors, timeouts, etc.). Since these cases are
133
+ // highly platform-dependent, we optimistically assume all internal
134
+ // errors are retryable.
119
135
  return true;
120
136
  }
121
137
  toResponse() {
122
- return Response.json(this.toJSON(), { status: 500 });
138
+ // Do not expose internal error details to downstream clients
139
+ return Response.json({ error: this.error }, { status: 500 });
123
140
  }
124
- static from(cause, message = cause instanceof lex_data_1.LexError
125
- ? cause.message
126
- : 'XRPC request failed') {
127
- if (cause instanceof XrpcUnexpectedError)
141
+ }
142
+ exports.XrpcInternalError = XrpcInternalError;
143
+ function asXrpcFailure(method, cause) {
144
+ if (cause instanceof XrpcResponseError ||
145
+ cause instanceof XrpcUpstreamError ||
146
+ cause instanceof XrpcInternalError) {
147
+ if (cause.method === method)
128
148
  return cause;
129
- return new XrpcUnexpectedError(message, { cause });
130
149
  }
150
+ return new XrpcInternalError(method, undefined, { cause });
131
151
  }
132
- exports.XrpcUnexpectedError = XrpcUnexpectedError;
133
152
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
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 { l } from '@atproto/lex-schema'\nimport { XrpcPayload } from './util.js'\n\nexport { LexError }\nexport type { LexErrorCode, LexErrorData }\n\nexport type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> =\n XrpcPayload<LexErrorData<N>, 'application/json'>\n\nexport class XrpcError<\n N extends LexErrorCode = LexErrorCode,\n> extends LexError<N> {\n name = 'XrpcError'\n\n constructor(\n error: N,\n message: string = `${error} Lexicon RPC error`,\n options?: ErrorOptions,\n ) {\n super(error, message, options)\n }\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: XrpcPayload | null,\n): payload is XrpcErrorPayload {\n return (\n payload !== null &&\n payload.encoding === 'application/json' &&\n l.lexErrorData.matches(payload.body)\n )\n}\n\n/**\n * Interface representing a failed XRPC request result.\n */\ntype LexRpcFailureResult<N extends LexErrorCode, E> = l.ResultFailure<E> & {\n readonly error: N\n shouldRetry(): boolean\n matchesSchema(): boolean\n}\n\n/**\n * Class used to represent an HTTP request that resulted in an XRPC method error\n * That is, a non-2xx response with a valid XRPC error payload.\n */\nexport class XrpcResponseError<\n M extends l.Procedure | l.Query = l.Procedure | l.Query,\n N extends LexErrorCode = LexErrorCode,\n >\n extends XrpcError<N>\n implements LexRpcFailureResult<N, XrpcResponseError<M, N>>\n{\n name = 'XrpcResponseError'\n\n constructor(\n readonly method: M,\n readonly status: number,\n readonly headers: Headers,\n readonly payload: XrpcErrorPayload<N>,\n options?: ErrorOptions,\n ) {\n const { error, message } = payload.body\n super(error, message, options)\n }\n\n readonly success = false\n\n get reason(): this {\n return this as this\n }\n\n get body(): LexErrorData {\n return this.payload.body\n }\n\n matchesSchema(): this is M extends {\n errors: readonly (infer E extends string)[]\n }\n ? XrpcResponseError<M, E>\n : never {\n return this.method.errors?.includes(this.error) ?? false\n }\n\n shouldRetry(): boolean {\n // Do not retry client errors\n if (this.status < 500) return false\n\n return true\n }\n\n toJSON() {\n return this.payload.body\n }\n\n toResponse(): Response {\n const { status, headers } = this\n return Response.json(this.toJSON(), { status, headers })\n }\n}\n\n/**\n * This class represents an invalid XRPC response from the server.\n */\nexport class XrpcUpstreamError<\n N extends 'InvalidResponse' | 'UpstreamFailure' =\n | 'InvalidResponse'\n | 'UpstreamFailure',\n >\n extends XrpcError<N>\n implements LexRpcFailureResult<N, XrpcUpstreamError<N>>\n{\n name = 'XrpcUpstreamError' as const\n\n // For debugging purposes, we keep the response details here\n readonly response: {\n status: number\n headers: Headers\n payload: XrpcPayload | null\n }\n\n constructor(\n error: N,\n message: string,\n response: { status: number; headers: Headers },\n payload: XrpcPayload | null,\n options?: ErrorOptions,\n ) {\n super(error, message, { cause: options?.cause })\n this.response = {\n status: response.status,\n headers: response.headers,\n payload,\n }\n }\n\n readonly success = false as const\n\n get reason(): this {\n return this\n }\n\n matchesSchema(): false {\n return false\n }\n\n shouldRetry(): boolean {\n // Do not retry client errors\n return this.response.status >= 500\n }\n\n toResponse(): Response {\n return Response.json(this.toJSON(), { status: 502 })\n }\n}\n\nexport class XrpcUnexpectedError\n extends XrpcError<'InternalServerError'>\n implements LexRpcFailureResult<'InternalServerError', unknown>\n{\n name = 'XrpcUnexpectedError' as const\n\n protected constructor(message: string, options: Required<ErrorOptions>) {\n super('InternalServerError', message, options)\n }\n\n readonly success = false\n\n get reason() {\n return this.cause\n }\n\n matchesSchema(): false {\n return false\n }\n\n shouldRetry(): boolean {\n return true\n }\n\n toResponse(): Response {\n return Response.json(this.toJSON(), { status: 500 })\n }\n\n static from(\n cause: unknown,\n message: string = cause instanceof LexError\n ? cause.message\n : 'XRPC request failed',\n ): XrpcUnexpectedError {\n if (cause instanceof XrpcUnexpectedError) return cause\n return new XrpcUnexpectedError(message, { cause })\n }\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 Procedure,\n Query,\n ResultFailure,\n lexErrorDataSchema,\n} from '@atproto/lex-schema'\nimport { XrpcPayload } from './util.js'\nimport {\n WWWAuthenticate,\n parseWWWAuthenticateHeader,\n} from './www-authenticate.js'\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\nexport type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> =\n XrpcPayload<LexErrorData<N>, 'application/json'>\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: XrpcPayload | null,\n): payload is XrpcErrorPayload {\n return (\n payload !== null &&\n payload.encoding === 'application/json' &&\n lexErrorDataSchema.matches(payload.body)\n )\n}\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 matchesSchema(): this is XrpcError<M, InferMethodError<M>> {\n return this.method.errors?.includes(this.error) ?? false\n }\n}\n\n/**\n * Class used to represent an HTTP request that resulted in an XRPC method\n * error. That is, a non-2xx response with a valid XRPC error payload.\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() {\n return this.payload.body\n }\n\n override toResponse(): Response {\n // Re-expose schema-valid errors as-is to downstream clients\n if (this.matchesSchema()) {\n const status = this.response.status >= 500 ? 502 : this.response.status\n return Response.json(this.toJSON(), { status })\n }\n\n return this.response.status >= 500\n ? // The upstream server had an error, return a generic upstream failure\n Response.json({ error: 'UpstreamFailure' }, { status: 502 })\n : // If the error is on our side, return a generic internal server error\n Response.json({ error: 'InternalServerError' }, { status: 500 })\n }\n\n get body(): LexErrorData {\n return this.payload.body\n }\n}\n\nexport type { WWWAuthenticate }\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 #wwwAuthenticate?: WWWAuthenticate\n get wwwAuthenticate(): WWWAuthenticate {\n return (this.#wwwAuthenticate ??=\n parseWWWAuthenticateHeader(\n this.response.headers.get('www-authenticate'),\n ) ?? {})\n }\n}\n\n/**\n * This class represents invalid or unprocessable XRPC response from the\n * upstream server.\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: XrpcPayload | 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 toResponse(): Response {\n return Response.json(this.toJSON(), { status: 502 })\n }\n}\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 toResponse(): Response {\n // Do not expose internal error details to downstream clients\n return Response.json({ error: this.error }, { status: 500 })\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\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"]}
@@ -7,7 +7,7 @@ export type XrpcResponsePayload<M extends Procedure | Query> = InferMethodOutput
7
7
  *
8
8
  * @implements {ResultSuccess<XrpcResponse<M>>} for convenience in result handling contexts.
9
9
  */
10
- export declare class XrpcResponse<const M extends Procedure | Query> implements ResultSuccess<XrpcResponse<M>> {
10
+ export declare class XrpcResponse<M extends Procedure | Query> implements ResultSuccess<XrpcResponse<M>> {
11
11
  readonly method: M;
12
12
  readonly status: number;
13
13
  readonly headers: Headers;
@@ -27,7 +27,8 @@ export declare class XrpcResponse<const M extends Procedure | Query> implements
27
27
  /**
28
28
  * @throws {XrpcResponseError} in case of (valid) XRPC error responses. Use
29
29
  * {@link XrpcResponseError.matchesSchema} to narrow the error type based on
30
- * the method's declared error schema.
30
+ * the method's declared error schema. This can be narrowed further as a
31
+ * {@link XrpcAuthenticationError} if the error is an authentication error.
31
32
  * @throws {XrpcUpstreamError} when the response is not a valid XRPC
32
33
  * response, or if the response does not conform to the method's schema.
33
34
  */
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,SAAS,EACT,KAAK,EACL,aAAa,EACd,MAAM,qBAAqB,CAAA;AAM5B,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAEvC,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,IACtD,qBAAqB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;AAEtC,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,IACzD,yBAAyB,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,SAAS,MAAM,GACvD,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GACnC,IAAI,CAAA;AAEV;;;;GAIG;AACH,qBAAa,YAAY,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,CACzD,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,IAAI,QAAQ,IACuB,yBAAyB,CAAC,CAAC,CAAC,CAC9D;IAED,IAAI,IAAI,IACuB,gBAAgB,CAAC,CAAC,CAAC,CACjD;IAED;;;;;;OAMG;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;CA0F5B"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,SAAS,EACT,KAAK,EACL,aAAa,EACd,MAAM,qBAAqB,CAAA;AAO5B,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAEvC,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,IACtD,qBAAqB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;AAEtC,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,IACzD,yBAAyB,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,SAAS,MAAM,GACvD,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GACnC,IAAI,CAAA;AAEV;;;;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,IAAI,QAAQ,IACuB,yBAAyB,CAAC,CAAC,CAAC,CAC9D;IAED,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;CAoG5B"}
package/dist/response.js CHANGED
@@ -41,7 +41,8 @@ class XrpcResponse {
41
41
  /**
42
42
  * @throws {XrpcResponseError} in case of (valid) XRPC error responses. Use
43
43
  * {@link XrpcResponseError.matchesSchema} to narrow the error type based on
44
- * the method's declared error schema.
44
+ * the method's declared error schema. This can be narrowed further as a
45
+ * {@link XrpcAuthenticationError} if the error is an authentication error.
45
46
  * @throws {XrpcUpstreamError} when the response is not a valid XRPC
46
47
  * response, or if the response does not conform to the method's schema.
47
48
  */
@@ -52,40 +53,47 @@ class XrpcResponse {
52
53
  // @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here
53
54
  if (response.status < 200 || response.status >= 300) {
54
55
  // Always parse json for error responses
55
- const payload = await readPayload(response, { parse: true });
56
+ const payload = await readPayload(response, { parse: true }).catch((cause) => {
57
+ throw new errors_js_1.XrpcUpstreamError(method, response, null, 'Unable to parse response payload', { cause });
58
+ });
59
+ // Properly formatted XRPC error response ?
56
60
  if (response.status >= 400 && (0, errors_js_1.isXrpcErrorPayload)(payload)) {
57
- throw new errors_js_1.XrpcResponseError(method, response.status, response.headers, payload);
61
+ throw response.status === 401
62
+ ? new errors_js_1.XrpcAuthenticationError(method, response, payload)
63
+ : new errors_js_1.XrpcResponseError(method, response, payload);
58
64
  }
59
- if (response.status >= 500) {
60
- throw new errors_js_1.XrpcUpstreamError('UpstreamFailure', `Upstream server encountered an error`, response, payload);
61
- }
62
- throw new errors_js_1.XrpcUpstreamError('InvalidResponse', response.status >= 400
63
- ? `Upstream server returned an invalid response payload`
64
- : `Upstream server returned an invalid status code`, response, payload);
65
+ // Invalid XRPC response (we probably did not hit an XRPC implementation)
66
+ throw new errors_js_1.XrpcUpstreamError(method, response, payload, response.status >= 500
67
+ ? 'Upstream server encountered an error'
68
+ : response.status >= 400
69
+ ? 'Invalid response payload'
70
+ : 'Invalid response status code');
65
71
  }
66
72
  // Only parse json if the schema expects it
67
73
  const payload = await readPayload(response, {
68
74
  parse: shouldParse(method),
75
+ }).catch((cause) => {
76
+ throw new errors_js_1.XrpcUpstreamError(method, response, null, 'Unable to parse response payload', { cause });
69
77
  });
70
78
  // Response is successful (2xx). Validate payload (data and encoding) against schema.
71
79
  if (method.output.encoding == null) {
72
80
  // Schema expects no payload
73
81
  if (payload) {
74
- throw new errors_js_1.XrpcUpstreamError('InvalidResponse', `Expected response with no body, got ${payload.encoding}`, response, payload);
82
+ throw new errors_js_1.XrpcUpstreamError(method, response, payload, `Expected response with no body, got ${payload.encoding}`);
75
83
  }
76
84
  }
77
85
  else {
78
86
  // Schema expects a payload
79
87
  if (!payload || !method.output.matchesEncoding(payload.encoding)) {
80
- throw new errors_js_1.XrpcUpstreamError('InvalidResponse', payload
88
+ throw new errors_js_1.XrpcUpstreamError(method, response, payload, payload
81
89
  ? `Expected ${method.output.encoding} response, got ${payload.encoding}`
82
- : `Expected non-empty response with content-type ${method.output.encoding}`, response, payload);
90
+ : `Expected non-empty response with content-type ${method.output.encoding}`);
83
91
  }
84
92
  // Assert valid response body.
85
93
  if (method.output.schema && options?.validateResponse !== false) {
86
94
  const result = method.output.schema.safeParse(payload.body);
87
95
  if (!result.success) {
88
- throw new errors_js_1.XrpcUpstreamError('InvalidResponse', `Response validation failed: ${result.reason.message}`, response, payload, { cause: result.reason });
96
+ throw new errors_js_1.XrpcUpstreamError(method, response, payload, `Response validation failed: ${result.reason.message}`, { cause: result.reason });
89
97
  }
90
98
  }
91
99
  }
@@ -126,16 +134,11 @@ async function readPayload(response, options) {
126
134
  // This would require adding encode/decode utilities to lex-json (similar
127
135
  // to @ipld/dag-json)
128
136
  const text = await response.text();
129
- try {
130
- // @NOTE Using `lexParse(text)` (instead of `jsonToLex(json)`) here as
131
- // using a reviver function during JSON.parse should be faster than
132
- // parsing to JSON then converting to Lex (?)
133
- // @TODO verify statement above
134
- return { encoding, body: (0, lex_json_1.lexParse)(text) };
135
- }
136
- catch (cause) {
137
- throw new errors_js_1.XrpcUpstreamError('InvalidResponse', 'Invalid JSON response body', response, null, { cause });
138
- }
137
+ // @NOTE Using `lexParse(text)` (instead of `jsonToLex(json)`) here as
138
+ // using a reviver function during JSON.parse should be faster than
139
+ // parsing to JSON then converting to Lex (?)
140
+ // @TODO verify statement above
141
+ return { encoding, body: (0, lex_json_1.lexParse)(text) };
139
142
  }
140
143
  return { encoding, body: new Uint8Array(await response.arrayBuffer()) };
141
144
  }
@@ -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 InferMethodOutputBody,\n InferMethodOutputEncoding,\n Procedure,\n Query,\n ResultSuccess,\n} from '@atproto/lex-schema'\nimport {\n XrpcResponseError,\n XrpcUpstreamError,\n isXrpcErrorPayload,\n} from './errors.js'\nimport { XrpcPayload } from './util.js'\n\nexport type XrpcResponseBody<M extends Procedure | Query> =\n InferMethodOutputBody<M, Uint8Array>\n\nexport type XrpcResponsePayload<M extends Procedure | Query> =\n InferMethodOutputEncoding<M> extends infer E extends string\n ? XrpcPayload<XrpcResponseBody<M>, E>\n : null\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<const 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.encoding === 'application/json' && shouldParse(this.method)\n }\n\n get encoding() {\n return this.payload?.encoding as InferMethodOutputEncoding<M>\n }\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.matchesSchema} to narrow the error type based on\n * the method's declared error schema.\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 })\n\n if (response.status >= 400 && isXrpcErrorPayload(payload)) {\n throw new XrpcResponseError(\n method,\n response.status,\n response.headers,\n payload,\n )\n }\n\n if (response.status >= 500) {\n throw new XrpcUpstreamError(\n 'UpstreamFailure',\n `Upstream server encountered an error`,\n response,\n payload,\n )\n }\n\n throw new XrpcUpstreamError(\n 'InvalidResponse',\n response.status >= 400\n ? `Upstream server returned an invalid response payload`\n : `Upstream server returned an invalid status code`,\n response,\n payload,\n )\n }\n\n // Only parse json if the schema expects it\n const payload = await readPayload(response, {\n parse: shouldParse(method),\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 'InvalidResponse',\n `Expected response with no body, got ${payload.encoding}`,\n response,\n payload,\n )\n }\n } else {\n // Schema expects a payload\n if (!payload || !method.output.matchesEncoding(payload.encoding)) {\n throw new XrpcUpstreamError(\n 'InvalidResponse',\n payload\n ? `Expected ${method.output.encoding} response, got ${payload.encoding}`\n : `Expected non-empty response with content-type ${method.output.encoding}`,\n response,\n payload,\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 XrpcUpstreamError(\n 'InvalidResponse',\n `Response validation failed: ${result.reason.message}`,\n response,\n payload,\n { cause: 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\nfunction shouldParse(method: Procedure | Query) {\n return method.output.encoding === 'application/json'\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<XrpcPayload | null> {\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 null (= no payload)\n const body = await response.arrayBuffer()\n if (body.byteLength === 0) return null\n\n // If we got data despite no content-type, treat it as binary\n return {\n encoding: 'application/octet-stream',\n body: new Uint8Array(body),\n }\n }\n\n if (options?.parse && encoding === 'application/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 try {\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 } catch (cause) {\n throw new XrpcUpstreamError(\n 'InvalidResponse',\n 'Invalid JSON response body',\n response,\n null,\n { cause },\n )\n }\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":";;;AAAA,gDAA4C;AAQ5C,2CAKoB;AAWpB;;;;GAIG;AACH,MAAa,YAAY;IAYZ;IACA;IACA;IACA;IAZX,yCAAyC;IAChC,OAAO,GAAG,IAAa,CAAA;IAEhC,uCAAuC;IACvC,IAAI,KAAK;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IAED,YACW,MAAS,EACT,MAAc,EACd,OAAgB,EAChB,OAA+B;QAH/B,WAAM,GAAN,MAAM,CAAG;QACT,WAAM,GAAN,MAAM,CAAQ;QACd,YAAO,GAAP,OAAO,CAAS;QAChB,YAAO,GAAP,OAAO,CAAwB;IACvC,CAAC;IAEJ;;;OAGG;IACH,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,QAAQ,KAAK,kBAAkB,IAAI,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACzE,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,OAAO,EAAE,QAAwC,CAAA;IAC/D,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,EAAE,IAA2B,CAAA;IAClD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAC5B,MAAS,EACT,QAAkB,EAClB,OAAwC;QAExC,0EAA0E;QAC1E,kEAAkE;QAClE,oDAAoD;QAEpD,4EAA4E;QAC5E,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YACpD,wCAAwC;YACxC,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAChE,CAAC,KAAK,EAAE,EAAE;gBACR,MAAM,IAAI,6BAAiB,CACzB,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,kCAAkC,EAClC,EAAE,KAAK,EAAE,CACV,CAAA;YACH,CAAC,CACF,CAAA;YAED,2CAA2C;YAC3C,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,IAAA,8BAAkB,EAAC,OAAO,CAAC,EAAE,CAAC;gBAC1D,MAAM,QAAQ,CAAC,MAAM,KAAK,GAAG;oBAC3B,CAAC,CAAC,IAAI,mCAAuB,CAAI,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;oBAC3D,CAAC,CAAC,IAAI,6BAAiB,CAAI,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;YACzD,CAAC;YAED,yEAAyE;YACzE,MAAM,IAAI,6BAAiB,CACzB,MAAM,EACN,QAAQ,EACR,OAAO,EACP,QAAQ,CAAC,MAAM,IAAI,GAAG;gBACpB,CAAC,CAAC,sCAAsC;gBACxC,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,GAAG;oBACtB,CAAC,CAAC,0BAA0B;oBAC5B,CAAC,CAAC,8BAA8B,CACrC,CAAA;QACH,CAAC;QAED,2CAA2C;QAC3C,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE;YAC1C,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC;SAC3B,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACjB,MAAM,IAAI,6BAAiB,CACzB,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,kCAAkC,EAClC,EAAE,KAAK,EAAE,CACV,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,qFAAqF;QACrF,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;YACnC,4BAA4B;YAC5B,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,IAAI,6BAAiB,CACzB,MAAM,EACN,QAAQ,EACR,OAAO,EACP,uCAAuC,OAAO,CAAC,QAAQ,EAAE,CAC1D,CAAA;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,2BAA2B;YAC3B,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjE,MAAM,IAAI,6BAAiB,CACzB,MAAM,EACN,QAAQ,EACR,OAAO,EACP,OAAO;oBACL,CAAC,CAAC,YAAY,MAAM,CAAC,MAAM,CAAC,QAAQ,kBAAkB,OAAO,CAAC,QAAQ,EAAE;oBACxE,CAAC,CAAC,iDAAiD,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAC9E,CAAA;YACH,CAAC;YAED,8BAA8B;YAC9B,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,OAAO,EAAE,gBAAgB,KAAK,KAAK,EAAE,CAAC;gBAChE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;gBAE3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpB,MAAM,IAAI,6BAAiB,CACzB,MAAM,EACN,QAAQ,EACR,OAAO,EACP,+BAA+B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EACtD,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CACzB,CAAA;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,IAAI,YAAY,CACrB,MAAM,EACN,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,OAAO,EAChB,OAAiC,CAClC,CAAA;IACH,CAAC;CACF;AAlJD,oCAkJC;AAED,SAAS,WAAW,CAAC,MAAyB;IAC5C,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,KAAK,kBAAkB,CAAA;AACtD,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,WAAW,CACxB,QAAkB,EAClB,OAA6B;IAE7B,2EAA2E;IAC3E,6BAA6B;IAE7B,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO;SAC9B,GAAG,CAAC,cAAc,CAAC;QACpB,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SACd,IAAI,EAAE;SACN,WAAW,EAAE,CAAA;IAEhB,qCAAqC;IACrC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,mDAAmD;QACnD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAA;QACzC,IAAI,IAAI,CAAC,UAAU,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QAEtC,6DAA6D;QAC7D,OAAO;YACL,QAAQ,EAAE,0BAA0B;YACpC,IAAI,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC;SAC3B,CAAA;IACH,CAAC;IAED,IAAI,OAAO,EAAE,KAAK,IAAI,QAAQ,KAAK,kBAAkB,EAAE,CAAC;QACtD,wEAAwE;QACxE,2DAA2D;QAC3D,sEAAsE;QACtE,yEAAyE;QACzE,qBAAqB;QACrB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAElC,sEAAsE;QACtE,mEAAmE;QACnE,6CAA6C;QAE7C,+BAA+B;QAC/B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAA,mBAAQ,EAAC,IAAI,CAAC,EAAE,CAAA;IAC3C,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,EAAE,CAAA;AACzE,CAAC","sourcesContent":["import { lexParse } from '@atproto/lex-json'\nimport {\n InferMethodOutputBody,\n InferMethodOutputEncoding,\n Procedure,\n Query,\n ResultSuccess,\n} from '@atproto/lex-schema'\nimport {\n XrpcAuthenticationError,\n XrpcResponseError,\n XrpcUpstreamError,\n isXrpcErrorPayload,\n} from './errors.js'\nimport { XrpcPayload } from './util.js'\n\nexport type XrpcResponseBody<M extends Procedure | Query> =\n InferMethodOutputBody<M, Uint8Array>\n\nexport type XrpcResponsePayload<M extends Procedure | Query> =\n InferMethodOutputEncoding<M> extends infer E extends string\n ? XrpcPayload<XrpcResponseBody<M>, E>\n : null\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.encoding === 'application/json' && shouldParse(this.method)\n }\n\n get encoding() {\n return this.payload?.encoding as InferMethodOutputEncoding<M>\n }\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.matchesSchema} 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: shouldParse(method),\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 XrpcUpstreamError(\n method,\n response,\n payload,\n `Response validation failed: ${result.reason.message}`,\n { cause: 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\nfunction shouldParse(method: Procedure | Query) {\n return method.output.encoding === 'application/json'\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<XrpcPayload | null> {\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 null (= no payload)\n const body = await response.arrayBuffer()\n if (body.byteLength === 0) return null\n\n // If we got data despite no content-type, treat it as binary\n return {\n encoding: 'application/octet-stream',\n body: new Uint8Array(body),\n }\n }\n\n if (options?.parse && encoding === 'application/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"]}
@@ -0,0 +1,12 @@
1
+ type WWWAuthenticateParams = {
2
+ [authParam in string]: string;
3
+ };
4
+ export type WWWAuthenticate = {
5
+ [authScheme in string]: string | WWWAuthenticateParams;
6
+ };
7
+ /**
8
+ * Returns `undefined` if the header is malformed.
9
+ */
10
+ export declare function parseWWWAuthenticateHeader(header?: unknown): undefined | WWWAuthenticate;
11
+ export {};
12
+ //# sourceMappingURL=www-authenticate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"www-authenticate.d.ts","sourceRoot":"","sources":["../src/www-authenticate.ts"],"names":[],"mappings":"AAAA,KAAK,qBAAqB,GAAG;KAAG,SAAS,IAAI,MAAM,GAAG,MAAM;CAAE,CAAA;AAC9D,MAAM,MAAM,eAAe,GAAG;KAC3B,UAAU,IAAI,MAAM,GACjB,MAAM,GACN,qBAAqB;CAC1B,CAAA;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,CAAC,EAAE,OAAO,GACf,SAAS,GAAG,eAAe,CAgE7B"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseWWWAuthenticateHeader = parseWWWAuthenticateHeader;
4
+ /**
5
+ * Returns `undefined` if the header is malformed.
6
+ */
7
+ function parseWWWAuthenticateHeader(header) {
8
+ if (typeof header !== 'string')
9
+ return undefined;
10
+ const wwwAuthenticate = {};
11
+ // Split over commas, not within quoted strings
12
+ const trimmedHeader = header.trim();
13
+ if (!trimmedHeader)
14
+ return wwwAuthenticate;
15
+ const parts = trimmedHeader.split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/);
16
+ let currentParams = null;
17
+ for (let part of parts) {
18
+ // Check if the part starts with an auth scheme
19
+ const schemeMatch = part.trim().match(/^([^"=\s]+)(\s+.*)?$/);
20
+ if (schemeMatch) {
21
+ const scheme = schemeMatch[1];
22
+ // Duplicate scheme
23
+ if (Object.hasOwn(wwwAuthenticate, scheme))
24
+ return undefined;
25
+ const rest = schemeMatch[2]?.trim();
26
+ if (!rest) {
27
+ // Scheme only (no params or token68)
28
+ currentParams = null;
29
+ wwwAuthenticate[scheme] = Object.create(null);
30
+ continue;
31
+ }
32
+ if (!rest.includes('=')) {
33
+ // Scheme with token68
34
+ currentParams = null;
35
+ wwwAuthenticate[scheme] = rest;
36
+ continue;
37
+ }
38
+ // Scheme with params
39
+ currentParams = Object.create(null);
40
+ wwwAuthenticate[scheme] = currentParams;
41
+ // Fall through to parse params
42
+ part = rest;
43
+ }
44
+ // Invalid header
45
+ if (!currentParams)
46
+ return undefined;
47
+ const param = part.match(/^\s*([^"\s=]+)=(?:("[^"\\]*(?:\\.[^"\\]*)*")|([^\s,"]*))\s*$/);
48
+ // invalid param
49
+ if (!param)
50
+ return undefined;
51
+ const paramName = param[1];
52
+ const paramValue = param[3] ?? param[2].slice(1, -1).replaceAll(/\\(.)/g, '$1');
53
+ currentParams[paramName] = paramValue;
54
+ }
55
+ return wwwAuthenticate;
56
+ }
57
+ //# sourceMappingURL=www-authenticate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"www-authenticate.js","sourceRoot":"","sources":["../src/www-authenticate.ts"],"names":[],"mappings":";;AAUA,gEAkEC;AArED;;GAEG;AACH,SAAgB,0BAA0B,CACxC,MAAgB;IAEhB,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IAEhD,MAAM,eAAe,GAAoB,EAAE,CAAA;IAE3C,+CAA+C;IAC/C,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,EAAE,CAAA;IACnC,IAAI,CAAC,aAAa;QAAE,OAAO,eAAe,CAAA;IAE1C,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAA;IAEjE,IAAI,aAAa,GAAiC,IAAI,CAAA;IAEtD,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,+CAA+C;QAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAA;QAC7D,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;YAE7B,mBAAmB;YACnB,IAAI,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC;gBAAE,OAAO,SAAS,CAAA;YAE5D,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAA;YACnC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,qCAAqC;gBACrC,aAAa,GAAG,IAAI,CAAA;gBACpB,eAAe,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;gBAC7C,SAAQ;YACV,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,sBAAsB;gBACtB,aAAa,GAAG,IAAI,CAAA;gBACpB,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA;gBAC9B,SAAQ;YACV,CAAC;YAED,qBAAqB;YAErB,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAA0B,CAAA;YAC5D,eAAe,CAAC,MAAM,CAAC,GAAG,aAAa,CAAA;YAEvC,+BAA+B;YAC/B,IAAI,GAAG,IAAI,CAAA;QACb,CAAC;QAED,iBAAiB;QACjB,IAAI,CAAC,aAAa;YAAE,OAAO,SAAS,CAAA;QAEpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CACtB,8DAA8D,CAC/D,CAAA;QAED,gBAAgB;QAChB,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAA;QAE5B,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QAC1B,MAAM,UAAU,GACd,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAE/D,aAAa,CAAC,SAAS,CAAC,GAAG,UAAU,CAAA;IACvC,CAAC;IAED,OAAO,eAAe,CAAA;AACxB,CAAC","sourcesContent":["type WWWAuthenticateParams = { [authParam in string]: string }\nexport type WWWAuthenticate = {\n [authScheme in string]:\n | string // token68\n | WWWAuthenticateParams\n}\n\n/**\n * Returns `undefined` if the header is malformed.\n */\nexport function parseWWWAuthenticateHeader(\n header?: unknown,\n): undefined | WWWAuthenticate {\n if (typeof header !== 'string') return undefined\n\n const wwwAuthenticate: WWWAuthenticate = {}\n\n // Split over commas, not within quoted strings\n const trimmedHeader = header.trim()\n if (!trimmedHeader) return wwwAuthenticate\n\n const parts = trimmedHeader.split(/,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/)\n\n let currentParams: WWWAuthenticateParams | null = null\n\n for (let part of parts) {\n // Check if the part starts with an auth scheme\n const schemeMatch = part.trim().match(/^([^\"=\\s]+)(\\s+.*)?$/)\n if (schemeMatch) {\n const scheme = schemeMatch[1]\n\n // Duplicate scheme\n if (Object.hasOwn(wwwAuthenticate, scheme)) return undefined\n\n const rest = schemeMatch[2]?.trim()\n if (!rest) {\n // Scheme only (no params or token68)\n currentParams = null\n wwwAuthenticate[scheme] = Object.create(null)\n continue\n }\n\n if (!rest.includes('=')) {\n // Scheme with token68\n currentParams = null\n wwwAuthenticate[scheme] = rest\n continue\n }\n\n // Scheme with params\n\n currentParams = Object.create(null) as WWWAuthenticateParams\n wwwAuthenticate[scheme] = currentParams\n\n // Fall through to parse params\n part = rest\n }\n\n // Invalid header\n if (!currentParams) return undefined\n\n const param = part.match(\n /^\\s*([^\"\\s=]+)=(?:(\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\")|([^\\s,\"]*))\\s*$/,\n )\n\n // invalid param\n if (!param) return undefined\n\n const paramName = param[1]\n const paramValue =\n param[3] ?? param[2]!.slice(1, -1).replaceAll(/\\\\(.)/g, '$1')\n\n currentParams[paramName] = paramValue\n }\n\n return wwwAuthenticate\n}\n"]}
package/dist/xrpc.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { InferInput, InferPayload, Main, Params, Procedure, Query, Restricted, Subscription } from '@atproto/lex-schema';
2
2
  import { Agent } from './agent.js';
3
- import { XrpcResponseError, XrpcUnexpectedError, XrpcUpstreamError } from './errors.js';
3
+ import { XrpcFailure } from './errors.js';
4
4
  import { XrpcResponse } from './response.js';
5
5
  import { BinaryBodyInit, CallOptions } from './types.js';
6
6
  type XrpcParamsOptions<P extends Params> = NonNullable<unknown> extends P ? {
@@ -21,20 +21,12 @@ type XrpcInputOptions<In> = In extends {
21
21
  encoding?: undefined;
22
22
  };
23
23
  export type XrpcOptions<M extends Procedure | Query = Procedure | Query> = CallOptions & XrpcInputOptions<XrpcRequestPayload<M>> & XrpcParamsOptions<XrpcRequestParams<M>>;
24
- export type XrpcFailure<M extends Procedure | Query> = XrpcResponseError<M> | XrpcUpstreamError | XrpcUnexpectedError;
25
- export type XrpcResult<M extends Procedure | Query> = XrpcResponse<M> | XrpcFailure<M>;
26
- /**
27
- * Utility method to type cast the error thrown by {@link xrpc} to an
28
- * {@link XrpcFailure} matching the provided method. Only use this function
29
- * inside a catch block right after calling {@link xrpc}, and use the same
30
- * method type parameter as used in the {@link xrpc} call.
31
- */
32
- export declare function asXrpcFailure<M extends Procedure | Query = Procedure | Query>(err: unknown): XrpcFailure<M>;
33
24
  /**
34
25
  * @throws XrpcFailure<M>
35
26
  */
36
27
  export declare function xrpc<const M extends Query | Procedure>(agent: Agent, ns: NonNullable<unknown> extends XrpcOptions<M> ? Main<M> : Restricted<'This XRPC method requires an "options" argument'>): Promise<XrpcResponse<M>>;
37
28
  export declare function xrpc<const M extends Query | Procedure>(agent: Agent, ns: Main<M>, options: XrpcOptions<M>): Promise<XrpcResponse<M>>;
29
+ export type XrpcResult<M extends Procedure | Query> = XrpcResponse<M> | XrpcFailure<M>;
38
30
  export declare function xrpcSafe<const M extends Query | Procedure>(agent: Agent, ns: NonNullable<unknown> extends XrpcOptions<M> ? Main<M> : Restricted<'This XRPC method requires an "options" argument'>): Promise<XrpcResult<M>>;
39
31
  export declare function xrpcSafe<const M extends Query | Procedure>(agent: Agent, ns: Main<M>, options: XrpcOptions<M>): Promise<XrpcResult<M>>;
40
32
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"xrpc.d.ts","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"xrpc.d.ts","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"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"}
package/dist/xrpc.js CHANGED
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.asXrpcFailure = asXrpcFailure;
4
3
  exports.xrpc = xrpc;
5
4
  exports.xrpcSafe = xrpcSafe;
6
5
  const lex_data_1 = require("@atproto/lex-data");
@@ -9,37 +8,25 @@ const lex_schema_1 = require("@atproto/lex-schema");
9
8
  const errors_js_1 = require("./errors.js");
10
9
  const response_js_1 = require("./response.js");
11
10
  const util_js_1 = require("./util.js");
12
- /**
13
- * Utility method to type cast the error thrown by {@link xrpc} to an
14
- * {@link XrpcFailure} matching the provided method. Only use this function
15
- * inside a catch block right after calling {@link xrpc}, and use the same
16
- * method type parameter as used in the {@link xrpc} call.
17
- */
18
- function asXrpcFailure(err) {
19
- if (err instanceof errors_js_1.XrpcResponseError)
20
- return err;
21
- if (err instanceof errors_js_1.XrpcUpstreamError)
22
- return err;
23
- return errors_js_1.XrpcUnexpectedError.from(err);
24
- }
25
11
  async function xrpc(agent, ns, options = {}) {
26
- try {
27
- return await lexRpcRequest(agent, ns, options);
28
- }
29
- catch (err) {
30
- throw asXrpcFailure(err);
31
- }
12
+ const response = await xrpcSafe(agent, ns, options);
13
+ if (response.success)
14
+ return response;
15
+ else
16
+ throw response;
32
17
  }
33
18
  async function xrpcSafe(agent, ns, options = {}) {
34
- return lexRpcRequest(agent, ns, options).catch((asXrpcFailure));
35
- }
36
- async function lexRpcRequest(agent, ns, options = {}) {
37
- const method = (0, lex_schema_1.getMain)(ns);
38
19
  options.signal?.throwIfAborted();
39
- const url = xrpcRequestUrl(method, options);
40
- const request = xrpcRequestInit(method, options);
41
- const response = await agent.fetchHandler(url, request);
42
- return response_js_1.XrpcResponse.fromFetchResponse(method, response, options);
20
+ const method = (0, lex_schema_1.getMain)(ns);
21
+ try {
22
+ const url = xrpcRequestUrl(method, options);
23
+ const request = xrpcRequestInit(method, options);
24
+ const response = await agent.fetchHandler(url, request);
25
+ return await response_js_1.XrpcResponse.fromFetchResponse(method, response, options);
26
+ }
27
+ catch (cause) {
28
+ return (0, errors_js_1.asXrpcFailure)(method, cause);
29
+ }
43
30
  }
44
31
  function xrpcRequestUrl(method, options) {
45
32
  const path = `/xrpc/${method.nsid}`;