@atproto/lex-client 0.0.12 → 0.0.14

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 (49) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/agent.d.ts +24 -19
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +11 -33
  5. package/dist/agent.js.map +1 -1
  6. package/dist/client.d.ts +21 -12
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +8 -3
  9. package/dist/client.js.map +1 -1
  10. package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts +13 -9
  11. package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts.map +1 -1
  12. package/dist/lexicons/com/atproto/repo/createRecord.defs.js +3 -2
  13. package/dist/lexicons/com/atproto/repo/createRecord.defs.js.map +1 -1
  14. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts +5 -5
  15. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts.map +1 -1
  16. package/dist/lexicons/com/atproto/repo/deleteRecord.defs.js.map +1 -1
  17. package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts +5 -5
  18. package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts.map +1 -1
  19. package/dist/lexicons/com/atproto/repo/getRecord.defs.js +1 -1
  20. package/dist/lexicons/com/atproto/repo/getRecord.defs.js.map +1 -1
  21. package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts +6 -6
  22. package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts.map +1 -1
  23. package/dist/lexicons/com/atproto/repo/listRecords.defs.js +1 -1
  24. package/dist/lexicons/com/atproto/repo/listRecords.defs.js.map +1 -1
  25. package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts +13 -9
  26. package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts.map +1 -1
  27. package/dist/lexicons/com/atproto/repo/putRecord.defs.js +3 -2
  28. package/dist/lexicons/com/atproto/repo/putRecord.defs.js.map +1 -1
  29. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts +5 -5
  30. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts.map +1 -1
  31. package/dist/lexicons/com/atproto/repo/uploadBlob.defs.js.map +1 -1
  32. package/dist/lexicons/com/atproto/sync/getBlob.defs.d.ts +3 -3
  33. package/dist/lexicons/com/atproto/sync/getBlob.defs.d.ts.map +1 -1
  34. package/dist/lexicons/com/atproto/sync/getBlob.defs.js.map +1 -1
  35. package/dist/xrpc.d.ts +14 -8
  36. package/dist/xrpc.d.ts.map +1 -1
  37. package/dist/xrpc.js +5 -3
  38. package/dist/xrpc.js.map +1 -1
  39. package/package.json +6 -6
  40. package/src/agent.ts +38 -21
  41. package/src/client.ts +16 -8
  42. package/src/lexicons/com/atproto/repo/createRecord.defs.ts +15 -7
  43. package/src/lexicons/com/atproto/repo/deleteRecord.defs.ts +11 -5
  44. package/src/lexicons/com/atproto/repo/getRecord.defs.ts +7 -4
  45. package/src/lexicons/com/atproto/repo/listRecords.defs.ts +8 -5
  46. package/src/lexicons/com/atproto/repo/putRecord.defs.ts +15 -7
  47. package/src/lexicons/com/atproto/repo/uploadBlob.defs.ts +11 -5
  48. package/src/lexicons/com/atproto/sync/getBlob.defs.ts +6 -3
  49. package/src/xrpc.ts +22 -14
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 Params,\n Payload,\n Procedure,\n Query,\n Restricted,\n Subscription,\n getMain,\n} from '@atproto/lex-schema'\nimport { Agent } from './agent.js'\nimport { XrpcFailure, asXrpcFailure } from './errors.js'\nimport { XrpcResponse } from './response.js'\nimport { BinaryBodyInit, CallOptions } from './types.js'\nimport {\n buildAtprotoHeaders,\n isAsyncIterable,\n isBlobLike,\n toReadableStream,\n} from './util.js'\n\n// If all params are optional, allow omitting the params object\ntype XrpcParamsOptions<P extends Params> =\n NonNullable<unknown> extends P ? { params?: P } : { params: P }\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\ntype XrpcRequestPayload<M extends Procedure | Query> = M extends Procedure\n ? InferPayload<M['input'], BinaryBodyInit>\n : undefined\n\ntype XrpcInputOptions<In> = In extends { body: infer B; encoding: infer E }\n ? // encoding will be inferred from the schema at runtime if not provided\n { body: B; encoding?: E }\n : { body?: undefined; encoding?: undefined }\n\n/**\n * Options for making an XRPC request.\n *\n * Combines {@link CallOptions} with method-specific params and body requirements.\n * The type system ensures required params/body are provided based on the method schema.\n *\n * @typeParam M - The XRPC method type (Procedure or Query)\n * @see {@link CallOptions} for general request options like signal and validateRequest\n * @see {@link XrpcParamsOptions} for method-specific query parameters\n * @see {@link XrpcInputOptions} for method-specific body and encoding requirements\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 CallOptions &\n XrpcInputOptions<XrpcRequestPayload<M>> &\n XrpcParamsOptions<XrpcRequestParams<M>>\n\n/**\n * Makes an XRPC request and throws on failure.\n *\n * This is the low-level function for making XRPC calls. For most use cases,\n * prefer using {@link Client.xrpc} which provides a more ergonomic API.\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(agent, app.bsky.feed.getTimeline.main, {\n * params: { limit: 50 }\n * })\n * ```\n */\nexport async function xrpc<const M extends Query | Procedure>(\n agent: Agent,\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 agent: Agent,\n ns: Main<M>,\n options: XrpcOptions<M>,\n): Promise<XrpcResponse<M>>\nexport async function xrpc<const M extends Query | Procedure>(\n agent: Agent,\n ns: Main<M>,\n options: XrpcOptions<M> = {} as XrpcOptions<M>,\n): Promise<XrpcResponse<M>> {\n const response = await xrpcSafe<M>(agent, 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(agent, app.bsky.actor.getProfile.main, {\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 agent: Agent,\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 agent: Agent,\n ns: Main<M>,\n options: XrpcOptions<M>,\n): Promise<XrpcResult<M>>\nexport async function xrpcSafe<const M extends Query | Procedure>(\n agent: Agent,\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 url = xrpcRequestUrl(method, options)\n const request = xrpcRequestInit(method, options)\n const response = await agent.fetchHandler(url, request)\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: CallOptions & { params?: Params },\n) {\n const path = `/xrpc/${method.nsid}`\n\n const queryString = method.parameters\n ?.toURLSearchParams(options.params ?? {})\n .toString()\n\n return queryString ? `${path}?${queryString}` : path\n}\n\nfunction xrpcRequestInit<T extends Procedure | Query>(\n schema: T,\n options: CallOptions & {\n body?: LexValue | BinaryBodyInit\n encoding?: string\n },\n): RequestInit & { duplex?: 'half' } {\n const headers = buildAtprotoHeaders(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\nfunction xrpcProcedureInput(\n method: Procedure,\n options: CallOptions & { body?: LexValue | BinaryBodyInit },\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 (\n ArrayBuffer.isView(body) ||\n body instanceof ArrayBuffer ||\n body instanceof ReadableStream\n ) {\n return buildPayload(input, body, encodingHint)\n } else if (isAsyncIterable(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(\n `Cannot send a ${typeof body} body with undefined encoding`,\n )\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"]}
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, asXrpcFailure } from './errors.js'\nimport { XrpcResponse } from './response.js'\nimport { BinaryBodyInit, CallOptions } from './types.js'\nimport {\n buildAtprotoHeaders,\n isAsyncIterable,\n isBlobLike,\n toReadableStream,\n} from './util.js'\n\n// If all params are optional, allow omitting the params object\ntype XrpcParamsOptions<P extends Params> =\n NonNullable<unknown> extends P ? { params?: P } : { params: P }\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\ntype XrpcRequestPayload<M extends Procedure | Query> = M extends Procedure\n ? InferPayload<M['input'], BinaryBodyInit>\n : undefined\n\ntype XrpcInputOptions<In> = In extends { body: infer B; encoding: infer E }\n ? // encoding will be inferred from the schema at runtime if not provided\n { body: B; encoding?: E }\n : { body?: undefined; encoding?: undefined }\n\n/**\n * Options for making an XRPC request.\n *\n * Combines {@link CallOptions} with method-specific params and body requirements.\n * The type system ensures required params/body are provided based on the method schema.\n *\n * @typeParam M - The XRPC method type (Procedure or Query)\n * @see {@link CallOptions} for general request options like signal and validateRequest\n * @see {@link XrpcParamsOptions} for method-specific query parameters\n * @see {@link XrpcInputOptions} for method-specific body and encoding requirements\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 CallOptions &\n XrpcInputOptions<XrpcRequestPayload<M>> &\n XrpcParamsOptions<XrpcRequestParams<M>>\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)\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: CallOptions & { params?: Params },\n): `/xrpc/${NsidString}${'' | `?${string}`}` {\n const path = `/xrpc/${method.nsid}` as const\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: CallOptions & {\n body?: LexValue | BinaryBodyInit\n encoding?: string\n },\n): RequestInit & { duplex?: 'half' } {\n const headers = buildAtprotoHeaders(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\nfunction xrpcProcedureInput(\n method: Procedure,\n options: CallOptions & { body?: LexValue | BinaryBodyInit },\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 (\n ArrayBuffer.isView(body) ||\n body instanceof ArrayBuffer ||\n body instanceof ReadableStream\n ) {\n return buildPayload(input, body, encodingHint)\n } else if (isAsyncIterable(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(\n `Cannot send a ${typeof body} body with undefined encoding`,\n )\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lex-client",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "license": "MIT",
5
5
  "description": "HTTP client for interacting with Lexicon based APIs",
