@atproto/lex-client 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/agent.d.ts +1 -1
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js.map +1 -1
  5. package/dist/client.d.ts +11 -12
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +17 -63
  8. package/dist/client.js.map +1 -1
  9. package/dist/errors.d.ts +6 -4
  10. package/dist/errors.d.ts.map +1 -1
  11. package/dist/errors.js +3 -3
  12. package/dist/errors.js.map +1 -1
  13. package/dist/response.d.ts +7 -7
  14. package/dist/response.d.ts.map +1 -1
  15. package/dist/response.js +2 -1
  16. package/dist/response.js.map +1 -1
  17. package/dist/types.d.ts +1 -1
  18. package/dist/types.d.ts.map +1 -1
  19. package/dist/types.js.map +1 -1
  20. package/dist/util.d.ts +3 -2
  21. package/dist/util.d.ts.map +1 -1
  22. package/dist/util.js +1 -0
  23. package/dist/util.js.map +1 -1
  24. package/dist/write-operation-builder.d.ts +3 -2
  25. package/dist/write-operation-builder.d.ts.map +1 -1
  26. package/dist/write-operation-builder.js +2 -2
  27. package/dist/write-operation-builder.js.map +1 -1
  28. package/dist/xrpc.d.ts +50 -8
  29. package/dist/xrpc.d.ts.map +1 -1
  30. package/dist/xrpc.js +62 -16
  31. package/dist/xrpc.js.map +1 -1
  32. package/package.json +11 -14
  33. package/src/agent.test.ts +0 -216
  34. package/src/agent.ts +0 -186
  35. package/src/client.ts +0 -1179
  36. package/src/errors.test.ts +0 -626
  37. package/src/errors.ts +0 -570
  38. package/src/index.ts +0 -6
  39. package/src/lexicons/com/atproto/repo/applyWrites.defs.ts +0 -201
  40. package/src/lexicons/com/atproto/repo/applyWrites.ts +0 -6
  41. package/src/lexicons/com/atproto/repo/createRecord.defs.ts +0 -58
  42. package/src/lexicons/com/atproto/repo/createRecord.ts +0 -6
  43. package/src/lexicons/com/atproto/repo/defs.defs.ts +0 -28
  44. package/src/lexicons/com/atproto/repo/defs.ts +0 -5
  45. package/src/lexicons/com/atproto/repo/deleteRecord.defs.ts +0 -52
  46. package/src/lexicons/com/atproto/repo/deleteRecord.ts +0 -6
  47. package/src/lexicons/com/atproto/repo/getRecord.defs.ts +0 -37
  48. package/src/lexicons/com/atproto/repo/getRecord.ts +0 -6
  49. package/src/lexicons/com/atproto/repo/listRecords.defs.ts +0 -65
  50. package/src/lexicons/com/atproto/repo/listRecords.ts +0 -6
  51. package/src/lexicons/com/atproto/repo/putRecord.defs.ts +0 -59
  52. package/src/lexicons/com/atproto/repo/putRecord.ts +0 -6
  53. package/src/lexicons/com/atproto/repo/uploadBlob.defs.ts +0 -35
  54. package/src/lexicons/com/atproto/repo/uploadBlob.ts +0 -6
  55. package/src/lexicons/com/atproto/repo.ts +0 -12
  56. package/src/lexicons/com/atproto/sync/getBlob.defs.ts +0 -37
  57. package/src/lexicons/com/atproto/sync/getBlob.ts +0 -6
  58. package/src/lexicons/com/atproto/sync.ts +0 -5
  59. package/src/lexicons/com/atproto.ts +0 -6
  60. package/src/lexicons/com.ts +0 -5
  61. package/src/lexicons/index.ts +0 -5
  62. package/src/response.bench.ts +0 -113
  63. package/src/response.ts +0 -366
  64. package/src/types.ts +0 -71
  65. package/src/util.test.ts +0 -333
  66. package/src/util.ts +0 -215
  67. package/src/write-operation-builder.ts +0 -110
  68. package/src/www-authenticate.test.ts +0 -227
  69. package/src/www-authenticate.ts +0 -101
  70. package/src/xrpc.test.ts +0 -1450
  71. package/src/xrpc.ts +0 -446
  72. package/tsconfig.build.json +0 -12
  73. package/tsconfig.json +0 -7
  74. package/tsconfig.tests.json +0 -8
package/dist/xrpc.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { isLexScalar, isPlainObject } from '@atproto/lex-data';
2
2
  import { lexStringify } from '@atproto/lex-json';
3
- import { getMain, } from '@atproto/lex-schema';
3
+ import { Payload, Procedure, Query, Subscription, getMain, } from '@atproto/lex-schema';
4
4
  import { buildAgent } from './agent.js';
5
- import { XrpcFetchError, asXrpcFailure } from './errors.js';
5
+ import { XrpcFetchError, XrpcResponseError, asXrpcFailure } from './errors.js';
6
6
  import { XrpcResponse } from './response.js';
