@atcute/car 1.0.1 → 1.1.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/README.md +10 -2
- package/dist/atproto-repo.d.ts +3 -1
- package/dist/atproto-repo.js +5 -3
- package/dist/atproto-repo.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/reader.d.ts +8 -0
- package/dist/reader.js +3 -1
- package/dist/reader.js.map +1 -1
- package/lib/atproto-repo.ts +118 -0
- package/lib/index.ts +2 -0
- package/lib/reader.ts +10 -0
- package/lib/utilities/byte-reader.ts +35 -0
- package/lib/utilities/sync-car-reader.ts +116 -0
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# @atcute/car
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CAR (content-addressable archvie) repository decoder
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
|
|
6
|
+
// convenient iterator for reading through an AT Protocol CAR repository
|
|
7
|
+
for (const { collection, rkey, record } of iterateAtpRepo(buf)) {
|
|
8
|
+
// ...
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// read through a CAR archive
|
|
12
|
+
const { roots, iterate } = readCar(buf);
|
|
13
|
+
|
|
14
|
+
for (const { cid, bytes } of iterate()) {
|
|
7
15
|
// ...
|
|
8
16
|
}
|
|
9
17
|
```
|
package/dist/atproto-repo.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export declare class RepoEntry {
|
|
|
7
7
|
constructor(collection: string, rkey: string, cid: CBOR.CIDLink, blockmap: BlockMap);
|
|
8
8
|
get record(): unknown;
|
|
9
9
|
}
|
|
10
|
-
export declare function
|
|
10
|
+
export declare function iterateAtpRepo(buf: Uint8Array): Generator<RepoEntry>;
|
|
11
|
+
/** @deprecated Use `iterateAtpRepo` instead */
|
|
12
|
+
export declare const iterateAtpCar: typeof iterateAtpRepo;
|
|
11
13
|
type BlockMap = Map<string, Uint8Array>;
|
|
12
14
|
export {};
|
package/dist/atproto-repo.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as CBOR from '@atcute/cbor';
|
|
2
2
|
import * as CID from '@atcute/cid';
|
|
3
|
-
import {
|
|
3
|
+
import { readCar } from './reader.js';
|
|
4
4
|
const decoder = new TextDecoder();
|
|
5
5
|
export class RepoEntry {
|
|
6
6
|
collection;
|
|
@@ -17,8 +17,8 @@ export class RepoEntry {
|
|
|
17
17
|
return readObject(this.blockmap, this.cid);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
export function*
|
|
21
|
-
const { roots, iterate } =
|
|
20
|
+
export function* iterateAtpRepo(buf) {
|
|
21
|
+
const { roots, iterate } = readCar(new Uint8Array(buf));
|
|
22
22
|
assert(roots.length === 1, `expected only 1 root in the car archive; got=${roots.length}`);
|
|
23
23
|
// Collect all archive entries into a mapping of CID string -> actual bytes
|
|
24
24
|
const blockmap = new Map();
|
|
@@ -32,6 +32,8 @@ export function* iterateAtpCar(buf) {
|
|
|
32
32
|
yield new RepoEntry(collection, rkey, cid, blockmap);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/** @deprecated Use `iterateAtpRepo` instead */
|
|
36
|
+
export const iterateAtpCar = iterateAtpRepo;
|
|
35
37
|
function readObject(map, link) {
|
|
36
38
|
const cid = link.$link;
|
|
37
39
|
const bytes = map.get(cid);
|
package/dist/atproto-repo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atproto-repo.js","sourceRoot":"","sources":["../lib/atproto-repo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,cAAc,CAAC;AACrC,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AAEnC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"atproto-repo.js","sourceRoot":"","sources":["../lib/atproto-repo.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { iterateAtpCar, type RepoEntry } from './atproto-repo.js';
|
|
2
|
-
export { fromUint8Array } from './reader.js';
|
|
1
|
+
export { iterateAtpCar, iterateAtpRepo, type RepoEntry } from './atproto-repo.js';
|
|
2
|
+
export { fromUint8Array, readCar } from './reader.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { iterateAtpCar } from './atproto-repo.js';
|
|
2
|
-
export { fromUint8Array } from './reader.js';
|
|
1
|
+
export { iterateAtpCar, iterateAtpRepo } from './atproto-repo.js';
|
|
2
|
+
export { fromUint8Array, readCar } from './reader.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAkB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAkB,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/reader.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export declare const readCar: (buffer: Uint8Array) => {
|
|
2
|
+
roots: import("@atcute/cbor").CIDLink[];
|
|
3
|
+
iterate(): Generator<{
|
|
4
|
+
cid: import("@atcute/cid").CID;
|
|
5
|
+
bytes: Uint8Array;
|
|
6
|
+
}>;
|
|
7
|
+
};
|
|
8
|
+
/** @deprecated Use `readCar` instead */
|
|
1
9
|
export declare const fromUint8Array: (buffer: Uint8Array) => {
|
|
2
10
|
roots: import("@atcute/cbor").CIDLink[];
|
|
3
11
|
iterate(): Generator<{
|
package/dist/reader.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createUint8Reader } from './utilities/byte-reader.js';
|
|
2
2
|
import { createCarReader } from './utilities/sync-car-reader.js';
|
|
3
|
-
export const
|
|
3
|
+
export const readCar = (buffer) => {
|
|
4
4
|
const reader = createUint8Reader(buffer);
|
|
5
5
|
return createCarReader(reader);
|
|
6
6
|
};
|
|
7
|
+
/** @deprecated Use `readCar` instead */
|
|
8
|
+
export const fromUint8Array = readCar;
|
|
7
9
|
//# sourceMappingURL=reader.js.map
|
package/dist/reader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reader.js","sourceRoot":"","sources":["../lib/reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEjE,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"reader.js","sourceRoot":"","sources":["../lib/reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEjE,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAkB,EAAE,EAAE;IAC7C,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC,CAAC;AAEF,wCAAwC;AACxC,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as CBOR from '@atcute/cbor';
|
|
2
|
+
import * as CID from '@atcute/cid';
|
|
3
|
+
|
|
4
|
+
import { readCar } from './reader.js';
|
|
5
|
+
|
|
6
|
+
const decoder = new TextDecoder();
|
|
7
|
+
|
|
8
|
+
export class RepoEntry {
|
|
9
|
+
constructor(
|
|
10
|
+
public readonly collection: string,
|
|
11
|
+
public readonly rkey: string,
|
|
12
|
+
public readonly cid: CBOR.CIDLink,
|
|
13
|
+
private blockmap: BlockMap,
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
get record(): unknown {
|
|
17
|
+
return readObject(this.blockmap, this.cid);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function* iterateAtpRepo(buf: Uint8Array): Generator<RepoEntry> {
|
|
22
|
+
const { roots, iterate } = readCar(new Uint8Array(buf));
|
|
23
|
+
assert(roots.length === 1, `expected only 1 root in the car archive; got=${roots.length}`);
|
|
24
|
+
|
|
25
|
+
// Collect all archive entries into a mapping of CID string -> actual bytes
|
|
26
|
+
const blockmap: BlockMap = new Map();
|
|
27
|
+
for (const entry of iterate()) {
|
|
28
|
+
blockmap.set(CID.format(entry.cid), entry.bytes);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Read the head, then walk through the MST tree from there.
|
|
32
|
+
const commit = readObject(blockmap, roots[0]) as Commit;
|
|
33
|
+
for (const { key, cid } of walkEntries(blockmap, commit.data)) {
|
|
34
|
+
const [collection, rkey] = key.split('/');
|
|
35
|
+
|
|
36
|
+
yield new RepoEntry(collection, rkey, cid, blockmap);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @deprecated Use `iterateAtpRepo` instead */
|
|
41
|
+
export const iterateAtpCar = iterateAtpRepo;
|
|
42
|
+
|
|
43
|
+
function readObject(map: BlockMap, link: CBOR.CIDLink): unknown {
|
|
44
|
+
const cid = link.$link;
|
|
45
|
+
|
|
46
|
+
const bytes = map.get(cid);
|
|
47
|
+
assert(bytes != null, `cid not found in blockmap; cid=${cid}`);
|
|
48
|
+
|
|
49
|
+
const data = CBOR.decode(bytes);
|
|
50
|
+
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function* walkEntries(map: BlockMap, pointer: CBOR.CIDLink): Generator<NodeEntry> {
|
|
55
|
+
const data = readObject(map, pointer) as MstNode;
|
|
56
|
+
const entries = data.e;
|
|
57
|
+
|
|
58
|
+
let lastKey = '';
|
|
59
|
+
|
|
60
|
+
if (data.l !== null) {
|
|
61
|
+
yield* walkEntries(map, data.l);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (let i = 0, il = entries.length; i < il; i++) {
|
|
65
|
+
const entry = entries[i];
|
|
66
|
+
|
|
67
|
+
const key_str = decoder.decode(CBOR.fromBytes(entry.k));
|
|
68
|
+
const key = lastKey.slice(0, entry.p) + key_str;
|
|
69
|
+
|
|
70
|
+
lastKey = key;
|
|
71
|
+
|
|
72
|
+
yield { key: key, cid: entry.v };
|
|
73
|
+
|
|
74
|
+
if (entry.t !== null) {
|
|
75
|
+
yield* walkEntries(map, entry.t);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function assert(condition: boolean, message: string): asserts condition {
|
|
81
|
+
if (!condition) {
|
|
82
|
+
throw new Error(message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type BlockMap = Map<string, Uint8Array>;
|
|
87
|
+
|
|
88
|
+
interface Commit {
|
|
89
|
+
version: 3;
|
|
90
|
+
did: string;
|
|
91
|
+
data: CBOR.CIDLink;
|
|
92
|
+
rev: string;
|
|
93
|
+
prev: CBOR.CIDLink | null;
|
|
94
|
+
sig: CBOR.Bytes;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface TreeEntry {
|
|
98
|
+
/** count of bytes shared with previous TreeEntry in this Node (if any) */
|
|
99
|
+
p: number;
|
|
100
|
+
/** remainder of key for this TreeEntry, after "prefixlen" have been removed */
|
|
101
|
+
k: CBOR.Bytes;
|
|
102
|
+
/** link to a sub-tree Node at a lower level which has keys sorting after this TreeEntry's key (to the "right"), but before the next TreeEntry's key in this Node (if any) */
|
|
103
|
+
v: CBOR.CIDLink;
|
|
104
|
+
/** next subtree (to the right of leaf) */
|
|
105
|
+
t: CBOR.CIDLink | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface MstNode {
|
|
109
|
+
/** link to sub-tree Node on a lower level and with all keys sorting before keys at this node */
|
|
110
|
+
l: CBOR.CIDLink | null;
|
|
111
|
+
/** ordered list of TreeEntry objects */
|
|
112
|
+
e: TreeEntry[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface NodeEntry {
|
|
116
|
+
key: string;
|
|
117
|
+
cid: CBOR.CIDLink;
|
|
118
|
+
}
|
package/lib/index.ts
ADDED
package/lib/reader.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createUint8Reader } from './utilities/byte-reader.js';
|
|
2
|
+
import { createCarReader } from './utilities/sync-car-reader.js';
|
|
3
|
+
|
|
4
|
+
export const readCar = (buffer: Uint8Array) => {
|
|
5
|
+
const reader = createUint8Reader(buffer);
|
|
6
|
+
return createCarReader(reader);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** @deprecated Use `readCar` instead */
|
|
10
|
+
export const fromUint8Array = readCar;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface SyncByteReader {
|
|
2
|
+
readonly pos: number;
|
|
3
|
+
upto(size: number): Uint8Array;
|
|
4
|
+
exactly(size: number, seek: boolean): Uint8Array;
|
|
5
|
+
seek(size: number): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const createUint8Reader = (buf: Uint8Array): SyncByteReader => {
|
|
9
|
+
let pos = 0;
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
get pos() {
|
|
13
|
+
return pos;
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
seek(size) {
|
|
17
|
+
pos += size;
|
|
18
|
+
},
|
|
19
|
+
upto(size) {
|
|
20
|
+
return buf.subarray(pos, pos + Math.min(size, buf.length - pos));
|
|
21
|
+
},
|
|
22
|
+
exactly(size, seek) {
|
|
23
|
+
if (size > buf.length - pos) {
|
|
24
|
+
throw new RangeError('unexpected end of data');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const slice = buf.subarray(pos, pos + size);
|
|
28
|
+
if (seek) {
|
|
29
|
+
pos += size;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return slice;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as CBOR from '@atcute/cbor';
|
|
2
|
+
import * as varint from '@atcute/varint';
|
|
3
|
+
import * as CID from '@atcute/cid';
|
|
4
|
+
|
|
5
|
+
import type { SyncByteReader } from './byte-reader.js';
|
|
6
|
+
|
|
7
|
+
interface CarV1Header {
|
|
8
|
+
version: 1;
|
|
9
|
+
roots: CBOR.CIDLink[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const isCarV1Header = (value: unknown): value is CarV1Header => {
|
|
13
|
+
if (value === null || typeof value !== 'object') {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { version, roots } = value as CarV1Header;
|
|
18
|
+
return version === 1 && Array.isArray(roots) && roots.every((root) => root instanceof CBOR.CIDLinkWrapper);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const readVarint = (reader: SyncByteReader, size: number): number => {
|
|
22
|
+
const buf = reader.upto(size);
|
|
23
|
+
if (buf.length === 0) {
|
|
24
|
+
throw new RangeError(`unexpected end of data`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const [int, read] = varint.decode(buf);
|
|
28
|
+
reader.seek(read);
|
|
29
|
+
|
|
30
|
+
return int;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const readHeader = (reader: SyncByteReader): CarV1Header => {
|
|
34
|
+
const length = readVarint(reader, 8);
|
|
35
|
+
if (length === 0) {
|
|
36
|
+
throw new RangeError(`invalid car header; length=0`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const rawHeader = reader.exactly(length, true);
|
|
40
|
+
const header = CBOR.decode(rawHeader);
|
|
41
|
+
if (!isCarV1Header(header)) {
|
|
42
|
+
throw new TypeError(`expected a car v1 archive`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return header;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const readMultihashDigest = (reader: SyncByteReader): CID.Digest => {
|
|
49
|
+
const first = reader.upto(8);
|
|
50
|
+
|
|
51
|
+
const [code, codeOffset] = varint.decode(first);
|
|
52
|
+
const [size, sizeOffset] = varint.decode(first.subarray(codeOffset));
|
|
53
|
+
|
|
54
|
+
const offset = codeOffset + sizeOffset;
|
|
55
|
+
|
|
56
|
+
const bytes = reader.exactly(offset + size, true);
|
|
57
|
+
const digest = bytes.subarray(offset);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
code: code,
|
|
61
|
+
size: size,
|
|
62
|
+
digest: digest,
|
|
63
|
+
bytes: bytes,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const readCid = (reader: SyncByteReader): CID.CID => {
|
|
68
|
+
const version = readVarint(reader, 8);
|
|
69
|
+
if (version !== 1) {
|
|
70
|
+
throw new Error(`expected a cidv1`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const codec = readVarint(reader, 8);
|
|
74
|
+
const digest = readMultihashDigest(reader);
|
|
75
|
+
|
|
76
|
+
const cid: CID.CID = {
|
|
77
|
+
version: version,
|
|
78
|
+
code: codec,
|
|
79
|
+
digest: digest,
|
|
80
|
+
bytes: CID.encode(version, codec, digest.bytes),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return cid;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const readBlockHeader = (reader: SyncByteReader): { cid: CID.CID; blockSize: number } => {
|
|
87
|
+
const start = reader.pos;
|
|
88
|
+
|
|
89
|
+
let size = readVarint(reader, 8);
|
|
90
|
+
if (size === 0) {
|
|
91
|
+
throw new Error(`invalid car section; length=0`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
size += reader.pos - start;
|
|
95
|
+
|
|
96
|
+
const cid = readCid(reader);
|
|
97
|
+
const blockSize = size - Number(reader.pos - start);
|
|
98
|
+
|
|
99
|
+
return { cid, blockSize };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const createCarReader = (reader: SyncByteReader) => {
|
|
103
|
+
const { roots } = readHeader(reader);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
roots,
|
|
107
|
+
*iterate(): Generator<{ cid: CID.CID; bytes: Uint8Array }> {
|
|
108
|
+
while (reader.upto(8).length > 0) {
|
|
109
|
+
const { cid, blockSize } = readBlockHeader(reader);
|
|
110
|
+
const bytes = reader.exactly(blockSize, true);
|
|
111
|
+
|
|
112
|
+
yield { cid, bytes };
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
};
|
package/package.json
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@atcute/car",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.1",
|
|
5
5
|
"description": "read AT Protocol's CAR (content-addressable archive) repositories",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
|
-
"url": "https://
|
|
8
|
+
"url": "https://github.com/mary-ext/atcute",
|
|
9
|
+
"directory": "packages/utilities/car"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
|
-
"dist/"
|
|
12
|
+
"dist/",
|
|
13
|
+
"lib/",
|
|
14
|
+
"!lib/**/*.bench.ts",
|
|
15
|
+
"!lib/**/*.test.ts"
|
|
12
16
|
],
|
|
13
17
|
"exports": {
|
|
14
18
|
".": "./dist/index.js"
|
|
15
19
|
},
|
|
16
20
|
"sideEffects": false,
|
|
17
21
|
"devDependencies": {
|
|
18
|
-
"@types/bun": "^1.1.
|
|
22
|
+
"@types/bun": "^1.1.12"
|
|
19
23
|
},
|
|
20
24
|
"dependencies": {
|
|
21
|
-
"@atcute/
|
|
22
|
-
"@atcute/varint": "^1.0.
|
|
23
|
-
"@atcute/
|
|
25
|
+
"@atcute/cbor": "^1.0.6",
|
|
26
|
+
"@atcute/varint": "^1.0.1",
|
|
27
|
+
"@atcute/cid": "^1.0.2"
|
|
24
28
|
},
|
|
25
29
|
"scripts": {
|
|
26
30
|
"build": "tsc --project tsconfig.build.json",
|