6
6
  "keywords": [
@@ -37,14 +37,14 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "tslib": "^2.8.1",
40
- "@atproto/lex-data": "^0.0.11",
41
- "@atproto/lex-json": "^0.0.11",
42
- "@atproto/lex-schema": "^0.0.12"
40
+ "@atproto/lex-data": "^0.0.12",
41
+ "@atproto/lex-json": "^0.0.12",
42
+ "@atproto/lex-schema": "^0.0.13"
43
43
  },
44
44
  "devDependencies": {
45
45
  "vitest": "^4.0.16",
46
- "@atproto/lex-cbor": "^0.0.11",
47
- "@atproto/lex-builder": "^0.0.14"
46
+ "@atproto/lex-cbor": "^0.0.13",
47
+ "@atproto/lex-builder": "^0.0.16"
48
48
  },
49
49
  "scripts": {
50
50
  "prebuild": "node ./scripts/lex-build.mjs",
package/src/agent.ts CHANGED
@@ -5,7 +5,7 @@ import { DidString } from '@atproto/lex-schema'
5
5
  *
6
6
  * The handler is responsible for adding the origin (protocol, hostname, and
7
7
  * port) to the provided path, typically based on authentication or service
8
- * configuration. The handler are also responsible for adding any necessary
8
+ * configuration. The handler can also be responsible for adding any necessary
9
9
  * headers, such as authorization tokens.
10
10
  *
11
11
  * @param path - The URL path (pathname + query parameters) without the origin
@@ -18,16 +18,23 @@ export type FetchHandler = (
18
18
  * origin. The origin (protocol, hostname, and port) must be added by this
19
19
  * {@link FetchHandler}, typically based on authentication or other factors.
20
20
  */
21
- path: string,
21
+ path: `/${string}`,
22
22
  init: RequestInit,
23
23
  ) => Promise<Response>
24
24
 
25
25
  /**
26
- * Core interface for making XRPC requests.
26
+ * Core interface for making XRPC requests towards a specific service.
27
27
  *
28
- * An Agent encapsulates an identity and request handling for AT Protocol
29
- * operations. It can represent an authenticated user session or an
30
- * unauthenticated service client.
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).
31
38
  *
32
39
  * @see {@link buildAgent} for creating (simple) Agent instances.
33
40
  *
@@ -35,7 +42,10 @@ export type FetchHandler = (
35
42
  * ```typescript
36
43
  * const agent: Agent = {
37
44
  * did: 'did:plc:example123',
38
- * fetchHandler: (path, init) => fetch(new URL(path, 'https://bsky.social'), init)
45
+ * fetchHandler: async (path, init) => {
46
+ * const url = new URL(path, 'https://bsky.social')
47
+ * return fetch(url, init)
48
+ * }
39
49
  * }
40
50
  * ```
41
51
  */
@@ -46,6 +56,18 @@ export interface Agent {
46
56
  fetchHandler: FetchHandler
47
57
  }
48
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
+
49
71
  export type AgentConfig = {
50
72
  /**
51
73
  * The identifier (DID) of the user represented by this agent.
@@ -84,20 +106,20 @@ export type AgentOptions = AgentConfig | string | URL
84
106
  /**
85
107
  * Creates an {@link Agent} from various input types.
86
108
  *
87
- * This factory function accepts an existing Agent (returned as-is), a service URL,
88
- * or a full configuration object. It handles the common case of creating an
89
- * unauthenticated agent from just a service URL.
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.
90
112
  *
91
113
  * @param options - Agent instance, configuration object, or service URL
92
114
  * @returns A configured Agent ready for making requests
93
115
  * @throws {TypeError} If fetch() is not available in the environment
94
116
  *
95
- * @example From URL string
117
+ * @example // From URL string
96
118
  * ```typescript
97
119
  * const agent = buildAgent('https://public.api.bsky.app')
98
120
  * ```
99
121
  *
100
- * @example From configuration
122
+ * @example // From configuration
101
123
  * ```typescript
102
124
  * const agent = buildAgent({
103
125
  * did: 'did:plc:example',
@@ -105,17 +127,12 @@ export type AgentOptions = AgentConfig | string | URL
105
127
  * headers: { 'Authorization': 'Bearer ...' }
106
128
  * })
107
129
  * ```
108
- *
109
- * @example Pass-through existing agent
110
- * ```typescript
111
- * const existing: Agent = { ... }
112
- * const agent = buildAgent(existing) // Returns existing unchanged
113
- * ```
114
130
  */
131
+ export function buildAgent<O extends Agent | AgentOptions>(
132
+ options: O,
133
+ ): O extends Agent ? O : Agent
115
134
  export function buildAgent(options: Agent | AgentOptions): Agent {
116
- if (typeof options === 'object' && 'fetchHandler' in options) {
117
- return options
118
- }
135
+ if (isAgent(options)) return options
119
136
 
120
137
  const config: Agent | AgentConfig =
121
138
  typeof options === 'string' || options instanceof URL
package/src/client.ts CHANGED
@@ -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'
@@ -263,9 +262,10 @@ export type ListOutput<T extends RecordSchema> = InferMethodOutputBody<
263
262
  /** Records that successfully validated against the schema. */
264
263
  records: ListRecord<Infer<T>>[]
265
264
  // @NOTE Because the schema uses "type": "unknown" instead of an open union,
266
- // we have to use UnknownObject instead of Unknown$TypedObject here.
265
+ // we have to use LexMap instead of Unknown$TypedObject here, which is
266
+ // unfortunate.
267
267
  /** Records that failed schema validation. */
268
- invalid: UnknownObject[]
268
+ invalid: LexMap[]
269
269
  }
270
270
 
271
271
  /**
@@ -282,11 +282,12 @@ export type ListRecord<Value extends LexMap> =
282
282
  * services. It provides type-safe methods for XRPC calls, record operations,
283
283
  * and blob handling.
284
284
  *
285
- * @example Basic usage
285
+ * @example // Basic usage
286
286
  * ```typescript
287
287
  * import { Client } from '@atproto/lex'
288
288
  *
289
- * const client = new Client(agent)
289
+ * const client = new Client(oauthSession)
290
+ *
290
291
  * const response = await client.xrpc(app.bsky.feed.getTimeline.main, {
291
292
  * params: { limit: 50 }
292
293
  * })
@@ -379,11 +380,18 @@ export class Client implements Agent {
379
380
  }
380
381
 
381
382
  /**
382
- * Low-level fetch handler for making requests.
383
+ * {@link Agent}'s {@link Agent.fetchHandler} implementation, which adds
384
+ * labelers and service proxying headers. This method allow a {@link Client}
385
+ * instance to be used directly as an {@link Agent} for another
386
+ * {@link Client}, enabling composition of headers (labelers, proxying, etc.).
387
+ *
383
388
  * @param path - The request path
384
389
  * @param init - Request initialization options
385
390
  */
386
- public fetchHandler(path: string, init: RequestInit): Promise<Response> {
391
+ public fetchHandler(
392
+ path: `/${string}`,
393
+ init: RequestInit,
394
+ ): Promise<Response> {
387
395
  const headers = buildAtprotoHeaders({
388
396
  headers: init.headers,
389
397
  service: this.service,
@@ -941,7 +949,7 @@ function getLiteralRecordKey<const T extends RecordSchema>(
941
949
  schema: T,
942
950
  ): InferRecordKey<T> {
943
951
  if (schema.key.startsWith('literal:')) {
944
- return schema.key.slice(8)
952
+ return schema.key.slice(8) as InferRecordKey<T>
945
953
  }
946
954
 
947
955
  throw new TypeError(
@@ -22,7 +22,7 @@ const main =
22
22
  /*#__PURE__*/ l.string({ format: 'record-key', maxLength: 512 }),
23
23
  ),
24
24
  validate: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
25
- record: /*#__PURE__*/ l.unknownObject(),
25
+ record: /*#__PURE__*/ l.lexMap(),
26
26
  swapCommit: /*#__PURE__*/ l.optional(
27
27
  /*#__PURE__*/ l.string({ format: 'cid' }),
28
28
  ),
@@ -35,17 +35,25 @@ const main =
35
35
  (() => RepoDefs.commitMeta) as any,
36
36
  ),
37
37
  ),
38
- validationStatus: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
38
+ validationStatus: /*#__PURE__*/ l.optional(
39
+ /*#__PURE__*/ l.string<{ knownValues: ['valid', 'unknown'] }>(),
40
+ ),
39
41
  }),
40
42
  ['InvalidSwap'],
41
43
  )
42
44
  export { main }
43
45
 
44
- export type Params = l.InferMethodParams<typeof main>
45
- export type Input = l.InferMethodInput<typeof main>
46
- export type InputBody = l.InferMethodInputBody<typeof main>
47
- export type Output = l.InferMethodOutput<typeof main>
48
- export type OutputBody = l.InferMethodOutputBody<typeof main>
46
+ export type $Params = l.InferMethodParams<typeof main>
47
+ export type $Input<B = l.BinaryData> = l.InferMethodInput<typeof main, B>
48
+ export type $InputBody<B = l.BinaryData> = l.InferMethodInputBody<
49
+ typeof main,
50
+ B
51
+ >
52
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
53
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
54
+ typeof main,
55
+ B
56
+ >
49
57
 
50
58
  export const $lxm = /*#__PURE__*/ main.nsid,
51
59
  $params = /*#__PURE__*/ main.parameters,
@@ -37,11 +37,17 @@ const main =
37
37
  )
38
38
  export { main }
39
39
 
40
- export type Params = l.InferMethodParams<typeof main>
41
- export type Input = l.InferMethodInput<typeof main>
42
- export type InputBody = l.InferMethodInputBody<typeof main>
43
- export type Output = l.InferMethodOutput<typeof main>
44
- export type OutputBody = l.InferMethodOutputBody<typeof main>
40
+ export type $Params = l.InferMethodParams<typeof main>
41
+ export type $Input<B = l.BinaryData> = l.InferMethodInput<typeof main, B>
42
+ export type $InputBody<B = l.BinaryData> = l.InferMethodInputBody<
43
+ typeof main,
44
+ B
45
+ >
46
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
47
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
48
+ typeof main,
49
+ B
50
+ >
45
51
 
46
52
  export const $lxm = /*#__PURE__*/ main.nsid,
47
53
  $params = /*#__PURE__*/ main.parameters,
@@ -22,15 +22,18 @@ const main =
22
22
  /*#__PURE__*/ l.jsonPayload({
23
23
  uri: /*#__PURE__*/ l.string({ format: 'at-uri' }),
24
24
  cid: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string({ format: 'cid' })),
25
- value: /*#__PURE__*/ l.unknownObject(),
25
+ value: /*#__PURE__*/ l.lexMap(),
26
26
  }),