7
- import { asUint8ArrayArrayBuffer, buildXrpcRequestHeaders, isAsyncIterable, isBlobLike, toReadableStream, } from './util.js';
7
+ import { asUint8ArrayArrayBuffer, buildXrpcRequestHeaders, isAsyncIterable, isBlobLike, toReadableStream, wait, } from './util.js';
8
8
  export async function xrpc(agentOpts, ns, options = {}) {
9
9
  const response = await xrpcSafe(agentOpts, ns, options);
10
10
  if (response.success)
@@ -13,20 +13,32 @@ export async function xrpc(agentOpts, ns, options = {}) {
13
13
  throw response;
14
14
  }
15
15
  export async function xrpcSafe(agentOpts, ns, options = {}) {
16
- options.signal?.throwIfAborted();
17
16
  const method = getMain(ns);
18
- try {
19
- const agent = buildAgent(agentOpts);
20
- const url = xrpcRequestUrl(method, options);
21
- const request = xrpcRequestInit(method, options);
22
- const response = await agent.fetchHandler(url, request).catch((err) => {
23
- const cause = extractFetchErrorCause(err);
24
- throw new XrpcFetchError(method, cause);
25
- });
26
- return await XrpcResponse.fromFetchResponse(method, response, options);
27
- }
28
- catch (cause) {
29
- return asXrpcFailure(method, cause);
17
+ for (let counter = 1;; counter++) {
18
+ options.signal?.throwIfAborted();
19
+ try {
20
+ const agent = buildAgent(agentOpts);
21
+ const url = xrpcRequestUrl(method, options);
22
+ const request = xrpcRequestInit(method, options);
23
+ const response = await agent.fetchHandler(url, request).catch((err) => {
24
+ const cause = extractFetchErrorCause(err);
25
+ throw new XrpcFetchError(method, cause);
26
+ });
27
+ return await XrpcResponse.fromFetchResponse(method, response, options);
28
+ }
29
+ catch (cause) {
30
+ const failure = asXrpcFailure(method, cause);
31
+ // Cannot retry a request with a consumable body
32
+ if (options.body instanceof ReadableStream ||
33
+ isAsyncIterable(options.body)) {
34
+ return failure;
35
+ }
36
+ const waitTime = getRetryWaitTime(failure, options, counter);
37
+ if (waitTime == null) {
38
+ return failure;
39
+ }
40
+ await wait(waitTime, options);
41
+ }
30
42
  }
31
43
  }
32
44
  function xrpcRequestUrl(method, options) {
@@ -186,4 +198,38 @@ export function extractFetchErrorCause(err) {
186
198
  // environments like React Native, Deno, Bun, Browser, etc.)
187
199
  return err;
188
200
  }
201
+ function getRetryWaitTime(failure, options, counter) {
202
+ const { retry, maxRetries = 0, minRetryTimeout = 500, maxRetryTimeout = 30_000, retryTimeoutFactor = 2, retryHeaders = true, } = options;
203
+ if (counter > maxRetries) {
204
+ return undefined;
205
+ }
206
+ const shouldRetry = retry
207
+ ? retry(failure, { counter })
208
+ : failure.shouldRetry();
209
+ if (!shouldRetry) {
210
+ return undefined;
211
+ }
212
+ const waitTime = retryHeaders && failure instanceof XrpcResponseError
213
+ ? getWaitTimeFromHeaders(failure.headers)
214
+ : undefined;
215
+ return Math.min(maxRetryTimeout, waitTime ?? minRetryTimeout * retryTimeoutFactor ** (counter - 1));
216
+ }
217
+ function getWaitTimeFromHeaders(headers) {
218
+ const retryAfterHeader = headers.get('retry-after');
219
+ if (retryAfterHeader) {
220
+ const waitTime = /^\s*\d+\s*$/.test(retryAfterHeader)
221
+ ? // Retry-After is in seconds
222
+ Number(retryAfterHeader) * 1000
223
+ : // Retry-After is an http-date
224
+ new Date(retryAfterHeader).getTime() - Date.now();
225
+ if (waitTime > 0)
226
+ return waitTime;
227
+ }
228
+ const resetsAt = headers.get('RateLimit-Reset'); // epoch
229
+ if (resetsAt) {
230
+ const waitTime = Number(resetsAt) * 1000 - Date.now();
231
+ if (waitTime > 0)
232
+ return waitTime;
233
+ }
234
+ }
189
235
  //# sourceMappingURL=xrpc.js.map
package/dist/xrpc.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"xrpc.js","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"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","sourcesContent":["import { LexValue, isLexScalar, isPlainObject } from '@atproto/lex-data'\nimport { lexStringify } from '@atproto/lex-json'\nimport {\n InferInput,\n InferPayload,\n Main,\n NsidString,\n Params,\n Payload,\n Procedure,\n Query,\n Restricted,\n Subscription,\n getMain,\n} from '@atproto/lex-schema'\nimport { Agent, AgentOptions, buildAgent } from './agent.js'\nimport { XrpcFailure, XrpcFetchError, asXrpcFailure } from './errors.js'\nimport { XrpcResponse, XrpcResponseOptions } from './response.js'\nimport { BinaryBodyInit } from './types.js'\nimport {\n XrpcRequestHeadersOptions,\n asUint8ArrayArrayBuffer,\n buildXrpcRequestHeaders,\n isAsyncIterable,\n isBlobLike,\n toReadableStream,\n} from './util.js'\n\n/**\n * The query/path parameters type for an XRPC method, inferred from its schema.\n *\n * @typeParam M - The XRPC method type (Procedure, Query, or Subscription)\n */\nexport type XrpcRequestParams<M extends Procedure | Query | Subscription> =\n InferInput<M['parameters']>\n\n// If all params are optional, allow omitting the params object\ntype XrpcRequestParamsOptions<P extends Params> =\n NonNullable<unknown> extends P ? { params?: P } : { params: P }\n\ntype XrpcRequestPayload<M extends Procedure | Query> = M extends Procedure\n ? InferPayload<M['input'], BinaryBodyInit>\n : undefined\n\ntype XrpcRequestPayloadOptions<TPayload> = TPayload extends {\n body: infer B\n encoding: infer E\n}\n ? {\n body: B\n\n /**\n * mime type hint for binary bodies\n *\n * Only needed for endpoints that accept binary input (e.g. file uploads)\n * when the body is a Blob-like object without a type (e.g. fetch-blob's\n * Blob). If the body is a Blob-like object with a type, that type will be\n * used as the content-type header instead of this option.\n *\n * @default \"application/octet-stream\"\n */\n encoding?: E\n }\n : { body?: undefined; encoding?: undefined }\n\n/**\n * Options for making an XRPC request, based on the method schema.\n *\n * Combines {@link XrpcRequestOptions} and {@link XrpcResponseOptions} with\n * method-specific params and body requirements. The type system ensures\n * required params/body are provided based on the method schema.\n *\n * @typeParam M - The XRPC method type (Procedure or Query)\n *\n * @example Query with params\n * ```typescript\n * const options: XrpcOptions<typeof app.bsky.feed.getTimeline.main> = {\n * params: { limit: 50 }\n * }\n * ```\n *\n * @example Procedure with body\n * ```typescript\n * const options: XrpcOptions<typeof com.atproto.repo.createRecord.main> = {\n * body: { repo: did, collection: 'app.bsky.feed.post', record: { ... } }\n * }\n * ```\n */\nexport type XrpcOptions<M extends Procedure | Query = Procedure | Query> =\n XrpcRequestOptions<M> & XrpcResponseOptions\n\nexport type XrpcRequestOptions<\n M extends Procedure | Query = Procedure | Query,\n> = XrpcRequestProcessingOptions &\n XrpcRequestHeadersOptions &\n XrpcRequestPayloadOptions<XrpcRequestPayload<M>> &\n XrpcRequestParamsOptions<XrpcRequestParams<M>>\n\nexport type XrpcRequestProcessingOptions = {\n /**\n * AbortSignal to cancel the request.\n */\n signal?: AbortSignal\n\n /**\n * Whether to validate the request against the method's input schema. Enabling\n * this can help catch errors early but may have a performance cost. This\n * would typically only be set to `true` in development or debugging\n * scenarios.\n *\n * @default false\n */\n validateRequest?: boolean\n}\n\n/**\n * Makes an XRPC request and throws on failure.\n *\n * This is the low-level function for making XRPC calls.\n *\n * @param agent - The {@link Agent} to use for making the request\n * @param ns - The lexicon method definition\n * @param options - Request {@link XrpcOptions options} (params, body, headers, etc.)\n * @returns The successful {@link XrpcResponse}\n * @throws {XrpcFailure} When the request fails\n *\n * @example\n * ```typescript\n * const response = await xrpc('https://bsky.network', com.atproto.identity.resolveHandle, {\n * params: { handle: \"atproto.com\" }\n * })\n * ```\n *\n * @example\n * ```typescript\n * const response = await xrpc(agent, app.bsky.feed.getTimeline.main, {\n * params: { limit: 50 }\n * })\n * ```\n */\nexport async function xrpc<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: NonNullable<unknown> extends XrpcOptions<M>\n ? Main<M>\n : Restricted<'This XRPC method requires an \"options\" argument'>,\n): Promise<XrpcResponse<M>>\nexport async function xrpc<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: Main<M>,\n options: XrpcOptions<M>,\n): Promise<XrpcResponse<M>>\nexport async function xrpc<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: Main<M>,\n options: XrpcOptions<M> = {} as XrpcOptions<M>,\n): Promise<XrpcResponse<M>> {\n const response = await xrpcSafe<M>(agentOpts, ns, options)\n if (response.success) return response\n else throw response\n}\n\n/**\n * Union type representing either a successful response or a failure.\n *\n * Both {@link XrpcResponse} and {@link XrpcFailure} have a `success` property\n * that can be used to discriminate between them.\n *\n * @typeParam M - The XRPC method type\n */\nexport type XrpcResult<M extends Procedure | Query> =\n | XrpcResponse<M>\n | XrpcFailure<M>\n\n/**\n * Makes an XRPC request without throwing on failure.\n *\n * Returns a discriminated union that can be checked via the `success` property.\n * This is useful for handling errors without try/catch blocks. This also allow\n * failure results to be typed with the method schema, which can provide better\n * type safety when handling errors (e.g. checking for specific error codes).\n *\n * @param agent - The {@link Agent} to use for making the request\n * @param ns - The lexicon method definition\n * @param options - Request {@link XrpcOptions options} (params, body, headers, etc.)\n * @returns Either a successful {@link XrpcResponse} or an {@link XrpcFailure}\n *\n * @example\n * ```typescript\n * const result = await xrpcSafe('https://example.com', app.bsky.actor.getProfile, {\n * params: { actor: 'alice.bsky.social' }\n * })\n *\n * if (result.success) {\n * console.log(result.body.displayName)\n * } else {\n * console.error('Request failed:', result.error)\n * }\n * ```\n */\nexport async function xrpcSafe<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: NonNullable<unknown> extends XrpcOptions<M>\n ? Main<M>\n : Restricted<'This XRPC method requires an \"options\" argument'>,\n): Promise<XrpcResult<M>>\nexport async function xrpcSafe<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: Main<M>,\n options: XrpcOptions<M>,\n): Promise<XrpcResult<M>>\nexport async function xrpcSafe<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: Main<M>,\n options: XrpcOptions<M> = {} as XrpcOptions<M>,\n): Promise<XrpcResult<M>> {\n options.signal?.throwIfAborted()\n const method: M = getMain(ns)\n try {\n const agent = buildAgent(agentOpts)\n const url = xrpcRequestUrl(method, options)\n const request = xrpcRequestInit(method, options)\n const response = await agent.fetchHandler(url, request).catch((err) => {\n const cause = extractFetchErrorCause(err)\n throw new XrpcFetchError(method, cause)\n })\n return await XrpcResponse.fromFetchResponse<M>(method, response, options)\n } catch (cause) {\n return asXrpcFailure(method, cause)\n }\n}\n\nfunction xrpcRequestUrl<M extends Procedure | Query | Subscription>(\n method: M,\n options: { params?: Params },\n): `/xrpc/${NsidString}${'' | `?${string}`}` {\n const path = `/xrpc/${method.nsid}` as const\n\n // @NOTE param.toURLSearchParams() will always validate the params in order to\n // apply default values, so we can't disable it with options.validateRequest\n\n const queryString = method.parameters\n ?.toURLSearchParams(options.params ?? {})\n .toString()\n\n return queryString ? (`${path}?${queryString}` as const) : path\n}\n\nfunction xrpcRequestInit<T extends Procedure | Query>(\n schema: T,\n options: XrpcRequestProcessingOptions &\n XrpcRequestHeadersOptions &\n XrpcProcedureInputOptions & {\n encoding?: string\n },\n): RequestInit & { duplex?: 'half' } {\n const headers = buildXrpcRequestHeaders(options)\n\n // Tell the server what type of response we're expecting\n if (schema.output.encoding) {\n headers.set('accept', schema.output.encoding)\n }\n\n // Caller should not set content-type header\n if (headers.has('content-type')) {\n const contentType = headers.get('content-type')\n throw new TypeError(`Unexpected content-type header (${contentType})`)\n }\n\n // Requests with body\n if ('input' in schema) {\n const encodingHint = options.encoding\n const input = xrpcProcedureInput(schema, options, encodingHint)\n\n if (input) {\n headers.set('content-type', input.encoding)\n } else if (encodingHint != null) {\n throw new TypeError(`Unexpected encoding hint (${encodingHint})`)\n }\n\n return {\n duplex: 'half',\n redirect: 'follow',\n referrerPolicy: 'strict-origin-when-cross-origin', // (default)\n mode: 'cors', // (default)\n signal: options.signal,\n method: 'POST',\n headers,\n body: input?.body,\n }\n }\n\n // Requests without body\n return {\n duplex: 'half',\n redirect: 'follow',\n referrerPolicy: 'strict-origin-when-cross-origin', // (default)\n mode: 'cors', // (default)\n signal: options.signal,\n method: 'GET',\n headers,\n }\n}\n\ntype XrpcProcedureInputOptions = {\n body?: LexValue | BinaryBodyInit\n validateRequest?: boolean\n}\n\nfunction xrpcProcedureInput(\n method: Procedure,\n options: XrpcProcedureInputOptions,\n encodingHint?: string,\n): null | { body: BodyInit; encoding: string } {\n const { input } = method\n const { body } = options\n\n if (options.validateRequest) {\n input.schema?.check(body)\n }\n\n // Special handling for endpoints expecting application/json input\n if (input.encoding === 'application/json') {\n // @NOTE **NOT** using isLexValue here to avoid deep checks in order to\n // distinguish between LexValue and BinaryBodyInit.\n if (!isLexScalar(body) && !isPlainObject(body) && !Array.isArray(body)) {\n throw new TypeError(`Expected LexValue body, got ${typeof body}`)\n }\n\n return buildPayload(input, lexStringify(body), encodingHint)\n }\n\n // Other encodings will be sent unaltered (ie. as binary data)\n switch (typeof body) {\n case 'undefined':\n case 'string':\n return buildPayload(input, body, encodingHint)\n case 'object': {\n if (body === null) break\n if (ArrayBuffer.isView(body)) {\n return buildPayload(input, asUint8ArrayArrayBuffer(body), encodingHint)\n } else if (\n body instanceof ArrayBuffer ||\n body instanceof ReadableStream\n ) {\n return buildPayload(input, body, encodingHint)\n } else if (isAsyncIterable(body)) {\n // @NOTE While fetch() does not allow SharedArrayBuffer-backed\n // Uint8Arrays as \"body\", it **does** allow using ReadableStreams made\n // of Uint8Arrays<SharedArrayBuffer> (tested on NodeJS 22) as \"body\".\n return buildPayload(input, toReadableStream(body), encodingHint)\n } else if (isBlobLike(body)) {\n return buildPayload(input, body, encodingHint || body.type)\n }\n }\n }\n\n throw new TypeError(\n `Invalid ${typeof body} body for ${input.encoding} encoding`,\n )\n}\n\nfunction buildPayload(\n schema: Payload,\n body: undefined | BodyInit,\n encodingHint?: string,\n): null | { body: BodyInit; encoding: string } {\n if (schema.encoding === undefined) {\n if (body !== undefined) {\n throw new TypeError(`Endpoint expects no payload`)\n }\n\n return null\n }\n\n if (body === undefined) {\n // This error would be returned by the server, but we can catch it earlier\n // to avoid un-necessary requests. Note that a content-length of 0 does not\n // necessary mean that the body is \"empty\" (e.g. an empty txt file).\n throw new TypeError(`A request body is expected but none was provided`)\n }\n\n const encoding = buildEncoding(schema, encodingHint)\n return { encoding, body }\n}\n\nfunction buildEncoding(schema: Payload, encodingHint?: string): string {\n // Should never happen (required for type safety)\n if (!schema.encoding) {\n throw new TypeError('Unexpected payload')\n }\n\n if (encodingHint?.length) {\n if (!schema.matchesEncoding(encodingHint)) {\n throw new TypeError(\n `Cannot send a body with content-type \"${encodingHint}\" for \"${schema.encoding}\" encoding`,\n )\n }\n return encodingHint\n }\n\n // Fallback\n\n if (schema.encoding === '*/*') {\n return 'application/octet-stream'\n }\n\n if (schema.encoding.startsWith('text/')) {\n return schema.encoding.includes('*')\n ? 'text/plain; charset=utf-8'\n : `${schema.encoding}; charset=utf-8`\n }\n\n if (!schema.encoding.includes('*')) {\n return schema.encoding\n }\n\n throw new TypeError(\n `Unable to determine payload encoding. Please provide a 'content-type' header matching ${schema.encoding}.`,\n )\n}\n\n/**\n * Extracts the root cause from an error, unwrapping common fetch-related errors\n * such as those from undici (Node's internal fetch implementation).\n *\n * @param err - The error to extract the root cause from\n * @returns The root cause error, or the original error if no specific pattern is matched\n * @remarks This is useful for getting more specific error information from fetch-related failures, especially in Node environments using undici.\n */\nexport function extractFetchErrorCause(err: unknown): unknown {\n // Unwrap the Network error from undici (i.e. Node's internal fetch() implementation)\n // https://github.com/nodejs/undici/blob/04cb77327f7ada95c2e5b67424cddcb22d7bf882/lib/web/fetch/index.js#L234-L239\n if (\n err instanceof TypeError &&\n err.message === 'fetch failed' &&\n err.cause !== undefined\n ) {\n return err.cause\n }\n\n // @TODO Add other unwrap patterns here as needed (e.g. for other fetch\n // implementations or common network libraries, like \"node:http\", or in other\n // environments like React Native, Deno, Bun, Browser, etc.)\n\n return err\n}\n"]}
1
+ {"version":3,"file":"xrpc.js","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LexValue } from '@atproto/lex-data'\nimport { isLexScalar, isPlainObject } from '@atproto/lex-data'\nimport { lexStringify } from '@atproto/lex-json'\nimport type {\n InferInput,\n InferPayload,\n Main,\n NsidString,\n Params,\n Restricted,\n} from '@atproto/lex-schema'\nimport {\n Payload,\n Procedure,\n Query,\n Subscription,\n getMain,\n} from '@atproto/lex-schema'\nimport type { Agent, AgentOptions } from './agent.js'\nimport { buildAgent } from './agent.js'\nimport type { XrpcFailure } from './errors.js'\nimport { XrpcFetchError, XrpcResponseError, asXrpcFailure } from './errors.js'\nimport type { XrpcResponseOptions } from './response.js'\nimport { XrpcResponse } from './response.js'\nimport type { BinaryBodyInit } from './types.js'\nimport type { XrpcRequestHeadersOptions } from './util.js'\nimport {\n asUint8ArrayArrayBuffer,\n buildXrpcRequestHeaders,\n isAsyncIterable,\n isBlobLike,\n toReadableStream,\n wait,\n} from './util.js'\n\n/**\n * The query/path parameters type for an XRPC method, inferred from its schema.\n *\n * @typeParam M - The XRPC method type (Procedure, Query, or Subscription)\n */\nexport type XrpcRequestParams<M extends Procedure | Query | Subscription> =\n InferInput<M['parameters']>\n\n// If all params are optional, allow omitting the params object\ntype XrpcRequestParamsOptions<P extends Params> =\n NonNullable<unknown> extends P ? { params?: P } : { params: P }\n\ntype XrpcRequestPayload<M extends Procedure | Query> = M extends Procedure\n ? InferPayload<M['input'], BinaryBodyInit>\n : undefined\n\ntype XrpcRequestPayloadOptions<TPayload> = TPayload extends {\n body: infer B\n encoding: infer E\n}\n ? {\n body: B\n\n /**\n * mime type hint for binary bodies\n *\n * Only needed for endpoints that accept binary input (e.g. file uploads)\n * when the body is a Blob-like object without a type (e.g. fetch-blob's\n * Blob). If the body is a Blob-like object with a type, that type will be\n * used as the content-type header instead of this option.\n *\n * @default \"application/octet-stream\"\n */\n encoding?: E\n }\n : { body?: undefined; encoding?: undefined }\n\n/**\n * Options for making an XRPC request, based on the method schema.\n *\n * Combines {@link XrpcRequestOptions} and {@link XrpcResponseOptions} with\n * method-specific params and body requirements. The type system ensures\n * required params/body are provided based on the method schema.\n *\n * @typeParam M - The XRPC method type (Procedure or Query)\n *\n * @example Query with params\n * ```typescript\n * const options: XrpcOptions<typeof app.bsky.feed.getTimeline.main> = {\n * params: { limit: 50 }\n * }\n * ```\n *\n * @example Procedure with body\n * ```typescript\n * const options: XrpcOptions<typeof com.atproto.repo.createRecord.main> = {\n * body: { repo: did, collection: 'app.bsky.feed.post', record: { ... } }\n * }\n * ```\n */\nexport type XrpcOptions<M extends Procedure | Query = Procedure | Query> =\n XrpcRequestOptions<M> & XrpcResponseOptions & RetryOptions\n\nexport type XrpcRequestOptions<\n M extends Procedure | Query = Procedure | Query,\n> = XrpcRequestProcessingOptions &\n XrpcRequestHeadersOptions &\n XrpcRequestPayloadOptions<XrpcRequestPayload<M>> &\n XrpcRequestParamsOptions<XrpcRequestParams<M>>\n\nexport type XrpcRequestProcessingOptions = {\n /**\n * AbortSignal to cancel the request.\n */\n signal?: AbortSignal\n\n /**\n * Whether to validate the request against the method's input schema. Enabling\n * this can help catch errors early but may have a performance cost. This\n * would typically only be set to `true` in development or debugging\n * scenarios.\n *\n * @default false\n */\n validateRequest?: boolean\n}\n\n/**\n * Makes an XRPC request and throws on failure.\n *\n * This is the low-level function for making XRPC calls.\n *\n * @param agent - The {@link Agent} to use for making the request\n * @param ns - The lexicon method definition\n * @param options - Request {@link XrpcOptions options} (params, body, headers, etc.)\n * @returns The successful {@link XrpcResponse}\n * @throws {XrpcFailure} When the request fails\n *\n * @example\n * ```typescript\n * const response = await xrpc('https://bsky.network', com.atproto.identity.resolveHandle, {\n * params: { handle: \"atproto.com\" }\n * })\n * ```\n *\n * @example\n * ```typescript\n * const response = await xrpc(agent, app.bsky.feed.getTimeline.main, {\n * params: { limit: 50 }\n * })\n * ```\n */\nexport async function xrpc<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: NonNullable<unknown> extends XrpcOptions<M>\n ? Main<M>\n : Restricted<'This XRPC method requires an \"options\" argument'>,\n): Promise<XrpcResponse<M>>\nexport async function xrpc<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: Main<M>,\n options: XrpcOptions<M>,\n): Promise<XrpcResponse<M>>\nexport async function xrpc<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: Main<M>,\n options: XrpcOptions<M> = {} as XrpcOptions<M>,\n): Promise<XrpcResponse<M>> {\n const response = await xrpcSafe<M>(agentOpts, ns, options)\n if (response.success) return response\n else throw response\n}\n\n/**\n * Union type representing either a successful response or a failure.\n *\n * Both {@link XrpcResponse} and {@link XrpcFailure} have a `success` property\n * that can be used to discriminate between them.\n *\n * @typeParam M - The XRPC method type\n */\nexport type XrpcResult<M extends Procedure | Query> =\n | XrpcResponse<M>\n | XrpcFailure<M>\n\n/**\n * Makes an XRPC request without throwing on failure.\n *\n * Returns a discriminated union that can be checked via the `success` property.\n * This is useful for handling errors without try/catch blocks. This also allows\n * failure results to be typed with the method schema, which can provide better\n * type safety when handling errors (e.g. checking for specific error codes).\n *\n * @param agent - The {@link Agent} to use for making the request\n * @param ns - The lexicon method definition\n * @param options - Request {@link XrpcOptions options} (params, body, headers, etc.)\n * @returns Either a successful {@link XrpcResponse} or an {@link XrpcFailure}\n *\n * @example\n * ```typescript\n * const result = await xrpcSafe('https://example.com', app.bsky.actor.getProfile, {\n * params: { actor: 'alice.bsky.social' }\n * })\n *\n * if (result.success) {\n * console.log(result.body.displayName)\n * } else {\n * console.error('Request failed:', result.error)\n * }\n * ```\n */\nexport async function xrpcSafe<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: NonNullable<unknown> extends XrpcOptions<M>\n ? Main<M>\n : Restricted<'This XRPC method requires an \"options\" argument'>,\n): Promise<XrpcResult<M>>\nexport async function xrpcSafe<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: Main<M>,\n options: XrpcOptions<M>,\n): Promise<XrpcResult<M>>\nexport async function xrpcSafe<const M extends Query | Procedure>(\n agentOpts: Agent | AgentOptions,\n ns: Main<M>,\n options: XrpcOptions<M> = {} as XrpcOptions<M>,\n): Promise<XrpcResult<M>> {\n const method: M = getMain(ns)\n\n for (let counter = 1; ; counter++) {\n options.signal?.throwIfAborted()\n try {\n const agent = buildAgent(agentOpts)\n const url = xrpcRequestUrl(method, options)\n const request = xrpcRequestInit(method, options)\n const response = await agent.fetchHandler(url, request).catch((err) => {\n const cause = extractFetchErrorCause(err)\n throw new XrpcFetchError(method, cause)\n })\n return await XrpcResponse.fromFetchResponse<M>(method, response, options)\n } catch (cause) {\n const failure = asXrpcFailure(method, cause)\n\n // Cannot retry a request with a consumable body\n if (\n options.body instanceof ReadableStream ||\n isAsyncIterable(options.body)\n ) {\n return failure\n }\n\n const waitTime = getRetryWaitTime(failure, options, counter)\n if (waitTime == null) {\n return failure\n }\n\n await wait(waitTime, options)\n }\n }\n}\n\nfunction xrpcRequestUrl<M extends Procedure | Query | Subscription>(\n method: M,\n options: { params?: Params },\n): `/xrpc/${NsidString}${'' | `?${string}`}` {\n const path = `/xrpc/${method.nsid}` as const\n\n // @NOTE param.toURLSearchParams() will always validate the params in order to\n // apply default values, so we can't disable it with options.validateRequest\n\n const queryString = method.parameters\n ?.toURLSearchParams(options.params ?? {})\n .toString()\n\n return queryString ? (`${path}?${queryString}` as const) : path\n}\n\nfunction xrpcRequestInit<T extends Procedure | Query>(\n schema: T,\n options: XrpcRequestProcessingOptions &\n XrpcRequestHeadersOptions &\n XrpcProcedureInputOptions & {\n encoding?: string\n },\n): RequestInit & { duplex?: 'half' } {\n const headers = buildXrpcRequestHeaders(options)\n\n // Tell the server what type of response we're expecting\n if (schema.output.encoding) {\n headers.set('accept', schema.output.encoding)\n }\n\n // Caller should not set content-type header\n if (headers.has('content-type')) {\n const contentType = headers.get('content-type')\n throw new TypeError(`Unexpected content-type header (${contentType})`)\n }\n\n // Requests with body\n if ('input' in schema) {\n const encodingHint = options.encoding\n const input = xrpcProcedureInput(schema, options, encodingHint)\n\n if (input) {\n headers.set('content-type', input.encoding)\n } else if (encodingHint != null) {\n throw new TypeError(`Unexpected encoding hint (${encodingHint})`)\n }\n\n return {\n duplex: 'half',\n redirect: 'follow',\n referrerPolicy: 'strict-origin-when-cross-origin', // (default)\n mode: 'cors', // (default)\n signal: options.signal,\n method: 'POST',\n headers,\n body: input?.body,\n }\n }\n\n // Requests without body\n return {\n duplex: 'half',\n redirect: 'follow',\n referrerPolicy: 'strict-origin-when-cross-origin', // (default)\n mode: 'cors', // (default)\n signal: options.signal,\n method: 'GET',\n headers,\n }\n}\n\ntype XrpcProcedureInputOptions = {\n body?: LexValue | BinaryBodyInit\n validateRequest?: boolean\n}\n\nfunction xrpcProcedureInput(\n method: Procedure,\n options: XrpcProcedureInputOptions,\n encodingHint?: string,\n): null | { body: BodyInit; encoding: string } {\n const { input } = method\n const { body } = options\n\n if (options.validateRequest) {\n input.schema?.check(body)\n }\n\n // Special handling for endpoints expecting application/json input\n if (input.encoding === 'application/json') {\n // @NOTE **NOT** using isLexValue here to avoid deep checks in order to\n // distinguish between LexValue and BinaryBodyInit.\n if (!isLexScalar(body) && !isPlainObject(body) && !Array.isArray(body)) {\n throw new TypeError(`Expected LexValue body, got ${typeof body}`)\n }\n\n return buildPayload(input, lexStringify(body), encodingHint)\n }\n\n // Other encodings will be sent unaltered (ie. as binary data)\n switch (typeof body) {\n case 'undefined':\n case 'string':\n return buildPayload(input, body, encodingHint)\n case 'object': {\n if (body === null) break\n if (ArrayBuffer.isView(body)) {\n return buildPayload(input, asUint8ArrayArrayBuffer(body), encodingHint)\n } else if (\n body instanceof ArrayBuffer ||\n body instanceof ReadableStream\n ) {\n return buildPayload(input, body, encodingHint)\n } else if (isAsyncIterable(body)) {\n // @NOTE While fetch() does not allow SharedArrayBuffer-backed\n // Uint8Arrays as \"body\", it **does** allow using ReadableStreams made\n // of Uint8Arrays<SharedArrayBuffer> (tested on NodeJS 22) as \"body\".\n return buildPayload(input, toReadableStream(body), encodingHint)\n } else if (isBlobLike(body)) {\n return buildPayload(input, body, encodingHint || body.type)\n }\n }\n }\n\n throw new TypeError(\n `Invalid ${typeof body} body for ${input.encoding} encoding`,\n )\n}\n\nfunction buildPayload(\n schema: Payload,\n body: undefined | BodyInit,\n encodingHint?: string,\n): null | { body: BodyInit; encoding: string } {\n if (schema.encoding === undefined) {\n if (body !== undefined) {\n throw new TypeError(`Endpoint expects no payload`)\n }\n\n return null\n }\n\n if (body === undefined) {\n // This error would be returned by the server, but we can catch it earlier\n // to avoid un-necessary requests. Note that a content-length of 0 does not\n // necessary mean that the body is \"empty\" (e.g. an empty txt file).\n throw new TypeError(`A request body is expected but none was provided`)\n }\n\n const encoding = buildEncoding(schema, encodingHint)\n return { encoding, body }\n}\n\nfunction buildEncoding(schema: Payload, encodingHint?: string): string {\n // Should never happen (required for type safety)\n if (!schema.encoding) {\n throw new TypeError('Unexpected payload')\n }\n\n if (encodingHint?.length) {\n if (!schema.matchesEncoding(encodingHint)) {\n throw new TypeError(\n `Cannot send a body with content-type \"${encodingHint}\" for \"${schema.encoding}\" encoding`,\n )\n }\n return encodingHint\n }\n\n // Fallback\n\n if (schema.encoding === '*/*') {\n return 'application/octet-stream'\n }\n\n if (schema.encoding.startsWith('text/')) {\n return schema.encoding.includes('*')\n ? 'text/plain; charset=utf-8'\n : `${schema.encoding}; charset=utf-8`\n }\n\n if (!schema.encoding.includes('*')) {\n return schema.encoding\n }\n\n throw new TypeError(\n `Unable to determine payload encoding. Please provide a 'content-type' header matching ${schema.encoding}.`,\n )\n}\n\n/**\n * Extracts the root cause from an error, unwrapping common fetch-related errors\n * such as those from undici (Node's internal fetch implementation).\n *\n * @param err - The error to extract the root cause from\n * @returns The root cause error, or the original error if no specific pattern is matched\n * @remarks This is useful for getting more specific error information from fetch-related failures, especially in Node environments using undici.\n */\nexport function extractFetchErrorCause(err: unknown): unknown {\n // Unwrap the Network error from undici (i.e. Node's internal fetch() implementation)\n // https://github.com/nodejs/undici/blob/04cb77327f7ada95c2e5b67424cddcb22d7bf882/lib/web/fetch/index.js#L234-L239\n if (\n err instanceof TypeError &&\n err.message === 'fetch failed' &&\n err.cause !== undefined\n ) {\n return err.cause\n }\n\n // @TODO Add other unwrap patterns here as needed (e.g. for other fetch\n // implementations or common network libraries, like \"node:http\", or in other\n // environments like React Native, Deno, Bun, Browser, etc.)\n\n return err\n}\n\nexport type RetryOptions = {\n /**\n * Function to determine whether a request should be retried after a failure.\n *\n * @default `(failure) => failure.shouldRetry()`\n */\n retry?: (failure: XrpcFailure, context: { counter: number }) => boolean\n /**\n * Maximum number of retries to allow.\n *\n * @default 0 (no retries)\n */\n maxRetries?: number\n /**\n * Max number of milliseconds allow between retries\n *\n * @default 30000\n */\n maxRetryTimeout?: number\n /**\n * Initial number of milliseconds to wait before retrying for the first time.\n *\n * @default 500\n */\n minRetryTimeout?: number\n /**\n * Factor to multiply the timeout factor between retries.\n *\n * @default 2\n */\n retryTimeoutFactor?: number\n /**\n * Automatically infer timeout between retries based on header values (`Retry-After`, `RateLimit-Reset`).\n *\n * @default true\n */\n retryHeaders?: boolean\n}\n\nfunction getRetryWaitTime(\n failure: XrpcFailure,\n options: RetryOptions,\n counter: number,\n): number | undefined {\n const {\n retry,\n maxRetries = 0,\n minRetryTimeout = 500,\n maxRetryTimeout = 30_000,\n retryTimeoutFactor = 2,\n retryHeaders = true,\n } = options\n\n if (counter > maxRetries) {\n return undefined\n }\n\n const shouldRetry = retry\n ? retry(failure, { counter })\n : failure.shouldRetry()\n\n if (!shouldRetry) {\n return undefined\n }\n\n const waitTime =\n retryHeaders && failure instanceof XrpcResponseError\n ? getWaitTimeFromHeaders(failure.headers)\n : undefined\n\n return Math.min(\n maxRetryTimeout,\n waitTime ?? minRetryTimeout * retryTimeoutFactor ** (counter - 1),\n )\n}\n\nfunction getWaitTimeFromHeaders(headers: Headers): number | undefined {\n const retryAfterHeader = headers.get('retry-after')\n if (retryAfterHeader) {\n const waitTime = /^\\s*\\d+\\s*$/.test(retryAfterHeader)\n ? // Retry-After is in seconds\n Number(retryAfterHeader) * 1000\n : // Retry-After is an http-date\n new Date(retryAfterHeader).getTime() - Date.now()\n if (waitTime > 0) return waitTime\n }\n\n const resetsAt = headers.get('RateLimit-Reset') // epoch\n if (resetsAt) {\n const waitTime = Number(resetsAt) * 1000 - Date.now()\n if (waitTime > 0) return waitTime\n }\n}\n"]}
package/package.json CHANGED
@@ -1,9 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lex-client",
3
- "version": "0.2.0",
4
- "engines": {
5
- "node": ">=22"
6
- },
3
+ "version": "0.2.2",
7
4
  "license": "MIT",
8
5
  "description": "HTTP client for interacting with Lexicon based APIs",
9
6
  "keywords": [
@@ -19,10 +16,6 @@
19
16
  "directory": "packages/lex/lex-client"
20
17
  },
21
18
  "files": [
22
- "./src",
23
- "./tsconfig.build.json",
24
- "./tsconfig.tests.json",
25
- "./tsconfig.json",
26
19
  "./dist",
27
20
  "./CHANGELOG.md"
28
21
  ],
@@ -34,19 +27,23 @@
34
27
  "default": "./dist/index.js"
35
28
  }
36
29
  },
