@atproto/lex-client 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/client.d.ts +24 -21
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +10 -7
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +6 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/response.d.ts +61 -6
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +59 -40
- package/dist/response.js.map +1 -1
- package/dist/types.d.ts +8 -37
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +14 -27
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +15 -6
- package/dist/util.js.map +1 -1
- package/dist/xrpc.d.ts +40 -15
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +3 -1
- package/dist/xrpc.js.map +1 -1
- package/package.json +6 -6
- package/src/client.ts +81 -31
- package/src/errors.ts +6 -4
- package/src/response.ts +186 -63
- package/src/types.ts +17 -40
- package/src/util.test.ts +11 -11
- package/src/util.ts +33 -36
- package/src/xrpc.test.ts +641 -92
- package/src/xrpc.ts +72 -26
package/dist/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;AAEA,sCAiBC;AAWD,gCAeC;AAED,0CAQC;AAuBD,0DAmBC;AAED,4CAYC;AAED,4DAqBC;AApID,SAAgB,aAAa,CAK3B,OAAiB,EAAE,QAAmB;IACtC,MAAM,QAAQ,GAAuB,EAAE,GAAG,OAAO,EAAE,CAAA;IAEnD,6EAA6E;IAC7E,8DAA8D;IAC9D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAA8B,EAAE,CAAC;QACrE,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;YAC/B,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IAED,OAAO,QAAgC,CAAA;AACzC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,UAAU,CAAC,KAAc;IACvC,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,KAAK,CAAA;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3C,IAAI,OAAO,IAAI,KAAK,UAAU,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,IAAI,CAAA;IAEpE,yEAAyE;IACzE,qCAAqC;IACrC,4GAA4G;IAE5G,MAAM,GAAG,GAAI,KAAa,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IAC9C,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QACrC,OAAO,QAAQ,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,UAAU,CAAA;IAChE,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAgB,eAAe,CAC7B,KAAQ;IAIR,OAAO,CACL,KAAK,IAAI,IAAI,IAAI,OAAQ,KAAa,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,UAAU,CAC5E,CAAA;AACH,CAAC;AAaD;;;;;;;;;GASG;AACH,SAAgB,uBAAuB,CACrC,OAAkC;IAElC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAE7C,IAAI,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/C,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CACT,yBAAyB,EACzB,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,EAAE,IAAI,EAAE,CAAC;aAClE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,IAAI,CAAC,CACd,CAAA;IACH,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAgB,gBAAgB,CAC9B,IAA+B;IAE/B,qDAAqD;IAErD,iCAAiC;IACjC,IAAI,MAAM,IAAI,cAAc,IAAI,OAAO,cAAc,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC1E,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClC,CAAC;IAED,iCAAiC;IACjC,OAAO,wBAAwB,CAAC,IAAI,CAAC,CAAA;AACvC,CAAC;AAED,SAAgB,wBAAwB,CACtC,IAA+B;IAE/B,IAAI,QAA+C,CAAA;IACnD,OAAO,IAAI,cAAc,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,UAAU;YACnB,IAAI,CAAC;gBACH,QAAQ,KAAK,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAA;gBACzC,MAAM,MAAM,GAAG,MAAM,QAAS,CAAC,IAAI,EAAE,CAAA;gBACrC,IAAI,MAAM,CAAC,IAAI;oBAAE,UAAU,CAAC,KAAK,EAAE,CAAA;;oBAC9B,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACvC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;gBACrB,QAAQ,GAAG,SAAS,CAAA;YACtB,CAAC;QACH,CAAC;QACD,KAAK,CAAC,MAAM;YACV,MAAM,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAA;YAC1B,QAAQ,GAAG,SAAS,CAAA;QACtB,CAAC;KACF,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { DidString, Service } from './types.js'\n\nexport function applyDefaults<\n TDefaults extends Record<string, unknown>,\n TOptions extends {\n [K in keyof TDefaults]?: TDefaults[K]\n },\n>(options: TOptions, defaults: TDefaults): TOptions & TDefaults {\n const combined: Partial<TDefaults> = { ...options }\n\n // @NOTE We make sure that options with an explicit `undefined` value get the\n // default, since spreading doesn't override with `undefined`.\n for (const key of Object.keys(defaults) as (keyof typeof defaults)[]) {\n if (options[key] === undefined) {\n combined[key] = defaults[key]\n }\n }\n\n return combined as TOptions & TDefaults\n}\n\n/**\n * Type guard to check if a value is {@link Blob}-like.\n *\n * Handles both native Blobs and polyfilled Blob implementations\n * (e.g., fetch-blob from node-fetch).\n *\n * @param value - The value to check\n * @returns `true` if the value is a Blob or Blob-like object\n */\nexport function isBlobLike(value: unknown): value is Blob {\n if (value == null) return false\n if (typeof value !== 'object') return false\n if (typeof Blob === 'function' && value instanceof Blob) return true\n\n // Support for Blobs provided by libraries that don't use the native Blob\n // (e.g. fetch-blob from node-fetch).\n // https://github.com/node-fetch/fetch-blob/blob/a1a182e5978811407bef4ea1632b517567dda01f/index.js#L233-L244\n\n const tag = (value as any)[Symbol.toStringTag]\n if (tag === 'Blob' || tag === 'File') {\n return 'stream' in value && typeof value.stream === 'function'\n }\n\n return false\n}\n\nexport function isAsyncIterable<T>(\n value: T,\n): value is unknown extends T\n ? T & AsyncIterable<unknown>\n : Extract<T, AsyncIterable<any>> {\n return (\n value != null && typeof (value as any)[Symbol.asyncIterator] === 'function'\n )\n}\n\nexport type XrpcRequestHeadersOptions = {\n /** Additional HTTP headers to include in the request. */\n headers?: HeadersInit\n\n /** Labeler DIDs to request labels from for content moderation. */\n labelers?: Iterable<DidString>\n\n /** Service proxy identifier for routing requests through a specific service. */\n service?: Service\n}\n\n/**\n * Builds HTTP headers for AT Protocol requests.\n *\n * Adds the following headers when applicable:\n * - `atproto-proxy`: Service routing header (if service is specified)\n * - `atproto-accept-labelers`: Comma-separated list of labeler DIDs\n *\n * @see {@link XrpcRequestHeadersOptions}\n * @returns A new Headers object with AT Protocol headers added\n */\nexport function buildXrpcRequestHeaders(\n options: XrpcRequestHeadersOptions,\n): Headers {\n const headers = new Headers(options?.headers)\n\n if (options.service && !headers.has('atproto-proxy')) {\n headers.set('atproto-proxy', options.service)\n }\n\n if (options.labelers) {\n headers.set(\n 'atproto-accept-labelers',\n [...options.labelers, headers.get('atproto-accept-labelers')?.trim()]\n .filter(Boolean)\n .join(', '),\n )\n }\n\n return headers\n}\n\nexport function toReadableStream(\n data: AsyncIterable<Uint8Array>,\n): ReadableStream<Uint8Array> {\n // Use the native ReadableStream.from() if available.\n\n /* v8 ignore next -- @preserve */\n if ('from' in ReadableStream && typeof ReadableStream.from === 'function') {\n return ReadableStream.from(data)\n }\n\n /* v8 ignore next -- @preserve */\n return toReadableStreamPonyfill(data)\n}\n\nexport function toReadableStreamPonyfill(\n data: AsyncIterable<Uint8Array>,\n): ReadableStream<Uint8Array> {\n let iterator: AsyncIterator<Uint8Array> | undefined\n return new ReadableStream({\n async pull(controller) {\n try {\n iterator ??= data[Symbol.asyncIterator]()\n const result = await iterator!.next()\n if (result.done) controller.close()\n else controller.enqueue(result.value)\n } catch (err) {\n controller.error(err)\n iterator = undefined\n }\n },\n async cancel() {\n await iterator?.return?.()\n iterator = undefined\n },\n })\n}\n"]}
|
package/dist/xrpc.d.ts
CHANGED
|
@@ -1,40 +1,49 @@
|
|
|
1
1
|
import { InferInput, InferPayload, Main, Params, Procedure, Query, Restricted, Subscription } from '@atproto/lex-schema';
|
|
2
2
|
import { Agent, AgentOptions } from './agent.js';
|
|
3
3
|
import { XrpcFailure } from './errors.js';
|
|
4
|
-
import { XrpcResponse } from './response.js';
|
|
5
|
-
import { BinaryBodyInit
|
|
6
|
-
|
|
7
|
-
params?: P;
|
|
8
|
-
} : {
|
|
9
|
-
params: P;
|
|
10
|
-
};
|
|
4
|
+
import { XrpcResponse, XrpcResponseOptions } from './response.js';
|
|
5
|
+
import { BinaryBodyInit } from './types.js';
|
|
6
|
+
import { XrpcRequestHeadersOptions } from './util.js';
|
|
11
7
|
/**
|
|
12
8
|
* The query/path parameters type for an XRPC method, inferred from its schema.
|
|
13
9
|
*
|
|
14
10
|
* @typeParam M - The XRPC method type (Procedure, Query, or Subscription)
|
|
15
11
|
*/
|
|
16
12
|
export type XrpcRequestParams<M extends Procedure | Query | Subscription> = InferInput<M['parameters']>;
|
|
13
|
+
type XrpcRequestParamsOptions<P extends Params> = NonNullable<unknown> extends P ? {
|
|
14
|
+
params?: P;
|
|
15
|
+
} : {
|
|
16
|
+
params: P;
|
|
17
|
+
};
|
|
17
18
|
type XrpcRequestPayload<M extends Procedure | Query> = M extends Procedure ? InferPayload<M['input'], BinaryBodyInit> : undefined;
|
|
18
|
-
type
|
|
19
|
+
type XrpcRequestPayloadOptions<TPayload> = TPayload extends {
|
|
19
20
|
body: infer B;
|
|
20
21
|
encoding: infer E;
|
|
21
22
|
} ? {
|
|
22
23
|
body: B;
|
|
24
|
+
/**
|
|
25
|
+
* mime type hint for binary bodies
|
|
26
|
+
*
|
|
27
|
+
* Only needed for endpoints that accept binary input (e.g. file uploads)
|
|
28
|
+
* when the body is a Blob-like object without a type (e.g. fetch-blob's
|
|
29
|
+
* Blob). If the body is a Blob-like object with a type, that type will be
|
|
30
|
+
* used as the content-type header instead of this option.
|
|
31
|
+
*
|
|
32
|
+
* @default "application/octet-stream"
|
|
33
|
+
*/
|
|
23
34
|
encoding?: E;
|
|
24
35
|
} : {
|
|
25
36
|
body?: undefined;
|
|
26
37
|
encoding?: undefined;
|
|
27
38
|
};
|
|
28
39
|
/**
|
|
29
|
-
* Options for making an XRPC request.
|
|
40
|
+
* Options for making an XRPC request, based on the method schema.
|
|
30
41
|
*
|
|
31
|
-
* Combines {@link
|
|
32
|
-
*
|
|
42
|
+
* Combines {@link XrpcRequestOptions} and {@link XrpcResponseOptions} with
|
|
43
|
+
* method-specific params and body requirements. The type system ensures
|
|
44
|
+
* required params/body are provided based on the method schema.
|
|
33
45
|
*
|
|
34
46
|
* @typeParam M - The XRPC method type (Procedure or Query)
|
|
35
|
-
* @see {@link CallOptions} for general request options like signal and validateRequest
|
|
36
|
-
* @see {@link XrpcParamsOptions} for method-specific query parameters
|
|
37
|
-
* @see {@link XrpcInputOptions} for method-specific body and encoding requirements
|
|
38
47
|
*
|
|
39
48
|
* @example Query with params
|
|
40
49
|
* ```typescript
|
|
@@ -50,7 +59,23 @@ type XrpcInputOptions<In> = In extends {
|
|
|
50
59
|
* }
|
|
51
60
|
* ```
|
|
52
61
|
*/
|
|
53
|
-
export type XrpcOptions<M extends Procedure | Query = Procedure | Query> =
|
|
62
|
+
export type XrpcOptions<M extends Procedure | Query = Procedure | Query> = XrpcRequestOptions<M> & XrpcResponseOptions;
|
|
63
|
+
export type XrpcRequestOptions<M extends Procedure | Query = Procedure | Query> = XrpcRequestProcessingOptions & XrpcRequestHeadersOptions & XrpcRequestPayloadOptions<XrpcRequestPayload<M>> & XrpcRequestParamsOptions<XrpcRequestParams<M>>;
|
|
64
|
+
export type XrpcRequestProcessingOptions = {
|
|
65
|
+
/**
|
|
66
|
+
* AbortSignal to cancel the request.
|
|
67
|
+
*/
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
/**
|
|
70
|
+
* Whether to validate the request against the method's input schema. Enabling
|
|
71
|
+
* this can help catch errors early but may have a performance cost. This
|
|
72
|
+
* would typically only be set to `true` in development or debugging
|
|
73
|
+
* scenarios.
|
|
74
|
+
*
|
|
75
|
+
* @default false
|
|
76
|
+
*/
|
|
77
|
+
validateRequest?: boolean;
|
|
78
|
+
};
|
|
54
79
|
/**
|
|
55
80
|
* Makes an XRPC request and throws on failure.
|
|
56
81
|
*
|
package/dist/xrpc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xrpc.d.ts","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,UAAU,EACV,YAAY,EACZ,IAAI,EAEJ,MAAM,EAEN,SAAS,EACT,KAAK,EACL,UAAU,EACV,YAAY,EAEb,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAc,MAAM,YAAY,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAiC,MAAM,aAAa,CAAA;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"xrpc.d.ts","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"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"}
|
package/dist/xrpc.js
CHANGED
|
@@ -34,13 +34,15 @@ async function xrpcSafe(agentOpts, ns, options = {}) {
|
|
|
34
34
|
}
|
|
35
35
|
function xrpcRequestUrl(method, options) {
|
|
36
36
|
const path = `/xrpc/${method.nsid}`;
|
|
37
|
+
// @NOTE param.toURLSearchParams() will always validate the params in order to
|
|
38
|
+
// apply default values, so we can't disable it with options.validateRequest
|
|
37
39
|
const queryString = method.parameters
|
|
38
40
|
?.toURLSearchParams(options.params ?? {})
|
|
39
41
|
.toString();
|
|
40
42
|
return queryString ? `${path}?${queryString}` : path;
|
|
41
43
|
}
|
|
42
44
|
function xrpcRequestInit(schema, options) {
|
|
43
|
-
const headers = (0, util_js_1.
|
|
45
|
+
const headers = (0, util_js_1.buildXrpcRequestHeaders)(options);
|
|
44
46
|
// Tell the server what type of response we're expecting
|
|
45
47
|
if (schema.output.encoding) {
|
|
46
48
|
headers.set('accept', schema.output.encoding);
|
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 } 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).catch((cause) => {\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: 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"]}
|
|
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 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((cause) => {\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 (\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.
|
|
3
|
+
"version": "0.0.17",
|
|
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.
|
|
41
|
-
"@atproto/lex-json": "^0.0.
|
|
42
|
-
"@atproto/lex-schema": "^0.0.
|
|
40
|
+
"@atproto/lex-data": "^0.0.14",
|
|
41
|
+
"@atproto/lex-json": "^0.0.14",
|
|
42
|
+
"@atproto/lex-schema": "^0.0.16"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"vitest": "^4.0.16",
|
|
46
|
-
"@atproto/lex-cbor": "^0.0.
|
|
47
|
-
"@atproto/lex-builder": "^0.0.
|
|
46
|
+
"@atproto/lex-cbor": "^0.0.15",
|
|
47
|
+
"@atproto/lex-builder": "^0.0.19"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"prebuild": "node ./scripts/lex-build.mjs",
|
package/src/client.ts
CHANGED
|
@@ -20,10 +20,24 @@ import {
|
|
|
20
20
|
import { Agent, AgentOptions, buildAgent } from './agent.js'
|
|
21
21
|
import { XrpcFailure } from './errors.js'
|
|
22
22
|
import { com } from './lexicons/index.js'
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
import {
|
|
24
|
+
XrpcResponse,
|
|
25
|
+
XrpcResponseBody,
|
|
26
|
+
XrpcResponseOptions,
|
|
27
|
+
} from './response.js'
|
|
28
|
+
import { BinaryBodyInit, Service } from './types.js'
|
|
29
|
+
import {
|
|
30
|
+
XrpcRequestHeadersOptions,
|
|
31
|
+
applyDefaults,
|
|
32
|
+
buildXrpcRequestHeaders,
|
|
33
|
+
} from './util.js'
|
|
34
|
+
import {
|
|
35
|
+
XrpcOptions,
|
|
36
|
+
XrpcRequestParams,
|
|
37
|
+
XrpcRequestProcessingOptions,
|
|
38
|
+
xrpc,
|
|
39
|
+
xrpcSafe,
|
|
40
|
+
} from './xrpc.js'
|
|
27
41
|
|
|
28
42
|
export type {
|
|
29
43
|
AtIdentifierString,
|
|
@@ -58,13 +72,13 @@ export type {
|
|
|
58
72
|
* }
|
|
59
73
|
* ```
|
|
60
74
|
*/
|
|
61
|
-
export type ClientOptions =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
|
|
75
|
+
export type ClientOptions = XrpcRequestHeadersOptions &
|
|
76
|
+
Pick<XrpcRequestProcessingOptions, 'validateRequest'> &
|
|
77
|
+
XrpcResponseOptions
|
|
78
|
+
|
|
79
|
+
export type ActionOptions = {
|
|
80
|
+
/** AbortSignal to cancel the request. */
|
|
81
|
+
signal?: AbortSignal
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
/**
|
|
@@ -87,7 +101,7 @@ export type ClientOptions = {
|
|
|
87
101
|
export type Action<I = any, O = any> = (
|
|
88
102
|
client: Client,
|
|
89
103
|
input: I,
|
|
90
|
-
options:
|
|
104
|
+
options: ActionOptions,
|
|
91
105
|
) => O | Promise<O>
|
|
92
106
|
|
|
93
107
|
/**
|
|
@@ -109,7 +123,10 @@ export type InferActionOutput<A extends Action> =
|
|
|
109
123
|
*
|
|
110
124
|
* @see {@link Client.createRecord}
|
|
111
125
|
*/
|
|
112
|
-
export type CreateRecordOptions =
|
|
126
|
+
export type CreateRecordOptions = Omit<
|
|
127
|
+
XrpcOptions<typeof com.atproto.repo.createRecord.main>,
|
|
128
|
+
'body'
|
|
129
|
+
> & {
|
|
113
130
|
/** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
|
|
114
131
|
repo?: AtIdentifierString
|
|
115
132
|
/** Compare-and-swap on the repo commit. If specified, must match current commit. */
|
|
@@ -123,7 +140,10 @@ export type CreateRecordOptions = CallOptions & {
|
|
|
123
140
|
*
|
|
124
141
|
* @see {@link Client.deleteRecord}
|
|
125
142
|
*/
|
|
126
|
-
export type DeleteRecordOptions =
|
|
143
|
+
export type DeleteRecordOptions = Omit<
|
|
144
|
+
XrpcOptions<typeof com.atproto.repo.deleteRecord.main>,
|
|
145
|
+
'params'
|
|
146
|
+
> & {
|
|
127
147
|
/** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
|
|
128
148
|
repo?: AtIdentifierString
|
|
129
149
|
/** Compare-and-swap on the repo commit. If specified, must match current commit. */
|
|
@@ -137,7 +157,10 @@ export type DeleteRecordOptions = CallOptions & {
|
|
|
137
157
|
*
|
|
138
158
|
* @see {@link Client.getRecord}
|
|
139
159
|
*/
|
|
140
|
-
export type GetRecordOptions =
|
|
160
|
+
export type GetRecordOptions = Omit<
|
|
161
|
+
XrpcOptions<typeof com.atproto.repo.getRecord.main>,
|
|
162
|
+
'params'
|
|
163
|
+
> & {
|
|
141
164
|
/** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
|
|
142
165
|
repo?: AtIdentifierString
|
|
143
166
|
}
|
|
@@ -147,7 +170,10 @@ export type GetRecordOptions = CallOptions & {
|
|
|
147
170
|
*
|
|
148
171
|
* @see {@link Client.putRecord}
|
|
149
172
|
*/
|
|
150
|
-
export type PutRecordOptions =
|
|
173
|
+
export type PutRecordOptions = Omit<
|
|
174
|
+
XrpcOptions<typeof com.atproto.repo.putRecord.main>,
|
|
175
|
+
'body'
|
|
176
|
+
> & {
|
|
151
177
|
/** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
|
|
152
178
|
repo?: AtIdentifierString
|
|
153
179
|
/** Compare-and-swap on the repo commit. If specified, must match current commit. */
|
|
@@ -163,7 +189,10 @@ export type PutRecordOptions = CallOptions & {
|
|
|
163
189
|
*
|
|
164
190
|
* @see {@link Client.listRecords}
|
|
165
191
|
*/
|
|
166
|
-
export type ListRecordsOptions =
|
|
192
|
+
export type ListRecordsOptions = Omit<
|
|
193
|
+
XrpcOptions<typeof com.atproto.repo.listRecords.main>,
|
|
194
|
+
'params'
|
|
195
|
+
> & {
|
|
167
196
|
/** Repository identifier (DID or handle). Defaults to authenticated user's DID. */
|
|
168
197
|
repo?: AtIdentifierString
|
|
169
198
|
/** Maximum number of records to return. */
|
|
@@ -174,6 +203,16 @@ export type ListRecordsOptions = CallOptions & {
|
|
|
174
203
|
reverse?: boolean
|
|
175
204
|
}
|
|
176
205
|
|
|
206
|
+
export type UploadBlobOptions = Omit<
|
|
207
|
+
XrpcOptions<typeof com.atproto.repo.uploadBlob.main>,
|
|
208
|
+
'body'
|
|
209
|
+
>
|
|
210
|
+
|
|
211
|
+
export type GetBlobOptions = Omit<
|
|
212
|
+
XrpcOptions<typeof com.atproto.sync.getBlob.main>,
|
|
213
|
+
'params'
|
|
214
|
+
>
|
|
215
|
+
|
|
177
216
|
export type RecordKeyOptions<
|
|
178
217
|
T extends RecordSchema,
|
|
179
218
|
AlsoOptionalWhenRecordKeyIs extends LexiconRecordKey = never,
|
|
@@ -315,11 +354,22 @@ export class Client implements Agent {
|
|
|
315
354
|
/** Set of labeler DIDs specific to this client instance. */
|
|
316
355
|
public readonly labelers: Set<DidString>
|
|
317
356
|
|
|
357
|
+
public readonly xrpcDefaults: {
|
|
358
|
+
readonly validateRequest: boolean
|
|
359
|
+
readonly validateResponse: boolean
|
|
360
|
+
readonly strictResponseProcessing: boolean
|
|
361
|
+
}
|
|
362
|
+
|
|
318
363
|
constructor(agent: Agent | AgentOptions, options: ClientOptions = {}) {
|
|
319
364
|
this.agent = buildAgent(agent)
|
|
320
365
|
this.service = options.service
|
|
321
366
|
this.labelers = new Set(options.labelers)
|
|
322
367
|
this.headers = new Headers(options.headers)
|
|
368
|
+
this.xrpcDefaults = Object.freeze({
|
|
369
|
+
validateRequest: options.validateRequest ?? false,
|
|
370
|
+
validateResponse: options.validateResponse ?? true,
|
|
371
|
+
strictResponseProcessing: options.strictResponseProcessing ?? true,
|
|
372
|
+
})
|
|
323
373
|
}
|
|
324
374
|
|
|
325
375
|
/**
|
|
@@ -392,7 +442,7 @@ export class Client implements Agent {
|
|
|
392
442
|
path: `/${string}`,
|
|
393
443
|
init: RequestInit,
|
|
394
444
|
): Promise<Response> {
|
|
395
|
-
const headers =
|
|
445
|
+
const headers = buildXrpcRequestHeaders({
|
|
396
446
|
headers: init.headers,
|
|
397
447
|
service: this.service,
|
|
398
448
|
labelers: [
|
|
@@ -454,7 +504,7 @@ export class Client implements Agent {
|
|
|
454
504
|
ns: Main<M>,
|
|
455
505
|
options: XrpcOptions<M> = {} as XrpcOptions<M>,
|
|
456
506
|
): Promise<XrpcResponse<M>> {
|
|
457
|
-
return xrpc(this, ns, options)
|
|
507
|
+
return xrpc(this, ns, applyDefaults(options, this.xrpcDefaults))
|
|
458
508
|
}
|
|
459
509
|
|
|
460
510
|
/**
|
|
@@ -493,7 +543,7 @@ export class Client implements Agent {
|
|
|
493
543
|
ns: Main<M>,
|
|
494
544
|
options: XrpcOptions<M> = {} as XrpcOptions<M>,
|
|
495
545
|
): Promise<XrpcResponse<M> | XrpcFailure<M>> {
|
|
496
|
-
return xrpcSafe(this, ns, options)
|
|
546
|
+
return xrpcSafe(this, ns, applyDefaults(options, this.xrpcDefaults))
|
|
497
547
|
}
|
|
498
548
|
|
|
499
549
|
/**
|
|
@@ -649,14 +699,8 @@ export class Client implements Agent {
|
|
|
649
699
|
* console.log(response.body.blob) // Use this ref in records
|
|
650
700
|
* ```
|
|
651
701
|
*/
|
|
652
|
-
async uploadBlob(
|
|
653
|
-
body
|
|
654
|
-
options?: CallOptions & { encoding?: `${string}/${string}` },
|
|
655
|
-
) {
|
|
656
|
-
return this.xrpc(com.atproto.repo.uploadBlob.main, {
|
|
657
|
-
...options,
|
|
658
|
-
body,
|
|
659
|
-
})
|
|
702
|
+
async uploadBlob(body: BinaryBodyInit, options?: UploadBlobOptions) {
|
|
703
|
+
return this.xrpc(com.atproto.repo.uploadBlob.main, { ...options, body })
|
|
660
704
|
}
|
|
661
705
|
|
|
662
706
|
/**
|
|
@@ -666,7 +710,7 @@ export class Client implements Agent {
|
|
|
666
710
|
* @param cid - The CID of the blob
|
|
667
711
|
* @param options - Call options
|
|
668
712
|
*/
|
|
669
|
-
async getBlob(did: DidString, cid: CidString, options?:
|
|
713
|
+
async getBlob(did: DidString, cid: CidString, options?: GetBlobOptions) {
|
|
670
714
|
return this.xrpc(com.atproto.sync.getBlob.main, {
|
|
671
715
|
...options,
|
|
672
716
|
params: { did, cid },
|
|
@@ -723,7 +767,13 @@ export class Client implements Agent {
|
|
|
723
767
|
: T extends Query
|
|
724
768
|
? XrpcRequestParams<T>
|
|
725
769
|
: never,
|
|
726
|
-
options?:
|
|
770
|
+
options?: T extends Action
|
|
771
|
+
? ActionOptions
|
|
772
|
+
: T extends Procedure
|
|
773
|
+
? Omit<XrpcOptions<T>, 'body'>
|
|
774
|
+
: T extends Query
|
|
775
|
+
? Omit<XrpcOptions<T>, 'params'>
|
|
776
|
+
: never,
|
|
727
777
|
): Promise<
|
|
728
778
|
T extends Action
|
|
729
779
|
? InferActionOutput<T>
|
|
@@ -736,7 +786,7 @@ export class Client implements Agent {
|
|
|
736
786
|
public async call(
|
|
737
787
|
ns: Main<Action> | Main<Procedure> | Main<Query>,
|
|
738
788
|
arg?: LexValue | Params,
|
|
739
|
-
options:
|
|
789
|
+
options: ActionOptions = {},
|
|
740
790
|
): Promise<unknown> {
|
|
741
791
|
const method = getMain(ns)
|
|
742
792
|
|
package/src/errors.ts
CHANGED
|
@@ -9,12 +9,14 @@ import {
|
|
|
9
9
|
} from '@atproto/lex-schema'
|
|
10
10
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
11
11
|
import { Agent } from './agent.js'
|
|
12
|
-
import {
|
|
12
|
+
import { XrpcUnknownResponsePayload } from './types.js'
|
|
13
13
|
import {
|
|
14
14
|
WWWAuthenticate,
|
|
15
15
|
parseWWWAuthenticateHeader,
|
|
16
16
|
} from './www-authenticate.js'
|
|
17
17
|
|
|
18
|
+
export type { XrpcUnknownResponsePayload }
|
|
19
|
+
|
|
18
20
|
export type DownstreamError<N extends LexErrorCode = LexErrorCode> = {
|
|
19
21
|
status: number
|
|
20
22
|
headers?: Headers
|
|
@@ -68,7 +70,7 @@ export type XrpcErrorPayload<N extends LexErrorCode = LexErrorCode> = {
|
|
|
68
70
|
* This function checks whether a given payload matches this schema.
|
|
69
71
|
*/
|
|
70
72
|
export function isXrpcErrorPayload(
|
|
71
|
-
payload:
|
|
73
|
+
payload: XrpcUnknownResponsePayload | null | undefined,
|
|
72
74
|
): payload is XrpcErrorPayload {
|
|
73
75
|
return (
|
|
74
76
|
payload != null &&
|
|
@@ -292,7 +294,7 @@ export class XrpcUpstreamError<
|
|
|
292
294
|
constructor(
|
|
293
295
|
method: M,
|
|
294
296
|
readonly response: Response,
|
|
295
|
-
readonly payload:
|
|
297
|
+
readonly payload: XrpcUnknownResponsePayload | null = null,
|
|
296
298
|
message: string = `Unexpected upstream XRPC response`,
|
|
297
299
|
options?: ErrorOptions,
|
|
298
300
|
) {
|
|
@@ -331,7 +333,7 @@ export class XrpcInvalidResponseError<
|
|
|
331
333
|
constructor(
|
|
332
334
|
method: M,
|
|
333
335
|
response: Response,
|
|
334
|
-
payload:
|
|
336
|
+
payload: XrpcUnknownResponsePayload,
|
|
335
337
|
readonly cause: LexValidationError,
|
|
336
338
|
) {
|
|
337
339
|
super(method, response, payload, `Invalid response: ${cause.message}`, {
|