gitnexus 1.6.3-rc.29 → 1.6.3-rc.30
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.
|
@@ -15,6 +15,22 @@
|
|
|
15
15
|
* from the same Database is the officially supported concurrency pattern.
|
|
16
16
|
*/
|
|
17
17
|
import lbug from '@ladybugdb/core';
|
|
18
|
+
/**
|
|
19
|
+
* Listeners notified when a pool entry is torn down (LRU eviction, idle
|
|
20
|
+
* timeout, explicit close). Used by upper layers (e.g. the BM25 search
|
|
21
|
+
* module) to invalidate per-repo caches that must not outlive the pool
|
|
22
|
+
* entry that produced them.
|
|
23
|
+
*
|
|
24
|
+
* Listeners run synchronously inside `closeOne` after the pool entry has
|
|
25
|
+
* been removed; throwing listeners are isolated so one bad listener does
|
|
26
|
+
* not prevent others from firing or break teardown.
|
|
27
|
+
*/
|
|
28
|
+
type PoolCloseListener = (repoId: string) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Subscribe to pool-close events. Returns a disposer that removes the
|
|
31
|
+
* listener (handy for tests).
|
|
32
|
+
*/
|
|
33
|
+
export declare function addPoolCloseListener(listener: PoolCloseListener): () => void;
|
|
18
34
|
/** Saved real stdout/stderr write — used to silence native module output without race conditions */
|
|
19
35
|
export declare const realStdoutWrite: any;
|
|
20
36
|
export declare const realStderrWrite: any;
|
|
@@ -74,3 +90,4 @@ export declare const isLbugReady: (repoId: string) => boolean;
|
|
|
74
90
|
export declare const CYPHER_WRITE_RE: RegExp;
|
|
75
91
|
/** Check if a Cypher query contains write operations */
|
|
76
92
|
export declare function isWriteQuery(query: string): boolean;
|
|
93
|
+
export {};
|
|
@@ -17,6 +17,17 @@
|
|
|
17
17
|
import fs from 'fs/promises';
|
|
18
18
|
import lbug from '@ladybugdb/core';
|
|
19
19
|
const pool = new Map();
|
|
20
|
+
const poolCloseListeners = new Set();
|
|
21
|
+
/**
|
|
22
|
+
* Subscribe to pool-close events. Returns a disposer that removes the
|
|
23
|
+
* listener (handy for tests).
|
|
24
|
+
*/
|
|
25
|
+
export function addPoolCloseListener(listener) {
|
|
26
|
+
poolCloseListeners.add(listener);
|
|
27
|
+
return () => {
|
|
28
|
+
poolCloseListeners.delete(listener);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
20
31
|
const dbCache = new Map();
|
|
21
32
|
/** Max repos in the pool (LRU eviction) */
|
|
22
33
|
const MAX_POOL_SIZE = 5;
|
|
@@ -119,6 +130,16 @@ function closeOne(repoId) {
|
|
|
119
130
|
}
|
|
120
131
|
}
|
|
121
132
|
pool.delete(repoId);
|
|
133
|
+
// Notify listeners AFTER the pool entry is gone so any cache-invalidation
|
|
134
|
+
// they perform is consistent with `isLbugReady(repoId) === false`.
|
|
135
|
+
for (const listener of poolCloseListeners) {
|
|
136
|
+
try {
|
|
137
|
+
listener(repoId);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Isolate listener failures — teardown must complete.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
122
143
|
}
|
|
123
144
|
/**
|
|
124
145
|
* Create a new Connection from a repo's Database.
|
|
@@ -16,6 +16,17 @@ export interface BM25SearchResult {
|
|
|
16
16
|
rank: number;
|
|
17
17
|
nodeIds?: string[];
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Drop all ensured-FTS cache entries for a given repoId.
|
|
21
|
+
*
|
|
22
|
+
* Called from the pool-close listener so that a pool teardown / recreation
|
|
23
|
+
* forces the next `searchFTSFromLbug` call to re-issue `CREATE_FTS_INDEX`
|
|
24
|
+
* against the fresh connection rather than trust stale ensure-state from a
|
|
25
|
+
* previous pool lifetime.
|
|
26
|
+
*
|
|
27
|
+
* Exported for tests; the listener wiring is internal.
|
|
28
|
+
*/
|
|
29
|
+
export declare function invalidateEnsuredFTSForRepo(repoId: string): void;
|
|
19
30
|
/**
|
|
20
31
|
* Search using LadybugDB's built-in FTS (always fresh, reads from disk)
|
|
21
32
|
*
|
|
@@ -27,8 +27,45 @@ const FTS_INDEXES = [
|
|
|
27
27
|
* Per-process cache for the MCP pool path: tracks which `(repoId, table)`
|
|
28
28
|
* pairs have been ensured. The CLI/pipeline path gets its own cache inside
|
|
29
29
|
* `lbug-adapter.ts` keyed by table/index, scoped to the singleton connection.
|
|
30
|
+
*
|
|
31
|
+
* IMPORTANT: an entry is added ONLY when the index was confirmed to exist
|
|
32
|
+
* (CREATE_FTS_INDEX succeeded, or failed with `'already exists'`). Other
|
|
33
|
+
* failures (transient lock errors, missing extension, etc.) leave the key
|
|
34
|
+
* unset so the next query retries instead of silently caching the failure.
|
|
35
|
+
*
|
|
36
|
+
* Entries for a given repoId are invalidated when its pool is closed —
|
|
37
|
+
* see the `addPoolCloseListener` registration in `searchFTSFromLbug`.
|
|
30
38
|
*/
|
|
31
39
|
const ensuredPoolFTS = new Set();
|
|
40
|
+
/**
|
|
41
|
+
* Drop all ensured-FTS cache entries for a given repoId.
|
|
42
|
+
*
|
|
43
|
+
* Called from the pool-close listener so that a pool teardown / recreation
|
|
44
|
+
* forces the next `searchFTSFromLbug` call to re-issue `CREATE_FTS_INDEX`
|
|
45
|
+
* against the fresh connection rather than trust stale ensure-state from a
|
|
46
|
+
* previous pool lifetime.
|
|
47
|
+
*
|
|
48
|
+
* Exported for tests; the listener wiring is internal.
|
|
49
|
+
*/
|
|
50
|
+
export function invalidateEnsuredFTSForRepo(repoId) {
|
|
51
|
+
const prefix = `${repoId}:`;
|
|
52
|
+
for (const key of ensuredPoolFTS) {
|
|
53
|
+
if (key.startsWith(prefix))
|
|
54
|
+
ensuredPoolFTS.delete(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Tracks whether we've already wired the pool-close listener for this
|
|
59
|
+
* process. The pool adapter is dynamically imported, so registration
|
|
60
|
+
* happens lazily on the first MCP-pool-backed FTS query.
|
|
61
|
+
*/
|
|
62
|
+
let poolCloseListenerRegistered = false;
|
|
63
|
+
function registerPoolCloseListenerOnce(addPoolCloseListener) {
|
|
64
|
+
if (poolCloseListenerRegistered)
|
|
65
|
+
return;
|
|
66
|
+
poolCloseListenerRegistered = true;
|
|
67
|
+
addPoolCloseListener((repoId) => invalidateEnsuredFTSForRepo(repoId));
|
|
68
|
+
}
|
|
32
69
|
async function ensureFTSIndexViaExecutor(executor, repoId, table, indexName, properties) {
|
|
33
70
|
const key = `${repoId}:${table}:${indexName}`;
|
|
34
71
|
if (ensuredPoolFTS.has(key))
|
|
@@ -36,17 +73,25 @@ async function ensureFTSIndexViaExecutor(executor, repoId, table, indexName, pro
|
|
|
36
73
|
const propList = properties.map((p) => `'${p}'`).join(', ');
|
|
37
74
|
try {
|
|
38
75
|
await executor(`CALL CREATE_FTS_INDEX('${table}', '${indexName}', [${propList}], stemmer := 'porter')`);
|
|
76
|
+
// Index was created successfully — safe to cache.
|
|
77
|
+
ensuredPoolFTS.add(key);
|
|
39
78
|
}
|
|
40
79
|
catch (e) {
|
|
41
80
|
// 'already exists' is the happy path (index persists on disk between
|
|
42
|
-
// process invocations) —
|
|
43
|
-
//
|
|
81
|
+
// process invocations) — cache it. Anything else is treated as a
|
|
82
|
+
// transient failure: surface a one-time warning and leave the key
|
|
83
|
+
// unset so the NEXT query retries rather than silently using a
|
|
84
|
+
// cached failure (which previously disabled BM25 for the whole
|
|
85
|
+
// process for that repo).
|
|
44
86
|
const msg = String(e?.message ?? '');
|
|
45
|
-
if (
|
|
46
|
-
|
|
87
|
+
if (msg.includes('already exists')) {
|
|
88
|
+
ensuredPoolFTS.add(key);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.warn(`[gitnexus] FTS index ensure failed for repo "${repoId}" table "${table}" ` +
|
|
92
|
+
`(index "${indexName}"): ${msg || e}. Will retry on next query.`);
|
|
47
93
|
}
|
|
48
94
|
}
|
|
49
|
-
ensuredPoolFTS.add(key);
|
|
50
95
|
}
|
|
51
96
|
/**
|
|
52
97
|
* Execute a single FTS query via a custom executor (for MCP connection pool).
|
|
@@ -94,7 +139,13 @@ export const searchFTSFromLbug = async (query, limit = 20, repoId) => {
|
|
|
94
139
|
// Use MCP connection pool via dynamic import
|
|
95
140
|
// IMPORTANT: FTS queries run sequentially to avoid connection contention.
|
|
96
141
|
// The MCP pool supports multiple connections, but FTS is best run serially.
|
|
97
|
-
const
|
|
142
|
+
const poolMod = await import('../lbug/pool-adapter.js');
|
|
143
|
+
const { executeQuery, addPoolCloseListener } = poolMod;
|
|
144
|
+
// Register the pool-close listener lazily on first use so a teardown of
|
|
145
|
+
// the pool entry (LRU eviction, idle timeout, explicit close) drops the
|
|
146
|
+
// matching `ensuredPoolFTS` entries. Without this, stale ensure-state
|
|
147
|
+
// can outlive the pool that produced it.
|
|
148
|
+
registerPoolCloseListenerOnce(addPoolCloseListener);
|
|
98
149
|
const executor = (cypher) => executeQuery(repoId, cypher);
|
|
99
150
|
// Lazy-create FTS indexes on first query for this repo (analyze no longer
|
|
100
151
|
// creates them up-front, so we ensure them here). Cached per-process.
|
package/package.json
CHANGED