@atproto/xrpc 0.6.0-rc.0 → 0.6.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.
package/src/util.ts CHANGED
@@ -193,21 +193,21 @@ export function combineHeaders(
193
193
 
194
194
  let headers: Headers | undefined = undefined
195
195
 
196
- for (const [key, getter] of defaultHeaders) {
196
+ for (const [name, definition] of defaultHeaders) {
197
197
  // Ignore undefined values (allowed for convenience when using
198
198
  // Object.entries).
199
- if (getter === undefined) continue
199
+ if (definition === undefined) continue
200
200
 
201
201
  // Lazy initialization of the headers object
202
202
  headers ??= new Headers(headersInit)
203
203
 
204
- if (headers.has(key)) continue
204
+ if (headers.has(name)) continue
205
205
 
206
- const value = typeof getter === 'function' ? getter() : getter
206
+ const value = typeof definition === 'function' ? definition() : definition
207
207
 
208
- if (typeof value === 'string') headers.set(key, value)
209
- else if (value === null) headers.delete(key)
210
- else throw new TypeError(`Invalid "${key}" header value: ${typeof value}`)
208
+ if (typeof value === 'string') headers.set(name, value)
209
+ else if (value === null) headers.delete(name)
210
+ else throw new TypeError(`Invalid "${name}" header value: ${typeof value}`)
211
211
  }
212
212
 
213
213
  return headers ?? headersInit
@@ -343,85 +343,16 @@ function iterableToReadableStream(
343
343
  return ReadableStream.from(iterable)
344
344
  }
345
345
 
346
- // Note, in environments where ReadableStream is not available either, we
347
- // *could* load the iterable into memory and create an Arraybuffer from it.
348
- // However, this would be a bad idea for large iterables. In order to keep
349
- // things simple, we'll just allow the anonymous ReadableStream constructor
350
- // to throw an error in those environments, hinting the user of the lib to find
351
- // an alternate solution in that case (e.g. use a Blob if available).
352
-
353
- let generator: AsyncGenerator<unknown, void, undefined>
354
- return new ReadableStream<Uint8Array>({
355
- type: 'bytes',
356
- start() {
357
- // Wrap the iterable in an async generator to handle both sync and async
358
- // iterables, and make sure that the return() method exists.
359
- generator = (async function* () {
360
- yield* iterable
361
- })()
362
- },
363
- async pull(controller: ReadableStreamDefaultController) {
364
- const { done, value } = await generator.next()
365
- if (done) {
366
- controller.close()
367
- } else {
368
- try {
369
- const buf = toUint8Array(value)
370
- if (buf) controller.enqueue(buf)
371
- } catch (cause) {
372
- // ReadableStream won't call cancel() if the stream is errored.
373
- await generator.return()
374
-
375
- controller.error(
376
- new TypeError(
377
- 'Converting iterable body to ReadableStream requires Buffer, ArrayBuffer or string values',
378
- { cause },
379
- ),
380
- )
381
- }
382
- }
383
- },
384
- async cancel() {
385
- await generator.return()
386
- },
387
- })
388
- }
389
-
390
- // Browsers don't have Buffer. This syntax is to avoid bundlers from including
391
- // a Buffer polyfill in the bundle if it's not used elsewhere.
392
- const globalName = `${{ toString: () => 'Buf' }}fer` as 'Buffer'
393
- const Buffer =
394
- typeof globalThis[globalName] === 'function'
395
- ? globalThis[globalName]
396
- : undefined
397
-
398
- const toUint8Array: (value: unknown) => Uint8Array | undefined = Buffer
399
- ? (value) => {
400
- // @ts-expect-error Buffer.from will throw if value is not a valid input
401
- const buf = Buffer.isBuffer(value) ? value : Buffer.from(value)
402
- return buf.byteLength ? new Uint8Array(buf) : undefined
403
- }
404
- : (value) => {
405
- if (value instanceof ArrayBuffer) {
406
- const buf = new Uint8Array(value)
407
- return buf.byteLength ? buf : undefined
408
- }
346
+ // If you see this error, consider using a polyfill for ReadableStream. For
347
+ // example, the "web-streams-polyfill" package:
348
+ // https://github.com/MattiasBuelens/web-streams-polyfill
409
349
 
410
- // Simulate Buffer.from() behavior for strings and and coercion
411
- if (typeof value === 'string') {
412
- return value.length ? new TextEncoder().encode(value) : undefined
413
- } else if (typeof value?.valueOf === 'function') {
414
- const coerced = value.valueOf()
415
- if (coerced instanceof ArrayBuffer) {
416
- const buf = new Uint8Array(coerced)
417
- return buf.byteLength ? buf : undefined
418
- } else if (typeof coerced === 'string') {
419
- return coerced.length ? new TextEncoder().encode(coerced) : undefined
420
- }
421
- }
422
-
423
- throw new TypeError(`Unable to convert "${typeof value}" to Uint8Array`)
424
- }
350
+ throw new TypeError(
351
+ 'ReadableStream.from() is not supported in this environment. ' +
352
+ 'It is required to support using iterables as the request body. ' +
353
+ 'Consider using a polyfill or re-write your code to use a different body type.',
354
+ )
355
+ }
425
356
 
