@hashtree/core 0.1.0
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/LICENSE +21 -0
- package/dist/bep52.d.ts +179 -0
- package/dist/bep52.d.ts.map +1 -0
- package/dist/bep52.js +384 -0
- package/dist/bep52.js.map +1 -0
- package/dist/builder.d.ts +137 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +281 -0
- package/dist/builder.js.map +1 -0
- package/dist/codec.d.ts +37 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +109 -0
- package/dist/codec.js.map +1 -0
- package/dist/crypto.d.ts +92 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +212 -0
- package/dist/crypto.js.map +1 -0
- package/dist/encrypted.d.ts +114 -0
- package/dist/encrypted.d.ts.map +1 -0
- package/dist/encrypted.js +446 -0
- package/dist/encrypted.js.map +1 -0
- package/dist/hash.d.ts +14 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +27 -0
- package/dist/hash.js.map +1 -0
- package/dist/hashtree.d.ts +237 -0
- package/dist/hashtree.d.ts.map +1 -0
- package/dist/hashtree.js +557 -0
- package/dist/hashtree.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/nhash.d.ts +94 -0
- package/dist/nhash.d.ts.map +1 -0
- package/dist/nhash.js +293 -0
- package/dist/nhash.js.map +1 -0
- package/dist/resolver/index.d.ts +5 -0
- package/dist/resolver/index.d.ts.map +1 -0
- package/dist/resolver/index.js +5 -0
- package/dist/resolver/index.js.map +1 -0
- package/dist/resolver/nostr.d.ts +82 -0
- package/dist/resolver/nostr.d.ts.map +1 -0
- package/dist/resolver/nostr.js +868 -0
- package/dist/resolver/nostr.js.map +1 -0
- package/dist/store/blossom.d.ts +100 -0
- package/dist/store/blossom.d.ts.map +1 -0
- package/dist/store/blossom.js +355 -0
- package/dist/store/blossom.js.map +1 -0
- package/dist/store/dexie.d.ts +44 -0
- package/dist/store/dexie.d.ts.map +1 -0
- package/dist/store/dexie.js +196 -0
- package/dist/store/dexie.js.map +1 -0
- package/dist/store/fallback.d.ts +40 -0
- package/dist/store/fallback.d.ts.map +1 -0
- package/dist/store/fallback.js +71 -0
- package/dist/store/fallback.js.map +1 -0
- package/dist/store/index.d.ts +6 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +6 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/memory.d.ts +29 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +66 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/opfs.d.ts +56 -0
- package/dist/store/opfs.d.ts.map +1 -0
- package/dist/store/opfs.js +200 -0
- package/dist/store/opfs.js.map +1 -0
- package/dist/streaming.d.ts +74 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +199 -0
- package/dist/streaming.js.map +1 -0
- package/dist/tree/create.d.ts +35 -0
- package/dist/tree/create.d.ts.map +1 -0
- package/dist/tree/create.js +90 -0
- package/dist/tree/create.js.map +1 -0
- package/dist/tree/edit.d.ts +28 -0
- package/dist/tree/edit.d.ts.map +1 -0
- package/dist/tree/edit.js +115 -0
- package/dist/tree/edit.js.map +1 -0
- package/dist/tree/editEncrypted.d.ts +46 -0
- package/dist/tree/editEncrypted.d.ts.map +1 -0
- package/dist/tree/editEncrypted.js +225 -0
- package/dist/tree/editEncrypted.js.map +1 -0
- package/dist/tree/index.d.ts +7 -0
- package/dist/tree/index.d.ts.map +1 -0
- package/dist/tree/index.js +7 -0
- package/dist/tree/index.js.map +1 -0
- package/dist/tree/read.d.ts +75 -0
- package/dist/tree/read.d.ts.map +1 -0
- package/dist/tree/read.js +389 -0
- package/dist/tree/read.js.map +1 -0
- package/dist/tree/writeAt.d.ts +44 -0
- package/dist/tree/writeAt.d.ts.map +1 -0
- package/dist/tree/writeAt.js +282 -0
- package/dist/tree/writeAt.js.map +1 -0
- package/dist/types.d.ts +274 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +47 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +12 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +32 -0
- package/dist/verify.js.map +1 -0
- package/dist/visibility.d.ts +50 -0
- package/dist/visibility.d.ts.map +1 -0
- package/dist/visibility.js +111 -0
- package/dist/visibility.js.map +1 -0
- package/dist/webrtc/index.d.ts +4 -0
- package/dist/webrtc/index.d.ts.map +1 -0
- package/dist/webrtc/index.js +4 -0
- package/dist/webrtc/index.js.map +1 -0
- package/dist/webrtc/lruCache.d.ts +20 -0
- package/dist/webrtc/lruCache.d.ts.map +1 -0
- package/dist/webrtc/lruCache.js +59 -0
- package/dist/webrtc/lruCache.js.map +1 -0
- package/dist/webrtc/peer.d.ts +122 -0
- package/dist/webrtc/peer.d.ts.map +1 -0
- package/dist/webrtc/peer.js +583 -0
- package/dist/webrtc/peer.js.map +1 -0
- package/dist/webrtc/protocol.d.ts +76 -0
- package/dist/webrtc/protocol.d.ts.map +1 -0
- package/dist/webrtc/protocol.js +167 -0
- package/dist/webrtc/protocol.js.map +1 -0
- package/dist/webrtc/store.d.ts +190 -0
- package/dist/webrtc/store.d.ts.map +1 -0
- package/dist/webrtc/store.js +1043 -0
- package/dist/webrtc/store.js.map +1 -0
- package/dist/webrtc/types.d.ts +196 -0
- package/dist/webrtc/types.d.ts.map +1 -0
- package/dist/webrtc/types.js +46 -0
- package/dist/webrtc/types.js.map +1 -0
- package/dist/worker/protocol.d.ts +493 -0
- package/dist/worker/protocol.d.ts.map +1 -0
- package/dist/worker/protocol.js +15 -0
- package/dist/worker/protocol.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree builder with chunking and fanout support
|
|
3
|
+
*
|
|
4
|
+
* - Large files are split into chunks
|
|
5
|
+
* - Large directories are split into sub-trees
|
|
6
|
+
* - Supports streaming appends
|
|
7
|
+
*/
|
|
8
|
+
import { Store, Hash, Link, LinkType } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Default chunk size: 2MB (optimized for blossom uploads)
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_CHUNK_SIZE: number;
|
|
13
|
+
/**
|
|
14
|
+
* Chunker function: returns chunk size for a given chunk index
|
|
15
|
+
* @param index - 0-based chunk index
|
|
16
|
+
* @returns chunk size in bytes
|
|
17
|
+
*/
|
|
18
|
+
export type Chunker = (index: number) => number;
|
|
19
|
+
/**
|
|
20
|
+
* Create a fixed-size chunker
|
|
21
|
+
*/
|
|
22
|
+
export declare function fixedChunker(size: number): Chunker;
|
|
23
|
+
/**
|
|
24
|
+
* Create a video-optimized chunker with smaller first chunk for fast playback start
|
|
25
|
+
* @param firstChunkSize - Size of first chunk (default: 256KB)
|
|
26
|
+
* @param regularChunkSize - Size of remaining chunks (default: 2MB)
|
|
27
|
+
*/
|
|
28
|
+
export declare function videoChunker(firstChunkSize?: number, regularChunkSize?: number): Chunker;
|
|
29
|
+
export interface BuilderConfig {
|
|
30
|
+
store: Store;
|
|
31
|
+
/** Chunk size for splitting blobs (ignored if chunker is provided) */
|
|
32
|
+
chunkSize?: number;
|
|
33
|
+
/** Custom chunker function for variable chunk sizes */
|
|
34
|
+
chunker?: Chunker;
|
|
35
|
+
/** Hash chunks in parallel (default: true) */
|
|
36
|
+
parallel?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface FileEntry {
|
|
39
|
+
name: string;
|
|
40
|
+
data: Uint8Array;
|
|
41
|
+
}
|
|
42
|
+
export interface DirEntry {
|
|
43
|
+
name: string;
|
|
44
|
+
hash: Hash;
|
|
45
|
+
size: number;
|
|
46
|
+
type: LinkType;
|
|
47
|
+
meta?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* TreeBuilder - builds content-addressed merkle trees
|
|
51
|
+
*/
|
|
52
|
+
export declare class TreeBuilder {
|
|
53
|
+
private store;
|
|
54
|
+
private chunker;
|
|
55
|
+
private parallel;
|
|
56
|
+
constructor(config: BuilderConfig);
|
|
57
|
+
/**
|
|
58
|
+
* Store a blob directly (small data)
|
|
59
|
+
* Returns the content hash
|
|
60
|
+
*/
|
|
61
|
+
putBlob(data: Uint8Array): Promise<Hash>;
|
|
62
|
+
/**
|
|
63
|
+
* Store a file, chunking if necessary
|
|
64
|
+
* Returns root hash and total size
|
|
65
|
+
*/
|
|
66
|
+
putFile(data: Uint8Array): Promise<{
|
|
67
|
+
hash: Hash;
|
|
68
|
+
size: number;
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Build a balanced tree from links (for chunked files)
|
|
72
|
+
* Handles fanout by creating intermediate nodes
|
|
73
|
+
*/
|
|
74
|
+
private buildTree;
|
|
75
|
+
/**
|
|
76
|
+
* Build a directory from entries
|
|
77
|
+
* Entries can be files or subdirectories
|
|
78
|
+
*
|
|
79
|
+
* Directories are encoded as MessagePack blobs. If the encoded blob exceeds
|
|
80
|
+
* chunkSize, it's chunked by bytes like files using putFile.
|
|
81
|
+
*
|
|
82
|
+
* @param entries Directory entries
|
|
83
|
+
*/
|
|
84
|
+
putDirectory(entries: DirEntry[]): Promise<Hash>;
|
|
85
|
+
/**
|
|
86
|
+
* Create a tree node
|
|
87
|
+
* @param nodeType - LinkType.File or LinkType.Dir
|
|
88
|
+
*/
|
|
89
|
+
putTreeNode(nodeType: LinkType.File | LinkType.Dir, links: Link[]): Promise<Hash>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* StreamBuilder - supports incremental appends
|
|
93
|
+
*/
|
|
94
|
+
export declare class StreamBuilder {
|
|
95
|
+
private store;
|
|
96
|
+
private chunker;
|
|
97
|
+
private buffer;
|
|
98
|
+
private bufferOffset;
|
|
99
|
+
private chunks;
|
|
100
|
+
private totalSize;
|
|
101
|
+
constructor(config: BuilderConfig);
|
|
102
|
+
/** Get current target chunk size */
|
|
103
|
+
private currentChunkSize;
|
|
104
|
+
/**
|
|
105
|
+
* Append data to the stream
|
|
106
|
+
*/
|
|
107
|
+
append(data: Uint8Array): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Flush current buffer as a chunk
|
|
110
|
+
*/
|
|
111
|
+
private flushChunk;
|
|
112
|
+
/**
|
|
113
|
+
* Get current root hash without finalizing
|
|
114
|
+
* Useful for checkpoints
|
|
115
|
+
*/
|
|
116
|
+
currentRoot(): Promise<Hash | null>;
|
|
117
|
+
/**
|
|
118
|
+
* Finalize the stream and return root hash
|
|
119
|
+
*/
|
|
120
|
+
finalize(): Promise<{
|
|
121
|
+
hash: Hash;
|
|
122
|
+
size: number;
|
|
123
|
+
}>;
|
|
124
|
+
/**
|
|
125
|
+
* Build flat tree from chunks (for streaming files)
|
|
126
|
+
*/
|
|
127
|
+
private buildTreeFromChunks;
|
|
128
|
+
/**
|
|
129
|
+
* Get stats
|
|
130
|
+
*/
|
|
131
|
+
get stats(): {
|
|
132
|
+
chunks: number;
|
|
133
|
+
buffered: number;
|
|
134
|
+
totalSize: number;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAY,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAInE;;GAEG;AACH,eAAO,MAAM,kBAAkB,QAAkB,CAAC;AAElD;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;AAEhD;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,cAAc,GAAE,MAAmB,EACnC,gBAAgB,GAAE,MAA2B,GAC5C,OAAO,CAET;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAU;gBAEd,MAAM,EAAE,aAAa;IAMjC;;;OAGG;IACG,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9C;;;OAGG;IACG,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IA8CtE;;;OAGG;YACW,SAAS;IAgBvB;;;;;;;;OAQG;IACG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BtD;;;OAGG;IACG,WAAW,CACf,QAAQ,EAAE,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAC,GAAG,EACtC,KAAK,EAAE,IAAI,EAAE,GACZ,OAAO,CAAC,IAAI,CAAC;CAUjB;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,OAAO,CAAU;IAGzB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,YAAY,CAAa;IAGjC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,SAAS,CAAa;gBAElB,MAAM,EAAE,aAAa;IAOjC,oCAAoC;IACpC,OAAO,CAAC,gBAAgB;IAIxB;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B7C;;OAEG;YACW,UAAU;IAWxB;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAiBzC;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAevD;;OAEG;YACW,mBAAmB;IAejC;;OAEG;IACH,IAAI,KAAK,IAAI;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAMnE;CACF"}
|
package/dist/builder.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree builder with chunking and fanout support
|
|
3
|
+
*
|
|
4
|
+
* - Large files are split into chunks
|
|
5
|
+
* - Large directories are split into sub-trees
|
|
6
|
+
* - Supports streaming appends
|
|
7
|
+
*/
|
|
8
|
+
import { LinkType } from './types.js';
|
|
9
|
+
import { sha256 } from './hash.js';
|
|
10
|
+
import { encodeAndHash } from './codec.js';
|
|
11
|
+
/**
|
|
12
|
+
* Default chunk size: 2MB (optimized for blossom uploads)
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024;
|
|
15
|
+
/**
|
|
16
|
+
* Create a fixed-size chunker
|
|
17
|
+
*/
|
|
18
|
+
export function fixedChunker(size) {
|
|
19
|
+
return () => size;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a video-optimized chunker with smaller first chunk for fast playback start
|
|
23
|
+
* @param firstChunkSize - Size of first chunk (default: 256KB)
|
|
24
|
+
* @param regularChunkSize - Size of remaining chunks (default: 2MB)
|
|
25
|
+
*/
|
|
26
|
+
export function videoChunker(firstChunkSize = 256 * 1024, regularChunkSize = DEFAULT_CHUNK_SIZE) {
|
|
27
|
+
return (index) => index === 0 ? firstChunkSize : regularChunkSize;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* TreeBuilder - builds content-addressed merkle trees
|
|
31
|
+
*/
|
|
32
|
+
export class TreeBuilder {
|
|
33
|
+
store;
|
|
34
|
+
chunker;
|
|
35
|
+
parallel;
|
|
36
|
+
constructor(config) {
|
|
37
|
+
this.store = config.store;
|
|
38
|
+
this.chunker = config.chunker ?? fixedChunker(config.chunkSize ?? DEFAULT_CHUNK_SIZE);
|
|
39
|
+
this.parallel = config.parallel ?? true;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Store a blob directly (small data)
|
|
43
|
+
* Returns the content hash
|
|
44
|
+
*/
|
|
45
|
+
async putBlob(data) {
|
|
46
|
+
const hash = await sha256(data);
|
|
47
|
+
await this.store.put(hash, data);
|
|
48
|
+
return hash;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Store a file, chunking if necessary
|
|
52
|
+
* Returns root hash and total size
|
|
53
|
+
*/
|
|
54
|
+
async putFile(data) {
|
|
55
|
+
const size = data.length;
|
|
56
|
+
const firstChunkSize = this.chunker(0);
|
|
57
|
+
// Small file - store as single blob
|
|
58
|
+
if (data.length <= firstChunkSize) {
|
|
59
|
+
const hash = await this.putBlob(data);
|
|
60
|
+
return { hash, size };
|
|
61
|
+
}
|
|
62
|
+
// Split into chunks using chunker
|
|
63
|
+
const chunkList = [];
|
|
64
|
+
const chunkSizes = [];
|
|
65
|
+
let offset = 0;
|
|
66
|
+
let chunkIndex = 0;
|
|
67
|
+
while (offset < data.length) {
|
|
68
|
+
const chunkSize = this.chunker(chunkIndex);
|
|
69
|
+
const end = Math.min(offset + chunkSize, data.length);
|
|
70
|
+
chunkList.push(data.slice(offset, end));
|
|
71
|
+
chunkSizes.push(end - offset);
|
|
72
|
+
offset = end;
|
|
73
|
+
chunkIndex++;
|
|
74
|
+
}
|
|
75
|
+
// Hash and store chunks (parallel or sequential)
|
|
76
|
+
let chunkHashes;
|
|
77
|
+
if (this.parallel) {
|
|
78
|
+
chunkHashes = await Promise.all(chunkList.map(chunk => this.putBlob(chunk)));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
chunkHashes = [];
|
|
82
|
+
for (const chunk of chunkList) {
|
|
83
|
+
chunkHashes.push(await this.putBlob(chunk));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Build tree from chunks (leaf chunks are raw blobs)
|
|
87
|
+
const chunks = chunkHashes.map((hash, i) => ({
|
|
88
|
+
hash,
|
|
89
|
+
size: chunkSizes[i],
|
|
90
|
+
type: LinkType.Blob,
|
|
91
|
+
}));
|
|
92
|
+
const rootHash = await this.buildTree(chunks, size);
|
|
93
|
+
return { hash: rootHash, size };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build a balanced tree from links (for chunked files)
|
|
97
|
+
* Handles fanout by creating intermediate nodes
|
|
98
|
+
*/
|
|
99
|
+
async buildTree(links, totalSize) {
|
|
100
|
+
// Single link - return it directly
|
|
101
|
+
if (links.length === 1 && links[0].size === totalSize) {
|
|
102
|
+
return links[0].hash;
|
|
103
|
+
}
|
|
104
|
+
// Create single flat node with all links
|
|
105
|
+
const node = {
|
|
106
|
+
type: LinkType.File,
|
|
107
|
+
links,
|
|
108
|
+
};
|
|
109
|
+
const { data, hash } = await encodeAndHash(node);
|
|
110
|
+
await this.store.put(hash, data);
|
|
111
|
+
return hash;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build a directory from entries
|
|
115
|
+
* Entries can be files or subdirectories
|
|
116
|
+
*
|
|
117
|
+
* Directories are encoded as MessagePack blobs. If the encoded blob exceeds
|
|
118
|
+
* chunkSize, it's chunked by bytes like files using putFile.
|
|
119
|
+
*
|
|
120
|
+
* @param entries Directory entries
|
|
121
|
+
*/
|
|
122
|
+
async putDirectory(entries) {
|
|
123
|
+
// Sort entries by name for deterministic hashing
|
|
124
|
+
const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
|
|
125
|
+
const links = sorted.map(e => ({
|
|
126
|
+
hash: e.hash,
|
|
127
|
+
name: e.name,
|
|
128
|
+
size: e.size,
|
|
129
|
+
type: e.type,
|
|
130
|
+
meta: e.meta,
|
|
131
|
+
}));
|
|
132
|
+
const node = {
|
|
133
|
+
type: LinkType.Dir,
|
|
134
|
+
links,
|
|
135
|
+
};
|
|
136
|
+
const { data, hash } = await encodeAndHash(node);
|
|
137
|
+
// Small directory - store directly
|
|
138
|
+
if (data.length <= this.chunker(0)) {
|
|
139
|
+
await this.store.put(hash, data);
|
|
140
|
+
return hash;
|
|
141
|
+
}
|
|
142
|
+
// Large directory - reuse putFile for chunking
|
|
143
|
+
const { hash: rootHash } = await this.putFile(data);
|
|
144
|
+
return rootHash;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create a tree node
|
|
148
|
+
* @param nodeType - LinkType.File or LinkType.Dir
|
|
149
|
+
*/
|
|
150
|
+
async putTreeNode(nodeType, links) {
|
|
151
|
+
const node = {
|
|
152
|
+
type: nodeType,
|
|
153
|
+
links,
|
|
154
|
+
};
|
|
155
|
+
const { data, hash } = await encodeAndHash(node);
|
|
156
|
+
await this.store.put(hash, data);
|
|
157
|
+
return hash;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* StreamBuilder - supports incremental appends
|
|
162
|
+
*/
|
|
163
|
+
export class StreamBuilder {
|
|
164
|
+
store;
|
|
165
|
+
chunker;
|
|
166
|
+
// Current partial chunk being built
|
|
167
|
+
buffer;
|
|
168
|
+
bufferOffset = 0;
|
|
169
|
+
// Completed chunks
|
|
170
|
+
chunks = [];
|
|
171
|
+
totalSize = 0;
|
|
172
|
+
constructor(config) {
|
|
173
|
+
this.store = config.store;
|
|
174
|
+
this.chunker = config.chunker ?? fixedChunker(config.chunkSize ?? DEFAULT_CHUNK_SIZE);
|
|
175
|
+
// Initialize buffer with first chunk size
|
|
176
|
+
this.buffer = new Uint8Array(this.chunker(0));
|
|
177
|
+
}
|
|
178
|
+
/** Get current target chunk size */
|
|
179
|
+
currentChunkSize() {
|
|
180
|
+
return this.chunker(this.chunks.length);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Append data to the stream
|
|
184
|
+
*/
|
|
185
|
+
async append(data) {
|
|
186
|
+
let offset = 0;
|
|
187
|
+
while (offset < data.length) {
|
|
188
|
+
const targetSize = this.currentChunkSize();
|
|
189
|
+
// Resize buffer if needed for new chunk size
|
|
190
|
+
if (this.buffer.length !== targetSize) {
|
|
191
|
+
const newBuffer = new Uint8Array(targetSize);
|
|
192
|
+
if (this.bufferOffset > 0) {
|
|
193
|
+
newBuffer.set(this.buffer.subarray(0, this.bufferOffset));
|
|
194
|
+
}
|
|
195
|
+
this.buffer = newBuffer;
|
|
196
|
+
}
|
|
197
|
+
const space = targetSize - this.bufferOffset;
|
|
198
|
+
const toWrite = Math.min(space, data.length - offset);
|
|
199
|
+
this.buffer.set(data.subarray(offset, offset + toWrite), this.bufferOffset);
|
|
200
|
+
this.bufferOffset += toWrite;
|
|
201
|
+
offset += toWrite;
|
|
202
|
+
// Flush full chunk
|
|
203
|
+
if (this.bufferOffset === targetSize) {
|
|
204
|
+
await this.flushChunk();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
this.totalSize += data.length;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Flush current buffer as a chunk
|
|
211
|
+
*/
|
|
212
|
+
async flushChunk() {
|
|
213
|
+
if (this.bufferOffset === 0)
|
|
214
|
+
return;
|
|
215
|
+
const chunk = this.buffer.slice(0, this.bufferOffset);
|
|
216
|
+
const hash = await sha256(chunk);
|
|
217
|
+
await this.store.put(hash, new Uint8Array(chunk));
|
|
218
|
+
this.chunks.push({ hash, size: chunk.length, type: LinkType.Blob });
|
|
219
|
+
this.bufferOffset = 0;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get current root hash without finalizing
|
|
223
|
+
* Useful for checkpoints
|
|
224
|
+
*/
|
|
225
|
+
async currentRoot() {
|
|
226
|
+
if (this.chunks.length === 0 && this.bufferOffset === 0) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
// Temporarily flush buffer
|
|
230
|
+
const tempChunks = [...this.chunks];
|
|
231
|
+
if (this.bufferOffset > 0) {
|
|
232
|
+
const chunk = this.buffer.slice(0, this.bufferOffset);
|
|
233
|
+
const hash = await sha256(chunk);
|
|
234
|
+
await this.store.put(hash, new Uint8Array(chunk));
|
|
235
|
+
tempChunks.push({ hash, size: chunk.length, type: LinkType.Blob });
|
|
236
|
+
}
|
|
237
|
+
return this.buildTreeFromChunks(tempChunks, this.totalSize);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Finalize the stream and return root hash
|
|
241
|
+
*/
|
|
242
|
+
async finalize() {
|
|
243
|
+
// Flush remaining buffer
|
|
244
|
+
await this.flushChunk();
|
|
245
|
+
if (this.chunks.length === 0) {
|
|
246
|
+
// Empty stream - return hash of empty data
|
|
247
|
+
const emptyHash = await sha256(new Uint8Array(0));
|
|
248
|
+
await this.store.put(emptyHash, new Uint8Array(0));
|
|
249
|
+
return { hash: emptyHash, size: 0 };
|
|
250
|
+
}
|
|
251
|
+
const hash = await this.buildTreeFromChunks(this.chunks, this.totalSize);
|
|
252
|
+
return { hash, size: this.totalSize };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Build flat tree from chunks (for streaming files)
|
|
256
|
+
*/
|
|
257
|
+
async buildTreeFromChunks(chunks, _totalSize) {
|
|
258
|
+
if (chunks.length === 1) {
|
|
259
|
+
return chunks[0].hash;
|
|
260
|
+
}
|
|
261
|
+
// Create single flat node with all links
|
|
262
|
+
const node = {
|
|
263
|
+
type: LinkType.File,
|
|
264
|
+
links: chunks,
|
|
265
|
+
};
|
|
266
|
+
const { data, hash } = await encodeAndHash(node);
|
|
267
|
+
await this.store.put(hash, data);
|
|
268
|
+
return hash;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get stats
|
|
272
|
+
*/
|
|
273
|
+
get stats() {
|
|
274
|
+
return {
|
|
275
|
+
chunks: this.chunks.length,
|
|
276
|
+
buffered: this.bufferOffset,
|
|
277
|
+
totalSize: this.totalSize,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.js","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
|
package/dist/codec.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessagePack encoding/decoding for tree nodes
|
|
3
|
+
*
|
|
4
|
+
* Blobs are stored raw (not wrapped) for efficiency.
|
|
5
|
+
* Tree nodes are MessagePack-encoded.
|
|
6
|
+
*
|
|
7
|
+
* **Determinism:** We ensure deterministic output by:
|
|
8
|
+
* 1. Using fixed field order in the encoded map
|
|
9
|
+
* 2. Sorting metadata keys alphabetically before encoding
|
|
10
|
+
*/
|
|
11
|
+
import { TreeNode, LinkType, Hash } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Encode a tree node to MessagePack
|
|
14
|
+
* Fields are ordered alphabetically for canonical encoding
|
|
15
|
+
*/
|
|
16
|
+
export declare function encodeTreeNode(node: TreeNode): Uint8Array;
|
|
17
|
+
/**
|
|
18
|
+
* Try to decode MessagePack data as a tree node
|
|
19
|
+
* Returns null if data is not a valid tree node (i.e., it's a raw blob)
|
|
20
|
+
*/
|
|
21
|
+
export declare function tryDecodeTreeNode(data: Uint8Array): TreeNode | null;
|
|
22
|
+
/**
|
|
23
|
+
* Decode MessagePack to a tree node (throws if not a tree node)
|
|
24
|
+
*/
|
|
25
|
+
export declare function decodeTreeNode(data: Uint8Array): TreeNode;
|
|
26
|
+
/**
|
|
27
|
+
* Encode a tree node and compute its hash
|
|
28
|
+
*/
|
|
29
|
+
export declare function encodeAndHash(node: TreeNode): Promise<{
|
|
30
|
+
data: Uint8Array;
|
|
31
|
+
hash: Hash;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Get the type of a chunk: File, Dir, or Blob
|
|
35
|
+
*/
|
|
36
|
+
export declare function getNodeType(data: Uint8Array): LinkType;
|
|
37
|
+
//# sourceMappingURL=codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,QAAQ,EAAQ,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AA2C5D;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAwBzD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,IAAI,CAuBnE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,CAMzD;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAI7F;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,CAGtD"}
|
package/dist/codec.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessagePack encoding/decoding for tree nodes
|
|
3
|
+
*
|
|
4
|
+
* Blobs are stored raw (not wrapped) for efficiency.
|
|
5
|
+
* Tree nodes are MessagePack-encoded.
|
|
6
|
+
*
|
|
7
|
+
* **Determinism:** We ensure deterministic output by:
|
|
8
|
+
* 1. Using fixed field order in the encoded map
|
|
9
|
+
* 2. Sorting metadata keys alphabetically before encoding
|
|
10
|
+
*/
|
|
11
|
+
import { encode, decode } from '@msgpack/msgpack';
|
|
12
|
+
import { LinkType } from './types.js';
|
|
13
|
+
import { sha256 } from './hash.js';
|
|
14
|
+
/**
|
|
15
|
+
* Sort object keys alphabetically for deterministic encoding
|
|
16
|
+
*/
|
|
17
|
+
function sortObjectKeys(obj) {
|
|
18
|
+
const sorted = {};
|
|
19
|
+
for (const key of Object.keys(obj).sort()) {
|
|
20
|
+
sorted[key] = obj[key];
|
|
21
|
+
}
|
|
22
|
+
return sorted;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Encode a tree node to MessagePack
|
|
26
|
+
* Fields are ordered alphabetically for canonical encoding
|
|
27
|
+
*/
|
|
28
|
+
export function encodeTreeNode(node) {
|
|
29
|
+
// TreeNode fields in alphabetical order: l, t
|
|
30
|
+
const msgpack = {
|
|
31
|
+
l: node.links.map(link => {
|
|
32
|
+
// Link fields in alphabetical order: h, k?, m?, n?, s, t
|
|
33
|
+
// Build object with all fields in order, undefined values are omitted by msgpack
|
|
34
|
+
const l = {
|
|
35
|
+
h: link.hash,
|
|
36
|
+
k: link.key,
|
|
37
|
+
m: link.meta !== undefined ? sortObjectKeys(link.meta) : undefined,
|
|
38
|
+
n: link.name,
|
|
39
|
+
s: link.size,
|
|
40
|
+
t: link.type,
|
|
41
|
+
};
|
|
42
|
+
// Remove undefined fields to match skip_serializing_if behavior
|
|
43
|
+
if (l.k === undefined)
|
|
44
|
+
delete l.k;
|
|
45
|
+
if (l.m === undefined)
|
|
46
|
+
delete l.m;
|
|
47
|
+
if (l.n === undefined)
|
|
48
|
+
delete l.n;
|
|
49
|
+
return l;
|
|
50
|
+
}),
|
|
51
|
+
t: node.type,
|
|
52
|
+
};
|
|
53
|
+
return encode(msgpack);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Try to decode MessagePack data as a tree node
|
|
57
|
+
* Returns null if data is not a valid tree node (i.e., it's a raw blob)
|
|
58
|
+
*/
|
|
59
|
+
export function tryDecodeTreeNode(data) {
|
|
60
|
+
try {
|
|
61
|
+
const msgpack = decode(data);
|
|
62
|
+
if (msgpack.t !== LinkType.File && msgpack.t !== LinkType.Dir) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const node = {
|
|
66
|
+
type: msgpack.t,
|
|
67
|
+
links: msgpack.l.map(l => {
|
|
68
|
+
const link = { hash: l.h, size: l.s ?? 0, type: l.t ?? LinkType.Blob };
|
|
69
|
+
if (l.n !== undefined)
|
|
70
|
+
link.name = l.n;
|
|
71
|
+
if (l.k !== undefined)
|
|
72
|
+
link.key = l.k;
|
|
73
|
+
if (l.m !== undefined)
|
|
74
|
+
link.meta = l.m;
|
|
75
|
+
return link;
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
return node;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Decode MessagePack to a tree node (throws if not a tree node)
|
|
86
|
+
*/
|
|
87
|
+
export function decodeTreeNode(data) {
|
|
88
|
+
const node = tryDecodeTreeNode(data);
|
|
89
|
+
if (!node) {
|
|
90
|
+
throw new Error('Data is not a valid tree node');
|
|
91
|
+
}
|
|
92
|
+
return node;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Encode a tree node and compute its hash
|
|
96
|
+
*/
|
|
97
|
+
export async function encodeAndHash(node) {
|
|
98
|
+
const data = encodeTreeNode(node);
|
|
99
|
+
const hash = await sha256(data);
|
|
100
|
+
return { data, hash };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get the type of a chunk: File, Dir, or Blob
|
|
104
|
+
*/
|
|
105
|
+
export function getNodeType(data) {
|
|
106
|
+
const node = tryDecodeTreeNode(data);
|
|
107
|
+
return node?.type ?? LinkType.Blob;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.js","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"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"}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encryption utilities for HashTree
|
|
3
|
+
*
|
|
4
|
+
* CHK (Content Hash Key) encryption for deterministic encryption:
|
|
5
|
+
* - key = SHA256(plaintext) - content hash becomes decryption key
|
|
6
|
+
* - encryption_key = HKDF(key, salt="hashtree-chk", info="encryption-key")
|
|
7
|
+
* - ciphertext = AES-256-GCM(encryption_key, zero_nonce, plaintext)
|
|
8
|
+
*
|
|
9
|
+
* Zero nonce is safe because CHK guarantees: same key = same content.
|
|
10
|
+
* This enables deduplication: same content → same ciphertext → same hash.
|
|
11
|
+
*
|
|
12
|
+
* Format: [ciphertext][16-byte auth tag] (no IV needed with CHK)
|
|
13
|
+
*
|
|
14
|
+
* Also includes legacy functions with random IV for backward compatibility:
|
|
15
|
+
* Format: [12-byte IV][ciphertext][16-byte auth tag]
|
|
16
|
+
*/
|
|
17
|
+
/** 32-byte encryption key (256 bits) */
|
|
18
|
+
export type EncryptionKey = Uint8Array;
|
|
19
|
+
/** Generate a random 32-byte encryption key */
|
|
20
|
+
export declare function generateKey(): EncryptionKey;
|
|
21
|
+
/**
|
|
22
|
+
* Compute content hash (SHA256) - this becomes the decryption key for CHK
|
|
23
|
+
*/
|
|
24
|
+
export declare function contentHash(data: Uint8Array): Promise<EncryptionKey>;
|
|
25
|
+
/**
|
|
26
|
+
* CHK encrypt: derive key from content, encrypt with zero nonce
|
|
27
|
+
*
|
|
28
|
+
* Returns: (ciphertext with auth tag, content_hash as decryption key)
|
|
29
|
+
*
|
|
30
|
+
* Zero nonce is safe because CHK guarantees: same key = same content.
|
|
31
|
+
* We never encrypt different content with the same key.
|
|
32
|
+
*
|
|
33
|
+
* The content_hash is both:
|
|
34
|
+
* - The decryption key (store securely, share with authorized users)
|
|
35
|
+
* - Enables dedup: same content → same ciphertext
|
|
36
|
+
*
|
|
37
|
+
* @param plaintext - Data to encrypt
|
|
38
|
+
* @returns Object with encrypted data and content hash (decryption key)
|
|
39
|
+
*/
|
|
40
|
+
export declare function encryptChk(plaintext: Uint8Array): Promise<{
|
|
41
|
+
ciphertext: Uint8Array;
|
|
42
|
+
key: EncryptionKey;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* CHK decrypt: derive key from content_hash, decrypt with zero nonce
|
|
46
|
+
*
|
|
47
|
+
* @param ciphertext - Encrypted data (includes auth tag)
|
|
48
|
+
* @param key - Content hash returned from encryptChk
|
|
49
|
+
* @returns Decrypted plaintext
|
|
50
|
+
* @throws Error if decryption fails (wrong key or tampered data)
|
|
51
|
+
*/
|
|
52
|
+
export declare function decryptChk(ciphertext: Uint8Array, key: EncryptionKey): Promise<Uint8Array>;
|
|
53
|
+
/**
|
|
54
|
+
* Calculate encrypted size for CHK (no nonce prefix, just ciphertext + auth tag)
|
|
55
|
+
*/
|
|
56
|
+
export declare function encryptedSizeChk(plaintextSize: number): number;
|
|
57
|
+
/**
|
|
58
|
+
* Encrypt data using AES-256-GCM with random IV
|
|
59
|
+
* @deprecated Use encryptChk for deterministic CHK encryption
|
|
60
|
+
*
|
|
61
|
+
* @param plaintext - Data to encrypt
|
|
62
|
+
* @param key - 32-byte encryption key
|
|
63
|
+
* @returns Encrypted data: [12-byte IV][ciphertext + 16-byte auth tag]
|
|
64
|
+
*/
|
|
65
|
+
export declare function encrypt(plaintext: Uint8Array, key: EncryptionKey): Promise<Uint8Array>;
|
|
66
|
+
/**
|
|
67
|
+
* Decrypt data using AES-256-GCM
|
|
68
|
+
*
|
|
69
|
+
* @param encrypted - Encrypted data: [12-byte IV][ciphertext + auth tag]
|
|
70
|
+
* @param key - 32-byte encryption key
|
|
71
|
+
* @returns Decrypted plaintext
|
|
72
|
+
* @throws Error if decryption fails (wrong key or tampered data)
|
|
73
|
+
*/
|
|
74
|
+
export declare function decrypt(encrypted: Uint8Array, key: EncryptionKey): Promise<Uint8Array>;
|
|
75
|
+
/**
|
|
76
|
+
* Check if data could be encrypted (based on minimum size).
|
|
77
|
+
* Note: This is a heuristic - actual encrypted data might be larger.
|
|
78
|
+
*/
|
|
79
|
+
export declare function couldBeEncrypted(data: Uint8Array): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Calculate encrypted size for given plaintext size
|
|
82
|
+
*/
|
|
83
|
+
export declare function encryptedSize(plaintextSize: number): number;
|
|
84
|
+
/**
|
|
85
|
+
* Calculate plaintext size from encrypted size
|
|
86
|
+
*/
|
|
87
|
+
export declare function plaintextSize(encryptedSize: number): number;
|
|
88
|
+
/** Convert key to hex string */
|
|
89
|
+
export declare function keyToHex(key: EncryptionKey): string;
|
|
90
|
+
/** Convert hex string to key */
|
|
91
|
+
export declare function keyFromHex(hex: string): EncryptionKey;
|
|
92
|
+
//# sourceMappingURL=crypto.d.ts.map
|