sensemaking 0.1.0
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/LICENSE +21 -0
- package/README.md +132 -0
- package/bin/cli.js +5 -0
- package/dist/cjs/cli.d.cts +1 -0
- package/dist/cjs/cli.d.ts +1 -0
- package/dist/cjs/cli.js +422 -0
- package/dist/cjs/cli.js.map +1 -0
- package/dist/cjs/config.d.cts +18 -0
- package/dist/cjs/config.d.ts +18 -0
- package/dist/cjs/config.js +147 -0
- package/dist/cjs/config.js.map +1 -0
- package/dist/cjs/db.d.cts +20 -0
- package/dist/cjs/db.d.ts +20 -0
- package/dist/cjs/db.js +326 -0
- package/dist/cjs/db.js.map +1 -0
- package/dist/cjs/errors.d.cts +5 -0
- package/dist/cjs/errors.d.ts +5 -0
- package/dist/cjs/errors.js +134 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.d.cts +12 -0
- package/dist/cjs/index.d.ts +12 -0
- package/dist/cjs/index.js +76 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/output.d.cts +2 -0
- package/dist/cjs/output.d.ts +2 -0
- package/dist/cjs/output.js +90 -0
- package/dist/cjs/output.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/scan.d.cts +18 -0
- package/dist/cjs/scan.d.ts +18 -0
- package/dist/cjs/scan.js +113 -0
- package/dist/cjs/scan.js.map +1 -0
- package/dist/cjs/watch.d.cts +19 -0
- package/dist/cjs/watch.d.ts +19 -0
- package/dist/cjs/watch.js +237 -0
- package/dist/cjs/watch.js.map +1 -0
- package/dist/esm/cli.d.ts +1 -0
- package/dist/esm/cli.js +171 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/config.d.ts +18 -0
- package/dist/esm/config.js +78 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/db.d.ts +20 -0
- package/dist/esm/db.js +176 -0
- package/dist/esm/db.js.map +1 -0
- package/dist/esm/errors.d.ts +5 -0
- package/dist/esm/errors.js +10 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/output.d.ts +2 -0
- package/dist/esm/output.js +25 -0
- package/dist/esm/output.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/scan.d.ts +18 -0
- package/dist/esm/scan.js +69 -0
- package/dist/esm/scan.js.map +1 -0
- package/dist/esm/watch.d.ts +19 -0
- package/dist/esm/watch.js +93 -0
- package/dist/esm/watch.js.map +1 -0
- package/package.json +63 -0
- package/schema.json +38 -0
- package/skills/sense/SKILL.md +95 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "runWatch", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return runWatch;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
var _nodefs = require("node:fs");
|
|
12
|
+
var _configts = require("./config.js");
|
|
13
|
+
var _dbts = require("./db.js");
|
|
14
|
+
var _errorsts = require("./errors.js");
|
|
15
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
16
|
+
try {
|
|
17
|
+
var info = gen[key](arg);
|
|
18
|
+
var value = info.value;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
reject(error);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (info.done) {
|
|
24
|
+
resolve(value);
|
|
25
|
+
} else {
|
|
26
|
+
Promise.resolve(value).then(_next, _throw);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function _async_to_generator(fn) {
|
|
30
|
+
return function() {
|
|
31
|
+
var self = this, args = arguments;
|
|
32
|
+
return new Promise(function(resolve, reject) {
|
|
33
|
+
var gen = fn.apply(self, args);
|
|
34
|
+
function _next(value) {
|
|
35
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
36
|
+
}
|
|
37
|
+
function _throw(err) {
|
|
38
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
39
|
+
}
|
|
40
|
+
_next(undefined);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function _ts_generator(thisArg, body) {
|
|
45
|
+
var f, y, t, _ = {
|
|
46
|
+
label: 0,
|
|
47
|
+
sent: function() {
|
|
48
|
+
if (t[0] & 1) throw t[1];
|
|
49
|
+
return t[1];
|
|
50
|
+
},
|
|
51
|
+
trys: [],
|
|
52
|
+
ops: []
|
|
53
|
+
}, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
|
|
54
|
+
return d(g, "next", {
|
|
55
|
+
value: verb(0)
|
|
56
|
+
}), d(g, "throw", {
|
|
57
|
+
value: verb(1)
|
|
58
|
+
}), d(g, "return", {
|
|
59
|
+
value: verb(2)
|
|
60
|
+
}), typeof Symbol === "function" && d(g, Symbol.iterator, {
|
|
61
|
+
value: function() {
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
}), g;
|
|
65
|
+
function verb(n) {
|
|
66
|
+
return function(v) {
|
|
67
|
+
return step([
|
|
68
|
+
n,
|
|
69
|
+
v
|
|
70
|
+
]);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function step(op) {
|
|
74
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
75
|
+
while(g && (g = 0, op[0] && (_ = 0)), _)try {
|
|
76
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
77
|
+
if (y = 0, t) op = [
|
|
78
|
+
op[0] & 2,
|
|
79
|
+
t.value
|
|
80
|
+
];
|
|
81
|
+
switch(op[0]){
|
|
82
|
+
case 0:
|
|
83
|
+
case 1:
|
|
84
|
+
t = op;
|
|
85
|
+
break;
|
|
86
|
+
case 4:
|
|
87
|
+
_.label++;
|
|
88
|
+
return {
|
|
89
|
+
value: op[1],
|
|
90
|
+
done: false
|
|
91
|
+
};
|
|
92
|
+
case 5:
|
|
93
|
+
_.label++;
|
|
94
|
+
y = op[1];
|
|
95
|
+
op = [
|
|
96
|
+
0
|
|
97
|
+
];
|
|
98
|
+
continue;
|
|
99
|
+
case 7:
|
|
100
|
+
op = _.ops.pop();
|
|
101
|
+
_.trys.pop();
|
|
102
|
+
continue;
|
|
103
|
+
default:
|
|
104
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
|
|
105
|
+
_ = 0;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
|
|
109
|
+
_.label = op[1];
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (op[0] === 6 && _.label < t[1]) {
|
|
113
|
+
_.label = t[1];
|
|
114
|
+
t = op;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
if (t && _.label < t[2]) {
|
|
118
|
+
_.label = t[2];
|
|
119
|
+
_.ops.push(op);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
if (t[2]) _.ops.pop();
|
|
123
|
+
_.trys.pop();
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
op = body.call(thisArg, _);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
op = [
|
|
129
|
+
6,
|
|
130
|
+
e
|
|
131
|
+
];
|
|
132
|
+
y = 0;
|
|
133
|
+
} finally{
|
|
134
|
+
f = t = 0;
|
|
135
|
+
}
|
|
136
|
+
if (op[0] & 5) throw op[1];
|
|
137
|
+
return {
|
|
138
|
+
value: op[0] ? op[1] : void 0,
|
|
139
|
+
done: true
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Watch is a cache pre-warmer, not a correctness mechanism: every query
|
|
144
|
+
// `open()` reconciles against the filesystem regardless, so a missed or
|
|
145
|
+
// coalesced fs event can never change a query result -- it only moves one
|
|
146
|
+
// file's parse from ahead-of-time to query-time. That's why any event, of
|
|
147
|
+
// any kind, triggers the exact same response: a debounced full reconcile.
|
|
148
|
+
// Per-file event handling would be complexity with no correctness payoff.
|
|
149
|
+
var DEBOUNCE_MS = 200;
|
|
150
|
+
var HEARTBEAT_INTERVAL_MS = 5000;
|
|
151
|
+
var STALE_HEARTBEAT_MS = 15000;
|
|
152
|
+
function runWatch(_0) {
|
|
153
|
+
return _async_to_generator(function(cfg) {
|
|
154
|
+
var opts, _opts_onEvent, onEvent, _open, db, dbPath, initialWarnings, initialParsed, baseDir, existingHeartbeat, age, touchHeartbeat, debounceTimer, scheduleReconcile, watcher, heartbeatTimer;
|
|
155
|
+
var _arguments = arguments;
|
|
156
|
+
return _ts_generator(this, function(_state) {
|
|
157
|
+
opts = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
|
|
158
|
+
onEvent = (_opts_onEvent = opts.onEvent) !== null && _opts_onEvent !== void 0 ? _opts_onEvent : function() {};
|
|
159
|
+
_open = (0, _dbts.open)(cfg), db = _open.db, dbPath = _open.dbPath, initialWarnings = _open.warnings, initialParsed = _open.parsed;
|
|
160
|
+
baseDir = cfg.baseDir;
|
|
161
|
+
existingHeartbeat = (0, _dbts.getMeta)(db, 'watch_heartbeat');
|
|
162
|
+
if (existingHeartbeat && !opts.force) {
|
|
163
|
+
age = Date.now() - Date.parse(existingHeartbeat);
|
|
164
|
+
if (age >= 0 && age < STALE_HEARTBEAT_MS) {
|
|
165
|
+
db.close();
|
|
166
|
+
throw new _errorsts.SenseError('WATCH_ACTIVE', "another watcher appears active (heartbeat ".concat(Math.round(age / 1000), "s ago); use --force to override"));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
onEvent({
|
|
170
|
+
type: 'started',
|
|
171
|
+
baseDir: baseDir,
|
|
172
|
+
dbPath: dbPath
|
|
173
|
+
});
|
|
174
|
+
if (initialWarnings.length > 0 || initialParsed > 0) {
|
|
175
|
+
onEvent({
|
|
176
|
+
type: 'reconciled',
|
|
177
|
+
parsed: initialParsed,
|
|
178
|
+
total: (0, _dbts.docCount)(db),
|
|
179
|
+
warnings: initialWarnings
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
touchHeartbeat = function touchHeartbeat() {
|
|
183
|
+
(0, _dbts.setMeta)(db, 'watch_heartbeat', new Date().toISOString());
|
|
184
|
+
(0, _dbts.setMeta)(db, 'watch_pid', String(process.pid));
|
|
185
|
+
};
|
|
186
|
+
touchHeartbeat();
|
|
187
|
+
debounceTimer = null;
|
|
188
|
+
scheduleReconcile = function scheduleReconcile() {
|
|
189
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
190
|
+
debounceTimer = setTimeout(function() {
|
|
191
|
+
debounceTimer = null;
|
|
192
|
+
try {
|
|
193
|
+
var _reconcile = (0, _dbts.reconcile)(db, cfg, baseDir), parsed = _reconcile.parsed, warnings = _reconcile.warnings;
|
|
194
|
+
onEvent({
|
|
195
|
+
type: 'reconciled',
|
|
196
|
+
parsed: parsed,
|
|
197
|
+
total: (0, _dbts.docCount)(db),
|
|
198
|
+
warnings: warnings
|
|
199
|
+
});
|
|
200
|
+
} catch (err) {
|
|
201
|
+
onEvent({
|
|
202
|
+
type: 'reconcile-error',
|
|
203
|
+
message: err.message
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}, DEBOUNCE_MS);
|
|
207
|
+
};
|
|
208
|
+
// Ignore events from our own state dir: the heartbeat writes cache.db
|
|
209
|
+
// every few seconds, and without this filter each write would schedule a
|
|
210
|
+
// (harmless but pointless) reconcile forever — a perpetual self-trigger.
|
|
211
|
+
watcher = (0, _nodefs.watch)(baseDir, {
|
|
212
|
+
recursive: true
|
|
213
|
+
}, function(_event, filename) {
|
|
214
|
+
if (typeof filename === 'string' && filename.startsWith(_configts.STATE_DIR)) return;
|
|
215
|
+
scheduleReconcile();
|
|
216
|
+
});
|
|
217
|
+
heartbeatTimer = setInterval(touchHeartbeat, HEARTBEAT_INTERVAL_MS);
|
|
218
|
+
return [
|
|
219
|
+
2,
|
|
220
|
+
new Promise(function(resolveShutdown) {
|
|
221
|
+
var cleanExit = function cleanExit() {
|
|
222
|
+
clearInterval(heartbeatTimer);
|
|
223
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
224
|
+
watcher.close();
|
|
225
|
+
(0, _dbts.setMeta)(db, 'watch_heartbeat', null);
|
|
226
|
+
(0, _dbts.setMeta)(db, 'watch_pid', null);
|
|
227
|
+
db.close();
|
|
228
|
+
resolveShutdown();
|
|
229
|
+
};
|
|
230
|
+
process.once('SIGINT', cleanExit);
|
|
231
|
+
process.once('SIGTERM', cleanExit);
|
|
232
|
+
})
|
|
233
|
+
];
|
|
234
|
+
});
|
|
235
|
+
}).apply(this, arguments);
|
|
236
|
+
}
|
|
237
|
+
/* 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/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config.ts';\nimport { STATE_DIR } from './config.ts';\nimport { docCount, getMeta, open, reconcile, setMeta } from './db.ts';\nimport { SenseError } from './errors.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: every query\n// `open()` reconciles against the filesystem regardless, so a missed or\n// coalesced fs event can never change a query result -- it only moves one\n// file's parse from ahead-of-time to query-time. That's why any event, of\n// any kind, triggers the exact same response: a debounced full reconcile.\n// Per-file event handling would be complexity with no correctness payoff.\nconst DEBOUNCE_MS = 200;\nconst HEARTBEAT_INTERVAL_MS = 5000;\nconst STALE_HEARTBEAT_MS = 15000;\n\n// This module never prints -- it emits events via `onEvent` and throws on\n// the one exit-worthy condition (a live watcher already present). cli.ts is\n// the only place that touches console/process.exit.\nexport type WatchEvent = { type: 'started'; baseDir: 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}\n\n// Runs in the foreground until SIGINT/SIGTERM; never forks or daemonizes --\n// process lifecycle belongs to the OS (launchd/systemd/terminal). Resolves\n// on clean shutdown; throws SenseError(\"WATCH_ACTIVE\", ...) if another\n// watcher's heartbeat is still fresh and `--force` wasn't given.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const { db, dbPath, warnings: initialWarnings, parsed: initialParsed } = open(cfg);\n const baseDir = cfg.baseDir;\n\n const existingHeartbeat = getMeta(db, 'watch_heartbeat');\n if (existingHeartbeat && !opts.force) {\n const age = Date.now() - Date.parse(existingHeartbeat);\n if (age >= 0 && age < STALE_HEARTBEAT_MS) {\n db.close();\n throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n }\n\n onEvent({ type: 'started', baseDir, dbPath });\n if (initialWarnings.length > 0 || initialParsed > 0) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: docCount(db), warnings: initialWarnings });\n }\n\n const touchHeartbeat = () => {\n setMeta(db, 'watch_heartbeat', new Date().toISOString());\n setMeta(db, 'watch_pid', String(process.pid));\n };\n touchHeartbeat();\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n try {\n const { parsed, warnings } = reconcile(db, cfg, baseDir);\n onEvent({ type: 'reconciled', parsed, total: docCount(db), warnings });\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n }, DEBOUNCE_MS);\n };\n\n // Ignore events from our own state dir: the heartbeat writes cache.db\n // every few seconds, and without this filter each write would schedule a\n // (harmless but pointless) reconcile forever — a perpetual self-trigger.\n const watcher = fsWatch(baseDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const heartbeatTimer = setInterval(touchHeartbeat, HEARTBEAT_INTERVAL_MS);\n\n return new Promise<void>((resolveShutdown) => {\n const cleanExit = () => {\n clearInterval(heartbeatTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n setMeta(db, 'watch_heartbeat', null);\n setMeta(db, 'watch_pid', null);\n db.close();\n resolveShutdown();\n };\n process.once('SIGINT', cleanExit);\n process.once('SIGTERM', cleanExit);\n });\n}\n"],"names":["runWatch","DEBOUNCE_MS","HEARTBEAT_INTERVAL_MS","STALE_HEARTBEAT_MS","cfg","opts","onEvent","open","db","dbPath","initialWarnings","initialParsed","baseDir","existingHeartbeat","age","touchHeartbeat","debounceTimer","scheduleReconcile","watcher","heartbeatTimer","warnings","parsed","getMeta","force","Date","now","parse","close","SenseError","Math","round","type","length","total","docCount","setMeta","toISOString","String","process","pid","clearTimeout","setTimeout","reconcile","err","message","fsWatch","recursive","_event","filename","startsWith","STATE_DIR","setInterval","Promise","resolveShutdown","cleanExit","clearInterval","once"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function cli(argv: string[], name: string): Promise<void>;
|
package/dist/esm/cli.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { initConfig, loadConfig } from './config.js';
|
|
3
|
+
import { docCount, getMeta, open, rebuild } from './db.js';
|
|
4
|
+
import { printRows } from './output.js';
|
|
5
|
+
import { runWatch } from './watch.js';
|
|
6
|
+
function usage(name) {
|
|
7
|
+
return `usage: ${name} <name> [params...] [--format table|json] [--config path]\n` + ` ${name} query "<sql>" [params...]\n` + ` ${name} --list\n` + ` ${name} init\n` + ` ${name} watch [--force]\n` + ` ${name} status\n` + ` ${name} rebuild`;
|
|
8
|
+
}
|
|
9
|
+
function parseCliArgs(argv, name) {
|
|
10
|
+
try {
|
|
11
|
+
return parseArgs({
|
|
12
|
+
args: argv,
|
|
13
|
+
options: {
|
|
14
|
+
format: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
default: 'table'
|
|
17
|
+
},
|
|
18
|
+
config: {
|
|
19
|
+
type: 'string'
|
|
20
|
+
},
|
|
21
|
+
list: {
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
default: false
|
|
24
|
+
},
|
|
25
|
+
force: {
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
default: false
|
|
28
|
+
},
|
|
29
|
+
help: {
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
default: false,
|
|
32
|
+
short: 'h'
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
allowPositionals: true
|
|
36
|
+
});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(err.message);
|
|
39
|
+
console.error(usage(name));
|
|
40
|
+
process.exit(2);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function printWarnings(warnings) {
|
|
44
|
+
for (const w of warnings)console.warn(w);
|
|
45
|
+
}
|
|
46
|
+
// Shared tail of both the named-query and ad-hoc `query` paths. An unbound
|
|
47
|
+
// `?` silently binds NULL and returns misleading empty results — fail
|
|
48
|
+
// loudly instead. (Naive count; queries putting '?' in string literals
|
|
49
|
+
// would miscount, which none of ours do.)
|
|
50
|
+
function runSql(cfg, sql, params, format, label) {
|
|
51
|
+
var _sql_match;
|
|
52
|
+
const placeholderCount = ((_sql_match = sql.match(/\?/g)) !== null && _sql_match !== void 0 ? _sql_match : []).length;
|
|
53
|
+
if (params.length !== placeholderCount) {
|
|
54
|
+
console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);
|
|
55
|
+
process.exit(2);
|
|
56
|
+
}
|
|
57
|
+
const { db, warnings } = open(cfg);
|
|
58
|
+
printWarnings(warnings);
|
|
59
|
+
const rows = db.prepare(sql).all(...params);
|
|
60
|
+
printRows(rows, format);
|
|
61
|
+
db.close();
|
|
62
|
+
}
|
|
63
|
+
function logWatchEvent(event) {
|
|
64
|
+
if (event.type === 'started') {
|
|
65
|
+
console.log(`sense watch: watching ${event.baseDir}`);
|
|
66
|
+
console.log(`sense watch: db ${event.dbPath}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (event.type === 'reconciled') {
|
|
70
|
+
printWarnings(event.warnings);
|
|
71
|
+
if (event.parsed > 0) {
|
|
72
|
+
console.log(`sense watch: reconciled, ${event.parsed} file(s) reparsed (${event.total} total)`);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.error(`sense watch: reconcile error: ${event.message}`);
|
|
77
|
+
}
|
|
78
|
+
// Everything below that can throw (bad JSON, a config `version` newer than
|
|
79
|
+
// this build supports, another watcher's fresh heartbeat, a SQLite error)
|
|
80
|
+
// is caught by the top-level handler below and reported as exit 1 with the
|
|
81
|
+
// error's message verbatim. Usage errors (missing/unknown query name, wrong
|
|
82
|
+
// parameter count) exit(2) directly instead of throwing.
|
|
83
|
+
export default async function cli(argv, name) {
|
|
84
|
+
const { values, positionals } = parseCliArgs(argv, name);
|
|
85
|
+
if (values.help) {
|
|
86
|
+
console.log(usage(name));
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
// cli.ts's only config knowledge is the --config flag; discovery, parsing,
|
|
90
|
+
// and version-gating all live in config.ts.
|
|
91
|
+
const resolveConfig = ()=>loadConfig(values.config);
|
|
92
|
+
try {
|
|
93
|
+
const [first, ...rest] = positionals;
|
|
94
|
+
if (first === 'init') {
|
|
95
|
+
const configPath = initConfig(process.cwd());
|
|
96
|
+
console.log(`created ${configPath}`);
|
|
97
|
+
console.log('edit the queries to fit your tree, then: sense --list');
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
if (first === 'watch') {
|
|
101
|
+
const cfg = resolveConfig();
|
|
102
|
+
await runWatch(cfg, {
|
|
103
|
+
force: values.force,
|
|
104
|
+
onEvent: logWatchEvent
|
|
105
|
+
});
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
if (first === 'status') {
|
|
109
|
+
const cfg = resolveConfig();
|
|
110
|
+
const { db, dbPath, warnings } = open(cfg);
|
|
111
|
+
printWarnings(warnings);
|
|
112
|
+
console.log(`db: ${dbPath}`);
|
|
113
|
+
console.log(`docs: ${docCount(db)}`);
|
|
114
|
+
const heartbeat = getMeta(db, 'watch_heartbeat');
|
|
115
|
+
if (heartbeat) {
|
|
116
|
+
const ageSec = Math.round((Date.now() - Date.parse(heartbeat)) / 1000);
|
|
117
|
+
console.log(`watcher: last heartbeat ${ageSec}s ago`);
|
|
118
|
+
} else {
|
|
119
|
+
console.log('watcher: no watcher');
|
|
120
|
+
}
|
|
121
|
+
db.close();
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
if (first === 'rebuild') {
|
|
125
|
+
const cfg = resolveConfig();
|
|
126
|
+
const result = rebuild(cfg);
|
|
127
|
+
printWarnings(result.warnings);
|
|
128
|
+
console.log(`rebuilt: ${docCount(result.db)} docs`);
|
|
129
|
+
result.db.close();
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
if (values.list) {
|
|
133
|
+
const cfg = resolveConfig();
|
|
134
|
+
for (const queryName of Object.keys(cfg.queries).sort())console.log(queryName);
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
const format = values.format === 'json' ? 'json' : 'table';
|
|
138
|
+
// Ad-hoc SQL without touching the config -- for one-off questions;
|
|
139
|
+
// save a query into sense.config.json only when it'll be reused.
|
|
140
|
+
if (first === 'query') {
|
|
141
|
+
const [sql, ...params] = rest;
|
|
142
|
+
if (!sql) {
|
|
143
|
+
console.error(`usage: ${name} query "<sql>" [params...]`);
|
|
144
|
+
process.exit(2);
|
|
145
|
+
}
|
|
146
|
+
runSql(resolveConfig(), sql, params, format, 'ad-hoc query');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const [name_, ...params] = [
|
|
150
|
+
first,
|
|
151
|
+
...rest
|
|
152
|
+
];
|
|
153
|
+
if (!name_) {
|
|
154
|
+
console.error(usage(name));
|
|
155
|
+
process.exit(2);
|
|
156
|
+
}
|
|
157
|
+
const cfg = resolveConfig();
|
|
158
|
+
const sql = cfg.queries[name_];
|
|
159
|
+
if (!sql) {
|
|
160
|
+
console.error(`unknown query: "${name_}"`);
|
|
161
|
+
console.error(`valid queries: ${Object.keys(cfg.queries).sort().join(', ')}`);
|
|
162
|
+
process.exit(2);
|
|
163
|
+
}
|
|
164
|
+
runSql(cfg, sql, params, format, `query "${name_}"`);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
// SQLite's own error message, a bad config, an unsupported config
|
|
167
|
+
// version, or an already-active watcher — printed verbatim.
|
|
168
|
+
console.error(err.message);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { parseArgs } from 'node:util';\nimport type { ResolvedConfig } from './config.ts';\nimport { initConfig, loadConfig } from './config.ts';\nimport { docCount, getMeta, open, rebuild } from './db.ts';\nimport type { Row } from './output.ts';\nimport { printRows } from './output.ts';\nimport type { WatchEvent } from './watch.ts';\nimport { runWatch } from './watch.ts';\n\nfunction usage(name: string): string {\n return `usage: ${name} <name> [params...] [--format table|json] [--config path]\\n` + ` ${name} query \"<sql>\" [params...]\\n` + ` ${name} --list\\n` + ` ${name} init\\n` + ` ${name} watch [--force]\\n` + ` ${name} status\\n` + ` ${name} rebuild`;\n}\n\nfunction parseCliArgs(argv: string[], name: string) {\n try {\n return parseArgs({\n args: argv,\n options: {\n format: { type: 'string', default: 'table' },\n config: { type: 'string' },\n list: { type: 'boolean', default: false },\n force: { type: 'boolean', default: false },\n help: { type: 'boolean', default: false, short: 'h' },\n },\n allowPositionals: true,\n });\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage(name));\n process.exit(2);\n }\n}\n\nfunction printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\n// Shared tail of both the named-query and ad-hoc `query` paths. An unbound\n// `?` silently binds NULL and returns misleading empty results — fail\n// loudly instead. (Naive count; queries putting '?' in string literals\n// would miscount, which none of ours do.)\nfunction runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n const rows = db.prepare(sql).all(...params) as Row[];\n printRows(rows, format);\n db.close();\n}\n\nfunction logWatchEvent(event: WatchEvent): void {\n if (event.type === 'started') {\n console.log(`sense watch: watching ${event.baseDir}`);\n console.log(`sense watch: db ${event.dbPath}`);\n return;\n }\n if (event.type === 'reconciled') {\n printWarnings(event.warnings);\n if (event.parsed > 0) {\n console.log(`sense watch: reconciled, ${event.parsed} file(s) reparsed (${event.total} total)`);\n }\n return;\n }\n console.error(`sense watch: reconcile error: ${event.message}`);\n}\n\n// Everything below that can throw (bad JSON, a config `version` newer than\n// this build supports, another watcher's fresh heartbeat, a SQLite error)\n// is caught by the top-level handler below and reported as exit 1 with the\n// error's message verbatim. Usage errors (missing/unknown query name, wrong\n// parameter count) exit(2) directly instead of throwing.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n const { values, positionals } = parseCliArgs(argv, name);\n\n if (values.help) {\n console.log(usage(name));\n process.exit(0);\n }\n\n // cli.ts's only config knowledge is the --config flag; discovery, parsing,\n // and version-gating all live in config.ts.\n const resolveConfig = () => loadConfig(values.config);\n\n try {\n const [first, ...rest] = positionals;\n\n if (first === 'init') {\n const configPath = initConfig(process.cwd());\n console.log(`created ${configPath}`);\n console.log('edit the queries to fit your tree, then: sense --list');\n process.exit(0);\n }\n\n if (first === 'watch') {\n const cfg = resolveConfig();\n await runWatch(cfg, { force: values.force, onEvent: logWatchEvent });\n process.exit(0);\n }\n\n if (first === 'status') {\n const cfg = resolveConfig();\n const { db, dbPath, warnings } = open(cfg);\n printWarnings(warnings);\n console.log(`db: ${dbPath}`);\n console.log(`docs: ${docCount(db)}`);\n const heartbeat = getMeta(db, 'watch_heartbeat');\n if (heartbeat) {\n const ageSec = Math.round((Date.now() - Date.parse(heartbeat)) / 1000);\n console.log(`watcher: last heartbeat ${ageSec}s ago`);\n } else {\n console.log('watcher: no watcher');\n }\n db.close();\n process.exit(0);\n }\n\n if (first === 'rebuild') {\n const cfg = resolveConfig();\n const result = rebuild(cfg);\n printWarnings(result.warnings);\n console.log(`rebuilt: ${docCount(result.db)} docs`);\n result.db.close();\n process.exit(0);\n }\n\n if (values.list) {\n const cfg = resolveConfig();\n for (const queryName of Object.keys(cfg.queries).sort()) console.log(queryName);\n process.exit(0);\n }\n\n const format = values.format === 'json' ? 'json' : 'table';\n\n // Ad-hoc SQL without touching the config -- for one-off questions;\n // save a query into sense.config.json only when it'll be reused.\n if (first === 'query') {\n const [sql, ...params] = rest;\n if (!sql) {\n console.error(`usage: ${name} query \"<sql>\" [params...]`);\n process.exit(2);\n }\n runSql(resolveConfig(), sql, params, format, 'ad-hoc query');\n return;\n }\n\n const [name_, ...params] = [first, ...rest];\n\n if (!name_) {\n console.error(usage(name));\n process.exit(2);\n }\n\n const cfg = resolveConfig();\n\n const sql = cfg.queries[name_];\n if (!sql) {\n console.error(`unknown query: \"${name_}\"`);\n console.error(`valid queries: ${Object.keys(cfg.queries).sort().join(', ')}`);\n process.exit(2);\n }\n\n runSql(cfg, sql, params, format, `query \"${name_}\"`);\n } catch (err) {\n // SQLite's own error message, a bad config, an unsupported config\n // version, or an already-active watcher — printed verbatim.\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n"],"names":["parseArgs","initConfig","loadConfig","docCount","getMeta","open","rebuild","printRows","runWatch","usage","name","parseCliArgs","argv","args","options","format","type","default","config","list","force","help","short","allowPositionals","err","console","error","message","process","exit","printWarnings","warnings","w","warn","runSql","cfg","sql","params","label","placeholderCount","match","length","db","rows","prepare","all","close","logWatchEvent","event","log","baseDir","dbPath","parsed","total","cli","values","positionals","resolveConfig","first","rest","configPath","cwd","onEvent","heartbeat","ageSec","Math","round","Date","now","parse","result","queryName","Object","keys","queries","sort","name_","join"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const CONFIG_FILENAME = "sense.config.json";
|
|
2
|
+
export declare const STATE_DIR = ".sense";
|
|
3
|
+
export declare const SUPPORTED_CONFIG_VERSION = 1;
|
|
4
|
+
export interface Config {
|
|
5
|
+
$schema?: string;
|
|
6
|
+
version?: number;
|
|
7
|
+
scan: {
|
|
8
|
+
include: string[];
|
|
9
|
+
};
|
|
10
|
+
queries: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export interface ResolvedConfig extends Config {
|
|
13
|
+
baseDir: string;
|
|
14
|
+
configPath: string | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function initConfig(dir: string): string;
|
|
17
|
+
export declare function findConfigPath(startDir: string): string | null;
|
|
18
|
+
export declare function loadConfig(explicitPath?: string): ResolvedConfig;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { SenseError } from './errors.js';
|
|
4
|
+
// All config concerns live here: the file name, discovery, parsing, and
|
|
5
|
+
// version gating. db.ts consumes only the result -- it does no discovery
|
|
6
|
+
// and no version logic of its own.
|
|
7
|
+
export const CONFIG_FILENAME = 'sense.config.json';
|
|
8
|
+
export const STATE_DIR = '.sense';
|
|
9
|
+
// The highest `sense.config.json` `version` this build understands. Bumped
|
|
10
|
+
// only when the config *format* changes in a breaking way -- unrelated to
|
|
11
|
+
// the package's own semver.
|
|
12
|
+
export const SUPPORTED_CONFIG_VERSION = 1;
|
|
13
|
+
// `sense init`: write a starter config into `dir` — the example doubles as
|
|
14
|
+
// the documentation of the format. Refuses to overwrite an existing one.
|
|
15
|
+
export function initConfig(dir) {
|
|
16
|
+
const configPath = join(dir, CONFIG_FILENAME);
|
|
17
|
+
if (existsSync(configPath)) {
|
|
18
|
+
throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);
|
|
19
|
+
}
|
|
20
|
+
const starter = {
|
|
21
|
+
$schema: 'https://unpkg.com/sensemaking/schema.json',
|
|
22
|
+
version: 1,
|
|
23
|
+
scan: {
|
|
24
|
+
include: [
|
|
25
|
+
'**/*.md'
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
queries: {
|
|
29
|
+
all: 'SELECT path, title FROM docs ORDER BY path',
|
|
30
|
+
'by-tag': 'SELECT path, title FROM docs WHERE has(tags, ?) ORDER BY path'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
writeFileSync(configPath, `${JSON.stringify(starter, null, 2)}\n`);
|
|
34
|
+
return configPath;
|
|
35
|
+
}
|
|
36
|
+
// Find sense.config.json by walking up from startDir, git-style.
|
|
37
|
+
export function findConfigPath(startDir) {
|
|
38
|
+
let dir = resolve(startDir);
|
|
39
|
+
for(;;){
|
|
40
|
+
const candidate = join(dir, CONFIG_FILENAME);
|
|
41
|
+
if (existsSync(candidate)) return candidate;
|
|
42
|
+
const parent = dirname(dir);
|
|
43
|
+
if (parent === dir) return null;
|
|
44
|
+
dir = parent;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Resolve + load sense.config.json: `explicitPath` bypasses walk-up
|
|
48
|
+
// discovery (as `--config` does on the CLI); otherwise discovery starts at
|
|
49
|
+
// process cwd. Throws on a missing config, invalid JSON, or a `version`
|
|
50
|
+
// newer than this build supports -- callers surface the message and exit 1.
|
|
51
|
+
export function loadConfig(explicitPath) {
|
|
52
|
+
var _cfg_version;
|
|
53
|
+
let configPath;
|
|
54
|
+
if (explicitPath) {
|
|
55
|
+
configPath = resolve(process.cwd(), explicitPath);
|
|
56
|
+
if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);
|
|
57
|
+
} else {
|
|
58
|
+
const found = findConfigPath(process.cwd());
|
|
59
|
+
if (!found) {
|
|
60
|
+
throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);
|
|
61
|
+
}
|
|
62
|
+
configPath = found;
|
|
63
|
+
}
|
|
64
|
+
const raw = readFileSync(configPath, 'utf8');
|
|
65
|
+
const cfg = JSON.parse(raw);
|
|
66
|
+
// Missing `version` is treated as 1. A version newer than this build
|
|
67
|
+
// supports is a hard error -- misinterpreting a future config format
|
|
68
|
+
// silently would be worse than refusing to run.
|
|
69
|
+
const version = (_cfg_version = cfg.version) !== null && _cfg_version !== void 0 ? _cfg_version : 1;
|
|
70
|
+
if (version > SUPPORTED_CONFIG_VERSION) {
|
|
71
|
+
throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
...cfg,
|
|
75
|
+
baseDir: dirname(configPath),
|
|
76
|
+
configPath
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.ts';\n\n// All config concerns live here: the file name, discovery, parsing, and\n// version gating. db.ts consumes only the result -- it does no discovery\n// and no version logic of its own.\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// The highest `sense.config.json` `version` this build understands. Bumped\n// only when the config *format* changes in a breaking way -- unrelated to\n// the package's own semver.\nexport const SUPPORTED_CONFIG_VERSION = 1;\n\n// sense.config.json's on-disk shape.\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n // Config format version. Omitted = 1.\n version?: number;\n scan: { include: string[] };\n queries: Record<string, string>;\n}\n\n// A Config resolved to where it lives on disk (or, for tests, a baseDir\n// supplied directly with no backing file). `scan.include` globs and the\n// `.sense/` state dir both resolve relative to `baseDir`, never process cwd.\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n}\n\n// `sense init`: write a starter config into `dir` — the example doubles as\n// the documentation of the format. Refuses to overwrite an existing one.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n const starter: Config = {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: 1,\n scan: { include: ['**/*.md'] },\n queries: {\n all: 'SELECT path, title FROM docs ORDER BY path',\n 'by-tag': 'SELECT path, title FROM docs WHERE has(tags, ?) ORDER BY path',\n },\n };\n writeFileSync(configPath, `${JSON.stringify(starter, null, 2)}\\n`);\n return configPath;\n}\n\n// Find sense.config.json by walking up from startDir, git-style.\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Resolve + load sense.config.json: `explicitPath` bypasses walk-up\n// discovery (as `--config` does on the CLI); otherwise discovery starts at\n// process cwd. Throws on a missing config, invalid JSON, or a `version`\n// newer than this build supports -- callers surface the message and exit 1.\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n const cfg = JSON.parse(raw) as Config;\n\n // Missing `version` is treated as 1. A version newer than this build\n // supports is a hard error -- misinterpreting a future config format\n // silently would be worse than refusing to run.\n const version = cfg.version ?? 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n return { ...cfg, baseDir: dirname(configPath), configPath };\n}\n"],"names":["existsSync","readFileSync","writeFileSync","dirname","join","resolve","SenseError","CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","initConfig","dir","configPath","starter","$schema","version","scan","include","queries","all","JSON","stringify","findConfigPath","startDir","candidate","parent","loadConfig","explicitPath","cfg","process","cwd","found","raw","parse","baseDir"],"mappings":"AAAA,SAASA,UAAU,EAAEC,YAAY,EAAEC,aAAa,QAAQ,UAAU;AAClE,SAASC,OAAO,EAAEC,IAAI,EAAEC,OAAO,QAAQ,YAAY;AACnD,SAASC,UAAU,QAAQ,cAAc;AAEzC,wEAAwE;AACxE,yEAAyE;AACzE,mCAAmC;AAEnC,OAAO,MAAMC,kBAAkB,oBAAoB;AACnD,OAAO,MAAMC,YAAY,SAAS;AAElC,2EAA2E;AAC3E,0EAA0E;AAC1E,4BAA4B;AAC5B,OAAO,MAAMC,2BAA2B,EAAE;AAoB1C,2EAA2E;AAC3E,yEAAyE;AACzE,OAAO,SAASC,WAAWC,GAAW;IACpC,MAAMC,aAAaR,KAAKO,KAAKJ;IAC7B,IAAIP,WAAWY,aAAa;QAC1B,MAAM,IAAIN,WAAW,iBAAiB,GAAGC,gBAAgB,mBAAmB,EAAEI,KAAK;IACrF;IACA,MAAME,UAAkB;QACtBC,SAAS;QACTC,SAAS;QACTC,MAAM;YAAEC,SAAS;gBAAC;aAAU;QAAC;QAC7BC,SAAS;YACPC,KAAK;YACL,UAAU;QACZ;IACF;IACAjB,cAAcU,YAAY,GAAGQ,KAAKC,SAAS,CAACR,SAAS,MAAM,GAAG,EAAE,CAAC;IACjE,OAAOD;AACT;AAEA,iEAAiE;AACjE,OAAO,SAASU,eAAeC,QAAgB;IAC7C,IAAIZ,MAAMN,QAAQkB;IAClB,OAAS;QACP,MAAMC,YAAYpB,KAAKO,KAAKJ;QAC5B,IAAIP,WAAWwB,YAAY,OAAOA;QAClC,MAAMC,SAAStB,QAAQQ;QACvB,IAAIc,WAAWd,KAAK,OAAO;QAC3BA,MAAMc;IACR;AACF;AAEA,oEAAoE;AACpE,2EAA2E;AAC3E,wEAAwE;AACxE,4EAA4E;AAC5E,OAAO,SAASC,WAAWC,YAAqB;QAmB9BC;IAlBhB,IAAIhB;IACJ,IAAIe,cAAc;QAChBf,aAAaP,QAAQwB,QAAQC,GAAG,IAAIH;QACpC,IAAI,CAAC3B,WAAWY,aAAa,MAAM,IAAIN,WAAW,oBAAoB,CAAC,kBAAkB,EAAEM,YAAY;IACzG,OAAO;QACL,MAAMmB,QAAQT,eAAeO,QAAQC,GAAG;QACxC,IAAI,CAACC,OAAO;YACV,MAAM,IAAIzB,WAAW,oBAAoB,CAAC,eAAe,EAAEC,gBAAgB,IAAI,EAAEsB,QAAQC,GAAG,GAAG,wBAAwB,CAAC;QAC1H;QACAlB,aAAamB;IACf;IAEA,MAAMC,MAAM/B,aAAaW,YAAY;IACrC,MAAMgB,MAAMR,KAAKa,KAAK,CAACD;IAEvB,qEAAqE;IACrE,qEAAqE;IACrE,gDAAgD;IAChD,MAAMjB,WAAUa,eAAAA,IAAIb,OAAO,cAAXa,0BAAAA,eAAe;IAC/B,IAAIb,UAAUN,0BAA0B;QACtC,MAAM,IAAIH,WAAW,8BAA8B,CAAC,eAAe,EAAES,QAAQ,uBAAuB,CAAC;IACvG;IAEA,OAAO;QAAE,GAAGa,GAAG;QAAEM,SAAS/B,QAAQS;QAAaA;IAAW;AAC5D"}
|
package/dist/esm/db.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { Config, ResolvedConfig } from './config.js';
|
|
3
|
+
export declare const DB_FILENAME = "cache.db";
|
|
4
|
+
export declare const SCHEMA_VERSION = "1";
|
|
5
|
+
export interface OpenResult {
|
|
6
|
+
db: DatabaseSync;
|
|
7
|
+
cfg: ResolvedConfig;
|
|
8
|
+
dbPath: string;
|
|
9
|
+
parsed: number;
|
|
10
|
+
warnings: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function getMeta(db: DatabaseSync, key: string): string | null;
|
|
13
|
+
export declare function setMeta(db: DatabaseSync, key: string, value: string | null): void;
|
|
14
|
+
export declare function docCount(db: DatabaseSync): number;
|
|
15
|
+
export declare function reconcile(db: DatabaseSync, cfg: Config, baseDir: string): {
|
|
16
|
+
parsed: number;
|
|
17
|
+
warnings: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function open(cfg: ResolvedConfig): OpenResult;
|
|
20
|
+
export declare function rebuild(cfg: ResolvedConfig): OpenResult;
|