opfs-worker
Version:
A robust TypeScript library for working with Origin Private File System (OPFS) through Web Workers
1 lines • 50.8 kB
Source Map (JSON)
{"version":3,"file":"raw.cjs","sources":["../src/worker.ts"],"sourcesContent":["import { expose } from 'comlink';\n\n\nimport { decodeBuffer } from './utils/encoder';\nimport {\n FileNotFoundError,\n OPFSError,\n PathError\n} from './utils/errors';\n\nimport {\n basename,\n calculateFileHash,\n checkOPFSSupport,\n convertBlobToUint8Array,\n dirname,\n isBinaryFileExtension,\n joinPath,\n matchMinimatch,\n normalizeMinimatch,\n normalizePath,\n readFileData,\n removeEntry,\n resolvePath,\n splitPath,\n writeFileData\n} from './utils/helpers';\n\nimport type { DirentData, Encoding, FileStat, OPFSOptions, RenameOptions, WatchEvent, WatchOptions, WatchSnapshot } from './types';\nimport type { BufferEncoding } from 'typescript';\n\n/**\n * OPFS (Origin Private File System) File System implementation\n * \n * This class provides a high-level interface for working with the browser's\n * Origin Private File System API, offering file and directory operations\n * similar to Node.js fs module.\n * \n * @example\n * ```typescript\n * const fs = new OPFSFileSystem();\n * await fs.init('/my-app');\n * await fs.writeFile('/data/config.json', JSON.stringify({ theme: 'dark' }));\n * const config = await fs.readFile('/data/config.json');\n * ```\n */\nexport class OPFSWorker {\n /** Root directory handle for the file system */\n private root!: FileSystemDirectoryHandle;\n\n /** Map of watched paths and options */\n private watchers = new Map<string, WatchSnapshot>();\n\n /** Promise to prevent concurrent mount operations */\n private mountingPromise: Promise<boolean> | null = null;\n\n /** BroadcastChannel instance for sending events */\n private broadcastChannel: BroadcastChannel | null = null;\n\n /** Configuration options */\n private options: Required<OPFSOptions> = {\n root: '/',\n namespace: '',\n maxFileSize: 50 * 1024 * 1024,\n hashAlgorithm: null,\n broadcastChannel: 'opfs-worker',\n };\n\n\n /**\n * Notify about internal changes to the file system\n * \n * This method is called by internal operations to notify clients about\n * changes, even when no specific paths are being watched.\n * \n * @param path - The path that was changed\n * @param type - The type of change (create, change, delete)\n */\n private async notifyChange(event: Omit<WatchEvent, 'timestamp' | 'hash' | 'namespace'>): Promise<void> {\n // This instance not configured to send events\n if (!this.options.broadcastChannel) {\n return;\n }\n\n const path = event.path;\n\n const match = [...this.watchers.values()].some((snapshot) => {\n return (\n matchMinimatch(path, snapshot.pattern)\n && snapshot.include.some(include => include && matchMinimatch(path, include))\n && !snapshot.exclude.some(exclude => exclude && matchMinimatch(path, exclude))\n );\n });\n\n if (!match) {\n return;\n }\n\n let hash: string | undefined;\n\n if (this.options.hashAlgorithm) {\n try {\n const stat = await this.stat(path);\n\n hash = stat.hash;\n }\n catch {}\n }\n\n // Send event via BroadcastChannel\n try {\n if (!this.broadcastChannel) {\n this.broadcastChannel = new BroadcastChannel(this.options.broadcastChannel as string);\n }\n\n const watchEvent: WatchEvent = {\n namespace: this.options.namespace,\n timestamp: new Date().toISOString(),\n ...event,\n ...(hash && { hash }),\n };\n\n this.broadcastChannel.postMessage(watchEvent);\n }\n catch (error) {\n console.warn('Failed to send event via BroadcastChannel:', error);\n }\n }\n\n /**\n * Creates a new OPFSFileSystem instance\n * \n * @param options - Optional configuration options\n * @param options.root - Root path for the file system (default: '/')\n * @param options.watchInterval - Polling interval in milliseconds for file watching\n * @param options.hashAlgorithm - Hash algorithm for file hashing\n * @param options.maxFileSize - Maximum file size for hashing in bytes (default: 50MB)\n * @throws {OPFSError} If OPFS is not supported in the current browser\n */\n constructor(options?: OPFSOptions) {\n checkOPFSSupport();\n\n if (options) {\n void this.setOptions(options);\n }\n }\n\n /**\n * Initialize the file system within a given directory\n * \n * This method sets up the root directory for all subsequent operations.\n * If no root is specified, it will use the OPFS root directory.\n * \n * @param root - The root path for the file system (default: '/')\n * @returns Promise that resolves to true if initialization was successful\n * @throws {OPFSError} If initialization fails\n * \n * @example\n * ```typescript\n * const fs = new OPFSFileSystem();\n * \n * // Use OPFS root (default)\n * await fs.mount();\n * \n * // Use custom directory\n * await fs.mount('/my-app');\n * ```\n */\n private async mount(): Promise<boolean> {\n const root = this.options.root;\n\n // If already mounting, wait for previous operation to complete first\n if (this.mountingPromise) {\n await this.mountingPromise;\n }\n\n // eslint-disable-next-line no-async-promise-executor\n this.mountingPromise = new Promise<boolean>(async(resolve, reject) => {\n try {\n const rootDir = await navigator.storage.getDirectory();\n\n this.root = (root === '/') ? rootDir : await this.getDirectoryHandle(root, true, rootDir);\n\n resolve(true);\n }\n catch (error) {\n reject(new OPFSError('Failed to initialize OPFS', 'INIT_FAILED', root, error));\n }\n finally {\n this.mountingPromise = null;\n }\n });\n\n return this.mountingPromise;\n }\n\n\n /**\n * Update configuration options\n * \n * @param options - Configuration options to update\n * @param options.root - Root path for the file system\n * @param options.watchInterval - Polling interval in milliseconds for file watching\n * @param options.hashAlgorithm - Hash algorithm for file hashing\n * @param options.maxFileSize - Maximum file size for hashing in bytes\n * @param options.broadcastChannel - Custom name for the broadcast channel\n */\n async setOptions(options: OPFSOptions): Promise<void> {\n if (options.hashAlgorithm !== undefined) {\n this.options.hashAlgorithm = options.hashAlgorithm;\n }\n\n if (options.maxFileSize !== undefined) {\n this.options.maxFileSize = options.maxFileSize;\n }\n\n if (options.broadcastChannel !== undefined) {\n // Close existing channel if name changed\n if (this.broadcastChannel && this.options.broadcastChannel !== options.broadcastChannel) {\n this.broadcastChannel.close();\n this.broadcastChannel = null;\n }\n\n this.options.broadcastChannel = options.broadcastChannel;\n }\n\n if (options.namespace) {\n this.options.namespace = options.namespace;\n }\n\n if (options.root !== undefined) {\n this.options.root = normalizePath(options.root);\n\n if (!this.options.namespace) {\n this.options.namespace = `opfs-worker:${ this.options.root }`;\n }\n\n await this.mount();\n }\n }\n\n /**\n * Get a directory handle from a path\n * \n * Navigates through the directory structure to find or create a directory\n * at the specified path.\n * \n * @param path - The path to the directory (string or array of segments)\n * @param create - Whether to create the directory if it doesn't exist (default: false)\n * @param from - The directory to start from (default: root directory)\n * @returns Promise that resolves to the directory handle\n * @throws {OPFSError} If the directory cannot be accessed or created\n * \n * @example\n * ```typescript\n * const docsDir = await fs.getDirectoryHandle('/users/john/documents', true);\n * const docsDir2 = await fs.getDirectoryHandle(['users', 'john', 'documents'], true);\n * ```\n */\n private async getDirectoryHandle(path: string | string[], create: boolean = false, from: FileSystemDirectoryHandle | null = this.root): Promise<FileSystemDirectoryHandle> {\n const segments = Array.isArray(path) ? path : splitPath(path);\n let current = from;\n\n for (const segment of segments) {\n current = await current!.getDirectoryHandle(segment, { create });\n }\n\n return current!;\n }\n\n /**\n * Get a file handle from a path\n * \n * Navigates to the parent directory and retrieves or creates a file handle\n * for the specified file path.\n * \n * @param path - The path to the file (string or array of segments)\n * @param create - Whether to create the file if it doesn't exist (default: false)\n * @param from - The directory to start from (default: root directory)\n * @returns Promise that resolves to the file handle\n * @throws {PathError} If the path is empty\n * @throws {OPFSError} If the file cannot be accessed or created\n * \n * @example\n * ```typescript\n * const fileHandle = await fs.getFileHandle('/config/settings.json', true);\n * const fileHandle2 = await fs.getFileHandle(['config', 'settings.json'], true);\n * ```\n */\n private async getFileHandle(path: string | string[], create = false, from: FileSystemDirectoryHandle | null = this.root): Promise<FileSystemFileHandle> {\n const segments = splitPath(path);\n\n if (segments.length === 0) {\n throw new PathError('Path must not be empty', Array.isArray(path) ? path.join('/') : path);\n }\n\n const fileName = segments.pop()!;\n const dir = await this.getDirectoryHandle(segments, create, from);\n\n return dir.getFileHandle(fileName, { create });\n }\n\n /**\n * Get a complete index of all files and directories in the file system\n * \n * This method recursively traverses the entire file system and returns\n * a Map containing FileStat objects for every file and directory.\n * \n * @returns Promise that resolves to a Map of paths to FileStat objects\n * @throws {OPFSError} If the file system is not mounted\n * \n * @example\n * ```typescript\n * const index = await fs.index();\n * const fileStats = index.get('/data/config.json');\n * if (fileStats) {\n * console.log(`File size: ${fileStats.size} bytes`);\n * if (fileStats.hash) console.log(`Hash: ${fileStats.hash}`);\n * }\n * ```\n */\n async index(): Promise<Map<string, FileStat>> {\n const result = new Map<string, FileStat>();\n\n const walk = async(dirPath: string) => {\n const items = await this.readDir(dirPath);\n\n for (const item of items) {\n const fullPath = `${ dirPath === '/' ? '' : dirPath }/${ item.name }`;\n\n try {\n const stat = await this.stat(fullPath);\n\n result.set(fullPath, stat);\n\n if (stat.isDirectory) {\n await walk(fullPath);\n }\n }\n catch (err) {\n console.warn(`Skipping broken entry: ${ fullPath }`, err);\n }\n }\n };\n\n result.set('/', {\n kind: 'directory',\n size: 0,\n mtime: new Date(0).toISOString(),\n ctime: new Date(0).toISOString(),\n isFile: false,\n isDirectory: true,\n });\n\n await walk('/');\n\n return result;\n }\n\n /**\n * Read a file from the file system\n * \n * Reads the contents of a file and returns it as a string or binary data\n * depending on the specified encoding.\n * \n * @param path - The path to the file to read\n * @param encoding - The encoding to use for reading the file\n * @returns Promise that resolves to the file contents\n * @throws {FileNotFoundError} If the file does not exist\n * @throws {OPFSError} If reading the file fails\n * \n * @example\n * ```typescript\n * // Read as text\n * const content = await fs.readFile('/config/settings.json');\n * \n * // Read as binary\n * const binaryData = await fs.readFile('/images/logo.png', 'binary');\n * \n * // Read with specific encoding\n * const utf8Content = await fs.readFile('/data/utf8.txt', 'utf-8');\n * ```\n */\n async readFile(path: string, encoding: 'binary'): Promise<Uint8Array>;\n async readFile(path: string, encoding: Encoding): Promise<string>;\n async readFile(path: string, encoding: Encoding | 'binary'): Promise<string | Uint8Array>;\n async readFile(\n path: string,\n encoding?: any\n ): Promise<string | Uint8Array> {\n await this.mount();\n\n if (!encoding) {\n encoding = isBinaryFileExtension(path) ? 'binary' : 'utf-8';\n }\n\n try {\n const fileHandle = await this.getFileHandle(path, false, this.root);\n const buffer = await readFileData(fileHandle, path);\n\n return (encoding === 'binary') ? buffer : decodeBuffer(buffer, encoding);\n }\n catch (err) {\n throw new FileNotFoundError(path, err);\n }\n }\n\n /**\n * Write data to a file\n * \n * Creates or overwrites a file with the specified data. If the file already\n * exists, it will be truncated before writing.\n * \n * @param path - The path to the file to write\n * @param data - The data to write to the file (string, Uint8Array, or ArrayBuffer)\n * @param encoding - The encoding to use when writing string data (default: 'utf-8')\n * @returns Promise that resolves when the write operation is complete\n * @throws {OPFSError} If writing the file fails\n * \n * @example\n * ```typescript\n * // Write text data\n * await fs.writeFile('/config/settings.json', JSON.stringify({ theme: 'dark' }));\n * \n * // Write binary data\n * const binaryData = new Uint8Array([1, 2, 3, 4, 5]);\n * await fs.writeFile('/data/binary.dat', binaryData);\n * \n * // Write with specific encoding\n * await fs.writeFile('/data/utf16.txt', 'Hello World', 'utf-16le');\n * ```\n */\n async writeFile(\n path: string,\n data: string | Uint8Array | ArrayBuffer,\n encoding?: BufferEncoding\n ): Promise<void> {\n await this.mount();\n\n const fileExists = await this.exists(path);\n const fileHandle = await this.getFileHandle(path, true);\n\n if (!encoding) {\n encoding = (typeof data !== 'string' || isBinaryFileExtension(path)) ? 'binary' : 'utf-8';\n }\n\n await writeFileData(fileHandle, data, encoding, path);\n\n // Only notify changes if the file didn't exist before or if content actually changed\n if (!fileExists) {\n await this.notifyChange({ path, type: 'added', isDirectory: false });\n }\n else {\n await this.notifyChange({ path, type: 'changed', isDirectory: false });\n }\n }\n\n /**\n * Append data to a file\n * \n * Adds data to the end of an existing file. If the file doesn't exist,\n * it will be created.\n * \n * @param path - The path to the file to append to\n * @param data - The data to append to the file (string, Uint8Array, or ArrayBuffer)\n * @param encoding - The encoding to use when appending string data (default: 'utf-8')\n * @returns Promise that resolves when the append operation is complete\n * @throws {OPFSError} If appending to the file fails\n * \n * @example\n * ```typescript\n * // Append text to a log file\n * await fs.appendFile('/logs/app.log', `[${new Date().toISOString()}] User logged in\\n`);\n * \n * // Append binary data\n * const additionalData = new Uint8Array([6, 7, 8]);\n * await fs.appendFile('/data/binary.dat', additionalData);\n * ```\n */\n async appendFile(\n path: string,\n data: string | Uint8Array | ArrayBuffer,\n encoding?: BufferEncoding\n ): Promise<void> {\n await this.mount();\n\n const fileHandle = await this.getFileHandle(path, true);\n\n if (!encoding) {\n encoding = (typeof data !== 'string' || isBinaryFileExtension(path)) ? 'binary' : 'utf-8';\n }\n\n await writeFileData(fileHandle, data, encoding, path, { append: true });\n await this.notifyChange({ path, type: 'changed', isDirectory: false });\n }\n\n /**\n * Create a directory\n * \n * Creates a new directory at the specified path. If the recursive option\n * is enabled, parent directories will be created as needed.\n * \n * @param path - The path where the directory should be created\n * @param options - Options for directory creation\n * @param options.recursive - Whether to create parent directories if they don't exist (default: false)\n * @returns Promise that resolves when the directory is created\n * @throws {OPFSError} If the directory cannot be created\n * \n * @example\n * ```typescript\n * // Create a single directory\n * await fs.mkdir('/users/john');\n * \n * // Create nested directories\n * await fs.mkdir('/users/john/documents/projects', { recursive: true });\n * ```\n */\n async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {\n await this.mount();\n\n const recursive = options?.recursive ?? false;\n const segments = splitPath(path);\n\n let current: FileSystemDirectoryHandle | null = this.root;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n\n try {\n current = await current.getDirectoryHandle(segment!, { create: recursive || i === segments.length - 1 });\n }\n catch (err: any) {\n if (err.name === 'NotFoundError') {\n throw new OPFSError(\n `Parent directory does not exist: ${ joinPath(segments.slice(0, i + 1)) }`,\n 'ENOENT',\n undefined,\n err\n );\n }\n\n if (err.name === 'TypeMismatchError') {\n throw new OPFSError(`Path segment is not a directory: ${ segment }`, 'ENOTDIR', undefined, err);\n }\n\n throw new OPFSError('Failed to create directory', 'MKDIR_FAILED', undefined, err);\n }\n }\n\n await this.notifyChange({ path, type: 'added', isDirectory: true });\n }\n\n /**\n * Get file or directory statistics\n * \n * Returns detailed information about a file or directory, including\n * size, modification time, and optionally a hash of the file content.\n * \n * @param path - The path to the file or directory\n * @returns Promise that resolves to FileStat object\n * @throws {OPFSError} If the path does not exist or cannot be accessed\n * \n * @example\n * ```typescript\n * const stats = await fs.stat('/data/config.json');\n * console.log(`File size: ${stats.size} bytes`);\n * console.log(`Last modified: ${stats.mtime}`);\n * \n * // If hashing is enabled, hash will be included\n * if (stats.hash) {\n * console.log(`Hash: ${stats.hash}`);\n * }\n * ```\n */\n async stat(path: string): Promise<FileStat> {\n await this.mount();\n\n // Special handling for root directory\n if (path === '/') {\n return {\n kind: 'directory',\n size: 0,\n mtime: new Date(0).toISOString(),\n ctime: new Date(0).toISOString(),\n isFile: false,\n isDirectory: true,\n };\n }\n\n const name = basename(path);\n const parentDir = await this.getDirectoryHandle(dirname(path), false);\n const includeHash = this.options.hashAlgorithm !== null;\n\n try {\n const fileHandle = await parentDir.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n\n const baseStat: FileStat = {\n kind: 'file',\n size: file.size,\n mtime: new Date(file.lastModified).toISOString(),\n ctime: new Date(file.lastModified).toISOString(),\n isFile: true,\n isDirectory: false,\n };\n\n if (includeHash && this.options.hashAlgorithm) {\n try {\n const hash = await calculateFileHash(file, this.options.hashAlgorithm, this.options.maxFileSize);\n\n baseStat.hash = hash;\n }\n catch (error) {\n console.warn(`Failed to calculate hash for ${ path }:`, error);\n }\n }\n\n return baseStat;\n }\n catch (e: any) {\n if (e.name !== 'TypeMismatchError' && e.name !== 'NotFoundError') {\n throw new OPFSError('Failed to stat (file)', 'STAT_FAILED', undefined, e);\n }\n }\n\n try {\n await parentDir.getDirectoryHandle(name, { create: false });\n\n return {\n kind: 'directory',\n size: 0,\n mtime: new Date(0).toISOString(),\n ctime: new Date(0).toISOString(),\n isFile: false,\n isDirectory: true,\n };\n }\n catch (e: any) {\n if (e.name === 'NotFoundError') {\n throw new OPFSError(`No such file or directory: ${ path }`, 'ENOENT', undefined, e);\n }\n\n throw new OPFSError('Failed to stat (directory)', 'STAT_FAILED', undefined, e);\n }\n }\n\n /**\n * Read a directory's contents\n * \n * Lists all files and subdirectories within the specified directory.\n * \n * @param path - The path to the directory to read\n * @returns Promise that resolves to an array of detailed file/directory information\n * @throws {OPFSError} If the directory does not exist or cannot be accessed\n * \n * @example\n * ```typescript\n * // Get detailed information about files and directories\n * const detailed = await fs.readDir('/users/john/documents');\n * detailed.forEach(item => {\n * console.log(`${item.name} - ${item.isFile ? 'file' : 'directory'}`);\n * });\n * ```\n */\n async readDir(path: string): Promise<DirentData[]> {\n await this.mount();\n\n const dir = await this.getDirectoryHandle(path, false);\n\n const results: DirentData[] = [];\n\n for await (const [name, handle] of (dir as any).entries()) {\n const isFile = handle.kind === 'file';\n\n results.push({\n name,\n kind: handle.kind,\n isFile,\n isDirectory: !isFile,\n });\n }\n\n return results;\n }\n\n /**\n * Check if a file or directory exists\n * \n * Verifies if a file or directory exists at the specified path.\n * \n * @param path - The path to check\n * @returns Promise that resolves to true if the file or directory exists, false otherwise \n * \n * @example\n * ```typescript\n * const exists = await fs.exists('/config/settings.json');\n * console.log(`File exists: ${exists}`);\n * ```\n */\n async exists(path: string): Promise<boolean> {\n await this.mount();\n\n if (path === '/') {\n return true;\n }\n\n const name = basename(path);\n let dir: FileSystemDirectoryHandle | null = null;\n\n try {\n dir = await this.getDirectoryHandle(dirname(path), false);\n }\n catch (e: any) {\n if (e.name === 'NotFoundError' || e.name === 'TypeMismatchError') {\n dir = null;\n }\n\n throw e;\n }\n\n if (!dir || !name) {\n return false;\n }\n\n try {\n await dir.getFileHandle(name, { create: false });\n\n return true;\n }\n catch (err: any) {\n if (err.name !== 'NotFoundError' && err.name !== 'TypeMismatchError') {\n throw err;\n }\n\n try {\n await dir.getDirectoryHandle(name, { create: false });\n\n return true;\n }\n catch (err: any) {\n if (err.name !== 'NotFoundError' && err.name !== 'TypeMismatchError') {\n throw err;\n }\n\n return false;\n }\n }\n }\n\n /**\n * Clear all contents of a directory without removing the directory itself\n * \n * Removes all files and subdirectories within the specified directory,\n * but keeps the directory itself.\n * \n * @param path - The path to the directory to clear (default: '/')\n * @returns Promise that resolves when all contents are removed\n * @throws {OPFSError} If the operation fails\n * \n * @example\n * ```typescript\n * // Clear root directory contents\n * await fs.clear('/');\n * \n * // Clear specific directory contents\n * await fs.clear('/data');\n * ```\n */\n async clear(path: string = '/'): Promise<void> {\n await this.mount();\n\n try {\n const items = await this.readDir(path);\n\n for (const item of items) {\n const itemPath = `${ path === '/' ? '' : path }/${ item.name }`;\n\n await this.remove(itemPath, { recursive: true });\n }\n\n await this.notifyChange({ path, type: 'changed', isDirectory: true });\n }\n catch (error: any) {\n if (error instanceof OPFSError) {\n throw error;\n }\n\n throw new OPFSError(`Failed to clear directory: ${ path }`, 'CLEAR_FAILED', undefined, error);\n }\n }\n\n /**\n * Remove files and directories\n * \n * Removes files and directories. Similar to Node.js fs.rm().\n * \n * @param path - The path to remove\n * @param options - Options for removal\n * @param options.recursive - Whether to remove directories and their contents recursively (default: false)\n * @param options.force - Whether to ignore errors if the path doesn't exist (default: false)\n * @returns Promise that resolves when the removal is complete\n * @throws {OPFSError} If the removal fails\n * \n * @example\n * ```typescript\n * // Remove a file\n * await fs.rm('/path/to/file.txt');\n * \n * // Remove a directory and all its contents\n * await fs.rm('/path/to/directory', { recursive: true });\n * \n * // Remove with force (ignore if doesn't exist)\n * await fs.rm('/maybe/exists', { force: true });\n * ```\n */\n async remove(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void> {\n await this.mount();\n\n // Special handling for root directory\n if (path === '/') {\n throw new OPFSError('Cannot remove root directory', 'EROOT');\n }\n\n const { recursive = false, force = false } = options || {};\n\n const parent = await this.getDirectoryHandle(dirname(path), false);\n\n await removeEntry(parent, path, { recursive, force });\n\n await this.notifyChange({ path, type: 'removed', isDirectory: false });\n }\n\n /**\n * Resolve a path to an absolute path\n * \n * Resolves relative paths and normalizes path segments (like '..' and '.').\n * Similar to Node.js fs.realpath() but without symlink resolution since OPFS doesn't support symlinks.\n * \n * @param path - The path to resolve\n * @returns Promise that resolves to the absolute normalized path\n * @throws {FileNotFoundError} If the path does not exist\n * @throws {OPFSError} If path resolution fails\n * \n * @example\n * ```typescript\n * // Resolve relative path\n * const absolute = await fs.realpath('./config/../data/file.txt');\n * console.log(absolute); // '/data/file.txt'\n * ```\n */\n async realpath(path: string): Promise<string> {\n await this.mount();\n\n try {\n const normalizedPath = resolvePath(path);\n const exists = await this.exists(normalizedPath);\n\n if (!exists) {\n throw new FileNotFoundError(normalizedPath);\n }\n\n return normalizedPath;\n }\n catch (error) {\n if (error instanceof OPFSError) {\n throw error;\n }\n\n throw new OPFSError(`Failed to resolve path: ${ path }`, 'REALPATH_FAILED', undefined, error);\n }\n }\n\n /**\n * Rename a file or directory\n * \n * Changes the name of a file or directory. If the target path already exists,\n * it will be replaced only if overwrite option is enabled.\n * \n * @param oldPath - The current path of the file or directory\n * @param newPath - The new path for the file or directory\n * @param options - Options for renaming\n * @param options.overwrite - Whether to overwrite existing files (default: false)\n * @returns Promise that resolves when the rename operation is complete\n * @throws {OPFSError} If the rename operation fails\n * \n * @example\n * ```typescript\n * // Basic rename (fails if target exists)\n * await fs.rename('/old/path/file.txt', '/new/path/renamed.txt');\n * \n * // Rename with overwrite\n * await fs.rename('/old/path/file.txt', '/new/path/renamed.txt', { overwrite: true });\n * ```\n */\n async rename(oldPath: string, newPath: string, options?: RenameOptions): Promise<void> {\n await this.mount();\n\n try {\n const overwrite = options?.overwrite ?? false;\n\n const sourceExists = await this.exists(oldPath);\n\n if (!sourceExists) {\n throw new FileNotFoundError(oldPath);\n }\n\n const destExists = await this.exists(newPath);\n\n if (destExists && !overwrite) {\n throw new OPFSError(`Destination already exists: ${ newPath }`, 'EEXIST', undefined);\n }\n\n await this.copy(oldPath, newPath, { recursive: true, overwrite });\n await this.remove(oldPath, { recursive: true });\n\n // Notify about the rename operation\n await this.notifyChange({ path: oldPath, type: 'removed', isDirectory: false });\n await this.notifyChange({ path: newPath, type: 'added', isDirectory: false });\n }\n catch (error) {\n if (error instanceof OPFSError) {\n throw error;\n }\n\n throw new OPFSError(`Failed to rename from ${ oldPath } to ${ newPath }`, 'RENAME_FAILED', undefined, error);\n }\n }\n\n /**\n * Copy files and directories\n * \n * Copies files and directories. Similar to Node.js fs.cp().\n * \n * @param source - The source path to copy from\n * @param destination - The destination path to copy to\n * @param options - Options for copying\n * @param options.recursive - Whether to copy directories recursively (default: false)\n * @param options.overwrite - Whether to overwrite existing files (default: true)\n * @returns Promise that resolves when the copy operation is complete\n * @throws {OPFSError} If the copy operation fails\n * \n * @example\n * ```typescript\n * // Copy a file\n * await fs.copy('/source/file.txt', '/dest/file.txt');\n * \n * // Copy a directory and all its contents\n * await fs.copy('/source/dir', '/dest/dir', { recursive: true });\n * \n * // Copy without overwriting existing files\n * await fs.copy('/source', '/dest', { recursive: true, overwrite: false });\n * ```\n */\n async copy(source: string, destination: string, options?: { recursive?: boolean; overwrite?: boolean }): Promise<void> {\n await this.mount();\n\n try {\n const recursive = options?.recursive ?? false;\n const overwrite = options?.overwrite ?? true;\n\n const sourceExists = await this.exists(source);\n\n if (!sourceExists) {\n throw new OPFSError(`Source does not exist: ${ source }`, 'ENOENT', undefined);\n }\n\n const destExists = await this.exists(destination);\n\n if (destExists && !overwrite) {\n throw new OPFSError(`Destination already exists: ${ destination }`, 'EEXIST', undefined);\n }\n\n const sourceStats = await this.stat(source);\n\n if (sourceStats.isFile) {\n const content = await this.readFile(source, 'binary');\n\n await this.writeFile(destination, content);\n }\n else {\n if (!recursive) {\n throw new OPFSError(`Cannot copy directory without recursive option: ${ source }`, 'EISDIR', undefined);\n }\n\n await this.mkdir(destination, { recursive: true });\n\n const items = await this.readDir(source);\n\n for (const item of items) {\n const sourceItemPath = `${ source }/${ item.name }`;\n const destItemPath = `${ destination }/${ item.name }`;\n\n await this.copy(sourceItemPath, destItemPath, { recursive: true, overwrite });\n }\n }\n }\n catch (error) {\n if (error instanceof OPFSError) {\n throw error;\n }\n\n throw new OPFSError(`Failed to copy from ${ source } to ${ destination }`, 'CP_FAILED', undefined, error);\n }\n }\n\n /**\n * Start watching a file or directory for changes\n * \n * @param path - The path to watch (minimatch syntax allowed)\n * @param options - Watch options\n * @param options.recursive - Whether to watch recursively (default: true)\n * @param options.exclude - Glob pattern(s) to exclude (minimatch).\n * @returns Promise that resolves when watching starts\n * \n * @example\n * ```typescript\n * // Watch entire directory tree recursively (default)\n * await fs.watch('/data');\n * \n * // Watch only immediate children (shallow)\n * await fs.watch('/data', { recursive: false });\n * \n * // Watch a single file\n * await fs.watch('/config.json', { recursive: false });\n * \n * // Watch all json files but not in dist directory\n * await fs.watch('/**\\/*.json', { recursive: false, exclude: ['dist/**'] });\n *\n * ```\n */\n async watch(path: string, options?: WatchOptions): Promise<void> {\n if (!this.options.broadcastChannel) {\n throw new OPFSError('This instance is not configured to send events. Please specify options.broadcastChannel to enable watching.', 'ENOENT');\n }\n\n const snapshot: WatchSnapshot = {\n pattern: normalizeMinimatch(path, options?.recursive ?? true),\n include: Array.isArray(options?.include) ? options.include : [options?.include ?? '**'],\n exclude: Array.isArray(options?.exclude) ? options.exclude : [options?.exclude ?? ''],\n };\n\n this.watchers.set(path, snapshot);\n }\n\n /**\n * Stop watching a previously watched path\n */\n unwatch(path: string): void {\n this.watchers.delete(path);\n }\n\n /**\n * Dispose of resources and clean up the file system instance\n * \n * This method should be called when the file system instance is no longer needed\n * to properly clean up resources like the broadcast channel and watch timers.\n */\n dispose(): void {\n if (this.broadcastChannel) {\n this.broadcastChannel.close();\n this.broadcastChannel = null;\n }\n\n this.watchers.clear();\n }\n\n /**\n * Synchronize the file system with external data\n * \n * Syncs the file system with an array of entries containing paths and data.\n * This is useful for importing data from external sources or syncing with remote data.\n * \n * @param entries - Array of [path, data] tuples to sync\n * @param options - Options for synchronization\n * @param options.cleanBefore - Whether to clear the file system before syncing (default: false)\n * @returns Promise that resolves when synchronization is complete\n * @throws {OPFSError} If the synchronization fails\n * \n * @example\n * ```typescript\n * // Sync with external data\n * const entries: [string, string | Uint8Array | Blob][] = [\n * ['/config.json', JSON.stringify({ theme: 'dark' })],\n * ['/data/binary.dat', new Uint8Array([1, 2, 3, 4])],\n * ['/upload.txt', new Blob(['file content'], { type: 'text/plain' })]\n * ];\n * \n * // Sync without clearing existing files\n * await fs.sync(entries);\n * \n * // Clean file system and then sync\n * await fs.sync(entries, { cleanBefore: true });\n * ```\n */\n async sync(entries: [string, string | Uint8Array | Blob][], options?: { cleanBefore?: boolean }): Promise<void> {\n await this.mount();\n\n try {\n const cleanBefore = options?.cleanBefore ?? false;\n\n if (cleanBefore) {\n await this.clear('/');\n }\n\n for (const [path, data] of entries) {\n const normalizedPath = normalizePath(path);\n\n let fileData: string | Uint8Array;\n\n if (data instanceof Blob) {\n fileData = await convertBlobToUint8Array(data);\n }\n else {\n fileData = data;\n }\n\n await this.writeFile(normalizedPath, fileData);\n }\n }\n catch (error) {\n if (error instanceof OPFSError) {\n throw error;\n }\n\n throw new OPFSError('Failed to sync file system', 'SYNC_FAILED', undefined, error);\n }\n }\n}\n\n// Only expose the worker when running in a Web Worker environment\nif (typeof globalThis !== 'undefined' && globalThis.constructor.name === 'DedicatedWorkerGlobalScope') {\n expose(new OPFSWorker());\n}\n"],"names":["OPFSWorker","event","path","snapshot","matchMinimatch","include","exclude","hash","watchEvent","error","options","checkOPFSSupport","root","resolve","reject","rootDir","OPFSError","normalizePath","create","from","segments","splitPath","current","segment","PathError","fileName","result","walk","dirPath","items","item","fullPath","stat","err","encoding","isBinaryFileExtension","fileHandle","buffer","readFileData","decodeBuffer","FileNotFoundError","data","fileExists","writeFileData","recursive","i","joinPath","name","basename","parentDir","dirname","includeHash","file","baseStat","calculateFileHash","e","dir","results","handle","isFile","itemPath","force","parent","removeEntry","normalizedPath","resolvePath","oldPath","newPath","overwrite","source","destination","content","sourceItemPath","destItemPath","normalizeMinimatch","entries","fileData","convertBlobToUint8Array","expose"],"mappings":"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