-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCDCStore.js
More file actions
139 lines (126 loc) · 4.03 KB
/
Copy pathCDCStore.js
File metadata and controls
139 lines (126 loc) · 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
* In-memory content-addressable store for CDC chunks.
*
* Chunks are keyed by their SHA-256 (32 bytes, stored as a lowercase hex string for Map
* compatibility — Uint8Array keys would only compare by identity). One chunk per unique
* content; storing the same bytes twice is a no-op.
*
* Use this as the storage backing for `CDCSync`. For long-term storage / large datasets
* you'd swap this for a disk-backed or remote store with the same interface.
*
* @example
* const store = new CDCStore();
* const hash = store.put(bytes);
* store.has(hash); // true
* store.get(hash); // → bytes (Uint8Array)
* store.size; // 1
*/
import { sha256 } from '@noble/hashes/sha2.js';
const BRAND = Symbol.for('doublesync.CDCStore');
export default class CDCStore {
constructor(params = {}) {
/** @type {Map<string, Uint8Array>} - hex(sha256) → bytes */
this._chunks = new Map();
this._copyBytes = params.copyBytes !== false;
this[BRAND] = true;
}
static [Symbol.hasInstance](obj) {
return !!obj && obj[BRAND] === true;
}
/**
* Compute the canonical hash of `bytes` (SHA-256 → 32 raw bytes).
*
* @param {Uint8Array} bytes
* @returns {Uint8Array}
*/
static hashOf(bytes) {
return sha256(bytes);
}
/**
* Convert a 32-byte raw hash to its lowercase hex key.
* @param {Uint8Array} hash
* @returns {string}
*/
static hashToHex(hash) {
let s = '';
for (let i = 0; i < hash.length; i++) s += hash[i].toString(16).padStart(2, '0');
return s;
}
/**
* Convert a hex key back to a 32-byte raw hash.
* @param {string} hex
* @returns {Uint8Array}
*/
static hashFromHex(hex) {
if (hex.length % 2 !== 0) throw new Error(`CDCStore.hashFromHex: odd-length hex string`);
const out = new Uint8Array(hex.length / 2);
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.substr(i * 2, 2), 16);
return out;
}
/** Number of distinct chunks currently stored. */
get size() {
return this._chunks.size;
}
/**
* Insert a chunk by content. Returns its hash. Idempotent — storing the same
* bytes twice is cheap and produces the same hash.
*
* @param {Uint8Array} bytes
* @returns {Uint8Array} the 32-byte raw SHA-256
*/
put(bytes) {
const hash = CDCStore.hashOf(bytes);
const key = CDCStore.hashToHex(hash);
if (!this._chunks.has(key)) {
// Copy so the store owns its bytes (callers may pass a subarray view that
// shares an underlying buffer they later mutate).
this._chunks.set(key, this._copyBytes ? bytes.slice() : bytes);
}
return hash;
}
/**
* Insert a chunk by raw hash + bytes — useful when receiving chunks from a remote
* peer (you have the hash from a manifest before the bytes arrive).
*
* @param {Uint8Array} hash
* @param {Uint8Array} bytes
*/
putWithHash(hash, bytes) {
this._chunks.set(CDCStore.hashToHex(hash), this._copyBytes ? bytes.slice() : bytes);
}
/**
* Whether a chunk with this hash is present.
*
* @param {Uint8Array} hash
* @returns {boolean}
*/
has(hash) {
return this._chunks.has(CDCStore.hashToHex(hash));
}
/**
* Retrieve a chunk by hash. Returns `null` if absent.
*
* @param {Uint8Array} hash
* @returns {?Uint8Array}
*/
get(hash) {
return this._chunks.get(CDCStore.hashToHex(hash)) ?? null;
}
/**
* Remove a chunk by hash. Returns true if the chunk existed.
*
* @param {Uint8Array} hash
* @returns {boolean}
*/
delete(hash) {
return this._chunks.delete(CDCStore.hashToHex(hash));
}
/**
* Iterate over all hashes (as raw 32-byte Uint8Arrays). Order is insertion order.
*
* @yields {Uint8Array}
*/
*hashes() {
for (const key of this._chunks.keys()) yield CDCStore.hashFromHex(key);
}
}