@atproto/lex-client 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/agent.d.ts +67 -0
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +31 -0
  5. package/dist/agent.js.map +1 -1
  6. package/dist/client.d.ts +443 -45
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +145 -1
  9. package/dist/client.js.map +1 -1
  10. package/dist/errors.d.ts +162 -9
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js +132 -8
  13. package/dist/errors.js.map +1 -1
  14. package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts +29 -25
  15. package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts.map +1 -1
  16. package/dist/lexicons/com/atproto/repo/createRecord.defs.js +3 -2
  17. package/dist/lexicons/com/atproto/repo/createRecord.defs.js.map +1 -1
  18. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts +17 -17
  19. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts.map +1 -1
  20. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.js.map +1 -1
  21. package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts +9 -9
  22. package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts.map +1 -1
  23. package/dist/lexicons/com/atproto/repo/getRecord.defs.js +1 -1
  24. package/dist/lexicons/com/atproto/repo/getRecord.defs.js.map +1 -1
  25. package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts +8 -8
  26. package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts.map +1 -1
  27. package/dist/lexicons/com/atproto/repo/listRecords.defs.js +1 -1
  28. package/dist/lexicons/com/atproto/repo/listRecords.defs.js.map +1 -1
  29. package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts +31 -27
  30. package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts.map +1 -1
  31. package/dist/lexicons/com/atproto/repo/putRecord.defs.js +3 -2
  32. package/dist/lexicons/com/atproto/repo/putRecord.defs.js.map +1 -1
  33. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts +7 -7
  34. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts.map +1 -1
  35. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.js.map +1 -1
  36. package/dist/lexicons/com/atproto/sync/getBlob.defs.d.ts +3 -3
  37. package/dist/lexicons/com/atproto/sync/getBlob.defs.d.ts.map +1 -1
  38. package/dist/lexicons/com/atproto/sync/getBlob.defs.js.map +1 -1
  39. package/dist/response.d.ts +14 -4
  40. package/dist/response.d.ts.map +1 -1
  41. package/dist/response.js +19 -9
  42. package/dist/response.js.map +1 -1
  43. package/dist/types.d.ts +51 -0
  44. package/dist/types.d.ts.map +1 -1
  45. package/dist/types.js.map +1 -1
  46. package/dist/util.d.ts +40 -5
  47. package/dist/util.d.ts.map +1 -1
  48. package/dist/util.js +22 -0
  49. package/dist/util.js.map +1 -1
  50. package/dist/www-authenticate.d.ts +23 -0
  51. package/dist/www-authenticate.d.ts.map +1 -1
  52. package/dist/www-authenticate.js.map +1 -1
  53. package/dist/xrpc.d.ts +81 -1
  54. package/dist/xrpc.d.ts.map +1 -1
  55. package/dist/xrpc.js.map +1 -1
  56. package/package.json +7 -7
  57. package/src/agent.ts +67 -0
  58. package/src/client.ts +424 -11
  59. package/src/errors.ts +165 -12
  60. package/src/lexicons/com/atproto/repo/createRecord.defs.ts +15 -7
  61. package/src/lexicons/com/atproto/repo/deleteRecord.defs.ts +11 -5
  62. package/src/lexicons/com/atproto/repo/getRecord.defs.ts +7 -4
  63. package/src/lexicons/com/atproto/repo/listRecords.defs.ts +8 -5
  64. package/src/lexicons/com/atproto/repo/putRecord.defs.ts +15 -7
  65. package/src/lexicons/com/atproto/repo/uploadBlob.defs.ts +11 -5
  66. package/src/lexicons/com/atproto/sync/getBlob.defs.ts +6 -3
  67. package/src/response.ts +22 -19
  68. package/src/types.ts +52 -0
  69. package/src/util.ts +50 -5
  70. package/src/www-authenticate.ts +24 -0
  71. package/src/xrpc.ts +83 -4
