@atproto/repo 0.10.1 → 0.10.3
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 +30 -0
- package/package.json +22 -17
- package/jest.config.cjs +0 -24
- package/src/block-map.ts +0 -131
- package/src/car.ts +0 -357
- package/src/cid-set.ts +0 -55
- package/src/data-diff.ts +0 -117
- package/src/error.ts +0 -43
- package/src/index.ts +0 -11
- package/src/logger.ts +0 -7
- package/src/mst/diff.ts +0 -114
- package/src/mst/index.ts +0 -4
- package/src/mst/mst.ts +0 -892
- package/src/mst/util.ts +0 -160
- package/src/mst/walker.ts +0 -118
- package/src/parse.ts +0 -44
- package/src/readable-repo.ts +0 -86
- package/src/repo.ts +0 -236
- package/src/storage/index.ts +0 -4
- package/src/storage/memory-blockstore.ts +0 -76
- package/src/storage/readable-blockstore.ts +0 -55
- package/src/storage/sync-storage.ts +0 -35
- package/src/storage/types.ts +0 -47
- package/src/sync/consumer.ts +0 -207
- package/src/sync/index.ts +0 -2
- package/src/sync/provider.ts +0 -67
- package/src/types.ts +0 -227
- package/src/util.ts +0 -146
- package/tests/_keys.ts +0 -156
- package/tests/_util.ts +0 -265
- package/tests/car-file-fixtures.json +0 -28
- package/tests/car.test.ts +0 -125
- package/tests/commit-data.test.ts +0 -94
- package/tests/commit-proof-fixtures.json +0 -118
- package/tests/commit-proofs.test.ts +0 -63
- package/tests/covering-proofs.test.ts +0 -256
- package/tests/mst.test.ts +0 -450
- package/tests/proofs.test.ts +0 -155
- package/tests/repo.test.ts +0 -106
- package/tests/sync.test.ts +0 -95
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @atproto/repo
|
|
2
2
|
|
|
3
|
+
## 0.10.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
|
|
8
|
+
|
|
9
|
+
- [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`28a0b58`](https://github.com/bluesky-social/atproto/commit/28a0b588147863eaef948cd2bb8fc0f19d08cda9), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07)]:
|
|
12
|
+
- @atproto/syntax@0.6.4
|
|
13
|
+
- @atproto/lex-cbor@0.1.3
|
|
14
|
+
- @atproto/lex-data@0.1.4
|
|
15
|
+
- @atproto/common-web@0.5.3
|
|
16
|
+
- @atproto/common@0.6.5
|
|
17
|
+
- @atproto/crypto@0.5.3
|
|
18
|
+
|
|
19
|
+
## 0.10.2
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [#5151](https://github.com/bluesky-social/atproto/pull/5151) [`a51c45d`](https://github.com/bluesky-social/atproto/commit/a51c45d38f6bd7b8765f640e564cf921d52162e7) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update dependencies
|
|
24
|
+
|
|
25
|
+
- Updated dependencies [[`f2cf8f7`](https://github.com/bluesky-social/atproto/commit/f2cf8f7fc5f3a10847f2e6d785e5fa2244ee8cfb), [`a51c45d`](https://github.com/bluesky-social/atproto/commit/a51c45d38f6bd7b8765f640e564cf921d52162e7), [`f2cf8f7`](https://github.com/bluesky-social/atproto/commit/f2cf8f7fc5f3a10847f2e6d785e5fa2244ee8cfb)]:
|
|
26
|
+
- @atproto/common@0.6.4
|
|
27
|
+
- @atproto/common-web@0.5.2
|
|
28
|
+
- @atproto/crypto@0.5.2
|
|
29
|
+
- @atproto/lex-cbor@0.1.2
|
|
30
|
+
- @atproto/lex-data@0.1.3
|
|
31
|
+
- @atproto/syntax@0.6.3
|
|
32
|
+
|
|
3
33
|
## 0.10.1
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/repo",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": ">=22"
|
|
6
|
+
},
|
|
4
7
|
"license": "MIT",
|
|
5
8
|
"description": "atproto repo and MST implementation",
|
|
6
9
|
"keywords": [
|
|
7
10
|
"atproto",
|
|
8
11
|
"mst"
|
|
9
12
|
],
|
|
10
|
-
"engines": {
|
|
11
|
-
"node": ">=22"
|
|
12
|
-
},
|
|
13
13
|
"homepage": "https://atproto.com",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
16
|
"url": "https://github.com/bluesky-social/atproto",
|
|
17
17
|
"directory": "packages/repo"
|
|
18
18
|
},
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"@atproto/crypto": "^0.5.1",
|
|
25
|
-
"@atproto/lex-cbor": "^0.1.1",
|
|
26
|
-
"@atproto/lex-data": "^0.1.2",
|
|
27
|
-
"@atproto/syntax": "^0.6.2"
|
|
28
|
-
},
|
|
29
|
-
"devDependencies": {
|
|
30
|
-
"jest": "^30.0.0"
|
|
31
|
-
},
|
|
19
|
+
"files": [
|
|
20
|
+
"./dist",
|
|
21
|
+
"./README.md",
|
|
22
|
+
"./CHANGELOG.md"
|
|
23
|
+
],
|
|
32
24
|
"type": "module",
|
|
33
25
|
"exports": {
|
|
34
26
|
".": {
|
|
@@ -36,6 +28,19 @@
|
|
|
36
28
|
"default": "./dist/index.js"
|
|
37
29
|
}
|
|
38
30
|
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"varint": "^6.0.0",
|
|
33
|
+
"zod": "^3.23.8",
|
|
34
|
+
"@atproto/common-web": "^0.5.3",
|
|
35
|
+
"@atproto/crypto": "^0.5.3",
|
|
36
|
+
"@atproto/lex-cbor": "^0.1.3",
|
|
37
|
+
"@atproto/syntax": "^0.6.4",
|
|
38
|
+
"@atproto/lex-data": "^0.1.4",
|
|
39
|
+
"@atproto/common": "^0.6.5"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"jest": "^30.0.0"
|
|
43
|
+
},
|
|
39
44
|
"scripts": {
|
|
40
45
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
41
46
|
"test:profile": "node --experimental-vm-modules --inspect ../../node_modules/.bin/jest",
|
package/jest.config.cjs
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/** @type {import('jest').Config} */
|
|
2
|
-
module.exports = {
|
|
3
|
-
displayName: 'Repo',
|
|
4
|
-
transform: {
|
|
5
|
-
'^.+\\.(t|j)s$': [
|
|
6
|
-
'@swc/jest',
|
|
7
|
-
{
|
|
8
|
-
jsc: {
|
|
9
|
-
parser: { syntax: 'typescript', importAttributes: true },
|
|
10
|
-
experimental: { keepImportAttributes: true },
|
|
11
|
-
transform: {},
|
|
12
|
-
},
|
|
13
|
-
module: { type: 'es6' },
|
|
14
|
-
},
|
|
15
|
-
],
|
|
16
|
-
},
|
|
17
|
-
extensionsToTreatAsEsm: ['.ts'],
|
|
18
|
-
transformIgnorePatterns: [],
|
|
19
|
-
setupFiles: ['<rootDir>/../../test.setup.ts'],
|
|
20
|
-
moduleNameMapper: {
|
|
21
|
-
'^varint$': '<rootDir>/../../jest.varint-shim.cjs',
|
|
22
|
-
'^(\\.\\.?\\/.+)\\.js$': ['$1.ts', '$1.js'],
|
|
23
|
-
},
|
|
24
|
-
}
|
package/src/block-map.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { encode } from '@atproto/lex-cbor'
|
|
2
|
-
import {
|
|
3
|
-
Cid,
|
|
4
|
-
LexValue,
|
|
5
|
-
cidForCbor,
|
|
6
|
-
parseCid,
|
|
7
|
-
ui8Equals,
|
|
8
|
-
} from '@atproto/lex-data'
|
|
9
|
-
|
|
10
|
-
export class BlockMap implements Iterable<[cid: Cid, bytes: Uint8Array]> {
|
|
11
|
-
private map: Map<string, Uint8Array> = new Map()
|
|
12
|
-
|
|
13
|
-
constructor(entries?: Iterable<readonly [cid: Cid, bytes: Uint8Array]>) {
|
|
14
|
-
if (entries) {
|
|
15
|
-
for (const [cid, bytes] of entries) {
|
|
16
|
-
this.set(cid, bytes)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async add(value: LexValue): Promise<Cid> {
|
|
22
|
-
const bytes = encode(value)
|
|
23
|
-
const cid = await cidForCbor(bytes)
|
|
24
|
-
this.set(cid, bytes)
|
|
25
|
-
return cid
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
set(cid: Cid, bytes: Uint8Array): BlockMap {
|
|
29
|
-
this.map.set(cid.toString(), bytes)
|
|
30
|
-
return this
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
get(cid: Cid): Uint8Array | undefined {
|
|
34
|
-
return this.map.get(cid.toString())
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
delete(cid: Cid): BlockMap {
|
|
38
|
-
this.map.delete(cid.toString())
|
|
39
|
-
return this
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getMany(cids: Cid[]): { blocks: BlockMap; missing: Cid[] } {
|
|
43
|
-
const missing: Cid[] = []
|
|
44
|
-
const blocks = new BlockMap()
|
|
45
|
-
for (const cid of cids) {
|
|
46
|
-
const got = this.map.get(cid.toString())
|
|
47
|
-
if (got) {
|
|
48
|
-
blocks.set(cid, got)
|
|
49
|
-
} else {
|
|
50
|
-
missing.push(cid)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return { blocks, missing }
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
has(cid: Cid): boolean {
|
|
57
|
-
return this.map.has(cid.toString())
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
clear(): void {
|
|
61
|
-
this.map.clear()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
forEach(cb: (bytes: Uint8Array, cid: Cid) => void): void {
|
|
65
|
-
for (const [cid, bytes] of this) cb(bytes, cid)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
entries(): Entry[] {
|
|
69
|
-
return Array.from(this, toEntry)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
cids(): Cid[] {
|
|
73
|
-
return Array.from(this.keys())
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
addMap(toAdd: BlockMap): BlockMap {
|
|
77
|
-
for (const [cid, bytes] of toAdd) this.set(cid, bytes)
|
|
78
|
-
return this
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
get size(): number {
|
|
82
|
-
return this.map.size
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
get byteSize(): number {
|
|
86
|
-
let size = 0
|
|
87
|
-
for (const bytes of this.values()) size += bytes.length
|
|
88
|
-
return size
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
equals(other: BlockMap): boolean {
|
|
92
|
-
if (this.size !== other.size) {
|
|
93
|
-
return false
|
|
94
|
-
}
|
|
95
|
-
for (const [cid, bytes] of this) {
|
|
96
|
-
const otherBytes = other.get(cid)
|
|
97
|
-
if (!otherBytes) return false
|
|
98
|
-
if (!ui8Equals(bytes, otherBytes)) {
|
|
99
|
-
return false
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return true
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
*keys(): Generator<Cid, void, unknown> {
|
|
106
|
-
for (const key of this.map.keys()) {
|
|
107
|
-
yield parseCid(key)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
*values(): Generator<Uint8Array, void, unknown> {
|
|
112
|
-
yield* this.map.values()
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
*[Symbol.iterator](): Generator<[Cid, Uint8Array], void, unknown> {
|
|
116
|
-
for (const [key, value] of this.map) {
|
|
117
|
-
yield [parseCid(key), value]
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function toEntry([cid, bytes]: readonly [Cid, Uint8Array]): Entry {
|
|
123
|
-
return { cid, bytes }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
type Entry = {
|
|
127
|
-
cid: Cid
|
|
128
|
-
bytes: Uint8Array
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export default BlockMap
|
package/src/car.ts
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
import { setImmediate } from 'node:timers/promises'
|
|
2
|
-
// eslint-disable-next-line import/default, import/no-named-as-default-member
|
|
3
|
-
import varint from 'varint'
|
|
4
|
-
import * as cbor from '@atproto/lex-cbor'
|
|
5
|
-
import { Cid, decodeCid, isCidForBytes } from '@atproto/lex-data'
|
|
6
|
-
import { BlockMap } from './block-map.js'
|
|
7
|
-
import { CarBlock, schema } from './types.js'
|
|
8
|
-
import { concatBytesAsync } from './util.js'
|
|
9
|
-
|
|
10
|
-
export async function* writeCarStream(
|
|
11
|
-
root: Cid | null,
|
|
12
|
-
blocks: AsyncIterable<CarBlock>,
|
|
13
|
-
): AsyncIterable<Uint8Array> {
|
|
14
|
-
const header = new Uint8Array(
|
|
15
|
-
cbor.encode({
|
|
16
|
-
version: 1,
|
|
17
|
-
roots: root ? [root] : [],
|
|
18
|
-
}),
|
|
19
|
-
)
|
|
20
|
-
yield new Uint8Array(varint.encode(header.byteLength))
|
|
21
|
-
yield header
|
|
22
|
-
for await (const block of blocks) {
|
|
23
|
-
yield new Uint8Array(
|
|
24
|
-
varint.encode(block.cid.bytes.byteLength + block.bytes.byteLength),
|
|
25
|
-
)
|
|
26
|
-
yield block.cid.bytes
|
|
27
|
-
yield block.bytes
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function blocksToCarFile(
|
|
32
|
-
root: Cid | null,
|
|
33
|
-
blocks: BlockMap,
|
|
34
|
-
): Promise<Uint8Array> {
|
|
35
|
-
return concatBytesAsync(blocksToCarStream(root, blocks))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const blocksToCarStream = (
|
|
39
|
-
root: Cid | null,
|
|
40
|
-
blocks: BlockMap,
|
|
41
|
-
): AsyncIterable<Uint8Array> => {
|
|
42
|
-
return writeCarStream(root, iterateBlocks(blocks))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function* iterateBlocks(blocks: BlockMap) {
|
|
46
|
-
for (const entry of blocks.entries()) {
|
|
47
|
-
yield { cid: entry.cid, bytes: entry.bytes }
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export type ReadCarOptions = {
|
|
52
|
-
/**
|
|
53
|
-
* When true, does not verify CID-to-content mapping within CAR.
|
|
54
|
-
*/
|
|
55
|
-
skipCidVerification?: boolean
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const readCar = async (
|
|
59
|
-
bytes: Uint8Array,
|
|
60
|
-
opts?: ReadCarOptions,
|
|
61
|
-
): Promise<{ roots: Cid[]; blocks: BlockMap }> => {
|
|
62
|
-
const { roots, blocks } = await readCarReader(new Ui8Reader(bytes), opts)
|
|
63
|
-
const blockMap = new BlockMap()
|
|
64
|
-
for await (const block of blocks) {
|
|
65
|
-
blockMap.set(block.cid, block.bytes)
|
|
66
|
-
}
|
|
67
|
-
return { roots, blocks: blockMap }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export const readCarWithRoot = async (
|
|
71
|
-
bytes: Uint8Array,
|
|
72
|
-
opts?: ReadCarOptions,
|
|
73
|
-
): Promise<{ root: Cid; blocks: BlockMap }> => {
|
|
74
|
-
const { roots, blocks } = await readCar(bytes, opts)
|
|
75
|
-
if (roots.length !== 1) {
|
|
76
|
-
throw new Error(`Expected one root, got ${roots.length}`)
|
|
77
|
-
}
|
|
78
|
-
const root = roots[0]
|
|
79
|
-
return {
|
|
80
|
-
root,
|
|
81
|
-
blocks,
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
export type CarBlockIterable = AsyncGenerator<CarBlock, void, unknown> & {
|
|
85
|
-
dump: () => Promise<void>
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export const readCarStream = async (
|
|
89
|
-
car: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,
|
|
90
|
-
opts?: ReadCarOptions,
|
|
91
|
-
): Promise<{
|
|
92
|
-
roots: Cid[]
|
|
93
|
-
blocks: CarBlockIterable
|
|
94
|
-
}> => {
|
|
95
|
-
return readCarReader(new BufferedReader(car), opts)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export const readCarReader = async (
|
|
99
|
-
reader: BytesReader,
|
|
100
|
-
opts?: ReadCarOptions,
|
|
101
|
-
): Promise<{
|
|
102
|
-
roots: Cid[]
|
|
103
|
-
blocks: CarBlockIterable
|
|
104
|
-
}> => {
|
|
105
|
-
try {
|
|
106
|
-
const headerSize = await readVarint(reader)
|
|
107
|
-
if (headerSize === null) {
|
|
108
|
-
throw new Error('Could not parse CAR header')
|
|
109
|
-
}
|
|
110
|
-
const headerBytes = await reader.read(headerSize)
|
|
111
|
-
const header = cbor.decode(headerBytes)
|
|
112
|
-
const result = schema.carHeader.safeParse(header)
|
|
113
|
-
if (!result.success) {
|
|
114
|
-
throw new Error('Could not parse CAR header', { cause: result.error })
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
roots: result.data.roots,
|
|
118
|
-
blocks: readCarBlocksIter(reader, opts),
|
|
119
|
-
}
|
|
120
|
-
} catch (err) {
|
|
121
|
-
await reader.close()
|
|
122
|
-
throw err
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const readCarBlocksIter = (
|
|
127
|
-
reader: BytesReader,
|
|
128
|
-
opts?: ReadCarOptions,
|
|
129
|
-
): CarBlockIterable => {
|
|
130
|
-
let generator = readCarBlocksIterGenerator(reader)
|
|
131
|
-
if (!opts?.skipCidVerification) {
|
|
132
|
-
generator = verifyIncomingCarBlocks(generator)
|
|
133
|
-
}
|
|
134
|
-
return Object.assign(generator, {
|
|
135
|
-
async dump() {
|
|
136
|
-
// try/finally to ensure that reader.close is called even if blocks.return throws.
|
|
137
|
-
try {
|
|
138
|
-
// Prevent the iterator from being started after this method is called.
|
|
139
|
-
await generator.return()
|
|
140
|
-
} finally {
|
|
141
|
-
// @NOTE the "finally" block of the async generator won't be called
|
|
142
|
-
// if the iteration was never started so we need to manually close here.
|
|
143
|
-
await reader.close()
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function* readCarBlocksIterGenerator(
|
|
150
|
-
reader: BytesReader,
|
|
151
|
-
): AsyncGenerator<CarBlock, void, unknown> {
|
|
152
|
-
let blocks = 0
|
|
153
|
-
try {
|
|
154
|
-
while (!reader.isDone) {
|
|
155
|
-
const blockSize = await readVarint(reader)
|
|
156
|
-
if (blockSize === null) {
|
|
157
|
-
break
|
|
158
|
-
}
|
|
159
|
-
const blockBytes = await reader.read(blockSize)
|
|
160
|
-
const cid = decodeCid(blockBytes.subarray(0, 36))
|
|
161
|
-
const bytes = blockBytes.subarray(36)
|
|
162
|
-
yield { cid, bytes }
|
|
163
|
-
|
|
164
|
-
// yield to the event loop every 25 blocks
|
|
165
|
-
// in the case the incoming CAR is synchronous, this can end up jamming up the thread
|
|
166
|
-
blocks++
|
|
167
|
-
if (blocks % 25 === 0) {
|
|
168
|
-
await setImmediate()
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} finally {
|
|
172
|
-
await reader.close()
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export async function* verifyIncomingCarBlocks(
|
|
177
|
-
car: AsyncIterable<CarBlock>,
|
|
178
|
-
): AsyncGenerator<CarBlock, void, unknown> {
|
|
179
|
-
for await (const block of car) {
|
|
180
|
-
if (!(await isCidForBytes(block.cid, block.bytes))) {
|
|
181
|
-
throw new Error(`Not a valid CID for bytes (${block.cid.toString()})`)
|
|
182
|
-
}
|
|
183
|
-
yield block
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const readVarint = async (reader: BytesReader): Promise<number | null> => {
|
|
188
|
-
let done = false
|
|
189
|
-
const bytes: Uint8Array[] = []
|
|
190
|
-
while (!done) {
|
|
191
|
-
const byte = await reader.read(1)
|
|
192
|
-
if (byte.byteLength === 0) {
|
|
193
|
-
if (bytes.length > 0) {
|
|
194
|
-
throw new Error('could not parse varint')
|
|
195
|
-
} else {
|
|
196
|
-
return null
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
bytes.push(byte)
|
|
200
|
-
if (byte[0] < 128) {
|
|
201
|
-
done = true
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
const concatted = Buffer.concat(bytes)
|
|
205
|
-
return varint.decode(concatted)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
interface BytesReader {
|
|
209
|
-
isDone: boolean
|
|
210
|
-
read(bytesToRead: number): Promise<Uint8Array>
|
|
211
|
-
close(): Promise<void>
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
class Ui8Reader implements BytesReader {
|
|
215
|
-
idx = 0
|
|
216
|
-
isDone = false
|
|
217
|
-
|
|
218
|
-
constructor(public bytes: Uint8Array) {}
|
|
219
|
-
|
|
220
|
-
async read(bytesToRead: number): Promise<Uint8Array> {
|
|
221
|
-
const value = this.bytes.subarray(this.idx, this.idx + bytesToRead)
|
|
222
|
-
this.idx += bytesToRead
|
|
223
|
-
if (this.idx >= this.bytes.length) {
|
|
224
|
-
this.isDone = true
|
|
225
|
-
}
|
|
226
|
-
return value
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async close(): Promise<void> {}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* This code was optimized for performance. See
|
|
234
|
-
* {@link https://github.com/bluesky-social/atproto/pull/4729 #4729} for more details
|
|
235
|
-
* and benchmarks.
|
|
236
|
-
*/
|
|
237
|
-
class BufferedReader implements BytesReader {
|
|
238
|
-
iterator: Iterator<Uint8Array> | AsyncIterator<Uint8Array>
|
|
239
|
-
isDone = false
|
|
240
|
-
|
|
241
|
-
/** fifo list of chunks to consume */
|
|
242
|
-
private chunks: Uint8Array[] = []
|
|
243
|
-
|
|
244
|
-
constructor(stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>) {
|
|
245
|
-
this.iterator =
|
|
246
|
-
Symbol.asyncIterator in stream
|
|
247
|
-
? stream[Symbol.asyncIterator]()
|
|
248
|
-
: stream[Symbol.iterator]()
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** Number of bytes currently buffered and available for reading */
|
|
252
|
-
get bufferedByteLength() {
|
|
253
|
-
let total = 0
|
|
254
|
-
for (let i = 0; i < this.chunks.length; i++) {
|
|
255
|
-
total += this.chunks[i].byteLength
|
|
256
|
-
}
|
|
257
|
-
return total
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* @note concurrent reads are **NOT** supported by the current implementation
|
|
262
|
-
* and would require call to readUntilBuffered to be using a fifo lock for
|
|
263
|
-
* read()s to be processed in fifo order.
|
|
264
|
-
*/
|
|
265
|
-
async read(bytesToRead: number): Promise<Uint8Array> {
|
|
266
|
-
const bytesNeeded = bytesToRead - this.bufferedByteLength
|
|
267
|
-
if (bytesNeeded > 0 && !this.isDone) {
|
|
268
|
-
await this.readUntilBuffered(bytesNeeded)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const resultLength = Math.min(bytesToRead, this.bufferedByteLength)
|
|
272
|
-
if (resultLength <= 0) return new Uint8Array()
|
|
273
|
-
|
|
274
|
-
const firstChunk = this.consumeChunk(resultLength)
|
|
275
|
-
if (firstChunk.byteLength === resultLength) {
|
|
276
|
-
// If the data consumed from the first chunk contains all we need, return
|
|
277
|
-
// it as-is. This allows to avoid any copy operation.
|
|
278
|
-
return firstChunk
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// The first chunk does not have all the data we need. We have to copy
|
|
282
|
-
// multiple chunks into a larger buffer
|
|
283
|
-
const result = new Uint8Array(resultLength)
|
|
284
|
-
let resultWriteIndex = 0
|
|
285
|
-
|
|
286
|
-
// Copy the first chunk into the result buffer
|
|
287
|
-
result.set(firstChunk, resultWriteIndex)
|
|
288
|
-
resultWriteIndex += firstChunk.byteLength
|
|
289
|
-
|
|
290
|
-
// Copy more chunks as needed (we use do-while because we *know* we need
|
|
291
|
-
// more than one chunk)
|
|
292
|
-
do {
|
|
293
|
-
const missingLength = resultLength - resultWriteIndex
|
|
294
|
-
const currentChunk = this.consumeChunk(missingLength)
|
|
295
|
-
|
|
296
|
-
result.set(currentChunk, resultWriteIndex)
|
|
297
|
-
resultWriteIndex += currentChunk.byteLength
|
|
298
|
-
} while (resultWriteIndex < resultLength)
|
|
299
|
-
|
|
300
|
-
return result
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
private async readUntilBuffered(bytesNeeded: number) {
|
|
304
|
-
let bytesRead = 0
|
|
305
|
-
while (bytesRead < bytesNeeded) {
|
|
306
|
-
const next = await this.iterator.next()
|
|
307
|
-
if (next.done) {
|
|
308
|
-
this.isDone = true
|
|
309
|
-
break
|
|
310
|
-
} else {
|
|
311
|
-
this.chunks.push(next.value)
|
|
312
|
-
bytesRead += next.value.byteLength
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return bytesRead
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
private consumeChunk(bytesToConsume: number) {
|
|
319
|
-
const firstChunk = this.chunks[0]!
|
|
320
|
-
if (bytesToConsume < firstChunk.byteLength) {
|
|
321
|
-
// return a sub-view of the data being read and replace the first chunk
|
|
322
|
-
// with a sub-view that does not contain that data.
|
|
323
|
-
|
|
324
|
-
// @NOTE for some reason, subarray() revealed to be 7-8% slower in NodeJS
|
|
325
|
-
// benchmarks.
|
|
326
|
-
|
|
327
|
-
// this.chunks[0] = firstChunk.subarray(bytesToConsume)
|
|
328
|
-
// return firstChunk.subarray(0, bytesToConsume)
|
|
329
|
-
|
|
330
|
-
this.chunks[0] = new Uint8Array(
|
|
331
|
-
firstChunk.buffer,
|
|
332
|
-
firstChunk.byteOffset + bytesToConsume,
|
|
333
|
-
firstChunk.byteLength - bytesToConsume,
|
|
334
|
-
)
|
|
335
|
-
return new Uint8Array(
|
|
336
|
-
firstChunk.buffer,
|
|
337
|
-
firstChunk.byteOffset,
|
|
338
|
-
bytesToConsume,
|
|
339
|
-
)
|
|
340
|
-
} else {
|
|
341
|
-
// First chunk is being read in full, discard it
|
|
342
|
-
this.chunks.shift()
|
|
343
|
-
return firstChunk
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
async close(): Promise<void> {
|
|
348
|
-
try {
|
|
349
|
-
if (!this.isDone && this.iterator.return) {
|
|
350
|
-
await this.iterator.return()
|
|
351
|
-
}
|
|
352
|
-
} finally {
|
|
353
|
-
this.isDone = true
|
|
354
|
-
this.chunks.length = 0
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
package/src/cid-set.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { Cid, parseCid } from '@atproto/lex-data'
|
|
2
|
-
|
|
3
|
-
export class CidSet implements Iterable<Cid> {
|
|
4
|
-
private set: Set<string>
|
|
5
|
-
|
|
6
|
-
constructor(arr: Cid[] = []) {
|
|
7
|
-
const strArr = arr.map((c) => c.toString())
|
|
8
|
-
this.set = new Set(strArr)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
add(cid: Cid): CidSet {
|
|
12
|
-
this.set.add(cid.toString())
|
|
13
|
-
return this
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
addSet(toMerge: CidSet): CidSet {
|
|
17
|
-
for (const c of toMerge.set) this.set.add(c)
|
|
18
|
-
return this
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
subtractSet(toSubtract: CidSet): CidSet {
|
|
22
|
-
for (const c of toSubtract.set) this.set.delete(c)
|
|
23
|
-
return this
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
delete(cid: Cid) {
|
|
27
|
-
this.set.delete(cid.toString())
|
|
28
|
-
return this
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
has(cid: Cid): boolean {
|
|
32
|
-
return this.set.has(cid.toString())
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
size(): number {
|
|
36
|
-
return this.set.size
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
clear(): CidSet {
|
|
40
|
-
this.set.clear()
|
|
41
|
-
return this
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
toList(): Cid[] {
|
|
45
|
-
return Array.from(this)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
*[Symbol.iterator](): Generator<Cid, void, unknown> {
|
|
49
|
-
for (const c of this.set) {
|
|
50
|
-
yield parseCid(c)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export default CidSet
|