@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/cli.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
  import pino2 from "pino";
6
6
 
7
7
  // package.json
8
- var version = "0.9.0";
8
+ var version = "0.9.2";
9
9
 
10
10
  // src/logger.ts
11
11
  import pino from "pino";
@@ -1220,7 +1220,7 @@ import { mkdirSync } from "fs";
1220
1220
  import { dirname as dirname3 } from "path";
1221
1221
 
1222
1222
  // src/persistent/schema.ts
1223
- var SCHEMA_VERSION = 3;
1223
+ var SCHEMA_VERSION = 4;
1224
1224
  var SCHEMA_SQL = `
1225
1225
  CREATE TABLE IF NOT EXISTS conversation_files (
1226
1226
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -2456,10 +2456,21 @@ var ConversationScanner = class {
2456
2456
  dbPath;
2457
2457
  sidecarEnabled;
2458
2458
  engineInstance = null;
2459
+ // The most recent scan()'s promise while it is still running, or null when
2460
+ // idle. close() awaits it before closing the SQLite handle so a fire-and-
2461
+ // forget scan can't have its DB shut mid-indexAll ("database connection is
2462
+ // not open"). Tracks the latest scan only — concurrent scans on one instance
2463
+ // aren't a supported pattern (a single writer DB), and the latest resolving
2464
+ // implies earlier ones already settled in practice.
2465
+ inFlightScan = null;
2459
2466
  emitter = new EventEmitter();
2460
2467
  watcher = null;
2461
2468
  queue = null;
2462
2469
  periodicTimer = null;
2470
+ // The in-flight periodicReconcile() job, if one is mid-run. unwatch() awaits
2471
+ // it before dropping the queue/engine so its post-await engine.files access
2472
+ // can't hit a closed DB (the watch-mode half of Bug #4).
2473
+ inFlightReconcile = null;
2463
2474
  constructor(options) {
2464
2475
  this.conversationLRU = new LRUCache(options?.conversationCacheSize ?? 5);
2465
2476
  if (options?.persistent === false) {
@@ -2482,14 +2493,31 @@ var ConversationScanner = class {
2482
2493
  return this.engineInstance;
2483
2494
  }
2484
2495
  // Release the SQLite connection. No-op in legacy mode. Safe to call
2485
- // repeatedly. Stops the watcher first if one is running; call unwatch()
2486
- // explicitly beforehand if you need to await watcher teardown.
2487
- close() {
2488
- if (this.watcher || this.queue || this.periodicTimer) void this.unwatch();
2496
+ // repeatedly. Awaits watcher teardown AND any in-flight scan before closing
2497
+ // the DB handle, so a fire-and-forget scan() can never have its connection
2498
+ // shut mid-indexAll() ("database connection is not open"). This is why close()
2499
+ // is async callers should await it during shutdown. (Scanner review Bug #4.)
2500
+ async close() {
2501
+ if (this.watcher || this.queue || this.periodicTimer) await this.unwatch();
2502
+ if (this.inFlightScan) {
2503
+ try {
2504
+ await this.inFlightScan;
2505
+ } catch {
2506
+ }
2507
+ }
2489
2508
  this.engineInstance?.close();
2490
2509
  this.engineInstance = null;
2491
2510
  }
2492
2511
  async scan(options = {}) {
2512
+ const promise = this.runScan(options);
2513
+ this.inFlightScan = promise;
2514
+ try {
2515
+ return await promise;
2516
+ } finally {
2517
+ if (this.inFlightScan === promise) this.inFlightScan = null;
2518
+ }
2519
+ }
2520
+ async runScan(options) {
2493
2521
  const profiles = await this.resolveProfiles(options.profiles);
2494
2522
  const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);
2495
2523
  this.lastTier = resolveTier(options.tier ?? "standard", options.tiers);
@@ -2925,7 +2953,10 @@ var ConversationScanner = class {
2925
2953
  const periodicMs = options.periodicMs ?? 6e4;
2926
2954
  if (periodicMs > 0) {
2927
2955
  this.periodicTimer = setInterval(() => {
2928
- void this.periodicReconcile(activeProfiles);
2956
+ const job = this.periodicReconcile(activeProfiles).finally(() => {
2957
+ if (this.inFlightReconcile === job) this.inFlightReconcile = null;
2958
+ });
2959
+ this.inFlightReconcile = job;
2929
2960
  }, periodicMs);
2930
2961
  this.periodicTimer.unref?.();
2931
2962
  }
@@ -2969,6 +3000,9 @@ var ConversationScanner = class {
2969
3000
  clearInterval(this.periodicTimer);
2970
3001
  this.periodicTimer = null;
2971
3002
  }
3003
+ if (this.inFlightReconcile) {
3004
+ await this.inFlightReconcile;
3005
+ }
2972
3006
  if (this.watcher) {
2973
3007
  await this.watcher.stop();
2974
3008
  this.watcher = null;