@tinycloud/replica 0.2.4-beta.1 → 0.2.4-beta.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.
package/dist/sqlite.cjs CHANGED
@@ -228,6 +228,29 @@ async function loadSqlite() {
228
228
 
229
229
  // src/sqlite/store.ts
230
230
  var SCHEMA_VERSION = 1;
231
+ var sqliteOpenQueue = Promise.resolve();
232
+ async function acquireSqliteOpen() {
233
+ const previous = sqliteOpenQueue;
234
+ let release;
235
+ sqliteOpenQueue = new Promise((resolve2) => {
236
+ release = resolve2;
237
+ });
238
+ await previous;
239
+ return release;
240
+ }
241
+ async function captureNamedPath(path) {
242
+ const names = [];
243
+ for (let current = (0, import_node_path.resolve)(path); ; current = (0, import_node_path.dirname)(current)) {
244
+ names.push(current);
245
+ if ((0, import_node_path.dirname)(current) === current) break;
246
+ }
247
+ const identities = [];
248
+ for (const name of names.reverse()) {
249
+ const stats = await (0, import_promises.lstat)(name, { bigint: true });
250
+ identities.push({ path: name, dev: stats.dev, ino: stats.ino });
251
+ }
252
+ return identities;
253
+ }
231
254
  var DURABILITY = "sqlite-wal-synchronous-full+fsynced-blobs";
232
255
  var SCHEMA = `
233
256
  CREATE TABLE IF NOT EXISTS replica (
@@ -326,22 +349,37 @@ async function entriesOf(path) {
326
349
  }
327
350
  }
328
351
  var FAULTS = /* @__PURE__ */ Symbol("tinycloud.replica.faults");
352
+ var pinnedDirectories = new FinalizationRegistry((fd) => {
353
+ try {
354
+ (0, import_node_fs.closeSync)(fd);
355
+ } catch {
356
+ }
357
+ });
329
358
  var SqliteReplicaStore = class _SqliteReplicaStore {
330
359
  dir;
331
360
  #db;
361
+ #pinnedDir;
362
+ #directoryFd;
332
363
  #holder = `${process.pid}-${(0, import_node_crypto.randomBytes)(6).toString("hex")}`;
333
364
  #now;
334
365
  #faults;
335
366
  #guard;
336
367
  /** Identity of the database file this store opened: a deleted-and-recreated replica is a different file. */
337
368
  #ino;
338
- constructor(dir, db, ino, options) {
369
+ #dev;
370
+ #namedAncestors;
371
+ constructor(dir, db, identity, options, directoryFd, namedAncestors = []) {
339
372
  this.dir = dir;
373
+ this.#pinnedDir = directoryFd !== void 0 && process.platform === "linux" ? `/proc/self/fd/${directoryFd}` : dir;
374
+ this.#directoryFd = directoryFd;
375
+ if (directoryFd !== void 0) pinnedDirectories.register(this, directoryFd, this);
340
376
  this.#db = db;
341
- this.#ino = ino;
377
+ this.#ino = identity.ino;
378
+ this.#dev = identity.dev;
342
379
  this.#now = options.now ?? Date.now;
343
380
  this.#faults = options[FAULTS] ?? {};
344
381
  this.#guard = options.guard ?? ((section) => section());
382
+ this.#namedAncestors = namedAncestors;
345
383
  }
346
384
  /**
347
385
  * Read-only status snapshot; does not create a replica, migrate, recover
@@ -354,9 +392,10 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
354
392
  };
355
393
  checkAbort();
356
394
  const dbPath = (0, import_node_path.join)(dir, "replica.db");
357
- let ino;
395
+ let identity;
358
396
  try {
359
- ino = (await (0, import_promises.stat)(dbPath)).ino;
397
+ identity = await (0, import_promises.lstat)(dbPath);
398
+ if (!identity.isFile()) return null;
360
399
  } catch (error) {
361
400
  if (errnoOf(error) === "ENOENT") return null;
362
401
  throw storageError(error, "Inspecting the replica");
@@ -369,14 +408,14 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
369
408
  db = opener(dbPath, { readonly: true });
370
409
  db.exec("PRAGMA busy_timeout = 0");
371
410
  checkAbort();
372
- const store = new _SqliteReplicaStore(dir, db, ino, { create: false, ...options.now ? { now: options.now } : {} });
411
+ const store = new _SqliteReplicaStore(dir, db, identity, { create: false, ...options.now ? { now: options.now } : {} });
373
412
  for (let attempt = 0; attempt < 2; attempt++) {
374
413
  const before = db.get("PRAGMA data_version").data_version;
375
414
  const state = await store.open();
376
415
  checkAbort();
377
416
  if (state === null) return null;
378
417
  const status = await store.status();
379
- await new Promise((resolve) => setImmediate(resolve));
418
+ await new Promise((resolve2) => setImmediate(resolve2));
380
419
  checkAbort();
381
420
  const after = db.get("PRAGMA data_version").data_version;
382
421
  if (before === after) return { state, status };
@@ -396,65 +435,145 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
396
435
  * REPLICA_NOT_FOUND and nothing is written to disk.
397
436
  */
398
437
  static async open(dir, options) {
399
- const dbPath = (0, import_node_path.join)(dir, "replica.db");
438
+ const namedDbPath = (0, import_node_path.join)(dir, "replica.db");
400
439
  if (!options.create) {
401
- const exists = await (0, import_promises.stat)(dbPath).then(
402
- () => true,
440
+ const exists = await (0, import_promises.lstat)(namedDbPath).then(
441
+ (stats) => stats.isFile(),
403
442
  () => false
404
443
  );
405
- if (!exists) throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `No replica at ${dir}.`);
444
+ if (!exists) throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `No regular replica database at ${dir}.`);
406
445
  }
407
446
  const opener = options.sqlite ?? await loadSqlite();
447
+ const releaseOpen = await acquireSqliteOpen();
408
448
  let store;
449
+ let directoryFd;
409
450
  try {
410
- if (options.create) {
411
- await (0, import_promises.mkdir)((0, import_node_path.join)(dir, "blobs", ".tmp"), { recursive: true, mode: 448 });
412
- await Promise.all([(0, import_promises.chmod)(dir, 448), (0, import_promises.chmod)((0, import_node_path.join)(dir, "blobs"), 448), (0, import_promises.chmod)((0, import_node_path.join)(dir, "blobs", ".tmp"), 448)]);
413
- const handle = await (0, import_promises.open)(dbPath, import_node_fs.constants.O_CREAT | import_node_fs.constants.O_RDWR, 384);
414
- await handle.close();
415
- await (0, import_promises.chmod)(dbPath, 384);
451
+ if (options.create) await (0, import_promises.mkdir)(dir, { recursive: true, mode: 448 });
452
+ if (process.platform === "linux") {
453
+ directoryFd = (0, import_node_fs.openSync)(dir, import_node_fs.constants.O_RDONLY | import_node_fs.constants.O_DIRECTORY | import_node_fs.constants.O_NOFOLLOW);
416
454
  }
417
- const ino = (await (0, import_promises.stat)(dbPath)).ino;
418
- const db = opener(dbPath);
419
- db.exec("PRAGMA busy_timeout = 5000");
420
- db.exec("PRAGMA journal_mode = WAL");
421
- db.exec("PRAGMA synchronous = FULL");
422
- db.exec("PRAGMA secure_delete = ON");
423
- db.exec("PRAGMA foreign_keys = ON");
424
- const version = db.get("PRAGMA user_version")?.user_version ?? 0;
425
- if (version > SCHEMA_VERSION) {
426
- db.close();
427
- throw new ReplicaError(
428
- ReplicaErrorCode.STORAGE_ERROR,
429
- `The replica at ${dir} was written by a newer tc (schema ${version}); upgrade tc to read it.`
430
- );
455
+ const opened = directoryFd === void 0 ? (0, import_node_fs.lstatSync)(dir, { bigint: true }) : (0, import_node_fs.fstatSync)(directoryFd, { bigint: true });
456
+ if (!opened.isDirectory() || opened.isSymbolicLink() || options.expectedDirectory && (opened.dev !== BigInt(options.expectedDirectory.dev) || opened.ino !== BigInt(options.expectedDirectory.ino))) {
457
+ throw new Error("The replica directory changed before SQLite opened it.");
458
+ }
459
+ const namedAncestors = process.platform === "linux" ? [] : await captureNamedPath(dir);
460
+ if (namedAncestors.length && (namedAncestors[namedAncestors.length - 1].dev !== opened.dev || namedAncestors[namedAncestors.length - 1].ino !== opened.ino)) {
461
+ throw new Error("The replica directory changed while checking its ancestry.");
431
462
  }
432
- if (version < SCHEMA_VERSION) {
433
- db.exec("BEGIN IMMEDIATE");
434
- db.exec(SCHEMA);
435
- db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
436
- db.exec("COMMIT");
463
+ const pinnedDir = directoryFd === void 0 ? dir : `/proc/self/fd/${directoryFd}`;
464
+ const dbPath = (0, import_node_path.join)(pinnedDir, "replica.db");
465
+ if (options.create) {
466
+ await (0, import_promises.mkdir)((0, import_node_path.join)(pinnedDir, "blobs", ".tmp"), { recursive: true, mode: 448 });
467
+ await Promise.all([(0, import_promises.chmod)(pinnedDir, 448), (0, import_promises.chmod)((0, import_node_path.join)(pinnedDir, "blobs"), 448), (0, import_promises.chmod)((0, import_node_path.join)(pinnedDir, "blobs", ".tmp"), 448)]);
468
+ const handle = await (0, import_promises.open)(dbPath, import_node_fs.constants.O_CREAT | import_node_fs.constants.O_EXCL | import_node_fs.constants.O_RDWR | import_node_fs.constants.O_NOFOLLOW, 384).catch((error) => {
469
+ if (errnoOf(error) === "EEXIST") return null;
470
+ throw error;
471
+ });
472
+ if (handle) {
473
+ try {
474
+ await handle.chmod(384);
475
+ } finally {
476
+ await handle.close();
477
+ }
478
+ }
479
+ }
480
+ const leaf = await (0, import_promises.open)(dbPath, import_node_fs.constants.O_RDONLY | import_node_fs.constants.O_NOFOLLOW);
481
+ let db;
482
+ try {
483
+ const identity = await leaf.stat();
484
+ const named = await (0, import_promises.lstat)(dbPath);
485
+ if (!identity.isFile() || !named.isFile() || named.dev !== identity.dev || named.ino !== identity.ino) {
486
+ throw new Error("The replica database changed before SQLite opened it.");
487
+ }
488
+ const previousFds = process.platform === "linux" && !options.sqlite ? new Map((0, import_node_fs.readdirSync)("/proc/self/fd").map((fd) => {
489
+ try {
490
+ return [fd, (0, import_node_fs.readlinkSync)(`/proc/self/fd/${fd}`)];
491
+ } catch {
492
+ return [fd, ""];
493
+ }
494
+ })) : null;
495
+ db = opener(dbPath);
496
+ const afterOpen = (0, import_node_fs.lstatSync)(dbPath);
497
+ if (!afterOpen.isFile() || afterOpen.dev !== identity.dev || afterOpen.ino !== identity.ino) {
498
+ throw new Error("The replica database changed while SQLite opened it.");
499
+ }
500
+ if (previousFds) db.get("SELECT count(*) FROM sqlite_master");
501
+ if (previousFds) {
502
+ const databases = (0, import_node_fs.readdirSync)("/proc/self/fd").filter((fd) => {
503
+ try {
504
+ const target = (0, import_node_fs.readlinkSync)(`/proc/self/fd/${fd}`);
505
+ return target.endsWith("/replica.db") && previousFds.get(fd) !== target;
506
+ } catch {
507
+ return false;
508
+ }
509
+ });
510
+ if (databases.length === 0 || databases.some((fd) => {
511
+ try {
512
+ const actual = (0, import_node_fs.fstatSync)(Number(fd));
513
+ return actual.dev !== identity.dev || actual.ino !== identity.ino;
514
+ } catch {
515
+ return true;
516
+ }
517
+ })) {
518
+ throw new Error("SQLite opened a different replica database.");
519
+ }
520
+ }
521
+ const after = (0, import_node_fs.lstatSync)(dbPath);
522
+ if (!after.isFile() || after.dev !== identity.dev || after.ino !== identity.ino) {
523
+ throw new Error("The replica database changed while SQLite opened it.");
524
+ }
525
+ db.exec("PRAGMA busy_timeout = 5000");
526
+ db.exec("PRAGMA journal_mode = WAL");
527
+ db.exec("PRAGMA synchronous = FULL");
528
+ db.exec("PRAGMA secure_delete = ON");
529
+ db.exec("PRAGMA foreign_keys = ON");
530
+ const version = db.get("PRAGMA user_version")?.user_version ?? 0;
531
+ if (version > SCHEMA_VERSION) {
532
+ throw new ReplicaError(
533
+ ReplicaErrorCode.STORAGE_ERROR,
534
+ `The replica at ${dir} was written by a newer tc (schema ${version}); upgrade tc to read it.`
535
+ );
536
+ }
537
+ if (version < SCHEMA_VERSION) {
538
+ db.exec("BEGIN IMMEDIATE");
539
+ db.exec(SCHEMA);
540
+ db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
541
+ db.exec("COMMIT");
542
+ }
543
+ store = new _SqliteReplicaStore(dir, db, identity, options, directoryFd, namedAncestors);
544
+ db = void 0;
545
+ } finally {
546
+ try {
547
+ db?.close();
548
+ } finally {
549
+ await leaf.close();
550
+ }
437
551
  }
438
- store = new _SqliteReplicaStore(dir, db, ino, options);
439
552
  } catch (error) {
553
+ if (store) await store.close().catch(() => void 0);
554
+ else if (directoryFd !== void 0) (0, import_node_fs.closeSync)(directoryFd);
440
555
  throw storageError(error, `Opening the replica at ${dir}`);
556
+ } finally {
557
+ releaseOpen();
441
558
  }
442
- const row = store.#row();
443
- if (row?.revoked_detail != null && (row.purge_pending === 1 || await store.#holdsContentFiles().catch(() => true))) {
444
- await store.#finishRevocationPurge().catch(() => void 0);
559
+ try {
560
+ const row = store.#row();
561
+ if (row?.revoked_detail != null && (row.purge_pending === 1 || await store.#holdsContentFiles().catch(() => true))) {
562
+ await store.#finishRevocationPurge().catch(() => void 0);
563
+ }
564
+ return store;
565
+ } catch (error) {
566
+ await store.close().catch(() => void 0);
567
+ throw error;
445
568
  }
446
- return store;
447
569
  }
448
570
  #row() {
449
571
  return this.#db.get("SELECT * FROM replica WHERE id = 1");
450
572
  }
451
573
  /** Refuse to touch the filesystem for a replica that was deleted (or replaced) under us. */
452
574
  async #assertPresent() {
453
- const ino = await (0, import_promises.stat)((0, import_node_path.join)(this.dir, "replica.db")).then(
454
- (stats) => stats.ino,
455
- () => void 0
456
- );
457
- if (ino !== this.#ino) {
575
+ const identity = await (0, import_promises.lstat)((0, import_node_path.join)(this.#pinnedDir, "replica.db")).catch(() => null);
576
+ if (!identity?.isFile() || identity.dev !== this.#dev || identity.ino !== this.#ino) {
458
577
  throw new ReplicaError(
459
578
  ReplicaErrorCode.NOT_FOUND,
460
579
  `The replica at ${this.dir} was deleted while this command ran (its profile was deleted or the replica purged); nothing more was written.`
@@ -684,7 +803,7 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
684
803
  return held;
685
804
  }
686
805
  #blobPath(hash) {
687
- return (0, import_node_path.join)(this.dir, "blobs", hash.slice(0, 2), hash);
806
+ return (0, import_node_path.join)(this.#pinnedDir, "blobs", hash.slice(0, 2), hash);
688
807
  }
689
808
  /**
690
809
  * Make every blob durable before any row references it: write to a fresh
@@ -701,11 +820,11 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
701
820
  () => false
702
821
  );
703
822
  if (exists) continue;
704
- const shard = (0, import_node_path.join)(this.dir, "blobs", hash.slice(0, 2));
823
+ const shard = (0, import_node_path.join)(this.#pinnedDir, "blobs", hash.slice(0, 2));
705
824
  await (0, import_promises.mkdir)(shard, { mode: 448 }).catch((error) => {
706
825
  if (errnoOf(error) !== "EEXIST") throw error;
707
826
  });
708
- const temp = (0, import_node_path.join)(this.dir, "blobs", ".tmp", `${hash}.${(0, import_node_crypto.randomBytes)(6).toString("hex")}`);
827
+ const temp = (0, import_node_path.join)(this.#pinnedDir, "blobs", ".tmp", `${hash}.${(0, import_node_crypto.randomBytes)(6).toString("hex")}`);
709
828
  const handle = await (0, import_promises.open)(temp, import_node_fs.constants.O_CREAT | import_node_fs.constants.O_EXCL | import_node_fs.constants.O_WRONLY, 384);
710
829
  try {
711
830
  await handle.writeFile(bytes);
@@ -721,7 +840,7 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
721
840
  await (0, import_promises.unlink)(temp);
722
841
  }
723
842
  dirs.add(shard);
724
- dirs.add((0, import_node_path.join)(this.dir, "blobs"));
843
+ dirs.add((0, import_node_path.join)(this.#pinnedDir, "blobs"));
725
844
  }
726
845
  for (const dir of dirs) {
727
846
  const handle = await (0, import_promises.open)(dir, import_node_fs.constants.O_RDONLY);
@@ -829,7 +948,7 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
829
948
  /** Unlink every blob not in `keep`, and every temp file. Only a missing directory counts as empty. */
830
949
  async #unlinkBlobs(keep) {
831
950
  let removed = 0;
832
- const root = (0, import_node_path.join)(this.dir, "blobs");
951
+ const root = (0, import_node_path.join)(this.#pinnedDir, "blobs");
833
952
  for (const shard of await entriesOf(root)) {
834
953
  const shardPath = (0, import_node_path.join)(root, shard);
835
954
  if (shard === ".tmp") {
@@ -854,7 +973,7 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
854
973
  }
855
974
  /** Whether any content file (a blob or a temp file) is on disk. */
856
975
  async #holdsContentFiles() {
857
- const root = (0, import_node_path.join)(this.dir, "blobs");
976
+ const root = (0, import_node_path.join)(this.#pinnedDir, "blobs");
858
977
  for (const shard of await entriesOf(root)) {
859
978
  if ((await entriesOf((0, import_node_path.join)(root, shard))).length > 0) return true;
860
979
  }
@@ -920,8 +1039,93 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
920
1039
  }
921
1040
  try {
922
1041
  this.#checkLease(t, { allowRevoked: true });
1042
+ const expected = this.#directoryFd === void 0 ? this.#namedAncestors.at(-1) : (0, import_node_fs.fstatSync)(this.#directoryFd, { bigint: true });
1043
+ if (!expected) {
1044
+ throw new ReplicaError(ReplicaErrorCode.RUNTIME_UNSUPPORTED, "Replica removal requires an opened or identity-checked directory.");
1045
+ }
1046
+ const assertDirectory = async () => {
1047
+ const current = await (0, import_promises.lstat)(this.dir, { bigint: true });
1048
+ if (!current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) {
1049
+ throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, "The replica directory changed during removal.");
1050
+ }
1051
+ return current;
1052
+ };
1053
+ const pinned = await assertDirectory();
923
1054
  await this.#assertPresent();
924
- await (0, import_promises.rm)(this.dir, { recursive: true, force: true });
1055
+ if (process.platform === "linux") {
1056
+ for (const entry of await (0, import_promises.readdir)(this.#pinnedDir)) {
1057
+ await (0, import_promises.rm)((0, import_node_path.join)(this.#pinnedDir, entry), { recursive: true, force: true });
1058
+ }
1059
+ await assertDirectory();
1060
+ await (0, import_promises.rmdir)(this.dir);
1061
+ } else {
1062
+ const ancestors = this.#namedAncestors;
1063
+ const checkAncestors = async () => {
1064
+ for (const expected2 of ancestors) {
1065
+ const current = await (0, import_promises.lstat)(expected2.path, { bigint: true });
1066
+ if (current.dev !== expected2.dev || current.ino !== expected2.ino) {
1067
+ throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, "The replica ancestry changed during removal.");
1068
+ }
1069
+ }
1070
+ };
1071
+ const directories = /* @__PURE__ */ new Map([[this.dir, pinned]]);
1072
+ const files = /* @__PURE__ */ new Map();
1073
+ const check = async (path, expected2, directory) => {
1074
+ if (path !== this.dir) {
1075
+ const parent = (0, import_node_path.dirname)(path);
1076
+ await check(parent, directories.get(parent), true);
1077
+ }
1078
+ await checkAncestors();
1079
+ const current = await (0, import_promises.lstat)(path, { bigint: true });
1080
+ if ((directory ? !current.isDirectory() : !current.isFile()) || current.dev !== expected2.dev || current.ino !== expected2.ino) {
1081
+ throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, "The replica layout changed during removal.");
1082
+ }
1083
+ };
1084
+ const record = async (path, directory) => {
1085
+ const parent = (0, import_node_path.dirname)(path);
1086
+ await check(parent, directories.get(parent), true);
1087
+ const current = await (0, import_promises.lstat)(path, { bigint: true });
1088
+ if (!(directory ? current.isDirectory() : current.isFile())) {
1089
+ throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, "Unexpected replica layout during removal.");
1090
+ }
1091
+ (directory ? directories : files).set(path, current);
1092
+ };
1093
+ const blobs = (0, import_node_path.join)(this.dir, "blobs");
1094
+ for (const name of await (0, import_promises.readdir)(this.dir)) {
1095
+ if (name === "blobs") await record(blobs, true);
1096
+ else if (["replica.db", "replica.db-wal", "replica.db-shm"].includes(name)) await record((0, import_node_path.join)(this.dir, name), false);
1097
+ else throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, "Unexpected replica layout during removal.");
1098
+ }
1099
+ if (directories.has(blobs)) {
1100
+ await check(blobs, directories.get(blobs), true);
1101
+ for (const shard of await (0, import_promises.readdir)(blobs)) {
1102
+ if (shard !== ".tmp" && !/^[0-9a-f]{2}$/.test(shard)) {
1103
+ throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, "Unexpected replica blob shard during removal.");
1104
+ }
1105
+ const shardPath = (0, import_node_path.join)(blobs, shard);
1106
+ await record(shardPath, true);
1107
+ await check(shardPath, directories.get(shardPath), true);
1108
+ for (const name of await (0, import_promises.readdir)(shardPath)) {
1109
+ if (shard === ".tmp" ? !/^[0-9a-f]{64}\.[0-9a-f]{12}$/.test(name) : !isHash(name) || !name.startsWith(shard)) {
1110
+ throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, "Unexpected replica blob file during removal.");
1111
+ }
1112
+ await record((0, import_node_path.join)(shardPath, name), false);
1113
+ }
1114
+ }
1115
+ }
1116
+ for (const [path, identity] of files) {
1117
+ await check(path, identity, false);
1118
+ await (0, import_promises.unlink)(path);
1119
+ }
1120
+ for (const [path, identity] of [...directories].reverse()) {
1121
+ if (path === this.dir) continue;
1122
+ await check(path, identity, true);
1123
+ await (0, import_promises.rmdir)(path);
1124
+ }
1125
+ await assertDirectory();
1126
+ await checkAncestors();
1127
+ await (0, import_promises.rmdir)(this.dir);
1128
+ }
925
1129
  } catch (error) {
926
1130
  throw storageError(error, "Removing the replica");
927
1131
  } finally {
@@ -1087,7 +1291,14 @@ var SqliteReplicaStore = class _SqliteReplicaStore {
1087
1291
  };
1088
1292
  }
1089
1293
  async close() {
1090
- this.#db.close();
1294
+ const fd = this.#directoryFd;
1295
+ this.#directoryFd = void 0;
1296
+ if (fd !== void 0) pinnedDirectories.unregister(this);
1297
+ try {
1298
+ this.#db.close();
1299
+ } finally {
1300
+ if (fd !== void 0) (0, import_node_fs.closeSync)(fd);
1301
+ }
1091
1302
  }
1092
1303
  };
1093
1304
  // Annotate the CommonJS export names for ESM import in node: