@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,13 @@
|
|
|
1
|
+
import { type ServerContext } from "./server.js";
|
|
2
|
+
export interface ServeStdioHandle {
|
|
3
|
+
/** Resolves when the transport closes. */
|
|
4
|
+
closed: Promise<void>;
|
|
5
|
+
/** Close the server + transport (idempotent). */
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Build the MCP server for `ctx` and connect it to stdio. Returns a handle
|
|
10
|
+
* whose `closed` promise settles when the client disconnects. stdout is the
|
|
11
|
+
* protocol channel — callers MUST NOT write anything else to it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function serveStdio(ctx: ServerContext): Promise<ServeStdioHandle>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import { buildServer } from "./server.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build the MCP server for `ctx` and connect it to stdio. Returns a handle
|
|
5
|
+
* whose `closed` promise settles when the client disconnects. stdout is the
|
|
6
|
+
* protocol channel — callers MUST NOT write anything else to it.
|
|
7
|
+
*/
|
|
8
|
+
export async function serveStdio(ctx) {
|
|
9
|
+
const server = buildServer(ctx);
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
let resolveClosed;
|
|
12
|
+
const closed = new Promise((res) => {
|
|
13
|
+
resolveClosed = res;
|
|
14
|
+
});
|
|
15
|
+
transport.onclose = () => resolveClosed();
|
|
16
|
+
await server.connect(transport);
|
|
17
|
+
return {
|
|
18
|
+
closed,
|
|
19
|
+
async close() {
|
|
20
|
+
await server.close();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Store } from "../core/store/store.js";
|
|
2
|
+
export interface MrplexDoc {
|
|
3
|
+
/** repo-relative path (no leading slash) */
|
|
4
|
+
path: string;
|
|
5
|
+
/** full markdown body including any frontmatter fence */
|
|
6
|
+
markdown: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ImportPlan {
|
|
9
|
+
repoId: string;
|
|
10
|
+
docCount: number;
|
|
11
|
+
totalBytes: number;
|
|
12
|
+
paths: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface ImportResult {
|
|
15
|
+
repoId: string;
|
|
16
|
+
imported: {
|
|
17
|
+
path: string;
|
|
18
|
+
docId: string;
|
|
19
|
+
blockCount: number;
|
|
20
|
+
converged: boolean;
|
|
21
|
+
}[];
|
|
22
|
+
allConverged: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Dry-run: report what would be imported without writing anything. */
|
|
25
|
+
export declare function planImport(repoId: string, docs: MrplexDoc[]): ImportPlan;
|
|
26
|
+
/** Import the documents as `import`-origin commits with freshly minted ids. */
|
|
27
|
+
export declare function importDocs(store: Store, repoId: string, docs: MrplexDoc[]): ImportResult;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ingestFile } from "../core/ingest.js";
|
|
2
|
+
import { makeReconcilingResolver } from "../sync/reconciling-ingest.js";
|
|
3
|
+
/** Dry-run: report what would be imported without writing anything. */
|
|
4
|
+
export function planImport(repoId, docs) {
|
|
5
|
+
return {
|
|
6
|
+
repoId,
|
|
7
|
+
docCount: docs.length,
|
|
8
|
+
totalBytes: docs.reduce((n, d) => n + Buffer.byteLength(d.markdown, "utf8"), 0),
|
|
9
|
+
paths: docs.map((d) => d.path).sort(),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** Import the documents as `import`-origin commits with freshly minted ids. */
|
|
13
|
+
export function importDocs(store, repoId, docs) {
|
|
14
|
+
const ts = new Date().toISOString();
|
|
15
|
+
const imported = [];
|
|
16
|
+
let allConverged = true;
|
|
17
|
+
for (const doc of docs) {
|
|
18
|
+
const res = ingestFile(store, repoId, doc.path, doc.markdown, {
|
|
19
|
+
ts,
|
|
20
|
+
origin: "import",
|
|
21
|
+
resolveIds: makeReconcilingResolver(store, repoId, { ts, path: doc.path }),
|
|
22
|
+
});
|
|
23
|
+
imported.push({ path: doc.path, docId: res.docId, blockCount: res.blockCount, converged: res.converged });
|
|
24
|
+
if (!res.converged)
|
|
25
|
+
allConverged = false;
|
|
26
|
+
}
|
|
27
|
+
return { repoId, imported, allConverged };
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=mrplex.js.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Store } from "../core/store/store.js";
|
|
2
|
+
import { type To, type Expect } from "./ops.js";
|
|
3
|
+
import { type DocStore } from "./doc-store.js";
|
|
4
|
+
export type Op = {
|
|
5
|
+
op: "insert";
|
|
6
|
+
doc?: string;
|
|
7
|
+
to: To;
|
|
8
|
+
markdown: string;
|
|
9
|
+
} | {
|
|
10
|
+
op: "update";
|
|
11
|
+
block: string;
|
|
12
|
+
markdown?: string;
|
|
13
|
+
attrs?: Record<string, unknown>;
|
|
14
|
+
expect?: Expect;
|
|
15
|
+
trivia?: string;
|
|
16
|
+
childIds?: Record<string, string>;
|
|
17
|
+
} | {
|
|
18
|
+
op: "move";
|
|
19
|
+
blocks: string[];
|
|
20
|
+
to: To;
|
|
21
|
+
} | {
|
|
22
|
+
op: "remove";
|
|
23
|
+
blocks: string[];
|
|
24
|
+
expect?: Record<string, Expect>;
|
|
25
|
+
} | {
|
|
26
|
+
op: "split";
|
|
27
|
+
block: string;
|
|
28
|
+
at: number[];
|
|
29
|
+
expect?: Expect;
|
|
30
|
+
} | {
|
|
31
|
+
op: "merge";
|
|
32
|
+
blocks: string[];
|
|
33
|
+
separator?: string;
|
|
34
|
+
expect?: Record<string, Expect>;
|
|
35
|
+
};
|
|
36
|
+
export interface ApplyRequest {
|
|
37
|
+
repoId: string;
|
|
38
|
+
/** Working-tree root for the default filesystem write target. Omit only when
|
|
39
|
+
* supplying an explicit `docStore` (e.g. a headless NullDocStore). */
|
|
40
|
+
rootPath?: string;
|
|
41
|
+
/** Write target (ADR-014 §5); defaults to a filesystem store at `rootPath`. */
|
|
42
|
+
docStore?: DocStore;
|
|
43
|
+
ops: Op[];
|
|
44
|
+
origin: {
|
|
45
|
+
actor: string;
|
|
46
|
+
reason?: string;
|
|
47
|
+
};
|
|
48
|
+
dryRun?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Workspace .omgbase/ dir. When set, the file-write + commit phase runs under
|
|
51
|
+
* the cross-process writer lock (11 §3.2). Omitted in single-process/in-memory
|
|
52
|
+
* contexts (tests) where the in-process serialization of Store.write suffices.
|
|
53
|
+
*/
|
|
54
|
+
omgbaseDir?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Document-level frontmatter overrides (doc id → new frontmatter raw incl.
|
|
57
|
+
* fences + trailing separator, or null to drop it). Frontmatter is a
|
|
58
|
+
* document-level materialization unit (rendered before the block tree), not a
|
|
59
|
+
* block the six ops address; the whole-document update planner sets it here
|
|
60
|
+
* when the proposed content changes the frontmatter. The doc is force-loaded
|
|
61
|
+
* so a frontmatter-only change still commits.
|
|
62
|
+
*/
|
|
63
|
+
setFrontmatter?: {
|
|
64
|
+
doc: string;
|
|
65
|
+
raw: string | null;
|
|
66
|
+
}[];
|
|
67
|
+
}
|
|
68
|
+
export interface OpResult {
|
|
69
|
+
ids: string[];
|
|
70
|
+
}
|
|
71
|
+
export interface ApplyResult {
|
|
72
|
+
results: OpResult[];
|
|
73
|
+
revisions: {
|
|
74
|
+
doc: string;
|
|
75
|
+
path: string;
|
|
76
|
+
}[];
|
|
77
|
+
diffs?: Record<string, {
|
|
78
|
+
before: string;
|
|
79
|
+
after: string;
|
|
80
|
+
}>;
|
|
81
|
+
committed: boolean;
|
|
82
|
+
}
|
|
83
|
+
export declare function apply(store: Store, req: ApplyRequest): ApplyResult;
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { sha256 } from "../core/hash.js";
|
|
2
|
+
import { isValidId } from "../core/ids.js";
|
|
3
|
+
import { findDocByRef } from "../core/read/reader.js";
|
|
4
|
+
import { ingestFile } from "../core/ingest.js";
|
|
5
|
+
import { makeReconcilingResolver } from "../sync/reconciling-ingest.js";
|
|
6
|
+
import { makeKnownIdResolver } from "./known-ids.js";
|
|
7
|
+
import { loadMutDoc } from "./load.js";
|
|
8
|
+
import { renderDoc, MutationError } from "./tree.js";
|
|
9
|
+
import { opInsert, opUpdate, opMove, opRemove, opSplit, opMerge } from "./ops.js";
|
|
10
|
+
import { withWriterLock } from "../sync/writer-lock.js";
|
|
11
|
+
import { resolveDocStore } from "./doc-store.js";
|
|
12
|
+
// Resolve "$n.ids[i]" placeholders in a string against prior op results.
|
|
13
|
+
function resolvePlaceholder(value, results) {
|
|
14
|
+
const m = /^\$(\d+)\.ids\[(\d+)\]$/.exec(value);
|
|
15
|
+
if (!m)
|
|
16
|
+
return value;
|
|
17
|
+
const opIdx = Number(m[1]);
|
|
18
|
+
const idIdx = Number(m[2]);
|
|
19
|
+
const id = results[opIdx]?.ids[idIdx];
|
|
20
|
+
if (!id)
|
|
21
|
+
throw new MutationError("target_missing", `placeholder ${value} did not resolve`);
|
|
22
|
+
return id;
|
|
23
|
+
}
|
|
24
|
+
function resolveTo(to, results) {
|
|
25
|
+
const parent = typeof to.parent === "string" ? resolvePlaceholder(to.parent, results) : to.parent;
|
|
26
|
+
let at = to.at;
|
|
27
|
+
if (typeof at === "object") {
|
|
28
|
+
if ("before" in at)
|
|
29
|
+
at = { before: resolvePlaceholder(at.before, results) };
|
|
30
|
+
else if ("after" in at)
|
|
31
|
+
at = { after: resolvePlaceholder(at.after, results) };
|
|
32
|
+
}
|
|
33
|
+
return { parent, at };
|
|
34
|
+
}
|
|
35
|
+
// Resolve an insert op's `doc` field, which may be a minted doc id (d_...) or a
|
|
36
|
+
// document path — the same id-or-path symmetry the read/graph surfaces expose
|
|
37
|
+
// (shared via findDocByRef; see core/read/reader.ts). A valid d_ id passes
|
|
38
|
+
// through WITHOUT a store lookup so a doc minted by an earlier op in the SAME
|
|
39
|
+
// changeset (not yet committed) still resolves; ensureDoc validates existence.
|
|
40
|
+
// A path is resolved via the shared helper; an unresolvable path throws
|
|
41
|
+
// doc_missing naming it.
|
|
42
|
+
function resolveDocRef(store, repoId, ref) {
|
|
43
|
+
if (isValidId(ref, "d"))
|
|
44
|
+
return ref;
|
|
45
|
+
const info = findDocByRef(store, repoId, ref);
|
|
46
|
+
if (!info)
|
|
47
|
+
throw new MutationError("doc_missing", `doc ${ref} not found`, { doc: ref });
|
|
48
|
+
return info.docId;
|
|
49
|
+
}
|
|
50
|
+
// Which doc a given block id lives in (searches loaded docs; falls back to store).
|
|
51
|
+
function docIdForBlock(store, repoId, blockId) {
|
|
52
|
+
const row = store.db.prepare("SELECT doc_id FROM blocks WHERE block_id = ? AND deleted_commit IS NULL").get(blockId);
|
|
53
|
+
return row?.doc_id ?? null;
|
|
54
|
+
}
|
|
55
|
+
export function apply(store, req) {
|
|
56
|
+
const { store: _s } = { store };
|
|
57
|
+
void _s;
|
|
58
|
+
// Load all docs that ops touch into memory; apply ops in order.
|
|
59
|
+
const loaded = new Map();
|
|
60
|
+
const before = new Map();
|
|
61
|
+
// Block ids present in each doc BEFORE any op — used to label intent
|
|
62
|
+
// dispositions (edited vs inserted) on the known-id commit path.
|
|
63
|
+
const priorIds = new Map();
|
|
64
|
+
const results = [];
|
|
65
|
+
const ensureDoc = (docId) => {
|
|
66
|
+
let d = loaded.get(docId);
|
|
67
|
+
if (!d) {
|
|
68
|
+
const md = loadMutDoc(store.db, docId);
|
|
69
|
+
if (!md)
|
|
70
|
+
throw new MutationError("doc_missing", `doc ${docId} not found`);
|
|
71
|
+
d = md;
|
|
72
|
+
loaded.set(docId, d);
|
|
73
|
+
before.set(docId, renderDoc(d));
|
|
74
|
+
const ids = store.db.prepare("SELECT block_id FROM blocks WHERE doc_id = ? AND deleted_commit IS NULL").all(docId);
|
|
75
|
+
priorIds.set(docId, new Set(ids.map((r) => r.block_id)));
|
|
76
|
+
}
|
|
77
|
+
return d;
|
|
78
|
+
};
|
|
79
|
+
// Document-level frontmatter overrides, before the op loop (so a change with
|
|
80
|
+
// no body ops still loads + commits the doc). Applied to the loaded MutDoc;
|
|
81
|
+
// renderDoc emits frontmatterRaw verbatim and the commit re-ingest re-derives
|
|
82
|
+
// properties/edges from the rendered bytes.
|
|
83
|
+
for (const fm of req.setFrontmatter ?? []) {
|
|
84
|
+
// ensureDoc captures `before` (pre-mutation render) at load time.
|
|
85
|
+
ensureDoc(fm.doc).frontmatterRaw = fm.raw;
|
|
86
|
+
}
|
|
87
|
+
req.ops.forEach((rawOp, i) => {
|
|
88
|
+
switch (rawOp.op) {
|
|
89
|
+
case "insert": {
|
|
90
|
+
const to = resolveTo(rawOp.to, results);
|
|
91
|
+
const docId = rawOp.doc !== undefined
|
|
92
|
+
? resolveDocRef(store, req.repoId, rawOp.doc)
|
|
93
|
+
: parentDoc(store, req.repoId, to, loaded);
|
|
94
|
+
const d = ensureDoc(docId);
|
|
95
|
+
results.push(opInsert(d, to, rawOp.markdown));
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "update": {
|
|
99
|
+
const block = resolvePlaceholder(rawOp.block, results);
|
|
100
|
+
const docId = docIdForBlockLoaded(loaded, block) ?? docIdForBlock(store, req.repoId, block);
|
|
101
|
+
if (!docId)
|
|
102
|
+
throw new MutationError("block_missing", `block ${block} not found`, { op_index: i });
|
|
103
|
+
results.push(opUpdate(ensureDoc(docId), block, i, rawOp.markdown, rawOp.attrs, rawOp.expect, rawOp.trivia, rawOp.childIds));
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case "move": {
|
|
107
|
+
const blocks = rawOp.blocks.map((b) => resolvePlaceholder(b, results));
|
|
108
|
+
const to = resolveTo(rawOp.to, results);
|
|
109
|
+
const srcDocId = docIdForBlockLoaded(loaded, blocks[0]) ?? docIdForBlock(store, req.repoId, blocks[0]);
|
|
110
|
+
if (!srcDocId)
|
|
111
|
+
throw new MutationError("block_missing", `block ${blocks[0]} not found`, { op_index: i });
|
|
112
|
+
// {doc:true} with a non-anchor `at` (start/end) means the source doc's
|
|
113
|
+
// top level; otherwise infer the destination doc from the target.
|
|
114
|
+
const topLevelSameDoc = typeof to.parent === "object" && "doc" in to.parent && typeof to.at === "string";
|
|
115
|
+
const dstDocId = topLevelSameDoc ? srcDocId : parentDoc(store, req.repoId, to, loaded);
|
|
116
|
+
if (dstDocId === srcDocId) {
|
|
117
|
+
results.push(opMove(ensureDoc(srcDocId), blocks, to, i));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Cross-document move (04 §1): extract from source, insert into target.
|
|
121
|
+
results.push(crossDocMove(ensureDoc(srcDocId), ensureDoc(dstDocId), blocks, to, i));
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "remove": {
|
|
126
|
+
const blocks = rawOp.blocks.map((b) => resolvePlaceholder(b, results));
|
|
127
|
+
const docId = docIdForBlockLoaded(loaded, blocks[0]) ?? docIdForBlock(store, req.repoId, blocks[0]);
|
|
128
|
+
if (!docId)
|
|
129
|
+
throw new MutationError("block_missing", `block ${blocks[0]} not found`, { op_index: i });
|
|
130
|
+
results.push(opRemove(ensureDoc(docId), blocks, i, rawOp.expect));
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "split": {
|
|
134
|
+
const block = resolvePlaceholder(rawOp.block, results);
|
|
135
|
+
const docId = docIdForBlockLoaded(loaded, block) ?? docIdForBlock(store, req.repoId, block);
|
|
136
|
+
if (!docId)
|
|
137
|
+
throw new MutationError("block_missing", `block ${block} not found`, { op_index: i });
|
|
138
|
+
results.push(opSplit(ensureDoc(docId), block, rawOp.at, i, rawOp.expect));
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "merge": {
|
|
142
|
+
const blocks = rawOp.blocks.map((b) => resolvePlaceholder(b, results));
|
|
143
|
+
const docId = docIdForBlockLoaded(loaded, blocks[0]) ?? docIdForBlock(store, req.repoId, blocks[0]);
|
|
144
|
+
if (!docId)
|
|
145
|
+
throw new MutationError("block_missing", `block ${blocks[0]} not found`, { op_index: i });
|
|
146
|
+
results.push(opMerge(ensureDoc(docId), blocks, i, rawOp.separator, rawOp.expect));
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
const revisions = [];
|
|
152
|
+
const diffs = {};
|
|
153
|
+
for (const [docId, d] of loaded) {
|
|
154
|
+
diffs[d.path] = { before: before.get(docId) ?? "", after: renderDoc(d) };
|
|
155
|
+
revisions.push({ doc: docId, path: d.path });
|
|
156
|
+
}
|
|
157
|
+
if (req.dryRun) {
|
|
158
|
+
return { results, revisions, diffs, committed: false };
|
|
159
|
+
}
|
|
160
|
+
// Write each touched file with the file-CAS + atomic-write protocol (04 §6),
|
|
161
|
+
// then re-ingest the rendered bytes as an api-origin commit so identity
|
|
162
|
+
// threads (the ops already assigned ids; reconciliation confirms carries).
|
|
163
|
+
// Steps 2–7 run under the cross-process writer lock when a workspace dir is
|
|
164
|
+
// supplied (11 §3.2); otherwise the Store's in-process serialization suffices.
|
|
165
|
+
const docStore = resolveDocStore(req);
|
|
166
|
+
const commitPhase = () => {
|
|
167
|
+
const ts = new Date().toISOString();
|
|
168
|
+
for (const [docId, d] of loaded) {
|
|
169
|
+
const rendered = renderDoc(d);
|
|
170
|
+
// File-CAS: on-disk bytes must equal the revision we computed against.
|
|
171
|
+
const current = store.db.prepare("SELECT file_hash FROM docs WHERE doc_id = ?").get(docId);
|
|
172
|
+
const onDiskContent = docStore.read(d.path);
|
|
173
|
+
if (onDiskContent !== null && current?.file_hash) {
|
|
174
|
+
const onDisk = sha256(onDiskContent);
|
|
175
|
+
if (!onDisk.equals(current.file_hash)) {
|
|
176
|
+
// A human edit landed first: ingest it, then the caller must retry.
|
|
177
|
+
ingestFile(store, req.repoId, d.path, onDiskContent, { ts, resolveIds: makeReconcilingResolver(store, req.repoId, { ts, path: d.path }) });
|
|
178
|
+
if (req.omgbaseDir)
|
|
179
|
+
docStore.recordStat(store, req.repoId, d.path, onDiskContent);
|
|
180
|
+
throw new MutationError("sync_conflict", `file ${d.path} changed on disk; re-ingested — retry`, { retriable: true });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Atomic write through the DocStore (no-op for a headless NullDocStore).
|
|
184
|
+
docStore.write(d.path, rendered);
|
|
185
|
+
// Commit (INTENT path): persist the ops' tree with its KNOWN block ids —
|
|
186
|
+
// never re-derive identity from the rendered bytes. The mutated MutDoc `d`
|
|
187
|
+
// already carries deterministic ids (opUpdate keeps a block's id in place,
|
|
188
|
+
// untouched blocks keep theirs, new blocks carry the op-minted id), so the
|
|
189
|
+
// known-id resolver threads them onto the re-parsed tree and records intent
|
|
190
|
+
// dispositions (confidence 1.0). Re-reconciling here would gamble block —
|
|
191
|
+
// and thus node — identity on the probabilistic matcher (node-editability).
|
|
192
|
+
ingestFile(store, req.repoId, d.path, rendered, {
|
|
193
|
+
ts,
|
|
194
|
+
origin: "import",
|
|
195
|
+
resolveIds: makeKnownIdResolver(store, req.repoId, d, priorIds.get(docId) ?? new Set(), { path: d.path }),
|
|
196
|
+
});
|
|
197
|
+
// Keep the freshness cache warm so this engine write isn't re-hashed by a
|
|
198
|
+
// later sweep (echo suppression already covers correctness; this avoids work).
|
|
199
|
+
if (req.omgbaseDir)
|
|
200
|
+
docStore.recordStat(store, req.repoId, d.path, rendered);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
if (req.omgbaseDir)
|
|
204
|
+
withWriterLock(req.omgbaseDir, commitPhase);
|
|
205
|
+
else
|
|
206
|
+
commitPhase();
|
|
207
|
+
return { results, revisions, committed: true };
|
|
208
|
+
}
|
|
209
|
+
function docIdForBlockLoaded(loaded, blockId) {
|
|
210
|
+
for (const [docId, d] of loaded) {
|
|
211
|
+
if (findInDoc(d, blockId))
|
|
212
|
+
return docId;
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
function findInDoc(d, blockId) {
|
|
217
|
+
const walk = (list) => list.some((b) => b.id === blockId || walk(b.children));
|
|
218
|
+
return walk(d.children);
|
|
219
|
+
}
|
|
220
|
+
// Determine the doc for a placement whose target is a block/heading/doc. When
|
|
221
|
+
// parent is {doc:true}, infer the doc from the anchor block in `at`.
|
|
222
|
+
function parentDoc(store, repoId, to, loaded) {
|
|
223
|
+
if (typeof to.parent === "string") {
|
|
224
|
+
return docIdForBlockLoaded(loaded, to.parent) ?? docIdForBlock(store, repoId, to.parent) ?? throwMissing(to.parent);
|
|
225
|
+
}
|
|
226
|
+
if (typeof to.parent === "object" && "heading" in to.parent) {
|
|
227
|
+
return docIdForBlockLoaded(loaded, to.parent.heading) ?? docIdForBlock(store, repoId, to.parent.heading) ?? throwMissing(to.parent.heading);
|
|
228
|
+
}
|
|
229
|
+
// parent: {doc:true} — infer from the anchor block in `at`.
|
|
230
|
+
if (typeof to.at === "object") {
|
|
231
|
+
const anchor = "before" in to.at ? to.at.before : to.at.after;
|
|
232
|
+
return docIdForBlockLoaded(loaded, anchor) ?? docIdForBlock(store, repoId, anchor) ?? throwMissing(anchor);
|
|
233
|
+
}
|
|
234
|
+
throw new MutationError("target_missing", "insert at top-level start/end requires an explicit doc");
|
|
235
|
+
}
|
|
236
|
+
function throwMissing(id) {
|
|
237
|
+
throw new MutationError("parent_missing", `parent ${id} not found`);
|
|
238
|
+
}
|
|
239
|
+
// Cross-document move: extract the contiguous run from src, insert into dst.
|
|
240
|
+
function crossDocMove(src, dst, blockIds, to, opIndex) {
|
|
241
|
+
// Extract from src (must be a contiguous top-level or same-parent run).
|
|
242
|
+
const moving = [];
|
|
243
|
+
for (const id of blockIds) {
|
|
244
|
+
const found = locateMut(src, id);
|
|
245
|
+
if (!found)
|
|
246
|
+
throw new MutationError("block_missing", `block ${id} not found in source doc`, { op_index: opIndex });
|
|
247
|
+
moving.push(found.block);
|
|
248
|
+
}
|
|
249
|
+
// remove from their source lists
|
|
250
|
+
for (const id of blockIds) {
|
|
251
|
+
const f = locateMut(src, id);
|
|
252
|
+
if (f)
|
|
253
|
+
f.siblings.splice(f.index, 1);
|
|
254
|
+
}
|
|
255
|
+
// Insert into dst at the resolved target.
|
|
256
|
+
const target = resolveDstTarget(dst, to);
|
|
257
|
+
target.siblings.splice(target.index, 0, ...moving);
|
|
258
|
+
return { ids: blockIds };
|
|
259
|
+
}
|
|
260
|
+
function locateMut(doc, blockId) {
|
|
261
|
+
const search = (list) => {
|
|
262
|
+
for (let i = 0; i < list.length; i++) {
|
|
263
|
+
if (list[i].id === blockId)
|
|
264
|
+
return { block: list[i], siblings: list, index: i };
|
|
265
|
+
const f = search(list[i].children);
|
|
266
|
+
if (f)
|
|
267
|
+
return f;
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
};
|
|
271
|
+
return search(doc.children);
|
|
272
|
+
}
|
|
273
|
+
function resolveDstTarget(dst, to) {
|
|
274
|
+
if (typeof to.parent === "object" && "doc" in to.parent) {
|
|
275
|
+
return { siblings: dst.children, index: resolveAt(dst.children, to.at) };
|
|
276
|
+
}
|
|
277
|
+
if (typeof to.parent === "string") {
|
|
278
|
+
const f = locateMut(dst, to.parent);
|
|
279
|
+
if (!f)
|
|
280
|
+
throw new MutationError("parent_missing", `parent ${to.parent} not found in dest`);
|
|
281
|
+
return { siblings: f.block.children, index: resolveAt(f.block.children, to.at) };
|
|
282
|
+
}
|
|
283
|
+
// heading/section: insert into top-level list at the anchor.
|
|
284
|
+
return { siblings: dst.children, index: resolveAt(dst.children, to.at) };
|
|
285
|
+
}
|
|
286
|
+
function resolveAt(siblings, at) {
|
|
287
|
+
if (at === "start")
|
|
288
|
+
return 0;
|
|
289
|
+
if (at === "end")
|
|
290
|
+
return siblings.length;
|
|
291
|
+
if ("before" in at) {
|
|
292
|
+
const i = siblings.findIndex((b) => b.id === at.before);
|
|
293
|
+
if (i < 0)
|
|
294
|
+
throw new MutationError("target_missing", `anchor ${at.before} not found`);
|
|
295
|
+
return i;
|
|
296
|
+
}
|
|
297
|
+
const i = siblings.findIndex((b) => b.id === at.after);
|
|
298
|
+
if (i < 0)
|
|
299
|
+
throw new MutationError("target_missing", `anchor ${at.after} not found`);
|
|
300
|
+
return i + 1;
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Store } from "../core/store/store.js";
|
|
2
|
+
export interface DocStore {
|
|
3
|
+
/** Does a file exist at repo-relative `path`? */
|
|
4
|
+
exists(path: string): boolean;
|
|
5
|
+
/** Current bytes at `path`, or null if absent. */
|
|
6
|
+
read(path: string): string | null;
|
|
7
|
+
/** Atomically write `bytes` to `path` (mkdir -p parents; temp file + rename). */
|
|
8
|
+
write(path: string, bytes: string): void;
|
|
9
|
+
/** Rename `from` → `to` (mkdir -p the destination's parents). */
|
|
10
|
+
rename(from: string, to: string): void;
|
|
11
|
+
/** Remove `path` if present. */
|
|
12
|
+
remove(path: string): void;
|
|
13
|
+
/** Refresh the freshness stat-cache row for `path` after writing `bytes`. */
|
|
14
|
+
recordStat(store: Store, repoId: string, path: string, bytes: string): void;
|
|
15
|
+
/** Drop the freshness stat-cache row for `path`. */
|
|
16
|
+
clearStat(store: Store, repoId: string, path: string): void;
|
|
17
|
+
}
|
|
18
|
+
/** Filesystem-backed DocStore rooted at a working-tree directory (v1 default). */
|
|
19
|
+
export declare class FsDocStore implements DocStore {
|
|
20
|
+
private readonly root;
|
|
21
|
+
constructor(root: string);
|
|
22
|
+
private abs;
|
|
23
|
+
exists(path: string): boolean;
|
|
24
|
+
read(path: string): string | null;
|
|
25
|
+
write(path: string, bytes: string): void;
|
|
26
|
+
rename(from: string, to: string): void;
|
|
27
|
+
remove(path: string): void;
|
|
28
|
+
recordStat(store: Store, repoId: string, path: string, bytes: string): void;
|
|
29
|
+
clearStat(store: Store, repoId: string, path: string): void;
|
|
30
|
+
}
|
|
31
|
+
/** Headless DocStore: no working tree. The DB is the canonical artifact, so the
|
|
32
|
+
* file write is a no-op and there is no on-disk state to CAS against or stat. */
|
|
33
|
+
export declare class NullDocStore implements DocStore {
|
|
34
|
+
exists(): boolean;
|
|
35
|
+
read(): string | null;
|
|
36
|
+
write(): void;
|
|
37
|
+
rename(): void;
|
|
38
|
+
remove(): void;
|
|
39
|
+
recordStat(): void;
|
|
40
|
+
clearStat(): void;
|
|
41
|
+
}
|
|
42
|
+
/** The write target for a mutation: an explicit DocStore, else a filesystem
|
|
43
|
+
* store rooted at `rootPath`. Throws if neither is supplied (a caller must name
|
|
44
|
+
* one or the other — a filesystem repo passes rootPath; a headless repo passes
|
|
45
|
+
* a NullDocStore). */
|
|
46
|
+
export declare function resolveDocStore(req: {
|
|
47
|
+
docStore?: DocStore;
|
|
48
|
+
rootPath?: string;
|
|
49
|
+
}): DocStore;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync, renameSync, unlinkSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { sha256 } from "../core/hash.js";
|
|
4
|
+
import { recordFileStat } from "../sync/freshness.js";
|
|
5
|
+
/** Filesystem-backed DocStore rooted at a working-tree directory (v1 default). */
|
|
6
|
+
export class FsDocStore {
|
|
7
|
+
root;
|
|
8
|
+
constructor(root) {
|
|
9
|
+
this.root = root;
|
|
10
|
+
}
|
|
11
|
+
abs(path) {
|
|
12
|
+
return join(this.root, path);
|
|
13
|
+
}
|
|
14
|
+
exists(path) {
|
|
15
|
+
return existsSync(this.abs(path));
|
|
16
|
+
}
|
|
17
|
+
read(path) {
|
|
18
|
+
const abs = this.abs(path);
|
|
19
|
+
return existsSync(abs) ? readFileSync(abs, "utf8") : null;
|
|
20
|
+
}
|
|
21
|
+
write(path, bytes) {
|
|
22
|
+
const abs = this.abs(path);
|
|
23
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
24
|
+
const tmp = `${abs}.omgtmp`;
|
|
25
|
+
writeFileSync(tmp, bytes);
|
|
26
|
+
renameSync(tmp, abs);
|
|
27
|
+
}
|
|
28
|
+
rename(from, to) {
|
|
29
|
+
const toAbs = this.abs(to);
|
|
30
|
+
mkdirSync(dirname(toAbs), { recursive: true });
|
|
31
|
+
renameSync(this.abs(from), toAbs);
|
|
32
|
+
}
|
|
33
|
+
remove(path) {
|
|
34
|
+
const abs = this.abs(path);
|
|
35
|
+
if (existsSync(abs))
|
|
36
|
+
unlinkSync(abs);
|
|
37
|
+
}
|
|
38
|
+
recordStat(store, repoId, path, bytes) {
|
|
39
|
+
recordFileStat(store, repoId, path, this.abs(path), sha256(bytes));
|
|
40
|
+
}
|
|
41
|
+
clearStat(store, repoId, path) {
|
|
42
|
+
store.db.prepare("DELETE FROM file_stats WHERE repo_id = ? AND path = ?").run(repoId, path);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Headless DocStore: no working tree. The DB is the canonical artifact, so the
|
|
46
|
+
* file write is a no-op and there is no on-disk state to CAS against or stat. */
|
|
47
|
+
export class NullDocStore {
|
|
48
|
+
exists() {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
read() {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
write() { }
|
|
55
|
+
rename() { }
|
|
56
|
+
remove() { }
|
|
57
|
+
recordStat() { }
|
|
58
|
+
clearStat() { }
|
|
59
|
+
}
|
|
60
|
+
/** The write target for a mutation: an explicit DocStore, else a filesystem
|
|
61
|
+
* store rooted at `rootPath`. Throws if neither is supplied (a caller must name
|
|
62
|
+
* one or the other — a filesystem repo passes rootPath; a headless repo passes
|
|
63
|
+
* a NullDocStore). */
|
|
64
|
+
export function resolveDocStore(req) {
|
|
65
|
+
if (req.docStore)
|
|
66
|
+
return req.docStore;
|
|
67
|
+
if (req.rootPath != null)
|
|
68
|
+
return new FsDocStore(req.rootPath);
|
|
69
|
+
throw new Error("mutation requires a rootPath or an explicit docStore");
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=doc-store.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Store } from "../core/store/store.js";
|
|
2
|
+
import { type InboundLink } from "../graph/inbound-links.js";
|
|
3
|
+
import { type DocStore } from "./doc-store.js";
|
|
4
|
+
export interface DocOpContext {
|
|
5
|
+
repoId: string;
|
|
6
|
+
/** Working-tree root for the default filesystem write target. Omit only when
|
|
7
|
+
* supplying an explicit `docStore` (e.g. a headless NullDocStore). */
|
|
8
|
+
rootPath?: string;
|
|
9
|
+
/** Write target (ADR-014 §5); defaults to a filesystem store at `rootPath`. */
|
|
10
|
+
docStore?: DocStore;
|
|
11
|
+
/** workspace .omgbase/ dir; when set, the write runs under the writer flock. */
|
|
12
|
+
omgbaseDir?: string;
|
|
13
|
+
actor?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DocOpResult {
|
|
16
|
+
docId: string;
|
|
17
|
+
path: string;
|
|
18
|
+
committed: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface DocMoveOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Also rewrite the inbound links that named the OLD path so they point at the
|
|
23
|
+
* new one — a destination-aware rewrite (anchors, link text, titles, and code
|
|
24
|
+
* spans preserved; absolute/relative style kept) applied as a CAS-checked
|
|
25
|
+
* `update` per source block in one follow-up api changeset. Frontmatter-level
|
|
26
|
+
* relations are not rewritten (reported in `dangling`).
|
|
27
|
+
*/
|
|
28
|
+
retargetInbound?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface DocMoveResult extends DocOpResult {
|
|
31
|
+
/**
|
|
32
|
+
* Inbound links still pointing at the OLD path after this call — the source
|
|
33
|
+
* blocks `links_stale` will now report (empty when `retargetInbound` rewrote
|
|
34
|
+
* them all). Computed from the open edge index before the move.
|
|
35
|
+
*/
|
|
36
|
+
dangling: InboundLink[];
|
|
37
|
+
/** With `retargetInbound`: the source blocks rewritten and the docs re-ingested. */
|
|
38
|
+
retargeted: {
|
|
39
|
+
blocks: string[];
|
|
40
|
+
docs: string[];
|
|
41
|
+
} | null;
|
|
42
|
+
}
|
|
43
|
+
/** docs_create: create a new document from complete file bytes (frontmatter incl.). */
|
|
44
|
+
export declare function docsCreate(store: Store, ctx: DocOpContext, path: string, markdown: string, frontmatter?: Record<string, unknown>): DocOpResult;
|
|
45
|
+
/**
|
|
46
|
+
* docs_move: rename a document to a new path (identity preserved). The edge
|
|
47
|
+
* index follows the path, not the identity: links written against the OLD path
|
|
48
|
+
* now dangle (their open edges are re-pointed at `phantom:<old>`, exactly what
|
|
49
|
+
* re-extracting the source would produce, so `links_stale` sees them), and
|
|
50
|
+
* phantom edges already written against the NEW path are adopted. The result
|
|
51
|
+
* lists the dangling inbound links; `retargetInbound` rewrites them.
|
|
52
|
+
*/
|
|
53
|
+
export declare function docsMove(store: Store, ctx: DocOpContext, docRef: string, toPath: string, opts?: DocMoveOptions): DocMoveResult;
|
|
54
|
+
/** docs_delete: mark a document deleted and remove its file (resurrection-poolable). */
|
|
55
|
+
export declare function docsDelete(store: Store, ctx: DocOpContext, docRef: string): DocOpResult;
|
|
56
|
+
/** docs_set_meta: surgical frontmatter patch (set/unset keys), re-ingested. */
|
|
57
|
+
export declare function docsSetMeta(store: Store, ctx: DocOpContext, docRef: string, patch: {
|
|
58
|
+
set?: Record<string, unknown>;
|
|
59
|
+
unset?: string[];
|
|
60
|
+
}): DocOpResult;
|