30
+ "engines": {
31
+ "node": ">=22"
32
+ },
37
33
  "dependencies": {
38
34
  "tslib": "^2.8.1",
39
- "@atproto/lex-data": "^0.1.3",
40
- "@atproto/lex-json": "^0.1.2",
41
- "@atproto/lex-schema": "^0.1.5"
35
+ "@atproto/lex-data": "^0.1.4",
36
+ "@atproto/lex-json": "^0.1.3",
37
+ "@atproto/lex-schema": "^0.2.0"
42
38
  },
43
39
  "devDependencies": {
44
40
  "vitest": "^4.0.16",
45
- "@atproto/lex-cbor": "^0.1.2",
46
- "@atproto/lex-builder": "^0.1.4"
41
+ "@atproto/lex-builder": "^0.1.6",
42
+ "@atproto/lex-cbor": "^0.1.3"
47
43
  },
48
44
  "scripts": {
49
- "prebuild": "node ./scripts/lex-build.mjs",
45
+ "codegen:lex": "node ./scripts/lex-build.mjs",
46
+ "prebuild": "pnpm run '/^(codegen:.+)$/'",
50
47
  "build": "tsgo --build tsconfig.build.json",
51
48
  "test": "vitest run"
52
49
  }
package/src/agent.test.ts DELETED
@@ -1,216 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
- import {
3
- type Agent,
4
- type AgentConfig,
5
- type FetchHandler,
6
- buildAgent,
7
- isAgent,
8
- } from './agent.js'
9
-
10
- // ============================================================================
11
- // isAgent
12
- // ============================================================================
13
-
14
- describe(isAgent, () => {
15
- it('returns true for a valid agent with did', () => {
16
- const agent: Agent = {
17
- did: 'did:plc:example',
18
- fetchHandler: async () => new Response(),
19
- }
20
- expect(isAgent(agent)).toBe(true)
21
- })
22
-
23
- it('returns true for an agent without did', () => {
24
- const agent = { fetchHandler: async () => new Response() }
25
- expect(isAgent(agent)).toBe(true)
26
- })
27
-
28
- it('returns true when did is undefined', () => {
29
- const agent = { did: undefined, fetchHandler: async () => new Response() }
30
- expect(isAgent(agent)).toBe(true)
31
- })
32
-
33
- it('returns false for null', () => {
34
- expect(isAgent(null)).toBe(false)
35
- })
36
-
37
- it('returns false for non-objects', () => {
38
- expect(isAgent('string')).toBe(false)
39
- expect(isAgent(42)).toBe(false)
40
- expect(isAgent(undefined)).toBe(false)
41
- })
42
-
43
- it('returns false when fetchHandler is not a function', () => {
44
- expect(isAgent({ fetchHandler: 'not-a-function' })).toBe(false)
45
- })
46
-
47
- it('returns false when did is not a string', () => {
48
- expect(isAgent({ did: 42, fetchHandler: async () => new Response() })).toBe(
49
- false,
50
- )
51
- })
52
- })
53
-
54
- // ============================================================================
55
- // buildAgent
56
- // ============================================================================
57
-
58
- describe(buildAgent, () => {
59
- describe('from Agent', () => {
60
- it('returns the same agent instance', () => {
61
- const agent: Agent = {
62
- did: 'did:plc:example',
63
- fetchHandler: async () => new Response(),
64
- }
65
- expect(buildAgent(agent)).toBe(agent)
66
- })
67
- })
68
-
69
- describe('from FetchHandler', () => {
70
- it('wraps a function as an agent', () => {
71
- const handler: FetchHandler = async () => new Response()
72
- const agent = buildAgent(handler)
73
-
74
- expect(agent.did).toBeUndefined()
75
- expect(typeof agent.fetchHandler).toBe('function')
76
- })
77
- })
78
-
79
- describe('from string URL', () => {
80
- it('creates an agent that prepends the service URL', async () => {
81
- const fetchFn = vi.fn<typeof globalThis.fetch>(async () =>
82
- Response.json({ ok: true }),
83
- )
84
- const agent = buildAgent({
85
- service: 'https://example.com',
86
- fetch: fetchFn,
87
- })
88
-
89
- await agent.fetchHandler('/xrpc/io.example.test', { method: 'GET' })
90
-
91
- expect(fetchFn).toHaveBeenCalledOnce()
92
- const [url, init] = fetchFn.mock.calls[0]
93
- expect(url).toEqual(
94
- new URL('/xrpc/io.example.test', 'https://example.com'),
95
- )
96
- expect(init?.method).toBe('GET')
97
- })
98
-
99
- it('has undefined did', () => {
100
- const agent = buildAgent('https://example.com')
101
- expect(agent.did).toBeUndefined()
102
- })
103
- })
104
-
105
- describe('from URL instance', () => {
106
- it('creates an agent with the URL as service', async () => {
107
- const fetchFn = vi.fn<typeof globalThis.fetch>(async () =>
108
- Response.json({ ok: true }),
109
- )
110
- const agent = buildAgent({
111
- service: new URL('https://example.com'),
112
- fetch: fetchFn,
113
- })
114
-
115
- await agent.fetchHandler('/xrpc/io.example.test', { method: 'GET' })
116
-
117
- expect(fetchFn).toHaveBeenCalledOnce()
118
- const [url] = fetchFn.mock.calls[0]
119
- expect(url).toEqual(
120
- new URL('/xrpc/io.example.test', 'https://example.com'),
121
- )
122
- })
123
- })
124
-
125
- describe('from AgentConfig', () => {
126
- it('exposes did from config', () => {
127
- const agent = buildAgent({
128
- did: 'did:plc:test123',
129
- service: 'https://example.com',
130
- })
131
- expect(agent.did).toBe('did:plc:test123')
132
- })
133
-
134
- it('reflects did changes on the config object', () => {
135
- const config: AgentConfig = {
136
- did: 'did:plc:original',
137
- service: 'https://example.com',
138
- }
139
- const agent = buildAgent(config)
140
- expect(agent.did).toBe('did:plc:original')
141
-
142
- config.did = 'did:plc:updated'
143
- expect(agent.did).toBe('did:plc:updated')
144
- })
145
-
146
- it('throws TypeError when fetch is not available', () => {
147
- const originalFetch = globalThis.fetch
148
- try {
149
- // @ts-expect-error removing fetch to simulate missing environment
150
- globalThis.fetch = undefined
151
- expect(() => buildAgent({ service: 'https://example.com' })).toThrow(
152
- TypeError,
153
- )
154
- } finally {
155
- globalThis.fetch = originalFetch
156
- }
157
- })
158
- })
159
-
160
- describe('headers', () => {
161
- it('sends config headers when no request headers', async () => {
162
- const fetchFn = vi.fn<typeof globalThis.fetch>(async () =>
163
- Response.json({}),
164
- )
165
- const agent = buildAgent({
166
- service: 'https://example.com',
167
- headers: { Authorization: 'Bearer token123' },
168
- fetch: fetchFn,
169
- })
170
-
171
- await agent.fetchHandler('/xrpc/test', { method: 'GET' })
172
-
173
- const [, init] = fetchFn.mock.calls[0]
174
- expect(init?.headers).toEqual({ Authorization: 'Bearer token123' })
175
- })
176
-
177
- it('sends request headers when no config headers', async () => {
178
- const fetchFn = vi.fn<typeof globalThis.fetch>(async () =>
179
- Response.json({}),
180
- )
181
- const agent = buildAgent({
182
- service: 'https://example.com',
183
- fetch: fetchFn,
184
- })
185
-
186
- await agent.fetchHandler('/xrpc/test', {
187
- method: 'GET',
188
- headers: { 'X-Custom': 'value' },
189
- })
190
-
191
- const [, init] = fetchFn.mock.calls[0]
192
- expect(init?.headers).toEqual({ 'X-Custom': 'value' })
193
- })
194
-
195
- it('merges config and request headers, with request taking priority', async () => {
196
- const fetchFn = vi.fn<typeof globalThis.fetch>(async () =>
197
- Response.json({}),
198
- )
199
- const agent = buildAgent({
200
- service: 'https://example.com',
201
- headers: { Authorization: 'Bearer default', 'X-Default': 'yes' },
202
- fetch: fetchFn,
203
- })
204
-
205
- await agent.fetchHandler('/xrpc/test', {
206
- method: 'GET',
207
- headers: { Authorization: 'Bearer override' },
208
- })
209
-
210
- const [, init] = fetchFn.mock.calls[0]
211
- const headers = new Headers(init?.headers)
212
- expect(headers.get('Authorization')).toBe('Bearer override')
213
- expect(headers.get('X-Default')).toBe('yes')
214
- })
215
- })
216
- })
package/src/agent.ts DELETED
@@ -1,186 +0,0 @@
1
- import { DidString } from '@atproto/lex-schema'
2
-
3
- /**
4
- * A function that performs HTTP requests towards a service endpoint.
5
- *
6
- * The handler is responsible for adding the origin (protocol, hostname, and
7
- * port) to the provided path, typically based on authentication or service
8
- * configuration. The handler can also be responsible for adding any necessary
9
- * headers, such as authorization tokens.
10
- *
11
- * @param path - The URL path (pathname + query parameters) without the origin
12
- * @param init - Standard fetch RequestInit options
13
- * @returns A Promise resolving to the HTTP Response
14
- */
15
- export type FetchHandler = (
16
- /**
17
- * The URL (pathname + query parameters) to make the request to, without the
18
- * origin. The origin (protocol, hostname, and port) must be added by this
19
- * {@link FetchHandler}, typically based on authentication or other factors.
20
- */
21
- path: `/${string}`,
22
- init: RequestInit,
23
- ) => Promise<Response>
24
-
25
- /**
26
- * Core interface for making XRPC requests towards a specific service.
27
- *
28
- * An {@link Agent} encapsulates an identity and request handling for AT
29
- * Protocol operations. Agents will typically handle authentication, service URL
30
- * resolution, and other request configuration, allowing client code to make
31
- * requests without needing to manage these details directly. The key component
32
- * of an Agent is the {@link FetchHandler}, which is responsible for
33
- * constructing the full request URL and adding any necessary headers or
34
- * authentication tokens. The Agent's `did` property represents the
35
- * authenticated user's DID, if available, and can be used for operations that
36
- * require knowledge of the user's identity (such as creating AT Protocol
37
- * records).
38
- *
39
- * @see {@link buildAgent} for creating (simple) Agent instances.
40
- *
41
- * @example
42
- * ```typescript
43
- * const agent: Agent = {
44
- * did: 'did:plc:example123',
45
- * fetchHandler: async (path, init) => {
46
- * const url = new URL(path, 'https://bsky.social')
47
- * return fetch(url, init)
48
- * }
49
- * }
50
- * ```
51
- */
52
- export interface Agent {
53
- /** The DID of the authenticated user, or `undefined` if unauthenticated. */
54
- readonly did?: DidString
55
- /** The fetch handler used to make HTTP requests. */
56
- fetchHandler: FetchHandler
57
- }
58
-
59
- export function isAgent(value: unknown): value is Agent {
60
- return (
61
- typeof value === 'object' &&
62
- value !== null &&
63
- 'fetchHandler' in value &&
64
- typeof value.fetchHandler === 'function' &&
65
- (!('did' in value) ||
66
- value.did === undefined ||
67
- typeof value.did === 'string')
68
- )
69
- }
70
-
71
- export type AgentConfig = {
72
- /**
73
- * The identifier (DID) of the user represented by this agent.
74
- */
75
- did?: DidString
76
-
77
- /**
78
- * The service URL to make requests to. This can be a string, URL, or a
79
- * function that returns a string or URL. This is useful for dynamic URLs,
80
- * such as a service URL that changes based on authentication.
81
- */
82
- service: string | URL
83
-
84
- /**
85
- * Optional headers to include with every request made by this agent, unless
86
- * overridden by the request-specific headers provided to the fetch handler.
87
- */
88
- headers?: HeadersInit
89
-
90
- /**
91
- * Bring your own fetch implementation. Typically useful for testing, logging,
92
- * mocking, or adding retries, session management, signatures, proof of
93
- * possession (DPoP), SSRF protection, etc. Defaults to the global `fetch`
94
- * function.
95
- */
96
- fetch?: typeof globalThis.fetch
97
- }
98
-
99
- /**
100
- * Options for creating an Agent.
101
- *
102
- * Can be a full {@link AgentConfig} object, or a simple service URL string/{@link URL}.
103
- */
104
- export type AgentOptions = AgentConfig | FetchHandler | string | URL
105
-
106
- /**
107
- * Creates an {@link Agent} from various input types.
108
- *
109
- * This factory function accepts an existing Agent (returned as-is), a service
110
- * URL, or a full configuration object. It handles the common case of creating
111
- * an unauthenticated agent from just a service URL.
112
- *
113
- * @param options - Agent instance, configuration object, or service URL
114
- * @returns A configured Agent ready for making requests
115
- * @throws {TypeError} If fetch() is not available in the environment
116
- *
117
- * @example // From URL string
118
- * ```typescript
119
- * const agent = buildAgent('https://public.api.bsky.app')
120
- * ```
121
- *
122
- * @example // From configuration
123
- * ```typescript
124
- * const agent = buildAgent({
125
- * did: 'did:plc:example',
126
- * service: 'https://bsky.social',
127
- * headers: { 'Authorization': 'Bearer ...' }
128
- * })
129
- * ```
130
- */
131
- export function buildAgent<O extends Agent | AgentOptions>(
132
- options: O,
133
- ): O extends Agent ? O : Agent
134
- export function buildAgent(options: Agent | AgentOptions): Agent {
135
- const config: Agent | AgentConfig =
136
- typeof options === 'function'
137
- ? { did: undefined, fetchHandler: options }
138
- : typeof options === 'string' || options instanceof URL
139
- ? { did: undefined, service: options }
140
- : options
141
-
142
- if (isAgent(config)) return config
143
-
144
- const { service, fetch = globalThis.fetch } = config
145
-
146
- if (typeof fetch !== 'function') {
147
- throw new TypeError('fetch() is not available in this environment')
148
- }
149
-
150
- return {
151
- get did() {
152
- return config.did
153
- },
154
-
155
- async fetchHandler(path, init) {
156
- const headers =
157
- config.headers != null && init.headers != null
158
- ? mergeHeaders(config.headers, init.headers)
159
- : config.headers || init.headers
160
-
161
- return fetch(
162
- new URL(path, service),
163
- headers !== init.headers ? { ...init, headers } : init,
164
- )
165
- },
166
- }
167
- }
168
-
169
- function mergeHeaders(
170
- defaultHeaders: HeadersInit,
171
- requestHeaders: HeadersInit,
172
- ): Headers {
173
- // We don't want to alter the original Headers objects, so we create a new one
174
- const result = new Headers(defaultHeaders)
175
-
176
- const overrides =
177
- requestHeaders instanceof Headers
178
- ? requestHeaders
179
- : new Headers(requestHeaders)
180
-
181
- for (const [key, value] of overrides.entries()) {
182
- result.set(key, value)
183
- }
184
-
185
- return result
186
- }