@componentor/fs 1.1.7
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/README.md +742 -0
- package/dist/index.d.ts +544 -0
- package/dist/index.js +2551 -0
- package/dist/index.js.map +1 -0
- package/dist/opfs-hybrid.d.ts +198 -0
- package/dist/opfs-hybrid.js +2552 -0
- package/dist/opfs-hybrid.js.map +1 -0
- package/dist/opfs-worker-proxy.d.ts +224 -0
- package/dist/opfs-worker-proxy.js +274 -0
- package/dist/opfs-worker-proxy.js.map +1 -0
- package/dist/opfs-worker.js +2732 -0
- package/dist/opfs-worker.js.map +1 -0
- package/package.json +66 -0
- package/src/constants.ts +52 -0
- package/src/errors.ts +88 -0
- package/src/file-handle.ts +100 -0
- package/src/global.d.ts +57 -0
- package/src/handle-manager.ts +250 -0
- package/src/index.ts +1404 -0
- package/src/opfs-hybrid.ts +265 -0
- package/src/opfs-worker-proxy.ts +374 -0
- package/src/opfs-worker.ts +253 -0
- package/src/packed-storage.ts +426 -0
- package/src/path-utils.ts +97 -0
- package/src/streams.ts +109 -0
- package/src/symlink-manager.ts +329 -0
- package/src/types.ts +285 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system constants matching Node.js fs.constants
|
|
3
|
+
*/
|
|
4
|
+
interface FSConstants {
|
|
5
|
+
F_OK: number;
|
|
6
|
+
R_OK: number;
|
|
7
|
+
W_OK: number;
|
|
8
|
+
X_OK: number;
|
|
9
|
+
COPYFILE_EXCL: number;
|
|
10
|
+
COPYFILE_FICLONE: number;
|
|
11
|
+
COPYFILE_FICLONE_FORCE: number;
|
|
12
|
+
O_RDONLY: number;
|
|
13
|
+
O_WRONLY: number;
|
|
14
|
+
O_RDWR: number;
|
|
15
|
+
O_CREAT: number;
|
|
16
|
+
O_EXCL: number;
|
|
17
|
+
O_TRUNC: number;
|
|
18
|
+
O_APPEND: number;
|
|
19
|
+
S_IFMT: number;
|
|
20
|
+
S_IFREG: number;
|
|
21
|
+
S_IFDIR: number;
|
|
22
|
+
S_IFLNK: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Options for readFile operation
|
|
26
|
+
*/
|
|
27
|
+
interface ReadFileOptions {
|
|
28
|
+
/** Text encoding (e.g., 'utf-8'). If not provided, returns Uint8Array */
|
|
29
|
+
encoding?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Options for writeFile operation
|
|
33
|
+
*/
|
|
34
|
+
interface WriteFileOptions {
|
|
35
|
+
/** Text encoding for string data */
|
|
36
|
+
encoding?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Entry for batch file write operation
|
|
40
|
+
*/
|
|
41
|
+
interface BatchWriteEntry {
|
|
42
|
+
/** File path to write */
|
|
43
|
+
path: string;
|
|
44
|
+
/** Data to write (string or binary) */
|
|
45
|
+
data: string | Uint8Array;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Result entry for batch file read operation
|
|
49
|
+
*/
|
|
50
|
+
interface BatchReadResult {
|
|
51
|
+
/** File path */
|
|
52
|
+
path: string;
|
|
53
|
+
/** File data (null if file doesn't exist or error occurred) */
|
|
54
|
+
data: Uint8Array | null;
|
|
55
|
+
/** Error if read failed */
|
|
56
|
+
error?: Error;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Options for readdir operation
|
|
60
|
+
*/
|
|
61
|
+
interface ReaddirOptions {
|
|
62
|
+
/** Return Dirent objects instead of strings */
|
|
63
|
+
withFileTypes?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Directory entry (Dirent-like object)
|
|
67
|
+
*/
|
|
68
|
+
interface Dirent {
|
|
69
|
+
name: string;
|
|
70
|
+
isFile(): boolean;
|
|
71
|
+
isDirectory(): boolean;
|
|
72
|
+
isSymbolicLink(): boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* File statistics
|
|
76
|
+
*/
|
|
77
|
+
interface Stats {
|
|
78
|
+
type: 'file' | 'dir' | 'symlink';
|
|
79
|
+
size: number;
|
|
80
|
+
mode: number;
|
|
81
|
+
ctime: Date;
|
|
82
|
+
ctimeMs: number;
|
|
83
|
+
mtime: Date;
|
|
84
|
+
mtimeMs: number;
|
|
85
|
+
target?: string;
|
|
86
|
+
isFile(): boolean;
|
|
87
|
+
isDirectory(): boolean;
|
|
88
|
+
isSymbolicLink(): boolean;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Options for rm operation
|
|
92
|
+
*/
|
|
93
|
+
interface RmOptions {
|
|
94
|
+
/** Remove directories and their contents recursively */
|
|
95
|
+
recursive?: boolean;
|
|
96
|
+
/** Ignore if path doesn't exist */
|
|
97
|
+
force?: boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Options for cp operation
|
|
101
|
+
*/
|
|
102
|
+
interface CpOptions {
|
|
103
|
+
/** Copy directories recursively */
|
|
104
|
+
recursive?: boolean;
|
|
105
|
+
/** Overwrite existing files */
|
|
106
|
+
force?: boolean;
|
|
107
|
+
/** Throw if destination exists */
|
|
108
|
+
errorOnExist?: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Symlink definition for batch operations
|
|
112
|
+
*/
|
|
113
|
+
interface SymlinkDefinition {
|
|
114
|
+
target: string;
|
|
115
|
+
path: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Disk usage result
|
|
119
|
+
*/
|
|
120
|
+
interface DiskUsage {
|
|
121
|
+
path: string;
|
|
122
|
+
size: number;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Filesystem statistics (similar to Node.js fs.statfs)
|
|
126
|
+
*/
|
|
127
|
+
interface StatFs {
|
|
128
|
+
/** Filesystem type (always 0 for OPFS) */
|
|
129
|
+
type: number;
|
|
130
|
+
/** Optimal transfer block size (simulated as 4096) */
|
|
131
|
+
bsize: number;
|
|
132
|
+
/** Total blocks in filesystem */
|
|
133
|
+
blocks: number;
|
|
134
|
+
/** Free blocks in filesystem */
|
|
135
|
+
bfree: number;
|
|
136
|
+
/** Available blocks for unprivileged users */
|
|
137
|
+
bavail: number;
|
|
138
|
+
/** Total file nodes (0 - not available in browser) */
|
|
139
|
+
files: number;
|
|
140
|
+
/** Free file nodes (0 - not available in browser) */
|
|
141
|
+
ffree: number;
|
|
142
|
+
/** Bytes used by origin (from Storage API) */
|
|
143
|
+
usage: number;
|
|
144
|
+
/** Total bytes available to origin (from Storage API) */
|
|
145
|
+
quota: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface OPFSWorkerOptions {
|
|
149
|
+
/** URL to the worker script (default: auto-detect) */
|
|
150
|
+
workerUrl?: string | URL;
|
|
151
|
+
/** Worker initialization options */
|
|
152
|
+
workerOptions?: WorkerOptions;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* OPFS Worker Proxy - runs OPFS operations in a Web Worker
|
|
156
|
+
*
|
|
157
|
+
* Benefits:
|
|
158
|
+
* - Non-blocking main thread
|
|
159
|
+
* - Uses sync access handles (faster) in the worker
|
|
160
|
+
* - Compatible with libraries that reuse buffers (e.g., isomorphic-git)
|
|
161
|
+
*/
|
|
162
|
+
declare class OPFSWorker {
|
|
163
|
+
private worker;
|
|
164
|
+
private pendingRequests;
|
|
165
|
+
private nextId;
|
|
166
|
+
private readyPromise;
|
|
167
|
+
private readyResolve;
|
|
168
|
+
/** File system constants */
|
|
169
|
+
readonly constants: FSConstants;
|
|
170
|
+
constructor(options?: OPFSWorkerOptions);
|
|
171
|
+
private initWorker;
|
|
172
|
+
/**
|
|
173
|
+
* Wait for the worker to be ready
|
|
174
|
+
*/
|
|
175
|
+
ready(): Promise<void>;
|
|
176
|
+
/**
|
|
177
|
+
* Terminate the worker
|
|
178
|
+
*/
|
|
179
|
+
terminate(): void;
|
|
180
|
+
private call;
|
|
181
|
+
readFile(path: string, options?: ReadFileOptions): Promise<string | Uint8Array>;
|
|
182
|
+
writeFile(path: string, data: string | Uint8Array, options?: WriteFileOptions): Promise<void>;
|
|
183
|
+
readFileBatch(paths: string[]): Promise<BatchReadResult[]>;
|
|
184
|
+
writeFileBatch(entries: BatchWriteEntry[]): Promise<void>;
|
|
185
|
+
appendFile(path: string, data: string | Uint8Array, options?: WriteFileOptions): Promise<void>;
|
|
186
|
+
copyFile(src: string, dest: string, mode?: number): Promise<void>;
|
|
187
|
+
unlink(path: string): Promise<void>;
|
|
188
|
+
truncate(path: string, len?: number): Promise<void>;
|
|
189
|
+
mkdir(path: string): Promise<void>;
|
|
190
|
+
rmdir(path: string): Promise<void>;
|
|
191
|
+
readdir(path: string, options?: ReaddirOptions): Promise<string[] | Dirent[]>;
|
|
192
|
+
cp(src: string, dest: string, options?: CpOptions): Promise<void>;
|
|
193
|
+
rm(path: string, options?: RmOptions): Promise<void>;
|
|
194
|
+
stat(path: string): Promise<Stats>;
|
|
195
|
+
lstat(path: string): Promise<Stats>;
|
|
196
|
+
private deserializeStats;
|
|
197
|
+
exists(path: string): Promise<boolean>;
|
|
198
|
+
access(path: string, mode?: number): Promise<void>;
|
|
199
|
+
statfs(path?: string): Promise<StatFs>;
|
|
200
|
+
du(path: string): Promise<DiskUsage>;
|
|
201
|
+
symlink(target: string, path: string): Promise<void>;
|
|
202
|
+
readlink(path: string): Promise<string>;
|
|
203
|
+
symlinkBatch(links: SymlinkDefinition[]): Promise<void>;
|
|
204
|
+
realpath(path: string): Promise<string>;
|
|
205
|
+
rename(oldPath: string, newPath: string): Promise<void>;
|
|
206
|
+
mkdtemp(prefix: string): Promise<string>;
|
|
207
|
+
chmod(path: string, mode: number): Promise<void>;
|
|
208
|
+
chown(path: string, uid: number, gid: number): Promise<void>;
|
|
209
|
+
utimes(path: string, atime: Date | number, mtime: Date | number): Promise<void>;
|
|
210
|
+
lutimes(path: string, atime: Date | number, mtime: Date | number): Promise<void>;
|
|
211
|
+
/**
|
|
212
|
+
* Reset internal caches to free memory
|
|
213
|
+
* Useful for long-running benchmarks or after bulk operations
|
|
214
|
+
*/
|
|
215
|
+
resetCache(): Promise<void>;
|
|
216
|
+
/**
|
|
217
|
+
* Force full garbage collection by reinitializing the OPFS instance in the worker
|
|
218
|
+
* This completely releases all handles and caches, preventing memory leaks in long-running operations
|
|
219
|
+
* More aggressive than resetCache() - use when resetCache() isn't sufficient
|
|
220
|
+
*/
|
|
221
|
+
gc(): Promise<void>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export { OPFSWorker, type OPFSWorkerOptions };
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var constants = {
|
|
3
|
+
// File access modes
|
|
4
|
+
F_OK: 0,
|
|
5
|
+
R_OK: 4,
|
|
6
|
+
W_OK: 2,
|
|
7
|
+
X_OK: 1,
|
|
8
|
+
// Copy file flags
|
|
9
|
+
COPYFILE_EXCL: 1,
|
|
10
|
+
COPYFILE_FICLONE: 2,
|
|
11
|
+
COPYFILE_FICLONE_FORCE: 4,
|
|
12
|
+
// File open flags
|
|
13
|
+
O_RDONLY: 0,
|
|
14
|
+
O_WRONLY: 1,
|
|
15
|
+
O_RDWR: 2,
|
|
16
|
+
O_CREAT: 64,
|
|
17
|
+
O_EXCL: 128,
|
|
18
|
+
O_TRUNC: 512,
|
|
19
|
+
O_APPEND: 1024,
|
|
20
|
+
// File type masks
|
|
21
|
+
S_IFMT: 61440,
|
|
22
|
+
S_IFREG: 32768,
|
|
23
|
+
S_IFDIR: 16384,
|
|
24
|
+
S_IFLNK: 40960
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/errors.ts
|
|
28
|
+
var FSError = class extends Error {
|
|
29
|
+
code;
|
|
30
|
+
syscall;
|
|
31
|
+
path;
|
|
32
|
+
original;
|
|
33
|
+
constructor(message, code, options) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "FSError";
|
|
36
|
+
this.code = code;
|
|
37
|
+
this.syscall = options?.syscall;
|
|
38
|
+
this.path = options?.path;
|
|
39
|
+
this.original = options?.original;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/opfs-worker-proxy.ts
|
|
44
|
+
var OPFSWorker = class {
|
|
45
|
+
worker = null;
|
|
46
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
47
|
+
nextId = 1;
|
|
48
|
+
readyPromise;
|
|
49
|
+
readyResolve;
|
|
50
|
+
/** File system constants */
|
|
51
|
+
constants = constants;
|
|
52
|
+
constructor(options = {}) {
|
|
53
|
+
this.readyPromise = new Promise((resolve) => {
|
|
54
|
+
this.readyResolve = resolve;
|
|
55
|
+
});
|
|
56
|
+
this.initWorker(options);
|
|
57
|
+
}
|
|
58
|
+
initWorker(options) {
|
|
59
|
+
const { workerUrl, workerOptions = { type: "module" } } = options;
|
|
60
|
+
if (workerUrl) {
|
|
61
|
+
this.worker = new Worker(workerUrl, workerOptions);
|
|
62
|
+
} else {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'OPFSWorker requires a workerUrl option pointing to the worker script. Example: new OPFSWorker({ workerUrl: new URL("./opfs-worker.js", import.meta.url) })'
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
this.worker.onmessage = (event) => {
|
|
68
|
+
const { id, type, result, error } = event.data;
|
|
69
|
+
if (type === "ready") {
|
|
70
|
+
this.readyResolve();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (id !== void 0) {
|
|
74
|
+
const pending = this.pendingRequests.get(id);
|
|
75
|
+
if (pending) {
|
|
76
|
+
this.pendingRequests.delete(id);
|
|
77
|
+
if (error) {
|
|
78
|
+
const fsError = new FSError(error.message, error.code || "UNKNOWN");
|
|
79
|
+
pending.reject(fsError);
|
|
80
|
+
} else {
|
|
81
|
+
pending.resolve(result);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
this.worker.onerror = (event) => {
|
|
87
|
+
console.error("[OPFSWorker] Worker error:", event);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Wait for the worker to be ready
|
|
92
|
+
*/
|
|
93
|
+
async ready() {
|
|
94
|
+
return this.readyPromise;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Terminate the worker
|
|
98
|
+
*/
|
|
99
|
+
terminate() {
|
|
100
|
+
if (this.worker) {
|
|
101
|
+
this.worker.terminate();
|
|
102
|
+
this.worker = null;
|
|
103
|
+
for (const [, pending] of this.pendingRequests) {
|
|
104
|
+
pending.reject(new Error("Worker terminated"));
|
|
105
|
+
}
|
|
106
|
+
this.pendingRequests.clear();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
call(method, args, transfer) {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
if (!this.worker) {
|
|
112
|
+
reject(new Error("Worker not initialized or terminated"));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const id = this.nextId++;
|
|
116
|
+
this.pendingRequests.set(id, {
|
|
117
|
+
resolve,
|
|
118
|
+
reject
|
|
119
|
+
});
|
|
120
|
+
const message = { id, method, args };
|
|
121
|
+
if (transfer && transfer.length > 0) {
|
|
122
|
+
this.worker.postMessage(message, transfer);
|
|
123
|
+
} else {
|
|
124
|
+
this.worker.postMessage(message);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// File operations
|
|
129
|
+
async readFile(path, options) {
|
|
130
|
+
const result = await this.call("readFile", [path, options]);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
async writeFile(path, data, options) {
|
|
134
|
+
await this.call("writeFile", [path, data, options]);
|
|
135
|
+
}
|
|
136
|
+
async readFileBatch(paths) {
|
|
137
|
+
return this.call("readFileBatch", [paths]);
|
|
138
|
+
}
|
|
139
|
+
async writeFileBatch(entries) {
|
|
140
|
+
await this.call("writeFileBatch", [entries]);
|
|
141
|
+
}
|
|
142
|
+
async appendFile(path, data, options) {
|
|
143
|
+
await this.call("appendFile", [path, data, options]);
|
|
144
|
+
}
|
|
145
|
+
async copyFile(src, dest, mode) {
|
|
146
|
+
await this.call("copyFile", [src, dest, mode]);
|
|
147
|
+
}
|
|
148
|
+
async unlink(path) {
|
|
149
|
+
await this.call("unlink", [path]);
|
|
150
|
+
}
|
|
151
|
+
async truncate(path, len) {
|
|
152
|
+
await this.call("truncate", [path, len]);
|
|
153
|
+
}
|
|
154
|
+
// Directory operations
|
|
155
|
+
async mkdir(path) {
|
|
156
|
+
await this.call("mkdir", [path]);
|
|
157
|
+
}
|
|
158
|
+
async rmdir(path) {
|
|
159
|
+
await this.call("rmdir", [path]);
|
|
160
|
+
}
|
|
161
|
+
async readdir(path, options) {
|
|
162
|
+
const result = await this.call("readdir", [path, options]);
|
|
163
|
+
if (options?.withFileTypes && Array.isArray(result)) {
|
|
164
|
+
return result.map((item) => {
|
|
165
|
+
if (typeof item === "object" && "name" in item) {
|
|
166
|
+
const entry = item;
|
|
167
|
+
return {
|
|
168
|
+
name: entry.name,
|
|
169
|
+
isFile: () => entry._isFile ?? false,
|
|
170
|
+
isDirectory: () => entry._isDir ?? false,
|
|
171
|
+
isSymbolicLink: () => entry._isSymlink ?? false
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return item;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
async cp(src, dest, options) {
|
|
180
|
+
await this.call("cp", [src, dest, options]);
|
|
181
|
+
}
|
|
182
|
+
async rm(path, options) {
|
|
183
|
+
await this.call("rm", [path, options]);
|
|
184
|
+
}
|
|
185
|
+
// Stat operations
|
|
186
|
+
async stat(path) {
|
|
187
|
+
const result = await this.call("stat", [path]);
|
|
188
|
+
return this.deserializeStats(result);
|
|
189
|
+
}
|
|
190
|
+
async lstat(path) {
|
|
191
|
+
const result = await this.call("lstat", [path]);
|
|
192
|
+
return this.deserializeStats(result);
|
|
193
|
+
}
|
|
194
|
+
deserializeStats(data) {
|
|
195
|
+
const ctime = new Date(data.ctime);
|
|
196
|
+
const mtime = new Date(data.mtime);
|
|
197
|
+
return {
|
|
198
|
+
type: data.type,
|
|
199
|
+
size: data.size,
|
|
200
|
+
mode: data.mode,
|
|
201
|
+
ctime,
|
|
202
|
+
ctimeMs: data.ctimeMs,
|
|
203
|
+
mtime,
|
|
204
|
+
mtimeMs: data.mtimeMs,
|
|
205
|
+
target: data.target,
|
|
206
|
+
isFile: () => data.type === "file",
|
|
207
|
+
isDirectory: () => data.type === "dir",
|
|
208
|
+
isSymbolicLink: () => data.type === "symlink"
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async exists(path) {
|
|
212
|
+
return this.call("exists", [path]);
|
|
213
|
+
}
|
|
214
|
+
async access(path, mode) {
|
|
215
|
+
await this.call("access", [path, mode]);
|
|
216
|
+
}
|
|
217
|
+
async statfs(path) {
|
|
218
|
+
return this.call("statfs", [path]);
|
|
219
|
+
}
|
|
220
|
+
async du(path) {
|
|
221
|
+
return this.call("du", [path]);
|
|
222
|
+
}
|
|
223
|
+
// Symlink operations
|
|
224
|
+
async symlink(target, path) {
|
|
225
|
+
await this.call("symlink", [target, path]);
|
|
226
|
+
}
|
|
227
|
+
async readlink(path) {
|
|
228
|
+
return this.call("readlink", [path]);
|
|
229
|
+
}
|
|
230
|
+
async symlinkBatch(links) {
|
|
231
|
+
await this.call("symlinkBatch", [links]);
|
|
232
|
+
}
|
|
233
|
+
async realpath(path) {
|
|
234
|
+
return this.call("realpath", [path]);
|
|
235
|
+
}
|
|
236
|
+
// Other operations
|
|
237
|
+
async rename(oldPath, newPath) {
|
|
238
|
+
await this.call("rename", [oldPath, newPath]);
|
|
239
|
+
}
|
|
240
|
+
async mkdtemp(prefix) {
|
|
241
|
+
return this.call("mkdtemp", [prefix]);
|
|
242
|
+
}
|
|
243
|
+
async chmod(path, mode) {
|
|
244
|
+
await this.call("chmod", [path, mode]);
|
|
245
|
+
}
|
|
246
|
+
async chown(path, uid, gid) {
|
|
247
|
+
await this.call("chown", [path, uid, gid]);
|
|
248
|
+
}
|
|
249
|
+
async utimes(path, atime, mtime) {
|
|
250
|
+
await this.call("utimes", [path, atime, mtime]);
|
|
251
|
+
}
|
|
252
|
+
async lutimes(path, atime, mtime) {
|
|
253
|
+
await this.call("lutimes", [path, atime, mtime]);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Reset internal caches to free memory
|
|
257
|
+
* Useful for long-running benchmarks or after bulk operations
|
|
258
|
+
*/
|
|
259
|
+
async resetCache() {
|
|
260
|
+
await this.call("resetCache", []);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Force full garbage collection by reinitializing the OPFS instance in the worker
|
|
264
|
+
* This completely releases all handles and caches, preventing memory leaks in long-running operations
|
|
265
|
+
* More aggressive than resetCache() - use when resetCache() isn't sufficient
|
|
266
|
+
*/
|
|
267
|
+
async gc() {
|
|
268
|
+
await this.call("gc", []);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export { OPFSWorker };
|
|
273
|
+
//# sourceMappingURL=opfs-worker-proxy.js.map
|
|
274
|
+
//# sourceMappingURL=opfs-worker-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/opfs-worker-proxy.ts"],"names":[],"mappings":"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","file":"opfs-worker-proxy.js","sourcesContent":["import type { FSConstants } from './types.js'\n\n/**\n * File system constants matching Node.js fs.constants\n */\nexport const constants: FSConstants = {\n // File access modes\n F_OK: 0,\n R_OK: 4,\n W_OK: 2,\n X_OK: 1,\n\n // Copy file flags\n COPYFILE_EXCL: 1,\n COPYFILE_FICLONE: 2,\n COPYFILE_FICLONE_FORCE: 4,\n\n // File open flags\n O_RDONLY: 0,\n O_WRONLY: 1,\n O_RDWR: 2,\n O_CREAT: 64,\n O_EXCL: 128,\n O_TRUNC: 512,\n O_APPEND: 1024,\n\n // File type masks\n S_IFMT: 61440,\n S_IFREG: 32768,\n S_IFDIR: 16384,\n S_IFLNK: 40960\n}\n\n/**\n * Convert numeric flags to string representation\n */\nexport function flagsToString(flags: number | string): string {\n if (typeof flags === 'string') return flags\n\n const map: Record<number, string> = {\n [constants.O_RDONLY]: 'r',\n [constants.O_WRONLY]: 'w',\n [constants.O_RDWR]: 'r+',\n [constants.O_CREAT | constants.O_WRONLY]: 'w',\n [constants.O_CREAT | constants.O_WRONLY | constants.O_TRUNC]: 'w',\n [constants.O_CREAT | constants.O_RDWR]: 'w+',\n [constants.O_APPEND | constants.O_WRONLY]: 'a',\n [constants.O_APPEND | constants.O_RDWR]: 'a+'\n }\n\n return map[flags] || 'r'\n}\n","/**\n * Custom error class for filesystem errors\n */\nexport class FSError extends Error {\n code: string\n syscall?: string\n path?: string\n original?: Error\n\n constructor(message: string, code: string, options?: { syscall?: string; path?: string; original?: Error }) {\n super(message)\n this.name = 'FSError'\n this.code = code\n this.syscall = options?.syscall\n this.path = options?.path\n this.original = options?.original\n }\n}\n\n/**\n * Create ENOENT (No such file or directory) error\n */\nexport function createENOENT(path: string): FSError {\n return new FSError(`ENOENT: No such file or directory, '${path}'`, 'ENOENT', { path })\n}\n\n/**\n * Create EEXIST (File exists) error\n */\nexport function createEEXIST(path: string, operation?: string): FSError {\n const message = operation\n ? `EEXIST: file already exists, ${operation} '${path}'`\n : `EEXIST: File exists, '${path}'`\n return new FSError(message, 'EEXIST', { path })\n}\n\n/**\n * Create EACCES (Permission denied) error\n */\nexport function createEACCES(path: string, syscall?: string): FSError {\n return new FSError(`EACCES: permission denied, access '${path}'`, 'EACCES', { syscall, path })\n}\n\n/**\n * Create EISDIR (Is a directory) error\n */\nexport function createEISDIR(path: string, operation = 'operation'): FSError {\n return new FSError(`EISDIR: illegal operation on a directory, ${operation} '${path}'`, 'EISDIR', { path })\n}\n\n/**\n * Create ELOOP (Too many symbolic links) error\n */\nexport function createELOOP(path: string): FSError {\n return new FSError(`ELOOP: Too many symbolic links, '${path}'`, 'ELOOP', { path })\n}\n\n/**\n * Create EINVAL (Invalid argument) error\n */\nexport function createEINVAL(path: string): FSError {\n return new FSError(`EINVAL: Invalid argument, '${path}'`, 'EINVAL', { path })\n}\n\n/**\n * Create ECORRUPTED (Data corruption detected) error\n */\nexport function createECORRUPTED(path: string): FSError {\n return new FSError(`ECORRUPTED: Pack file integrity check failed, '${path}'`, 'ECORRUPTED', { path })\n}\n\n/**\n * Wrap an error with a standard code if it doesn't have one\n */\nexport function wrapError(err: unknown): FSError {\n if (err instanceof FSError) return err\n\n const error = err as Error\n if (typeof (error as FSError).code === 'string') {\n const fsErr = new FSError(error.message, (error as FSError).code)\n fsErr.original = error\n return fsErr\n }\n\n const wrapped = new FSError(error.message || 'Unknown error', 'UNKNOWN')\n wrapped.original = error\n return wrapped\n}\n","/**\n * OPFS Worker Proxy\n * Main thread class that communicates with an OPFS worker\n *\n * This allows non-blocking OPFS operations on the main thread\n * while the actual work happens in a dedicated Web Worker\n */\n\nimport type {\n ReadFileOptions,\n WriteFileOptions,\n BatchWriteEntry,\n BatchReadResult,\n ReaddirOptions,\n Dirent,\n Stats,\n StatFs,\n RmOptions,\n CpOptions,\n DiskUsage,\n SymlinkDefinition\n} from './types.js'\nimport { constants } from './constants.js'\nimport { FSError } from './errors.js'\n\ninterface PendingRequest {\n resolve: (value: unknown) => void\n reject: (error: Error) => void\n}\n\ninterface WorkerResponse {\n id?: number\n type?: string\n result?: unknown\n error?: { message: string; code?: string }\n}\n\nexport interface OPFSWorkerOptions {\n /** URL to the worker script (default: auto-detect) */\n workerUrl?: string | URL\n /** Worker initialization options */\n workerOptions?: WorkerOptions\n}\n\n/**\n * OPFS Worker Proxy - runs OPFS operations in a Web Worker\n *\n * Benefits:\n * - Non-blocking main thread\n * - Uses sync access handles (faster) in the worker\n * - Compatible with libraries that reuse buffers (e.g., isomorphic-git)\n */\nexport class OPFSWorker {\n private worker: Worker | null = null\n private pendingRequests = new Map<number, PendingRequest>()\n private nextId = 1\n private readyPromise: Promise<void>\n private readyResolve!: () => void\n\n /** File system constants */\n public readonly constants = constants\n\n constructor(options: OPFSWorkerOptions = {}) {\n this.readyPromise = new Promise((resolve) => {\n this.readyResolve = resolve\n })\n\n this.initWorker(options)\n }\n\n private initWorker(options: OPFSWorkerOptions): void {\n const { workerUrl, workerOptions = { type: 'module' } } = options\n\n if (workerUrl) {\n this.worker = new Worker(workerUrl, workerOptions)\n } else {\n // Try to create worker from the bundled script\n // Users should provide workerUrl in production\n throw new Error(\n 'OPFSWorker requires a workerUrl option pointing to the worker script. ' +\n 'Example: new OPFSWorker({ workerUrl: new URL(\"./opfs-worker.js\", import.meta.url) })'\n )\n }\n\n this.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n const { id, type, result, error } = event.data\n\n // Handle ready signal\n if (type === 'ready') {\n this.readyResolve()\n return\n }\n\n // Handle response to a request\n if (id !== undefined) {\n const pending = this.pendingRequests.get(id)\n if (pending) {\n this.pendingRequests.delete(id)\n if (error) {\n const fsError = new FSError(error.message, error.code || 'UNKNOWN')\n pending.reject(fsError)\n } else {\n pending.resolve(result)\n }\n }\n }\n }\n\n this.worker.onerror = (event) => {\n console.error('[OPFSWorker] Worker error:', event)\n }\n }\n\n /**\n * Wait for the worker to be ready\n */\n async ready(): Promise<void> {\n return this.readyPromise\n }\n\n /**\n * Terminate the worker\n */\n terminate(): void {\n if (this.worker) {\n this.worker.terminate()\n this.worker = null\n\n // Reject all pending requests\n for (const [, pending] of this.pendingRequests) {\n pending.reject(new Error('Worker terminated'))\n }\n this.pendingRequests.clear()\n }\n }\n\n private call<T>(method: string, args: unknown[], transfer?: Transferable[]): Promise<T> {\n return new Promise((resolve, reject) => {\n if (!this.worker) {\n reject(new Error('Worker not initialized or terminated'))\n return\n }\n\n const id = this.nextId++\n this.pendingRequests.set(id, {\n resolve: resolve as (value: unknown) => void,\n reject\n })\n\n const message = { id, method, args }\n if (transfer && transfer.length > 0) {\n this.worker.postMessage(message, transfer)\n } else {\n this.worker.postMessage(message)\n }\n })\n }\n\n // File operations\n\n async readFile(path: string, options?: ReadFileOptions): Promise<string | Uint8Array> {\n const result = await this.call<string | Uint8Array>('readFile', [path, options])\n return result\n }\n\n async writeFile(path: string, data: string | Uint8Array, options?: WriteFileOptions): Promise<void> {\n // Note: We don't use Transferables here because the caller may reuse the buffer\n // (e.g., isomorphic-git reuses buffers). Structured cloning copies the data.\n await this.call<void>('writeFile', [path, data, options])\n }\n\n async readFileBatch(paths: string[]): Promise<BatchReadResult[]> {\n return this.call<BatchReadResult[]>('readFileBatch', [paths])\n }\n\n async writeFileBatch(entries: BatchWriteEntry[]): Promise<void> {\n // Note: We don't use Transferables here because the caller may reuse the buffers\n await this.call<void>('writeFileBatch', [entries])\n }\n\n async appendFile(path: string, data: string | Uint8Array, options?: WriteFileOptions): Promise<void> {\n // Note: We don't use Transferables here because the caller may reuse the buffer\n await this.call<void>('appendFile', [path, data, options])\n }\n\n async copyFile(src: string, dest: string, mode?: number): Promise<void> {\n await this.call<void>('copyFile', [src, dest, mode])\n }\n\n async unlink(path: string): Promise<void> {\n await this.call<void>('unlink', [path])\n }\n\n async truncate(path: string, len?: number): Promise<void> {\n await this.call<void>('truncate', [path, len])\n }\n\n // Directory operations\n\n async mkdir(path: string): Promise<void> {\n await this.call<void>('mkdir', [path])\n }\n\n async rmdir(path: string): Promise<void> {\n await this.call<void>('rmdir', [path])\n }\n\n async readdir(path: string, options?: ReaddirOptions): Promise<string[] | Dirent[]> {\n const result = await this.call<string[] | { name: string }[]>('readdir', [path, options])\n\n // Reconstruct Dirent objects with methods\n if (options?.withFileTypes && Array.isArray(result)) {\n return result.map((item) => {\n if (typeof item === 'object' && 'name' in item) {\n const entry = item as { name: string; _isFile?: boolean; _isDir?: boolean; _isSymlink?: boolean }\n return {\n name: entry.name,\n isFile: () => entry._isFile ?? false,\n isDirectory: () => entry._isDir ?? false,\n isSymbolicLink: () => entry._isSymlink ?? false\n }\n }\n return item as unknown as Dirent\n })\n }\n\n return result as string[]\n }\n\n async cp(src: string, dest: string, options?: CpOptions): Promise<void> {\n await this.call<void>('cp', [src, dest, options])\n }\n\n async rm(path: string, options?: RmOptions): Promise<void> {\n await this.call<void>('rm', [path, options])\n }\n\n // Stat operations\n\n async stat(path: string): Promise<Stats> {\n const result = await this.call<{\n type: string\n size: number\n mode: number\n ctime: string\n ctimeMs: number\n mtime: string\n mtimeMs: number\n target?: string\n }>('stat', [path])\n\n return this.deserializeStats(result)\n }\n\n async lstat(path: string): Promise<Stats> {\n const result = await this.call<{\n type: string\n size: number\n mode: number\n ctime: string\n ctimeMs: number\n mtime: string\n mtimeMs: number\n target?: string\n }>('lstat', [path])\n\n return this.deserializeStats(result)\n }\n\n private deserializeStats(data: {\n type: string\n size: number\n mode: number\n ctime: string\n ctimeMs: number\n mtime: string\n mtimeMs: number\n target?: string\n }): Stats {\n const ctime = new Date(data.ctime)\n const mtime = new Date(data.mtime)\n\n return {\n type: data.type as 'file' | 'dir' | 'symlink',\n size: data.size,\n mode: data.mode,\n ctime,\n ctimeMs: data.ctimeMs,\n mtime,\n mtimeMs: data.mtimeMs,\n target: data.target,\n isFile: () => data.type === 'file',\n isDirectory: () => data.type === 'dir',\n isSymbolicLink: () => data.type === 'symlink'\n }\n }\n\n async exists(path: string): Promise<boolean> {\n return this.call<boolean>('exists', [path])\n }\n\n async access(path: string, mode?: number): Promise<void> {\n await this.call<void>('access', [path, mode])\n }\n\n async statfs(path?: string): Promise<StatFs> {\n return this.call<StatFs>('statfs', [path])\n }\n\n async du(path: string): Promise<DiskUsage> {\n return this.call<DiskUsage>('du', [path])\n }\n\n // Symlink operations\n\n async symlink(target: string, path: string): Promise<void> {\n await this.call<void>('symlink', [target, path])\n }\n\n async readlink(path: string): Promise<string> {\n return this.call<string>('readlink', [path])\n }\n\n async symlinkBatch(links: SymlinkDefinition[]): Promise<void> {\n await this.call<void>('symlinkBatch', [links])\n }\n\n async realpath(path: string): Promise<string> {\n return this.call<string>('realpath', [path])\n }\n\n // Other operations\n\n async rename(oldPath: string, newPath: string): Promise<void> {\n await this.call<void>('rename', [oldPath, newPath])\n }\n\n async mkdtemp(prefix: string): Promise<string> {\n return this.call<string>('mkdtemp', [prefix])\n }\n\n async chmod(path: string, mode: number): Promise<void> {\n await this.call<void>('chmod', [path, mode])\n }\n\n async chown(path: string, uid: number, gid: number): Promise<void> {\n await this.call<void>('chown', [path, uid, gid])\n }\n\n async utimes(path: string, atime: Date | number, mtime: Date | number): Promise<void> {\n await this.call<void>('utimes', [path, atime, mtime])\n }\n\n async lutimes(path: string, atime: Date | number, mtime: Date | number): Promise<void> {\n await this.call<void>('lutimes', [path, atime, mtime])\n }\n\n /**\n * Reset internal caches to free memory\n * Useful for long-running benchmarks or after bulk operations\n */\n async resetCache(): Promise<void> {\n await this.call<void>('resetCache', [])\n }\n\n /**\n * Force full garbage collection by reinitializing the OPFS instance in the worker\n * This completely releases all handles and caches, preventing memory leaks in long-running operations\n * More aggressive than resetCache() - use when resetCache() isn't sufficient\n */\n async gc(): Promise<void> {\n await this.call<void>('gc', [])\n }\n}\n"]}
|