@crouton-kit/tsym 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/bin/tsym.js +2 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +89 -0
- package/dist/commands/handlers/check.d.ts +3 -0
- package/dist/commands/handlers/check.js +94 -0
- package/dist/commands/handlers/find.d.ts +3 -0
- package/dist/commands/handlers/find.js +56 -0
- package/dist/commands/handlers/inspect.d.ts +3 -0
- package/dist/commands/handlers/inspect.js +81 -0
- package/dist/commands/handlers/outline.d.ts +3 -0
- package/dist/commands/handlers/outline.js +101 -0
- package/dist/commands/handlers/query.d.ts +3 -0
- package/dist/commands/handlers/query.js +196 -0
- package/dist/commands/handlers/rename.d.ts +3 -0
- package/dist/commands/handlers/rename.js +53 -0
- package/dist/commands/handlers/search.d.ts +5 -0
- package/dist/commands/handlers/search.js +32 -0
- package/dist/commands/handlers/server-reload.d.ts +3 -0
- package/dist/commands/handlers/server-reload.js +7 -0
- package/dist/commands/handlers/server-status.d.ts +3 -0
- package/dist/commands/handlers/server-status.js +7 -0
- package/dist/commands/handlers/server-stop.d.ts +3 -0
- package/dist/commands/handlers/server-stop.js +7 -0
- package/dist/commands/handlers/show.d.ts +3 -0
- package/dist/commands/handlers/show.js +63 -0
- package/dist/commands/handlers/type.d.ts +3 -0
- package/dist/commands/handlers/type.js +45 -0
- package/dist/commands/handlers.d.ts +12 -0
- package/dist/commands/handlers.js +44 -0
- package/dist/commands/help.d.ts +2 -0
- package/dist/commands/help.js +27 -0
- package/dist/commands/rename-apply.d.ts +37 -0
- package/dist/commands/rename-apply.js +115 -0
- package/dist/commands/tree.d.ts +9 -0
- package/dist/commands/tree.js +277 -0
- package/dist/commands/types.d.ts +80 -0
- package/dist/commands/types.js +1 -0
- package/dist/core/declarations.d.ts +48 -0
- package/dist/core/declarations.js +153 -0
- package/dist/core/receipt.d.ts +7 -0
- package/dist/core/receipt.js +9 -0
- package/dist/core/search.d.ts +29 -0
- package/dist/core/search.js +47 -0
- package/dist/core/source.d.ts +32 -0
- package/dist/core/source.js +96 -0
- package/dist/core/workspace.d.ts +28 -0
- package/dist/core/workspace.js +137 -0
- package/dist/index/build.d.ts +22 -0
- package/dist/index/build.js +79 -0
- package/dist/index/classify.d.ts +20 -0
- package/dist/index/classify.js +60 -0
- package/dist/index/commits.d.ts +15 -0
- package/dist/index/commits.js +201 -0
- package/dist/index/descriptor.d.ts +19 -0
- package/dist/index/descriptor.js +295 -0
- package/dist/index/graph.d.ts +82 -0
- package/dist/index/graph.js +1 -0
- package/dist/index/heritage.d.ts +15 -0
- package/dist/index/heritage.js +122 -0
- package/dist/index/refresh.d.ts +28 -0
- package/dist/index/refresh.js +110 -0
- package/dist/index/scope.d.ts +4 -0
- package/dist/index/scope.js +21 -0
- package/dist/index/state.d.ts +22 -0
- package/dist/index/state.js +63 -0
- package/dist/index/walk.d.ts +23 -0
- package/dist/index/walk.js +550 -0
- package/dist/index/watch.d.ts +18 -0
- package/dist/index/watch.js +175 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.js +54 -0
- package/dist/read/read.d.ts +32 -0
- package/dist/read/read.js +89 -0
- package/dist/relations/graph.d.ts +23 -0
- package/dist/relations/graph.js +115 -0
- package/dist/relations/mermaid.d.ts +3 -0
- package/dist/relations/mermaid.js +37 -0
- package/dist/relations/store-relations.d.ts +20 -0
- package/dist/relations/store-relations.js +158 -0
- package/dist/relations/types.d.ts +58 -0
- package/dist/relations/types.js +12 -0
- package/dist/server/client.d.ts +5 -0
- package/dist/server/client.js +215 -0
- package/dist/server/context.d.ts +21 -0
- package/dist/server/context.js +12 -0
- package/dist/server/lifecycle.d.ts +28 -0
- package/dist/server/lifecycle.js +62 -0
- package/dist/server/main.d.ts +1 -0
- package/dist/server/main.js +541 -0
- package/dist/server/projects.d.ts +21 -0
- package/dist/server/projects.js +28 -0
- package/dist/server/wire.d.ts +39 -0
- package/dist/server/wire.js +19 -0
- package/dist/store/load.d.ts +67 -0
- package/dist/store/load.js +193 -0
- package/dist/store/meta.d.ts +28 -0
- package/dist/store/meta.js +137 -0
- package/dist/store/query.d.ts +5 -0
- package/dist/store/query.js +4 -0
- package/dist/store/schema.d.ts +4 -0
- package/dist/store/schema.js +19 -0
- package/dist/store/store.d.ts +36 -0
- package/dist/store/store.js +240 -0
- package/dist/ts7/api.d.ts +35 -0
- package/dist/ts7/api.js +164 -0
- package/dist/ts7/ast.d.ts +1 -0
- package/dist/ts7/ast.js +1 -0
- package/dist/ts7/checker.d.ts +55 -0
- package/dist/ts7/checker.js +383 -0
- package/dist/ts7/projects.d.ts +27 -0
- package/dist/ts7/projects.js +94 -0
- package/package.json +47 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Store } from './store.js';
|
|
2
|
+
export interface DeclarationRow {
|
|
3
|
+
key: string;
|
|
4
|
+
at: string;
|
|
5
|
+
name: string;
|
|
6
|
+
qname: string;
|
|
7
|
+
kind: string;
|
|
8
|
+
exported: boolean;
|
|
9
|
+
file: string;
|
|
10
|
+
dir: string;
|
|
11
|
+
top: string;
|
|
12
|
+
line: number;
|
|
13
|
+
col: number;
|
|
14
|
+
endLine: number;
|
|
15
|
+
lines: number;
|
|
16
|
+
declCount: number;
|
|
17
|
+
container: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface ExternalRow {
|
|
20
|
+
key: string;
|
|
21
|
+
module: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CommitRow {
|
|
25
|
+
hash: string;
|
|
26
|
+
ts: string;
|
|
27
|
+
author: string;
|
|
28
|
+
subject: string;
|
|
29
|
+
}
|
|
30
|
+
export type RelationshipType = 'CONTAINS' | 'REFERENCES' | 'CALLS' | 'IMPLEMENTS' | 'EXTENDS' | 'TYPE_MENTION' | 'IMPORTS' | 'EXPORTS' | 'CHANGED' | 'RENAMED_FROM';
|
|
31
|
+
export type RelationshipTarget = 'Declaration' | 'External' | 'Commit';
|
|
32
|
+
export interface RelationshipRow {
|
|
33
|
+
type: RelationshipType;
|
|
34
|
+
from: string;
|
|
35
|
+
to: string;
|
|
36
|
+
target: RelationshipTarget;
|
|
37
|
+
line?: number;
|
|
38
|
+
col?: number;
|
|
39
|
+
role?: string;
|
|
40
|
+
dispatch?: boolean;
|
|
41
|
+
position?: string;
|
|
42
|
+
names?: string;
|
|
43
|
+
name?: string;
|
|
44
|
+
added?: number;
|
|
45
|
+
removed?: number;
|
|
46
|
+
oldKey?: string;
|
|
47
|
+
basis?: 'text-identical' | 'container-renamed' | 'file-moved';
|
|
48
|
+
}
|
|
49
|
+
export interface StoreGraph {
|
|
50
|
+
declarations: readonly DeclarationRow[];
|
|
51
|
+
externals: readonly ExternalRow[];
|
|
52
|
+
commits?: readonly CommitRow[];
|
|
53
|
+
relationships: readonly RelationshipRow[];
|
|
54
|
+
}
|
|
55
|
+
export interface LoadReceipt {
|
|
56
|
+
declarations: number;
|
|
57
|
+
externals: number;
|
|
58
|
+
commits: number;
|
|
59
|
+
relationships: Record<RelationshipType, number>;
|
|
60
|
+
}
|
|
61
|
+
export declare function loadGraph(store: Store, graph: StoreGraph): LoadReceipt;
|
|
62
|
+
/** Rehydrates the persisted graph when an unchanged workspace reuses its store across a resident restart. */
|
|
63
|
+
export declare function readGraph(store: Store): StoreGraph;
|
|
64
|
+
/** Atomically replaces every changed file, so relationships can target declarations created by the same refresh. */
|
|
65
|
+
export declare function replaceFiles(store: Store, files: readonly string[], graph: StoreGraph): LoadReceipt;
|
|
66
|
+
/** One-file compatibility call of the unified replacement transaction. */
|
|
67
|
+
export declare function replaceFile(store: Store, file: string, graph: StoreGraph): LoadReceipt;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const declarationColumns = ['key', 'at', 'name', 'qname', 'kind', 'exported', 'file', 'dir', 'top', 'line', 'col', 'endLine', 'lines', 'declCount', 'container'];
|
|
5
|
+
const externalColumns = ['key', 'module', 'name'];
|
|
6
|
+
const commitColumns = ['hash', 'ts', 'author', 'subject'];
|
|
7
|
+
export function loadGraph(store, graph) {
|
|
8
|
+
validateGraph(graph);
|
|
9
|
+
return store.transaction(() => {
|
|
10
|
+
const loaded = withCheckpointHistory(graph, checkpointHistory(store));
|
|
11
|
+
clearGraph(store);
|
|
12
|
+
copyGraph(store, loaded);
|
|
13
|
+
return receiptFor(loaded);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/** Rehydrates the persisted graph when an unchanged workspace reuses its store across a resident restart. */
|
|
17
|
+
export function readGraph(store) {
|
|
18
|
+
const declarations = store.execute('MATCH (d:Declaration) RETURN d.key AS key, d.at AS at, d.name AS name, d.qname AS qname, d.kind AS kind, d.exported AS exported, d.file AS file, d.dir AS dir, d.top AS top, d.line AS line, d.col AS col, d.endLine AS endLine, d.lines AS lines, d.declCount AS declCount, d.container AS container').map((row) => ({
|
|
19
|
+
key: String(row.key), at: String(row.at), name: String(row.name), qname: String(row.qname), kind: String(row.kind), exported: Boolean(row.exported), file: String(row.file), dir: String(row.dir), top: String(row.top), line: Number(row.line), col: Number(row.col), endLine: Number(row.endLine), lines: Number(row.lines), declCount: Number(row.declCount), container: row.container === null ? null : String(row.container),
|
|
20
|
+
}));
|
|
21
|
+
const externals = store.execute('MATCH (e:External) RETURN e.key AS key, e.module AS module, e.name AS name').map((row) => ({ key: String(row.key), module: String(row.module), name: String(row.name) }));
|
|
22
|
+
const commits = store.execute('MATCH (c:Commit) RETURN c.hash AS hash, c.ts AS ts, c.author AS author, c.subject AS subject').map((row) => ({ hash: String(row.hash), ts: String(row.ts), author: String(row.author), subject: String(row.subject) }));
|
|
23
|
+
const relationships = relationshipTypes.flatMap((type) => relationshipTargets(type).flatMap((target) => readRelationships(store, type, target)));
|
|
24
|
+
return { declarations, externals, commits, relationships };
|
|
25
|
+
}
|
|
26
|
+
/** Atomically replaces every changed file, so relationships can target declarations created by the same refresh. */
|
|
27
|
+
export function replaceFiles(store, files, graph) {
|
|
28
|
+
const replaced = new Set(files);
|
|
29
|
+
if (graph.declarations.some((declaration) => !replaced.has(declaration.file)))
|
|
30
|
+
throw new Error('Replacement graph contains a declaration outside the replaced files.');
|
|
31
|
+
validateGraph(graph);
|
|
32
|
+
return store.transaction(() => {
|
|
33
|
+
const history = checkpointHistory(store, replaced);
|
|
34
|
+
for (const file of replaced)
|
|
35
|
+
store.execute(`MATCH (d:Declaration) WHERE d.file = ${literal(file)} DETACH DELETE d`);
|
|
36
|
+
copyRows(store, 'Declaration', declarationColumns, graph.declarations);
|
|
37
|
+
copyMissingExternals(store, graph.externals);
|
|
38
|
+
copyMissingCommits(store, graph.commits ?? []);
|
|
39
|
+
copyRelationships(store, graph.relationships);
|
|
40
|
+
copyRelationships(store, history.changed.filter((relationship) => graph.declarations.some((declaration) => declaration.key === relationship.to)));
|
|
41
|
+
copyRelationships(store, history.renamed.filter((relationship) => graph.declarations.some((declaration) => declaration.key === relationship.from)));
|
|
42
|
+
store.execute('MATCH (e:External) WHERE NOT EXISTS { MATCH (:Declaration)-[]->(e) } DETACH DELETE e');
|
|
43
|
+
return receiptFor(graph);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** One-file compatibility call of the unified replacement transaction. */
|
|
47
|
+
export function replaceFile(store, file, graph) {
|
|
48
|
+
return replaceFiles(store, [file], graph);
|
|
49
|
+
}
|
|
50
|
+
function clearGraph(store) {
|
|
51
|
+
store.execute('MATCH (d:Declaration) DETACH DELETE d');
|
|
52
|
+
store.execute('MATCH (e:External) DETACH DELETE e');
|
|
53
|
+
store.execute('MATCH (c:Commit) DETACH DELETE c');
|
|
54
|
+
}
|
|
55
|
+
function copyGraph(store, graph) {
|
|
56
|
+
copyRows(store, 'Declaration', declarationColumns, graph.declarations);
|
|
57
|
+
copyRows(store, 'External', externalColumns, graph.externals);
|
|
58
|
+
copyRows(store, 'Commit', commitColumns, graph.commits ?? []);
|
|
59
|
+
copyRelationships(store, graph.relationships);
|
|
60
|
+
}
|
|
61
|
+
/** Commit checkpoints survive graph replacement when their declaration target still exists. */
|
|
62
|
+
function withCheckpointHistory(graph, history) {
|
|
63
|
+
const keys = new Set(graph.declarations.map((declaration) => declaration.key));
|
|
64
|
+
const commits = new Map((graph.commits ?? []).map((commit) => [commit.hash, commit]));
|
|
65
|
+
for (const commit of history.commits)
|
|
66
|
+
commits.set(commit.hash, commit);
|
|
67
|
+
return {
|
|
68
|
+
...graph,
|
|
69
|
+
commits: [...commits.values()],
|
|
70
|
+
relationships: [...graph.relationships, ...history.changed.filter((relationship) => keys.has(relationship.to)), ...history.renamed.filter((relationship) => keys.has(relationship.from))],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function checkpointHistory(store, files) {
|
|
74
|
+
const commits = store.execute('MATCH (c:Commit) RETURN c.hash AS hash, c.ts AS ts, c.author AS author, c.subject AS subject').map((row) => ({ hash: String(row.hash), ts: String(row.ts), author: String(row.author), subject: String(row.subject) }));
|
|
75
|
+
const where = files === undefined ? '' : ` WHERE ${[...files].map((file) => `d.file = ${literal(file)}`).join(' OR ')}`;
|
|
76
|
+
const changed = store.execute(`MATCH (c:Commit)-[r:CHANGED]->(d:Declaration)${where} RETURN c.hash AS from, d.key AS to, r.added AS added, r.removed AS removed`).map((row) => ({ type: 'CHANGED', from: String(row.from), to: String(row.to), target: 'Declaration', added: Number(row.added), removed: Number(row.removed) }));
|
|
77
|
+
const renamed = store.execute(`MATCH (d:Declaration)-[r:RENAMED_FROM]->(c:Commit)${where} RETURN d.key AS from, c.hash AS to, r.oldKey AS oldKey, r.basis AS basis`).map((row) => ({ type: 'RENAMED_FROM', from: String(row.from), to: String(row.to), target: 'Commit', oldKey: String(row.oldKey), basis: String(row.basis) }));
|
|
78
|
+
return { commits, changed, renamed };
|
|
79
|
+
}
|
|
80
|
+
function copyMissingExternals(store, externals) {
|
|
81
|
+
const missing = externals.filter((external) => store.execute(`MATCH (e:External) WHERE e.key = ${literal(external.key)} RETURN e.key`).length === 0);
|
|
82
|
+
copyRows(store, 'External', externalColumns, missing);
|
|
83
|
+
}
|
|
84
|
+
function copyMissingCommits(store, commits) {
|
|
85
|
+
const missing = commits.filter((commit) => store.execute(`MATCH (c:Commit) WHERE c.hash = ${literal(commit.hash)} RETURN c.hash`).length === 0);
|
|
86
|
+
copyRows(store, 'Commit', commitColumns, missing);
|
|
87
|
+
}
|
|
88
|
+
function copyRelationships(store, relationships) {
|
|
89
|
+
for (const type of relationshipTypes) {
|
|
90
|
+
for (const target of relationshipTargets(type)) {
|
|
91
|
+
const rows = relationships.filter((relationship) => relationship.type === type && relationship.target === target);
|
|
92
|
+
if (rows.length === 0)
|
|
93
|
+
continue;
|
|
94
|
+
copyRows(store, type, relationshipColumns(type), rows, relationshipTargets(type).length === 1 ? undefined : target);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function copyRows(store, table, columns, rows, target) {
|
|
99
|
+
if (rows.length === 0)
|
|
100
|
+
return;
|
|
101
|
+
const directory = mkdtempSync(path.join(tmpdir(), 'tsym-store-'));
|
|
102
|
+
const csvPath = path.join(directory, `${table}.csv`);
|
|
103
|
+
try {
|
|
104
|
+
writeFileSync(csvPath, csv(columns, rows), { mode: 0o600 });
|
|
105
|
+
const pair = target === undefined ? '' : `, from='Declaration', to='${target}'`;
|
|
106
|
+
store.execute(`COPY ${table} FROM ${literal(csvPath)} (HEADER=true${pair})`);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
rmSync(directory, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function csv(columns, rows) {
|
|
113
|
+
return `${columns.join(',')}\n${rows.map((row) => columns.map((column) => csvValue(row[column])).join(',')).join('\n')}\n`;
|
|
114
|
+
}
|
|
115
|
+
function csvValue(value) {
|
|
116
|
+
if (value === undefined || value === null)
|
|
117
|
+
return '';
|
|
118
|
+
if (typeof value === 'boolean')
|
|
119
|
+
return value ? 'true' : 'false';
|
|
120
|
+
return `"${String(value).replaceAll('"', '""')}"`;
|
|
121
|
+
}
|
|
122
|
+
function literal(value) {
|
|
123
|
+
return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
|
|
124
|
+
}
|
|
125
|
+
function relationshipColumns(type) {
|
|
126
|
+
switch (type) {
|
|
127
|
+
case 'CONTAINS':
|
|
128
|
+
case 'IMPLEMENTS':
|
|
129
|
+
case 'EXTENDS': return ['from', 'to'];
|
|
130
|
+
case 'REFERENCES': return ['from', 'to', 'line', 'col', 'role'];
|
|
131
|
+
case 'CALLS': return ['from', 'to', 'line', 'col', 'dispatch'];
|
|
132
|
+
case 'TYPE_MENTION': return ['from', 'to', 'line', 'col', 'position'];
|
|
133
|
+
case 'IMPORTS': return ['from', 'to', 'line', 'names'];
|
|
134
|
+
case 'EXPORTS': return ['from', 'to', 'name', 'line', 'col'];
|
|
135
|
+
case 'CHANGED': return ['from', 'to', 'added', 'removed'];
|
|
136
|
+
case 'RENAMED_FROM': return ['from', 'to', 'oldKey', 'basis'];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function relationshipTargets(type) {
|
|
140
|
+
switch (type) {
|
|
141
|
+
case 'REFERENCES':
|
|
142
|
+
case 'CALLS':
|
|
143
|
+
case 'TYPE_MENTION':
|
|
144
|
+
case 'IMPORTS': return ['Declaration', 'External'];
|
|
145
|
+
case 'RENAMED_FROM': return ['Commit'];
|
|
146
|
+
default: return ['Declaration'];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const relationshipTypes = ['CONTAINS', 'REFERENCES', 'CALLS', 'IMPLEMENTS', 'EXTENDS', 'TYPE_MENTION', 'IMPORTS', 'EXPORTS', 'CHANGED', 'RENAMED_FROM'];
|
|
150
|
+
function readRelationships(store, type, target) {
|
|
151
|
+
const source = type === 'CHANGED' ? 'Commit' : 'Declaration';
|
|
152
|
+
const columns = relationshipColumns(type).filter((column) => column !== 'from' && column !== 'to').map((column) => `r.${column} AS ${column}`).join(', ');
|
|
153
|
+
const returned = [`a.${type === 'CHANGED' ? 'hash' : 'key'} AS from`, `b.${target === 'Commit' ? 'hash' : 'key'} AS to`, columns].filter(Boolean).join(', ');
|
|
154
|
+
const rows = [];
|
|
155
|
+
const pageSize = 50_000;
|
|
156
|
+
for (let offset = 0;; offset += pageSize) {
|
|
157
|
+
const page = store.execute(`MATCH (a:${source})-[r:${type}]->(b:${target}) RETURN ${returned} SKIP ${offset} LIMIT ${pageSize}`);
|
|
158
|
+
rows.push(...page);
|
|
159
|
+
if (page.length < pageSize)
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
return rows.map((row) => {
|
|
163
|
+
const relationship = { type, from: String(row.from), to: String(row.to), target };
|
|
164
|
+
for (const column of relationshipColumns(type)) {
|
|
165
|
+
if (column === 'from' || column === 'to' || row[column] === undefined)
|
|
166
|
+
continue;
|
|
167
|
+
const value = row[column];
|
|
168
|
+
if (column === 'line' || column === 'col' || column === 'added' || column === 'removed')
|
|
169
|
+
Object.assign(relationship, { [column]: Number(value) });
|
|
170
|
+
else if (column === 'dispatch')
|
|
171
|
+
Object.assign(relationship, { dispatch: Boolean(value) });
|
|
172
|
+
else
|
|
173
|
+
Object.assign(relationship, { [column]: String(value) });
|
|
174
|
+
}
|
|
175
|
+
return relationship;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function validateGraph(graph) {
|
|
179
|
+
for (const relationship of graph.relationships) {
|
|
180
|
+
if (!relationshipTargets(relationship.type).includes(relationship.target))
|
|
181
|
+
throw new Error(`${relationship.type} cannot target ${relationship.target}.`);
|
|
182
|
+
if (relationship.type === 'CHANGED' && relationship.target !== 'Declaration')
|
|
183
|
+
throw new Error('CHANGED must target a Declaration.');
|
|
184
|
+
if (relationship.type === 'RENAMED_FROM' && relationship.target !== 'Commit')
|
|
185
|
+
throw new Error('RENAMED_FROM must target a Commit.');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function receiptFor(graph) {
|
|
189
|
+
const relationships = Object.fromEntries(relationshipTypes.map((type) => [type, 0]));
|
|
190
|
+
for (const relationship of graph.relationships)
|
|
191
|
+
relationships[relationship.type] += 1;
|
|
192
|
+
return { declarations: graph.declarations.length, externals: graph.externals.length, commits: graph.commits?.length ?? 0, relationships };
|
|
193
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ConfiguredProjects } from '../ts7/projects.js';
|
|
2
|
+
export interface IndexedFileMeta {
|
|
3
|
+
size: number;
|
|
4
|
+
mtimeMs: number;
|
|
5
|
+
}
|
|
6
|
+
export interface StoreMetadata {
|
|
7
|
+
schemaVersion: number;
|
|
8
|
+
tsVersion: string;
|
|
9
|
+
tsymBuild: string;
|
|
10
|
+
indexedCommit: string;
|
|
11
|
+
configSet: string[];
|
|
12
|
+
configHashes: Record<string, string>;
|
|
13
|
+
packageHash: string;
|
|
14
|
+
lockfileHash: string;
|
|
15
|
+
/** Duration of the index operation that wrote this metadata; reported by `server status` after a reuse. */
|
|
16
|
+
indexDurationMs: number;
|
|
17
|
+
files: Record<string, IndexedFileMeta>;
|
|
18
|
+
}
|
|
19
|
+
export declare function metadataPath(storePath: string): string;
|
|
20
|
+
export declare function readMetadata(storePath: string): StoreMetadata | null;
|
|
21
|
+
export declare function writeMetadata(storePath: string, metadata: StoreMetadata): void;
|
|
22
|
+
/** Captures every input whose change can alter the configured-program graph. */
|
|
23
|
+
export declare function metadataFor(root: string, projects: ConfiguredProjects, indexedCommit: string, indexDurationMs?: number, tsVersion?: string): StoreMetadata;
|
|
24
|
+
/** A persisted store can be rehydrated only when its schema, program shape, and checkpoint still match. */
|
|
25
|
+
export declare function canReuse(metadata: StoreMetadata | null, expected: StoreMetadata): boolean;
|
|
26
|
+
/** Source changes do not invalidate the graph shape; startup rehydrates then performs the ordinary refresh. */
|
|
27
|
+
export declare function hasSourceDrift(metadata: StoreMetadata, expected: StoreMetadata): boolean;
|
|
28
|
+
export declare function hashTsymBuild(dist?: string): string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import * as ts from 'ts5';
|
|
7
|
+
import { STORE_SCHEMA_VERSION } from './schema.js';
|
|
8
|
+
const META_FILE = 'meta.json';
|
|
9
|
+
const lockfiles = ['package-lock.json', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'yarn.lock'];
|
|
10
|
+
export function metadataPath(storePath) {
|
|
11
|
+
return path.join(storePath, META_FILE);
|
|
12
|
+
}
|
|
13
|
+
export function readMetadata(storePath) {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(readFileSync(metadataPath(storePath), 'utf8'));
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT')
|
|
19
|
+
return null;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function writeMetadata(storePath, metadata) {
|
|
24
|
+
if (metadata.schemaVersion !== STORE_SCHEMA_VERSION)
|
|
25
|
+
throw new Error(`Store metadata schema version must be ${STORE_SCHEMA_VERSION}.`);
|
|
26
|
+
mkdirSync(storePath, { recursive: true, mode: 0o700 });
|
|
27
|
+
const target = metadataPath(storePath);
|
|
28
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
29
|
+
writeFileSync(temporary, `${JSON.stringify(metadata)}\n`, { mode: 0o600 });
|
|
30
|
+
renameSync(temporary, target);
|
|
31
|
+
}
|
|
32
|
+
/** Captures every input whose change can alter the configured-program graph. */
|
|
33
|
+
export function metadataFor(root, projects, indexedCommit, indexDurationMs = 0, tsVersion = bundledTsVersion()) {
|
|
34
|
+
const configs = [...new Set(projects.records().map((project) => path.resolve(project.config)))].sort();
|
|
35
|
+
const shapeConfigs = inheritedConfigs(configs);
|
|
36
|
+
const files = [...new Set(projects.records().flatMap((project) => [...project.files]))].sort();
|
|
37
|
+
return {
|
|
38
|
+
schemaVersion: STORE_SCHEMA_VERSION,
|
|
39
|
+
tsVersion,
|
|
40
|
+
tsymBuild: hashTsymBuild(),
|
|
41
|
+
indexedCommit,
|
|
42
|
+
configSet: configs.map((file) => relative(root, file)),
|
|
43
|
+
configHashes: Object.fromEntries(shapeConfigs.map((file) => [relative(root, file), hashFile(file)])),
|
|
44
|
+
packageHash: hashOptional(path.join(root, 'package.json')),
|
|
45
|
+
lockfileHash: lockfiles.map((name) => `${name}:${hashOptional(path.join(root, name))}`).join('|'),
|
|
46
|
+
indexDurationMs,
|
|
47
|
+
files: Object.fromEntries(files.map((file) => {
|
|
48
|
+
const stat = statSync(file);
|
|
49
|
+
return [relative(root, file), { size: stat.size, mtimeMs: stat.mtimeMs }];
|
|
50
|
+
})),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** A persisted store can be rehydrated only when its schema, program shape, and checkpoint still match. */
|
|
54
|
+
export function canReuse(metadata, expected) {
|
|
55
|
+
return metadata !== null && JSON.stringify(shapeOf(metadata)) === JSON.stringify(shapeOf(expected));
|
|
56
|
+
}
|
|
57
|
+
/** Source changes do not invalidate the graph shape; startup rehydrates then performs the ordinary refresh. */
|
|
58
|
+
export function hasSourceDrift(metadata, expected) {
|
|
59
|
+
return JSON.stringify(metadata.files) !== JSON.stringify(expected.files);
|
|
60
|
+
}
|
|
61
|
+
function shapeOf(metadata) {
|
|
62
|
+
const { files: _files, indexDurationMs: _duration, ...shape } = metadata;
|
|
63
|
+
return shape;
|
|
64
|
+
}
|
|
65
|
+
function inheritedConfigs(configs) {
|
|
66
|
+
const found = new Set();
|
|
67
|
+
const visit = (config) => {
|
|
68
|
+
const absolute = path.resolve(config);
|
|
69
|
+
if (found.has(absolute))
|
|
70
|
+
return;
|
|
71
|
+
found.add(absolute);
|
|
72
|
+
const result = ts.readConfigFile(absolute, ts.sys.readFile);
|
|
73
|
+
if (result.error || !result.config || typeof result.config.extends !== 'string')
|
|
74
|
+
return;
|
|
75
|
+
const inherited = resolveExtends(absolute, result.config.extends);
|
|
76
|
+
if (inherited !== undefined)
|
|
77
|
+
visit(inherited);
|
|
78
|
+
};
|
|
79
|
+
for (const config of configs)
|
|
80
|
+
visit(config);
|
|
81
|
+
return [...found].sort();
|
|
82
|
+
}
|
|
83
|
+
function resolveExtends(config, extend) {
|
|
84
|
+
if (extend.startsWith('.') || path.isAbsolute(extend)) {
|
|
85
|
+
const candidate = path.resolve(path.dirname(config), extend);
|
|
86
|
+
const filename = path.extname(candidate) === '' ? `${candidate}.json` : candidate;
|
|
87
|
+
try {
|
|
88
|
+
statSync(filename);
|
|
89
|
+
return filename;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const resolved = ts.nodeNextJsonConfigResolver(extend, path.join(path.dirname(config), 'tsconfig.json'), ts.sys).resolvedModule?.resolvedFileName;
|
|
96
|
+
return resolved === undefined ? undefined : path.resolve(resolved);
|
|
97
|
+
}
|
|
98
|
+
function bundledTsVersion() {
|
|
99
|
+
return createRequire(import.meta.url)('typescript/package.json').version;
|
|
100
|
+
}
|
|
101
|
+
export function hashTsymBuild(dist = path.resolve(fileURLToPath(new URL('../../dist/', import.meta.url)))) {
|
|
102
|
+
const files = [];
|
|
103
|
+
const visit = (directory) => {
|
|
104
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
105
|
+
const file = path.join(directory, entry.name);
|
|
106
|
+
if (entry.isDirectory())
|
|
107
|
+
visit(file);
|
|
108
|
+
else if (entry.isFile() && entry.name.endsWith('.js'))
|
|
109
|
+
files.push(file);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
visit(dist);
|
|
113
|
+
const hash = createHash('sha256');
|
|
114
|
+
for (const file of files.sort()) {
|
|
115
|
+
const content = readFileSync(file);
|
|
116
|
+
hash.update(`${path.relative(dist, file).split(path.sep).join('/')}\0${content.byteLength}\0`);
|
|
117
|
+
hash.update(content);
|
|
118
|
+
}
|
|
119
|
+
return hash.digest('hex');
|
|
120
|
+
}
|
|
121
|
+
function relative(root, file) {
|
|
122
|
+
const result = path.relative(root, file);
|
|
123
|
+
return result.startsWith(`..${path.sep}`) || path.isAbsolute(result) ? file : result;
|
|
124
|
+
}
|
|
125
|
+
function hashOptional(file) {
|
|
126
|
+
try {
|
|
127
|
+
return hashFile(file);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (error.code === 'ENOENT')
|
|
131
|
+
return '<missing>';
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function hashFile(file) {
|
|
136
|
+
return createHash('sha256').update(readFileSync(file)).digest('hex');
|
|
137
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type LbugValue } from '@ladybugdb/core';
|
|
2
|
+
import { Store, type StoreRow } from './store.js';
|
|
3
|
+
export type QueryParameters = Record<string, LbugValue>;
|
|
4
|
+
/** Executes a parameterized Cypher statement against the resident-owned store. */
|
|
5
|
+
export declare function query(store: Store, cypher: string, parameters?: QueryParameters): StoreRow[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const STORE_SCHEMA_VERSION = 2;
|
|
2
|
+
export declare const NODE_TABLES: readonly ["CREATE NODE TABLE Declaration(key STRING, at STRING, name STRING, qname STRING, kind STRING, exported BOOL, file STRING, dir STRING, top STRING, line INT64, col INT64, endLine INT64, lines INT64, declCount INT64, container STRING, PRIMARY KEY(key))", "CREATE NODE TABLE External(key STRING, module STRING, name STRING, PRIMARY KEY(key))", "CREATE NODE TABLE Commit(hash STRING, ts STRING, author STRING, subject STRING, PRIMARY KEY(hash))"];
|
|
3
|
+
export declare const RELATION_TABLES: readonly ["CREATE REL TABLE CONTAINS(FROM Declaration TO Declaration)", "CREATE REL TABLE REFERENCES(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, col INT64, role STRING)", "CREATE REL TABLE CALLS(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, col INT64, dispatch BOOL)", "CREATE REL TABLE IMPLEMENTS(FROM Declaration TO Declaration)", "CREATE REL TABLE EXTENDS(FROM Declaration TO Declaration)", "CREATE REL TABLE TYPE_MENTION(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, col INT64, position STRING)", "CREATE REL TABLE IMPORTS(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, names STRING)", "CREATE REL TABLE EXPORTS(FROM Declaration TO Declaration, name STRING, line INT64, col INT64)", "CREATE REL TABLE CHANGED(FROM Commit TO Declaration, added INT64, removed INT64)", "CREATE REL TABLE RENAMED_FROM(FROM Declaration TO Commit, oldKey STRING, basis STRING)"];
|
|
4
|
+
export declare const SCHEMA_STATEMENTS: readonly ["CREATE NODE TABLE Declaration(key STRING, at STRING, name STRING, qname STRING, kind STRING, exported BOOL, file STRING, dir STRING, top STRING, line INT64, col INT64, endLine INT64, lines INT64, declCount INT64, container STRING, PRIMARY KEY(key))", "CREATE NODE TABLE External(key STRING, module STRING, name STRING, PRIMARY KEY(key))", "CREATE NODE TABLE Commit(hash STRING, ts STRING, author STRING, subject STRING, PRIMARY KEY(hash))", "CREATE REL TABLE CONTAINS(FROM Declaration TO Declaration)", "CREATE REL TABLE REFERENCES(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, col INT64, role STRING)", "CREATE REL TABLE CALLS(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, col INT64, dispatch BOOL)", "CREATE REL TABLE IMPLEMENTS(FROM Declaration TO Declaration)", "CREATE REL TABLE EXTENDS(FROM Declaration TO Declaration)", "CREATE REL TABLE TYPE_MENTION(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, col INT64, position STRING)", "CREATE REL TABLE IMPORTS(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, names STRING)", "CREATE REL TABLE EXPORTS(FROM Declaration TO Declaration, name STRING, line INT64, col INT64)", "CREATE REL TABLE CHANGED(FROM Commit TO Declaration, added INT64, removed INT64)", "CREATE REL TABLE RENAMED_FROM(FROM Declaration TO Commit, oldKey STRING, basis STRING)"];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const STORE_SCHEMA_VERSION = 2;
|
|
2
|
+
export const NODE_TABLES = [
|
|
3
|
+
'CREATE NODE TABLE Declaration(key STRING, at STRING, name STRING, qname STRING, kind STRING, exported BOOL, file STRING, dir STRING, top STRING, line INT64, col INT64, endLine INT64, lines INT64, declCount INT64, container STRING, PRIMARY KEY(key))',
|
|
4
|
+
'CREATE NODE TABLE External(key STRING, module STRING, name STRING, PRIMARY KEY(key))',
|
|
5
|
+
'CREATE NODE TABLE Commit(hash STRING, ts STRING, author STRING, subject STRING, PRIMARY KEY(hash))',
|
|
6
|
+
];
|
|
7
|
+
export const RELATION_TABLES = [
|
|
8
|
+
'CREATE REL TABLE CONTAINS(FROM Declaration TO Declaration)',
|
|
9
|
+
'CREATE REL TABLE REFERENCES(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, col INT64, role STRING)',
|
|
10
|
+
'CREATE REL TABLE CALLS(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, col INT64, dispatch BOOL)',
|
|
11
|
+
'CREATE REL TABLE IMPLEMENTS(FROM Declaration TO Declaration)',
|
|
12
|
+
'CREATE REL TABLE EXTENDS(FROM Declaration TO Declaration)',
|
|
13
|
+
'CREATE REL TABLE TYPE_MENTION(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, col INT64, position STRING)',
|
|
14
|
+
'CREATE REL TABLE IMPORTS(FROM Declaration TO Declaration, FROM Declaration TO External, line INT64, names STRING)',
|
|
15
|
+
'CREATE REL TABLE EXPORTS(FROM Declaration TO Declaration, name STRING, line INT64, col INT64)',
|
|
16
|
+
'CREATE REL TABLE CHANGED(FROM Commit TO Declaration, added INT64, removed INT64)',
|
|
17
|
+
'CREATE REL TABLE RENAMED_FROM(FROM Declaration TO Commit, oldKey STRING, basis STRING)',
|
|
18
|
+
];
|
|
19
|
+
export const SCHEMA_STATEMENTS = [...NODE_TABLES, ...RELATION_TABLES];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { LbugValue } from '@ladybugdb/core';
|
|
2
|
+
export declare class EngineBinaryError extends Error {
|
|
3
|
+
readonly code = "no-engine-binary";
|
|
4
|
+
readonly platform: string;
|
|
5
|
+
constructor(platform?: string);
|
|
6
|
+
}
|
|
7
|
+
export interface StoreOptions {
|
|
8
|
+
boundedQueryTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class StoreOwnershipError extends Error {
|
|
11
|
+
readonly storePath: string;
|
|
12
|
+
readonly ownerPid?: number | undefined;
|
|
13
|
+
constructor(storePath: string, ownerPid?: number | undefined);
|
|
14
|
+
}
|
|
15
|
+
export declare class StoreReadOnlyError extends Error {
|
|
16
|
+
constructor();
|
|
17
|
+
}
|
|
18
|
+
export type StoreRow = Record<string, LbugValue>;
|
|
19
|
+
export declare class Store {
|
|
20
|
+
readonly path: string;
|
|
21
|
+
private readonly ownerPath;
|
|
22
|
+
private database;
|
|
23
|
+
private connection;
|
|
24
|
+
private boundedConnection;
|
|
25
|
+
private closed;
|
|
26
|
+
private readonly boundedQueryTimeoutMs;
|
|
27
|
+
constructor(storePath: string, options?: StoreOptions);
|
|
28
|
+
execute(statement: string, parameters?: Record<string, LbugValue>): StoreRow[];
|
|
29
|
+
/** Executes a read query on a separate connection whose 30-second engine timeout never affects writes. */
|
|
30
|
+
executeBounded(statement: string, parameters?: Record<string, LbugValue>): StoreRow[];
|
|
31
|
+
transaction<T>(work: () => T): T;
|
|
32
|
+
checkpoint(): void;
|
|
33
|
+
close(): void;
|
|
34
|
+
private bounded;
|
|
35
|
+
private assertOpen;
|
|
36
|
+
}
|