@atproto/lex-client 0.0.4 → 0.0.5
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 +27 -0
- package/dist/agent.d.ts +9 -8
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js.map +1 -1
- package/dist/client.d.ts +32 -96
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +31 -31
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts +7 -7
- package/dist/lexicons/com/atproto/repo/createRecord.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/createRecord.defs.js +3 -5
- package/dist/lexicons/com/atproto/repo/createRecord.defs.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts +7 -7
- package/dist/lexicons/com/atproto/repo/deleteRecord.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/deleteRecord.defs.js +3 -5
- package/dist/lexicons/com/atproto/repo/deleteRecord.defs.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts +5 -6
- package/dist/lexicons/com/atproto/repo/getRecord.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/getRecord.defs.js +3 -5
- package/dist/lexicons/com/atproto/repo/getRecord.defs.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts +5 -6
- package/dist/lexicons/com/atproto/repo/listRecords.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/listRecords.defs.js +3 -5
- package/dist/lexicons/com/atproto/repo/listRecords.defs.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts +7 -7
- package/dist/lexicons/com/atproto/repo/putRecord.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/putRecord.defs.js +3 -5
- package/dist/lexicons/com/atproto/repo/putRecord.defs.js.map +1 -1
- package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts +7 -7
- package/dist/lexicons/com/atproto/repo/uploadBlob.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/repo/uploadBlob.defs.js +3 -5
- package/dist/lexicons/com/atproto/repo/uploadBlob.defs.js.map +1 -1
- package/dist/lexicons/com/atproto/sync/getBlob.d.ts +3 -0
- package/dist/lexicons/com/atproto/sync/getBlob.d.ts.map +1 -0
- package/dist/lexicons/com/atproto/sync/getBlob.defs.d.ts +25 -0
- package/dist/lexicons/com/atproto/sync/getBlob.defs.d.ts.map +1 -0
- package/dist/lexicons/com/atproto/sync/getBlob.defs.js +27 -0
- package/dist/lexicons/com/atproto/sync/getBlob.defs.js.map +1 -0
- package/dist/lexicons/com/atproto/sync/getBlob.js +10 -0
- package/dist/lexicons/com/atproto/sync/getBlob.js.map +1 -0
- package/dist/lexicons/com/atproto/sync.d.ts +2 -0
- package/dist/lexicons/com/atproto/sync.d.ts.map +1 -0
- package/dist/lexicons/com/atproto/sync.js +9 -0
- package/dist/lexicons/com/atproto/sync.js.map +1 -0
- package/dist/lexicons/com/atproto.d.ts +1 -0
- package/dist/lexicons/com/atproto.d.ts.map +1 -1
- package/dist/lexicons/com/atproto.js +2 -1
- package/dist/lexicons/com/atproto.js.map +1 -1
- package/dist/lexicons.d.ts +2 -0
- package/dist/lexicons.d.ts.map +1 -0
- package/dist/lexicons.js +6 -0
- package/dist/lexicons.js.map +1 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +14 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +65 -0
- package/dist/util.js.map +1 -0
- package/dist/xrpc-error.d.ts +87 -0
- package/dist/xrpc-error.d.ts.map +1 -0
- package/dist/xrpc-error.js +127 -0
- package/dist/xrpc-error.js.map +1 -0
- package/dist/xrpc-response.d.ts +35 -0
- package/dist/xrpc-response.d.ts.map +1 -0
- package/dist/xrpc-response.js +140 -0
- package/dist/xrpc-response.js.map +1 -0
- package/dist/xrpc.d.ts +29 -32
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +119 -125
- package/dist/xrpc.js.map +1 -1
- package/package.json +6 -6
- package/src/agent.ts +12 -12
- package/src/client.ts +92 -77
- package/src/index.ts +0 -2
- package/src/lexicons/com/atproto/repo/createRecord.defs.ts +9 -8
- package/src/lexicons/com/atproto/repo/deleteRecord.defs.ts +9 -8
- package/src/lexicons/com/atproto/repo/getRecord.defs.ts +7 -7
- package/src/lexicons/com/atproto/repo/listRecords.defs.ts +7 -6
- package/src/lexicons/com/atproto/repo/putRecord.defs.ts +9 -8
- package/src/lexicons/com/atproto/repo/uploadBlob.defs.ts +9 -8
- package/src/lexicons/com/atproto/sync/getBlob.defs.ts +37 -0
- package/src/lexicons/com/atproto/sync/getBlob.ts +6 -0
- package/src/lexicons/com/atproto/sync.ts +5 -0
- package/src/lexicons/com/atproto.ts +1 -0
- package/src/lexicons.ts +1 -0
- package/src/types.ts +27 -0
- package/src/util.ts +84 -0
- package/src/xrpc-error.ts +195 -0
- package/src/xrpc-response.ts +213 -0
- package/src/xrpc.ts +209 -220
- package/dist/error.d.ts +0 -66
- package/dist/error.d.ts.map +0 -1
- package/dist/error.js +0 -100
- package/dist/error.js.map +0 -1
- package/dist/response.d.ts +0 -21
- package/dist/response.d.ts.map +0 -1
- package/dist/response.js +0 -31
- package/dist/response.js.map +0 -1
- package/src/error.ts +0 -145
- package/src/response.ts +0 -42
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.XrpcResponse = void 0;
|
|
4
|
+
const lex_json_1 = require("@atproto/lex-json");
|
|
5
|
+
const xrpc_error_js_1 = require("./xrpc-error.js");
|
|
6
|
+
/**
|
|
7
|
+
* Small container for XRPC response data.
|
|
8
|
+
*
|
|
9
|
+
* @implements {ResultSuccess<XrpcResponse<M>>} for convenience in result handling contexts.
|
|
10
|
+
*/
|
|
11
|
+
class XrpcResponse {
|
|
12
|
+
method;
|
|
13
|
+
status;
|
|
14
|
+
headers;
|
|
15
|
+
payload;
|
|
16
|
+
/** @see {@link ResultSuccess.success} */
|
|
17
|
+
success = true;
|
|
18
|
+
/** @see {@link ResultSuccess.value} */
|
|
19
|
+
get value() {
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
constructor(method, status, headers, payload) {
|
|
23
|
+
this.method = method;
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.headers = headers;
|
|
26
|
+
this.payload = payload;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether the response payload was parsed as {@link LexValue} (`true`) or is
|
|
30
|
+
* in binary form {@link Uint8Array} (`false`).
|
|
31
|
+
*/
|
|
32
|
+
get isParsed() {
|
|
33
|
+
return this.encoding === 'application/json' && shouldParse(this.method);
|
|
34
|
+
}
|
|
35
|
+
get encoding() {
|
|
36
|
+
return this.payload?.encoding;
|
|
37
|
+
}
|
|
38
|
+
get body() {
|
|
39
|
+
return this.payload?.body;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @throws {XrpcInvalidResponseError} when the response is invalid according
|
|
43
|
+
* to the method schema.
|
|
44
|
+
*/
|
|
45
|
+
static async fromFetchResponse(method, response, options) {
|
|
46
|
+
// @NOTE The body MUST either be read or canceled to avoid resource leaks.
|
|
47
|
+
// Since nothing should cause an exception before "readPayload" is
|
|
48
|
+
// called, we can safely not use a try/finally here.
|
|
49
|
+
// @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here
|
|
50
|
+
if (response.status < 200 || response.status >= 300) {
|
|
51
|
+
// Always parse json for error responses
|
|
52
|
+
const payload = await readPayload(response, { parse: true });
|
|
53
|
+
if (response.status >= 400 && (0, xrpc_error_js_1.isXrpcErrorPayload)(payload)) {
|
|
54
|
+
throw new xrpc_error_js_1.XrpcResponseError(method, response.status, response.headers, payload);
|
|
55
|
+
}
|
|
56
|
+
throw new xrpc_error_js_1.XrpcInvalidResponseError(response.status >= 500
|
|
57
|
+
? `Upstream server encountered an error`
|
|
58
|
+
: response.status >= 400
|
|
59
|
+
? `Upstream server returned an invalid response payload`
|
|
60
|
+
: `Upstream server returned an invalid status code`, response, payload);
|
|
61
|
+
}
|
|
62
|
+
// Only parse json if the schema expects it
|
|
63
|
+
const payload = await readPayload(response, {
|
|
64
|
+
parse: shouldParse(method),
|
|
65
|
+
});
|
|
66
|
+
// Response is successful (2xx). Validate payload (data and encoding) against schema.
|
|
67
|
+
if (method.output.encoding == null) {
|
|
68
|
+
// Schema expects no payload
|
|
69
|
+
if (payload) {
|
|
70
|
+
throw new xrpc_error_js_1.XrpcInvalidResponseError(`Expected response with no body, got ${payload.encoding}`, response, payload);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Schema expects a payload
|
|
75
|
+
if (!payload || !method.output.matchesEncoding(payload.encoding)) {
|
|
76
|
+
throw new xrpc_error_js_1.XrpcInvalidResponseError(payload
|
|
77
|
+
? `Expected ${method.output.encoding} response, got ${payload.encoding}`
|
|
78
|
+
: `Expected non-empty response with content-type ${method.output.encoding}`, response, payload);
|
|
79
|
+
}
|
|
80
|
+
// Assert valid response body.
|
|
81
|
+
if (method.output.schema && options?.validateResponse !== false) {
|
|
82
|
+
const result = method.output.schema.safeParse(payload.body, {
|
|
83
|
+
allowTransform: false,
|
|
84
|
+
});
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
throw new xrpc_error_js_1.XrpcInvalidResponseError(`Response validation failed: ${result.reason.message}`, response, payload, { cause: result.reason });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return new XrpcResponse(method, response.status, response.headers, payload);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.XrpcResponse = XrpcResponse;
|
|
94
|
+
function shouldParse(method) {
|
|
95
|
+
return method.output.encoding === 'application/json';
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* @note this function always consumes the response body
|
|
99
|
+
*/
|
|
100
|
+
async function readPayload(response, options) {
|
|
101
|
+
// @TODO Should we limit the maximum response size here (this could also be
|
|
102
|
+
// done by the FetchHandler)?
|
|
103
|
+
const encoding = response.headers
|
|
104
|
+
.get('content-type')
|
|
105
|
+
?.split(';')[0]
|
|
106
|
+
.trim()
|
|
107
|
+
.toLowerCase();
|
|
108
|
+
// Response content-type is undefined
|
|
109
|
+
if (!encoding) {
|
|
110
|
+
// If the body is empty, return null (= no payload)
|
|
111
|
+
const body = await response.arrayBuffer();
|
|
112
|
+
if (body.byteLength === 0)
|
|
113
|
+
return null;
|
|
114
|
+
// If we got data despite no content-type, treat it as binary
|
|
115
|
+
return {
|
|
116
|
+
encoding: 'application/octet-stream',
|
|
117
|
+
body: new Uint8Array(body),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (options?.parse && encoding === 'application/json') {
|
|
121
|
+
// @NOTE It might be worth returning the raw bytes here (Uint8Array) and
|
|
122
|
+
// perform the lex parsing using cborg/json, allowing to do
|
|
123
|
+
// bytes->LexValue in one step instead of bytes->text->JSON->LexValue.
|
|
124
|
+
// This would require adding encode/decode utilities to lex-json (similar
|
|
125
|
+
// to @ipld/dag-json)
|
|
126
|
+
const text = await response.text();
|
|
127
|
+
try {
|
|
128
|
+
// @NOTE Using `lexParse(text)` (instead of `jsonToLex(json)`) here as
|
|
129
|
+
// using a reviver function during JSON.parse should be faster than
|
|
130
|
+
// parsing to JSON then converting to Lex (?)
|
|
131
|
+
// @TODO verify statement above
|
|
132
|
+
return { encoding, body: (0, lex_json_1.lexParse)(text) };
|
|
133
|
+
}
|
|
134
|
+
catch (cause) {
|
|
135
|
+
throw new xrpc_error_js_1.XrpcInvalidResponseError('Invalid JSON response body', response, null, { cause });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { encoding, body: new Uint8Array(await response.arrayBuffer()) };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=xrpc-response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xrpc-response.js","sourceRoot":"","sources":["../src/xrpc-response.ts"],"names":[],"mappings":"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","sourcesContent":["import { lexParse } from '@atproto/lex-json'\nimport {\n InferMethodOutputBody,\n InferMethodOutputEncoding,\n Procedure,\n Query,\n ResultSuccess,\n} from '@atproto/lex-schema'\nimport { Payload } from './util.js'\nimport {\n XrpcInvalidResponseError,\n XrpcResponseError,\n isXrpcErrorPayload,\n} from './xrpc-error.js'\n\nexport type XrpcResponseBody<M extends Procedure | Query> =\n InferMethodOutputBody<M, Uint8Array>\n\nexport type XrpcResponsePayload<M extends Procedure | Query> =\n InferMethodOutputEncoding<M> extends infer E extends string\n ? Payload<XrpcResponseBody<M>, E>\n : null\n\n/**\n * Small container for XRPC response data.\n *\n * @implements {ResultSuccess<XrpcResponse<M>>} for convenience in result handling contexts.\n */\nexport class XrpcResponse<const M extends Procedure | Query>\n implements ResultSuccess<XrpcResponse<M>>\n{\n /** @see {@link ResultSuccess.success} */\n readonly success = true as const\n\n /** @see {@link ResultSuccess.value} */\n get value(): this {\n return this\n }\n\n constructor(\n readonly method: M,\n readonly status: number,\n readonly headers: Headers,\n readonly payload: XrpcResponsePayload<M>,\n ) {}\n\n /**\n * Whether the response payload was parsed as {@link LexValue} (`true`) or is\n * in binary form {@link Uint8Array} (`false`).\n */\n get isParsed() {\n return this.encoding === 'application/json' && shouldParse(this.method)\n }\n\n get encoding() {\n return this.payload?.encoding as InferMethodOutputEncoding<M>\n }\n\n get body() {\n return this.payload?.body as XrpcResponseBody<M>\n }\n\n /**\n * @throws {XrpcInvalidResponseError} when the response is invalid according\n * to the method schema.\n */\n static async fromFetchResponse<const M extends Procedure | Query>(\n method: M,\n response: Response,\n options?: { validateResponse?: boolean },\n ): Promise<XrpcResponse<M>> {\n // @NOTE The body MUST either be read or canceled to avoid resource leaks.\n // Since nothing should cause an exception before \"readPayload\" is\n // called, we can safely not use a try/finally here.\n\n // @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here\n if (response.status < 200 || response.status >= 300) {\n // Always parse json for error responses\n const payload = await readPayload(response, { parse: true })\n\n if (response.status >= 400 && isXrpcErrorPayload(payload)) {\n throw new XrpcResponseError(\n method,\n response.status,\n response.headers,\n payload,\n )\n }\n\n throw new XrpcInvalidResponseError(\n response.status >= 500\n ? `Upstream server encountered an error`\n : response.status >= 400\n ? `Upstream server returned an invalid response payload`\n : `Upstream server returned an invalid status code`,\n response,\n payload,\n )\n }\n\n // Only parse json if the schema expects it\n const payload = await readPayload(response, {\n parse: shouldParse(method),\n })\n\n // Response is successful (2xx). Validate payload (data and encoding) against schema.\n if (method.output.encoding == null) {\n // Schema expects no payload\n if (payload) {\n throw new XrpcInvalidResponseError(\n `Expected response with no body, got ${payload.encoding}`,\n response,\n payload,\n )\n }\n } else {\n // Schema expects a payload\n if (!payload || !method.output.matchesEncoding(payload.encoding)) {\n throw new XrpcInvalidResponseError(\n payload\n ? `Expected ${method.output.encoding} response, got ${payload.encoding}`\n : `Expected non-empty response with content-type ${method.output.encoding}`,\n response,\n payload,\n )\n }\n\n // Assert valid response body.\n if (method.output.schema && options?.validateResponse !== false) {\n const result = method.output.schema.safeParse(payload.body, {\n allowTransform: false,\n })\n\n if (!result.success) {\n throw new XrpcInvalidResponseError(\n `Response validation failed: ${result.reason.message}`,\n response,\n payload,\n { cause: result.reason },\n )\n }\n }\n }\n\n return new XrpcResponse<M>(\n method,\n response.status,\n response.headers,\n payload as XrpcResponsePayload<M>,\n )\n }\n}\n\nfunction shouldParse(method: Procedure | Query) {\n return method.output.encoding === 'application/json'\n}\n\n/**\n * @note this function always consumes the response body\n */\nasync function readPayload(\n response: Response,\n options?: { parse?: boolean },\n): Promise<Payload | null> {\n // @TODO Should we limit the maximum response size here (this could also be\n // done by the FetchHandler)?\n\n const encoding = response.headers\n .get('content-type')\n ?.split(';')[0]\n .trim()\n .toLowerCase()\n\n // Response content-type is undefined\n if (!encoding) {\n // If the body is empty, return null (= no payload)\n const body = await response.arrayBuffer()\n if (body.byteLength === 0) return null\n\n // If we got data despite no content-type, treat it as binary\n return {\n encoding: 'application/octet-stream',\n body: new Uint8Array(body),\n }\n }\n\n if (options?.parse && encoding === 'application/json') {\n // @NOTE It might be worth returning the raw bytes here (Uint8Array) and\n // perform the lex parsing using cborg/json, allowing to do\n // bytes->LexValue in one step instead of bytes->text->JSON->LexValue.\n // This would require adding encode/decode utilities to lex-json (similar\n // to @ipld/dag-json)\n const text = await response.text()\n\n try {\n // @NOTE Using `lexParse(text)` (instead of `jsonToLex(json)`) here as\n // using a reviver function during JSON.parse should be faster than\n // parsing to JSON then converting to Lex (?)\n\n // @TODO verify statement above\n return { encoding, body: lexParse(text) }\n } catch (cause) {\n throw new XrpcInvalidResponseError(\n 'Invalid JSON response body',\n response,\n null,\n { cause },\n )\n }\n }\n\n return { encoding, body: new Uint8Array(await response.arrayBuffer()) }\n}\n"]}
|
package/dist/xrpc.d.ts
CHANGED
|
@@ -1,37 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DidString, InferParamsSchema, InferPayloadBody, Params, ParamsSchema, Procedure, Query, Restricted, Subscription } from '@atproto/lex-schema';
|
|
1
|
+
import { InferMethodInput, InferMethodParams, Params, Procedure, Query, Restricted } from '@atproto/lex-schema';
|
|
3
2
|
import { Agent } from './agent.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
params?:
|
|
11
|
-
} : {
|
|
12
|
-
params: InferParamsSchema<M['parameters']>;
|
|
13
|
-
});
|
|
14
|
-
export declare function xrpcRequestUrl<M extends Procedure | Query | Subscription>(method: M, options: XrpcRequestUrlOptions<M>): string;
|
|
15
|
-
export declare function xrpcRequestParams(schema: ParamsSchema | undefined, params: Params | undefined, options: CallOptions): undefined | string;
|
|
16
|
-
export type XrpcRequestInitOptions<T extends Query | Procedure> = CallOptions & (T extends Procedure ? never extends InferPayloadBody<T['input']> ? {
|
|
17
|
-
body?: InferPayloadBody<T['input']>;
|
|
3
|
+
import { BinaryBodyInit, CallOptions, Namespace } from './types.js';
|
|
4
|
+
import { XrpcInvalidResponseError, XrpcResponseError, XrpcUnexpectedError } from './xrpc-error.js';
|
|
5
|
+
import { XrpcResponse } from './xrpc-response.js';
|
|
6
|
+
export * from './xrpc-error.js';
|
|
7
|
+
export * from './xrpc-response.js';
|
|
8
|
+
type XrpcParamsOptions<P extends Params> = NonNullable<unknown> extends P ? {
|
|
9
|
+
params?: P;
|
|
18
10
|
} : {
|
|
19
|
-
|
|
11
|
+
params: P;
|
|
12
|
+
};
|
|
13
|
+
type XrpcRequestPayload<M extends Procedure | Query> = InferMethodInput<M, BinaryBodyInit>;
|
|
14
|
+
type XrpcInputOptions<In> = In extends {
|
|
15
|
+
body: infer B;
|
|
16
|
+
encoding: infer E;
|
|
17
|
+
} ? {
|
|
18
|
+
body: B;
|
|
19
|
+
encoding?: E;
|
|
20
20
|
} : {
|
|
21
|
-
body?:
|
|
22
|
-
|
|
23
|
-
export declare function xrpcRequestInit<T extends Procedure | Query>(schema: T, options: XrpcRequestInitOptions<T>): RequestInit & {
|
|
24
|
-
duplex?: 'half';
|
|
21
|
+
body?: undefined;
|
|
22
|
+
encoding?: undefined;
|
|
25
23
|
};
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export declare function
|
|
35
|
-
export declare function
|
|
36
|
-
export declare function readResponseBody(response: Response, encoding: string | undefined): Promise<LexValue | undefined>;
|
|
24
|
+
export type XrpcOptions<M extends Procedure | Query = Procedure | Query> = CallOptions & XrpcInputOptions<XrpcRequestPayload<M>> & XrpcParamsOptions<InferMethodParams<M>>;
|
|
25
|
+
export type XrpcFailure<M extends Procedure | Query> = XrpcResponseError<M> | XrpcInvalidResponseError | XrpcUnexpectedError;
|
|
26
|
+
export type XrpcResult<M extends Procedure | Query> = XrpcResponse<M> | XrpcFailure<M>;
|
|
27
|
+
/**
|
|
28
|
+
* @throws XrpcFailure<M>
|
|
29
|
+
*/
|
|
30
|
+
export declare function xrpc<const M extends Query | Procedure>(agent: Agent, ns: NonNullable<unknown> extends XrpcOptions<M> ? Namespace<M> : Restricted<'This XRPC method requires an "options" argument'>): Promise<XrpcResponse<M>>;
|
|
31
|
+
export declare function xrpc<const M extends Query | Procedure>(agent: Agent, ns: Namespace<M>, options: XrpcOptions<M>): Promise<XrpcResponse<M>>;
|
|
32
|
+
export declare function xrpcSafe<const M extends Query | Procedure>(agent: Agent, ns: NonNullable<unknown> extends XrpcOptions<M> ? Namespace<M> : Restricted<'This XRPC method requires an "options" argument'>): Promise<XrpcResult<M>>;
|
|
33
|
+
export declare function xrpcSafe<const M extends Query | Procedure>(agent: Agent, ns: Namespace<M>, options: XrpcOptions<M>): Promise<XrpcResult<M>>;
|
|
37
34
|
//# sourceMappingURL=xrpc.d.ts.map
|
package/dist/xrpc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xrpc.d.ts","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"xrpc.d.ts","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,MAAM,EAGN,SAAS,EACT,KAAK,EACL,UAAU,EAEX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,YAAY,CAAA;AAQ5E,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAGlC,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,IACrC,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,CAAC,CAAA;CAAE,CAAA;AAEjE,KAAK,kBAAkB,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,IAAI,gBAAgB,CACrE,CAAC,EACD,cAAc,CACf,CAAA;AAED,KAAK,gBAAgB,CAAC,EAAE,IAAI,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;CAAE,GAEvE;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;CAAE,GACzB;IAAE,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAE9C,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,IACrE,WAAW,GACT,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,GACvC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAA;AAE3C,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,IAE/C,iBAAiB,CAAC,CAAC,CAAC,GAEpB,wBAAwB,GAExB,mBAAmB,CAAA;AAEvB,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,IAC9C,YAAY,CAAC,CAAC,CAAC,GACf,WAAW,CAAC,CAAC,CAAC,CAAA;AAgBlB;;GAEG;AACH,wBAAsB,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,KAAK,GAAG,SAAS,EAC1D,KAAK,EAAE,KAAK,EACZ,EAAE,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAC3C,SAAS,CAAC,CAAC,CAAC,GACZ,UAAU,CAAC,iDAAiD,CAAC,GAChE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;AAC3B,wBAAsB,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,KAAK,GAAG,SAAS,EAC1D,KAAK,EAAE,KAAK,EACZ,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,EAChB,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;AAa3B,wBAAsB,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,KAAK,GAAG,SAAS,EAC9D,KAAK,EAAE,KAAK,EACZ,EAAE,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAC3C,SAAS,CAAC,CAAC,CAAC,GACZ,UAAU,CAAC,iDAAiD,CAAC,GAChE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;AACzB,wBAAsB,QAAQ,CAAC,KAAK,CAAC,CAAC,SAAS,KAAK,GAAG,SAAS,EAC9D,KAAK,EAAE,KAAK,EACZ,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,EAChB,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA"}
|
package/dist/xrpc.js
CHANGED
|
@@ -1,25 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.xrpc = xrpc;
|
|
4
|
-
exports.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
exports.xrpcRequestHeaders = xrpcRequestHeaders;
|
|
8
|
-
exports.xrpcResponseHandler = xrpcResponseHandler;
|
|
9
|
-
exports.extractEncoding = extractEncoding;
|
|
10
|
-
exports.readResponseBody = readResponseBody;
|
|
4
|
+
exports.xrpcSafe = xrpcSafe;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const lex_data_1 = require("@atproto/lex-data");
|
|
11
7
|
const lex_json_1 = require("@atproto/lex-json");
|
|
12
|
-
const lex_schema_1 = require("@atproto/lex-schema");
|
|
13
|
-
const error_js_1 = require("./error.js");
|
|
14
|
-
const response_js_1 = require("./response.js");
|
|
15
8
|
const types_js_1 = require("./types.js");
|
|
9
|
+
const util_js_1 = require("./util.js");
|
|
10
|
+
const xrpc_error_js_1 = require("./xrpc-error.js");
|
|
11
|
+
const xrpc_response_js_1 = require("./xrpc-response.js");
|
|
12
|
+
tslib_1.__exportStar(require("./xrpc-error.js"), exports);
|
|
13
|
+
tslib_1.__exportStar(require("./xrpc-response.js"), exports);
|
|
14
|
+
/**
|
|
15
|
+
* Utility method to type cast the error thrown by {@link xrpc} to an
|
|
16
|
+
* {@link XrpcFailure} matching the provided method. Only use this function
|
|
17
|
+
* inside a catch block right after calling {@link xrpc}, and use the same
|
|
18
|
+
* method type parameter as used in the {@link xrpc} call.
|
|
19
|
+
*/
|
|
20
|
+
function asXrpcFailure(err) {
|
|
21
|
+
if (err instanceof xrpc_error_js_1.XrpcResponseError)
|
|
22
|
+
return err;
|
|
23
|
+
if (err instanceof xrpc_error_js_1.XrpcInvalidResponseError)
|
|
24
|
+
return err;
|
|
25
|
+
return xrpc_error_js_1.XrpcUnexpectedError.from(err);
|
|
26
|
+
}
|
|
16
27
|
async function xrpc(agent, ns, options = {}) {
|
|
17
|
-
|
|
28
|
+
try {
|
|
29
|
+
return await xrpcRequest(agent, ns, options);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
throw asXrpcFailure(err);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function xrpcSafe(agent, ns, options = {}) {
|
|
36
|
+
return xrpcRequest(agent, ns, options).catch((asXrpcFailure));
|
|
37
|
+
}
|
|
38
|
+
async function xrpcRequest(agent, ns, options = {}) {
|
|
18
39
|
const method = (0, types_js_1.getMain)(ns);
|
|
40
|
+
options.signal?.throwIfAborted();
|
|
19
41
|
const url = xrpcRequestUrl(method, options);
|
|
20
42
|
const request = xrpcRequestInit(method, options);
|
|
21
43
|
const response = await agent.fetchHandler(url, request);
|
|
22
|
-
return
|
|
44
|
+
return xrpc_response_js_1.XrpcResponse.fromFetchResponse(method, response, options);
|
|
23
45
|
}
|
|
24
46
|
function xrpcRequestUrl(method, options) {
|
|
25
47
|
const path = `/xrpc/${method.nsid}`;
|
|
@@ -33,15 +55,26 @@ function xrpcRequestParams(schema, params, options) {
|
|
|
33
55
|
return urlSearchParams?.size ? urlSearchParams.toString() : undefined;
|
|
34
56
|
}
|
|
35
57
|
function xrpcRequestInit(schema, options) {
|
|
36
|
-
const headers =
|
|
58
|
+
const headers = (0, util_js_1.buildAtprotoHeaders)(options);
|
|
59
|
+
// Tell the server what type of response we're expecting
|
|
60
|
+
if (schema.output.encoding) {
|
|
61
|
+
headers.set('accept', schema.output.encoding);
|
|
62
|
+
}
|
|
63
|
+
// Caller should not set content-type header
|
|
64
|
+
if (headers.has('content-type')) {
|
|
65
|
+
const contentType = headers.get('content-type');
|
|
66
|
+
throw new TypeError(`Unexpected content-type header (${contentType})`);
|
|
67
|
+
}
|
|
37
68
|
// Requests with body
|
|
38
|
-
if ('input' in schema
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
if ('input' in schema) {
|
|
70
|
+
const encodingHint = options.encoding;
|
|
71
|
+
const input = xrpcProcedureInput(schema, options, encodingHint);
|
|
72
|
+
if (input) {
|
|
73
|
+
headers.set('content-type', input.encoding);
|
|
74
|
+
}
|
|
75
|
+
else if (encodingHint != null) {
|
|
76
|
+
throw new TypeError(`Unexpected encoding hint (${encodingHint})`);
|
|
43
77
|
}
|
|
44
|
-
headers.set('content-type', schema.input.encoding);
|
|
45
78
|
return {
|
|
46
79
|
duplex: 'half',
|
|
47
80
|
redirect: 'follow',
|
|
@@ -50,9 +83,7 @@ function xrpcRequestInit(schema, options) {
|
|
|
50
83
|
signal: options.signal,
|
|
51
84
|
method: 'POST',
|
|
52
85
|
headers,
|
|
53
|
-
body:
|
|
54
|
-
? schema.input?.body.parse(options.body)
|
|
55
|
-
: options.body),
|
|
86
|
+
body: input?.body,
|
|
56
87
|
};
|
|
57
88
|
}
|
|
58
89
|
// Requests without body
|
|
@@ -62,124 +93,87 @@ function xrpcRequestInit(schema, options) {
|
|
|
62
93
|
referrerPolicy: 'strict-origin-when-cross-origin', // (default)
|
|
63
94
|
mode: 'cors', // (default)
|
|
64
95
|
signal: options.signal,
|
|
65
|
-
method:
|
|
96
|
+
method: 'GET',
|
|
66
97
|
headers,
|
|
67
98
|
};
|
|
68
99
|
}
|
|
69
|
-
function
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (options.labelers) {
|
|
75
|
-
headers.set('atproto-accept-labelers', [...options.labelers, headers.get('atproto-accept-labelers')?.trim()]
|
|
76
|
-
.filter(Boolean)
|
|
77
|
-
.join(', '));
|
|
78
|
-
}
|
|
79
|
-
return headers;
|
|
80
|
-
}
|
|
81
|
-
function xrpcRequestBody(encoding, body) {
|
|
82
|
-
if (encoding === undefined) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
if (encoding === 'application/json') {
|
|
86
|
-
if (body !== undefined)
|
|
87
|
-
return (0, lex_json_1.lexStringify)(body);
|
|
88
|
-
}
|
|
89
|
-
else if (encoding.startsWith('text/')) {
|
|
90
|
-
if (typeof body === 'string')
|
|
91
|
-
return body;
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
if (ArrayBuffer.isView(body) || body instanceof ArrayBuffer)
|
|
95
|
-
return body;
|
|
100
|
+
function xrpcProcedureInput(method, options, encodingHint) {
|
|
101
|
+
const { input } = method;
|
|
102
|
+
const { body } = options;
|
|
103
|
+
if (options.validateRequest) {
|
|
104
|
+
input.schema?.check(body);
|
|
96
105
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const encoding = extractEncoding(response.headers);
|
|
104
|
-
const body = await readResponseBody(response, encoding).catch((cause) => {
|
|
105
|
-
throw new error_js_1.XrpcServiceError(error_js_1.KnownError.InvalidResponse, response.status, response.headers, undefined, 'Failed to read XRPC response', { cause });
|
|
106
|
-
});
|
|
107
|
-
// @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here
|
|
108
|
-
if (response.status < 200 || response.status >= 300) {
|
|
109
|
-
// All unsuccessful responses should follow a standard error response
|
|
110
|
-
// schema. The Content-Type should be application/json, and the payload
|
|
111
|
-
// should be a JSON object with the following fields:
|
|
112
|
-
// - error (string, required): type name of the error (generic ASCII
|
|
113
|
-
// constant, no whitespace)
|
|
114
|
-
// - message (string, optional): description of the error, appropriate for
|
|
115
|
-
// display to humans
|
|
116
|
-
if (body != null &&
|
|
117
|
-
encoding === 'application/json' &&
|
|
118
|
-
error_js_1.xrpcErrorBodySchema.matches(body)) {
|
|
119
|
-
throw new error_js_1.XrpcResponseError(response.status, response.headers, encoding, body);
|
|
106
|
+
// Special handling for endpoints expecting application/json input
|
|
107
|
+
if (input.encoding === 'application/json') {
|
|
108
|
+
// @NOTE **NOT** using isLexValue here to avoid deep checks in order to
|
|
109
|
+
// distinguish between LexValue and BinaryBodyInit.
|
|
110
|
+
if (!(0, lex_data_1.isLexScalar)(body) && !(0, lex_data_1.isPlainObject)(body) && !Array.isArray(body)) {
|
|
111
|
+
throw new TypeError(`Expected LexValue body, got ${typeof body}`);
|
|
120
112
|
}
|
|
121
|
-
|
|
122
|
-
? error_js_1.KnownError.InternalServerError
|
|
123
|
-
: error_js_1.KnownError.InvalidResponse, response.status, response.headers, body);
|
|
113
|
+
return buildPayload(input, (0, lex_json_1.lexStringify)(body), encodingHint);
|
|
124
114
|
}
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
115
|
+
// Other encodings will be sent unaltered (ie. as binary data)
|
|
116
|
+
switch (typeof body) {
|
|
117
|
+
case 'undefined':
|
|
118
|
+
case 'string':
|
|
119
|
+
return buildPayload(input, body, encodingHint);
|
|
120
|
+
case 'object': {
|
|
121
|
+
if (body === null)
|
|
122
|
+
break;
|
|
123
|
+
if (ArrayBuffer.isView(body) ||
|
|
124
|
+
body instanceof ArrayBuffer ||
|
|
125
|
+
body instanceof ReadableStream) {
|
|
126
|
+
return buildPayload(input, body, encodingHint);
|
|
127
|
+
}
|
|
128
|
+
else if ((0, util_js_1.isAsyncIterable)(body)) {
|
|
129
|
+
return buildPayload(input, (0, util_js_1.toReadableStream)(body), encodingHint);
|
|
130
|
+
}
|
|
131
|
+
else if ((0, util_js_1.isBlobLike)(body)) {
|
|
132
|
+
return buildPayload(input, body, encodingHint || body.type);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
128
135
|
}
|
|
129
|
-
|
|
136
|
+
throw new TypeError(`Invalid ${typeof body} body for ${input.encoding} encoding`);
|
|
137
|
+
}
|
|
138
|
+
function buildPayload(schema, body, encodingHint) {
|
|
139
|
+
if (schema.encoding === undefined) {
|
|
130
140
|
if (body !== undefined) {
|
|
131
|
-
throw new
|
|
141
|
+
throw new TypeError(`Cannot send a ${typeof body} body with undefined encoding`);
|
|
132
142
|
}
|
|
133
|
-
return
|
|
143
|
+
return null;
|
|
134
144
|
}
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return new response_js_1.XrpcResponse(schema, response.status, response.headers, schema.output.schema == null || options?.validateResponse === false
|
|
141
|
-
? body
|
|
142
|
-
: schema.output.schema.parse(body));
|
|
145
|
+
if (body === undefined) {
|
|
146
|
+
// This error would be returned by the server, but we can catch it earlier
|
|
147
|
+
// to avoid un-necessary requests. Note that a content-length of 0 does not
|
|
148
|
+
// necessary mean that the body is "empty" (e.g. an empty txt file).
|
|
149
|
+
throw new TypeError(`A request body is expected but none was provided`);
|
|
143
150
|
}
|
|
151
|
+
const encoding = buildEncoding(schema, encodingHint);
|
|
152
|
+
return { encoding, body };
|
|
144
153
|
}
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
if (!
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (encoding == null) {
|
|
154
|
-
if (response.body == null)
|
|
155
|
-
return undefined;
|
|
156
|
-
// Let's make sure the body is empty (while avoiding reading it all).
|
|
157
|
-
if (!('getReader' in response.body)) {
|
|
158
|
-
// Some environments may not support body.getReader(), fall back to
|
|
159
|
-
// reading the whole body.
|
|
160
|
-
const buffer = await response.arrayBuffer();
|
|
161
|
-
if (buffer.byteLength === 0)
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
const reader = response.body.getReader();
|
|
166
|
-
const next = await reader.read();
|
|
167
|
-
if (next.done)
|
|
168
|
-
return undefined;
|
|
169
|
-
await reader.cancel(); // Drain the rest of the (non-empty) body stream
|
|
154
|
+
function buildEncoding(schema, encodingHint) {
|
|
155
|
+
// Should never happen (required for type safety)
|
|
156
|
+
if (!schema.encoding) {
|
|
157
|
+
throw new TypeError('Unexpected payload');
|
|
158
|
+
}
|
|
159
|
+
if (encodingHint?.length) {
|
|
160
|
+
if (!schema.matchesEncoding(encodingHint)) {
|
|
161
|
+
throw new TypeError(`Cannot send a body with content-type "${encodingHint}" for "${schema.encoding}" encoding`);
|
|
170
162
|
}
|
|
171
|
-
|
|
163
|
+
return encodingHint;
|
|
164
|
+
}
|
|
165
|
+
// Fallback
|
|
166
|
+
if (schema.encoding === '*/*') {
|
|
167
|
+
return 'application/octet-stream';
|
|
172
168
|
}
|
|
173
|
-
if (encoding
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// @TODO verify statement above
|
|
178
|
-
return (0, lex_json_1.lexParse)(await response.text());
|
|
169
|
+
if (schema.encoding.startsWith('text/')) {
|
|
170
|
+
return schema.encoding.includes('*')
|
|
171
|
+
? 'text/plain; charset=utf-8'
|
|
172
|
+
: `${schema.encoding}; charset=utf-8`;
|
|
179
173
|
}
|
|
180
|
-
if (encoding.
|
|
181
|
-
return
|
|
174
|
+
if (!schema.encoding.includes('*')) {
|
|
175
|
+
return schema.encoding;
|
|
182
176
|
}
|
|
183
|
-
|
|
177
|
+
throw new TypeError(`Unable to determine payload encoding. Please provide a 'content-type' header matching ${schema.encoding}.`);
|
|
184
178
|
}
|
|
185
179
|
//# sourceMappingURL=xrpc.js.map
|