@gmickel/gno 1.46.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +46 -0
- package/assets/skill/SKILL.md +7 -6
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
- package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/browser-extension/dist/preview.html +1 -1
- package/browser-extension/dist/service-worker.js +32 -33
- package/bunfig.toml +2 -0
- package/package.json +40 -26
- package/spec/cli.md +21 -4
- package/spec/db/schema.sql +146 -1
- package/spec/mcp.md +26 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +2 -0
- package/src/cli/commands/ask.ts +6 -1
- package/src/cli/commands/daemon.ts +21 -8
- package/src/cli/commands/embed.ts +77 -41
- package/src/cli/detach.ts +3 -2
- package/src/config/types.ts +3 -3
- package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
- package/src/converters/adapters/officeparser/adapter.ts +1 -2
- package/src/converters/versions.ts +6 -8
- package/src/core/context-evidence.ts +8 -4
- package/src/core/job-manager.ts +95 -13
- package/src/core/network-boundary-inventory.ts +10 -0
- package/src/core/shutdown-budget.ts +45 -0
- package/src/embed/backlog.ts +107 -4
- package/src/embed/batch.ts +42 -2
- package/src/embed/fingerprint.ts +16 -0
- package/src/embed/retry.ts +113 -5
- package/src/embed/variant-backlog.ts +105 -0
- package/src/embed/variant-plan.ts +62 -0
- package/src/embed/variant-retry.ts +113 -0
- package/src/ingestion/graph-reconciliation.ts +327 -0
- package/src/ingestion/sync.ts +9 -272
- package/src/llm/http-inference.ts +6 -0
- package/src/llm/httpEmbedding.ts +37 -6
- package/src/llm/httpGeneration.ts +18 -3
- package/src/llm/httpRerank.ts +23 -5
- package/src/llm/inference-cancellation.ts +168 -0
- package/src/llm/inference-scope.ts +202 -0
- package/src/llm/lazy-ports.ts +115 -0
- package/src/llm/native-worker/client.ts +541 -0
- package/src/llm/native-worker/dispatcher.ts +228 -0
- package/src/llm/native-worker/embedding-identity.ts +33 -0
- package/src/llm/native-worker/entry.ts +173 -0
- package/src/llm/native-worker/errors.ts +32 -0
- package/src/llm/native-worker/evaluation.ts +16 -0
- package/src/llm/native-worker/owned-exit.ts +108 -0
- package/src/llm/native-worker/owner.ts +141 -0
- package/src/llm/native-worker/ports.ts +317 -0
- package/src/llm/native-worker/protocol.ts +442 -0
- package/src/llm/native-worker/runtime-config.ts +92 -0
- package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
- package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
- package/src/llm/nodeLlamaCpp/generation.ts +34 -5
- package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
- package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
- package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
- package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
- package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
- package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
- package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
- package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
- package/src/llm/types.ts +35 -5
- package/src/mcp/context.ts +27 -0
- package/src/mcp/http-transport.ts +12 -10
- package/src/mcp/server.ts +3 -0
- package/src/mcp/tool-profile.ts +30 -8
- package/src/mcp/tools/context.ts +8 -11
- package/src/mcp/tools/embed.ts +1 -1
- package/src/mcp/tools/index-cmd.ts +1 -1
- package/src/mcp/tools/index.ts +10 -8
- package/src/mcp/tools/query.ts +14 -30
- package/src/mcp/tools/vsearch.ts +1 -1
- package/src/pipeline/answer.ts +23 -3
- package/src/pipeline/claim-verifier.ts +6 -0
- package/src/pipeline/expansion.ts +43 -40
- package/src/pipeline/explain.ts +6 -2
- package/src/pipeline/filters.ts +63 -0
- package/src/pipeline/fusion.ts +29 -9
- package/src/pipeline/graph-retrieval.ts +29 -9
- package/src/pipeline/hybrid.ts +198 -55
- package/src/pipeline/hydration.ts +161 -0
- package/src/pipeline/owner-fusion.ts +87 -0
- package/src/pipeline/rerank.ts +35 -11
- package/src/pipeline/search.ts +13 -2
- package/src/pipeline/types.ts +5 -3
- package/src/pipeline/vsearch.ts +87 -7
- package/src/sdk/client.ts +47 -3
- package/src/sdk/embed.ts +63 -39
- package/src/serve/background-runtime.ts +1 -1
- package/src/serve/context.ts +41 -56
- package/src/serve/embed-scheduler.ts +58 -35
- package/src/serve/public/components/IndexingProgress.tsx +46 -60
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/shiki-language-ids.ts +14 -0
- package/src/serve/resident-admission.ts +36 -36
- package/src/serve/resident-background-work.ts +20 -2
- package/src/serve/resident-request.ts +11 -5
- package/src/serve/resident-runtime.ts +97 -61
- package/src/serve/resident-shutdown.ts +153 -0
- package/src/serve/routes/api.ts +3 -1
- package/src/serve/server.ts +47 -26
- package/src/store/migrations/028-vector-variants.ts +54 -0
- package/src/store/migrations/029-graph-reference-state.ts +77 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +251 -183
- package/src/store/sqlite/eligibility.ts +174 -0
- package/src/store/sqlite/graph-edge-application.ts +66 -0
- package/src/store/sqlite/graph-reference-state.ts +194 -0
- package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
- package/src/store/types.ts +80 -12
- package/src/store/vector/eligibility.ts +36 -0
- package/src/store/vector/freshness.ts +33 -6
- package/src/store/vector/lazy.ts +81 -0
- package/src/store/vector/sqlite-vec.ts +106 -54
- package/src/store/vector/stats.ts +14 -3
- package/src/store/vector/types.ts +35 -2
- package/src/store/vector/variant-search.ts +192 -0
- package/src/store/vector/variants.ts +451 -0
- package/vendor/converters/markitdown-ts/LICENSE +21 -0
- package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
- package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
- package/vendor/converters/markitdown-ts/package.json +77 -0
- package/vendor/converters/officeparser/LICENSE +21 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
- package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
- package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
- package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
- package/vendor/converters/officeparser/dist/cli.js +381 -0
- package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
- package/vendor/converters/officeparser/dist/defaults.js +218 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
- package/vendor/converters/officeparser/dist/index.d.ts +60 -0
- package/vendor/converters/officeparser/dist/index.js +72 -0
- package/vendor/converters/officeparser/dist/index.mjs +18 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
- package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
- package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
- package/vendor/converters/officeparser/dist/types.js +107 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
- package/vendor/converters/officeparser/package.json +147 -0
- package/vendor/converters/upstream-manifest.json +124 -0
- package/vendor/dependency-fixes/README.md +77 -0
- package/vendor/dependency-fixes/vendor-converters.py +83 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip.sha256 +0 -1
- package/browser-extension/dist/chunk-627emwpj.js +0 -75
- /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { EmbeddingPort } from "../llm/types";
|
|
2
|
+
import type { VectorOwnerInput } from "../store/vector/types";
|
|
3
|
+
import type { VectorVariantStore } from "../store/vector/variants";
|
|
4
|
+
|
|
5
|
+
import { err, ok } from "../store/types";
|
|
6
|
+
import { embedTextsWithRecovery } from "./batch";
|
|
7
|
+
import {
|
|
8
|
+
chunkRetryKey,
|
|
9
|
+
isUpsertLockContention,
|
|
10
|
+
upsertVectorsWithContentionRetry,
|
|
11
|
+
} from "./retry";
|
|
12
|
+
|
|
13
|
+
function isCurrent(
|
|
14
|
+
store: VectorVariantStore,
|
|
15
|
+
owner: VectorOwnerInput
|
|
16
|
+
): boolean {
|
|
17
|
+
const current = store.current(owner.documentId, owner.seq);
|
|
18
|
+
return (
|
|
19
|
+
current?.mirrorHash === owner.mirrorHash &&
|
|
20
|
+
current.inputHash === owner.inputHash &&
|
|
21
|
+
current.formattedInput === owner.formattedInput
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Exact inputs are inferred once; successful current owners checkpoint atomically. */
|
|
26
|
+
export async function embedVariantBatch(params: {
|
|
27
|
+
store: VectorVariantStore;
|
|
28
|
+
embedPort: EmbeddingPort;
|
|
29
|
+
owners: VectorOwnerInput[];
|
|
30
|
+
identityStillCurrent: () => boolean;
|
|
31
|
+
delays?: number[];
|
|
32
|
+
force?: boolean;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
embedded: number;
|
|
35
|
+
errors: number;
|
|
36
|
+
contentionErrors: number;
|
|
37
|
+
retryOwners: VectorOwnerInput[];
|
|
38
|
+
}> {
|
|
39
|
+
const { store, embedPort, identityStillCurrent } = params;
|
|
40
|
+
const empty = {
|
|
41
|
+
embedded: 0,
|
|
42
|
+
errors: 0,
|
|
43
|
+
contentionErrors: 0,
|
|
44
|
+
retryOwners: [] as VectorOwnerInput[],
|
|
45
|
+
};
|
|
46
|
+
if (!identityStillCurrent()) return empty;
|
|
47
|
+
const owners = [
|
|
48
|
+
...new Map(
|
|
49
|
+
params.owners.map((owner) => [chunkRetryKey(owner), owner])
|
|
50
|
+
).values(),
|
|
51
|
+
].filter((owner) => isCurrent(store, owner));
|
|
52
|
+
const inputs = new Map<string, string>();
|
|
53
|
+
const reusable = new Set<string>();
|
|
54
|
+
for (const owner of owners) {
|
|
55
|
+
if (!params.force && store.reusable(owner)) reusable.add(owner.inputHash);
|
|
56
|
+
else inputs.set(owner.inputHash, owner.formattedInput);
|
|
57
|
+
}
|
|
58
|
+
const keys = [...inputs.keys()];
|
|
59
|
+
const result = await embedTextsWithRecovery(embedPort, [...inputs.values()]);
|
|
60
|
+
if (!identityStillCurrent()) return empty;
|
|
61
|
+
const current = owners.filter((owner) => isCurrent(store, owner));
|
|
62
|
+
const vectors = new Map<string, Float32Array>();
|
|
63
|
+
if (result.ok) {
|
|
64
|
+
for (const [index, key] of keys.entries()) {
|
|
65
|
+
const vector = result.value.vectors[index];
|
|
66
|
+
if (vector) vectors.set(key, new Float32Array(vector));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const retryOwners = current.filter(
|
|
70
|
+
(owner) => !reusable.has(owner.inputHash) && !vectors.has(owner.inputHash)
|
|
71
|
+
);
|
|
72
|
+
const rows = current
|
|
73
|
+
.filter(
|
|
74
|
+
(owner) => reusable.has(owner.inputHash) || vectors.has(owner.inputHash)
|
|
75
|
+
)
|
|
76
|
+
.map((owner) => ({ owner, embedding: vectors.get(owner.inputHash) }));
|
|
77
|
+
if (!rows.length) return { ...empty, retryOwners };
|
|
78
|
+
let written = 0;
|
|
79
|
+
const persisted = await upsertVectorsWithContentionRetry(
|
|
80
|
+
{
|
|
81
|
+
upsertVectors: () => {
|
|
82
|
+
try {
|
|
83
|
+
// A contention wait may allow document or runtime mutations. Revalidate each attempt.
|
|
84
|
+
if (!identityStillCurrent()) return Promise.resolve(ok(undefined));
|
|
85
|
+
const valid = rows.filter(({ owner }) => isCurrent(store, owner));
|
|
86
|
+
store.write(valid);
|
|
87
|
+
written = valid.length;
|
|
88
|
+
return Promise.resolve(ok(undefined));
|
|
89
|
+
} catch (cause) {
|
|
90
|
+
return Promise.resolve(
|
|
91
|
+
err(
|
|
92
|
+
"QUERY_FAILED",
|
|
93
|
+
cause instanceof Error ? cause.message : String(cause),
|
|
94
|
+
cause
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
[],
|
|
101
|
+
params.delays
|
|
102
|
+
);
|
|
103
|
+
if (!persisted.ok)
|
|
104
|
+
return {
|
|
105
|
+
...empty,
|
|
106
|
+
retryOwners,
|
|
107
|
+
errors: isUpsertLockContention(persisted.error) ? 0 : rows.length,
|
|
108
|
+
contentionErrors: isUpsertLockContention(persisted.error)
|
|
109
|
+
? rows.length
|
|
110
|
+
: 0,
|
|
111
|
+
};
|
|
112
|
+
return { ...empty, embedded: written, retryOwners };
|
|
113
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/** Incremental graph inventory closure; full projection remains the repair oracle. */
|
|
2
|
+
import type {
|
|
3
|
+
DocEdgeInput,
|
|
4
|
+
DocumentRow,
|
|
5
|
+
GraphReferenceDocument,
|
|
6
|
+
GraphReferenceInventory,
|
|
7
|
+
StorePort,
|
|
8
|
+
} from "../store/types";
|
|
9
|
+
import type { SyncOptions } from "./types";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
isRelationMap,
|
|
13
|
+
normalizeRelationEdgeType,
|
|
14
|
+
normalizeRelationTarget,
|
|
15
|
+
} from "../core/change-diff";
|
|
16
|
+
import {
|
|
17
|
+
normalizeMarkdownPath,
|
|
18
|
+
normalizeWikiName,
|
|
19
|
+
parseTargetParts,
|
|
20
|
+
} from "../core/links";
|
|
21
|
+
import { parseFrontmatter } from "./frontmatter";
|
|
22
|
+
|
|
23
|
+
const VERSION = 1;
|
|
24
|
+
const EDGE_TYPE = /^[a-z][a-z0-9_]*$/;
|
|
25
|
+
type ProjectionError = { relPath: string; code: string; message: string };
|
|
26
|
+
|
|
27
|
+
function identity(doc: DocumentRow): GraphReferenceDocument {
|
|
28
|
+
return {
|
|
29
|
+
documentId: doc.id,
|
|
30
|
+
collection: doc.collection,
|
|
31
|
+
relPath: doc.relPath,
|
|
32
|
+
docid: doc.docid,
|
|
33
|
+
uri: doc.uri,
|
|
34
|
+
title: doc.title,
|
|
35
|
+
mirrorHash: doc.mirrorHash,
|
|
36
|
+
sourceHash: doc.sourceHash,
|
|
37
|
+
contentType: doc.contentType ?? null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Insert once in catalog order: preserves legacy Array.find ambiguity precedence. */
|
|
42
|
+
function relationResolver(docs: GraphReferenceDocument[]) {
|
|
43
|
+
const docids = new Map<string, GraphReferenceDocument>();
|
|
44
|
+
const uris = new Map<string, GraphReferenceDocument>();
|
|
45
|
+
const paths = new Map<string, GraphReferenceDocument>();
|
|
46
|
+
const wiki = new Map<string, GraphReferenceDocument>();
|
|
47
|
+
const localWiki = new Map<string, GraphReferenceDocument>();
|
|
48
|
+
const put = (
|
|
49
|
+
map: Map<string, GraphReferenceDocument>,
|
|
50
|
+
key: string,
|
|
51
|
+
doc: GraphReferenceDocument
|
|
52
|
+
) => {
|
|
53
|
+
if (!map.has(key)) map.set(key, doc);
|
|
54
|
+
};
|
|
55
|
+
for (const doc of docs) {
|
|
56
|
+
put(docids, doc.docid, doc);
|
|
57
|
+
put(uris, doc.uri, doc);
|
|
58
|
+
put(paths, `${doc.collection}/${doc.relPath}`, doc);
|
|
59
|
+
const title = doc.title ?? doc.relPath.split("/").pop() ?? doc.relPath;
|
|
60
|
+
for (const value of [
|
|
61
|
+
title,
|
|
62
|
+
doc.relPath,
|
|
63
|
+
doc.relPath.replace(/\.[^/.]+$/, ""),
|
|
64
|
+
]) {
|
|
65
|
+
const key = normalizeWikiName(value);
|
|
66
|
+
put(wiki, key, doc);
|
|
67
|
+
put(localWiki, `${doc.collection}\0${key}`, doc);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return (
|
|
71
|
+
source: GraphReferenceDocument,
|
|
72
|
+
raw: string
|
|
73
|
+
): GraphReferenceDocument | undefined => {
|
|
74
|
+
const target = normalizeRelationTarget(raw);
|
|
75
|
+
if (!target) return;
|
|
76
|
+
if (target.startsWith("#")) return docids.get(target);
|
|
77
|
+
if (target.startsWith("gno://")) return uris.get(target);
|
|
78
|
+
const parts = parseTargetParts(target);
|
|
79
|
+
if (!parts.ref) return;
|
|
80
|
+
const key = normalizeWikiName(parts.ref);
|
|
81
|
+
if (parts.collection)
|
|
82
|
+
return (
|
|
83
|
+
paths.get(`${parts.collection}/${parts.ref}`) ??
|
|
84
|
+
localWiki.get(`${parts.collection}\0${key}`)
|
|
85
|
+
);
|
|
86
|
+
const relativePath = normalizeMarkdownPath(parts.ref, source.relPath);
|
|
87
|
+
return (
|
|
88
|
+
(relativePath
|
|
89
|
+
? paths.get(`${source.collection}/${relativePath}`)
|
|
90
|
+
: undefined) ??
|
|
91
|
+
paths.get(parts.ref) ??
|
|
92
|
+
localWiki.get(`${source.collection}\0${key}`) ??
|
|
93
|
+
wiki.get(key)
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function projectGraph(
|
|
99
|
+
store: StorePort,
|
|
100
|
+
options: SyncOptions,
|
|
101
|
+
requestedSources?: Set<number>,
|
|
102
|
+
forceFull = false
|
|
103
|
+
): Promise<ProjectionError[]> {
|
|
104
|
+
const errors: ProjectionError[] = [];
|
|
105
|
+
try {
|
|
106
|
+
const graph = store.graphReferenceStore?.();
|
|
107
|
+
let fingerprint = "";
|
|
108
|
+
if (graph) {
|
|
109
|
+
const collections = await store.getCollections();
|
|
110
|
+
if (!collections.ok) throw new Error(collections.error.message);
|
|
111
|
+
fingerprint = new Bun.CryptoHasher("sha256")
|
|
112
|
+
.update(
|
|
113
|
+
JSON.stringify({
|
|
114
|
+
rules: options.contentTypeRules ?? [],
|
|
115
|
+
collections: collections.value
|
|
116
|
+
.map(({ syncedAt: _syncedAt, ...config }) => config)
|
|
117
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
118
|
+
})
|
|
119
|
+
)
|
|
120
|
+
.digest("hex");
|
|
121
|
+
}
|
|
122
|
+
const state = graph?.state(VERSION, fingerprint);
|
|
123
|
+
if (!forceFull && state?.complete) return [];
|
|
124
|
+
const documents = await store.listDocuments();
|
|
125
|
+
if (!documents.ok) throw new Error(documents.error.message);
|
|
126
|
+
const activeDocs = documents.value.filter((doc) => doc.active);
|
|
127
|
+
const current = activeDocs.map(identity);
|
|
128
|
+
const currentById = new Map(current.map((doc) => [doc.documentId, doc]));
|
|
129
|
+
// Read before begin: a new version/config deliberately resets the old inventory.
|
|
130
|
+
const previous = graph?.readInventory() ?? [];
|
|
131
|
+
const previousById = new Map(
|
|
132
|
+
previous.map((row) => [row.document.documentId, row])
|
|
133
|
+
);
|
|
134
|
+
const full =
|
|
135
|
+
forceFull ||
|
|
136
|
+
!graph ||
|
|
137
|
+
!state ||
|
|
138
|
+
state.inProgress ||
|
|
139
|
+
state.version !== VERSION ||
|
|
140
|
+
state.configFingerprint !== fingerprint;
|
|
141
|
+
const resolve = relationResolver(current);
|
|
142
|
+
let selected: Set<number> | undefined;
|
|
143
|
+
if (!full && graph) {
|
|
144
|
+
selected = new Set(requestedSources);
|
|
145
|
+
const changed = new Map<number, GraphReferenceDocument[]>();
|
|
146
|
+
for (const doc of current) {
|
|
147
|
+
const old = previousById.get(doc.documentId)?.document;
|
|
148
|
+
if (!old || JSON.stringify(old) !== JSON.stringify(doc)) {
|
|
149
|
+
changed.set(doc.documentId, old ? [old, doc] : [doc]);
|
|
150
|
+
selected.add(doc.documentId);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const row of previous) {
|
|
154
|
+
if (!currentById.has(row.document.documentId)) {
|
|
155
|
+
changed.set(row.document.documentId, [row.document]);
|
|
156
|
+
selected.add(row.document.documentId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Input mutation without a changed identity can be a direct parsed-link edit.
|
|
160
|
+
// No durable per-source link journal exists; conservatively rebuild in that case.
|
|
161
|
+
if (changed.size === 0 && state.dirty) selected = undefined;
|
|
162
|
+
else {
|
|
163
|
+
for (const id of graph.incomingLinkSources(
|
|
164
|
+
[...changed.values()].flat()
|
|
165
|
+
))
|
|
166
|
+
selected.add(id);
|
|
167
|
+
const resolveOld = relationResolver(
|
|
168
|
+
previous.map((row) => row.document)
|
|
169
|
+
);
|
|
170
|
+
for (const row of previous) {
|
|
171
|
+
const source = currentById.get(row.document.documentId);
|
|
172
|
+
if (!source || selected.has(source.documentId)) continue;
|
|
173
|
+
if (
|
|
174
|
+
row.references.some(
|
|
175
|
+
(ref) =>
|
|
176
|
+
resolveOld(row.document, ref.target)?.documentId !==
|
|
177
|
+
resolve(source, ref.target)?.documentId
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
selected.add(source.documentId);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Persist interruption authority before starting the projection transaction.
|
|
185
|
+
const epoch = graph?.begin(VERSION, fingerprint);
|
|
186
|
+
if (state && epoch !== state.epoch + 1)
|
|
187
|
+
throw new Error("Graph inputs changed while computing affected sources");
|
|
188
|
+
const work = async () => {
|
|
189
|
+
const targets = selected
|
|
190
|
+
? documents.value.filter((doc) => selected.has(doc.id))
|
|
191
|
+
: documents.value;
|
|
192
|
+
// Configured sources get their final sets below. Applying defaults first
|
|
193
|
+
// would delete/recreate an unchanged configured edge on every repair pass.
|
|
194
|
+
const defaults = targets.filter((doc) => {
|
|
195
|
+
const hint = options.contentTypeRules?.find(
|
|
196
|
+
(rule) => rule.id === doc.contentType
|
|
197
|
+
)?.graphHints?.[0];
|
|
198
|
+
return !doc.active || !hint || !EDGE_TYPE.test(hint);
|
|
199
|
+
});
|
|
200
|
+
const backfill = await store.backfillDocEdges(
|
|
201
|
+
defaults.map((doc) => doc.id)
|
|
202
|
+
);
|
|
203
|
+
if (!backfill.ok) throw new Error(backfill.error.message);
|
|
204
|
+
for (const [index, doc] of targets.entries()) {
|
|
205
|
+
if (index > 0 && index % 25 === 0) await Bun.sleep(0);
|
|
206
|
+
if (!doc.active) {
|
|
207
|
+
const cleared = await store.setDocEdges(
|
|
208
|
+
doc.id,
|
|
209
|
+
[],
|
|
210
|
+
"frontmatter-relation"
|
|
211
|
+
);
|
|
212
|
+
if (!cleared.ok)
|
|
213
|
+
errors.push({ relPath: doc.relPath, ...cleared.error });
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const snapshot = currentById.get(doc.id)!;
|
|
217
|
+
const cached = selected ? previousById.get(doc.id) : undefined;
|
|
218
|
+
let references: GraphReferenceInventory["references"];
|
|
219
|
+
if (
|
|
220
|
+
cached &&
|
|
221
|
+
cached.document.mirrorHash === doc.mirrorHash &&
|
|
222
|
+
cached.document.sourceHash === doc.sourceHash
|
|
223
|
+
)
|
|
224
|
+
references = cached.references;
|
|
225
|
+
else {
|
|
226
|
+
references = [];
|
|
227
|
+
if (doc.mirrorHash) {
|
|
228
|
+
const content = await store.getContent(doc.mirrorHash);
|
|
229
|
+
if (!content.ok || content.value === null) {
|
|
230
|
+
errors.push({
|
|
231
|
+
relPath: doc.relPath,
|
|
232
|
+
code: "QUERY_FAILED",
|
|
233
|
+
message: content.ok
|
|
234
|
+
? "Missing graph source content"
|
|
235
|
+
: content.error.message,
|
|
236
|
+
});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const relations = parseFrontmatter(content.value).metadata
|
|
240
|
+
.relations;
|
|
241
|
+
if (isRelationMap(relations)) {
|
|
242
|
+
for (const [rawType, refs] of Object.entries(relations)) {
|
|
243
|
+
const edgeType = normalizeRelationEdgeType(rawType);
|
|
244
|
+
if (EDGE_TYPE.test(edgeType)) {
|
|
245
|
+
for (const target of refs)
|
|
246
|
+
references.push({ edgeType, target });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const relationEdges: DocEdgeInput[] = [];
|
|
253
|
+
for (const ref of references) {
|
|
254
|
+
const target = resolve(snapshot, ref.target);
|
|
255
|
+
if (target)
|
|
256
|
+
relationEdges.push({
|
|
257
|
+
targetDocId: target.documentId,
|
|
258
|
+
edgeType: ref.edgeType,
|
|
259
|
+
confidence: "manual",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const relationsResult = await store.setDocEdges(
|
|
263
|
+
doc.id,
|
|
264
|
+
relationEdges,
|
|
265
|
+
"frontmatter-relation"
|
|
266
|
+
);
|
|
267
|
+
if (!relationsResult.ok)
|
|
268
|
+
errors.push({ relPath: doc.relPath, ...relationsResult.error });
|
|
269
|
+
graph?.writeInventory({ document: snapshot, references });
|
|
270
|
+
const hint = options.contentTypeRules?.find(
|
|
271
|
+
(rule) => rule.id === doc.contentType
|
|
272
|
+
)?.graphHints?.[0];
|
|
273
|
+
if (!hint || !EDGE_TYPE.test(hint)) continue;
|
|
274
|
+
const links = await store.getLinksForDoc(doc.id);
|
|
275
|
+
if (!links.ok) {
|
|
276
|
+
errors.push({ relPath: doc.relPath, ...links.error });
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const relationIds = new Set(
|
|
280
|
+
relationEdges.map((edge) => edge.targetDocId)
|
|
281
|
+
);
|
|
282
|
+
const wikiEdges: DocEdgeInput[] = [];
|
|
283
|
+
const markdownEdges: DocEdgeInput[] = [];
|
|
284
|
+
for (const link of links.value) {
|
|
285
|
+
const ref =
|
|
286
|
+
link.linkType === "markdown"
|
|
287
|
+
? `${doc.collection}/${link.targetRefNorm}`
|
|
288
|
+
: link.targetCollection
|
|
289
|
+
? `${link.targetCollection}:${link.targetRef}`
|
|
290
|
+
: link.targetRefNorm;
|
|
291
|
+
const target = resolve(snapshot, ref);
|
|
292
|
+
if (!target || relationIds.has(target.documentId)) continue;
|
|
293
|
+
const edge: DocEdgeInput = {
|
|
294
|
+
targetDocId: target.documentId,
|
|
295
|
+
edgeType: hint,
|
|
296
|
+
confidence: "configured",
|
|
297
|
+
};
|
|
298
|
+
(link.linkType === "wiki" ? wikiEdges : markdownEdges).push(edge);
|
|
299
|
+
}
|
|
300
|
+
for (const [source, edges] of [
|
|
301
|
+
["wikilink", wikiEdges],
|
|
302
|
+
["markdown-link", markdownEdges],
|
|
303
|
+
] as const) {
|
|
304
|
+
const result = await store.setDocEdges(doc.id, edges, source);
|
|
305
|
+
if (!result.ok)
|
|
306
|
+
errors.push({ relPath: doc.relPath, ...result.error });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (errors.length) throw new Error("Graph projection incomplete");
|
|
310
|
+
if (graph && epoch !== undefined) graph.complete(epoch);
|
|
311
|
+
};
|
|
312
|
+
if (store.withTransaction) {
|
|
313
|
+
const result = await store.withTransaction(work);
|
|
314
|
+
if (!result.ok && errors.length === 0)
|
|
315
|
+
throw new Error(result.error.message);
|
|
316
|
+
} else await work();
|
|
317
|
+
} catch (cause) {
|
|
318
|
+
if (errors.length === 0)
|
|
319
|
+
errors.push({
|
|
320
|
+
relPath: "(typed edge projection)",
|
|
321
|
+
code: "QUERY_FAILED",
|
|
322
|
+
message:
|
|
323
|
+
cause instanceof Error ? cause.message : "Graph projection failed",
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return errors;
|
|
327
|
+
}
|