@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.d.cts CHANGED
@@ -371,15 +371,18 @@ declare class ConversationScanner {
371
371
  private readonly dbPath;
372
372
  private readonly sidecarEnabled;
373
373
  private engineInstance;
374
+ private inFlightScan;
374
375
  private emitter;
375
376
  private watcher;
376
377
  private queue;
377
378
  private periodicTimer;
379
+ private inFlightReconcile;
378
380
  constructor(options?: ConversationScannerOptions);
379
381
  private get persistent();
380
382
  private engine;
381
- close(): void;
383
+ close(): Promise<void>;
382
384
  scan(options?: ScanOptions): Promise<ScanResult>;
385
+ private runScan;
383
386
  private scanPersistent;
384
387
  private finalize;
385
388
  private scanInMemory;
package/dist/index.d.ts CHANGED
@@ -371,15 +371,18 @@ declare class ConversationScanner {
371
371
  private readonly dbPath;
372
372
  private readonly sidecarEnabled;
373
373
  private engineInstance;
374
+ private inFlightScan;
374
375
  private emitter;
375
376
  private watcher;
376
377
  private queue;
377
378
  private periodicTimer;
379
+ private inFlightReconcile;
378
380
  constructor(options?: ConversationScannerOptions);
379
381
  private get persistent();
380
382
  private engine;
381
- close(): void;
383
+ close(): Promise<void>;
382
384
  scan(options?: ScanOptions): Promise<ScanResult>;
385
+ private runScan;
383
386
  private scanPersistent;
384
387
  private finalize;
385
388
  private scanInMemory;
package/dist/index.js CHANGED
@@ -1297,7 +1297,7 @@ import { mkdirSync } from "fs";
1297
1297
  import { dirname as dirname3 } from "path";
1298
1298
 
1299
1299
  // src/persistent/schema.ts
1300
- var SCHEMA_VERSION = 3;
1300
+ var SCHEMA_VERSION = 4;
1301
1301
  var SCHEMA_SQL = `
1302
1302
  CREATE TABLE IF NOT EXISTS conversation_files (
1303
1303
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -2464,10 +2464,21 @@ var ConversationScanner = class {
2464
2464
  dbPath;
2465
2465
  sidecarEnabled;
2466
2466
  engineInstance = null;
2467
+ // The most recent scan()'s promise while it is still running, or null when
2468
+ // idle. close() awaits it before closing the SQLite handle so a fire-and-
2469
+ // forget scan can't have its DB shut mid-indexAll ("database connection is
2470
+ // not open"). Tracks the latest scan only — concurrent scans on one instance
2471
+ // aren't a supported pattern (a single writer DB), and the latest resolving
2472
+ // implies earlier ones already settled in practice.
2473
+ inFlightScan = null;
2467
2474
  emitter = new EventEmitter();
2468
2475
  watcher = null;
2469
2476
  queue = null;
2470
2477
  periodicTimer = null;
2478
+ // The in-flight periodicReconcile() job, if one is mid-run. unwatch() awaits
2479
+ // it before dropping the queue/engine so its post-await engine.files access
2480
+ // can't hit a closed DB (the watch-mode half of Bug #4).
2481
+ inFlightReconcile = null;
2471
2482
  constructor(options) {
2472
2483
  this.conversationLRU = new LRUCache(options?.conversationCacheSize ?? 5);
2473
2484
  if (options?.persistent === false) {
@@ -2490,14 +2501,31 @@ var ConversationScanner = class {
2490
2501
  return this.engineInstance;
2491
2502
  }
2492
2503
  // Release the SQLite connection. No-op in legacy mode. Safe to call
2493
- // repeatedly. Stops the watcher first if one is running; call unwatch()
2494
- // explicitly beforehand if you need to await watcher teardown.
2495
- close() {
2496
- if (this.watcher || this.queue || this.periodicTimer) void this.unwatch();
2504
+ // repeatedly. Awaits watcher teardown AND any in-flight scan before closing
2505
+ // the DB handle, so a fire-and-forget scan() can never have its connection
2506
+ // shut mid-indexAll() ("database connection is not open"). This is why close()
2507
+ // is async callers should await it during shutdown. (Scanner review Bug #4.)
2508
+ async close() {
2509
+ if (this.watcher || this.queue || this.periodicTimer) await this.unwatch();
2510
+ if (this.inFlightScan) {
2511
+ try {
2512
+ await this.inFlightScan;
2513
+ } catch {
2514
+ }
2515
+ }
2497
2516
  this.engineInstance?.close();
2498
2517
  this.engineInstance = null;
2499
2518
  }
2500
2519
  async scan(options = {}) {
2520
+ const promise = this.runScan(options);
2521
+ this.inFlightScan = promise;
2522
+ try {
2523
+ return await promise;
2524
+ } finally {
2525
+ if (this.inFlightScan === promise) this.inFlightScan = null;
2526
+ }
2527
+ }
2528
+ async runScan(options) {
2501
2529
  const profiles = await this.resolveProfiles(options.profiles);
2502
2530
  const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);
2503
2531
  this.lastTier = resolveTier(options.tier ?? "standard", options.tiers);
@@ -2933,7 +2961,10 @@ var ConversationScanner = class {
2933
2961
  const periodicMs = options.periodicMs ?? 6e4;
2934
2962
  if (periodicMs > 0) {
2935
2963
  this.periodicTimer = setInterval(() => {
2936
- void this.periodicReconcile(activeProfiles);
2964
+ const job = this.periodicReconcile(activeProfiles).finally(() => {
2965
+ if (this.inFlightReconcile === job) this.inFlightReconcile = null;
2966
+ });
2967
+ this.inFlightReconcile = job;
2937
2968
  }, periodicMs);
2938
2969
  this.periodicTimer.unref?.();
2939
2970
  }
@@ -2977,6 +3008,9 @@ var ConversationScanner = class {
2977
3008
  clearInterval(this.periodicTimer);
2978
3009
  this.periodicTimer = null;
2979
3010
  }
3011
+ if (this.inFlightReconcile) {
3012
+ await this.inFlightReconcile;
3013
+ }
2980
3014
  if (this.watcher) {
2981
3015
  await this.watcher.stop();
2982
3016
  this.watcher = null;