@atproto/lex-client 0.0.4 → 0.0.6

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 (103) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/agent.d.ts +10 -9
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +3 -0
  5. package/dist/agent.js.map +1 -1
  6. package/dist/client.d.ts +51 -113
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +38 -42
  9. package/dist/client.js.map +1 -1
  10. package/dist/errors.d.ts +82 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +132 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts +7 -7
  19. package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts.map +1 -1
  20. package/dist/lexicons/com/atproto/repo/createRecord.defs.js +8 -12
  21. package/dist/lexicons/com/atproto/repo/createRecord.defs.js.map +1 -1
  22. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts +7 -7
  23. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts.map +1 -1
  24. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.js +8 -12
  25. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.js.map +1 -1
  26. package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts +5 -6
  27. package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts.map +1 -1
  28. package/dist/lexicons/com/atproto/repo/getRecord.defs.js +6 -10
  29. package/dist/lexicons/com/atproto/repo/getRecord.defs.js.map +1 -1
  30. package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts +5 -6
  31. package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts.map +1 -1
  32. package/dist/lexicons/com/atproto/repo/listRecords.defs.js +5 -8
  33. package/dist/lexicons/com/atproto/repo/listRecords.defs.js.map +1 -1
  34. package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts +7 -7
  35. package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts.map +1 -1
  36. package/dist/lexicons/com/atproto/repo/putRecord.defs.js +8 -12
  37. package/dist/lexicons/com/atproto/repo/putRecord.defs.js.map +1 -1
  38. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts +7 -7
  39. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts.map +1 -1
  40. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.js +6 -9
  41. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.js.map +1 -1
  42. package/dist/lexicons/com/atproto/sync/getBlob.d.ts +3 -0
  43. package/dist/lexicons/com/atproto/sync/getBlob.d.ts.map +1 -0
  44. package/dist/lexicons/com/atproto/sync/getBlob.defs.d.ts +25 -0
  45. package/dist/lexicons/com/atproto/sync/getBlob.defs.d.ts.map +1 -0
  46. package/dist/lexicons/com/atproto/sync/getBlob.defs.js +27 -0
  47. package/dist/lexicons/com/atproto/sync/getBlob.defs.js.map +1 -0
  48. package/dist/lexicons/com/atproto/sync/getBlob.js +10 -0
  49. package/dist/lexicons/com/atproto/sync/getBlob.js.map +1 -0
  50. package/dist/lexicons/com/atproto/sync.d.ts +2 -0
  51. package/dist/lexicons/com/atproto/sync.d.ts.map +1 -0
  52. package/dist/lexicons/com/atproto/sync.js +9 -0
  53. package/dist/lexicons/com/atproto/sync.js.map +1 -0
  54. package/dist/lexicons/com/atproto.d.ts +1 -0
  55. package/dist/lexicons/com/atproto.d.ts.map +1 -1
  56. package/dist/lexicons/com/atproto.js +2 -1
  57. package/dist/lexicons/com/atproto.js.map +1 -1
  58. package/dist/lexicons.d.ts +2 -0
  59. package/dist/lexicons.d.ts.map +1 -0
  60. package/dist/lexicons.js +6 -0
  61. package/dist/lexicons.js.map +1 -0
  62. package/dist/response.d.ts +25 -8
  63. package/dist/response.d.ts.map +1 -1
  64. package/dist/response.js +123 -10
  65. package/dist/response.js.map +1 -1
  66. package/dist/types.d.ts +18 -4
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/types.js +0 -4
  69. package/dist/types.js.map +1 -1
  70. package/dist/util.d.ts +14 -0
  71. package/dist/util.d.ts.map +1 -0
  72. package/dist/util.js +65 -0
  73. package/dist/util.js.map +1 -0
  74. package/dist/xrpc.d.ts +35 -32
  75. package/dist/xrpc.d.ts.map +1 -1
  76. package/dist/xrpc.js +116 -124
  77. package/dist/xrpc.js.map +1 -1
  78. package/package.json +10 -10
  79. package/src/agent.ts +18 -14
  80. package/src/client.ts +135 -114
  81. package/src/errors.ts +206 -0
  82. package/src/index.ts +1 -1
  83. package/src/lexicons/com/atproto/repo/createRecord.defs.ts +31 -36
  84. package/src/lexicons/com/atproto/repo/deleteRecord.defs.ts +27 -32
  85. package/src/lexicons/com/atproto/repo/getRecord.defs.ts +12 -17
  86. package/src/lexicons/com/atproto/repo/listRecords.defs.ts +13 -15
  87. package/src/lexicons/com/atproto/repo/putRecord.defs.ts +32 -37
  88. package/src/lexicons/com/atproto/repo/uploadBlob.defs.ts +13 -15
  89. package/src/lexicons/com/atproto/sync/getBlob.defs.ts +37 -0
  90. package/src/lexicons/com/atproto/sync/getBlob.ts +6 -0
  91. package/src/lexicons/com/atproto/sync.ts +5 -0
  92. package/src/lexicons/com/atproto.ts +1 -0
  93. package/src/lexicons.ts +1 -0
  94. package/src/response.ts +201 -15
  95. package/src/types.ts +26 -5
  96. package/src/util.ts +84 -0
  97. package/src/xrpc.ts +220 -232
  98. package/tsconfig.tests.json +4 -7
  99. package/dist/error.d.ts +0 -66
  100. package/dist/error.d.ts.map +0 -1
  101. package/dist/error.js +0 -100
  102. package/dist/error.js.map +0 -1
  103. package/src/error.ts +0 -145
