{"version":3,"file":"types.d.ts","sources":["../src/fs/assertions.ts","../src/fs/constants.ts","../src/fs/defines.ts","../src/fs/opfs_core.ts","../src/fs/opfs_download.ts","../src/fs/opfs_ext.ts","../src/fs/opfs_tmp.ts","../src/fs/opfs_unzip.ts","../src/fs/opfs_upload.ts","../src/fs/opfs_zip.ts","../src/fs/support.ts","../src/fs/utils.ts","../src/worker/opfs_worker.ts","../src/worker/shared.ts","../src/worker/opfs_worker_adapter.ts"],"sourcesContent":["import invariant from 'tiny-invariant';\nimport { ROOT_DIR } from './constants.ts';\n\n/**\n * Asserts that the provided path is an absolute path.\n *\n * @param path - The file path to validate.\n * @throws Will throw an error if the path is not an absolute path.\n */\nexport function assertAbsolutePath(path: string): void {\n    invariant(typeof path === 'string', () => `Path must be a string but received ${ path }`);\n    invariant(path[0] === ROOT_DIR, () => `Path must start with / but received ${ path }`);\n}\n\n/**\n * Asserts that the provided URL is a valid file URL.\n *\n * @param fileUrl - The file URL to validate.\n * @throws Will throw an error if the URL is not a valid file URL.\n */\nexport function assertFileUrl(fileUrl: string): void {\n    invariant(typeof fileUrl === 'string', () => `File url must be a string but received ${ fileUrl }`);\n}","export { ABORT_ERROR, TIMEOUT_ERROR } from '@happy-ts/fetch-t';\n\n/**\n * A constant representing the error thrown when a file or directory is not found.\n * Name of DOMException.NOT_FOUND_ERR.\n */\nexport const NOT_FOUND_ERROR = 'NotFoundError' as const;\n\n/**\n * A constant representing the root directory path.\n */\nexport const ROOT_DIR = '/' as const;\n\n/**\n * A constant representing the current directory path.\n */\nexport const CURRENT_DIR = '.' as const;\n\n/**\n * A constant representing the temporary directory path.\n */\nexport const TMP_DIR = '/tmp' as const;","import type { FetchInit } from '@happy-ts/fetch-t';\n\n/**\n * Represents the possible content types that can be written to a file.\n */\nexport type WriteFileContent = BufferSource | Blob | string;\n\n/**\n * Represents the possible content types that can be written synchronously to a file.\n */\nexport type WriteSyncFileContent = BufferSource | string;\n\n/**\n * Represents the possible content types that can be read from a file.\n */\nexport type ReadFileContent = ArrayBuffer | File | string;\n\n/**\n * Options for reading files with specified encoding.\n */\nexport interface ReadOptions {\n    /**\n     * The encoding to use for reading the file's content.\n     * @defaultValue `'binary'`\n     */\n    encoding?: FileEncoding;\n}\n\n/**\n * Options for writing files, including flags for creation and appending.\n */\nexport interface WriteOptions {\n    /**\n     * Whether to create the file if it does not exist.\n     * @defaultValue `true`\n     */\n    create?: boolean;\n\n    /**\n     * Whether to append to the file if it already exists.\n     * @defaultValue `false`\n     */\n    append?: boolean;\n}\n\n/**\n * Options to determine the existence of a file or directory.\n */\nexport interface ExistsOptions {\n    /**\n     * Whether to check for the existence of a directory.\n     * @defaultValue `false`\n     */\n    isDirectory?: boolean;\n\n    /**\n     * Whether to check for the existence of a file.\n     * @defaultValue `false`\n     */\n    isFile?: boolean;\n}\n\n/**\n * Supported file encodings for reading and writing files.\n */\nexport type FileEncoding = 'binary' | 'utf8' | 'blob';\n\n/**\n * fetch-t options for download and upload.\n */\nexport type FsRequestInit = Omit<FetchInit, 'abortable' | 'responseType'>\n\n/**\n * fetch-t request options for uploading files.\n */\nexport interface UploadRequestInit extends FsRequestInit {\n    /**\n     * The filename to use when uploading the file.\n     */\n    filename?: string;\n}\n\n/**\n * Options for reading directories.\n */\nexport interface ReadDirOptions {\n    /**\n     * Whether to recursively read the contents of directories.\n     */\n    recursive: boolean;\n}\n\n/**\n * An entry returned by `readDir`.\n */\nexport interface ReadDirEntry {\n    /**\n     * The relative path of the entry from readDir the path parameter.\n     */\n    path: string;\n\n    /**\n     * The handle of the entry.\n     */\n    handle: FileSystemHandle;\n}\n\n/**\n * An entry returned by `readDirSync`.\n */\nexport interface ReadDirEntrySync {\n    /**\n     * The relative path of the entry from readDir the path parameter.\n     */\n    path: string;\n\n    /**\n     * The handle of the entry.\n     */\n    handle: FileSystemHandleLike;\n}\n\n/**\n * A handle to a file or directory returned by `statSync`.\n */\nexport interface FileSystemHandleLike {\n    /**\n     * The name of the entry.\n     */\n    name: string;\n\n    /**\n     * The kind of the entry.\n     */\n    kind: FileSystemHandleKind;\n}\n\nexport interface FileSystemFileHandleLike extends FileSystemHandleLike {\n    /**\n     * The type of the file.\n     */\n    type: string;\n\n    /**\n     * The size of the file.\n     */\n    size: number;\n\n    /**\n     * The last modified time of the file.\n     */\n    lastModified: number;\n}\n\n/**\n * Serializable version of Error.\n */\nexport interface ErrorLike {\n    /**\n     * The name of the error.\n     */\n    name: string;\n\n    /**\n     * The message of the error.\n     */\n    message: string;\n}\n\n/**\n * Serializable version of File.\n */\nexport interface FileLike {\n    /**\n     * The name of the file.\n     */\n    name: string;\n\n    /**\n     * The type of the file.\n     */\n    type: string;\n\n    /**\n     * The last modified time of the file.\n     */\n    lastModified: number;\n\n    /**\n     * The size of the file.\n     */\n    size: number;\n\n    /**\n     * The binary data of the file.\n     */\n    data: ArrayBuffer;\n}\n\n/**\n * Setup options of `connectSyncAgent`.\n */\nexport interface SyncAgentOptions {\n    /**\n     * The worker to communicate with.\n     */\n    worker: Worker | URL | string;\n\n    /**\n     * The length of the buffer to use for communication.\n    */\n    bufferLength?: number;\n\n    /**\n     * The timeout for operations.\n     */\n    opTimeout?: number;\n}\n\n/**\n * Options for `zip`.\n */\nexport interface ZipOptions {\n    /**\n     * Whether to preserve the root directory in the zip file.\n     * @defaultValue `true`\n     */\n    preserveRoot: boolean;\n}\n\n/**\n * Options for `mkTemp`.\n */\nexport interface TempOptions {\n    /**\n     * Whether to create a directory.\n     * eg: `mktemp -d`\n     * @defaultValue `false`\n     */\n    isDirectory?: boolean;\n\n    /**\n     * The basename of the file or directory.\n     * eg: `mktemp -t basename.XXX`\n     * @defaultValue `tmp`\n     */\n    basename?: string;\n\n    /**\n     * The extension of the file.\n     * eg: `mktemp --suffix .txt`\n     */\n    extname?: string;\n}\n\n/**\n * Options for `copy`.\n */\nexport interface CopyOptions {\n    /**\n     * Whether to overwrite the destination file if it already exists.\n     * @defaultValue `true`\n     */\n    overwrite?: boolean;\n}\n\n/**\n * Result of `downloadFile` when the file is saved to a temporary path.\n */\nexport interface DownloadFileTempResponse {\n    /**\n     * The temporary path of the downloaded file to be saved.\n     */\n    tempFilePath: string;\n\n    /**\n     * The raw response.\n     */\n    rawResponse: Response;\n}\n\n/**\n * Options for `move`.\n */\nexport interface MoveOptions {\n    /**\n     * Whether to overwrite the destination file if it already exists.\n     * @defaultValue `true`\n     */\n    overwrite?: boolean;\n}","import { basename, dirname, join } from '@std/path/posix';\nimport { Err, Ok, RESULT_VOID, type AsyncIOResult, type AsyncVoidIOResult } from 'happy-rusty';\nimport { assertAbsolutePath } from './assertions.ts';\nimport { NOT_FOUND_ERROR } from './constants.ts';\nimport type { ReadDirEntry, ReadDirOptions, ReadFileContent, ReadOptions, WriteFileContent, WriteOptions } from './defines.ts';\nimport { getDirHandle, getFileHandle, isNotFoundError, isRootPath } from './helpers.ts';\nimport { isDirectoryHandle } from './utils.ts';\n\n/**\n * Creates a new file at the specified path same as `touch`.\n *\n * @param filePath - The path of the file to create.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the file was successfully created.\n */\nexport async function createFile(filePath: string): AsyncVoidIOResult {\n    assertAbsolutePath(filePath);\n\n    const fileHandleRes = await getFileHandle(filePath, {\n        create: true,\n    });\n\n    return fileHandleRes.and(RESULT_VOID);\n}\n\n/**\n * Creates a new directory at the specified path same as `mkdir -p`.\n *\n * @param dirPath - The path where the new directory will be created.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the directory was successfully created.\n */\nexport async function mkdir(dirPath: string): AsyncVoidIOResult {\n    assertAbsolutePath(dirPath);\n\n    const dirHandleRes = await getDirHandle(dirPath, {\n        create: true,\n    });\n\n    return dirHandleRes.and(RESULT_VOID);\n}\n\n/**\n * Reads the contents of a directory at the specified path.\n *\n * @param dirPath - The path of the directory to read.\n * @param options - Options of readdir.\n * @returns A promise that resolves to an `AsyncIOResult` containing an async iterable iterator over the entries of the directory.\n */\nexport async function readDir(dirPath: string, options?: ReadDirOptions): AsyncIOResult<AsyncIterableIterator<ReadDirEntry>> {\n    assertAbsolutePath(dirPath);\n\n    const dirHandleRes = await getDirHandle(dirPath);\n\n    async function* read(dirHandle: FileSystemDirectoryHandle, subDirPath: string): AsyncIterableIterator<ReadDirEntry> {\n        const entries = dirHandle.entries();\n\n        for await (const [name, handle] of entries) {\n            // relative path from `dirPath`\n            const path = subDirPath === dirPath ? name : join(subDirPath, name);\n            yield {\n                path,\n                handle,\n            };\n\n            if (isDirectoryHandle(handle) && options?.recursive) {\n                yield* read(await dirHandle.getDirectoryHandle(name), path);\n            }\n        }\n    }\n\n    return dirHandleRes.andThen(x => Ok(read(x, dirPath)));\n}\n\n/**\n * Reads the content of a file at the specified path as a File.\n *\n * @param filePath - The path of the file to read.\n * @param options - Read options specifying the 'blob' encoding.\n * @returns A promise that resolves to an `AsyncIOResult` containing the file content as a File.\n */\nexport function readFile(filePath: string, options: ReadOptions & {\n    encoding: 'blob';\n}): AsyncIOResult<File>;\n\n/**\n * Reads the content of a file at the specified path as a string.\n *\n * @param filePath - The path of the file to read.\n * @param options - Read options specifying the 'utf8' encoding.\n * @returns A promise that resolves to an `AsyncIOResult` containing the file content as a string.\n */\nexport function readFile(filePath: string, options: ReadOptions & {\n    encoding: 'utf8';\n}): AsyncIOResult<string>;\n\n/**\n * Reads the content of a file at the specified path as an ArrayBuffer by default.\n *\n * @param filePath - The path of the file to read.\n * @param options - Read options specifying the 'binary' encoding.\n * @returns A promise that resolves to an `AsyncIOResult` containing the file content as an ArrayBuffer.\n */\nexport function readFile(filePath: string, options?: ReadOptions & {\n    encoding: 'binary';\n}): AsyncIOResult<ArrayBuffer>;\n\n/**\n * Reads the content of a file at the specified path with the specified options.\n *\n * @template T The type of the content to read from the file.\n * @param filePath - The path of the file to read.\n * @param options - Optional read options.\n * @returns A promise that resolves to an `AsyncIOResult` containing the file content.\n */\nexport async function readFile<T extends ReadFileContent>(filePath: string, options?: ReadOptions): AsyncIOResult<T> {\n    assertAbsolutePath(filePath);\n\n    const fileHandleRes = await getFileHandle(filePath);\n\n    return fileHandleRes.andThenAsync(async fileHandle => {\n        const file = await fileHandle.getFile();\n        switch (options?.encoding) {\n            case 'blob': {\n                return Ok(file as unknown as T);\n            }\n            case 'utf8': {\n                const text = await file.text();\n                return Ok(text as unknown as T);\n            }\n            default: {\n                const data = await file.arrayBuffer();\n                return Ok(data as unknown as T);\n            }\n        }\n    });\n}\n\n/**\n * Removes a file or directory at the specified path same as `rm -rf`.\n *\n * @param path - The path of the file or directory to remove.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the file or directory was successfully removed.\n */\nexport async function remove(path: string): AsyncVoidIOResult {\n    assertAbsolutePath(path);\n\n    const dirPath = dirname(path);\n    const childName = basename(path);\n\n    const dirHandleRes = await getDirHandle(dirPath);\n\n    return (await dirHandleRes.andThenAsync(async (dirHandle): AsyncVoidIOResult => {\n        try {\n            // root\n            if (isRootPath(dirPath) && isRootPath(childName)) {\n                // TODO ts not support yet\n                // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                await (dirHandle as any).remove({\n                    recursive: true,\n                });\n            } else {\n                await dirHandle.removeEntry(childName, {\n                    recursive: true,\n                });\n            }\n        } catch (e) {\n            return Err(e as DOMException);\n        }\n\n        return RESULT_VOID;\n    })).orElse<Error>(err => {\n        // not found as success\n        return isNotFoundError(err) ? RESULT_VOID : Err(err);\n    });\n}\n\n/**\n * Retrieves the status of a file or directory at the specified path.\n *\n * @param path - The path of the file or directory to retrieve status for.\n * @returns A promise that resolves to an `AsyncIOResult` containing the `FileSystemHandle`.\n */\nexport async function stat(path: string): AsyncIOResult<FileSystemHandle> {\n    assertAbsolutePath(path);\n\n    const dirPath = dirname(path);\n    const childName = basename(path);\n\n    const dirHandleRes = await getDirHandle(dirPath);\n    if (!childName) {\n        // root\n        return dirHandleRes;\n    }\n\n    return dirHandleRes.andThenAsync(async dirHandle => {\n        // currently only rely on traversal inspection\n        for await (const [name, handle] of dirHandle.entries()) {\n            if (name === childName) {\n                return Ok(handle);\n            }\n        }\n\n        const err = new Error(`${ NOT_FOUND_ERROR }: '${ childName }' does not exist. Full path is '${ path }'.`);\n        err.name = NOT_FOUND_ERROR;\n\n        return Err(err);\n    });\n}\n\n/**\n * Writes content to a file at the specified path.\n *\n * @param filePath - The path of the file to write to.\n * @param contents - The content to write to the file.\n * @param options - Optional write options.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the file was successfully written.\n */\nexport async function writeFile(filePath: string, contents: WriteFileContent, options?: WriteOptions): AsyncVoidIOResult {\n    assertAbsolutePath(filePath);\n\n    // create as default\n    const { append = false, create = true } = options ?? {};\n\n    const fileHandleRes = await getFileHandle(filePath, {\n        create,\n    });\n\n    return fileHandleRes.andThenAsync(async fileHandle => {\n        const writable = await fileHandle.createWritable({\n            keepExistingData: append,\n        });\n        const params: WriteParams = {\n            type: 'write',\n            data: contents,\n        };\n\n        // append?\n        if (append) {\n            const { size } = await fileHandle.getFile();\n            params.position = size;\n        }\n\n        await writable.write(params);\n        await writable.close();\n\n        return RESULT_VOID;\n    });\n}","import { fetchT, type FetchResponse, type FetchTask } from '@happy-ts/fetch-t';\nimport { extname } from '@std/path/posix';\nimport { Err, Ok } from 'happy-rusty';\nimport { assertAbsolutePath, assertFileUrl } from './assertions.ts';\nimport type { DownloadFileTempResponse, FsRequestInit } from './defines.ts';\nimport { createAbortError } from './helpers.ts';\nimport { writeFile } from './opfs_core.ts';\nimport { generateTempPath } from './utils.ts';\n\n/**\n * Downloads a file from a URL and saves it to a temporary file.\n * The returned response will contain the temporary file path.\n *\n * @param fileUrl - The URL of the file to download.\n * @param requestInit - Optional request initialization parameters.\n * @returns A task that can be aborted and contains the result of the download.\n */\nexport function downloadFile(fileUrl: string, requestInit?: FsRequestInit): FetchTask<DownloadFileTempResponse>;\n/**\n * Downloads a file from a URL and saves it to the specified path.\n *\n * @param fileUrl - The URL of the file to download.\n * @param filePath - The path where the downloaded file will be saved.\n * @param requestInit - Optional request initialization parameters.\n * @returns A task that can be aborted and contains the result of the download.\n */\nexport function downloadFile(fileUrl: string, filePath: string, requestInit?: FsRequestInit): FetchTask<Response>;\nexport function downloadFile(fileUrl: string, filePath?: string | FsRequestInit, requestInit?: FsRequestInit): FetchTask<Response | DownloadFileTempResponse> {\n    assertFileUrl(fileUrl);\n\n    let saveToTemp = false;\n\n    if (typeof filePath === 'string') {\n        assertAbsolutePath(filePath);\n    } else {\n        requestInit = filePath;\n        // save to a temporary file, reserve the extension\n        filePath = generateTempPath({\n            extname: extname(fileUrl),\n        });\n        saveToTemp = true;\n    }\n\n    let aborted = false;\n\n    const fetchTask = fetchT(fileUrl, {\n        redirect: 'follow',\n        ...requestInit,\n        abortable: true,\n    });\n\n    const response = (async (): FetchResponse<Response> => {\n        const responseRes = await fetchTask.response;\n\n        return responseRes.andThenAsync(async response => {\n            const blob = await response.blob();\n\n            // maybe aborted\n            if (aborted) {\n                return Err(createAbortError());\n            }\n\n            const writeRes = await writeFile(filePath, blob);\n\n            return writeRes.and(Ok(response));\n        });\n    })();\n\n    return {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        abort(reason?: any): void {\n            aborted = true;\n            fetchTask.abort(reason);\n        },\n\n        get aborted(): boolean {\n            return aborted;\n        },\n\n        get response(): FetchResponse<Response | DownloadFileTempResponse> {\n            return saveToTemp\n                ? response.then(res => {\n                    return res.map<DownloadFileTempResponse>(rawResponse => {\n                        return {\n                            tempFilePath: filePath,\n                            rawResponse,\n                        };\n                    });\n                })\n                : response;\n        },\n    };\n}","import { basename, dirname, join } from '@std/path/posix';\nimport { Err, Ok, RESULT_FALSE, RESULT_VOID, type AsyncIOResult, type AsyncVoidIOResult, type IOResult } from 'happy-rusty';\nimport invariant from 'tiny-invariant';\nimport { assertAbsolutePath } from './assertions.ts';\nimport type { CopyOptions, ExistsOptions, MoveOptions, WriteFileContent } from './defines.ts';\nimport { getDirHandle, getFinalResult, isNotFoundError } from './helpers.ts';\nimport { mkdir, readDir, readFile, remove, stat, writeFile } from './opfs_core.ts';\nimport { isDirectoryHandle, isFileHandle } from './utils.ts';\n\n/**\n * Moves a file handle to a new path.\n *\n * @param fileHandle - The file handle to move.\n * @param newPath - The new path of the file handle.\n * @returns A promise that resolves to an `AsyncVoidIOResult` indicating whether the file handle was successfully moved.\n */\nasync function moveHandle(fileHandle: FileSystemFileHandle, newPath: string): AsyncVoidIOResult {\n    const newDirPath = dirname(newPath);\n\n    return (await getDirHandle(newDirPath, {\n        create: true,\n    })).andThenAsync(async newDirHandle => {\n        const newName = basename(newPath);\n\n        try {\n            // TODO ts not support yet\n            // eslint-disable-next-line @typescript-eslint/no-explicit-any\n            await (fileHandle as any).move(newDirHandle, newName);\n            return RESULT_VOID;\n        } catch (e) {\n            return Err(e as DOMException);\n        }\n    });\n}\n\n/**\n * @param srcFileHandle - The source file handle to move or copy.\n * @param destFilePath - The destination file path.\n */\ntype handleSrcFileToDest = (srcFileHandle: FileSystemFileHandle, destFilePath: string) => AsyncVoidIOResult;\n/**\n * Copy or move a file or directory from one path to another.\n * @param srcPath - The source file/directory path.\n * @param destPath - The destination file/directory path.\n * @param handler - How to handle the file handle to the destination.\n * @param overwrite - Whether to overwrite the destination file if it exists.\n * @returns A promise that resolves to an `AsyncVoidIOResult` indicating whether the file was successfully copied/moved.\n */\nasync function mkDestFromSrc(srcPath: string, destPath: string, handler: handleSrcFileToDest, overwrite = true): AsyncVoidIOResult {\n    assertAbsolutePath(destPath);\n\n    return (await stat(srcPath)).andThenAsync(async srcHandle => {\n        // if overwrite is false, we need this flag to determine whether to write file.\n        let destExists = false;\n        const destHandleRes = await stat(destPath);\n\n        if (destHandleRes.isErr()) {\n            if (!isNotFoundError(destHandleRes.unwrapErr())) {\n                return destHandleRes.asErr();\n            }\n        } else {\n            destExists = true;\n            // check\n            const destHandle = destHandleRes.unwrap();\n            if (!((isFileHandle(srcHandle) && isFileHandle(destHandle))\n                || (isDirectoryHandle(srcHandle) && isDirectoryHandle(destHandle)))) {\n                return Err(new Error(`Both 'srcPath' and 'destPath' must both be a file or directory.`));\n            }\n        }\n\n        // both are files\n        if (isFileHandle(srcHandle)) {\n            return (overwrite || !destExists) ? await handler(srcHandle, destPath) : RESULT_VOID;\n        }\n\n        // both are directories\n        const readDirRes = await readDir(srcPath, {\n            recursive: true,\n        });\n        return readDirRes.andThenAsync(async entries => {\n            const tasks: AsyncVoidIOResult[] = [\n                // make sure new dir created\n                mkdir(destPath),\n            ];\n\n            for await (const { path, handle } of entries) {\n                const newEntryPath = join(destPath, path);\n\n                let newPathExists = false;\n                if (destExists) {\n                    // should check every file\n                    const existsRes = await exists(newEntryPath);\n                    if (existsRes.isErr()) {\n                        tasks.push(Promise.resolve(existsRes.asErr()));\n                        continue;\n                    }\n\n                    newPathExists = existsRes.unwrap();\n                }\n\n                const res: AsyncVoidIOResult = isFileHandle(handle)\n                    ? (overwrite || !newPathExists ? handler(handle, newEntryPath) : Promise.resolve(RESULT_VOID))\n                    : mkdir(newEntryPath);\n\n                tasks.push(res);\n            }\n\n            return getFinalResult(tasks);\n        });\n    });\n}\n\n/**\n * Appends content to a file at the specified path.\n *\n * @param filePath - The path of the file to append to.\n * @param contents - The content to append to the file.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the content was successfully appended.\n */\nexport function appendFile(filePath: string, contents: WriteFileContent): AsyncVoidIOResult {\n    return writeFile(filePath, contents, {\n        append: true,\n    });\n}\n\n/**\n * Copies a file or directory from one location to another same as `cp -r`.\n *\n * Both `srcPath` and `destPath` must both be a file or directory.\n *\n * @param srcPath - The source file/directory path.\n * @param destPath - The destination file/directory path.\n * @param options - The copy options.\n * @returns A promise that resolves to an `AsyncVoidIOResult` indicating whether the file was successfully copied.\n */\nexport async function copy(srcPath: string, destPath: string, options?: CopyOptions): AsyncVoidIOResult {\n    const {\n        overwrite = true,\n    } = options ?? {};\n\n    return mkDestFromSrc(srcPath, destPath, async (srcHandle, destPath) => {\n        return await writeFile(destPath, await srcHandle.getFile());\n    }, overwrite);\n}\n\n/**\n * Empties the contents of a directory at the specified path.\n *\n * @param dirPath - The path of the directory to empty.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the directory was successfully emptied.\n */\nexport async function emptyDir(dirPath: string): AsyncVoidIOResult {\n    const readDirRes = await readDir(dirPath);\n\n    if (readDirRes.isErr()) {\n        // create if not exist\n        return isNotFoundError(readDirRes.unwrapErr()) ? mkdir(dirPath) : readDirRes.asErr();\n    }\n\n    const tasks: AsyncVoidIOResult[] = [];\n\n    for await (const { path } of readDirRes.unwrap()) {\n        tasks.push(remove(join(dirPath, path)));\n    }\n\n    return getFinalResult(tasks);\n}\n\n/**\n * Checks whether a file or directory exists at the specified path.\n *\n * @param path - The path of the file or directory to check for existence.\n * @param options - Optional existence options.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the file or directory exists.\n */\nexport async function exists(path: string, options?: ExistsOptions): AsyncIOResult<boolean> {\n    const { isDirectory = false, isFile = false } = options ?? {};\n\n    invariant(!(isDirectory && isFile), () => 'ExistsOptions.isDirectory and ExistsOptions.isFile must not be true together.');\n\n    const statRes = await stat(path);\n\n    return statRes.andThen(handle => {\n        const notExist =\n            (isDirectory && isFileHandle(handle))\n            || (isFile && isDirectoryHandle(handle));\n\n        return Ok(!notExist);\n    }).orElse((err): IOResult<boolean> => {\n        return isNotFoundError(err) ? RESULT_FALSE : statRes.asErr();\n    });\n}\n\n/**\n * Move a file or directory from an old path to a new path.\n *\n * @param srcPath - The current path of the file or directory.\n * @param destPath - The new path of the file or directory.\n * @param options - Options of move.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the file or directory was successfully moved.\n */\nexport async function move(srcPath: string, destPath: string, options?: MoveOptions): AsyncVoidIOResult {\n    const {\n        overwrite = true,\n    } = options ?? {};\n\n    return (await mkDestFromSrc(srcPath, destPath, moveHandle, overwrite)).andThenAsync(() => {\n        // finally remove src\n        return remove(srcPath);\n    });\n}\n\n/**\n * Reads the content of a file at the specified path as a File.\n *\n * @param filePath - The path of the file to read.\n * @returns A promise that resolves to an `AsyncIOResult` containing the file content as a File.\n */\nexport function readBlobFile(filePath: string): AsyncIOResult<File> {\n    return readFile(filePath, {\n        encoding: 'blob',\n    });\n}\n\n/**\n * Reads the content of a file at the specified path as a string and returns it as a JSON object.\n *\n * @param filePath - The path of the file to read.\n * @returns A promise that resolves to an `AsyncIOResult` containing the file content as a JSON object.\n */\nexport async function readJsonFile<T>(filePath: string): AsyncIOResult<T> {\n    return (await readTextFile(filePath)).andThenAsync(async contents => {\n        try {\n            return Ok(JSON.parse(contents));\n        } catch (e) {\n            return Err(e as Error);\n        }\n    });\n}\n\n/**\n * Reads the content of a file at the specified path as a string.\n *\n * @param filePath - The path of the file to read.\n * @returns A promise that resolves to an `AsyncIOResult` containing the file content as a string.\n */\nexport function readTextFile(filePath: string): AsyncIOResult<string> {\n    return readFile(filePath, {\n        encoding: 'utf8',\n    });\n}","import { Err, Ok, RESULT_VOID, type AsyncIOResult, type AsyncVoidIOResult } from 'happy-rusty';\nimport invariant from 'tiny-invariant';\nimport { TMP_DIR } from './constants.ts';\nimport type { TempOptions } from './defines.ts';\nimport { createFile, mkdir, readDir, remove } from './opfs_core.ts';\nimport { generateTempPath, isFileHandle } from './utils.ts';\n\n/**\n * Create a temporary file or directory.\n *\n * @param options - Options and flags.\n * @returns A promise that resolves the result of the temporary file or directory path.\n */\nexport async function mkTemp(options?: TempOptions): AsyncIOResult<string> {\n    const {\n        isDirectory = false,\n    } = options ?? {};\n\n    const path = generateTempPath(options);\n    const res = await (isDirectory ? mkdir : createFile)(path);\n\n    return res.and(Ok(path));\n}\n\n/**\n * Delete the temporary directory and all its contents.\n * @returns A promise that resolves to an `AsyncVoidIOResult` indicating whether the temporary directory was successfully deleted.\n */\nexport function deleteTemp(): AsyncVoidIOResult {\n    return remove(TMP_DIR);\n}\n\n/**\n * Prune the temporary directory and delete all expired files.\n * @param expired - The date to determine whether a file is expired.\n * @returns A promise that resolves to an `AsyncVoidIOResult` indicating whether the temporary directory was successfully pruned.\n */\nexport async function pruneTemp(expired: Date): AsyncVoidIOResult {\n    invariant(expired instanceof Date, () => `Expired must be a Date but received ${ expired }`);\n\n    const readDirRes = await readDir(TMP_DIR, {\n        recursive: true,\n    });\n\n    return readDirRes.andThenAsync(async entries => {\n        try {\n            for await (const { handle } of entries) {\n                if (isFileHandle(handle) && (await handle.getFile()).lastModified <= expired.getTime()) {\n                    // TODO ts not support yet\n                    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                    await (handle as any).remove();\n                }\n            }\n        } catch (e) {\n            return Err(e as DOMException);\n        }\n\n        return RESULT_VOID;\n    });\n}","import { fetchT } from '@happy-ts/fetch-t';\nimport { join, SEPARATOR } from '@std/path/posix';\nimport * as fflate from 'fflate/browser';\nimport { Err, type AsyncVoidIOResult, type VoidIOResult } from 'happy-rusty';\nimport { Future } from 'tiny-future';\nimport { assertAbsolutePath, assertFileUrl } from './assertions.ts';\nimport type { FsRequestInit } from './defines.ts';\nimport { getFinalResult } from './helpers.ts';\nimport { readFile, writeFile } from './opfs_core.ts';\n\n/**\n * Unzip a buffer then write to the target path.\n * @param buffer - Zipped ArrayBuffer.\n * @param targetPath - Target directory path.\n */\nasync function unzipBufferToTarget(buffer: ArrayBuffer, targetPath: string): AsyncVoidIOResult {\n    const data = new Uint8Array(buffer);\n\n    const future = new Future<VoidIOResult>();\n\n    fflate.unzip(data, async (err, unzipped) => {\n        if (err) {\n            future.resolve(Err(err));\n            return;\n        }\n\n        const tasks: AsyncVoidIOResult[] = [];\n\n        for (const path in unzipped) {\n            // ignore directory\n            if (path.at(-1) !== SEPARATOR) {\n                tasks.push(writeFile(join(targetPath, path), unzipped[path]));\n            }\n        }\n\n        future.resolve(getFinalResult(tasks));\n    });\n\n    return await future.promise;\n}\n\n/**\n * Unzip a zip file to a directory.\n * Equivalent to `unzip -o <zipFilePath> -d <targetPath>\n *\n * Use [fflate](https://github.com/101arrowz/fflate) as the unzip backend.\n * @param zipFilePath - Zip file path.\n * @param targetPath - The directory to unzip to.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the zip file was successfully unzipped.\n */\nexport async function unzip(zipFilePath: string, targetPath: string): AsyncVoidIOResult {\n    assertAbsolutePath(targetPath);\n\n    const fileRes = await readFile(zipFilePath);\n\n    return fileRes.andThenAsync(buffer => {\n        return unzipBufferToTarget(buffer, targetPath);\n    });\n}\n\n/**\n * Unzip a remote zip file to a directory.\n * Equivalent to `unzip -o <zipFilePath> -d <targetPath>\n *\n * Use [fflate](https://github.com/101arrowz/fflate) as the unzip backend.\n * @param zipFileUrl - Zip file url.\n * @param targetPath - The directory to unzip to.\n * @param requestInit - Optional request initialization parameters.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the zip file was successfully unzipped.\n */\nexport async function unzipFromUrl(zipFileUrl: string, targetPath: string, requestInit?: FsRequestInit): AsyncVoidIOResult {\n    assertFileUrl(zipFileUrl);\n    assertAbsolutePath(targetPath);\n\n    const fetchRes = await fetchT(zipFileUrl, {\n        redirect: 'follow',\n        ...requestInit,\n        responseType: 'arraybuffer',\n        abortable: false,\n    });\n\n    return fetchRes.andThenAsync(buffer => {\n        return unzipBufferToTarget(buffer, targetPath);\n    });\n}","import { fetchT, type FetchResponse, type FetchTask } from '@happy-ts/fetch-t';\nimport { basename } from '@std/path/posix';\nimport { Err } from 'happy-rusty';\nimport { assertFileUrl } from './assertions.ts';\nimport type { UploadRequestInit } from './defines.ts';\nimport { createAbortError } from './helpers.ts';\nimport { readBlobFile } from './opfs_ext.ts';\n\n/**\n * Uploads a file from the specified path to a URL.\n *\n * @param filePath - The path of the file to upload.\n * @param fileUrl - The URL where the file will be uploaded.\n * @param requestInit - Optional request initialization parameters.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the file was successfully uploaded.\n */\nexport function uploadFile(filePath: string, fileUrl: string, requestInit?: UploadRequestInit): FetchTask<Response> {\n    type T = Response;\n\n    assertFileUrl(fileUrl);\n\n    let aborted = false;\n\n    let fetchTask: FetchTask<T>;\n\n    const response = (async (): FetchResponse<T> => {\n        const fileRes = await readBlobFile(filePath)\n\n        return fileRes.andThenAsync(async file => {\n            // maybe aborted\n            if (aborted) {\n                return Err(createAbortError());\n            }\n\n            const {\n                // default file name\n                filename = basename(filePath),\n                ...rest\n            } = requestInit ?? {};\n\n            const formData = new FormData();\n            formData.append(filename, file, filename);\n\n            fetchTask = fetchT(fileUrl, {\n                method: 'POST',\n                ...rest,\n                abortable: true,\n                body: formData,\n            });\n\n            return fetchTask.response;\n        });\n    })();\n\n    return {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        abort(reason?: any): void {\n            aborted = true;\n            fetchTask?.abort(reason);\n        },\n\n        get aborted(): boolean {\n            return aborted;\n        },\n\n        get response(): FetchResponse<T> {\n            return response;\n        },\n    };\n}","import { fetchT } from '@happy-ts/fetch-t';\nimport { basename, join } from '@std/path/posix';\nimport * as fflate from 'fflate/browser';\nimport { Err, Ok, type AsyncIOResult, type AsyncVoidIOResult, type IOResult } from 'happy-rusty';\nimport { Future } from 'tiny-future';\nimport { assertAbsolutePath, assertFileUrl } from './assertions.ts';\nimport type { FsRequestInit, ZipOptions } from './defines.ts';\nimport { readDir, stat, writeFile } from './opfs_core.ts';\nimport { getFileDataByHandle, isFileHandle } from './utils.ts';\n\n/**\n * Zip a zippable data then write to the target path.\n * @param zippable - Zippable data.\n * @param zipFilePath - Target zip file path.\n */\nasync function zipTo<T>(zippable: fflate.AsyncZippable, zipFilePath?: string): AsyncIOResult<T> {\n    const future = new Future<IOResult<T>>();\n\n    fflate.zip(zippable, {\n        consume: true,\n    }, async (err, u8a) => {\n        if (err) {\n            future.resolve(Err(err));\n            return;\n        }\n\n        // whether to write to file\n        if (zipFilePath) {\n            const res = await writeFile(zipFilePath, u8a);\n            future.resolve(res as IOResult<T>);\n        } else {\n            future.resolve(Ok(u8a as T));\n        }\n    });\n\n    return await future.promise;\n}\n\n/**\n * Zip a file or directory and write to a zip file.\n * Equivalent to `zip -r <zipFilePath> <targetPath>`.\n *\n * Use [fflate](https://github.com/101arrowz/fflate) as the zip backend.\n * @param sourcePath - The path to be zipped.\n * @param zipFilePath - The path to the zip file.\n * @param options - Options of zip.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the source was successfully zipped.\n */\nexport async function zip(sourcePath: string, zipFilePath: string, options?: ZipOptions): AsyncVoidIOResult;\n\n/**\n * Zip a file or directory and return the zip file data.\n * Equivalent to `zip -r <zipFilePath> <targetPath>`.\n *\n * Use [fflate](https://github.com/101arrowz/fflate) as the zip backend.\n * @param sourcePath - The path to be zipped.\n * @param options - Options of zip.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the source was successfully zipped.\n */\nexport async function zip(sourcePath: string, options?: ZipOptions): AsyncIOResult<Uint8Array>;\nexport async function zip<T>(sourcePath: string, zipFilePath?: string | ZipOptions, options?: ZipOptions): AsyncIOResult<T> {\n    if (typeof zipFilePath === 'string') {\n        assertAbsolutePath(zipFilePath);\n    } else {\n        options = zipFilePath;\n        zipFilePath = undefined;\n    }\n\n    const statRes = await stat(sourcePath);\n\n    return statRes.andThenAsync(async handle => {\n        const sourceName = basename(sourcePath);\n        const zippable: fflate.AsyncZippable = {};\n\n        if (isFileHandle(handle)) {\n            // file\n            const data = await getFileDataByHandle(handle);\n            zippable[sourceName] = data;\n        } else {\n            // directory\n            const readDirRes = await readDir(sourcePath, {\n                recursive: true,\n            });\n            if (readDirRes.isErr()) {\n                return readDirRes.asErr();\n            }\n\n            // default to preserve root\n            const preserveRoot = options?.preserveRoot ?? true;\n\n            for await (const { path, handle } of readDirRes.unwrap()) {\n                // path\n                if (isFileHandle(handle)) {\n                    const entryName = preserveRoot ? join(sourceName, path) : path;\n                    const data = await getFileDataByHandle(handle);\n                    zippable[entryName] = data;\n                }\n            }\n        }\n\n        return zipTo(zippable, zipFilePath);\n    });\n}\n\n/**\n * Zip a remote file and write to a zip file.\n *\n * Use [fflate](https://github.com/101arrowz/fflate) as the zip backend.\n * @param sourceUrl - The url to be zipped.\n * @param zipFilePath - The path to the zip file.\n * @param requestInit - Optional request initialization parameters.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the source was successfully zipped.\n */\nexport async function zipFromUrl(sourceUrl: string, zipFilePath: string, requestInit?: FsRequestInit): AsyncVoidIOResult;\n\n/**\n * Zip a remote file and return the zip file data.\n *\n * Use [fflate](https://github.com/101arrowz/fflate) as the zip backend.\n * @param sourceUrl - The url to be zipped.\n * @param requestInit - Optional request initialization parameters.\n * @returns A promise that resolves to an `AsyncIOResult` indicating whether the source was successfully zipped.\n */\nexport async function zipFromUrl(sourceUrl: string, requestInit?: FsRequestInit): AsyncIOResult<Uint8Array>;\nexport async function zipFromUrl<T>(sourceUrl: string, zipFilePath?: string | FsRequestInit, requestInit?: FsRequestInit): AsyncIOResult<T> {\n    assertFileUrl(sourceUrl);\n\n    if (typeof zipFilePath === 'string') {\n        assertAbsolutePath(zipFilePath);\n    } else {\n        requestInit = zipFilePath;\n        zipFilePath = undefined;\n    }\n\n    const fetchRes = await fetchT(sourceUrl, {\n        redirect: 'follow',\n        ...requestInit,\n        responseType: 'arraybuffer',\n        abortable: false,\n    });\n\n    return fetchRes.andThenAsync(buffer => {\n        const sourceName = basename(sourceUrl);\n        const zippable: fflate.AsyncZippable = {};\n\n        zippable[sourceName] = new Uint8Array(buffer);\n\n        return zipTo(zippable, zipFilePath);\n    });\n}","/**\n * Checks if the Origin Private File System (OPFS) is supported in the current environment.\n *\n * @returns A boolean indicating whether OPFS is supported.\n */\nexport function isOPFSSupported(): boolean {\n    return typeof navigator?.storage?.getDirectory === 'function';\n}","import { join, SEPARATOR } from '@std/path/posix';\nimport { TMP_DIR } from './constants.ts';\nimport type { FileSystemFileHandleLike, FileSystemHandleLike, TempOptions } from './defines.ts';\n\n/**\n * Generate a temporary path but not create it.\n *\n * @param options - Options and flags.\n * @returns The temporary path.\n */\nexport function generateTempPath(options?: TempOptions): string {\n    const {\n        isDirectory = false,\n        basename = 'tmp',\n        extname = '',\n    } = options ?? {};\n\n    const base = basename ? `${ basename }-` : '';\n    const ext = isDirectory ? '' : extname;\n\n    // use uuid to generate a unique name\n    return join(TMP_DIR, `${ base }${ crypto.randomUUID() }${ ext }`);\n}\n\n/**\n * Check whether the path is a temporary path.\n * @param path - The path to check.\n * @returns `true` if the path is a temporary path otherwise `false`.\n */\nexport function isTempPath(path: string): boolean {\n    return path.startsWith(`${ TMP_DIR }${ SEPARATOR }`);\n}\n\n/**\n * Serialize a `FileSystemHandle` to plain object.\n * @param handle - `FileSystemHandle` object.\n * @returns Serializable version of FileSystemHandle that is FileSystemHandleLike.\n */\nexport async function toFileSystemHandleLike(handle: FileSystemHandle): Promise<FileSystemHandleLike> {\n    const { name, kind } = 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,\n            type,\n            size,\n            lastModified,\n        };\n\n        return fileHandle;\n    }\n\n    const handleLike: FileSystemHandleLike= {\n        name,\n        kind,\n    };\n\n    return handleLike;\n}\n\n/**\n * Whether the handle is a file.\n * @param handle - The handle which is a FileSystemHandle.\n * @returns `true` if the handle is a file, otherwise `false`.\n */\nexport function isFileHandle(handle: FileSystemHandle): handle is FileSystemFileHandle {\n    return handle.kind === 'file';\n}\n\n/**\n * Whether the handle is a directory.\n * @param handle - The handle which is a FileSystemHandle.\n * @returns `true` if the handle is a directory, otherwise `false`.\n */\nexport function isDirectoryHandle(handle: FileSystemHandle): handle is FileSystemDirectoryHandle {\n    return handle.kind === 'directory';\n}\n\n/**\n * Whether the handle is a file-like.\n * @param handle -  The handle which is a FileSystemHandleLike.\n * @returns `true` if the handle is a file, otherwise `false`.\n */\nexport function isFileHandleLike(handle: FileSystemHandleLike): handle is FileSystemFileHandleLike {\n    return handle.kind === 'file';\n}\n\n/**\n * Gets the data of a file handle.\n * @param handle - The file handle.\n * @returns A promise that resolves to the data of the file.\n */\nexport async function getFileDataByHandle(handle: FileSystemFileHandle): Promise<Uint8Array> {\n    const file = await handle.getFile();\n    const ab = await file.arrayBuffer();\n    return new Uint8Array(ab);\n}","import type { IOResult } from 'happy-rusty';\nimport type { ReadDirEntry, ReadDirEntrySync } from '../fs/defines.ts';\nimport { createFile, mkdir, readDir, remove, stat, writeFile } from '../fs/opfs_core.ts';\nimport { appendFile, copy, emptyDir, exists, move, readBlobFile, } from '../fs/opfs_ext.ts';\nimport { deleteTemp, mkTemp, pruneTemp } from '../fs/opfs_tmp.ts';\nimport { unzip } from '../fs/opfs_unzip.ts';\nimport { zip } from '../fs/opfs_zip.ts';\nimport { toFileSystemHandleLike } from '../fs/utils.ts';\nimport { serializeError, serializeFile } from './helpers.ts';\nimport { decodeFromBuffer, encodeToBuffer, respondToMainFromWorker, SyncMessenger, WorkerAsyncOp } from './shared.ts';\n\n/**\n * Async I/O operations which allow to call from main thread.\n */\nconst asyncOps = {\n    [WorkerAsyncOp.createFile]: createFile,\n    [WorkerAsyncOp.mkdir]: mkdir,\n    [WorkerAsyncOp.move]: move,\n    [WorkerAsyncOp.readDir]: readDir,\n    [WorkerAsyncOp.remove]: remove,\n    [WorkerAsyncOp.stat]: stat,\n    [WorkerAsyncOp.writeFile]: writeFile,\n    [WorkerAsyncOp.appendFile]: appendFile,\n    [WorkerAsyncOp.copy]: copy,\n    [WorkerAsyncOp.emptyDir]: emptyDir,\n    [WorkerAsyncOp.exists]: exists,\n    [WorkerAsyncOp.deleteTemp]: deleteTemp,\n    [WorkerAsyncOp.mkTemp]: mkTemp,\n    [WorkerAsyncOp.pruneTemp]: pruneTemp,\n    [WorkerAsyncOp.readBlobFile]: readBlobFile,\n    [WorkerAsyncOp.unzip]: unzip,\n    [WorkerAsyncOp.zip]: zip,\n};\n\n/**\n * Cache the messenger instance.\n */\nlet messenger: SyncMessenger;\n\n/**\n * Start worker agent.\n * Listens to postMessage from main thread.\n * Start runner loop.\n */\nexport function startSyncAgent() {\n    if (typeof window !== 'undefined') {\n        throw new Error('Only can use in worker');\n    }\n\n    if (messenger) {\n        throw new Error('Worker messenger already started');\n    }\n\n    addEventListener('message', (event: MessageEvent<SharedArrayBuffer>) => {\n        // created at main thread and transfer to worker\n        const sab = event.data;\n\n        if (!(sab instanceof SharedArrayBuffer)) {\n            throw new TypeError('Only can post SharedArrayBuffer to Worker');\n        }\n\n        messenger = new SyncMessenger(sab);\n\n        // notify main thread that worker is ready\n        postMessage(true);\n\n        // start waiting for request\n        runWorkerLoop();\n    });\n}\n\n/**\n * Run worker loop.\n */\nasync function runWorkerLoop(): Promise<void> {\n    // loop forever\n    while (true) {\n        try {\n            await respondToMainFromWorker(messenger, async (data) => {\n                const [op, ...args] = decodeFromBuffer(data) as [WorkerAsyncOp, ...Parameters<typeof asyncOps[WorkerAsyncOp]>];\n\n                // handling unequal parameters for serialization and deserialization\n                if (op === WorkerAsyncOp.writeFile || op === WorkerAsyncOp.appendFile) {\n                    // actually is an byte array\n                    if (Array.isArray(args[1])) {\n                        args[1] = new Uint8Array(args[1]);\n                    }\n                } else if (op === WorkerAsyncOp.pruneTemp) {\n                    // actually is a Date string\n                    args[0] = new Date(args[0] as Date);\n                }\n\n                let response: Uint8Array;\n\n                const handle = asyncOps[op];\n\n                try {\n                    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                    const res: IOResult<any> = await (handle as any)(...args);\n\n                    if (res.isErr()) {\n                        // without result success\n                        response = encodeToBuffer([serializeError(res.unwrapErr())]);\n                    } else {\n                        // manually serialize response\n                        let rawResponse;\n\n                        if (op === WorkerAsyncOp.readBlobFile) {\n                            const file: File = res.unwrap();\n\n                            const fileLike = await serializeFile(file);\n\n                            rawResponse = {\n                                ...fileLike,\n                                // for serialize\n                                data: [...new Uint8Array(fileLike.data)],\n                            };\n                        } else if (op === WorkerAsyncOp.readDir) {\n                            const iterator: AsyncIterableIterator<ReadDirEntry> = res.unwrap();\n                            const entries: ReadDirEntrySync[] = [];\n\n                            for await (const { path, handle } of iterator) {\n                                const handleLike = await toFileSystemHandleLike(handle);\n                                entries.push({\n                                    path,\n                                    handle: handleLike,\n                                });\n                            }\n\n                            rawResponse = entries;\n                        } else if (op === WorkerAsyncOp.stat) {\n                            const handle: FileSystemHandle = res.unwrap();\n                            const data = await toFileSystemHandleLike(handle);\n\n                            rawResponse = data;\n                        } else if (op === WorkerAsyncOp.zip) {\n                            const data: Uint8Array | undefined = res.unwrap();\n\n                            rawResponse = data instanceof Uint8Array ? [...data] : data;\n                        } else {\n                            // others are all boolean\n                            rawResponse = res.unwrap();\n                        }\n\n                        // without error\n                        response = encodeToBuffer([null, rawResponse]);\n                    }\n                } catch (e) {\n                    response = encodeToBuffer([serializeError(e as Error)]);\n                }\n\n                return response;\n            });\n        } catch (err) {\n            console.error(err instanceof Error ? err.stack : err);\n        }\n    }\n}","import { sleepUntil } from './helpers.ts';\n\n/**\n * Async I/O operations called from main thread to worker thread.\n */\nexport const enum WorkerAsyncOp {\n    // core\n    createFile,\n    mkdir,\n    move,\n    readDir,\n    remove,\n    stat,\n    writeFile,\n    // ext\n    appendFile,\n    copy,\n    emptyDir,\n    exists,\n    deleteTemp,\n    mkTemp,\n    pruneTemp,\n    readBlobFile,\n    unzip,\n    zip,\n}\n\n/**\n * Main thread lock index used in Int32Array.\n */\nconst MAIN_LOCK_INDEX = 0;\n\n/**\n * Worker thread lock index used in Int32Array.\n */\nconst WORKER_LOCK_INDEX = 1;\n\n/**\n * Data index used in Int32Array.\n */\nconst DATA_INDEX = 2;\n\n/**\n * Main thread locked value.\n */\nconst MAIN_LOCKED = 1;\n\n/**\n * Main thread unlocked value.\n * Default.\n */\nconst MAIN_UNLOCKED = 0;\n\n/**\n * Worker thread locked value.\n * Default.\n */\nconst WORKER_LOCKED = MAIN_UNLOCKED;\n\n/**\n * Worker thread unlocked value.\n */\nconst WORKER_UNLOCKED = MAIN_LOCKED;\n\n/**\n * Cache the `TextEncoder` instance.\n */\nlet encoder: TextEncoder;\n\n/**\n * Cache the `TextDecoder` instance.\n */\nlet decoder: TextDecoder;\n\n/**\n * Get the cached `TextEncoder` instance.\n * @returns Instance of `TextEncoder`.\n */\nfunction getEncoder(): TextEncoder {\n    encoder ??= new TextEncoder();\n    return encoder;\n}\n\n/**\n * Get the cached `TextDecoder` instance.\n * @returns Instance of `TextDecoder`.\n */\nfunction getDecoder(): TextDecoder {\n    decoder ??= new TextDecoder();\n    return decoder;\n}\n\n/**\n * Used to encode parameters and return values.\n * @param data - Any data to encode.\n * @returns Encoded binary data.\n */\nexport function encodeToBuffer<T>(data: T): Uint8Array {\n    const str = JSON.stringify(data);\n    return getEncoder().encode(str);\n}\n\n/**\n * Used to decode parameters and return values.\n * @param data - Binary data to decode.\n * @returns Decoded data.\n */\nexport function decodeFromBuffer<T>(data: Uint8Array): T {\n    const str = decodeToString(data);\n    return JSON.parse(str);\n}\n\n/**\n * Commonly decode binary data to string.\n * @param data - Binary data to decode.\n * @returns Decoded string.\n */\nexport function decodeToString(data: Uint8Array): string {\n    return getDecoder().decode(data);\n}\n\n/**\n * Inspired by [memfs](https://github.com/streamich/memfs/blob/master/src/fsa-to-node/worker/SyncMessenger.ts).\n *\n * Used both in main thread and worker thread.\n */\nexport class SyncMessenger {\n    // View of SharedArrayBuffer, used to communicate between main thread and worker.\n    readonly i32a: Int32Array;\n    // View of the same SharedArrayBuffer, used to read and write binary data.\n    readonly u8a: Uint8Array;\n    // 4 int: MAIN_LOCK_INDEX WORKER_LOCK_INDEX DATA_INDEX NOT_USE\n    readonly headerLength = 4 * 4;\n    // maximum length of data to be sent. If data is longer than this, it will throw an error.\n    readonly maxDataLength: number;\n\n    constructor(sab: SharedArrayBuffer) {\n        this.i32a = new Int32Array(sab);\n        this.u8a = new Uint8Array(sab);\n        this.maxDataLength = sab.byteLength - this.headerLength;\n    }\n}\n\n/**\n * Calls a function in worker thread from main tread and returns the result.\n * Used in main thread.\n * @param messenger - SyncMessenger\n * @param data - Request buffer which is encoded parameters.\n * @returns - Response buffer which is encoded return value.\n */\nexport function callWorkerFromMain(messenger: SyncMessenger, data: Uint8Array): Uint8Array {\n    const { i32a, u8a, headerLength, maxDataLength } = messenger;\n    const requestLength = data.byteLength;\n\n    // check whether request is too large\n    if (requestLength > maxDataLength) {\n        throw new RangeError(`Request is too large: ${ requestLength } > ${ maxDataLength }. Consider grow the size of SharedArrayBuffer.`);\n    }\n\n    // lock main thread\n    Atomics.store(i32a, MAIN_LOCK_INDEX, MAIN_LOCKED);\n\n    // payload and length\n    i32a[DATA_INDEX] = requestLength;\n    u8a.set(data, headerLength);\n\n    // wakeup worker\n    // Atomics.notify(i32a, WORKER_LOCK_INDEX); // this may not work\n    Atomics.store(i32a, WORKER_LOCK_INDEX, WORKER_UNLOCKED);\n\n    // wait for worker to finish\n    sleepUntil(() => Atomics.load(i32a, MAIN_LOCK_INDEX) === MAIN_UNLOCKED);\n\n    // worker return\n    const responseLength = i32a[DATA_INDEX];\n    const response = u8a.slice(headerLength, headerLength + responseLength);\n\n    return response;\n}\n\n/**\n * Responds to main thread from worker thread.\n * Used in worker thread.\n * @param messenger - SyncMessenger\n * @param transfer - Function to transfer request data.\n */\nexport async function respondToMainFromWorker(messenger: SyncMessenger, transfer: (data: Uint8Array) => Promise<Uint8Array>): Promise<void> {\n    const { i32a, u8a, headerLength, maxDataLength } = messenger;\n\n    while (true) {\n        if (Atomics.load(i32a, WORKER_LOCK_INDEX) === WORKER_UNLOCKED) {\n            break;\n        }\n    }\n\n    // because of `Atomics.notify` may not work\n    // const waitRes = Atomics.wait(i32a, WORKER_LOCK_INDEX, WORKER_LOCKED);\n    // if (waitRes !== 'ok') {\n    //     throw new Error(`Unexpected Atomics.wait result: ${ waitRes }`);\n    // }\n\n    // payload and length\n    const requestLength = i32a[DATA_INDEX];\n    // console.log(`requestLength: ${ requestLength }`);\n    const data = u8a.slice(headerLength, headerLength + requestLength);\n\n    // call async I/O operation\n    let response = await transfer(data);\n    const responseLength = response.byteLength;\n\n    // check whether response is too large\n    if (responseLength > maxDataLength) {\n        const message = `Response is too large: ${ responseLength } > ${ maxDataLength }. Consider grow the size of SharedArrayBuffer.`;\n\n        response = encodeToBuffer([{\n            name: 'RangeError',\n            message,\n        }]);\n\n        // the error is too large?\n        if (response.byteLength > maxDataLength) {\n            // lock worker thread before throw\n            Atomics.store(i32a, WORKER_LOCK_INDEX, WORKER_LOCKED);\n\n            throw new RangeError(message);\n        }\n    }\n\n    // write response data\n    i32a[DATA_INDEX] = response.byteLength;\n    u8a.set(response, headerLength);\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}","import { Err, Ok, type IOResult, type VoidIOResult } from 'happy-rusty';\nimport invariant from 'tiny-invariant';\nimport type { CopyOptions, ExistsOptions, FileLike, FileSystemHandleLike, MoveOptions, ReadDirEntrySync, ReadDirOptions, ReadFileContent, ReadOptions, SyncAgentOptions, TempOptions, WriteOptions, WriteSyncFileContent, ZipOptions } from '../fs/defines.ts';\nimport { deserializeError, setGlobalOpTimeout } from './helpers.ts';\nimport { callWorkerFromMain, decodeFromBuffer, decodeToString, encodeToBuffer, SyncMessenger, WorkerAsyncOp } from './shared.ts';\n\n/**\n * Cache the messenger instance.\n */\nlet messenger: SyncMessenger;\n\n/**\n * Communicate with worker.\n * @param options - SyncAgentOptions\n * @returns\n */\nexport function connectSyncAgent(options: SyncAgentOptions): Promise<void> {\n    if (typeof window === 'undefined') {\n        throw new Error('Only can use in main thread');\n    }\n\n    if (messenger) {\n        throw new Error('Main messenger already started');\n    }\n\n    return new Promise(resolve => {\n        const {\n            worker,\n            bufferLength = 1024 * 1024,\n            opTimeout = 1000,\n        } = options;\n\n        // check parameters\n        invariant(worker instanceof Worker || worker instanceof URL || (typeof worker === 'string' && worker), () => 'worker must be Worker or valid URL(string)');\n        invariant(bufferLength > 16 && bufferLength % 4 === 0, () => 'bufferLength must be a multiple of 4')\n        invariant(Number.isInteger(opTimeout) && opTimeout > 0, () => 'opTimeout must be integer and greater than 0');\n\n        setGlobalOpTimeout(opTimeout);\n\n        const workerAdapter = worker instanceof Worker\n            ? worker\n            : new Worker(worker);\n\n        const sab = new SharedArrayBuffer(bufferLength);\n\n        workerAdapter.addEventListener('message', (event: MessageEvent<boolean>) => {\n            if (event.data) {\n                messenger = new SyncMessenger(sab);\n\n                resolve();\n            }\n        });\n\n        workerAdapter.postMessage(sab);\n    });\n}\n\n/**\n * Get messenger instance.\n * Use `setSyncMessenger` to pass the messenger to other environments for sharing.\n *\n * @returns SyncMessenger instance.\n */\nexport function getSyncMessenger(): SyncMessenger {\n    return messenger;\n}\n\n/**\n * Set messenger instance.\n * Use this method to share messenger with other environments.\n *\n * @param syncMessenger - SyncMessenger instance.\n */\nexport function setSyncMessenger(syncMessenger: SyncMessenger): void {\n    invariant(syncMessenger != null, () => 'syncMessenger is null or undefined');\n    messenger = syncMessenger;\n}\n\n/**\n * Call worker I/O operation.\n * @param op - I/O operation enum.\n * @param args - I/O operation arguments.\n * @returns - I/O operation result.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction callWorkerOp<T>(op: WorkerAsyncOp, ...args: any[]): IOResult<T> {\n    if (!messenger) {\n        // too early\n        return Err(new Error('Worker not initialized. Come back later.'));\n    }\n\n    const request = [op, ...args];\n    const requestData = encodeToBuffer(request);\n\n    try {\n        const response = callWorkerFromMain(messenger, requestData);\n\n        const decodedResponse = decodeFromBuffer(response) as [Error, T];\n        const err = decodedResponse[0];\n        const result: IOResult<T> = err ? Err(deserializeError(err)) : Ok((decodedResponse[1] ?? undefined) as T);\n\n        return result;\n    } catch (err) {\n        return Err(err as Error);\n    }\n}\n\n/**\n * Sync version of `createFile`.\n */\nexport function createFileSync(filePath: string): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.createFile, filePath);\n}\n\n/**\n * Sync version of `mkdir`.\n */\nexport function mkdirSync(dirPath: string): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.mkdir, dirPath);\n}\n\n/**\n * Sync version of `move`.\n */\nexport function moveSync(srcPath: string, destPath: string, options?: MoveOptions): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.move, srcPath, destPath, options);\n}\n\n/**\n * Sync version of `readDir`.\n */\nexport function readDirSync(dirPath: string, options?: ReadDirOptions): IOResult<ReadDirEntrySync[]> {\n    return callWorkerOp(WorkerAsyncOp.readDir, dirPath, options);\n}\n\n/**\n * Sync version of `readFile`.\n */\nexport function readFileSync(filePath: string, options: ReadOptions & {\n    encoding: 'blob';\n}): IOResult<FileLike>;\nexport function readFileSync(filePath: string, options: ReadOptions & {\n    encoding: 'utf8';\n}): IOResult<string>;\nexport function readFileSync(filePath: string, options?: ReadOptions & {\n    encoding: 'binary';\n}): IOResult<ArrayBuffer>;\nexport function readFileSync<T extends ReadFileContent>(filePath: string, options?: ReadOptions): IOResult<T> {\n    const res: IOResult<FileLike> = callWorkerOp(WorkerAsyncOp.readBlobFile, filePath);\n\n    return res.map(file => {\n        // actually data is number array\n        const u8a = new Uint8Array(file.data);\n        file.data = u8a.buffer.slice(u8a.byteOffset, u8a.byteOffset + u8a.byteLength);\n\n        switch (options?.encoding) {\n            case 'blob': {\n                return file as unknown as T;\n            }\n            case 'utf8': {\n                return decodeToString(new Uint8Array(file.data)) as unknown as T;\n            }\n            default: {\n                return file.data as unknown as T;\n            }\n        }\n    });\n}\n\n/**\n * Sync version of `remove`.\n */\nexport function removeSync(path: string): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.remove, path);\n}\n\n/**\n * Sync version of `stat`.\n */\nexport function statSync(path: string): IOResult<FileSystemHandleLike> {\n    return callWorkerOp(WorkerAsyncOp.stat, path);\n}\n\n/**\n * Serialize contents to an byte array or a string that can be sent to worker.\n * @param contents\n * @returns\n */\nfunction serializeWriteContents(contents: WriteSyncFileContent): number[] | string {\n    return contents instanceof ArrayBuffer\n        ? [...new Uint8Array(contents)]\n        : ArrayBuffer.isView(contents)\n            ? [...new Uint8Array(contents.buffer)]\n            : contents;\n}\n\n/**\n * Sync version of `writeFile`.\n */\nexport function writeFileSync(filePath: string, contents: WriteSyncFileContent, options?: WriteOptions): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.writeFile, filePath, serializeWriteContents(contents), options);\n}\n\n/**\n * Sync version of `appendFile`.\n */\nexport function appendFileSync(filePath: string, contents: WriteSyncFileContent): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.appendFile, filePath, serializeWriteContents(contents));\n}\n\n/**\n * Sync version of `copy`.\n */\nexport function copySync(srcPath: string, destPath: string, options?: CopyOptions): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.copy, srcPath, destPath, options);\n}\n\n/**\n * Sync version of `emptyDir`.\n */\nexport function emptyDirSync(dirPath: string): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.emptyDir, dirPath);\n}\n\n/**\n * Sync version of `exists`.\n */\nexport function existsSync(path: string, options?: ExistsOptions): IOResult<boolean> {\n    return callWorkerOp(WorkerAsyncOp.exists, path, options);\n}\n\n/**\n * Sync version of `deleteTemp`.\n */\nexport function deleteTempSync(): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.deleteTemp);\n}\n\n/**\n * Sync version of `mkTemp`.\n */\nexport function mkTempSync(options?: TempOptions): IOResult<string> {\n    return callWorkerOp(WorkerAsyncOp.mkTemp, options);\n}\n\n/**\n * Sync version of `pruneTemp`.\n */\nexport function pruneTempSync(expired: Date): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.pruneTemp, expired);\n}\n\n/**\n * Sync version of `readBlobFile`.\n */\nexport function readBlobFileSync(filePath: string): IOResult<FileLike> {\n    return readFileSync(filePath, {\n        encoding: 'blob',\n    });\n}\n\n/**\n * Sync version of `readJsonFile`.\n */\nexport function readJsonFileSync<T>(filePath: string): IOResult<T> {\n    return readTextFileSync(filePath).andThen(contents => {\n        try {\n            return Ok(JSON.parse(contents));\n        } catch (e) {\n            return Err(e as Error);\n        }\n    });\n}\n\n/**\n * Sync version of `readTextFile`.\n */\nexport function readTextFileSync(filePath: string): IOResult<string> {\n    return readFileSync(filePath, {\n        encoding: 'utf8',\n    });\n}\n\n/**\n * Sync version of `unzip`.\n */\nexport function unzipSync(zipFilePath: string, targetPath: string): VoidIOResult {\n    return callWorkerOp(WorkerAsyncOp.unzip, zipFilePath, targetPath);\n}\n\n/**\n * Sync version of `zip`.\n */\nexport function zipSync(sourcePath: string, zipFilePath: string, options?: ZipOptions): VoidIOResult;\n\n/**\n * Sync version of `zip`.\n */\nexport function zipSync(sourcePath: string, options?: ZipOptions): IOResult<Uint8Array>;\n\n/**\n * Sync version of `zip`.\n */\nexport function zipSync<T>(sourcePath: string, zipFilePath?: string | ZipOptions, options?: ZipOptions): IOResult<T> {\n    const res = callWorkerOp(WorkerAsyncOp.zip, sourcePath, zipFilePath, options) as IOResult<number[]> | VoidIOResult;\n\n    return res.map(data => {\n        return (data ? new Uint8Array(data) : data) as T;\n    });\n}"],"names":[],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;ACZP;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;;AChBP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;;ACzPA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;AC1EP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACjBP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;AC/DP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;;ACjBP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACpBP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACRP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACzCP;AACA;AACA;AACA;AACA;AACO;;ACJP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;;AC3CP;AACA;AACA;AACA;AACA;AACO;;ACmCP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;;AChDA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;;;"}