opfs-worker 1.3.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +64 -458
  2. package/dist/BaseOPFS-BPw9AIQ-.cjs +4 -0
  3. package/dist/BaseOPFS-BPw9AIQ-.cjs.map +1 -0
  4. package/dist/BaseOPFS-Cylz6ijk.js +2036 -0
  5. package/dist/BaseOPFS-Cylz6ijk.js.map +1 -0
  6. package/dist/OPFSAsync-BKSAJJeh.js +124 -0
  7. package/dist/OPFSAsync-BKSAJJeh.js.map +1 -0
  8. package/dist/OPFSAsync-s2P7y3sw.cjs +2 -0
  9. package/dist/OPFSAsync-s2P7y3sw.cjs.map +1 -0
  10. package/dist/OPFSFacade-DePnNRgt.cjs +2 -0
  11. package/dist/OPFSFacade-DePnNRgt.cjs.map +1 -0
  12. package/dist/OPFSFacade-XohWlHuu.js +281 -0
  13. package/dist/OPFSFacade-XohWlHuu.js.map +1 -0
  14. package/dist/OPFSSync-C7KBepe_.cjs +2 -0
  15. package/dist/OPFSSync-C7KBepe_.cjs.map +1 -0
  16. package/dist/OPFSSync-CsSQkpk5.js +208 -0
  17. package/dist/OPFSSync-CsSQkpk5.js.map +1 -0
  18. package/dist/assets/dedicated.worker-nY3tK4zi.js.map +1 -0
  19. package/dist/{worker.d.ts → core/BaseOPFS.d.ts} +34 -300
  20. package/dist/core/BaseOPFS.d.ts.map +1 -0
  21. package/dist/core/OPFSAsync.d.ts +55 -0
  22. package/dist/core/OPFSAsync.d.ts.map +1 -0
  23. package/dist/core/OPFSSync.d.ts +54 -0
  24. package/dist/core/OPFSSync.d.ts.map +1 -0
  25. package/dist/createOPFSAsync-BwKDmHsy.cjs +2 -0
  26. package/dist/createOPFSAsync-BwKDmHsy.cjs.map +1 -0
  27. package/dist/createOPFSAsync-Y4Mg6qte.js +10 -0
  28. package/dist/createOPFSAsync-Y4Mg6qte.js.map +1 -0
  29. package/dist/createOPFSDedicated-CsnV6MLP.js +2333 -0
  30. package/dist/createOPFSDedicated-CsnV6MLP.js.map +1 -0
  31. package/dist/createOPFSDedicated-pKTRWLdz.cjs +2283 -0
  32. package/dist/createOPFSDedicated-pKTRWLdz.cjs.map +1 -0
  33. package/dist/createOPFSShared-CGdd__PH.js +23 -0
  34. package/dist/createOPFSShared-CGdd__PH.js.map +1 -0
  35. package/dist/createOPFSShared-CskROt7U.cjs +2 -0
  36. package/dist/createOPFSShared-CskROt7U.cjs.map +1 -0
  37. package/dist/dedicated.worker.js +2281 -0
  38. package/dist/dedicated.worker.js.map +1 -0
  39. package/dist/{facade.d.ts → facade/OPFSFacade.d.ts} +35 -10
  40. package/dist/facade/OPFSFacade.d.ts.map +1 -0
  41. package/dist/facade/createOPFSAsync.d.ts +16 -0
  42. package/dist/facade/createOPFSAsync.d.ts.map +1 -0
  43. package/dist/facade/createOPFSDedicated.d.ts +17 -0
  44. package/dist/facade/createOPFSDedicated.d.ts.map +1 -0
  45. package/dist/facade/createOPFSShared.d.ts +24 -0
  46. package/dist/facade/createOPFSShared.d.ts.map +1 -0
  47. package/dist/index.async.cjs +2 -0
  48. package/dist/index.async.cjs.map +1 -0
  49. package/dist/index.async.d.ts +10 -0
  50. package/dist/index.async.d.ts.map +1 -0
  51. package/dist/index.async.js +55 -0
  52. package/dist/index.async.js.map +1 -0
  53. package/dist/index.cjs +1 -2466
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.ts +15 -7
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +61 -2799
  58. package/dist/index.js.map +1 -1
  59. package/dist/index.pure.cjs +2 -0
  60. package/dist/index.pure.cjs.map +1 -0
  61. package/dist/index.pure.d.ts +8 -0
  62. package/dist/index.pure.d.ts.map +1 -0
  63. package/dist/index.pure.js +53 -0
  64. package/dist/index.pure.js.map +1 -0
  65. package/dist/index.sharedworker.cjs +2 -0
  66. package/dist/index.sharedworker.cjs.map +1 -0
  67. package/dist/index.sharedworker.d.ts +11 -0
  68. package/dist/index.sharedworker.d.ts.map +1 -0
  69. package/dist/index.sharedworker.js +55 -0
  70. package/dist/index.sharedworker.js.map +1 -0
  71. package/dist/index.sync.cjs +2 -0
  72. package/dist/index.sync.cjs.map +1 -0
  73. package/dist/index.sync.d.ts +11 -0
  74. package/dist/index.sync.d.ts.map +1 -0
  75. package/dist/index.sync.js +55 -0
  76. package/dist/index.sync.js.map +1 -0
  77. package/dist/shared.worker.js +2166 -0
  78. package/dist/shared.worker.js.map +1 -0
  79. package/dist/types.d.ts +9 -4
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/utils/helpers.d.ts +11 -1
  82. package/dist/utils/helpers.d.ts.map +1 -1
  83. package/dist/worker/createDedicatedWorker.d.ts +30 -0
  84. package/dist/worker/createDedicatedWorker.d.ts.map +1 -0
  85. package/dist/worker/createSharedWorker.d.ts +37 -0
  86. package/dist/worker/createSharedWorker.d.ts.map +1 -0
  87. package/dist/worker/dedicated.worker.d.ts +2 -0
  88. package/dist/worker/dedicated.worker.d.ts.map +1 -0
  89. package/dist/worker/shared.worker.d.ts +2 -0
  90. package/dist/worker/shared.worker.d.ts.map +1 -0
  91. package/docs/README.md +30 -0
  92. package/docs/api/backend.md +36 -0
  93. package/docs/api/create.md +82 -0
  94. package/docs/api/facade.md +52 -0
  95. package/docs/choosing-a-mode.md +41 -0
  96. package/docs/file-descriptors.md +695 -0
  97. package/docs/guides/async.md +36 -0
  98. package/docs/guides/dedicated.md +76 -0
  99. package/docs/guides/hashing.md +34 -0
  100. package/docs/guides/pure.md +46 -0
  101. package/docs/guides/sharedworker.md +57 -0
  102. package/docs/guides/streaming.md +51 -0
  103. package/docs/guides/watching.md +35 -0
  104. package/docs/migration.md +41 -0
  105. package/docs/types.md +246 -0
  106. package/package.json +55 -10
  107. package/src/core/BaseOPFS.ts +951 -0
  108. package/src/core/OPFSAsync.ts +248 -0
  109. package/src/core/OPFSSync.ts +460 -0
  110. package/src/facade/OPFSFacade.ts +489 -0
  111. package/src/facade/createOPFSAsync.ts +22 -0
  112. package/src/facade/createOPFSDedicated.ts +23 -0
  113. package/src/facade/createOPFSShared.ts +30 -0
  114. package/src/index.async.ts +11 -0
  115. package/src/index.pure.ts +8 -0
  116. package/src/index.sharedworker.ts +12 -0
  117. package/src/index.sync.ts +12 -0
  118. package/src/index.ts +44 -0
  119. package/src/types.ts +103 -0
  120. package/src/utils/encoder.ts +205 -0
  121. package/src/utils/errors.ts +297 -0
  122. package/src/utils/helpers.ts +465 -0
  123. package/src/worker/createDedicatedWorker.ts +74 -0
  124. package/src/worker/createSharedWorker.ts +67 -0
  125. package/src/worker/dedicated.worker.ts +13 -0
  126. package/src/worker/shared.worker.ts +17 -0
  127. package/dist/assets/worker-1Wh1cr7P.js.map +0 -1
  128. package/dist/assets/worker-BeJaVyBV.js.map +0 -1
  129. package/dist/assets/worker-DYYLzR1Y.js.map +0 -1
  130. package/dist/facade.d.ts.map +0 -1
  131. package/dist/helpers-BuGfPAg0.js +0 -1439
  132. package/dist/helpers-BuGfPAg0.js.map +0 -1
  133. package/dist/helpers-CF7A2WQG.cjs +0 -4
  134. package/dist/helpers-CF7A2WQG.cjs.map +0 -1
  135. package/dist/helpers-CIiblZ8d.cjs +0 -4
  136. package/dist/helpers-CIiblZ8d.cjs.map +0 -1
  137. package/dist/helpers-CKOebsbw.js +0 -1423
  138. package/dist/helpers-CKOebsbw.js.map +0 -1
  139. package/dist/helpers-Ca2c767K.js +0 -1427
  140. package/dist/helpers-Ca2c767K.js.map +0 -1
  141. package/dist/helpers-Dwc92hv9.cjs +0 -4
  142. package/dist/helpers-Dwc92hv9.cjs.map +0 -1
  143. package/dist/raw.cjs +0 -2
  144. package/dist/raw.cjs.map +0 -1
  145. package/dist/raw.d.ts +0 -13
  146. package/dist/raw.d.ts.map +0 -1
  147. package/dist/raw.js +0 -1001
  148. package/dist/raw.js.map +0 -1
  149. package/dist/worker.d.ts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OPFSSync-C7KBepe_.cjs","sources":["../src/core/OPFSSync.ts"],"sourcesContent":["import { transfer } from 'comlink';\n\nimport { BaseOPFS } from './BaseOPFS';\nimport { WatchEventType } from '../types';\nimport {\n AlreadyExistsError,\n OPFSError,\n ValidationError,\n createFDError,\n mapDomError\n} from '../utils/errors';\n\nimport {\n calculateReadLength,\n createSyncHandleSafe,\n normalizePath,\n resolvePath,\n safeCloseSyncHandle,\n validateReadWriteArgs,\n withLock\n} from '../utils/helpers';\n\nimport type { FileOpenOptions, FileStat, OPFSOptions } from '../types';\n\n/**\n * Sync OPFS backend using `FileSystemSyncAccessHandle` (dedicated worker only).\n *\n * Provides high-level file I/O and Node-like file descriptors.\n *\n * @example\n * ```typescript\n * import { OPFSSync } from 'opfs-worker/pure';\n *\n * const fs = new OPFSSync();\n * await fs.writeFile('/data/config.json', new TextEncoder().encode(JSON.stringify({ theme: 'dark' })));\n * const config = await fs.readFile('/data/config.json');\n * ```\n */\nexport class OPFSSync extends BaseOPFS {\n /** Shared sync handles per path (OPFS allows only one handle per file) */\n private openHandles = new Map<string, {\n fileHandle: FileSystemFileHandle;\n syncHandle: FileSystemSyncAccessHandle;\n refCount: number;\n }>();\n\n /** Map of open file descriptors to their metadata */\n private openFiles = new Map<number, {\n path: string;\n fileHandle: FileSystemFileHandle;\n syncHandle: FileSystemSyncAccessHandle;\n position: number;\n }>();\n\n /** Next available file descriptor number */\n private nextFd = 1;\n\n constructor(options?: OPFSOptions) {\n super(options);\n }\n\n /**\n * Get file info by descriptor with validation\n * @private\n */\n private _getFileDescriptor(fd: number): { path: string; fileHandle: FileSystemFileHandle; syncHandle: FileSystemSyncAccessHandle; position: number } {\n const fileInfo = this.openFiles.get(fd);\n\n if (!fileInfo) {\n throw new ValidationError('descriptor', `Invalid file descriptor: ${ fd }`);\n }\n\n return fileInfo;\n }\n\n async readFile(path: string): Promise<Uint8Array> {\n await this.mount();\n\n try {\n return await withLock(path, async() => {\n const fd = await this.open(path);\n\n try {\n const { size } = await this.fstat(fd);\n const buffer = new Uint8Array(size);\n\n if (size > 0) {\n await this.read(fd, buffer, 0, size, 0);\n }\n\n return transfer(buffer, [buffer.buffer]);\n }\n finally {\n await this.close(fd);\n }\n });\n }\n catch (err) {\n if (err instanceof OPFSError) {\n throw err;\n }\n\n throw mapDomError(err, { path, isDirectory: false });\n }\n }\n\n async writeFile(\n path: string,\n data: Uint8Array | ArrayBuffer\n ): Promise<void> {\n await this.mount();\n\n const buffer = data instanceof Uint8Array ? data : new Uint8Array(data);\n\n await withLock(path, async() => {\n const existed = await this.exists(path);\n const fd = await this.open(path, { create: true, truncate: true });\n\n try {\n await this.write(fd, buffer, 0, buffer.length, null, false);\n await this.fsync(fd);\n }\n finally {\n await this.close(fd);\n }\n\n await this.notifyChange({ path, type: existed ? WatchEventType.Changed : WatchEventType.Added, isDirectory: false });\n });\n }\n\n async appendFile(\n path: string,\n data: Uint8Array | ArrayBuffer\n ): Promise<void> {\n await this.mount();\n\n const buffer = data instanceof Uint8Array ? data : new Uint8Array(data);\n\n await withLock(path, async() => {\n const fd = await this.open(path, { create: true });\n\n try {\n const { size } = await this.fstat(fd);\n\n await this.write(fd, buffer, 0, buffer.length, size, false);\n await this.fsync(fd);\n }\n finally {\n await this.close(fd);\n }\n\n await this.notifyChange({ path, type: WatchEventType.Changed, isDirectory: false });\n });\n }\n\n async writeStream(\n path: string,\n stream: ReadableStream<Uint8Array>,\n onProgress?: (bytesWritten: number) => unknown\n ): Promise<number> {\n await this.mount();\n\n return withLock(path, async() => {\n const existed = await this.exists(path);\n const fd = await this.open(path, { create: true, truncate: true });\n const reader = stream.getReader();\n let totalBytes = 0;\n\n try {\n while (true) {\n const { done, value: chunk } = await reader.read();\n\n if (done) {\n break;\n }\n\n let offset = 0;\n\n while (offset < chunk.byteLength) {\n offset += await this.write(\n fd,\n chunk,\n offset,\n chunk.byteLength - offset,\n null,\n false\n );\n }\n\n totalBytes += chunk.byteLength;\n await onProgress?.(totalBytes);\n }\n\n await this.fsync(fd);\n }\n catch (error) {\n await reader.cancel(error).catch(() => {});\n\n throw error;\n }\n finally {\n reader.releaseLock();\n await this.close(fd);\n }\n\n await this.notifyChange({\n path,\n type: existed ? WatchEventType.Changed : WatchEventType.Added,\n isDirectory: false,\n });\n\n return totalBytes;\n });\n }\n\n async open(path: string, options?: FileOpenOptions): Promise<number> {\n await this.mount();\n\n const { create = false, exclusive = false, truncate = false } = options || {};\n\n // Normalize path to prevent path-related issues\n const normalizedPath = normalizePath(resolvePath(path));\n\n try {\n // Use lock for atomic operations when creating files\n if (create && exclusive) {\n return await withLock(normalizedPath, async() => {\n const exists = await this.exists(normalizedPath);\n\n if (exists) {\n throw new AlreadyExistsError(normalizedPath);\n }\n\n return this._openFile(normalizedPath, create, truncate);\n });\n }\n\n return await this._openFile(normalizedPath, create, truncate);\n }\n catch (error: any) {\n if (error instanceof OPFSError) {\n throw error;\n }\n\n // TypeMismatchError here means the path actually refers to a directory\n // so we map it as a directory-type error (EISDIR) for better Node.js parity.\n const isTypeMismatchDirectory = error && error.name === 'TypeMismatchError';\n\n throw mapDomError(error, {\n path: normalizedPath,\n isDirectory: !!isTypeMismatchDirectory,\n });\n }\n }\n\n /**\n * Internal method to open a file (without locking)\n *\n * Multiple FDs for the same path share one sync access handle (OPFS limit),\n * with independent per-FD positions — similar to Node.js.\n * @private\n */\n private async _openFile(path: string, create: boolean, truncate: boolean): Promise<number> {\n let shared = this.openHandles.get(path);\n\n if (!shared) {\n const fileHandle = await this.getFileHandle(path, create);\n\n // Verify that we got a file handle, not a directory\n try {\n await fileHandle.getFile();\n }\n catch (error: any) {\n throw mapDomError(error, { path, isDirectory: true });\n }\n\n const syncHandle = await createSyncHandleSafe(fileHandle, path);\n\n shared = { fileHandle, syncHandle, refCount: 0 };\n this.openHandles.set(path, shared);\n }\n\n shared.refCount++;\n\n if (truncate) {\n shared.syncHandle.truncate(0);\n shared.syncHandle.flush();\n }\n\n const fd = this.nextFd++;\n\n this.openFiles.set(fd, {\n path,\n fileHandle: shared.fileHandle,\n syncHandle: shared.syncHandle,\n position: 0,\n });\n\n return fd;\n }\n\n async close(fd: number): Promise<void> {\n const fileInfo = this._getFileDescriptor(fd);\n\n this.openFiles.delete(fd);\n\n const shared = this.openHandles.get(fileInfo.path);\n\n if (!shared) {\n return;\n }\n\n shared.refCount--;\n\n // Only close the underlying sync handle when the last FD for this path is gone\n if (shared.refCount <= 0) {\n safeCloseSyncHandle(fd, shared.syncHandle, fileInfo.path);\n this.openHandles.delete(fileInfo.path);\n }\n }\n\n async read(\n fd: number,\n buffer: Uint8Array,\n offset: number,\n length: number,\n position: number | null | undefined\n ): Promise<{ bytesRead: number; buffer: Uint8Array }> {\n const fileInfo = this._getFileDescriptor(fd);\n\n // Validate arguments\n validateReadWriteArgs(buffer.length, offset, length, position);\n\n try {\n const readPosition = position ?? fileInfo.position;\n\n // Get file size and calculate read length\n const fileSize = fileInfo.syncHandle.getSize();\n const { isEOF, actualLength } = calculateReadLength(readPosition, length, fileSize);\n\n if (isEOF) {\n return transfer({ bytesRead: 0, buffer }, [buffer.buffer]); // End of file\n }\n\n // Create a subarray view for the read operation\n const targetBuffer = buffer.subarray(offset, offset + actualLength);\n\n // Perform efficient positioned read\n const bytesRead = fileInfo.syncHandle.read(targetBuffer, { at: readPosition });\n\n // Update position if position was not explicitly specified (null means use current position)\n if (position == null) {\n fileInfo.position = readPosition + bytesRead;\n }\n\n return transfer({ bytesRead, buffer }, [buffer.buffer]);\n }\n catch (error) {\n throw createFDError('read', fd, fileInfo.path, error);\n }\n }\n\n async write(\n fd: number,\n buffer: Uint8Array,\n offset: number = 0,\n length?: number,\n position?: number | null | undefined,\n emitEvent: boolean = true\n ): Promise<number> {\n const fileInfo = this._getFileDescriptor(fd);\n\n // Calculate actual length to write\n const actualLength = length ?? (buffer.length - offset);\n\n // Validate arguments using helper\n validateReadWriteArgs(buffer.length, offset, actualLength, position);\n\n try {\n // Determine write position: use specified position, or current position if null/undefined\n const writePosition = position ?? fileInfo.position;\n\n // Create a subarray view for the write operation\n const sourceBuffer = buffer.subarray(offset, offset + actualLength);\n\n // Perform efficient positioned write\n const bytesWritten = fileInfo.syncHandle.write(sourceBuffer, { at: writePosition });\n\n // Update position if position was null or undefined (i.e., use current position)\n // Also update position when writing at current position (position === fileInfo.position)\n if (position == null || position === fileInfo.position) {\n fileInfo.position = writePosition + bytesWritten;\n }\n\n if (emitEvent) {\n await this.notifyChange({ path: fileInfo.path, type: WatchEventType.Changed, isDirectory: false });\n }\n\n return bytesWritten;\n }\n catch (error) {\n throw createFDError('write', fd, fileInfo.path, error);\n }\n }\n\n async fstat(fd: number): Promise<FileStat> {\n const fileInfo = this._getFileDescriptor(fd);\n\n // Simply reuse existing stat() method with the file path\n return this.stat(fileInfo.path);\n }\n\n async ftruncate(fd: number, size: number = 0): Promise<void> {\n const fileInfo = this._getFileDescriptor(fd);\n\n // Validate size parameter\n if (size < 0 || !Number.isInteger(size)) {\n throw new ValidationError('argument', 'Invalid size');\n }\n\n try {\n fileInfo.syncHandle.truncate(size);\n fileInfo.syncHandle.flush();\n\n // Adjust position if it's beyond the new file size\n if (fileInfo.position > size) {\n fileInfo.position = size;\n }\n\n await this.notifyChange({ path: fileInfo.path, type: WatchEventType.Changed, isDirectory: false });\n }\n catch (error) {\n throw createFDError('truncate', fd, fileInfo.path, error);\n }\n }\n\n async fsync(fd: number): Promise<void> {\n const fileInfo = this._getFileDescriptor(fd);\n\n try {\n fileInfo.syncHandle.flush();\n }\n catch (error) {\n throw createFDError('sync', fd, fileInfo.path, error);\n }\n }\n\n override dispose(): void {\n // Close each shared sync handle once (not per FD)\n for (const [path, shared] of this.openHandles) {\n safeCloseSyncHandle(-1, shared.syncHandle, path);\n }\n\n this.openHandles.clear();\n this.openFiles.clear();\n this.nextFd = 1;\n\n super.dispose();\n }\n}\n"],"names":["OPFSSync","BaseOPFS","options","fd","fileInfo","ValidationError","path","withLock","size","buffer","transfer","err","OPFSError","mapDomError","data","existed","WatchEventType","stream","onProgress","reader","totalBytes","done","chunk","offset","error","create","exclusive","truncate","normalizedPath","normalizePath","resolvePath","AlreadyExistsError","isTypeMismatchDirectory","shared","fileHandle","syncHandle","createSyncHandleSafe","safeCloseSyncHandle","length","position","validateReadWriteArgs","readPosition","fileSize","isEOF","actualLength","calculateReadLength","targetBuffer","bytesRead","createFDError","emitEvent","writePosition","sourceBuffer","bytesWritten"],"mappings":"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"}
@@ -0,0 +1,208 @@
1
+ import { transfer as d } from "comlink";
2
+ import { B as D, V as g, w as y, O as F, m as u, n as C, r as x, A, J as I, G as H, C as m, H as S, h as w, W as h } from "./BaseOPFS-Cylz6ijk.js";
3
+ class L extends D {
4
+ /** Shared sync handles per path (OPFS allows only one handle per file) */
5
+ openHandles = /* @__PURE__ */ new Map();
6
+ /** Map of open file descriptors to their metadata */
7
+ openFiles = /* @__PURE__ */ new Map();
8
+ /** Next available file descriptor number */
9
+ nextFd = 1;
10
+ constructor(e) {
11
+ super(e);
12
+ }
13
+ /**
14
+ * Get file info by descriptor with validation
15
+ * @private
16
+ */
17
+ _getFileDescriptor(e) {
18
+ const t = this.openFiles.get(e);
19
+ if (!t)
20
+ throw new g("descriptor", `Invalid file descriptor: ${e}`);
21
+ return t;
22
+ }
23
+ async readFile(e) {
24
+ await this.mount();
25
+ try {
26
+ return await y(e, async () => {
27
+ const t = await this.open(e);
28
+ try {
29
+ const { size: a } = await this.fstat(t), s = new Uint8Array(a);
30
+ return a > 0 && await this.read(t, s, 0, a, 0), d(s, [s.buffer]);
31
+ } finally {
32
+ await this.close(t);
33
+ }
34
+ });
35
+ } catch (t) {
36
+ throw t instanceof F ? t : u(t, { path: e, isDirectory: !1 });
37
+ }
38
+ }
39
+ async writeFile(e, t) {
40
+ await this.mount();
41
+ const a = t instanceof Uint8Array ? t : new Uint8Array(t);
42
+ await y(e, async () => {
43
+ const s = await this.exists(e), i = await this.open(e, { create: !0, truncate: !0 });
44
+ try {
45
+ await this.write(i, a, 0, a.length, null, !1), await this.fsync(i);
46
+ } finally {
47
+ await this.close(i);
48
+ }
49
+ await this.notifyChange({ path: e, type: s ? h.Changed : h.Added, isDirectory: !1 });
50
+ });
51
+ }
52
+ async appendFile(e, t) {
53
+ await this.mount();
54
+ const a = t instanceof Uint8Array ? t : new Uint8Array(t);
55
+ await y(e, async () => {
56
+ const s = await this.open(e, { create: !0 });
57
+ try {
58
+ const { size: i } = await this.fstat(s);
59
+ await this.write(s, a, 0, a.length, i, !1), await this.fsync(s);
60
+ } finally {
61
+ await this.close(s);
62
+ }
63
+ await this.notifyChange({ path: e, type: h.Changed, isDirectory: !1 });
64
+ });
65
+ }
66
+ async writeStream(e, t, a) {
67
+ return await this.mount(), y(e, async () => {
68
+ const s = await this.exists(e), i = await this.open(e, { create: !0, truncate: !0 }), r = t.getReader();
69
+ let n = 0;
70
+ try {
71
+ for (; ; ) {
72
+ const { done: o, value: c } = await r.read();
73
+ if (o)
74
+ break;
75
+ let l = 0;
76
+ for (; l < c.byteLength; )
77
+ l += await this.write(
78
+ i,
79
+ c,
80
+ l,
81
+ c.byteLength - l,
82
+ null,
83
+ !1
84
+ );
85
+ n += c.byteLength, await a?.(n);
86
+ }
87
+ await this.fsync(i);
88
+ } catch (o) {
89
+ throw await r.cancel(o).catch(() => {
90
+ }), o;
91
+ } finally {
92
+ r.releaseLock(), await this.close(i);
93
+ }
94
+ return await this.notifyChange({
95
+ path: e,
96
+ type: s ? h.Changed : h.Added,
97
+ isDirectory: !1
98
+ }), n;
99
+ });
100
+ }
101
+ async open(e, t) {
102
+ await this.mount();
103
+ const { create: a = !1, exclusive: s = !1, truncate: i = !1 } = t || {}, r = C(x(e));
104
+ try {
105
+ return a && s ? await y(r, async () => {
106
+ if (await this.exists(r))
107
+ throw new A(r);
108
+ return this._openFile(r, a, i);
109
+ }) : await this._openFile(r, a, i);
110
+ } catch (n) {
111
+ if (n instanceof F)
112
+ throw n;
113
+ const o = n && n.name === "TypeMismatchError";
114
+ throw u(n, {
115
+ path: r,
116
+ isDirectory: !!o
117
+ });
118
+ }
119
+ }
120
+ /**
121
+ * Internal method to open a file (without locking)
122
+ *
123
+ * Multiple FDs for the same path share one sync access handle (OPFS limit),
124
+ * with independent per-FD positions — similar to Node.js.
125
+ * @private
126
+ */
127
+ async _openFile(e, t, a) {
128
+ let s = this.openHandles.get(e);
129
+ if (!s) {
130
+ const r = await this.getFileHandle(e, t);
131
+ try {
132
+ await r.getFile();
133
+ } catch (o) {
134
+ throw u(o, { path: e, isDirectory: !0 });
135
+ }
136
+ const n = await I(r, e);
137
+ s = { fileHandle: r, syncHandle: n, refCount: 0 }, this.openHandles.set(e, s);
138
+ }
139
+ s.refCount++, a && (s.syncHandle.truncate(0), s.syncHandle.flush());
140
+ const i = this.nextFd++;
141
+ return this.openFiles.set(i, {
142
+ path: e,
143
+ fileHandle: s.fileHandle,
144
+ syncHandle: s.syncHandle,
145
+ position: 0
146
+ }), i;
147
+ }
148
+ async close(e) {
149
+ const t = this._getFileDescriptor(e);
150
+ this.openFiles.delete(e);
151
+ const a = this.openHandles.get(t.path);
152
+ a && (a.refCount--, a.refCount <= 0 && (H(e, a.syncHandle, t.path), this.openHandles.delete(t.path)));
153
+ }
154
+ async read(e, t, a, s, i) {
155
+ const r = this._getFileDescriptor(e);
156
+ m(t.length, a, s, i);
157
+ try {
158
+ const n = i ?? r.position, o = r.syncHandle.getSize(), { isEOF: c, actualLength: l } = S(n, s, o);
159
+ if (c)
160
+ return d({ bytesRead: 0, buffer: t }, [t.buffer]);
161
+ const f = t.subarray(a, a + l), p = r.syncHandle.read(f, { at: n });
162
+ return i == null && (r.position = n + p), d({ bytesRead: p, buffer: t }, [t.buffer]);
163
+ } catch (n) {
164
+ throw w("read", e, r.path, n);
165
+ }
166
+ }
167
+ async write(e, t, a = 0, s, i, r = !0) {
168
+ const n = this._getFileDescriptor(e), o = s ?? t.length - a;
169
+ m(t.length, a, o, i);
170
+ try {
171
+ const c = i ?? n.position, l = t.subarray(a, a + o), f = n.syncHandle.write(l, { at: c });
172
+ return (i == null || i === n.position) && (n.position = c + f), r && await this.notifyChange({ path: n.path, type: h.Changed, isDirectory: !1 }), f;
173
+ } catch (c) {
174
+ throw w("write", e, n.path, c);
175
+ }
176
+ }
177
+ async fstat(e) {
178
+ const t = this._getFileDescriptor(e);
179
+ return this.stat(t.path);
180
+ }
181
+ async ftruncate(e, t = 0) {
182
+ const a = this._getFileDescriptor(e);
183
+ if (t < 0 || !Number.isInteger(t))
184
+ throw new g("argument", "Invalid size");
185
+ try {
186
+ a.syncHandle.truncate(t), a.syncHandle.flush(), a.position > t && (a.position = t), await this.notifyChange({ path: a.path, type: h.Changed, isDirectory: !1 });
187
+ } catch (s) {
188
+ throw w("truncate", e, a.path, s);
189
+ }
190
+ }
191
+ async fsync(e) {
192
+ const t = this._getFileDescriptor(e);
193
+ try {
194
+ t.syncHandle.flush();
195
+ } catch (a) {
196
+ throw w("sync", e, t.path, a);
197
+ }
198
+ }
199
+ dispose() {
200
+ for (const [e, t] of this.openHandles)
201
+ H(-1, t.syncHandle, e);
202
+ this.openHandles.clear(), this.openFiles.clear(), this.nextFd = 1, super.dispose();
203
+ }
204
+ }
205
+ export {
206
+ L as O
207
+ };
208
+ //# sourceMappingURL=OPFSSync-CsSQkpk5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OPFSSync-CsSQkpk5.js","sources":["../src/core/OPFSSync.ts"],"sourcesContent":["import { transfer } from 'comlink';\n\nimport { BaseOPFS } from './BaseOPFS';\nimport { WatchEventType } from '../types';\nimport {\n AlreadyExistsError,\n OPFSError,\n ValidationError,\n createFDError,\n mapDomError\n} from '../utils/errors';\n\nimport {\n calculateReadLength,\n createSyncHandleSafe,\n normalizePath,\n resolvePath,\n safeCloseSyncHandle,\n validateReadWriteArgs,\n withLock\n} from '../utils/helpers';\n\nimport type { FileOpenOptions, FileStat, OPFSOptions } from '../types';\n\n/**\n * Sync OPFS backend using `FileSystemSyncAccessHandle` (dedicated worker only).\n *\n * Provides high-level file I/O and Node-like file descriptors.\n *\n * @example\n * ```typescript\n * import { OPFSSync } from 'opfs-worker/pure';\n *\n * const fs = new OPFSSync();\n * await fs.writeFile('/data/config.json', new TextEncoder().encode(JSON.stringify({ theme: 'dark' })));\n * const config = await fs.readFile('/data/config.json');\n * ```\n */\nexport class OPFSSync extends BaseOPFS {\n /** Shared sync handles per path (OPFS allows only one handle per file) */\n private openHandles = new Map<string, {\n fileHandle: FileSystemFileHandle;\n syncHandle: FileSystemSyncAccessHandle;\n refCount: number;\n }>();\n\n /** Map of open file descriptors to their metadata */\n private openFiles = new Map<number, {\n path: string;\n fileHandle: FileSystemFileHandle;\n syncHandle: FileSystemSyncAccessHandle;\n position: number;\n }>();\n\n /** Next available file descriptor number */\n private nextFd = 1;\n\n constructor(options?: OPFSOptions) {\n super(options);\n }\n\n /**\n * Get file info by descriptor with validation\n * @private\n */\n private _getFileDescriptor(fd: number): { path: string; fileHandle: FileSystemFileHandle; syncHandle: FileSystemSyncAccessHandle; position: number } {\n const fileInfo = this.openFiles.get(fd);\n\n if (!fileInfo) {\n throw new ValidationError('descriptor', `Invalid file descriptor: ${ fd }`);\n }\n\n return fileInfo;\n }\n\n async readFile(path: string): Promise<Uint8Array> {\n await this.mount();\n\n try {\n return await withLock(path, async() => {\n const fd = await this.open(path);\n\n try {\n const { size } = await this.fstat(fd);\n const buffer = new Uint8Array(size);\n\n if (size > 0) {\n await this.read(fd, buffer, 0, size, 0);\n }\n\n return transfer(buffer, [buffer.buffer]);\n }\n finally {\n await this.close(fd);\n }\n });\n }\n catch (err) {\n if (err instanceof OPFSError) {\n throw err;\n }\n\n throw mapDomError(err, { path, isDirectory: false });\n }\n }\n\n async writeFile(\n path: string,\n data: Uint8Array | ArrayBuffer\n ): Promise<void> {\n await this.mount();\n\n const buffer = data instanceof Uint8Array ? data : new Uint8Array(data);\n\n await withLock(path, async() => {\n const existed = await this.exists(path);\n const fd = await this.open(path, { create: true, truncate: true });\n\n try {\n await this.write(fd, buffer, 0, buffer.length, null, false);\n await this.fsync(fd);\n }\n finally {\n await this.close(fd);\n }\n\n await this.notifyChange({ path, type: existed ? WatchEventType.Changed : WatchEventType.Added, isDirectory: false });\n });\n }\n\n async appendFile(\n path: string,\n data: Uint8Array | ArrayBuffer\n ): Promise<void> {\n await this.mount();\n\n const buffer = data instanceof Uint8Array ? data : new Uint8Array(data);\n\n await withLock(path, async() => {\n const fd = await this.open(path, { create: true });\n\n try {\n const { size } = await this.fstat(fd);\n\n await this.write(fd, buffer, 0, buffer.length, size, false);\n await this.fsync(fd);\n }\n finally {\n await this.close(fd);\n }\n\n await this.notifyChange({ path, type: WatchEventType.Changed, isDirectory: false });\n });\n }\n\n async writeStream(\n path: string,\n stream: ReadableStream<Uint8Array>,\n onProgress?: (bytesWritten: number) => unknown\n ): Promise<number> {\n await this.mount();\n\n return withLock(path, async() => {\n const existed = await this.exists(path);\n const fd = await this.open(path, { create: true, truncate: true });\n const reader = stream.getReader();\n let totalBytes = 0;\n\n try {\n while (true) {\n const { done, value: chunk } = await reader.read();\n\n if (done) {\n break;\n }\n\n let offset = 0;\n\n while (offset < chunk.byteLength) {\n offset += await this.write(\n fd,\n chunk,\n offset,\n chunk.byteLength - offset,\n null,\n false\n );\n }\n\n totalBytes += chunk.byteLength;\n await onProgress?.(totalBytes);\n }\n\n await this.fsync(fd);\n }\n catch (error) {\n await reader.cancel(error).catch(() => {});\n\n throw error;\n }\n finally {\n reader.releaseLock();\n await this.close(fd);\n }\n\n await this.notifyChange({\n path,\n type: existed ? WatchEventType.Changed : WatchEventType.Added,\n isDirectory: false,\n });\n\n return totalBytes;\n });\n }\n\n async open(path: string, options?: FileOpenOptions): Promise<number> {\n await this.mount();\n\n const { create = false, exclusive = false, truncate = false } = options || {};\n\n // Normalize path to prevent path-related issues\n const normalizedPath = normalizePath(resolvePath(path));\n\n try {\n // Use lock for atomic operations when creating files\n if (create && exclusive) {\n return await withLock(normalizedPath, async() => {\n const exists = await this.exists(normalizedPath);\n\n if (exists) {\n throw new AlreadyExistsError(normalizedPath);\n }\n\n return this._openFile(normalizedPath, create, truncate);\n });\n }\n\n return await this._openFile(normalizedPath, create, truncate);\n }\n catch (error: any) {\n if (error instanceof OPFSError) {\n throw error;\n }\n\n // TypeMismatchError here means the path actually refers to a directory\n // so we map it as a directory-type error (EISDIR) for better Node.js parity.\n const isTypeMismatchDirectory = error && error.name === 'TypeMismatchError';\n\n throw mapDomError(error, {\n path: normalizedPath,\n isDirectory: !!isTypeMismatchDirectory,\n });\n }\n }\n\n /**\n * Internal method to open a file (without locking)\n *\n * Multiple FDs for the same path share one sync access handle (OPFS limit),\n * with independent per-FD positions — similar to Node.js.\n * @private\n */\n private async _openFile(path: string, create: boolean, truncate: boolean): Promise<number> {\n let shared = this.openHandles.get(path);\n\n if (!shared) {\n const fileHandle = await this.getFileHandle(path, create);\n\n // Verify that we got a file handle, not a directory\n try {\n await fileHandle.getFile();\n }\n catch (error: any) {\n throw mapDomError(error, { path, isDirectory: true });\n }\n\n const syncHandle = await createSyncHandleSafe(fileHandle, path);\n\n shared = { fileHandle, syncHandle, refCount: 0 };\n this.openHandles.set(path, shared);\n }\n\n shared.refCount++;\n\n if (truncate) {\n shared.syncHandle.truncate(0);\n shared.syncHandle.flush();\n }\n\n const fd = this.nextFd++;\n\n this.openFiles.set(fd, {\n path,\n fileHandle: shared.fileHandle,\n syncHandle: shared.syncHandle,\n position: 0,\n });\n\n return fd;\n }\n\n async close(fd: number): Promise<void> {\n const fileInfo = this._getFileDescriptor(fd);\n\n this.openFiles.delete(fd);\n\n const shared = this.openHandles.get(fileInfo.path);\n\n if (!shared) {\n return;\n }\n\n shared.refCount--;\n\n // Only close the underlying sync handle when the last FD for this path is gone\n if (shared.refCount <= 0) {\n safeCloseSyncHandle(fd, shared.syncHandle, fileInfo.path);\n this.openHandles.delete(fileInfo.path);\n }\n }\n\n async read(\n fd: number,\n buffer: Uint8Array,\n offset: number,\n length: number,\n position: number | null | undefined\n ): Promise<{ bytesRead: number; buffer: Uint8Array }> {\n const fileInfo = this._getFileDescriptor(fd);\n\n // Validate arguments\n validateReadWriteArgs(buffer.length, offset, length, position);\n\n try {\n const readPosition = position ?? fileInfo.position;\n\n // Get file size and calculate read length\n const fileSize = fileInfo.syncHandle.getSize();\n const { isEOF, actualLength } = calculateReadLength(readPosition, length, fileSize);\n\n if (isEOF) {\n return transfer({ bytesRead: 0, buffer }, [buffer.buffer]); // End of file\n }\n\n // Create a subarray view for the read operation\n const targetBuffer = buffer.subarray(offset, offset + actualLength);\n\n // Perform efficient positioned read\n const bytesRead = fileInfo.syncHandle.read(targetBuffer, { at: readPosition });\n\n // Update position if position was not explicitly specified (null means use current position)\n if (position == null) {\n fileInfo.position = readPosition + bytesRead;\n }\n\n return transfer({ bytesRead, buffer }, [buffer.buffer]);\n }\n catch (error) {\n throw createFDError('read', fd, fileInfo.path, error);\n }\n }\n\n async write(\n fd: number,\n buffer: Uint8Array,\n offset: number = 0,\n length?: number,\n position?: number | null | undefined,\n emitEvent: boolean = true\n ): Promise<number> {\n const fileInfo = this._getFileDescriptor(fd);\n\n // Calculate actual length to write\n const actualLength = length ?? (buffer.length - offset);\n\n // Validate arguments using helper\n validateReadWriteArgs(buffer.length, offset, actualLength, position);\n\n try {\n // Determine write position: use specified position, or current position if null/undefined\n const writePosition = position ?? fileInfo.position;\n\n // Create a subarray view for the write operation\n const sourceBuffer = buffer.subarray(offset, offset + actualLength);\n\n // Perform efficient positioned write\n const bytesWritten = fileInfo.syncHandle.write(sourceBuffer, { at: writePosition });\n\n // Update position if position was null or undefined (i.e., use current position)\n // Also update position when writing at current position (position === fileInfo.position)\n if (position == null || position === fileInfo.position) {\n fileInfo.position = writePosition + bytesWritten;\n }\n\n if (emitEvent) {\n await this.notifyChange({ path: fileInfo.path, type: WatchEventType.Changed, isDirectory: false });\n }\n\n return bytesWritten;\n }\n catch (error) {\n throw createFDError('write', fd, fileInfo.path, error);\n }\n }\n\n async fstat(fd: number): Promise<FileStat> {\n const fileInfo = this._getFileDescriptor(fd);\n\n // Simply reuse existing stat() method with the file path\n return this.stat(fileInfo.path);\n }\n\n async ftruncate(fd: number, size: number = 0): Promise<void> {\n const fileInfo = this._getFileDescriptor(fd);\n\n // Validate size parameter\n if (size < 0 || !Number.isInteger(size)) {\n throw new ValidationError('argument', 'Invalid size');\n }\n\n try {\n fileInfo.syncHandle.truncate(size);\n fileInfo.syncHandle.flush();\n\n // Adjust position if it's beyond the new file size\n if (fileInfo.position > size) {\n fileInfo.position = size;\n }\n\n await this.notifyChange({ path: fileInfo.path, type: WatchEventType.Changed, isDirectory: false });\n }\n catch (error) {\n throw createFDError('truncate', fd, fileInfo.path, error);\n }\n }\n\n async fsync(fd: number): Promise<void> {\n const fileInfo = this._getFileDescriptor(fd);\n\n try {\n fileInfo.syncHandle.flush();\n }\n catch (error) {\n throw createFDError('sync', fd, fileInfo.path, error);\n }\n }\n\n override dispose(): void {\n // Close each shared sync handle once (not per FD)\n for (const [path, shared] of this.openHandles) {\n safeCloseSyncHandle(-1, shared.syncHandle, path);\n }\n\n this.openHandles.clear();\n this.openFiles.clear();\n this.nextFd = 1;\n\n super.dispose();\n }\n}\n"],"names":["OPFSSync","BaseOPFS","options","fd","fileInfo","ValidationError","path","withLock","size","buffer","transfer","err","OPFSError","mapDomError","data","existed","WatchEventType","stream","onProgress","reader","totalBytes","done","chunk","offset","error","create","exclusive","truncate","normalizedPath","normalizePath","resolvePath","AlreadyExistsError","isTypeMismatchDirectory","shared","fileHandle","syncHandle","createSyncHandleSafe","safeCloseSyncHandle","length","position","validateReadWriteArgs","readPosition","fileSize","isEOF","actualLength","calculateReadLength","targetBuffer","bytesRead","createFDError","emitEvent","writePosition","sourceBuffer","bytesWritten"],"mappings":"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"}