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