@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.
@@ -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 pathTableUsed;
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
- pathTableUsed = view.getUint32(SUPERBLOCK.PATH_USED, true);
1504
- if (blockSize === 0 || (blockSize & blockSize - 1) !== 0 || inodeCount === 0 || inodeTableOffset >= fileSize || pathTableOffset >= fileSize || dataOffset >= fileSize) {
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
- pathTableUsed = INITIAL_PATH_TABLE_SIZE;
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
- pathTableUsed = INITIAL_PATH_TABLE_SIZE;
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 > pathTableUsed) {
1543
+ if (pathLength === 0 || pathLength > 4096 || absPathOffset + pathLength > fileSize || pathOff + pathLength > pathTableSize) {
1540
1544
  lost++;
1541
1545
  continue;
1542
1546
  }