@componentor/fs 3.0.24 → 3.0.26
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/dist/index.d.mts +92 -11
- package/dist/index.js +1234 -483
- package/dist/index.js.map +1 -1
- package/dist/workers/async-relay.worker.js +5 -5
- package/dist/workers/async-relay.worker.js.map +1 -1
- package/dist/workers/repair.worker.js +42 -6
- package/dist/workers/repair.worker.js.map +1 -1
- package/dist/workers/server.worker.js +47 -11
- package/dist/workers/server.worker.js.map +1 -1
- package/dist/workers/sync-relay.worker.js +61 -23
- package/dist/workers/sync-relay.worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -267,20 +267,20 @@ function encodeData(data) {
|
|
|
267
267
|
function encodeFdRequest(op, args) {
|
|
268
268
|
switch (op) {
|
|
269
269
|
case OP.FREAD: {
|
|
270
|
-
const buf = new Uint8Array(
|
|
270
|
+
const buf = new Uint8Array(16);
|
|
271
271
|
const view = new DataView(buf.buffer);
|
|
272
272
|
view.setUint32(0, args.fd, true);
|
|
273
273
|
view.setUint32(4, args.length ?? 0, true);
|
|
274
|
-
view.
|
|
274
|
+
view.setFloat64(8, args.position ?? -1, true);
|
|
275
275
|
return encodeRequest(op, "", 0, buf);
|
|
276
276
|
}
|
|
277
277
|
case OP.FWRITE: {
|
|
278
278
|
const writeData = args.data ?? new Uint8Array(0);
|
|
279
|
-
const buf = new Uint8Array(
|
|
279
|
+
const buf = new Uint8Array(12 + writeData.byteLength);
|
|
280
280
|
const view = new DataView(buf.buffer);
|
|
281
281
|
view.setUint32(0, args.fd, true);
|
|
282
|
-
view.
|
|
283
|
-
buf.set(writeData,
|
|
282
|
+
view.setFloat64(4, args.position ?? -1, true);
|
|
283
|
+
buf.set(writeData, 12);
|
|
284
284
|
return encodeRequest(op, "", 0, buf);
|
|
285
285
|
}
|
|
286
286
|
case OP.FSTAT:
|
|
@@ -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 // 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 non-final chunk\n Atomics.wait(asyncCtrl!, 0, sent === chunkSize ? SIGNAL.REQUEST : SIGNAL.CHUNK);\n }\n }\n // Wait for sync-relay to ack the LAST chunk before looking for response.\n // Without this, ctrl[0] is still our SIGNAL.CHUNK and we can't distinguish\n // it from a response CHUNK signal.\n while (Atomics.load(asyncCtrl!, 0) === SIGNAL.CHUNK) {\n Atomics.wait(asyncCtrl!, 0, SIGNAL.CHUNK, 100);\n }\n }\n\n // Wait for sync-relay to write the response.\n // After single-chunk: ctrl transitions REQUEST → RESPONSE (or CHUNK for multi-response)\n // After multi-chunk: ctrl transitions CHUNK → CHUNK_ACK → RESPONSE (or CHUNK for multi-response)\n // At this point ctrl[0] is NOT our CHUNK (we waited above). It's either\n // CHUNK_ACK (sync still processing), RESPONSE (done), or CHUNK (multi-response first chunk).\n let signal: number;\n for (;;) {\n signal = Atomics.load(asyncCtrl!, 0);\n if (signal === SIGNAL.RESPONSE || signal === SIGNAL.CHUNK) break;\n Atomics.wait(asyncCtrl!, 0, signal, 1000);\n }\n\n // Read response (may be multi-chunk)\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 non-final chunk\n Atomics.wait(asyncCtrl!, 0, sent === chunkSize ? SIGNAL.REQUEST : SIGNAL.CHUNK);\n }\n }\n // Wait for sync-relay to ack the LAST chunk before looking for response.\n // Without this, ctrl[0] is still our SIGNAL.CHUNK and we can't distinguish\n // it from a response CHUNK signal.\n while (Atomics.load(asyncCtrl!, 0) === SIGNAL.CHUNK) {\n Atomics.wait(asyncCtrl!, 0, SIGNAL.CHUNK, 100);\n }\n }\n\n // Wait for sync-relay to write the response.\n // After single-chunk: ctrl transitions REQUEST → RESPONSE (or CHUNK for multi-response)\n // After multi-chunk: ctrl transitions CHUNK → CHUNK_ACK → RESPONSE (or CHUNK for multi-response)\n // At this point ctrl[0] is NOT our CHUNK (we waited above). It's either\n // CHUNK_ACK (sync still processing), RESPONSE (done), or CHUNK (multi-response first chunk).\n let signal: number;\n for (;;) {\n signal = Atomics.load(asyncCtrl!, 0);\n if (signal === SIGNAL.RESPONSE || signal === SIGNAL.CHUNK) break;\n Atomics.wait(asyncCtrl!, 0, signal, 1000);\n }\n\n // Read response (may be multi-chunk)\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(16);\n const view = new DataView(buf.buffer);\n view.setUint32(0, args.fd, true);\n view.setUint32(4, args.length ?? 0, true);\n view.setFloat64(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(12 + writeData.byteLength);\n const view = new DataView(buf.buffer);\n view.setUint32(0, args.fd, true);\n view.setFloat64(4, args.position ?? -1, true);\n buf.set(writeData, 12);\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"]}
|
|
@@ -40,8 +40,8 @@ var INODE = {
|
|
|
40
40
|
// uint32 - byte offset into path table
|
|
41
41
|
PATH_LENGTH: 8,
|
|
42
42
|
// uint16 - length of path string
|
|
43
|
-
|
|
44
|
-
// uint16
|
|
43
|
+
NLINK: 10,
|
|
44
|
+
// uint16 - hard link count
|
|
45
45
|
MODE: 12,
|
|
46
46
|
// uint32 - permissions (e.g. 0o100644)
|
|
47
47
|
SIZE: 16,
|
|
@@ -415,6 +415,7 @@ var VFSEngine = class {
|
|
|
415
415
|
type,
|
|
416
416
|
pathOffset,
|
|
417
417
|
pathLength,
|
|
418
|
+
nlink: inodeView.getUint16(off + INODE.NLINK, true) || 1,
|
|
418
419
|
mode: inodeView.getUint32(off + INODE.MODE, true),
|
|
419
420
|
size,
|
|
420
421
|
firstBlock,
|
|
@@ -449,6 +450,7 @@ var VFSEngine = class {
|
|
|
449
450
|
type: v.getUint8(INODE.TYPE),
|
|
450
451
|
pathOffset: v.getUint32(INODE.PATH_OFFSET, true),
|
|
451
452
|
pathLength: v.getUint16(INODE.PATH_LENGTH, true),
|
|
453
|
+
nlink: v.getUint16(INODE.NLINK, true) || 1,
|
|
452
454
|
mode: v.getUint32(INODE.MODE, true),
|
|
453
455
|
size: v.getFloat64(INODE.SIZE, true),
|
|
454
456
|
firstBlock: v.getUint32(INODE.FIRST_BLOCK, true),
|
|
@@ -475,7 +477,7 @@ var VFSEngine = class {
|
|
|
475
477
|
v.setUint8(INODE.FLAGS + 2, 0);
|
|
476
478
|
v.setUint32(INODE.PATH_OFFSET, inode.pathOffset, true);
|
|
477
479
|
v.setUint16(INODE.PATH_LENGTH, inode.pathLength, true);
|
|
478
|
-
v.setUint16(INODE.
|
|
480
|
+
v.setUint16(INODE.NLINK, inode.nlink, true);
|
|
479
481
|
v.setUint32(INODE.MODE, inode.mode, true);
|
|
480
482
|
v.setFloat64(INODE.SIZE, inode.size, true);
|
|
481
483
|
v.setUint32(INODE.FIRST_BLOCK, inode.firstBlock, true);
|
|
@@ -717,6 +719,7 @@ var VFSEngine = class {
|
|
|
717
719
|
type,
|
|
718
720
|
pathOffset: pathOff,
|
|
719
721
|
pathLength: pathLen,
|
|
722
|
+
nlink: type === INODE_TYPE.DIRECTORY ? 2 : 1,
|
|
720
723
|
mode,
|
|
721
724
|
size,
|
|
722
725
|
firstBlock,
|
|
@@ -869,6 +872,7 @@ var VFSEngine = class {
|
|
|
869
872
|
if (idx === void 0) return { status: CODE_TO_STATUS.ENOENT };
|
|
870
873
|
const inode = this.readInode(idx);
|
|
871
874
|
if (inode.type === INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.EISDIR };
|
|
875
|
+
inode.nlink = Math.max(0, inode.nlink - 1);
|
|
872
876
|
this.freeBlockRange(inode.firstBlock, inode.blockCount);
|
|
873
877
|
inode.type = INODE_TYPE.FREE;
|
|
874
878
|
this.writeInode(idx, inode);
|
|
@@ -893,7 +897,21 @@ var VFSEngine = class {
|
|
|
893
897
|
}
|
|
894
898
|
encodeStatResponse(idx) {
|
|
895
899
|
const inode = this.readInode(idx);
|
|
896
|
-
|
|
900
|
+
let nlink = inode.nlink;
|
|
901
|
+
if (inode.type === INODE_TYPE.DIRECTORY) {
|
|
902
|
+
const path = this.readPath(inode.pathOffset, inode.pathLength);
|
|
903
|
+
const children = this.getDirectChildren(path);
|
|
904
|
+
let subdirCount = 0;
|
|
905
|
+
for (const child of children) {
|
|
906
|
+
const childIdx = this.pathIndex.get(child);
|
|
907
|
+
if (childIdx !== void 0) {
|
|
908
|
+
const childInode = this.readInode(childIdx);
|
|
909
|
+
if (childInode.type === INODE_TYPE.DIRECTORY) subdirCount++;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
nlink = 2 + subdirCount;
|
|
913
|
+
}
|
|
914
|
+
const buf = new Uint8Array(53);
|
|
897
915
|
const view = new DataView(buf.buffer);
|
|
898
916
|
view.setUint8(0, inode.type);
|
|
899
917
|
view.setUint32(1, inode.mode, true);
|
|
@@ -904,6 +922,7 @@ var VFSEngine = class {
|
|
|
904
922
|
view.setUint32(37, inode.uid, true);
|
|
905
923
|
view.setUint32(41, inode.gid, true);
|
|
906
924
|
view.setUint32(45, idx, true);
|
|
925
|
+
view.setUint32(49, nlink, true);
|
|
907
926
|
return { status: 0, data: buf };
|
|
908
927
|
}
|
|
909
928
|
// ---- MKDIR ----
|
|
@@ -1216,9 +1235,26 @@ var VFSEngine = class {
|
|
|
1216
1235
|
const target = this.readData(inode.firstBlock, inode.blockCount, inode.size);
|
|
1217
1236
|
return { status: 0, data: target };
|
|
1218
1237
|
}
|
|
1219
|
-
// ---- LINK (hard link — copies the file) ----
|
|
1238
|
+
// ---- LINK (hard link — copies the file data, tracks nlink) ----
|
|
1220
1239
|
link(existingPath, newPath) {
|
|
1221
|
-
|
|
1240
|
+
existingPath = this.normalizePath(existingPath);
|
|
1241
|
+
newPath = this.normalizePath(newPath);
|
|
1242
|
+
const srcIdx = this.resolvePathComponents(existingPath, true);
|
|
1243
|
+
if (srcIdx === void 0) return { status: CODE_TO_STATUS.ENOENT };
|
|
1244
|
+
const srcInode = this.readInode(srcIdx);
|
|
1245
|
+
if (srcInode.type === INODE_TYPE.DIRECTORY) return { status: CODE_TO_STATUS.EPERM };
|
|
1246
|
+
if (this.pathIndex.has(newPath)) return { status: CODE_TO_STATUS.EEXIST };
|
|
1247
|
+
const result = this.copy(existingPath, newPath);
|
|
1248
|
+
if (result.status !== 0) return result;
|
|
1249
|
+
srcInode.nlink++;
|
|
1250
|
+
this.writeInode(srcIdx, srcInode);
|
|
1251
|
+
const destIdx = this.pathIndex.get(newPath);
|
|
1252
|
+
if (destIdx !== void 0) {
|
|
1253
|
+
const destInode = this.readInode(destIdx);
|
|
1254
|
+
destInode.nlink = srcInode.nlink;
|
|
1255
|
+
this.writeInode(destIdx, destInode);
|
|
1256
|
+
}
|
|
1257
|
+
return { status: 0 };
|
|
1222
1258
|
}
|
|
1223
1259
|
// ---- OPEN (file descriptor) ----
|
|
1224
1260
|
open(path, flags, tabId) {
|