27
27
  ['RecordNotFound'],
28
28
  )
29
29
  export { main }
30
30
 
31
- export type Params = l.InferMethodParams<typeof main>
32
- export type Output = l.InferMethodOutput<typeof main>
33
- export type OutputBody = l.InferMethodOutputBody<typeof main>
31
+ export type $Params = l.InferMethodParams<typeof main>
32
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
33
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
34
+ typeof main,
35
+ B
36
+ >
34
37
 
35
38
  export const $lxm = /*#__PURE__*/ main.nsid,
36
39
  $params = main.parameters,
@@ -34,9 +34,12 @@ const main =
34
34
  )
35
35
  export { main }
36
36
 
37
- export type Params = l.InferMethodParams<typeof main>
38
- export type Output = l.InferMethodOutput<typeof main>
39
- export type OutputBody = l.InferMethodOutputBody<typeof main>
37
+ export type $Params = l.InferMethodParams<typeof main>
38
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
39
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
40
+ typeof main,
41
+ B
42
+ >
40
43
 
41
44
  export const $lxm = /*#__PURE__*/ main.nsid,
42
45
  $params = main.parameters,
@@ -46,7 +49,7 @@ type Record$0 = {
46
49
  $type?: 'com.atproto.repo.listRecords#record'
47
50
  uri: l.AtUriString
48
51
  cid: l.CidString
49
- value: l.UnknownObject
52
+ value: l.LexMap
50
53
  }
