@atproto/lex-client 0.0.4 → 0.0.6
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 +42 -0
- package/dist/agent.d.ts +10 -9
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +3 -0
- package/dist/agent.js.map +1 -1
- package/dist/client.d.ts +51 -113
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +38 -42
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +82 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +132 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- 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 +8 -12
- 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 +8 -12
- 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 +6 -10
- 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 +5 -8
- 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 +8 -12
- 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 +6 -9
- 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/response.d.ts +25 -8
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +123 -10
- package/dist/response.js.map +1 -1
- package/dist/types.d.ts +18 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -4
- 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.d.ts +35 -32
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +116 -124
- package/dist/xrpc.js.map +1 -1
- package/package.json +10 -10
- package/src/agent.ts +18 -14
- package/src/client.ts +135 -114
- package/src/errors.ts +206 -0
- package/src/index.ts +1 -1
- package/src/lexicons/com/atproto/repo/createRecord.defs.ts +31 -36
- package/src/lexicons/com/atproto/repo/deleteRecord.defs.ts +27 -32
- package/src/lexicons/com/atproto/repo/getRecord.defs.ts +12 -17
- package/src/lexicons/com/atproto/repo/listRecords.defs.ts +13 -15
- package/src/lexicons/com/atproto/repo/putRecord.defs.ts +32 -37
- package/src/lexicons/com/atproto/repo/uploadBlob.defs.ts +13 -15
- 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/response.ts +201 -15
- package/src/types.ts +26 -5
- package/src/util.ts +84 -0
- package/src/xrpc.ts +220 -232
- package/tsconfig.tests.json +4 -7
- 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/src/error.ts +0 -145
package/src/xrpc.ts
CHANGED
|
@@ -1,62 +1,140 @@
|
|
|
1
|
-
import { LexValue } from '@atproto/lex-data'
|
|
2
|
-
import {
|
|
1
|
+
import { LexValue, isLexScalar, isPlainObject } from '@atproto/lex-data'
|
|
2
|
+
import { lexStringify } from '@atproto/lex-json'
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
InferMethodInput,
|
|
5
|
+
InferMethodParams,
|
|
6
|
+
Main,
|
|
7
7
|
Params,
|
|
8
8
|
ParamsSchema,
|
|
9
|
+
Payload as LexPayload,
|
|
9
10
|
Procedure,
|
|
10
11
|
Query,
|
|
11
12
|
Restricted,
|
|
12
13
|
Subscription,
|
|
14
|
+
getMain,
|
|
13
15
|
} from '@atproto/lex-schema'
|
|
14
16
|
import { Agent } from './agent.js'
|
|
15
17
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from './
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
LexRpcResponseError,
|
|
19
|
+
LexRpcUnexpectedError,
|
|
20
|
+
LexRpcUpstreamError,
|
|
21
|
+
} from './errors.js'
|
|
22
|
+
import { LexRpcResponse } from './response.js'
|
|
23
|
+
import { BinaryBodyInit, CallOptions } from './types.js'
|
|
24
|
+
import {
|
|
25
|
+
Payload,
|
|
26
|
+
buildAtprotoHeaders,
|
|
27
|
+
isAsyncIterable,
|
|
28
|
+
isBlobLike,
|
|
29
|
+
toReadableStream,
|
|
30
|
+
} from './util.js'
|
|
31
|
+
|
|
32
|
+
// If all params are optional, allow omitting the params object
|
|
33
|
+
type LexRpcParamsOptions<P extends Params> =
|
|
34
|
+
NonNullable<unknown> extends P ? { params?: P } : { params: P }
|
|
35
|
+
|
|
36
|
+
type LexRpcRequestPayload<M extends Procedure | Query> = InferMethodInput<
|
|
37
|
+
M,
|
|
38
|
+
BinaryBodyInit
|
|
39
|
+
>
|
|
40
|
+
|
|
41
|
+
type LexRpcInputOptions<In> = In extends { body: infer B; encoding: infer E }
|
|
42
|
+
? // encoding will be inferred from the schema at runtime if not provided
|
|
43
|
+
{ body: B; encoding?: E }
|
|
44
|
+
: { body?: undefined; encoding?: undefined }
|
|
45
|
+
|
|
46
|
+
export type LexRpcOptions<M extends Procedure | Query = Procedure | Query> =
|
|
47
|
+
CallOptions &
|
|
48
|
+
LexRpcInputOptions<LexRpcRequestPayload<M>> &
|
|
49
|
+
LexRpcParamsOptions<InferMethodParams<M>>
|
|
50
|
+
|
|
51
|
+
export type LexRpcFailure<M extends Procedure | Query> =
|
|
52
|
+
// The server returned a valid XRPC error response
|
|
53
|
+
| LexRpcResponseError<M>
|
|
54
|
+
// The response was not a valid XRPC response, or it does not match the schema
|
|
55
|
+
| LexRpcUpstreamError
|
|
56
|
+
// Something went wrong (network error, etc.)
|
|
57
|
+
| LexRpcUnexpectedError
|
|
58
|
+
|
|
59
|
+
export type LexRpcResult<M extends Procedure | Query> =
|
|
60
|
+
| LexRpcResponse<M>
|
|
61
|
+
| LexRpcFailure<M>
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Utility method to type cast the error thrown by {@link xrpc} to an
|
|
65
|
+
* {@link LexRpcFailure} matching the provided method. Only use this function
|
|
66
|
+
* inside a catch block right after calling {@link xrpc}, and use the same
|
|
67
|
+
* method type parameter as used in the {@link xrpc} call.
|
|
68
|
+
*/
|
|
69
|
+
export function asLexRpcFailure<
|
|
70
|
+
M extends Procedure | Query = Procedure | Query,
|
|
71
|
+
>(err: unknown): LexRpcFailure<M> {
|
|
72
|
+
if (err instanceof LexRpcResponseError) return err
|
|
73
|
+
if (err instanceof LexRpcUpstreamError) return err
|
|
74
|
+
return LexRpcUnexpectedError.from(err)
|
|
75
|
+
}
|
|
26
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @throws LexRpcFailure<M>
|
|
79
|
+
*/
|
|
27
80
|
export async function xrpc<const M extends Query | Procedure>(
|
|
28
81
|
agent: Agent,
|
|
29
|
-
ns: NonNullable<unknown> extends
|
|
30
|
-
?
|
|
82
|
+
ns: NonNullable<unknown> extends LexRpcOptions<M>
|
|
83
|
+
? Main<M>
|
|
31
84
|
: Restricted<'This XRPC method requires an "options" argument'>,
|
|
32
|
-
): Promise<
|
|
85
|
+
): Promise<LexRpcResponse<M>>
|
|
33
86
|
export async function xrpc<const M extends Query | Procedure>(
|
|
34
87
|
agent: Agent,
|
|
35
|
-
ns:
|
|
36
|
-
options:
|
|
37
|
-
): Promise<
|
|
88
|
+
ns: Main<M>,
|
|
89
|
+
options: LexRpcOptions<M>,
|
|
90
|
+
): Promise<LexRpcResponse<M>>
|
|
38
91
|
export async function xrpc<const M extends Query | Procedure>(
|
|
39
92
|
agent: Agent,
|
|
40
|
-
ns:
|
|
41
|
-
options:
|
|
42
|
-
): Promise<
|
|
43
|
-
|
|
93
|
+
ns: Main<M>,
|
|
94
|
+
options: LexRpcOptions<M> = {} as LexRpcOptions<M>,
|
|
95
|
+
): Promise<LexRpcResponse<M>> {
|
|
96
|
+
try {
|
|
97
|
+
return await lexRpcRequest<M>(agent, ns, options)
|
|
98
|
+
} catch (err) {
|
|
99
|
+
throw asLexRpcFailure<M>(err)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function xrpcSafe<const M extends Query | Procedure>(
|
|
104
|
+
agent: Agent,
|
|
105
|
+
ns: NonNullable<unknown> extends LexRpcOptions<M>
|
|
106
|
+
? Main<M>
|
|
107
|
+
: Restricted<'This XRPC method requires an "options" argument'>,
|
|
108
|
+
): Promise<LexRpcResult<M>>
|
|
109
|
+
export async function xrpcSafe<const M extends Query | Procedure>(
|
|
110
|
+
agent: Agent,
|
|
111
|
+
ns: Main<M>,
|
|
112
|
+
options: LexRpcOptions<M>,
|
|
113
|
+
): Promise<LexRpcResult<M>>
|
|
114
|
+
export async function xrpcSafe<const M extends Query | Procedure>(
|
|
115
|
+
agent: Agent,
|
|
116
|
+
ns: Main<M>,
|
|
117
|
+
options: LexRpcOptions<M> = {} as LexRpcOptions<M>,
|
|
118
|
+
): Promise<LexRpcResult<M>> {
|
|
119
|
+
return lexRpcRequest<M>(agent, ns, options).catch(asLexRpcFailure<M>)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function lexRpcRequest<const M extends Query | Procedure>(
|
|
123
|
+
agent: Agent,
|
|
124
|
+
ns: Main<M>,
|
|
125
|
+
options: LexRpcOptions<M> = {} as LexRpcOptions<M>,
|
|
126
|
+
): Promise<LexRpcResponse<M>> {
|
|
44
127
|
const method = getMain(ns)
|
|
128
|
+
options.signal?.throwIfAborted()
|
|
45
129
|
const url = xrpcRequestUrl(method, options)
|
|
46
130
|
const request = xrpcRequestInit(method, options)
|
|
47
131
|
const response = await agent.fetchHandler(url, request)
|
|
48
|
-
return
|
|
132
|
+
return LexRpcResponse.fromFetchResponse<M>(method, response, options)
|
|
49
133
|
}
|
|
50
134
|
|
|
51
|
-
|
|
52
|
-
CallOptions &
|
|
53
|
-
(undefined extends InferParamsSchema<M['parameters']>
|
|
54
|
-
? { params?: InferParamsSchema<M['parameters']> }
|
|
55
|
-
: { params: InferParamsSchema<M['parameters']> })
|
|
56
|
-
|
|
57
|
-
export function xrpcRequestUrl<M extends Procedure | Query | Subscription>(
|
|
135
|
+
function xrpcRequestUrl<M extends Procedure | Query | Subscription>(
|
|
58
136
|
method: M,
|
|
59
|
-
options:
|
|
137
|
+
options: CallOptions & { params?: Params },
|
|
60
138
|
) {
|
|
61
139
|
const path = `/xrpc/${method.nsid}`
|
|
62
140
|
|
|
@@ -67,7 +145,7 @@ export function xrpcRequestUrl<M extends Procedure | Query | Subscription>(
|
|
|
67
145
|
return queryString ? `${path}?${queryString}` : path
|
|
68
146
|
}
|
|
69
147
|
|
|
70
|
-
|
|
148
|
+
function xrpcRequestParams(
|
|
71
149
|
schema: ParamsSchema | undefined,
|
|
72
150
|
params: Params | undefined,
|
|
73
151
|
options: CallOptions,
|
|
@@ -78,32 +156,37 @@ export function xrpcRequestParams(
|
|
|
78
156
|
return urlSearchParams?.size ? urlSearchParams.toString() : undefined
|
|
79
157
|
}
|
|
80
158
|
|
|
81
|
-
|
|
82
|
-
(T extends Procedure
|
|
83
|
-
? never extends InferPayloadBody<T['input']>
|
|
84
|
-
? { body?: InferPayloadBody<T['input']> }
|
|
85
|
-
: { body: InferPayloadBody<T['input']> }
|
|
86
|
-
: { body?: never })
|
|
87
|
-
|
|
88
|
-
export function xrpcRequestInit<T extends Procedure | Query>(
|
|
159
|
+
function xrpcRequestInit<T extends Procedure | Query>(
|
|
89
160
|
schema: T,
|
|
90
|
-
options:
|
|
161
|
+
options: CallOptions & {
|
|
162
|
+
body?: LexValue | BinaryBodyInit
|
|
163
|
+
encoding?: string
|
|
164
|
+
},
|
|
91
165
|
): RequestInit & { duplex?: 'half' } {
|
|
92
|
-
const headers =
|
|
166
|
+
const headers = buildAtprotoHeaders(options)
|
|
167
|
+
|
|
168
|
+
// Tell the server what type of response we're expecting
|
|
169
|
+
if (schema.output.encoding) {
|
|
170
|
+
headers.set('accept', schema.output.encoding)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Caller should not set content-type header
|
|
174
|
+
if (headers.has('content-type')) {
|
|
175
|
+
const contentType = headers.get('content-type')
|
|
176
|
+
throw new TypeError(`Unexpected content-type header (${contentType})`)
|
|
177
|
+
}
|
|
93
178
|
|
|
94
179
|
// Requests with body
|
|
95
|
-
if ('input' in schema
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
180
|
+
if ('input' in schema) {
|
|
181
|
+
const encodingHint = options.encoding
|
|
182
|
+
const input = xrpcProcedureInput(schema, options, encodingHint)
|
|
183
|
+
|
|
184
|
+
if (input) {
|
|
185
|
+
headers.set('content-type', input.encoding)
|
|
186
|
+
} else if (encodingHint != null) {
|
|
187
|
+
throw new TypeError(`Unexpected encoding hint (${encodingHint})`)
|
|
104
188
|
}
|
|
105
189
|
|
|
106
|
-
headers.set('content-type', schema.input.encoding)
|
|
107
190
|
return {
|
|
108
191
|
duplex: 'half',
|
|
109
192
|
redirect: 'follow',
|
|
@@ -112,12 +195,7 @@ export function xrpcRequestInit<T extends Procedure | Query>(
|
|
|
112
195
|
signal: options.signal,
|
|
113
196
|
method: 'POST',
|
|
114
197
|
headers,
|
|
115
|
-
body:
|
|
116
|
-
schema.input?.encoding,
|
|
117
|
-
options.validateRequest
|
|
118
|
-
? schema.input?.body.parse(options.body)
|
|
119
|
-
: options.body,
|
|
120
|
-
),
|
|
198
|
+
body: input?.body,
|
|
121
199
|
}
|
|
122
200
|
}
|
|
123
201
|
|
|
@@ -128,208 +206,118 @@ export function xrpcRequestInit<T extends Procedure | Query>(
|
|
|
128
206
|
referrerPolicy: 'strict-origin-when-cross-origin', // (default)
|
|
129
207
|
mode: 'cors', // (default)
|
|
130
208
|
signal: options.signal,
|
|
131
|
-
method:
|
|
209
|
+
method: 'GET',
|
|
132
210
|
headers,
|
|
133
211
|
}
|
|
134
212
|
}
|
|
135
213
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
214
|
+
function xrpcProcedureInput(
|
|
215
|
+
method: Procedure,
|
|
216
|
+
options: CallOptions & { body?: LexValue | BinaryBodyInit },
|
|
217
|
+
encodingHint?: string,
|
|
218
|
+
): null | Payload<BodyInit> {
|
|
219
|
+
const { input } = method
|
|
220
|
+
const { body } = options
|
|
142
221
|
|
|
143
|
-
if (options.
|
|
144
|
-
|
|
222
|
+
if (options.validateRequest) {
|
|
223
|
+
input.schema?.check(body)
|
|
145
224
|
}
|
|
146
225
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return headers
|
|
157
|
-
}
|
|
226
|
+
// Special handling for endpoints expecting application/json input
|
|
227
|
+
if (input.encoding === 'application/json') {
|
|
228
|
+
// @NOTE **NOT** using isLexValue here to avoid deep checks in order to
|
|
229
|
+
// distinguish between LexValue and BinaryBodyInit.
|
|
230
|
+
if (!isLexScalar(body) && !isPlainObject(body) && !Array.isArray(body)) {
|
|
231
|
+
throw new TypeError(`Expected LexValue body, got ${typeof body}`)
|
|
232
|
+
}
|
|
158
233
|
|
|
159
|
-
|
|
160
|
-
encoding: string | undefined,
|
|
161
|
-
body: LexValue | undefined,
|
|
162
|
-
): BodyInit | null {
|
|
163
|
-
if (encoding === undefined) {
|
|
164
|
-
return null
|
|
234
|
+
return buildPayload(input, lexStringify(body), encodingHint)
|
|
165
235
|
}
|
|
166
236
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
237
|
+
// Other encodings will be sent unaltered (ie. as binary data)
|
|
238
|
+
switch (typeof body) {
|
|
239
|
+
case 'undefined':
|
|
240
|
+
case 'string':
|
|
241
|
+
return buildPayload(input, body, encodingHint)
|
|
242
|
+
case 'object': {
|
|
243
|
+
if (body === null) break
|
|
244
|
+
if (
|
|
245
|
+
ArrayBuffer.isView(body) ||
|
|
246
|
+
body instanceof ArrayBuffer ||
|
|
247
|
+
body instanceof ReadableStream
|
|
248
|
+
) {
|
|
249
|
+
return buildPayload(input, body, encodingHint)
|
|
250
|
+
} else if (isAsyncIterable(body)) {
|
|
251
|
+
return buildPayload(input, toReadableStream(body), encodingHint)
|
|
252
|
+
} else if (isBlobLike(body)) {
|
|
253
|
+
return buildPayload(input, body, encodingHint || body.type)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
173
256
|
}
|
|
174
257
|
|
|
175
|
-
throw new TypeError(
|
|
258
|
+
throw new TypeError(
|
|
259
|
+
`Invalid ${typeof body} body for ${input.encoding} encoding`,
|
|
260
|
+
)
|
|
176
261
|
}
|
|
177
262
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
):
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const encoding = extractEncoding(response.headers)
|
|
188
|
-
|
|
189
|
-
const body = await readResponseBody(response, encoding).catch((cause) => {
|
|
190
|
-
throw new XrpcServiceError(
|
|
191
|
-
KnownError.InvalidResponse,
|
|
192
|
-
response.status,
|
|
193
|
-
response.headers,
|
|
194
|
-
undefined,
|
|
195
|
-
'Failed to read XRPC response',
|
|
196
|
-
{ cause },
|
|
197
|
-
)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
// @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here
|
|
201
|
-
if (response.status < 200 || response.status >= 300) {
|
|
202
|
-
// All unsuccessful responses should follow a standard error response
|
|
203
|
-
// schema. The Content-Type should be application/json, and the payload
|
|
204
|
-
// should be a JSON object with the following fields:
|
|
205
|
-
// - error (string, required): type name of the error (generic ASCII
|
|
206
|
-
// constant, no whitespace)
|
|
207
|
-
// - message (string, optional): description of the error, appropriate for
|
|
208
|
-
// display to humans
|
|
209
|
-
if (
|
|
210
|
-
body != null &&
|
|
211
|
-
encoding === 'application/json' &&
|
|
212
|
-
xrpcErrorBodySchema.matches(body)
|
|
213
|
-
) {
|
|
214
|
-
throw new XrpcResponseError(
|
|
215
|
-
response.status,
|
|
216
|
-
response.headers,
|
|
217
|
-
encoding,
|
|
218
|
-
body,
|
|
263
|
+
function buildPayload(
|
|
264
|
+
schema: LexPayload,
|
|
265
|
+
body: undefined | BodyInit,
|
|
266
|
+
encodingHint?: string,
|
|
267
|
+
): null | Payload<BodyInit> {
|
|
268
|
+
if (schema.encoding === undefined) {
|
|
269
|
+
if (body !== undefined) {
|
|
270
|
+
throw new TypeError(
|
|
271
|
+
`Cannot send a ${typeof body} body with undefined encoding`,
|
|
219
272
|
)
|
|
220
273
|
}
|
|
221
274
|
|
|
222
|
-
|
|
223
|
-
response.status >= 500
|
|
224
|
-
? KnownError.InternalServerError
|
|
225
|
-
: KnownError.InvalidResponse,
|
|
226
|
-
response.status,
|
|
227
|
-
response.headers,
|
|
228
|
-
body,
|
|
229
|
-
)
|
|
275
|
+
return null
|
|
230
276
|
}
|
|
231
277
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
response.headers,
|
|
238
|
-
body,
|
|
239
|
-
`Expected response with content-type ${schema.output.encoding}, got ${encoding}`,
|
|
240
|
-
)
|
|
278
|
+
if (body === undefined) {
|
|
279
|
+
// This error would be returned by the server, but we can catch it earlier
|
|
280
|
+
// to avoid un-necessary requests. Note that a content-length of 0 does not
|
|
281
|
+
// necessary mean that the body is "empty" (e.g. an empty txt file).
|
|
282
|
+
throw new TypeError(`A request body is expected but none was provided`)
|
|
241
283
|
}
|
|
242
284
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
285
|
+
const encoding = buildEncoding(schema, encodingHint)
|
|
286
|
+
return { encoding, body }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function buildEncoding(schema: LexPayload, encodingHint?: string): string {
|
|
290
|
+
// Should never happen (required for type safety)
|
|
291
|
+
if (!schema.encoding) {
|
|
292
|
+
throw new TypeError('Unexpected payload')
|
|
293
|
+
}
|
|
253
294
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
undefined as XrpcResponseBody<M>,
|
|
259
|
-
)
|
|
260
|
-
} else {
|
|
261
|
-
// @NOTE this should already be enforced by readXrpcResponseBody
|
|
262
|
-
if (body === undefined) {
|
|
263
|
-
throw new XrpcServiceError(
|
|
264
|
-
KnownError.InvalidResponse,
|
|
265
|
-
response.status,
|
|
266
|
-
response.headers,
|
|
267
|
-
body,
|
|
268
|
-
`Expected non-empty response body`,
|
|
295
|
+
if (encodingHint?.length) {
|
|
296
|
+
if (!schema.matchesEncoding(encodingHint)) {
|
|
297
|
+
throw new TypeError(
|
|
298
|
+
`Cannot send a body with content-type "${encodingHint}" for "${schema.encoding}" encoding`,
|
|
269
299
|
)
|
|
270
300
|
}
|
|
271
|
-
|
|
272
|
-
return new XrpcResponse<M>(
|
|
273
|
-
schema,
|
|
274
|
-
response.status,
|
|
275
|
-
response.headers,
|
|
276
|
-
schema.output.schema == null || options?.validateResponse === false
|
|
277
|
-
? (body as XrpcResponseBody<M>)
|
|
278
|
-
: (schema.output.schema.parse(body) as XrpcResponseBody<M>),
|
|
279
|
-
)
|
|
301
|
+
return encodingHint
|
|
280
302
|
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export function extractEncoding(headers: Headers): string | undefined {
|
|
284
|
-
const contentType = headers.get('content-type')
|
|
285
|
-
if (!contentType) return undefined
|
|
286
|
-
return contentType.split(';')[0].trim()
|
|
287
|
-
}
|
|
288
303
|
|
|
289
|
-
|
|
290
|
-
response: Response,
|
|
291
|
-
encoding: string,
|
|
292
|
-
): Promise<LexValue>
|
|
293
|
-
export async function readResponseBody(
|
|
294
|
-
response: Response,
|
|
295
|
-
encoding: string | undefined,
|
|
296
|
-
): Promise<LexValue | undefined>
|
|
297
|
-
export async function readResponseBody(
|
|
298
|
-
response: Response,
|
|
299
|
-
encoding: string | undefined,
|
|
300
|
-
): Promise<LexValue | undefined> {
|
|
301
|
-
// When encoding is undefined or empty, we expect no body
|
|
302
|
-
if (encoding == null) {
|
|
303
|
-
if (response.body == null) return undefined
|
|
304
|
-
|
|
305
|
-
// Let's make sure the body is empty (while avoiding reading it all).
|
|
306
|
-
if (!('getReader' in response.body)) {
|
|
307
|
-
// Some environments may not support body.getReader(), fall back to
|
|
308
|
-
// reading the whole body.
|
|
309
|
-
const buffer = await response.arrayBuffer()
|
|
310
|
-
if (buffer.byteLength === 0) return undefined
|
|
311
|
-
} else {
|
|
312
|
-
const reader = response.body.getReader()
|
|
313
|
-
const next = await reader.read()
|
|
314
|
-
if (next.done) return undefined
|
|
315
|
-
await reader.cancel() // Drain the rest of the (non-empty) body stream
|
|
316
|
-
}
|
|
304
|
+
// Fallback
|
|
317
305
|
|
|
318
|
-
|
|
306
|
+
if (schema.encoding === '*/*') {
|
|
307
|
+
return 'application/octet-stream'
|
|
319
308
|
}
|
|
320
309
|
|
|
321
|
-
if (encoding
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
// @TODO verify statement above
|
|
327
|
-
return lexParse(await response.text())
|
|
310
|
+
if (schema.encoding.startsWith('text/')) {
|
|
311
|
+
return schema.encoding.includes('*')
|
|
312
|
+
? 'text/plain; charset=utf-8'
|
|
313
|
+
: `${schema.encoding}; charset=utf-8`
|
|
328
314
|
}
|
|
329
315
|
|
|
330
|
-
if (encoding.
|
|
331
|
-
return
|
|
316
|
+
if (!schema.encoding.includes('*')) {
|
|
317
|
+
return schema.encoding
|
|
332
318
|
}
|
|
333
319
|
|
|
334
|
-
|
|
320
|
+
throw new TypeError(
|
|
321
|
+
`Unable to determine payload encoding. Please provide a 'content-type' header matching ${schema.encoding}.`,
|
|
322
|
+
)
|
|
335
323
|
}
|
package/tsconfig.tests.json
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "../../../tsconfig/
|
|
3
|
-
"include": ["./tests"],
|
|
2
|
+
"extends": "../../../tsconfig/vitest.json",
|
|
3
|
+
"include": ["./tests", "./src/**/*.test.ts"],
|
|
4
4
|
"compilerOptions": {
|
|
5
5
|
"noImplicitAny": true,
|
|
6
|
-
"rootDir": "./
|
|
7
|
-
"baseUrl": "./
|
|
8
|
-
"paths": {
|
|
9
|
-
"@atproto/lex-client": ["./dist/index.js"]
|
|
10
|
-
}
|
|
6
|
+
"rootDir": "./",
|
|
7
|
+
"baseUrl": "./"
|
|
11
8
|
}
|
|
12
9
|
}
|
package/dist/error.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { LexValue } from '@atproto/lex-data';
|
|
2
|
-
import { l } from '@atproto/lex-schema';
|
|
3
|
-
export declare enum KnownError {
|
|
4
|
-
Unknown = "Unknown",
|
|
5
|
-
AuthenticationRequired = "AuthenticationRequired",
|
|
6
|
-
Forbidden = "Forbidden",
|
|
7
|
-
InternalServerError = "InternalServerError",
|
|
8
|
-
InvalidRequest = "InvalidRequest",
|
|
9
|
-
InvalidResponse = "InvalidResponse",
|
|
10
|
-
MethodNotImplemented = "MethodNotImplemented",
|
|
11
|
-
NotAcceptable = "NotAcceptable",
|
|
12
|
-
NotEnoughResources = "NotEnoughResources",
|
|
13
|
-
PayloadTooLarge = "PayloadTooLarge",
|
|
14
|
-
RateLimitExceeded = "RateLimitExceeded",
|
|
15
|
-
UnsupportedMediaType = "UnsupportedMediaType",
|
|
16
|
-
UpstreamFailure = "UpstreamFailure",
|
|
17
|
-
UpstreamTimeout = "UpstreamTimeout",
|
|
18
|
-
XRPCNotSupported = "XRPCNotSupported"
|
|
19
|
-
}
|
|
20
|
-
export type XrpcFailure<N extends string, E> = l.ResultFailure<E> & {
|
|
21
|
-
name: N;
|
|
22
|
-
};
|
|
23
|
-
export type XrpcErrorName = l.UnknownString | KnownError;
|
|
24
|
-
export declare const xrpcErrorNameSchema: l.StringSchema<{
|
|
25
|
-
readonly minLength: 1;
|
|
26
|
-
}>;
|
|
27
|
-
export type XrpcErrorBody<N extends XrpcErrorName = XrpcErrorName> = {
|
|
28
|
-
error: N;
|
|
29
|
-
message?: string;
|
|
30
|
-
};
|
|
31
|
-
export declare const xrpcErrorBodySchema: l.ObjectSchema<{
|
|
32
|
-
readonly error: l.StringSchema<{
|
|
33
|
-
readonly minLength: 1;
|
|
34
|
-
}>;
|
|
35
|
-
readonly message: l.OptionalSchema<string>;
|
|
36
|
-
}>;
|
|
37
|
-
/**
|
|
38
|
-
* @implements {XrpcFailure<N, XrpcError<N>>} for convenience in result handling contexts.
|
|
39
|
-
*/
|
|
40
|
-
export declare class XrpcError<N extends XrpcErrorName = XrpcErrorName> extends Error implements XrpcFailure<N, XrpcError<N>> {
|
|
41
|
-
readonly name: N;
|
|
42
|
-
constructor(name: N, message?: string, options?: ErrorOptions);
|
|
43
|
-
/** @see {@link l.ResultFailure.success} */
|
|
44
|
-
readonly success: false;
|
|
45
|
-
/** @see {@link l.ResultFailure.error} */
|
|
46
|
-
get error(): this;
|
|
47
|
-
static from(cause: unknown, message?: string): XrpcError;
|
|
48
|
-
}
|
|
49
|
-
export declare class XrpcServiceError<N extends XrpcErrorName = XrpcErrorName> extends XrpcError<N> {
|
|
50
|
-
readonly status: number;
|
|
51
|
-
readonly headers: Headers;
|
|
52
|
-
readonly body: undefined | LexValue;
|
|
53
|
-
constructor(name: N, status: number, headers: Headers, body: undefined | LexValue, message?: string, options?: ErrorOptions);
|
|
54
|
-
}
|
|
55
|
-
export declare class XrpcResponseError<N extends XrpcErrorName = XrpcErrorName, B extends XrpcErrorBody<N> = XrpcErrorBody<N>> extends XrpcError<N> {
|
|
56
|
-
readonly status: number;
|
|
57
|
-
readonly headers: Headers;
|
|
58
|
-
readonly encoding: undefined | string;
|
|
59
|
-
readonly body: B;
|
|
60
|
-
constructor(status: number, headers: Headers, encoding: undefined | string, body: B, options?: ErrorOptions);
|
|
61
|
-
}
|
|
62
|
-
export type XrpcRequestFailure<M extends l.Procedure | l.Query> = (M extends {
|
|
63
|
-
errors: readonly (infer N extends string)[];
|
|
64
|
-
} ? XrpcResponseError<N> : never) | XrpcFailure<'Unknown', XrpcResponseError<string>> | XrpcFailure<'UnexpectedError', unknown>;
|
|
65
|
-
export declare function asXrpcRequestFailureFor<M extends l.Procedure | l.Query>(schema: M): (error: unknown) => XrpcRequestFailure<M>;
|
|
66
|
-
//# sourceMappingURL=error.d.ts.map
|
package/dist/error.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"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"}
|