@atproto/lex-client 0.1.5 → 0.2.1

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 (74) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/agent.d.ts +1 -1
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js.map +1 -1
  5. package/dist/client.d.ts +42 -21
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +97 -16
  8. package/dist/client.js.map +1 -1
  9. package/dist/errors.d.ts +6 -4
  10. package/dist/errors.d.ts.map +1 -1
  11. package/dist/errors.js +3 -3
  12. package/dist/errors.js.map +1 -1
  13. package/dist/response.d.ts +3 -2
  14. package/dist/response.d.ts.map +1 -1
  15. package/dist/response.js +2 -1
  16. package/dist/response.js.map +1 -1
  17. package/dist/types.d.ts +1 -1
  18. package/dist/types.d.ts.map +1 -1
  19. package/dist/types.js.map +1 -1
  20. package/dist/util.d.ts +6 -2
  21. package/dist/util.d.ts.map +1 -1
  22. package/dist/util.js +25 -0
  23. package/dist/util.js.map +1 -1
  24. package/dist/write-operation-builder.d.ts +3 -2
  25. package/dist/write-operation-builder.d.ts.map +1 -1
  26. package/dist/write-operation-builder.js +2 -2
  27. package/dist/write-operation-builder.js.map +1 -1
  28. package/dist/xrpc.d.ts +8 -6
  29. package/dist/xrpc.d.ts.map +1 -1
  30. package/dist/xrpc.js +1 -1
  31. package/dist/xrpc.js.map +1 -1
  32. package/package.json +11 -14
  33. package/src/agent.test.ts +0 -216
  34. package/src/agent.ts +0 -186
  35. package/src/client.ts +0 -1086
  36. package/src/errors.test.ts +0 -626
  37. package/src/errors.ts +0 -570
  38. package/src/index.ts +0 -6
  39. package/src/lexicons/com/atproto/repo/applyWrites.defs.ts +0 -201
  40. package/src/lexicons/com/atproto/repo/applyWrites.ts +0 -6
  41. package/src/lexicons/com/atproto/repo/createRecord.defs.ts +0 -58
  42. package/src/lexicons/com/atproto/repo/createRecord.ts +0 -6
  43. package/src/lexicons/com/atproto/repo/defs.defs.ts +0 -28
  44. package/src/lexicons/com/atproto/repo/defs.ts +0 -5
  45. package/src/lexicons/com/atproto/repo/deleteRecord.defs.ts +0 -52
  46. package/src/lexicons/com/atproto/repo/deleteRecord.ts +0 -6
  47. package/src/lexicons/com/atproto/repo/getRecord.defs.ts +0 -37
  48. package/src/lexicons/com/atproto/repo/getRecord.ts +0 -6
  49. package/src/lexicons/com/atproto/repo/listRecords.defs.ts +0 -65
  50. package/src/lexicons/com/atproto/repo/listRecords.ts +0 -6
  51. package/src/lexicons/com/atproto/repo/putRecord.defs.ts +0 -59
  52. package/src/lexicons/com/atproto/repo/putRecord.ts +0 -6
  53. package/src/lexicons/com/atproto/repo/uploadBlob.defs.ts +0 -35
  54. package/src/lexicons/com/atproto/repo/uploadBlob.ts +0 -6
  55. package/src/lexicons/com/atproto/repo.ts +0 -12
  56. package/src/lexicons/com/atproto/sync/getBlob.defs.ts +0 -37
  57. package/src/lexicons/com/atproto/sync/getBlob.ts +0 -6
  58. package/src/lexicons/com/atproto/sync.ts +0 -5
  59. package/src/lexicons/com/atproto.ts +0 -6
  60. package/src/lexicons/com.ts +0 -5
  61. package/src/lexicons/index.ts +0 -5
  62. package/src/response.bench.ts +0 -113
  63. package/src/response.ts +0 -366
  64. package/src/types.ts +0 -71
  65. package/src/util.test.ts +0 -333
  66. package/src/util.ts +0 -182
  67. package/src/write-operation-builder.ts +0 -110
  68. package/src/www-authenticate.test.ts +0 -227
  69. package/src/www-authenticate.ts +0 -101
  70. package/src/xrpc.test.ts +0 -1450
  71. package/src/xrpc.ts +0 -446
  72. package/tsconfig.build.json +0 -12
  73. package/tsconfig.json +0 -7
  74. package/tsconfig.tests.json +0 -8
