@velvetmonkey/vault-core 2.0.137 → 2.0.139

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.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SQLite Schema Migrations
3
+ *
4
+ * Database path resolution, schema initialization, migration logic,
5
+ * and database file management utilities.
6
+ */
7
+ import Database from 'better-sqlite3';
8
+ /**
9
+ * Get the database path for a vault
10
+ */
11
+ export declare function getStateDbPath(vaultPath: string): string;
12
+ /**
13
+ * Initialize schema and run migrations
14
+ */
15
+ export declare function initSchema(db: Database.Database): void;
16
+ export declare function deleteStateDbFiles(dbPath: string): void;
17
+ /** Back up state.db before opening (skip if missing or 0 bytes). */
18
+ export declare function backupStateDb(dbPath: string): void;
19
+ /** Preserve a corrupted database for inspection before deleting. */
20
+ export declare function preserveCorruptedDb(dbPath: string): void;
21
+ //# sourceMappingURL=migrations.d.ts.map
@@ -0,0 +1,320 @@
1
+ /**
2
+ * SQLite Schema Migrations
3
+ *
4
+ * Database path resolution, schema initialization, migration logic,
5
+ * and database file management utilities.
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { SCHEMA_VERSION, SCHEMA_SQL, STATE_DB_FILENAME, FLYWHEEL_DIR } from './schema.js';
10
+ // =============================================================================
11
+ // Database Path Resolution
12
+ // =============================================================================
13
+ /**
14
+ * Get the database path for a vault
15
+ */
16
+ export function getStateDbPath(vaultPath) {
17
+ const flywheelDir = path.join(vaultPath, FLYWHEEL_DIR);
18
+ if (!fs.existsSync(flywheelDir)) {
19
+ fs.mkdirSync(flywheelDir, { recursive: true });
20
+ }
21
+ return path.join(flywheelDir, STATE_DB_FILENAME);
22
+ }
23
+ // =============================================================================
24
+ // Schema Initialization & Migrations
25
+ // =============================================================================
26
+ /**
27
+ * Initialize schema and run migrations
28
+ */
29
+ export function initSchema(db) {
30
+ // Enable WAL mode for better concurrent read performance
31
+ db.pragma('journal_mode = WAL');
32
+ // Enable foreign keys
33
+ db.pragma('foreign_keys = ON');
34
+ // Performance tuning
35
+ db.pragma('synchronous = NORMAL'); // Safe with WAL — fsync only on checkpoint, not every commit
36
+ db.pragma('cache_size = -64000'); // 64 MB page cache (default is ~2 MB)
37
+ db.pragma('temp_store = MEMORY'); // Temp tables/indices in RAM instead of disk
38
+ // Run schema creation
39
+ db.exec(SCHEMA_SQL);
40
+ // Guard: Verify critical tables were created
41
+ // This catches cases where schema execution silently failed (e.g., corrupted db)
42
+ const tables = db.prepare(`
43
+ SELECT name FROM sqlite_master
44
+ WHERE type='table' AND name IN ('entities', 'schema_version', 'metadata')
45
+ `).all();
46
+ if (tables.length < 3) {
47
+ const foundTables = tables.map(t => t.name).join(', ') || 'none';
48
+ throw new Error(`[vault-core] Schema validation failed: expected 3 critical tables, found ${tables.length} (${foundTables}). ` +
49
+ `Database may be corrupted. Delete ${db.name} and restart.`);
50
+ }
51
+ // Check and record schema version
52
+ const versionRow = db.prepare('SELECT MAX(version) as version FROM schema_version').get();
53
+ const currentVersion = versionRow?.version ?? 0;
54
+ if (currentVersion < SCHEMA_VERSION) {
55
+ // v2: Drop dead notes/links tables if they exist from v1
56
+ if (currentVersion < 2) {
57
+ db.exec('DROP TABLE IF EXISTS notes');
58
+ db.exec('DROP TABLE IF EXISTS links');
59
+ }
60
+ // v3: Rename crank_state → write_state
61
+ if (currentVersion < 3) {
62
+ const hasCrankState = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='crank_state'`).get();
63
+ const hasWriteState = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='write_state'`).get();
64
+ if (hasCrankState && !hasWriteState) {
65
+ db.exec('ALTER TABLE crank_state RENAME TO write_state');
66
+ }
67
+ else if (hasCrankState && hasWriteState) {
68
+ // Both exist (stale db) — drop the old one
69
+ db.exec('DROP TABLE crank_state');
70
+ }
71
+ }
72
+ // v4: vault_metrics, wikilink_feedback, wikilink_suppressions tables
73
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
74
+ // v5: wikilink_applications table (implicit feedback tracking)
75
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
76
+ // v6: index_events table (index activity history)
77
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
78
+ // v7: tool_invocations table (usage analytics)
79
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
80
+ // v8: graph_snapshots table (structural evolution)
81
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
82
+ // v9: note_embeddings table (semantic search)
83
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
84
+ // v10: entity_embeddings table (semantic entity search)
85
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
86
+ // v11: notes_fts gains frontmatter column (4-col: path, title, frontmatter, content)
87
+ // Virtual tables can't ALTER, so drop and recreate
88
+ if (currentVersion < 11) {
89
+ db.exec('DROP TABLE IF EXISTS notes_fts');
90
+ db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(
91
+ path, title, frontmatter, content,
92
+ tokenize='porter'
93
+ )`);
94
+ // Clear FTS metadata to force rebuild with new schema
95
+ db.exec(`DELETE FROM fts_metadata WHERE key = 'last_built'`);
96
+ }
97
+ // v12: tasks cache table (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
98
+ // v13: merge_dismissals table (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
99
+ // v14: Add steps column to index_events (pipeline observability)
100
+ if (currentVersion < 14) {
101
+ const hasSteps = db.prepare(`SELECT COUNT(*) as cnt FROM pragma_table_info('index_events') WHERE name = 'steps'`).get();
102
+ if (hasSteps.cnt === 0) {
103
+ db.exec('ALTER TABLE index_events ADD COLUMN steps TEXT');
104
+ }
105
+ }
106
+ // v15: suggestion_events table (pipeline observability audit log)
107
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
108
+ // v16: note_links table (forward-link persistence for diff-based feedback)
109
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
110
+ // v17: entity_changes table (entity field change audit log)
111
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
112
+ // v18: note_tags table (tag persistence for diff-based feedback)
113
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
114
+ // v19: note_link_history table (wikilink survival tracking for positive feedback)
115
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
116
+ // v20: note_moves table (records file renames/moves detected by the watcher)
117
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
118
+ // v21: description TEXT column on entities table
119
+ if (currentVersion < 21) {
120
+ const hasDesc = db.prepare(`SELECT COUNT(*) as cnt FROM pragma_table_info('entities') WHERE name = 'description'`).get();
121
+ if (hasDesc.cnt === 0) {
122
+ db.exec('ALTER TABLE entities ADD COLUMN description TEXT');
123
+ }
124
+ }
125
+ // v22: Edge weight columns on note_links table
126
+ if (currentVersion < 22) {
127
+ const hasWeight = db.prepare(`SELECT COUNT(*) as cnt FROM pragma_table_info('note_links') WHERE name = 'weight'`).get();
128
+ if (hasWeight.cnt === 0) {
129
+ db.exec('ALTER TABLE note_links ADD COLUMN weight REAL NOT NULL DEFAULT 1.0');
130
+ db.exec('ALTER TABLE note_links ADD COLUMN weight_updated_at INTEGER');
131
+ }
132
+ }
133
+ // v23: Case-insensitive unique index on wikilink_applications
134
+ if (currentVersion < 23) {
135
+ db.exec('DROP INDEX IF EXISTS idx_wl_apps_unique');
136
+ db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_wl_apps_unique ON wikilink_applications(entity COLLATE NOCASE, note_path)');
137
+ }
138
+ // v24: corrections table (persistent correction records)
139
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
140
+ // v25: confidence column on wikilink_feedback (signal quality weighting)
141
+ if (currentVersion < 25) {
142
+ const hasConfidence = db.prepare(`SELECT COUNT(*) as cnt FROM pragma_table_info('wikilink_feedback') WHERE name = 'confidence'`).get();
143
+ if (hasConfidence.cnt === 0) {
144
+ db.exec('ALTER TABLE wikilink_feedback ADD COLUMN confidence REAL NOT NULL DEFAULT 1.0');
145
+ }
146
+ }
147
+ // v26: memories table, memories_fts, session_summaries table
148
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
149
+ // v27: cooccurrence_cache table (persist co-occurrence index)
150
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
151
+ // v28: content_hashes table (persist watcher content hashes across restarts)
152
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
153
+ // v29: index on wikilink_feedback(note_path) for temporal analysis queries
154
+ // (created by SCHEMA_SQL above via CREATE INDEX IF NOT EXISTS)
155
+ // v31: proactive_queue table (deferred proactive linking)
156
+ // (created by SCHEMA_SQL above via CREATE TABLE IF NOT EXISTS)
157
+ // v30: token economics columns on tool_invocations
158
+ if (currentVersion < 30) {
159
+ const hasResponseTokens = db.prepare(`SELECT COUNT(*) as cnt FROM pragma_table_info('tool_invocations') WHERE name = 'response_tokens'`).get();
160
+ if (hasResponseTokens.cnt === 0) {
161
+ db.exec('ALTER TABLE tool_invocations ADD COLUMN response_tokens INTEGER');
162
+ db.exec('ALTER TABLE tool_invocations ADD COLUMN baseline_tokens INTEGER');
163
+ }
164
+ }
165
+ // v32: Drop composite PRIMARY KEY on entity_changes (was causing UNIQUE constraint
166
+ // crashes when two changes hit the same entity+field within one second).
167
+ // Recreate as rowid table — audit log doesn't need dedup.
168
+ if (currentVersion < 32) {
169
+ const hasTable = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='entity_changes'`).get();
170
+ if (hasTable) {
171
+ db.exec(`
172
+ CREATE TABLE IF NOT EXISTS entity_changes_new (
173
+ entity TEXT NOT NULL,
174
+ field TEXT NOT NULL,
175
+ old_value TEXT,
176
+ new_value TEXT,
177
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
178
+ );
179
+ INSERT INTO entity_changes_new SELECT entity, field, old_value, new_value, changed_at FROM entity_changes;
180
+ DROP TABLE entity_changes;
181
+ ALTER TABLE entity_changes_new RENAME TO entity_changes;
182
+ `);
183
+ }
184
+ }
185
+ // v33: performance_benchmarks table (longitudinal tracking)
186
+ if (currentVersion < 33) {
187
+ const hasTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='performance_benchmarks'").get();
188
+ if (!hasTable) {
189
+ db.exec(`
190
+ CREATE TABLE performance_benchmarks (
191
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
192
+ timestamp INTEGER NOT NULL,
193
+ version TEXT NOT NULL,
194
+ benchmark TEXT NOT NULL,
195
+ mean_ms REAL NOT NULL,
196
+ p50_ms REAL,
197
+ p95_ms REAL,
198
+ iterations INTEGER NOT NULL DEFAULT 1
199
+ );
200
+ CREATE INDEX idx_perf_bench_ts ON performance_benchmarks(timestamp);
201
+ CREATE INDEX idx_perf_bench_name ON performance_benchmarks(benchmark, timestamp);
202
+ `);
203
+ }
204
+ db.prepare('INSERT OR REPLACE INTO schema_version (version) VALUES (?)').run(33);
205
+ }
206
+ // v34: Fix entities_fts — switch from content='entities' to contentless
207
+ // The old FTS5 declared content='entities' with column 'aliases', but the entities
208
+ // table has 'aliases_json'. This caused "no such column: T.aliases" on every query.
209
+ // Contentless FTS5 eliminates the column name dependency; triggers handle sync.
210
+ if (currentVersion < 34) {
211
+ // Drop old FTS5 and shadow tables
212
+ db.exec(`DROP TABLE IF EXISTS entities_fts`);
213
+ db.exec(`DROP TABLE IF EXISTS entities_fts_data`);
214
+ db.exec(`DROP TABLE IF EXISTS entities_fts_idx`);
215
+ db.exec(`DROP TABLE IF EXISTS entities_fts_docsize`);
216
+ db.exec(`DROP TABLE IF EXISTS entities_fts_config`);
217
+ // Recreate as contentless
218
+ db.exec(`
219
+ CREATE VIRTUAL TABLE entities_fts USING fts5(
220
+ name, aliases, category,
221
+ content='',
222
+ tokenize='porter unicode61'
223
+ )
224
+ `);
225
+ // Drop and recreate triggers (unchanged logic, now targeting contentless table)
226
+ db.exec(`DROP TRIGGER IF EXISTS entities_ai`);
227
+ db.exec(`DROP TRIGGER IF EXISTS entities_ad`);
228
+ db.exec(`DROP TRIGGER IF EXISTS entities_au`);
229
+ db.exec(`
230
+ CREATE TRIGGER entities_ai AFTER INSERT ON entities BEGIN
231
+ INSERT INTO entities_fts(rowid, name, aliases, category)
232
+ VALUES (
233
+ new.id,
234
+ new.name,
235
+ COALESCE((SELECT group_concat(value, ' ') FROM json_each(new.aliases_json)), ''),
236
+ new.category
237
+ );
238
+ END
239
+ `);
240
+ db.exec(`
241
+ CREATE TRIGGER entities_ad AFTER DELETE ON entities BEGIN
242
+ INSERT INTO entities_fts(entities_fts, rowid, name, aliases, category)
243
+ VALUES (
244
+ 'delete',
245
+ old.id,
246
+ old.name,
247
+ COALESCE((SELECT group_concat(value, ' ') FROM json_each(old.aliases_json)), ''),
248
+ old.category
249
+ );
250
+ END
251
+ `);
252
+ db.exec(`
253
+ CREATE TRIGGER entities_au AFTER UPDATE ON entities BEGIN
254
+ INSERT INTO entities_fts(entities_fts, rowid, name, aliases, category)
255
+ VALUES (
256
+ 'delete',
257
+ old.id,
258
+ old.name,
259
+ COALESCE((SELECT group_concat(value, ' ') FROM json_each(old.aliases_json)), ''),
260
+ old.category
261
+ );
262
+ INSERT INTO entities_fts(rowid, name, aliases, category)
263
+ VALUES (
264
+ new.id,
265
+ new.name,
266
+ COALESCE((SELECT group_concat(value, ' ') FROM json_each(new.aliases_json)), ''),
267
+ new.category
268
+ );
269
+ END
270
+ `);
271
+ // Populate FTS from existing entities
272
+ db.exec(`
273
+ INSERT INTO entities_fts(rowid, name, aliases, category)
274
+ SELECT id, name,
275
+ COALESCE((SELECT group_concat(value, ' ') FROM json_each(aliases_json)), ''),
276
+ category
277
+ FROM entities
278
+ `);
279
+ db.prepare('INSERT OR REPLACE INTO schema_version (version) VALUES (?)').run(34);
280
+ }
281
+ db.prepare('INSERT OR IGNORE INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);
282
+ }
283
+ }
284
+ // =============================================================================
285
+ // Database File Management
286
+ // =============================================================================
287
+ export function deleteStateDbFiles(dbPath) {
288
+ for (const suffix of ['', '-wal', '-shm']) {
289
+ const p = dbPath + suffix;
290
+ if (fs.existsSync(p))
291
+ fs.unlinkSync(p);
292
+ }
293
+ }
294
+ /** Back up state.db before opening (skip if missing or 0 bytes). */
295
+ export function backupStateDb(dbPath) {
296
+ try {
297
+ if (!fs.existsSync(dbPath))
298
+ return;
299
+ const stat = fs.statSync(dbPath);
300
+ if (stat.size === 0)
301
+ return;
302
+ fs.copyFileSync(dbPath, dbPath + '.backup');
303
+ }
304
+ catch (err) {
305
+ console.error(`[vault-core] Failed to back up state.db: ${err instanceof Error ? err.message : err}`);
306
+ }
307
+ }
308
+ /** Preserve a corrupted database for inspection before deleting. */
309
+ export function preserveCorruptedDb(dbPath) {
310
+ try {
311
+ if (fs.existsSync(dbPath)) {
312
+ fs.copyFileSync(dbPath, dbPath + '.corrupt');
313
+ console.error(`[vault-core] Corrupted database preserved at ${dbPath}.corrupt`);
314
+ }
315
+ }
316
+ catch {
317
+ // Best effort — don't block recovery
318
+ }
319
+ }
320
+ //# sourceMappingURL=migrations.js.map
@@ -0,0 +1,209 @@
1
+ /**
2
+ * SQLite Query Functions
3
+ *
4
+ * All database query operations: entity search, recency, write state,
5
+ * flywheel config, merge dismissals, metadata, vault index cache,
6
+ * and content hashes.
7
+ */
8
+ import type { EntityIndex } from './types.js';
9
+ import type { StateDb, EntitySearchResult, RecencyRow, StateDbMetadata } from './sqlite.js';
10
+ /**
11
+ * Search entities using FTS5 with porter stemming
12
+ *
13
+ * @param stateDb - State database instance
14
+ * @param query - Search query (supports FTS5 syntax)
15
+ * @param limit - Maximum results to return
16
+ * @returns Array of matching entities with relevance scores
17
+ */
18
+ export declare function searchEntities(stateDb: StateDb, query: string, limit?: number): EntitySearchResult[];
19
+ /**
20
+ * Search entities by prefix for autocomplete
21
+ *
22
+ * @param stateDb - State database instance
23
+ * @param prefix - Prefix to search for
24
+ * @param limit - Maximum results to return
25
+ */
26
+ export declare function searchEntitiesPrefix(stateDb: StateDb, prefix: string, limit?: number): EntitySearchResult[];
27
+ /**
28
+ * Get entity by exact name (case-insensitive)
29
+ */
30
+ export declare function getEntityByName(stateDb: StateDb, name: string): EntitySearchResult | null;
31
+ /**
32
+ * Get all entities from the database
33
+ */
34
+ export declare function getAllEntitiesFromDb(stateDb: StateDb): EntitySearchResult[];
35
+ /**
36
+ * Convert database entities back to EntityIndex format
37
+ */
38
+ export declare function getEntityIndexFromDb(stateDb: StateDb): EntityIndex;
39
+ /**
40
+ * Get entities that have a given alias (case-insensitive)
41
+ *
42
+ * @param stateDb - State database instance
43
+ * @param alias - Alias to search for (case-insensitive)
44
+ * @returns Array of matching entities
45
+ */
46
+ export declare function getEntitiesByAlias(stateDb: StateDb, alias: string): EntitySearchResult[];
47
+ /**
48
+ * Record a mention of an entity
49
+ */
50
+ export declare function recordEntityMention(stateDb: StateDb, entityName: string, mentionedAt?: Date): void;
51
+ /**
52
+ * Get recency info for an entity
53
+ */
54
+ export declare function getEntityRecency(stateDb: StateDb, entityName: string): RecencyRow | null;
55
+ /**
56
+ * Get all recency data ordered by most recent
57
+ */
58
+ export declare function getAllRecency(stateDb: StateDb): RecencyRow[];
59
+ /**
60
+ * Set a write state value
61
+ */
62
+ export declare function setWriteState(stateDb: StateDb, key: string, value: unknown): void;
63
+ /**
64
+ * Get a write state value
65
+ */
66
+ export declare function getWriteState<T>(stateDb: StateDb, key: string): T | null;
67
+ /**
68
+ * Delete a write state key
69
+ */
70
+ export declare function deleteWriteState(stateDb: StateDb, key: string): void;
71
+ /** Flywheel config row from database */
72
+ export interface FlywheelConfigRow {
73
+ key: string;
74
+ value: string;
75
+ }
76
+ /**
77
+ * Set a flywheel config value
78
+ */
79
+ export declare function setFlywheelConfig(stateDb: StateDb, key: string, value: unknown): void;
80
+ /**
81
+ * Get a flywheel config value
82
+ */
83
+ export declare function getFlywheelConfig<T>(stateDb: StateDb, key: string): T | null;
84
+ /**
85
+ * Get all flywheel config values as an object
86
+ */
87
+ export declare function getAllFlywheelConfig(stateDb: StateDb): Record<string, unknown>;
88
+ /**
89
+ * Delete a flywheel config key
90
+ */
91
+ export declare function deleteFlywheelConfig(stateDb: StateDb, key: string): void;
92
+ /**
93
+ * Save entire Flywheel config object to database
94
+ * Stores each top-level key as a separate row
95
+ */
96
+ export declare function saveFlywheelConfigToDb(stateDb: StateDb, config: Record<string, unknown>): void;
97
+ /**
98
+ * Load Flywheel config from database and reconstruct as typed object
99
+ */
100
+ export declare function loadFlywheelConfigFromDb(stateDb: StateDb): Record<string, unknown> | null;
101
+ /**
102
+ * Record a merge dismissal so the pair never reappears in suggestions.
103
+ */
104
+ export declare function recordMergeDismissal(db: StateDb, sourcePath: string, targetPath: string, sourceName: string, targetName: string, reason: string): void;
105
+ /**
106
+ * Get all dismissed merge pair keys for filtering.
107
+ */
108
+ export declare function getDismissedMergePairs(db: StateDb): Set<string>;
109
+ /**
110
+ * Get database metadata
111
+ */
112
+ export declare function getStateDbMetadata(stateDb: StateDb): StateDbMetadata;
113
+ /**
114
+ * Check if entity data is stale (older than threshold)
115
+ */
116
+ export declare function isEntityDataStale(stateDb: StateDb, thresholdMs?: number): boolean;
117
+ /**
118
+ * Escape special FTS5 characters in a query
119
+ */
120
+ export declare function escapeFts5Query(query: string): string;
121
+ /**
122
+ * Rebuild the entities_fts index from the entities table.
123
+ * Contentless FTS5 tables don't support the 'rebuild' command,
124
+ * so we manually delete all entries and re-insert from the entities table.
125
+ */
126
+ export declare function rebuildEntitiesFts(stateDb: StateDb): void;
127
+ /**
128
+ * Check if the state database exists for a vault
129
+ */
130
+ export declare function stateDbExists(vaultPath: string): boolean;
131
+ /**
132
+ * Delete the state database (for testing or reset)
133
+ */
134
+ export declare function deleteStateDb(vaultPath: string): void;
135
+ /** Serializable VaultIndex for caching */
136
+ export interface VaultIndexCacheData {
137
+ notes: Array<{
138
+ path: string;
139
+ title: string;
140
+ aliases: string[];
141
+ frontmatter: Record<string, unknown>;
142
+ outlinks: Array<{
143
+ target: string;
144
+ alias?: string;
145
+ line: number;
146
+ }>;
147
+ tags: string[];
148
+ modified: number;
149
+ created?: number;
150
+ }>;
151
+ backlinks: Array<[string, Array<{
152
+ source: string;
153
+ line: number;
154
+ context?: string;
155
+ }>]>;
156
+ entities: Array<[string, string]>;
157
+ prospects?: Array<[string, {
158
+ displayName: string;
159
+ backlinkCount: number;
160
+ }]>;
161
+ tags: Array<[string, string[]]>;
162
+ builtAt: number;
163
+ }
164
+ /** Cache metadata */
165
+ export interface VaultIndexCacheInfo {
166
+ builtAt: Date;
167
+ noteCount: number;
168
+ version: number;
169
+ }
170
+ /**
171
+ * Save VaultIndex to cache
172
+ *
173
+ * @param stateDb - State database instance
174
+ * @param indexData - Serialized VaultIndex data
175
+ */
176
+ export declare function saveVaultIndexCache(stateDb: StateDb, indexData: VaultIndexCacheData): void;
177
+ /**
178
+ * Load VaultIndex from cache
179
+ *
180
+ * @param stateDb - State database instance
181
+ * @returns Cached VaultIndex data or null if not found
182
+ */
183
+ export declare function loadVaultIndexCache(stateDb: StateDb): VaultIndexCacheData | null;
184
+ /**
185
+ * Get cache metadata without loading full data
186
+ */
187
+ export declare function getVaultIndexCacheInfo(stateDb: StateDb): VaultIndexCacheInfo | null;
188
+ /**
189
+ * Clear the vault index cache
190
+ */
191
+ export declare function clearVaultIndexCache(stateDb: StateDb): void;
192
+ /**
193
+ * Check if cache is valid (not too old and note count matches)
194
+ *
195
+ * @param stateDb - State database instance
196
+ * @param actualNoteCount - Current number of notes in vault
197
+ * @param maxAgeMs - Maximum cache age in milliseconds (default 24 hours)
198
+ */
199
+ export declare function isVaultIndexCacheValid(stateDb: StateDb, actualNoteCount: number, maxAgeMs?: number): boolean;
200
+ /** Load all persisted content hashes */
201
+ export declare function loadContentHashes(stateDb: StateDb): Map<string, string>;
202
+ /** Persist hash changes from a watcher batch (upserts + deletes in one transaction) */
203
+ export declare function saveContentHashBatch(stateDb: StateDb, upserts: Array<{
204
+ path: string;
205
+ hash: string;
206
+ }>, deletes: string[]): void;
207
+ /** Rename a hash entry (for file renames) */
208
+ export declare function renameContentHash(stateDb: StateDb, oldPath: string, newPath: string): void;
209
+ //# sourceMappingURL=queries.d.ts.map