@omgbase/core 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/dist/src/cli/eval-matcher.d.ts +13 -0
- package/dist/src/cli/eval-matcher.js +36 -0
- package/dist/src/core/attach.d.ts +5 -0
- package/dist/src/core/attach.js +38 -0
- package/dist/src/core/cursor.d.ts +11 -0
- package/dist/src/core/cursor.js +35 -0
- package/dist/src/core/hash.d.ts +23 -0
- package/dist/src/core/hash.js +82 -0
- package/dist/src/core/ids.d.ts +6 -0
- package/dist/src/core/ids.js +26 -0
- package/dist/src/core/index.d.ts +1 -0
- package/dist/src/core/index.js +2 -0
- package/dist/src/core/ingest.d.ts +43 -0
- package/dist/src/core/ingest.js +287 -0
- package/dist/src/core/order-key.d.ts +8 -0
- package/dist/src/core/order-key.js +72 -0
- package/dist/src/core/parse/parse.d.ts +3 -0
- package/dist/src/core/parse/parse.js +104 -0
- package/dist/src/core/parse/render.d.ts +3 -0
- package/dist/src/core/parse/render.js +20 -0
- package/dist/src/core/parse/tree.d.ts +8 -0
- package/dist/src/core/parse/tree.js +43 -0
- package/dist/src/core/parse/types.d.ts +52 -0
- package/dist/src/core/parse/types.js +9 -0
- package/dist/src/core/read/document.d.ts +107 -0
- package/dist/src/core/read/document.js +191 -0
- package/dist/src/core/read/nodes.d.ts +47 -0
- package/dist/src/core/read/nodes.js +102 -0
- package/dist/src/core/read/outline.d.ts +12 -0
- package/dist/src/core/read/outline.js +76 -0
- package/dist/src/core/read/reader.d.ts +125 -0
- package/dist/src/core/read/reader.js +206 -0
- package/dist/src/core/read/refs.d.ts +15 -0
- package/dist/src/core/read/refs.js +51 -0
- package/dist/src/core/store/edges.d.ts +26 -0
- package/dist/src/core/store/edges.js +101 -0
- package/dist/src/core/store/fts.d.ts +10 -0
- package/dist/src/core/store/fts.js +22 -0
- package/dist/src/core/store/gc.d.ts +18 -0
- package/dist/src/core/store/gc.js +69 -0
- package/dist/src/core/store/nodes.d.ts +5 -0
- package/dist/src/core/store/nodes.js +57 -0
- package/dist/src/core/store/properties.d.ts +38 -0
- package/dist/src/core/store/properties.js +177 -0
- package/dist/src/core/store/rebuild.d.ts +3 -0
- package/dist/src/core/store/rebuild.js +29 -0
- package/dist/src/core/store/schema.d.ts +9 -0
- package/dist/src/core/store/schema.js +386 -0
- package/dist/src/core/store/sections.d.ts +2 -0
- package/dist/src/core/store/sections.js +29 -0
- package/dist/src/core/store/store.d.ts +24 -0
- package/dist/src/core/store/store.js +164 -0
- package/dist/src/core/store/writers.d.ts +52 -0
- package/dist/src/core/store/writers.js +89 -0
- package/dist/src/core/vec.d.ts +10 -0
- package/dist/src/core/vec.js +33 -0
- package/dist/src/format/adapter.d.ts +97 -0
- package/dist/src/format/adapter.js +14 -0
- package/dist/src/format/index.d.ts +6 -0
- package/dist/src/format/index.js +15 -0
- package/dist/src/format/json.d.ts +3 -0
- package/dist/src/format/json.js +324 -0
- package/dist/src/format/markdown.d.ts +5 -0
- package/dist/src/format/markdown.js +217 -0
- package/dist/src/format/registry.d.ts +6 -0
- package/dist/src/format/registry.js +24 -0
- package/dist/src/format/yaml.d.ts +5 -0
- package/dist/src/format/yaml.js +413 -0
- package/dist/src/graph/extract.d.ts +38 -0
- package/dist/src/graph/extract.js +242 -0
- package/dist/src/graph/history.d.ts +103 -0
- package/dist/src/graph/history.js +210 -0
- package/dist/src/graph/inbound-links.d.ts +33 -0
- package/dist/src/graph/inbound-links.js +130 -0
- package/dist/src/graph/link-destinations.d.ts +22 -0
- package/dist/src/graph/link-destinations.js +84 -0
- package/dist/src/graph/link-health.d.ts +79 -0
- package/dist/src/graph/link-health.js +146 -0
- package/dist/src/graph/links.d.ts +23 -0
- package/dist/src/graph/links.js +42 -0
- package/dist/src/index.d.ts +60 -0
- package/dist/src/index.js +70 -0
- package/dist/src/mcp/errors.d.ts +17 -0
- package/dist/src/mcp/errors.js +18 -0
- package/dist/src/mcp/graph.d.ts +63 -0
- package/dist/src/mcp/graph.js +212 -0
- package/dist/src/mcp/reference.d.ts +1 -0
- package/dist/src/mcp/reference.js +227 -0
- package/dist/src/mcp/server.d.ts +30 -0
- package/dist/src/mcp/server.js +1076 -0
- package/dist/src/mcp/stdio.d.ts +13 -0
- package/dist/src/mcp/stdio.js +24 -0
- package/dist/src/migrate/mrplex.d.ts +27 -0
- package/dist/src/migrate/mrplex.js +29 -0
- package/dist/src/mutate/apply.d.ts +83 -0
- package/dist/src/mutate/apply.js +302 -0
- package/dist/src/mutate/doc-store.d.ts +49 -0
- package/dist/src/mutate/doc-store.js +71 -0
- package/dist/src/mutate/docs.d.ts +60 -0
- package/dist/src/mutate/docs.js +232 -0
- package/dist/src/mutate/known-ids.d.ts +12 -0
- package/dist/src/mutate/known-ids.js +154 -0
- package/dist/src/mutate/load.d.ts +3 -0
- package/dist/src/mutate/load.js +66 -0
- package/dist/src/mutate/lower.d.ts +32 -0
- package/dist/src/mutate/lower.js +232 -0
- package/dist/src/mutate/macros.d.ts +91 -0
- package/dist/src/mutate/macros.js +230 -0
- package/dist/src/mutate/ops.d.ts +40 -0
- package/dist/src/mutate/ops.js +513 -0
- package/dist/src/mutate/opset.d.ts +92 -0
- package/dist/src/mutate/opset.js +79 -0
- package/dist/src/mutate/plan-update.d.ts +51 -0
- package/dist/src/mutate/plan-update.js +187 -0
- package/dist/src/mutate/tree.d.ts +46 -0
- package/dist/src/mutate/tree.js +167 -0
- package/dist/src/oqx/run.d.ts +1 -0
- package/dist/src/oqx/run.js +6 -0
- package/dist/src/oqx-js/context.d.ts +23 -0
- package/dist/src/oqx-js/context.js +564 -0
- package/dist/src/oqx-js/planner.d.ts +10 -0
- package/dist/src/oqx-js/planner.js +91 -0
- package/dist/src/oqx-js/run.d.ts +41 -0
- package/dist/src/oqx-js/run.js +332 -0
- package/dist/src/oqx-js/sql/translate.d.ts +27 -0
- package/dist/src/oqx-js/sql/translate.js +290 -0
- package/dist/src/reconcile/crossdoc.d.ts +34 -0
- package/dist/src/reconcile/crossdoc.js +76 -0
- package/dist/src/reconcile/eval/generator.d.ts +21 -0
- package/dist/src/reconcile/eval/generator.js +129 -0
- package/dist/src/reconcile/eval/metrics.d.ts +32 -0
- package/dist/src/reconcile/eval/metrics.js +74 -0
- package/dist/src/reconcile/flatten.d.ts +12 -0
- package/dist/src/reconcile/flatten.js +37 -0
- package/dist/src/reconcile/phase5.d.ts +8 -0
- package/dist/src/reconcile/phase5.js +167 -0
- package/dist/src/reconcile/phase6a.d.ts +2 -0
- package/dist/src/reconcile/phase6a.js +159 -0
- package/dist/src/reconcile/phases.d.ts +28 -0
- package/dist/src/reconcile/phases.js +0 -0
- package/dist/src/reconcile/reconcile.d.ts +21 -0
- package/dist/src/reconcile/reconcile.js +115 -0
- package/dist/src/reconcile/similarity.d.ts +7 -0
- package/dist/src/reconcile/similarity.js +41 -0
- package/dist/src/reconcile/types.d.ts +46 -0
- package/dist/src/reconcile/types.js +19 -0
- package/dist/src/search/cel/ast.d.ts +74 -0
- package/dist/src/search/cel/ast.js +3 -0
- package/dist/src/search/cel/compile.d.ts +60 -0
- package/dist/src/search/cel/compile.js +836 -0
- package/dist/src/search/cel/lexer.d.ts +9 -0
- package/dist/src/search/cel/lexer.js +139 -0
- package/dist/src/search/cel/parser.d.ts +7 -0
- package/dist/src/search/cel/parser.js +219 -0
- package/dist/src/search/drain.d.ts +39 -0
- package/dist/src/search/drain.js +97 -0
- package/dist/src/search/embeddings.d.ts +131 -0
- package/dist/src/search/embeddings.js +263 -0
- package/dist/src/search/external.d.ts +13 -0
- package/dist/src/search/external.js +177 -0
- package/dist/src/search/fts-query.d.ts +1 -0
- package/dist/src/search/fts-query.js +64 -0
- package/dist/src/search/provider.d.ts +17 -0
- package/dist/src/search/provider.js +13 -0
- package/dist/src/search/resolve.d.ts +18 -0
- package/dist/src/search/resolve.js +23 -0
- package/dist/src/search/rrf.d.ts +36 -0
- package/dist/src/search/rrf.js +84 -0
- package/dist/src/search/tasks.d.ts +12 -0
- package/dist/src/search/tasks.js +131 -0
- package/dist/src/search/text.d.ts +16 -0
- package/dist/src/search/text.js +21 -0
- package/dist/src/search/vector.d.ts +18 -0
- package/dist/src/search/vector.js +49 -0
- package/dist/src/sync/admin.d.ts +59 -0
- package/dist/src/sync/admin.js +53 -0
- package/dist/src/sync/attach.d.ts +13 -0
- package/dist/src/sync/attach.js +29 -0
- package/dist/src/sync/checkpoint.d.ts +21 -0
- package/dist/src/sync/checkpoint.js +52 -0
- package/dist/src/sync/driver.d.ts +25 -0
- package/dist/src/sync/driver.js +80 -0
- package/dist/src/sync/external-source.d.ts +14 -0
- package/dist/src/sync/external-source.js +137 -0
- package/dist/src/sync/freshness.d.ts +42 -0
- package/dist/src/sync/freshness.js +152 -0
- package/dist/src/sync/fs-util.d.ts +12 -0
- package/dist/src/sync/fs-util.js +55 -0
- package/dist/src/sync/git-heuristics.d.ts +6 -0
- package/dist/src/sync/git-heuristics.js +16 -0
- package/dist/src/sync/observe.d.ts +63 -0
- package/dist/src/sync/observe.js +84 -0
- package/dist/src/sync/plugin.d.ts +54 -0
- package/dist/src/sync/plugin.js +12 -0
- package/dist/src/sync/reconciling-ingest.d.ts +12 -0
- package/dist/src/sync/reconciling-ingest.js +209 -0
- package/dist/src/sync/recovery.d.ts +6 -0
- package/dist/src/sync/recovery.js +34 -0
- package/dist/src/sync/settings.d.ts +18 -0
- package/dist/src/sync/settings.js +58 -0
- package/dist/src/sync/sources.d.ts +37 -0
- package/dist/src/sync/sources.js +75 -0
- package/dist/src/sync/tombstone.d.ts +9 -0
- package/dist/src/sync/tombstone.js +55 -0
- package/dist/src/sync/watch-lease.d.ts +14 -0
- package/dist/src/sync/watch-lease.js +91 -0
- package/dist/src/sync/watcher.d.ts +19 -0
- package/dist/src/sync/watcher.js +34 -0
- package/dist/src/sync/workspace.d.ts +34 -0
- package/dist/src/sync/workspace.js +120 -0
- package/dist/src/sync/writer-lock.d.ts +16 -0
- package/dist/src/sync/writer-lock.js +121 -0
- package/package.json +80 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const EXTRACTION_VERSION = "x2";
|
|
2
|
+
export type DstKind = "document" | "block" | "external" | "collection";
|
|
3
|
+
export type Provenance = "link" | "frontmatter" | "inline_field";
|
|
4
|
+
export interface ExtractedEdge {
|
|
5
|
+
srcBlock: string | null;
|
|
6
|
+
srcField: string | null;
|
|
7
|
+
predicate: string;
|
|
8
|
+
dstKind: DstKind;
|
|
9
|
+
/** raw target string: a repo path, wikilink name, or URL (store resolves) */
|
|
10
|
+
target: string;
|
|
11
|
+
anchor: string | null;
|
|
12
|
+
provenance: Provenance;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a `./foo` / `../bar` destination against the source document's
|
|
16
|
+
* directory (`docDir` = path with the filename stripped, e.g. "a/b/" or "").
|
|
17
|
+
* Any other destination is returned as-is (root-relative). The ONE resolution
|
|
18
|
+
* rule shared by edge extraction (sync/reconciling-ingest.ts) and docs_move's
|
|
19
|
+
* inbound-link scan (graph/inbound-links.ts) so they agree on what a link
|
|
20
|
+
* points at.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveRelativePath(target: string, docDir: string): string;
|
|
23
|
+
export declare function normalizeUri(uri: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Mask code in a block's raw markdown — fenced code (``` / ~~~, incl. the fence
|
|
26
|
+
* lines, closed by a same-char fence at least as long, or running to the end)
|
|
27
|
+
* and inline code spans (a backtick run of length n closed by the next run of
|
|
28
|
+
* EXACTLY n; an unmatched run stays literal, per CommonMark). Length-preserving:
|
|
29
|
+
* masked characters become spaces, newlines are kept. Container blocks (list,
|
|
30
|
+
* list_item, blockquote) carry their children's raw, so a fence nested in a
|
|
31
|
+
* list item is masked here too. Shared by edge extraction and md:* node
|
|
32
|
+
* projection so nodes and edges agree on what counts as a link.
|
|
33
|
+
*/
|
|
34
|
+
export declare function maskCode(raw: string): string;
|
|
35
|
+
/** Extract edges from a single block's raw markdown. Code is skipped: a
|
|
36
|
+
* `code_fence` block yields nothing, and inline code spans are masked. */
|
|
37
|
+
export declare function extractFromBlock(blockId: string, blockType: string, rawInput: string): ExtractedEdge[];
|
|
38
|
+
export declare function extractFromFrontmatter(fm: Record<string, unknown>): ExtractedEdge[];
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Edge extraction, extraction_version x2 (05 §2). A pure function of
|
|
2
|
+
// (block content, frontmatter). Produces edge descriptors; the store layer
|
|
3
|
+
// resolves targets to node ids and maintains intervals. No identity is
|
|
4
|
+
// inherited here — extraction is recomputed fresh at every revision.
|
|
5
|
+
//
|
|
6
|
+
// x2: code is not prose. `code_fence` blocks yield no edges, and inline code
|
|
7
|
+
// spans (`` `…` ``, any backtick-run length) are masked before scanning, so a
|
|
8
|
+
// backticked `[[wikilink]]` example, a placeholder `[t](/path)` in a fence, or
|
|
9
|
+
// a regex fragment with square brackets no longer mints a `references` edge
|
|
10
|
+
// (and links_stale no longer reports them as dangling). There is no persisted
|
|
11
|
+
// extraction version — an existing repo re-extracts a document the next time
|
|
12
|
+
// that document is ingested (checkpoint/observe/apply), not via rebuild-index.
|
|
13
|
+
export const EXTRACTION_VERSION = "x2";
|
|
14
|
+
// --- link/inline scanners (operate on a block's raw markdown) ----------------
|
|
15
|
+
const MD_LINK = /(!?)\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g; // [t](dest) / 
|
|
16
|
+
const WIKILINK = /(!?)\[\[([^\]]+)\]\]/g; // [[note]] / [[note#H]] / [[note^ref]]
|
|
17
|
+
const BARE_URL = /(?<![("[])\bhttps?:\/\/[^\s)>\]]+/g;
|
|
18
|
+
const AUTOLINK = /<(https?:\/\/[^>]+)>/g;
|
|
19
|
+
const INLINE_FIELD = /(?:^|\s)([a-z][a-z0-9_]*)::\s*(\[\[[^\]]+\]\]|\/[^\s]+|https?:\/\/[^\s]+)/gi;
|
|
20
|
+
function splitAnchor(target) {
|
|
21
|
+
const hashIdx = target.indexOf("#");
|
|
22
|
+
const caretIdx = target.indexOf("^");
|
|
23
|
+
if (caretIdx >= 0 && (hashIdx < 0 || caretIdx < hashIdx)) {
|
|
24
|
+
return { path: target.slice(0, caretIdx), anchor: target.slice(caretIdx + 1), anchorKind: "ref" };
|
|
25
|
+
}
|
|
26
|
+
if (hashIdx >= 0) {
|
|
27
|
+
return { path: target.slice(0, hashIdx), anchor: target.slice(hashIdx + 1), anchorKind: "heading" };
|
|
28
|
+
}
|
|
29
|
+
return { path: target, anchor: null, anchorKind: null };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a `./foo` / `../bar` destination against the source document's
|
|
33
|
+
* directory (`docDir` = path with the filename stripped, e.g. "a/b/" or "").
|
|
34
|
+
* Any other destination is returned as-is (root-relative). The ONE resolution
|
|
35
|
+
* rule shared by edge extraction (sync/reconciling-ingest.ts) and docs_move's
|
|
36
|
+
* inbound-link scan (graph/inbound-links.ts) so they agree on what a link
|
|
37
|
+
* points at.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveRelativePath(target, docDir) {
|
|
40
|
+
if (!target.startsWith("./") && !target.startsWith("../"))
|
|
41
|
+
return target;
|
|
42
|
+
const parts = (docDir + target).split("/");
|
|
43
|
+
const resolved = [];
|
|
44
|
+
for (const p of parts) {
|
|
45
|
+
if (p === "." || p === "")
|
|
46
|
+
continue;
|
|
47
|
+
if (p === "..") {
|
|
48
|
+
resolved.pop();
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
resolved.push(p);
|
|
52
|
+
}
|
|
53
|
+
return resolved.join("/");
|
|
54
|
+
}
|
|
55
|
+
function isExternal(dest) {
|
|
56
|
+
return /^https?:\/\//.test(dest) || /^[a-z][a-z0-9+.-]*:/i.test(dest) === true && !dest.startsWith("/");
|
|
57
|
+
}
|
|
58
|
+
function classifyTarget(dest) {
|
|
59
|
+
if (/^https?:\/\//.test(dest))
|
|
60
|
+
return { dstKind: "external", target: normalizeUri(dest), anchor: null };
|
|
61
|
+
const { path, anchor, anchorKind } = splitAnchor(dest);
|
|
62
|
+
// A pure fragment link (#H or ^ref) with empty path targets the same doc; we
|
|
63
|
+
// still emit a document edge with the anchor (store resolves to self).
|
|
64
|
+
const dstKind = anchorKind === "ref" ? "block" : "document";
|
|
65
|
+
return { dstKind, target: path, anchor };
|
|
66
|
+
}
|
|
67
|
+
export function normalizeUri(uri) {
|
|
68
|
+
try {
|
|
69
|
+
const u = new URL(uri);
|
|
70
|
+
u.hash = "";
|
|
71
|
+
u.protocol = u.protocol.toLowerCase();
|
|
72
|
+
u.hostname = u.hostname.toLowerCase();
|
|
73
|
+
if ((u.protocol === "http:" && u.port === "80") || (u.protocol === "https:" && u.port === "443"))
|
|
74
|
+
u.port = "";
|
|
75
|
+
return u.toString().replace(/\/$/, "");
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return uri;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// --- code masking -------------------------------------------------------------
|
|
82
|
+
const FENCE_OPEN = /^[ \t]*(`{3,}|~{3,})/;
|
|
83
|
+
const FENCE_CLOSE = /^[ \t]*(`{3,}|~{3,})[ \t]*$/;
|
|
84
|
+
// Blank out every non-newline character of `s` so offsets are preserved (node
|
|
85
|
+
// projection records spans into the ORIGINAL raw; a length-preserving mask keeps
|
|
86
|
+
// those spans valid).
|
|
87
|
+
function blank(s) {
|
|
88
|
+
return s.replace(/[^\n]/g, " ");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Mask code in a block's raw markdown — fenced code (``` / ~~~, incl. the fence
|
|
92
|
+
* lines, closed by a same-char fence at least as long, or running to the end)
|
|
93
|
+
* and inline code spans (a backtick run of length n closed by the next run of
|
|
94
|
+
* EXACTLY n; an unmatched run stays literal, per CommonMark). Length-preserving:
|
|
95
|
+
* masked characters become spaces, newlines are kept. Container blocks (list,
|
|
96
|
+
* list_item, blockquote) carry their children's raw, so a fence nested in a
|
|
97
|
+
* list item is masked here too. Shared by edge extraction and md:* node
|
|
98
|
+
* projection so nodes and edges agree on what counts as a link.
|
|
99
|
+
*/
|
|
100
|
+
export function maskCode(raw) {
|
|
101
|
+
if (!raw.includes("`") && !raw.includes("~~~"))
|
|
102
|
+
return raw;
|
|
103
|
+
// 1. Fenced code blocks, line by line.
|
|
104
|
+
const lines = raw.split("\n");
|
|
105
|
+
let open = null;
|
|
106
|
+
for (let i = 0; i < lines.length; i++) {
|
|
107
|
+
const line = lines[i];
|
|
108
|
+
if (open) {
|
|
109
|
+
const m = FENCE_CLOSE.exec(line);
|
|
110
|
+
lines[i] = blank(line);
|
|
111
|
+
if (m && m[1][0] === open.ch && m[1].length >= open.len)
|
|
112
|
+
open = null;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const m = FENCE_OPEN.exec(line);
|
|
116
|
+
if (m) {
|
|
117
|
+
const fence = m[1];
|
|
118
|
+
// A backtick fence's info string may not contain a backtick.
|
|
119
|
+
if (fence[0] === "`" && line.slice(m[0].length).includes("`"))
|
|
120
|
+
continue;
|
|
121
|
+
open = { ch: fence[0], len: fence.length };
|
|
122
|
+
lines[i] = blank(line);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
let out = lines.join("\n");
|
|
126
|
+
// 2. Inline code spans over what remains.
|
|
127
|
+
let i = 0;
|
|
128
|
+
while (i < out.length) {
|
|
129
|
+
if (out[i] !== "`") {
|
|
130
|
+
i++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
let j = i;
|
|
134
|
+
while (j < out.length && out[j] === "`")
|
|
135
|
+
j++;
|
|
136
|
+
const n = j - i;
|
|
137
|
+
// Find the next backtick run of exactly n.
|
|
138
|
+
let k = j;
|
|
139
|
+
let close = -1;
|
|
140
|
+
while (k < out.length) {
|
|
141
|
+
if (out[k] !== "`") {
|
|
142
|
+
k++;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
let e = k;
|
|
146
|
+
while (e < out.length && out[e] === "`")
|
|
147
|
+
e++;
|
|
148
|
+
if (e - k === n) {
|
|
149
|
+
close = k;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
k = e;
|
|
153
|
+
}
|
|
154
|
+
if (close < 0) {
|
|
155
|
+
i = j;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
out = out.slice(0, i) + blank(out.slice(i, close + n)) + out.slice(close + n);
|
|
159
|
+
i = close + n;
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
/** Extract edges from a single block's raw markdown. Code is skipped: a
|
|
164
|
+
* `code_fence` block yields nothing, and inline code spans are masked. */
|
|
165
|
+
export function extractFromBlock(blockId, blockType, rawInput) {
|
|
166
|
+
const edges = [];
|
|
167
|
+
if (blockType === "code_fence")
|
|
168
|
+
return edges;
|
|
169
|
+
const raw = maskCode(rawInput);
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
const push = (e) => {
|
|
172
|
+
const k = `${e.predicate}|${e.target}|${e.anchor ?? ""}|${e.srcField ?? ""}`;
|
|
173
|
+
if (seen.has(k))
|
|
174
|
+
return;
|
|
175
|
+
seen.add(k);
|
|
176
|
+
edges.push(e);
|
|
177
|
+
};
|
|
178
|
+
// Inline fields first (so their targets aren't double-counted as plain links).
|
|
179
|
+
const inlineTargets = new Set();
|
|
180
|
+
for (const m of raw.matchAll(INLINE_FIELD)) {
|
|
181
|
+
const key = m[1].toLowerCase();
|
|
182
|
+
const rawTarget = m[2];
|
|
183
|
+
inlineTargets.add(rawTarget);
|
|
184
|
+
const wl = /^\[\[([^\]]+)\]\]$/.exec(rawTarget);
|
|
185
|
+
const dest = wl ? wl[1] : rawTarget;
|
|
186
|
+
const c = classifyTarget(dest);
|
|
187
|
+
push({ srcBlock: blockId, srcField: key, predicate: key, dstKind: c.dstKind, target: c.target, anchor: c.anchor, provenance: "inline_field" });
|
|
188
|
+
}
|
|
189
|
+
// Markdown links and images.
|
|
190
|
+
for (const m of raw.matchAll(MD_LINK)) {
|
|
191
|
+
const isImg = m[1] === "!";
|
|
192
|
+
const dest = m[2];
|
|
193
|
+
if (inlineTargets.has(dest))
|
|
194
|
+
continue;
|
|
195
|
+
const c = classifyTarget(dest);
|
|
196
|
+
push({ srcBlock: blockId, srcField: null, predicate: isImg ? "embeds" : "references", dstKind: c.dstKind, target: c.target, anchor: c.anchor, provenance: "link" });
|
|
197
|
+
}
|
|
198
|
+
// Wikilinks.
|
|
199
|
+
for (const m of raw.matchAll(WIKILINK)) {
|
|
200
|
+
const isImg = m[1] === "!";
|
|
201
|
+
const inner = m[2];
|
|
202
|
+
if (inlineTargets.has(`[[${inner}]]`))
|
|
203
|
+
continue;
|
|
204
|
+
const c = classifyTarget(inner);
|
|
205
|
+
push({ srcBlock: blockId, srcField: null, predicate: isImg ? "embeds" : "references", dstKind: c.dstKind, target: c.target, anchor: c.anchor, provenance: "link" });
|
|
206
|
+
}
|
|
207
|
+
// Autolinks + bare URLs → external.
|
|
208
|
+
for (const m of raw.matchAll(AUTOLINK))
|
|
209
|
+
push({ srcBlock: blockId, srcField: null, predicate: "references", dstKind: "external", target: normalizeUri(m[1]), anchor: null, provenance: "link" });
|
|
210
|
+
for (const m of raw.matchAll(BARE_URL))
|
|
211
|
+
push({ srcBlock: blockId, srcField: null, predicate: "references", dstKind: "external", target: normalizeUri(m[0]), anchor: null, provenance: "link" });
|
|
212
|
+
void isExternal;
|
|
213
|
+
return edges;
|
|
214
|
+
}
|
|
215
|
+
// --- frontmatter relation fields --------------------------------------------
|
|
216
|
+
// Repo-path or wikilink values under a frontmatter key → doc-grain edges.
|
|
217
|
+
export function extractFromFrontmatter(fm) {
|
|
218
|
+
const edges = [];
|
|
219
|
+
const consider = (key, value) => {
|
|
220
|
+
if (typeof value !== "string")
|
|
221
|
+
return;
|
|
222
|
+
let dest = null;
|
|
223
|
+
const wl = /^\[\[([^\]]+)\]\]$/.exec(value);
|
|
224
|
+
if (wl)
|
|
225
|
+
dest = wl[1];
|
|
226
|
+
else if (value.startsWith("/"))
|
|
227
|
+
dest = value;
|
|
228
|
+
if (!dest)
|
|
229
|
+
return;
|
|
230
|
+
const c = classifyTarget(dest);
|
|
231
|
+
edges.push({ srcBlock: null, srcField: key, predicate: key, dstKind: c.dstKind, target: c.target, anchor: c.anchor, provenance: "frontmatter" });
|
|
232
|
+
};
|
|
233
|
+
for (const [key, value] of Object.entries(fm)) {
|
|
234
|
+
if (Array.isArray(value))
|
|
235
|
+
for (const v of value)
|
|
236
|
+
consider(key, v);
|
|
237
|
+
else
|
|
238
|
+
consider(key, value);
|
|
239
|
+
}
|
|
240
|
+
return edges;
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Store } from "../core/store/store.js";
|
|
2
|
+
export interface NodeChange {
|
|
3
|
+
commitId: string;
|
|
4
|
+
seq: number;
|
|
5
|
+
ts: string;
|
|
6
|
+
origin: string;
|
|
7
|
+
kind: string;
|
|
8
|
+
confidence: number | null;
|
|
9
|
+
reason: string | null;
|
|
10
|
+
}
|
|
11
|
+
/** history_node: a block's biography from block_changes + commits (06 §3). */
|
|
12
|
+
export declare function historyNode(store: Store, blockId: string, opts?: {
|
|
13
|
+
limit?: number;
|
|
14
|
+
}): NodeChange[];
|
|
15
|
+
export interface DiffEntry {
|
|
16
|
+
kind: "added" | "removed" | "changed" | "unchanged";
|
|
17
|
+
blockId: string;
|
|
18
|
+
before?: string;
|
|
19
|
+
after?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function diffBlocks(store: Store, docId: string, fromRev: string, toRev: string): DiffEntry[];
|
|
22
|
+
/** Unified textual diff (line-based) between two revisions' rendered files. */
|
|
23
|
+
export declare function diffUnified(store: Store, docId: string, fromRev: string, toRev: string): string;
|
|
24
|
+
export interface CommitDigest {
|
|
25
|
+
commit: string;
|
|
26
|
+
seq: number;
|
|
27
|
+
ts: string;
|
|
28
|
+
origin: string;
|
|
29
|
+
actor: string | null;
|
|
30
|
+
summary: string;
|
|
31
|
+
/** `contentHash` (hex of the revision's rendered_hash) lets a puller decide
|
|
32
|
+
* "changed vs echo" without a follow-up docs_read/docs_history (ADR-014 §4.2). */
|
|
33
|
+
revisions: {
|
|
34
|
+
doc: string;
|
|
35
|
+
path: string;
|
|
36
|
+
contentHash: string;
|
|
37
|
+
}[];
|
|
38
|
+
}
|
|
39
|
+
/** changes_since: commit digests after a cursor (repo commit seq) — the change
|
|
40
|
+
* feed (06 §3). `seq` is a dense per-REPO total order, so a cursor is only
|
|
41
|
+
* meaningful against the repo it came from; `head` (the repo's current max seq)
|
|
42
|
+
* lets a caller tell "no new changes" (cursor == head) from "cursor is beyond
|
|
43
|
+
* this repo's feed" (cursor > head — e.g. a cursor from another repo/server),
|
|
44
|
+
* which otherwise both look like an empty page. */
|
|
45
|
+
export declare function changesSince(store: Store, repoId: string, opts?: {
|
|
46
|
+
cursor?: number;
|
|
47
|
+
limit?: number;
|
|
48
|
+
origin?: string;
|
|
49
|
+
}): {
|
|
50
|
+
digests: CommitDigest[];
|
|
51
|
+
cursor: number;
|
|
52
|
+
truncated: boolean;
|
|
53
|
+
head: number;
|
|
54
|
+
};
|
|
55
|
+
/** One stored version (revision) of a document. */
|
|
56
|
+
export interface DocVersion {
|
|
57
|
+
rev: string;
|
|
58
|
+
seq: number;
|
|
59
|
+
commit: string;
|
|
60
|
+
ts: string;
|
|
61
|
+
origin: string;
|
|
62
|
+
actor: string | null;
|
|
63
|
+
/** hex of the revision's rendered_hash — spot no-op vs real change, feed into docs_read_at/diff. */
|
|
64
|
+
contentHash: string;
|
|
65
|
+
/** true when this rev == the doc's current_rev. */
|
|
66
|
+
isCurrent: boolean;
|
|
67
|
+
}
|
|
68
|
+
/** A matching document with its ordered version history. */
|
|
69
|
+
export interface DocVersions {
|
|
70
|
+
docId: string;
|
|
71
|
+
path: string;
|
|
72
|
+
deleted: boolean;
|
|
73
|
+
currentRev: string | null;
|
|
74
|
+
versions: DocVersion[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* docHistory: the version history of matching documents, grouped by document.
|
|
78
|
+
*
|
|
79
|
+
* - `doc` (id OR path): list that single document's versions. Resolves via
|
|
80
|
+
* findDoc for non-deleted docs; falls back to a direct docs lookup (by id or
|
|
81
|
+
* repo+path) so `includeDeleted` can surface a tombstoned doc's history too.
|
|
82
|
+
* - `pathGlob`: match docs.path with the SAME LIKE conversion as within()
|
|
83
|
+
* (compileWithin): escape %/_ then map `*`→SQL `%`. So `journal/*` and
|
|
84
|
+
* `journal/**` both become `journal/%`; `*` matches ACROSS `/` (there is no
|
|
85
|
+
* distinct single-segment wildcard in this dialect), consistent with within().
|
|
86
|
+
* - By default only NON-deleted docs (deleted_commit IS NULL). includeDeleted:
|
|
87
|
+
* true also returns tombstoned docs, whose past versions remain valuable for
|
|
88
|
+
* audit (a deleted journal file's history is intact).
|
|
89
|
+
*
|
|
90
|
+
* Docs are ordered by path (ascending). Versions within a doc are ordered by
|
|
91
|
+
* seq ASCENDING (chronological — reads as a timeline). `limit` (default 50)
|
|
92
|
+
* caps the number of DOCUMENTS returned (not revisions); `truncated` is set
|
|
93
|
+
* when more matching docs existed.
|
|
94
|
+
*/
|
|
95
|
+
export declare function docHistory(store: Store, repoId: string, opts?: {
|
|
96
|
+
pathGlob?: string;
|
|
97
|
+
doc?: string;
|
|
98
|
+
includeDeleted?: boolean;
|
|
99
|
+
limit?: number;
|
|
100
|
+
}): {
|
|
101
|
+
docs: DocVersions[];
|
|
102
|
+
truncated: boolean;
|
|
103
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { findDocByRef } from "../core/read/reader.js";
|
|
2
|
+
import { isValidId } from "../core/ids.js";
|
|
3
|
+
/** history_node: a block's biography from block_changes + commits (06 §3). */
|
|
4
|
+
export function historyNode(store, blockId, opts = {}) {
|
|
5
|
+
const limit = opts.limit ?? 100;
|
|
6
|
+
return store.db
|
|
7
|
+
.prepare(`SELECT bc.commit_id AS commitId, c.seq AS seq, c.ts AS ts, c.origin AS origin, bc.kind AS kind,
|
|
8
|
+
d.confidence AS confidence, d.reason AS reason
|
|
9
|
+
FROM block_changes bc
|
|
10
|
+
JOIN commits c ON c.commit_id = bc.commit_id
|
|
11
|
+
LEFT JOIN dispositions d ON d.commit_id = bc.commit_id AND d.block_id = bc.block_id AND d.kind = bc.kind
|
|
12
|
+
WHERE bc.block_id = ?
|
|
13
|
+
ORDER BY c.seq DESC
|
|
14
|
+
LIMIT ?`)
|
|
15
|
+
.all(blockId, limit);
|
|
16
|
+
}
|
|
17
|
+
// Block-grain diff between two revisions of a document: compare block id → raw.
|
|
18
|
+
export function diffBlocks(store, docId, fromRev, toRev) {
|
|
19
|
+
const at = (rev) => blocksAtRevision(store, docId, rev);
|
|
20
|
+
const before = at(fromRev);
|
|
21
|
+
const after = at(toRev);
|
|
22
|
+
const entries = [];
|
|
23
|
+
for (const [id, raw] of before) {
|
|
24
|
+
if (!after.has(id))
|
|
25
|
+
entries.push({ kind: "removed", blockId: id, before: raw });
|
|
26
|
+
else if (after.get(id) !== raw)
|
|
27
|
+
entries.push({ kind: "changed", blockId: id, before: raw, after: after.get(id) });
|
|
28
|
+
}
|
|
29
|
+
for (const [id, raw] of after)
|
|
30
|
+
if (!before.has(id))
|
|
31
|
+
entries.push({ kind: "added", blockId: id, after: raw });
|
|
32
|
+
return entries;
|
|
33
|
+
}
|
|
34
|
+
// Reconstruct the (block id → raw) map for a document at a revision by walking
|
|
35
|
+
// its Merkle tree from the revision's root_tree. Recurses into child subtrees so
|
|
36
|
+
// the map spans every block (roots + nested), which block-grain diff compares.
|
|
37
|
+
// NOTE: the persisted tree-node entry is a six-field canonical tuple (see
|
|
38
|
+
// core/hash.ts serializeTreeEntries): [blockId, rawHashHex, childTreeHashHex,
|
|
39
|
+
// type, attrs, triviaHashHex]. Diff needs only the first three; whole-document
|
|
40
|
+
// byte-faithful reconstruction (which also needs trivia + top-level order) lives
|
|
41
|
+
// in core/read/document.ts readDocumentAtRevision.
|
|
42
|
+
function blocksAtRevision(store, docId, revId) {
|
|
43
|
+
const rev = store.db.prepare("SELECT root_tree FROM revisions WHERE rev_id = ? AND doc_id = ?").get(revId, docId);
|
|
44
|
+
const out = new Map();
|
|
45
|
+
if (!rev)
|
|
46
|
+
return out;
|
|
47
|
+
const blob = store.db.prepare("SELECT bytes FROM blobs WHERE hash = ?");
|
|
48
|
+
const treeNode = store.db.prepare("SELECT entries FROM tree_nodes WHERE hash = ?");
|
|
49
|
+
const walk = (treeHashHex) => {
|
|
50
|
+
const node = treeNode.get(Buffer.from(treeHashHex, "hex"));
|
|
51
|
+
if (!node)
|
|
52
|
+
return;
|
|
53
|
+
const entries = JSON.parse(node.entries);
|
|
54
|
+
for (const [blockId, rawHashHex, childHashHex] of entries) {
|
|
55
|
+
const raw = blob.get(Buffer.from(rawHashHex, "hex"))?.bytes.toString("utf8") ?? "";
|
|
56
|
+
out.set(blockId, raw);
|
|
57
|
+
if (childHashHex)
|
|
58
|
+
walk(childHashHex);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
walk(rev.root_tree.toString("hex"));
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
/** Unified textual diff (line-based) between two revisions' rendered files. */
|
|
65
|
+
export function diffUnified(store, docId, fromRev, toRev) {
|
|
66
|
+
const rendered = (rev) => {
|
|
67
|
+
const map = blocksAtRevision(store, docId, rev);
|
|
68
|
+
return [...map.values()].join("\n");
|
|
69
|
+
};
|
|
70
|
+
const a = rendered(fromRev).split("\n");
|
|
71
|
+
const b = rendered(toRev).split("\n");
|
|
72
|
+
// Minimal line diff (not a full Myers; sufficient for digests/preview).
|
|
73
|
+
const out = [];
|
|
74
|
+
const max = Math.max(a.length, b.length);
|
|
75
|
+
for (let i = 0; i < max; i++) {
|
|
76
|
+
if (a[i] === b[i])
|
|
77
|
+
continue;
|
|
78
|
+
if (a[i] !== undefined)
|
|
79
|
+
out.push(`- ${a[i]}`);
|
|
80
|
+
if (b[i] !== undefined)
|
|
81
|
+
out.push(`+ ${b[i]}`);
|
|
82
|
+
}
|
|
83
|
+
return out.join("\n");
|
|
84
|
+
}
|
|
85
|
+
/** changes_since: commit digests after a cursor (repo commit seq) — the change
|
|
86
|
+
* feed (06 §3). `seq` is a dense per-REPO total order, so a cursor is only
|
|
87
|
+
* meaningful against the repo it came from; `head` (the repo's current max seq)
|
|
88
|
+
* lets a caller tell "no new changes" (cursor == head) from "cursor is beyond
|
|
89
|
+
* this repo's feed" (cursor > head — e.g. a cursor from another repo/server),
|
|
90
|
+
* which otherwise both look like an empty page. */
|
|
91
|
+
export function changesSince(store, repoId, opts = {}) {
|
|
92
|
+
const cursor = opts.cursor ?? 0;
|
|
93
|
+
const head = store.db.prepare("SELECT COALESCE(MAX(seq), 0) AS head FROM commits WHERE repo_id = ?").get(repoId).head;
|
|
94
|
+
const limit = opts.limit ?? 50;
|
|
95
|
+
const originClause = opts.origin ? "AND origin = ?" : "";
|
|
96
|
+
const params = [repoId, cursor];
|
|
97
|
+
if (opts.origin)
|
|
98
|
+
params.push(opts.origin);
|
|
99
|
+
params.push(limit + 1);
|
|
100
|
+
const commits = store.db
|
|
101
|
+
.prepare(`SELECT commit_id, seq, ts, origin, actor FROM commits WHERE repo_id = ? AND seq > ? ${originClause} ORDER BY seq LIMIT ?`)
|
|
102
|
+
.all(...params);
|
|
103
|
+
const truncated = commits.length > limit;
|
|
104
|
+
const page = commits.slice(0, limit);
|
|
105
|
+
const digests = page.map((c) => {
|
|
106
|
+
const revs = store.db.prepare("SELECT r.doc_id AS doc, r.path AS path, r.rendered_hash AS rendered_hash FROM revisions r WHERE r.commit_id = ?").all(c.commit_id).map((r) => ({ doc: r.doc, path: r.path, contentHash: r.rendered_hash.toString("hex") }));
|
|
107
|
+
const dispCounts = store.db.prepare("SELECT kind, count(*) n FROM dispositions WHERE commit_id = ? GROUP BY kind").all(c.commit_id);
|
|
108
|
+
const summary = renderSummary(c.origin, c.actor, revs, dispCounts);
|
|
109
|
+
return { commit: c.commit_id, seq: c.seq, ts: c.ts, origin: c.origin, actor: c.actor, summary, revisions: revs };
|
|
110
|
+
});
|
|
111
|
+
const nextCursor = page.length > 0 ? page[page.length - 1].seq : cursor;
|
|
112
|
+
return { digests, cursor: nextCursor, truncated, head };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* docHistory: the version history of matching documents, grouped by document.
|
|
116
|
+
*
|
|
117
|
+
* - `doc` (id OR path): list that single document's versions. Resolves via
|
|
118
|
+
* findDoc for non-deleted docs; falls back to a direct docs lookup (by id or
|
|
119
|
+
* repo+path) so `includeDeleted` can surface a tombstoned doc's history too.
|
|
120
|
+
* - `pathGlob`: match docs.path with the SAME LIKE conversion as within()
|
|
121
|
+
* (compileWithin): escape %/_ then map `*`→SQL `%`. So `journal/*` and
|
|
122
|
+
* `journal/**` both become `journal/%`; `*` matches ACROSS `/` (there is no
|
|
123
|
+
* distinct single-segment wildcard in this dialect), consistent with within().
|
|
124
|
+
* - By default only NON-deleted docs (deleted_commit IS NULL). includeDeleted:
|
|
125
|
+
* true also returns tombstoned docs, whose past versions remain valuable for
|
|
126
|
+
* audit (a deleted journal file's history is intact).
|
|
127
|
+
*
|
|
128
|
+
* Docs are ordered by path (ascending). Versions within a doc are ordered by
|
|
129
|
+
* seq ASCENDING (chronological — reads as a timeline). `limit` (default 50)
|
|
130
|
+
* caps the number of DOCUMENTS returned (not revisions); `truncated` is set
|
|
131
|
+
* when more matching docs existed.
|
|
132
|
+
*/
|
|
133
|
+
export function docHistory(store, repoId, opts = {}) {
|
|
134
|
+
const limit = opts.limit ?? 50;
|
|
135
|
+
const includeDeleted = opts.includeDeleted ?? false;
|
|
136
|
+
let docRows;
|
|
137
|
+
if (opts.doc) {
|
|
138
|
+
const row = resolveDocRow(store, repoId, opts.doc, includeDeleted);
|
|
139
|
+
docRows = row ? [row] : [];
|
|
140
|
+
}
|
|
141
|
+
else if (opts.pathGlob) {
|
|
142
|
+
const deletedClause = includeDeleted ? "" : "AND deleted_commit IS NULL";
|
|
143
|
+
const params = [repoId];
|
|
144
|
+
let pathClause;
|
|
145
|
+
if (opts.pathGlob.includes("*")) {
|
|
146
|
+
const like = opts.pathGlob.replace(/[%_]/g, "\\$&").replace(/\*/g, "%");
|
|
147
|
+
pathClause = "path LIKE ? ESCAPE '\\'";
|
|
148
|
+
params.push(like);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
pathClause = "path = ?";
|
|
152
|
+
params.push(opts.pathGlob);
|
|
153
|
+
}
|
|
154
|
+
params.push(limit + 1);
|
|
155
|
+
docRows = store.db
|
|
156
|
+
.prepare(`SELECT doc_id, path, current_rev, deleted_commit FROM docs WHERE repo_id = ? AND ${pathClause} ${deletedClause} ORDER BY path LIMIT ?`)
|
|
157
|
+
.all(...params);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
throw new Error("docHistory requires one of { doc, pathGlob }");
|
|
161
|
+
}
|
|
162
|
+
const truncated = docRows.length > limit;
|
|
163
|
+
const page = docRows.slice(0, limit);
|
|
164
|
+
const revStmt = store.db.prepare(`SELECT r.rev_id AS rev, r.seq AS seq, r.commit_id AS commitId, r.rendered_hash AS rendered_hash,
|
|
165
|
+
c.ts AS ts, c.origin AS origin, c.actor AS actor
|
|
166
|
+
FROM revisions r JOIN commits c ON c.commit_id = r.commit_id
|
|
167
|
+
WHERE r.doc_id = ? ORDER BY r.seq ASC`);
|
|
168
|
+
const docs = page.map((d) => {
|
|
169
|
+
const rows = revStmt.all(d.doc_id);
|
|
170
|
+
const versions = rows.map((r) => ({
|
|
171
|
+
rev: r.rev,
|
|
172
|
+
seq: r.seq,
|
|
173
|
+
commit: r.commitId,
|
|
174
|
+
ts: r.ts,
|
|
175
|
+
origin: r.origin,
|
|
176
|
+
actor: r.actor,
|
|
177
|
+
contentHash: r.rendered_hash.toString("hex"),
|
|
178
|
+
isCurrent: r.rev === d.current_rev,
|
|
179
|
+
}));
|
|
180
|
+
return { docId: d.doc_id, path: d.path, deleted: d.deleted_commit !== null, currentRev: d.current_rev, versions };
|
|
181
|
+
});
|
|
182
|
+
return { docs, truncated };
|
|
183
|
+
}
|
|
184
|
+
// Resolve a `doc` ref (id or path) to a docs row. Prefers findDoc (live docs);
|
|
185
|
+
// when includeDeleted is set and findDoc misses (a tombstoned doc), falls back
|
|
186
|
+
// to a direct lookup that ignores the deleted_commit filter.
|
|
187
|
+
function resolveDocRow(store, repoId, ref, includeDeleted) {
|
|
188
|
+
const info = findDocByRef(store, repoId, ref);
|
|
189
|
+
if (info) {
|
|
190
|
+
return store.db.prepare("SELECT doc_id, path, current_rev, deleted_commit FROM docs WHERE doc_id = ?").get(info.docId);
|
|
191
|
+
}
|
|
192
|
+
if (!includeDeleted)
|
|
193
|
+
return undefined;
|
|
194
|
+
// findDocByRef filters deleted docs; for includeDeleted, look through the
|
|
195
|
+
// tombstone using the SAME id-or-path dispatch (isValidId, not a "d_" prefix).
|
|
196
|
+
const asId = isValidId(ref, "d");
|
|
197
|
+
const stmt = asId
|
|
198
|
+
? store.db.prepare("SELECT doc_id, path, current_rev, deleted_commit FROM docs WHERE doc_id = ?")
|
|
199
|
+
: store.db.prepare("SELECT doc_id, path, current_rev, deleted_commit FROM docs WHERE repo_id = ? AND path = ?");
|
|
200
|
+
return (asId ? stmt.get(ref) : stmt.get(repoId, ref));
|
|
201
|
+
}
|
|
202
|
+
function renderSummary(origin, actor, revs, disp) {
|
|
203
|
+
const paths = revs.map((r) => r.path).join(", ");
|
|
204
|
+
const parts = disp.filter((d) => d.kind !== "same").map((d) => `${d.n} ${d.kind}`);
|
|
205
|
+
const detail = parts.length > 0 ? ` — ${parts.join(", ")}` : "";
|
|
206
|
+
if (origin === "api" || origin === "import")
|
|
207
|
+
return `${origin}(${actor ?? "?"}): ${paths}${detail}`;
|
|
208
|
+
return `observed: ${paths}${detail}`;
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Store } from "../core/store/store.js";
|
|
2
|
+
export interface InboundLink {
|
|
3
|
+
/** source document id */
|
|
4
|
+
doc: string;
|
|
5
|
+
/** source document repo-relative path */
|
|
6
|
+
path: string;
|
|
7
|
+
/** source block id; null for a frontmatter-level edge */
|
|
8
|
+
block: string | null;
|
|
9
|
+
/** the link's destination path as written (e.g. "/old.md", "../old.md", "old.md") */
|
|
10
|
+
target: string;
|
|
11
|
+
/** #heading / ^ref fragment, if any */
|
|
12
|
+
anchor: string | null;
|
|
13
|
+
/** frontmatter key or inline-field key for typed edges */
|
|
14
|
+
field?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Every link occurrence in the repo whose destination resolves to `docPath`,
|
|
18
|
+
* found via the open edges that point at `docId`. Block-level edges are
|
|
19
|
+
* re-scanned from the block raw so `target`/`anchor` are as written (and so a
|
|
20
|
+
* self-doc pure-fragment link `#H`, which also has dst_node == docId, is NOT
|
|
21
|
+
* reported — it does not depend on the path). Frontmatter edges are reported
|
|
22
|
+
* with `block: null`, `target: docPath`, and their `field`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function inboundLinksTo(store: Store, repoId: string, docId: string, docPath: string): InboundLink[];
|
|
25
|
+
/**
|
|
26
|
+
* Rewrite every link destination in `raw` that resolves (relative to `matchDir`,
|
|
27
|
+
* the source doc's directory at extraction time) to `fromPath` so it points at
|
|
28
|
+
* `toPath`, writing relative forms against `writeDir` (the source doc's current
|
|
29
|
+
* directory — differs from `matchDir` only for links inside the moved doc
|
|
30
|
+
* itself). Anchors, link text, titles, and code spans are preserved. Returns
|
|
31
|
+
* null when nothing changed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function retargetLinksInRaw(raw: string, matchDir: string, writeDir: string, fromPath: string, toPath: string): string | null;
|