51
54
 
52
55
  export type { Record$0 as Record }
@@ -57,7 +60,7 @@ const record$0 = /*#__PURE__*/ l.typedObject<Record$0>(
57
60
  /*#__PURE__*/ l.object({
58
61
  uri: /*#__PURE__*/ l.string({ format: 'at-uri' }),
59
62
  cid: /*#__PURE__*/ l.string({ format: 'cid' }),
60
- value: /*#__PURE__*/ l.unknownObject(),
63
+ value: /*#__PURE__*/ l.lexMap(),
61
64
  }),
62
65
  )
63
66
 
@@ -20,7 +20,7 @@ const main =
20
20
  collection: /*#__PURE__*/ l.string({ format: 'nsid' }),
21
21
  rkey: /*#__PURE__*/ l.string({ format: 'record-key', maxLength: 512 }),
22
22
  validate: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.boolean()),
23
- record: /*#__PURE__*/ l.unknownObject(),
23
+ record: /*#__PURE__*/ l.lexMap(),
24
24
  swapRecord: /*#__PURE__*/ l.optional(
25
25
  /*#__PURE__*/ l.nullable(/*#__PURE__*/ l.string({ format: 'cid' })),
26
26
  ),
@@ -36,17 +36,25 @@ const main =
36
36
  (() => RepoDefs.commitMeta) as any,
