@componentor/fs 1.2.4 → 1.2.5
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/dist/index.js +286 -227
- package/dist/index.js.map +1 -1
- package/dist/opfs-hybrid.js +286 -227
- package/dist/opfs-hybrid.js.map +1 -1
- package/dist/opfs-worker.js +286 -227
- package/dist/opfs-worker.js.map +1 -1
- package/package.json +1 -1
- package/src/handle-manager.ts +64 -0
- package/src/index.ts +51 -19
- package/src/packed-storage.ts +216 -243
- package/src/symlink-manager.ts +15 -6
package/package.json
CHANGED
package/src/handle-manager.ts
CHANGED
|
@@ -16,6 +16,70 @@ export interface GetHandleOptions {
|
|
|
16
16
|
const FILE_HANDLE_POOL_SIZE = 50
|
|
17
17
|
const DIR_CACHE_MAX_SIZE = 200
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Manages file-level locks to prevent concurrent sync access handle creation.
|
|
21
|
+
* OPFS only allows one sync access handle per file at a time.
|
|
22
|
+
*/
|
|
23
|
+
export class FileLockManager {
|
|
24
|
+
private locks: Map<string, Promise<void>> = new Map()
|
|
25
|
+
private lockResolvers: Map<string, () => void> = new Map()
|
|
26
|
+
private waitQueues: Map<string, Array<() => void>> = new Map()
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Acquire an exclusive lock on a file path.
|
|
30
|
+
* If the file is already locked, waits until it's released.
|
|
31
|
+
* Returns a release function that MUST be called when done.
|
|
32
|
+
*/
|
|
33
|
+
async acquire(path: string): Promise<() => void> {
|
|
34
|
+
const normalizedPath = normalize(path)
|
|
35
|
+
|
|
36
|
+
// If file is currently locked, wait for it
|
|
37
|
+
while (this.locks.has(normalizedPath)) {
|
|
38
|
+
await this.locks.get(normalizedPath)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Create new lock
|
|
42
|
+
let resolve: () => void
|
|
43
|
+
const lockPromise = new Promise<void>(r => {
|
|
44
|
+
resolve = r
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
this.locks.set(normalizedPath, lockPromise)
|
|
48
|
+
this.lockResolvers.set(normalizedPath, resolve!)
|
|
49
|
+
|
|
50
|
+
// Return release function
|
|
51
|
+
return () => {
|
|
52
|
+
const resolver = this.lockResolvers.get(normalizedPath)
|
|
53
|
+
this.locks.delete(normalizedPath)
|
|
54
|
+
this.lockResolvers.delete(normalizedPath)
|
|
55
|
+
if (resolver) resolver()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if a file is currently locked
|
|
61
|
+
*/
|
|
62
|
+
isLocked(path: string): boolean {
|
|
63
|
+
return this.locks.has(normalize(path))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Clear all locks (use with caution, mainly for cleanup)
|
|
68
|
+
*/
|
|
69
|
+
clearAll(): void {
|
|
70
|
+
// Resolve all pending locks
|
|
71
|
+
for (const resolver of this.lockResolvers.values()) {
|
|
72
|
+
resolver()
|
|
73
|
+
}
|
|
74
|
+
this.locks.clear()
|
|
75
|
+
this.lockResolvers.clear()
|
|
76
|
+
this.waitQueues.clear()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Global file lock manager instance
|
|
81
|
+
export const fileLockManager = new FileLockManager()
|
|
82
|
+
|
|
19
83
|
/**
|
|
20
84
|
* Manages OPFS handles with caching for improved performance
|
|
21
85
|
*/
|
package/src/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
import { constants, flagsToString } from './constants.js'
|
|
25
25
|
import { createENOENT, createEEXIST, createEACCES, createEISDIR, wrapError } from './errors.js'
|
|
26
26
|
import { normalize, dirname, basename, join, isRoot, segments } from './path-utils.js'
|
|
27
|
-
import { HandleManager } from './handle-manager.js'
|
|
27
|
+
import { HandleManager, fileLockManager } from './handle-manager.js'
|
|
28
28
|
import { SymlinkManager } from './symlink-manager.js'
|
|
29
29
|
import { PackedStorage } from './packed-storage.js'
|
|
30
30
|
import { createFileHandle } from './file-handle.js'
|
|
@@ -187,11 +187,19 @@ export default class OPFS {
|
|
|
187
187
|
let buffer: Uint8Array
|
|
188
188
|
|
|
189
189
|
if (this.useSync) {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
const releaseLock = await fileLockManager.acquire(resolvedPath)
|
|
191
|
+
try {
|
|
192
|
+
const access = await fileHandle.createSyncAccessHandle()
|
|
193
|
+
try {
|
|
194
|
+
const size = access.getSize()
|
|
195
|
+
buffer = new Uint8Array(size)
|
|
196
|
+
access.read(buffer)
|
|
197
|
+
} finally {
|
|
198
|
+
access.close()
|
|
199
|
+
}
|
|
200
|
+
} finally {
|
|
201
|
+
releaseLock()
|
|
202
|
+
}
|
|
195
203
|
} else {
|
|
196
204
|
const file = await fileHandle.getFile()
|
|
197
205
|
buffer = new Uint8Array(await file.arrayBuffer())
|
|
@@ -269,11 +277,19 @@ export default class OPFS {
|
|
|
269
277
|
|
|
270
278
|
let buffer: Uint8Array
|
|
271
279
|
if (this.useSync) {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
280
|
+
const releaseLock = await fileLockManager.acquire(resolvedPath)
|
|
281
|
+
try {
|
|
282
|
+
const access = await fileHandle.createSyncAccessHandle()
|
|
283
|
+
try {
|
|
284
|
+
const size = access.getSize()
|
|
285
|
+
buffer = new Uint8Array(size)
|
|
286
|
+
access.read(buffer)
|
|
287
|
+
} finally {
|
|
288
|
+
access.close()
|
|
289
|
+
}
|
|
290
|
+
} finally {
|
|
291
|
+
releaseLock()
|
|
292
|
+
}
|
|
277
293
|
} else {
|
|
278
294
|
const file = await fileHandle.getFile()
|
|
279
295
|
buffer = new Uint8Array(await file.arrayBuffer())
|
|
@@ -310,11 +326,19 @@ export default class OPFS {
|
|
|
310
326
|
const buffer = typeof data === 'string' ? new TextEncoder().encode(data) : data
|
|
311
327
|
|
|
312
328
|
if (this.useSync) {
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
329
|
+
const releaseLock = await fileLockManager.acquire(resolvedPath)
|
|
330
|
+
try {
|
|
331
|
+
const access = await fileHandle!.createSyncAccessHandle()
|
|
332
|
+
try {
|
|
333
|
+
// Set exact size (more efficient than truncate(0) + write)
|
|
334
|
+
access.truncate(buffer.length)
|
|
335
|
+
access.write(buffer, { at: 0 })
|
|
336
|
+
} finally {
|
|
337
|
+
access.close()
|
|
338
|
+
}
|
|
339
|
+
} finally {
|
|
340
|
+
releaseLock()
|
|
341
|
+
}
|
|
318
342
|
} else {
|
|
319
343
|
const writable = await fileHandle!.createWritable()
|
|
320
344
|
await writable.write(buffer)
|
|
@@ -1036,9 +1060,17 @@ export default class OPFS {
|
|
|
1036
1060
|
if (!fileHandle) throw createENOENT(path)
|
|
1037
1061
|
|
|
1038
1062
|
if (this.useSync) {
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1063
|
+
const releaseLock = await fileLockManager.acquire(resolvedPath)
|
|
1064
|
+
try {
|
|
1065
|
+
const access = await fileHandle.createSyncAccessHandle()
|
|
1066
|
+
try {
|
|
1067
|
+
access.truncate(len)
|
|
1068
|
+
} finally {
|
|
1069
|
+
access.close()
|
|
1070
|
+
}
|
|
1071
|
+
} finally {
|
|
1072
|
+
releaseLock()
|
|
1073
|
+
}
|
|
1042
1074
|
} else {
|
|
1043
1075
|
const file = await fileHandle.getFile()
|
|
1044
1076
|
const data = new Uint8Array(await file.arrayBuffer())
|