@componentor/fs 1.2.5 → 1.2.7
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 +56 -88
- package/dist/index.js.map +1 -1
- package/dist/opfs-hybrid.js +56 -88
- package/dist/opfs-hybrid.js.map +1 -1
- package/dist/opfs-worker.js +56 -88
- package/dist/opfs-worker.js.map +1 -1
- package/package.json +1 -1
- package/src/handle-manager.ts +37 -49
- package/src/index.ts +19 -39
- package/src/packed-storage.ts +11 -11
- package/src/symlink-manager.ts +3 -3
package/package.json
CHANGED
package/src/handle-manager.ts
CHANGED
|
@@ -17,68 +17,56 @@ const FILE_HANDLE_POOL_SIZE = 50
|
|
|
17
17
|
const DIR_CACHE_MAX_SIZE = 200
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* Simple in-memory lock for preventing concurrent sync access handle creation
|
|
21
|
+
* within the same JavaScript context. This is needed because sync access handles
|
|
22
|
+
* are exclusive per file - only one can exist at a time.
|
|
23
|
+
*
|
|
24
|
+
* Optimized for the uncontended case (no Promise creation when lock is free).
|
|
22
25
|
*/
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
private
|
|
26
|
-
private waitQueues: Map<string, Array<() => void>> = new Map()
|
|
26
|
+
class FileLock {
|
|
27
|
+
private active = new Set<string>()
|
|
28
|
+
private queues = new Map<string, Array<() => void>>()
|
|
27
29
|
|
|
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
30
|
async acquire(path: string): Promise<() => void> {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
await this.locks.get(normalizedPath)
|
|
31
|
+
if (!this.active.has(path)) {
|
|
32
|
+
// Fast path: no contention, just mark as active
|
|
33
|
+
this.active.add(path)
|
|
34
|
+
return this.createRelease(path)
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
// Slow path: wait in queue
|
|
38
|
+
await new Promise<void>(resolve => {
|
|
39
|
+
let queue = this.queues.get(path)
|
|
40
|
+
if (!queue) {
|
|
41
|
+
queue = []
|
|
42
|
+
this.queues.set(path, queue)
|
|
43
|
+
}
|
|
44
|
+
queue.push(resolve)
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
-
this.
|
|
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))
|
|
47
|
+
return this.createRelease(path)
|
|
64
48
|
}
|
|
65
49
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
50
|
+
private createRelease(path: string): () => void {
|
|
51
|
+
return () => {
|
|
52
|
+
const queue = this.queues.get(path)
|
|
53
|
+
if (queue && queue.length > 0) {
|
|
54
|
+
// Pass ownership to next waiter
|
|
55
|
+
const next = queue.shift()!
|
|
56
|
+
if (queue.length === 0) {
|
|
57
|
+
this.queues.delete(path)
|
|
58
|
+
}
|
|
59
|
+
next()
|
|
60
|
+
} else {
|
|
61
|
+
// No waiters, release the lock
|
|
62
|
+
this.active.delete(path)
|
|
63
|
+
}
|
|
73
64
|
}
|
|
74
|
-
this.locks.clear()
|
|
75
|
-
this.lockResolvers.clear()
|
|
76
|
-
this.waitQueues.clear()
|
|
77
65
|
}
|
|
78
66
|
}
|
|
79
67
|
|
|
80
|
-
|
|
81
|
-
export const
|
|
68
|
+
/** Global file lock instance for sync access handle serialization */
|
|
69
|
+
export const fileLock = new FileLock()
|
|
82
70
|
|
|
83
71
|
/**
|
|
84
72
|
* Manages OPFS handles with caching for improved performance
|
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
|
|
27
|
+
import { HandleManager } 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,18 +187,13 @@ export default class OPFS {
|
|
|
187
187
|
let buffer: Uint8Array
|
|
188
188
|
|
|
189
189
|
if (this.useSync) {
|
|
190
|
-
const
|
|
190
|
+
const access = await fileHandle.createSyncAccessHandle()
|
|
191
191
|
try {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
buffer = new Uint8Array(size)
|
|
196
|
-
access.read(buffer)
|
|
197
|
-
} finally {
|
|
198
|
-
access.close()
|
|
199
|
-
}
|
|
192
|
+
const size = access.getSize()
|
|
193
|
+
buffer = new Uint8Array(size)
|
|
194
|
+
access.read(buffer)
|
|
200
195
|
} finally {
|
|
201
|
-
|
|
196
|
+
access.close()
|
|
202
197
|
}
|
|
203
198
|
} else {
|
|
204
199
|
const file = await fileHandle.getFile()
|
|
@@ -277,18 +272,13 @@ export default class OPFS {
|
|
|
277
272
|
|
|
278
273
|
let buffer: Uint8Array
|
|
279
274
|
if (this.useSync) {
|
|
280
|
-
const
|
|
275
|
+
const access = await fileHandle.createSyncAccessHandle()
|
|
281
276
|
try {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
buffer = new Uint8Array(size)
|
|
286
|
-
access.read(buffer)
|
|
287
|
-
} finally {
|
|
288
|
-
access.close()
|
|
289
|
-
}
|
|
277
|
+
const size = access.getSize()
|
|
278
|
+
buffer = new Uint8Array(size)
|
|
279
|
+
access.read(buffer)
|
|
290
280
|
} finally {
|
|
291
|
-
|
|
281
|
+
access.close()
|
|
292
282
|
}
|
|
293
283
|
} else {
|
|
294
284
|
const file = await fileHandle.getFile()
|
|
@@ -326,18 +316,13 @@ export default class OPFS {
|
|
|
326
316
|
const buffer = typeof data === 'string' ? new TextEncoder().encode(data) : data
|
|
327
317
|
|
|
328
318
|
if (this.useSync) {
|
|
329
|
-
const
|
|
319
|
+
const access = await fileHandle!.createSyncAccessHandle()
|
|
330
320
|
try {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
access.truncate(buffer.length)
|
|
335
|
-
access.write(buffer, { at: 0 })
|
|
336
|
-
} finally {
|
|
337
|
-
access.close()
|
|
338
|
-
}
|
|
321
|
+
// Set exact size (more efficient than truncate(0) + write)
|
|
322
|
+
access.truncate(buffer.length)
|
|
323
|
+
access.write(buffer, { at: 0 })
|
|
339
324
|
} finally {
|
|
340
|
-
|
|
325
|
+
access.close()
|
|
341
326
|
}
|
|
342
327
|
} else {
|
|
343
328
|
const writable = await fileHandle!.createWritable()
|
|
@@ -1060,16 +1045,11 @@ export default class OPFS {
|
|
|
1060
1045
|
if (!fileHandle) throw createENOENT(path)
|
|
1061
1046
|
|
|
1062
1047
|
if (this.useSync) {
|
|
1063
|
-
const
|
|
1048
|
+
const access = await fileHandle.createSyncAccessHandle()
|
|
1064
1049
|
try {
|
|
1065
|
-
|
|
1066
|
-
try {
|
|
1067
|
-
access.truncate(len)
|
|
1068
|
-
} finally {
|
|
1069
|
-
access.close()
|
|
1070
|
-
}
|
|
1050
|
+
access.truncate(len)
|
|
1071
1051
|
} finally {
|
|
1072
|
-
|
|
1052
|
+
access.close()
|
|
1073
1053
|
}
|
|
1074
1054
|
} else {
|
|
1075
1055
|
const file = await fileHandle.getFile()
|
package/src/packed-storage.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { HandleManager } from './handle-manager.js'
|
|
18
|
-
import {
|
|
18
|
+
import { fileLock } from './handle-manager.js'
|
|
19
19
|
import { createECORRUPTED } from './errors.js'
|
|
20
20
|
|
|
21
21
|
// ============ Compression ============
|
|
@@ -174,7 +174,7 @@ export class PackedStorage {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
if (this.useSync) {
|
|
177
|
-
const
|
|
177
|
+
const release = await fileLock.acquire(PACK_FILE)
|
|
178
178
|
try {
|
|
179
179
|
const access = await fileHandle.createSyncAccessHandle()
|
|
180
180
|
try {
|
|
@@ -210,7 +210,7 @@ export class PackedStorage {
|
|
|
210
210
|
access.close()
|
|
211
211
|
}
|
|
212
212
|
} finally {
|
|
213
|
-
|
|
213
|
+
release()
|
|
214
214
|
}
|
|
215
215
|
} else {
|
|
216
216
|
const file = await fileHandle.getFile()
|
|
@@ -274,7 +274,7 @@ export class PackedStorage {
|
|
|
274
274
|
let buffer: Uint8Array
|
|
275
275
|
|
|
276
276
|
if (this.useSync) {
|
|
277
|
-
const
|
|
277
|
+
const release = await fileLock.acquire(PACK_FILE)
|
|
278
278
|
try {
|
|
279
279
|
const access = await fileHandle.createSyncAccessHandle()
|
|
280
280
|
try {
|
|
@@ -284,7 +284,7 @@ export class PackedStorage {
|
|
|
284
284
|
access.close()
|
|
285
285
|
}
|
|
286
286
|
} finally {
|
|
287
|
-
|
|
287
|
+
release()
|
|
288
288
|
}
|
|
289
289
|
} else {
|
|
290
290
|
const file = await fileHandle.getFile()
|
|
@@ -336,7 +336,7 @@ export class PackedStorage {
|
|
|
336
336
|
const decompressPromises: Array<{ path: string; promise: Promise<Uint8Array> }> = []
|
|
337
337
|
|
|
338
338
|
if (this.useSync) {
|
|
339
|
-
const
|
|
339
|
+
const release = await fileLock.acquire(PACK_FILE)
|
|
340
340
|
try {
|
|
341
341
|
const access = await fileHandle.createSyncAccessHandle()
|
|
342
342
|
try {
|
|
@@ -355,7 +355,7 @@ export class PackedStorage {
|
|
|
355
355
|
access.close()
|
|
356
356
|
}
|
|
357
357
|
} finally {
|
|
358
|
-
|
|
358
|
+
release()
|
|
359
359
|
}
|
|
360
360
|
} else {
|
|
361
361
|
const file = await fileHandle.getFile()
|
|
@@ -475,7 +475,7 @@ export class PackedStorage {
|
|
|
475
475
|
if (!fileHandle) return
|
|
476
476
|
|
|
477
477
|
if (this.useSync) {
|
|
478
|
-
const
|
|
478
|
+
const release = await fileLock.acquire(PACK_FILE)
|
|
479
479
|
try {
|
|
480
480
|
const access = await fileHandle.createSyncAccessHandle()
|
|
481
481
|
try {
|
|
@@ -485,7 +485,7 @@ export class PackedStorage {
|
|
|
485
485
|
access.close()
|
|
486
486
|
}
|
|
487
487
|
} finally {
|
|
488
|
-
|
|
488
|
+
release()
|
|
489
489
|
}
|
|
490
490
|
} else {
|
|
491
491
|
const writable = await fileHandle.createWritable()
|
|
@@ -512,7 +512,7 @@ export class PackedStorage {
|
|
|
512
512
|
const newIndexBuf = encoder.encode(JSON.stringify(index))
|
|
513
513
|
|
|
514
514
|
if (this.useSync) {
|
|
515
|
-
const
|
|
515
|
+
const release = await fileLock.acquire(PACK_FILE)
|
|
516
516
|
try {
|
|
517
517
|
const access = await fileHandle.createSyncAccessHandle()
|
|
518
518
|
try {
|
|
@@ -558,7 +558,7 @@ export class PackedStorage {
|
|
|
558
558
|
access.close()
|
|
559
559
|
}
|
|
560
560
|
} finally {
|
|
561
|
-
|
|
561
|
+
release()
|
|
562
562
|
}
|
|
563
563
|
} else {
|
|
564
564
|
// For non-sync, rewrite the whole file
|
package/src/symlink-manager.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SymlinkCache, SymlinkDefinition } from './types.js'
|
|
2
2
|
import type { HandleManager } from './handle-manager.js'
|
|
3
|
-
import {
|
|
3
|
+
import { fileLock } from './handle-manager.js'
|
|
4
4
|
import { normalize } from './path-utils.js'
|
|
5
5
|
import { createELOOP, createEINVAL, createEEXIST } from './errors.js'
|
|
6
6
|
|
|
@@ -102,7 +102,7 @@ export class SymlinkManager {
|
|
|
102
102
|
const buffer = new TextEncoder().encode(data)
|
|
103
103
|
|
|
104
104
|
if (this.useSync) {
|
|
105
|
-
const
|
|
105
|
+
const release = await fileLock.acquire(SYMLINK_FILE)
|
|
106
106
|
try {
|
|
107
107
|
const access = await fileHandle.createSyncAccessHandle()
|
|
108
108
|
try {
|
|
@@ -115,7 +115,7 @@ export class SymlinkManager {
|
|
|
115
115
|
access.close()
|
|
116
116
|
}
|
|
117
117
|
} finally {
|
|
118
|
-
|
|
118
|
+
release()
|
|
119
119
|
}
|
|
120
120
|
} else {
|
|
121
121
|
const writable = await fileHandle.createWritable()
|