package/src/errors.ts DELETED
@@ -1,570 +0,0 @@
1
- import {
2
- LexError,
3
- LexErrorCode,
4
- LexErrorData,
5
- LexValue,
6
- } from '@atproto/lex-data'
7
- import {
8
- InferMethodError,
9
- LexValidationError,
10
- Procedure,
11
- Query,
12
- ResultFailure,
13
- lexErrorDataSchema,
14
- } from '@atproto/lex-schema'
15
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
- import { Agent } from './agent.js'
17
- import { XrpcUnknownResponsePayload } from './types.js'
18
- import {
19
- WWWAuthenticate,
20
- parseWWWAuthenticateHeader,
21
- } from './www-authenticate.js'
22
-
23
- /**
24
- * Mapping that allows generating an XRPC error code from an HTTP status code
25
- * when the response does not contain a valid XRPC error payload. This is used
26
- * to convert non-XRPC error responses from upstream servers into a standardized
27
- * XRPC error for downstream clients.
28
- */
29
- const StatusErrorCodes = new Map<number, LexErrorCode>([
30
- [400, 'InvalidRequest'],
31
- [401, 'AuthenticationRequired'],
32
- [403, 'Forbidden'],
33
- [404, 'XRPCNotSupported'],
34
- [406, 'NotAcceptable'],
35
- [413, 'PayloadTooLarge'],
36
- [415, 'UnsupportedMediaType'],
37
- [429, 'RateLimitExceeded'],
38
- [500, 'InternalServerError'],
39
- [501, 'MethodNotImplemented'],
40
- [502, 'UpstreamFailure'],
41
- [503, 'NotEnoughResources'],
42
- [504, 'UpstreamTimeout'],
43
- ])
44
-
45
- export type { XrpcUnknownResponsePayload }
46
-
47
- export type DownstreamError<N extends LexErrorCode = LexErrorCode> = {
48
- status: number
49
- headers?: Headers
50
- encoding?: 'application/json'
51
- body: LexErrorData<N>
52
- }
53
-
54
- /**
55
- * HTTP status codes that indicate a transient error that may succeed on retry.
56
- *
57
- * Includes:
58
- * - 408 Request Timeout
59
- * - 425 Too Early
60
- * - 429 Too Many Requests (rate limited)
61
- * - 500 Internal Server Error
62
- * - 502 Bad Gateway
63
- * - 503 Service Unavailable
64
- * - 504 Gateway Timeout
65
- * - 522 Connection Timed Out (Cloudflare)
66
- * - 524 A Timeout Occurred (Cloudflare)
67
- */
68
- export const RETRYABLE_HTTP_STATUS_CODES: ReadonlySet<number> = new Set([
69
- 408, 425, 429, 500, 502, 503, 504, 522, 524,
70
- ])
71
-
72
- export { LexError }
73
- export type { LexErrorCode, LexErrorData }
74
-
75
- /**
76
- * The payload structure for XRPC error responses.
77
- *
78
- * All XRPC errors return JSON with an `error` code and optional `message`.
79
- *
80
- * @typeParam N - The specific error code type
81
- */
82
- export type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> = {
83
- body: LexErrorData<N>
84
- encoding: 'application/json'
85
- }
86
-
87
- /**
88
- * All unsuccessful responses should follow a standard error response
89
- * schema. The Content-Type should be application/json, and the payload
90
- * should be a JSON object with the following fields:
91
- *
92
- * - `error` (string, required): type name of the error (generic ASCII
93
- * constant, no whitespace)
94
- * - `message` (string, optional): description of the error, appropriate for
95
- * display to humans
96
- *
97
- * This function checks whether a given payload matches this schema.
98
- */
99
- export function isXrpcErrorPayload(
100
- payload: XrpcUnknownResponsePayload | null | undefined,
101
- ): payload is XrpcErrorPayload {
102
- return (
103
- payload != null &&
104
- payload.encoding === 'application/json' &&
105
- lexErrorDataSchema.matches(payload.body)
106
- )
107
- }
108
-
109
- /**
110
- * Abstract base class for all XRPC errors.
111
- *
112
- * Extends {@link LexError} and implements {@link ResultFailure} for use with
113
- * safe/result-based error handling patterns.
114
- *
115
- * @typeParam M - The XRPC method type (Procedure or Query)
116
- * @typeParam N - The error code type
117
- * @typeParam TReason - The reason type for ResultFailure
118
- *
119
- * @see {@link XrpcResponseError} - For valid XRPC error responses
120
- * @see {@link XrpcInvalidResponseError} - For invalid/unexpected responses
121
- * @see {@link XrpcInternalError} - For network/internal errors
122
- */
123
- export abstract class XrpcError<
124
- M extends Procedure | Query = Procedure | Query,
125
- N extends LexErrorCode = LexErrorCode,
126
- TReason = unknown,
127
- >
128
- extends LexError<N>
129
- implements ResultFailure<TReason>
130
- {
131
- name = 'XrpcError'
132
-
133
- constructor(
134
- readonly method: M,
135
- error: N,
136
- message: string = `${error} Lexicon RPC error`,
137
- options?: ErrorOptions,
138
- ) {
139
- super(error, message, options)
140
- }
141
-
142
- /**
143
- * @see {@link ResultFailure.success}
144
- */
145
- readonly success = false as const
146
-
147
- /**
148
- * @see {@link ResultFailure.reason}
149
- */
150
- abstract readonly reason: TReason
151
-
152
- /**
153
- * Indicates whether the error is transient and can be retried.
154
- */
155
- abstract shouldRetry(): boolean
156
-
157
- abstract toDownstreamError(): DownstreamError
158
-
159
- matchesSchemaErrors(): this is XrpcError<M, InferMethodError<M>> {
160
- return this.method.errors?.includes(this.error) ?? false
161
- }
162
- }
163
-
164
- /**
165
- * Error class for valid XRPC error responses from the server.
166
- *
167
- * This represents a properly formatted XRPC error where the server returned
168
- * a non-2xx status with a valid JSON error payload containing `error` and
169
- * optional `message` fields.
170
- *
171
- * Use {@link matchesSchemaErrors} to check if the error matches the method's declared
172
- * error types for type-safe error handling.
173
- *
174
- * @typeParam M - The XRPC method type
175
- * @typeParam N - The error code type (inferred from method or generic)
176
- *
177
- * @example Handling specific errors
178
- * ```typescript
179
- * try {
180
- * await client.xrpc(someMethod, options)
181
- * } catch (err) {
182
- * if (err instanceof XrpcResponseError && err.error === 'RecordNotFound') {
183
- * // Handle not found case
184
- * }
185
- * }
186
- * ```
187
- */
188
- export class XrpcResponseError<
189
- M extends Procedure | Query = Procedure | Query,
190
- > extends XrpcError<M, LexErrorCode, XrpcResponseError<M>> {
191
- name = 'XrpcResponseError'
192
-
193
- constructor(
194
- method: M,
195
- readonly response: Response,
196
- readonly payload?: XrpcUnknownResponsePayload,
197
- options?: ErrorOptions,
198
- ) {
199
- const { error, message } = isXrpcErrorPayload(payload)
200
- ? payload.body
201
- : {
202
- error:
203
- StatusErrorCodes.get(response.status) ??
204
- (response.status >= 500 ? 'UpstreamFailure' : 'InvalidRequest'),
205
- message: buildResponseOverviewMessage(response),
206
- }
207
- super(method, error, message, options)
208
- }
209
-
210
- override get reason(): this {
211
- return this
212
- }
213
-
214
- override shouldRetry(): boolean {
215
- return RETRYABLE_HTTP_STATUS_CODES.has(this.response.status)
216
- }
217
-
218
- override toJSON(): LexErrorData {
219
- // Return the original error payload if it's a valid XRPC error, otherwise
220
- // convert to an XRPC error format.
221
- const { payload } = this
222
- if (isXrpcErrorPayload(payload)) {
223
- return payload.body
224
- }
225
-
226
- return super.toJSON()
227
- }
228
-
229
- override toDownstreamError(): DownstreamError {
230
- const { status, headers } = this.response
231
- // If the upstream server returned a 500 error, we want to return a 502 Bad
232
- // Gateway to downstream clients, as the issue is with the upstream server,
233
- // not us. We still return the original error code and message in the body
234
- // for transparency, but we do not want to expose internal server errors
235
- // from the upstream server as-is to downstream clients.
236
- return {
237
- status: status === 500 ? 502 : status,
238
- headers: stripHopByHopHeaders(headers),
239
- body: this.toJSON(),
240
- }
241
- }
242
-
243
- get status(): number {
244
- return this.response.status
245
- }
246
-
247
- get headers(): Headers {
248
- return this.response.headers
249
- }
250
-
251
- get body(): undefined | Uint8Array | LexValue {
252
- return this.payload?.body
253
- }
254
- }
255
-
256
- export type { WWWAuthenticate }
257
-
258
- /**
259
- * Error class for 401 Unauthorized XRPC responses.
260
- *
261
- * Extends {@link XrpcResponseError} with access to parsed WWW-Authenticate header
262
- * information, useful for implementing authentication flows.
263
- *
264
- * Authentication errors are never retryable as they require user intervention
265
- * (e.g., re-authentication, token refresh).
266
- *
267
- * @typeParam M - The XRPC method type
268
- * @typeParam N - The error code type
269
- *
270
- * @example Handling authentication errors
271
- * ```typescript
272
- * try {
273
- * await client.xrpc(someMethod, options)
274
- * } catch (err) {
275
- * if (err instanceof XrpcAuthenticationError) {
276
- * const { DPoP } = err.wwwAuthenticate
277
- * if (DPoP?.error === 'use_dpop_nonce') {
278
- * // Handle DPoP nonce requirement
279
- * }
280
- * }
281
- * }
282
- * ```
283
- */
284
- export class XrpcAuthenticationError<
285
- M extends Procedure | Query = Procedure | Query,
286
- > extends XrpcResponseError<M> {
287
- name = 'XrpcAuthenticationError'
288
-
289
- override shouldRetry(): boolean {
290
- return false
291
- }
292
-
293
- #wwwAuthenticateCached?: WWWAuthenticate
294
- /**
295
- * Parsed WWW-Authenticate header from the response.
296
- * Contains authentication scheme parameters (e.g., Bearer realm, DPoP nonce).
297
- */
298
- get wwwAuthenticate(): WWWAuthenticate {
299
- return (this.#wwwAuthenticateCached ??=
300
- parseWWWAuthenticateHeader(
301
- this.response.headers.get('www-authenticate'),
302
- ) ?? {})
303
- }
304
- }
305
-
306
- /**
307
- * Error class for invalid or unprocessable XRPC responses from upstream servers.
308
- *
309
- * This occurs when the server returns a response that doesn't conform to the
310
- * XRPC protocol, such as:
311
- * - Missing or invalid Content-Type header
312
- * - Response body that doesn't match the method's output schema
313
- * - Non-JSON error responses
314
- * - Responses from non-XRPC endpoints
315
- *
316
- * The error code is always 'InvalidResponse' and maps to HTTP 502 Bad Gateway
317
- * when converted to a response. This should allow downstream clients to
318
- * determine at which boundary the error occurred.
319
- *
320
- * @typeParam M - The XRPC method type
321
- */
322
- export class XrpcInvalidResponseError<
323
- M extends Procedure | Query = Procedure | Query,
324
- > extends XrpcError<M, 'InvalidResponse', XrpcInvalidResponseError<M>> {
325
- name = 'XrpcInvalidResponseError'
326
-
327
- constructor(
328
- method: M,
329
- readonly response: Response,
330
- readonly payload?: XrpcUnknownResponsePayload,
331
- message: string = buildResponseOverviewMessage(response),
332
- options?: ErrorOptions,
333
- ) {
334
- super(method, 'InvalidResponse', message, options)
335
- }
336
-
337
- override get reason(): this {
338
- return this
339
- }
340
-
341
- override shouldRetry(): boolean {
342
- return RETRYABLE_HTTP_STATUS_CODES.has(this.response.status)
343
- }
344
-
345
- override toDownstreamError(): DownstreamError {
346
- return { status: 502, body: this.toJSON() }
347
- }
348
- }
349
-
350
- /**
351
- * Error class for invalid XRPC responses that fail schema validation.
352
- *
353
- * This is a specific type of {@link XrpcInvalidResponseError} that indicates the
354
- * upstream server returned a response that was structurally valid but did not
355
- * conform to the expected schema for the method. This likely indicates a
356
- * mismatch between client and server versions or an issue with the server's
357
- * XRPC implementation.
358
- *
359
- * @typeParam M - The XRPC method type
360
- */
361
- export class XrpcResponseValidationError<
362
- M extends Procedure | Query = Procedure | Query,
363
- > extends XrpcInvalidResponseError<M> {
364
- name = 'XrpcResponseValidationError'
365
-
366
- constructor(
367
- method: M,
368
- response: Response,
369
- payload: XrpcUnknownResponsePayload,
370
- readonly cause: LexValidationError,
371
- ) {
372
- super(
373
- method,
374
- response,
375
- payload,
376
- `Invalid response payload: ${cause.message}`,
377
- { cause },
378
- )
379
- }
380
- }
381
-
382
- /**
383
- * Error class for unexpected internal/client-side errors during XRPC requests.
384
- *
385
- * The error code is always 'InternalServerError' and these errors not
386
- * considered retryable as they stem from unforeseen issues in the
387
- * implementation.
388
- *
389
- * @typeParam M - The XRPC method type
390
- */
391
- export class XrpcInternalError<
392
- M extends Procedure | Query = Procedure | Query,
393
- > extends XrpcError<M, 'InternalServerError', XrpcInternalError<M>> {
394
- name = 'XrpcInternalError'
395
-
396
- constructor(method: M, message?: string, options?: ErrorOptions) {
397
- super(
398
- method,
399
- 'InternalServerError',
400
- message ?? 'Unable to fulfill XRPC request',
401
- options,
402
- )
403
- }
404
-
405
- override get reason(): this {
406
- return this
407
- }
408
-
409
- override shouldRetry(): boolean {
410
- return false
411
- }
412
-
413
- override toJSON(): LexErrorData {
414
- // @NOTE Do not expose internal error details to downstream clients
415
- return { error: this.error, message: 'Internal Server Error' }
416
- }
417
-
418
- override toDownstreamError(): DownstreamError {
419
- return { status: 500, body: this.toJSON() }
420
- }
421
- }
422
-
423
- /**
424
- * Special case of XrpcInternalError that specifically represents errors thrown
425
- * by {@link Agent.fetchHandler} during the XRPC request. This includes:
426
- * - Network errors (connection refused, DNS failure)
427
- * - Request timeouts
428
- * - Request aborted via AbortSignal
429
- *
430
- * These errors are optimistically considered retryable, as many fetch errors
431
- * are transient and may succeed on retry.
432
- */
433
- export class XrpcFetchError<
434
- M extends Procedure | Query = Procedure | Query,
435
- > extends XrpcInternalError<M> {
436
- name = 'XrpcFetchError'
437
-
438
- constructor(method: M, cause: unknown) {
439
- const message = cause instanceof Error ? cause.message : String(cause)
440
- super(method, `Unexpected fetchHandler() error: ${message}`, { cause })
441
- }
442
-
443
- override shouldRetry(): boolean {
444
- // Ideally, we would inspect the reason to determine if it's retryable (by
445
- // detecting network errors, timeouts, etc.). Since these cases are highly
446
- // platform-dependent, we optimistically assume all fetch errors are
447
- // transient and retryable.
448
- return true
449
- }
450
-
451
- override toJSON(): LexErrorData {
452
- // @NOTE Do not expose internal error details to downstream clients
453
- return { error: this.error, message: 'Failed to perform upstream request' }
454
- }
455
-
456
- override toDownstreamError(): DownstreamError {
457
- // While it might technically be a 500 error, we use 502 Bad Gateway here to
458
- // indicate that the error occurred while communicating with the upstream
459
- // server, allowing downstream clients to distinguish between errors in our
460
- // internal processing (500) and errors in the upstream server or network
461
- // (502).
462
- return { status: 502, body: this.toJSON() }
463
- }
464
- }
465
-
466
- /**
467
- * Union type of all possible XRPC failure types.
468
- *
469
- * Used as the return type for safe/non-throwing XRPC methods. Check the
470
- * `success` property to distinguish between success and failure:
471
- *
472
- * @typeParam M - The XRPC method type
473
- *
474
- * @example
475
- * ```typescript
476
- * const result = await client.xrpcSafe(someMethod, options)
477
- * if (result.success) {
478
- * console.log(result.body) // XrpcResponse
479
- * } else {
480
- * // result is XrpcFailure (XrpcResponseError | XrpcInvalidResponseError | XrpcInternalError)
481
- * console.error(result.error, result.message)
482
- * }
483
- * ```
484
- */
485
- export type XrpcFailure<M extends Procedure | Query = Procedure | Query> =
486
- // The server returned a valid XRPC error response
487
- | XrpcResponseError<M>
488
- // The response was not a valid XRPC response, or it does not match the schema
489
- | XrpcInvalidResponseError<M>
490
- // Something went wrong (network error, etc.)
491
- | XrpcInternalError<M>
492
-
493
- /**
494
- * Converts an unknown error into an appropriate {@link XrpcFailure} type.
495
- *
496
- * If the error is already an XrpcFailure for the given method, returns it as-is.
497
- * Otherwise, wraps it in an {@link XrpcInternalError}.
498
- *
499
- * @param method - The XRPC method that was called
500
- * @param cause - The error to convert
501
- * @returns An XrpcFailure instance
502
- *
503
- * @example
504
- * ```typescript
505
- * try {
506
- * const response = await fetch(...)
507
- * // ... process response
508
- * } catch (err) {
509
- * return asXrpcFailure(method, err)
510
- * }
511
- * ```
512
- */
513
- export function asXrpcFailure<M extends Procedure | Query>(
514
- method: M,
515
- cause: unknown,
516
- ): XrpcFailure<M> {
517
- if (
518
- cause instanceof XrpcResponseError ||
519
- cause instanceof XrpcInvalidResponseError ||
520
- cause instanceof XrpcInternalError
521
- ) {
522
- if (cause.method === method) return cause
523
- }
524
-
525
- return new XrpcInternalError(method, undefined, { cause })
526
- }
527
-
528
- const HOP_BY_HOP_HEADERS = new Set([
529
- 'connection',
530
- 'keep-alive',
531
- 'proxy-authenticate',
532
- 'proxy-authorization',
533
- 'te',
534
- 'trailer',
535
- 'transfer-encoding',
536
- 'upgrade',
537
- ])
538
-
539
- function stripHopByHopHeaders(headers: Headers): Headers {
540
- const result = new Headers(headers)
541
-
542
- // Remove statically known hop-by-hop headers
543
- for (const name of HOP_BY_HOP_HEADERS) {
544
- result.delete(name)
545
- }
546
-
547
- // Remove headers listed in the "Connection" header
548
- const connection = headers.get('connection')
549
- if (connection) {
550
- for (const name of connection.split(',')) {
551
- result.delete(name.trim())
552
- }
553
- }
554
-
555
- // These are not actually hop-by-hop headers, but we remove them because the
556
- // upstream payload gets parsed and re-serialized, so content length and
557
- // encoding may no longer be accurate.
558
- result.delete('content-length')
559
- result.delete('content-encoding')
560
-
561
- return result
562
- }
563
-
564
- function buildResponseOverviewMessage(response: Response): string {
565
- if (response.status < 400) {
566
- return `Upstream server responded with an invalid status code (${response.status})`
567
- }
568
-
569
- return `Upstream server responded with a ${response.status} error`
570
- }
package/src/index.ts DELETED
@@ -1,6 +0,0 @@
1
- export * from './agent.js'
2
- export * from './client.js'
3
- export * from './errors.js'
4
- export * from './response.js'
5
- export * from './types.js'
6
- export * from './xrpc.js'