@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/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,
@@ -15,7 +15,6 @@ import {
15
15
  Query,
16
16
  RecordSchema,
17
17
  Restricted,
18
- UnknownObject,
19
18
  getMain,
20
19
  } from '@atproto/lex-schema'
21
20
  import { Agent, AgentOptions, buildAgent } from './agent.js'
@@ -30,63 +29,148 @@ export type {
30
29
  AtIdentifierString,
31
30
  CidString,
32
31
  DidString,
32
+ Infer,
33
33
  InferMethodInputBody,
34
34
  InferMethodOutputBody,
35
35
  InferRecordKey,
36
36
  LexMap,
37
37
  LexValue,
38
38
  LexiconRecordKey,
39
+ Main,
39
40
  NsidString,
40
41
  Params,
41
42
  Procedure,
42
43
  Query,
43
44
  RecordSchema,
44
45
  Restricted,
46
+ TypedLexMap,
45
47
  }
46
48
 
49
+ /**
50
+ * Configuration options for creating a {@link Client}.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const options: ClientOptions = {
55
+ * labelers: ['did:plc:labeler1'],
56
+ * service: 'did:web:api.bsky.app#bsky_appview',
57
+ * headers: { 'X-Custom-Header': 'value' }
58
+ * }
59
+ * ```
60
+ */
47
61
  export type ClientOptions = {
62
+ /** Labeler DIDs to include in requests for content moderation. */
48
63
  labelers?: Iterable<DidString>
64
+ /** Custom headers to include in all requests made by this client. */
49
65
  headers?: HeadersInit
66
+ /** Service proxy identifier for routing requests through a specific service. */
50
67
  service?: Service
51
68
  }
52
69
 
70
+ /**
71
+ * A composable action that can be invoked via {@link Client.call}.
72
+ *
73
+ * Actions provide a way to define custom operations that integrate with the
74
+ * Client's call interface, enabling type-safe, reusable business logic.
75
+ *
76
+ * @typeParam I - The input type for the action
77
+ * @typeParam O - The output type for the action
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const myAction: Action<{ userId: string }, { profile: Profile }> = async (client, input, options) => {
82
+ * const response = await client.xrpc(someMethod, { params: { actor: input.userId }, ...options })
83
+ * return { profile: response.body }
84
+ * }
85
+ * ```
86
+ */
53
87
  export type Action<I = any, O = any> = (
54
88
  client: Client,
55
89
  input: I,
56
90
  options: CallOptions,
57
91
  ) => O | Promise<O>
92
+
93
+ /**
94
+ * Extracts the input type from an {@link Action}.
95
+ * @typeParam A - The Action type to extract from
96
+ */
58
97
  export type InferActionInput<A extends Action> =
59
98
  A extends Action<infer I, any> ? I : never
99
+
100
+ /**
101
+ * Extracts the output type from an {@link Action}.
102
+ * @typeParam A - The Action type to extract from
103
+ */
60
104
  export type InferActionOutput<A extends Action> =
61
105
  A extends Action<any, infer O> ? O : never
62
106
 
107
+ /**
108
+ * Options for creating a record in an AT Protocol repository.
109
+ *
110
+ * @see {@link Client.createRecord}
111
+ */
63
112
  export type CreateRecordOptions = CallOptions & {
113
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
64
114
  repo?: AtIdentifierString
115
+ /** Compare-and-swap on the repo commit. If specified, must match current commit. */
65
116
  swapCommit?: string
117
+ /** Whether to validate the record against its lexicon schema. */
66
118
  validate?: boolean
67
119
  }
68
120
 
121
+ /**
122
+ * Options for deleting a record from an AT Protocol repository.
123
+ *
124
+ * @see {@link Client.deleteRecord}
125
+ */
69
126
  export type DeleteRecordOptions = CallOptions & {
127
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
70
128
  repo?: AtIdentifierString
129
+ /** Compare-and-swap on the repo commit. If specified, must match current commit. */
71
130
  swapCommit?: string
131
+ /** Compare-and-swap on the record CID. If specified, must match current record. */
72
132
  swapRecord?: string
73
133
  }
