@atproto/lex-client 0.1.5 → 0.2.1
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 +25 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js.map +1 -1
- package/dist/client.d.ts +42 -21
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +97 -16
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +6 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -3
- package/dist/errors.js.map +1 -1
- package/dist/response.d.ts +3 -2
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +2 -1
- package/dist/response.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +6 -2
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +25 -0
- package/dist/util.js.map +1 -1
- package/dist/write-operation-builder.d.ts +3 -2
- package/dist/write-operation-builder.d.ts.map +1 -1
- package/dist/write-operation-builder.js +2 -2
- package/dist/write-operation-builder.js.map +1 -1
- package/dist/xrpc.d.ts +8 -6
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +1 -1
- package/dist/xrpc.js.map +1 -1
- package/package.json +11 -14
- package/src/agent.test.ts +0 -216
- package/src/agent.ts +0 -186
- package/src/client.ts +0 -1086
- package/src/errors.test.ts +0 -626
- package/src/errors.ts +0 -570
- package/src/index.ts +0 -6
- package/src/lexicons/com/atproto/repo/applyWrites.defs.ts +0 -201
- package/src/lexicons/com/atproto/repo/applyWrites.ts +0 -6
- package/src/lexicons/com/atproto/repo/createRecord.defs.ts +0 -58
- package/src/lexicons/com/atproto/repo/createRecord.ts +0 -6
- package/src/lexicons/com/atproto/repo/defs.defs.ts +0 -28
- package/src/lexicons/com/atproto/repo/defs.ts +0 -5
- package/src/lexicons/com/atproto/repo/deleteRecord.defs.ts +0 -52
- package/src/lexicons/com/atproto/repo/deleteRecord.ts +0 -6
- package/src/lexicons/com/atproto/repo/getRecord.defs.ts +0 -37
- package/src/lexicons/com/atproto/repo/getRecord.ts +0 -6
- package/src/lexicons/com/atproto/repo/listRecords.defs.ts +0 -65
- package/src/lexicons/com/atproto/repo/listRecords.ts +0 -6
- package/src/lexicons/com/atproto/repo/putRecord.defs.ts +0 -59
- package/src/lexicons/com/atproto/repo/putRecord.ts +0 -6
- package/src/lexicons/com/atproto/repo/uploadBlob.defs.ts +0 -35
- package/src/lexicons/com/atproto/repo/uploadBlob.ts +0 -6
- package/src/lexicons/com/atproto/repo.ts +0 -12
- package/src/lexicons/com/atproto/sync/getBlob.defs.ts +0 -37
- package/src/lexicons/com/atproto/sync/getBlob.ts +0 -6
- package/src/lexicons/com/atproto/sync.ts +0 -5
- package/src/lexicons/com/atproto.ts +0 -6
- package/src/lexicons/com.ts +0 -5
- package/src/lexicons/index.ts +0 -5
- package/src/response.bench.ts +0 -113
- package/src/response.ts +0 -366
- package/src/types.ts +0 -71
- package/src/util.test.ts +0 -333
- package/src/util.ts +0 -182
- package/src/write-operation-builder.ts +0 -110
- package/src/www-authenticate.test.ts +0 -227
- package/src/www-authenticate.ts +0 -101
- package/src/xrpc.test.ts +0 -1450
- package/src/xrpc.ts +0 -446
- package/tsconfig.build.json +0 -12
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -8
package/src/lexicons/index.ts
DELETED
package/src/response.bench.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { bench, describe } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
LexParseOptions,
|
|
4
|
-
jsonToLex,
|
|
5
|
-
lexParse,
|
|
6
|
-
lexParseJsonBytes,
|
|
7
|
-
} from '@atproto/lex-json'
|
|
8
|
-
|
|
9
|
-
// This benchmark compares the performance of three approaches to parsing JSON
|
|
10
|
-
// responses in the Lex client:
|
|
11
|
-
// 1. lexParse(await response.text()): An approach that reads the response as
|
|
12
|
-
// text and directly parses it with lexParse.
|
|
13
|
-
// 2. jsonToLex(await response.json()): An approach that first parses the
|
|
14
|
-
// response as JSON to a plain JS object and then converts it to LexValue
|
|
15
|
-
// using jsonToLex.
|
|
16
|
-
// 3. lexParseJsonBytes(await response.bytes()): An approach that reads the
|
|
17
|
-
// response as bytes and uses a specialized function to parse JSON directly
|
|
18
|
-
// from bytes.
|
|
19
|
-
|
|
20
|
-
describe('small object', () => {
|
|
21
|
-
benchData({
|
|
22
|
-
$type: 'app.bsky.feed.post',
|
|
23
|
-
text: 'Hello world! 👋',
|
|
24
|
-
createdAt: '2024-01-01T00:00:00Z',
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
describe('simple mixed structure', () => {
|
|
29
|
-
benchData({
|
|
30
|
-
cid: {
|
|
31
|
-
$link: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
32
|
-
},
|
|
33
|
-
bytes: {
|
|
34
|
-
$bytes: 'nFERjvLLiw9qm45JrqH9QTzyC2Lu1Xb4ne6+sBrCzI0',
|
|
35
|
-
},
|
|
36
|
-
blob: {
|
|
37
|
-
$type: 'blob',
|
|
38
|
-
ref: {
|
|
39
|
-
$link: 'bafkreig77vqcdozl2wyk6z3cscaj5q5fggi53aoh64fewkdiri3cdauyn4',
|
|
40
|
-
},
|
|
41
|
-
mimeType: 'image/jpeg',
|
|
42
|
-
size: 10000,
|
|
43
|
-
},
|
|
44
|
-
nested: {
|
|
45
|
-
array: [
|
|
46
|
-
{
|
|
47
|
-
number: 42,
|
|
48
|
-
string: 'hello world',
|
|
49
|
-
bool: true,
|
|
50
|
-
null: null,
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
string: 'Hello 世界! 🌍🌎🌏 Ñoño',
|
|
54
|
-
createdAt: '2024-01-01T00:00:00Z',
|
|
55
|
-
},
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
describe('large structure', () => {
|
|
60
|
-
benchData({
|
|
61
|
-
items: Array.from({ length: 25 }, (_, i) => ({
|
|
62
|
-
id: i,
|
|
63
|
-
name: `Item ${i}`,
|
|
64
|
-
longUnicode:
|
|
65
|
-
'Lorem ipsum dolor sit amet, consectetur adipiscing elit 🤩.\n'.repeat(
|
|
66
|
-
2,
|
|
67
|
-
),
|
|
68
|
-
tags: ['tag1', 'tag2', 'tag3'],
|
|
69
|
-
bytes: {
|
|
70
|
-
$bytes: Buffer.from(`This is some byte data for item ${i}`).toString(
|
|
71
|
-
'base64',
|
|
72
|
-
),
|
|
73
|
-
},
|
|
74
|
-
cid: {
|
|
75
|
-
$link: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
76
|
-
},
|
|
77
|
-
metadata: {
|
|
78
|
-
created: '2024-01-01T00:00:00Z',
|
|
79
|
-
count: i * 10,
|
|
80
|
-
nested: {
|
|
81
|
-
flag: i % 2 === 0,
|
|
82
|
-
values: [i, i * 2, i * 3],
|
|
83
|
-
},
|
|
84
|
-
items: Array.from({ length: 5 }, (_, j) => ({
|
|
85
|
-
id: `${i}-${j}`,
|
|
86
|
-
value: `Value ${i}-${j}`,
|
|
87
|
-
})),
|
|
88
|
-
},
|
|
89
|
-
})),
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
function benchData(data: unknown, options?: LexParseOptions) {
|
|
94
|
-
const body = Buffer.from(JSON.stringify(data))
|
|
95
|
-
const init = {
|
|
96
|
-
headers: { 'Content-Type': 'application/json' },
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
bench('lexParse(await response.text())', async () => {
|
|
100
|
-
const response = new Response(body, init)
|
|
101
|
-
lexParse(await response.text(), options)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
bench('jsonToLex(await response.json())', async () => {
|
|
105
|
-
const response = new Response(body, init)
|
|
106
|
-
jsonToLex(await response.json(), options)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
bench('lexParseJsonBytes(await response.bytes())', async () => {
|
|
110
|
-
const response = new Response(body, init)
|
|
111
|
-
lexParseJsonBytes(await response.bytes(), options)
|
|
112
|
-
})
|
|
113
|
-
}
|
package/src/response.ts
DELETED
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
import { LexParseOptions, jsonToLex } from '@atproto/lex-json'
|
|
2
|
-
import {
|
|
3
|
-
InferMethodOutputEncoding,
|
|
4
|
-
InferOutput,
|
|
5
|
-
LexValue,
|
|
6
|
-
Payload,
|
|
7
|
-
Procedure,
|
|
8
|
-
Query,
|
|
9
|
-
ResultSuccess,
|
|
10
|
-
Validator,
|
|
11
|
-
} from '@atproto/lex-schema'
|
|
12
|
-
import {
|
|
13
|
-
XrpcAuthenticationError,
|
|
14
|
-
XrpcInvalidResponseError,
|
|
15
|
-
XrpcResponseError,
|
|
16
|
-
XrpcResponseValidationError,
|
|
17
|
-
} from './errors.js'
|
|
18
|
-
import {
|
|
19
|
-
EncodingString,
|
|
20
|
-
XrpcUnknownResponsePayload,
|
|
21
|
-
isEncodingString,
|
|
22
|
-
} from './types.js'
|
|
23
|
-
|
|
24
|
-
const CONTENT_TYPE_BINARY = 'application/octet-stream'
|
|
25
|
-
const CONTENT_TYPE_JSON = 'application/json'
|
|
26
|
-
|
|
27
|
-
// @NOTE the output schema is used in "parse" mode (safeParse), which means that
|
|
28
|
-
// defaults will be applied and coercions will be performed, so we need to use
|
|
29
|
-
// InferOutput here to get the final parsed type, not Infer/InferInput. For this
|
|
30
|
-
// reason, we cannot use InferMethodOutputBody and InferMethodOutput from
|
|
31
|
-
// lex-schema here.
|
|
32
|
-
|
|
33
|
-
type InferEncodingType<TEncoding extends string> = TEncoding extends '*/*'
|
|
34
|
-
? EncodingString
|
|
35
|
-
: TEncoding extends `${infer T extends string}/*`
|
|
36
|
-
? `${T}/${string}`
|
|
37
|
-
: TEncoding
|
|
38
|
-
|
|
39
|
-
type InferBodyType<
|
|
40
|
-
TEncoding extends string,
|
|
41
|
-
TSchema,
|
|
42
|
-
> = TSchema extends Validator
|
|
43
|
-
? InferOutput<TSchema>
|
|
44
|
-
: TEncoding extends `application/json`
|
|
45
|
-
? LexValue
|
|
46
|
-
: Uint8Array
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* The body type of an XRPC response, inferred from the method's output schema.
|
|
50
|
-
*
|
|
51
|
-
* For JSON responses, this is the parsed LexValue. For binary responses,
|
|
52
|
-
* this is a Uint8Array.
|
|
53
|
-
*
|
|
54
|
-
* @typeParam M - The XRPC method type (Procedure or Query)
|
|
55
|
-
*/
|
|
56
|
-
export type XrpcResponseBody<M extends Procedure | Query> =
|
|
57
|
-
M['output'] extends Payload<infer TEncoding, infer TSchema>
|
|
58
|
-
? TEncoding extends string
|
|
59
|
-
? InferBodyType<TEncoding, TSchema>
|
|
60
|
-
: undefined | LexValue | Uint8Array
|
|
61
|
-
: never
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The full payload type of an XRPC response, including body and encoding.
|
|
65
|
-
*
|
|
66
|
-
* Returns `null` for methods that have no output.
|
|
67
|
-
*
|
|
68
|
-
* @typeParam M - The XRPC method type (Procedure or Query)
|
|
69
|
-
*/
|
|
70
|
-
export type XrpcResponsePayload<M extends Procedure | Query> =
|
|
71
|
-
M['output'] extends Payload<infer TEncoding, infer TSchema>
|
|
72
|
-
? TEncoding extends string
|
|
73
|
-
? {
|
|
74
|
-
encoding: InferEncodingType<TEncoding>
|
|
75
|
-
body: InferBodyType<TEncoding, TSchema>
|
|
76
|
-
}
|
|
77
|
-
: // If the schema does not specify an output encoding, anything could be
|
|
78
|
-
// returned, including no payload at all (undefined).
|
|
79
|
-
undefined | { body: LexValue | Uint8Array; encoding: string }
|
|
80
|
-
: never
|
|
81
|
-
|
|
82
|
-
export type XrpcResponseOptions = {
|
|
83
|
-
/**
|
|
84
|
-
* Whether to validate the response against the method's output schema.
|
|
85
|
-
* Disabling this can improve performance but may lead to runtime errors if
|
|
86
|
-
* the response does not conform to the expected schema. Only set this to
|
|
87
|
-
* `false` if you are certain that the upstream service will always return
|
|
88
|
-
* valid responses.
|
|
89
|
-
*
|
|
90
|
-
* @default true
|
|
91
|
-
*/
|
|
92
|
-
validateResponse?: boolean
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Whether to strictly process response payloads according to Lex encoding
|
|
96
|
-
* rules. By default, the client will reject responses with invalid Lex data
|
|
97
|
-
* (floats and invalid $bytes / $link objects).
|
|
98
|
-
*
|
|
99
|
-
* Setting this option to `false` will allow the client to accept such
|
|
100
|
-
* responses in a non-strict mode, where invalid Lex data will be returned
|
|
101
|
-
* as-is (e.g., floats will not be rejected, and invalid $bytes / $link
|
|
102
|
-
* objects will not be converted to Uint8Array / Cid). When in non-strict
|
|
103
|
-
* mode, the validation will also be relaxed when validating the response
|
|
104
|
-
* against the method's output schema, allowing values that do not strictly
|
|
105
|
-
* conform to the schema (e.g. datetime strings that are not valid RFC3339
|
|
106
|
-
* format, blobs that are not of the right size/mime-type, etc.) to be
|
|
107
|
-
* accepted as long as their basic structure is correct.
|
|
108
|
-
*
|
|
109
|
-
* When validation is enabled (the default), the values defined through the
|
|
110
|
-
* method schema will be enforced, ensuring that the client can still process
|
|
111
|
-
* the response even if the server returns invalid Lex data.
|
|
112
|
-
*
|
|
113
|
-
* @default true
|
|
114
|
-
* @see {@link LexParseOptions.strict}
|
|
115
|
-
*/
|
|
116
|
-
strictResponseProcessing?: boolean
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Small container for XRPC response.
|
|
121
|
-
*
|
|
122
|
-
* @implements {ResultSuccess<XrpcResponse<M>>} for convenience in result handling contexts.
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
*
|
|
126
|
-
* ```typescript
|
|
127
|
-
* import { app } from '#/lexicons'
|
|
128
|
-
* import { XrpcResponse } from '@atproto/lex-client'
|
|
129
|
-
*
|
|
130
|
-
* const fetchResponse = await fetch('https://example.com/xrpc/app.bsky.feed.getTimeline')
|
|
131
|
-
*
|
|
132
|
-
* const response = await XrpcResponse.fromFetchResponse(
|
|
133
|
-
* app.bsky.feed.getTimeline.main,
|
|
134
|
-
* fetchResponse,
|
|
135
|
-
* )
|
|
136
|
-
*
|
|
137
|
-
* // Fully typed (validated) response body, according to the method's output schema
|
|
138
|
-
* const { cursor, feed } = response.body
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
export class XrpcResponse<M extends Procedure | Query>
|
|
142
|
-
implements ResultSuccess<XrpcResponse<M>>
|
|
143
|
-
{
|
|
144
|
-
/** @see {@link ResultSuccess.success} */
|
|
145
|
-
readonly success = true as const
|
|
146
|
-
|
|
147
|
-
/** @see {@link ResultSuccess.value} */
|
|
148
|
-
get value(): this {
|
|
149
|
-
return this
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
constructor(
|
|
153
|
-
readonly method: M,
|
|
154
|
-
readonly status: number,
|
|
155
|
-
readonly headers: Headers,
|
|
156
|
-
readonly payload: XrpcResponsePayload<M>,
|
|
157
|
-
) {}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Whether the response payload was parsed as {@link LexValue} (`true`) or is
|
|
161
|
-
* in binary form {@link Uint8Array} (`false`).
|
|
162
|
-
*/
|
|
163
|
-
get isParsed() {
|
|
164
|
-
return this.method.output.encoding === CONTENT_TYPE_JSON
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* The Content-Type encoding of the response (e.g., 'application/json').
|
|
169
|
-
* Returns `undefined` if the response has no body.
|
|
170
|
-
*/
|
|
171
|
-
get encoding() {
|
|
172
|
-
return this.payload?.encoding as InferMethodOutputEncoding<M>
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* The parsed response body.
|
|
177
|
-
*
|
|
178
|
-
* For 'application/json' responses, this is the parsed and validated LexValue.
|
|
179
|
-
* For binary responses, this is a Uint8Array.
|
|
180
|
-
* Returns `undefined` if the response has no body.
|
|
181
|
-
*/
|
|
182
|
-
get body() {
|
|
183
|
-
return this.payload?.body as XrpcResponseBody<M>
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* @throws {XrpcResponseError} in case of (valid) XRPC error responses. Use
|
|
188
|
-
* {@link XrpcResponseError.matchesSchemaErrors} to narrow the error type based on
|
|
189
|
-
* the method's declared error schema. This can be narrowed further as a
|
|
190
|
-
* {@link XrpcAuthenticationError} if the error is an authentication error.
|
|
191
|
-
* @throws {XrpcInvalidResponseError} when the response is not a valid XRPC
|
|
192
|
-
* response, or if the response does not conform to the method's schema.
|
|
193
|
-
*/
|
|
194
|
-
static async fromFetchResponse<const M extends Procedure | Query>(
|
|
195
|
-
method: M,
|
|
196
|
-
response: Response,
|
|
197
|
-
options?: XrpcResponseOptions,
|
|
198
|
-
): Promise<XrpcResponse<M>> {
|
|
199
|
-
// @NOTE The body MUST either be read or canceled to avoid resource leaks.
|
|
200
|
-
// Since nothing should cause an exception before "readPayload" is
|
|
201
|
-
// called, we can safely not use a try/finally here.
|
|
202
|
-
|
|
203
|
-
// Always turn 4xx/5xx responses into XrpcResponseError
|
|
204
|
-
if (response.status >= 400) {
|
|
205
|
-
const payload = await readPayload(method, response, {
|
|
206
|
-
// Always parse errors in non-strict mode
|
|
207
|
-
parse: { strict: false },
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
if (response.status === 401) {
|
|
211
|
-
throw new XrpcAuthenticationError<M>(method, response, payload)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
throw new XrpcResponseError<M>(method, response, payload)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// @NOTE redirect is set to 'follow', so we shouldn't get 3xx responses here
|
|
218
|
-
if (response.status < 200 || response.status >= 300) {
|
|
219
|
-
await response.body?.cancel()
|
|
220
|
-
|
|
221
|
-
throw new XrpcInvalidResponseError(
|
|
222
|
-
method,
|
|
223
|
-
response,
|
|
224
|
-
undefined,
|
|
225
|
-
`Unexpected status code ${response.status}`,
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const payload = await readPayload(method, response, {
|
|
230
|
-
// Parse response if there is a schema, or if the encoding is
|
|
231
|
-
// "application/json"
|
|
232
|
-
parse:
|
|
233
|
-
method.output.schema || method.output.encoding === CONTENT_TYPE_JSON
|
|
234
|
-
? { strict: options?.strictResponseProcessing ?? true }
|
|
235
|
-
: // If there is no declared output encoding, we'll parse the output (in loose mode)
|
|
236
|
-
method.output.encoding == null
|
|
237
|
-
? { strict: false }
|
|
238
|
-
: false,
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
if (!method.output.matchesEncoding(payload?.encoding)) {
|
|
242
|
-
throw new XrpcInvalidResponseError(
|
|
243
|
-
method,
|
|
244
|
-
response,
|
|
245
|
-
payload,
|
|
246
|
-
`Expected ${stringifyEncoding(method.output.encoding)} response (got ${stringifyEncoding(payload?.encoding)})`,
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Response is successful (2xx). Validate payload (data and encoding) against schema.
|
|
251
|
-
if (method.output.encoding != null) {
|
|
252
|
-
// If the schema specifies an output, verify that the response properly
|
|
253
|
-
// matches the expected format (encoding and schema, if present). If no
|
|
254
|
-
// output is specified, any payload could be returned.
|
|
255
|
-
|
|
256
|
-
// Needed for type safety. Should never happen since matchesEncoding()
|
|
257
|
-
// should return not succeed if there is a schema encoding but no payload.
|
|
258
|
-
if (!payload) throw new Error('Expected payload')
|
|
259
|
-
|
|
260
|
-
// Assert valid response body.
|
|
261
|
-
if (method.output.schema && options?.validateResponse !== false) {
|
|
262
|
-
const result = method.output.schema.safeParse(payload.body, {
|
|
263
|
-
strict: options?.strictResponseProcessing ?? true,
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
if (!result.success) {
|
|
267
|
-
throw new XrpcResponseValidationError(
|
|
268
|
-
method,
|
|
269
|
-
response,
|
|
270
|
-
payload,
|
|
271
|
-
result.reason,
|
|
272
|
-
)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const parsedPayload = {
|
|
276
|
-
body: result.value,
|
|
277
|
-
encoding: payload.encoding,
|
|
278
|
-
} as XrpcResponsePayload<M>
|
|
279
|
-
|
|
280
|
-
return new XrpcResponse<M>(
|
|
281
|
-
method,
|
|
282
|
-
response.status,
|
|
283
|
-
response.headers,
|
|
284
|
-
parsedPayload,
|
|
285
|
-
)
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return new XrpcResponse<M>(
|
|
290
|
-
method,
|
|
291
|
-
response.status,
|
|
292
|
-
response.headers,
|
|
293
|
-
payload as XrpcResponsePayload<M>,
|
|
294
|
-
)
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
type ReadPayloadOptions = {
|
|
299
|
-
/**
|
|
300
|
-
* Whether to parse the response body as JSON and convert it to LexValue.
|
|
301
|
-
*
|
|
302
|
-
* @default false
|
|
303
|
-
*/
|
|
304
|
-
parse?: false | LexParseOptions
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* @note this function always consumes the response body
|
|
309
|
-
*/
|
|
310
|
-
async function readPayload(
|
|
311
|
-
method: Query | Procedure,
|
|
312
|
-
response: Response,
|
|
313
|
-
options?: ReadPayloadOptions,
|
|
314
|
-
): Promise<undefined | XrpcUnknownResponsePayload> {
|
|
315
|
-
try {
|
|
316
|
-
// @TODO Should we limit the maximum response size here (this could also be
|
|
317
|
-
// done by the FetchHandler)?
|
|
318
|
-
|
|
319
|
-
const encoding = response.headers
|
|
320
|
-
.get('content-type')
|
|
321
|
-
?.split(';')[0]
|
|
322
|
-
.trim()
|
|
323
|
-
.toLowerCase()
|
|
324
|
-
|
|
325
|
-
// Response content-type is undefined
|
|
326
|
-
if (!encoding) {
|
|
327
|
-
// If the body is empty, return undefined (= no payload)
|
|
328
|
-
const arrayBuffer = await response.arrayBuffer()
|
|
329
|
-
if (arrayBuffer.byteLength === 0) return undefined
|
|
330
|
-
|
|
331
|
-
// If we got data despite no content-type, treat it as binary
|
|
332
|
-
return {
|
|
333
|
-
encoding: CONTENT_TYPE_BINARY,
|
|
334
|
-
body: new Uint8Array(arrayBuffer),
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (!isEncodingString(encoding)) {
|
|
339
|
-
throw new TypeError(`Invalid content-type "${encoding}" in response`)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (options?.parse && encoding === CONTENT_TYPE_JSON) {
|
|
343
|
-
// @NOTE See ./response.bench.ts to comparison of different parsing approaches.
|
|
344
|
-
const json = await response.json()
|
|
345
|
-
const body = jsonToLex(json, options.parse)
|
|
346
|
-
return { encoding, body }
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const arrayBuffer = await response.arrayBuffer()
|
|
350
|
-
return { encoding, body: new Uint8Array(arrayBuffer) }
|
|
351
|
-
} catch (cause) {
|
|
352
|
-
const message = 'Unable to parse response payload'
|
|
353
|
-
const messageDetail = cause instanceof TypeError ? cause.message : undefined
|
|
354
|
-
throw new XrpcInvalidResponseError(
|
|
355
|
-
method,
|
|
356
|
-
response,
|
|
357
|
-
undefined,
|
|
358
|
-
messageDetail ? `${message}: ${messageDetail}` : message,
|
|
359
|
-
{ cause },
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function stringifyEncoding(encoding: string | undefined) {
|
|
365
|
-
return encoding ? `"${encoding}"` : 'no payload'
|
|
366
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { DidString, LexValue, UnknownString } from '@atproto/lex-schema'
|
|
2
|
-
|
|
3
|
-
export type { DidString, LexValue, UnknownString }
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Service identifier fragment for DID service endpoints.
|
|
7
|
-
*
|
|
8
|
-
* Common values include 'atproto_labeler' for labeling services,
|
|
9
|
-
* or custom service identifiers.
|
|
10
|
-
*/
|
|
11
|
-
export type DidServiceIdentifier = 'atproto_labeler' | UnknownString
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A full service proxy identifier combining a DID with a service fragment.
|
|
15
|
-
*
|
|
16
|
-
* Used to route requests through a specific service endpoint.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```typescript
|
|
20
|
-
* const service: Service = 'did:web:api.bsky.app#bsky_appview'
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export type Service = `${DidString}#${DidServiceIdentifier}`
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Valid input types for binary request bodies.
|
|
27
|
-
*
|
|
28
|
-
* These types can be used as the body for procedures that expect
|
|
29
|
-
* non-JSON content (e.g., blob uploads, binary data).
|
|
30
|
-
*
|
|
31
|
-
* @example Uploading a blob
|
|
32
|
-
* ```typescript
|
|
33
|
-
* const imageData: BinaryBodyInit = new Uint8Array(buffer)
|
|
34
|
-
* await client.uploadBlob(imageData, { encoding: 'image/png' })
|
|
35
|
-
* ```
|
|
36
|
-
*
|
|
37
|
-
* @example Streaming upload
|
|
38
|
-
* ```typescript
|
|
39
|
-
* const stream: BinaryBodyInit = someReadableStream
|
|
40
|
-
* await client.xrpc(uploadMethod, { body: stream })
|
|
41
|
-
* ```
|
|
42
|
-
*
|
|
43
|
-
* @example File upload in browser
|
|
44
|
-
* ```typescript
|
|
45
|
-
* const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
46
|
-
* const file: BinaryBodyInit = fileInput.files[0]
|
|
47
|
-
* await client.xrpc(uploadMethod, { body: file })
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
export type BinaryBodyInit =
|
|
51
|
-
| string
|
|
52
|
-
| Blob
|
|
53
|
-
| Uint8Array
|
|
54
|
-
| ArrayBuffer
|
|
55
|
-
| ReadableStream<Uint8Array>
|
|
56
|
-
| AsyncIterable<Uint8Array>
|
|
57
|
-
|
|
58
|
-
export type EncodingString = `${string}/${string}`
|
|
59
|
-
|
|
60
|
-
export function isEncodingString(
|
|
61
|
-
contentType: string,
|
|
62
|
-
): contentType is EncodingString {
|
|
63
|
-
return contentType.includes('/')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export type XrpcUnknownResponsePayload<
|
|
67
|
-
TBinary extends BinaryBodyInit = Uint8Array,
|
|
68
|
-
> = {
|
|
69
|
-
encoding: EncodingString
|
|
70
|
-
body: LexValue | TBinary
|
|
71
|
-
}
|