@componentor/fs 1.2.4 → 1.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@componentor/fs",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "A blazing-fast, Node.js-compatible filesystem for the browser using OPFS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -188,10 +188,13 @@ export default class OPFS {
188
188
 
189
189
  if (this.useSync) {
190
190
  const access = await fileHandle.createSyncAccessHandle()
191
- const size = access.getSize()
192
- buffer = new Uint8Array(size)
193
- access.read(buffer)
194
- access.close()
191
+ try {
192
+ const size = access.getSize()
193
+ buffer = new Uint8Array(size)
194
+ access.read(buffer)
195
+ } finally {
196
+ access.close()
197
+ }
195
198
  } else {
196
199
  const file = await fileHandle.getFile()
197
200
  buffer = new Uint8Array(await file.arrayBuffer())
@@ -270,10 +273,13 @@ export default class OPFS {
270
273
  let buffer: Uint8Array
271
274
  if (this.useSync) {
272
275
  const access = await fileHandle.createSyncAccessHandle()
273
- const size = access.getSize()
274
- buffer = new Uint8Array(size)
275
- access.read(buffer)
276
- access.close()
276
+ try {
277
+ const size = access.getSize()
278
+ buffer = new Uint8Array(size)
279
+ access.read(buffer)
280
+ } finally {
281
+ access.close()
282
+ }
277
283
  } else {
278
284
  const file = await fileHandle.getFile()
279
285
  buffer = new Uint8Array(await file.arrayBuffer())
@@ -311,10 +317,13 @@ export default class OPFS {
311
317
 
312
318
  if (this.useSync) {
313
319
  const access = await fileHandle!.createSyncAccessHandle()
314
- // Set exact size (more efficient than truncate(0) + write)
315
- access.truncate(buffer.length)
316
- access.write(buffer, { at: 0 })
317
- access.close()
320
+ try {
321
+ // Set exact size (more efficient than truncate(0) + write)
322
+ access.truncate(buffer.length)
323
+ access.write(buffer, { at: 0 })
324
+ } finally {
325
+ access.close()
326
+ }
318
327
  } else {
319
328
  const writable = await fileHandle!.createWritable()
320
329
  await writable.write(buffer)
@@ -1037,8 +1046,11 @@ export default class OPFS {
1037
1046
 
1038
1047
  if (this.useSync) {
1039
1048
  const access = await fileHandle.createSyncAccessHandle()
1040
- access.truncate(len)
1041
- access.close()
1049
+ try {
1050
+ access.truncate(len)
1051
+ } finally {
1052
+ access.close()
1053
+ }
1042
1054
  } else {
1043
1055
  const file = await fileHandle.getFile()
1044
1056
  const data = new Uint8Array(await file.arrayBuffer())
@@ -128,7 +128,6 @@ export class PackedStorage {
128
128
  private useChecksum: boolean
129
129
  private index: PackIndex | null = null
130
130
  private indexLoaded = false
131
- private lockPromise: Promise<void> | null = null
132
131
 
133
132
  constructor(handleManager: HandleManager, useSync: boolean, useCompression = false, useChecksum = true) {
134
133
  this.handleManager = handleManager
@@ -138,23 +137,6 @@ export class PackedStorage {
138
137
  this.useChecksum = useChecksum
139
138
  }
140
139
 
141
- /**
142
- * Acquire lock for pack file access (prevents concurrent handle conflicts)
143
- */
144
- private async acquireLock(): Promise<() => void> {
145
- while (this.lockPromise) {
146
- await this.lockPromise
147
- }
148
- let release: () => void
149
- this.lockPromise = new Promise(resolve => {
150
- release = () => {
151
- this.lockPromise = null
152
- resolve()
153
- }
154
- })
155
- return release!
156
- }
157
-
158
140
  /**
159
141
  * Reset pack storage state (memory only)
160
142
  */
@@ -256,13 +238,8 @@ export class PackedStorage {
256
238
  * Check if a path exists in the pack
257
239
  */
258
240
  async has(path: string): Promise<boolean> {
259
- const release = await this.acquireLock()
260
- try {
261
- const index = await this.loadIndex()
262
- return path in index
263
- } finally {
264
- release()
265
- }
241
+ const index = await this.loadIndex()
242
+ return path in index
266
243
  }
267
244
 
268
245
  /**
@@ -270,15 +247,10 @@ export class PackedStorage {
270
247
  * Returns originalSize if compressed, otherwise size
271
248
  */
272
249
  async getSize(path: string): Promise<number | null> {
273
- const release = await this.acquireLock()
274
- try {
275
- const index = await this.loadIndex()
276
- const entry = index[path]
277
- if (!entry) return null
278
- return entry.originalSize ?? entry.size
279
- } finally {
280
- release()
281
- }
250
+ const index = await this.loadIndex()
251
+ const entry = index[path]
252
+ if (!entry) return null
253
+ return entry.originalSize ?? entry.size
282
254
  }
283
255
 
284
256
  /**
@@ -286,40 +258,35 @@ export class PackedStorage {
286
258
  * Handles decompression if file was stored compressed
287
259
  */
288
260
  async read(path: string): Promise<Uint8Array | null> {
289
- const release = await this.acquireLock()
290
- try {
291
- const index = await this.loadIndex()
292
- const entry = index[path]
293
- if (!entry) return null
294
-
295
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
296
- if (!fileHandle) return null
261
+ const index = await this.loadIndex()
262
+ const entry = index[path]
263
+ if (!entry) return null
297
264
 
298
- let buffer: Uint8Array
265
+ const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
266
+ if (!fileHandle) return null
299
267
 
300
- if (this.useSync) {
301
- const access = await fileHandle.createSyncAccessHandle()
302
- try {
303
- buffer = new Uint8Array(entry.size)
304
- access.read(buffer, { at: entry.offset })
305
- } finally {
306
- access.close()
307
- }
308
- } else {
309
- const file = await fileHandle.getFile()
310
- const data = new Uint8Array(await file.arrayBuffer())
311
- buffer = data.slice(entry.offset, entry.offset + entry.size)
312
- }
268
+ let buffer: Uint8Array
313
269
 
314
- // Decompress if needed
315
- if (entry.originalSize !== undefined) {
316
- return decompress(buffer)
270
+ if (this.useSync) {
271
+ const access = await fileHandle.createSyncAccessHandle()
272
+ try {
273
+ buffer = new Uint8Array(entry.size)
274
+ access.read(buffer, { at: entry.offset })
275
+ } finally {
276
+ access.close()
317
277
  }
278
+ } else {
279
+ const file = await fileHandle.getFile()
280
+ const data = new Uint8Array(await file.arrayBuffer())
281
+ buffer = data.slice(entry.offset, entry.offset + entry.size)
282
+ }
318
283
 
319
- return buffer
320
- } finally {
321
- release()
284
+ // Decompress if needed
285
+ if (entry.originalSize !== undefined) {
286
+ return decompress(buffer)
322
287
  }
288
+
289
+ return buffer
323
290
  }
324
291
 
325
292
  /**
@@ -331,74 +298,69 @@ export class PackedStorage {
331
298
  const results = new Map<string, Uint8Array | null>()
332
299
  if (paths.length === 0) return results
333
300
 
334
- const release = await this.acquireLock()
335
- try {
336
- const index = await this.loadIndex()
337
-
338
- // Find which paths are in the pack
339
- const toRead: Array<{ path: string; offset: number; size: number; originalSize?: number }> = []
340
- for (const path of paths) {
341
- const entry = index[path]
342
- if (entry) {
343
- toRead.push({ path, offset: entry.offset, size: entry.size, originalSize: entry.originalSize })
344
- } else {
345
- results.set(path, null)
346
- }
301
+ const index = await this.loadIndex()
302
+
303
+ // Find which paths are in the pack
304
+ const toRead: Array<{ path: string; offset: number; size: number; originalSize?: number }> = []
305
+ for (const path of paths) {
306
+ const entry = index[path]
307
+ if (entry) {
308
+ toRead.push({ path, offset: entry.offset, size: entry.size, originalSize: entry.originalSize })
309
+ } else {
310
+ results.set(path, null)
347
311
  }
312
+ }
348
313
 
349
- if (toRead.length === 0) return results
314
+ if (toRead.length === 0) return results
350
315
 
351
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
352
- if (!fileHandle) {
353
- for (const { path } of toRead) {
354
- results.set(path, null)
355
- }
356
- return results
316
+ const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
317
+ if (!fileHandle) {
318
+ for (const { path } of toRead) {
319
+ results.set(path, null)
357
320
  }
321
+ return results
322
+ }
358
323
 
359
- // Read all files
360
- const decompressPromises: Array<{ path: string; promise: Promise<Uint8Array> }> = []
324
+ // Read all files
325
+ const decompressPromises: Array<{ path: string; promise: Promise<Uint8Array> }> = []
361
326
 
362
- if (this.useSync) {
363
- const access = await fileHandle.createSyncAccessHandle()
364
- try {
365
- for (const { path, offset, size, originalSize } of toRead) {
366
- const buffer = new Uint8Array(size)
367
- access.read(buffer, { at: offset })
368
-
369
- if (originalSize !== undefined) {
370
- // Queue for decompression
371
- decompressPromises.push({ path, promise: decompress(buffer) })
372
- } else {
373
- results.set(path, buffer)
374
- }
375
- }
376
- } finally {
377
- access.close()
378
- }
379
- } else {
380
- const file = await fileHandle.getFile()
381
- const data = new Uint8Array(await file.arrayBuffer())
327
+ if (this.useSync) {
328
+ const access = await fileHandle.createSyncAccessHandle()
329
+ try {
382
330
  for (const { path, offset, size, originalSize } of toRead) {
383
- const buffer = data.slice(offset, offset + size)
331
+ const buffer = new Uint8Array(size)
332
+ access.read(buffer, { at: offset })
384
333
 
385
334
  if (originalSize !== undefined) {
335
+ // Queue for decompression
386
336
  decompressPromises.push({ path, promise: decompress(buffer) })
387
337
  } else {
388
338
  results.set(path, buffer)
389
339
  }
390
340
  }
341
+ } finally {
342
+ access.close()
391
343
  }
344
+ } else {
345
+ const file = await fileHandle.getFile()
346
+ const data = new Uint8Array(await file.arrayBuffer())
347
+ for (const { path, offset, size, originalSize } of toRead) {
348
+ const buffer = data.slice(offset, offset + size)
392
349
 
393
- // Wait for all decompressions
394
- for (const { path, promise } of decompressPromises) {
395
- results.set(path, await promise)
350
+ if (originalSize !== undefined) {
351
+ decompressPromises.push({ path, promise: decompress(buffer) })
352
+ } else {
353
+ results.set(path, buffer)
354
+ }
396
355
  }
356
+ }
397
357
 
398
- return results
399
- } finally {
400
- release()
358
+ // Wait for all decompressions
359
+ for (const { path, promise } of decompressPromises) {
360
+ results.set(path, await promise)
401
361
  }
362
+
363
+ return results
402
364
  }
403
365
 
404
366
  /**
@@ -411,86 +373,81 @@ export class PackedStorage {
411
373
  async writeBatch(entries: Array<{ path: string; data: Uint8Array }>): Promise<void> {
412
374
  if (entries.length === 0) return
413
375
 
414
- const release = await this.acquireLock()
415
- try {
416
- const encoder = new TextEncoder()
417
-
418
- // Compress data if enabled
419
- let processedEntries: Array<{ path: string; data: Uint8Array; originalSize?: number }>
420
- if (this.useCompression) {
421
- processedEntries = await Promise.all(
422
- entries.map(async ({ path, data }) => {
423
- const compressed = await compress(data)
424
- // Only use compressed if it's actually smaller
425
- if (compressed.length < data.length) {
426
- return { path, data: compressed, originalSize: data.length }
427
- }
428
- return { path, data }
429
- })
430
- )
431
- } else {
432
- processedEntries = entries
433
- }
434
-
435
- // Calculate total data size (using compressed sizes where applicable)
436
- let totalDataSize = 0
437
- for (const { data } of processedEntries) {
438
- totalDataSize += data.length
439
- }
440
-
441
- // Build index - iterate until offsets stabilize
442
- // (offset changes -> JSON length changes -> header size changes -> offset changes)
443
- // Header format: [index length: 4][CRC32: 4][JSON index][file data...]
444
- const newIndex: PackIndex = {}
445
- let headerSize = 8 // 4 bytes index length + 4 bytes CRC32
446
- let prevHeaderSize = 0
376
+ const encoder = new TextEncoder()
377
+
378
+ // Compress data if enabled
379
+ let processedEntries: Array<{ path: string; data: Uint8Array; originalSize?: number }>
380
+ if (this.useCompression) {
381
+ processedEntries = await Promise.all(
382
+ entries.map(async ({ path, data }) => {
383
+ const compressed = await compress(data)
384
+ // Only use compressed if it's actually smaller
385
+ if (compressed.length < data.length) {
386
+ return { path, data: compressed, originalSize: data.length }
387
+ }
388
+ return { path, data }
389
+ })
390
+ )
391
+ } else {
392
+ processedEntries = entries
393
+ }
447
394
 
448
- // Iterate until stable (usually 2-3 iterations)
449
- while (headerSize !== prevHeaderSize) {
450
- prevHeaderSize = headerSize
395
+ // Calculate total data size (using compressed sizes where applicable)
396
+ let totalDataSize = 0
397
+ for (const { data } of processedEntries) {
398
+ totalDataSize += data.length
399
+ }
451
400
 
452
- let currentOffset = headerSize
453
- for (const { path, data, originalSize } of processedEntries) {
454
- const entry: PackIndexEntry = { offset: currentOffset, size: data.length }
455
- if (originalSize !== undefined) {
456
- entry.originalSize = originalSize
457
- }
458
- newIndex[path] = entry
459
- currentOffset += data.length
401
+ // Build index - iterate until offsets stabilize
402
+ // (offset changes -> JSON length changes -> header size changes -> offset changes)
403
+ // Header format: [index length: 4][CRC32: 4][JSON index][file data...]
404
+ const newIndex: PackIndex = {}
405
+ let headerSize = 8 // 4 bytes index length + 4 bytes CRC32
406
+ let prevHeaderSize = 0
407
+
408
+ // Iterate until stable (usually 2-3 iterations)
409
+ while (headerSize !== prevHeaderSize) {
410
+ prevHeaderSize = headerSize
411
+
412
+ let currentOffset = headerSize
413
+ for (const { path, data, originalSize } of processedEntries) {
414
+ const entry: PackIndexEntry = { offset: currentOffset, size: data.length }
415
+ if (originalSize !== undefined) {
416
+ entry.originalSize = originalSize
460
417
  }
461
-
462
- const indexBuf = encoder.encode(JSON.stringify(newIndex))
463
- headerSize = 8 + indexBuf.length
418
+ newIndex[path] = entry
419
+ currentOffset += data.length
464
420
  }
465
421
 
466
- // Build the complete pack file
467
- const finalIndexBuf = encoder.encode(JSON.stringify(newIndex))
468
- const totalSize = headerSize + totalDataSize
469
- const packBuffer = new Uint8Array(totalSize)
470
- const view = new DataView(packBuffer.buffer)
422
+ const indexBuf = encoder.encode(JSON.stringify(newIndex))
423
+ headerSize = 8 + indexBuf.length
424
+ }
425
+
426
+ // Build the complete pack file
427
+ const finalIndexBuf = encoder.encode(JSON.stringify(newIndex))
428
+ const totalSize = headerSize + totalDataSize
429
+ const packBuffer = new Uint8Array(totalSize)
430
+ const view = new DataView(packBuffer.buffer)
471
431
 
472
- // Write index JSON at offset 8
473
- packBuffer.set(finalIndexBuf, 8)
432
+ // Write index JSON at offset 8
433
+ packBuffer.set(finalIndexBuf, 8)
474
434
 
475
- // Write data at correct offsets
476
- for (const { path, data } of processedEntries) {
477
- const entry = newIndex[path]
478
- packBuffer.set(data, entry.offset)
479
- }
435
+ // Write data at correct offsets
436
+ for (const { path, data } of processedEntries) {
437
+ const entry = newIndex[path]
438
+ packBuffer.set(data, entry.offset)
439
+ }
480
440
 
481
- // Calculate CRC32 over content (index + data, everything after header) if enabled
482
- const content = packBuffer.subarray(8)
483
- const checksum = this.useChecksum ? crc32(content) : 0
441
+ // Calculate CRC32 over content (index + data, everything after header) if enabled
442
+ const content = packBuffer.subarray(8)
443
+ const checksum = this.useChecksum ? crc32(content) : 0
484
444
 
485
- // Write header (index length + CRC32)
486
- view.setUint32(0, finalIndexBuf.length, true)
487
- view.setUint32(4, checksum, true)
445
+ // Write header (index length + CRC32)
446
+ view.setUint32(0, finalIndexBuf.length, true)
447
+ view.setUint32(4, checksum, true)
488
448
 
489
- await this.writePackFile(packBuffer)
490
- this.index = newIndex
491
- } finally {
492
- release()
493
- }
449
+ await this.writePackFile(packBuffer)
450
+ this.index = newIndex
494
451
  }
495
452
 
496
453
  /**
@@ -521,111 +478,101 @@ export class PackedStorage {
521
478
  * Note: Doesn't reclaim space, just removes from index and recalculates CRC32
522
479
  */
523
480
  async remove(path: string): Promise<boolean> {
524
- const release = await this.acquireLock()
525
- try {
526
- const index = await this.loadIndex()
527
- if (!(path in index)) return false
528
-
529
- delete index[path]
530
-
531
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
532
- if (!fileHandle) return true
533
-
534
- // Need to read existing file to recalculate CRC32
535
- const encoder = new TextEncoder()
536
- const newIndexBuf = encoder.encode(JSON.stringify(index))
537
-
538
- if (this.useSync) {
539
- const access = await fileHandle.createSyncAccessHandle()
540
- try {
541
- const size = access.getSize()
542
-
543
- // Read old header to get old index length
544
- const oldHeader = new Uint8Array(8)
545
- access.read(oldHeader, { at: 0 })
546
- const oldIndexLen = new DataView(oldHeader.buffer).getUint32(0, true)
547
-
548
- // Read data portion (after old index)
549
- const dataStart = 8 + oldIndexLen
550
- const dataSize = size - dataStart
551
- const dataPortion = new Uint8Array(dataSize)
552
- if (dataSize > 0) {
553
- access.read(dataPortion, { at: dataStart })
554
- }
555
-
556
- // Build new content (new index + data)
557
- const newContent = new Uint8Array(newIndexBuf.length + dataSize)
558
- newContent.set(newIndexBuf, 0)
559
- if (dataSize > 0) {
560
- newContent.set(dataPortion, newIndexBuf.length)
561
- }
481
+ const index = await this.loadIndex()
482
+ if (!(path in index)) return false
562
483
 
563
- // Calculate new CRC32 if enabled
564
- const checksum = this.useChecksum ? crc32(newContent) : 0
484
+ delete index[path]
565
485
 
566
- // Build new header
567
- const newHeader = new Uint8Array(8)
568
- const view = new DataView(newHeader.buffer)
569
- view.setUint32(0, newIndexBuf.length, true)
570
- view.setUint32(4, checksum, true)
486
+ const { fileHandle } = await this.handleManager.getHandle(PACK_FILE)
487
+ if (!fileHandle) return true
571
488
 
572
- // Write new file
573
- const newFile = new Uint8Array(8 + newContent.length)
574
- newFile.set(newHeader, 0)
575
- newFile.set(newContent, 8)
489
+ // Need to read existing file to recalculate CRC32
490
+ const encoder = new TextEncoder()
491
+ const newIndexBuf = encoder.encode(JSON.stringify(index))
576
492
 
577
- access.truncate(newFile.length)
578
- access.write(newFile, { at: 0 })
579
- } finally {
580
- access.close()
581
- }
582
- } else {
583
- // For non-sync, rewrite the whole file
584
- const file = await fileHandle.getFile()
585
- const oldData = new Uint8Array(await file.arrayBuffer())
493
+ if (this.useSync) {
494
+ const access = await fileHandle.createSyncAccessHandle()
495
+ try {
496
+ const size = access.getSize()
586
497
 
587
- if (oldData.length < 8) return true
498
+ // Read old header to get old index length
499
+ const oldHeader = new Uint8Array(8)
500
+ access.read(oldHeader, { at: 0 })
501
+ const oldIndexLen = new DataView(oldHeader.buffer).getUint32(0, true)
588
502
 
589
- const oldIndexLen = new DataView(oldData.buffer).getUint32(0, true)
503
+ // Read data portion (after old index)
590
504
  const dataStart = 8 + oldIndexLen
591
- const dataPortion = oldData.subarray(dataStart)
505
+ const dataSize = size - dataStart
506
+ const dataPortion = new Uint8Array(dataSize)
507
+ if (dataSize > 0) {
508
+ access.read(dataPortion, { at: dataStart })
509
+ }
592
510
 
593
- // Build new content
594
- const newContent = new Uint8Array(newIndexBuf.length + dataPortion.length)
511
+ // Build new content (new index + data)
512
+ const newContent = new Uint8Array(newIndexBuf.length + dataSize)
595
513
  newContent.set(newIndexBuf, 0)
596
- newContent.set(dataPortion, newIndexBuf.length)
514
+ if (dataSize > 0) {
515
+ newContent.set(dataPortion, newIndexBuf.length)
516
+ }
597
517
 
598
- // Calculate CRC32 if enabled
518
+ // Calculate new CRC32 if enabled
599
519
  const checksum = this.useChecksum ? crc32(newContent) : 0
600
520
 
601
- // Build new file
602
- const newFile = new Uint8Array(8 + newContent.length)
603
- const view = new DataView(newFile.buffer)
521
+ // Build new header
522
+ const newHeader = new Uint8Array(8)
523
+ const view = new DataView(newHeader.buffer)
604
524
  view.setUint32(0, newIndexBuf.length, true)
605
525
  view.setUint32(4, checksum, true)
526
+
527
+ // Write new file
528
+ const newFile = new Uint8Array(8 + newContent.length)
529
+ newFile.set(newHeader, 0)
606
530
  newFile.set(newContent, 8)
607
531
 
608
- const writable = await fileHandle.createWritable()
609
- await writable.write(newFile)
610
- await writable.close()
532
+ access.truncate(newFile.length)
533
+ access.write(newFile, { at: 0 })
534
+ } finally {
535
+ access.close()
611
536
  }
537
+ } else {
538
+ // For non-sync, rewrite the whole file
539
+ const file = await fileHandle.getFile()
540
+ const oldData = new Uint8Array(await file.arrayBuffer())
541
+
542
+ if (oldData.length < 8) return true
543
+
544
+ const oldIndexLen = new DataView(oldData.buffer).getUint32(0, true)
545
+ const dataStart = 8 + oldIndexLen
546
+ const dataPortion = oldData.subarray(dataStart)
547
+
548
+ // Build new content
549
+ const newContent = new Uint8Array(newIndexBuf.length + dataPortion.length)
550
+ newContent.set(newIndexBuf, 0)
551
+ newContent.set(dataPortion, newIndexBuf.length)
612
552
 
613
- return true
614
- } finally {
615
- release()
553
+ // Calculate CRC32 if enabled
554
+ const checksum = this.useChecksum ? crc32(newContent) : 0
555
+
556
+ // Build new file
557
+ const newFile = new Uint8Array(8 + newContent.length)
558
+ const view = new DataView(newFile.buffer)
559
+ view.setUint32(0, newIndexBuf.length, true)
560
+ view.setUint32(4, checksum, true)
561
+ newFile.set(newContent, 8)
562
+
563
+ const writable = await fileHandle.createWritable()
564
+ await writable.write(newFile)
565
+ await writable.close()
616
566
  }
567
+
568
+ return true
617
569
  }
618
570
 
619
571
  /**
620
572
  * Check if pack file is being used (has entries)
621
573
  */
622
574
  async isEmpty(): Promise<boolean> {
623
- const release = await this.acquireLock()
624
- try {
625
- const index = await this.loadIndex()
626
- return Object.keys(index).length === 0
627
- } finally {
628
- release()
629
- }
575
+ const index = await this.loadIndex()
576
+ return Object.keys(index).length === 0
630
577
  }
631
578
  }