package/src/client.ts CHANGED
@@ -1,14 +1,15 @@
1
- import { LexMap, LexValue } from '@atproto/lex-data'
1
+ import { LexError, LexMap, LexValue } from '@atproto/lex-data'
2
2
  import {
3
3
  AtIdentifierString,
4
+ CidString,
4
5
  DidString,
5
6
  Infer,
6
- InferProcedureInputBody,
7
- InferProcedureOutputBody,
8
- InferQueryOutputBody,
9
- InferQueryParameters,
7
+ InferMethodInputBody,
8
+ InferMethodOutputBody,
9
+ InferMethodParams,
10
10
  InferRecordKey,
11
11
  LexiconRecordKey,
12
+ Main,
12
13
  NsidString,
13
14
  Params,
14
15
  Procedure,
@@ -16,18 +17,34 @@ import {
16
17
  RecordSchema,
17
18
  Restricted,
18
19
  Schema,
20
+ getMain,
19
21
  } from '@atproto/lex-schema'
20
22
  import { Agent, AgentOptions, buildAgent } from './agent.js'
21
- import {
22
- KnownError,
23
- XrpcError,
24
- XrpcRequestFailure,
25
- asXrpcRequestFailureFor,
26
- } from './error.js'
27
- import * as com from './lexicons/com.js'
28
- import { XrpcResponse, XrpcResponseBody } from './response.js'
29
- import { CallOptions, Namespace, Service, getMain } from './types.js'
30
- import { XrpcOptions, xrpc, xrpcRequestHeaders } from './xrpc.js'
23
+ import { com } from './lexicons.js'
24
+ import { LexRpcResponse, LexRpcResponseBody } from './response.js'
25
+ import { BinaryBodyInit, CallOptions, Service } from './types.js'
26
+ import { buildAtprotoHeaders } from './util.js'
27
+ import { LexRpcFailure, LexRpcOptions, xrpc, xrpcSafe } from './xrpc.js'
28
+
29
+ export type {
30
+ AtIdentifierString,
31
+ CidString,
32
+ DidString,
33
+ InferMethodInputBody,
34
+ InferMethodOutputBody,
35
+ InferMethodParams,
36
+ InferRecordKey,
37
+ LexMap,
38
+ LexValue,
39
+ LexiconRecordKey,
40
+ NsidString,
41
+ Params,
42
+ Procedure,
43
+ Query,
44
+ RecordSchema,
45
+ Restricted,
46
+ Schema,
47
+ }
31
48
 
32
49
  export type ClientOptions = {
33
50
  labelers?: Iterable<DidString>
@@ -84,29 +101,35 @@ export type RecordKeyOptions<
84
101
 
85
102
  export type CreateOptions<T extends RecordSchema> = CreateRecordOptions &
86
103
  RecordKeyOptions<T, 'tid'>
87
- export type CreateOutput = XrpcResponseBody<
88
- typeof com.atproto.repo.createRecord.main
104
+ export type CreateOutput = InferMethodOutputBody<
105
+ typeof com.atproto.repo.createRecord.main,
106
+ Uint8Array
89
107
  >
90
108
 
91
109
  export type DeleteOptions<T extends RecordSchema> = DeleteRecordOptions &
92
110
  RecordKeyOptions<T>
93
- export type DeleteOutput = XrpcResponseBody<
94
- typeof com.atproto.repo.deleteRecord.main
111
+ export type DeleteOutput = InferMethodOutputBody<
112
+ typeof com.atproto.repo.deleteRecord.main,
113
+ Uint8Array
95
114
  >
96
115
  export type GetOptions<T extends RecordSchema> = GetRecordOptions &
97
116
  RecordKeyOptions<T>
98
117
  export type GetOutput<T extends RecordSchema> = Omit<
99
- XrpcResponseBody<typeof com.atproto.repo.getRecord.main>,
118
+ InferMethodOutputBody<typeof com.atproto.repo.getRecord.main, Uint8Array>,
100
119
  'value'
101
120
  > & { value: Infer<T> }
102
121
 
103
122
  export type PutOptions<T extends RecordSchema> = PutRecordOptions &
104
123
  RecordKeyOptions<T>
105
- export type PutOutput = XrpcResponseBody<typeof com.atproto.repo.putRecord.main>
124
+ export type PutOutput = InferMethodOutputBody<
125
+ typeof com.atproto.repo.putRecord.main,
126
+ Uint8Array
127
+ >
106
128
 
107
129
  export type ListOptions = ListRecordsOptions
108
- export type ListOutput<T extends RecordSchema> = XrpcResponseBody<
109
- typeof com.atproto.repo.listRecords.main
130
+ export type ListOutput<T extends RecordSchema> = InferMethodOutputBody<
131
+ typeof com.atproto.repo.listRecords.main,
132
+ Uint8Array
110
133
  > & {
111
134
  records: ListRecord<T>[]
112
135
  // @NOTE Because the schema uses "type": "unknown" instead of an open union,
@@ -134,10 +157,7 @@ export class Client implements Agent {
134
157
  public readonly labelers: Set<DidString>
135
158
 
136
159
  constructor(agent: Agent | AgentOptions, options: ClientOptions = {}) {
137
- this.agent =
138
- typeof agent === 'object' && 'fetchHandler' in agent
139
- ? agent
140
- : buildAgent(agent)
160
+ this.agent = buildAgent(agent)
141
161
  this.service = options.service
142
162
  this.labelers = new Set(options.labelers)
143
163
  this.headers = new Headers(options.headers)
@@ -153,7 +173,7 @@ export class Client implements Agent {
153
173
  }
154
174
 
155
175
  public assertAuthenticated(): asserts this is { did: DidString } {
156
- if (!this.did) throw new XrpcError(KnownError.AuthenticationRequired)
176
+ if (!this.did) throw new LexError('AuthenticationRequired')
157
177
  }
158
178
 
159
179
  public setLabelers(labelers: Iterable<DidString> = []) {
@@ -170,7 +190,7 @@ export class Client implements Agent {
170
190
  }
171
191
 
172
192
  public fetchHandler(path: string, init: RequestInit): Promise<Response> {
173
- const headers = xrpcRequestHeaders({
193
+ const headers = buildAtprotoHeaders({
174
194
  headers: init.headers,
175
195
  service: this.service,
176
196
  labelers: [
@@ -186,40 +206,43 @@ export class Client implements Agent {
186
206
  if (!headers.has(key)) headers.set(key, value)
187
207
  }
188
208
 
209
+ // @NOTE The agent here could be another Client instance.
189
210
  return this.agent.fetchHandler(path, { ...init, headers })
190
211
  }
191
212
 
213
+ /**
214
+ * @throws {LexRpcFailure<M>} when the request fails or the response is an error
215
+ */
192
216
  async xrpc<const M extends Query | Procedure>(
193
- ns: NonNullable<unknown> extends XrpcOptions<M>
194
- ? Namespace<M>
217
+ ns: NonNullable<unknown> extends LexRpcOptions<M>
218
+ ? Main<M>
195
219
  : Restricted<'This XRPC method requires an "options" argument'>,
196
- ): Promise<XrpcResponse<M>>
220
+ ): Promise<LexRpcResponse<M>>
197
221
  async xrpc<const M extends Query | Procedure>(
198
- ns: Namespace<M>,
199
- options: XrpcOptions<M>,
200
- ): Promise<XrpcResponse<M>>
222
+ ns: Main<M>,
223
+ options: LexRpcOptions<M>,
224
+ ): Promise<LexRpcResponse<M>>
201
225
  async xrpc<const M extends Query | Procedure>(
202
- ns: Namespace<M>,
203
- options: XrpcOptions<M> = {} as XrpcOptions<M>,
204
- ): Promise<XrpcResponse<M>> {
226
+ ns: Main<M>,
227
+ options: LexRpcOptions<M> = {} as LexRpcOptions<M>,
228
+ ): Promise<LexRpcResponse<M>> {
205
229
  return xrpc(this, ns, options)
206
230
  }
207
231
 
208
232
  async xrpcSafe<const M extends Query | Procedure>(
209
- ns: NonNullable<unknown> extends XrpcOptions<M>
210
- ? Namespace<M>
233
+ ns: NonNullable<unknown> extends LexRpcOptions<M>
234
+ ? Main<M>
211
235
  : Restricted<'This XRPC method requires an "options" argument'>,
212
- ): Promise<XrpcResponse<M> | XrpcRequestFailure<M>>
236
+ ): Promise<LexRpcResponse<M> | LexRpcFailure<M>>
213
237
  async xrpcSafe<const M extends Query | Procedure>(
214
- ns: Namespace<M>,
215
- options: XrpcOptions<M>,
216
- ): Promise<XrpcResponse<M> | XrpcRequestFailure<M>>
238
+ ns: Main<M>,
239
+ options: LexRpcOptions<M>,
240
+ ): Promise<LexRpcResponse<M> | LexRpcFailure<M>>
217
241
  async xrpcSafe<const M extends Query | Procedure>(
218
- ns: Namespace<M>,
219
- options: XrpcOptions<M> = {} as XrpcOptions<M>,
220
- ): Promise<unknown> {
221
- const schema = getMain(ns)
222
- return this.xrpc(schema, options).catch(asXrpcRequestFailureFor(schema))
242
+ ns: Main<M>,
243
+ options: LexRpcOptions<M> = {} as LexRpcOptions<M>,
244
+ ): Promise<LexRpcResponse<M> | LexRpcFailure<M>> {
245
+ return xrpcSafe(this, ns, options)
223
246
  }
224
247
 
225
248
  /**
@@ -243,12 +266,6 @@ export class Client implements Agent {
243
266
  })
244
267
  }
245
268
 
246
- async createRecordsSafe(...args: Parameters<Client['createRecord']>) {
247
- return this.createRecord(...args).catch(
248
- asXrpcRequestFailureFor(com.atproto.repo.createRecord.main),
249
- )
250
- }
251
-
252
269
  async deleteRecord(
253
270
  collection: NsidString,
254
271
  rkey: string,
@@ -266,12 +283,6 @@ export class Client implements Agent {
266
283
  })
267
284
  }
268
285
 
269
- async deleteRecordsSafe(...args: Parameters<Client['deleteRecord']>) {
270
- return this.deleteRecord(...args).catch(
271
- asXrpcRequestFailureFor(com.atproto.repo.deleteRecord.main),
272
- )
273
- }
274
-
275
286
  public async getRecord(
276
287
  collection: NsidString,
277
288
  rkey: string,
@@ -287,12 +298,6 @@ export class Client implements Agent {
287
298
  })
288
299
  }
289
300
 
290
- async getRecordsSafe(...args: Parameters<Client['getRecord']>) {
291
- return this.getRecord(...args).catch(
292
- asXrpcRequestFailureFor(com.atproto.repo.getRecord.main),
293
- )
294
- }
295
-
296
301
  async putRecord(
297
302
  record: { $type: NsidString } & LexMap,
298
303
  rkey: string,
@@ -312,12 +317,6 @@ export class Client implements Agent {
312
317
  })
313
318
  }
314
319
 
315
- async putRecordsSafe(...args: Parameters<Client['putRecord']>) {
316
- return this.putRecord(...args).catch(
317
- asXrpcRequestFailureFor(com.atproto.repo.putRecord.main),
318
- )
319
- }
320
-
321
320
  async listRecords(nsid: NsidString, options?: ListRecordsOptions) {
322
321
  return this.xrpc(com.atproto.repo.listRecords.main, {
323
322
  ...options,
@@ -331,30 +330,54 @@ export class Client implements Agent {
331
330
  })
332
331
  }
333
332
 
334
- public async call<const T extends Action>(
335
- ns: Namespace<T>,
336
- input: InferActionInput<T>,
337
- options?: CallOptions,
338
- ): Promise<InferActionOutput<T>>
339
- public async call<const T extends Procedure>(
340
- ns: Namespace<T>,
341
- body: InferProcedureInputBody<T>,
342
- options?: CallOptions,
343
- ): Promise<InferProcedureOutputBody<T>>
333
+ async uploadBlob(
334
+ body: BinaryBodyInit,
335
+ options?: CallOptions & { encoding?: `${string}/${string}` },
336
+ ) {
337
+ return this.xrpc(com.atproto.repo.uploadBlob.main, {
338
+ ...options,
339
+ body,
340
+ })
341
+ }
342
+
343
+ async getBlob(did: DidString, cid: CidString, options?: CallOptions) {
344
+ return this.xrpc(com.atproto.sync.getBlob.main, {
345
+ ...options,
346
+ params: { did, cid },
347
+ })
348
+ }
349
+
344
350
  public async call<const T extends Query>(
345
- ns: NonNullable<unknown> extends InferQueryParameters<T>
346
- ? Namespace<T>
351
+ ns: NonNullable<unknown> extends InferMethodParams<T>
352
+ ? Main<T>
347
353
  : Restricted<'This query type requires a "params" argument'>,
348
- ): Promise<InferQueryOutputBody<T>>
349
- public async call<const T extends Query>(
350
- ns: Namespace<T>,
351
- params: NonNullable<unknown> extends InferQueryParameters<T>
352
- ? InferQueryParameters<T> | undefined
353
- : InferQueryParameters<T>,
354
+ ): Promise<LexRpcResponseBody<T>>
355
+ public async call<const T extends Action>(
356
+ ns: void extends InferActionInput<T>
357
+ ? Main<T>
358
+ : Restricted<'This action type requires an "input" argument'>,
359
+ ): Promise<InferActionOutput<T>>
360
+ public async call<const T extends Action | Procedure | Query>(
361
+ ns: Main<T>,
362
+ arg: T extends Action
363
+ ? InferActionInput<T>
364
+ : T extends Procedure
365
+ ? InferMethodInputBody<T, Uint8Array>
366
+ : T extends Query
367
+ ? InferMethodParams<T>
368
+ : never,
354
369
  options?: CallOptions,
355
- ): Promise<InferQueryOutputBody<T>>
370
+ ): Promise<
371
+ T extends Action
372
+ ? InferActionOutput<T>
373
+ : T extends Procedure
374
+ ? LexRpcResponseBody<T>
375
+ : T extends Query
376
+ ? LexRpcResponseBody<T>
377
+ : never
378
+ >
356
379
  public async call(
357
- ns: Namespace<Action> | Namespace<Procedure> | Namespace<Query>,
380
+ ns: Main<Action> | Main<Procedure> | Main<Query>,
358
381
  arg?: LexValue | Params,
359
382
  options: CallOptions = {},
360
383
  ): Promise<unknown> {
@@ -365,12 +388,10 @@ export class Client implements Agent {
365
388
  }
366
389
 
367
390
  if (method instanceof Procedure) {
368
- const body = arg as LexValue | undefined
369
- const result = await this.xrpc(method, { ...options, body })
391
+ const result = await this.xrpc(method, { ...options, body: arg as any })
370
392
  return result.body
371
393
  } else if (method instanceof Query) {
372
- const params = arg as Params | undefined
373
- const result = await this.xrpc(method, { ...options, params })
394
+ const result = await this.xrpc(method, { ...options, params: arg as any })
374
395
  return result.body
375
396
  } else {
376
397
  throw new TypeError('Invalid lexicon')
@@ -379,24 +400,23 @@ export class Client implements Agent {
379
400
 
380
401
  public async create<const T extends RecordSchema>(
381
402
  ns: NonNullable<unknown> extends CreateOptions<T>
382
- ? Namespace<T>
403
+ ? Main<T>
383
404
  : Restricted<'This record type requires an "options" argument'>,
384
405
  input: Omit<Infer<T>, '$type'>,
385
406
  ): Promise<CreateOutput>
386
407
  public async create<const T extends RecordSchema>(
387
- ns: Namespace<T>,
408
+ ns: Main<T>,
388
409
  input: Omit<Infer<T>, '$type'>,
389
410
  options: CreateOptions<T>,
390
411
  ): Promise<CreateOutput>
391
412
  public async create<const T extends RecordSchema>(
392
- ns: Namespace<T>,
413
+ ns: Main<T>,
393
414
  input: Omit<Infer<T>, '$type'>,
394
415
  options: CreateOptions<T> = {} as CreateOptions<T>,
395
416
  ): Promise<CreateOutput> {
396
417
  const schema: T = getMain(ns)
397
- const record = options.validate
398
- ? schema.parse(schema.build(input))
399
- : schema.build(input)
418
+ const record = schema.build(input)
419
+ if (options.validateRequest) schema.assert(record)
400
420
  const rkey = options.rkey ?? getDefaultRecordKey(schema)
401
421
  if (rkey !== undefined) schema.keySchema.assert(rkey)
402
422
  const response = await this.createRecord(record, rkey, options)
@@ -405,15 +425,15 @@ export class Client implements Agent {
405
425
 
406
426
  public async delete<const T extends RecordSchema>(
407
427
  ns: NonNullable<unknown> extends DeleteOptions<T>
408
- ? Namespace<T>
428
+ ? Main<T>
409
429
  : Restricted<'This record type requires an "options" argument'>,
410
430
  ): Promise<DeleteOutput>
411
431
  public async delete<const T extends RecordSchema>(
412
- ns: Namespace<T>,
432
+ ns: Main<T>,
413
433
  options?: DeleteOptions<T>,
414
434
  ): Promise<DeleteOutput>
415
435
  public async delete<const T extends RecordSchema>(
416
- ns: Namespace<T>,
436
+ ns: Main<T>,
417
437
  options: DeleteOptions<T> = {} as DeleteOptions<T>,
418
438
  ): Promise<DeleteOutput> {
419
439
  const schema = getMain(ns)
@@ -426,15 +446,15 @@ export class Client implements Agent {
426
446
 
427
447
  public async get<const T extends RecordSchema>(
428
448
  ns: T['key'] extends `literal:${string}`
429
- ? Namespace<T>
449
+ ? Main<T>
430
450
  : Restricted<'This record type requires an "options" argument'>,
431
451
  ): Promise<GetOutput<T>>
432
452
  public async get<const T extends RecordSchema>(
433
- ns: Namespace<T>,
453
+ ns: Main<T>,
434
454
  options?: GetOptions<T>,
435
455
  ): Promise<GetOutput<T>>
436
456
  public async get<const T extends RecordSchema>(
437
- ns: Namespace<T>,
457
+ ns: Main<T>,
438
458
  options: GetOptions<T> = {} as GetOptions<T>,
439
459
  ): Promise<GetOutput<T>> {
440
460
  const schema = getMain(ns)
@@ -448,29 +468,30 @@ export class Client implements Agent {
448
468
 
449
469
  public async put<const T extends RecordSchema>(
450
470
  ns: NonNullable<unknown> extends PutOptions<T>
451
- ? Namespace<T>
471
+ ? Main<T>
452
472
  : Restricted<'This record type requires an "options" argument'>,
453
473
  input: Omit<Infer<T>, '$type'>,
454
474
  ): Promise<PutOutput>
455
475
  public async put<const T extends RecordSchema>(
456
- ns: Namespace<T>,
476
+ ns: Main<T>,
457
477
  input: Omit<Infer<T>, '$type'>,
458
478
  options: PutOptions<T>,
459
479
  ): Promise<PutOutput>
460
480
  public async put<const T extends RecordSchema>(
461
- ns: Namespace<T>,
481
+ ns: Main<T>,
462
482
  input: Omit<Infer<T>, '$type'>,
463
483
  options: PutOptions<T> = {} as PutOptions<T>,
464
484
  ): Promise<PutOutput> {
465
- const schema = getMain(ns)
485
+ const schema: T = getMain(ns)
466
486
  const record = schema.build(input)
487
+ if (options.validateRequest) schema.assert(record)
467
488
  const rkey = options.rkey ?? getLiteralRecordKey(schema)
468
489
  const response = await this.putRecord(record, rkey, options)
469
490
  return response.body
470
491
  }
471
492
 
472
493
  async list<const T extends RecordSchema>(
473
- ns: Namespace<T>,
494
+ ns: Main<T>,
474
495
  options?: ListOptions,
475
496
  ): Promise<ListOutput<T>> {
476
497
  const schema = getMain(ns)
package/src/errors.ts ADDED
@@ -0,0 +1,206 @@
1
+ import { LexError, LexErrorCode, LexErrorData } from '@atproto/lex-data'
2
+ import { l } from '@atproto/lex-schema'
3
+ import { Payload } from './util.js'
4
+
5
+ export type LexRpcErrorPayload<N extends LexErrorCode = LexErrorCode> = Payload<
6
+ LexErrorData<N>,
7
+ 'application/json'
8
+ >
9
+
10
+ export class LexRpcError<
11
+ N extends LexErrorCode = LexErrorCode,
12
+ > extends LexError<N> {
13
+ name = 'LexRpcError'
14
+
15
+ constructor(
16
+ error: N,
17
+ message: string = `${error} Lexicon RPC error`,
18
+ options?: ErrorOptions,
19
+ ) {
20
+ super(error, message, options)
21
+ }
22
+ }
23
+
24
+ /**
25
+ * All unsuccessful responses should follow a standard error response
26
+ * schema. The Content-Type should be application/json, and the payload
27
+ * should be a JSON object with the following fields:
28
+ *
29
+ * - `error` (string, required): type name of the error (generic ASCII
30
+ * constant, no whitespace)
31
+ * - `message` (string, optional): description of the error, appropriate for
32
+ * display to humans
33
+ *
34
+ * This function checks whether a given payload matches this schema.
35
+ */
36
+ export function isLexRpcErrorPayload(
37
+ payload: Payload | null,
38
+ ): payload is LexRpcErrorPayload {
39
+ return (
40
+ payload !== null &&
41
+ payload.encoding === 'application/json' &&
42
+ l.lexErrorData.matches(payload.body)
43
+ )
44
+ }
45
+
46
+ /**
47
+ * Interface representing a failed XRPC request result.
48
+ */
49
+ type LexRpcFailureResult<N extends LexErrorCode, E> = l.ResultFailure<E> & {
50
+ readonly error: N
51
+ shouldRetry(): boolean
52
+ matchesSchema(): boolean
53
+ }
54
+
55
+ /**
56
+ * Class used to represent an HTTP request that resulted in an XRPC method error
57
+ * That is, a non-2xx response with a valid XRPC error payload.
58
+ */
59
+ export class LexRpcResponseError<
60
+ M extends l.Procedure | l.Query = l.Procedure | l.Query,
61
+ N extends LexErrorCode = LexErrorCode,
62
+ >
63
+ extends LexRpcError<N>
64
+ implements LexRpcFailureResult<N, LexRpcResponseError<M, N>>
65
+ {
66
+ name = 'LexRpcResponseError'
67
+
68
+ constructor(
69
+ readonly method: M,
70
+ readonly status: number,
71
+ readonly headers: Headers,
72
+ readonly payload: LexRpcErrorPayload<N>,
73
+ options?: ErrorOptions,
74
+ ) {
75
+ const { error, message } = payload.body
76
+ super(error, message, options)
77
+ }
78
+
79
+ readonly success = false
80
+
81
+ get reason(): this {
82
+ return this as this
83
+ }
84
+
85
+ get body(): LexErrorData {
86
+ return this.payload.body
87
+ }
88
+
89
+ matchesSchema(): this is M extends {
90
+ errors: readonly (infer E extends string)[]
91
+ }
92
+ ? LexRpcResponseError<M, E>
93
+ : never {
94
+ return this.method.errors?.includes(this.error) ?? false
95
+ }
96
+
97
+ shouldRetry(): boolean {
98
+ // Do not retry client errors
99
+ if (this.status < 500) return false
100
+
101
+ return true
102
+ }
103
+
104
+ toJSON() {
105
+ return this.payload.body
106
+ }
107
+
108
+ toResponse(): Response {
109
+ const { status, headers } = this
110
+ return Response.json(this.toJSON(), { status, headers })
111
+ }
112
+ }
113
+
114
+ /**
115
+ * This class represents an invalid XRPC response from the server.
116
+ */
117
+ export class LexRpcUpstreamError<
118
+ N extends 'InvalidResponse' | 'UpstreamFailure' =
119
+ | 'InvalidResponse'
120
+ | 'UpstreamFailure',
121
+ >
122
+ extends LexRpcError<N>
123
+ implements LexRpcFailureResult<N, LexRpcUpstreamError<N>>
124
+ {
125
+ name = 'LexRpcUpstreamError' as const
126
+
127
+ // For debugging purposes, we keep the response details here
128
+ readonly response: {
129
+ status: number
130
+ headers: Headers
131
+ payload: Payload | null
132
+ }
133
+
134
+ constructor(
135
+ error: N,
136
+ message: string,
137
+ response: { status: number; headers: Headers },
138
+ payload: Payload | null,
139
+ options?: ErrorOptions,
140
+ ) {
141
+ super(error, message, { cause: options?.cause })
142
+ this.response = {
143
+ status: response.status,
144
+ headers: response.headers,
145
+ payload,
146
+ }
147
+ }
148
+
149
+ readonly success = false as const
150
+
151
+ get reason(): this {
152
+ return this
153
+ }
154
+
155
+ matchesSchema(): false {
156
+ return false
157
+ }
158
+
159
+ shouldRetry(): boolean {
160
+ // Do not retry client errors
161
+ return this.response.status >= 500
162
+ }
163
+
164
+ toResponse(): Response {
165
+ return Response.json(this.toJSON(), { status: 502 })
166
+ }
167
+ }
168
+
169
+ export class LexRpcUnexpectedError
170
+ extends LexRpcError<'InternalServerError'>
171
+ implements LexRpcFailureResult<'InternalServerError', unknown>
172
+ {
173
+ name = 'LexRpcUnexpectedError' as const
174
+
175
+ protected constructor(message: string, options: Required<ErrorOptions>) {
176
+ super('InternalServerError', message, options)
177
+ }
178
+
179
+ readonly success = false
180
+
181
+ get reason() {
182
+ return this.cause
183
+ }
184
+
185
+ matchesSchema(): false {
186
+ return false
187
+ }
188
+
189
+ shouldRetry(): boolean {
190
+ return true
191
+ }
192
+
193
+ toResponse(): Response {
194
+ return Response.json(this.toJSON(), { status: 500 })
195
+ }
196
+
197
+ static from(
198
+ cause: unknown,
199
+ message: string = cause instanceof LexError
200
+ ? cause.message
201
+ : 'XRPC request failed',
202
+ ): LexRpcUnexpectedError {
203
+ if (cause instanceof LexRpcUnexpectedError) return cause
204
+ return new LexRpcUnexpectedError(message, { cause })
205
+ }
206
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './agent.js'
2
2
  export * from './client.js'
3
- export * from './error.js'
3
+ export * from './errors.js'
4
4
  export * from './response.js'
5
5
  export * from './types.js'
6
6
  export * from './xrpc.js'