@componentor/fs 3.0.13 → 3.0.15
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/README.md +13 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/workers/async-relay.worker.js.map +1 -1
- package/dist/workers/repair.worker.js +11 -7
- package/dist/workers/repair.worker.js.map +1 -1
- package/dist/workers/server.worker.js +8 -1
- package/dist/workers/server.worker.js.map +1 -1
- package/dist/workers/sync-relay.worker.js +26 -3
- package/dist/workers/sync-relay.worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/src/protocol/opcodes.ts","../../src/src/workers/async-relay.worker.ts"],"sourcesContent":["/**\n * Binary protocol operation codes and header encoding/decoding.\n * All inter-worker messages use this minimal binary protocol — no JSON, no strings.\n */\n\n// Operation codes\nexport const OP = {\n READ: 1,\n WRITE: 2,\n UNLINK: 3,\n STAT: 4,\n LSTAT: 5,\n MKDIR: 6,\n RMDIR: 7,\n READDIR: 8,\n RENAME: 9,\n EXISTS: 10,\n TRUNCATE: 11,\n APPEND: 12,\n COPY: 13,\n ACCESS: 14,\n REALPATH: 15,\n CHMOD: 16,\n CHOWN: 17,\n UTIMES: 18,\n SYMLINK: 19,\n READLINK: 20,\n LINK: 21,\n OPEN: 22,\n CLOSE: 23,\n FREAD: 24,\n FWRITE: 25,\n FSTAT: 26,\n FTRUNCATE: 27,\n FSYNC: 28,\n OPENDIR: 29,\n MKDTEMP: 30,\n} as const;\n\nexport type OpCode = (typeof OP)[keyof typeof OP];\n\n// Response status codes\nexport const STATUS = {\n OK: 0,\n ENOENT: 1,\n EEXIST: 2,\n EISDIR: 3,\n ENOTDIR: 4,\n ENOTEMPTY: 5,\n EACCES: 6,\n EINVAL: 7,\n EBADF: 8,\n ELOOP: 9,\n ENOSPC: 10,\n} as const;\n\n// SAB layout offsets\nexport const SAB_OFFSETS = {\n CONTROL: 0, // Int32 - signal (0=idle, 1=request, 2=response, 3=chunk, 4=ack)\n OPCODE: 4, // Int32 - operation code\n STATUS: 8, // Int32 - response status / error\n CHUNK_LEN: 12, // Int32 - bytes in this chunk\n TOTAL_LEN: 16, // BigUint64 - full data size across all chunks\n CHUNK_IDX: 24, // Int32 - 0-based chunk index\n RESERVED: 28, // Int32 - reserved\n HEADER_SIZE: 32, // Data payload starts here\n} as const;\n\n// SAB control signals\nexport const SIGNAL = {\n IDLE: 0,\n REQUEST: 1,\n RESPONSE: 2,\n CHUNK: 3,\n CHUNK_ACK: 4,\n} as const;\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/**\n * Encode a request into an ArrayBuffer for MessageChannel transfer.\n *\n * Request format (16-byte header + path + data):\n * bytes 0-3: operation (uint32)\n * bytes 4-7: flags (uint32)\n * bytes 8-11: pathLen (uint32)\n * bytes 12-15: dataLen (uint32)\n * bytes 16+: path (UTF-8)\n * bytes 16+pathLen: data payload\n */\nexport function encodeRequest(\n op: number,\n path: string,\n flags: number = 0,\n data?: Uint8Array\n): ArrayBuffer {\n const pathBytes = encoder.encode(path);\n const dataLen = data ? data.byteLength : 0;\n const totalLen = 16 + pathBytes.byteLength + dataLen;\n const buf = new ArrayBuffer(totalLen);\n const view = new DataView(buf);\n\n view.setUint32(0, op, true);\n view.setUint32(4, flags, true);\n view.setUint32(8, pathBytes.byteLength, true);\n view.setUint32(12, dataLen, true);\n\n const bytes = new Uint8Array(buf);\n bytes.set(pathBytes, 16);\n if (data) {\n bytes.set(data, 16 + pathBytes.byteLength);\n }\n\n return buf;\n}\n\n/**\n * Decode a request ArrayBuffer.\n */\nexport function decodeRequest(buf: ArrayBuffer): {\n op: number;\n flags: number;\n path: string;\n data: Uint8Array | null;\n} {\n const view = new DataView(buf);\n const op = view.getUint32(0, true);\n const flags = view.getUint32(4, true);\n const pathLen = view.getUint32(8, true);\n const dataLen = view.getUint32(12, true);\n\n const bytes = new Uint8Array(buf);\n const path = decoder.decode(bytes.subarray(16, 16 + pathLen));\n const data = dataLen > 0\n ? bytes.subarray(16 + pathLen, 16 + pathLen + dataLen)\n : null;\n\n return { op, flags, path, data };\n}\n\n/**\n * Encode a response into an ArrayBuffer.\n *\n * Response format (8-byte header + data):\n * bytes 0-3: status (uint32)\n * bytes 4-7: dataLen (uint32)\n * bytes 8+: data payload\n */\nexport function encodeResponse(status: number, data?: Uint8Array): ArrayBuffer {\n const dataLen = data ? data.byteLength : 0;\n const buf = new ArrayBuffer(8 + dataLen);\n const view = new DataView(buf);\n\n view.setUint32(0, status, true);\n view.setUint32(4, dataLen, true);\n\n if (data) {\n new Uint8Array(buf).set(data, 8);\n }\n\n return buf;\n}\n\n/**\n * Decode a response ArrayBuffer.\n */\nexport function decodeResponse(buf: ArrayBuffer): {\n status: number;\n data: Uint8Array | null;\n} {\n const view = new DataView(buf);\n const status = view.getUint32(0, true);\n const dataLen = view.getUint32(4, true);\n\n const data = dataLen > 0\n ? new Uint8Array(buf, 8, dataLen)\n : null;\n\n return { status, data };\n}\n\n/**\n * Encode a two-path request (rename, copy, symlink, link).\n * Data payload contains: [pathLen2:u32] [path2 bytes]\n */\nexport function encodeTwoPathRequest(\n op: number,\n path1: string,\n path2: string,\n flags: number = 0\n): ArrayBuffer {\n const path2Bytes = encoder.encode(path2);\n const payload = new Uint8Array(4 + path2Bytes.byteLength);\n const pv = new DataView(payload.buffer);\n pv.setUint32(0, path2Bytes.byteLength, true);\n payload.set(path2Bytes, 4);\n\n return encodeRequest(op, path1, flags, payload);\n}\n\n/**\n * Decode the second path from a two-path request's data payload.\n */\nexport function decodeSecondPath(data: Uint8Array): string {\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const pathLen = view.getUint32(0, true);\n return decoder.decode(data.subarray(4, 4 + pathLen));\n}\n","/**\n * Async Relay Worker — handles encoding/decoding off the main thread.\n *\n * Operates in one of two modes:\n *\n * LEADER MODE (primary tab):\n * - Communicates with own sync-relay via asyncSAB (SharedArrayBuffer)\n * - Uses Atomics.wait to block until sync-relay writes response\n * - No MessagePort hop — direct SAB-based communication\n *\n * FOLLOWER MODE (secondary tabs):\n * - Communicates with leader's sync-relay via MessagePort\n * - Same protocol as current server port communication\n * - Port is obtained through service worker tab discovery\n *\n * Both modes encode requests the same way (binary protocol) and decode\n * responses the same way. Only the transport differs.\n */\n\nimport {\n SAB_OFFSETS, SIGNAL,\n encodeRequest, encodeTwoPathRequest, decodeResponse,\n OP,\n} from '../protocol/opcodes.js';\n\nconst encoder = new TextEncoder();\nconst HEADER_SIZE = SAB_OFFSETS.HEADER_SIZE;\n\n// ========== Leader mode: asyncSAB communication ==========\n\nlet asyncSab: SharedArrayBuffer | null = null;\nlet asyncCtrl: Int32Array | null = null;\n\n// Wake hint: sync-relay's SAB ctrl — notify to wake leader loop immediately\nlet wakeCtrl: Int32Array | null = null;\n\n/**\n * Send a request via asyncSAB and block until response (leader mode).\n */\nfunction sabRequest(requestBuf: ArrayBuffer): { status: number; data: Uint8Array | null } {\n const maxChunk = asyncSab!.byteLength - HEADER_SIZE;\n const requestBytes = new Uint8Array(requestBuf);\n const totalLenView = new BigUint64Array(asyncSab!, SAB_OFFSETS.TOTAL_LEN, 1);\n\n // Write request to asyncSAB\n if (requestBytes.byteLength <= maxChunk) {\n // Fast path: single chunk\n new Uint8Array(asyncSab!, HEADER_SIZE, requestBytes.byteLength).set(requestBytes);\n Atomics.store(asyncCtrl!, 3, requestBytes.byteLength);\n Atomics.store(totalLenView, 0, BigInt(requestBytes.byteLength));\n Atomics.store(asyncCtrl!, 0, SIGNAL.REQUEST);\n Atomics.notify(asyncCtrl!, 0);\n // Wake the leader loop (which waits on syncSAB's ctrl, not asyncCtrl)\n if (wakeCtrl) Atomics.notify(wakeCtrl, 0);\n } else {\n // Multi-chunk request\n let sent = 0;\n while (sent < requestBytes.byteLength) {\n const chunkSize = Math.min(maxChunk, requestBytes.byteLength - sent);\n new Uint8Array(asyncSab!, HEADER_SIZE, chunkSize).set(\n requestBytes.subarray(sent, sent + chunkSize)\n );\n Atomics.store(asyncCtrl!, 3, chunkSize);\n Atomics.store(totalLenView, 0, BigInt(requestBytes.byteLength));\n Atomics.store(asyncCtrl!, 6, Math.floor(sent / maxChunk));\n\n if (sent === 0) {\n Atomics.store(asyncCtrl!, 0, SIGNAL.REQUEST);\n } else {\n Atomics.store(asyncCtrl!, 0, SIGNAL.CHUNK);\n }\n Atomics.notify(asyncCtrl!, 0);\n // Wake leader loop on first chunk\n if (sent === 0 && wakeCtrl) Atomics.notify(wakeCtrl, 0);\n\n sent += chunkSize;\n if (sent < requestBytes.byteLength) {\n // Wait for sync-relay to ack chunk\n Atomics.wait(asyncCtrl!, 0, sent === chunkSize ? SIGNAL.REQUEST : SIGNAL.CHUNK);\n }\n }\n }\n\n // Wait for response from sync-relay\n Atomics.wait(asyncCtrl!, 0, SIGNAL.REQUEST);\n\n // Read response (may be multi-chunk)\n const signal = Atomics.load(asyncCtrl!, 0);\n const respChunkLen = Atomics.load(asyncCtrl!, 3);\n const respTotalLen = Number(Atomics.load(totalLenView, 0));\n\n let responseBytes: Uint8Array;\n\n if (signal === SIGNAL.RESPONSE && respTotalLen <= maxChunk) {\n // Single chunk response\n responseBytes = new Uint8Array(asyncSab!, HEADER_SIZE, respChunkLen).slice();\n } else {\n // Multi-chunk response\n responseBytes = new Uint8Array(respTotalLen);\n let received = 0;\n\n responseBytes.set(new Uint8Array(asyncSab!, HEADER_SIZE, respChunkLen), 0);\n received += respChunkLen;\n\n while (received < respTotalLen) {\n Atomics.store(asyncCtrl!, 0, SIGNAL.CHUNK_ACK);\n Atomics.notify(asyncCtrl!, 0);\n Atomics.wait(asyncCtrl!, 0, SIGNAL.CHUNK_ACK);\n\n const nextLen = Atomics.load(asyncCtrl!, 3);\n responseBytes.set(new Uint8Array(asyncSab!, HEADER_SIZE, nextLen), received);\n received += nextLen;\n }\n }\n\n // Reset to IDLE and notify sync-relay so it can proceed\n Atomics.store(asyncCtrl!, 0, SIGNAL.IDLE);\n Atomics.notify(asyncCtrl!, 0);\n\n return decodeResponse(responseBytes.buffer as ArrayBuffer);\n}\n\n// ========== Follower mode: MessagePort communication ==========\n\nlet leaderPort: MessagePort | null = null;\nconst pending = new Map<string, (response: ArrayBuffer) => void>();\nlet requestId = 0;\n\nfunction nextId(): string {\n return 'a' + (requestId++);\n}\n\nfunction portRequest(buffer: ArrayBuffer): Promise<{ status: number; data: Uint8Array | null }> {\n return new Promise(resolve => {\n const id = nextId();\n pending.set(id, (respBuf) => {\n resolve(decodeResponse(respBuf));\n });\n leaderPort!.postMessage({ id, buffer }, [buffer]);\n });\n}\n\n// ========== Unified request dispatch ==========\n\nasync function sendRequest(reqBuffer: ArrayBuffer): Promise<{ status: number; data: Uint8Array | null }> {\n if (asyncSab) {\n // Leader mode: SAB-based (synchronous in worker, wrapped in promise for uniform API)\n return sabRequest(reqBuffer);\n } else if (leaderPort) {\n // Follower mode: MessagePort-based\n return portRequest(reqBuffer);\n }\n return { status: 7, data: null }; // EINVAL — no channel\n}\n\n// ========== Main thread message handling ==========\n\nself.onmessage = async (e: MessageEvent) => {\n const msg = e.data;\n\n // --- Leader mode init (with SAB) ---\n if (msg.type === 'init-leader') {\n asyncSab = msg.asyncSab;\n asyncCtrl = new Int32Array(msg.asyncSab, 0, 8);\n if (msg.wakeSab) {\n wakeCtrl = new Int32Array(msg.wakeSab, 0, 1);\n }\n return;\n }\n\n // --- Port mode init (no SAB: communicate with sync-relay via MessagePort) ---\n if (msg.type === 'init-port') {\n const port = msg.port ?? e.ports[0];\n if (port) {\n leaderPort = port;\n leaderPort!.onmessage = (ev: MessageEvent) => {\n const { id, buffer } = ev.data;\n const resolve = pending.get(id);\n if (resolve) {\n pending.delete(id);\n resolve(buffer);\n }\n };\n leaderPort!.start();\n }\n return;\n }\n\n // --- Follower mode init ---\n if (msg.type === 'init-follower') {\n // Nothing to do yet — port arrives separately\n return;\n }\n\n // --- Leader port (follower mode) ---\n if (msg.type === 'leader-port') {\n leaderPort = msg.port;\n leaderPort!.onmessage = (ev: MessageEvent) => {\n const { id, buffer } = ev.data;\n const resolve = pending.get(id);\n if (resolve) {\n pending.delete(id);\n resolve(buffer);\n }\n };\n leaderPort!.start();\n return;\n }\n\n // --- Handle async fs operation request from main thread ---\n if (msg.type === 'request') {\n const { callId, op, path, data, flags, path2, fdArgs } = msg;\n\n try {\n let reqBuffer: ArrayBuffer;\n\n // Encode request based on operation type\n if (path2 !== undefined) {\n // Two-path operations (rename, copy, symlink, link)\n reqBuffer = encodeTwoPathRequest(op, path, path2, flags ?? 0);\n } else if (fdArgs) {\n // File descriptor operations\n reqBuffer = encodeFdRequest(op, fdArgs);\n } else {\n // Standard single-path operations\n const encodedData = encodeData(data);\n reqBuffer = encodeRequest(op, path ?? '', flags ?? 0, encodedData ?? undefined);\n }\n\n const { status, data: respData } = await sendRequest(reqBuffer);\n\n (self as unknown as Worker).postMessage(\n { type: 'response', callId, status, data: respData },\n respData ? [respData.buffer] : []\n );\n } catch (err) {\n (self as unknown as Worker).postMessage({\n type: 'response',\n callId,\n status: 7, // EINVAL\n data: null,\n error: (err as Error).message,\n });\n }\n }\n};\n\n// ========== Encoding helpers ==========\n\nfunction encodeData(data: unknown): Uint8Array | null {\n if (data === null || data === undefined) return null;\n if (data instanceof Uint8Array) return data;\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n if (typeof data === 'string') return encoder.encode(data);\n return null;\n}\n\nfunction encodeFdRequest(op: number, args: { fd: number; length?: number; position?: number; data?: Uint8Array }): ArrayBuffer {\n switch (op) {\n case OP.FREAD: {\n const buf = new Uint8Array(12);\n const view = new DataView(buf.buffer);\n view.setUint32(0, args.fd, true);\n view.setUint32(4, args.length ?? 0, true);\n view.setInt32(8, args.position ?? -1, true);\n return encodeRequest(op, '', 0, buf);\n }\n case OP.FWRITE: {\n const writeData = args.data ?? new Uint8Array(0);\n const buf = new Uint8Array(8 + writeData.byteLength);\n const view = new DataView(buf.buffer);\n view.setUint32(0, args.fd, true);\n view.setInt32(4, args.position ?? -1, true);\n buf.set(writeData, 8);\n return encodeRequest(op, '', 0, buf);\n }\n case OP.FSTAT:\n case OP.CLOSE: {\n const buf = new Uint8Array(4);\n new DataView(buf.buffer).setUint32(0, args.fd, true);\n return encodeRequest(op, '', 0, buf);\n }\n case OP.FTRUNCATE: {\n const buf = new Uint8Array(8);\n const view = new DataView(buf.buffer);\n view.setUint32(0, args.fd, true);\n view.setUint32(4, args.length ?? 0, true);\n return encodeRequest(op, '', 0, buf);\n }\n case OP.FSYNC:\n return encodeRequest(op, '', 0);\n default:\n return encodeRequest(op, '', 0);\n }\n}\n"],"mappings":"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","names":["encoder"]}
|
|
1
|
+
{"version":3,"sources":["../../src/src/protocol/opcodes.ts","../../src/src/workers/async-relay.worker.ts"],"sourcesContent":["/**\n * Binary protocol operation codes and header encoding/decoding.\n * All inter-worker messages use this minimal binary protocol — no JSON, no strings.\n */\n\n// Operation codes\nexport const OP = {\n READ: 1,\n WRITE: 2,\n UNLINK: 3,\n STAT: 4,\n LSTAT: 5,\n MKDIR: 6,\n RMDIR: 7,\n READDIR: 8,\n RENAME: 9,\n EXISTS: 10,\n TRUNCATE: 11,\n APPEND: 12,\n COPY: 13,\n ACCESS: 14,\n REALPATH: 15,\n CHMOD: 16,\n CHOWN: 17,\n UTIMES: 18,\n SYMLINK: 19,\n READLINK: 20,\n LINK: 21,\n OPEN: 22,\n CLOSE: 23,\n FREAD: 24,\n FWRITE: 25,\n FSTAT: 26,\n FTRUNCATE: 27,\n FSYNC: 28,\n OPENDIR: 29,\n MKDTEMP: 30,\n} as const;\n\nexport type OpCode = (typeof OP)[keyof typeof OP];\n\n// Response status codes\nexport const STATUS = {\n OK: 0,\n ENOENT: 1,\n EEXIST: 2,\n EISDIR: 3,\n ENOTDIR: 4,\n ENOTEMPTY: 5,\n EACCES: 6,\n EINVAL: 7,\n EBADF: 8,\n ELOOP: 9,\n ENOSPC: 10,\n} as const;\n\n// SAB layout offsets\nexport const SAB_OFFSETS = {\n CONTROL: 0, // Int32 - signal (0=idle, 1=request, 2=response, 3=chunk, 4=ack)\n OPCODE: 4, // Int32 - operation code\n STATUS: 8, // Int32 - response status / error\n CHUNK_LEN: 12, // Int32 - bytes in this chunk\n TOTAL_LEN: 16, // BigUint64 - full data size across all chunks\n CHUNK_IDX: 24, // Int32 - 0-based chunk index\n RESERVED: 28, // Int32 - reserved\n HEADER_SIZE: 32, // Data payload starts here\n} as const;\n\n// SAB control signals\nexport const SIGNAL = {\n IDLE: 0,\n REQUEST: 1,\n RESPONSE: 2,\n CHUNK: 3,\n CHUNK_ACK: 4,\n} as const;\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/**\n * Encode a request into an ArrayBuffer for MessageChannel transfer.\n *\n * Request format (16-byte header + path + data):\n * bytes 0-3: operation (uint32)\n * bytes 4-7: flags (uint32)\n * bytes 8-11: pathLen (uint32)\n * bytes 12-15: dataLen (uint32)\n * bytes 16+: path (UTF-8)\n * bytes 16+pathLen: data payload\n */\nexport function encodeRequest(\n op: number,\n path: string,\n flags: number = 0,\n data?: Uint8Array\n): ArrayBuffer {\n const pathBytes = encoder.encode(path);\n const dataLen = data ? data.byteLength : 0;\n const totalLen = 16 + pathBytes.byteLength + dataLen;\n const buf = new ArrayBuffer(totalLen);\n const view = new DataView(buf);\n\n view.setUint32(0, op, true);\n view.setUint32(4, flags, true);\n view.setUint32(8, pathBytes.byteLength, true);\n view.setUint32(12, dataLen, true);\n\n const bytes = new Uint8Array(buf);\n bytes.set(pathBytes, 16);\n if (data) {\n bytes.set(data, 16 + pathBytes.byteLength);\n }\n\n return buf;\n}\n\n/**\n * Decode a request ArrayBuffer.\n */\nexport function decodeRequest(buf: ArrayBuffer): {\n op: number;\n flags: number;\n path: string;\n data: Uint8Array | null;\n} {\n // Minimum header: 16 bytes (op + flags + pathLen + dataLen)\n if (buf.byteLength < 16) {\n throw new Error(`Request buffer too small: ${buf.byteLength} < 16 bytes (possible SAB race)`);\n }\n\n const view = new DataView(buf);\n const op = view.getUint32(0, true);\n const flags = view.getUint32(4, true);\n const pathLen = view.getUint32(8, true);\n const dataLen = view.getUint32(12, true);\n\n // Validate payload fits in buffer\n const expectedMin = 16 + pathLen + dataLen;\n if (buf.byteLength < expectedMin) {\n throw new Error(`Request buffer truncated: ${buf.byteLength} < ${expectedMin} bytes (op=${op}, pathLen=${pathLen}, dataLen=${dataLen})`);\n }\n\n const bytes = new Uint8Array(buf);\n const path = decoder.decode(bytes.subarray(16, 16 + pathLen));\n const data = dataLen > 0\n ? bytes.subarray(16 + pathLen, 16 + pathLen + dataLen)\n : null;\n\n return { op, flags, path, data };\n}\n\n/**\n * Encode a response into an ArrayBuffer.\n *\n * Response format (8-byte header + data):\n * bytes 0-3: status (uint32)\n * bytes 4-7: dataLen (uint32)\n * bytes 8+: data payload\n */\nexport function encodeResponse(status: number, data?: Uint8Array): ArrayBuffer {\n const dataLen = data ? data.byteLength : 0;\n const buf = new ArrayBuffer(8 + dataLen);\n const view = new DataView(buf);\n\n view.setUint32(0, status, true);\n view.setUint32(4, dataLen, true);\n\n if (data) {\n new Uint8Array(buf).set(data, 8);\n }\n\n return buf;\n}\n\n/**\n * Decode a response ArrayBuffer.\n */\nexport function decodeResponse(buf: ArrayBuffer): {\n status: number;\n data: Uint8Array | null;\n} {\n const view = new DataView(buf);\n const status = view.getUint32(0, true);\n const dataLen = view.getUint32(4, true);\n\n const data = dataLen > 0\n ? new Uint8Array(buf, 8, dataLen)\n : null;\n\n return { status, data };\n}\n\n/**\n * Encode a two-path request (rename, copy, symlink, link).\n * Data payload contains: [pathLen2:u32] [path2 bytes]\n */\nexport function encodeTwoPathRequest(\n op: number,\n path1: string,\n path2: string,\n flags: number = 0\n): ArrayBuffer {\n const path2Bytes = encoder.encode(path2);\n const payload = new Uint8Array(4 + path2Bytes.byteLength);\n const pv = new DataView(payload.buffer);\n pv.setUint32(0, path2Bytes.byteLength, true);\n payload.set(path2Bytes, 4);\n\n return encodeRequest(op, path1, flags, payload);\n}\n\n/**\n * Decode the second path from a two-path request's data payload.\n */\nexport function decodeSecondPath(data: Uint8Array): string {\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const pathLen = view.getUint32(0, true);\n return decoder.decode(data.subarray(4, 4 + pathLen));\n}\n","/**\n * Async Relay Worker — handles encoding/decoding off the main thread.\n *\n * Operates in one of two modes:\n *\n * LEADER MODE (primary tab):\n * - Communicates with own sync-relay via asyncSAB (SharedArrayBuffer)\n * - Uses Atomics.wait to block until sync-relay writes response\n * - No MessagePort hop — direct SAB-based communication\n *\n * FOLLOWER MODE (secondary tabs):\n * - Communicates with leader's sync-relay via MessagePort\n * - Same protocol as current server port communication\n * - Port is obtained through service worker tab discovery\n *\n * Both modes encode requests the same way (binary protocol) and decode\n * responses the same way. Only the transport differs.\n */\n\nimport {\n SAB_OFFSETS, SIGNAL,\n encodeRequest, encodeTwoPathRequest, decodeResponse,\n OP,\n} from '../protocol/opcodes.js';\n\nconst encoder = new TextEncoder();\nconst HEADER_SIZE = SAB_OFFSETS.HEADER_SIZE;\n\n// ========== Leader mode: asyncSAB communication ==========\n\nlet asyncSab: SharedArrayBuffer | null = null;\nlet asyncCtrl: Int32Array | null = null;\n\n// Wake hint: sync-relay's SAB ctrl — notify to wake leader loop immediately\nlet wakeCtrl: Int32Array | null = null;\n\n/**\n * Send a request via asyncSAB and block until response (leader mode).\n */\nfunction sabRequest(requestBuf: ArrayBuffer): { status: number; data: Uint8Array | null } {\n const maxChunk = asyncSab!.byteLength - HEADER_SIZE;\n const requestBytes = new Uint8Array(requestBuf);\n const totalLenView = new BigUint64Array(asyncSab!, SAB_OFFSETS.TOTAL_LEN, 1);\n\n // Write request to asyncSAB\n if (requestBytes.byteLength <= maxChunk) {\n // Fast path: single chunk\n new Uint8Array(asyncSab!, HEADER_SIZE, requestBytes.byteLength).set(requestBytes);\n Atomics.store(asyncCtrl!, 3, requestBytes.byteLength);\n Atomics.store(totalLenView, 0, BigInt(requestBytes.byteLength));\n Atomics.store(asyncCtrl!, 0, SIGNAL.REQUEST);\n Atomics.notify(asyncCtrl!, 0);\n // Wake the leader loop (which waits on syncSAB's ctrl, not asyncCtrl)\n if (wakeCtrl) Atomics.notify(wakeCtrl, 0);\n } else {\n // Multi-chunk request\n let sent = 0;\n while (sent < requestBytes.byteLength) {\n const chunkSize = Math.min(maxChunk, requestBytes.byteLength - sent);\n new Uint8Array(asyncSab!, HEADER_SIZE, chunkSize).set(\n requestBytes.subarray(sent, sent + chunkSize)\n );\n Atomics.store(asyncCtrl!, 3, chunkSize);\n Atomics.store(totalLenView, 0, BigInt(requestBytes.byteLength));\n Atomics.store(asyncCtrl!, 6, Math.floor(sent / maxChunk));\n\n if (sent === 0) {\n Atomics.store(asyncCtrl!, 0, SIGNAL.REQUEST);\n } else {\n Atomics.store(asyncCtrl!, 0, SIGNAL.CHUNK);\n }\n Atomics.notify(asyncCtrl!, 0);\n // Wake leader loop on first chunk\n if (sent === 0 && wakeCtrl) Atomics.notify(wakeCtrl, 0);\n\n sent += chunkSize;\n if (sent < requestBytes.byteLength) {\n // Wait for sync-relay to ack chunk\n Atomics.wait(asyncCtrl!, 0, sent === chunkSize ? SIGNAL.REQUEST : SIGNAL.CHUNK);\n }\n }\n }\n\n // Wait for response from sync-relay\n Atomics.wait(asyncCtrl!, 0, SIGNAL.REQUEST);\n\n // Read response (may be multi-chunk)\n const signal = Atomics.load(asyncCtrl!, 0);\n const respChunkLen = Atomics.load(asyncCtrl!, 3);\n const respTotalLen = Number(Atomics.load(totalLenView, 0));\n\n let responseBytes: Uint8Array;\n\n if (signal === SIGNAL.RESPONSE && respTotalLen <= maxChunk) {\n // Single chunk response\n responseBytes = new Uint8Array(asyncSab!, HEADER_SIZE, respChunkLen).slice();\n } else {\n // Multi-chunk response\n responseBytes = new Uint8Array(respTotalLen);\n let received = 0;\n\n responseBytes.set(new Uint8Array(asyncSab!, HEADER_SIZE, respChunkLen), 0);\n received += respChunkLen;\n\n while (received < respTotalLen) {\n Atomics.store(asyncCtrl!, 0, SIGNAL.CHUNK_ACK);\n Atomics.notify(asyncCtrl!, 0);\n Atomics.wait(asyncCtrl!, 0, SIGNAL.CHUNK_ACK);\n\n const nextLen = Atomics.load(asyncCtrl!, 3);\n responseBytes.set(new Uint8Array(asyncSab!, HEADER_SIZE, nextLen), received);\n received += nextLen;\n }\n }\n\n // Reset to IDLE and notify sync-relay so it can proceed\n Atomics.store(asyncCtrl!, 0, SIGNAL.IDLE);\n Atomics.notify(asyncCtrl!, 0);\n\n return decodeResponse(responseBytes.buffer as ArrayBuffer);\n}\n\n// ========== Follower mode: MessagePort communication ==========\n\nlet leaderPort: MessagePort | null = null;\nconst pending = new Map<string, (response: ArrayBuffer) => void>();\nlet requestId = 0;\n\nfunction nextId(): string {\n return 'a' + (requestId++);\n}\n\nfunction portRequest(buffer: ArrayBuffer): Promise<{ status: number; data: Uint8Array | null }> {\n return new Promise(resolve => {\n const id = nextId();\n pending.set(id, (respBuf) => {\n resolve(decodeResponse(respBuf));\n });\n leaderPort!.postMessage({ id, buffer }, [buffer]);\n });\n}\n\n// ========== Unified request dispatch ==========\n\nasync function sendRequest(reqBuffer: ArrayBuffer): Promise<{ status: number; data: Uint8Array | null }> {\n if (asyncSab) {\n // Leader mode: SAB-based (synchronous in worker, wrapped in promise for uniform API)\n return sabRequest(reqBuffer);\n } else if (leaderPort) {\n // Follower mode: MessagePort-based\n return portRequest(reqBuffer);\n }\n return { status: 7, data: null }; // EINVAL — no channel\n}\n\n// ========== Main thread message handling ==========\n\nself.onmessage = async (e: MessageEvent) => {\n const msg = e.data;\n\n // --- Leader mode init (with SAB) ---\n if (msg.type === 'init-leader') {\n asyncSab = msg.asyncSab;\n asyncCtrl = new Int32Array(msg.asyncSab, 0, 8);\n if (msg.wakeSab) {\n wakeCtrl = new Int32Array(msg.wakeSab, 0, 1);\n }\n return;\n }\n\n // --- Port mode init (no SAB: communicate with sync-relay via MessagePort) ---\n if (msg.type === 'init-port') {\n const port = msg.port ?? e.ports[0];\n if (port) {\n leaderPort = port;\n leaderPort!.onmessage = (ev: MessageEvent) => {\n const { id, buffer } = ev.data;\n const resolve = pending.get(id);\n if (resolve) {\n pending.delete(id);\n resolve(buffer);\n }\n };\n leaderPort!.start();\n }\n return;\n }\n\n // --- Follower mode init ---\n if (msg.type === 'init-follower') {\n // Nothing to do yet — port arrives separately\n return;\n }\n\n // --- Leader port (follower mode) ---\n if (msg.type === 'leader-port') {\n leaderPort = msg.port;\n leaderPort!.onmessage = (ev: MessageEvent) => {\n const { id, buffer } = ev.data;\n const resolve = pending.get(id);\n if (resolve) {\n pending.delete(id);\n resolve(buffer);\n }\n };\n leaderPort!.start();\n return;\n }\n\n // --- Handle async fs operation request from main thread ---\n if (msg.type === 'request') {\n const { callId, op, path, data, flags, path2, fdArgs } = msg;\n\n try {\n let reqBuffer: ArrayBuffer;\n\n // Encode request based on operation type\n if (path2 !== undefined) {\n // Two-path operations (rename, copy, symlink, link)\n reqBuffer = encodeTwoPathRequest(op, path, path2, flags ?? 0);\n } else if (fdArgs) {\n // File descriptor operations\n reqBuffer = encodeFdRequest(op, fdArgs);\n } else {\n // Standard single-path operations\n const encodedData = encodeData(data);\n reqBuffer = encodeRequest(op, path ?? '', flags ?? 0, encodedData ?? undefined);\n }\n\n const { status, data: respData } = await sendRequest(reqBuffer);\n\n (self as unknown as Worker).postMessage(\n { type: 'response', callId, status, data: respData },\n respData ? [respData.buffer] : []\n );\n } catch (err) {\n (self as unknown as Worker).postMessage({\n type: 'response',\n callId,\n status: 7, // EINVAL\n data: null,\n error: (err as Error).message,\n });\n }\n }\n};\n\n// ========== Encoding helpers ==========\n\nfunction encodeData(data: unknown): Uint8Array | null {\n if (data === null || data === undefined) return null;\n if (data instanceof Uint8Array) return data;\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n if (typeof data === 'string') return encoder.encode(data);\n return null;\n}\n\nfunction encodeFdRequest(op: number, args: { fd: number; length?: number; position?: number; data?: Uint8Array }): ArrayBuffer {\n switch (op) {\n case OP.FREAD: {\n const buf = new Uint8Array(12);\n const view = new DataView(buf.buffer);\n view.setUint32(0, args.fd, true);\n view.setUint32(4, args.length ?? 0, true);\n view.setInt32(8, args.position ?? -1, true);\n return encodeRequest(op, '', 0, buf);\n }\n case OP.FWRITE: {\n const writeData = args.data ?? new Uint8Array(0);\n const buf = new Uint8Array(8 + writeData.byteLength);\n const view = new DataView(buf.buffer);\n view.setUint32(0, args.fd, true);\n view.setInt32(4, args.position ?? -1, true);\n buf.set(writeData, 8);\n return encodeRequest(op, '', 0, buf);\n }\n case OP.FSTAT:\n case OP.CLOSE: {\n const buf = new Uint8Array(4);\n new DataView(buf.buffer).setUint32(0, args.fd, true);\n return encodeRequest(op, '', 0, buf);\n }\n case OP.FTRUNCATE: {\n const buf = new Uint8Array(8);\n const view = new DataView(buf.buffer);\n view.setUint32(0, args.fd, true);\n view.setUint32(4, args.length ?? 0, true);\n return encodeRequest(op, '', 0, buf);\n }\n case OP.FSYNC:\n return encodeRequest(op, '', 0);\n default:\n return encodeRequest(op, '', 0);\n }\n}\n"],"mappings":"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","names":["encoder"]}
|
|
@@ -777,8 +777,8 @@ var VFSEngine = class {
|
|
|
777
777
|
tData = tAlloc;
|
|
778
778
|
tInode = tAlloc;
|
|
779
779
|
}
|
|
780
|
+
this.commitPending();
|
|
780
781
|
if (flags & 1) {
|
|
781
|
-
this.commitPending();
|
|
782
782
|
this.handle.flush();
|
|
783
783
|
}
|
|
784
784
|
const tFlush = this.debug ? performance.now() : 0;
|
|
@@ -1489,7 +1489,8 @@ async function handleRepair(root) {
|
|
|
1489
1489
|
let inodeTableOffset;
|
|
1490
1490
|
let pathTableOffset;
|
|
1491
1491
|
let dataOffset;
|
|
1492
|
-
let
|
|
1492
|
+
let bitmapOffset;
|
|
1493
|
+
let pathTableSize;
|
|
1493
1494
|
const magic = view.getUint32(SUPERBLOCK.MAGIC, true);
|
|
1494
1495
|
const version = view.getUint32(SUPERBLOCK.VERSION, true);
|
|
1495
1496
|
const superblockValid = magic === VFS_MAGIC && version === VFS_VERSION;
|
|
@@ -1500,8 +1501,9 @@ async function handleRepair(root) {
|
|
|
1500
1501
|
inodeTableOffset = view.getFloat64(SUPERBLOCK.INODE_OFFSET, true);
|
|
1501
1502
|
pathTableOffset = view.getFloat64(SUPERBLOCK.PATH_OFFSET, true);
|
|
1502
1503
|
dataOffset = view.getFloat64(SUPERBLOCK.DATA_OFFSET, true);
|
|
1503
|
-
|
|
1504
|
-
|
|
1504
|
+
bitmapOffset = view.getFloat64(SUPERBLOCK.BITMAP_OFFSET, true);
|
|
1505
|
+
pathTableSize = bitmapOffset - pathTableOffset;
|
|
1506
|
+
if (blockSize === 0 || (blockSize & blockSize - 1) !== 0 || inodeCount === 0 || inodeTableOffset >= fileSize || pathTableOffset >= fileSize || dataOffset >= fileSize || pathTableSize <= 0) {
|
|
1505
1507
|
const layout = calculateLayout(DEFAULT_INODE_COUNT, DEFAULT_BLOCK_SIZE, INITIAL_DATA_BLOCKS);
|
|
1506
1508
|
inodeCount = DEFAULT_INODE_COUNT;
|
|
1507
1509
|
blockSize = DEFAULT_BLOCK_SIZE;
|
|
@@ -1509,7 +1511,8 @@ async function handleRepair(root) {
|
|
|
1509
1511
|
inodeTableOffset = layout.inodeTableOffset;
|
|
1510
1512
|
pathTableOffset = layout.pathTableOffset;
|
|
1511
1513
|
dataOffset = layout.dataOffset;
|
|
1512
|
-
|
|
1514
|
+
bitmapOffset = layout.bitmapOffset;
|
|
1515
|
+
pathTableSize = bitmapOffset - pathTableOffset;
|
|
1513
1516
|
}
|
|
1514
1517
|
} else {
|
|
1515
1518
|
const layout = calculateLayout(DEFAULT_INODE_COUNT, DEFAULT_BLOCK_SIZE, INITIAL_DATA_BLOCKS);
|
|
@@ -1519,7 +1522,8 @@ async function handleRepair(root) {
|
|
|
1519
1522
|
inodeTableOffset = layout.inodeTableOffset;
|
|
1520
1523
|
pathTableOffset = layout.pathTableOffset;
|
|
1521
1524
|
dataOffset = layout.dataOffset;
|
|
1522
|
-
|
|
1525
|
+
bitmapOffset = layout.bitmapOffset;
|
|
1526
|
+
pathTableSize = bitmapOffset - pathTableOffset;
|
|
1523
1527
|
}
|
|
1524
1528
|
const decoder2 = new TextDecoder("utf-8", { fatal: true });
|
|
1525
1529
|
const recovered = [];
|
|
@@ -1536,7 +1540,7 @@ async function handleRepair(root) {
|
|
|
1536
1540
|
const size = inodeView.getFloat64(INODE.SIZE, true);
|
|
1537
1541
|
const firstBlock = inodeView.getUint32(INODE.FIRST_BLOCK, true);
|
|
1538
1542
|
const absPathOffset = pathTableOffset + pathOff;
|
|
1539
|
-
if (pathLength === 0 || pathLength > 4096 || absPathOffset + pathLength > fileSize || pathOff + pathLength >
|
|
1543
|
+
if (pathLength === 0 || pathLength > 4096 || absPathOffset + pathLength > fileSize || pathOff + pathLength > pathTableSize) {
|
|
1540
1544
|
lost++;
|
|
1541
1545
|
continue;
|
|
1542
1546
|
}
|