37
37
  ),
38
38
  ),
39
- validationStatus: /*#__PURE__*/ l.optional(/*#__PURE__*/ l.string()),
39
+ validationStatus: /*#__PURE__*/ l.optional(
40
+ /*#__PURE__*/ l.string<{ knownValues: ['valid', 'unknown'] }>(),
41
+ ),
40
42
  }),
41
43
  ['InvalidSwap'],
42
44
  )
43
45
  export { main }
44
46
 
45
- export type Params = l.InferMethodParams<typeof main>
46
- export type Input = l.InferMethodInput<typeof main>
47
- export type InputBody = l.InferMethodInputBody<typeof main>
48
- export type Output = l.InferMethodOutput<typeof main>
49
- export type OutputBody = l.InferMethodOutputBody<typeof main>
47
+ export type $Params = l.InferMethodParams<typeof main>
48
+ export type $Input<B = l.BinaryData> = l.InferMethodInput<typeof main, B>
49
+ export type $InputBody<B = l.BinaryData> = l.InferMethodInputBody<
50
+ typeof main,
51
+ B
52
+ >
53
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
54
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
55
+ typeof main,
56
+ B
57
+ >
50
58
 
51
59
  export const $lxm = /*#__PURE__*/ main.nsid,
52
60
  $params = /*#__PURE__*/ main.parameters,