package/src/errors.ts CHANGED
@@ -6,12 +6,26 @@ import {
6
6
  ResultFailure,
7
7
  lexErrorDataSchema,
8
8
  } from '@atproto/lex-schema'
9
- import { XrpcPayload } from './util.js'
9
+ import { XrpcResponsePayload } from './util.js'
10
10
  import {
11
11
  WWWAuthenticate,
12
12
  parseWWWAuthenticateHeader,
13
13
  } from './www-authenticate.js'
14
14
 
15
+ /**
16
+ * HTTP status codes that indicate a transient error that may succeed on retry.
17
+ *
18
+ * Includes:
19
+ * - 408 Request Timeout
20
+ * - 425 Too Early
21
+ * - 429 Too Many Requests (rate limited)
22
+ * - 500 Internal Server Error
23
+ * - 502 Bad Gateway
24
+ * - 503 Service Unavailable
25
+ * - 504 Gateway Timeout
26
+ * - 522 Connection Timed Out (Cloudflare)
27
+ * - 524 A Timeout Occurred (Cloudflare)
28
+ */
15
29
  export const RETRYABLE_HTTP_STATUS_CODES: ReadonlySet<number> = new Set([
16
30
  408, 425, 429, 500, 502, 503, 504, 522, 524,
17
31
  ])
@@ -19,8 +33,17 @@ export const RETRYABLE_HTTP_STATUS_CODES: ReadonlySet<number> = new Set([
19
33
  export { LexError }
20
34
  export type { LexErrorCode, LexErrorData }
21
35
 
22
- export type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> =
23
- XrpcPayload<LexErrorData<N>, 'application/json'>
36
+ /**
37
+ * The payload structure for XRPC error responses.
38
+ *
39
+ * All XRPC errors return JSON with an `error` code and optional `message`.
40
+ *
41
+ * @typeParam N - The specific error code type
42
+ */
43
+ export type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> = {
44
+ body: LexErrorData<N>
45
+ encoding: 'application/json'
46
+ }
24
47
 
25
48
  /**
26
49
  * All unsuccessful responses should follow a standard error response
@@ -35,15 +58,29 @@ export type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> =
35
58
  * This function checks whether a given payload matches this schema.
36
59
  */
37
60
  export function isXrpcErrorPayload(
38
- payload: XrpcPayload | null,
61
+ payload: XrpcResponsePayload | null | undefined,
39
62
  ): payload is XrpcErrorPayload {
40
63
  return (
41
- payload !== null &&
64
+ payload != null &&
42
65
  payload.encoding === 'application/json' &&
43
66
  lexErrorDataSchema.matches(payload.body)
44
67
  )
45
68
  }
46
69
 
70
+ /**
71
+ * Abstract base class for all XRPC errors.
72
+ *
73
+ * Extends {@link LexError} and implements {@link ResultFailure} for use with
74
+ * safe/result-based error handling patterns.
75
+ *
76
+ * @typeParam M - The XRPC method type (Procedure or Query)
77
+ * @typeParam N - The error code type
78
+ * @typeParam TReason - The reason type for ResultFailure
79
+ *
80
+ * @see {@link XrpcResponseError} - For valid XRPC error responses
81
+ * @see {@link XrpcUpstreamError} - For invalid/unexpected responses
82
+ * @see {@link XrpcInternalError} - For network/internal errors
83
+ */
47
84
  export abstract class XrpcError<
48
85
  M extends Procedure | Query = Procedure | Query,
49
86
  N extends LexErrorCode = LexErrorCode,
@@ -84,8 +121,28 @@ export abstract class XrpcError<
84
121
  }
85
122
 
86
123
  /**
87
- * Class used to represent an HTTP request that resulted in an XRPC method
88
- * error. That is, a non-2xx response with a valid XRPC error payload.
124
+ * Error class for valid XRPC error responses from the server.
125
+ *
126
+ * This represents a properly formatted XRPC error where the server returned
127
+ * a non-2xx status with a valid JSON error payload containing `error` and
128
+ * optional `message` fields.
129
+ *
130
+ * Use {@link matchesSchema} to check if the error matches the method's declared
131
+ * error types for type-safe error handling.
132
+ *
133
+ * @typeParam M - The XRPC method type
134
+ * @typeParam N - The error code type (inferred from method or generic)
135
+ *
136
+ * @example Handling specific errors
137
+ * ```typescript
138
+ * try {
139
+ * await client.xrpc(someMethod, options)
140
+ * } catch (err) {
141
+ * if (err instanceof XrpcResponseError && err.error === 'RecordNotFound') {
142
+ * // Handle not found case
143
+ * }
144
+ * }
145
+ * ```
89
146
  */
90
147
  export class XrpcResponseError<
91
148
  M extends Procedure | Query = Procedure | Query,
@@ -135,6 +192,33 @@ export class XrpcResponseError<
135
192
  }
