@atproto/lex-client 0.0.10 → 0.0.12

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 (48) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/agent.d.ts +72 -0
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +46 -1
  5. package/dist/agent.js.map +1 -1
  6. package/dist/client.d.ts +442 -46
  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 +202 -48
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js +208 -65
  13. package/dist/errors.js.map +1 -1
  14. package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts +20 -20
  15. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts +12 -12
  16. package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts +6 -6
  17. package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts +6 -6
  18. package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts +22 -22
  19. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts +2 -2
  20. package/dist/response.d.ts +17 -6
  21. package/dist/response.d.ts.map +1 -1
  22. package/dist/response.js +45 -32
  23. package/dist/response.js.map +1 -1
  24. package/dist/types.d.ts +51 -0
  25. package/dist/types.d.ts.map +1 -1
  26. package/dist/types.js.map +1 -1
  27. package/dist/util.d.ts +40 -5
  28. package/dist/util.d.ts.map +1 -1
  29. package/dist/util.js +22 -0
  30. package/dist/util.js.map +1 -1
  31. package/dist/www-authenticate.d.ts +35 -0
  32. package/dist/www-authenticate.d.ts.map +1 -0
  33. package/dist/www-authenticate.js +57 -0
  34. package/dist/www-authenticate.js.map +1 -0
  35. package/dist/xrpc.d.ts +82 -10
  36. package/dist/xrpc.d.ts.map +1 -1
  37. package/dist/xrpc.js +15 -28
  38. package/dist/xrpc.js.map +1 -1
  39. package/package.json +7 -7
  40. package/src/agent.ts +101 -1
  41. package/src/client.ts +428 -15
  42. package/src/errors.ts +308 -120
  43. package/src/response.ts +68 -63
  44. package/src/types.ts +52 -0
  45. package/src/util.ts +50 -5
  46. package/src/www-authenticate.test.ts +227 -0
  47. package/src/www-authenticate.ts +101 -0
  48. package/src/xrpc.ts +100 -53
