{"version":3,"file":"SyncChannel.mjs","names":[],"sources":["../src/sync/channel/connect.ts","../src/sync/channel/listen.ts"],"sourcesContent":["/**\n * SyncChannel connection APIs.\n * Provides functions for connecting, attaching, and checking sync channel status.\n *\n * @module\n */\n\nimport { Err, Ok, RESULT_VOID, type AsyncIOResult, type VoidIOResult } from 'happy-rusty';\nimport { Future } from 'tiny-future';\nimport type { AttachSyncChannelOptions, ConnectSyncChannelOptions } from '../../shared/mod.ts';\nimport { isSyncChannelSupported, TIMEOUT_ERROR } from '../../shared/mod.ts';\nimport { SyncMessenger } from '../protocol.ts';\nimport {\n    getSyncChannelState,\n    resetSyncChannel,\n    setGlobalSyncOpTimeout,\n    setMessenger,\n    setOwnedWorker,\n    setSyncChannelState,\n} from './state.ts';\n\n// #region Internal Variables\n\n/**\n * Default size of SharedArrayBuffer in bytes (1MB).\n */\nconst DEFAULT_BUFFER_LENGTH = 1024 * 1024;\n\n/**\n * Minimum required buffer size in bytes.\n * 16 bytes header + ~131 bytes for largest error response = 147 bytes.\n * Using 256 (power of 2) for better memory alignment.\n */\nconst MIN_BUFFER_LENGTH = 256;\n\n/**\n * Default timeout for sync operations in milliseconds.\n */\nconst DEFAULT_OP_TIMEOUT = 1000;\n\n/**\n * Default timeout for establishing the connection in milliseconds.\n * Covers worker startup, script load, and `SyncChannel.listen()` readiness.\n */\nconst DEFAULT_CONNECT_TIMEOUT = 10000;\n\n// #endregion\n\n/**\n * Connects to a worker and establishes a sync channel for synchronous file system operations.\n * Must be called before using any sync API functions.\n *\n * @param worker - The worker to communicate with. Can be a `Worker` instance, a `URL`, or a URL string.\n *                 A URL is loaded as a classic worker unless `workerType: 'module'` is set.\n * @param options - Optional configuration options for the sync channel.\n * @returns A promise that resolves with an `AsyncIOResult` containing the `SharedArrayBuffer` when the worker is ready.\n *          The returned buffer can be shared with other contexts (e.g., iframes) via `postMessage`.\n * @since 1.1.0\n * @example\n * ```typescript\n * // Connect to worker and get SharedArrayBuffer\n * const result = await SyncChannel.connect(\n *     new URL('./worker.js', import.meta.url),\n *     { sharedBufferLength: 1024 * 1024, opTimeout: 5000, connectTimeout: 10000 }\n * );\n * result.inspect(sharedBuffer => {\n *     // Share with iframe\n *     iframe.contentWindow.postMessage({ sharedBuffer }, '*');\n * });\n * ```\n */\nexport async function connectSyncChannel(\n    worker: Worker | URL | string,\n    options?: ConnectSyncChannelOptions,\n): AsyncIOResult<SharedArrayBuffer> {\n    // Guard the environment first: constructing a SharedArrayBuffer in a\n    // non-isolated context throws, which would reject instead of returning Err\n    if (!isSyncChannelSupported()) {\n        return Err(createUnsupportedEnvironmentError());\n    }\n\n    const state = getSyncChannelState();\n    if (state === 'ready') {\n        return Err(new Error('Sync channel already connected'));\n    }\n    if (state === 'connecting') {\n        return Err(new Error('Sync channel is connecting'));\n    }\n\n    const {\n        sharedBufferLength = DEFAULT_BUFFER_LENGTH,\n        opTimeout = DEFAULT_OP_TIMEOUT,\n        connectTimeout = DEFAULT_CONNECT_TIMEOUT,\n        workerType = 'classic',\n    } = options ?? {};\n\n    // check parameters\n    if (\n        !(\n            worker instanceof Worker ||\n            worker instanceof URL ||\n            (typeof worker === 'string' && worker)\n        )\n    ) {\n        return Err(new TypeError('worker must be a Worker, URL, or non-empty string'));\n    }\n    if (!(sharedBufferLength >= MIN_BUFFER_LENGTH && sharedBufferLength % 4 === 0)) {\n        return Err(new TypeError('sharedBufferLength must be at least 256 and a multiple of 4'));\n    }\n    if (!(Number.isInteger(opTimeout) && opTimeout > 0)) {\n        return Err(new TypeError('opTimeout must be a positive integer'));\n    }\n    if (!(Number.isInteger(connectTimeout) && connectTimeout > 0)) {\n        return Err(new TypeError('connectTimeout must be a positive integer'));\n    }\n\n    // May throw if the worker url is invalid or the worker type is unknown\n    let workerAdapter: Worker;\n    try {\n        workerAdapter =\n            worker instanceof Worker ? worker : new Worker(worker, { type: workerType });\n    } catch (e) {\n        return Err(e as Error);\n    }\n\n    // Whether this function created the worker. Only owned workers may be\n    // terminated on failure; caller-supplied Workers are left intact.\n    const ownsWorker = !(worker instanceof Worker);\n\n    // Set state after all validations pass\n    setSyncChannelState('connecting');\n\n    // Construction can still fail with SharedArrayBuffer available, e.g. when\n    // sharedBufferLength exceeds the maximum size the browser can allocate\n    let sab: SharedArrayBuffer;\n    let channel: MessageChannel;\n    try {\n        sab = new SharedArrayBuffer(sharedBufferLength);\n        channel = new MessageChannel();\n    } catch (e) {\n        // Leave no partial state behind so the caller can retry\n        setSyncChannelState('idle');\n        if (ownsWorker) {\n            workerAdapter.terminate();\n        }\n        return Err(e as Error);\n    }\n\n    setGlobalSyncOpTimeout(opTimeout);\n\n    const future = new Future<SharedArrayBuffer>();\n\n    // Shared teardown: clear the timer, detach all event sources (error listener +\n    // port onmessage) and release the handshake port. Called exactly once —\n    // success and failure paths below are the only callers, and teardown detaches\n    // every other event source, so no second caller can reach teardown again.\n    const teardown = (): void => {\n        clearTimeout(timer);\n        workerAdapter.removeEventListener('error', onError);\n        channel.port1.onmessage = null;\n        // The port only carries the one-shot handshake, release it either way\n        channel.port1.close();\n    };\n\n    // Failure path: terminate owned worker, reset state so the caller may retry,\n    // and reject the future — connectSyncChannel returns Err instead of hanging\n    // forever when the worker never calls SyncChannel.listen().\n    const cleanup = (reason: unknown): void => {\n        teardown();\n        if (ownsWorker) {\n            workerAdapter.terminate();\n        }\n        setSyncChannelState('idle');\n        future.reject(reason);\n    };\n\n    const onError = (e: ErrorEvent): void => {\n        cleanup(e.error ?? new Error(e.message || 'Worker failed to load'));\n    };\n\n    const timer = setTimeout(\n        () => cleanup(new DOMException('Sync channel connection timed out', TIMEOUT_ERROR)),\n        connectTimeout,\n    );\n\n    workerAdapter.addEventListener('error', onError);\n\n    // Use MessageChannel for the one-shot handshake only: worker sends a\n    // single null payload via port2 once it has called SyncChannel.listen()\n    // (see listen.ts). After that, all further communication goes through\n    // the SharedArrayBuffer-based SyncMessenger, not this port — so the\n    // onmessage handler fires exactly once and is detached by teardown().\n    // port1 stays in main thread, port2 is transferred to worker.\n    channel.port1.onmessage = (): void => {\n        teardown();\n        setMessenger(new SyncMessenger(sab));\n\n        if (ownsWorker) {\n            // Remember the worker we created, so disconnect() can terminate it\n            setOwnedWorker(workerAdapter);\n        }\n\n        future.resolve(sab);\n    };\n\n    workerAdapter.postMessage({ port: channel.port2, sab }, [channel.port2]);\n\n    try {\n        return Ok(await future.promise);\n    } catch (e) {\n        // future.reject path already cleaned up state/resources\n        return Err(e as Error);\n    }\n}\n\n/**\n * Checks if the sync channel is ready to use.\n *\n * @returns `true` if ready, `false` otherwise.\n * @since 1.11.0\n * @example\n * ```typescript\n * if (!SyncChannel.isReady()) {\n *     await SyncChannel.connect(new URL('./worker.js', import.meta.url));\n * }\n * ```\n */\nexport function isSyncChannelReady(): boolean {\n    return getSyncChannelState() === 'ready';\n}\n\n/**\n * Disconnects the sync channel and releases the resources owned by this context.\n *\n * A worker created by `connect` is terminated. A caller-supplied worker, or the\n * worker behind a buffer shared with `attach`, is left running so other contexts\n * that still use that buffer keep working. After disconnecting, `connect` or\n * `attach` can be called again — this is also the way out of a channel that got\n * stuck (worker crashed, response never arrived).\n *\n * Calling it while not connected is a no-op.\n *\n * @returns A `VoidIOResult` indicating success.\n * @since 2.3.0\n * @see {@link connectSyncChannel} for establishing a connection\n * @example\n * ```typescript\n * SyncChannel.disconnect();\n *\n * // A fresh connection can be established afterwards\n * await SyncChannel.connect(new URL('./worker.js', import.meta.url));\n * ```\n */\nexport function disconnectSyncChannel(): VoidIOResult {\n    const worker = resetSyncChannel();\n\n    // Only the worker this context created may be terminated\n    if (worker) {\n        worker.terminate();\n    }\n\n    return RESULT_VOID;\n}\n\n/**\n * Attaches to an existing `SharedArrayBuffer` for synchronous file system operations.\n * Used to share a sync channel connection with other contexts (e.g., iframes).\n *\n * After calling this function, sync APIs (e.g., `readFileSync`, `writeFileSync`)\n * can be used in the current context without calling `connect`.\n *\n * @param sharedBuffer - The `SharedArrayBuffer` received from another context.\n * @param options - Optional configuration options.\n * @returns A `VoidIOResult` indicating success or failure.\n * @since 1.8.5\n * @example\n * ```typescript\n * // In iframe: receive SharedArrayBuffer from main page\n * window.addEventListener('message', (event) => {\n *     if (event.data.sharedBuffer) {\n *         const result = SyncChannel.attach(event.data.sharedBuffer, { opTimeout: 5000 });\n *         if (result.isOk()) {\n *             // Now sync APIs can be used\n *             const fileResult = readTextFileSync('/data/file.txt');\n *         }\n *     }\n * });\n * ```\n */\nexport function attachSyncChannel(\n    sharedBuffer: SharedArrayBuffer,\n    options?: AttachSyncChannelOptions,\n): VoidIOResult {\n    // Without this guard the `instanceof` check below throws when the runtime\n    // has no SharedArrayBuffer at all\n    if (!isSyncChannelSupported()) {\n        return Err(createUnsupportedEnvironmentError());\n    }\n\n    const state = getSyncChannelState();\n    if (state === 'connecting') {\n        return Err(new Error('Cannot attach: sync channel is connecting'));\n    }\n\n    if (!(sharedBuffer instanceof SharedArrayBuffer)) {\n        return Err(new TypeError('sharedBuffer must be a SharedArrayBuffer'));\n    }\n\n    const { opTimeout = DEFAULT_OP_TIMEOUT } = options ?? {};\n    if (!(Number.isInteger(opTimeout) && opTimeout > 0)) {\n        return Err(new TypeError('opTimeout must be a positive integer'));\n    }\n\n    setGlobalSyncOpTimeout(opTimeout);\n    setMessenger(new SyncMessenger(sharedBuffer));\n\n    return RESULT_VOID;\n}\n\n// #region Internal Functions\n\n/**\n * Creates the error returned when the runtime cannot provide a SharedArrayBuffer,\n * which only exists in cross-origin isolated contexts (COOP/COEP headers).\n *\n * @returns An `Error` describing the missing requirement.\n */\nfunction createUnsupportedEnvironmentError(): Error {\n    return new Error(\n        'SharedArrayBuffer is unavailable: SyncChannel requires a cross-origin isolated context (COOP/COEP headers)',\n    );\n}\n\n// #endregion\n","import { Err, RESULT_VOID, type AsyncIOResult, type VoidIOResult } from 'happy-rusty';\nimport {\n    copy,\n    createFile,\n    deleteTemp,\n    emptyDir,\n    exists,\n    mkdir,\n    mkTemp,\n    move,\n    pruneTemp,\n    readBlobFile,\n    readDir,\n    readFile,\n    remove,\n    stat,\n    truncate,\n    unzip,\n    writeFile,\n    zip,\n} from '../../async/mod.ts';\nimport { readBlobBytesSync } from '../../shared/internal/mod.ts';\nimport {\n    isFileHandle,\n    type DirEntry,\n    type DirEntryLike,\n    type DirEntrySlim,\n    type FileSystemDirectoryHandleLike,\n    type FileSystemFileHandleLike,\n    type FileSystemHandleLike,\n    type ReadDirSyncOptions,\n} from '../../shared/mod.ts';\nimport type { ErrorLike, FileMetadata } from '../defines.ts';\nimport {\n    DATA_INDEX,\n    decodePayload,\n    encodePayload,\n    MAIN_LOCK_INDEX,\n    MAIN_UNLOCKED,\n    SyncMessenger,\n    WORKER_LOCK_INDEX,\n    WorkerOp,\n} from '../protocol.ts';\n\n// #region Internal Variables\n\n/**\n * Worker thread locked value.\n * Default.\n */\nconst WORKER_LOCKED = MAIN_UNLOCKED;\n\n/**\n * Safety net for the worker's blocking wait, in milliseconds.\n * `Atomics.notify` is what normally releases the worker; the timeout only exists\n * so a missed notification (or a future caller that never notifies) cannot hang\n * the channel forever. A wake-up per second costs nothing.\n */\nconst WORKER_WAIT_TIMEOUT = 1000;\n\n/**\n * Mapping of async operation enums to their corresponding OPFS functions.\n * Used by the worker loop to dispatch operations from the main thread.\n *\n * Key: `WorkerOp` value\n * Value: The async function to execute\n */\nconst opHandlers = {\n    [WorkerOp.createFile]: createFile,\n    [WorkerOp.mkdir]: mkdir,\n    [WorkerOp.move]: move,\n    [WorkerOp.readDir]: readDir,\n    [WorkerOp.readFile]: readFile,\n    [WorkerOp.remove]: remove,\n    [WorkerOp.stat]: stat,\n    [WorkerOp.truncate]: truncate,\n    [WorkerOp.writeFile]: writeFile,\n    [WorkerOp.copy]: copy,\n    [WorkerOp.emptyDir]: emptyDir,\n    [WorkerOp.exists]: exists,\n    [WorkerOp.deleteTemp]: deleteTemp,\n    [WorkerOp.mkTemp]: mkTemp,\n    [WorkerOp.pruneTemp]: pruneTemp,\n    [WorkerOp.readBlobFile]: readBlobFile,\n    [WorkerOp.unzip]: unzip,\n    [WorkerOp.zip]: zip,\n};\n\n/**\n * Cache the messenger instance.\n */\nlet messenger: SyncMessenger;\n\n/**\n * Flag to track if listenSyncChannel has been called.\n * Used to prevent multiple event listeners from being registered.\n */\nlet isListening = false;\n\n// #endregion\n\n/**\n * Starts listening for sync channel requests in a Web Worker.\n * Waits for a SharedArrayBuffer from the main thread and begins processing requests.\n *\n * @returns A `VoidIOResult` indicating success or failure.\n * @since 1.1.0\n * @example\n * ```typescript\n * // In worker.js\n * import { SyncChannel } from 'happy-opfs';\n * const result = SyncChannel.listen();\n * if (result.isErr()) {\n *     console.error('Failed to start listening:', result.unwrapErr());\n * }\n * ```\n */\nexport function listenSyncChannel(): VoidIOResult {\n    if (!isWorkerRuntime()) {\n        return Err(new Error('listenSyncChannel can only be called in Worker'));\n    }\n    if (isListening) {\n        return Err(new Error('Sync channel already listening'));\n    }\n\n    isListening = true;\n\n    const messageHandler = (event: MessageEvent<unknown>): void => {\n        const { data } = event;\n\n        // Ignore messages that are not SyncChannelInitMessage\n        if (!isSyncChannelInitMessage(data)) {\n            return;\n        }\n\n        // Remove listener after receiving valid init message\n        removeEventListener('message', messageHandler);\n\n        const { port, sab } = data;\n\n        messenger = new SyncMessenger(sab);\n\n        // Notify main thread that the worker is ready. This MessagePort is\n        // used for the one-shot handshake only — after this, requests and\n        // responses flow through the SharedArrayBuffer-based SyncMessenger.\n        // The main thread detaches port1.onmessage after receiving this signal.\n        port.postMessage(null);\n\n        // Start waiting for requests\n        void runWorkerLoop();\n    };\n\n    addEventListener('message', messageHandler);\n\n    return RESULT_VOID;\n}\n\n// #region Internal Types\n\n/**\n * Message sent from main thread to worker to initialize sync channel.\n * Uses MessageChannel for isolated communication.\n */\ninterface SyncChannelInitMessage {\n    /**\n     * MessagePort for dedicated communication back to main thread.\n     */\n    port: MessagePort;\n    /**\n     * SharedArrayBuffer for cross-thread synchronous communication.\n     */\n    sab: SharedArrayBuffer;\n}\n\n// #endregion\n\n// #region Internal Functions\n\n/**\n * Checks whether the code is running in a Web Worker context.\n */\nfunction isWorkerRuntime(): boolean {\n    return typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;\n}\n\n/**\n * Type guard to check if a message is a SyncChannelInitMessage.\n * @param data - The message data to check.\n * @returns `true` if the message is a valid SyncChannelInitMessage.\n */\nfunction isSyncChannelInitMessage(data: unknown): data is SyncChannelInitMessage {\n    if (data == null || typeof data !== 'object') {\n        return false;\n    }\n\n    const message = data as SyncChannelInitMessage;\n    return message.port instanceof MessagePort && message.sab instanceof SharedArrayBuffer;\n}\n\n/**\n * Serializes a `FileSystemHandle` to a plain object for cross-thread communication.\n *\n * @param handle - `FileSystemHandle` object.\n * @returns Serializable version of FileSystemHandle that is FileSystemHandleLike.\n */\nasync function serializeFileSystemHandle(handle: FileSystemHandle): Promise<FileSystemHandleLike> {\n    const { name } = handle;\n\n    if (isFileHandle(handle)) {\n        const file = await handle.getFile();\n        const { size, lastModified, type } = file;\n\n        const fileHandle: FileSystemFileHandleLike = {\n            name,\n            kind: 'file',\n            type,\n            size,\n            lastModified,\n        };\n\n        return fileHandle;\n    }\n\n    const handleLike: FileSystemDirectoryHandleLike = {\n        name,\n        kind: 'directory',\n    };\n\n    return handleLike;\n}\n\n/**\n * Serializes an `Error` object to a plain object for cross-thread communication.\n *\n * @param error - The `Error` object to serialize, or `null`.\n * @returns A serializable `ErrorLike` object, or `null` if input is `null`.\n */\nfunction serializeError(error: Error | null): ErrorLike | null {\n    return error\n        ? {\n              name: error.name,\n              message: error.message,\n          }\n        : error;\n}\n\n/**\n * Handles incoming requests from main thread and sends responses.\n * This function runs in the worker thread and processes one request.\n *\n * @param messenger - The `SyncMessenger` instance for communication.\n * @param transfer - Async function that processes request data and returns response data.\n */\nasync function respondToMainFromWorker(\n    messenger: SyncMessenger,\n    transfer: (data: Uint8Array<SharedArrayBuffer>) => Promise<Uint8Array<ArrayBuffer>>,\n): Promise<void> {\n    const { i32a, maxDataLength } = messenger;\n\n    // Block until the main thread signals a request. `Atomics.wait` is only\n    // available off the main thread, and a notification can be missed when it\n    // arrives between the load and the wait, so the condition is re-checked in a\n    // bounded loop instead of waiting forever.\n    while (Atomics.load(i32a, WORKER_LOCK_INDEX) === WORKER_LOCKED) {\n        Atomics.wait(i32a, WORKER_LOCK_INDEX, WORKER_LOCKED, WORKER_WAIT_TIMEOUT);\n    }\n\n    // payload and length\n    const requestLength = i32a[DATA_INDEX];\n    const data = messenger.getPayload(requestLength);\n\n    // call async I/O operation\n    let response = await transfer(data);\n\n    // check whether response is too large\n    if (response.byteLength > maxDataLength) {\n        const message = `Response is too large: ${response.byteLength} > ${maxDataLength}. Consider increasing the size of SharedArrayBuffer`;\n\n        // Error response is guaranteed to fit since bufferLength >= 256 bytes\n        // and error response is ~147 bytes (16 header + 131 payload)\n        response = encodePayload([\n            {\n                name: 'RangeError',\n                message,\n            },\n        ]);\n    }\n\n    // write response data\n    i32a[DATA_INDEX] = response.byteLength;\n    messenger.setPayload(response);\n\n    // lock worker thread\n    Atomics.store(i32a, WORKER_LOCK_INDEX, WORKER_LOCKED);\n\n    // wakeup main thread\n    Atomics.store(i32a, MAIN_LOCK_INDEX, MAIN_UNLOCKED);\n}\n\n/**\n * Deserializes request arguments based on operation type.\n * Binary data is received as the last element from the binary protocol.\n *\n * @param op - The operation type.\n * @param args - The arguments to deserialize.\n */\nfunction deserializeArgs(op: WorkerOp, args: unknown[]): void {\n    switch (op) {\n        case WorkerOp.writeFile: {\n            // Request format: [filePath, options?, Uint8Array]\n            // Expected args: [filePath, Uint8Array, options?]\n            // Move Uint8Array from last position to second position\n            const data = args.pop();\n            const options = args[1];\n            args[1] = data;\n            args[2] = options;\n            break;\n        }\n        case WorkerOp.readFile: {\n            // Always use bytes encoding (default) - adapter handles encoding conversion\n            // This avoids double encoding/decoding for utf8 (string -> bytes -> string)\n            args.length = 1;\n            break;\n        }\n        case WorkerOp.pruneTemp: {\n            // Date was serialized as ISO string, reconstruct Date object\n            args[0] = new Date(args[0] as Date);\n            break;\n        }\n        // Other operations need no deserialization\n    }\n}\n\n/**\n * Serializes a readDir result (async iterator) to an array of serializable entries.\n * Uses parallel processing for better performance since getFile() involves disk I/O.\n *\n * @param iterator - The async iterator from readDir.\n * @param withMetadata - Whether file entries carry metadata; `false` skips the per-file\n *                       `getFile()` lookup and returns `{ path, kind }` entries instead.\n * @returns Promise resolving to array of serializable directory entries.\n */\nasync function serializeReadDirResult(\n    iterator: AsyncIterableIterator<DirEntry>,\n    withMetadata: boolean,\n): Promise<(DirEntryLike | DirEntrySlim)[]> {\n    // Collect and serialize handles in parallel - getFile() involves disk I/O\n    const tasks: Promise<DirEntryLike | DirEntrySlim>[] = [];\n\n    for await (const { path, handle } of iterator) {\n        tasks.push(\n            withMetadata\n                ? (async () => ({\n                      path,\n                      handle: await serializeFileSystemHandle(handle),\n                  }))()\n                : Promise.resolve({ path, kind: handle.kind }),\n        );\n    }\n\n    return Promise.all(tasks);\n}\n\n/**\n * Serializes readBlobFile result to [metadata, Uint8Array] for binary protocol.\n *\n * @param file - The File object from readBlobFile.\n * @returns Array of [metadata, bytes] to be spread into response.\n */\nfunction serializeReadBlobFileResult(file: File): [FileMetadata, Uint8Array<ArrayBuffer>] {\n    const metadata: FileMetadata = {\n        name: file.name,\n        type: file.type,\n        lastModified: file.lastModified,\n    };\n    return [metadata, readBlobBytesSync(file)];\n}\n\n/**\n * Processes a single request from the main thread.\n *\n * @param data - The encoded request data.\n * @returns The encoded response data.\n */\nasync function processRequest(\n    data: Uint8Array<SharedArrayBuffer>,\n): Promise<Uint8Array<ArrayBuffer>> {\n    try {\n        // Decode the request: [operation, ...arguments]\n        const [op, ...args] =\n            decodePayload<[WorkerOp, ...Parameters<(typeof opHandlers)[WorkerOp]>]>(data);\n\n        deserializeArgs(op, args);\n\n        const handle = opHandlers[op] as (...args: unknown[]) => AsyncIOResult<unknown>;\n        const res = await handle(...args);\n\n        if (res.isErr()) {\n            // Operation failed - encode error only: [serializedError]\n            return encodePayload([serializeError(res.unwrapErr())]);\n        }\n\n        const result = res.unwrap();\n        // Build response array: [null (no error), ...serialized result]\n        const response: [null, ...unknown[]] = [null];\n\n        // Serialize result based on operation type\n        switch (op) {\n            case WorkerOp.stat: {\n                response.push(await serializeFileSystemHandle(result as FileSystemHandle));\n                break;\n            }\n            case WorkerOp.readDir: {\n                const options = args[1] as ReadDirSyncOptions | undefined;\n                response.push(\n                    await serializeReadDirResult(\n                        result as AsyncIterableIterator<DirEntry>,\n                        options?.withMetadata ?? true,\n                    ),\n                );\n                break;\n            }\n            case WorkerOp.readBlobFile: {\n                response.push(...serializeReadBlobFileResult(result as File));\n                break;\n            }\n            default: {\n                // Result is void, boolean, string, or Uint8Array\n                response.push(result);\n            }\n        }\n\n        return encodePayload(response);\n    } catch (e) {\n        return encodePayload([serializeError(e as Error)]);\n    }\n}\n\n/**\n * Main loop that continuously processes requests from the main thread.\n * Runs indefinitely until the worker is terminated.\n */\nasync function runWorkerLoop(): Promise<void> {\n    // No try-catch needed:\n    // - processRequest catches all exceptions and returns encoded error response\n    // - respondToMainFromWorker never throws (error response always fits in buffer >= 256 bytes)\n    while (true) {\n        await respondToMainFromWorker(messenger, processRequest);\n    }\n}\n\n// #endregion\n"],"mappings":"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"}