136
193
 
137
194
  export type { WWWAuthenticate }
195
+
196
+ /**
197
+ * Error class for 401 Unauthorized XRPC responses.
198
+ *
199
+ * Extends {@link XrpcResponseError} with access to parsed WWW-Authenticate header
200
+ * information, useful for implementing authentication flows.
201
+ *
202
+ * Authentication errors are never retryable as they require user intervention
203
+ * (e.g., re-authentication, token refresh).
204
+ *
205
+ * @typeParam M - The XRPC method type
206
+ * @typeParam N - The error code type
207
+ *
208
+ * @example Handling authentication errors
209
+ * ```typescript
210
+ * try {
211
+ * await client.xrpc(someMethod, options)
212
+ * } catch (err) {
213
+ * if (err instanceof XrpcAuthenticationError) {
214
+ * const { DPoP } = err.wwwAuthenticate
215
+ * if (DPoP?.error === 'use_dpop_nonce') {
216
+ * // Handle DPoP nonce requirement
217
+ * }
218
+ * }
219
+ * }
220
+ * ```
221
+ */
138
222
  export class XrpcAuthenticationError<
139
223
  M extends Procedure | Query = Procedure | Query,
140
224
  N extends LexErrorCode = LexErrorCode,
@@ -145,9 +229,13 @@ export class XrpcAuthenticationError<
145
229
  return false
146
230
  }
147
231
 
148
- #wwwAuthenticate?: WWWAuthenticate
232
+ #wwwAuthenticateCached?: WWWAuthenticate
233
+ /**
234
+ * Parsed WWW-Authenticate header from the response.
235
+ * Contains authentication scheme parameters (e.g., Bearer realm, DPoP nonce).
236
+ */
149
237
  get wwwAuthenticate(): WWWAuthenticate {
150
- return (this.#wwwAuthenticate ??=
238
+ return (this.#wwwAuthenticateCached ??=
151
239
  parseWWWAuthenticateHeader(
152
240
  this.response.headers.get('www-authenticate'),
153
241
  ) ?? {})
@@ -155,8 +243,19 @@ export class XrpcAuthenticationError<
155
243
  }
156
244
 
157
245
  /**
158
- * This class represents invalid or unprocessable XRPC response from the
159
- * upstream server.
246
+ * Error class for invalid or unprocessable XRPC responses from upstream servers.
247
+ *
248
+ * This occurs when the server returns a response that doesn't conform to the
249
+ * XRPC protocol, such as:
250
+ * - Missing or invalid Content-Type header
251
+ * - Response body that doesn't match the method's output schema
252
+ * - Non-JSON error responses
253
+ * - Responses from non-XRPC endpoints
254
+ *
255
+ * The error code is always 'UpstreamFailure' and maps to HTTP 502 Bad Gateway
256
+ * when converted to a response.
257
+ *
258
+ * @typeParam M - The XRPC method type
160
259
  */
161
260
  export class XrpcUpstreamError<
162
261
  M extends Procedure | Query = Procedure | Query,
@@ -166,7 +265,7 @@ export class XrpcUpstreamError<
166
265
  constructor(
167
266
  method: M,
168
267
  readonly response: Response,
169
- readonly payload: XrpcPayload | null,
268
+ readonly payload: XrpcResponsePayload | null = null,
170
269
  message: string = `Unexpected upstream XRPC response`,
171
270
  options?: ErrorOptions,
172
271
  ) {
@@ -186,6 +285,21 @@ export class XrpcUpstreamError<
186
285
  }
187
286
  }