@@ -21,11 +21,17 @@ const main =
21
21
  )
22
22
  export { main }
23
23
 
24
- export type Params = l.InferMethodParams<typeof main>
25
- export type Input = l.InferMethodInput<typeof main>
26
- export type InputBody = l.InferMethodInputBody<typeof main>
27
- export type Output = l.InferMethodOutput<typeof main>
28
- export type OutputBody = l.InferMethodOutputBody<typeof main>
24
+ export type $Params = l.InferMethodParams<typeof main>
25
+ export type $Input<B = l.BinaryData> = l.InferMethodInput<typeof main, B>
26
+ export type $InputBody<B = l.BinaryData> = l.InferMethodInputBody<
27
+ typeof main,
28
+ B
29
+ >
30
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
31
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
32
+ typeof main,
33
+ B
34
+ >
29
35
 
30
36
  export const $lxm = /*#__PURE__*/ main.nsid,
31
37
  $params = /*#__PURE__*/ main.parameters,
@@ -28,9 +28,12 @@ const main =
28
28
  )
29
29
  export { main }
30
30
 
31
- export type Params = l.InferMethodParams<typeof main>
32
- export type Output = l.InferMethodOutput<typeof main>
33
- export type OutputBody = l.InferMethodOutputBody<typeof main>
31
+ export type $Params = l.InferMethodParams<typeof main>
32
+ export type $Output<B = l.BinaryData> = l.InferMethodOutput<typeof main, B>
33
+ export type $OutputBody<B = l.BinaryData> = l.InferMethodOutputBody<
34
+ typeof main,
35
+ B
36
+ >
34
37
 
