@componentor/fs 2.0.12 → 3.0.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.
@@ -0,0 +1,1547 @@
1
+ // src/vfs/layout.ts
2
+ var VFS_MAGIC = 1447449377;
3
+ var VFS_VERSION = 1;
4
+ var DEFAULT_BLOCK_SIZE = 4096;
5
+ var DEFAULT_INODE_COUNT = 1e4;
6
+ var INODE_SIZE = 64;
7
+ var SUPERBLOCK = {
8
+ SIZE: 64,
9
+ MAGIC: 0,
10
+ // uint32 - 0x56465321
11
+ VERSION: 4,
12
+ // uint32
13
+ INODE_COUNT: 8,
14
+ // uint32 - total inodes allocated
15
+ BLOCK_SIZE: 12,
16
+ // uint32 - data block size (default 4096)
17
+ TOTAL_BLOCKS: 16,
18
+ // uint32 - total data blocks
19
+ FREE_BLOCKS: 20,
20
+ // uint32 - available data blocks
21
+ INODE_OFFSET: 24,
22
+ // float64 - byte offset to inode table
23
+ PATH_OFFSET: 32,
24
+ // float64 - byte offset to path table
25
+ DATA_OFFSET: 40,
26
+ // float64 - byte offset to data region
27
+ BITMAP_OFFSET: 48,
28
+ // float64 - byte offset to free block bitmap
29
+ PATH_USED: 56,
30
+ // uint32 - bytes used in path table
31
+ RESERVED: 60
32
+ // uint32
33
+ };
34
+ var INODE = {
35
+ TYPE: 0,
36
+ // uint8 - 0=free, 1=file, 2=directory, 3=symlink
37
+ FLAGS: 1,
38
+ // uint8[3] - reserved
39
+ PATH_OFFSET: 4,
40
+ // uint32 - byte offset into path table
41
+ PATH_LENGTH: 8,
42
+ // uint16 - length of path string
43
+ RESERVED_10: 10,
44
+ // uint16
45
+ MODE: 12,
46
+ // uint32 - permissions (e.g. 0o100644)
47
+ SIZE: 16,
48
+ // float64 - file content size in bytes (using f64 for >4GB)
49
+ FIRST_BLOCK: 24,
50
+ // uint32 - index of first data block
51
+ BLOCK_COUNT: 28,
52
+ // uint32 - number of contiguous data blocks
53
+ MTIME: 32,
54
+ // float64 - last modification time (ms since epoch)
55
+ CTIME: 40,
56
+ // float64 - creation/change time (ms since epoch)
57
+ ATIME: 48,
58
+ // float64 - last access time (ms since epoch)
59
+ UID: 56,
60
+ // uint32 - owner
61
+ GID: 60
62
+ // uint32 - group
63
+ };
64
+ var INODE_TYPE = {
65
+ FREE: 0,
66
+ FILE: 1,
67
+ DIRECTORY: 2,
68
+ SYMLINK: 3
69
+ };
70
+ var DEFAULT_FILE_MODE = 33188;
71
+ var DEFAULT_DIR_MODE = 16877;
72
+ var DEFAULT_SYMLINK_MODE = 41471;
73
+ var DEFAULT_UMASK = 18;
74
+ var S_IFMT = 61440;
75
+ var MAX_SYMLINK_DEPTH = 40;
76
+ var INITIAL_PATH_TABLE_SIZE = 256 * 1024;
77
+ var INITIAL_DATA_BLOCKS = 1024;
78
+ function calculateLayout(inodeCount = DEFAULT_INODE_COUNT, blockSize = DEFAULT_BLOCK_SIZE, totalBlocks = INITIAL_DATA_BLOCKS) {
79
+ const inodeTableOffset = SUPERBLOCK.SIZE;
80
+ const inodeTableSize = inodeCount * INODE_SIZE;
81
+ const pathTableOffset = inodeTableOffset + inodeTableSize;
82
+ const pathTableSize = INITIAL_PATH_TABLE_SIZE;
83
+ const bitmapOffset = pathTableOffset + pathTableSize;
84
+ const bitmapSize = Math.ceil(totalBlocks / 8);
85
+ const dataOffset = Math.ceil((bitmapOffset + bitmapSize) / blockSize) * blockSize;
86
+ const totalSize = dataOffset + totalBlocks * blockSize;
87
+ return {
88
+ inodeTableOffset,
89
+ inodeTableSize,
90
+ pathTableOffset,
91
+ pathTableSize,
92
+ bitmapOffset,
93
+ bitmapSize,
94
+ dataOffset,
95
+ totalSize,
96
+ totalBlocks
97
+ };
98
+ }
99
+
100
+ // src/errors.ts
101
+ var CODE_TO_STATUS = {
102
+ OK: 0,
103
+ ENOENT: 1,
104
+ EEXIST: 2,
105
+ EISDIR: 3,
106
+ ENOTDIR: 4,
107
+ ENOTEMPTY: 5,
108
+ EACCES: 6,
109
+ EINVAL: 7,
110
+ EBADF: 8,
111
+ ELOOP: 9,
112
+ ENOSPC: 10
113
+ };
114
+
115
+ // src/vfs/engine.ts
116
+ var encoder = new TextEncoder();
117
+ var decoder = new TextDecoder();
118
+ var VFSEngine = class {
119
+ handle;
120
+ pathIndex = /* @__PURE__ */ new Map();
121
+ // path → inode index
122
+ inodeCount = 0;
123
+ blockSize = DEFAULT_BLOCK_SIZE;
124
+ totalBlocks = 0;
125
+ freeBlocks = 0;
126
+ inodeTableOffset = 0;
127
+ pathTableOffset = 0;
128
+ pathTableUsed = 0;
129
+ pathTableSize = 0;
130
+ bitmapOffset = 0;
131
+ dataOffset = 0;
132
+ umask = DEFAULT_UMASK;
133
+ processUid = 0;
134
+ processGid = 0;
135
+ strictPermissions = false;
136
+ debug = false;
137
+ // File descriptor table
138
+ fdTable = /* @__PURE__ */ new Map();
139
+ nextFd = 3;
140
+ // 0=stdin, 1=stdout, 2=stderr reserved
141
+ // Reusable buffers to avoid allocations
142
+ inodeBuf = new Uint8Array(INODE_SIZE);
143
+ inodeView = new DataView(this.inodeBuf.buffer);
144
+ // In-memory inode cache — eliminates disk reads for hot inodes
145
+ inodeCache = /* @__PURE__ */ new Map();
146
+ superblockBuf = new Uint8Array(SUPERBLOCK.SIZE);
147
+ superblockView = new DataView(this.superblockBuf.buffer);
148
+ // In-memory bitmap cache — eliminates bitmap reads from OPFS
149
+ bitmap = null;
150
+ bitmapDirtyLo = Infinity;
151
+ // lowest dirty byte index
152
+ bitmapDirtyHi = -1;
153
+ // highest dirty byte index (inclusive)
154
+ superblockDirty = false;
155
+ // Free inode hint — skip O(n) scan
156
+ freeInodeHint = 0;
157
+ init(handle, opts) {
158
+ this.handle = handle;
159
+ this.processUid = opts?.uid ?? 0;
160
+ this.processGid = opts?.gid ?? 0;
161
+ this.umask = opts?.umask ?? DEFAULT_UMASK;
162
+ this.strictPermissions = opts?.strictPermissions ?? false;
163
+ this.debug = opts?.debug ?? false;
164
+ const size = handle.getSize();
165
+ if (size === 0) {
166
+ this.format();
167
+ } else {
168
+ this.mount();
169
+ }
170
+ }
171
+ /** Format a fresh VFS */
172
+ format() {
173
+ const layout = calculateLayout(DEFAULT_INODE_COUNT, DEFAULT_BLOCK_SIZE, INITIAL_DATA_BLOCKS);
174
+ this.inodeCount = DEFAULT_INODE_COUNT;
175
+ this.blockSize = DEFAULT_BLOCK_SIZE;
176
+ this.totalBlocks = layout.totalBlocks;
177
+ this.freeBlocks = layout.totalBlocks;
178
+ this.inodeTableOffset = layout.inodeTableOffset;
179
+ this.pathTableOffset = layout.pathTableOffset;
180
+ this.pathTableSize = layout.pathTableSize;
181
+ this.pathTableUsed = 0;
182
+ this.bitmapOffset = layout.bitmapOffset;
183
+ this.dataOffset = layout.dataOffset;
184
+ this.handle.truncate(layout.totalSize);
185
+ this.writeSuperblock();
186
+ const zeroBuf = new Uint8Array(layout.inodeTableSize);
187
+ this.handle.write(zeroBuf, { at: this.inodeTableOffset });
188
+ this.bitmap = new Uint8Array(layout.bitmapSize);
189
+ this.handle.write(this.bitmap, { at: this.bitmapOffset });
190
+ this.createInode("/", INODE_TYPE.DIRECTORY, DEFAULT_DIR_MODE, 0);
191
+ this.handle.flush();
192
+ }
193
+ /** Mount an existing VFS from disk */
194
+ mount() {
195
+ this.handle.read(this.superblockBuf, { at: 0 });
196
+ const v = this.superblockView;
197
+ const magic = v.getUint32(SUPERBLOCK.MAGIC, true);
198
+ if (magic !== VFS_MAGIC) {
199
+ throw new Error(`Invalid VFS: bad magic 0x${magic.toString(16)}`);
200
+ }
201
+ this.inodeCount = v.getUint32(SUPERBLOCK.INODE_COUNT, true);
202
+ this.blockSize = v.getUint32(SUPERBLOCK.BLOCK_SIZE, true);
203
+ this.totalBlocks = v.getUint32(SUPERBLOCK.TOTAL_BLOCKS, true);
204
+ this.freeBlocks = v.getUint32(SUPERBLOCK.FREE_BLOCKS, true);
205
+ this.inodeTableOffset = v.getFloat64(SUPERBLOCK.INODE_OFFSET, true);
206
+ this.pathTableOffset = v.getFloat64(SUPERBLOCK.PATH_OFFSET, true);
207
+ this.dataOffset = v.getFloat64(SUPERBLOCK.DATA_OFFSET, true);
208
+ this.bitmapOffset = v.getFloat64(SUPERBLOCK.BITMAP_OFFSET, true);
209
+ this.pathTableUsed = v.getUint32(SUPERBLOCK.PATH_USED, true);
210
+ this.pathTableSize = this.bitmapOffset - this.pathTableOffset;
211
+ const bitmapSize = Math.ceil(this.totalBlocks / 8);
212
+ this.bitmap = new Uint8Array(bitmapSize);
213
+ this.handle.read(this.bitmap, { at: this.bitmapOffset });
214
+ this.rebuildIndex();
215
+ }
216
+ writeSuperblock() {
217
+ const v = this.superblockView;
218
+ v.setUint32(SUPERBLOCK.MAGIC, VFS_MAGIC, true);
219
+ v.setUint32(SUPERBLOCK.VERSION, VFS_VERSION, true);
220
+ v.setUint32(SUPERBLOCK.INODE_COUNT, this.inodeCount, true);
221
+ v.setUint32(SUPERBLOCK.BLOCK_SIZE, this.blockSize, true);
222
+ v.setUint32(SUPERBLOCK.TOTAL_BLOCKS, this.totalBlocks, true);
223
+ v.setUint32(SUPERBLOCK.FREE_BLOCKS, this.freeBlocks, true);
224
+ v.setFloat64(SUPERBLOCK.INODE_OFFSET, this.inodeTableOffset, true);
225
+ v.setFloat64(SUPERBLOCK.PATH_OFFSET, this.pathTableOffset, true);
226
+ v.setFloat64(SUPERBLOCK.DATA_OFFSET, this.dataOffset, true);
227
+ v.setFloat64(SUPERBLOCK.BITMAP_OFFSET, this.bitmapOffset, true);
228
+ v.setUint32(SUPERBLOCK.PATH_USED, this.pathTableUsed, true);
229
+ this.handle.write(this.superblockBuf, { at: 0 });
230
+ }
231
+ /** Flush pending bitmap and superblock writes to disk (one write each) */
232
+ markBitmapDirty(lo, hi) {
233
+ if (lo < this.bitmapDirtyLo) this.bitmapDirtyLo = lo;
234
+ if (hi > this.bitmapDirtyHi) this.bitmapDirtyHi = hi;
235
+ }
236
+ commitPending() {
237
+ if (this.bitmapDirtyHi >= 0) {
238
+ const lo = this.bitmapDirtyLo;
239
+ const hi = this.bitmapDirtyHi;
240
+ this.handle.write(this.bitmap.subarray(lo, hi + 1), { at: this.bitmapOffset + lo });
241
+ this.bitmapDirtyLo = Infinity;
242
+ this.bitmapDirtyHi = -1;
243
+ }
244
+ if (this.superblockDirty) {
245
+ this.writeSuperblock();
246
+ this.superblockDirty = false;
247
+ }
248
+ }
249
+ /** Rebuild in-memory path→inode index from disk */
250
+ rebuildIndex() {
251
+ this.pathIndex.clear();
252
+ for (let i = 0; i < this.inodeCount; i++) {
253
+ const inode = this.readInode(i);
254
+ if (inode.type === INODE_TYPE.FREE) continue;
255
+ const path = this.readPath(inode.pathOffset, inode.pathLength);
256
+ this.pathIndex.set(path, i);
257
+ }
258
+ }
259
+ // ========== Low-level inode I/O ==========
260
+ readInode(idx) {
261
+ const cached = this.inodeCache.get(idx);
262
+ if (cached) return cached;
263
+ const offset = this.inodeTableOffset + idx * INODE_SIZE;
264
+ this.handle.read(this.inodeBuf, { at: offset });
265
+ const v = this.inodeView;
266
+ const inode = {
267
+ type: v.getUint8(INODE.TYPE),
268
+ pathOffset: v.getUint32(INODE.PATH_OFFSET, true),
269
+ pathLength: v.getUint16(INODE.PATH_LENGTH, true),
270
+ mode: v.getUint32(INODE.MODE, true),
271
+ size: v.getFloat64(INODE.SIZE, true),
272
+ firstBlock: v.getUint32(INODE.FIRST_BLOCK, true),
273
+ blockCount: v.getUint32(INODE.BLOCK_COUNT, true),
274
+ mtime: v.getFloat64(INODE.MTIME, true),
275
+ ctime: v.getFloat64(INODE.CTIME, true),
276
+ atime: v.getFloat64(INODE.ATIME, true),
277
+ uid: v.getUint32(INODE.UID, true),
278
+ gid: v.getUint32(INODE.GID, true)
279
+ };
280
+ this.inodeCache.set(idx, inode);
281
+ return inode;
282
+ }
283
+ writeInode(idx, inode) {
284
+ if (inode.type === INODE_TYPE.FREE) {
285
+ this.inodeCache.delete(idx);
286
+ } else {
287
+ this.inodeCache.set(idx, inode);
288
+ }
289
+ const v = this.inodeView;
290
+ v.setUint8(INODE.TYPE, inode.type);
291
+ v.setUint8(INODE.FLAGS, 0);
292
+ v.setUint8(INODE.FLAGS + 1, 0);
293
+ v.setUint8(INODE.FLAGS + 2, 0);
294
+ v.setUint32(INODE.PATH_OFFSET, inode.pathOffset, true);
295
+ v.setUint16(INODE.PATH_LENGTH, inode.pathLength, true);
296
+ v.setUint16(INODE.RESERVED_10, 0, true);
297
+ v.setUint32(INODE.MODE, inode.mode, true);
298
+ v.setFloat64(INODE.SIZE, inode.size, true);
299
+ v.setUint32(INODE.FIRST_BLOCK, inode.firstBlock, true);
300
+ v.setUint32(INODE.BLOCK_COUNT, inode.blockCount, true);
301
+ v.setFloat64(INODE.MTIME, inode.mtime, true);
302
+ v.setFloat64(INODE.CTIME, inode.ctime, true);
303
+ v.setFloat64(INODE.ATIME, inode.atime, true);
304
+ v.setUint32(INODE.UID, inode.uid, true);
305
+ v.setUint32(INODE.GID, inode.gid, true);
306
+ const offset = this.inodeTableOffset + idx * INODE_SIZE;
307
+ this.handle.write(this.inodeBuf, { at: offset });
308
+ }
309
+ // ========== Path table I/O ==========
310
+ readPath(offset, length) {
311
+ const buf = new Uint8Array(length);
312
+ this.handle.read(buf, { at: this.pathTableOffset + offset });
313
+ return decoder.decode(buf);
314
+ }
315
+ appendPath(path) {
316
+ const bytes = encoder.encode(path);
317
+ const offset = this.pathTableUsed;
318
+ if (offset + bytes.byteLength > this.pathTableSize) {
319
+ this.growPathTable(offset + bytes.byteLength);
320
+ }
321
+ this.handle.write(bytes, { at: this.pathTableOffset + offset });
322
+ this.pathTableUsed += bytes.byteLength;
323
+ this.superblockDirty = true;
324
+ return { offset, length: bytes.byteLength };
325
+ }
326
+ growPathTable(needed) {
327
+ const newSize = Math.max(this.pathTableSize * 2, needed + INITIAL_PATH_TABLE_SIZE);
328
+ const growth = newSize - this.pathTableSize;
329
+ const dataSize = this.totalBlocks * this.blockSize;
330
+ const dataBuf = new Uint8Array(dataSize);
331
+ this.handle.read(dataBuf, { at: this.dataOffset });
332
+ const newTotalSize = this.handle.getSize() + growth;
333
+ this.handle.truncate(newTotalSize);
334
+ const newBitmapOffset = this.bitmapOffset + growth;
335
+ const newDataOffset = this.dataOffset + growth;
336
+ this.handle.write(dataBuf, { at: newDataOffset });
337
+ this.handle.write(this.bitmap, { at: newBitmapOffset });
338
+ this.pathTableSize = newSize;
339
+ this.bitmapOffset = newBitmapOffset;
340
+ this.dataOffset = newDataOffset;
341
+ this.superblockDirty = true;
342
+ }
343
+ // ========== Bitmap I/O ==========
344
+ allocateBlocks(count) {
345
+ if (count === 0) return 0;
346
+ const bitmap = this.bitmap;
347
+ let run = 0;
348
+ let start = 0;
349
+ for (let i = 0; i < this.totalBlocks; i++) {
350
+ const byteIdx = i >>> 3;
351
+ const bitIdx = i & 7;
352
+ const used = bitmap[byteIdx] >>> bitIdx & 1;
353
+ if (used) {
354
+ run = 0;
355
+ start = i + 1;
356
+ } else {
357
+ run++;
358
+ if (run === count) {
359
+ for (let j = start; j <= i; j++) {
360
+ const bj = j >>> 3;
361
+ const bi = j & 7;
362
+ bitmap[bj] |= 1 << bi;
363
+ }
364
+ this.markBitmapDirty(start >>> 3, i >>> 3);
365
+ this.freeBlocks -= count;
366
+ this.superblockDirty = true;
367
+ return start;
368
+ }
369
+ }
370
+ }
371
+ return this.growAndAllocate(count);
372
+ }
373
+ growAndAllocate(count) {
374
+ const oldTotal = this.totalBlocks;
375
+ const newTotal = Math.max(oldTotal * 2, oldTotal + count);
376
+ const addedBlocks = newTotal - oldTotal;
377
+ const newFileSize = this.dataOffset + newTotal * this.blockSize;
378
+ this.handle.truncate(newFileSize);
379
+ const newBitmapSize = Math.ceil(newTotal / 8);
380
+ const newBitmap = new Uint8Array(newBitmapSize);
381
+ newBitmap.set(this.bitmap);
382
+ this.bitmap = newBitmap;
383
+ this.totalBlocks = newTotal;
384
+ this.freeBlocks += addedBlocks;
385
+ const start = oldTotal;
386
+ for (let j = start; j < start + count; j++) {
387
+ const bj = j >>> 3;
388
+ const bi = j & 7;
389
+ this.bitmap[bj] |= 1 << bi;
390
+ }
391
+ this.markBitmapDirty(start >>> 3, start + count - 1 >>> 3);
392
+ this.freeBlocks -= count;
393
+ this.superblockDirty = true;
394
+ return start;
395
+ }
396
+ freeBlockRange(start, count) {
397
+ if (count === 0) return;
398
+ const bitmap = this.bitmap;
399
+ for (let i = start; i < start + count; i++) {
400
+ const byteIdx = i >>> 3;
401
+ const bitIdx = i & 7;
402
+ bitmap[byteIdx] &= ~(1 << bitIdx);
403
+ }
404
+ this.markBitmapDirty(start >>> 3, start + count - 1 >>> 3);
405
+ this.freeBlocks += count;
406
+ this.superblockDirty = true;
407
+ }
408
+ // updateSuperblockFreeBlocks is no longer needed — superblock writes are coalesced via commitPending()
409
+ // ========== Inode allocation ==========
410
+ findFreeInode() {
411
+ for (let i = this.freeInodeHint; i < this.inodeCount; i++) {
412
+ if (this.inodeCache.has(i)) continue;
413
+ const offset = this.inodeTableOffset + i * INODE_SIZE;
414
+ const typeBuf = new Uint8Array(1);
415
+ this.handle.read(typeBuf, { at: offset });
416
+ if (typeBuf[0] === INODE_TYPE.FREE) {
417
+ this.freeInodeHint = i + 1;
418
+ return i;
419
+ }
420
+ }
421
+ const idx = this.growInodeTable();
422
+ this.freeInodeHint = idx + 1;
423
+ return idx;
424
+ }
425
+ growInodeTable() {
426
+ const oldCount = this.inodeCount;
427
+ const newCount = oldCount * 2;
428
+ const growth = (newCount - oldCount) * INODE_SIZE;
429
+ const afterInodeOffset = this.inodeTableOffset + oldCount * INODE_SIZE;
430
+ const afterSize = this.handle.getSize() - afterInodeOffset;
431
+ const afterBuf = new Uint8Array(afterSize);
432
+ this.handle.read(afterBuf, { at: afterInodeOffset });
433
+ this.handle.truncate(this.handle.getSize() + growth);
434
+ this.handle.write(afterBuf, { at: afterInodeOffset + growth });
435
+ const zeroes = new Uint8Array(growth);
436
+ this.handle.write(zeroes, { at: afterInodeOffset });
437
+ this.pathTableOffset += growth;
438
+ this.bitmapOffset += growth;
439
+ this.dataOffset += growth;
440
+ this.inodeCount = newCount;
441
+ this.superblockDirty = true;
442
+ return oldCount;
443
+ }
444
+ // ========== Data I/O ==========
445
+ readData(firstBlock, blockCount, size) {
446
+ const buf = new Uint8Array(size);
447
+ const offset = this.dataOffset + firstBlock * this.blockSize;
448
+ this.handle.read(buf, { at: offset });
449
+ return buf;
450
+ }
451
+ writeData(firstBlock, data) {
452
+ const offset = this.dataOffset + firstBlock * this.blockSize;
453
+ this.handle.write(data, { at: offset });
454
+ }
455
+ // ========== Path resolution ==========
456
+ resolvePath(path, depth = 0) {
457
+ if (depth > MAX_SYMLINK_DEPTH) return void 0;
458
+ const idx = this.pathIndex.get(path);
459
+ if (idx === void 0) return void 0;
460
+ const inode = this.readInode(idx);
461
+ if (inode.type === INODE_TYPE.SYMLINK) {
462
+ const target = decoder.decode(this.readData(inode.firstBlock, inode.blockCount, inode.size));
463
+ const resolved = target.startsWith("/") ? target : this.resolveRelative(path, target);
464
+ return this.resolvePath(resolved, depth + 1);
465
+ }
466
+ return idx;
467
+ }
468
+ /** Resolve symlinks in intermediate path components */
469
+ resolvePathComponents(path, followLast = true) {
470
+ const parts = path.split("/").filter(Boolean);
471
+ let current = "/";
472
+ for (let i = 0; i < parts.length; i++) {
473
+ const isLast = i === parts.length - 1;
474
+ current = current === "/" ? "/" + parts[i] : current + "/" + parts[i];
475
+ const idx = this.pathIndex.get(current);
476
+ if (idx === void 0) return void 0;
477
+ const inode = this.readInode(idx);
478
+ if (inode.type === INODE_TYPE.SYMLINK && (!isLast || followLast)) {
479
+ const target = decoder.decode(this.readData(inode.firstBlock, inode.blockCount, inode.size));
480
+ const resolved = target.startsWith("/") ? target : this.resolveRelative(current, target);
481
+ if (isLast) {
482
+ return this.resolvePath(resolved);
483
+ }
484
+ const remaining = parts.slice(i + 1).join("/");
485
+ const newPath = resolved + (remaining ? "/" + remaining : "");
486
+ return this.resolvePathComponents(newPath, followLast);
487
+ }
488
+ }
489
+ return this.pathIndex.get(current);
490
+ }
491
+ resolveRelative(from, target) {
492
+ const dir = from.substring(0, from.lastIndexOf("/")) || "/";
493
+ const parts = (dir + "/" + target).split("/").filter(Boolean);
494
+ const resolved = [];
495
+ for (const p of parts) {
496
+ if (p === ".") continue;
497
+ if (p === "..") {
498
+ resolved.pop();
499
+ continue;
500
+ }
501
+ resolved.push(p);
502
+ }
503
+ return "/" + resolved.join("/");
504
+ }
505
+ // ========== Core inode creation helper ==========
506
+ createInode(path, type, mode, size, data) {
507
+ const idx = this.findFreeInode();
508
+ const { offset: pathOff, length: pathLen } = this.appendPath(path);
509
+ const now = Date.now();
510
+ let firstBlock = 0;
511
+ let blockCount = 0;
512
+ if (data && data.byteLength > 0) {
513
+ blockCount = Math.ceil(data.byteLength / this.blockSize);
514
+ firstBlock = this.allocateBlocks(blockCount);
515
+ this.writeData(firstBlock, data);
516
+ }
517
+ const inode = {
518
+ type,
519
+ pathOffset: pathOff,
520
+ pathLength: pathLen,
521
+ mode,
522
+ size,
523
+ firstBlock,
524
+ blockCount,
525
+ mtime: now,
526
+ ctime: now,
527
+ atime: now,
528
+ uid: this.processUid,
529
+ gid: this.processGid
530
+ };
531
+ this.writeInode(idx, inode);
532
+ this.pathIndex.set(path, idx);
533
+ return idx;
534
+ }
535
+ // ========== Public API — called by server worker dispatch ==========
536
+ /** Normalize a path: ensure leading /, resolve . and .. */
537
+ normalizePath(p) {
538
+ if (p.charCodeAt(0) !== 47) p = "/" + p;
539
+ if (p.length === 1) return p;
540
+ if (p.indexOf("/.") === -1 && p.indexOf("//") === -1 && p.charCodeAt(p.length - 1) !== 47) {
541
+ return p;
542
+ }
543
+ const parts = p.split("/").filter(Boolean);
544
+ const resolved = [];
545
+ for (const part of parts) {
546
+ if (part === ".") continue;
547
+ if (part === "..") {
548
+ resolved.pop();
549
+ continue;
550
+ }
551
+ resolved.push(part);
552
+ }
553
+ return "/" + resolved.join("/");
554
+ }
555
+ // ---- READ ----
556
+ read(path) {
557
+ const t0 = this.debug ? performance.now() : 0;
558
+ path = this.normalizePath(path);
559
+ let idx = this.pathIndex.get(path);
560
+ if (idx !== void 0) {
561
+ const inode2 = this.inodeCache.get(idx);
562
+ if (inode2) {
563
+ if (inode2.type === INODE_TYPE.SYMLINK) {
564
+ idx = this.resolvePathComponents(path, true);
565
+ } else if (inode2.type === INODE_TYPE.DIRECTORY) {
566
+ return { status: CODE_TO_STATUS.EISDIR, data: null };
567
+ } else {
568
+ const data2 = inode2.size > 0 ? this.readData(inode2.firstBlock, inode2.blockCount, inode2.size) : new Uint8Array(0);
569
+ if (this.debug) {
570
+ const t1 = performance.now();
571
+ console.log(`[VFS read] path=${path} size=${inode2.size} TOTAL=${(t1 - t0).toFixed(3)}ms (fast)`);
572
+ }
573
+ return { status: 0, data: data2 };
574
+ }
575
+ }
576
+ }
577
+ if (idx === void 0) idx = this.resolvePathComponents(path, true);
578
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT, data: null };
579
+ const inode = this.readInode(idx);
580
+ if (inode.type === INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.EISDIR, data: null };
581
+ const data = inode.size > 0 ? this.readData(inode.firstBlock, inode.blockCount, inode.size) : new Uint8Array(0);
582
+ if (this.debug) {
583
+ const t1 = performance.now();
584
+ console.log(`[VFS read] path=${path} size=${inode.size} TOTAL=${(t1 - t0).toFixed(3)}ms (slow path)`);
585
+ }
586
+ return { status: 0, data };
587
+ }
588
+ // ---- WRITE ----
589
+ write(path, data, flags = 0) {
590
+ const t0 = this.debug ? performance.now() : 0;
591
+ path = this.normalizePath(path);
592
+ const t1 = this.debug ? performance.now() : 0;
593
+ const parentStatus = this.ensureParent(path);
594
+ if (parentStatus !== 0) return { status: parentStatus };
595
+ const t2 = this.debug ? performance.now() : 0;
596
+ const existingIdx = this.resolvePathComponents(path, true);
597
+ const t3 = this.debug ? performance.now() : 0;
598
+ let tAlloc = t3, tData = t3, tInode = t3;
599
+ if (existingIdx !== void 0) {
600
+ const inode = this.readInode(existingIdx);
601
+ if (inode.type === INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.EISDIR };
602
+ const neededBlocks = Math.ceil(data.byteLength / this.blockSize);
603
+ if (neededBlocks <= inode.blockCount) {
604
+ tAlloc = this.debug ? performance.now() : 0;
605
+ this.writeData(inode.firstBlock, data);
606
+ tData = this.debug ? performance.now() : 0;
607
+ if (neededBlocks < inode.blockCount) {
608
+ this.freeBlockRange(inode.firstBlock + neededBlocks, inode.blockCount - neededBlocks);
609
+ }
610
+ } else {
611
+ this.freeBlockRange(inode.firstBlock, inode.blockCount);
612
+ const newFirst = this.allocateBlocks(neededBlocks);
613
+ tAlloc = this.debug ? performance.now() : 0;
614
+ this.writeData(newFirst, data);
615
+ tData = this.debug ? performance.now() : 0;
616
+ inode.firstBlock = newFirst;
617
+ }
618
+ inode.size = data.byteLength;
619
+ inode.blockCount = neededBlocks;
620
+ inode.mtime = Date.now();
621
+ this.writeInode(existingIdx, inode);
622
+ tInode = this.debug ? performance.now() : 0;
623
+ } else {
624
+ const mode = DEFAULT_FILE_MODE & ~(this.umask & 511);
625
+ this.createInode(path, INODE_TYPE.FILE, mode, data.byteLength, data);
626
+ tAlloc = this.debug ? performance.now() : 0;
627
+ tData = tAlloc;
628
+ tInode = tAlloc;
629
+ }
630
+ if (flags & 1) {
631
+ this.commitPending();
632
+ this.handle.flush();
633
+ }
634
+ const tFlush = this.debug ? performance.now() : 0;
635
+ if (this.debug) {
636
+ const existing = existingIdx !== void 0;
637
+ console.log(`[VFS write] path=${path} size=${data.byteLength} ${existing ? "UPDATE" : "CREATE"} normalize=${(t1 - t0).toFixed(3)}ms parent=${(t2 - t1).toFixed(3)}ms resolve=${(t3 - t2).toFixed(3)}ms alloc=${(tAlloc - t3).toFixed(3)}ms data=${(tData - tAlloc).toFixed(3)}ms inode=${(tInode - tData).toFixed(3)}ms flush=${(tFlush - tInode).toFixed(3)}ms TOTAL=${(tFlush - t0).toFixed(3)}ms`);
638
+ }
639
+ return { status: 0 };
640
+ }
641
+ // ---- APPEND ----
642
+ append(path, data) {
643
+ path = this.normalizePath(path);
644
+ const existingIdx = this.resolvePathComponents(path, true);
645
+ if (existingIdx === void 0) {
646
+ return this.write(path, data);
647
+ }
648
+ const inode = this.readInode(existingIdx);
649
+ if (inode.type === INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.EISDIR };
650
+ const existing = inode.size > 0 ? this.readData(inode.firstBlock, inode.blockCount, inode.size) : new Uint8Array(0);
651
+ const combined = new Uint8Array(existing.byteLength + data.byteLength);
652
+ combined.set(existing);
653
+ combined.set(data, existing.byteLength);
654
+ const neededBlocks = Math.ceil(combined.byteLength / this.blockSize);
655
+ this.freeBlockRange(inode.firstBlock, inode.blockCount);
656
+ const newFirst = this.allocateBlocks(neededBlocks);
657
+ this.writeData(newFirst, combined);
658
+ inode.firstBlock = newFirst;
659
+ inode.blockCount = neededBlocks;
660
+ inode.size = combined.byteLength;
661
+ inode.mtime = Date.now();
662
+ this.writeInode(existingIdx, inode);
663
+ this.commitPending();
664
+ return { status: 0 };
665
+ }
666
+ // ---- UNLINK ----
667
+ unlink(path) {
668
+ path = this.normalizePath(path);
669
+ const idx = this.pathIndex.get(path);
670
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT };
671
+ const inode = this.readInode(idx);
672
+ if (inode.type === INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.EISDIR };
673
+ this.freeBlockRange(inode.firstBlock, inode.blockCount);
674
+ inode.type = INODE_TYPE.FREE;
675
+ this.writeInode(idx, inode);
676
+ this.pathIndex.delete(path);
677
+ if (idx < this.freeInodeHint) this.freeInodeHint = idx;
678
+ this.commitPending();
679
+ return { status: 0 };
680
+ }
681
+ // ---- STAT ----
682
+ stat(path) {
683
+ path = this.normalizePath(path);
684
+ const idx = this.resolvePathComponents(path, true);
685
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT, data: null };
686
+ return this.encodeStatResponse(idx);
687
+ }
688
+ // ---- LSTAT (no symlink follow) ----
689
+ lstat(path) {
690
+ path = this.normalizePath(path);
691
+ const idx = this.pathIndex.get(path);
692
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT, data: null };
693
+ return this.encodeStatResponse(idx);
694
+ }
695
+ encodeStatResponse(idx) {
696
+ const inode = this.readInode(idx);
697
+ const buf = new Uint8Array(49);
698
+ const view = new DataView(buf.buffer);
699
+ view.setUint8(0, inode.type);
700
+ view.setUint32(1, inode.mode, true);
701
+ view.setFloat64(5, inode.size, true);
702
+ view.setFloat64(13, inode.mtime, true);
703
+ view.setFloat64(21, inode.ctime, true);
704
+ view.setFloat64(29, inode.atime, true);
705
+ view.setUint32(37, inode.uid, true);
706
+ view.setUint32(41, inode.gid, true);
707
+ view.setUint32(45, idx, true);
708
+ return { status: 0, data: buf };
709
+ }
710
+ // ---- MKDIR ----
711
+ mkdir(path, flags = 0) {
712
+ path = this.normalizePath(path);
713
+ const recursive = (flags & 1) !== 0;
714
+ if (recursive) {
715
+ return this.mkdirRecursive(path);
716
+ }
717
+ if (this.pathIndex.has(path)) return { status: CODE_TO_STATUS.EEXIST, data: null };
718
+ const parentStatus = this.ensureParent(path);
719
+ if (parentStatus !== 0) return { status: parentStatus, data: null };
720
+ const mode = DEFAULT_DIR_MODE & ~(this.umask & 511);
721
+ this.createInode(path, INODE_TYPE.DIRECTORY, mode, 0);
722
+ this.commitPending();
723
+ const pathBytes = encoder.encode(path);
724
+ return { status: 0, data: pathBytes };
725
+ }
726
+ mkdirRecursive(path) {
727
+ const parts = path.split("/").filter(Boolean);
728
+ let current = "";
729
+ let firstCreated = null;
730
+ for (const part of parts) {
731
+ current += "/" + part;
732
+ if (this.pathIndex.has(current)) {
733
+ const idx = this.pathIndex.get(current);
734
+ const inode = this.readInode(idx);
735
+ if (inode.type !== INODE_TYPE.DIRECTORY) {
736
+ return { status: CODE_TO_STATUS.ENOTDIR, data: null };
737
+ }
738
+ continue;
739
+ }
740
+ const mode = DEFAULT_DIR_MODE & ~(this.umask & 511);
741
+ this.createInode(current, INODE_TYPE.DIRECTORY, mode, 0);
742
+ if (!firstCreated) firstCreated = current;
743
+ }
744
+ this.commitPending();
745
+ const result = firstCreated ? encoder.encode(firstCreated) : void 0;
746
+ return { status: 0, data: result ?? null };
747
+ }
748
+ // ---- RMDIR ----
749
+ rmdir(path, flags = 0) {
750
+ path = this.normalizePath(path);
751
+ const recursive = (flags & 1) !== 0;
752
+ const idx = this.pathIndex.get(path);
753
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT };
754
+ const inode = this.readInode(idx);
755
+ if (inode.type !== INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.ENOTDIR };
756
+ const children = this.getDirectChildren(path);
757
+ if (children.length > 0) {
758
+ if (!recursive) return { status: CODE_TO_STATUS.ENOTEMPTY };
759
+ for (const child of this.getAllDescendants(path)) {
760
+ const childIdx = this.pathIndex.get(child);
761
+ const childInode = this.readInode(childIdx);
762
+ this.freeBlockRange(childInode.firstBlock, childInode.blockCount);
763
+ childInode.type = INODE_TYPE.FREE;
764
+ this.writeInode(childIdx, childInode);
765
+ this.pathIndex.delete(child);
766
+ }
767
+ }
768
+ inode.type = INODE_TYPE.FREE;
769
+ this.writeInode(idx, inode);
770
+ this.pathIndex.delete(path);
771
+ if (idx < this.freeInodeHint) this.freeInodeHint = idx;
772
+ this.commitPending();
773
+ return { status: 0 };
774
+ }
775
+ // ---- READDIR ----
776
+ readdir(path, flags = 0) {
777
+ path = this.normalizePath(path);
778
+ const idx = this.resolvePathComponents(path, true);
779
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT, data: null };
780
+ const inode = this.readInode(idx);
781
+ if (inode.type !== INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.ENOTDIR, data: null };
782
+ const withFileTypes = (flags & 1) !== 0;
783
+ const children = this.getDirectChildren(path);
784
+ if (withFileTypes) {
785
+ let totalSize2 = 4;
786
+ const entries = [];
787
+ for (const childPath of children) {
788
+ const name = childPath.substring(childPath.lastIndexOf("/") + 1);
789
+ const nameBytes = encoder.encode(name);
790
+ const childIdx = this.pathIndex.get(childPath);
791
+ const childInode = this.readInode(childIdx);
792
+ entries.push({ name: nameBytes, type: childInode.type });
793
+ totalSize2 += 2 + nameBytes.byteLength + 1;
794
+ }
795
+ const buf2 = new Uint8Array(totalSize2);
796
+ const view2 = new DataView(buf2.buffer);
797
+ view2.setUint32(0, entries.length, true);
798
+ let offset2 = 4;
799
+ for (const entry of entries) {
800
+ view2.setUint16(offset2, entry.name.byteLength, true);
801
+ offset2 += 2;
802
+ buf2.set(entry.name, offset2);
803
+ offset2 += entry.name.byteLength;
804
+ buf2[offset2++] = entry.type;
805
+ }
806
+ return { status: 0, data: buf2 };
807
+ }
808
+ let totalSize = 4;
809
+ const nameEntries = [];
810
+ for (const childPath of children) {
811
+ const name = childPath.substring(childPath.lastIndexOf("/") + 1);
812
+ const nameBytes = encoder.encode(name);
813
+ nameEntries.push(nameBytes);
814
+ totalSize += 2 + nameBytes.byteLength;
815
+ }
816
+ const buf = new Uint8Array(totalSize);
817
+ const view = new DataView(buf.buffer);
818
+ view.setUint32(0, nameEntries.length, true);
819
+ let offset = 4;
820
+ for (const nameBytes of nameEntries) {
821
+ view.setUint16(offset, nameBytes.byteLength, true);
822
+ offset += 2;
823
+ buf.set(nameBytes, offset);
824
+ offset += nameBytes.byteLength;
825
+ }
826
+ return { status: 0, data: buf };
827
+ }
828
+ // ---- RENAME ----
829
+ rename(oldPath, newPath) {
830
+ oldPath = this.normalizePath(oldPath);
831
+ newPath = this.normalizePath(newPath);
832
+ const idx = this.pathIndex.get(oldPath);
833
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT };
834
+ const parentStatus = this.ensureParent(newPath);
835
+ if (parentStatus !== 0) return { status: parentStatus };
836
+ const existingIdx = this.pathIndex.get(newPath);
837
+ if (existingIdx !== void 0) {
838
+ const existingInode = this.readInode(existingIdx);
839
+ this.freeBlockRange(existingInode.firstBlock, existingInode.blockCount);
840
+ existingInode.type = INODE_TYPE.FREE;
841
+ this.writeInode(existingIdx, existingInode);
842
+ this.pathIndex.delete(newPath);
843
+ }
844
+ const inode = this.readInode(idx);
845
+ const { offset: pathOff, length: pathLen } = this.appendPath(newPath);
846
+ inode.pathOffset = pathOff;
847
+ inode.pathLength = pathLen;
848
+ inode.mtime = Date.now();
849
+ this.writeInode(idx, inode);
850
+ this.pathIndex.delete(oldPath);
851
+ this.pathIndex.set(newPath, idx);
852
+ if (inode.type === INODE_TYPE.DIRECTORY) {
853
+ const prefix = oldPath === "/" ? "/" : oldPath + "/";
854
+ const toRename = [];
855
+ for (const [p, i] of this.pathIndex) {
856
+ if (p.startsWith(prefix)) {
857
+ toRename.push([p, i]);
858
+ }
859
+ }
860
+ for (const [p, i] of toRename) {
861
+ const suffix = p.substring(oldPath.length);
862
+ const childNewPath = newPath + suffix;
863
+ const childInode = this.readInode(i);
864
+ const { offset: cpo, length: cpl } = this.appendPath(childNewPath);
865
+ childInode.pathOffset = cpo;
866
+ childInode.pathLength = cpl;
867
+ this.writeInode(i, childInode);
868
+ this.pathIndex.delete(p);
869
+ this.pathIndex.set(childNewPath, i);
870
+ }
871
+ }
872
+ this.commitPending();
873
+ return { status: 0 };
874
+ }
875
+ // ---- EXISTS ----
876
+ exists(path) {
877
+ path = this.normalizePath(path);
878
+ const idx = this.resolvePathComponents(path, true);
879
+ const buf = new Uint8Array(1);
880
+ buf[0] = idx !== void 0 ? 1 : 0;
881
+ return { status: 0, data: buf };
882
+ }
883
+ // ---- TRUNCATE ----
884
+ truncate(path, len = 0) {
885
+ path = this.normalizePath(path);
886
+ const idx = this.resolvePathComponents(path, true);
887
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT };
888
+ const inode = this.readInode(idx);
889
+ if (inode.type === INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.EISDIR };
890
+ if (len === 0) {
891
+ this.freeBlockRange(inode.firstBlock, inode.blockCount);
892
+ inode.firstBlock = 0;
893
+ inode.blockCount = 0;
894
+ inode.size = 0;
895
+ } else if (len < inode.size) {
896
+ const neededBlocks = Math.ceil(len / this.blockSize);
897
+ if (neededBlocks < inode.blockCount) {
898
+ this.freeBlockRange(inode.firstBlock + neededBlocks, inode.blockCount - neededBlocks);
899
+ }
900
+ inode.blockCount = neededBlocks;
901
+ inode.size = len;
902
+ } else if (len > inode.size) {
903
+ const neededBlocks = Math.ceil(len / this.blockSize);
904
+ if (neededBlocks > inode.blockCount) {
905
+ const oldData = this.readData(inode.firstBlock, inode.blockCount, inode.size);
906
+ this.freeBlockRange(inode.firstBlock, inode.blockCount);
907
+ const newFirst = this.allocateBlocks(neededBlocks);
908
+ const newData = new Uint8Array(len);
909
+ newData.set(oldData);
910
+ this.writeData(newFirst, newData);
911
+ inode.firstBlock = newFirst;
912
+ }
913
+ inode.blockCount = neededBlocks;
914
+ inode.size = len;
915
+ }
916
+ inode.mtime = Date.now();
917
+ this.writeInode(idx, inode);
918
+ this.commitPending();
919
+ return { status: 0 };
920
+ }
921
+ // ---- COPY ----
922
+ copy(srcPath, destPath, flags = 0) {
923
+ srcPath = this.normalizePath(srcPath);
924
+ destPath = this.normalizePath(destPath);
925
+ const srcIdx = this.resolvePathComponents(srcPath, true);
926
+ if (srcIdx === void 0) return { status: CODE_TO_STATUS.ENOENT };
927
+ const srcInode = this.readInode(srcIdx);
928
+ if (srcInode.type === INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.EISDIR };
929
+ if (flags & 1 && this.pathIndex.has(destPath)) {
930
+ return { status: CODE_TO_STATUS.EEXIST };
931
+ }
932
+ const data = srcInode.size > 0 ? this.readData(srcInode.firstBlock, srcInode.blockCount, srcInode.size) : new Uint8Array(0);
933
+ return this.write(destPath, data);
934
+ }
935
+ // ---- ACCESS ----
936
+ access(path, mode = 0) {
937
+ path = this.normalizePath(path);
938
+ const idx = this.resolvePathComponents(path, true);
939
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT };
940
+ if (mode === 0) return { status: 0 };
941
+ if (!this.strictPermissions) return { status: 0 };
942
+ const inode = this.readInode(idx);
943
+ const filePerm = this.getEffectivePermission(inode);
944
+ if (mode & 4 && !(filePerm & 4)) return { status: CODE_TO_STATUS.EACCES };
945
+ if (mode & 2 && !(filePerm & 2)) return { status: CODE_TO_STATUS.EACCES };
946
+ if (mode & 1 && !(filePerm & 1)) return { status: CODE_TO_STATUS.EACCES };
947
+ return { status: 0 };
948
+ }
949
+ getEffectivePermission(inode) {
950
+ const modeBits = inode.mode & 511;
951
+ if (this.processUid === inode.uid) return modeBits >>> 6 & 7;
952
+ if (this.processGid === inode.gid) return modeBits >>> 3 & 7;
953
+ return modeBits & 7;
954
+ }
955
+ // ---- REALPATH ----
956
+ realpath(path) {
957
+ path = this.normalizePath(path);
958
+ const idx = this.resolvePathComponents(path, true);
959
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT, data: null };
960
+ const inode = this.readInode(idx);
961
+ const resolvedPath = this.readPath(inode.pathOffset, inode.pathLength);
962
+ return { status: 0, data: encoder.encode(resolvedPath) };
963
+ }
964
+ // ---- CHMOD ----
965
+ chmod(path, mode) {
966
+ path = this.normalizePath(path);
967
+ const idx = this.resolvePathComponents(path, true);
968
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT };
969
+ const inode = this.readInode(idx);
970
+ inode.mode = inode.mode & S_IFMT | mode & 4095;
971
+ inode.ctime = Date.now();
972
+ this.writeInode(idx, inode);
973
+ return { status: 0 };
974
+ }
975
+ // ---- CHOWN ----
976
+ chown(path, uid, gid) {
977
+ path = this.normalizePath(path);
978
+ const idx = this.resolvePathComponents(path, true);
979
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT };
980
+ const inode = this.readInode(idx);
981
+ inode.uid = uid;
982
+ inode.gid = gid;
983
+ inode.ctime = Date.now();
984
+ this.writeInode(idx, inode);
985
+ return { status: 0 };
986
+ }
987
+ // ---- UTIMES ----
988
+ utimes(path, atime, mtime) {
989
+ path = this.normalizePath(path);
990
+ const idx = this.resolvePathComponents(path, true);
991
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT };
992
+ const inode = this.readInode(idx);
993
+ inode.atime = atime;
994
+ inode.mtime = mtime;
995
+ inode.ctime = Date.now();
996
+ this.writeInode(idx, inode);
997
+ return { status: 0 };
998
+ }
999
+ // ---- SYMLINK ----
1000
+ symlink(target, linkPath) {
1001
+ linkPath = this.normalizePath(linkPath);
1002
+ if (this.pathIndex.has(linkPath)) return { status: CODE_TO_STATUS.EEXIST };
1003
+ const parentStatus = this.ensureParent(linkPath);
1004
+ if (parentStatus !== 0) return { status: parentStatus };
1005
+ const targetBytes = encoder.encode(target);
1006
+ this.createInode(linkPath, INODE_TYPE.SYMLINK, DEFAULT_SYMLINK_MODE, targetBytes.byteLength, targetBytes);
1007
+ this.commitPending();
1008
+ return { status: 0 };
1009
+ }
1010
+ // ---- READLINK ----
1011
+ readlink(path) {
1012
+ path = this.normalizePath(path);
1013
+ const idx = this.pathIndex.get(path);
1014
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT, data: null };
1015
+ const inode = this.readInode(idx);
1016
+ if (inode.type !== INODE_TYPE.SYMLINK) return { status: CODE_TO_STATUS.EINVAL, data: null };
1017
+ const target = this.readData(inode.firstBlock, inode.blockCount, inode.size);
1018
+ return { status: 0, data: target };
1019
+ }
1020
+ // ---- LINK (hard link — copies the file) ----
1021
+ link(existingPath, newPath) {
1022
+ return this.copy(existingPath, newPath);
1023
+ }
1024
+ // ---- OPEN (file descriptor) ----
1025
+ open(path, flags, tabId) {
1026
+ path = this.normalizePath(path);
1027
+ const hasCreate = (flags & 64) !== 0;
1028
+ const hasTrunc = (flags & 512) !== 0;
1029
+ const hasExcl = (flags & 128) !== 0;
1030
+ let idx = this.resolvePathComponents(path, true);
1031
+ if (idx === void 0) {
1032
+ if (!hasCreate) return { status: CODE_TO_STATUS.ENOENT, data: null };
1033
+ const mode = DEFAULT_FILE_MODE & ~(this.umask & 511);
1034
+ idx = this.createInode(path, INODE_TYPE.FILE, mode, 0);
1035
+ } else if (hasExcl && hasCreate) {
1036
+ return { status: CODE_TO_STATUS.EEXIST, data: null };
1037
+ }
1038
+ if (hasTrunc) {
1039
+ this.truncate(path, 0);
1040
+ }
1041
+ const fd = this.nextFd++;
1042
+ this.fdTable.set(fd, { tabId, inodeIdx: idx, position: 0, flags });
1043
+ const buf = new Uint8Array(4);
1044
+ new DataView(buf.buffer).setUint32(0, fd, true);
1045
+ return { status: 0, data: buf };
1046
+ }
1047
+ // ---- CLOSE ----
1048
+ close(fd) {
1049
+ if (!this.fdTable.has(fd)) return { status: CODE_TO_STATUS.EBADF };
1050
+ this.fdTable.delete(fd);
1051
+ return { status: 0 };
1052
+ }
1053
+ // ---- FREAD ----
1054
+ fread(fd, length, position) {
1055
+ const entry = this.fdTable.get(fd);
1056
+ if (!entry) return { status: CODE_TO_STATUS.EBADF, data: null };
1057
+ const inode = this.readInode(entry.inodeIdx);
1058
+ const pos = position ?? entry.position;
1059
+ const readLen = Math.min(length, inode.size - pos);
1060
+ if (readLen <= 0) return { status: 0, data: new Uint8Array(0) };
1061
+ const dataOffset = this.dataOffset + inode.firstBlock * this.blockSize + pos;
1062
+ const buf = new Uint8Array(readLen);
1063
+ this.handle.read(buf, { at: dataOffset });
1064
+ if (position === null) {
1065
+ entry.position += readLen;
1066
+ }
1067
+ return { status: 0, data: buf };
1068
+ }
1069
+ // ---- FWRITE ----
1070
+ fwrite(fd, data, position) {
1071
+ const entry = this.fdTable.get(fd);
1072
+ if (!entry) return { status: CODE_TO_STATUS.EBADF, data: null };
1073
+ const inode = this.readInode(entry.inodeIdx);
1074
+ const isAppend = (entry.flags & 1024) !== 0;
1075
+ const pos = isAppend ? inode.size : position ?? entry.position;
1076
+ const endPos = pos + data.byteLength;
1077
+ if (endPos > inode.size) {
1078
+ const neededBlocks = Math.ceil(endPos / this.blockSize);
1079
+ if (neededBlocks > inode.blockCount) {
1080
+ const oldData = inode.size > 0 ? this.readData(inode.firstBlock, inode.blockCount, inode.size) : new Uint8Array(0);
1081
+ this.freeBlockRange(inode.firstBlock, inode.blockCount);
1082
+ const newFirst = this.allocateBlocks(neededBlocks);
1083
+ const newBuf = new Uint8Array(endPos);
1084
+ newBuf.set(oldData);
1085
+ newBuf.set(data, pos);
1086
+ this.writeData(newFirst, newBuf);
1087
+ inode.firstBlock = newFirst;
1088
+ inode.blockCount = neededBlocks;
1089
+ } else {
1090
+ const dataOffset = this.dataOffset + inode.firstBlock * this.blockSize + pos;
1091
+ this.handle.write(data, { at: dataOffset });
1092
+ }
1093
+ inode.size = endPos;
1094
+ } else {
1095
+ const dataOffset = this.dataOffset + inode.firstBlock * this.blockSize + pos;
1096
+ this.handle.write(data, { at: dataOffset });
1097
+ }
1098
+ inode.mtime = Date.now();
1099
+ this.writeInode(entry.inodeIdx, inode);
1100
+ if (position === null) {
1101
+ entry.position = endPos;
1102
+ }
1103
+ this.commitPending();
1104
+ const buf = new Uint8Array(4);
1105
+ new DataView(buf.buffer).setUint32(0, data.byteLength, true);
1106
+ return { status: 0, data: buf };
1107
+ }
1108
+ // ---- FSTAT ----
1109
+ fstat(fd) {
1110
+ const entry = this.fdTable.get(fd);
1111
+ if (!entry) return { status: CODE_TO_STATUS.EBADF, data: null };
1112
+ return this.encodeStatResponse(entry.inodeIdx);
1113
+ }
1114
+ // ---- FTRUNCATE ----
1115
+ ftruncate(fd, len = 0) {
1116
+ const entry = this.fdTable.get(fd);
1117
+ if (!entry) return { status: CODE_TO_STATUS.EBADF };
1118
+ const inode = this.readInode(entry.inodeIdx);
1119
+ const path = this.readPath(inode.pathOffset, inode.pathLength);
1120
+ return this.truncate(path, len);
1121
+ }
1122
+ // ---- FSYNC ----
1123
+ fsync() {
1124
+ this.commitPending();
1125
+ this.handle.flush();
1126
+ return { status: 0 };
1127
+ }
1128
+ // ---- OPENDIR ----
1129
+ opendir(path, tabId) {
1130
+ path = this.normalizePath(path);
1131
+ const idx = this.resolvePathComponents(path, true);
1132
+ if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT, data: null };
1133
+ const inode = this.readInode(idx);
1134
+ if (inode.type !== INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.ENOTDIR, data: null };
1135
+ const fd = this.nextFd++;
1136
+ this.fdTable.set(fd, { tabId, inodeIdx: idx, position: 0, flags: 0 });
1137
+ const buf = new Uint8Array(4);
1138
+ new DataView(buf.buffer).setUint32(0, fd, true);
1139
+ return { status: 0, data: buf };
1140
+ }
1141
+ // ---- MKDTEMP ----
1142
+ mkdtemp(prefix) {
1143
+ const suffix = Math.random().toString(36).substring(2, 8);
1144
+ const path = this.normalizePath(prefix + suffix);
1145
+ const parentStatus = this.ensureParent(path);
1146
+ if (parentStatus !== 0) {
1147
+ const parentPath = path.substring(0, path.lastIndexOf("/"));
1148
+ if (parentPath) {
1149
+ this.mkdirRecursive(parentPath);
1150
+ }
1151
+ }
1152
+ const mode = DEFAULT_DIR_MODE & ~(this.umask & 511);
1153
+ this.createInode(path, INODE_TYPE.DIRECTORY, mode, 0);
1154
+ this.commitPending();
1155
+ return { status: 0, data: encoder.encode(path) };
1156
+ }
1157
+ // ========== Helpers ==========
1158
+ getDirectChildren(dirPath) {
1159
+ const prefix = dirPath === "/" ? "/" : dirPath + "/";
1160
+ const children = [];
1161
+ for (const path of this.pathIndex.keys()) {
1162
+ if (path === dirPath) continue;
1163
+ if (!path.startsWith(prefix)) continue;
1164
+ const rest = path.substring(prefix.length);
1165
+ if (!rest.includes("/")) {
1166
+ children.push(path);
1167
+ }
1168
+ }
1169
+ return children.sort();
1170
+ }
1171
+ getAllDescendants(dirPath) {
1172
+ const prefix = dirPath === "/" ? "/" : dirPath + "/";
1173
+ const descendants = [];
1174
+ for (const path of this.pathIndex.keys()) {
1175
+ if (path.startsWith(prefix)) descendants.push(path);
1176
+ }
1177
+ return descendants.sort((a, b) => {
1178
+ const da = a.split("/").length;
1179
+ const db = b.split("/").length;
1180
+ return db - da;
1181
+ });
1182
+ }
1183
+ ensureParent(path) {
1184
+ const lastSlash = path.lastIndexOf("/");
1185
+ if (lastSlash <= 0) return 0;
1186
+ const parentPath = path.substring(0, lastSlash);
1187
+ const parentIdx = this.pathIndex.get(parentPath);
1188
+ if (parentIdx === void 0) return CODE_TO_STATUS.ENOENT;
1189
+ const parentInode = this.readInode(parentIdx);
1190
+ if (parentInode.type !== INODE_TYPE.DIRECTORY) return CODE_TO_STATUS.ENOTDIR;
1191
+ return 0;
1192
+ }
1193
+ /** Clean up all fds owned by a tab */
1194
+ cleanupTab(tabId) {
1195
+ for (const [fd, entry] of this.fdTable) {
1196
+ if (entry.tabId === tabId) {
1197
+ this.fdTable.delete(fd);
1198
+ }
1199
+ }
1200
+ }
1201
+ /** Get all file paths and their data for OPFS sync */
1202
+ getAllFiles() {
1203
+ const files = [];
1204
+ for (const [path, idx] of this.pathIndex) {
1205
+ files.push({ path, idx });
1206
+ }
1207
+ return files;
1208
+ }
1209
+ /** Get file path for a file descriptor (used by OPFS sync for FD-based ops) */
1210
+ getPathForFd(fd) {
1211
+ const entry = this.fdTable.get(fd);
1212
+ if (!entry) return null;
1213
+ const inode = this.readInode(entry.inodeIdx);
1214
+ return this.readPath(inode.pathOffset, inode.pathLength);
1215
+ }
1216
+ /** Get file data by inode index */
1217
+ getInodeData(idx) {
1218
+ const inode = this.readInode(idx);
1219
+ const data = inode.size > 0 ? this.readData(inode.firstBlock, inode.blockCount, inode.size) : new Uint8Array(0);
1220
+ return { type: inode.type, data, mtime: inode.mtime };
1221
+ }
1222
+ flush() {
1223
+ this.handle.flush();
1224
+ }
1225
+ };
1226
+
1227
+ // src/protocol/opcodes.ts
1228
+ var OP = {
1229
+ READ: 1,
1230
+ WRITE: 2,
1231
+ UNLINK: 3,
1232
+ STAT: 4,
1233
+ LSTAT: 5,
1234
+ MKDIR: 6,
1235
+ RMDIR: 7,
1236
+ READDIR: 8,
1237
+ RENAME: 9,
1238
+ EXISTS: 10,
1239
+ TRUNCATE: 11,
1240
+ APPEND: 12,
1241
+ COPY: 13,
1242
+ ACCESS: 14,
1243
+ REALPATH: 15,
1244
+ CHMOD: 16,
1245
+ CHOWN: 17,
1246
+ UTIMES: 18,
1247
+ SYMLINK: 19,
1248
+ READLINK: 20,
1249
+ LINK: 21,
1250
+ OPEN: 22,
1251
+ CLOSE: 23,
1252
+ FREAD: 24,
1253
+ FWRITE: 25,
1254
+ FSTAT: 26,
1255
+ FTRUNCATE: 27,
1256
+ FSYNC: 28,
1257
+ OPENDIR: 29,
1258
+ MKDTEMP: 30
1259
+ };
1260
+ var encoder2 = new TextEncoder();
1261
+ var decoder2 = new TextDecoder();
1262
+ function decodeRequest(buf) {
1263
+ const view = new DataView(buf);
1264
+ const op = view.getUint32(0, true);
1265
+ const flags = view.getUint32(4, true);
1266
+ const pathLen = view.getUint32(8, true);
1267
+ const dataLen = view.getUint32(12, true);
1268
+ const bytes = new Uint8Array(buf);
1269
+ const path = decoder2.decode(bytes.subarray(16, 16 + pathLen));
1270
+ const data = dataLen > 0 ? bytes.subarray(16 + pathLen, 16 + pathLen + dataLen) : null;
1271
+ return { op, flags, path, data };
1272
+ }
1273
+ function encodeResponse(status, data) {
1274
+ const dataLen = data ? data.byteLength : 0;
1275
+ const buf = new ArrayBuffer(8 + dataLen);
1276
+ const view = new DataView(buf);
1277
+ view.setUint32(0, status, true);
1278
+ view.setUint32(4, dataLen, true);
1279
+ if (data) {
1280
+ new Uint8Array(buf).set(data, 8);
1281
+ }
1282
+ return buf;
1283
+ }
1284
+ function decodeSecondPath(data) {
1285
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1286
+ const pathLen = view.getUint32(0, true);
1287
+ return decoder2.decode(data.subarray(4, 4 + pathLen));
1288
+ }
1289
+
1290
+ // src/workers/server.worker.ts
1291
+ var engine = new VFSEngine();
1292
+ var ports = /* @__PURE__ */ new Map();
1293
+ var opfsSyncPort = null;
1294
+ var config = {
1295
+ root: "/",
1296
+ opfsSync: true,
1297
+ uid: 0,
1298
+ gid: 0,
1299
+ umask: 18,
1300
+ strictPermissions: false
1301
+ };
1302
+ function handleRequest(tabId, buffer) {
1303
+ const { op, flags, path, data } = decodeRequest(buffer);
1304
+ let result;
1305
+ switch (op) {
1306
+ case OP.READ:
1307
+ result = engine.read(path);
1308
+ break;
1309
+ case OP.WRITE:
1310
+ result = engine.write(path, data ?? new Uint8Array(0), flags);
1311
+ notifyOPFSSync("write", path, data);
1312
+ break;
1313
+ case OP.APPEND:
1314
+ result = engine.append(path, data ?? new Uint8Array(0));
1315
+ notifyOPFSSync("write", path, data);
1316
+ break;
1317
+ case OP.UNLINK:
1318
+ result = engine.unlink(path);
1319
+ notifyOPFSSync("delete", path);
1320
+ break;
1321
+ case OP.STAT:
1322
+ result = engine.stat(path);
1323
+ break;
1324
+ case OP.LSTAT:
1325
+ result = engine.lstat(path);
1326
+ break;
1327
+ case OP.MKDIR:
1328
+ result = engine.mkdir(path, flags);
1329
+ notifyOPFSSync("mkdir", path);
1330
+ break;
1331
+ case OP.RMDIR:
1332
+ result = engine.rmdir(path, flags);
1333
+ notifyOPFSSync("delete", path);
1334
+ break;
1335
+ case OP.READDIR:
1336
+ result = engine.readdir(path, flags);
1337
+ break;
1338
+ case OP.RENAME: {
1339
+ const newPath = data ? decodeSecondPath(data) : "";
1340
+ result = engine.rename(path, newPath);
1341
+ notifyOPFSSync("rename", path, void 0, newPath);
1342
+ break;
1343
+ }
1344
+ case OP.EXISTS:
1345
+ result = engine.exists(path);
1346
+ break;
1347
+ case OP.TRUNCATE: {
1348
+ const len = data ? new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0, true) : 0;
1349
+ result = engine.truncate(path, len);
1350
+ break;
1351
+ }
1352
+ case OP.COPY: {
1353
+ const destPath = data ? decodeSecondPath(data) : "";
1354
+ result = engine.copy(path, destPath, flags);
1355
+ break;
1356
+ }
1357
+ case OP.ACCESS:
1358
+ result = engine.access(path, flags);
1359
+ break;
1360
+ case OP.REALPATH:
1361
+ result = engine.realpath(path);
1362
+ break;
1363
+ case OP.CHMOD: {
1364
+ const mode = data ? new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0, true) : 0;
1365
+ result = engine.chmod(path, mode);
1366
+ break;
1367
+ }
1368
+ case OP.CHOWN: {
1369
+ if (!data || data.byteLength < 8) {
1370
+ result = { status: 7 };
1371
+ break;
1372
+ }
1373
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
1374
+ const uid = dv.getUint32(0, true);
1375
+ const gid = dv.getUint32(4, true);
1376
+ result = engine.chown(path, uid, gid);
1377
+ break;
1378
+ }
1379
+ case OP.UTIMES: {
1380
+ if (!data || data.byteLength < 16) {
1381
+ result = { status: 7 };
1382
+ break;
1383
+ }
1384
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
1385
+ const atime = dv.getFloat64(0, true);
1386
+ const mtime = dv.getFloat64(8, true);
1387
+ result = engine.utimes(path, atime, mtime);
1388
+ break;
1389
+ }
1390
+ case OP.SYMLINK: {
1391
+ const target = data ? new TextDecoder().decode(data) : "";
1392
+ result = engine.symlink(target, path);
1393
+ break;
1394
+ }
1395
+ case OP.READLINK:
1396
+ result = engine.readlink(path);
1397
+ break;
1398
+ case OP.LINK: {
1399
+ const newPath = data ? decodeSecondPath(data) : "";
1400
+ result = engine.link(path, newPath);
1401
+ break;
1402
+ }
1403
+ case OP.OPEN: {
1404
+ result = engine.open(path, flags, tabId);
1405
+ break;
1406
+ }
1407
+ case OP.CLOSE: {
1408
+ const fd = data ? new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0, true) : 0;
1409
+ result = engine.close(fd);
1410
+ break;
1411
+ }
1412
+ case OP.FREAD: {
1413
+ if (!data || data.byteLength < 12) {
1414
+ result = { status: 7 };
1415
+ break;
1416
+ }
1417
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
1418
+ const fd = dv.getUint32(0, true);
1419
+ const length = dv.getUint32(4, true);
1420
+ const pos = dv.getInt32(8, true);
1421
+ result = engine.fread(fd, length, pos === -1 ? null : pos);
1422
+ break;
1423
+ }
1424
+ case OP.FWRITE: {
1425
+ if (!data || data.byteLength < 8) {
1426
+ result = { status: 7 };
1427
+ break;
1428
+ }
1429
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
1430
+ const fd = dv.getUint32(0, true);
1431
+ const pos = dv.getInt32(4, true);
1432
+ const writeData = data.subarray(8);
1433
+ result = engine.fwrite(fd, writeData, pos === -1 ? null : pos);
1434
+ break;
1435
+ }
1436
+ case OP.FSTAT: {
1437
+ const fd = data ? new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0, true) : 0;
1438
+ result = engine.fstat(fd);
1439
+ break;
1440
+ }
1441
+ case OP.FTRUNCATE: {
1442
+ if (!data || data.byteLength < 8) {
1443
+ result = { status: 7 };
1444
+ break;
1445
+ }
1446
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
1447
+ const fd = dv.getUint32(0, true);
1448
+ const len = dv.getUint32(4, true);
1449
+ result = engine.ftruncate(fd, len);
1450
+ break;
1451
+ }
1452
+ case OP.FSYNC:
1453
+ result = engine.fsync();
1454
+ break;
1455
+ case OP.OPENDIR:
1456
+ result = engine.opendir(path, tabId);
1457
+ break;
1458
+ case OP.MKDTEMP:
1459
+ result = engine.mkdtemp(path);
1460
+ break;
1461
+ default:
1462
+ result = { status: 7 };
1463
+ }
1464
+ const responseData = result.data instanceof Uint8Array ? result.data : void 0;
1465
+ return encodeResponse(result.status, responseData);
1466
+ }
1467
+ function notifyOPFSSync(op, path, data, newPath) {
1468
+ if (!opfsSyncPort) return;
1469
+ const msg = { op, path, ts: Date.now() };
1470
+ const transfers = [];
1471
+ if (op === "write" && data) {
1472
+ const copy = data.slice().buffer;
1473
+ msg.data = copy;
1474
+ transfers.push(copy);
1475
+ }
1476
+ if (op === "rename" && newPath) {
1477
+ msg.newPath = newPath;
1478
+ }
1479
+ opfsSyncPort.postMessage(msg, transfers);
1480
+ }
1481
+ function setupClientPort(tabId, port) {
1482
+ ports.set(tabId, port);
1483
+ port.onmessage = (e) => {
1484
+ const { buffer, id } = e.data;
1485
+ if (buffer instanceof ArrayBuffer) {
1486
+ const response = handleRequest(tabId, buffer);
1487
+ port.postMessage({ id, buffer: response }, [response]);
1488
+ }
1489
+ };
1490
+ port.start();
1491
+ }
1492
+ function onTabLost(tabId) {
1493
+ engine.cleanupTab(tabId);
1494
+ const port = ports.get(tabId);
1495
+ if (port) {
1496
+ port.close();
1497
+ ports.delete(tabId);
1498
+ }
1499
+ }
1500
+ async function init(initData) {
1501
+ config = initData;
1502
+ let rootDir = await navigator.storage.getDirectory();
1503
+ if (config.root && config.root !== "/") {
1504
+ const segments = config.root.split("/").filter(Boolean);
1505
+ for (const segment of segments) {
1506
+ rootDir = await rootDir.getDirectoryHandle(segment, { create: true });
1507
+ }
1508
+ }
1509
+ const vfsFileHandle = await rootDir.getFileHandle(".vfs.bin", { create: true });
1510
+ const vfsHandle = await vfsFileHandle.createSyncAccessHandle();
1511
+ engine.init(vfsHandle, {
1512
+ uid: config.uid,
1513
+ gid: config.gid,
1514
+ umask: config.umask,
1515
+ strictPermissions: config.strictPermissions
1516
+ });
1517
+ }
1518
+ self.onmessage = async (e) => {
1519
+ const msg = e.data;
1520
+ if (msg.type === "init") {
1521
+ await init(msg.config);
1522
+ self.postMessage({ type: "ready" });
1523
+ return;
1524
+ }
1525
+ if (msg.type === "port") {
1526
+ setupClientPort(msg.tabId, msg.port);
1527
+ return;
1528
+ }
1529
+ if (msg.type === "tab-lost") {
1530
+ onTabLost(msg.tabId);
1531
+ return;
1532
+ }
1533
+ if (msg.type === "opfs-sync-port") {
1534
+ opfsSyncPort = msg.port;
1535
+ opfsSyncPort.start();
1536
+ return;
1537
+ }
1538
+ if (msg.buffer instanceof ArrayBuffer) {
1539
+ const tabId = msg.tabId || "local";
1540
+ const response = handleRequest(tabId, msg.buffer);
1541
+ self.postMessage(
1542
+ { id: msg.id, buffer: response },
1543
+ [response]
1544
+ );
1545
+ }
1546
+ };
1547
+ //# sourceMappingURL=server.worker.js.map