188
287
 
288
+ /**
289
+ * Error class for internal/client-side errors during XRPC requests.
290
+ *
291
+ * This represents errors that occur before or during the request that are not
292
+ * server responses, such as:
293
+ * - Network errors (connection refused, DNS failure)
294
+ * - Request timeouts
295
+ * - Request aborted via AbortSignal
296
+ * - Invalid request construction
297
+ *
298
+ * The error code is always 'InternalServerError' and these errors are
299
+ * optimistically considered retryable.
300
+ *
301
+ * @typeParam M - The XRPC method type
302
+ */
189
303
  export class XrpcInternalError<
190
304
  M extends Procedure | Query = Procedure | Query,
191
305
  > extends XrpcError<M, 'InternalServerError', XrpcInternalError<M>> {
@@ -218,6 +332,25 @@ export class XrpcInternalError<
218
332
  }
219
333
  }
220
334
 
335
+ /**
336
+ * Union type of all possible XRPC failure types.
337
+ *
338
+ * Used as the return type for safe/non-throwing XRPC methods. Check the
339
+ * `success` property to distinguish between success and failure:
340
+ *
341
+ * @typeParam M - The XRPC method type
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * const result = await client.xrpcSafe(someMethod, options)
346
+ * if (result.success) {
347
+ * console.log(result.body) // XrpcResponse
348
+ * } else {
349
+ * // result is XrpcFailure (XrpcResponseError | XrpcUpstreamError | XrpcInternalError)
350
+ * console.error(result.error, result.message)
351
+ * }
352
+ * ```
353
+ */
221
354
  export type XrpcFailure<M extends Procedure | Query = Procedure | Query> =
222
355
  // The server returned a valid XRPC error response
223
356
  | XrpcResponseError<M>
@@ -226,6 +359,26 @@ export type XrpcFailure<M extends Procedure | Query = Procedure | Query> =
226
359
  // Something went wrong (network error, etc.)
227
360
  | XrpcInternalError<M>
228
361
 
362
+ /**
363
+ * Converts an unknown error into an appropriate {@link XrpcFailure} type.
364
+ *
365
+ * If the error is already an XrpcFailure for the given method, returns it as-is.
366
+ * Otherwise, wraps it in an {@link XrpcInternalError}.
367
+ *
368
+ * @param method - The XRPC method that was called
369
+ * @param cause - The error to convert
370
+ * @returns An XrpcFailure instance
371
+ *
372
+ * @example
373
+ * ```typescript
374
+ * try {
375
+ * const response = await fetch(...)
376
+ * // ... process response
377
+ * } catch (err) {
378
+ * return asXrpcFailure(method, err)
379
+ * }
380
+ * ```
381
+ */
229
382
  export function asXrpcFailure<M extends Procedure | Query>(
230
383
  method: M,
231
384
  cause: unknown,
@@ -22,7 +22,7 @@ const main =
22
22
  /*#__PURE__*/ l.string({ format: 'record-key', maxLength: 512 }),
23
23
  ),
24
24
  validate: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
25
- record: /*#__PURE__*/ l.unknownObject(),
25
+ record: /*#__PURE__*/ l.lexMap(),
26
26
  swapCommit: /*#__PURE__*/ l.optional(
27
27
  /*#__PURE__*/ l.string({ format: 'cid' }),
28
28
  ),
