@reqlan/analytical 0.9.1 → 0.10.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 +13 -2
- package/README.template.md +1 -1
- package/out/core/barrel-page.d.ts +2 -1
- package/out/core/barrel-page.js +18 -83
- package/out/core/barrel-page.js.map +1 -1
- package/out/core/base-discovery.d.ts +6 -0
- package/out/core/base-discovery.js +47 -2
- package/out/core/base-discovery.js.map +1 -1
- package/out/core/create-base.js +10 -14
- package/out/core/create-base.js.map +1 -1
- package/out/core/file-reference-resolve.js +3 -1
- package/out/core/file-reference-resolve.js.map +1 -1
- package/out/core/index-error.d.ts +3 -3
- package/out/core/index-error.js.map +1 -1
- package/out/core/index-state-types.d.ts +47 -0
- package/out/core/index-state-types.js +11 -0
- package/out/core/index-state-types.js.map +1 -0
- package/out/core/path-relative.d.ts +15 -0
- package/out/core/path-relative.js +84 -0
- package/out/core/path-relative.js.map +1 -0
- package/out/core/rqignore.js +62 -62
- package/out/core/rqignore.js.map +1 -1
- package/out/core/types.d.ts +4 -3
- package/out/core/types.js +4 -1
- package/out/core/types.js.map +1 -1
- package/out/core/workspace-paths.d.ts +1 -2
- package/out/core/workspace-paths.js +6 -46
- package/out/core/workspace-paths.js.map +1 -1
- package/out/export/build-export-snapshot.js +1 -1
- package/out/export/build-export-snapshot.js.map +1 -1
- package/out/export/export-utils.d.ts +5 -0
- package/out/export/export-utils.js +38 -0
- package/out/export/export-utils.js.map +1 -0
- package/out/export/write-csv-export.js +1 -1
- package/out/export/write-csv-export.js.map +1 -1
- package/out/export/write-markdown-export.js +1 -1
- package/out/export/write-markdown-export.js.map +1 -1
- package/out/index-store/base-registry.d.ts +2 -3
- package/out/index-store/base-registry.js +8 -4
- package/out/index-store/base-registry.js.map +1 -1
- package/out/index-store/fuzzy-search-hit.d.ts +10 -0
- package/out/index-store/fuzzy-search-hit.js +2 -0
- package/out/index-store/fuzzy-search-hit.js.map +1 -0
- package/out/index-store/index-diagnostics-store.d.ts +4 -0
- package/out/index-store/index-diagnostics-store.js +44 -88
- package/out/index-store/index-diagnostics-store.js.map +1 -1
- package/out/index-store/index-file-error.d.ts +1 -1
- package/out/index-store/index-parse-issues.d.ts +4 -3
- package/out/index-store/index-parse-issues.js +0 -38
- package/out/index-store/index-parse-issues.js.map +1 -1
- package/out/index-store/index-status.d.ts +1 -1
- package/out/index-store/sqlite-store.d.ts +6 -2
- package/out/index-store/sqlite-store.js +70 -613
- package/out/index-store/sqlite-store.js.map +1 -1
- package/out/index-store/webview-graph-queries.d.ts +5 -0
- package/out/index-store/webview-graph-queries.js.map +1 -1
- package/out/index-store/workspace-index-file.d.ts +3 -14
- package/out/index-store/workspace-index-file.js +1 -114
- package/out/index-store/workspace-index-file.js.map +1 -1
- package/out/index-store/workspace-index-sync.d.ts +4 -17
- package/out/index-store/workspace-index-sync.js +29 -11
- package/out/index-store/workspace-index-sync.js.map +1 -1
- package/out/index-store/workspace-index.d.ts +36 -9
- package/out/index-store/workspace-index.js +223 -120
- package/out/index-store/workspace-index.js.map +1 -1
- package/out/index.d.ts +16 -43
- package/out/index.js +9 -25
- package/out/index.js.map +1 -1
- package/out/native/generated.d.ts +102 -0
- package/out/native/generated.js +4 -0
- package/out/native/generated.js.map +1 -0
- package/out/native/index.d.ts +17 -0
- package/out/native/index.js +12 -0
- package/out/native/index.js.map +1 -0
- package/out/native/load-native.d.ts +50 -0
- package/out/native/load-native.js +152 -0
- package/out/native/load-native.js.map +1 -0
- package/out/{analysis-api.d.ts → native/native-analysis-api.d.ts} +19 -19
- package/out/native/native-analysis-api.js +212 -0
- package/out/native/native-analysis-api.js.map +1 -0
- package/out/native/native-index-store.d.ts +5 -0
- package/out/native/native-index-store.js +6 -0
- package/out/native/native-index-store.js.map +1 -0
- package/out/native/native-sql-db.d.ts +135 -0
- package/out/native/native-sql-db.js +217 -0
- package/out/native/native-sql-db.js.map +1 -0
- package/out/native/native-workspace-index.d.ts +164 -0
- package/out/native/native-workspace-index.js +283 -0
- package/out/native/native-workspace-index.js.map +1 -0
- package/out/native/open-analysis-api.d.ts +16 -0
- package/out/native/open-analysis-api.js +21 -0
- package/out/native/open-analysis-api.js.map +1 -0
- package/out/native/parse-source.d.ts +24 -0
- package/out/native/parse-source.js +26 -0
- package/out/native/parse-source.js.map +1 -0
- package/package.json +18 -30
- package/out/analysis/analyser-registry.d.ts +0 -21
- package/out/analysis/analyser-registry.js +0 -33
- package/out/analysis/analyser-registry.js.map +0 -1
- package/out/analysis/completion-tracking-analyser.d.ts +0 -3
- package/out/analysis/completion-tracking-analyser.js +0 -37
- package/out/analysis/completion-tracking-analyser.js.map +0 -1
- package/out/analysis/contextual-search.d.ts +0 -26
- package/out/analysis/contextual-search.js +0 -94
- package/out/analysis/contextual-search.js.map +0 -1
- package/out/analysis/deprecation-impact-analyser.d.ts +0 -3
- package/out/analysis/deprecation-impact-analyser.js +0 -21
- package/out/analysis/deprecation-impact-analyser.js.map +0 -1
- package/out/analysis/file-related-analyser.d.ts +0 -5
- package/out/analysis/file-related-analyser.js +0 -41
- package/out/analysis/file-related-analyser.js.map +0 -1
- package/out/analysis/fuzzy-search-analyser.d.ts +0 -17
- package/out/analysis/fuzzy-search-analyser.js +0 -25
- package/out/analysis/fuzzy-search-analyser.js.map +0 -1
- package/out/analysis/fuzzy-search-worker-client.d.ts +0 -39
- package/out/analysis/fuzzy-search-worker-client.js +0 -178
- package/out/analysis/fuzzy-search-worker-client.js.map +0 -1
- package/out/analysis/fuzzy-search-worker.d.ts +0 -32
- package/out/analysis/fuzzy-search-worker.js +0 -81
- package/out/analysis/fuzzy-search-worker.js.map +0 -1
- package/out/analysis/fuzzy-search.d.ts +0 -62
- package/out/analysis/fuzzy-search.js +0 -471
- package/out/analysis/fuzzy-search.js.map +0 -1
- package/out/analysis/git-dates-analyser.d.ts +0 -12
- package/out/analysis/git-dates-analyser.js +0 -96
- package/out/analysis/git-dates-analyser.js.map +0 -1
- package/out/analysis/list-ideas-analyser.d.ts +0 -3
- package/out/analysis/list-ideas-analyser.js +0 -7
- package/out/analysis/list-ideas-analyser.js.map +0 -1
- package/out/analysis/local-graph-analyser.d.ts +0 -6
- package/out/analysis/local-graph-analyser.js +0 -42
- package/out/analysis/local-graph-analyser.js.map +0 -1
- package/out/analysis/semantic-search-analyser.d.ts +0 -8
- package/out/analysis/semantic-search-analyser.js +0 -51
- package/out/analysis/semantic-search-analyser.js.map +0 -1
- package/out/analysis-api.js +0 -199
- package/out/analysis-api.js.map +0 -1
- package/out/core/analytical-store.d.ts +0 -85
- package/out/core/analytical-store.js +0 -143
- package/out/core/analytical-store.js.map +0 -1
- package/out/core/context-model.d.ts +0 -204
- package/out/core/context-model.js +0 -36
- package/out/core/context-model.js.map +0 -1
- package/out/core/context-signals.d.ts +0 -154
- package/out/core/context-signals.js +0 -406
- package/out/core/context-signals.js.map +0 -1
- package/out/create-runtime.d.ts +0 -47
- package/out/create-runtime.js +0 -79
- package/out/create-runtime.js.map +0 -1
- package/out/export/export-html.d.ts +0 -4
- package/out/export/export-html.js +0 -12
- package/out/export/export-html.js.map +0 -1
- package/out/export/html-export-assets.d.ts +0 -3
- package/out/export/html-export-assets.js +0 -2294
- package/out/export/html-export-assets.js.map +0 -1
- package/out/export/html-export-template.d.ts +0 -18
- package/out/export/html-export-template.js +0 -1154
- package/out/export/html-export-template.js.map +0 -1
- package/out/export/html-export-utils.d.ts +0 -86
- package/out/export/html-export-utils.js +0 -485
- package/out/export/html-export-utils.js.map +0 -1
- package/out/export/write-html-export.d.ts +0 -2
- package/out/export/write-html-export.js +0 -181
- package/out/export/write-html-export.js.map +0 -1
- package/out/graph/physics-core-source.d.ts +0 -5
- package/out/graph/physics-core-source.js +0 -8
- package/out/graph/physics-core-source.js.map +0 -1
- package/out/graph/physics-core.d.ts +0 -141
- package/out/graph/physics-core.js +0 -237
- package/out/graph/physics-core.js.map +0 -1
- package/out/index-store/file-treatment.d.ts +0 -40
- package/out/index-store/file-treatment.js +0 -144
- package/out/index-store/file-treatment.js.map +0 -1
- package/out/index-store/idea-extractor.d.ts +0 -11
- package/out/index-store/idea-extractor.js +0 -351
- package/out/index-store/idea-extractor.js.map +0 -1
- package/out/index-store/overview-coverage.d.ts +0 -23
- package/out/index-store/overview-coverage.js +0 -254
- package/out/index-store/overview-coverage.js.map +0 -1
|
@@ -1,112 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SQLite-backed persistence for the workspace idea graph index.
|
|
3
|
-
*
|
|
3
|
+
* Backed by rusqlite via the core native engine (NativeSqlDb).
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { basename
|
|
7
|
-
import {
|
|
5
|
+
import { unlink } from 'node:fs/promises';
|
|
6
|
+
import { basename } from 'node:path';
|
|
7
|
+
import { NativeSqlConnection } from '../native/native-sql-db.js';
|
|
8
8
|
import { ACTIVITY_BAR_TODO_LIMIT, BLOCKING_STATUSES, ideaStatus, ideaTags, parseAttributes } from '../core/types.js';
|
|
9
9
|
import { statusFilterKeyFromAttributes, tagsFilterKeysFromAttributes, todoNoteFromAttributes } from '../core/filter-specials.js';
|
|
10
10
|
import { resolveReferencedFilePath } from '../core/file-reference-resolve.js';
|
|
11
|
-
import {
|
|
12
|
-
import { aggregateAttributesFromRows, attributeValuesForKeys, buildIdeasetsOrderClause, buildIdeasetsWhereClause, buildIdeasOrderClause, buildIdeasWhereClause, buildReferencesOrderClause, buildReferencesWhereClause, filterAndPageAttributes } from './webview-table-queries.js';
|
|
13
|
-
import { buildGraphFilterWhereClause } from './webview-graph-queries.js';
|
|
11
|
+
import { aggregateAttributesFromRows, attributeValuesForKeys, filterAndPageAttributes } from './webview-table-queries.js';
|
|
14
12
|
export class SqliteIndexStore {
|
|
15
13
|
constructor(db) {
|
|
16
14
|
this.db = db;
|
|
17
15
|
}
|
|
18
16
|
static async open(dbPath) {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
const conn = await NativeSqlConnection.open(dbPath);
|
|
18
|
+
return SqliteIndexStore.fromConnection(conn);
|
|
19
|
+
}
|
|
20
|
+
/** Wrap an already-open native connection (e.g. NativeWorkspaceIndex ideas DB). */
|
|
21
|
+
static async fromConnection(conn) {
|
|
22
|
+
const store = new SqliteIndexStore({ conn });
|
|
22
23
|
await store.migrate();
|
|
23
24
|
return store;
|
|
24
25
|
}
|
|
25
26
|
async close() {
|
|
26
|
-
|
|
27
|
+
this.db.conn.close();
|
|
27
28
|
}
|
|
28
|
-
/** Close
|
|
29
|
+
/** Close without extra flush work (file-backed rusqlite already durable). */
|
|
29
30
|
closeWithoutPersist() {
|
|
30
|
-
this.db.
|
|
31
|
+
this.db.conn.close();
|
|
31
32
|
}
|
|
32
33
|
static async deleteDatabaseFile(dbPath) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
35
|
+
try {
|
|
36
|
+
await unlink(`${dbPath}${suffix}`);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (!isMissingFileError(error)) {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
async getDocumentHash(fileUri) {
|
|
43
|
-
|
|
44
|
-
return row?.content_hash;
|
|
46
|
+
return this.db.conn.getDocumentHash(fileUri) ?? undefined;
|
|
45
47
|
}
|
|
46
48
|
async getDocumentMtimeMs(fileUri) {
|
|
47
|
-
const
|
|
48
|
-
return
|
|
49
|
+
const value = this.db.conn.getDocumentMtimeMs(fileUri);
|
|
50
|
+
return value == null ? undefined : value;
|
|
49
51
|
}
|
|
50
52
|
async updateDocumentMtime(fileUri, mtimeMs) {
|
|
51
|
-
|
|
53
|
+
this.db.conn.updateDocumentMtime(fileUri, mtimeMs);
|
|
52
54
|
}
|
|
53
55
|
async listDocumentMtimes() {
|
|
54
|
-
const rows =
|
|
56
|
+
const rows = this.db.conn.listDocumentMtimeRows();
|
|
55
57
|
const result = new Map();
|
|
56
58
|
for (const row of rows) {
|
|
57
59
|
result.set(row.file_uri, row.mtime_ms == null ? undefined : row.mtime_ms);
|
|
58
60
|
}
|
|
59
61
|
return result;
|
|
60
62
|
}
|
|
63
|
+
async listDocumentUris() {
|
|
64
|
+
// rq:["../../../reqlan rq/core_analysis/search.rq".file_search]
|
|
65
|
+
return this.db.conn.listDocumentUris();
|
|
66
|
+
}
|
|
61
67
|
async upsertDocument(fileUri, contentHash, ideas, edges, mtimeMs) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Preserve analyser-populated git dates/counts across delete+reinsert reindex.
|
|
65
|
-
const existingGit = await all(this.db, `
|
|
66
|
-
SELECT id, git_created_at, git_modified_at, git_change_count
|
|
67
|
-
FROM ideas
|
|
68
|
-
WHERE file_uri = ?
|
|
69
|
-
`, fileUri);
|
|
70
|
-
const gitById = new Map(existingGit.map(row => [row.id, row]));
|
|
71
|
-
await run(this.db, 'DELETE FROM edges WHERE source_id IN (SELECT id FROM ideas WHERE file_uri = ?)', fileUri);
|
|
72
|
-
await run(this.db, 'DELETE FROM ideas WHERE file_uri = ?', fileUri);
|
|
73
|
-
const insertIdeaSql = `
|
|
74
|
-
INSERT INTO ideas (
|
|
75
|
-
id, name, kind, file_uri, line_start, line_end, summary,
|
|
76
|
-
attributes_json, content_hash, git_created_at, git_modified_at, git_change_count
|
|
77
|
-
) VALUES (
|
|
78
|
-
?, ?, ?, ?, ?, ?, ?,
|
|
79
|
-
?, ?, ?, ?, ?
|
|
80
|
-
)
|
|
81
|
-
`;
|
|
82
|
-
for (const idea of ideas) {
|
|
83
|
-
const previous = gitById.get(idea.id);
|
|
84
|
-
await run(this.db, insertIdeaSql, idea.id, idea.name, idea.kind, idea.fileUri, idea.lineStart, idea.lineEnd, idea.summary, idea.attributesJson, idea.contentHash, idea.gitCreatedAt ?? previous?.git_created_at ?? null, idea.gitModifiedAt ?? previous?.git_modified_at ?? null, idea.gitChangeCount ?? previous?.git_change_count ?? null);
|
|
85
|
-
}
|
|
86
|
-
const insertEdgeSql = `
|
|
87
|
-
INSERT OR IGNORE INTO edges (
|
|
88
|
-
id, source_id, target_id, target_file, kind, label,
|
|
89
|
-
source_line, snippet, is_resolved
|
|
90
|
-
)
|
|
91
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
92
|
-
`;
|
|
93
|
-
for (const edge of edges) {
|
|
94
|
-
await run(this.db, insertEdgeSql, edge.id, edge.sourceId, edge.targetId ?? null, edge.targetFile ?? null, edge.kind, edge.label ?? null, edge.sourceLine ?? null, edge.snippet ?? null, edge.isResolved === false ? 0 : 1);
|
|
95
|
-
}
|
|
96
|
-
await run(this.db, `
|
|
97
|
-
INSERT INTO documents (file_uri, content_hash, indexed_at, mtime_ms)
|
|
98
|
-
VALUES (?, ?, datetime('now'), ?)
|
|
99
|
-
ON CONFLICT(file_uri) DO UPDATE SET
|
|
100
|
-
content_hash = excluded.content_hash,
|
|
101
|
-
indexed_at = excluded.indexed_at,
|
|
102
|
-
mtime_ms = excluded.mtime_ms
|
|
103
|
-
`, fileUri, contentHash, mtimeMs ?? null);
|
|
104
|
-
await run(this.db, 'COMMIT');
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
await run(this.db, 'ROLLBACK');
|
|
108
|
-
throw error;
|
|
109
|
-
}
|
|
68
|
+
// Native transaction preserves analyser-populated git dates across the reindex.
|
|
69
|
+
this.db.conn.upsertDocument(fileUri, contentHash, ideas, edges, mtimeMs);
|
|
110
70
|
}
|
|
111
71
|
async removeDocument(fileUri) {
|
|
112
72
|
await this.removeDocuments([fileUri]);
|
|
@@ -116,39 +76,13 @@ export class SqliteIndexStore {
|
|
|
116
76
|
if (fileUris.length === 0) {
|
|
117
77
|
return;
|
|
118
78
|
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
for (const fileUri of fileUris) {
|
|
122
|
-
await run(this.db, 'DELETE FROM edges WHERE source_id IN (SELECT id FROM ideas WHERE file_uri = ?)', fileUri);
|
|
123
|
-
await run(this.db, 'DELETE FROM ideas WHERE file_uri = ?', fileUri);
|
|
124
|
-
await run(this.db, 'DELETE FROM documents WHERE file_uri = ?', fileUri);
|
|
125
|
-
}
|
|
126
|
-
await run(this.db, 'COMMIT');
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
await run(this.db, 'ROLLBACK');
|
|
130
|
-
throw error;
|
|
131
|
-
}
|
|
79
|
+
this.db.conn.removeDocuments(fileUris);
|
|
132
80
|
}
|
|
133
81
|
async clearAll() {
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
await run(this.db, 'DELETE FROM edges');
|
|
137
|
-
await run(this.db, 'DELETE FROM ideas');
|
|
138
|
-
await run(this.db, 'DELETE FROM documents');
|
|
139
|
-
await run(this.db, 'COMMIT');
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
await run(this.db, 'ROLLBACK');
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
82
|
+
this.db.conn.clearAll();
|
|
145
83
|
}
|
|
146
84
|
async listAllIdeas() {
|
|
147
|
-
const rows =
|
|
148
|
-
SELECT id, name, kind, file_uri, line_start, summary, attributes_json
|
|
149
|
-
FROM ideas
|
|
150
|
-
ORDER BY file_uri, line_start
|
|
151
|
-
`);
|
|
85
|
+
const rows = this.db.conn.listAllIdeaRows();
|
|
152
86
|
const ideas = [];
|
|
153
87
|
for (let index = 0; index < rows.length; index += 1) {
|
|
154
88
|
if (index > 0 && index % 250 === 0) {
|
|
@@ -159,109 +93,38 @@ export class SqliteIndexStore {
|
|
|
159
93
|
return ideas;
|
|
160
94
|
}
|
|
161
95
|
async getIdea(id) {
|
|
162
|
-
const row =
|
|
163
|
-
SELECT id, name, kind, file_uri, line_start, summary, attributes_json,
|
|
164
|
-
git_created_at, git_modified_at, git_change_count
|
|
165
|
-
FROM ideas WHERE id = ?
|
|
166
|
-
`, id);
|
|
96
|
+
const row = this.db.conn.getIdeaRow(id);
|
|
167
97
|
return row ? this.toSummary(row) : undefined;
|
|
168
98
|
}
|
|
169
99
|
async getIdeasInFile(fileUri) {
|
|
170
|
-
const rows =
|
|
171
|
-
SELECT id, name, kind, file_uri, line_start, summary, attributes_json
|
|
172
|
-
FROM ideas WHERE file_uri = ?
|
|
173
|
-
ORDER BY line_start
|
|
174
|
-
`, fileUri);
|
|
100
|
+
const rows = this.db.conn.getIdeasInFileRows(fileUri);
|
|
175
101
|
return rows.map(row => this.toSummary(row));
|
|
176
102
|
}
|
|
177
103
|
async getIdeaAtLine(fileUri, line) {
|
|
178
|
-
const row =
|
|
179
|
-
SELECT id, name, kind, file_uri, line_start, summary, attributes_json,
|
|
180
|
-
git_created_at, git_modified_at, git_change_count
|
|
181
|
-
FROM ideas
|
|
182
|
-
WHERE file_uri = ? AND line_start <= ? AND ? <= line_end AND kind != 'ideaset'
|
|
183
|
-
ORDER BY line_start DESC
|
|
184
|
-
LIMIT 1
|
|
185
|
-
`, fileUri, line, line);
|
|
104
|
+
const row = this.db.conn.getIdeaAtLineRow(fileUri, line);
|
|
186
105
|
return row ? this.toSummary(row) : undefined;
|
|
187
106
|
}
|
|
188
107
|
/** Innermost ideaset whose line range contains `line` (0-based). */
|
|
189
108
|
async getIdeasetAtLine(fileUri, line) {
|
|
190
|
-
const row =
|
|
191
|
-
SELECT id, name, kind, file_uri, line_start, summary, attributes_json,
|
|
192
|
-
git_created_at, git_modified_at, git_change_count
|
|
193
|
-
FROM ideas
|
|
194
|
-
WHERE file_uri = ? AND line_start <= ? AND ? <= line_end AND kind = 'ideaset'
|
|
195
|
-
ORDER BY line_start DESC
|
|
196
|
-
LIMIT 1
|
|
197
|
-
`, fileUri, line, line);
|
|
109
|
+
const row = this.db.conn.getIdeasetAtLineRow(fileUri, line);
|
|
198
110
|
return row ? this.toSummary(row) : undefined;
|
|
199
111
|
}
|
|
200
112
|
async listIdeasInFileWithRanges(fileUri) {
|
|
201
|
-
const rows =
|
|
202
|
-
SELECT id, name, kind, file_uri, line_start, line_end, summary, attributes_json,
|
|
203
|
-
git_created_at, git_modified_at, git_change_count
|
|
204
|
-
FROM ideas
|
|
205
|
-
WHERE file_uri = ? AND kind != 'ideaset'
|
|
206
|
-
ORDER BY line_start ASC
|
|
207
|
-
`, fileUri);
|
|
113
|
+
const rows = this.db.conn.listIdeasInFileWithRangeRows(fileUri);
|
|
208
114
|
return rows.map(row => ({
|
|
209
115
|
...this.toSummary(row),
|
|
210
116
|
lineEnd: row.line_end
|
|
211
117
|
}));
|
|
212
118
|
}
|
|
213
119
|
async listIdeasetsInFileWithRanges(fileUri) {
|
|
214
|
-
const rows =
|
|
215
|
-
SELECT id, name, kind, file_uri, line_start, line_end, summary, attributes_json,
|
|
216
|
-
git_created_at, git_modified_at, git_change_count
|
|
217
|
-
FROM ideas
|
|
218
|
-
WHERE file_uri = ? AND kind = 'ideaset'
|
|
219
|
-
ORDER BY line_start ASC
|
|
220
|
-
`, fileUri);
|
|
120
|
+
const rows = this.db.conn.listIdeasetsInFileWithRangeRows(fileUri);
|
|
221
121
|
return rows.map(row => ({
|
|
222
122
|
...this.toSummary(row),
|
|
223
123
|
lineEnd: row.line_end
|
|
224
124
|
}));
|
|
225
125
|
}
|
|
226
126
|
async listReferencesForIdea(ideaId) {
|
|
227
|
-
const outbound =
|
|
228
|
-
SELECT
|
|
229
|
-
e.id AS edge_id,
|
|
230
|
-
e.kind,
|
|
231
|
-
e.source_id,
|
|
232
|
-
e.target_id,
|
|
233
|
-
e.target_file,
|
|
234
|
-
e.label,
|
|
235
|
-
e.source_line,
|
|
236
|
-
e.snippet,
|
|
237
|
-
e.is_resolved,
|
|
238
|
-
ti.name AS target_name,
|
|
239
|
-
ti.file_uri AS target_uri,
|
|
240
|
-
ti.line_start AS target_line
|
|
241
|
-
FROM edges e
|
|
242
|
-
LEFT JOIN ideas ti ON ti.id = e.target_id
|
|
243
|
-
WHERE e.source_id = ?
|
|
244
|
-
ORDER BY e.kind, e.id
|
|
245
|
-
`, ideaId);
|
|
246
|
-
const inbound = await all(this.db, `
|
|
247
|
-
SELECT
|
|
248
|
-
e.id AS edge_id,
|
|
249
|
-
e.kind,
|
|
250
|
-
e.source_id,
|
|
251
|
-
e.target_id,
|
|
252
|
-
e.target_file,
|
|
253
|
-
e.label,
|
|
254
|
-
e.source_line,
|
|
255
|
-
e.snippet,
|
|
256
|
-
e.is_resolved,
|
|
257
|
-
si.name AS source_name,
|
|
258
|
-
si.file_uri AS source_uri,
|
|
259
|
-
si.line_start AS source_line_idea
|
|
260
|
-
FROM edges e
|
|
261
|
-
JOIN ideas si ON si.id = e.source_id
|
|
262
|
-
WHERE e.target_id = ?
|
|
263
|
-
ORDER BY e.kind, e.id
|
|
264
|
-
`, ideaId);
|
|
127
|
+
const { outbound, inbound } = this.db.conn.listReferencesForIdea(ideaId);
|
|
265
128
|
const rows = [];
|
|
266
129
|
for (const row of outbound) {
|
|
267
130
|
rows.push(toOutboundReferenceListRow(row));
|
|
@@ -300,17 +163,10 @@ export class SqliteIndexStore {
|
|
|
300
163
|
return rows;
|
|
301
164
|
}
|
|
302
165
|
async countUnresolvedForIdea(ideaId) {
|
|
303
|
-
return
|
|
304
|
-
SELECT COUNT(*) AS count FROM edges
|
|
305
|
-
WHERE source_id = ? AND is_resolved = 0
|
|
306
|
-
`, ideaId)).count;
|
|
166
|
+
return this.db.conn.countUnresolvedForIdea(ideaId);
|
|
307
167
|
}
|
|
308
168
|
async countEdgesFromFile(fileUri) {
|
|
309
|
-
return
|
|
310
|
-
SELECT COUNT(*) AS count
|
|
311
|
-
FROM edges
|
|
312
|
-
WHERE source_id IN (SELECT id FROM ideas WHERE file_uri = ?)
|
|
313
|
-
`, fileUri)).count;
|
|
169
|
+
return this.db.conn.countEdgesFromFile(fileUri);
|
|
314
170
|
}
|
|
315
171
|
async listAncestorChain(ideaId, maxDepth = 8) {
|
|
316
172
|
const ancestors = [];
|
|
@@ -352,11 +208,11 @@ export class SqliteIndexStore {
|
|
|
352
208
|
return { ideaId, ancestors, statusRollup, blocking };
|
|
353
209
|
}
|
|
354
210
|
async getEdgesFrom(sourceId) {
|
|
355
|
-
const rows =
|
|
211
|
+
const rows = this.db.conn.getEdgesFromRows(sourceId);
|
|
356
212
|
return rows.map(mapEdgeRow);
|
|
357
213
|
}
|
|
358
214
|
async getEdgesTo(targetId) {
|
|
359
|
-
const rows =
|
|
215
|
+
const rows = this.db.conn.getEdgesToRows(targetId);
|
|
360
216
|
return rows.map(mapEdgeRow);
|
|
361
217
|
}
|
|
362
218
|
/** Batched fetch of every edge touching any of the given node ids (source or target). */
|
|
@@ -364,8 +220,7 @@ export class SqliteIndexStore {
|
|
|
364
220
|
if (nodeIds.length === 0) {
|
|
365
221
|
return [];
|
|
366
222
|
}
|
|
367
|
-
const
|
|
368
|
-
const rows = await all(this.db, `SELECT * FROM edges WHERE source_id IN (${placeholders}) OR target_id IN (${placeholders})`, ...nodeIds, ...nodeIds);
|
|
223
|
+
const rows = this.db.conn.getEdgesForNodesRows([...nodeIds]);
|
|
369
224
|
return rows.map(mapEdgeRow);
|
|
370
225
|
}
|
|
371
226
|
/** Batched fetch of idea summaries by id, preserving no particular order. */
|
|
@@ -373,54 +228,34 @@ export class SqliteIndexStore {
|
|
|
373
228
|
if (ids.length === 0) {
|
|
374
229
|
return [];
|
|
375
230
|
}
|
|
376
|
-
const
|
|
377
|
-
const rows = await all(this.db, `SELECT id, name, kind, file_uri, line_start, summary, attributes_json
|
|
378
|
-
FROM ideas WHERE id IN (${placeholders})`, ...ids);
|
|
231
|
+
const rows = this.db.conn.getIdeasByIdsRows([...ids]);
|
|
379
232
|
return rows.map(row => this.toSummary(row));
|
|
380
233
|
}
|
|
381
234
|
async getEdgesReferencingFile(filePath) {
|
|
382
|
-
const rows =
|
|
383
|
-
SELECT * FROM edges
|
|
384
|
-
WHERE target_file LIKE ? OR target_file LIKE ?
|
|
385
|
-
`, `%${filePath}%`, filePath);
|
|
235
|
+
const rows = this.db.conn.getEdgesReferencingFileRows(filePath);
|
|
386
236
|
return rows.map(mapEdgeRow);
|
|
387
237
|
}
|
|
388
238
|
async getAllEdges() {
|
|
389
|
-
const rows =
|
|
239
|
+
const rows = this.db.conn.getAllEdgeRows();
|
|
390
240
|
return rows.map(mapEdgeRow);
|
|
391
241
|
}
|
|
392
242
|
/** Outbound file_reference targets for coverage metrics (source id + authored path). */
|
|
393
243
|
async listFileReferenceTargets() {
|
|
394
|
-
const rows =
|
|
395
|
-
FROM edges
|
|
396
|
-
WHERE target_file IS NOT NULL AND target_file != ''
|
|
397
|
-
AND (kind = 'file_reference' OR target_id IS NULL)`);
|
|
244
|
+
const rows = this.db.conn.listFileReferenceTargetRows();
|
|
398
245
|
return rows.map(row => ({
|
|
399
246
|
sourceId: row.source_id,
|
|
400
247
|
targetFile: row.target_file
|
|
401
248
|
}));
|
|
402
249
|
}
|
|
403
250
|
async getAllIdeasRaw() {
|
|
404
|
-
const rows =
|
|
251
|
+
const rows = this.db.conn.allIdeaRawRows();
|
|
405
252
|
return rows.map(mapIdeaRow);
|
|
406
253
|
}
|
|
407
254
|
async updateGitDates(id, createdAt, modifiedAt, changeCount) {
|
|
408
|
-
|
|
409
|
-
await run(this.db, `
|
|
410
|
-
UPDATE ideas SET git_created_at = ?, git_modified_at = ?, git_change_count = ?
|
|
411
|
-
WHERE id = ?
|
|
412
|
-
`, createdAt ?? null, modifiedAt ?? null, changeCount, id);
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
await run(this.db, `
|
|
416
|
-
UPDATE ideas SET git_created_at = ?, git_modified_at = ?
|
|
417
|
-
WHERE id = ?
|
|
418
|
-
`, createdAt ?? null, modifiedAt ?? null, id);
|
|
255
|
+
this.db.conn.updateGitDates(id, createdAt, modifiedAt, changeCount);
|
|
419
256
|
}
|
|
420
257
|
async counts() {
|
|
421
|
-
|
|
422
|
-
const edges = (await get(this.db, 'SELECT COUNT(*) as count FROM edges')).count;
|
|
423
|
-
return { ideas, edges };
|
|
258
|
+
return this.db.conn.counts();
|
|
424
259
|
}
|
|
425
260
|
async getAttributeCatalog() {
|
|
426
261
|
const ideas = await this.getAllIdeasRaw();
|
|
@@ -452,13 +287,7 @@ export class SqliteIndexStore {
|
|
|
452
287
|
};
|
|
453
288
|
}
|
|
454
289
|
async searchByNameOrSummary(query) {
|
|
455
|
-
const
|
|
456
|
-
const rows = await all(this.db, `
|
|
457
|
-
SELECT id, name, kind, file_uri, line_start, summary, attributes_json
|
|
458
|
-
FROM ideas
|
|
459
|
-
WHERE name LIKE ? OR summary LIKE ?
|
|
460
|
-
ORDER BY name
|
|
461
|
-
`, pattern, pattern);
|
|
290
|
+
const rows = this.db.conn.searchIdeaRows(query);
|
|
462
291
|
return rows.map(row => this.toSummary(row));
|
|
463
292
|
}
|
|
464
293
|
/**
|
|
@@ -468,13 +297,7 @@ export class SqliteIndexStore {
|
|
|
468
297
|
async listTodoIdeas(limit = ACTIVITY_BAR_TODO_LIMIT) {
|
|
469
298
|
const cappedLimit = Math.max(0, Math.min(Math.floor(limit), 500));
|
|
470
299
|
// Coarse prefilter: `"todo":` matches the attribute key, not `@status todo`.
|
|
471
|
-
const rows =
|
|
472
|
-
SELECT id, name, kind, file_uri, line_start, summary, attributes_json
|
|
473
|
-
FROM ideas
|
|
474
|
-
WHERE kind != 'ideaset'
|
|
475
|
-
AND attributes_json LIKE '%"todo":%'
|
|
476
|
-
ORDER BY name ASC, file_uri ASC, line_start ASC
|
|
477
|
-
`);
|
|
300
|
+
const rows = this.db.conn.listTodoIdeaRows();
|
|
478
301
|
const ideas = [];
|
|
479
302
|
for (const row of rows) {
|
|
480
303
|
const attributes = parseAttributes(row.attributes_json);
|
|
@@ -498,60 +321,17 @@ export class SqliteIndexStore {
|
|
|
498
321
|
};
|
|
499
322
|
}
|
|
500
323
|
async listIdeasForGraphQuery(query, limit) {
|
|
501
|
-
const {
|
|
502
|
-
const orderSql = buildGraphTruncationOrderClause(query.truncationBasis);
|
|
503
|
-
const totalMatching = (await get(this.db, `SELECT COUNT(*) as count FROM ideas i WHERE ${sql}`, ...params)).count;
|
|
504
|
-
const rows = await all(this.db, `
|
|
505
|
-
SELECT id, name, kind, file_uri, line_start, summary, attributes_json
|
|
506
|
-
FROM ideas i
|
|
507
|
-
WHERE ${sql}
|
|
508
|
-
ORDER BY ${orderSql}
|
|
509
|
-
LIMIT ?
|
|
510
|
-
`, ...params, limit);
|
|
324
|
+
const { rows, totalMatching } = this.db.conn.listIdeasForGraphQuery(query, limit);
|
|
511
325
|
return {
|
|
512
326
|
candidates: rows.map(row => this.toSummary(row)),
|
|
513
327
|
totalMatching
|
|
514
328
|
};
|
|
515
329
|
}
|
|
516
330
|
async countIdeas(query = { page: 0, pageSize: 50, attributeColumns: [], referenceFilters: [] }) {
|
|
517
|
-
|
|
518
|
-
return (await get(this.db, `SELECT COUNT(*) as count FROM ideas i WHERE ${sql}`, ...params)).count;
|
|
331
|
+
return this.db.conn.countIdeas(query);
|
|
519
332
|
}
|
|
520
333
|
async listIdeasPage(query) {
|
|
521
|
-
const
|
|
522
|
-
const orderSql = buildIdeasOrderClause(query);
|
|
523
|
-
const rows = await all(this.db, `
|
|
524
|
-
SELECT
|
|
525
|
-
i.id,
|
|
526
|
-
i.name,
|
|
527
|
-
i.kind,
|
|
528
|
-
i.file_uri,
|
|
529
|
-
i.line_start,
|
|
530
|
-
i.summary,
|
|
531
|
-
i.attributes_json,
|
|
532
|
-
i.git_created_at,
|
|
533
|
-
i.git_modified_at,
|
|
534
|
-
i.git_change_count,
|
|
535
|
-
(
|
|
536
|
-
SELECT COUNT(*)
|
|
537
|
-
FROM edges e
|
|
538
|
-
WHERE e.source_id = i.id
|
|
539
|
-
) AS outbound_count,
|
|
540
|
-
(
|
|
541
|
-
SELECT COUNT(*)
|
|
542
|
-
FROM edges e
|
|
543
|
-
WHERE e.target_id = i.id
|
|
544
|
-
) AS inbound_count,
|
|
545
|
-
(
|
|
546
|
-
SELECT COUNT(*)
|
|
547
|
-
FROM edges e
|
|
548
|
-
WHERE e.source_id = i.id OR e.target_id = i.id
|
|
549
|
-
) AS reference_count
|
|
550
|
-
FROM ideas i
|
|
551
|
-
WHERE ${whereSql}
|
|
552
|
-
ORDER BY ${orderSql}
|
|
553
|
-
LIMIT ? OFFSET ?
|
|
554
|
-
`, ...whereParams, query.pageSize, query.page * query.pageSize);
|
|
334
|
+
const rows = this.db.conn.listIdeasPageRows(query);
|
|
555
335
|
const ideaIds = rows.map(row => row.id);
|
|
556
336
|
const referencesByIdea = await this.listReferenceChipsForIdeas(ideaIds);
|
|
557
337
|
return rows.map(row => {
|
|
@@ -564,27 +344,7 @@ export class SqliteIndexStore {
|
|
|
564
344
|
if (ideaIds.length === 0) {
|
|
565
345
|
return result;
|
|
566
346
|
}
|
|
567
|
-
const
|
|
568
|
-
const rows = await all(this.db, `
|
|
569
|
-
SELECT
|
|
570
|
-
e.id,
|
|
571
|
-
e.kind,
|
|
572
|
-
e.source_id,
|
|
573
|
-
e.target_id,
|
|
574
|
-
e.target_file,
|
|
575
|
-
e.label,
|
|
576
|
-
si.name AS source_name,
|
|
577
|
-
si.file_uri AS source_uri,
|
|
578
|
-
si.line_start AS source_line,
|
|
579
|
-
ti.name AS target_name,
|
|
580
|
-
ti.file_uri AS target_uri,
|
|
581
|
-
ti.line_start AS target_line
|
|
582
|
-
FROM edges e
|
|
583
|
-
JOIN ideas si ON si.id = e.source_id
|
|
584
|
-
LEFT JOIN ideas ti ON ti.id = e.target_id
|
|
585
|
-
WHERE e.source_id IN (${placeholders}) OR e.target_id IN (${placeholders})
|
|
586
|
-
ORDER BY e.id
|
|
587
|
-
`, ...ideaIds, ...ideaIds);
|
|
347
|
+
const rows = this.db.conn.listReferenceChipRows(ideaIds);
|
|
588
348
|
for (const ideaId of ideaIds) {
|
|
589
349
|
result.set(ideaId, { outbound: [], inbound: [] });
|
|
590
350
|
}
|
|
@@ -599,78 +359,10 @@ export class SqliteIndexStore {
|
|
|
599
359
|
return result;
|
|
600
360
|
}
|
|
601
361
|
async countIdeasets(query = { page: 0, pageSize: 50 }) {
|
|
602
|
-
|
|
603
|
-
const row = await get(this.db, `
|
|
604
|
-
SELECT COUNT(*) AS count
|
|
605
|
-
FROM (
|
|
606
|
-
SELECT
|
|
607
|
-
d.file_uri AS id,
|
|
608
|
-
'file' AS kind,
|
|
609
|
-
d.file_uri AS file_uri,
|
|
610
|
-
0 AS line_start,
|
|
611
|
-
NULL AS name,
|
|
612
|
-
(
|
|
613
|
-
SELECT COUNT(*)
|
|
614
|
-
FROM ideas i
|
|
615
|
-
WHERE i.file_uri = d.file_uri
|
|
616
|
-
) AS member_count
|
|
617
|
-
FROM documents d
|
|
618
|
-
UNION ALL
|
|
619
|
-
SELECT
|
|
620
|
-
i.id,
|
|
621
|
-
'explicit' AS kind,
|
|
622
|
-
i.file_uri,
|
|
623
|
-
i.line_start,
|
|
624
|
-
i.name,
|
|
625
|
-
(
|
|
626
|
-
SELECT COUNT(*)
|
|
627
|
-
FROM edges e
|
|
628
|
-
WHERE e.source_id = i.id AND e.kind = 'ideaset_member'
|
|
629
|
-
) AS member_count
|
|
630
|
-
FROM ideas i
|
|
631
|
-
WHERE i.kind = 'ideaset'
|
|
632
|
-
) ideasets
|
|
633
|
-
WHERE ${sql}
|
|
634
|
-
`, ...params);
|
|
635
|
-
return row?.count ?? 0;
|
|
362
|
+
return this.db.conn.countIdeasets(query);
|
|
636
363
|
}
|
|
637
364
|
async listIdeasetsPage(query) {
|
|
638
|
-
const
|
|
639
|
-
const orderSql = buildIdeasetsOrderClause(query);
|
|
640
|
-
const rows = await all(this.db, `
|
|
641
|
-
SELECT *
|
|
642
|
-
FROM (
|
|
643
|
-
SELECT
|
|
644
|
-
d.file_uri AS id,
|
|
645
|
-
'file' AS kind,
|
|
646
|
-
d.file_uri AS file_uri,
|
|
647
|
-
0 AS line_start,
|
|
648
|
-
NULL AS name,
|
|
649
|
-
(
|
|
650
|
-
SELECT COUNT(*)
|
|
651
|
-
FROM ideas i
|
|
652
|
-
WHERE i.file_uri = d.file_uri
|
|
653
|
-
) AS member_count
|
|
654
|
-
FROM documents d
|
|
655
|
-
UNION ALL
|
|
656
|
-
SELECT
|
|
657
|
-
i.id,
|
|
658
|
-
'explicit' AS kind,
|
|
659
|
-
i.file_uri,
|
|
660
|
-
i.line_start,
|
|
661
|
-
i.name,
|
|
662
|
-
(
|
|
663
|
-
SELECT COUNT(*)
|
|
664
|
-
FROM edges e
|
|
665
|
-
WHERE e.source_id = i.id AND e.kind = 'ideaset_member'
|
|
666
|
-
) AS member_count
|
|
667
|
-
FROM ideas i
|
|
668
|
-
WHERE i.kind = 'ideaset'
|
|
669
|
-
) ideasets
|
|
670
|
-
WHERE ${whereSql}
|
|
671
|
-
ORDER BY ${orderSql}
|
|
672
|
-
LIMIT ? OFFSET ?
|
|
673
|
-
`, ...whereParams, query.pageSize, query.page * query.pageSize);
|
|
365
|
+
const rows = this.db.conn.listIdeasetsPageRows(query);
|
|
674
366
|
const ideasets = rows.map(row => toIdeasetTableRow(row));
|
|
675
367
|
return Promise.all(ideasets.map(async (ideaset) => ({
|
|
676
368
|
...ideaset,
|
|
@@ -678,20 +370,7 @@ export class SqliteIndexStore {
|
|
|
678
370
|
})));
|
|
679
371
|
}
|
|
680
372
|
async listIdeasetMembers(ideasetId, kind, fileUri) {
|
|
681
|
-
const rows = kind
|
|
682
|
-
? await all(this.db, `
|
|
683
|
-
SELECT name, file_uri, line_start
|
|
684
|
-
FROM ideas
|
|
685
|
-
WHERE file_uri = ? AND kind != 'ideaset'
|
|
686
|
-
ORDER BY line_start
|
|
687
|
-
`, fileUri)
|
|
688
|
-
: await all(this.db, `
|
|
689
|
-
SELECT i.name, i.file_uri, i.line_start
|
|
690
|
-
FROM edges e
|
|
691
|
-
JOIN ideas i ON i.id = e.target_id
|
|
692
|
-
WHERE e.source_id = ? AND e.kind = 'ideaset_member'
|
|
693
|
-
ORDER BY i.line_start
|
|
694
|
-
`, ideasetId);
|
|
373
|
+
const rows = this.db.conn.listIdeasetMemberRows(ideasetId, kind, fileUri);
|
|
695
374
|
return rows.map(row => ({
|
|
696
375
|
name: row.name,
|
|
697
376
|
fileUri: row.file_uri,
|
|
@@ -699,37 +378,10 @@ export class SqliteIndexStore {
|
|
|
699
378
|
}));
|
|
700
379
|
}
|
|
701
380
|
async countReferences(query = { page: 0, pageSize: 50 }) {
|
|
702
|
-
|
|
703
|
-
return (await get(this.db, `
|
|
704
|
-
SELECT COUNT(*) as count
|
|
705
|
-
FROM edges e
|
|
706
|
-
JOIN ideas si ON si.id = e.source_id
|
|
707
|
-
LEFT JOIN ideas ti ON ti.id = e.target_id
|
|
708
|
-
WHERE ${sql}
|
|
709
|
-
`, ...params)).count;
|
|
381
|
+
return this.db.conn.countReferences(query);
|
|
710
382
|
}
|
|
711
383
|
async listReferencesPage(query) {
|
|
712
|
-
const
|
|
713
|
-
const orderSql = buildReferencesOrderClause(query);
|
|
714
|
-
const rows = await all(this.db, `
|
|
715
|
-
SELECT
|
|
716
|
-
e.kind,
|
|
717
|
-
e.source_id,
|
|
718
|
-
e.target_id,
|
|
719
|
-
e.target_file,
|
|
720
|
-
e.label,
|
|
721
|
-
si.name AS source_name,
|
|
722
|
-
si.file_uri AS source_uri,
|
|
723
|
-
si.line_start AS source_line,
|
|
724
|
-
ti.name AS target_name,
|
|
725
|
-
ti.file_uri AS target_uri
|
|
726
|
-
FROM edges e
|
|
727
|
-
JOIN ideas si ON si.id = e.source_id
|
|
728
|
-
LEFT JOIN ideas ti ON ti.id = e.target_id
|
|
729
|
-
WHERE ${whereSql}
|
|
730
|
-
ORDER BY ${orderSql}
|
|
731
|
-
LIMIT ? OFFSET ?
|
|
732
|
-
`, ...whereParams, query.pageSize, query.page * query.pageSize);
|
|
384
|
+
const rows = this.db.conn.listReferencesPageRows(query);
|
|
733
385
|
return rows.map(row => toReferenceTableRow(row));
|
|
734
386
|
}
|
|
735
387
|
/**
|
|
@@ -745,11 +397,7 @@ export class SqliteIndexStore {
|
|
|
745
397
|
return rows;
|
|
746
398
|
}
|
|
747
399
|
async queryAttributesPage(query) {
|
|
748
|
-
const rows =
|
|
749
|
-
SELECT id, attributes_json
|
|
750
|
-
FROM ideas
|
|
751
|
-
WHERE kind != 'ideaset'
|
|
752
|
-
`);
|
|
400
|
+
const rows = this.db.conn.listAttributeIdeaRows();
|
|
753
401
|
return filterAndPageAttributes(aggregateAttributesFromRows(rows), query);
|
|
754
402
|
}
|
|
755
403
|
/**
|
|
@@ -764,46 +412,7 @@ export class SqliteIndexStore {
|
|
|
764
412
|
* rq:["../../../../reqlan rq/extension/module/ideas_summary/webview.rq".ideas_list]
|
|
765
413
|
*/
|
|
766
414
|
async listIdeaIdsMissingGitDates(limit = 40, options) {
|
|
767
|
-
|
|
768
|
-
const missingSql = `(
|
|
769
|
-
(git_created_at IS NULL AND git_modified_at IS NULL)
|
|
770
|
-
OR git_change_count IS NULL
|
|
771
|
-
)`;
|
|
772
|
-
const fileUri = options?.fileUri?.trim();
|
|
773
|
-
if (fileUri) {
|
|
774
|
-
const rows = await all(this.db, `
|
|
775
|
-
SELECT id
|
|
776
|
-
FROM ideas
|
|
777
|
-
WHERE kind != 'ideaset'
|
|
778
|
-
AND ${missingSql}
|
|
779
|
-
AND file_uri = ?
|
|
780
|
-
ORDER BY line_start ASC
|
|
781
|
-
LIMIT ?
|
|
782
|
-
`, fileUri, capped);
|
|
783
|
-
return rows.map(row => row.id);
|
|
784
|
-
}
|
|
785
|
-
const preferFileUri = options?.preferFileUri?.trim();
|
|
786
|
-
if (preferFileUri) {
|
|
787
|
-
const rows = await all(this.db, `
|
|
788
|
-
SELECT id
|
|
789
|
-
FROM ideas
|
|
790
|
-
WHERE kind != 'ideaset'
|
|
791
|
-
AND ${missingSql}
|
|
792
|
-
ORDER BY CASE WHEN file_uri = ? THEN 0 ELSE 1 END,
|
|
793
|
-
file_uri ASC, line_start ASC
|
|
794
|
-
LIMIT ?
|
|
795
|
-
`, preferFileUri, capped);
|
|
796
|
-
return rows.map(row => row.id);
|
|
797
|
-
}
|
|
798
|
-
const rows = await all(this.db, `
|
|
799
|
-
SELECT id
|
|
800
|
-
FROM ideas
|
|
801
|
-
WHERE kind != 'ideaset'
|
|
802
|
-
AND ${missingSql}
|
|
803
|
-
ORDER BY file_uri ASC, line_start ASC
|
|
804
|
-
LIMIT ?
|
|
805
|
-
`, capped);
|
|
806
|
-
return rows.map(row => row.id);
|
|
415
|
+
return this.db.conn.listIdeaIdsMissingGitDates(limit, options?.fileUri?.trim() || undefined, options?.preferFileUri?.trim() || undefined);
|
|
807
416
|
}
|
|
808
417
|
/**
|
|
809
418
|
* Recent idea evolution events from indexed git dates for the Timeline tab.
|
|
@@ -811,15 +420,7 @@ export class SqliteIndexStore {
|
|
|
811
420
|
* per ["../../../../reqlan rq/extension/module/ideas_summary/webview.rq".timeline_page]
|
|
812
421
|
*/
|
|
813
422
|
async listRecentGitIdeaEvents(limit = 50) {
|
|
814
|
-
const rows =
|
|
815
|
-
SELECT id, name, kind, file_uri, line_start, summary, attributes_json,
|
|
816
|
-
git_created_at, git_modified_at, git_change_count
|
|
817
|
-
FROM ideas
|
|
818
|
-
WHERE kind != 'ideaset'
|
|
819
|
-
AND (git_modified_at IS NOT NULL OR git_created_at IS NOT NULL)
|
|
820
|
-
ORDER BY COALESCE(git_modified_at, git_created_at) DESC
|
|
821
|
-
LIMIT ?
|
|
822
|
-
`, Math.max(1, Math.min(limit * 2, 400)));
|
|
423
|
+
const rows = this.db.conn.listRecentGitIdeaRows(limit);
|
|
823
424
|
const events = [];
|
|
824
425
|
for (const row of rows) {
|
|
825
426
|
const attributes = parseAttributes(row.attributes_json);
|
|
@@ -872,152 +473,8 @@ export class SqliteIndexStore {
|
|
|
872
473
|
};
|
|
873
474
|
}
|
|
874
475
|
async migrate() {
|
|
875
|
-
|
|
876
|
-
await exec(this.db, statement);
|
|
877
|
-
}
|
|
878
|
-
const currentVersion = Number((await get(this.db, `SELECT value FROM meta WHERE key = 'schema_version'`))?.value ?? '1');
|
|
879
|
-
for (let version = currentVersion + 1; version <= SCHEMA_VERSION; version++) {
|
|
880
|
-
const steps = VERSION_MIGRATIONS[version] ?? [];
|
|
881
|
-
for (const statement of steps) {
|
|
882
|
-
try {
|
|
883
|
-
await exec(this.db, statement);
|
|
884
|
-
}
|
|
885
|
-
catch (error) {
|
|
886
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
887
|
-
if (!message.includes('duplicate column')) {
|
|
888
|
-
throw error;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
await run(this.db, `
|
|
894
|
-
INSERT INTO meta (key, value) VALUES (?, ?)
|
|
895
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
896
|
-
`, 'schema_version', String(SCHEMA_VERSION));
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
async function openDatabase(path) {
|
|
900
|
-
await mkdir(dirname(path), { recursive: true });
|
|
901
|
-
const SQL = await getSqlJsModule();
|
|
902
|
-
const bytes = await readDatabaseBytes(path);
|
|
903
|
-
return {
|
|
904
|
-
db: bytes ? new SQL.Database(bytes) : new SQL.Database(),
|
|
905
|
-
dbPath: path,
|
|
906
|
-
inTransaction: false,
|
|
907
|
-
dirty: false
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
async function closeDatabase(db) {
|
|
911
|
-
await persistDatabase(db);
|
|
912
|
-
db.db.close();
|
|
913
|
-
}
|
|
914
|
-
async function run(db, sql, ...params) {
|
|
915
|
-
db.db.run(sql, params);
|
|
916
|
-
const statementKind = classifyStatement(sql);
|
|
917
|
-
if (statementKind === 'begin') {
|
|
918
|
-
db.inTransaction = true;
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
if (statementKind === 'commit') {
|
|
922
|
-
db.inTransaction = false;
|
|
923
|
-
db.dirty = true;
|
|
924
|
-
await persistDatabase(db);
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
if (statementKind === 'rollback') {
|
|
928
|
-
db.inTransaction = false;
|
|
929
|
-
db.dirty = false;
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
if (statementKind === 'write') {
|
|
933
|
-
db.dirty = true;
|
|
934
|
-
if (!db.inTransaction) {
|
|
935
|
-
await persistDatabase(db);
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
async function get(db, sql, ...params) {
|
|
940
|
-
const rows = await all(db, sql, ...params);
|
|
941
|
-
return rows[0];
|
|
942
|
-
}
|
|
943
|
-
async function all(db, sql, ...params) {
|
|
944
|
-
const statement = db.db.prepare(sql, params);
|
|
945
|
-
const rows = [];
|
|
946
|
-
try {
|
|
947
|
-
while (statement.step()) {
|
|
948
|
-
rows.push(statement.getAsObject());
|
|
949
|
-
}
|
|
950
|
-
return rows;
|
|
951
|
-
}
|
|
952
|
-
finally {
|
|
953
|
-
statement.free();
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
/** Like `all`, but yields to the event loop every chunk so interactive work can run. */
|
|
957
|
-
async function allYielding(db, sql, ...params) {
|
|
958
|
-
const statement = db.db.prepare(sql, params);
|
|
959
|
-
const rows = [];
|
|
960
|
-
try {
|
|
961
|
-
let stepped = 0;
|
|
962
|
-
while (statement.step()) {
|
|
963
|
-
rows.push(statement.getAsObject());
|
|
964
|
-
stepped += 1;
|
|
965
|
-
if (stepped % 250 === 0) {
|
|
966
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
return rows;
|
|
970
|
-
}
|
|
971
|
-
finally {
|
|
972
|
-
statement.free();
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
async function exec(db, sql) {
|
|
976
|
-
db.db.exec(sql);
|
|
977
|
-
db.dirty = true;
|
|
978
|
-
if (!db.inTransaction) {
|
|
979
|
-
await persistDatabase(db);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
let sqlJsModulePromise;
|
|
983
|
-
async function getSqlJsModule() {
|
|
984
|
-
sqlJsModulePromise ?? (sqlJsModulePromise = import('sql.js/dist/sql-asm.js').then(({ default: initSqlJs }) => initSqlJs({})));
|
|
985
|
-
return sqlJsModulePromise;
|
|
986
|
-
}
|
|
987
|
-
async function readDatabaseBytes(path) {
|
|
988
|
-
try {
|
|
989
|
-
const file = await readFile(path);
|
|
990
|
-
return new Uint8Array(file);
|
|
991
|
-
}
|
|
992
|
-
catch (error) {
|
|
993
|
-
if (isMissingFileError(error)) {
|
|
994
|
-
return undefined;
|
|
995
|
-
}
|
|
996
|
-
throw error;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
async function persistDatabase(db) {
|
|
1000
|
-
if (!db.dirty) {
|
|
1001
|
-
return;
|
|
1002
|
-
}
|
|
1003
|
-
await writeFile(db.dbPath, db.db.export());
|
|
1004
|
-
db.dirty = false;
|
|
1005
|
-
}
|
|
1006
|
-
function classifyStatement(sql) {
|
|
1007
|
-
const keyword = sql.trimStart().match(/^[A-Za-z]+/)?.[0]?.toUpperCase();
|
|
1008
|
-
if (keyword === 'BEGIN') {
|
|
1009
|
-
return 'begin';
|
|
1010
|
-
}
|
|
1011
|
-
if (keyword === 'COMMIT') {
|
|
1012
|
-
return 'commit';
|
|
1013
|
-
}
|
|
1014
|
-
if (keyword === 'ROLLBACK') {
|
|
1015
|
-
return 'rollback';
|
|
1016
|
-
}
|
|
1017
|
-
if (keyword && ['INSERT', 'UPDATE', 'DELETE', 'REPLACE', 'CREATE', 'DROP', 'ALTER', 'PRAGMA'].includes(keyword)) {
|
|
1018
|
-
return 'write';
|
|
476
|
+
this.db.conn.migrateIdeasSchema();
|
|
1019
477
|
}
|
|
1020
|
-
return 'read';
|
|
1021
478
|
}
|
|
1022
479
|
function isMissingFileError(error) {
|
|
1023
480
|
return typeof error === 'object'
|