35
38
  export const $lxm = /*#__PURE__*/ main.nsid,
36
39
  $params = main.parameters,
package/src/xrpc.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  InferInput,
5
5
  InferPayload,
6
6
  Main,
7
+ NsidString,
7
8
  Params,
8
9
  Payload,
9
10
  Procedure,
@@ -12,7 +13,7 @@ import {
12
13
  Subscription,
13
14
  getMain,
14
15
  } from '@atproto/lex-schema'
15
- import { Agent } from './agent.js'
16
+ import { Agent, AgentOptions, buildAgent } from './agent.js'
16
17
  import { XrpcFailure, asXrpcFailure } from './errors.js'
17
18
  import { XrpcResponse } from './response.js'
18
19
  import { BinaryBodyInit, CallOptions } from './types.js'
@@ -77,8 +78,7 @@ export type XrpcOptions<M extends Procedure | Query = Procedure | Query> =
77
78
  /**
78
79
  * Makes an XRPC request and throws on failure.
79
80
  *
80
- * This is the low-level function for making XRPC calls. For most use cases,
81
- * prefer using {@link Client.xrpc} which provides a more ergonomic API.
81
+ * This is the low-level function for making XRPC calls.
82
82
  *
83
83
  * @param agent - The {@link Agent} to use for making the request
84
84
  * @param ns - The lexicon method definition
@@ -88,28 +88,35 @@ export type XrpcOptions<M extends Procedure | Query = Procedure | Query> =
88
88
  *
89
89
  * @example
90
90
  * ```typescript
91
+ * const response = await xrpc('https://bsky.network', com.atproto.identity.resolveHandle, {
92
+ * params: { handle: "atproto.com" }
93
+ * })
94
+ * ```
95
+ *
96
+ * @example
97
+ * ```typescript
91
98
  * const response = await xrpc(agent, app.bsky.feed.getTimeline.main, {
92
99
  * params: { limit: 50 }
93
100
  * })
94
101
  * ```
95
102
  */