@@ -35,17 +35,25 @@ const main =
35
35
  (() => RepoDefs.commitMeta) as any,
36
36
  ),
37
37
  ),
38
- validationStatus: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
38
+ validationStatus: /*#__PURE__*/ l.optional(
39
+ /*#__PURE__*/ l.string<{ knownValues: ['valid', 'unknown'] }>(),
40
+ ),
39
41
  }),
40
42
  ['InvalidSwap'],
41
43
  )
42
44
  export { main }
43
45
 
44
- export type Params = l.InferMethodParams<typeof main>
45
- export type Input = l.InferMethodInput<typeof main>
46
- export type InputBody = l.InferMethodInputBody<typeof main>
47
- export type Output = l.InferMethodOutput<typeof main>
48
- export type OutputBody = l.InferMethodOutputBody<typeof main>
46
+ export type $Params = l.InferMethodParams<typeof main>
47
+ export type $Input<B = l.BinaryData> = l.InferMethodInput<typeof main, B>
48
+ export type $InputBody<B = l.BinaryData> = l.InferMethodInputBody<
49
+ typeof main,
50
+ B
51
+ >
52
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
53
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
54
+ typeof main,
55
+ B
56
+ >
49
57
 
50
58
  export const $lxm = /*#__PURE__*/ main.nsid,
51
59
  $params = /*#__PURE__*/ main.parameters,
@@ -37,11 +37,17 @@ const main =
37
37
  )
38
38
  export { main }
39
39
 
40
- export type Params = l.InferMethodParams<typeof main>
41
- export type Input = l.InferMethodInput<typeof main>
42
- export type InputBody = l.InferMethodInputBody<typeof main>
43
- export type Output = l.InferMethodOutput<typeof main>
44
- export type OutputBody = l.InferMethodOutputBody<typeof main>
40
+ export type $Params = l.InferMethodParams<typeof main>
41
+ export type $Input<B = l.BinaryData> = l.InferMethodInput<typeof main, B>
42
+ export type $InputBody<B = l.BinaryData> = l.InferMethodInputBody<
43
+ typeof main,
44
+ B
45
+ >
46
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
47
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
48
+ typeof main,
49
+ B
50
+ >
45
51
 
46
52
  export const $lxm = /*#__PURE__*/ main.nsid,
47
53
  $params = /*#__PURE__*/ main.parameters,
@@ -22,15 +22,18 @@ const main =
22
22
  /*#__PURE__*/ l.jsonPayload({
23
23
  uri: /*#__PURE__*/ l.string({ format: 'at-uri' }),
24
24
  cid: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string({ format: 'cid' })),
25
- value: /*#__PURE__*/ l.unknownObject(),
25
+ value: /*#__PURE__*/ l.lexMap(),
26
26
  }),
27
27
  ['RecordNotFound'],
28
28
  )
29
29
  export { main }
30
30
 
31
- export type Params = l.InferMethodParams<typeof main>
32
- export type Output = l.InferMethodOutput<typeof main>
33
- export type OutputBody = l.InferMethodOutputBody<typeof main>
31
+ export type $Params = l.InferMethodParams<typeof main>
32
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
33
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
34
+ typeof main,
35
+ B
36
+ >
34
37
 
35
38
  export const $lxm = /*#__PURE__*/ main.nsid,
36
39
  $params = main.parameters,
@@ -34,9 +34,12 @@ const main =
34
34
  )
35
35
  export { main }
36
36
 
37
- export type Params = l.InferMethodParams<typeof main>
38
- export type Output = l.InferMethodOutput<typeof main>
39
- export type OutputBody = l.InferMethodOutputBody<typeof main>
37
+ export type $Params = l.InferMethodParams<typeof main>
38
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
39
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
40
+ typeof main,
41
+ B
42
+ >
40
43
 
41
44
  export const $lxm = /*#__PURE__*/ main.nsid,
42
45
  $params = main.parameters,
