dsh-codegraph-visualizer 0.1.0 → 0.3.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/README.md +391 -223
- package/assets/icon-180.png +0 -0
- package/assets/icon.svg +30 -0
- package/cordis.patch.yml +22 -17
- package/dist/CodeGraphAdapter-C8JvcS7m.js +10 -0
- package/dist/CodeGraphAdapter-eyrhs0K0.js +422 -0
- package/dist/CodeGraphAdapter-eyrhs0K0.js.map +1 -0
- package/dist/FileSystemAdapter-CgasSGbB.js +524 -0
- package/dist/FileSystemAdapter-CgasSGbB.js.map +1 -0
- package/dist/LensAdapter-C9Saf_i_.js +89 -0
- package/dist/LensAdapter-C9Saf_i_.js.map +1 -0
- package/dist/Logger-DUIJSeNu.js +80 -0
- package/dist/Logger-DUIJSeNu.js.map +1 -0
- package/dist/TreeSitterAdapter-C-EWy8br.js +631 -0
- package/dist/TreeSitterAdapter-C-EWy8br.js.map +1 -0
- package/dist/TreeSitterAdapter-C6FcO2_s.js +8 -0
- package/dist/client/index.css +8900 -1777
- package/dist/client/index.css.map +1 -0
- package/dist/client/index.d.ts +634 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.esm.js +57219 -20978
- package/dist/client/index.js.map +1 -0
- package/dist/client/{index.js → worker/layoutWorker.js} +7657 -8787
- package/dist/client.js +90067 -0
- package/dist/codeGraphDb-BT9nZCEO.js +357 -0
- package/dist/codeGraphDb-BT9nZCEO.js.map +1 -0
- package/dist/codeGraphDb-Bg7bWud2.js +9 -0
- package/dist/codeGraphRows-BhZX34uB.js +6 -0
- package/dist/codeGraphRows-C7hqu35E.js +246 -0
- package/dist/codeGraphRows-C7hqu35E.js.map +1 -0
- package/dist/constants-CQq93l3e.js +399 -0
- package/dist/constants-CQq93l3e.js.map +1 -0
- package/dist/graphFingerprint-CeARo7jG.js +4 -0
- package/dist/graphFingerprint-PuxORZUF.js +277 -0
- package/dist/graphFingerprint-PuxORZUF.js.map +1 -0
- package/dist/graphMetrics-C0F30730.js +555 -0
- package/dist/graphMetrics-C0F30730.js.map +1 -0
- package/dist/index.d.ts +572 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14734 -2003
- package/dist/index.js.map +1 -1
- package/dist/inheritanceEnhancer-Bi3UoYJS.js +291 -0
- package/dist/inheritanceEnhancer-Bi3UoYJS.js.map +1 -0
- package/dist/layerUtils-DDm-EsaD.js +72 -0
- package/dist/layerUtils-DDm-EsaD.js.map +1 -0
- package/dist/nativeIndex-Cx7RLXUp.js +5 -0
- package/dist/nativeIndex-DlSpD611.js +347 -0
- package/dist/nativeIndex-DlSpD611.js.map +1 -0
- package/dist/treeSitterExtractors-B1ybKNrC.js +394 -0
- package/dist/treeSitterExtractors-B1ybKNrC.js.map +1 -0
- package/dist/types-BUDxI_00.js +23 -0
- package/dist/types-BUDxI_00.js.map +1 -0
- package/locale/en.json +6 -0
- package/locale/zh-CN.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +101 -21
- package/dist/client-cjs/index.cjs +0 -44283
- package/dist/client-cjs/index.css +0 -2404
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { SQLITE } from "./constants-CQq93l3e.js";
|
|
2
|
+
import { scoped } from "./Logger-DUIJSeNu.js";
|
|
3
|
+
import { EDGE_ROW_COLUMNS, NOISE_PATH_SEGMENTS_EXPORT } from "./codeGraphRows-C7hqu35E.js";
|
|
4
|
+
import { collapseRepeatedBackslashes } from "./nativeIndex-DlSpD611.js";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
|
|
8
|
+
//#region src/adapters/codeGraphDb.ts
|
|
9
|
+
const log = scoped("codegraph-adapter");
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const dbPool = new Map();
|
|
12
|
+
const MAX_DB_POOL_SIZE = 5;
|
|
13
|
+
function getDbConnection(dbPath) {
|
|
14
|
+
if (!existsSync(dbPath)) return null;
|
|
15
|
+
const existing = dbPool.get(dbPath);
|
|
16
|
+
if (existing) {
|
|
17
|
+
dbPool.delete(dbPath);
|
|
18
|
+
dbPool.set(dbPath, existing);
|
|
19
|
+
return existing;
|
|
20
|
+
}
|
|
21
|
+
if (dbPool.size >= MAX_DB_POOL_SIZE) {
|
|
22
|
+
const oldest = dbPool.keys().next().value;
|
|
23
|
+
if (oldest !== void 0) {
|
|
24
|
+
const oldDb = dbPool.get(oldest);
|
|
25
|
+
dbPool.delete(oldest);
|
|
26
|
+
try {
|
|
27
|
+
oldDb?.close();
|
|
28
|
+
} catch {}
|
|
29
|
+
log.info("db pool: evicted connection", { dbPath: oldest });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const { DatabaseSync } = require("node:sqlite");
|
|
33
|
+
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
34
|
+
dbPool.set(dbPath, db);
|
|
35
|
+
log.info("db pool: opened connection", {
|
|
36
|
+
dbPath,
|
|
37
|
+
poolSize: dbPool.size
|
|
38
|
+
});
|
|
39
|
+
return db;
|
|
40
|
+
}
|
|
41
|
+
function closeAllDbConnections() {
|
|
42
|
+
for (const [, db] of dbPool.entries()) try {
|
|
43
|
+
db.close();
|
|
44
|
+
} catch {}
|
|
45
|
+
dbPool.clear();
|
|
46
|
+
log.info("db pool: all connections closed");
|
|
47
|
+
}
|
|
48
|
+
function countEdgesInDb(dbPath) {
|
|
49
|
+
const db = getDbConnection(dbPath);
|
|
50
|
+
if (!db) return 0;
|
|
51
|
+
try {
|
|
52
|
+
const result = db.prepare("SELECT COUNT(*) as cnt FROM edges").get();
|
|
53
|
+
return result?.cnt ?? 0;
|
|
54
|
+
} catch {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function isDbNodeRow(row) {
|
|
59
|
+
if (typeof row !== "object" || row === null) return false;
|
|
60
|
+
const r = row;
|
|
61
|
+
return typeof r.id === "string" && typeof r.kind === "string" && typeof r.name === "string" && typeof r.file_path === "string";
|
|
62
|
+
}
|
|
63
|
+
function isDbEdgeRow(row) {
|
|
64
|
+
if (typeof row !== "object" || row === null) return false;
|
|
65
|
+
const r = row;
|
|
66
|
+
if (!(typeof r.source === "string" && typeof r.target === "string" && typeof r.kind === "string")) return false;
|
|
67
|
+
if (r.metadata !== void 0 && r.metadata !== null && typeof r.metadata !== "string") return false;
|
|
68
|
+
r.metadata = r.metadata ?? null;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
function validateNodeRows(rows) {
|
|
72
|
+
const valid = [];
|
|
73
|
+
let skipped = 0;
|
|
74
|
+
for (const row of rows) if (isDbNodeRow(row)) valid.push(row);
|
|
75
|
+
else skipped++;
|
|
76
|
+
if (skipped > 0) log.warn("db node rows: skipped invalid rows", {
|
|
77
|
+
skipped,
|
|
78
|
+
total: rows.length
|
|
79
|
+
});
|
|
80
|
+
return valid;
|
|
81
|
+
}
|
|
82
|
+
function validateEdgeRows(rows) {
|
|
83
|
+
const valid = [];
|
|
84
|
+
let skipped = 0;
|
|
85
|
+
for (const row of rows) if (isDbEdgeRow(row)) valid.push(row);
|
|
86
|
+
else skipped++;
|
|
87
|
+
if (skipped > 0) log.warn("db edge rows: skipped invalid rows", {
|
|
88
|
+
skipped,
|
|
89
|
+
total: rows.length
|
|
90
|
+
});
|
|
91
|
+
return valid;
|
|
92
|
+
}
|
|
93
|
+
/** Read the whole graph from a codegraph.db. Returns null when unreadable. */
|
|
94
|
+
function readGraphFromDb(dbPath) {
|
|
95
|
+
const db = getDbConnection(dbPath);
|
|
96
|
+
if (!db) return null;
|
|
97
|
+
try {
|
|
98
|
+
const nodes = validateNodeRows(db.prepare("SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes").all());
|
|
99
|
+
const edges = validateEdgeRows(db.prepare(`SELECT ${EDGE_ROW_COLUMNS} FROM edges`).all());
|
|
100
|
+
return {
|
|
101
|
+
nodes,
|
|
102
|
+
edges
|
|
103
|
+
};
|
|
104
|
+
} catch (e) {
|
|
105
|
+
log.warn("readGraphFromDb failed", e);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function encodeCursor(state) {
|
|
110
|
+
return Buffer.from(JSON.stringify(state), "utf8").toString("base64");
|
|
111
|
+
}
|
|
112
|
+
function decodeCursor(cursor) {
|
|
113
|
+
return JSON.parse(Buffer.from(cursor, "base64").toString("utf8"));
|
|
114
|
+
}
|
|
115
|
+
function readGraphFromDbPaged(dbPath, cursor, limit) {
|
|
116
|
+
const db = getDbConnection(dbPath);
|
|
117
|
+
if (!db) return null;
|
|
118
|
+
const state = cursor ? decodeCursor(cursor) : {
|
|
119
|
+
phase: "nodes",
|
|
120
|
+
nodeCursor: null,
|
|
121
|
+
edgeCursor: null,
|
|
122
|
+
totalNodes: 0,
|
|
123
|
+
totalEdges: 0
|
|
124
|
+
};
|
|
125
|
+
try {
|
|
126
|
+
if (state.phase === "nodes") {
|
|
127
|
+
const nodes = validateNodeRows(db.prepare("SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE id > ? ORDER BY id LIMIT ?").all(state.nodeCursor ?? "", limit));
|
|
128
|
+
state.totalNodes += nodes.length;
|
|
129
|
+
if (nodes.length < limit) {
|
|
130
|
+
state.phase = "edges";
|
|
131
|
+
state.nodeCursor = null;
|
|
132
|
+
const edges$1 = validateEdgeRows(db.prepare(`SELECT ${EDGE_ROW_COLUMNS} FROM edges ORDER BY id LIMIT ?`).all(limit));
|
|
133
|
+
state.totalEdges += edges$1.length;
|
|
134
|
+
state.edgeCursor = edges$1.length > 0 ? edges$1[edges$1.length - 1].id : null;
|
|
135
|
+
const done$1 = edges$1.length < limit;
|
|
136
|
+
return {
|
|
137
|
+
nodes,
|
|
138
|
+
edges: edges$1,
|
|
139
|
+
nextCursor: done$1 ? null : encodeCursor(state),
|
|
140
|
+
done: done$1,
|
|
141
|
+
stats: {
|
|
142
|
+
nodeCount: state.totalNodes,
|
|
143
|
+
edgeCount: state.totalEdges,
|
|
144
|
+
phase: done$1 ? "done" : "edges"
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
state.nodeCursor = nodes[nodes.length - 1].id;
|
|
149
|
+
return {
|
|
150
|
+
nodes,
|
|
151
|
+
edges: [],
|
|
152
|
+
nextCursor: encodeCursor(state),
|
|
153
|
+
done: false,
|
|
154
|
+
stats: {
|
|
155
|
+
nodeCount: state.totalNodes,
|
|
156
|
+
edgeCount: 0,
|
|
157
|
+
phase: "nodes"
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const edges = validateEdgeRows(db.prepare(`SELECT ${EDGE_ROW_COLUMNS} FROM edges WHERE id > ? ORDER BY id LIMIT ?`).all(state.edgeCursor ?? 0, limit));
|
|
162
|
+
state.totalEdges += edges.length;
|
|
163
|
+
const done = edges.length < limit;
|
|
164
|
+
state.edgeCursor = edges.length > 0 ? edges[edges.length - 1].id : null;
|
|
165
|
+
return {
|
|
166
|
+
nodes: [],
|
|
167
|
+
edges,
|
|
168
|
+
nextCursor: done ? null : encodeCursor(state),
|
|
169
|
+
done,
|
|
170
|
+
stats: {
|
|
171
|
+
nodeCount: state.totalNodes,
|
|
172
|
+
edgeCount: state.totalEdges,
|
|
173
|
+
phase: done ? "done" : "edges"
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
} catch (e) {
|
|
177
|
+
log.warn("readGraphFromDbPaged failed", e);
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function buildNoiseWhereClause() {
|
|
182
|
+
const segments = NOISE_PATH_SEGMENTS_EXPORT;
|
|
183
|
+
const filters = segments.map((seg) => "REPLACE(file_path, '\\', '/') NOT LIKE '%/" + seg + "%'");
|
|
184
|
+
return filters.join(" AND ");
|
|
185
|
+
}
|
|
186
|
+
function readTopLevelNodesFromDb(dbPath, limit) {
|
|
187
|
+
const db = getDbConnection(dbPath);
|
|
188
|
+
if (!db) return null;
|
|
189
|
+
try {
|
|
190
|
+
const noiseWhere = buildNoiseWhereClause();
|
|
191
|
+
const kindWhere = "kind IN ('file', 'module', 'import')";
|
|
192
|
+
const topLevelOrderBy = [
|
|
193
|
+
"CASE WHEN REPLACE(id,'\\','/') LIKE 'dir:%' THEN 1 WHEN REPLACE(id,'\\','/') LIKE 'file:%' THEN 2 ELSE 3 END",
|
|
194
|
+
"LENGTH(file_path)",
|
|
195
|
+
"id"
|
|
196
|
+
].join(", ");
|
|
197
|
+
const whereClause = noiseWhere ? kindWhere + " AND " + noiseWhere : kindWhere;
|
|
198
|
+
const total = db.prepare("SELECT COUNT(*) as cnt FROM nodes WHERE " + whereClause).get().cnt;
|
|
199
|
+
const nodes = validateNodeRows(db.prepare(`SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported
|
|
200
|
+
FROM nodes WHERE ${whereClause} ORDER BY ${topLevelOrderBy} LIMIT ?`).all(limit));
|
|
201
|
+
for (const row of nodes) {
|
|
202
|
+
row.id = collapseRepeatedBackslashes(row.id);
|
|
203
|
+
row.file_path = collapseRepeatedBackslashes(row.file_path);
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
nodes,
|
|
207
|
+
total
|
|
208
|
+
};
|
|
209
|
+
} catch (e) {
|
|
210
|
+
log.warn("readTopLevelNodesFromDb failed", e);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function readNodeChildrenFromDb(dbPath, parentId, cursor, limit) {
|
|
215
|
+
const db = getDbConnection(dbPath);
|
|
216
|
+
if (!db) return null;
|
|
217
|
+
try {
|
|
218
|
+
if (parentId.startsWith("dir:")) {
|
|
219
|
+
const expandBackslashes = (s) => s.replace(/\\/g, "\\\\");
|
|
220
|
+
const probe = (id) => db.prepare("SELECT COUNT(*) as cnt FROM edges WHERE source = ? AND kind = 'contains'").get(id).cnt;
|
|
221
|
+
let effectiveId = parentId;
|
|
222
|
+
if (probe(parentId) === 0 && parentId.includes("\\")) {
|
|
223
|
+
const expanded = expandBackslashes(parentId);
|
|
224
|
+
if (probe(expanded) > 0) effectiveId = expanded;
|
|
225
|
+
}
|
|
226
|
+
const total$1 = db.prepare("SELECT COUNT(*) as cnt FROM edges WHERE source = ? AND kind = 'contains'").get(effectiveId).cnt;
|
|
227
|
+
const rows = cursor ? db.prepare("SELECT e.target AS target FROM edges e WHERE e.source = ? AND e.kind = 'contains' AND e.target > ? ORDER BY e.target LIMIT ?").all(effectiveId, cursor, limit) : db.prepare("SELECT e.target AS target FROM edges e WHERE e.source = ? AND e.kind = 'contains' ORDER BY e.target LIMIT ?").all(effectiveId, limit);
|
|
228
|
+
if (rows.length === 0) return {
|
|
229
|
+
nodes: [],
|
|
230
|
+
total: total$1
|
|
231
|
+
};
|
|
232
|
+
const targets = rows.map((r) => r.target);
|
|
233
|
+
const placeholders = targets.map(() => "?").join(",");
|
|
234
|
+
const nodes$1 = validateNodeRows(db.prepare(`SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE id IN (${placeholders})`).all(...targets));
|
|
235
|
+
for (const row of nodes$1) {
|
|
236
|
+
row.id = collapseRepeatedBackslashes(row.id);
|
|
237
|
+
row.file_path = collapseRepeatedBackslashes(row.file_path);
|
|
238
|
+
}
|
|
239
|
+
const order = new Map(targets.map((t, i) => [t, i]));
|
|
240
|
+
nodes$1.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
|
|
241
|
+
return {
|
|
242
|
+
nodes: nodes$1,
|
|
243
|
+
total: total$1
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const total = db.prepare("SELECT COUNT(*) as cnt FROM nodes WHERE file_path = (SELECT file_path FROM nodes WHERE id = ?) AND kind NOT IN ('file', 'module', 'import')").get(resolveNodeIdForQuery(db, parentId)).cnt;
|
|
247
|
+
const effectiveParent = resolveNodeIdForQuery(db, parentId);
|
|
248
|
+
const nodes = cursor ? validateNodeRows(db.prepare("SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE file_path = (SELECT file_path FROM nodes WHERE id = ?) AND kind NOT IN ('file', 'module', 'import') AND id > ? ORDER BY id LIMIT ?").all(effectiveParent, cursor, limit)) : validateNodeRows(db.prepare("SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE file_path = (SELECT file_path FROM nodes WHERE id = ?) AND kind NOT IN ('file', 'module', 'import') ORDER BY id LIMIT ?").all(effectiveParent, limit));
|
|
249
|
+
for (const row of nodes) {
|
|
250
|
+
row.id = collapseRepeatedBackslashes(row.id);
|
|
251
|
+
row.file_path = collapseRepeatedBackslashes(row.file_path);
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
nodes,
|
|
255
|
+
total
|
|
256
|
+
};
|
|
257
|
+
} catch (e) {
|
|
258
|
+
log.warn("readNodeChildrenFromDb failed", e);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
|
|
264
|
+
* 存量脏库的 id 归一查询(cycle-141 第六轮)。
|
|
265
|
+
|
|
266
|
+
*
|
|
267
|
+
|
|
268
|
+
* 修复前写入的库 id 带双反斜杠(真机 dsh-anic 实测),而修复后调用方传的是折叠
|
|
269
|
+
|
|
270
|
+
* 后的单形态 id——按单形态查脏库必落空。策略:先按调用方原样查(新库恒命中);
|
|
271
|
+
|
|
272
|
+
* 0 命中且 id 含反斜杠时**扩展成双反斜杠**再查一次(单→双是脏库方向:脏库每个
|
|
273
|
+
|
|
274
|
+
* 分隔符都是两个字符)。命中则返回扩展形态——调用方用它做实际查询。
|
|
275
|
+
|
|
276
|
+
*/
|
|
277
|
+
function resolveNodeIdForQuery(db, nodeId) {
|
|
278
|
+
const exists = db.prepare("SELECT 1 AS ok FROM nodes WHERE id = ?").get(nodeId);
|
|
279
|
+
if (exists) return nodeId;
|
|
280
|
+
if (!nodeId.includes("\\")) return nodeId;
|
|
281
|
+
const expanded = nodeId.replace(/\\/g, "\\\\");
|
|
282
|
+
const existsExpanded = db.prepare("SELECT 1 AS ok FROM nodes WHERE id = ?").get(expanded);
|
|
283
|
+
return existsExpanded ? expanded : nodeId;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
|
|
287
|
+
* 脏库行消毒:id/file_path 折叠回单形态再返回给调用方。
|
|
288
|
+
|
|
289
|
+
*
|
|
290
|
+
|
|
291
|
+
* 返回行是客户端可见的数据面——bootstrap(本文件顶层读取)与 children/outline
|
|
292
|
+
|
|
293
|
+
* 的 id 域必须一致,否则客户端展开后挂不上树(真机 "Projects (50)" 家族的
|
|
294
|
+
|
|
295
|
+
* 第二形态:不是查不到数据,而是两批数据的 id 域不同)。
|
|
296
|
+
|
|
297
|
+
*/
|
|
298
|
+
function sanitizeDirtyRows(rows) {
|
|
299
|
+
for (const row of rows) {
|
|
300
|
+
row.id = collapseRepeatedBackslashes(row.id);
|
|
301
|
+
row.file_path = collapseRepeatedBackslashes(row.file_path);
|
|
302
|
+
}
|
|
303
|
+
return rows;
|
|
304
|
+
}
|
|
305
|
+
function readFileOutlineFromDb(dbPath, fileId) {
|
|
306
|
+
const db = getDbConnection(dbPath);
|
|
307
|
+
if (!db) return null;
|
|
308
|
+
try {
|
|
309
|
+
const effectiveId = resolveNodeIdForQuery(db, fileId);
|
|
310
|
+
const nodes = sanitizeDirtyRows(validateNodeRows(db.prepare(`SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE file_path = (SELECT file_path FROM nodes WHERE id = ?) AND kind NOT IN ('file', 'module', 'import')`).all(effectiveId)));
|
|
311
|
+
return {
|
|
312
|
+
nodes,
|
|
313
|
+
total: nodes.length
|
|
314
|
+
};
|
|
315
|
+
} catch (e) {
|
|
316
|
+
log.warn("readFileOutlineFromDb failed", e);
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function readSymbolASTFromDb(dbPath, symbolId) {
|
|
321
|
+
const db = getDbConnection(dbPath);
|
|
322
|
+
if (!db) return null;
|
|
323
|
+
try {
|
|
324
|
+
const effectiveId = resolveNodeIdForQuery(db, symbolId);
|
|
325
|
+
const symbolRow = db.prepare("SELECT file_path, start_line FROM nodes WHERE id = ?").get(effectiveId);
|
|
326
|
+
if (!symbolRow) return {
|
|
327
|
+
nodes: [],
|
|
328
|
+
total: 0
|
|
329
|
+
};
|
|
330
|
+
const total = db.prepare("SELECT COUNT(*) as cnt FROM edges WHERE source = ?").get(effectiveId).cnt;
|
|
331
|
+
const calleeIds = db.prepare("SELECT target FROM edges WHERE source = ?").all(effectiveId);
|
|
332
|
+
if (calleeIds.length === 0) return {
|
|
333
|
+
nodes: [],
|
|
334
|
+
total: 0
|
|
335
|
+
};
|
|
336
|
+
const idList = calleeIds.map((r) => r.target);
|
|
337
|
+
const allNodes = [];
|
|
338
|
+
const batchSize = SQLITE.MAX_IN_PARAMS;
|
|
339
|
+
for (let i = 0; i < idList.length; i += batchSize) {
|
|
340
|
+
const batch = idList.slice(i, i + batchSize);
|
|
341
|
+
const placeholders = batch.map(() => "?").join(",");
|
|
342
|
+
const batchNodes = validateNodeRows(db.prepare(`SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE id IN (${placeholders})`).all(...batch));
|
|
343
|
+
allNodes.push(...batchNodes);
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
nodes: sanitizeDirtyRows(allNodes),
|
|
347
|
+
total
|
|
348
|
+
};
|
|
349
|
+
} catch (e) {
|
|
350
|
+
log.warn("readSymbolASTFromDb failed", e);
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
//#endregion
|
|
356
|
+
export { closeAllDbConnections, countEdgesInDb, getDbConnection, readFileOutlineFromDb, readGraphFromDb, readGraphFromDbPaged, readNodeChildrenFromDb, readSymbolASTFromDb, readTopLevelNodesFromDb, validateEdgeRows, validateNodeRows };
|
|
357
|
+
//# sourceMappingURL=codeGraphDb-BT9nZCEO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codeGraphDb-BT9nZCEO.js","names":["dbPath: string","row: unknown","rows: unknown[]","valid: DbNodeRow[]","valid: DbEdgeRow[]","state: CursorState","cursor: string","cursor: GraphPageCursor | null","limit: number","edges","done","parentId: string","cursor: string | null","s: string","id: string","total","nodes","db: SqliteDb","nodeId: string","rows: DbNodeRow[]","fileId: string","symbolId: string","allNodes: DbNodeRow[]"],"sources":["../src/adapters/codeGraphDb.ts"],"sourcesContent":["/**\r\n * codegraph.db 的连接池 + 行校验 + **节点侧**读取(cycle-110 Q7 行为等价提取)。\r\n *\r\n * 抽取契约:\r\n * · `dbPool` 是**唯一**的连接池(模块级单例)。连接按 dbPath 复用,超过 `MAX_DB_POOL_SIZE`\r\n * 按最久未用淘汰;`closeAllDbConnections()` 是测试与 dispose 的唯一清池入口。\r\n * 把池搬走不会产生第二个池——ESM 模块实例唯一。\r\n * · 所有读取函数遵循同一形状:拿不到连接返回 `null`;SQLite 抛错 catch 后 `log.warn` 并返回\r\n * `null`(调用方据此回落到\"整图取数\"),**不抛出**。这与重构前完全一致。\r\n * · 行一律经 `validateNodeRows` / `validateEdgeRows` 过滤:上游 DB 可能被半途写入,\r\n * 无效行只记 warn 不中断读取。\r\n * · 分页游标是 base64(JSON) 的不透明串;`encodeCursor`/`decodeCursor` 不校验版本,\r\n * 旧游标喂给新代码由调用方(`fetchData`)自己保证不可能发生。\r\n *\r\n * 边侧读取见 `codeGraphEdgeReads.ts`,行→图映射见 `codeGraphRows.ts`。\r\n */\r\nimport { existsSync } from 'node:fs';\r\nimport { createRequire } from 'node:module';\r\nimport { scoped } from '../shared/Logger.ts';\r\nimport { SQLITE } from '../shared/constants.ts';\r\nimport { EDGE_ROW_COLUMNS, type DbNodeRow, type DbEdgeRow, NOISE_PATH_SEGMENTS_EXPORT } from './codeGraphRows.ts';\r\nimport { collapseRepeatedBackslashes } from './nativeIndex.ts';\r\n\r\nconst log = scoped('codegraph-adapter');\r\n\r\nconst require = createRequire(import.meta.url);\r\n\r\n// ---- DB connection pool ---------------------------------------------------\r\n\r\ntype SqliteDb = {\r\n prepare(sql: string): { all(...params: unknown[]): unknown[]; get(...params: unknown[]): unknown };\r\n close(): void;\r\n};\r\n\r\nconst dbPool = new Map<string, SqliteDb>();\r\nconst MAX_DB_POOL_SIZE = 5;\r\n\r\nexport function getDbConnection(dbPath: string): SqliteDb | null {\r\n if (!existsSync(dbPath)) return null;\r\n const existing = dbPool.get(dbPath);\r\n if (existing) {\r\n dbPool.delete(dbPath);\r\n dbPool.set(dbPath, existing);\r\n return existing;\r\n }\r\n if (dbPool.size >= MAX_DB_POOL_SIZE) {\r\n const oldest = dbPool.keys().next().value;\r\n if (oldest !== undefined) {\r\n const oldDb = dbPool.get(oldest);\r\n dbPool.delete(oldest);\r\n try { oldDb?.close(); } catch { /* ignore */ }\r\n log.info('db pool: evicted connection', { dbPath: oldest });\r\n }\r\n }\r\n const { DatabaseSync } = require('node:sqlite') as typeof import('node:sqlite');\r\n const db = new DatabaseSync(dbPath, { readOnly: true }) as unknown as SqliteDb;\r\n dbPool.set(dbPath, db);\r\n log.info('db pool: opened connection', { dbPath, poolSize: dbPool.size });\r\n return db;\r\n}\r\n\r\nexport function closeAllDbConnections(): void {\r\n for (const [, db] of dbPool.entries()) {\r\n try {\r\n db.close();\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n dbPool.clear();\r\n log.info('db pool: all connections closed');\r\n}\r\n\r\nexport function countEdgesInDb(dbPath: string): number {\r\n const db = getDbConnection(dbPath);\r\n if (!db) return 0;\r\n try {\r\n const result = db.prepare('SELECT COUNT(*) as cnt FROM edges').get() as { cnt: number } | undefined;\r\n return result?.cnt ?? 0;\r\n } catch {\r\n return 0;\r\n }\r\n}\r\n\r\nfunction isDbNodeRow(row: unknown): row is DbNodeRow {\r\n if (typeof row !== 'object' || row === null) return false;\r\n const r = row as Record<string, unknown>;\r\n return typeof r.id === 'string' && typeof r.kind === 'string' &&\r\n typeof r.name === 'string' && typeof r.file_path === 'string';\r\n}\r\n\r\nfunction isDbEdgeRow(row: unknown): row is DbEdgeRow {\r\n if (typeof row !== 'object' || row === null) return false;\r\n const r = row as Record<string, unknown>;\r\n if (!(typeof r.source === 'string' && typeof r.target === 'string' &&\r\n typeof r.kind === 'string')) return false;\r\n // metadata:cycle-119 起参与 SELECT。行级校验**容忍缺失**(undefined 归一为 null)——\r\n // 直接喂字面量行的测试与旧 schema 的 fixture 不必同步建列;真正的列缺失会在\r\n // SELECT 处 throw(catch 落 null 返回),不会走到这里。\r\n if (r.metadata !== undefined && r.metadata !== null && typeof r.metadata !== 'string') return false;\r\n (r as { metadata: string | null }).metadata = r.metadata ?? null;\r\n return true;\r\n}\r\n\r\nexport function validateNodeRows(rows: unknown[]): DbNodeRow[] {\r\n const valid: DbNodeRow[] = [];\r\n let skipped = 0;\r\n for (const row of rows) {\r\n if (isDbNodeRow(row)) valid.push(row);\r\n else skipped++;\r\n }\r\n if (skipped > 0) log.warn('db node rows: skipped invalid rows', { skipped, total: rows.length });\r\n return valid;\r\n}\r\n\r\nexport function validateEdgeRows(rows: unknown[]): DbEdgeRow[] {\r\n const valid: DbEdgeRow[] = [];\r\n let skipped = 0;\r\n for (const row of rows) {\r\n if (isDbEdgeRow(row)) valid.push(row);\r\n else skipped++;\r\n }\r\n if (skipped > 0) log.warn('db edge rows: skipped invalid rows', { skipped, total: rows.length });\r\n return valid;\r\n}\r\n\r\n/** Read the whole graph from a codegraph.db. Returns null when unreadable. */\r\nexport function readGraphFromDb(dbPath: string): { nodes: DbNodeRow[]; edges: DbEdgeRow[] } | null {\r\n const db = getDbConnection(dbPath);\r\n if (!db) return null;\r\n try {\r\n const nodes = validateNodeRows(db.prepare(\r\n 'SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes',\r\n ).all());\r\n const edges = validateEdgeRows(db.prepare(\r\n `SELECT ${EDGE_ROW_COLUMNS} FROM edges`,\r\n ).all());\r\n return { nodes, edges };\r\n } catch (e) {\r\n log.warn('readGraphFromDb failed', e);\r\n return null;\r\n }\r\n}\r\n\r\nexport type GraphPageCursor = string;\r\n\r\nexport interface GraphPageResult {\r\n nodes: DbNodeRow[];\r\n edges: DbEdgeRow[];\r\n nextCursor: GraphPageCursor | null;\r\n done: boolean;\r\n stats: { nodeCount: number; edgeCount: number; phase: 'nodes' | 'edges' | 'done' };\r\n}\r\n\r\ninterface CursorState {\r\n phase: 'nodes' | 'edges';\r\n nodeCursor: string | null;\r\n edgeCursor: number | null;\r\n totalNodes: number;\r\n totalEdges: number;\r\n}\r\n\r\nfunction encodeCursor(state: CursorState): string {\r\n return Buffer.from(JSON.stringify(state), 'utf8').toString('base64');\r\n}\r\n\r\nfunction decodeCursor(cursor: string): CursorState {\r\n return JSON.parse(Buffer.from(cursor, 'base64').toString('utf8')) as CursorState;\r\n}\r\n\r\nexport function readGraphFromDbPaged(\r\n dbPath: string,\r\n cursor: GraphPageCursor | null,\r\n limit: number,\r\n): GraphPageResult | null {\r\n const db = getDbConnection(dbPath);\r\n if (!db) return null;\r\n\r\n const state: CursorState = cursor\r\n ? decodeCursor(cursor)\r\n : { phase: 'nodes', nodeCursor: null, edgeCursor: null, totalNodes: 0, totalEdges: 0 };\r\n\r\n try {\r\n if (state.phase === 'nodes') {\r\n const nodes = validateNodeRows(db.prepare(\r\n 'SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE id > ? ORDER BY id LIMIT ?',\r\n ).all(state.nodeCursor ?? '', limit));\r\n state.totalNodes += nodes.length;\r\n\r\n if (nodes.length < limit) {\r\n state.phase = 'edges';\r\n state.nodeCursor = null;\r\n const edges = validateEdgeRows(db.prepare(\r\n `SELECT ${EDGE_ROW_COLUMNS} FROM edges ORDER BY id LIMIT ?`,\r\n ).all(limit));\r\n state.totalEdges += edges.length;\r\n state.edgeCursor = edges.length > 0 ? edges[edges.length - 1]!.id : null;\r\n const done = edges.length < limit;\r\n return {\r\n nodes, edges,\r\n nextCursor: done ? null : encodeCursor(state),\r\n done,\r\n stats: { nodeCount: state.totalNodes, edgeCount: state.totalEdges, phase: done ? 'done' : 'edges' },\r\n };\r\n }\r\n\r\n state.nodeCursor = nodes[nodes.length - 1]!.id;\r\n return {\r\n nodes, edges: [],\r\n nextCursor: encodeCursor(state),\r\n done: false,\r\n stats: { nodeCount: state.totalNodes, edgeCount: 0, phase: 'nodes' },\r\n };\r\n }\r\n\r\n const edges = validateEdgeRows(db.prepare(\r\n `SELECT ${EDGE_ROW_COLUMNS} FROM edges WHERE id > ? ORDER BY id LIMIT ?`,\r\n ).all(state.edgeCursor ?? 0, limit));\r\n state.totalEdges += edges.length;\r\n const done = edges.length < limit;\r\n state.edgeCursor = edges.length > 0 ? edges[edges.length - 1]!.id : null;\r\n return {\r\n nodes: [], edges,\r\n nextCursor: done ? null : encodeCursor(state),\r\n done,\r\n stats: { nodeCount: state.totalNodes, edgeCount: state.totalEdges, phase: done ? 'done' : 'edges' },\r\n };\r\n } catch (e) {\r\n log.warn('readGraphFromDbPaged failed', e);\r\n return null;\r\n }\r\n}\r\n\r\n// cycle-141:噪音路径 SQL 过滤——在 LIMIT 之前排除非源码目录。\r\n// 必须在 SQL 侧过滤而非映射侧:LIMIT 50 会选出前 50 行,如果 .cycles/\r\n// 归档目录(字母序 . < s)先被索引,映射阶段再过滤就只剩不到 50 个节点。\r\nfunction buildNoiseWhereClause(): string {\r\n const segments = NOISE_PATH_SEGMENTS_EXPORT;\r\n const filters = segments.map((seg) => \"REPLACE(file_path, '\\\\', '/') NOT LIKE '%/\" + seg + \"%'\");\r\n return filters.join(' AND ');\r\n}\r\nexport function readTopLevelNodesFromDb(dbPath: string, limit: number): { nodes: DbNodeRow[]; total: number } | null {\r\n const db = getDbConnection(dbPath);\r\n if (!db) return null;\r\n try {\r\n const noiseWhere = buildNoiseWhereClause();\r\n const kindWhere = \"kind IN ('file', 'module', 'import')\";\r\n // 【P0-R19-02】优先选出 dir:/file: 节点——避免 1777 个<import_statement>伪节点被 id 字母序挤出顶层(dsh-anic 真机症结)\r\n // 排序键:dir: > file: > import_statement module;次按长度再 id,保证稳定顺序\r\n const topLevelOrderBy = [\r\n \"CASE WHEN REPLACE(id,'\\\\','/') LIKE 'dir:%' THEN 1 WHEN REPLACE(id,'\\\\','/') LIKE 'file:%' THEN 2 ELSE 3 END\",\r\n \"LENGTH(file_path)\",\r\n \"id\"\r\n ].join(', ');\r\n \r\n const whereClause = noiseWhere ? kindWhere + ' AND ' + noiseWhere : kindWhere;\r\n const total = (db.prepare('SELECT COUNT(*) as cnt FROM nodes WHERE ' + whereClause).get() as { cnt: number }).cnt;\r\n const nodes = validateNodeRows(db.prepare(\r\n `SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported\r\n FROM nodes WHERE ${whereClause} ORDER BY ${topLevelOrderBy} LIMIT ?`,\r\n ).all(limit));\r\n // 存量脏库兼容:修复前写入的行 id/file_path 带双反斜杠——返回前折叠,保证\r\n // bootstrap 下发的 id 与后续 children/edges 查询参数同域(单形态)。\r\n for (const row of nodes) {\r\n (row as { id: string }).id = collapseRepeatedBackslashes(row.id);\r\n (row as { file_path: string }).file_path = collapseRepeatedBackslashes(row.file_path);\r\n }\r\n return { nodes, total };\r\n } catch (e) {\r\n log.warn('readTopLevelNodesFromDb failed', e);\r\n return null;\r\n }\r\n}\r\n\r\nexport function readNodeChildrenFromDb(dbPath: string, parentId: string, cursor: string | null, limit: number): { nodes: DbNodeRow[]; total: number } | null {\r\n const db = getDbConnection(dbPath);\r\n if (!db) return null;\r\n try {\r\n // cycle-141 第六轮(dsh-anic 真机复现:展开 src 目录 total=0):**dir: 目录节点\r\n // 走 contains 边语义**。原生索引的目录树是 dir→dir / dir→file 的 contains 边\r\n //(FileSystemAdapter 生成),现有「file_path = 父.file_path AND kind NOT IN 顶层」\r\n // 语义只为上游索引的 file→符号 设计——对 dir: 节点该查询恒返回 0(实测:\r\n // dir 节点 existing children semantics = 0,而 contains 边有 3 条)。\r\n // dir: 分支按 contains 边读孩子(分页 + 与文件符号同分页语义);file: 分支\r\n // 保持原语义逐字节不变(上游行为零改动)。\r\n //\r\n // 存量脏库兼容(collapseRepeatedBackslashes 的对称问题):修复前写入的库\r\n // id/边端点带**双反斜杠**(真机 dsh-anic 实测),而修复后客户端拿到的 bootstrap id\r\n // 是折叠后的单反斜杠——直接用单形态查双形态库必落空。查询参数与脏库值同时\r\n // 归一到「原样」域做不到(不知道库是哪个形态),所以做**双形态查询**:\r\n // 先按调用方形态查,0 命中且 parentId 含反斜杠时**扩展成双反斜杠**再查一次\r\n //(单→双是脏库方向:脏库每个分隔符都是两个字符)。写侧已消毒,新库恒单形态、\r\n // 第一查必命中,双查是纯存量兼容路径。\r\n if (parentId.startsWith('dir:')) {\r\n const expandBackslashes = (s: string) => s.replace(/\\\\/g, '\\\\\\\\');\r\n const probe = (id: string) =>\r\n (db.prepare('SELECT COUNT(*) as cnt FROM edges WHERE source = ? AND kind = \\'contains\\'').get(id) as { cnt: number }).cnt;\r\n let effectiveId = parentId;\r\n if (probe(parentId) === 0 && parentId.includes('\\\\')) {\r\n const expanded = expandBackslashes(parentId);\r\n if (probe(expanded) > 0) effectiveId = expanded;\r\n }\r\n const total = (db.prepare('SELECT COUNT(*) as cnt FROM edges WHERE source = ? AND kind = \\'contains\\'').get(effectiveId) as { cnt: number }).cnt;\r\n const rows = cursor\r\n ? db.prepare(\r\n 'SELECT e.target AS target FROM edges e WHERE e.source = ? AND e.kind = \\'contains\\' AND e.target > ? ORDER BY e.target LIMIT ?',\r\n ).all(effectiveId, cursor, limit) as { target: string }[]\r\n : db.prepare(\r\n 'SELECT e.target AS target FROM edges e WHERE e.source = ? AND e.kind = \\'contains\\' ORDER BY e.target LIMIT ?',\r\n ).all(effectiveId, limit) as { target: string }[];\r\n if (rows.length === 0) return { nodes: [], total };\r\n const targets = rows.map((r) => r.target);\r\n const placeholders = targets.map(() => '?').join(',');\r\n const nodes = validateNodeRows(db.prepare(\r\n `SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE id IN (${placeholders})`,\r\n ).all(...targets));\r\n // 脏库行 id 也是双形态:随行返回(客户端可见)前折叠——bootstrap 与 children\r\n // 的 id 域必须一致,否则客户端展开后挂不上树。\r\n for (const row of nodes) {\r\n (row as { id: string }).id = collapseRepeatedBackslashes(row.id);\r\n (row as { file_path: string }).file_path = collapseRepeatedBackslashes(row.file_path);\r\n }\r\n // 按 target id 排序(IN 的返回顺序不保证稳定——分页游标语义要求与 ORDER BY target 一致)。\r\n const order = new Map(targets.map((t, i) => [t, i]));\r\n nodes.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));\r\n // 返回形状与 file: 分支一致({nodes, total}):cursor 推进由调用方用\r\n // 「本页最后一条 id」判定(dataRoutes /children 的既有约定),不在此引入第二套游标语义。\r\n return { nodes, total };\r\n }\r\n const total = (db.prepare('SELECT COUNT(*) as cnt FROM nodes WHERE file_path = (SELECT file_path FROM nodes WHERE id = ?) AND kind NOT IN (\\'file\\', \\'module\\', \\'import\\')').get(resolveNodeIdForQuery(db, parentId)) as { cnt: number }).cnt;\r\n const effectiveParent = resolveNodeIdForQuery(db, parentId);\r\n const nodes = cursor\r\n ? validateNodeRows(db.prepare(\r\n 'SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE file_path = (SELECT file_path FROM nodes WHERE id = ?) AND kind NOT IN (\\'file\\', \\'module\\', \\'import\\') AND id > ? ORDER BY id LIMIT ?',\r\n ).all(effectiveParent, cursor, limit))\r\n : validateNodeRows(db.prepare(\r\n 'SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE file_path = (SELECT file_path FROM nodes WHERE id = ?) AND kind NOT IN (\\'file\\', \\'module\\', \\'import\\') ORDER BY id LIMIT ?',\r\n ).all(effectiveParent, limit));\r\n // 脏库行折叠(与 dir: 分支同一理由):返回 id 与 bootstrap 域一致。\r\n for (const row of nodes) {\r\n (row as { id: string }).id = collapseRepeatedBackslashes(row.id);\r\n (row as { file_path: string }).file_path = collapseRepeatedBackslashes(row.file_path);\r\n }\r\n return { nodes, total };\r\n } catch (e) {\r\n log.warn('readNodeChildrenFromDb failed', e);\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * 存量脏库的 id 归一查询(cycle-141 第六轮)。\r\n *\r\n * 修复前写入的库 id 带双反斜杠(真机 dsh-anic 实测),而修复后调用方传的是折叠\r\n * 后的单形态 id——按单形态查脏库必落空。策略:先按调用方原样查(新库恒命中);\r\n * 0 命中且 id 含反斜杠时**扩展成双反斜杠**再查一次(单→双是脏库方向:脏库每个\r\n * 分隔符都是两个字符)。命中则返回扩展形态——调用方用它做实际查询。\r\n */\r\nfunction resolveNodeIdForQuery(db: SqliteDb, nodeId: string): string {\r\n const exists = db.prepare('SELECT 1 AS ok FROM nodes WHERE id = ?').get(nodeId);\r\n if (exists) return nodeId;\r\n if (!nodeId.includes('\\\\')) return nodeId;\r\n const expanded = nodeId.replace(/\\\\/g, '\\\\\\\\');\r\n const existsExpanded = db.prepare('SELECT 1 AS ok FROM nodes WHERE id = ?').get(expanded);\r\n return existsExpanded ? expanded : nodeId;\r\n}\r\n\r\n/**\r\n * 脏库行消毒:id/file_path 折叠回单形态再返回给调用方。\r\n *\r\n * 返回行是客户端可见的数据面——bootstrap(本文件顶层读取)与 children/outline\r\n * 的 id 域必须一致,否则客户端展开后挂不上树(真机 \"Projects (50)\" 家族的\r\n * 第二形态:不是查不到数据,而是两批数据的 id 域不同)。\r\n */\r\nfunction sanitizeDirtyRows(rows: DbNodeRow[]): DbNodeRow[] {\r\n for (const row of rows) {\r\n (row as { id: string }).id = collapseRepeatedBackslashes(row.id);\r\n (row as { file_path: string }).file_path = collapseRepeatedBackslashes(row.file_path);\r\n }\r\n return rows;\r\n}\r\n\r\nexport function readFileOutlineFromDb(dbPath: string, fileId: string): { nodes: DbNodeRow[]; total: number } | null {\r\n const db = getDbConnection(dbPath);\r\n if (!db) return null;\r\n try {\r\n const effectiveId = resolveNodeIdForQuery(db, fileId);\r\n const nodes = sanitizeDirtyRows(validateNodeRows(db.prepare(\r\n `SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE file_path = (SELECT file_path FROM nodes WHERE id = ?) AND kind NOT IN ('file', 'module', 'import')`,\r\n ).all(effectiveId)));\r\n return { nodes, total: nodes.length };\r\n } catch (e) {\r\n log.warn('readFileOutlineFromDb failed', e);\r\n return null;\r\n }\r\n}\r\n\r\nexport function readSymbolASTFromDb(dbPath: string, symbolId: string): { nodes: DbNodeRow[]; total: number } | null {\r\n const db = getDbConnection(dbPath);\r\n if (!db) return null;\r\n try {\r\n const effectiveId = resolveNodeIdForQuery(db, symbolId);\r\n const symbolRow = db.prepare('SELECT file_path, start_line FROM nodes WHERE id = ?').get(effectiveId) as { file_path: string; start_line: number } | undefined;\r\n if (!symbolRow) return { nodes: [], total: 0 };\r\n const total = (db.prepare('SELECT COUNT(*) as cnt FROM edges WHERE source = ?').get(effectiveId) as { cnt: number }).cnt;\r\n const calleeIds = db.prepare('SELECT target FROM edges WHERE source = ?').all(effectiveId) as { target: string }[];\r\n if (calleeIds.length === 0) return { nodes: [], total: 0 };\r\n const idList = calleeIds.map(r => r.target);\r\n const allNodes: DbNodeRow[] = [];\r\n const batchSize = SQLITE.MAX_IN_PARAMS;\r\n for (let i = 0; i < idList.length; i += batchSize) {\r\n const batch = idList.slice(i, i + batchSize);\r\n const placeholders = batch.map(() => '?').join(',');\r\n const batchNodes = validateNodeRows(db.prepare(\r\n `SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes WHERE id IN (${placeholders})`,\r\n ).all(...batch));\r\n allNodes.push(...batchNodes);\r\n }\r\n return { nodes: sanitizeDirtyRows(allNodes), total };\r\n } catch (e) {\r\n log.warn('readSymbolASTFromDb failed', e);\r\n return null;\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import "./types-BUDxI_00.js";
|
|
2
|
+
import "./constants-CQq93l3e.js";
|
|
3
|
+
import "./Logger-DUIJSeNu.js";
|
|
4
|
+
import "./layerUtils-DDm-EsaD.js";
|
|
5
|
+
import "./codeGraphRows-C7hqu35E.js";
|
|
6
|
+
import "./nativeIndex-DlSpD611.js";
|
|
7
|
+
import { closeAllDbConnections, countEdgesInDb, getDbConnection, readFileOutlineFromDb, readGraphFromDb, readGraphFromDbPaged, readNodeChildrenFromDb, readSymbolASTFromDb, readTopLevelNodesFromDb, validateEdgeRows, validateNodeRows } from "./codeGraphDb-BT9nZCEO.js";
|
|
8
|
+
|
|
9
|
+
export { closeAllDbConnections, countEdgesInDb, getDbConnection, readFileOutlineFromDb, readGraphFromDb, readGraphFromDbPaged, readNodeChildrenFromDb, readSymbolASTFromDb, readTopLevelNodesFromDb, validateEdgeRows, validateNodeRows };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "./types-BUDxI_00.js";
|
|
2
|
+
import "./constants-CQq93l3e.js";
|
|
3
|
+
import "./layerUtils-DDm-EsaD.js";
|
|
4
|
+
import { EDGE_ROW_COLUMNS, FILTERED_EDGE_KINDS, NOISE_PATH_SEGMENTS_EXPORT, isNoisePath, mapEdgeKind, mapNodeKind, mapRowsToGraphEdges, mapRowsToGraphNodes, normalizeRepoPath, parseEdgeConfidence } from "./codeGraphRows-C7hqu35E.js";
|
|
5
|
+
|
|
6
|
+
export { EDGE_ROW_COLUMNS, FILTERED_EDGE_KINDS, NOISE_PATH_SEGMENTS_EXPORT, isNoisePath, mapEdgeKind, mapNodeKind, mapRowsToGraphEdges, mapRowsToGraphNodes, normalizeRepoPath, parseEdgeConfidence };
|