426
357
  export function httpResponseBodyParse(
427
358
  mimeType: string | null,
@@ -1,11 +1,13 @@
1
1
  import { LexiconDoc, Lexicons, ValidationError } from '@atproto/lexicon'
2
2
  import {
3
3
  FetchHandler,
4
+ FetchHandlerObject,
4
5
  FetchHandlerOptions,
5
6
  buildFetchHandler,
6
7
  } from './fetch-handler'
7
8
  import {
8
9
  CallOptions,
10
+ Gettable,
9
11
  QueryParams,
10
12
  ResponseType,
11
13
  XRPCError,
@@ -14,6 +16,7 @@ import {
14
16
  httpResponseCodeToEnum,
15
17
  } from './types'
16
18
  import {
19
+ combineHeaders,
17
20
  constructMethodCallHeaders,
18
21
  constructMethodCallUrl,
19
22
  encodeMethodCallBody,
@@ -24,20 +27,32 @@ import {
24
27
 
25
28
  export class XrpcClient {
26
29
  readonly fetchHandler: FetchHandler
30
+ readonly headers = new Map<string, Gettable<null | string>>()
27
31
  readonly lex: Lexicons
28
32
 
29
33
  constructor(
30
- fetchHandler: FetchHandler | FetchHandlerOptions,
34
+ fetchHandlerOpts: FetchHandler | FetchHandlerObject | FetchHandlerOptions,
35
+ // "Lexicons" is redundant here (because that class implements
36
+ // "Iterable<LexiconDoc>") but we keep it for explicitness:
31
37
  lex: Lexicons | Iterable<LexiconDoc>,
32
38
  ) {
33
- this.fetchHandler =
34
- typeof fetchHandler === 'function'
35
- ? fetchHandler
36
- : buildFetchHandler(fetchHandler)
39
+ this.fetchHandler = buildFetchHandler(fetchHandlerOpts)
37
40
 
38
41
  this.lex = lex instanceof Lexicons ? lex : new Lexicons(lex)
39
42
  }
40
43
 
44
+ setHeader(key: string, value: Gettable<null | string>): void {
45
+ this.headers.set(key.toLowerCase(), value)
46
+ }
47
+
48
+ unsetHeader(key: string): void {
49
+ this.headers.delete(key.toLowerCase())
50
+ }
51
+
52
+ clearHeaders(): void {
53
+ this.headers.clear()
54
+ }
55
+
41
56
  async call(
42
57
  methodNsid: string,
43
58
  params?: QueryParams,
@@ -51,7 +66,7 @@ export class XrpcClient {
51
66
  )
52
67
  }
53
68
 
54
- //@TODO: should we validate the params and data here?
69
+ // @TODO: should we validate the params and data here?
55
70
  // this.lex.assertValidXrpcParams(methodNsid, params)
56
71
  // if (data !== undefined) {
57
72
  // this.lex.assertValidXrpcInput(methodNsid, data)
@@ -66,7 +81,7 @@ export class XrpcClient {
66
81
  // anywhere in docs or types. See whatwg/fetch#1438, nodejs/node#46221.
67
82
  const init: RequestInit & { duplex: 'half' } = {
68
83
  method: reqMethod,
69
- headers: reqHeaders,
84
+ headers: combineHeaders(reqHeaders, this.headers),
70
85
  body: reqBody,
71
86
  duplex: 'half',
72
87
  signal: opts?.signal,