-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDoubleSyncFile.js
More file actions
93 lines (81 loc) · 3.03 KB
/
Copy pathDoubleSyncFile.js
File metadata and controls
93 lines (81 loc) · 3.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
import { sha256 } from '@noble/hashes/sha2.js';
const FILE_BRAND = Symbol.for('doublesync.DoubleSyncFile');
/**
* Abstract read+write file. Concrete subclasses implement the storage backing.
*
* Methods marked optional throw "not writable" by default — subclasses opt into
* mutability by overriding them. Async everywhere so the same code paths work for
* in-memory, on-disk, and network-backed implementations.
*/
export class DoubleSyncFile {
constructor() {
this[FILE_BRAND] = true;
}
static [Symbol.hasInstance](obj) {
return !!obj && obj[FILE_BRAND] === true;
}
/**
* Leaf name (no path separators). Subclasses must populate this.
* @returns {string}
*/
get name() { throw new Error('DoubleSyncFile.name: not implemented'); }
/**
* Read the file's bytes.
* @returns {Promise<Uint8Array>}
*/
async getContent() { throw new Error('DoubleSyncFile.getContent: not implemented'); }
/**
* Byte length. Default reads the full content; backends that can stat cheaply
* should override.
* @returns {Promise<number>}
*/
async getSize() {
return (await this.getContent()).length;
}
/**
* Content fingerprint (default: SHA-256 of getContent). Used by the snapshot layer
* to detect "this file is already up to date" without rechunking. Backends with
* a cheaper cached hash should override.
* @returns {Promise<Uint8Array>} 32 bytes
*/
async getFingerprint() {
return sha256(await this.getContent());
}
/**
* Replace the file's content. Optional — read-only backends throw.
* @param {Uint8Array} bytes
* @returns {Promise<void>}
*/
async setContent(_bytes) { throw new Error(`${this.constructor.name}: not writable`); }
}
/**
* In-memory `DoubleSyncFile`. Stores its content in a `Uint8Array` field; `setContent`
* just replaces the reference (copying so external mutations don't leak in).
*
* @example
* const f = new DoubleSyncMemoryFile('readme.txt', new TextEncoder().encode('hi'));
* await f.getContent(); // → bytes
* await f.setContent(...); // replace
*/
export class DoubleSyncMemoryFile extends DoubleSyncFile {
/**
* @param {string} name
* @param {Uint8Array} [content]
*/
constructor(name, content = new Uint8Array(0)) {
super();
if (typeof name !== 'string' || !name) throw new Error('DoubleSyncMemoryFile: name must be a non-empty string');
if (!(content instanceof Uint8Array)) throw new Error('DoubleSyncMemoryFile: content must be Uint8Array');
/** @type {string} */
this._name = name;
/** @type {Uint8Array} */
this._content = content.slice();
}
get name() { return this._name; }
async getContent() { return this._content; }
async getSize() { return this._content.length; }
async setContent(bytes) {
if (!(bytes instanceof Uint8Array)) throw new Error('DoubleSyncMemoryFile.setContent: bytes must be Uint8Array');
this._content = bytes.slice();
}
}