sensemaking 0.24.5 → 0.24.6
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/cjs/lib/watch-notification.d.cts +1 -0
- package/dist/cjs/lib/watch-notification.d.ts +1 -0
- package/dist/cjs/lib/watch-notification.js +15 -0
- package/dist/cjs/lib/watch-notification.js.map +1 -0
- package/dist/cjs/store/duckdb/native.d.cts +2 -2
- package/dist/cjs/store/duckdb/native.d.ts +2 -2
- package/dist/cjs/store/duckdb/native.js +1 -1
- package/dist/cjs/store/duckdb/native.js.map +1 -1
- package/dist/cjs/watch.js +8 -8
- package/dist/cjs/watch.js.map +1 -1
- package/dist/esm/lib/watch-notification.d.ts +1 -0
- package/dist/esm/lib/watch-notification.js +4 -0
- package/dist/esm/lib/watch-notification.js.map +1 -0
- package/dist/esm/store/duckdb/native.d.ts +2 -2
- package/dist/esm/store/duckdb/native.js +1 -1
- package/dist/esm/store/duckdb/native.js.map +1 -1
- package/dist/esm/watch.js +2 -2
- package/dist/esm/watch.js.map +1 -1
- package/package.json +2 -2
- package/skills/sense-setup/references/store-benchmarks.md +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shouldReconcileWatchNotification(filename: string | Buffer | null): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shouldReconcileWatchNotification(filename: string | Buffer | null): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "shouldReconcileWatchNotification", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return shouldReconcileWatchNotification;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
var _typests = require("../config/types.js");
|
|
12
|
+
function shouldReconcileWatchNotification(filename) {
|
|
13
|
+
return typeof filename !== 'string' || !filename.startsWith(_typests.STATE_DIR);
|
|
14
|
+
}
|
|
15
|
+
/* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/lib/watch-notification.ts"],"sourcesContent":["import { STATE_DIR } from '../config/types.ts';\n\nexport function shouldReconcileWatchNotification(filename: string | Buffer | null): boolean {\n return typeof filename !== 'string' || !filename.startsWith(STATE_DIR);\n}\n"],"names":["shouldReconcileWatchNotification","filename","startsWith","STATE_DIR"],"mappings":";;;;+BAEgBA;;;eAAAA;;;uBAFU;AAEnB,SAASA,iCAAiCC,QAAgC;IAC/E,OAAO,OAAOA,aAAa,YAAY,CAACA,SAASC,UAAU,CAACC,kBAAS;AACvE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const DUCKDB_PACKAGE = "@duckdb/node-api";
|
|
2
|
-
export declare const DUCKDB_VERSION = "1.5.5-r.
|
|
3
|
-
export declare const DUCKDB_INSTALL_SPEC = "@duckdb/node-api@1.5.5-r.
|
|
2
|
+
export declare const DUCKDB_VERSION = "1.5.5-r.5";
|
|
3
|
+
export declare const DUCKDB_INSTALL_SPEC = "@duckdb/node-api@1.5.5-r.5";
|
|
4
4
|
export declare function duckdbApi(): Promise<typeof import('@duckdb/node-api')>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const DUCKDB_PACKAGE = "@duckdb/node-api";
|
|
2
|
-
export declare const DUCKDB_VERSION = "1.5.5-r.
|
|
3
|
-
export declare const DUCKDB_INSTALL_SPEC = "@duckdb/node-api@1.5.5-r.
|
|
2
|
+
export declare const DUCKDB_VERSION = "1.5.5-r.5";
|
|
3
|
+
export declare const DUCKDB_INSTALL_SPEC = "@duckdb/node-api@1.5.5-r.5";
|
|
4
4
|
export declare function duckdbApi(): Promise<typeof import('@duckdb/node-api')>;
|
|
@@ -24,7 +24,7 @@ _export(exports, {
|
|
|
24
24
|
});
|
|
25
25
|
var _nativets = require("../native.js");
|
|
26
26
|
var DUCKDB_PACKAGE = '@duckdb/node-api';
|
|
27
|
-
var DUCKDB_VERSION = '1.5.5-r.
|
|
27
|
+
var DUCKDB_VERSION = '1.5.5-r.5';
|
|
28
28
|
var DUCKDB_INSTALL_SPEC = "".concat(DUCKDB_PACKAGE, "@").concat(DUCKDB_VERSION);
|
|
29
29
|
var DUCKDB = {
|
|
30
30
|
store: 'duckdb',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/native.ts"],"sourcesContent":["import { loadOrInstall, type NativeDescriptor, packageNodeModules } from '../native.ts';\n\nexport const DUCKDB_PACKAGE = '@duckdb/node-api';\nexport const DUCKDB_VERSION = '1.5.5-r.
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/native.ts"],"sourcesContent":["import { loadOrInstall, type NativeDescriptor, packageNodeModules } from '../native.ts';\n\nexport const DUCKDB_PACKAGE = '@duckdb/node-api';\nexport const DUCKDB_VERSION = '1.5.5-r.5';\nexport const DUCKDB_INSTALL_SPEC = `${DUCKDB_PACKAGE}@${DUCKDB_VERSION}`;\n\nconst DUCKDB: NativeDescriptor = {\n store: 'duckdb',\n pkg: DUCKDB_PACKAGE,\n sizeHint: '~110MB',\n version: DUCKDB_VERSION,\n installSpec: DUCKDB_INSTALL_SPEC,\n};\n\nlet duckdbApiPromise: Promise<typeof import('@duckdb/node-api')> | undefined;\n\n// The one accessor every consumer of @duckdb/node-api must go through: after a fresh install in this process, Node keeps returning\n// the pre-install \"not found\" miss to any new bare-specifier lookup of the same path, so caching the resolved module here means only the first caller ever resolves it.\nexport function duckdbApi(): Promise<typeof import('@duckdb/node-api')> {\n if (!duckdbApiPromise) duckdbApiPromise = loadOrInstall<typeof import('@duckdb/node-api')>(DUCKDB, packageNodeModules());\n return duckdbApiPromise;\n}\n"],"names":["DUCKDB_INSTALL_SPEC","DUCKDB_PACKAGE","DUCKDB_VERSION","duckdbApi","DUCKDB","store","pkg","sizeHint","version","installSpec","duckdbApiPromise","loadOrInstall","packageNodeModules"],"mappings":";;;;;;;;;;;QAIaA;eAAAA;;QAFAC;eAAAA;;QACAC;eAAAA;;QAeGC;eAAAA;;;wBAlByD;AAElE,IAAMF,iBAAiB;AACvB,IAAMC,iBAAiB;AACvB,IAAMF,sBAAsB,AAAC,GAAoBE,OAAlBD,gBAAe,KAAkB,OAAfC;AAExD,IAAME,SAA2B;IAC/BC,OAAO;IACPC,KAAKL;IACLM,UAAU;IACVC,SAASN;IACTO,aAAaT;AACf;AAEA,IAAIU;AAIG,SAASP;IACd,IAAI,CAACO,kBAAkBA,mBAAmBC,IAAAA,uBAAa,EAAoCP,QAAQQ,IAAAA,4BAAkB;IACrH,OAAOF;AACT"}
|
package/dist/cjs/watch.js
CHANGED
|
@@ -9,10 +9,10 @@ Object.defineProperty(exports, "runWatch", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
var _nodefs = require("node:fs");
|
|
12
|
-
var _indexts = require("./config/index.js");
|
|
13
12
|
var _errorsts = require("./errors.js");
|
|
14
13
|
var _guardedtickts = require("./lib/guarded-tick.js");
|
|
15
|
-
var
|
|
14
|
+
var _watchnotificationts = require("./lib/watch-notification.js");
|
|
15
|
+
var _indexts = require("./store/index.js");
|
|
16
16
|
var _watchclaimts = require("./watch-claim.js");
|
|
17
17
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
18
18
|
try {
|
|
@@ -188,7 +188,7 @@ function runWatch(_0) {
|
|
|
188
188
|
]);
|
|
189
189
|
return [
|
|
190
190
|
4,
|
|
191
|
-
(0,
|
|
191
|
+
(0, _indexts.openStore)(cfg)
|
|
192
192
|
];
|
|
193
193
|
case 3:
|
|
194
194
|
_ref = _state.sent(), initialStore = _ref.store, dbPath = _ref.dbPath, initialWarnings = _ref.warnings, initialParsed = _ref.parsed;
|
|
@@ -207,7 +207,7 @@ function runWatch(_0) {
|
|
|
207
207
|
];
|
|
208
208
|
return [
|
|
209
209
|
4,
|
|
210
|
-
(0,
|
|
210
|
+
(0, _indexts.docCount)(initialStore)
|
|
211
211
|
];
|
|
212
212
|
case 5:
|
|
213
213
|
_tmp = _state.sent();
|
|
@@ -256,7 +256,7 @@ function runWatch(_0) {
|
|
|
256
256
|
]);
|
|
257
257
|
return [
|
|
258
258
|
4,
|
|
259
|
-
(0,
|
|
259
|
+
(0, _indexts.openStore)(cfg)
|
|
260
260
|
];
|
|
261
261
|
case 1:
|
|
262
262
|
_ref = _state.sent(), store = _ref.store, parsed = _ref.parsed, warnings = _ref.warnings;
|
|
@@ -278,7 +278,7 @@ function runWatch(_0) {
|
|
|
278
278
|
};
|
|
279
279
|
return [
|
|
280
280
|
4,
|
|
281
|
-
(0,
|
|
281
|
+
(0, _indexts.docCount)(store)
|
|
282
282
|
];
|
|
283
283
|
case 3:
|
|
284
284
|
onEvent.apply(void 0, [
|
|
@@ -354,7 +354,7 @@ function runWatch(_0) {
|
|
|
354
354
|
watcher = (0, _nodefs.watch)(rootDir, {
|
|
355
355
|
recursive: true
|
|
356
356
|
}, function(_event, filename) {
|
|
357
|
-
if (
|
|
357
|
+
if (!(0, _watchnotificationts.shouldReconcileWatchNotification)(filename)) return;
|
|
358
358
|
scheduleReconcile();
|
|
359
359
|
});
|
|
360
360
|
reconcileTimer = setInterval((0, _guardedtickts.guardedTick)(function() {
|
|
@@ -410,7 +410,7 @@ function runWatch(_0) {
|
|
|
410
410
|
];
|
|
411
411
|
return [
|
|
412
412
|
4,
|
|
413
|
-
(0,
|
|
413
|
+
(0, _indexts.openStore)(cfg)
|
|
414
414
|
];
|
|
415
415
|
case 3:
|
|
416
416
|
_ref = _state.sent(), finalStore = _ref.store;
|
package/dist/cjs/watch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { STATE_DIR } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { docCount, openStore } from './store/index.ts';\nimport { startWatchClaim, WATCH_HEARTBEAT_INTERVAL_MS } from './watch-claim.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\n\nexport type WatchEvent = { type: 'started'; rootDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. DuckDB and Turso hand the cache file to\n// one process at a time, so every reconcile cycle opens and closes instead of holding an idle lock.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const reconcileIntervalMs = opts.heartbeatIntervalMs ?? WATCH_HEARTBEAT_INTERVAL_MS;\n if (!Number.isFinite(reconcileIntervalMs) || reconcileIntervalMs <= 0) throw new SenseError('CONFIG_INVALID', 'watch heartbeatIntervalMs must be a finite positive number');\n // Reconciliation may be parked for a caller, but live ownership must renew before fixed expiry.\n const claimIntervalMs = Math.min(reconcileIntervalMs, WATCH_HEARTBEAT_INTERVAL_MS);\n const rootDir = cfg.rootDir ?? cfg.baseDir;\n const configDir = cfg.configDir ?? rootDir;\n let claimFailure: Error | null = null;\n let requestShutdown: ((reason: Error) => void) | undefined;\n const claim = await startWatchClaim(configDir, opts.force ?? false, claimIntervalMs, (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n let claimCloseStarted = false;\n const closeClaim = (): Promise<void> => {\n claimCloseStarted = true;\n return claim.close();\n };\n try {\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg);\n let initialTotal = 0;\n try {\n // total is read before close: an event fires only once nothing is held open again.\n initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n } finally {\n await initialStore.close();\n }\n if (claimFailure) throw claimFailure;\n\n let stopping = false;\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg);\n try {\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n\n // Ignore our own state files, or cache and claim writes would retrigger reconciliation forever.\n // An unresolvable filename reconciles because parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(rootDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const reconcileTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n reconcileIntervalMs\n );\n\n return await new Promise<void>((resolveShutdown, rejectShutdown) => {\n let shutdownReason: Error | null = null;\n const shutdown = (reason?: Error) => {\n shutdownReason ??= reason ?? null;\n if (stopping) return;\n stopping = true;\n const shutdownTask = (async () => {\n process.off('SIGINT', stopNormally);\n process.off('SIGTERM', stopNormally);\n opts.signal?.removeEventListener('abort', stopNormally);\n clearInterval(reconcileTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await running;\n let finalError: unknown;\n try {\n // A normal stop performs one final freshness pass. Ownership loss drains existing work\n // but starts nothing new because the replacement watcher now owns that responsibility.\n if (!shutdownReason) {\n const { store: finalStore } = await openStore(cfg);\n await finalStore.close();\n }\n } catch (err) {\n finalError = err;\n }\n try {\n await closeClaim();\n } catch (err) {\n if (finalError) throw new AggregateError([finalError, err], 'watch shutdown and claim cleanup both failed');\n throw err;\n }\n if (finalError) throw finalError;\n if (shutdownReason) throw shutdownReason;\n })();\n shutdownTask.then(resolveShutdown, rejectShutdown);\n };\n const stopNormally = () => shutdown();\n requestShutdown = shutdown;\n process.once('SIGINT', stopNormally);\n process.once('SIGTERM', stopNormally);\n if (claimFailure) shutdown(claimFailure);\n else if (opts.signal?.aborted) shutdown();\n else {\n opts.signal?.addEventListener('abort', stopNormally, { once: true });\n try {\n onEvent({ type: 'started', rootDir, dbPath });\n if (!stopping && (initialWarnings.length > 0 || initialParsed > 0)) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n }\n } catch (err) {\n shutdown(err instanceof Error ? err : new Error(String(err)));\n }\n }\n });\n } catch (err) {\n if (claimCloseStarted) throw err;\n try {\n await closeClaim();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch setup and claim cleanup both failed');\n }\n throw err;\n } finally {\n requestShutdown = undefined;\n }\n}\n"],"names":["runWatch","DEBOUNCE_MS","cfg","opts","onEvent","debounceMs","reconcileIntervalMs","claimIntervalMs","rootDir","configDir","claimFailure","requestShutdown","claim","claimCloseStarted","closeClaim","initialStore","dbPath","initialWarnings","initialParsed","initialTotal","stopping","running","queuedAlways","tick","startCycle","requestCycle","debounceTimer","scheduleReconcile","watcher","reconcileTimer","err","closeError","heartbeatIntervalMs","WATCH_HEARTBEAT_INTERVAL_MS","Number","isFinite","SenseError","Math","min","baseDir","startWatchClaim","force","close","openStore","store","warnings","parsed","length","docCount","alwaysEmit","type","total","message","then","again","clearTimeout","setTimeout","fsWatch","recursive","_event","filename","startsWith","STATE_DIR","setInterval","guardedTick","Promise","resolveShutdown","rejectShutdown","shutdownReason","shutdown","reason","shutdownTask","finalError","finalStore","process","off","stopNormally","signal","removeEventListener","clearInterval","AggregateError","once","aborted","addEventListener","Error","String","undefined"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { shouldReconcileWatchNotification } from './lib/watch-notification.ts';\nimport { docCount, openStore } from './store/index.ts';\nimport { startWatchClaim, WATCH_HEARTBEAT_INTERVAL_MS } from './watch-claim.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\n\nexport type WatchEvent = { type: 'started'; rootDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. DuckDB and Turso hand the cache file to\n// one process at a time, so every reconcile cycle opens and closes instead of holding an idle lock.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const reconcileIntervalMs = opts.heartbeatIntervalMs ?? WATCH_HEARTBEAT_INTERVAL_MS;\n if (!Number.isFinite(reconcileIntervalMs) || reconcileIntervalMs <= 0) throw new SenseError('CONFIG_INVALID', 'watch heartbeatIntervalMs must be a finite positive number');\n // Reconciliation may be parked for a caller, but live ownership must renew before fixed expiry.\n const claimIntervalMs = Math.min(reconcileIntervalMs, WATCH_HEARTBEAT_INTERVAL_MS);\n const rootDir = cfg.rootDir ?? cfg.baseDir;\n const configDir = cfg.configDir ?? rootDir;\n let claimFailure: Error | null = null;\n let requestShutdown: ((reason: Error) => void) | undefined;\n const claim = await startWatchClaim(configDir, opts.force ?? false, claimIntervalMs, (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n let claimCloseStarted = false;\n const closeClaim = (): Promise<void> => {\n claimCloseStarted = true;\n return claim.close();\n };\n try {\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg);\n let initialTotal = 0;\n try {\n // total is read before close: an event fires only once nothing is held open again.\n initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n } finally {\n await initialStore.close();\n }\n if (claimFailure) throw claimFailure;\n\n let stopping = false;\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg);\n try {\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n\n // Ignore our own state files, or cache and claim writes would retrigger reconciliation forever.\n // An unresolvable filename reconciles because parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(rootDir, { recursive: true }, (_event, filename) => {\n if (!shouldReconcileWatchNotification(filename)) return;\n scheduleReconcile();\n });\n const reconcileTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n reconcileIntervalMs\n );\n\n return await new Promise<void>((resolveShutdown, rejectShutdown) => {\n let shutdownReason: Error | null = null;\n const shutdown = (reason?: Error) => {\n shutdownReason ??= reason ?? null;\n if (stopping) return;\n stopping = true;\n const shutdownTask = (async () => {\n process.off('SIGINT', stopNormally);\n process.off('SIGTERM', stopNormally);\n opts.signal?.removeEventListener('abort', stopNormally);\n clearInterval(reconcileTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await running;\n let finalError: unknown;\n try {\n // A normal stop performs one final freshness pass. Ownership loss drains existing work\n // but starts nothing new because the replacement watcher now owns that responsibility.\n if (!shutdownReason) {\n const { store: finalStore } = await openStore(cfg);\n await finalStore.close();\n }\n } catch (err) {\n finalError = err;\n }\n try {\n await closeClaim();\n } catch (err) {\n if (finalError) throw new AggregateError([finalError, err], 'watch shutdown and claim cleanup both failed');\n throw err;\n }\n if (finalError) throw finalError;\n if (shutdownReason) throw shutdownReason;\n })();\n shutdownTask.then(resolveShutdown, rejectShutdown);\n };\n const stopNormally = () => shutdown();\n requestShutdown = shutdown;\n process.once('SIGINT', stopNormally);\n process.once('SIGTERM', stopNormally);\n if (claimFailure) shutdown(claimFailure);\n else if (opts.signal?.aborted) shutdown();\n else {\n opts.signal?.addEventListener('abort', stopNormally, { once: true });\n try {\n onEvent({ type: 'started', rootDir, dbPath });\n if (!stopping && (initialWarnings.length > 0 || initialParsed > 0)) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n }\n } catch (err) {\n shutdown(err instanceof Error ? err : new Error(String(err)));\n }\n }\n });\n } catch (err) {\n if (claimCloseStarted) throw err;\n try {\n await closeClaim();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch setup and claim cleanup both failed');\n }\n throw err;\n } finally {\n requestShutdown = undefined;\n }\n}\n"],"names":["runWatch","DEBOUNCE_MS","cfg","opts","onEvent","debounceMs","reconcileIntervalMs","claimIntervalMs","rootDir","configDir","claimFailure","requestShutdown","claim","claimCloseStarted","closeClaim","initialStore","dbPath","initialWarnings","initialParsed","initialTotal","stopping","running","queuedAlways","tick","startCycle","requestCycle","debounceTimer","scheduleReconcile","watcher","reconcileTimer","err","closeError","heartbeatIntervalMs","WATCH_HEARTBEAT_INTERVAL_MS","Number","isFinite","SenseError","Math","min","baseDir","startWatchClaim","force","close","openStore","store","warnings","parsed","length","docCount","alwaysEmit","type","total","message","then","again","clearTimeout","setTimeout","fsWatch","recursive","_event","filename","shouldReconcileWatchNotification","setInterval","guardedTick","Promise","resolveShutdown","rejectShutdown","shutdownReason","shutdown","reason","shutdownTask","finalError","finalStore","process","off","stopNormally","signal","removeEventListener","clearInterval","AggregateError","once","aborted","addEventListener","Error","String","undefined"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shouldReconcileWatchNotification(filename: string | Buffer | null): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/lib/watch-notification.ts"],"sourcesContent":["import { STATE_DIR } from '../config/types.ts';\n\nexport function shouldReconcileWatchNotification(filename: string | Buffer | null): boolean {\n return typeof filename !== 'string' || !filename.startsWith(STATE_DIR);\n}\n"],"names":["STATE_DIR","shouldReconcileWatchNotification","filename","startsWith"],"mappings":"AAAA,SAASA,SAAS,QAAQ,qBAAqB;AAE/C,OAAO,SAASC,iCAAiCC,QAAgC;IAC/E,OAAO,OAAOA,aAAa,YAAY,CAACA,SAASC,UAAU,CAACH;AAC9D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const DUCKDB_PACKAGE = "@duckdb/node-api";
|
|
2
|
-
export declare const DUCKDB_VERSION = "1.5.5-r.
|
|
3
|
-
export declare const DUCKDB_INSTALL_SPEC = "@duckdb/node-api@1.5.5-r.
|
|
2
|
+
export declare const DUCKDB_VERSION = "1.5.5-r.5";
|
|
3
|
+
export declare const DUCKDB_INSTALL_SPEC = "@duckdb/node-api@1.5.5-r.5";
|
|
4
4
|
export declare function duckdbApi(): Promise<typeof import('@duckdb/node-api')>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadOrInstall, packageNodeModules } from '../native.js';
|
|
2
2
|
export const DUCKDB_PACKAGE = '@duckdb/node-api';
|
|
3
|
-
export const DUCKDB_VERSION = '1.5.5-r.
|
|
3
|
+
export const DUCKDB_VERSION = '1.5.5-r.5';
|
|
4
4
|
export const DUCKDB_INSTALL_SPEC = `${DUCKDB_PACKAGE}@${DUCKDB_VERSION}`;
|
|
5
5
|
const DUCKDB = {
|
|
6
6
|
store: 'duckdb',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/native.ts"],"sourcesContent":["import { loadOrInstall, type NativeDescriptor, packageNodeModules } from '../native.ts';\n\nexport const DUCKDB_PACKAGE = '@duckdb/node-api';\nexport const DUCKDB_VERSION = '1.5.5-r.
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/native.ts"],"sourcesContent":["import { loadOrInstall, type NativeDescriptor, packageNodeModules } from '../native.ts';\n\nexport const DUCKDB_PACKAGE = '@duckdb/node-api';\nexport const DUCKDB_VERSION = '1.5.5-r.5';\nexport const DUCKDB_INSTALL_SPEC = `${DUCKDB_PACKAGE}@${DUCKDB_VERSION}`;\n\nconst DUCKDB: NativeDescriptor = {\n store: 'duckdb',\n pkg: DUCKDB_PACKAGE,\n sizeHint: '~110MB',\n version: DUCKDB_VERSION,\n installSpec: DUCKDB_INSTALL_SPEC,\n};\n\nlet duckdbApiPromise: Promise<typeof import('@duckdb/node-api')> | undefined;\n\n// The one accessor every consumer of @duckdb/node-api must go through: after a fresh install in this process, Node keeps returning\n// the pre-install \"not found\" miss to any new bare-specifier lookup of the same path, so caching the resolved module here means only the first caller ever resolves it.\nexport function duckdbApi(): Promise<typeof import('@duckdb/node-api')> {\n if (!duckdbApiPromise) duckdbApiPromise = loadOrInstall<typeof import('@duckdb/node-api')>(DUCKDB, packageNodeModules());\n return duckdbApiPromise;\n}\n"],"names":["loadOrInstall","packageNodeModules","DUCKDB_PACKAGE","DUCKDB_VERSION","DUCKDB_INSTALL_SPEC","DUCKDB","store","pkg","sizeHint","version","installSpec","duckdbApiPromise","duckdbApi"],"mappings":"AAAA,SAASA,aAAa,EAAyBC,kBAAkB,QAAQ,eAAe;AAExF,OAAO,MAAMC,iBAAiB,mBAAmB;AACjD,OAAO,MAAMC,iBAAiB,YAAY;AAC1C,OAAO,MAAMC,sBAAsB,GAAGF,eAAe,CAAC,EAAEC,gBAAgB,CAAC;AAEzE,MAAME,SAA2B;IAC/BC,OAAO;IACPC,KAAKL;IACLM,UAAU;IACVC,SAASN;IACTO,aAAaN;AACf;AAEA,IAAIO;AAEJ,mIAAmI;AACnI,wKAAwK;AACxK,OAAO,SAASC;IACd,IAAI,CAACD,kBAAkBA,mBAAmBX,cAAiDK,QAAQJ;IACnG,OAAOU;AACT"}
|
package/dist/esm/watch.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { watch as fsWatch } from 'node:fs';
|
|
2
|
-
import { STATE_DIR } from './config/index.js';
|
|
3
2
|
import { SenseError } from './errors.js';
|
|
4
3
|
import { guardedTick } from './lib/guarded-tick.js';
|
|
4
|
+
import { shouldReconcileWatchNotification } from './lib/watch-notification.js';
|
|
5
5
|
import { docCount, openStore } from './store/index.js';
|
|
6
6
|
import { startWatchClaim, WATCH_HEARTBEAT_INTERVAL_MS } from './watch-claim.js';
|
|
7
7
|
// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.
|
|
@@ -97,7 +97,7 @@ export async function runWatch(cfg, opts = {}) {
|
|
|
97
97
|
const watcher = fsWatch(rootDir, {
|
|
98
98
|
recursive: true
|
|
99
99
|
}, (_event, filename)=>{
|
|
100
|
-
if (
|
|
100
|
+
if (!shouldReconcileWatchNotification(filename)) return;
|
|
101
101
|
scheduleReconcile();
|
|
102
102
|
});
|
|
103
103
|
const reconcileTimer = setInterval(guardedTick(async ()=>requestCycle(false), ()=>stopping), reconcileIntervalMs);
|
package/dist/esm/watch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { STATE_DIR } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { docCount, openStore } from './store/index.ts';\nimport { startWatchClaim, WATCH_HEARTBEAT_INTERVAL_MS } from './watch-claim.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\n\nexport type WatchEvent = { type: 'started'; rootDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. DuckDB and Turso hand the cache file to\n// one process at a time, so every reconcile cycle opens and closes instead of holding an idle lock.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const reconcileIntervalMs = opts.heartbeatIntervalMs ?? WATCH_HEARTBEAT_INTERVAL_MS;\n if (!Number.isFinite(reconcileIntervalMs) || reconcileIntervalMs <= 0) throw new SenseError('CONFIG_INVALID', 'watch heartbeatIntervalMs must be a finite positive number');\n // Reconciliation may be parked for a caller, but live ownership must renew before fixed expiry.\n const claimIntervalMs = Math.min(reconcileIntervalMs, WATCH_HEARTBEAT_INTERVAL_MS);\n const rootDir = cfg.rootDir ?? cfg.baseDir;\n const configDir = cfg.configDir ?? rootDir;\n let claimFailure: Error | null = null;\n let requestShutdown: ((reason: Error) => void) | undefined;\n const claim = await startWatchClaim(configDir, opts.force ?? false, claimIntervalMs, (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n let claimCloseStarted = false;\n const closeClaim = (): Promise<void> => {\n claimCloseStarted = true;\n return claim.close();\n };\n try {\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg);\n let initialTotal = 0;\n try {\n // total is read before close: an event fires only once nothing is held open again.\n initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n } finally {\n await initialStore.close();\n }\n if (claimFailure) throw claimFailure;\n\n let stopping = false;\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg);\n try {\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n\n // Ignore our own state files, or cache and claim writes would retrigger reconciliation forever.\n // An unresolvable filename reconciles because parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(rootDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const reconcileTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n reconcileIntervalMs\n );\n\n return await new Promise<void>((resolveShutdown, rejectShutdown) => {\n let shutdownReason: Error | null = null;\n const shutdown = (reason?: Error) => {\n shutdownReason ??= reason ?? null;\n if (stopping) return;\n stopping = true;\n const shutdownTask = (async () => {\n process.off('SIGINT', stopNormally);\n process.off('SIGTERM', stopNormally);\n opts.signal?.removeEventListener('abort', stopNormally);\n clearInterval(reconcileTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await running;\n let finalError: unknown;\n try {\n // A normal stop performs one final freshness pass. Ownership loss drains existing work\n // but starts nothing new because the replacement watcher now owns that responsibility.\n if (!shutdownReason) {\n const { store: finalStore } = await openStore(cfg);\n await finalStore.close();\n }\n } catch (err) {\n finalError = err;\n }\n try {\n await closeClaim();\n } catch (err) {\n if (finalError) throw new AggregateError([finalError, err], 'watch shutdown and claim cleanup both failed');\n throw err;\n }\n if (finalError) throw finalError;\n if (shutdownReason) throw shutdownReason;\n })();\n shutdownTask.then(resolveShutdown, rejectShutdown);\n };\n const stopNormally = () => shutdown();\n requestShutdown = shutdown;\n process.once('SIGINT', stopNormally);\n process.once('SIGTERM', stopNormally);\n if (claimFailure) shutdown(claimFailure);\n else if (opts.signal?.aborted) shutdown();\n else {\n opts.signal?.addEventListener('abort', stopNormally, { once: true });\n try {\n onEvent({ type: 'started', rootDir, dbPath });\n if (!stopping && (initialWarnings.length > 0 || initialParsed > 0)) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n }\n } catch (err) {\n shutdown(err instanceof Error ? err : new Error(String(err)));\n }\n }\n });\n } catch (err) {\n if (claimCloseStarted) throw err;\n try {\n await closeClaim();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch setup and claim cleanup both failed');\n }\n throw err;\n } finally {\n requestShutdown = undefined;\n }\n}\n"],"names":["watch","fsWatch","STATE_DIR","SenseError","guardedTick","docCount","openStore","startWatchClaim","WATCH_HEARTBEAT_INTERVAL_MS","DEBOUNCE_MS","runWatch","cfg","opts","onEvent","debounceMs","reconcileIntervalMs","heartbeatIntervalMs","Number","isFinite","claimIntervalMs","Math","min","rootDir","baseDir","configDir","claimFailure","requestShutdown","claim","force","err","claimCloseStarted","closeClaim","close","store","initialStore","dbPath","warnings","initialWarnings","parsed","initialParsed","initialTotal","length","stopping","running","queuedAlways","tick","alwaysEmit","type","total","message","startCycle","then","again","requestCycle","debounceTimer","scheduleReconcile","clearTimeout","setTimeout","watcher","recursive","_event","filename","startsWith","reconcileTimer","setInterval","Promise","resolveShutdown","rejectShutdown","shutdownReason","shutdown","reason","shutdownTask","process","off","stopNormally","signal","removeEventListener","clearInterval","finalError","finalStore","AggregateError","once","aborted","addEventListener","Error","String","closeError","undefined"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { shouldReconcileWatchNotification } from './lib/watch-notification.ts';\nimport { docCount, openStore } from './store/index.ts';\nimport { startWatchClaim, WATCH_HEARTBEAT_INTERVAL_MS } from './watch-claim.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\n\nexport type WatchEvent = { type: 'started'; rootDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. DuckDB and Turso hand the cache file to\n// one process at a time, so every reconcile cycle opens and closes instead of holding an idle lock.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const reconcileIntervalMs = opts.heartbeatIntervalMs ?? WATCH_HEARTBEAT_INTERVAL_MS;\n if (!Number.isFinite(reconcileIntervalMs) || reconcileIntervalMs <= 0) throw new SenseError('CONFIG_INVALID', 'watch heartbeatIntervalMs must be a finite positive number');\n // Reconciliation may be parked for a caller, but live ownership must renew before fixed expiry.\n const claimIntervalMs = Math.min(reconcileIntervalMs, WATCH_HEARTBEAT_INTERVAL_MS);\n const rootDir = cfg.rootDir ?? cfg.baseDir;\n const configDir = cfg.configDir ?? rootDir;\n let claimFailure: Error | null = null;\n let requestShutdown: ((reason: Error) => void) | undefined;\n const claim = await startWatchClaim(configDir, opts.force ?? false, claimIntervalMs, (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n let claimCloseStarted = false;\n const closeClaim = (): Promise<void> => {\n claimCloseStarted = true;\n return claim.close();\n };\n try {\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg);\n let initialTotal = 0;\n try {\n // total is read before close: an event fires only once nothing is held open again.\n initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n } finally {\n await initialStore.close();\n }\n if (claimFailure) throw claimFailure;\n\n let stopping = false;\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg);\n try {\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n\n // Ignore our own state files, or cache and claim writes would retrigger reconciliation forever.\n // An unresolvable filename reconciles because parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(rootDir, { recursive: true }, (_event, filename) => {\n if (!shouldReconcileWatchNotification(filename)) return;\n scheduleReconcile();\n });\n const reconcileTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n reconcileIntervalMs\n );\n\n return await new Promise<void>((resolveShutdown, rejectShutdown) => {\n let shutdownReason: Error | null = null;\n const shutdown = (reason?: Error) => {\n shutdownReason ??= reason ?? null;\n if (stopping) return;\n stopping = true;\n const shutdownTask = (async () => {\n process.off('SIGINT', stopNormally);\n process.off('SIGTERM', stopNormally);\n opts.signal?.removeEventListener('abort', stopNormally);\n clearInterval(reconcileTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await running;\n let finalError: unknown;\n try {\n // A normal stop performs one final freshness pass. Ownership loss drains existing work\n // but starts nothing new because the replacement watcher now owns that responsibility.\n if (!shutdownReason) {\n const { store: finalStore } = await openStore(cfg);\n await finalStore.close();\n }\n } catch (err) {\n finalError = err;\n }\n try {\n await closeClaim();\n } catch (err) {\n if (finalError) throw new AggregateError([finalError, err], 'watch shutdown and claim cleanup both failed');\n throw err;\n }\n if (finalError) throw finalError;\n if (shutdownReason) throw shutdownReason;\n })();\n shutdownTask.then(resolveShutdown, rejectShutdown);\n };\n const stopNormally = () => shutdown();\n requestShutdown = shutdown;\n process.once('SIGINT', stopNormally);\n process.once('SIGTERM', stopNormally);\n if (claimFailure) shutdown(claimFailure);\n else if (opts.signal?.aborted) shutdown();\n else {\n opts.signal?.addEventListener('abort', stopNormally, { once: true });\n try {\n onEvent({ type: 'started', rootDir, dbPath });\n if (!stopping && (initialWarnings.length > 0 || initialParsed > 0)) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n }\n } catch (err) {\n shutdown(err instanceof Error ? err : new Error(String(err)));\n }\n }\n });\n } catch (err) {\n if (claimCloseStarted) throw err;\n try {\n await closeClaim();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch setup and claim cleanup both failed');\n }\n throw err;\n } finally {\n requestShutdown = undefined;\n }\n}\n"],"names":["watch","fsWatch","SenseError","guardedTick","shouldReconcileWatchNotification","docCount","openStore","startWatchClaim","WATCH_HEARTBEAT_INTERVAL_MS","DEBOUNCE_MS","runWatch","cfg","opts","onEvent","debounceMs","reconcileIntervalMs","heartbeatIntervalMs","Number","isFinite","claimIntervalMs","Math","min","rootDir","baseDir","configDir","claimFailure","requestShutdown","claim","force","err","claimCloseStarted","closeClaim","close","store","initialStore","dbPath","warnings","initialWarnings","parsed","initialParsed","initialTotal","length","stopping","running","queuedAlways","tick","alwaysEmit","type","total","message","startCycle","then","again","requestCycle","debounceTimer","scheduleReconcile","clearTimeout","setTimeout","watcher","recursive","_event","filename","reconcileTimer","setInterval","Promise","resolveShutdown","rejectShutdown","shutdownReason","shutdown","reason","shutdownTask","process","off","stopNormally","signal","removeEventListener","clearInterval","finalError","finalStore","AggregateError","once","aborted","addEventListener","Error","String","closeError","undefined"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sensemaking",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.6",
|
|
4
4
|
"description": "Query and search your markdown notes with context-aware progressive disclosure: SQL over frontmatter, links, and text, plus semantic search and link-graph ranking. No server, no build step.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"yaml": "^2.9.0"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
|
-
"@duckdb/node-api": "1.5.5-r.
|
|
96
|
+
"@duckdb/node-api": "1.5.5-r.5",
|
|
97
97
|
"@tursodatabase/database": "*",
|
|
98
98
|
"@types/markdown-it-footnote": "^3.0.4",
|
|
99
99
|
"@types/mocha": "*",
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
<!-- sense-store-benchmark release=0.24.
|
|
1
|
+
<!-- sense-store-benchmark release=0.24.6 -->
|
|
2
2
|
# Current store benchmark summary
|
|
3
3
|
|
|
4
|
-
The release assessment generated this file for store selection. Release `0.24.
|
|
4
|
+
The release assessment generated this file for store selection. Release `0.24.6` passed on 2026-09-16, measured on Apple M4 Pro with Node v26.7.0.
|
|
5
5
|
|
|
6
6
|
The timing rows ran on the same 6,566-note tree. They include CLI startup and each store's complete selected path. Ranked candidates and downstream work can differ by store, so these are current operating measurements rather than an isolated database-engine contest.
|
|
7
7
|
|
|
8
8
|
| Store | Cold index | Warm count | Lexical search | Semantic search | Portable semantic nDCG@10 |
|
|
9
9
|
|---|---|---|---|---|---|
|
|
10
|
-
| sqlite | 1,
|
|
11
|
-
| duckdb | 2,
|
|
12
|
-
| turso | 2,
|
|
10
|
+
| sqlite | 1,471 ms | 143 ms | 192 ms | 318 ms | 0.3306 |
|
|
11
|
+
| duckdb | 2,149 ms | 162 ms | 279 ms | 399 ms | 0.3284 |
|
|
12
|
+
| turso | 2,727 ms | 138 ms | 237 ms | 584 ms | 0.3307 |
|
|
13
13
|
|
|
14
14
|
Cold index is the first `status` that builds the cache. Warm count is a no-change `COUNT(*)` query. Lexical and semantic search are steady-state `sense search` commands. Lower timing is faster. Higher nDCG@10 is better; that quality column uses the same NFCorpus queries, judgments, result count, and model on every store.
|
|
15
15
|
|