@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/util.test.ts
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
buildXrpcRequestHeaders,
|
|
4
|
-
isAsyncIterable,
|
|
5
|
-
isBlobLike,
|
|
6
|
-
toReadableStream,
|
|
7
|
-
toReadableStreamPonyfill,
|
|
8
|
-
} from './util.js'
|
|
9
|
-
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// isBlobLike
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
|
-
describe(isBlobLike, () => {
|
|
15
|
-
it('returns true for native Blob', () => {
|
|
16
|
-
expect(isBlobLike(new Blob(['hello']))).toBe(true)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('returns true for native File', () => {
|
|
20
|
-
expect(isBlobLike(new File(['hello'], 'test.txt'))).toBe(true)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('returns false for null', () => {
|
|
24
|
-
expect(isBlobLike(null)).toBe(false)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('returns false for undefined', () => {
|
|
28
|
-
expect(isBlobLike(undefined)).toBe(false)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('returns false for primitives', () => {
|
|
32
|
-
expect(isBlobLike(42)).toBe(false)
|
|
33
|
-
expect(isBlobLike('string')).toBe(false)
|
|
34
|
-
expect(isBlobLike(true)).toBe(false)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('returns false for plain objects', () => {
|
|
38
|
-
expect(isBlobLike({})).toBe(false)
|
|
39
|
-
expect(isBlobLike({ stream: () => {} })).toBe(false)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('returns true for Blob-like objects with [Symbol.toStringTag] = "Blob"', () => {
|
|
43
|
-
const blobLike = {
|
|
44
|
-
[Symbol.toStringTag]: 'Blob',
|
|
45
|
-
stream: () => new ReadableStream(),
|
|
46
|
-
}
|
|
47
|
-
expect(isBlobLike(blobLike)).toBe(true)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('returns true for File-like objects with [Symbol.toStringTag] = "File"', () => {
|
|
51
|
-
const fileLike = {
|
|
52
|
-
[Symbol.toStringTag]: 'File',
|
|
53
|
-
stream: () => new ReadableStream(),
|
|
54
|
-
}
|
|
55
|
-
expect(isBlobLike(fileLike)).toBe(true)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('returns false for objects with Blob tag but no stream method', () => {
|
|
59
|
-
const notBlob = {
|
|
60
|
-
[Symbol.toStringTag]: 'Blob',
|
|
61
|
-
}
|
|
62
|
-
expect(isBlobLike(notBlob)).toBe(false)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('returns false for objects with Blob tag but non-function stream', () => {
|
|
66
|
-
const notBlob = {
|
|
67
|
-
[Symbol.toStringTag]: 'Blob',
|
|
68
|
-
stream: 'not a function',
|
|
69
|
-
}
|
|
70
|
-
expect(isBlobLike(notBlob)).toBe(false)
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
// ============================================================================
|
|
75
|
-
// isAsyncIterable
|
|
76
|
-
// ============================================================================
|
|
77
|
-
|
|
78
|
-
describe(isAsyncIterable, () => {
|
|
79
|
-
it('returns true for async generators', () => {
|
|
80
|
-
async function* gen() {
|
|
81
|
-
yield 1
|
|
82
|
-
}
|
|
83
|
-
expect(isAsyncIterable(gen())).toBe(true)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('returns true for objects with Symbol.asyncIterator', () => {
|
|
87
|
-
const iterable = {
|
|
88
|
-
[Symbol.asyncIterator]() {
|
|
89
|
-
return { next: async () => ({ done: true, value: undefined }) }
|
|
90
|
-
},
|
|
91
|
-
}
|
|
92
|
-
expect(isAsyncIterable(iterable)).toBe(true)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('returns false for null', () => {
|
|
96
|
-
expect(isAsyncIterable(null)).toBe(false)
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('returns false for undefined', () => {
|
|
100
|
-
expect(isAsyncIterable(undefined)).toBe(false)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('returns false for plain objects', () => {
|
|
104
|
-
expect(isAsyncIterable({})).toBe(false)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('returns false for sync iterables', () => {
|
|
108
|
-
expect(isAsyncIterable([1, 2, 3])).toBe(false)
|
|
109
|
-
expect(isAsyncIterable('string')).toBe(false)
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
// ============================================================================
|
|
114
|
-
// buildXrpcRequestHeaders
|
|
115
|
-
// ============================================================================
|
|
116
|
-
|
|
117
|
-
describe(buildXrpcRequestHeaders, () => {
|
|
118
|
-
it('returns empty headers when no options are set', () => {
|
|
119
|
-
const headers = buildXrpcRequestHeaders({})
|
|
120
|
-
expect([...headers.entries()]).toEqual([])
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it('sets atproto-proxy header from service option', () => {
|
|
124
|
-
const headers = buildXrpcRequestHeaders({
|
|
125
|
-
service: 'did:plc:1234#atproto_labeler',
|
|
126
|
-
})
|
|
127
|
-
expect(headers.get('atproto-proxy')).toBe('did:plc:1234#atproto_labeler')
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('does not override existing atproto-proxy header', () => {
|
|
131
|
-
const headers = buildXrpcRequestHeaders({
|
|
132
|
-
headers: { 'atproto-proxy': 'did:plc:existing#service' },
|
|
133
|
-
service: 'did:plc:new#service',
|
|
134
|
-
})
|
|
135
|
-
expect(headers.get('atproto-proxy')).toBe('did:plc:existing#service')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('sets atproto-accept-labelers from labelers option', () => {
|
|
139
|
-
const headers = buildXrpcRequestHeaders({
|
|
140
|
-
labelers: ['did:plc:labeler1', 'did:plc:labeler2'] as const,
|
|
141
|
-
})
|
|
142
|
-
expect(headers.get('atproto-accept-labelers')).toBe(
|
|
143
|
-
'did:plc:labeler1, did:plc:labeler2',
|
|
144
|
-
)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('appends to existing atproto-accept-labelers header', () => {
|
|
148
|
-
const headers = buildXrpcRequestHeaders({
|
|
149
|
-
headers: { 'atproto-accept-labelers': 'did:plc:existing' },
|
|
150
|
-
labelers: ['did:plc:new'] as const,
|
|
151
|
-
})
|
|
152
|
-
expect(headers.get('atproto-accept-labelers')).toBe(
|
|
153
|
-
'did:plc:new, did:plc:existing',
|
|
154
|
-
)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('passes through base headers', () => {
|
|
158
|
-
const headers = buildXrpcRequestHeaders({
|
|
159
|
-
headers: { Authorization: 'Bearer token123' },
|
|
160
|
-
})
|
|
161
|
-
expect(headers.get('Authorization')).toBe('Bearer token123')
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('accepts Headers instance as base headers', () => {
|
|
165
|
-
const base = new Headers({ 'X-Custom': 'value' })
|
|
166
|
-
const headers = buildXrpcRequestHeaders({ headers: base })
|
|
167
|
-
expect(headers.get('X-Custom')).toBe('value')
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('sets empty header for empty labelers iterable', () => {
|
|
171
|
-
const headers = buildXrpcRequestHeaders({ labelers: [] })
|
|
172
|
-
// An empty array still sets the header (to empty string), distinguishing
|
|
173
|
-
// "no labelers requested" from "labelers option not provided"
|
|
174
|
-
expect(headers.has('atproto-accept-labelers')).toBe(true)
|
|
175
|
-
expect(headers.get('atproto-accept-labelers')).toBe('')
|
|
176
|
-
})
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
// ============================================================================
|
|
180
|
-
// toReadableStream
|
|
181
|
-
// ============================================================================
|
|
182
|
-
|
|
183
|
-
describe(toReadableStream, () => {
|
|
184
|
-
it('converts async iterable to ReadableStream', async () => {
|
|
185
|
-
async function* gen() {
|
|
186
|
-
yield new Uint8Array([1, 2])
|
|
187
|
-
yield new Uint8Array([3, 4])
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const stream = toReadableStream(gen())
|
|
191
|
-
const reader = stream.getReader()
|
|
192
|
-
|
|
193
|
-
const chunk1 = await reader.read()
|
|
194
|
-
expect(chunk1.done).toBe(false)
|
|
195
|
-
expect(chunk1.value).toEqual(new Uint8Array([1, 2]))
|
|
196
|
-
|
|
197
|
-
const chunk2 = await reader.read()
|
|
198
|
-
expect(chunk2.done).toBe(false)
|
|
199
|
-
expect(chunk2.value).toEqual(new Uint8Array([3, 4]))
|
|
200
|
-
|
|
201
|
-
const end = await reader.read()
|
|
202
|
-
expect(end.done).toBe(true)
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('handles empty async iterable', async () => {
|
|
206
|
-
async function* gen() {
|
|
207
|
-
// yields nothing
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const stream = toReadableStream(gen())
|
|
211
|
-
const reader = stream.getReader()
|
|
212
|
-
|
|
213
|
-
const result = await reader.read()
|
|
214
|
-
expect(result.done).toBe(true)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('can be consumed with Response API', async () => {
|
|
218
|
-
async function* gen() {
|
|
219
|
-
yield new TextEncoder().encode('hello ')
|
|
220
|
-
yield new TextEncoder().encode('world')
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const stream = toReadableStream(gen())
|
|
224
|
-
const response = new Response(stream)
|
|
225
|
-
const text = await response.text()
|
|
226
|
-
expect(text).toBe('hello world')
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
it('propagates errors from the async iterable', async () => {
|
|
230
|
-
async function* gen() {
|
|
231
|
-
yield new Uint8Array([1])
|
|
232
|
-
throw new Error('stream error')
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const stream = toReadableStream(gen())
|
|
236
|
-
const reader = stream.getReader()
|
|
237
|
-
|
|
238
|
-
// First chunk succeeds
|
|
239
|
-
await reader.read()
|
|
240
|
-
|
|
241
|
-
// Second read should reject
|
|
242
|
-
await expect(reader.read()).rejects.toThrow('stream error')
|
|
243
|
-
})
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
// ============================================================================
|
|
247
|
-
// toReadableStreamPonyfill
|
|
248
|
-
// ============================================================================
|
|
249
|
-
|
|
250
|
-
describe(toReadableStreamPonyfill, () => {
|
|
251
|
-
it('converts async iterable to ReadableStream', async () => {
|
|
252
|
-
async function* gen() {
|
|
253
|
-
yield new Uint8Array([1, 2])
|
|
254
|
-
yield new Uint8Array([3, 4])
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const stream = toReadableStreamPonyfill(gen())
|
|
258
|
-
const reader = stream.getReader()
|
|
259
|
-
|
|
260
|
-
const chunk1 = await reader.read()
|
|
261
|
-
expect(chunk1.done).toBe(false)
|
|
262
|
-
expect(chunk1.value).toEqual(new Uint8Array([1, 2]))
|
|
263
|
-
|
|
264
|
-
const chunk2 = await reader.read()
|
|
265
|
-
expect(chunk2.done).toBe(false)
|
|
266
|
-
expect(chunk2.value).toEqual(new Uint8Array([3, 4]))
|
|
267
|
-
|
|
268
|
-
const end = await reader.read()
|
|
269
|
-
expect(end.done).toBe(true)
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
it('handles empty async iterable', async () => {
|
|
273
|
-
async function* gen() {
|
|
274
|
-
// yields nothing
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const stream = toReadableStreamPonyfill(gen())
|
|
278
|
-
const reader = stream.getReader()
|
|
279
|
-
|
|
280
|
-
const result = await reader.read()
|
|
281
|
-
expect(result.done).toBe(true)
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
it('can be consumed with Response API', async () => {
|
|
285
|
-
async function* gen() {
|
|
286
|
-
yield new TextEncoder().encode('hello ')
|
|
287
|
-
yield new TextEncoder().encode('world')
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const stream = toReadableStreamPonyfill(gen())
|
|
291
|
-
const response = new Response(stream)
|
|
292
|
-
const text = await response.text()
|
|
293
|
-
expect(text).toBe('hello world')
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
it('propagates errors from the async iterable', async () => {
|
|
297
|
-
async function* gen() {
|
|
298
|
-
yield new Uint8Array([1])
|
|
299
|
-
throw new Error('stream error')
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const stream = toReadableStreamPonyfill(gen())
|
|
303
|
-
const reader = stream.getReader()
|
|
304
|
-
|
|
305
|
-
await reader.read()
|
|
306
|
-
await expect(reader.read()).rejects.toThrow('stream error')
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
it('calls iterator.return() on cancel', async () => {
|
|
310
|
-
let returned = false
|
|
311
|
-
const iterable: AsyncIterable<Uint8Array> = {
|
|
312
|
-
[Symbol.asyncIterator]() {
|
|
313
|
-
return {
|
|
314
|
-
async next() {
|
|
315
|
-
return { done: false, value: new Uint8Array([1]) }
|
|
316
|
-
},
|
|
317
|
-
async return() {
|
|
318
|
-
returned = true
|
|
319
|
-
return { done: true, value: undefined }
|
|
320
|
-
},
|
|
321
|
-
}
|
|
322
|
-
},
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const stream = toReadableStreamPonyfill(iterable)
|
|
326
|
-
const reader = stream.getReader()
|
|
327
|
-
|
|
328
|
-
await reader.read()
|
|
329
|
-
await reader.cancel()
|
|
330
|
-
|
|
331
|
-
expect(returned).toBe(true)
|
|
332
|
-
})
|
|
333
|
-
})
|
package/src/util.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
InferRecordKey,
|
|
3
|
-
LexiconRecordKey,
|
|
4
|
-
RecordSchema,
|
|
5
|
-
} from '@atproto/lex-schema'
|
|
6
|
-
import type { DidString, Service } from './types.js'
|
|
7
|
-
|
|
8
|
-
export function applyDefaults<
|
|
9
|
-
TDefaults extends Record<string, unknown>,
|
|
10
|
-
TOptions extends {
|
|
11
|
-
[K in keyof TDefaults]?: TDefaults[K]
|
|
12
|
-
},
|
|
13
|
-
>(options: TOptions, defaults: TDefaults): TOptions & TDefaults {
|
|
14
|
-
const combined: Partial<TDefaults> = { ...options }
|
|
15
|
-
|
|
16
|
-
// @NOTE We make sure that options with an explicit `undefined` value get the
|
|
17
|
-
// default, since spreading doesn't override with `undefined`.
|
|
18
|
-
for (const key of Object.keys(defaults) as (keyof typeof defaults)[]) {
|
|
19
|
-
if (options[key] === undefined) {
|
|
20
|
-
combined[key] = defaults[key]
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return combined as TOptions & TDefaults
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Type guard to check if a value is {@link Blob}-like.
|
|
29
|
-
*
|
|
30
|
-
* Handles both native Blobs and polyfilled Blob implementations
|
|
31
|
-
* (e.g., fetch-blob from node-fetch).
|
|
32
|
-
*
|
|
33
|
-
* @param value - The value to check
|
|
34
|
-
* @returns `true` if the value is a Blob or Blob-like object
|
|
35
|
-
*/
|
|
36
|
-
export function isBlobLike(value: unknown): value is Blob {
|
|
37
|
-
if (value == null) return false
|
|
38
|
-
if (typeof value !== 'object') return false
|
|
39
|
-
if (typeof Blob === 'function' && value instanceof Blob) return true
|
|
40
|
-
|
|
41
|
-
// Support for Blobs provided by libraries that don't use the native Blob
|
|
42
|
-
// (e.g. fetch-blob from node-fetch).
|
|
43
|
-
// https://github.com/node-fetch/fetch-blob/blob/a1a182e5978811407bef4ea1632b517567dda01f/index.js#L233-L244
|
|
44
|
-
|
|
45
|
-
const tag = (value as any)[Symbol.toStringTag]
|
|
46
|
-
if (tag === 'Blob' || tag === 'File') {
|
|
47
|
-
return 'stream' in value && typeof value.stream === 'function'
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return false
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function isAsyncIterable<T>(
|
|
54
|
-
value: T,
|
|
55
|
-
): value is unknown extends T
|
|
56
|
-
? T & AsyncIterable<unknown>
|
|
57
|
-
: Extract<T, AsyncIterable<any>> {
|
|
58
|
-
return (
|
|
59
|
-
value != null && typeof (value as any)[Symbol.asyncIterator] === 'function'
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function asUint8ArrayArrayBuffer(
|
|
64
|
-
bytes: Uint8Array,
|
|
65
|
-
): Uint8Array<ArrayBuffer> {
|
|
66
|
-
// If the Uint8Array is already backed by a non-shared ArrayBuffer, we can use
|
|
67
|
-
// it directly.
|
|
68
|
-
if (bytes.buffer instanceof ArrayBuffer) {
|
|
69
|
-
return bytes as Uint8Array<ArrayBuffer>
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Otherwise, we need to create a new ArrayBuffer and copy the data.
|
|
73
|
-
return new Uint8Array(bytes)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type XrpcRequestHeadersOptions = {
|
|
77
|
-
/** Additional HTTP headers to include in the request. */
|
|
78
|
-
headers?: HeadersInit
|
|
79
|
-
|
|
80
|
-
/** Labeler DIDs to request labels from for content moderation. */
|
|
81
|
-
labelers?: Iterable<DidString>
|
|
82
|
-
|
|
83
|
-
/** Service proxy identifier for routing requests through a specific service. */
|
|
84
|
-
service?: Service
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Builds HTTP headers for AT Protocol requests.
|
|
89
|
-
*
|
|
90
|
-
* Adds the following headers when applicable:
|
|
91
|
-
* - `atproto-proxy`: Service routing header (if service is specified)
|
|
92
|
-
* - `atproto-accept-labelers`: Comma-separated list of labeler DIDs
|
|
93
|
-
*
|
|
94
|
-
* @see {@link XrpcRequestHeadersOptions}
|
|
95
|
-
* @returns A new Headers object with AT Protocol headers added
|
|
96
|
-
*/
|
|
97
|
-
export function buildXrpcRequestHeaders(
|
|
98
|
-
options: XrpcRequestHeadersOptions,
|
|
99
|
-
): Headers {
|
|
100
|
-
const headers = new Headers(options?.headers)
|
|
101
|
-
|
|
102
|
-
if (options.service && !headers.has('atproto-proxy')) {
|
|
103
|
-
headers.set('atproto-proxy', options.service)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (options.labelers) {
|
|
107
|
-
headers.set(
|
|
108
|
-
'atproto-accept-labelers',
|
|
109
|
-
[...options.labelers, headers.get('atproto-accept-labelers')?.trim()]
|
|
110
|
-
.filter(Boolean)
|
|
111
|
-
.join(', '),
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return headers
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function toReadableStream(
|
|
119
|
-
data: AsyncIterable<Uint8Array>,
|
|
120
|
-
): ReadableStream<Uint8Array> {
|
|
121
|
-
// Use the native ReadableStream.from() if available.
|
|
122
|
-
|
|
123
|
-
/* v8 ignore next -- @preserve */
|
|
124
|
-
if ('from' in ReadableStream && typeof ReadableStream.from === 'function') {
|
|
125
|
-
return ReadableStream.from(data)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/* v8 ignore next -- @preserve */
|
|
129
|
-
return toReadableStreamPonyfill(data)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function toReadableStreamPonyfill(
|
|
133
|
-
data: AsyncIterable<Uint8Array>,
|
|
134
|
-
): ReadableStream<Uint8Array> {
|
|
135
|
-
let iterator: AsyncIterator<Uint8Array> | undefined
|
|
136
|
-
return new ReadableStream<Uint8Array>({
|
|
137
|
-
async pull(controller) {
|
|
138
|
-
try {
|
|
139
|
-
iterator ??= data[Symbol.asyncIterator]()
|
|
140
|
-
const result = await iterator!.next()
|
|
141
|
-
if (result.done) controller.close()
|
|
142
|
-
else controller.enqueue(result.value)
|
|
143
|
-
} catch (err) {
|
|
144
|
-
controller.error(err)
|
|
145
|
-
iterator = undefined
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
async cancel() {
|
|
149
|
-
await iterator?.return?.()
|
|
150
|
-
iterator = undefined
|
|
151
|
-
},
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export type RecordKeyOptions<
|
|
156
|
-
T extends RecordSchema,
|
|
157
|
-
AlsoOptionalWhenRecordKeyIs extends LexiconRecordKey = never,
|
|
158
|
-
> = T['key'] extends `literal:${string}` | AlsoOptionalWhenRecordKeyIs
|
|
159
|
-
? { rkey?: InferRecordKey<T> }
|
|
160
|
-
: { rkey: InferRecordKey<T> }
|
|
161
|
-
|
|
162
|
-
export function getDefaultRecordKey<const T extends RecordSchema>(
|
|
163
|
-
schema: T,
|
|
164
|
-
): undefined | InferRecordKey<T> {
|
|
165
|
-
// Let the server generate the TID
|
|
166
|
-
if (schema.key === 'tid') return undefined
|
|
167
|
-
if (schema.key === 'any') return undefined
|
|
168
|
-
|
|
169
|
-
return getLiteralRecordKey(schema)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function getLiteralRecordKey<const T extends RecordSchema>(
|
|
173
|
-
schema: T,
|
|
174
|
-
): InferRecordKey<T> {
|
|
175
|
-
if (schema.key.startsWith('literal:')) {
|
|
176
|
-
return schema.key.slice(8) as InferRecordKey<T>
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
throw new TypeError(
|
|
180
|
-
`An "rkey" must be provided for record key type "${schema.key}" (${schema.$type})`,
|
|
181
|
-
)
|
|
182
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
$Typed,
|
|
3
|
-
InferInput,
|
|
4
|
-
Main,
|
|
5
|
-
RecordSchema,
|
|
6
|
-
Restricted,
|
|
7
|
-
getMain,
|
|
8
|
-
} from '@atproto/lex-schema'
|
|
9
|
-
import { com } from './lexicons/index.js'
|
|
10
|
-
import {
|
|
11
|
-
RecordKeyOptions,
|
|
12
|
-
getDefaultRecordKey,
|
|
13
|
-
getLiteralRecordKey,
|
|
14
|
-
} from './util.js'
|
|
15
|
-
|
|
16
|
-
export type WriteOperation =
|
|
17
|
-
| $Typed<com.atproto.repo.applyWrites.Create>
|
|
18
|
-
| $Typed<com.atproto.repo.applyWrites.Update>
|
|
19
|
-
| $Typed<com.atproto.repo.applyWrites.Delete>
|
|
20
|
-
|
|
21
|
-
export type WriteOperationCreateOptions<T extends RecordSchema> =
|
|
22
|
-
RecordKeyOptions<T, 'tid' | 'any'>
|
|
23
|
-
|
|
24
|
-
export type WriteOperationUpdateOptions<T extends RecordSchema> =
|
|
25
|
-
RecordKeyOptions<T>
|
|
26
|
-
|
|
27
|
-
export type WriteOperationDeleteOptions<T extends RecordSchema> =
|
|
28
|
-
RecordKeyOptions<T>
|
|
29
|
-
|
|
30
|
-
export type WriteOperationsFactory = (
|
|
31
|
-
helper: WriteOperationHelper,
|
|
32
|
-
) => Iterable<WriteOperation>
|
|
33
|
-
|
|
34
|
-
export class WriteOperationHelper {
|
|
35
|
-
private constructor() {}
|
|
36
|
-
|
|
37
|
-
create<const T extends RecordSchema>(
|
|
38
|
-
ns: NonNullable<unknown> extends WriteOperationCreateOptions<T>
|
|
39
|
-
? Main<T>
|
|
40
|
-
: Restricted<'This record type requires an "options" argument'>,
|
|
41
|
-
input: Omit<InferInput<T>, '$type'>,
|
|
42
|
-
): $Typed<com.atproto.repo.applyWrites.Create>
|
|
43
|
-
create<const T extends RecordSchema>(
|
|
44
|
-
ns: Main<T>,
|
|
45
|
-
input: Omit<InferInput<T>, '$type'>,
|
|
46
|
-
options: WriteOperationCreateOptions<T>,
|
|
47
|
-
): $Typed<com.atproto.repo.applyWrites.Create>
|
|
48
|
-
create<const T extends RecordSchema>(
|
|
49
|
-
ns: Main<T>,
|
|
50
|
-
input: Omit<InferInput<T>, '$type'>,
|
|
51
|
-
options: WriteOperationCreateOptions<T> = {} as WriteOperationCreateOptions<T>,
|
|
52
|
-
): $Typed<com.atproto.repo.applyWrites.Create> {
|
|
53
|
-
const schema: T = getMain(ns)
|
|
54
|
-
const value = schema.build(input)
|
|
55
|
-
return com.atproto.repo.applyWrites.create.$build({
|
|
56
|
-
collection: schema.$type,
|
|
57
|
-
value,
|
|
58
|
-
rkey: options?.rkey ?? getDefaultRecordKey(schema),
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
update<const T extends RecordSchema>(
|
|
63
|
-
ns: NonNullable<unknown> extends WriteOperationUpdateOptions<T>
|
|
64
|
-
? Main<T>
|
|
65
|
-
: Restricted<'This record type requires an "options" argument'>,
|
|
66
|
-
input: Omit<InferInput<T>, '$type'>,
|
|
67
|
-
): $Typed<com.atproto.repo.applyWrites.Update>
|
|
68
|
-
update<const T extends RecordSchema>(
|
|
69
|
-
ns: Main<T>,
|
|
70
|
-
input: Omit<InferInput<T>, '$type'>,
|
|
71
|
-
options: WriteOperationUpdateOptions<T>,
|
|
72
|
-
): $Typed<com.atproto.repo.applyWrites.Update>
|
|
73
|
-
update<const T extends RecordSchema>(
|
|
74
|
-
ns: Main<T>,
|
|
75
|
-
input: Omit<InferInput<T>, '$type'>,
|
|
76
|
-
options: WriteOperationUpdateOptions<T> = {} as WriteOperationUpdateOptions<T>,
|
|
77
|
-
): $Typed<com.atproto.repo.applyWrites.Update> {
|
|
78
|
-
const schema: T = getMain(ns)
|
|
79
|
-
const value = schema.build(input)
|
|
80
|
-
return com.atproto.repo.applyWrites.update.$build({
|
|
81
|
-
collection: schema.$type,
|
|
82
|
-
value,
|
|
83
|
-
rkey: options?.rkey ?? getLiteralRecordKey(schema),
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
delete<const T extends RecordSchema>(
|
|
88
|
-
ns: NonNullable<unknown> extends WriteOperationDeleteOptions<T>
|
|
89
|
-
? Main<T>
|
|
90
|
-
: Restricted<'This record type requires an "options" argument'>,
|
|
91
|
-
): $Typed<com.atproto.repo.applyWrites.Delete>
|
|
92
|
-
delete<const T extends RecordSchema>(
|
|
93
|
-
ns: Main<T>,
|
|
94
|
-
options: WriteOperationDeleteOptions<T>,
|
|
95
|
-
): $Typed<com.atproto.repo.applyWrites.Delete>
|
|
96
|
-
delete<const T extends RecordSchema>(
|
|
97
|
-
ns: Main<T>,
|
|
98
|
-
options: WriteOperationDeleteOptions<T> = {} as WriteOperationDeleteOptions<T>,
|
|
99
|
-
): $Typed<com.atproto.repo.applyWrites.Delete> {
|
|
100
|
-
const schema: T = getMain(ns)
|
|
101
|
-
return com.atproto.repo.applyWrites.delete.$build({
|
|
102
|
-
collection: schema.$type,
|
|
103
|
-
rkey: options?.rkey ?? getLiteralRecordKey(schema),
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
static build(factory: WriteOperationsFactory): WriteOperation[] {
|
|
108
|
-
return Array.from(factory(new WriteOperationHelper()))
|
|
109
|
-
}
|
|
110
|
-
}
|