@threadbase-sh/scanner 0.9.0 → 0.9.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/index.cjs CHANGED
@@ -1361,7 +1361,7 @@ var import_fs7 = require("fs");
1361
1361
  var import_path6 = require("path");
1362
1362
 
1363
1363
  // src/persistent/schema.ts
1364
- var SCHEMA_VERSION = 3;
1364
+ var SCHEMA_VERSION = 4;
1365
1365
  var SCHEMA_SQL = `
1366
1366
  CREATE TABLE IF NOT EXISTS conversation_files (
1367
1367
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -2528,10 +2528,21 @@ var ConversationScanner = class {
2528
2528
  dbPath;
2529
2529
  sidecarEnabled;
2530
2530
  engineInstance = null;
2531
+ // The most recent scan()'s promise while it is still running, or null when
2532
+ // idle. close() awaits it before closing the SQLite handle so a fire-and-
2533
+ // forget scan can't have its DB shut mid-indexAll ("database connection is
2534
+ // not open"). Tracks the latest scan only — concurrent scans on one instance
2535
+ // aren't a supported pattern (a single writer DB), and the latest resolving
2536
+ // implies earlier ones already settled in practice.
2537
+ inFlightScan = null;
2531
2538
  emitter = new import_events.EventEmitter();
2532
2539
  watcher = null;
2533
2540
  queue = null;
2534
2541
  periodicTimer = null;
2542
+ // The in-flight periodicReconcile() job, if one is mid-run. unwatch() awaits
2543
+ // it before dropping the queue/engine so its post-await engine.files access
2544
+ // can't hit a closed DB (the watch-mode half of Bug #4).
2545
+ inFlightReconcile = null;
2535
2546
  constructor(options) {
2536
2547
  this.conversationLRU = new LRUCache(options?.conversationCacheSize ?? 5);
2537
2548
  if (options?.persistent === false) {
@@ -2554,14 +2565,31 @@ var ConversationScanner = class {
2554
2565
  return this.engineInstance;
2555
2566
  }
2556
2567
  // Release the SQLite connection. No-op in legacy mode. Safe to call
2557
- // repeatedly. Stops the watcher first if one is running; call unwatch()
2558
- // explicitly beforehand if you need to await watcher teardown.
2559
- close() {
2560
- if (this.watcher || this.queue || this.periodicTimer) void this.unwatch();
2568
+ // repeatedly. Awaits watcher teardown AND any in-flight scan before closing
2569
+ // the DB handle, so a fire-and-forget scan() can never have its connection
2570
+ // shut mid-indexAll() ("database connection is not open"). This is why close()
2571
+ // is async callers should await it during shutdown. (Scanner review Bug #4.)
2572
+ async close() {
2573
+ if (this.watcher || this.queue || this.periodicTimer) await this.unwatch();
2574
+ if (this.inFlightScan) {
2575
+ try {
2576
+ await this.inFlightScan;
2577
+ } catch {
2578
+ }
2579
+ }
2561
2580
  this.engineInstance?.close();
2562
2581
  this.engineInstance = null;
2563
2582
  }
2564
2583
  async scan(options = {}) {
2584
+ const promise = this.runScan(options);
2585
+ this.inFlightScan = promise;
2586
+ try {
2587
+ return await promise;
2588
+ } finally {
2589
+ if (this.inFlightScan === promise) this.inFlightScan = null;
2590
+ }
2591
+ }
2592
+ async runScan(options) {
2565
2593
  const profiles = await this.resolveProfiles(options.profiles);
2566
2594
  const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);
2567
2595
  this.lastTier = resolveTier(options.tier ?? "standard", options.tiers);
@@ -2997,7 +3025,10 @@ var ConversationScanner = class {
2997
3025
  const periodicMs = options.periodicMs ?? 6e4;
2998
3026
  if (periodicMs > 0) {
2999
3027
  this.periodicTimer = setInterval(() => {
3000
- void this.periodicReconcile(activeProfiles);
3028
+ const job = this.periodicReconcile(activeProfiles).finally(() => {
3029
+ if (this.inFlightReconcile === job) this.inFlightReconcile = null;
3030
+ });
3031
+ this.inFlightReconcile = job;
3001
3032
  }, periodicMs);
3002
3033
  this.periodicTimer.unref?.();
3003
3034
  }
@@ -3041,6 +3072,9 @@ var ConversationScanner = class {
3041
3072
  clearInterval(this.periodicTimer);
3042
3073
  this.periodicTimer = null;
3043
3074
  }
3075
+ if (this.inFlightReconcile) {
3076
+ await this.inFlightReconcile;
3077
+ }
3044
3078
  if (this.watcher) {
3045
3079
  await this.watcher.stop();
3046
3080
  this.watcher = null;