package/src/client.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { LexError, LexMap, LexValue } from '@atproto/lex-data'
1
+ import { LexError, LexMap, LexValue, TypedLexMap } from '@atproto/lex-data'
2
2
  import {
3
3
  AtIdentifierString,
4
4
  CidString,
@@ -19,79 +19,159 @@ import {
19
19
  getMain,
20
20
  } from '@atproto/lex-schema'
21
21
  import { Agent, AgentOptions, buildAgent } from './agent.js'
22
+ import { XrpcFailure } from './errors.js'
22
23
  import { com } from './lexicons/index.js'
23
24
  import { XrpcResponse, XrpcResponseBody } from './response.js'
24
25
  import { BinaryBodyInit, CallOptions, Service } from './types.js'
25
26
  import { buildAtprotoHeaders } from './util.js'
26
- import {
27
- XrpcFailure,
28
- XrpcOptions,
29
- XrpcRequestParams,
30
- xrpc,
31
- xrpcSafe,
32
- } from './xrpc.js'
27
+ import { XrpcOptions, XrpcRequestParams, xrpc, xrpcSafe } from './xrpc.js'
33
28
 
34
29
  export type {
35
30
  AtIdentifierString,
36
31
  CidString,
37
32
  DidString,
33
+ Infer,
38
34
  InferMethodInputBody,
39
35
  InferMethodOutputBody,
40
36
  InferRecordKey,
41
37
  LexMap,
42
38
  LexValue,
43
39
  LexiconRecordKey,
40
+ Main,
44
41
  NsidString,
45
42
  Params,
46
43
  Procedure,
47
44
  Query,
48
45
  RecordSchema,
49
46
  Restricted,
47
+ TypedLexMap,
50
48
  }
51
49
 
50
+ /**
51
+ * Configuration options for creating a {@link Client}.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const options: ClientOptions = {
56
+ * labelers: ['did:plc:labeler1'],
57
+ * service: 'did:web:api.bsky.app#bsky_appview',
58
+ * headers: { 'X-Custom-Header': 'value' }
59
+ * }
60
+ * ```
61
+ */
52
62
  export type ClientOptions = {
63
+ /** Labeler DIDs to include in requests for content moderation. */
53
64
  labelers?: Iterable<DidString>
65
+ /** Custom headers to include in all requests made by this client. */
54
66
  headers?: HeadersInit
67
+ /** Service proxy identifier for routing requests through a specific service. */
55
68
  service?: Service
56
69
  }
57
70
 
71
+ /**
72
+ * A composable action that can be invoked via {@link Client.call}.
73
+ *
74
+ * Actions provide a way to define custom operations that integrate with the
75
+ * Client's call interface, enabling type-safe, reusable business logic.
76
+ *
77
+ * @typeParam I - The input type for the action
78
+ * @typeParam O - The output type for the action
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const myAction: Action<{ userId: string }, { profile: Profile }> = async (client, input, options) => {
83
+ * const response = await client.xrpc(someMethod, { params: { actor: input.userId }, ...options })
84
+ * return { profile: response.body }
85
+ * }
86
+ * ```
87
+ */
58
88
  export type Action<I = any, O = any> = (
59
89
  client: Client,
60
90
  input: I,
61
91
  options: CallOptions,
62
92
  ) => O | Promise<O>
93
+
94
+ /**
95
+ * Extracts the input type from an {@link Action}.
96
+ * @typeParam A - The Action type to extract from
97
+ */
63
98
  export type InferActionInput<A extends Action> =
64
99
  A extends Action<infer I, any> ? I : never
100
+
101
+ /**
102
+ * Extracts the output type from an {@link Action}.
103
+ * @typeParam A - The Action type to extract from
104
+ */
65
105
  export type InferActionOutput<A extends Action> =
66
106
  A extends Action<any, infer O> ? O : never
67
107
 
108
+ /**
109
+ * Options for creating a record in an AT Protocol repository.
110
+ *
111
+ * @see {@link Client.createRecord}
112
+ */
68
113
  export type CreateRecordOptions = CallOptions & {
114
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
69
115
  repo?: AtIdentifierString
116
+ /** Compare-and-swap on the repo commit. If specified, must match current commit. */
70
117
  swapCommit?: string
118
+ /** Whether to validate the record against its lexicon schema. */
71
119
  validate?: boolean
72
120
  }
73
121
 
122
+ /**
123
+ * Options for deleting a record from an AT Protocol repository.
124
+ *
125
+ * @see {@link Client.deleteRecord}
126
+ */
74
127
  export type DeleteRecordOptions = CallOptions & {
128
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
75
129
  repo?: AtIdentifierString
130
+ /** Compare-and-swap on the repo commit. If specified, must match current commit. */
76
131
  swapCommit?: string
132
+ /** Compare-and-swap on the record CID. If specified, must match current record. */
77
133
  swapRecord?: string
78
134
  }
79
135
 
136
+ /**
137
+ * Options for retrieving a record from an AT Protocol repository.
138
+ *
139
+ * @see {@link Client.getRecord}
140
+ */
80
141
  export type GetRecordOptions = CallOptions & {
142
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
81
143
  repo?: AtIdentifierString
82
144
  }
83
145
 
146
+ /**
147
+ * Options for creating or updating a record in an AT Protocol repository.
148
+ *
149
+ * @see {@link Client.putRecord}
150
+ */
84
151
  export type PutRecordOptions = CallOptions & {
152
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
85
153
  repo?: AtIdentifierString
154
+ /** Compare-and-swap on the repo commit. If specified, must match current commit. */
86
155
  swapCommit?: string
156
+ /** Compare-and-swap on the record CID. If specified, must match current record. */
87
157
  swapRecord?: string
158
+ /** Whether to validate the record against its lexicon schema. */
88
159
  validate?: boolean
89
160
  }
90
161
 
162
+ /**
163
+ * Options for listing records in an AT Protocol repository collection.
164
+ *
165
+ * @see {@link Client.listRecords}
166
+ */
91
167
  export type ListRecordsOptions = CallOptions & {
168
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
92
169
  repo?: AtIdentifierString
170
+ /** Maximum number of records to return. */
93
171
  limit?: number
172
+ /** Pagination cursor from a previous response. */
94
173
  cursor?: string
174
+ /** If true, returns records in reverse chronological order. */
95
175
  reverse?: boolean
96
176
  }
97
177
 
@@ -102,48 +182,116 @@ export type RecordKeyOptions<
102
182
  ? { rkey?: InferRecordKey<T> }
103
183
  : { rkey: InferRecordKey<T> }
104
184
 
185
+ /**
186
+ * Type-safe options for {@link Client.create}, combining record options with key requirements.
187
+ * @typeParam T - The record schema type
188
+ */
105
189
  export type CreateOptions<T extends RecordSchema> = CreateRecordOptions &
106
- RecordKeyOptions<T, 'tid'>
190
+ RecordKeyOptions<T, 'tid' | 'any'>
191
+
192
+ /**
193
+ * Output type for record creation operations.
194
+ * Contains the URI and CID of the newly created record.
195
+ */
107
196
  export type CreateOutput = InferMethodOutputBody<
108
197
  typeof com.atproto.repo.createRecord.main,
109
198
  Uint8Array
110
199
  >
111
200
 
201
+ /**
202
+ * Type-safe options for {@link Client.delete}, combining delete options with key requirements.
203
+ * @typeParam T - The record schema type
204
+ */
112
205
  export type DeleteOptions<T extends RecordSchema> = DeleteRecordOptions &
113
206
  RecordKeyOptions<T>
207
+
208
+ /**
209
+ * Output type for record deletion operations.
210
+ */
114
211
  export type DeleteOutput = InferMethodOutputBody<
115
212
  typeof com.atproto.repo.deleteRecord.main,
116
213
  Uint8Array
117
214
  >
215
+
216
+ /**
217
+ * Type-safe options for {@link Client.get}, combining get options with key requirements.
218
+ * @typeParam T - The record schema type
219
+ */
118
220
  export type GetOptions<T extends RecordSchema> = GetRecordOptions &
119
221
  RecordKeyOptions<T>
222
+
223
+ /**
224
+ * Output type for record retrieval operations.
225
+ * Contains the record value validated against the schema type.
226
+ * @typeParam T - The record schema type
227
+ */
120
228
  export type GetOutput<T extends RecordSchema> = Omit<
121
229
  InferMethodOutputBody<typeof com.atproto.repo.getRecord.main, Uint8Array>,
122
230
  'value'
123
231
  > & { value: Infer<T> }
124
232
 
233
+ /**
234
+ * Type-safe options for {@link Client.put}, combining put options with key requirements.
235
+ * @typeParam T - The record schema type
236
+ */
125
237
  export type PutOptions<T extends RecordSchema> = PutRecordOptions &
126
238
  RecordKeyOptions<T>
239
+
240
+ /**
241
+ * Output type for record put (create/update) operations.
242
+ * Contains the URI and CID of the record.
243
+ */
127
244
  export type PutOutput = InferMethodOutputBody<
128
245
  typeof com.atproto.repo.putRecord.main,
129
246
  Uint8Array
130
247
  >
131
248
 
249
+ /**
250
+ * Options for {@link Client.list} operations.
251
+ */
132
252
  export type ListOptions = ListRecordsOptions
253
+
254
+ /**
255
+ * Output type for record listing operations.
256
+ * Contains validated records and any invalid records that failed schema validation.
257
+ * @typeParam T - The record schema type
258
+ */
133
259
  export type ListOutput<T extends RecordSchema> = InferMethodOutputBody<
134
260
  typeof com.atproto.repo.listRecords.main,
135
261
  Uint8Array
136
262
  > & {
263
+ /** Records that successfully validated against the schema. */
137
264
  records: ListRecord<Infer<T>>[]
138
265
  // @NOTE Because the schema uses "type": "unknown" instead of an open union,
139
266
  // we have to use UnknownObject instead of Unknown$TypedObject here.
267
+ /** Records that failed schema validation. */
140
268
  invalid: UnknownObject[]
141
269
  }
270
+
271
+ /**
272
+ * A record from a list operation with its value typed to the schema.
273
+ * @typeParam Value - The validated record value type
274
+ */
142
275
  export type ListRecord<Value extends LexMap> =
143
276
  com.atproto.repo.listRecords.Record & {
144
277
  value: Value
145
278
  }
146
279
 
280
+ /**
281
+ * The Client class is the primary interface for interacting with AT Protocol
282
+ * services. It provides type-safe methods for XRPC calls, record operations,
283
+ * and blob handling.
284
+ *
285
+ * @example Basic usage
286
+ * ```typescript
287
+ * import { Client } from '@atproto/lex'
288
+ *
289
+ * const client = new Client(agent)
290
+ * const response = await client.xrpc(app.bsky.feed.getTimeline.main, {
291
+ * params: { limit: 50 }
292
+ * })
293
+ * ```
294
+ */
147
295
  export class Client implements Agent {
148
296
  static appLabelers: readonly DidString[] = []
149
297
 
@@ -154,9 +302,16 @@ export class Client implements Agent {
154
302
  if (opts.appLabelers) this.appLabelers = [...opts.appLabelers]
155
303
  }
156
304
 
305
+ /** The underlying agent used for making requests. */
157
306
  public readonly agent: Agent
307
+
308
+ /** Custom headers included in all requests. */
158
309
  public readonly headers: Headers
310
+
311
+ /** Optional service identifier for routing requests. */
159
312
  public readonly service?: Service
313
+
314
+ /** Set of labeler DIDs specific to this client instance. */
160
315
  public readonly labelers: Set<DidString>
161
316
 
162
317
  constructor(agent: Agent | AgentOptions, options: ClientOptions = {}) {
@@ -166,32 +321,68 @@ export class Client implements Agent {
166
321
  this.headers = new Headers(options.headers)
167
322
  }
168
323
 
324
+ /**
325
+ * The DID of the authenticated user, or `undefined` if not authenticated.
326
+ */
169
327
  get did(): DidString | undefined {
170
328
  return this.agent.did
171
329
  }
172
330
 
331
+ /**
332
+ * The DID of the authenticated user.
333
+ * @throws {LexError} with code 'AuthenticationRequired' if not authenticated
334
+ */
173
335
  get assertDid(): DidString {
174
336
  this.assertAuthenticated()
175
337
  return this.did
176
338
  }
177
339
 
340
+ /**
341
+ * Asserts that the client is authenticated.
342
+ * Use as a type guard when you need to ensure authentication.
343
+ *
344
+ * @throws {LexError} with code 'AuthenticationRequired' if not authenticated
345
+ *
346
+ * @example
347
+ * ```typescript
348
+ * client.assertAuthenticated()
349
+ * // TypeScript now knows client.did is defined
350
+ * console.log(client.did)
351
+ * ```
352
+ */
178
353
  public assertAuthenticated(): asserts this is { did: DidString } {
179
354
  if (!this.did) throw new LexError('AuthenticationRequired')
180
355
  }
181
356
 
357
+ /**
358
+ * Replaces all labelers with the given set.
359
+ * @param labelers - Iterable of labeler DIDs
360
+ */
182
361
  public setLabelers(labelers: Iterable<DidString> = []) {
183
362
  this.clearLabelers()
184
363
  this.addLabelers(labelers)
185
364
  }
186
365
 
366
+ /**
367
+ * Adds labelers to the current set.
368
+ * @param labelers - Iterable of labeler DIDs to add
369
+ */
187
370
  public addLabelers(labelers: Iterable<DidString>) {
188
371
  for (const labeler of labelers) this.labelers.add(labeler)
189
372
  }
190
373
 
374
+ /**
375
+ * Removes all labelers from this client instance.
376
+ */
191
377
  public clearLabelers() {
192
378
  this.labelers.clear()
193
379
  }
194
380
 
381
+ /**
382
+ * Low-level fetch handler for making requests.
383
+ * @param path - The request path
384
+ * @param init - Request initialization options
385
+ */
195
386
  public fetchHandler(path: string, init: RequestInit): Promise<Response> {
196
387
  const headers = buildAtprotoHeaders({
197
388
  headers: init.headers,
@@ -214,7 +405,33 @@ export class Client implements Agent {
214
405
  }
215
406
 
216
407
  /**
217
- * @throws {XrpcFailure<M>} when the request fails or the response is an error
408
+ * Makes an XRPC request. Throws on failure.
409
+ *
410
+ * @param ns - The lexicon method definition (e.g., `app.bsky.feed.getTimeline`)
411
+ * @param options - Request options including params and body
412
+ * @returns The successful XRPC response
413
+ * @throws {XrpcFailure} when the request fails or returns an error
414
+ *
415
+ * @example Query with parameters
416
+ * ```typescript
417
+ * const response = await client.xrpc(app.bsky.feed.getTimeline, {
418
+ * params: { limit: 50, cursor: 'abc123' }
419
+ * })
420
+ * console.log(response.body.feed)
421
+ * ```
422
+ *
423
+ * @example Procedure with body
424
+ * ```typescript
425
+ * const response = await client.xrpc(com.atproto.repo.createRecord, {
426
+ * body: {
427
+ * repo: client.assertDid,
428
+ * collection: 'app.bsky.feed.post',
429
+ * record: { text: 'Hello!', createdAt: new Date().toISOString() }
430
+ * }
431
+ * })
432
+ * ```
433
+ *
434
+ * @see {@link xrpcSafe} for a non-throwing variant
218
435
  */
219
436
  async xrpc<const M extends Query | Procedure>(
220
437
  ns: NonNullable<unknown> extends XrpcOptions<M>
@@ -232,6 +449,29 @@ export class Client implements Agent {
232
449
  return xrpc(this, ns, options)
233
450
  }
234
451
 
452
+ /**
453
+ * Makes an XRPC request without throwing on failure.
454
+ * Returns either a successful response or a failure object.
455
+ *
456
+ * @param ns - The lexicon method definition
457
+ * @param options - Request options
458
+ * @returns Either an XrpcResponse on success or XrpcFailure on failure
459
+ *
460
+ * @example
461
+ * ```typescript
462
+ * const result = await client.xrpcSafe(app.bsky.actor.getProfile.main, {
463
+ * params: { actor: 'alice.bsky.social' }
464
+ * })
465
+ *
466
+ * if (result.success) {
467
+ * console.log(result.body.displayName)
468
+ * } else {
469
+ * console.error('Failed:', result.error)
470
+ * }
471
+ * ```
472
+ *
473
+ * @see {@link xrpc} for a throwing variant
474
+ */
235
475
  async xrpcSafe<const M extends Query | Procedure>(
236
476
  ns: NonNullable<unknown> extends XrpcOptions<M>
237
477
  ? Main<M>
@@ -249,10 +489,27 @@ export class Client implements Agent {
249
489
  }
250
490
 
251
491
  /**
252
- * @param rkey Leave `undefined` to have the server generate a TID.
492
+ * Creates a new record in an AT Protocol repository.
493
+ *
494
+ * @param record - The record to create, must include an {@link NsidString} `$type`
495
+ * @param rkey - Optional record key; if omitted, server generates a TID
496
+ * @param options - Create options including repo, swapCommit, validate
497
+ * @returns The XRPC response containing the created record's URI and CID
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * const response = await client.createRecord(
502
+ * { $type: 'app.bsky.feed.post', text: 'Hello!', createdAt: new Date().toISOString() },
503
+ * undefined, // Let server generate rkey
504
+ * { validate: true }
505
+ * )
506
+ * console.log(response.body.uri)
507
+ * ```
508
+ *
509
+ * @see {@link create} for a higher-level typed alternative
253
510
  */
254
511
  public async createRecord(
255
- record: { $type: NsidString } & LexMap,
512
+ record: TypedLexMap<NsidString>,
256
513
  rkey?: string,
257
514
  options?: CreateRecordOptions,
258
515
  ) {
@@ -269,6 +526,15 @@ export class Client implements Agent {
269
526
  })
270
527
  }
271
528
 
529
+ /**
530
+ * Deletes a record from an AT Protocol repository.
531
+ *
532
+ * @param collection - The collection NSID
533
+ * @param rkey - The record key
534
+ * @param options - Delete options including repo, swapCommit, swapRecord
535
+ *
536
+ * @see {@link delete} for a higher-level typed alternative
537
+ */
272
538
  async deleteRecord(
273
539
  collection: NsidString,
274
540
  rkey: string,
@@ -286,6 +552,15 @@ export class Client implements Agent {
286
552
  })
287
553
  }
288
554
 
555
+ /**
556
+ * Retrieves a record from an AT Protocol repository.
557
+ *
558
+ * @param collection - The collection NSID
559
+ * @param rkey - The record key
560
+ * @param options - Get options including repo
561
+ *
562
+ * @see {@link get} for a higher-level typed alternative
563
+ */
289
564
  public async getRecord(
290
565
  collection: NsidString,
291
566
  rkey: string,
@@ -301,8 +576,17 @@ export class Client implements Agent {
301
576
  })
302
577
  }
303
578
 
579
+ /**
580
+ * Creates or updates a record in a repository.
581
+ *
582
+ * @param record - The record to put, must include an {@link NsidString} `$type`
583
+ * @param rkey - The record key
584
+ * @param options - Put options including repo, swapCommit, swapRecord, validate
585
+ *
586
+ * @see {@link put} for a higher-level typed alternative
587
+ */
304
588
  async putRecord(
305
- record: { $type: NsidString } & LexMap,
589
+ record: TypedLexMap<NsidString>,
306
590
  rkey: string,
307
591
  options?: PutRecordOptions,
308
592
  ) {
@@ -320,6 +604,14 @@ export class Client implements Agent {
320
604
  })
321
605
  }
322
606
 
607
+ /**
608
+ * Lists records in a collection.
609
+ *
610
+ * @param nsid - The collection NSID
611
+ * @param options - List options including repo, limit, cursor, reverse
612
+ *
613
+ * @see {@link list} for a higher-level typed alternative
614
+ */
323
615
  async listRecords(nsid: NsidString, options?: ListRecordsOptions) {
324
616
  return this.xrpc(com.atproto.repo.listRecords.main, {
325
617
  ...options,
@@ -333,6 +625,22 @@ export class Client implements Agent {
333
625
  })
334
626
  }
335
627
 
628
+ /**
629
+ * Uploads a blob to an AT Protocol repository.
630
+ *
631
+ * @param body - The blob data (Uint8Array, ReadableStream, Blob, etc.)
632
+ * @param options - Upload options including encoding hint
633
+ * @returns Response containing the blob reference
634
+ *
635
+ * @example
636
+ * ```typescript
637
+ * const imageData = await fetch('image.png').then(r => r.arrayBuffer())
638
+ * const response = await client.uploadBlob(new Uint8Array(imageData), {
639
+ * encoding: 'image/png'
640
+ * })
641
+ * console.log(response.body.blob) // Use this ref in records
642
+ * ```
643
+ */
336
644
  async uploadBlob(
337
645
  body: BinaryBodyInit,
338
646
  options?: CallOptions & { encoding?: `${string}/${string}` },
@@ -343,6 +651,13 @@ export class Client implements Agent {
343
651
  })
344
652
  }
345
653
 
654
+ /**
655
+ * Retrieves a blob by DID and CID.
656
+ *
657
+ * @param did - The DID of the repository containing the blob
658
+ * @param cid - The CID of the blob
659
+ * @param options - Call options
660
+ */
346
661
  async getBlob(did: DidString, cid: CidString, options?: CallOptions) {
347
662
  return this.xrpc(com.atproto.sync.getBlob.main, {
348
663
  ...options,
@@ -350,11 +665,42 @@ export class Client implements Agent {
350
665
  })
351
666
  }
352
667
 
668
+ /**
669
+ * Universal call method for queries, procedures, and custom actions.
670
+ * Automatically determines the call type based on the lexicon definition.
671
+ *
672
+ * @param ns - The lexicon method or action definition
673
+ * @param arg - The input argument (params for queries, body for procedures, input for actions)
674
+ * @param options - Call options
675
+ * @returns The method response body or action output
676
+ * @see {@link xrpc} if you need access to the full response object
677
+ *
678
+ * @example Query
679
+ * ```typescript
680
+ * const profile = await client.call(app.bsky.actor.getProfile.main, {
681
+ * actor: 'alice.bsky.social'
682
+ * })
683
+ * ```
684
+ *
685
+ * @example Procedure
686
+ * ```typescript
687
+ * const result = await client.call(com.atproto.repo.createRecord.main, {
688
+ * repo: did,
689
+ * collection: 'app.bsky.feed.post',
690
+ * record: { text: 'Hello!' }
691
+ * })
692
+ * ```
693
+ */
353
694
  public async call<const T extends Query>(
354
695
  ns: NonNullable<unknown> extends XrpcRequestParams<T>
355
696
  ? Main<T>
356
697
  : Restricted<'This query type requires a "params" argument'>,
357
698
  ): Promise<XrpcResponseBody<T>>
699
+ public async call<const T extends Procedure>(
700
+ ns: undefined extends InferMethodInputBody<T, Uint8Array>
701
+ ? Main<T>
702
+ : Restricted<'This procedure type requires an "input" argument'>,
703
+ ): Promise<XrpcResponseBody<T>>
358
704
  public async call<const T extends Action>(
359
705
  ns: void extends InferActionInput<T>
360
706
  ? Main<T>
@@ -401,6 +747,30 @@ export class Client implements Agent {
401
747
  }
402
748
  }
403
749
 
750
+ /**
751
+ * Creates a new record with full type safety based on the schema.
752
+ *
753
+ * @param ns - The record schema definition
754
+ * @param input - The record data (without `$type`, which is added automatically)
755
+ * @param options - Create options including rkey (required for some record types)
756
+ * @returns The create output including URI and CID
757
+ *
758
+ * @example Creating a post
759
+ * ```typescript
760
+ * const result = await client.create(app.bsky.feed.post.main, {
761
+ * text: 'Hello, world!',
762
+ * createdAt: new Date().toISOString()
763
+ * })
764
+ * console.log(result.uri)
765
+ * ```
766
+ *
767
+ * @example Creating a record with explicit rkey
768
+ * ```typescript
769
+ * const result = await client.create(app.bsky.actor.profile.main, {
770
+ * displayName: 'Alice'
771
+ * }, { rkey: 'self' })
772
+ * ```
773
+ */
404
774
  public async create<const T extends RecordSchema>(
405
775
  ns: NonNullable<unknown> extends CreateOptions<T>
406
776
  ? Main<T>
@@ -418,13 +788,20 @@ export class Client implements Agent {
418
788
  options: CreateOptions<T> = {} as CreateOptions<T>,
419
789
  ): Promise<CreateOutput> {
420
790
  const schema: T = getMain(ns)
421
- const record = schema.build(input) as { $type: NsidString } & LexMap
791
+ const record = schema.build(input) as TypedLexMap<NsidString>
422
792
  const rkey = options.rkey ?? getDefaultRecordKey(schema)
423
793
  if (rkey !== undefined) schema.keySchema.assert(rkey)
424
794
  const response = await this.createRecord(record, rkey, options)
425
795
  return response.body
426
796
  }
427
797
 
798
+ /**
799
+ * Deletes a record with type-safe options.
800
+ *
801
+ * @param ns - The record schema definition
802
+ * @param options - Delete options (rkey required for non-literal keys)
803
+ * @returns The delete output
804
+ */
428
805
  public async delete<const T extends RecordSchema>(
429
806
  ns: NonNullable<unknown> extends DeleteOptions<T>
430
807
  ? Main<T>
@@ -446,6 +823,20 @@ export class Client implements Agent {
446
823
  return response.body
447
824
  }
448
825
 
826
+ /**
827
+ * Retrieves a record with type-safe validation.
828
+ *
829
+ * @param ns - The record schema definition
830
+ * @param options - Get options (rkey required for non-literal keys)
831
+ * @returns The record data validated against the schema
832
+ *
833
+ * @example
834
+ * ```typescript
835
+ * const profile = await client.get(app.bsky.actor.profile.main)
836
+ * // profile.value is typed as app.bsky.actor.profile.Record
837
+ * console.log(profile.value.displayName)
838
+ * ```
839
+ */
449
840
  public async get<const T extends RecordSchema>(
450
841
  ns: T['key'] extends `literal:${string}`
451
842
  ? Main<T>
@@ -468,6 +859,14 @@ export class Client implements Agent {
468
859
  return { ...response.body, value }
469
860
  }
470
861
 
862
+ /**
863
+ * Creates or updates a record with full type safety.
864
+ *
865
+ * @param ns - The record schema definition
866
+ * @param input - The record data
867
+ * @param options - Put options (rkey required for non-literal keys)
868
+ * @returns The put output including URI and CID
869
+ */
471
870
  public async put<const T extends RecordSchema>(
472
871
  ns: NonNullable<unknown> extends PutOptions<T>
473
872
  ? Main<T>
@@ -485,12 +884,26 @@ export class Client implements Agent {
485
884
  options: PutOptions<T> = {} as PutOptions<T>,
486
885
  ): Promise<PutOutput> {
487
886
  const schema: T = getMain(ns)
488
- const record = schema.build(input) as { $type: NsidString } & LexMap
887
+ const record = schema.build(input) as TypedLexMap<NsidString>
489
888
  const rkey = options.rkey ?? getLiteralRecordKey(schema)
490
889
  const response = await this.putRecord(record, rkey, options)
491
890
  return response.body
492
891
  }
493
892
 
893
+ /**
894
+ * Lists records with type-safe validation and separation of valid/invalid records.
895
+ *
896
+ * @param ns - The record schema definition
897
+ * @param options - List options
898
+ * @returns Records split into valid (matching schema) and invalid arrays
899
+ *
900
+ * @example
901
+ * ```typescript
902
+ * const result = await client.list(app.bsky.feed.post.main, { limit: 100 })
903
+ * console.log(`Found ${result.records.length} valid posts`)
904
+ * console.log(`Found ${result.invalid.length} invalid records`)
905
+ * ```
906
+ */
494
907
  async list<const T extends RecordSchema>(
495
908
  ns: Main<T>,
496
909
  options?: ListOptions,