@anokye-labs/kbexplorer-engine 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 +77 -0
- package/dist/build-manifest-BT0sY84J.d.ts +239 -0
- package/dist/build-manifest-BkgZUL0E.d.cts +239 -0
- package/dist/chunk-ASLGNOYV.js +220 -0
- package/dist/chunk-BA526FDQ.js +16 -0
- package/dist/chunk-DJ2DBEEK.js +424 -0
- package/dist/chunk-FJ4GTN4U.js +973 -0
- package/dist/chunk-HXAS3BSD.js +187 -0
- package/dist/chunk-IWEURXYH.js +273 -0
- package/dist/chunk-JNQVSNLC.js +55 -0
- package/dist/chunk-MW4BLXVK.js +72 -0
- package/dist/chunk-NOGBKE7C.js +351 -0
- package/dist/chunk-XVI6CBSX.js +412 -0
- package/dist/chunk-YTMIERYM.js +67 -0
- package/dist/github-api-source-KV5HZARH.js +4 -0
- package/dist/index.cjs +6218 -0
- package/dist/index.d.cts +1396 -0
- package/dist/index.d.ts +1396 -0
- package/dist/index.js +2892 -0
- package/dist/node-wasm-VSXBOCPD.js +9 -0
- package/dist/plugin-loader-K4VU6ODV.js +2 -0
- package/dist/repo-data-0JvFdLGv.d.cts +461 -0
- package/dist/repo-data-0JvFdLGv.d.ts +461 -0
- package/dist/sources.cjs +1191 -0
- package/dist/sources.d.cts +194 -0
- package/dist/sources.d.ts +194 -0
- package/dist/sources.js +357 -0
- package/dist/sqlite-graph-store-NH2NHL2B.js +2 -0
- package/dist/sqlite-runtime-CysPjjzX.d.cts +96 -0
- package/dist/sqlite-runtime-CysPjjzX.d.ts +96 -0
- package/dist/store-orchestrator-337XWIXO.js +5 -0
- package/dist/store.cjs +1112 -0
- package/dist/store.d.cts +37 -0
- package/dist/store.d.ts +37 -0
- package/dist/store.js +7 -0
- package/package.json +70 -0
package/dist/sources.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { globToRegex } from './chunk-MW4BLXVK.js';
|
|
2
|
+
export { buildManifest } from './chunk-MW4BLXVK.js';
|
|
3
|
+
export { GitHubApiSource } from './chunk-NOGBKE7C.js';
|
|
4
|
+
import { collectNodemapData } from './chunk-XVI6CBSX.js';
|
|
5
|
+
export { GITHUB_ENDPOINT_PATTERNS, GitHubApiError, NotModifiedError, RateLimitError, fetchCommits, fetchFile, fetchFiles, fetchIssues, fetchPullRequests, fetchReleases, fetchTree } from './chunk-FJ4GTN4U.js';
|
|
6
|
+
import './chunk-JNQVSNLC.js';
|
|
7
|
+
import { readFile, readdir, stat } from 'fs/promises';
|
|
8
|
+
import { resolve, basename, join, isAbsolute, relative, sep } from 'path';
|
|
9
|
+
import yaml from 'yaml';
|
|
10
|
+
|
|
11
|
+
// src/sources/manifest-source.ts
|
|
12
|
+
var ManifestSource = class {
|
|
13
|
+
id = "manifest";
|
|
14
|
+
name = "Repo Manifest";
|
|
15
|
+
/** A manifest is a frozen snapshot — read is the only possible affordance. */
|
|
16
|
+
possibleAffordances = ["read"];
|
|
17
|
+
manifest;
|
|
18
|
+
config;
|
|
19
|
+
constructor(manifest, config) {
|
|
20
|
+
this.manifest = manifest;
|
|
21
|
+
this.config = config;
|
|
22
|
+
}
|
|
23
|
+
async getRepoData() {
|
|
24
|
+
const manifest = this.manifest;
|
|
25
|
+
const listFiles = async (pattern) => {
|
|
26
|
+
const regex = globToRegex(pattern);
|
|
27
|
+
return Object.keys(manifest.nodemapFiles ?? {}).filter((p) => regex.test(p));
|
|
28
|
+
};
|
|
29
|
+
const data = {
|
|
30
|
+
repo: this.config.source.repo,
|
|
31
|
+
tree: manifest.tree,
|
|
32
|
+
authoredContent: manifest.authoredContent,
|
|
33
|
+
nodemapRaw: manifest.nodemapRaw ?? null,
|
|
34
|
+
listFiles,
|
|
35
|
+
issues: manifest.issues,
|
|
36
|
+
pullRequests: manifest.pullRequests,
|
|
37
|
+
commits: manifest.commits,
|
|
38
|
+
branches: manifest.branches ?? [],
|
|
39
|
+
repoMetadata: manifest.repoMetadata ?? null,
|
|
40
|
+
releases: manifest.releases ?? [],
|
|
41
|
+
structuralFiles: manifest.structuralFiles ?? {},
|
|
42
|
+
structuredNodeMapRaw: manifest.structuredNodeMapRaw ?? null,
|
|
43
|
+
contentModel: manifest.contentModel ?? null,
|
|
44
|
+
readme: manifest.readme,
|
|
45
|
+
themeFileRaw: manifest.themeFileRaw ?? null
|
|
46
|
+
};
|
|
47
|
+
if (manifest.nodemapFiles !== void 0) data.nodemapFiles = manifest.nodemapFiles;
|
|
48
|
+
if (manifest.nodemapDirs !== void 0) {
|
|
49
|
+
data.nodemapDirs = manifest.nodemapDirs;
|
|
50
|
+
}
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Read-only resource surface. Files (tree blobs/trees) and issues are
|
|
55
|
+
* retrievable; every resource is afforded `['read']` and links only to
|
|
56
|
+
* itself. No write/stage, no staging area.
|
|
57
|
+
*/
|
|
58
|
+
async retrieve(query) {
|
|
59
|
+
const kind = query.kind;
|
|
60
|
+
const out = [];
|
|
61
|
+
if (!kind || kind === "file" || kind === "tree") {
|
|
62
|
+
for (const item of this.manifest.tree) {
|
|
63
|
+
if (kind === "file" && item.type !== "blob") continue;
|
|
64
|
+
if (kind === "tree" && item.type !== "tree") continue;
|
|
65
|
+
out.push(this.fileResource(item));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!kind || kind === "issue") {
|
|
69
|
+
for (const issue of this.manifest.issues) {
|
|
70
|
+
out.push(this.issueResource(issue.number, issue.title));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
async get(href) {
|
|
76
|
+
const all = await this.retrieve({});
|
|
77
|
+
return all.find((r) => r.href === href);
|
|
78
|
+
}
|
|
79
|
+
fileResource(item) {
|
|
80
|
+
return {
|
|
81
|
+
href: `git://${this.config.source.repo}/${item.path}`,
|
|
82
|
+
kind: item.type === "tree" ? "tree" : "file",
|
|
83
|
+
affordances: ["read"],
|
|
84
|
+
links: [{ rel: "self", href: `git://${this.config.source.repo}/${item.path}` }],
|
|
85
|
+
body: { path: item.path }
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
issueResource(number, title) {
|
|
89
|
+
return {
|
|
90
|
+
href: `github://${this.config.source.repo}/issues/${number}`,
|
|
91
|
+
kind: "issue",
|
|
92
|
+
affordances: ["read"],
|
|
93
|
+
links: [{ rel: "self", href: `github://${this.config.source.repo}/issues/${number}` }],
|
|
94
|
+
body: { number, title }
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var ALWAYS_IGNORE = /* @__PURE__ */ new Set([".git", "node_modules", "dist"]);
|
|
99
|
+
var MAX_STRUCTURAL_FILE_SIZE = 256 * 1024;
|
|
100
|
+
function isStructuralPath(path) {
|
|
101
|
+
return path.startsWith(".github/") || /(^|\/)CODEOWNERS$/.test(path);
|
|
102
|
+
}
|
|
103
|
+
var FileSystemSource = class {
|
|
104
|
+
id = "filesystem";
|
|
105
|
+
name = "File System";
|
|
106
|
+
/** A local snapshot read — read is the only possible affordance. */
|
|
107
|
+
possibleAffordances = ["read"];
|
|
108
|
+
rootDir;
|
|
109
|
+
repo;
|
|
110
|
+
contentPath;
|
|
111
|
+
contentModelPath;
|
|
112
|
+
ignore;
|
|
113
|
+
includeFileTree;
|
|
114
|
+
cache = null;
|
|
115
|
+
constructor(rootDir, options = {}) {
|
|
116
|
+
this.rootDir = resolve(rootDir);
|
|
117
|
+
this.repo = options.repo ?? basename(this.rootDir);
|
|
118
|
+
this.contentPath = options.contentPath ?? "content";
|
|
119
|
+
this.contentModelPath = options.contentModelPath ?? "content-model";
|
|
120
|
+
this.ignore = /* @__PURE__ */ new Set([...ALWAYS_IGNORE, ...options.ignore ?? []]);
|
|
121
|
+
this.includeFileTree = options.includeFileTree ?? true;
|
|
122
|
+
}
|
|
123
|
+
async getRepoData() {
|
|
124
|
+
if (!this.cache) this.cache = this.readRepoData();
|
|
125
|
+
return this.cache;
|
|
126
|
+
}
|
|
127
|
+
async readRepoData() {
|
|
128
|
+
const files = await this.walk();
|
|
129
|
+
const paths = files.map((f) => f.path);
|
|
130
|
+
const tree = this.includeFileTree ? files.map((f) => ({ path: f.path, type: f.type })) : [];
|
|
131
|
+
const authoredContent = {};
|
|
132
|
+
const structuralFiles = {};
|
|
133
|
+
const rootScan = this.contentPath === "" || this.contentPath === ".";
|
|
134
|
+
const contentPrefix = rootScan ? "" : `${this.contentPath}/`;
|
|
135
|
+
const contentModelPrefix = `${this.contentModelPath}/`;
|
|
136
|
+
const contentModelFiles = {};
|
|
137
|
+
let readme = null;
|
|
138
|
+
let structuredNodeMapRaw = null;
|
|
139
|
+
for (const file of files) {
|
|
140
|
+
if (file.type !== "blob") continue;
|
|
141
|
+
const isAuthoredMd = file.path.endsWith(".md") && file.path !== "README.md" && (rootScan ? !file.path.includes("/") : file.path.startsWith(contentPrefix));
|
|
142
|
+
if (isAuthoredMd) {
|
|
143
|
+
authoredContent[file.path] = await this.read(file.path);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (isStructuralPath(file.path) && (file.size ?? 0) <= MAX_STRUCTURAL_FILE_SIZE) {
|
|
147
|
+
const content = await this.read(file.path);
|
|
148
|
+
if (content.length <= MAX_STRUCTURAL_FILE_SIZE) structuralFiles[file.path] = content;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (file.path.startsWith(contentModelPrefix)) {
|
|
152
|
+
const relativePath = file.path.slice(contentModelPrefix.length);
|
|
153
|
+
contentModelFiles[relativePath] = await this.read(file.path);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (file.path === "README.md") {
|
|
157
|
+
readme = await this.read(file.path);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (structuredNodeMapRaw === null && (file.path === "structured-node-map.yaml" || file.path === "structured-node-map.yml")) {
|
|
161
|
+
structuredNodeMapRaw = await this.read(file.path);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const contentModel = Object.keys(contentModelFiles).length > 0 ? { root: this.contentModelPath, files: contentModelFiles } : null;
|
|
165
|
+
const listFiles = async (pattern) => {
|
|
166
|
+
const regex = globToRegex(pattern);
|
|
167
|
+
return paths.filter((p) => regex.test(p));
|
|
168
|
+
};
|
|
169
|
+
const nodemapRaw = await this.readNodemapRaw();
|
|
170
|
+
let nodemapFiles;
|
|
171
|
+
let nodemapDirs;
|
|
172
|
+
if (nodemapRaw !== null) {
|
|
173
|
+
const collected = await collectNodemapData(
|
|
174
|
+
nodemapRaw,
|
|
175
|
+
files,
|
|
176
|
+
(path) => this.readSafe(path),
|
|
177
|
+
(dir) => this.listNodemapDir(dir)
|
|
178
|
+
);
|
|
179
|
+
nodemapFiles = collected.nodemapFiles;
|
|
180
|
+
nodemapDirs = collected.nodemapDirs;
|
|
181
|
+
}
|
|
182
|
+
const themeFileRaw = await this.resolveThemeFileRaw();
|
|
183
|
+
return {
|
|
184
|
+
repo: this.repo,
|
|
185
|
+
tree,
|
|
186
|
+
authoredContent,
|
|
187
|
+
nodemapRaw,
|
|
188
|
+
...nodemapFiles !== void 0 ? { nodemapFiles } : {},
|
|
189
|
+
...nodemapDirs !== void 0 ? { nodemapDirs } : {},
|
|
190
|
+
listFiles,
|
|
191
|
+
issues: [],
|
|
192
|
+
pullRequests: [],
|
|
193
|
+
commits: [],
|
|
194
|
+
// Legitimately empty/null in local mode — there is no live GitHub to
|
|
195
|
+
// fetch branches/repo metadata from (anokye-labs/kbexplorer-engine#23).
|
|
196
|
+
branches: [],
|
|
197
|
+
repoMetadata: null,
|
|
198
|
+
releases: [],
|
|
199
|
+
structuralFiles,
|
|
200
|
+
structuredNodeMapRaw,
|
|
201
|
+
contentModel,
|
|
202
|
+
readme,
|
|
203
|
+
themeFileRaw
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/** Read one repo-relative file as UTF-8. */
|
|
207
|
+
read(repoRelative) {
|
|
208
|
+
return readFile(join(this.rootDir, repoRelative), "utf8");
|
|
209
|
+
}
|
|
210
|
+
/** Read one repo-relative file as UTF-8, returning `null` when missing/unreadable. */
|
|
211
|
+
async readSafe(repoRelative) {
|
|
212
|
+
try {
|
|
213
|
+
return await readFile(join(this.rootDir, repoRelative), "utf8");
|
|
214
|
+
} catch {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** Read `nodemap.yaml`/`nodemap.yml` from the repo root, or `null` if neither exists. */
|
|
219
|
+
async readNodemapRaw() {
|
|
220
|
+
for (const name of ["nodemap.yaml", "nodemap.yml"]) {
|
|
221
|
+
const content = await this.readSafe(name);
|
|
222
|
+
if (content !== null) return content;
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
/** Read `${contentPath}/config.yaml`(`.yml`), falling back to a root-level `config.yaml`. */
|
|
227
|
+
async readConfigRaw() {
|
|
228
|
+
const rootScan = this.contentPath === "" || this.contentPath === ".";
|
|
229
|
+
const contentPrefix = rootScan ? "" : `${this.contentPath}/`;
|
|
230
|
+
const candidates = [`${contentPrefix}config.yaml`, `${contentPrefix}config.yml`, "config.yaml"];
|
|
231
|
+
for (const candidate of candidates) {
|
|
232
|
+
const content = await this.readSafe(candidate);
|
|
233
|
+
if (content !== null) return content;
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Read the raw contents of the dedicated theme file referenced by
|
|
239
|
+
* `config.theme.themesFile`, mirroring the old generator's `readThemeFile`.
|
|
240
|
+
* `null` when no `themesFile` is configured, the config can't be parsed, the
|
|
241
|
+
* path escapes the repo root, or the file is missing — a safe no-op.
|
|
242
|
+
*/
|
|
243
|
+
async resolveThemeFileRaw() {
|
|
244
|
+
const configRaw = await this.readConfigRaw();
|
|
245
|
+
if (!configRaw) return null;
|
|
246
|
+
let parsed;
|
|
247
|
+
try {
|
|
248
|
+
parsed = yaml.parse(configRaw);
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const themesFile = parsed?.theme?.themesFile;
|
|
253
|
+
if (!themesFile || typeof themesFile !== "string") return null;
|
|
254
|
+
if (isAbsolute(themesFile)) return null;
|
|
255
|
+
const abs = resolve(this.rootDir, themesFile);
|
|
256
|
+
const rel = relative(this.rootDir, abs);
|
|
257
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return null;
|
|
258
|
+
try {
|
|
259
|
+
return await readFile(abs, "utf8");
|
|
260
|
+
} catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* One-level directory listing (with file sizes) for a nodemap `directory:`
|
|
266
|
+
* entry — distinct from `walk()`, which is recursive and does not stat
|
|
267
|
+
* files. Mirrors the old generator's `listDir`.
|
|
268
|
+
*/
|
|
269
|
+
async listNodemapDir(dirPath) {
|
|
270
|
+
const abs = join(this.rootDir, dirPath);
|
|
271
|
+
let entries;
|
|
272
|
+
try {
|
|
273
|
+
entries = await readdir(abs, { withFileTypes: true });
|
|
274
|
+
} catch {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
const out = [];
|
|
278
|
+
for (const entry of entries) {
|
|
279
|
+
if (entry.name.startsWith(".")) continue;
|
|
280
|
+
const entryPath = dirPath ? `${dirPath}/${entry.name}` : entry.name;
|
|
281
|
+
if (entry.isDirectory()) {
|
|
282
|
+
out.push({ path: entryPath, type: "tree" });
|
|
283
|
+
} else if (entry.isFile()) {
|
|
284
|
+
try {
|
|
285
|
+
const st = await stat(join(abs, entry.name));
|
|
286
|
+
out.push({ path: entryPath, type: "blob", size: st.size });
|
|
287
|
+
} catch {
|
|
288
|
+
out.push({ path: entryPath, type: "blob" });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
/** Convert an absolute path under the root into a POSIX repo-relative path. */
|
|
295
|
+
toRepoRelative(absolutePath) {
|
|
296
|
+
return absolutePath.slice(this.rootDir.length + 1).split(sep).join("/");
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Recursively enumerate the root directory into blob/tree entries with
|
|
300
|
+
* POSIX-separated, repo-relative paths (sorted for determinism). Skips
|
|
301
|
+
* dotfiles/dot-directories (except `.github`, which the StructuralProvider
|
|
302
|
+
* reads) in addition to the always-ignored directories — mirrors the old
|
|
303
|
+
* generator's `walkFileSystem` (anokye-labs/kbexplorer-engine#23).
|
|
304
|
+
*/
|
|
305
|
+
async walk() {
|
|
306
|
+
const out = [];
|
|
307
|
+
const stack = [this.rootDir];
|
|
308
|
+
while (stack.length > 0) {
|
|
309
|
+
const dir = stack.pop();
|
|
310
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
311
|
+
for (const entry of entries) {
|
|
312
|
+
if (entry.name.startsWith(".") && entry.name !== ".github") continue;
|
|
313
|
+
if (entry.isDirectory() && this.ignore.has(entry.name)) continue;
|
|
314
|
+
const absolutePath = join(dir, entry.name);
|
|
315
|
+
const repoRelative = this.toRepoRelative(absolutePath);
|
|
316
|
+
if (entry.isDirectory()) {
|
|
317
|
+
out.push({ path: repoRelative, type: "tree" });
|
|
318
|
+
stack.push(absolutePath);
|
|
319
|
+
} else if (entry.isFile()) {
|
|
320
|
+
out.push({ path: repoRelative, type: "blob" });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
325
|
+
return out;
|
|
326
|
+
}
|
|
327
|
+
// ── Read-only resource surface (mirrors ManifestSource) ───────────────────
|
|
328
|
+
async retrieve(query) {
|
|
329
|
+
const kind = query.kind;
|
|
330
|
+
const out = [];
|
|
331
|
+
if (!kind || kind === "file" || kind === "tree") {
|
|
332
|
+
const { tree } = await this.getRepoData();
|
|
333
|
+
for (const item of tree) {
|
|
334
|
+
if (kind === "file" && item.type !== "blob") continue;
|
|
335
|
+
if (kind === "tree" && item.type !== "tree") continue;
|
|
336
|
+
out.push(this.fileResource(item));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
async get(href) {
|
|
342
|
+
const all = await this.retrieve({});
|
|
343
|
+
return all.find((r) => r.href === href);
|
|
344
|
+
}
|
|
345
|
+
fileResource(item) {
|
|
346
|
+
const href = `file://${this.repo}/${item.path}`;
|
|
347
|
+
return {
|
|
348
|
+
href,
|
|
349
|
+
kind: item.type === "tree" ? "tree" : "file",
|
|
350
|
+
affordances: ["read"],
|
|
351
|
+
links: [{ rel: "self", href }],
|
|
352
|
+
body: { path: item.path }
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export { FileSystemSource, ManifestSource };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { KBNode, KBConfig, KBEdge } from '@anokye-labs/kbexplorer-core';
|
|
2
|
+
import { SqlJsStatic, Database } from 'sql.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Source-agnostic input the transform stage needs beyond the provider nodes.
|
|
6
|
+
* The README arrives as raw markdown (or null when the source has none); every
|
|
7
|
+
* other input is derived from the node list itself.
|
|
8
|
+
*/
|
|
9
|
+
interface TransformContext {
|
|
10
|
+
/** Raw README markdown for the source, or null when absent. */
|
|
11
|
+
readme: string | null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A single ordered post-provider transform. `apply` receives the current node
|
|
15
|
+
* list and returns the next one — it may mutate nodes in place (e.g. add
|
|
16
|
+
* connections), append nodes, or replace nodes (e.g. split). Transforms must be
|
|
17
|
+
* pure with respect to ordering: they recompute any subsets they need.
|
|
18
|
+
*/
|
|
19
|
+
interface GraphTransform {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
apply(nodes: KBNode[], ctx: TransformContext): KBNode[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Synthesize the README node and cross-reference it to issues + directories it
|
|
25
|
+
* mentions (by issue ref, fuzzy title match, directory name, and inline links).
|
|
26
|
+
* No-op when the source has no README.
|
|
27
|
+
*/
|
|
28
|
+
declare const readmeTransform: GraphTransform;
|
|
29
|
+
/**
|
|
30
|
+
* Auto-link each issue to directories whose name it references in its body.
|
|
31
|
+
* Runs before {@link issueSplitTransform} so links stay on the original nodes.
|
|
32
|
+
*/
|
|
33
|
+
declare const issueDirectoryLinkTransform: GraphTransform;
|
|
34
|
+
/**
|
|
35
|
+
* Split issues with 2+ headings into a parent + section nodes, replacing the
|
|
36
|
+
* original issue node with its expansion.
|
|
37
|
+
*/
|
|
38
|
+
declare const issueSplitTransform: GraphTransform;
|
|
39
|
+
/**
|
|
40
|
+
* Default ordered post-provider transform stage. Order is significant: README
|
|
41
|
+
* synthesis, then issue→directory linking (before splitting), then issue
|
|
42
|
+
* splitting. Matches the original inline loader sequence exactly.
|
|
43
|
+
*/
|
|
44
|
+
declare const DEFAULT_TRANSFORMS: readonly GraphTransform[];
|
|
45
|
+
/** Run an ordered list of transforms over the node list. */
|
|
46
|
+
declare function applyTransforms(nodes: KBNode[], ctx: TransformContext, transforms?: readonly GraphTransform[]): KBNode[];
|
|
47
|
+
|
|
48
|
+
/** Result produced by a single provider run */
|
|
49
|
+
interface ProviderResult {
|
|
50
|
+
nodes: KBNode[];
|
|
51
|
+
edges: KBEdge[];
|
|
52
|
+
}
|
|
53
|
+
/** A graph provider produces nodes and edges from a data source */
|
|
54
|
+
interface GraphProvider {
|
|
55
|
+
/** Unique provider identifier */
|
|
56
|
+
id: string;
|
|
57
|
+
/** Human-readable name */
|
|
58
|
+
name: string;
|
|
59
|
+
/** Provider IDs this provider depends on (for ordering) */
|
|
60
|
+
dependencies?: string[];
|
|
61
|
+
/**
|
|
62
|
+
* Resolve content from this provider.
|
|
63
|
+
* @param config - the KB config
|
|
64
|
+
* @param existingNodes - nodes from previously-run providers (for cross-referencing)
|
|
65
|
+
*/
|
|
66
|
+
resolve(config: KBConfig, existingNodes: KBNode[]): Promise<ProviderResult>;
|
|
67
|
+
}
|
|
68
|
+
/** Provider registry — ordered collection of providers */
|
|
69
|
+
declare class ProviderRegistry {
|
|
70
|
+
private providers;
|
|
71
|
+
register(provider: GraphProvider): void;
|
|
72
|
+
/** Get providers in dependency-safe execution order */
|
|
73
|
+
getExecutionOrder(): GraphProvider[];
|
|
74
|
+
get(id: string): GraphProvider | undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface SqliteByteStore {
|
|
78
|
+
load(): Promise<Uint8Array | undefined>;
|
|
79
|
+
save(bytes: Uint8Array): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
declare function loadSqlJs(locateFile?: (file: string) => string): Promise<SqlJsStatic>;
|
|
82
|
+
declare function openPersistedDatabase(byteStore?: SqliteByteStore, locateFile?: (file: string) => string): Promise<{
|
|
83
|
+
db: Database;
|
|
84
|
+
persist: () => Promise<void>;
|
|
85
|
+
}>;
|
|
86
|
+
declare class MemorySqliteByteStore implements SqliteByteStore {
|
|
87
|
+
private bytes?;
|
|
88
|
+
load(): Promise<Uint8Array | undefined>;
|
|
89
|
+
save(bytes: Uint8Array): Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
declare class IndexedDbSqliteByteStore implements SqliteByteStore {
|
|
92
|
+
load(): Promise<Uint8Array | undefined>;
|
|
93
|
+
save(bytes: Uint8Array): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { DEFAULT_TRANSFORMS as D, type GraphProvider as G, IndexedDbSqliteByteStore as I, MemorySqliteByteStore as M, type ProviderResult as P, type SqliteByteStore as S, type TransformContext as T, ProviderRegistry as a, type GraphTransform as b, applyTransforms as c, issueSplitTransform as d, issueDirectoryLinkTransform as i, loadSqlJs as l, openPersistedDatabase as o, readmeTransform as r };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { KBNode, KBConfig, KBEdge } from '@anokye-labs/kbexplorer-core';
|
|
2
|
+
import { SqlJsStatic, Database } from 'sql.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Source-agnostic input the transform stage needs beyond the provider nodes.
|
|
6
|
+
* The README arrives as raw markdown (or null when the source has none); every
|
|
7
|
+
* other input is derived from the node list itself.
|
|
8
|
+
*/
|
|
9
|
+
interface TransformContext {
|
|
10
|
+
/** Raw README markdown for the source, or null when absent. */
|
|
11
|
+
readme: string | null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A single ordered post-provider transform. `apply` receives the current node
|
|
15
|
+
* list and returns the next one — it may mutate nodes in place (e.g. add
|
|
16
|
+
* connections), append nodes, or replace nodes (e.g. split). Transforms must be
|
|
17
|
+
* pure with respect to ordering: they recompute any subsets they need.
|
|
18
|
+
*/
|
|
19
|
+
interface GraphTransform {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
apply(nodes: KBNode[], ctx: TransformContext): KBNode[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Synthesize the README node and cross-reference it to issues + directories it
|
|
25
|
+
* mentions (by issue ref, fuzzy title match, directory name, and inline links).
|
|
26
|
+
* No-op when the source has no README.
|
|
27
|
+
*/
|
|
28
|
+
declare const readmeTransform: GraphTransform;
|
|
29
|
+
/**
|
|
30
|
+
* Auto-link each issue to directories whose name it references in its body.
|
|
31
|
+
* Runs before {@link issueSplitTransform} so links stay on the original nodes.
|
|
32
|
+
*/
|
|
33
|
+
declare const issueDirectoryLinkTransform: GraphTransform;
|
|
34
|
+
/**
|
|
35
|
+
* Split issues with 2+ headings into a parent + section nodes, replacing the
|
|
36
|
+
* original issue node with its expansion.
|
|
37
|
+
*/
|
|
38
|
+
declare const issueSplitTransform: GraphTransform;
|
|
39
|
+
/**
|
|
40
|
+
* Default ordered post-provider transform stage. Order is significant: README
|
|
41
|
+
* synthesis, then issue→directory linking (before splitting), then issue
|
|
42
|
+
* splitting. Matches the original inline loader sequence exactly.
|
|
43
|
+
*/
|
|
44
|
+
declare const DEFAULT_TRANSFORMS: readonly GraphTransform[];
|
|
45
|
+
/** Run an ordered list of transforms over the node list. */
|
|
46
|
+
declare function applyTransforms(nodes: KBNode[], ctx: TransformContext, transforms?: readonly GraphTransform[]): KBNode[];
|
|
47
|
+
|
|
48
|
+
/** Result produced by a single provider run */
|
|
49
|
+
interface ProviderResult {
|
|
50
|
+
nodes: KBNode[];
|
|
51
|
+
edges: KBEdge[];
|
|
52
|
+
}
|
|
53
|
+
/** A graph provider produces nodes and edges from a data source */
|
|
54
|
+
interface GraphProvider {
|
|
55
|
+
/** Unique provider identifier */
|
|
56
|
+
id: string;
|
|
57
|
+
/** Human-readable name */
|
|
58
|
+
name: string;
|
|
59
|
+
/** Provider IDs this provider depends on (for ordering) */
|
|
60
|
+
dependencies?: string[];
|
|
61
|
+
/**
|
|
62
|
+
* Resolve content from this provider.
|
|
63
|
+
* @param config - the KB config
|
|
64
|
+
* @param existingNodes - nodes from previously-run providers (for cross-referencing)
|
|
65
|
+
*/
|
|
66
|
+
resolve(config: KBConfig, existingNodes: KBNode[]): Promise<ProviderResult>;
|
|
67
|
+
}
|
|
68
|
+
/** Provider registry — ordered collection of providers */
|
|
69
|
+
declare class ProviderRegistry {
|
|
70
|
+
private providers;
|
|
71
|
+
register(provider: GraphProvider): void;
|
|
72
|
+
/** Get providers in dependency-safe execution order */
|
|
73
|
+
getExecutionOrder(): GraphProvider[];
|
|
74
|
+
get(id: string): GraphProvider | undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface SqliteByteStore {
|
|
78
|
+
load(): Promise<Uint8Array | undefined>;
|
|
79
|
+
save(bytes: Uint8Array): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
declare function loadSqlJs(locateFile?: (file: string) => string): Promise<SqlJsStatic>;
|
|
82
|
+
declare function openPersistedDatabase(byteStore?: SqliteByteStore, locateFile?: (file: string) => string): Promise<{
|
|
83
|
+
db: Database;
|
|
84
|
+
persist: () => Promise<void>;
|
|
85
|
+
}>;
|
|
86
|
+
declare class MemorySqliteByteStore implements SqliteByteStore {
|
|
87
|
+
private bytes?;
|
|
88
|
+
load(): Promise<Uint8Array | undefined>;
|
|
89
|
+
save(bytes: Uint8Array): Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
declare class IndexedDbSqliteByteStore implements SqliteByteStore {
|
|
92
|
+
load(): Promise<Uint8Array | undefined>;
|
|
93
|
+
save(bytes: Uint8Array): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { DEFAULT_TRANSFORMS as D, type GraphProvider as G, IndexedDbSqliteByteStore as I, MemorySqliteByteStore as M, type ProviderResult as P, type SqliteByteStore as S, type TransformContext as T, ProviderRegistry as a, type GraphTransform as b, applyTransforms as c, issueSplitTransform as d, issueDirectoryLinkTransform as i, loadSqlJs as l, openPersistedDatabase as o, readmeTransform as r };
|