"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var db_exports = {};
__export(db_exports, {
  JsonlDB: () => JsonlDB
});
module.exports = __toCommonJS(db_exports);
var lockfile = __toESM(require("@alcalzone/proper-lockfile"), 1);
var import_async = require("alcalzone-shared/async");
var import_deferred_promise = require("alcalzone-shared/deferred-promise");
var fs = __toESM(require("node:fs/promises"), 1);
var path = __toESM(require("path"), 1);
var readline = __toESM(require("readline"), 1);
var import_signal = require("./signal.js");
async function ensureDir(dirPath) {
  await fs.mkdir(dirPath, { recursive: true });
}
__name(ensureDir, "ensureDir");
async function remove(filePath) {
  await fs.rm(filePath, { recursive: true, force: true });
}
__name(remove, "remove");
var Operation;
(function(Operation2) {
  Operation2[Operation2["Clear"] = 0] = "Clear";
  Operation2[Operation2["Write"] = 1] = "Write";
  Operation2[Operation2["Delete"] = 2] = "Delete";
})(Operation || (Operation = {}));
async function fsyncDir(dirname) {
  if (process.platform === "win32")
    return;
  const fd = await fs.open(dirname, "r");
  await fd.sync();
  await fd.close();
}
__name(fsyncDir, "fsyncDir");
function getCurrentErrorStack() {
  const tmp = { message: "" };
  Error.captureStackTrace(tmp);
  return tmp.stack.split("\n").slice(2).join("\n");
}
__name(getCurrentErrorStack, "getCurrentErrorStack");
class JsonlDB {
  static {
    __name(this, "JsonlDB");
  }
  constructor(filename, options = {}) {
    this.validateOptions(options);
    this.filename = filename;
    this.dumpFilename = this.filename + ".dump";
    this.backupFilename = this.filename + ".bak";
    const lockfileDirectory = options.lockfile?.directory ?? options.lockfileDirectory;
    this.lockfileName = lockfileDirectory ? path.join(lockfileDirectory, path.basename(this.filename)) : this.filename;
    this.options = options;
    this.forEach = this._db.forEach.bind(this._db);
    this.get = this._db.get.bind(this._db);
    this.has = this._db.has.bind(this._db);
    this.entries = this._db.entries.bind(this._db);
    this.keys = this._db.keys.bind(this._db);
    this.values = this._db.values.bind(this._db);
    this[Symbol.iterator] = this._db[Symbol.iterator].bind(this._db);
  }
  validateOptions(options) {
    if (options.autoCompress) {
      const { sizeFactor, sizeFactorMinimumSize, intervalMs, intervalMinChanges } = options.autoCompress;
      if (sizeFactor != void 0 && sizeFactor <= 1) {
        throw new Error("sizeFactor must be > 1");
      }
      if (sizeFactorMinimumSize != void 0 && sizeFactorMinimumSize < 0) {
        throw new Error("sizeFactorMinimumSize must be >= 0");
      }
      if (intervalMs != void 0 && intervalMs < 10) {
        throw new Error("intervalMs must be >= 10");
      }
      if (intervalMinChanges != void 0 && intervalMinChanges < 1) {
        throw new Error("intervalMinChanges must be >= 1");
      }
    }
    if (options.throttleFS) {
      const { intervalMs, maxBufferedCommands } = options.throttleFS;
      if (intervalMs < 0) {
        throw new Error("intervalMs must be >= 0");
      }
      if (maxBufferedCommands != void 0 && maxBufferedCommands < 0) {
        throw new Error("maxBufferedCommands must be >= 0");
      }
    }
    if (options.lockfile) {
      const { directory, retries, staleMs = 1e4, updateMs = staleMs / 2, retryMinTimeoutMs } = options.lockfile;
      if (staleMs < 2e3) {
        throw new Error("staleMs must be >= 2000");
      }
      if (updateMs < 1e3) {
        throw new Error("updateMs must be >= 1000");
      }
      if (updateMs > staleMs / 2) {
        throw new Error(`updateMs must be <= ${staleMs / 2}`);
      }
      if (retries != void 0 && retries < 0) {
        throw new Error("retries must be >= 0");
      }
      if (retries != void 0 && retries > 10) {
        throw new Error("retries must be <= 10");
      }
      if (retryMinTimeoutMs != void 0 && retryMinTimeoutMs < 100) {
        throw new Error("retryMinTimeoutMs must be >= 100");
      }
      if (options.lockfileDirectory != void 0 && directory != void 0) {
        throw new Error("lockfileDirectory and lockfile.directory must not both be specified");
      }
    }
  }
  filename;
  dumpFilename;
  backupFilename;
  lockfileName;
  options;
  _db = /* @__PURE__ */ new Map();
  _timestamps = /* @__PURE__ */ new Map();
  getTimestamp(key) {
    return this._timestamps.get(key);
  }
  get size() {
    return this._db.size;
  }
  _uncompressedSize = Number.NaN;
  /** Returns the line count of the appendonly file, excluding empty lines */
  get uncompressedSize() {
    if (!this._isOpen) {
      throw new Error("The database is not open!");
    }
    return this._uncompressedSize;
  }
  _changesSinceLastCompress = 0;
  _compressBySizeThreshold = Number.POSITIVE_INFINITY;
  // Signals that the conditions to compress the DB by size are fulfilled
  _compressBySizeNeeded = new import_signal.Signal();
  // Signals that the minimum number of changes to automatically compress were exceeded
  _compressIntervalMinChangesExceeded = false;
  // Signals that the next change may immediately trigger a write to disk
  _writeIntervalElapsed = false;
  // Signals that the journal has exceeded the maximum buffered commands
  // or that the journal contains entries that may be written to disk immediately
  _journalFlushable = new import_signal.Signal();
  updateCompressBySizeThreshold() {
    if (!this.options.autoCompress)
      return;
    if (!this.options.autoCompress.sizeFactor)
      return;
    const { sizeFactor = Number.POSITIVE_INFINITY, sizeFactorMinimumSize = 0 } = this.options.autoCompress;
    this._compressBySizeThreshold = Math.max(sizeFactorMinimumSize, sizeFactor * this.size);
  }
  triggerJournalFlush() {
    if (
      // ... immediately if writing isn't throttled
      !this.options.throttleFS?.intervalMs || // ... immediately if the timer elapsed
      this._writeIntervalElapsed || // ... or the maximum buffered commands were exceeded
      this.exceededMaxBufferedCommands()
    ) {
      this._journalFlushable.set();
    }
  }
  _isOpen = false;
  get isOpen() {
    return this._isOpen;
  }
  // Resolves when the persistence thread ends
  _persistencePromise;
  // An array of tasks to be handled by the persistence thread
  _persistenceTasks = [];
  // Indicates to the persistence thread that there is a pending task
  _persistenceTaskSignal = new import_signal.Signal();
  _journal = [];
  _handle;
  drainJournal() {
    return this._journal.splice(0, this._journal.length);
  }
  pushPersistenceTask(task) {
    this._persistenceTasks.push(task);
    this._persistenceTaskSignal.set();
  }
  _openPromise;
  // /** Opens the database file or creates it if it doesn't exist */
  async open() {
    await ensureDir(path.dirname(this.filename));
    let retryOptions;
    if (this.options.lockfile?.retries) {
      retryOptions = {
        minTimeout: this.options.lockfile.retryMinTimeoutMs ?? (this.options.lockfile.updateMs ?? 2e3) / 2,
        retries: this.options.lockfile.retries,
        factor: 1.25
      };
    }
    try {
      await ensureDir(path.dirname(this.lockfileName));
      await lockfile.lock(this.lockfileName, {
        // We cannot be sure that the file exists before acquiring the lock
        realpath: false,
        stale: (
          // Avoid timeouts during testing
          process.env.NODE_ENV === "test" ? 1e5 : (
            /* istanbul ignore next - this is impossible to test */
            this.options.lockfile?.staleMs
          )
        ),
        update: this.options.lockfile?.updateMs,
        retries: retryOptions,
        onCompromised: (
          /* istanbul ignore next */
          /* @__PURE__ */ __name(() => {
          }, "onCompromised")
        )
      });
    } catch {
      throw new Error(`Failed to lock DB file "${this.lockfileName}"!`);
    }
    await this.tryRecoverDBFiles();
    this._handle = await fs.open(this.filename, "a+");
    const readStream = this._handle.createReadStream({
      encoding: "utf8",
      autoClose: false
    });
    const rl = readline.createInterface(readStream);
    let lineNo = 0;
    this._uncompressedSize = 0;
    try {
      await new Promise((resolve, reject) => {
        const actualLines = /* @__PURE__ */ new Map();
        rl.on("line", (line) => {
          lineNo++;
          if (!line)
            return;
          try {
            this._uncompressedSize++;
            const key = this.parseKey(line);
            actualLines.set(key, [lineNo, line]);
          } catch {
            if (this.options.ignoreReadErrors === true) {
              return;
            } else {
              reject(new Error(`Cannot open file: Invalid data in line ${lineNo}`));
            }
          }
        });
        rl.on("close", () => {
          for (const [lineNo2, line] of actualLines.values()) {
            try {
              this.parseLine(line);
            } catch {
              if (this.options.ignoreReadErrors === true) {
                continue;
              } else {
                reject(new Error(`Cannot open file: Invalid data in line ${lineNo2}`));
              }
            }
          }
          resolve();
        });
      });
    } finally {
      rl.close();
      await this._handle.close();
      this._handle = void 0;
    }
    this.updateCompressBySizeThreshold();
    this._persistencePromise = this.persistenceThread();
    await this._openPromise;
    this._isOpen = true;
    if (this.options.autoCompress?.onOpen)
      await this.compress();
  }
  /**
   * Makes sure that there are no remains of a previous broken compress attempt and restores
   * a DB backup if it exists.
   */
  async tryRecoverDBFiles() {
    let dbFileIsOK = false;
    try {
      const dbFileStats = await fs.stat(this.filename);
      dbFileIsOK = dbFileStats.isFile() && dbFileStats.size > 0;
    } catch {
    }
    if (dbFileIsOK) {
      try {
        await remove(this.backupFilename);
      } catch {
      }
      try {
        await remove(this.dumpFilename);
      } catch {
      }
      return;
    }
    let bakFileIsOK = false;
    try {
      const bakFileStats = await fs.stat(this.backupFilename);
      bakFileIsOK = bakFileStats.isFile() && bakFileStats.size > 0;
    } catch {
    }
    if (bakFileIsOK) {
      try {
        await fs.rename(this.backupFilename, this.filename);
        try {
          await remove(this.dumpFilename);
        } catch {
        }
        return;
      } catch {
      }
    }
    let dumpFileIsOK = false;
    try {
      const dumpFileStats = await fs.stat(this.dumpFilename);
      dumpFileIsOK = dumpFileStats.isFile() && dumpFileStats.size > 0;
    } catch {
    }
    if (dumpFileIsOK) {
      try {
        await fs.rename(this.dumpFilename, this.filename);
        try {
          await remove(this.backupFilename);
        } catch {
        }
        return;
      } catch {
      }
    }
  }
  /** Reads a line and extracts the key without doing a full-blown JSON.parse() */
  parseKey(line) {
    if (0 !== line.indexOf(`{"k":"`)) {
      throw new Error("invalid data");
    }
    const keyStart = 6;
    let keyEnd = line.indexOf(`","v":`, keyStart);
    if (-1 === keyEnd) {
      if (line.endsWith(`"}`)) {
        keyEnd = line.length - 2;
      } else {
        throw new Error("invalid data");
      }
    }
    return line.slice(keyStart, keyEnd);
  }
  /** Parses a line and updates the internal DB correspondingly */
  parseLine(line) {
    const record = JSON.parse(line);
    const { k, v, ts } = record;
    if (v !== void 0) {
      this._db.set(k, typeof this.options.reviver === "function" ? this.options.reviver(k, v) : v);
      if (this.options.enableTimestamps && ts !== void 0) {
        this._timestamps.set(k, ts);
      }
    } else {
      if (this._db.delete(k))
        this._timestamps.delete(k);
    }
  }
  clear() {
    if (!this._isOpen) {
      throw new Error("The database is not open!");
    }
    this._db.clear();
    this.drainJournal();
    this._journal.push(this.makeLazyClear());
  }
  delete(key) {
    if (!this._isOpen) {
      throw new Error("The database is not open!");
    }
    const ret = this._db.delete(key);
    if (ret) {
      this._journal.push(this.makeLazyDelete(key));
      this._timestamps.delete(key);
      this.updateCompressBySizeThreshold();
      this.triggerJournalFlush();
    }
    return ret;
  }
  set(key, value, updateTimestamp = true) {
    if (!this._isOpen) {
      throw new Error("The database is not open!");
    }
    this._db.set(key, value);
    if (this.options.enableTimestamps) {
      let ts;
      if (updateTimestamp) {
        ts = Date.now();
        this._timestamps.set(key, ts);
      } else {
        ts = this._timestamps.get(key);
      }
      this._journal.push(this.makeLazyWrite(key, value, ts));
    } else {
      this._journal.push(this.makeLazyWrite(key, value));
    }
    this.updateCompressBySizeThreshold();
    this.triggerJournalFlush();
    return this;
  }
  importJSON(json) {
    if (!this._isOpen) {
      throw new Error("The database is not open!");
    }
    for (const [key, value] of Object.entries(json)) {
      this._db.set(key, value);
      this._journal.push(this.makeLazyWrite(key, value));
    }
    this.updateCompressBySizeThreshold();
    this.triggerJournalFlush();
  }
  toJSON() {
    if (!this._isOpen) {
      throw new Error("The database is not open!");
    }
    return Object.fromEntries([...this._db]);
  }
  entryToLine(key, value, timestamp) {
    if (value !== void 0) {
      const k = key;
      let v;
      try {
        v = this.options.serializer?.(key, value);
      } catch {
      }
      v ??= value;
      if (this.options.enableTimestamps && timestamp !== void 0) {
        return JSON.stringify({ k, v, ts: timestamp });
      } else {
        return JSON.stringify({ k, v });
      }
    } else {
      return JSON.stringify({ k: key });
    }
  }
  makeLazyClear() {
    return {
      op: Operation.Clear,
      serialize: (
        /* istanbul ignore next - this is impossible to test since it requires exact timing */
        /* @__PURE__ */ __name(() => "", "serialize")
      )
    };
  }
  makeLazyDelete(key) {
    let serialized;
    return {
      op: Operation.Delete,
      key,
      serialize: /* @__PURE__ */ __name(() => {
        if (serialized == void 0) {
          serialized = this.entryToLine(key);
        }
        return serialized;
      }, "serialize")
    };
  }
  makeLazyWrite(key, value, timestamp) {
    let serialized;
    return {
      op: Operation.Write,
      key,
      value,
      timestamp,
      serialize: /* @__PURE__ */ __name(() => {
        if (serialized == void 0) {
          serialized = this.entryToLine(key, value, timestamp);
        }
        return serialized;
      }, "serialize")
    };
  }
  /**
   * Saves a compressed copy of the DB into the given path.
   *
   * **WARNING:** This MUST be called from {@link persistenceThread}!
   * @param targetFilename Where the compressed copy should be written. Default: `<filename>.dump`
   * @param drainJournal Whether the journal should be drained when writing the compressed copy or simply cloned.
   */
  async dumpInternal(targetFilename = this.dumpFilename, drainJournal) {
    const fd = await fs.open(targetFilename, "w+");
    const entries = [...this._db];
    const timestamps = new Map([...this._timestamps]);
    const journalLength = this._journal.length;
    let serialized = "";
    for (const [key, value] of entries) {
      if (this.options.enableTimestamps && timestamps.has(key)) {
        serialized += this.entryToLine(key, value, timestamps.get(key)) + "\n";
      } else {
        serialized += this.entryToLine(key, value) + "\n";
      }
    }
    await fd.appendFile(serialized);
    let journal = drainJournal ? this._journal.splice(0, this._journal.length) : this._journal;
    journal = journal.slice(journalLength);
    await this.writeJournalToFile(fd, journal, false);
    await fd.close();
  }
  /**
   * Saves a compressed copy of the DB into the given path.
   * @param targetFilename Where the compressed copy should be written. Default: `<filename>.dump`
   */
  async dump(targetFilename = this.dumpFilename) {
    if (!this._isOpen)
      return;
    const done = (0, import_deferred_promise.createDeferredPromise)();
    this.pushPersistenceTask({
      type: "dump",
      filename: targetFilename,
      done
    });
    const stack = getCurrentErrorStack();
    try {
      await done;
    } catch (e) {
      e.stack += "\n" + stack;
      throw e;
    }
  }
  exceededMaxBufferedCommands() {
    const maxBufferedCommands = this.options.throttleFS?.maxBufferedCommands;
    if (maxBufferedCommands == void 0) {
      return false;
    } else {
      return this._journal.length > 0 && this._journal.length > maxBufferedCommands;
    }
  }
  needToCompressBySize() {
    if (!this._isOpen)
      return false;
    return this._uncompressedSize >= this._compressBySizeThreshold;
  }
  needToCompressByTime(lastCompress) {
    if (!this.options.autoCompress)
      return false;
    const { intervalMs, intervalMinChanges = 1 } = this.options.autoCompress;
    if (!intervalMs)
      return false;
    return this._changesSinceLastCompress >= intervalMinChanges && Date.now() - lastCompress >= intervalMs;
  }
  async persistenceThread() {
    const compressInterval = this.options.autoCompress?.intervalMs;
    const throttleInterval = this.options.throttleFS?.intervalMs ?? 0;
    let lastWrite = Date.now();
    let lastCompress = Date.now();
    this._handle = await fs.open(this.filename, "a+");
    this._openPromise?.resolve();
    while (true) {
      const now = Date.now();
      let compressByTimeSleepDuration;
      if (compressInterval) {
        const nextCompress = lastCompress + compressInterval;
        if (nextCompress > now) {
          compressByTimeSleepDuration = nextCompress - now;
        } else if (this._compressIntervalMinChangesExceeded) {
          compressByTimeSleepDuration = 0;
        }
      }
      let throttledWriteSleepDuration;
      if (throttleInterval) {
        const nextWrite = lastWrite + throttleInterval;
        if (nextWrite > now) {
          throttledWriteSleepDuration = nextWrite - now;
        } else if (this._journal.length > 0) {
          throttledWriteSleepDuration = 0;
        } else {
          this._writeIntervalElapsed = true;
        }
      } else if (this._journal.length > 0) {
        throttledWriteSleepDuration = 0;
      }
      const input = await Promise.race([
        // The journal has exceeded the maximum buffered commands
        // and needs to be written to disk
        this._journalFlushable.then(() => "flush journal"),
        // The timer to flush the journal to disk has elapsed
        throttledWriteSleepDuration != void 0 && (0, import_async.wait)(throttledWriteSleepDuration, true).then(() => "write"),
        // The timer to compress by time has elapsed
        compressByTimeSleepDuration != void 0 && (0, import_async.wait)(compressByTimeSleepDuration, true).then(() => {
          if (this._compressIntervalMinChangesExceeded) {
            return "compress";
          }
        }),
        this._compressBySizeNeeded.then(() => "compress"),
        // A task was received from the outside
        this._persistenceTaskSignal.then(() => "task")
      ].filter((p) => !!p));
      let task;
      if (input === "flush journal") {
        task = { type: "write" };
      } else if (input === "write") {
        task = { type: "write" };
      } else if (input === "compress") {
        task = {
          type: "compress",
          done: (0, import_deferred_promise.createDeferredPromise)()
        };
        task.done.catch(() => {
        });
      } else if (input === "task") {
        task = this._persistenceTasks.shift();
        if (!task)
          this._persistenceTaskSignal.reset();
      }
      if (!task)
        continue;
      let isStopCmd = false;
      switch (task.type) {
        case "stop":
          isStopCmd = true;
        // fall through
        case "write": {
          const shouldWrite = this._journal.length > 0;
          if (shouldWrite) {
            const journal = this.drainJournal();
            this._handle = await this.writeJournalToFile(this._handle, journal);
            lastWrite = Date.now();
          }
          if (isStopCmd) {
            await this._handle.close();
            this._handle = void 0;
            return;
          } else {
            if (this.needToCompressBySize()) {
              this._compressBySizeNeeded.set();
            }
            this._compressIntervalMinChangesExceeded = this._changesSinceLastCompress >= (this.options.autoCompress?.intervalMinChanges ?? 1);
          }
          break;
        }
        case "dump": {
          try {
            await this.dumpInternal(task.filename, false);
            task.done.resolve();
          } catch (e) {
            task.done.reject(e);
          }
          break;
        }
        case "compress": {
          try {
            await this.doCompress();
            lastCompress = Date.now();
            task.done?.resolve();
          } catch (e) {
            if (!this._handle) {
              this._handle = await fs.open(this.filename, "a+");
            }
            task.done?.reject(e);
          }
          break;
        }
      }
    }
  }
  /** Writes the given journal to the given file descriptor. Returns the new file descriptor if the file was re-opened during the process */
  async writeJournalToFile(fd, journal, updateStatistics = true) {
    const chunk = /* @__PURE__ */ new Map();
    let serialized = "";
    let truncate = false;
    for (const entry of journal) {
      if (entry.op === Operation.Clear) {
        chunk.clear();
        truncate = true;
      } else {
        chunk.set(entry.key, entry);
      }
    }
    if (truncate) {
      await fd.close();
      fd = await fs.open(this.filename, "w+");
      truncate = false;
      if (updateStatistics) {
        this._uncompressedSize = 0;
        this._changesSinceLastCompress = 0;
      }
    }
    for (const entry of chunk.values()) {
      serialized += entry.serialize() + "\n";
      if (updateStatistics) {
        this._uncompressedSize++;
        this._changesSinceLastCompress++;
      }
    }
    await fd.appendFile(serialized);
    await fd.sync();
    this._journalFlushable.reset();
    this._writeIntervalElapsed = false;
    return fd;
  }
  /**
   * Compresses the db by dumping it and overwriting the aof file.
   *
   * **WARNING:** This MUST be called from {@link persistenceThread}!
   */
  async doCompress() {
    const journal = this.drainJournal();
    this._handle = await this.writeJournalToFile(this._handle, journal);
    await this._handle.close();
    this._handle = void 0;
    await this.dumpInternal(this.dumpFilename, true);
    this._uncompressedSize = this._db.size;
    this.updateCompressBySizeThreshold();
    this._changesSinceLastCompress = 0;
    this._compressIntervalMinChangesExceeded = false;
    await fsyncDir(path.dirname(this.filename));
    await fs.rename(this.filename, this.backupFilename);
    await fs.rename(this.dumpFilename, this.filename);
    await fsyncDir(path.dirname(this.filename));
    await fs.unlink(this.backupFilename);
    this._handle = await fs.open(this.filename, "a+");
    this._compressBySizeNeeded.reset();
  }
  /** Compresses the db by dumping it and overwriting the aof file. */
  async compress() {
    if (!this._isOpen)
      return;
    await this.compressInternal();
  }
  /** Compresses the db by dumping it and overwriting the aof file. */
  async compressInternal() {
    const task = this._persistenceTasks.find((t) => t.type === "compress");
    if (task)
      return task.done;
    const done = (0, import_deferred_promise.createDeferredPromise)();
    this.pushPersistenceTask({
      type: "compress",
      done
    });
    const stack = getCurrentErrorStack();
    try {
      await done;
    } catch (e) {
      e.stack += "\n" + stack;
      throw e;
    }
  }
  /** Closes the DB and waits for all data to be written */
  async close() {
    if (!this._isOpen)
      return;
    this._isOpen = false;
    if (this.options.autoCompress?.onClose) {
      await this.compressInternal();
    }
    this.pushPersistenceTask({ type: "stop" });
    await this._persistencePromise;
    this._db.clear();
    this._changesSinceLastCompress = 0;
    this._uncompressedSize = Number.NaN;
    this.updateCompressBySizeThreshold();
    try {
      if (await lockfile.check(this.lockfileName, { realpath: false }))
        await lockfile.unlock(this.lockfileName, { realpath: false });
    } catch {
    }
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  JsonlDB
});
//# sourceMappingURL=db.js.map