@@ -46,7 +49,7 @@ type Record$0 = {
46
49
  $type?: 'com.atproto.repo.listRecords#record'
47
50
  uri: l.AtUriString
48
51
  cid: l.CidString
49
- value: l.UnknownObject
52
+ value: l.LexMap
50
53
  }
51
54
 
52
55
  export type { Record$0 as Record }
@@ -57,7 +60,7 @@ const record$0 = /*#__PURE__*/ l.typedObject<Record$0>(
57
60
  /*#__PURE__*/ l.object({
58
61
  uri: /*#__PURE__*/ l.string({ format: 'at-uri' }),
59
62
  cid: /*#__PURE__*/ l.string({ format: 'cid' }),
60
- value: /*#__PURE__*/ l.unknownObject(),
63
+ value: /*#__PURE__*/ l.lexMap(),
61
64
  }),
62
65
  )
63
66
 
@@ -20,7 +20,7 @@ const main =
20
20
  collection: /*#__PURE__*/ l.string({ format: 'nsid' }),
21
21
  rkey: /*#__PURE__*/ l.string({ format: 'record-key', maxLength: 512 }),
22
22
  validate: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
23
- record: /*#__PURE__*/ l.unknownObject(),
23
+ record: /*#__PURE__*/ l.lexMap(),
24
24
  swapRecord: /*#__PURE__*/ l.optional(
25
25
  /*#__PURE__*/ l.nullable(/*#__PURE__*/ l.string({ format: 'cid' })),
26
26
  ),
@@ -36,17 +36,25 @@ const main =
36
36
  (() => RepoDefs.commitMeta) as any,
37
37
  ),
38
38
  ),
39
- validationStatus: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
39
+ validationStatus: /*#__PURE__*/ l.optional(
40
+ /*#__PURE__*/ l.string<{ knownValues: ['valid', 'unknown'] }>(),
41
+ ),
40
42
  }),
41
43
  ['InvalidSwap'],
42
44
  )
43
45
  export { main }
44
46
 
45
- export type Params = l.InferMethodParams<typeof main>
46
- export type Input = l.InferMethodInput<typeof main>
47
- export type InputBody = l.InferMethodInputBody<typeof main>
48
- export type Output = l.InferMethodOutput<typeof main>
49
- export type OutputBody = l.InferMethodOutputBody<typeof main>
47
+ export type $Params = l.InferMethodParams<typeof main>
48
+ export type $Input<B = l.BinaryData> = l.InferMethodInput<typeof main, B>
49
+ export type $InputBody<B = l.BinaryData> = l.InferMethodInputBody<
50
+ typeof main,
51
+ B
52
+ >
53
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
54
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
55
+ typeof main,
56
+ B
57
+ >
50
58
 
51
59
  export const $lxm = /*#__PURE__*/ main.nsid,
52
60
  $params = /*#__PURE__*/ main.parameters,
@@ -21,11 +21,17 @@ const main =
21
21
  )
22
22
  export { main }
23
23
 
24
- export type Params = l.InferMethodParams<typeof main>
25
- export type Input = l.InferMethodInput<typeof main>
26
- export type InputBody = l.InferMethodInputBody<typeof main>
27
- export type Output = l.InferMethodOutput<typeof main>
28
- export type OutputBody = l.InferMethodOutputBody<typeof main>
24
+ export type $Params = l.InferMethodParams<typeof main>
25
+ export type $Input<B = l.BinaryData> = l.InferMethodInput<typeof main, B>
26
+ export type $InputBody<B = l.BinaryData> = l.InferMethodInputBody<
27
+ typeof main,
28
+ B
29
+ >
30
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
31
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
32
+ typeof main,
33
+ B
34
+ >
29
35
 
30
36
  export const $lxm = /*#__PURE__*/ main.nsid,
31
37
  $params = /*#__PURE__*/ main.parameters,
@@ -28,9 +28,12 @@ const main =
28
28
  )
29
29
  export { main }
30
30
 
31
- export type Params = l.InferMethodParams<typeof main>
32
- export type Output = l.InferMethodOutput<typeof main>
33
- export type OutputBody = l.InferMethodOutputBody<typeof main>
31
+ export type $Params = l.InferMethodParams<typeof main>
32
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
33
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
34
+ typeof main,
35
+ B
36
+ >
34
37
 
35
38
  export const $lxm = /*#__PURE__*/ main.nsid,
36
39
  $params = main.parameters,
package/src/response.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { lexParse } from '@atproto/lex-json'
2
2
  import {
3
- InferMethodOutputBody,
4
3
  InferMethodOutputEncoding,
5
4
  Procedure,
6
5
  Query,
@@ -12,15 +11,12 @@ import {
12
11
  XrpcUpstreamError,
13
12
  isXrpcErrorPayload,
14
13
  } from './errors.js'
15
- import { XrpcPayload } from './util.js'
14
+ import { XrpcResponseBody, XrpcResponsePayload } from './util.js'
16
15
 
17
- export type XrpcResponseBody<M extends Procedure | Query> =
18
- InferMethodOutputBody<M, Uint8Array>
16
+ const CONTENT_TYPE_BINARY = 'application/octet-stream'
17
+ const CONTENT_TYPE_JSON = 'application/json'
19
18
 
20
- export type XrpcResponsePayload<M extends Procedure | Query> =
21
- InferMethodOutputEncoding<M> extends infer E extends string
22
- ? XrpcPayload<XrpcResponseBody<M>, E>
23
- : null
19
+ export type { XrpcResponseBody, XrpcResponsePayload }
24
20
 
25
21
  /**
26
22
  * Small container for XRPC response data.
@@ -50,13 +46,24 @@ export class XrpcResponse<M extends Procedure | Query>
50
46
  * in binary form {@link Uint8Array} (`false`).
51
47
  */
52
48
  get isParsed() {
53
- return this.encoding === 'application/json' && shouldParse(this.method)
49
+ return this.method.output.encoding === CONTENT_TYPE_JSON
54
50
  }
55
51
 
52
+ /**
53
+ * The Content-Type encoding of the response (e.g., 'application/json').
54
+ * Returns `undefined` if the response has no body.
55
+ */
56
56
  get encoding() {
57
57
  return this.payload?.encoding as InferMethodOutputEncoding<M>
58
58
  }
59
59
 
60
+ /**
61
+ * The parsed response body.
62
+ *
63
+ * For 'application/json' responses, this is the parsed and validated LexValue.
64
+ * For binary responses, this is a Uint8Array.
65
+ * Returns `undefined` if the response has no body.
66
+ */
60
67
  get body() {
61
68
  return this.payload?.body as XrpcResponseBody<M>
62
69
  }
@@ -115,7 +122,7 @@ export class XrpcResponse<M extends Procedure | Query>
115
122
 
116
123
  // Only parse json if the schema expects it
117
124
  const payload = await readPayload(response, {
118
- parse: shouldParse(method),
125
+ parse: method.output.encoding === CONTENT_TYPE_JSON,
119
126
  }).catch((cause) => {
120
127
  throw new XrpcUpstreamError(
121
128
  method,
@@ -175,17 +182,13 @@ export class XrpcResponse<M extends Procedure | Query>
175
182
  }
176
183
  }
177
184
 
178
- function shouldParse(method: Procedure | Query) {
179
- return method.output.encoding === 'application/json'
180
- }
181
-
182
185
  /**
183
186
  * @note this function always consumes the response body
184
187
  */
185
188
  async function readPayload(
186
189
  response: Response,
187
190
  options?: { parse?: boolean },
188
- ): Promise<XrpcPayload | null> {
191
+ ): Promise<XrpcResponsePayload> {
189
192
  // @TODO Should we limit the maximum response size here (this could also be
190
193
  // done by the FetchHandler)?
191
194
 
@@ -197,18 +200,18 @@ async function readPayload(
197
200
 
198
201
  // Response content-type is undefined
199
202
  if (!encoding) {
200
- // If the body is empty, return null (= no payload)
203
+ // If the body is empty, return undefined (= no payload)
201
204
  const body = await response.arrayBuffer()
202
- if (body.byteLength === 0) return null
205
+ if (body.byteLength === 0) return undefined
203
206
 
204
207
  // If we got data despite no content-type, treat it as binary
205
208
  return {
206
- encoding: 'application/octet-stream',
209
+ encoding: CONTENT_TYPE_BINARY,
207
210
  body: new Uint8Array(body),
208
211
  }
209
212
  }
210
213
 
211
- if (options?.parse && encoding === 'application/json') {
214
+ if (options?.parse && encoding === CONTENT_TYPE_JSON) {
212
215
  // @NOTE It might be worth returning the raw bytes here (Uint8Array) and
213
216
  // perform the lex parsing using cborg/json, allowing to do
214
217
  // bytes->LexValue in one step instead of bytes->text->JSON->LexValue.
package/src/types.ts CHANGED
@@ -2,13 +2,40 @@ import { DidString, UnknownString } from '@atproto/lex-schema'
2
2
 
3
3
  export type { DidString, UnknownString }
4
4
 
5
+ /**
6
+ * Service identifier fragment for DID service endpoints.
7
+ *
8
+ * Common values include 'atproto_labeler' for labeling services,
9
+ * or custom service identifiers.
10
+ */
5
11
  export type DidServiceIdentifier = 'atproto_labeler' | UnknownString
12
+
13
+ /**
14
+ * A full service proxy identifier combining a DID with a service fragment.
15
+ *
16
+ * Used to route requests through a specific service endpoint.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const service: Service = 'did:web:api.bsky.app#bsky_appview'
21
+ * ```
22
+ */
6
23
  export type Service = `${DidString}#${DidServiceIdentifier}`
7
24
 
25
+ /**
26
+ * Common options available for all XRPC calls.
27
+ *
28
+ * These options can be passed to any method that makes XRPC requests,
29
+ * including `xrpc()`, `call()`, and record operations.
30
+ */
8
31
  export type CallOptions = {
32
+ /** Labeler DIDs to request labels from for content moderation. */
9
33
  labelers?: Iterable<DidString>
34
+ /** AbortSignal to cancel the request. */
10
35
  signal?: AbortSignal
36
+ /** Additional HTTP headers to include in the request. */
11
37
  headers?: HeadersInit
38
+ /** Service proxy identifier for routing requests through a specific service. */
12
39
  service?: Service
13
40
 
14
41
  /**
@@ -33,6 +60,31 @@ export type CallOptions = {
33
60
  validateResponse?: boolean
34
61
  }
35
62
 
63
+ /**
64
+ * Valid input types for binary request bodies.
65
+ *
66
+ * These types can be used as the body for procedures that expect
67
+ * non-JSON content (e.g., blob uploads, binary data).
68
+ *
69
+ * @example Uploading a blob
70
+ * ```typescript
71
+ * const imageData: BinaryBodyInit = new Uint8Array(buffer)
72
+ * await client.uploadBlob(imageData, { encoding: 'image/png' })
73
+ * ```
74
+ *
75
+ * @example Streaming upload
76
+ * ```typescript
77
+ * const stream: BinaryBodyInit = someReadableStream
78
+ * await client.xrpc(uploadMethod, { body: stream })
79
+ * ```
80
+ *
81
+ * @example File upload in browser
82
+ * ```typescript
83
+ * const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
84
+ * const file: BinaryBodyInit = fileInput.files[0]
85
+ * await client.xrpc(uploadMethod, { body: file })
86
+ * ```
87
+ */
36
88
  export type BinaryBodyInit =
37
89
  | Uint8Array
38
90
  | ArrayBuffer