@componentor/fs 3.0.14 → 3.0.16

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":";AAMO,IAAM,KAAK;AAAA,EAChB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,OAAO;AAAA,EACP,SAAS;AAAA,EACT,SAAS;AACX;AAoBO,IAAM,cAAc;AAAA,EACzB,SAAS;AAAA;AAAA,EACT,QAAQ;AAAA;AAAA,EACR,QAAQ;AAAA;AAAA,EACR,WAAW;AAAA;AAAA,EACX,WAAW;AAAA;AAAA,EACX,WAAW;AAAA;AAAA,EACX,UAAU;AAAA;AAAA,EACV,aAAa;AAAA;AACf;AAGO,IAAM,SAAS;AAAA,EACpB,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,WAAW;AACb;AAEA,IAAM,UAAU,IAAI,YAAY;AAChC,IAAM,UAAU,IAAI,YAAY;AAazB,SAAS,cACd,IACA,MACA,QAAgB,GAChB,MACa;AACb,QAAM,YAAY,QAAQ,OAAO,IAAI;AACrC,QAAM,UAAU,OAAO,KAAK,aAAa;AACzC,QAAM,WAAW,KAAK,UAAU,aAAa;AAC7C,QAAM,MAAM,IAAI,YAAY,QAAQ;AACpC,QAAM,OAAO,IAAI,SAAS,GAAG;AAE7B,OAAK,UAAU,GAAG,IAAI,IAAI;AAC1B,OAAK,UAAU,GAAG,OAAO,IAAI;AAC7B,OAAK,UAAU,GAAG,UAAU,YAAY,IAAI;AAC5C,OAAK,UAAU,IAAI,SAAS,IAAI;AAEhC,QAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAM,IAAI,WAAW,EAAE;AACvB,MAAI,MAAM;AACR,UAAM,IAAI,MAAM,KAAK,UAAU,UAAU;AAAA,EAC3C;AAEA,SAAO;AACT;AAoDO,SAAS,eAAe,KAG7B;AACA,QAAM,OAAO,IAAI,SAAS,GAAG;AAC7B,QAAM,SAAS,KAAK,UAAU,GAAG,IAAI;AACrC,QAAM,UAAU,KAAK,UAAU,GAAG,IAAI;AAEtC,QAAM,OAAO,UAAU,IACnB,IAAI,WAAW,KAAK,GAAG,OAAO,IAC9B;AAEJ,SAAO,EAAE,QAAQ,KAAK;AACxB;AAMO,SAAS,qBACd,IACA,OACA,OACA,QAAgB,GACH;AACb,QAAM,aAAa,QAAQ,OAAO,KAAK;AACvC,QAAM,UAAU,IAAI,WAAW,IAAI,WAAW,UAAU;AACxD,QAAM,KAAK,IAAI,SAAS,QAAQ,MAAM;AACtC,KAAG,UAAU,GAAG,WAAW,YAAY,IAAI;AAC3C,UAAQ,IAAI,YAAY,CAAC;AAEzB,SAAO,cAAc,IAAI,OAAO,OAAO,OAAO;AAChD;;;AC9KA,IAAMA,WAAU,IAAI,YAAY;AAChC,IAAM,cAAc,YAAY;AAIhC,IAAI,WAAqC;AACzC,IAAI,YAA+B;AAGnC,IAAI,WAA8B;AAKlC,SAAS,WAAW,YAAsE;AACxF,QAAM,WAAW,SAAU,aAAa;AACxC,QAAM,eAAe,IAAI,WAAW,UAAU;AAC9C,QAAM,eAAe,IAAI,eAAe,UAAW,YAAY,WAAW,CAAC;AAG3E,MAAI,aAAa,cAAc,UAAU;AAEvC,QAAI,WAAW,UAAW,aAAa,aAAa,UAAU,EAAE,IAAI,YAAY;AAChF,YAAQ,MAAM,WAAY,GAAG,aAAa,UAAU;AACpD,YAAQ,MAAM,cAAc,GAAG,OAAO,aAAa,UAAU,CAAC;AAC9D,YAAQ,MAAM,WAAY,GAAG,OAAO,OAAO;AAC3C,YAAQ,OAAO,WAAY,CAAC;AAE5B,QAAI,SAAU,SAAQ,OAAO,UAAU,CAAC;AAAA,EAC1C,OAAO;AAEL,QAAI,OAAO;AACX,WAAO,OAAO,aAAa,YAAY;AACrC,YAAM,YAAY,KAAK,IAAI,UAAU,aAAa,aAAa,IAAI;AACnE,UAAI,WAAW,UAAW,aAAa,SAAS,EAAE;AAAA,QAChD,aAAa,SAAS,MAAM,OAAO,SAAS;AAAA,MAC9C;AACA,cAAQ,MAAM,WAAY,GAAG,SAAS;AACtC,cAAQ,MAAM,cAAc,GAAG,OAAO,aAAa,UAAU,CAAC;AAC9D,cAAQ,MAAM,WAAY,GAAG,KAAK,MAAM,OAAO,QAAQ,CAAC;AAExD,UAAI,SAAS,GAAG;AACd,gBAAQ,MAAM,WAAY,GAAG,OAAO,OAAO;AAAA,MAC7C,OAAO;AACL,gBAAQ,MAAM,WAAY,GAAG,OAAO,KAAK;AAAA,MAC3C;AACA,cAAQ,OAAO,WAAY,CAAC;AAE5B,UAAI,SAAS,KAAK,SAAU,SAAQ,OAAO,UAAU,CAAC;AAEtD,cAAQ;AACR,UAAI,OAAO,aAAa,YAAY;AAElC,gBAAQ,KAAK,WAAY,GAAG,SAAS,YAAY,OAAO,UAAU,OAAO,KAAK;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AAGA,UAAQ,KAAK,WAAY,GAAG,OAAO,OAAO;AAG1C,QAAM,SAAS,QAAQ,KAAK,WAAY,CAAC;AACzC,QAAM,eAAe,QAAQ,KAAK,WAAY,CAAC;AAC/C,QAAM,eAAe,OAAO,QAAQ,KAAK,cAAc,CAAC,CAAC;AAEzD,MAAI;AAEJ,MAAI,WAAW,OAAO,YAAY,gBAAgB,UAAU;AAE1D,oBAAgB,IAAI,WAAW,UAAW,aAAa,YAAY,EAAE,MAAM;AAAA,EAC7E,OAAO;AAEL,oBAAgB,IAAI,WAAW,YAAY;AAC3C,QAAI,WAAW;AAEf,kBAAc,IAAI,IAAI,WAAW,UAAW,aAAa,YAAY,GAAG,CAAC;AACzE,gBAAY;AAEZ,WAAO,WAAW,cAAc;AAC9B,cAAQ,MAAM,WAAY,GAAG,OAAO,SAAS;AAC7C,cAAQ,OAAO,WAAY,CAAC;AAC5B,cAAQ,KAAK,WAAY,GAAG,OAAO,SAAS;AAE5C,YAAM,UAAU,QAAQ,KAAK,WAAY,CAAC;AAC1C,oBAAc,IAAI,IAAI,WAAW,UAAW,aAAa,OAAO,GAAG,QAAQ;AAC3E,kBAAY;AAAA,IACd;AAAA,EACF;AAGA,UAAQ,MAAM,WAAY,GAAG,OAAO,IAAI;AACxC,UAAQ,OAAO,WAAY,CAAC;AAE5B,SAAO,eAAe,cAAc,MAAqB;AAC3D;AAIA,IAAI,aAAiC;AACrC,IAAM,UAAU,oBAAI,IAA6C;AACjE,IAAI,YAAY;AAEhB,SAAS,SAAiB;AACxB,SAAO,MAAO;AAChB;AAEA,SAAS,YAAY,QAA2E;AAC9F,SAAO,IAAI,QAAQ,aAAW;AAC5B,UAAM,KAAK,OAAO;AAClB,YAAQ,IAAI,IAAI,CAAC,YAAY;AAC3B,cAAQ,eAAe,OAAO,CAAC;AAAA,IACjC,CAAC;AACD,eAAY,YAAY,EAAE,IAAI,OAAO,GAAG,CAAC,MAAM,CAAC;AAAA,EAClD,CAAC;AACH;AAIA,eAAe,YAAY,WAA8E;AACvG,MAAI,UAAU;AAEZ,WAAO,WAAW,SAAS;AAAA,EAC7B,WAAW,YAAY;AAErB,WAAO,YAAY,SAAS;AAAA,EAC9B;AACA,SAAO,EAAE,QAAQ,GAAG,MAAM,KAAK;AACjC;AAIA,KAAK,YAAY,OAAO,MAAoB;AAC1C,QAAM,MAAM,EAAE;AAGd,MAAI,IAAI,SAAS,eAAe;AAC9B,eAAW,IAAI;AACf,gBAAY,IAAI,WAAW,IAAI,UAAU,GAAG,CAAC;AAC7C,QAAI,IAAI,SAAS;AACf,iBAAW,IAAI,WAAW,IAAI,SAAS,GAAG,CAAC;AAAA,IAC7C;AACA;AAAA,EACF;AAGA,MAAI,IAAI,SAAS,aAAa;AAC5B,UAAM,OAAO,IAAI,QAAQ,EAAE,MAAM,CAAC;AAClC,QAAI,MAAM;AACR,mBAAa;AACb,iBAAY,YAAY,CAAC,OAAqB;AAC5C,cAAM,EAAE,IAAI,OAAO,IAAI,GAAG;AAC1B,cAAM,UAAU,QAAQ,IAAI,EAAE;AAC9B,YAAI,SAAS;AACX,kBAAQ,OAAO,EAAE;AACjB,kBAAQ,MAAM;AAAA,QAChB;AAAA,MACF;AACA,iBAAY,MAAM;AAAA,IACpB;AACA;AAAA,EACF;AAGA,MAAI,IAAI,SAAS,iBAAiB;AAEhC;AAAA,EACF;AAGA,MAAI,IAAI,SAAS,eAAe;AAC9B,iBAAa,IAAI;AACjB,eAAY,YAAY,CAAC,OAAqB;AAC5C,YAAM,EAAE,IAAI,OAAO,IAAI,GAAG;AAC1B,YAAM,UAAU,QAAQ,IAAI,EAAE;AAC9B,UAAI,SAAS;AACX,gBAAQ,OAAO,EAAE;AACjB,gBAAQ,MAAM;AAAA,MAChB;AAAA,IACF;AACA,eAAY,MAAM;AAClB;AAAA,EACF;AAGA,MAAI,IAAI,SAAS,WAAW;AAC1B,UAAM,EAAE,QAAQ,IAAI,MAAM,MAAM,OAAO,OAAO,OAAO,IAAI;AAEzD,QAAI;AACF,UAAI;AAGJ,UAAI,UAAU,QAAW;AAEvB,oBAAY,qBAAqB,IAAI,MAAM,OAAO,SAAS,CAAC;AAAA,MAC9D,WAAW,QAAQ;AAEjB,oBAAY,gBAAgB,IAAI,MAAM;AAAA,MACxC,OAAO;AAEL,cAAM,cAAc,WAAW,IAAI;AACnC,oBAAY,cAAc,IAAI,QAAQ,IAAI,SAAS,GAAG,eAAe,MAAS;AAAA,MAChF;AAEA,YAAM,EAAE,QAAQ,MAAM,SAAS,IAAI,MAAM,YAAY,SAAS;AAE9D,MAAC,KAA2B;AAAA,QAC1B,EAAE,MAAM,YAAY,QAAQ,QAAQ,MAAM,SAAS;AAAA,QACnD,WAAW,CAAC,SAAS,MAAM,IAAI,CAAC;AAAA,MAClC;AAAA,IACF,SAAS,KAAK;AACZ,MAAC,KAA2B,YAAY;AAAA,QACtC,MAAM;AAAA,QACN;AAAA,QACA,QAAQ;AAAA;AAAA,QACR,MAAM;AAAA,QACN,OAAQ,IAAc;AAAA,MACxB,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAIA,SAAS,WAAW,MAAkC;AACpD,MAAI,SAAS,QAAQ,SAAS,OAAW,QAAO;AAChD,MAAI,gBAAgB,WAAY,QAAO;AACvC,MAAI,gBAAgB,YAAa,QAAO,IAAI,WAAW,IAAI;AAC3D,MAAI,OAAO,SAAS,SAAU,QAAOA,SAAQ,OAAO,IAAI;AACxD,SAAO;AACT;AAEA,SAAS,gBAAgB,IAAY,MAA0F;AAC7H,UAAQ,IAAI;AAAA,IACV,KAAK,GAAG,OAAO;AACb,YAAM,MAAM,IAAI,WAAW,EAAE;AAC7B,YAAM,OAAO,IAAI,SAAS,IAAI,MAAM;AACpC,WAAK,UAAU,GAAG,KAAK,IAAI,IAAI;AAC/B,WAAK,UAAU,GAAG,KAAK,UAAU,GAAG,IAAI;AACxC,WAAK,SAAS,GAAG,KAAK,YAAY,IAAI,IAAI;AAC1C,aAAO,cAAc,IAAI,IAAI,GAAG,GAAG;AAAA,IACrC;AAAA,IACA,KAAK,GAAG,QAAQ;AACd,YAAM,YAAY,KAAK,QAAQ,IAAI,WAAW,CAAC;AAC/C,YAAM,MAAM,IAAI,WAAW,IAAI,UAAU,UAAU;AACnD,YAAM,OAAO,IAAI,SAAS,IAAI,MAAM;AACpC,WAAK,UAAU,GAAG,KAAK,IAAI,IAAI;AAC/B,WAAK,SAAS,GAAG,KAAK,YAAY,IAAI,IAAI;AAC1C,UAAI,IAAI,WAAW,CAAC;AACpB,aAAO,cAAc,IAAI,IAAI,GAAG,GAAG;AAAA,IACrC;AAAA,IACA,KAAK,GAAG;AAAA,IACR,KAAK,GAAG,OAAO;AACb,YAAM,MAAM,IAAI,WAAW,CAAC;AAC5B,UAAI,SAAS,IAAI,MAAM,EAAE,UAAU,GAAG,KAAK,IAAI,IAAI;AACnD,aAAO,cAAc,IAAI,IAAI,GAAG,GAAG;AAAA,IACrC;AAAA,IACA,KAAK,GAAG,WAAW;AACjB,YAAM,MAAM,IAAI,WAAW,CAAC;AAC5B,YAAM,OAAO,IAAI,SAAS,IAAI,MAAM;AACpC,WAAK,UAAU,GAAG,KAAK,IAAI,IAAI;AAC/B,WAAK,UAAU,GAAG,KAAK,UAAU,GAAG,IAAI;AACxC,aAAO,cAAc,IAAI,IAAI,GAAG,GAAG;AAAA,IACrC;AAAA,IACA,KAAK,GAAG;AACN,aAAO,cAAc,IAAI,IAAI,CAAC;AAAA,IAChC;AACE,aAAO,cAAc,IAAI,IAAI,CAAC;AAAA,EAClC;AACF;","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"]}
@@ -1428,11 +1428,18 @@ var OP = {
1428
1428
  var encoder2 = new TextEncoder();
1429
1429
  var decoder2 = new TextDecoder();
1430
1430
  function decodeRequest(buf) {
1431
+ if (buf.byteLength < 16) {
1432
+ throw new Error(`Request buffer too small: ${buf.byteLength} < 16 bytes (possible SAB race)`);
1433
+ }
1431
1434
  const view = new DataView(buf);
1432
1435
  const op = view.getUint32(0, true);
1433
1436
  const flags = view.getUint32(4, true);
1434
1437
  const pathLen = view.getUint32(8, true);
1435
1438
  const dataLen = view.getUint32(12, true);
1439
+ const expectedMin = 16 + pathLen + dataLen;
1440
+ if (buf.byteLength < expectedMin) {
1441
+ throw new Error(`Request buffer truncated: ${buf.byteLength} < ${expectedMin} bytes (op=${op}, pathLen=${pathLen}, dataLen=${dataLen})`);
1442
+ }
1436
1443
  const bytes = new Uint8Array(buf);
1437
1444
  const path = decoder2.decode(bytes.subarray(16, 16 + pathLen));
1438
1445
  const data = dataLen > 0 ? bytes.subarray(16 + pathLen, 16 + pathLen + dataLen) : null;