74
134
 
135
+ /**
136
+ * Options for retrieving a record from an AT Protocol repository.
137
+ *
138
+ * @see {@link Client.getRecord}
139
+ */
75
140
  export type GetRecordOptions = CallOptions & {
141
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
76
142
  repo?: AtIdentifierString
77
143
  }
78
144
 
145
+ /**
146
+ * Options for creating or updating a record in an AT Protocol repository.
147
+ *
148
+ * @see {@link Client.putRecord}
149
+ */
79
150
  export type PutRecordOptions = CallOptions & {
151
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
80
152
  repo?: AtIdentifierString
153
+ /** Compare-and-swap on the repo commit. If specified, must match current commit. */
81
154
  swapCommit?: string
155
+ /** Compare-and-swap on the record CID. If specified, must match current record. */
82
156
  swapRecord?: string
157
+ /** Whether to validate the record against its lexicon schema. */
83
158
  validate?: boolean
84
159
  }
85
160
 
161
+ /**
162
+ * Options for listing records in an AT Protocol repository collection.
163
+ *
164
+ * @see {@link Client.listRecords}
165
+ */
86
166
  export type ListRecordsOptions = CallOptions & {
167
+ /** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
87
168
  repo?: AtIdentifierString
169
+ /** Maximum number of records to return. */
88
170
  limit?: number
171
+ /** Pagination cursor from a previous response. */
89
172
  cursor?: string
173
+ /** If true, returns records in reverse chronological order. */
90
174
  reverse?: boolean
91
175
  }
92
176
 
@@ -97,48 +181,117 @@ export type RecordKeyOptions<
97
181
  ? { rkey?: InferRecordKey<T> }
98
182
  : { rkey: InferRecordKey<T> }
99
183
 
184
+ /**
185
+ * Type-safe options for {@link Client.create}, combining record options with key requirements.
186
+ * @typeParam T - The record schema type
187
+ */
100
188
  export type CreateOptions<T extends RecordSchema> = CreateRecordOptions &
101
189
  RecordKeyOptions<T, 'tid' | 'any'>
190
+
191
+ /**
192
+ * Output type for record creation operations.
193
+ * Contains the URI and CID of the newly created record.
194
+ */
102
195
  export type CreateOutput = InferMethodOutputBody<
103
196
  typeof com.atproto.repo.createRecord.main,
104
197
  Uint8Array
105
198
  >
106
199
 
200
+ /**
201
+ * Type-safe options for {@link Client.delete}, combining delete options with key requirements.
202
+ * @typeParam T - The record schema type
203
+ */
107
204
  export type DeleteOptions<T extends RecordSchema> = DeleteRecordOptions &
108
205
  RecordKeyOptions<T>
206
+
207
+ /**
208
+ * Output type for record deletion operations.
209
+ */
109
210
  export type DeleteOutput = InferMethodOutputBody<
110
211
  typeof com.atproto.repo.deleteRecord.main,
111
212
  Uint8Array
112
213
  >
214
+
215
+ /**
216
+ * Type-safe options for {@link Client.get}, combining get options with key requirements.
217
+ * @typeParam T - The record schema type
218
+ */
113
219
  export type GetOptions<T extends RecordSchema> = GetRecordOptions &
114
220
  RecordKeyOptions<T>
221
+
222
+ /**
223
+ * Output type for record retrieval operations.
224
+ * Contains the record value validated against the schema type.
225
+ * @typeParam T - The record schema type
226
+ */
115
227
  export type GetOutput<T extends RecordSchema> = Omit<
116
228
  InferMethodOutputBody<typeof com.atproto.repo.getRecord.main, Uint8Array>,
117
229
  'value'
118
230
  > & { value: Infer<T> }
119
231
 
232
+ /**
233
+ * Type-safe options for {@link Client.put}, combining put options with key requirements.
234
+ * @typeParam T - The record schema type
235
+ */
120
236
  export type PutOptions<T extends RecordSchema> = PutRecordOptions &