96
103
  export async function xrpc<const M extends Query | Procedure>(
97
- agent: Agent,
104
+ agentOpts: Agent | AgentOptions,
98
105
  ns: NonNullable<unknown> extends XrpcOptions<M>
99
106
  ? Main<M>
100
107
  : Restricted<'This XRPC method requires an "options" argument'>,
101
108
  ): Promise<XrpcResponse<M>>
102
109
  export async function xrpc<const M extends Query | Procedure>(
103
- agent: Agent,
110
+ agentOpts: Agent | AgentOptions,
104
111
  ns: Main<M>,
105
112
  options: XrpcOptions<M>,
106
113
  ): Promise<XrpcResponse<M>>
107
114
  export async function xrpc<const M extends Query | Procedure>(
108
- agent: Agent,
115
+ agentOpts: Agent | AgentOptions,
109
116
  ns: Main<M>,
110
117
  options: XrpcOptions<M> = {} as XrpcOptions<M>,
111
118
  ): Promise<XrpcResponse<M>> {
112
- const response = await xrpcSafe<M>(agent, ns, options)
119
+ const response = await xrpcSafe<M>(agentOpts, ns, options)
113
120
  if (response.success) return response
114
121
  else throw response
115
122
  }
@@ -141,7 +148,7 @@ export type XrpcResult<M extends Procedure | Query> =
141
148
  *
142
149
  * @example
143
150
  * ```typescript
144
- * const result = await xrpcSafe(agent, app.bsky.actor.getProfile.main, {
151
+ * const result = await xrpcSafe('https://example.com', app.bsky.actor.getProfile, {
145
152
  * params: { actor: 'alice.bsky.social' }
146
153
  * })
147
154
  *
@@ -153,24 +160,25 @@ export type XrpcResult<M extends Procedure | Query> =
153
160
  * ```
154
161
  */
155
162
  export async function xrpcSafe<const M extends Query | Procedure>(
156
- agent: Agent,
163
+ agentOpts: Agent | AgentOptions,
157
164
  ns: NonNullable<unknown> extends XrpcOptions<M>
158
165
  ? Main<M>
159
166
  : Restricted<'This XRPC method requires an "options" argument'>,
160
167
  ): Promise<XrpcResult<M>>
161
168
  export async function xrpcSafe<const M extends Query | Procedure>(
162
- agent: Agent,
169
+ agentOpts: Agent | AgentOptions,
163
170
  ns: Main<M>,
164
171
  options: XrpcOptions<M>,
165
172
  ): Promise<XrpcResult<M>>
166
173
  export async function xrpcSafe<const M extends Query | Procedure>(
167
- agent: Agent,
174
+ agentOpts: Agent | AgentOptions,
168
175
  ns: Main<M>,
169
176
  options: XrpcOptions<M> = {} as XrpcOptions<M>,
170
177
  ): Promise<XrpcResult<M>> {
171
178
  options.signal?.throwIfAborted()
172
179
  const method: M = getMain(ns)
173
180
  try {
181
+ const agent = buildAgent(agentOpts)
174
182
  const url = xrpcRequestUrl(method, options)
175
183
  const request = xrpcRequestInit(method, options)
176
184
  const response = await agent.fetchHandler(url, request)
@@ -183,14 +191,14 @@ export async function xrpcSafe<const M extends Query | Procedure>(
183
191
  function xrpcRequestUrl<M extends Procedure | Query | Subscription>(
184
192
  method: M,
185
193
  options: CallOptions & { params?: Params },
186
- ) {
187
- const path = `/xrpc/${method.nsid}`
194
+ ): `/xrpc/${NsidString}${'' | `?${string}`}` {
195
+ const path = `/xrpc/${method.nsid}` as const
188
196
 
189
197
  const queryString = method.parameters
190
198
  ?.toURLSearchParams(options.params ?? {})
191
199
  .toString()
192
200
 
193
- return queryString ? `${path}?${queryString}` : path
201
+ return queryString ? (`${path}?${queryString}` as const) : path
194
202
  }
195
203
 
196
204
  function xrpcRequestInit<T extends Procedure | Query>(