@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,451 @@
|
|
|
1
|
+
/** Exact-input storage. Synchronous methods throw and compose with DB transactions. */
|
|
2
|
+
import type { Database } from "bun:sqlite";
|
|
3
|
+
|
|
4
|
+
import type { VectorOwnerInput, VectorVariantIdentity } from "./types";
|
|
5
|
+
|
|
6
|
+
import { formatDocForEmbedding } from "../../pipeline/contextual";
|
|
7
|
+
import { decodeEmbedding, encodeEmbedding } from "./sqlite-vec";
|
|
8
|
+
|
|
9
|
+
export function embeddingInputHash(input: string): string {
|
|
10
|
+
return new Bun.CryptoHasher("sha256").update(input).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function vectorVariantFingerprint(
|
|
14
|
+
identity: VectorVariantIdentity
|
|
15
|
+
): string {
|
|
16
|
+
return embeddingInputHash(
|
|
17
|
+
JSON.stringify([
|
|
18
|
+
"embedding-input-v1",
|
|
19
|
+
identity.model,
|
|
20
|
+
identity.modelFingerprint,
|
|
21
|
+
identity.contextSize,
|
|
22
|
+
identity.truncationPolicy,
|
|
23
|
+
])
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface OwnerRow {
|
|
28
|
+
documentId: number;
|
|
29
|
+
mirrorHash: string;
|
|
30
|
+
seq: number;
|
|
31
|
+
text: string;
|
|
32
|
+
title: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface VariantRow {
|
|
36
|
+
variant_id: number;
|
|
37
|
+
input_hash: string;
|
|
38
|
+
embedding: Uint8Array;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class VectorVariantStore {
|
|
42
|
+
readonly partitionId: string;
|
|
43
|
+
readonly tableName: string;
|
|
44
|
+
readonly fingerprint: string;
|
|
45
|
+
readonly identity: Readonly<VectorVariantIdentity>;
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
private readonly db: Database,
|
|
49
|
+
identity: VectorVariantIdentity,
|
|
50
|
+
readonly searchAvailable: boolean
|
|
51
|
+
) {
|
|
52
|
+
if (
|
|
53
|
+
!identity.model ||
|
|
54
|
+
!identity.modelFingerprint ||
|
|
55
|
+
!identity.truncationPolicy ||
|
|
56
|
+
!Number.isSafeInteger(identity.contextSize) ||
|
|
57
|
+
identity.contextSize < 1 ||
|
|
58
|
+
!Number.isSafeInteger(identity.dimensions) ||
|
|
59
|
+
identity.dimensions < 1
|
|
60
|
+
) {
|
|
61
|
+
throw new Error("Invalid embedding variant identity");
|
|
62
|
+
}
|
|
63
|
+
this.identity = Object.freeze({ ...identity });
|
|
64
|
+
this.fingerprint = vectorVariantFingerprint(identity);
|
|
65
|
+
this.partitionId = embeddingInputHash(
|
|
66
|
+
JSON.stringify([identity.model, this.fingerprint, identity.dimensions])
|
|
67
|
+
);
|
|
68
|
+
this.tableName = `vec_v1_${this.partitionId}`;
|
|
69
|
+
db.transaction(() => {
|
|
70
|
+
db.run(
|
|
71
|
+
`INSERT OR IGNORE INTO vector_partitions
|
|
72
|
+
(partition_id, version, model, fingerprint, dimensions) VALUES (?, 1, ?, ?, ?)`,
|
|
73
|
+
[
|
|
74
|
+
this.partitionId,
|
|
75
|
+
identity.model,
|
|
76
|
+
this.fingerprint,
|
|
77
|
+
identity.dimensions,
|
|
78
|
+
]
|
|
79
|
+
);
|
|
80
|
+
if (searchAvailable) {
|
|
81
|
+
if (
|
|
82
|
+
!db
|
|
83
|
+
.query("SELECT 1 FROM sqlite_master WHERE name = ?")
|
|
84
|
+
.get(this.tableName)
|
|
85
|
+
)
|
|
86
|
+
db.run(
|
|
87
|
+
"UPDATE vector_variant_epoch SET epoch = epoch + 1 WHERE id = 1"
|
|
88
|
+
);
|
|
89
|
+
db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS ${this.tableName} USING vec0(
|
|
90
|
+
variant_id INTEGER PRIMARY KEY,
|
|
91
|
+
embedding FLOAT[${identity.dimensions}] distance_metric=cosine
|
|
92
|
+
)`);
|
|
93
|
+
}
|
|
94
|
+
}).immediate();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
epoch(): number {
|
|
98
|
+
return this.db
|
|
99
|
+
.query<{ epoch: number }, []>(
|
|
100
|
+
"SELECT epoch FROM vector_variant_epoch WHERE id = 1"
|
|
101
|
+
)
|
|
102
|
+
.get()!.epoch;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private input(row: OwnerRow): VectorOwnerInput {
|
|
106
|
+
const formattedInput = formatDocForEmbedding(
|
|
107
|
+
row.text,
|
|
108
|
+
row.title ?? undefined,
|
|
109
|
+
this.identity.model
|
|
110
|
+
);
|
|
111
|
+
return {
|
|
112
|
+
documentId: row.documentId,
|
|
113
|
+
mirrorHash: row.mirrorHash,
|
|
114
|
+
seq: row.seq,
|
|
115
|
+
formattedInput,
|
|
116
|
+
inputHash: embeddingInputHash(formattedInput),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Current active input; never accepts historical legacy origin as proof. */
|
|
121
|
+
current(documentId: number, seq: number): VectorOwnerInput | null {
|
|
122
|
+
const row = this.db
|
|
123
|
+
.query<OwnerRow, [number, number]>(`
|
|
124
|
+
SELECT d.id AS documentId, d.mirror_hash AS mirrorHash, c.seq, c.text, d.title
|
|
125
|
+
FROM documents d JOIN content_chunks c ON c.mirror_hash = d.mirror_hash
|
|
126
|
+
WHERE d.id = ? AND c.seq = ? AND d.active = 1
|
|
127
|
+
`)
|
|
128
|
+
.get(documentId, seq);
|
|
129
|
+
return row ? this.input(row) : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private variant(input: VectorOwnerInput): VariantRow | null {
|
|
133
|
+
return this.db
|
|
134
|
+
.query<VariantRow, [string, string]>(`
|
|
135
|
+
SELECT variant_id, input_hash, embedding FROM vector_variants
|
|
136
|
+
WHERE partition_id = ? AND input_hash = ?
|
|
137
|
+
`)
|
|
138
|
+
.get(this.partitionId, input.inputHash);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Returns a proven reusable vector, even before this owner has a binding. */
|
|
142
|
+
reusable(
|
|
143
|
+
input: VectorOwnerInput
|
|
144
|
+
): { variantId: number; embedding: Float32Array } | null {
|
|
145
|
+
this.validate(input);
|
|
146
|
+
const row = this.variant(input);
|
|
147
|
+
return row
|
|
148
|
+
? { variantId: row.variant_id, embedding: decodeEmbedding(row.embedding) }
|
|
149
|
+
: null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private validate(input: VectorOwnerInput): void {
|
|
153
|
+
const current = this.current(input.documentId, input.seq);
|
|
154
|
+
if (
|
|
155
|
+
!current ||
|
|
156
|
+
current.mirrorHash !== input.mirrorHash ||
|
|
157
|
+
current.formattedInput !== input.formattedInput ||
|
|
158
|
+
current.inputHash !== input.inputHash
|
|
159
|
+
) {
|
|
160
|
+
throw new Error("Stale or invalid vector owner input");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private assertWritable(): void {
|
|
165
|
+
if (
|
|
166
|
+
!this.searchAvailable &&
|
|
167
|
+
this.db
|
|
168
|
+
.query<{ name: string }, [string]>(
|
|
169
|
+
"SELECT name FROM sqlite_master WHERE name = ?"
|
|
170
|
+
)
|
|
171
|
+
.get(this.tableName)
|
|
172
|
+
) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"Cannot mutate an existing variant index without sqlite-vec"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Atomic batch: validates current owners, stores variants, materializes vec0, binds owners. */
|
|
180
|
+
write(
|
|
181
|
+
rows: { owner: VectorOwnerInput; embedding?: Float32Array }[]
|
|
182
|
+
): number[] {
|
|
183
|
+
this.assertWritable();
|
|
184
|
+
return this.db
|
|
185
|
+
.transaction(() =>
|
|
186
|
+
rows.map(({ owner, embedding }) => {
|
|
187
|
+
this.validate(owner);
|
|
188
|
+
if (
|
|
189
|
+
embedding &&
|
|
190
|
+
(embedding.length !== this.identity.dimensions ||
|
|
191
|
+
!embedding.every(Number.isFinite))
|
|
192
|
+
)
|
|
193
|
+
throw new Error("Invalid vector dimensions or values");
|
|
194
|
+
let row = this.variant(owner);
|
|
195
|
+
if (!row) {
|
|
196
|
+
if (!embedding)
|
|
197
|
+
throw new Error("Exact input variant needs embedding");
|
|
198
|
+
this.db.run(
|
|
199
|
+
`INSERT INTO vector_variants(partition_id, input_hash, embedding)
|
|
200
|
+
VALUES (?, ?, ?)`,
|
|
201
|
+
[this.partitionId, owner.inputHash, encodeEmbedding(embedding)]
|
|
202
|
+
);
|
|
203
|
+
row = this.variant(owner)!;
|
|
204
|
+
}
|
|
205
|
+
if (embedding && row) {
|
|
206
|
+
this.db.run(
|
|
207
|
+
"UPDATE vector_variants SET embedding = ? WHERE variant_id = ? AND partition_id = ?",
|
|
208
|
+
[encodeEmbedding(embedding), row.variant_id, this.partitionId]
|
|
209
|
+
);
|
|
210
|
+
row = this.variant(owner)!;
|
|
211
|
+
}
|
|
212
|
+
if (this.searchAvailable) {
|
|
213
|
+
this.db.run(`DELETE FROM ${this.tableName} WHERE variant_id = ?`, [
|
|
214
|
+
row.variant_id,
|
|
215
|
+
]);
|
|
216
|
+
this.db.run(
|
|
217
|
+
`INSERT INTO ${this.tableName}(variant_id, embedding) VALUES (?, ?)`,
|
|
218
|
+
[row.variant_id, row.embedding]
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
this.db.run(
|
|
222
|
+
`INSERT INTO vector_owners(document_id, mirror_hash, seq, partition_id, variant_id)
|
|
223
|
+
VALUES (?, ?, ?, ?, ?) ON CONFLICT(document_id, seq, partition_id) DO UPDATE SET
|
|
224
|
+
mirror_hash = excluded.mirror_hash, variant_id = excluded.variant_id`,
|
|
225
|
+
[
|
|
226
|
+
owner.documentId,
|
|
227
|
+
owner.mirrorHash,
|
|
228
|
+
owner.seq,
|
|
229
|
+
this.partitionId,
|
|
230
|
+
row.variant_id,
|
|
231
|
+
]
|
|
232
|
+
);
|
|
233
|
+
return row.variant_id;
|
|
234
|
+
})
|
|
235
|
+
)
|
|
236
|
+
.immediate();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Resolve only correct current active owners, never every document sharing a mirror. */
|
|
240
|
+
owners(variantId: number): VectorOwnerInput[] {
|
|
241
|
+
const variant = this.db
|
|
242
|
+
.query<VariantRow, [string, number]>(`
|
|
243
|
+
SELECT variant_id, input_hash, embedding FROM vector_variants
|
|
244
|
+
WHERE partition_id = ? AND variant_id = ?
|
|
245
|
+
`)
|
|
246
|
+
.get(this.partitionId, variantId);
|
|
247
|
+
if (!variant) return [];
|
|
248
|
+
const rows = this.db
|
|
249
|
+
.query<
|
|
250
|
+
{ document_id: number; seq: number; mirror_hash: string },
|
|
251
|
+
[string, number]
|
|
252
|
+
>(`
|
|
253
|
+
SELECT document_id, seq, mirror_hash FROM vector_owners WHERE partition_id = ? AND variant_id = ?
|
|
254
|
+
`)
|
|
255
|
+
.all(this.partitionId, variantId);
|
|
256
|
+
return rows.flatMap((row) => {
|
|
257
|
+
const current = this.current(row.document_id, row.seq);
|
|
258
|
+
return current &&
|
|
259
|
+
current.mirrorHash === row.mirror_hash &&
|
|
260
|
+
current.inputHash === variant.input_hash
|
|
261
|
+
? [current]
|
|
262
|
+
: [];
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Resumable owner cursor; repeat from start after epoch changes or a completed pass. */
|
|
267
|
+
pending(
|
|
268
|
+
options: {
|
|
269
|
+
limit?: number;
|
|
270
|
+
after?: { documentId: number; seq: number };
|
|
271
|
+
} = {}
|
|
272
|
+
): VectorOwnerInput[] {
|
|
273
|
+
const limit = options.limit ?? 1000;
|
|
274
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
275
|
+
throw new Error("Invalid pending limit");
|
|
276
|
+
const statement = this.db.prepare<OwnerRow, [number, number, number]>(`
|
|
277
|
+
SELECT d.id AS documentId, d.mirror_hash AS mirrorHash, c.seq, c.text, d.title
|
|
278
|
+
FROM documents d JOIN content_chunks c ON c.mirror_hash = d.mirror_hash
|
|
279
|
+
WHERE d.active = 1 AND (d.id > ? OR (d.id = ? AND c.seq > ?)) ORDER BY d.id, c.seq
|
|
280
|
+
`);
|
|
281
|
+
const rows = statement.iterate(
|
|
282
|
+
options.after?.documentId ?? -1,
|
|
283
|
+
options.after?.documentId ?? -1,
|
|
284
|
+
options.after?.seq ?? -1
|
|
285
|
+
);
|
|
286
|
+
const pending: VectorOwnerInput[] = [];
|
|
287
|
+
try {
|
|
288
|
+
for (const row of rows) {
|
|
289
|
+
const input = this.input(row);
|
|
290
|
+
const binding = this.db
|
|
291
|
+
.query<
|
|
292
|
+
{ input_hash: string; mirror_hash: string },
|
|
293
|
+
[number, number, string]
|
|
294
|
+
>(`
|
|
295
|
+
SELECT v.input_hash, o.mirror_hash FROM vector_owners o
|
|
296
|
+
JOIN vector_variants v ON v.variant_id = o.variant_id AND v.partition_id = o.partition_id
|
|
297
|
+
WHERE o.document_id = ? AND o.seq = ? AND o.partition_id = ?
|
|
298
|
+
`)
|
|
299
|
+
.get(input.documentId, input.seq, this.partitionId);
|
|
300
|
+
if (
|
|
301
|
+
binding?.input_hash !== input.inputHash ||
|
|
302
|
+
binding.mirror_hash !== input.mirrorHash
|
|
303
|
+
)
|
|
304
|
+
pending.push(input);
|
|
305
|
+
if (pending.length === limit) break;
|
|
306
|
+
}
|
|
307
|
+
} finally {
|
|
308
|
+
// Early cursor exit must not leave a cached Bun statement mid-iteration.
|
|
309
|
+
statement.finalize();
|
|
310
|
+
}
|
|
311
|
+
return pending;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Activation is fenced by a caller-observed epoch and complete current coverage. */
|
|
315
|
+
activate(expectedEpoch: number): void {
|
|
316
|
+
this.db
|
|
317
|
+
.transaction(() => {
|
|
318
|
+
if (this.epoch() !== expectedEpoch)
|
|
319
|
+
throw new Error("Variant mutation epoch changed");
|
|
320
|
+
if (this.pending({ limit: 1 }).length)
|
|
321
|
+
throw new Error("Variant coverage incomplete");
|
|
322
|
+
if (!this.searchAvailable) throw new Error("Variant index unavailable");
|
|
323
|
+
const mismatch = this.db
|
|
324
|
+
.query<{ count: number }, [string]>(`
|
|
325
|
+
SELECT count(*) AS count FROM vector_variants v LEFT JOIN ${this.tableName} x
|
|
326
|
+
ON x.variant_id = v.variant_id WHERE v.partition_id = ?
|
|
327
|
+
AND (x.variant_id IS NULL OR x.embedding != v.embedding)
|
|
328
|
+
`)
|
|
329
|
+
.get(this.partitionId)!.count;
|
|
330
|
+
const orphans = this.db
|
|
331
|
+
.query<{ count: number }, [string]>(`
|
|
332
|
+
SELECT count(*) AS count FROM ${this.tableName} x WHERE NOT EXISTS
|
|
333
|
+
(SELECT 1 FROM vector_variants v WHERE v.variant_id = x.variant_id AND v.partition_id = ?)
|
|
334
|
+
`)
|
|
335
|
+
.get(this.partitionId)!.count;
|
|
336
|
+
if (mismatch || orphans) throw new Error("Variant index inconsistent");
|
|
337
|
+
this.db.run(
|
|
338
|
+
`UPDATE vector_partitions SET state = 'active', activated_epoch = ? WHERE partition_id = ?`,
|
|
339
|
+
[expectedEpoch, this.partitionId]
|
|
340
|
+
);
|
|
341
|
+
})
|
|
342
|
+
.immediate();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Durable authority after initial promotion, independent of later mutations.
|
|
347
|
+
* Retrieval uses this marker plus current owner validation; an incomplete
|
|
348
|
+
* epoch must never force valid owners back to legacy vectors. Availability
|
|
349
|
+
* remains a separate capability, not permission to change authority.
|
|
350
|
+
*/
|
|
351
|
+
hasActivated(): boolean {
|
|
352
|
+
return !!this.db
|
|
353
|
+
.query<{ active: number }, [string]>(`
|
|
354
|
+
SELECT 1 AS active FROM vector_partitions WHERE partition_id = ?
|
|
355
|
+
AND state = 'active' AND activated_epoch IS NOT NULL
|
|
356
|
+
`)
|
|
357
|
+
.get(this.partitionId);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Current-epoch completeness receipt only; never a blanket retrieval gate. */
|
|
361
|
+
isActive(): boolean {
|
|
362
|
+
if (!this.searchAvailable) return false;
|
|
363
|
+
return !!this.db
|
|
364
|
+
.query<{ active: number }, [string]>(`
|
|
365
|
+
SELECT 1 AS active FROM vector_partitions WHERE partition_id = ?
|
|
366
|
+
AND state = 'active' AND activated_epoch = (SELECT epoch FROM vector_variant_epoch WHERE id = 1)
|
|
367
|
+
`)
|
|
368
|
+
.get(this.partitionId);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Repair materialization after storage-only backfill; never changes legacy authority. */
|
|
372
|
+
syncIndex(): void {
|
|
373
|
+
if (!this.searchAvailable) throw new Error("Variant index unavailable");
|
|
374
|
+
this.db
|
|
375
|
+
.transaction(() => {
|
|
376
|
+
this.db.exec(`DELETE FROM ${this.tableName}`);
|
|
377
|
+
const rows = this.db
|
|
378
|
+
.query<VariantRow, [string]>(
|
|
379
|
+
"SELECT variant_id, input_hash, embedding FROM vector_variants WHERE partition_id = ?"
|
|
380
|
+
)
|
|
381
|
+
.iterate(this.partitionId);
|
|
382
|
+
for (const row of rows) {
|
|
383
|
+
this.db.run(
|
|
384
|
+
`INSERT INTO ${this.tableName}(variant_id, embedding) VALUES (?, ?)`,
|
|
385
|
+
[row.variant_id, row.embedding]
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
.immediate();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Explicit owner release and last-valid-owner GC share the vec0 transaction. */
|
|
393
|
+
release(documentId: number): void {
|
|
394
|
+
this.assertWritable();
|
|
395
|
+
this.db
|
|
396
|
+
.transaction(() => {
|
|
397
|
+
this.db.run(
|
|
398
|
+
"DELETE FROM vector_owners WHERE document_id = ? AND partition_id = ?",
|
|
399
|
+
[documentId, this.partitionId]
|
|
400
|
+
);
|
|
401
|
+
this.collectGarbage();
|
|
402
|
+
})
|
|
403
|
+
.immediate();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
collectGarbage(): number {
|
|
407
|
+
this.assertWritable();
|
|
408
|
+
return this.db
|
|
409
|
+
.transaction(() => {
|
|
410
|
+
let removed = 0;
|
|
411
|
+
const rows = this.db
|
|
412
|
+
.query<{ variant_id: number }, [string]>(
|
|
413
|
+
"SELECT variant_id FROM vector_variants WHERE partition_id = ?"
|
|
414
|
+
)
|
|
415
|
+
.all(this.partitionId);
|
|
416
|
+
for (const { variant_id: id } of rows) {
|
|
417
|
+
if (this.owners(id).length) continue;
|
|
418
|
+
if (this.searchAvailable)
|
|
419
|
+
this.db.run(`DELETE FROM ${this.tableName} WHERE variant_id = ?`, [
|
|
420
|
+
id,
|
|
421
|
+
]);
|
|
422
|
+
this.db.run(
|
|
423
|
+
"DELETE FROM vector_owners WHERE partition_id = ? AND variant_id = ?",
|
|
424
|
+
[this.partitionId, id]
|
|
425
|
+
);
|
|
426
|
+
this.db.run(
|
|
427
|
+
"DELETE FROM vector_variants WHERE partition_id = ? AND variant_id = ?",
|
|
428
|
+
[this.partitionId, id]
|
|
429
|
+
);
|
|
430
|
+
removed++;
|
|
431
|
+
}
|
|
432
|
+
return removed;
|
|
433
|
+
})
|
|
434
|
+
.immediate();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export async function createVectorVariantStore(
|
|
439
|
+
db: Database,
|
|
440
|
+
identity: VectorVariantIdentity
|
|
441
|
+
): Promise<VectorVariantStore> {
|
|
442
|
+
let searchAvailable = false;
|
|
443
|
+
try {
|
|
444
|
+
const sqliteVec = await import("sqlite-vec");
|
|
445
|
+
sqliteVec.load(db);
|
|
446
|
+
searchAvailable = true;
|
|
447
|
+
} catch {
|
|
448
|
+
// Storage remains usable offline; activation waits for a materialized index.
|
|
449
|
+
}
|
|
450
|
+
return new VectorVariantStore(db, identity, searchAvailable);
|
|
451
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Vaibhav Raj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|