121
237
  RecordKeyOptions<T>
238
+
239
+ /**
240
+ * Output type for record put (create/update) operations.
241
+ * Contains the URI and CID of the record.
242
+ */
122
243
  export type PutOutput = InferMethodOutputBody<
123
244
  typeof com.atproto.repo.putRecord.main,
124
245
  Uint8Array
125
246
  >
126
247
 
248
+ /**
249
+ * Options for {@link Client.list} operations.
250
+ */
127
251
  export type ListOptions = ListRecordsOptions
252
+
253
+ /**
254
+ * Output type for record listing operations.
255
+ * Contains validated records and any invalid records that failed schema validation.
256
+ * @typeParam T - The record schema type
257
+ */
128
258
  export type ListOutput<T extends RecordSchema> = InferMethodOutputBody<
129
259
  typeof com.atproto.repo.listRecords.main,
130
260
  Uint8Array
131
261
  > & {
262
+ /** Records that successfully validated against the schema. */
132
263
  records: ListRecord<Infer<T>>[]
133
264
  // @NOTE Because the schema uses "type": "unknown" instead of an open union,
134
- // we have to use UnknownObject instead of Unknown$TypedObject here.
135
- invalid: UnknownObject[]
265
+ // we have to use LexMap instead of Unknown$TypedObject here, which is
266
+ // unfortunate.
267
+ /** Records that failed schema validation. */
268
+ invalid: LexMap[]
136
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
+ */
137
275
  export type ListRecord<Value extends LexMap> =
138
276
  com.atproto.repo.listRecords.Record & {
139
277
  value: Value
140
278
  }
141
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
+ */
142
295
  export class Client implements Agent {
143
296
  static appLabelers: readonly DidString[] = []
144
297
 
@@ -149,9 +302,16 @@ export class Client implements Agent {
149
302
  if (opts.appLabelers) this.appLabelers = [...opts.appLabelers]
150
303
  }
151
304
 
305
+ /** The underlying agent used for making requests. */
152
306
  public readonly agent: Agent
307
+
308
+ /** Custom headers included in all requests. */
153
309
  public readonly headers: Headers
310
+
311
+ /** Optional service identifier for routing requests. */
154
312
  public readonly service?: Service
313
+
314
+ /** Set of labeler DIDs specific to this client instance. */
155
315
  public readonly labelers: Set<DidString>
156
316
 
157
317
  constructor(agent: Agent | AgentOptions, options: ClientOptions = {}) {
@@ -161,32 +321,68 @@ export class Client implements Agent {
161
321
  this.headers = new Headers(options.headers)
162
322
  }
163
323
 
324
+ /**
325
+ * The DID of the authenticated user, or `undefined` if not authenticated.
326
+ */
164
327
  get did(): DidString | undefined {
165
328
  return this.agent.did
166
329
  }
167
330
 
331
+ /**
332
+ * The DID of the authenticated user.
333
+ * @throws {LexError} with code 'AuthenticationRequired' if not authenticated
334
+ */
168
335
  get assertDid(): DidString {
169
336
  this.assertAuthenticated()
170
337
  return this.did
171
338
  }
172
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
+ */
173
353
  public assertAuthenticated(): asserts this is { did: DidString } {
174
354
  if (!this.did) throw new LexError('AuthenticationRequired')
175
355
  }
176
356
 
357
+ /**
358
+ * Replaces all labelers with the given set.
359
+ * @param labelers - Iterable of labeler DIDs
360
+ */
177
361
  public setLabelers(labelers: Iterable<DidString> = []) {
178
362
  this.clearLabelers()
179
363
  this.addLabelers(labelers)
180
364
  }
181
365
 
366
+ /**
367
+ * Adds labelers to the current set.
368
+ * @param labelers - Iterable of labeler DIDs to add
369
+ */
182
370
  public addLabelers(labelers: Iterable<DidString>) {
183
371
  for (const labeler of labelers) this.labelers.add(labeler)
184
372
  }
185
373
 
374
+ /**
375
+ * Removes all labelers from this client instance.
376
+ */
186
377
  public clearLabelers() {
187
378
  this.labelers.clear()
188
379
  }
189
380
 
381
+ /**
382
+ * Low-level fetch handler for making requests.
383
+ * @param path - The request path
384
+ * @param init - Request initialization options
385
+ */
190
386
  public fetchHandler(path: string, init: RequestInit): Promise<Response> {
191
387
  const headers = buildAtprotoHeaders({
192
388
  headers: init.headers,
@@ -209,7 +405,33 @@ export class Client implements Agent {
209
405
  }
210
406
 
211
407
  /**
212
- * @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
213
435
  */
214
436
  async xrpc<const M extends Query | Procedure>(
215
437
  ns: NonNullable<unknown> extends XrpcOptions<M>
@@ -227,6 +449,29 @@ export class Client implements Agent {
227
449
  return xrpc(this, ns, options)
228
450
  }
229
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
+ */
230
475
  async xrpcSafe<const M extends Query | Procedure>(
231
476
  ns: NonNullable<unknown> extends XrpcOptions<M>
232
477
  ? Main<M>
@@ -244,10 +489,27 @@ export class Client implements Agent {
244
489
  }
245
490
 
246
491
  /**
247
- * @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
248
510
  */
249
511
  public async createRecord(
250
- record: { $type: NsidString } & LexMap,
512
+ record: TypedLexMap<NsidString>,
251
513
  rkey?: string,
252
514
  options?: CreateRecordOptions,
253
515
  ) {
@@ -264,6 +526,15 @@ export class Client implements Agent {
264
526
  })
265
527
  }
266
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
+ */
267
538
  async deleteRecord(
268
539
  collection: NsidString,
269
540
  rkey: string,
@@ -281,6 +552,15 @@ export class Client implements Agent {
281
552
  })
282
553
  }
283
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
+ */
284
564
  public async getRecord(
285
565
  collection: NsidString,
286
566
  rkey: string,
@@ -296,8 +576,17 @@ export class Client implements Agent {
296
576
  })
297
577
  }
298
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
+ */
299
588
  async putRecord(
300
- record: { $type: NsidString } & LexMap,
589
+ record: TypedLexMap<NsidString>,
301
590
  rkey: string,
302
591
  options?: PutRecordOptions,
303
592
  ) {
@@ -315,6 +604,14 @@ export class Client implements Agent {
315
604
  })
316
605
  }
317
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
+ */
318
615
  async listRecords(nsid: NsidString, options?: ListRecordsOptions) {
319
616
  return this.xrpc(com.atproto.repo.listRecords.main, {
320
617
  ...options,
@@ -328,6 +625,22 @@ export class Client implements Agent {
328
625
  })
329
626
  }
330
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
+ */
331
644
  async uploadBlob(
332
645
  body: BinaryBodyInit,
333
646
  options?: CallOptions & { encoding?: `${string}/${string}` },
@@ -338,6 +651,13 @@ export class Client implements Agent {
338
651
  })
339
652
  }
340
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
+ */
341
661
  async getBlob(did: DidString, cid: CidString, options?: CallOptions) {
342
662
  return this.xrpc(com.atproto.sync.getBlob.main, {
343
663
  ...options,
@@ -345,6 +665,32 @@ export class Client implements Agent {
345
665
  })
346
666
  }
347
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
+ */
348
694
  public async call<const T extends Query>(
349
695
  ns: NonNullable<unknown> extends XrpcRequestParams<T>
350
696
  ? 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,
@@ -528,7 +941,7 @@ function getLiteralRecordKey<const T extends RecordSchema>(
528
941
  schema: T,
529
942
  ): InferRecordKey<T> {
530
943
  if (schema.key.startsWith('literal:')) {
531
- return schema.key.slice(8)
944
+ return schema.key.slice(8) as InferRecordKey<T>
532
945
  }
533
946
 
534
947
  throw new TypeError(