opfs-worker 2.1.0 → 2.1.2

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 (111) hide show
  1. package/README.md +51 -37
  2. package/dist/BaseOPFS-Bu7FZtCb.cjs +2 -0
  3. package/dist/BaseOPFS-Bu7FZtCb.cjs.map +1 -0
  4. package/dist/{BaseOPFS-Cylz6ijk.js → BaseOPFS-C3NE4BKr.js} +804 -484
  5. package/dist/BaseOPFS-C3NE4BKr.js.map +1 -0
  6. package/dist/OPFSAsync-C0RsxCX5.cjs +2 -0
  7. package/dist/OPFSAsync-C0RsxCX5.cjs.map +1 -0
  8. package/dist/{OPFSAsync-BKSAJJeh.js → OPFSAsync-aeLUdIqd.js} +19 -19
  9. package/dist/OPFSAsync-aeLUdIqd.js.map +1 -0
  10. package/dist/OPFSFacade-BJv2yuWU.cjs +2 -0
  11. package/dist/OPFSFacade-BJv2yuWU.cjs.map +1 -0
  12. package/dist/OPFSFacade-DiogtQoq.js +308 -0
  13. package/dist/OPFSFacade-DiogtQoq.js.map +1 -0
  14. package/dist/OPFSSync-3qlSBYmz.cjs +2 -0
  15. package/dist/OPFSSync-3qlSBYmz.cjs.map +1 -0
  16. package/dist/{OPFSSync-CsSQkpk5.js → OPFSSync-CvSOswhW.js} +57 -57
  17. package/dist/OPFSSync-CvSOswhW.js.map +1 -0
  18. package/dist/assets/dedicated.worker-Bqqr9UBA.js.map +1 -0
  19. package/dist/core/BaseOPFS.d.ts +60 -9
  20. package/dist/core/BaseOPFS.d.ts.map +1 -1
  21. package/dist/core/OPFSAsync.d.ts.map +1 -1
  22. package/dist/core/OPFSSync.d.ts.map +1 -1
  23. package/dist/createOPFSAsync-B611B63X.js +10 -0
  24. package/dist/{createOPFSAsync-Y4Mg6qte.js.map → createOPFSAsync-B611B63X.js.map} +1 -1
  25. package/dist/createOPFSAsync-BATjJg8k.cjs +2 -0
  26. package/dist/{createOPFSAsync-BwKDmHsy.cjs.map → createOPFSAsync-BATjJg8k.cjs.map} +1 -1
  27. package/dist/createOPFSDedicated-DgudPTB8.js +2676 -0
  28. package/dist/createOPFSDedicated-DgudPTB8.js.map +1 -0
  29. package/dist/createOPFSDedicated-EmIwluQo.cjs +2597 -0
  30. package/dist/createOPFSDedicated-EmIwluQo.cjs.map +1 -0
  31. package/dist/{createOPFSShared-CGdd__PH.js → createOPFSShared-BWYln8jJ.js} +2 -2
  32. package/dist/{createOPFSShared-CGdd__PH.js.map → createOPFSShared-BWYln8jJ.js.map} +1 -1
  33. package/dist/{createOPFSShared-CskROt7U.cjs → createOPFSShared-DPJpCd7z.cjs} +2 -2
  34. package/dist/{createOPFSShared-CskROt7U.cjs.map → createOPFSShared-DPJpCd7z.cjs.map} +1 -1
  35. package/dist/dedicated.worker.js +1104 -790
  36. package/dist/dedicated.worker.js.map +1 -1
  37. package/dist/facade/OPFSFacade.d.ts +21 -3
  38. package/dist/facade/OPFSFacade.d.ts.map +1 -1
  39. package/dist/facade/createOPFSDedicated.d.ts +4 -2
  40. package/dist/facade/createOPFSDedicated.d.ts.map +1 -1
  41. package/dist/index.async.cjs +1 -1
  42. package/dist/index.async.js +33 -32
  43. package/dist/index.cjs +1 -1
  44. package/dist/index.d.ts +1 -5
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +44 -43
  47. package/dist/index.pure.cjs +1 -1
  48. package/dist/index.pure.js +38 -37
  49. package/dist/index.sharedworker.cjs +1 -1
  50. package/dist/index.sharedworker.js +34 -33
  51. package/dist/index.sync.cjs +1 -1
  52. package/dist/index.sync.d.ts +2 -0
  53. package/dist/index.sync.d.ts.map +1 -1
  54. package/dist/index.sync.js +34 -32
  55. package/dist/shared.worker.js +1101 -791
  56. package/dist/shared.worker.js.map +1 -1
  57. package/dist/types.d.ts +38 -5
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/utils/helpers.d.ts +13 -3
  60. package/dist/utils/helpers.d.ts.map +1 -1
  61. package/dist/worker/createDedicatedWorker.d.ts +8 -3
  62. package/dist/worker/createDedicatedWorker.d.ts.map +1 -1
  63. package/docs/README.md +13 -25
  64. package/docs/api/README.md +221 -0
  65. package/docs/api/file-descriptors.md +137 -0
  66. package/docs/guides/async.md +4 -4
  67. package/docs/guides/dedicated.md +10 -8
  68. package/docs/guides/hashing.md +3 -3
  69. package/docs/guides/pure.md +4 -4
  70. package/docs/guides/sharedworker.md +2 -2
  71. package/docs/guides/streaming.md +146 -12
  72. package/docs/guides/watching.md +3 -3
  73. package/docs/migration.md +10 -11
  74. package/docs/types.md +93 -155
  75. package/package.json +20 -9
  76. package/src/core/BaseOPFS.ts +194 -29
  77. package/src/core/OPFSAsync.ts +4 -5
  78. package/src/core/OPFSSync.ts +6 -7
  79. package/src/facade/OPFSFacade.ts +51 -4
  80. package/src/facade/createOPFSDedicated.ts +4 -2
  81. package/src/index.sync.ts +2 -0
  82. package/src/index.ts +1 -5
  83. package/src/types.ts +42 -3
  84. package/src/utils/helpers.ts +32 -6
  85. package/src/worker/createDedicatedWorker.ts +85 -7
  86. package/src/worker/dedicated.worker.ts +12 -1
  87. package/dist/BaseOPFS-BPw9AIQ-.cjs +0 -4
  88. package/dist/BaseOPFS-BPw9AIQ-.cjs.map +0 -1
  89. package/dist/BaseOPFS-Cylz6ijk.js.map +0 -1
  90. package/dist/OPFSAsync-BKSAJJeh.js.map +0 -1
  91. package/dist/OPFSAsync-s2P7y3sw.cjs +0 -2
  92. package/dist/OPFSAsync-s2P7y3sw.cjs.map +0 -1
  93. package/dist/OPFSFacade-DePnNRgt.cjs +0 -2
  94. package/dist/OPFSFacade-DePnNRgt.cjs.map +0 -1
  95. package/dist/OPFSFacade-XohWlHuu.js +0 -281
  96. package/dist/OPFSFacade-XohWlHuu.js.map +0 -1
  97. package/dist/OPFSSync-C7KBepe_.cjs +0 -2
  98. package/dist/OPFSSync-C7KBepe_.cjs.map +0 -1
  99. package/dist/OPFSSync-CsSQkpk5.js.map +0 -1
  100. package/dist/assets/dedicated.worker-nY3tK4zi.js.map +0 -1
  101. package/dist/createOPFSAsync-BwKDmHsy.cjs +0 -2
  102. package/dist/createOPFSAsync-Y4Mg6qte.js +0 -10
  103. package/dist/createOPFSDedicated-CsnV6MLP.js +0 -2333
  104. package/dist/createOPFSDedicated-CsnV6MLP.js.map +0 -1
  105. package/dist/createOPFSDedicated-pKTRWLdz.cjs +0 -2283
  106. package/dist/createOPFSDedicated-pKTRWLdz.cjs.map +0 -1
  107. package/docs/api/backend.md +0 -36
  108. package/docs/api/create.md +0 -82
  109. package/docs/api/facade.md +0 -52
  110. package/docs/choosing-a-mode.md +0 -41
  111. package/docs/file-descriptors.md +0 -695
@@ -1,695 +0,0 @@
1
- # File Descriptors
2
-
3
- Low-level positional I/O, POSIX-style.
4
-
5
- > Needs the **dedicated / sync** backend (`createOPFSDedicated` / `createOPFS`, or `OPFSSync` from `/pure`). Async and SharedWorker throw `ENOTSUP`.
6
-
7
- ## Requirements
8
-
9
- If you talk to **`fs.backend`** from the main thread for FD `read`/`write`, transfer buffers with Comlink:
10
-
11
- ```typescript
12
- import { transfer } from 'comlink';
13
- ```
14
-
15
- The [facade](./api/facade.md) methods do this for you.
16
-
17
- ## Overview
18
-
19
- Useful when you need chunks, random access, or to avoid loading a whole file into memory.
20
-
21
- ## Table of Contents
22
-
23
- - [Requirements](#requirements)
24
- - [File Descriptor Basics](#file-descriptor-basics)
25
- - [Opening Files](#opening-files)
26
- - [Reading Data](#reading-data)
27
- - [Writing Data](#writing-data)
28
- - [File Positioning](#file-positioning)
29
- - [File Operations](#file-operations)
30
- - [Closing Files](#closing-files)
31
- - [Error Handling](#error-handling)
32
- - [Performance Considerations](#performance-considerations)
33
- - [Examples](#examples)
34
-
35
- ## File Descriptor Basics
36
-
37
- A file descriptor is a small integer that represents an open file. In OPFS Worker, file descriptors provide:
38
-
39
- - **Sequential access**: Read/write operations that advance the file position
40
- - **Random access**: Read/write at specific file positions
41
- - **Efficient I/O**: Direct access to file data without intermediate buffers
42
- - **Resource management**: Automatic cleanup when closed
43
-
44
- ### File Descriptor Lifecycle
45
-
46
- 1. **Open**: Create a file descriptor with `open()`
47
- 2. **Use**: Perform read/write operations
48
- 3. **Close**: Release the file descriptor with `close()`
49
-
50
- ## Opening Files
51
-
52
- ### `open(path: string, options?: FileOpenOptions): Promise<number>`
53
-
54
- Opens a file and returns a file descriptor number.
55
-
56
- ```typescript
57
- // Basic file opening
58
- const fd = await fs.open('/data/config.json');
59
-
60
- // Create new file if it doesn't exist
61
- const fd = await fs.open('/data/new.txt', { create: true });
62
-
63
- // Create file exclusively (fails if exists)
64
- const fd = await fs.open('/data/unique.txt', { create: true, exclusive: true });
65
-
66
- // Open and truncate file to zero length
67
- const fd = await fs.open('/data/log.txt', { create: true, truncate: true });
68
- ```
69
-
70
- **Parameters:**
71
-
72
- - `path`: The path to the file to open
73
- - `options.create` (optional): Whether to create the file if it doesn't exist (default: `false`)
74
- - `options.exclusive` (optional): If `true` and `create` is `true`, fails if file already exists (default: `false`)
75
- - `options.truncate` (optional): Whether to truncate the file to zero length (default: `false`)
76
-
77
- **Returns:** `Promise<number>` - File descriptor number
78
-
79
- **Throws:**
80
-
81
- - `AlreadyExistsError` if file exists and `exclusive: true`
82
- - `FileTypeError` if path is a directory
83
- - `FileSystemOperationError` for other failures
84
-
85
- **⚠️ Race Condition Warning**: The `exclusive` option provides **best-effort atomicity only**. **Race conditions ARE possible** if two workers try to create the same file simultaneously. OPFS does not provide true file locking, so don't rely on `exclusive` for critical synchronization. If you need real atomicity, implement your own locking mechanism.
86
-
87
- ## Reading Data
88
-
89
- ### `read(fd: number, buffer: Uint8Array, offset: number, length: number, position?: number | null): Promise<{bytesRead: number, buffer: Uint8Array}>`
90
-
91
- Reads data from a file descriptor into a buffer.
92
-
93
- ```typescript
94
- const fd = await fs.open('/data/file.txt');
95
-
96
- // Read 1024 bytes starting at current position
97
- const buffer = new Uint8Array(1024);
98
- const result = await fs.read(fd, buffer, 0, 1024, null);
99
- console.log(`Read ${result.bytesRead} bytes`);
100
-
101
- // Read 512 bytes at specific position
102
- const buffer2 = new Uint8Array(512);
103
- const result2 = await fs.read(fd, buffer2, 0, 512, 1000);
104
- console.log(`Read ${result2.bytesRead} bytes starting at position 1000`);
105
-
106
- await fs.close(fd);
107
- ```
108
-
109
- **Parameters:**
110
-
111
- - `fd`: File descriptor to read from
112
- - `buffer`: The buffer to read data into
113
- - `offset`: Offset in the buffer to start writing at
114
- - `length`: Number of bytes to read
115
- - `position`: Position in file to read from (`null` for current position, `undefined` for current position)
116
-
117
- **Returns:** `Promise<{bytesRead: number, buffer: Uint8Array}>` - Object containing bytes read and modified buffer
118
-
119
- **⚠️ Node.js Compatibility Note**: While similar to Node.js `fs.read()`, the returned `buffer` may be a **different object** due to `Comlink.transfer()`. Unlike Node.js where the same buffer object is modified in-place, here you must use the returned buffer for safety.
120
-
121
- **⚠️ CRITICAL WARNING**: The `read()` method uses `Comlink.transfer()` for efficient buffer handling across Web Worker boundaries. This requires **bidirectional transfer**:
122
-
123
- 1. **From main window to worker**: You must transfer buffer ownership using `transfer(buffer, [buffer.buffer])`
124
- 2. **From worker to main window**: The worker transfers the modified buffer back to you
125
-
126
- **🚨 NEVER USE THE ORIGINAL BUFFER AFTER TRANSFER** - it becomes detached and unusable. **ALWAYS use the buffer returned from the method**. Using the original buffer will result in silent data corruption or errors.
127
-
128
- This ensures zero-copy performance but requires careful buffer management. See the usage examples below for proper implementation.
129
-
130
- **Behavior:**
131
-
132
- - Returns `0` when reaching end of file
133
- - Automatically advances file position if `position` is `null` or `undefined`
134
- - Reads up to `length` bytes, but may read fewer if end of file is reached
135
- - Throws error if reading beyond file bounds
136
-
137
- ### Buffer Transfer Usage
138
-
139
- **Important**: When using `read()` from the main window, you must transfer buffer ownership to the worker. The worker then transfers the modified buffer back to you.
140
-
141
- #### From Main Window (Browser)
142
-
143
- ```typescript
144
- import { transfer } from 'comlink';
145
-
146
- // Create buffer in main window
147
- const buffer = new Uint8Array(64);
148
-
149
- // Transfer buffer ownership to worker when calling read()
150
- const { bytesRead, buffer: modifiedBuffer } = await fs.read(
151
- fd,
152
- transfer(buffer, [buffer.buffer]), // Transfer ownership to worker
153
- 0,
154
- buffer.length,
155
- null
156
- );
157
-
158
- // Now modifiedBuffer is "alive" again in main window
159
- console.log('bytesRead =', bytesRead);
160
-
161
- // Work directly with the returned buffer
162
- const text = new TextDecoder().decode(modifiedBuffer.subarray(0, bytesRead));
163
- console.log('read:', text);
164
-
165
- // Note: The original 'buffer' is now detached and cannot be used
166
- // Always use 'modifiedBuffer' from the result
167
- ```
168
-
169
- #### From Worker (Direct Usage)
170
-
171
- ```typescript
172
- // When using OPFSSync directly in a worker
173
- const buffer = new Uint8Array(64);
174
- const { bytesRead, buffer: modifiedBuffer } = await fs.read(fd, buffer, 0, buffer.length, null);
175
-
176
- // modifiedBuffer contains the read data
177
- const data = modifiedBuffer.subarray(0, bytesRead);
178
- ```
179
-
180
- #### Common Patterns
181
-
182
- **Reading in chunks:**
183
-
184
- ```typescript
185
- const fd = await fs.open('/data/large-file.txt');
186
- const chunkSize = 1024;
187
- let buffer = new Uint8Array(chunkSize); // Use 'let' for reassignment
188
-
189
- try {
190
- while (true) {
191
- const result = await fs.read(
192
- fd,
193
- transfer(buffer, [buffer.buffer]),
194
- 0,
195
- chunkSize,
196
- null
197
- );
198
-
199
- if (result.bytesRead === 0) break; // EOF
200
-
201
- // Process the chunk
202
- const chunk = result.buffer.subarray(0, result.bytesRead);
203
- processChunk(chunk);
204
-
205
- // Create new buffer for next iteration (previous one was transferred)
206
- buffer = new Uint8Array(chunkSize);
207
- }
208
- } finally {
209
- await fs.close(fd);
210
- }
211
- ```
212
-
213
- **Reading at specific positions:**
214
-
215
- ```typescript
216
- const fd = await fs.open('/data/file.txt');
217
- const buffer = new Uint8Array(100);
218
-
219
- // Read 100 bytes starting at position 500
220
- const result = await fs.read(
221
- fd,
222
- transfer(buffer, [buffer.buffer]),
223
- 0,
224
- 100,
225
- 500
226
- );
227
-
228
- console.log(`Read ${result.bytesRead} bytes from position 500`);
229
- ```
230
-
231
- ## Writing Data
232
-
233
- ### `write(fd: number, buffer: Uint8Array, offset?: number, length?: number, position?: number | null): Promise<number>`
234
-
235
- Writes data from a buffer to a file descriptor.
236
-
237
- ```typescript
238
- const fd = await fs.open('/data/output.txt', { create: true });
239
-
240
- // Write entire buffer at current position
241
- const data = new TextEncoder().encode('Hello, World!');
242
- const bytesWritten = await fs.write(fd, data);
243
- console.log(`Wrote ${bytesWritten} bytes`);
244
-
245
- // Write portion of buffer at specific position
246
- const data2 = new TextEncoder().encode('Additional text');
247
- const bytesWritten2 = await fs.write(fd, data2, 0, 10, 100);
248
- console.log(`Wrote ${bytesWritten2} bytes at position 100`);
249
-
250
- await fs.close(fd);
251
- ```
252
-
253
- **Parameters:**
254
-
255
- - `fd`: File descriptor to write to
256
- - `buffer`: Buffer containing data to write
257
- - `offset` (optional): Offset in buffer to start reading from (default: `0`)
258
- - `length` (optional): Number of bytes to write (default: entire buffer from offset)
259
- - `position` (optional): Position in file to write to (`null`/`undefined` for current position)
260
-
261
- **Returns:** `Promise<number>` - Number of bytes actually written
262
-
263
- **Behavior:**
264
-
265
- - Automatically advances file position if `position` is `null` or `undefined`
266
- - Extends file size if writing beyond current end
267
- - Triggers file change notifications for watching systems
268
-
269
- ## File Positioning
270
-
271
- File descriptors maintain a current position that advances with read/write operations. You can control positioning through the `position` parameter:
272
-
273
- ```typescript
274
- const fd = await fs.open('/data/file.txt');
275
-
276
- // Read at current position (starts at 0)
277
- const buffer1 = new Uint8Array(10);
278
- const result1 = await fs.read(fd, buffer1, 0, 10, null); // Position advances to 10
279
-
280
- // Read at current position (continues from 10)
281
- const buffer2 = new Uint8Array(10);
282
- const result2 = await fs.read(fd, buffer2, 0, 10, null); // Position advances to 20
283
-
284
- // Read at specific position (doesn't change current position)
285
- const buffer3 = new Uint8Array(10);
286
- const result3 = await fs.read(fd, buffer3, 0, 10, 0); // Reads from beginning, position stays at 20
287
-
288
- await fs.close(fd);
289
- ```
290
-
291
- **Position Behavior:**
292
-
293
- - **`null`**: Use current position, then advance it
294
- - **`undefined`**: Use current position, then advance it
295
- - **`number`**: Use specified position, don't change current position
296
-
297
- ## File Operations
298
-
299
- ### `fstat(fd: number): Promise<FileStat>`
300
-
301
- Get file status information by file descriptor.
302
-
303
- ```typescript
304
- const fd = await fs.open('/data/file.txt');
305
- const stats = await fs.fstat(fd);
306
-
307
- console.log(`File size: ${stats.size} bytes`);
308
- console.log(`Last modified: ${stats.mtime}`);
309
- console.log(`Is file: ${stats.isFile}`);
310
-
311
- // If hashing is enabled, hash will be included
312
- if (stats.hash) {
313
- console.log(`Hash: ${stats.hash}`);
314
- }
315
-
316
- await fs.close(fd);
317
- ```
318
-
319
- **Returns:** `Promise<FileStat>` - File statistics object
320
-
321
- ### `ftruncate(fd: number, size: number = 0): Promise<void>`
322
-
323
- Truncate file to specified size.
324
-
325
- ```typescript
326
- const fd = await fs.open('/data/file.txt', { create: true });
327
-
328
- // Write some data
329
- const data = new TextEncoder().encode('Hello, World!');
330
- await fs.write(fd, data);
331
-
332
- // Truncate to 5 bytes
333
- await fs.ftruncate(fd, 5);
334
-
335
- // File now contains only "Hello"
336
- const buffer = new Uint8Array(10);
337
- const result = await fs.read(fd, buffer, 0, 10, 0);
338
- console.log(new TextDecoder().decode(result.buffer.subarray(0, result.bytesRead))); // "Hello"
339
-
340
- await fs.close(fd);
341
- ```
342
-
343
- **Parameters:**
344
-
345
- - `fd`: File descriptor
346
- - `size`: New file size in bytes (default: `0`)
347
-
348
- **Behavior:**
349
-
350
- - Adjusts current position if it's beyond the new file size
351
- - Triggers file change notifications
352
- - Automatically flushes changes to storage
353
-
354
- ### `fsync(fd: number): Promise<void>`
355
-
356
- Synchronize file data to storage (equivalent to POSIX `fsync`).
357
-
358
- ```typescript
359
- const fd = await fs.open('/data/critical.txt', { create: true });
360
-
361
- // Write critical data
362
- const data = new TextEncoder().encode('Important information');
363
- await fs.write(fd, data);
364
-
365
- // Ensure data is written to storage
366
- await fs.fsync(fd);
367
-
368
- await fs.close(fd);
369
- ```
370
-
371
- **⚠️ Power-Loss Safety Disclaimer**: Unlike traditional filesystems, OPFS `fsync()` is **best-effort only**. **There are NO guarantees of power-loss safety** - the browser decides when/if to actually write data to disk. This is fundamentally different from POSIX `fsync()` which provides durability guarantees.
372
-
373
- **Use Cases:**
374
-
375
- - Ensuring critical data is persisted before closing
376
- - Database transaction commits
377
- - Log file integrity
378
- - Critical file operations
379
-
380
- ## Closing Files
381
-
382
- ### `close(fd: number): Promise<void>`
383
-
384
- Closes a file descriptor and releases associated resources.
385
-
386
- ```typescript
387
- const fd = await fs.open('/data/file.txt');
388
-
389
- // Use the file descriptor
390
- const buffer = new Uint8Array(1024);
391
- await fs.read(fd, buffer, 0, 1024, null);
392
-
393
- // Always close when done
394
- await fs.close(fd);
395
- ```
396
-
397
- **Important Notes:**
398
-
399
- - **Always close file descriptors** when you're done with them
400
- - Unclosed file descriptors consume system resources
401
- - The file system automatically flushes pending writes before closing
402
- - File descriptors are automatically closed when the worker is disposed
403
-
404
- ## Error Handling
405
-
406
- File descriptor operations can fail for various reasons. Always handle errors appropriately:
407
-
408
- ```typescript
409
- try {
410
- const fd = await fs.open('/data/file.txt');
411
-
412
- try {
413
- const buffer = new Uint8Array(1024);
414
- const result = await fs.read(fd, buffer, 0, 1024, null);
415
- console.log(`Read ${result.bytesRead} bytes`);
416
- } finally {
417
- // Always close, even if read fails
418
- await fs.close(fd);
419
- }
420
- } catch (error) {
421
- if (error instanceof OPFSError) {
422
- switch (error.code) {
423
- case 'EEXIST':
424
- console.error('File already exists');
425
- break;
426
- case 'EISDIR':
427
- console.error('Path is a directory');
428
- break;
429
- default:
430
- console.error('File operation failed:', error.message);
431
- }
432
- } else {
433
- console.error('Unexpected error:', error);
434
- }
435
- }
436
- ```
437
-
438
- **Common Error Types:**
439
-
440
- - `AlreadyExistsError`: File already exists (with `exclusive: true`)
441
- - `ExistenceError`: File not found
442
- - `FileTypeError`: Path is directory when file expected (or vice versa)
443
- - `ValidationError`: Invalid arguments (offset, length, etc.)
444
- - `FileSystemOperationError`: General file system operation failures
445
- - `IOError`: I/O operation failures
446
- - `PermissionError`: Permission denied
447
-
448
- ## Performance Considerations
449
-
450
- ### 🚨 **CRITICAL MEMORY WARNING**
451
-
452
- **⚠️ FOR LARGE FILES - ALWAYS READ IN CHUNKS, NEVER READ THE ENTIRE FILE AT ONCE!**
453
-
454
- **Reading large files in one operation can crash the browser tab due to memory exhaustion.** Browsers have limited memory per tab, and creating massive buffers (hundreds of MB or GB) will cause:
455
-
456
- - **Tab crashes**
457
- - **Browser freezes**
458
- - **Out of memory errors**
459
- - **Poor user experience**
460
-
461
- **✅ GOOD**: `const buffer = new Uint8Array(8192); // 8KB chunks`
462
- **❌ BAD**: `const buffer = new Uint8Array(fileSize); // Entire file!`
463
-
464
- ### Buffer Management
465
-
466
- Reuse buffers when possible to reduce memory allocation:
467
-
468
- ```typescript
469
- const fd = await fs.open('/data/large-file.txt');
470
- const buffer = new Uint8Array(8192); // 8KB buffer
471
-
472
- let totalBytes = 0;
473
- let bytesRead;
474
-
475
- // Read file in chunks using the same buffer
476
- while (true) {
477
- const result = await fs.read(fd, buffer, 0, buffer.length, null);
478
- if (result.bytesRead === 0) break;
479
-
480
- totalBytes += result.bytesRead;
481
- // Process result.buffer.subarray(0, result.bytesRead)
482
- }
483
-
484
- await fs.close(fd);
485
- ```
486
-
487
- ### Batch Operations
488
-
489
- Group related operations together:
490
-
491
- ```typescript
492
- const fd = await fs.open('/data/log.txt', { create: true });
493
-
494
- // Batch multiple writes
495
- const entries = [
496
- 'User login',
497
- 'Data processed',
498
- 'Operation completed'
499
- ];
500
-
501
- for (const entry of entries) {
502
- const data = new TextEncoder().encode(entry + '\n');
503
- await fs.write(fd, data);
504
- }
505
-
506
- // Single sync at the end
507
- await fs.fsync(fd);
508
- await fs.close(fd);
509
- ```
510
-
511
- ### File Descriptor Limits
512
-
513
- Monitor the number of open file descriptors:
514
-
515
- ```typescript
516
- // Check how many files are open
517
- console.log(`Open files: ${fs.openFiles.size}`);
518
-
519
- // Close unused file descriptors promptly
520
- const unusedFds = [];
521
- // ... track unused file descriptors ...
522
- for (const fd of unusedFds) {
523
- await fs.close(fd);
524
- }
525
- ```
526
-
527
- ## Examples
528
-
529
- ### Reading Large Files in Chunks
530
-
531
- ```typescript
532
- async function readLargeFile(filePath: string, chunkSize: number = 8192) {
533
- const fd = await fs.open(filePath);
534
- const chunks: Uint8Array[] = [];
535
-
536
- try {
537
- while (true) {
538
- // Create new buffer for each iteration (previous one was transferred)
539
- const buffer = new Uint8Array(chunkSize);
540
-
541
- const result = await fs.read(fd, buffer, 0, chunkSize, null);
542
- if (result.bytesRead === 0) break;
543
-
544
- // Copy the chunk data from the returned buffer
545
- const chunk = new Uint8Array(result.bytesRead);
546
- chunk.set(result.buffer.subarray(0, result.bytesRead));
547
- chunks.push(chunk);
548
- }
549
-
550
- return chunks;
551
- } finally {
552
- await fs.close(fd);
553
- }
554
- }
555
-
556
- // Usage
557
- const chunks = await readLargeFile('/data/large-file.dat', 16384);
558
- console.log(`Read ${chunks.length} chunks`);
559
- ```
560
-
561
- ### Binary File Processing
562
-
563
- ```typescript
564
- async function processBinaryFile(filePath: string) {
565
- const fd = await fs.open(filePath);
566
-
567
- try {
568
- // Read header (first 16 bytes)
569
- const headerBuffer = new Uint8Array(16);
570
- const headerResult = await fs.read(fd, headerBuffer, 0, 16, 0);
571
-
572
- // Process header
573
- const header = new DataView(headerResult.buffer.buffer);
574
- const fileType = header.getUint32(0, true);
575
- const dataSize = header.getUint32(4, true);
576
-
577
- // Read data section
578
- const dataBuffer = new Uint8Array(dataSize);
579
- const dataResult = await fs.read(fd, dataBuffer, 0, dataSize, 16);
580
-
581
- return { fileType, dataSize, data: dataBuffer };
582
- } finally {
583
- await fs.close(fd);
584
- }
585
- }
586
- ```
587
-
588
- ### Log File Management
589
-
590
- ```typescript
591
- class LogManager {
592
- private fd: number | null = null;
593
- private logPath: string;
594
-
595
- constructor(logPath: string) {
596
- this.logPath = logPath;
597
- }
598
-
599
- async open() {
600
- this.fd = await fs.open(this.logPath, { create: true });
601
- }
602
-
603
- async writeLog(level: string, message: string) {
604
- if (!this.fd) throw new Error('Log file not open');
605
-
606
- const timestamp = new Date().toISOString();
607
- const logEntry = `[${timestamp}] [${level}] ${message}\n`;
608
- const data = new TextEncoder().encode(logEntry);
609
-
610
- await fs.write(this.fd, data);
611
- }
612
-
613
- async flush() {
614
- if (this.fd) {
615
- await fs.fsync(this.fd);
616
- }
617
- }
618
-
619
- async close() {
620
- if (this.fd) {
621
- await fs.close(this.fd);
622
- this.fd = null;
623
- }
624
- }
625
- }
626
-
627
- // Usage
628
- const logger = new LogManager('/logs/app.log');
629
- await logger.open();
630
- await logger.writeLog('INFO', 'Application started');
631
- await logger.writeLog('ERROR', 'Something went wrong');
632
- await logger.flush();
633
- await logger.close();
634
- ```
635
-
636
- ### File Copy with Progress
637
-
638
- ```typescript
639
- async function copyFileWithProgress(sourcePath: string, destPath: string, onProgress?: (bytes: number) => void) {
640
- const sourceFd = await fs.open(sourcePath);
641
- const destFd = await fs.open(destPath, { create: true, truncate: true });
642
-
643
- try {
644
- const buffer = new Uint8Array(8192);
645
- let totalBytes = 0;
646
-
647
- while (true) {
648
- const result = await fs.read(sourceFd, buffer, 0, buffer.length, null);
649
- if (result.bytesRead === 0) break;
650
-
651
- await fs.write(destFd, result.buffer, 0, result.bytesRead);
652
- totalBytes += result.bytesRead;
653
-
654
- if (onProgress) {
655
- onProgress(totalBytes);
656
- }
657
- }
658
-
659
- await fs.fsync(destFd);
660
- } finally {
661
- await fs.close(sourceFd);
662
- await fs.close(destFd);
663
- }
664
-
665
- return totalBytes;
666
- }
667
-
668
- // Usage
669
- const totalBytes = await copyFileWithProgress(
670
- '/source/large-file.dat',
671
- '/dest/large-file.dat',
672
- (bytes) => console.log(`Copied ${bytes} bytes`)
673
- );
674
- console.log(`Copy complete: ${totalBytes} bytes`);
675
- ```
676
-
677
- ## Best Practices
678
-
679
- 1. **🚨 NEVER read entire large files at once** - use chunk-based reading to prevent browser crashes
680
- 2. **Always close file descriptors** in a `finally` block or use try-with-resources pattern
681
- 3. **Reuse buffers** for multiple read/write operations to reduce memory allocation
682
- 4. **Use appropriate buffer sizes** - too small causes many operations, too large wastes memory (8KB-64KB recommended)
683
- 5. **Flush critical data** with `fsync()` before closing important files
684
- 6. **Handle errors gracefully** and provide meaningful error messages
685
- 7. **Monitor resource usage** to avoid hitting file descriptor limits
686
- 8. **Use positioned I/O** when you need random access without changing current position
687
- 9. **Batch operations** when possible to reduce overhead
688
-
689
- ## Limitations
690
-
691
- - **Browser-specific**: File descriptor behavior may vary between browsers
692
- - **OPFS constraints**: Subject to browser storage limits and OPFS implementation details
693
- - **No file locking**: Concurrent access to the same file from multiple workers may cause issues
694
- - **Position limits**: File positions are limited by JavaScript number precision (2^53 - 1)
695
- - **No direct memory mapping**: Files must be read into buffers rather than memory-mapped