@dikolab/kbdb 0.2.2 → 0.3.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 +50 -15
- package/dist/cli.cjs +461 -72
- package/dist/cli.cjs.map +4 -4
- package/dist/cli.mjs +453 -64
- package/dist/cli.mjs.map +4 -4
- package/dist/kbdb-worker.cjs +395 -60
- package/dist/kbdb-worker.cjs.map +4 -4
- package/dist/mod.cjs +17 -0
- package/dist/mod.cjs.map +2 -2
- package/dist/mod.mjs +17 -0
- package/dist/mod.mjs.map +2 -2
- package/dist/src/shared/cli/functions/dispatch-command.function.d.ts +3 -1
- package/dist/src/shared/cli/functions/install-default-skills.function.d.ts +12 -0
- package/dist/src/shared/cli/functions/run-check.function.d.ts +47 -5
- package/dist/src/shared/cli/functions/run-export.function.d.ts +34 -0
- package/dist/src/shared/cli/functions/run-gc.function.d.ts +6 -0
- package/dist/src/shared/cli/functions/run-learn.function.d.ts +35 -6
- package/dist/src/shared/cli/functions/run-recall.function.d.ts +45 -0
- package/dist/src/shared/cli/functions/run-search.function.d.ts +52 -15
- package/dist/src/shared/cli/index.d.ts +4 -0
- package/dist/src/shared/cli/typings/cli-options.interface.d.ts +13 -0
- package/dist/src/shared/cli/typings/learn-result.model.d.ts +19 -0
- package/dist/src/shared/cli/typings/search-display.model.d.ts +18 -0
- package/dist/src/shared/mcp/classes/mcp-server.class.d.ts +8 -0
- package/dist/src/shared/mcp/typings/mcp-client.interface.d.ts +43 -0
- package/dist/src/shared/wasm-codec/functions/decode-add-section-result.function.d.ts +48 -0
- package/dist/src/shared/wasm-codec/functions/decode-scored-results.function.d.ts +58 -0
- package/dist/src/shared/wasm-codec/functions/decode-section-records.function.d.ts +33 -0
- package/dist/src/shared/wasm-codec/functions/encode-add-section-params.function.d.ts +52 -0
- package/dist/src/shared/wasm-codec/index.d.ts +2 -0
- package/dist/src/shared/worker-client/classes/worker-client.class.d.ts +16 -0
- package/dist/src/shared/worker-client/index.d.ts +1 -1
- package/dist/src/shared/worker-client/typings/add-section-result.model.d.ts +29 -15
- package/dist/src/shared/worker-client/typings/export-result.model.d.ts +14 -0
- package/dist/src/shared/worker-client/typings/gc-result.model.d.ts +6 -0
- package/dist/src/shared/worker-client/typings/search-result.model.d.ts +12 -0
- package/dist/src/shared/worker-client/typings/section.model.d.ts +16 -0
- package/dist/src/shared/worker-daemon/classes/worker-daemon.class.d.ts +35 -0
- package/dist/wasm/fs-database/kbdb_fs_database.d.ts +22 -1
- package/dist/wasm/fs-database/kbdb_fs_database.js +28 -1
- package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
- package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm.d.ts +1 -0
- package/dist/wasm/kb-worker/kbdb_worker.d.ts +48 -1
- package/dist/wasm/kb-worker/kbdb_worker.js +60 -1
- package/dist/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
- package/dist/wasm/kb-worker/kbdb_worker_bg.wasm.d.ts +2 -0
- package/dist/wasm/query-parser/kbdb_query_parser_bg.wasm +0 -0
- package/dist/worker.mjs +396 -60
- package/dist/worker.mjs.map +4 -4
- package/docs/details/cli.md +102 -15
- package/docs/details/library-api.md +55 -6
- package/docs/details/mcp-server.md +8 -4
- package/docs/details/search-and-ranking.md +63 -5
- package/docs/details/storage.md +2 -0
- package/docs/goals/cli.md +255 -14
- package/docs/goals/database.md +244 -17
- package/docs/goals/document.md +1 -1
- package/docs/goals/mcp.md +83 -14
- package/docs/goals/query-parser.md +98 -4
- package/docs/goals/query-result.md +177 -10
- package/docs/goals/skills.md +72 -0
- package/docs/goals/worker-daemon.md +54 -7
- package/docs/modules/rust/fs-database/functions/integrity.md +11 -0
- package/docs/modules/rust/fs-database/functions/section.md +23 -6
- package/docs/modules/rust/fs-database/module.md +1 -0
- package/docs/modules/rust/fs-database/types/section.md +27 -1
- package/docs/modules/rust/overview.md +2 -1
- package/docs/modules/rust/query-parser/functions/module.md +2 -2
- package/docs/modules/rust/query-parser/functions/pipeline.md +44 -6
- package/docs/modules/rust/query-parser/module.md +5 -4
- package/docs/modules/rust/query-parser/types/keyphrase.md +11 -0
- package/docs/modules/rust/query-parser/types/module.md +3 -3
- package/docs/modules/typescript/shared/cli/functions.md +68 -3
- package/package.json +1 -1
- package/src/shared/cli/functions/dispatch-command.function.ts +53 -1
- package/src/shared/cli/functions/format-output.function.ts +36 -12
- package/src/shared/cli/functions/generate-help.function.ts +56 -1
- package/src/shared/cli/functions/install-default-skills.function.ts +296 -0
- package/src/shared/cli/functions/parse-args.function.ts +13 -0
- package/src/shared/cli/functions/run-check.function.ts +87 -6
- package/src/shared/cli/functions/run-export.function.ts +40 -0
- package/src/shared/cli/functions/run-gc.function.ts +6 -0
- package/src/shared/cli/functions/run-init.function.ts +2 -0
- package/src/shared/cli/functions/run-learn.function.ts +68 -16
- package/src/shared/cli/functions/run-recall.function.ts +66 -0
- package/src/shared/cli/functions/run-search.function.ts +58 -15
- package/src/shared/cli/index.ts +4 -0
- package/src/shared/cli/typings/cli-options.interface.ts +13 -0
- package/src/shared/cli/typings/learn-result.model.ts +16 -0
- package/src/shared/cli/typings/search-display.model.ts +18 -0
- package/src/shared/mcp/classes/mcp-server.class.ts +30 -1
- package/src/shared/mcp/functions/create-tool-definitions.function.ts +46 -5
- package/src/shared/mcp/functions/handle-tool-call.function.ts +70 -2
- package/src/shared/mcp/typings/mcp-client.interface.ts +45 -0
- package/src/shared/wasm-codec/functions/decode-add-section-result.function.ts +112 -0
- package/src/shared/wasm-codec/functions/decode-scored-results.function.ts +86 -0
- package/src/shared/wasm-codec/functions/decode-section-records.function.ts +87 -0
- package/src/shared/wasm-codec/functions/encode-add-section-params.function.ts +68 -0
- package/src/shared/wasm-codec/index.ts +5 -0
- package/src/shared/worker-client/classes/worker-client.class.ts +19 -0
- package/src/shared/worker-client/index.ts +4 -1
- package/src/shared/worker-client/typings/add-section-result.model.ts +30 -15
- package/src/shared/worker-client/typings/export-result.model.ts +14 -0
- package/src/shared/worker-client/typings/gc-result.model.ts +6 -0
- package/src/shared/worker-client/typings/search-result.model.ts +12 -0
- package/src/shared/worker-client/typings/section.model.ts +16 -0
- package/src/shared/worker-daemon/classes/worker-daemon.class.ts +315 -46
- package/src/shared/worker-daemon/functions/create-host-imports.function.ts +19 -4
- package/src/shared/worker-daemon/functions/create-ipc-server.function.ts +5 -1
- package/src/wasm/fs-database/kbdb_fs_database.d.ts +22 -1
- package/src/wasm/fs-database/kbdb_fs_database.js +28 -1
- package/src/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
- package/src/wasm/fs-database/kbdb_fs_database_bg.wasm.d.ts +1 -0
- package/src/wasm/kb-worker/kbdb_worker.d.ts +48 -1
- package/src/wasm/kb-worker/kbdb_worker.js +60 -1
- package/src/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
- package/src/wasm/kb-worker/kbdb_worker_bg.wasm.d.ts +2 -0
- package/src/wasm/query-parser/kbdb_query_parser_bg.wasm +0 -0
package/dist/worker.mjs
CHANGED
|
@@ -85,6 +85,7 @@ import { tmpdir as tmpdir2 } from "node:os";
|
|
|
85
85
|
import {
|
|
86
86
|
existsSync as existsSync2,
|
|
87
87
|
readFileSync as readFileSync4,
|
|
88
|
+
readdirSync as readdirSync2,
|
|
88
89
|
accessSync,
|
|
89
90
|
writeFileSync as writeFileSync2,
|
|
90
91
|
unlinkSync as unlinkSync2,
|
|
@@ -135,8 +136,9 @@ function createHostImports(contextPath, memoryRef = nullMemory, fs = nodeFsAdapt
|
|
|
135
136
|
}
|
|
136
137
|
function readPathArg(ptr, len) {
|
|
137
138
|
const bytes = new Uint8Array(getBuffer(), ptr, len);
|
|
138
|
-
const
|
|
139
|
-
|
|
139
|
+
const decoded = new TextDecoder().decode(bytes);
|
|
140
|
+
if (decoded.startsWith("/")) return decoded;
|
|
141
|
+
return join2(contextPath, decoded);
|
|
140
142
|
}
|
|
141
143
|
return {
|
|
142
144
|
/**
|
|
@@ -156,14 +158,15 @@ function createHostImports(contextPath, memoryRef = nullMemory, fs = nodeFsAdapt
|
|
|
156
158
|
const filePath = readPathArg(pathPtr, pathLen);
|
|
157
159
|
const data = fs.readFileSync(filePath);
|
|
158
160
|
const dataLen = data.byteLength;
|
|
159
|
-
const dv = new DataView(getBuffer());
|
|
160
161
|
if (dataLen === 0) {
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
const dv2 = new DataView(getBuffer());
|
|
163
|
+
dv2.setUint32(outPtr, 0, true);
|
|
164
|
+
dv2.setUint32(outLen, 0, true);
|
|
163
165
|
return 0;
|
|
164
166
|
}
|
|
165
167
|
const dataPtr = allocator.alloc(dataLen);
|
|
166
168
|
if (dataPtr === 0) return -1;
|
|
169
|
+
const dv = new DataView(getBuffer());
|
|
167
170
|
const memView = new Uint8Array(getBuffer());
|
|
168
171
|
memView.set(
|
|
169
172
|
new Uint8Array(
|
|
@@ -241,14 +244,15 @@ function createHostImports(contextPath, memoryRef = nullMemory, fs = nodeFsAdapt
|
|
|
241
244
|
const entries = fs.readdirSync(dirPath);
|
|
242
245
|
const text = entries.join("\n");
|
|
243
246
|
const encoded = new TextEncoder().encode(text);
|
|
244
|
-
const dv = new DataView(getBuffer());
|
|
245
247
|
if (encoded.length === 0) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
+
const dv2 = new DataView(getBuffer());
|
|
249
|
+
dv2.setUint32(outPtr, 0, true);
|
|
250
|
+
dv2.setUint32(outLen, 0, true);
|
|
248
251
|
return 0;
|
|
249
252
|
}
|
|
250
253
|
const dataPtr = allocator.alloc(encoded.length);
|
|
251
254
|
if (dataPtr === 0) return -1;
|
|
255
|
+
const dv = new DataView(getBuffer());
|
|
252
256
|
new Uint8Array(getBuffer(), dataPtr, encoded.length).set(
|
|
253
257
|
encoded
|
|
254
258
|
);
|
|
@@ -352,13 +356,16 @@ import { createServer } from "node:net";
|
|
|
352
356
|
function createIpcServer(socketPath, handler) {
|
|
353
357
|
const server = createServer((socket) => {
|
|
354
358
|
let buffer = "";
|
|
359
|
+
let processingQueue = Promise.resolve();
|
|
355
360
|
socket.on("data", (chunk) => {
|
|
356
361
|
buffer += chunk.toString();
|
|
357
362
|
const lines = buffer.split("\n");
|
|
358
363
|
buffer = lines.pop() ?? "";
|
|
359
364
|
for (const line of lines) {
|
|
360
365
|
if (!line.trim()) continue;
|
|
361
|
-
|
|
366
|
+
processingQueue = processingQueue.then(
|
|
367
|
+
() => handleLine(line, handler, socket)
|
|
368
|
+
);
|
|
362
369
|
}
|
|
363
370
|
});
|
|
364
371
|
socket.on("error", (err) => {
|
|
@@ -439,13 +446,27 @@ function encodeSectionType(kind, mimeType) {
|
|
|
439
446
|
|
|
440
447
|
// src/shared/wasm-codec/functions/encode-add-section-params.function.ts
|
|
441
448
|
function encodeAddSectionParams(params) {
|
|
449
|
+
const tags = params.tags ?? [];
|
|
450
|
+
const tagParts = [];
|
|
451
|
+
for (const tag of tags) {
|
|
452
|
+
tagParts.push(encodeString(tag));
|
|
453
|
+
}
|
|
454
|
+
const tagsCountBytes = encodeU32(tags.length);
|
|
455
|
+
const replaceByte = new Uint8Array([
|
|
456
|
+
params.replace === true ? 1 : 0
|
|
457
|
+
]);
|
|
442
458
|
const parts = [
|
|
443
459
|
encodeSectionType(params.sectionType, params.mimeType),
|
|
444
460
|
encodeString(params.content),
|
|
445
461
|
encodeOptionString(params.title),
|
|
446
462
|
encodeOptionString(params.description),
|
|
447
463
|
encodeOptionString(params.docid),
|
|
448
|
-
encodeOptionString(params.typeName)
|
|
464
|
+
encodeOptionString(params.typeName),
|
|
465
|
+
encodeOptionString(params.sourcePath),
|
|
466
|
+
encodeOptionString(params.sourceMtime),
|
|
467
|
+
tagsCountBytes,
|
|
468
|
+
...tagParts,
|
|
469
|
+
replaceByte
|
|
449
470
|
];
|
|
450
471
|
const totalLen = parts.reduce((sum, p) => sum + p.length, 0);
|
|
451
472
|
const result = new Uint8Array(totalLen);
|
|
@@ -602,6 +623,10 @@ function decodePagedSearchResult(bytes) {
|
|
|
602
623
|
offset += limit.bytesRead;
|
|
603
624
|
const hasMore = bytes[offset] === 1;
|
|
604
625
|
offset += 1;
|
|
626
|
+
const relaxed = offset < bytes.length && bytes[offset] === 1;
|
|
627
|
+
if (offset < bytes.length) {
|
|
628
|
+
offset += 1;
|
|
629
|
+
}
|
|
605
630
|
const itemCount = decodeU32(bytes, offset);
|
|
606
631
|
offset += itemCount.bytesRead;
|
|
607
632
|
const items = [];
|
|
@@ -622,6 +647,8 @@ function decodePagedSearchResult(bytes) {
|
|
|
622
647
|
}
|
|
623
648
|
const score = decodeF32(bytes, offset);
|
|
624
649
|
offset += score.bytesRead;
|
|
650
|
+
const confidence = decodeF32(bytes, offset);
|
|
651
|
+
offset += confidence.bytesRead;
|
|
625
652
|
const snippet = decodeString(bytes, offset);
|
|
626
653
|
offset += snippet.bytesRead;
|
|
627
654
|
const termCount = decodeU32(bytes, offset);
|
|
@@ -632,14 +659,27 @@ function decodePagedSearchResult(bytes) {
|
|
|
632
659
|
offset += term.bytesRead;
|
|
633
660
|
matched_terms.push(term.value);
|
|
634
661
|
}
|
|
662
|
+
const fieldCount = decodeU32(bytes, offset);
|
|
663
|
+
offset += fieldCount.bytesRead;
|
|
664
|
+
const matched_fields = [];
|
|
665
|
+
for (let j = 0; j < fieldCount.value; j++) {
|
|
666
|
+
const field = decodeString(bytes, offset);
|
|
667
|
+
offset += field.bytesRead;
|
|
668
|
+
matched_fields.push(field.value);
|
|
669
|
+
}
|
|
670
|
+
const createdAt = decodeString(bytes, offset);
|
|
671
|
+
offset += createdAt.bytesRead;
|
|
635
672
|
items.push({
|
|
636
673
|
kbid: kbid.value,
|
|
637
674
|
heading: heading.value,
|
|
638
675
|
type: sectionType.value,
|
|
639
676
|
docids,
|
|
640
677
|
score: score.value,
|
|
678
|
+
confidence: confidence.value,
|
|
641
679
|
snippet: snippet.value,
|
|
642
|
-
matched_terms
|
|
680
|
+
matched_terms,
|
|
681
|
+
matched_fields,
|
|
682
|
+
created_at: createdAt.value
|
|
643
683
|
});
|
|
644
684
|
}
|
|
645
685
|
return {
|
|
@@ -647,7 +687,8 @@ function decodePagedSearchResult(bytes) {
|
|
|
647
687
|
total: total.value,
|
|
648
688
|
offset: resultOffset.value,
|
|
649
689
|
limit: limit.value,
|
|
650
|
-
has_more: hasMore
|
|
690
|
+
has_more: hasMore,
|
|
691
|
+
relaxed
|
|
651
692
|
};
|
|
652
693
|
}
|
|
653
694
|
|
|
@@ -707,6 +748,47 @@ function decodeSectionRecords(bytes) {
|
|
|
707
748
|
offset += codeTokenCount.bytesRead;
|
|
708
749
|
const typeName = decodeString(bytes, offset);
|
|
709
750
|
offset += typeName.bytesRead;
|
|
751
|
+
let sourceKey = null;
|
|
752
|
+
if (offset < bytes.length) {
|
|
753
|
+
const v = decodeOptionString2(bytes, offset);
|
|
754
|
+
offset += v.bytesRead;
|
|
755
|
+
sourceKey = v.value;
|
|
756
|
+
}
|
|
757
|
+
let sourcePath = null;
|
|
758
|
+
if (offset < bytes.length) {
|
|
759
|
+
const v = decodeOptionString2(bytes, offset);
|
|
760
|
+
offset += v.bytesRead;
|
|
761
|
+
sourcePath = v.value;
|
|
762
|
+
}
|
|
763
|
+
let sourceMtime = null;
|
|
764
|
+
if (offset < bytes.length) {
|
|
765
|
+
const v = decodeOptionString2(bytes, offset);
|
|
766
|
+
offset += v.bytesRead;
|
|
767
|
+
sourceMtime = v.value;
|
|
768
|
+
}
|
|
769
|
+
let updatedAt = null;
|
|
770
|
+
if (offset < bytes.length) {
|
|
771
|
+
const v = decodeOptionString2(bytes, offset);
|
|
772
|
+
offset += v.bytesRead;
|
|
773
|
+
updatedAt = v.value;
|
|
774
|
+
}
|
|
775
|
+
let tags = [];
|
|
776
|
+
if (offset < bytes.length) {
|
|
777
|
+
const tagCount = decodeU32(bytes, offset);
|
|
778
|
+
offset += tagCount.bytesRead;
|
|
779
|
+
tags = [];
|
|
780
|
+
for (let j = 0; j < tagCount.value; j++) {
|
|
781
|
+
const tag = decodeString(bytes, offset);
|
|
782
|
+
offset += tag.bytesRead;
|
|
783
|
+
tags.push(tag.value);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
let supersedes = null;
|
|
787
|
+
if (offset < bytes.length) {
|
|
788
|
+
const v = decodeOptionString2(bytes, offset);
|
|
789
|
+
offset += v.bytesRead;
|
|
790
|
+
supersedes = v.value;
|
|
791
|
+
}
|
|
710
792
|
const record = {
|
|
711
793
|
kbid: kbid.value,
|
|
712
794
|
docid: docid.value,
|
|
@@ -721,7 +803,13 @@ function decodeSectionRecords(bytes) {
|
|
|
721
803
|
headingTokenCount: headingTokenCount.value,
|
|
722
804
|
bodyTokenCount: bodyTokenCount.value,
|
|
723
805
|
codeTokenCount: codeTokenCount.value,
|
|
724
|
-
typeName: typeName.value
|
|
806
|
+
typeName: typeName.value,
|
|
807
|
+
sourceKey,
|
|
808
|
+
sourcePath,
|
|
809
|
+
sourceMtime,
|
|
810
|
+
updatedAt,
|
|
811
|
+
tags,
|
|
812
|
+
supersedes
|
|
725
813
|
};
|
|
726
814
|
if (st.mimeType !== void 0) {
|
|
727
815
|
record.mimeType = st.mimeType;
|
|
@@ -1041,6 +1129,54 @@ function decodeOneResult(bytes, startOffset) {
|
|
|
1041
1129
|
};
|
|
1042
1130
|
}
|
|
1043
1131
|
|
|
1132
|
+
// src/shared/wasm-codec/functions/decode-add-section-result.function.ts
|
|
1133
|
+
function decodeAddSectionResult(bytes) {
|
|
1134
|
+
let offset = 0;
|
|
1135
|
+
const kbid = decodeString(bytes, offset);
|
|
1136
|
+
offset += kbid.bytesRead;
|
|
1137
|
+
const indexedTermsCount = decodeU32(bytes, offset);
|
|
1138
|
+
offset += indexedTermsCount.bytesRead;
|
|
1139
|
+
const elapsedMsBig = decodeU64(bytes, offset);
|
|
1140
|
+
offset += elapsedMsBig.bytesRead;
|
|
1141
|
+
const supersededTag = bytes[offset];
|
|
1142
|
+
offset += 1;
|
|
1143
|
+
let superseded = null;
|
|
1144
|
+
if (supersededTag === 1) {
|
|
1145
|
+
const supersededStr = decodeString(bytes, offset);
|
|
1146
|
+
offset += supersededStr.bytesRead;
|
|
1147
|
+
superseded = supersededStr.value;
|
|
1148
|
+
}
|
|
1149
|
+
const warningCount = decodeU32(bytes, offset);
|
|
1150
|
+
offset += warningCount.bytesRead;
|
|
1151
|
+
const warnings = [];
|
|
1152
|
+
for (let i = 0; i < warningCount.value; i++) {
|
|
1153
|
+
const warning = decodeString(bytes, offset);
|
|
1154
|
+
offset += warning.bytesRead;
|
|
1155
|
+
warnings.push(warning.value);
|
|
1156
|
+
}
|
|
1157
|
+
const dupCount = decodeU32(bytes, offset);
|
|
1158
|
+
offset += dupCount.bytesRead;
|
|
1159
|
+
const nearDuplicates = [];
|
|
1160
|
+
for (let i = 0; i < dupCount.value; i++) {
|
|
1161
|
+
const dupKbid = decodeString(bytes, offset);
|
|
1162
|
+
offset += dupKbid.bytesRead;
|
|
1163
|
+
const similarity = decodeF32(bytes, offset);
|
|
1164
|
+
offset += similarity.bytesRead;
|
|
1165
|
+
nearDuplicates.push({
|
|
1166
|
+
kbid: dupKbid.value,
|
|
1167
|
+
similarity: similarity.value
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
kbid: kbid.value,
|
|
1172
|
+
indexedTermsCount: indexedTermsCount.value,
|
|
1173
|
+
elapsedMs: Number(elapsedMsBig.value),
|
|
1174
|
+
superseded,
|
|
1175
|
+
warnings,
|
|
1176
|
+
nearDuplicates
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1044
1180
|
// src/shared/worker-daemon/classes/worker-daemon.class.ts
|
|
1045
1181
|
var WASM_METHOD_MAP = {
|
|
1046
1182
|
search: "worker_search",
|
|
@@ -1080,7 +1216,9 @@ var WASM_METHOD_MAP = {
|
|
|
1080
1216
|
list_by_type: "worker_list_by_type",
|
|
1081
1217
|
listDocumentsByType: "worker_list_docs_by_type",
|
|
1082
1218
|
list_docs_by_type: "worker_list_docs_by_type",
|
|
1083
|
-
recall: "worker_recall"
|
|
1219
|
+
recall: "worker_recall",
|
|
1220
|
+
export: "worker_export",
|
|
1221
|
+
exportDb: "worker_export"
|
|
1084
1222
|
};
|
|
1085
1223
|
var CONFIG_PREFIX = "config_";
|
|
1086
1224
|
var CONFIG_KEY_MAP = {
|
|
@@ -1115,6 +1253,14 @@ var WorkerDaemon = class {
|
|
|
1115
1253
|
_idleTimer = null;
|
|
1116
1254
|
_server = null;
|
|
1117
1255
|
_wasmDir;
|
|
1256
|
+
/** Queue of waiters for the single-writer lock. */
|
|
1257
|
+
_writeQueue = [];
|
|
1258
|
+
/** Whether the write lock is currently held. */
|
|
1259
|
+
_writeLocked = false;
|
|
1260
|
+
/** Whether the startup integrity check passed. */
|
|
1261
|
+
_integrityOk = true;
|
|
1262
|
+
/** Integrity errors found at startup, if any. */
|
|
1263
|
+
_integrityErrors = [];
|
|
1118
1264
|
/**
|
|
1119
1265
|
* @param ctx - context ID (16-hex characters), the first 16 of
|
|
1120
1266
|
* the SHA-256 hash of `targetDir`
|
|
@@ -1176,6 +1322,7 @@ var WorkerDaemon = class {
|
|
|
1176
1322
|
);
|
|
1177
1323
|
this._wasmModules = wasmModules;
|
|
1178
1324
|
this.initWorkerContext();
|
|
1325
|
+
this.runStartupIntegrityCheck();
|
|
1179
1326
|
const pidPath = join3(tmpdir(), `kbdb-${this.ctx}.pid`);
|
|
1180
1327
|
writeFileSync2(pidPath, process.pid.toString());
|
|
1181
1328
|
const ipcPath = getIpcPath(this.ctx);
|
|
@@ -1364,7 +1511,11 @@ var WorkerDaemon = class {
|
|
|
1364
1511
|
*/
|
|
1365
1512
|
dispatch(method, params) {
|
|
1366
1513
|
if (method === "status") {
|
|
1367
|
-
return
|
|
1514
|
+
return {
|
|
1515
|
+
status: this._connectionCount > 0 ? "busy" : "idle",
|
|
1516
|
+
integrityOk: this._integrityOk,
|
|
1517
|
+
integrityErrors: this._integrityErrors
|
|
1518
|
+
};
|
|
1368
1519
|
}
|
|
1369
1520
|
if (method === "idle_at") {
|
|
1370
1521
|
return this._idleAt;
|
|
@@ -1381,67 +1532,31 @@ var WorkerDaemon = class {
|
|
|
1381
1532
|
}
|
|
1382
1533
|
throw new IpcError(-32601, `Method not found: ${method}`);
|
|
1383
1534
|
}
|
|
1384
|
-
dispatchWasm(method, wasmExport, params) {
|
|
1535
|
+
async dispatchWasm(method, wasmExport, params) {
|
|
1385
1536
|
if (method === "search" || method === "query") {
|
|
1386
1537
|
return this.handleSearch(params);
|
|
1387
1538
|
}
|
|
1388
|
-
if (method === "addSection" || method === "add_section") {
|
|
1389
|
-
return this.handleAddSection(params);
|
|
1390
|
-
}
|
|
1391
|
-
if (method === "updateSection" || method === "update_section") {
|
|
1392
|
-
return this.handleUpdateSection(params);
|
|
1393
|
-
}
|
|
1394
|
-
if (method === "removeSection" || method === "remove_section") {
|
|
1395
|
-
return this.handleRemoveSection(params);
|
|
1396
|
-
}
|
|
1397
1539
|
if (method === "readSections" || method === "read_sections") {
|
|
1398
1540
|
return this.handleReadSections(params);
|
|
1399
1541
|
}
|
|
1400
1542
|
if (method === "retrieveDocument" || method === "retrieve_doc") {
|
|
1401
1543
|
return this.handleRetrieveDoc(params);
|
|
1402
1544
|
}
|
|
1403
|
-
if (method === "groupSections" || method === "group_sections") {
|
|
1404
|
-
return this.handleGroupSections(params);
|
|
1405
|
-
}
|
|
1406
|
-
if (method === "removeGrouping" || method === "remove_grouping") {
|
|
1407
|
-
return this.handleRemoveGrouping(params);
|
|
1408
|
-
}
|
|
1409
1545
|
if (method === "content" || method === "compose") {
|
|
1410
1546
|
return this.handleCompose(params);
|
|
1411
1547
|
}
|
|
1412
1548
|
if (method === "integrityCheck" || method === "integrity_check") {
|
|
1413
1549
|
return this.handleIntegrityCheck();
|
|
1414
1550
|
}
|
|
1415
|
-
if (method === "gc") {
|
|
1416
|
-
return this.handleGc();
|
|
1417
|
-
}
|
|
1418
|
-
if (method === "rebuildIndexes" || method === "rebuild_indexes") {
|
|
1419
|
-
return this.handleRebuild();
|
|
1420
|
-
}
|
|
1421
1551
|
if (method === "dbStatus" || method === "db_status") {
|
|
1422
1552
|
return this.handleDbStatus();
|
|
1423
1553
|
}
|
|
1424
|
-
if (method === "initDirectory" || method === "init_directory") {
|
|
1425
|
-
return this.handleInitDirectory(params);
|
|
1426
|
-
}
|
|
1427
|
-
if (method === "invalidate") {
|
|
1428
|
-
return this.handleInvalidate(params);
|
|
1429
|
-
}
|
|
1430
|
-
if (method === "invalidateAll" || method === "invalidate_all") {
|
|
1431
|
-
return this.handleInvalidateAll();
|
|
1432
|
-
}
|
|
1433
1554
|
if (method === "get_result_status") {
|
|
1434
1555
|
return this.handleResultStatus(params);
|
|
1435
1556
|
}
|
|
1436
1557
|
if (method === "get_result") {
|
|
1437
1558
|
return this.handleGetResult(params);
|
|
1438
1559
|
}
|
|
1439
|
-
if (method === "migrateDatabase") {
|
|
1440
|
-
throw new IpcError(
|
|
1441
|
-
-32601,
|
|
1442
|
-
"migrateDatabase is not yet supported"
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
1445
1560
|
if (method === "listByType" || method === "list_by_type") {
|
|
1446
1561
|
return this.handleListByType(params);
|
|
1447
1562
|
}
|
|
@@ -1451,7 +1566,51 @@ var WorkerDaemon = class {
|
|
|
1451
1566
|
if (method === "recall") {
|
|
1452
1567
|
return this.handleRecall(params);
|
|
1453
1568
|
}
|
|
1454
|
-
|
|
1569
|
+
if (method === "export" || method === "exportDb") {
|
|
1570
|
+
return this.handleExport(params);
|
|
1571
|
+
}
|
|
1572
|
+
await this.acquireWriteLock();
|
|
1573
|
+
try {
|
|
1574
|
+
if (method === "invalidate") {
|
|
1575
|
+
return this.handleInvalidate(params);
|
|
1576
|
+
}
|
|
1577
|
+
if (method === "invalidateAll" || method === "invalidate_all") {
|
|
1578
|
+
return this.handleInvalidateAll();
|
|
1579
|
+
}
|
|
1580
|
+
if (method === "addSection" || method === "add_section") {
|
|
1581
|
+
return this.handleAddSection(params);
|
|
1582
|
+
}
|
|
1583
|
+
if (method === "updateSection" || method === "update_section") {
|
|
1584
|
+
return this.handleUpdateSection(params);
|
|
1585
|
+
}
|
|
1586
|
+
if (method === "removeSection" || method === "remove_section") {
|
|
1587
|
+
return this.handleRemoveSection(params);
|
|
1588
|
+
}
|
|
1589
|
+
if (method === "groupSections" || method === "group_sections") {
|
|
1590
|
+
return this.handleGroupSections(params);
|
|
1591
|
+
}
|
|
1592
|
+
if (method === "removeGrouping" || method === "remove_grouping") {
|
|
1593
|
+
return this.handleRemoveGrouping(params);
|
|
1594
|
+
}
|
|
1595
|
+
if (method === "gc") {
|
|
1596
|
+
return this.handleGc();
|
|
1597
|
+
}
|
|
1598
|
+
if (method === "rebuildIndexes" || method === "rebuild_indexes") {
|
|
1599
|
+
return this.handleRebuild();
|
|
1600
|
+
}
|
|
1601
|
+
if (method === "initDirectory" || method === "init_directory") {
|
|
1602
|
+
return this.handleInitDirectory(params);
|
|
1603
|
+
}
|
|
1604
|
+
if (method === "migrateDatabase") {
|
|
1605
|
+
throw new IpcError(
|
|
1606
|
+
-32601,
|
|
1607
|
+
"migrateDatabase is not yet supported"
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
return this.invokeWasmRaw(wasmExport, new Uint8Array(0));
|
|
1611
|
+
} finally {
|
|
1612
|
+
this.releaseWriteLock();
|
|
1613
|
+
}
|
|
1455
1614
|
}
|
|
1456
1615
|
// -- Search/query (token-based async) --
|
|
1457
1616
|
handleSearch(params) {
|
|
@@ -1506,6 +1665,7 @@ var WorkerDaemon = class {
|
|
|
1506
1665
|
const sectionType = params["sectionType"] ?? "text";
|
|
1507
1666
|
const semanticType = sectionType === "text" || sectionType === "code" || sectionType === "image" ? sectionType : "text";
|
|
1508
1667
|
const typeName = sectionType !== semanticType ? sectionType : null;
|
|
1668
|
+
const tags = Array.isArray(params["tags"]) ? params["tags"] : [];
|
|
1509
1669
|
const input = {
|
|
1510
1670
|
sectionType: semanticType,
|
|
1511
1671
|
mimeType: params["mimeType"],
|
|
@@ -1513,12 +1673,24 @@ var WorkerDaemon = class {
|
|
|
1513
1673
|
title: params["title"] ?? null,
|
|
1514
1674
|
description: params["description"] ?? null,
|
|
1515
1675
|
docid: params["docid"] ?? null,
|
|
1516
|
-
typeName
|
|
1676
|
+
typeName,
|
|
1677
|
+
sourcePath: typeof params["sourcePath"] === "string" ? params["sourcePath"] : void 0,
|
|
1678
|
+
sourceMtime: typeof params["sourceMtime"] === "string" ? params["sourceMtime"] : void 0,
|
|
1679
|
+
tags,
|
|
1680
|
+
replace: params["replace"] === true
|
|
1517
1681
|
};
|
|
1518
1682
|
const encoded = encodeAddSectionParams(input);
|
|
1519
1683
|
const bytes = this.callWasmSync("worker_add_section", encoded);
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1684
|
+
const decoded = decodeAddSectionResult(bytes);
|
|
1685
|
+
const nearDups = decoded.nearDuplicates.length > 0 ? decoded.nearDuplicates : this.scanNearDuplicates(input.content, decoded.kbid);
|
|
1686
|
+
return {
|
|
1687
|
+
kbid: decoded.kbid,
|
|
1688
|
+
indexed_terms_count: decoded.indexedTermsCount,
|
|
1689
|
+
elapsed_ms: decoded.elapsedMs,
|
|
1690
|
+
superseded: decoded.superseded,
|
|
1691
|
+
warnings: decoded.warnings,
|
|
1692
|
+
near_duplicates: nearDups
|
|
1693
|
+
};
|
|
1522
1694
|
}
|
|
1523
1695
|
handleUpdateSection(params) {
|
|
1524
1696
|
const oldKbid = params["kbid"] ?? "";
|
|
@@ -1592,7 +1764,18 @@ var WorkerDaemon = class {
|
|
|
1592
1764
|
handleInvalidate(params) {
|
|
1593
1765
|
const kbid = params["kbid"] ?? "";
|
|
1594
1766
|
const encoded = new TextEncoder().encode(kbid);
|
|
1595
|
-
this.
|
|
1767
|
+
const fn = this.getExport("worker_invalidate");
|
|
1768
|
+
const ptr = this.wasmAlloc(encoded.length);
|
|
1769
|
+
this.wasmWrite(ptr, encoded);
|
|
1770
|
+
const rc = fn(ptr, encoded.length);
|
|
1771
|
+
this.wasmFree(ptr, encoded.length);
|
|
1772
|
+
if (rc === -1) {
|
|
1773
|
+
const errBytes = this.readReturnSlot();
|
|
1774
|
+
throw new IpcError(
|
|
1775
|
+
-32e3,
|
|
1776
|
+
new TextDecoder().decode(errBytes) || "invalidate failed"
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1596
1779
|
return { ok: true };
|
|
1597
1780
|
}
|
|
1598
1781
|
handleListByType(params) {
|
|
@@ -1636,20 +1819,173 @@ var WorkerDaemon = class {
|
|
|
1636
1819
|
return decodeKeyValueText(text);
|
|
1637
1820
|
}
|
|
1638
1821
|
handleGc() {
|
|
1822
|
+
const start = Date.now();
|
|
1639
1823
|
this.callWasmZeroArg("worker_garbage_collect");
|
|
1640
1824
|
const bytes = this.readReturnSlot();
|
|
1641
1825
|
const text = new TextDecoder().decode(bytes);
|
|
1642
|
-
|
|
1826
|
+
const kv = decodeKeyValueText(text);
|
|
1827
|
+
const removedCount = Number(kv["removed"] ?? 0);
|
|
1828
|
+
const staleSources = Number(kv["stale_sources"] ?? 0);
|
|
1829
|
+
return {
|
|
1830
|
+
removed_sections: removedCount,
|
|
1831
|
+
removed_bytes: 0,
|
|
1832
|
+
elapsed_ms: Date.now() - start,
|
|
1833
|
+
stale_sources: staleSources
|
|
1834
|
+
};
|
|
1643
1835
|
}
|
|
1644
1836
|
handleRebuild() {
|
|
1645
|
-
this.
|
|
1646
|
-
|
|
1837
|
+
const fn = this.getExport("worker_rebuild_indexes");
|
|
1838
|
+
const rc = fn();
|
|
1839
|
+
if (rc === -1) {
|
|
1840
|
+
const errBytes = this.readReturnSlot();
|
|
1841
|
+
const errMsg = new TextDecoder().decode(errBytes);
|
|
1842
|
+
throw new IpcError(-32e3, errMsg || "rebuild failed");
|
|
1843
|
+
}
|
|
1844
|
+
const resultBytes = this.readReturnSlot();
|
|
1845
|
+
const reindexed = resultBytes.length >= 4 ? new DataView(
|
|
1846
|
+
resultBytes.buffer,
|
|
1847
|
+
resultBytes.byteOffset,
|
|
1848
|
+
resultBytes.byteLength
|
|
1849
|
+
).getUint32(0, true) : 0;
|
|
1850
|
+
return { ok: true, sections_reindexed: reindexed };
|
|
1851
|
+
}
|
|
1852
|
+
// -- Export (read operation, no lock required) --
|
|
1853
|
+
handleExport(params) {
|
|
1854
|
+
const targetPath = params["targetPath"] ?? params["path"] ?? "./kbdb-export/";
|
|
1855
|
+
const encoded = new TextEncoder().encode(targetPath);
|
|
1856
|
+
const start = Date.now();
|
|
1857
|
+
const bytes = this.callWasmSync("worker_export", encoded);
|
|
1858
|
+
const elapsed = Date.now() - start;
|
|
1859
|
+
const text = new TextDecoder().decode(bytes);
|
|
1860
|
+
const kv = decodeKeyValueText(text);
|
|
1861
|
+
return {
|
|
1862
|
+
path: kv["path"] ?? targetPath,
|
|
1863
|
+
sections_exported: Number(kv["sections_exported"] ?? 0),
|
|
1864
|
+
documents_exported: Number(kv["documents_exported"] ?? 0),
|
|
1865
|
+
elapsed_ms: elapsed
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
// -- Near-duplicate detection --
|
|
1869
|
+
scanNearDuplicates(content, newKbid) {
|
|
1870
|
+
const newTerms = this.extractTermSet(content);
|
|
1871
|
+
if (newTerms.size === 0) return [];
|
|
1872
|
+
const sectionsDir = join3(this.targetDir, "sections");
|
|
1873
|
+
let shards;
|
|
1874
|
+
try {
|
|
1875
|
+
shards = readdirSync2(sectionsDir);
|
|
1876
|
+
} catch {
|
|
1877
|
+
return [];
|
|
1878
|
+
}
|
|
1879
|
+
const results = [];
|
|
1880
|
+
for (const shard of shards) {
|
|
1881
|
+
let files;
|
|
1882
|
+
try {
|
|
1883
|
+
files = readdirSync2(join3(sectionsDir, shard));
|
|
1884
|
+
} catch {
|
|
1885
|
+
continue;
|
|
1886
|
+
}
|
|
1887
|
+
for (const file of files) {
|
|
1888
|
+
if (!file.endsWith(".section")) continue;
|
|
1889
|
+
const kbid = file.slice(0, -8);
|
|
1890
|
+
if (kbid === newKbid) continue;
|
|
1891
|
+
try {
|
|
1892
|
+
const data = readFileSync4(
|
|
1893
|
+
join3(sectionsDir, shard, file),
|
|
1894
|
+
"utf-8"
|
|
1895
|
+
);
|
|
1896
|
+
const sep = data.indexOf("\n---\n");
|
|
1897
|
+
const body = sep >= 0 ? data.slice(sep + 5) : data;
|
|
1898
|
+
const existing = this.extractTermSet(body);
|
|
1899
|
+
let intersection = 0;
|
|
1900
|
+
for (const t of newTerms) {
|
|
1901
|
+
if (existing.has(t)) intersection++;
|
|
1902
|
+
}
|
|
1903
|
+
const union = newTerms.size + existing.size - intersection;
|
|
1904
|
+
const sim = union > 0 ? intersection / union : 0;
|
|
1905
|
+
if (sim >= 0.85) {
|
|
1906
|
+
results.push({
|
|
1907
|
+
kbid,
|
|
1908
|
+
similarity: Math.round(sim * 100) / 100
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
} catch {
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
return results;
|
|
1917
|
+
}
|
|
1918
|
+
extractTermSet(text) {
|
|
1919
|
+
return new Set(
|
|
1920
|
+
text.toLowerCase().split(/\W+/).filter((w) => w.length > 2)
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
// -- Single-writer lock --
|
|
1924
|
+
/**
|
|
1925
|
+
* Acquires the write lock.
|
|
1926
|
+
*
|
|
1927
|
+
* If the lock is free it is granted immediately. If it is held the
|
|
1928
|
+
* caller is queued and its promise resolves once the lock is
|
|
1929
|
+
* released by the current holder.
|
|
1930
|
+
*/
|
|
1931
|
+
acquireWriteLock() {
|
|
1932
|
+
if (!this._writeLocked) {
|
|
1933
|
+
this._writeLocked = true;
|
|
1934
|
+
return Promise.resolve();
|
|
1935
|
+
}
|
|
1936
|
+
return new Promise((resolve2) => {
|
|
1937
|
+
this._writeQueue.push({ resolve: resolve2 });
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Releases the write lock.
|
|
1942
|
+
*
|
|
1943
|
+
* If waiters are queued the next one is granted the lock
|
|
1944
|
+
* immediately. Otherwise the lock is marked free.
|
|
1945
|
+
*/
|
|
1946
|
+
releaseWriteLock() {
|
|
1947
|
+
const next = this._writeQueue.shift();
|
|
1948
|
+
if (next !== void 0) {
|
|
1949
|
+
next.resolve();
|
|
1950
|
+
} else {
|
|
1951
|
+
this._writeLocked = false;
|
|
1952
|
+
}
|
|
1647
1953
|
}
|
|
1648
1954
|
// -- WASM lifecycle --
|
|
1649
1955
|
initWorkerContext() {
|
|
1650
1956
|
const pathBytes = new TextEncoder().encode(".");
|
|
1651
1957
|
this.callWasmSync("worker_init", pathBytes);
|
|
1652
1958
|
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Runs a database integrity check immediately after context
|
|
1961
|
+
* initialisation.
|
|
1962
|
+
*
|
|
1963
|
+
* Stores the result in `_integrityOk` and `_integrityErrors`.
|
|
1964
|
+
* Logs a warning to stderr when issues are found so operators
|
|
1965
|
+
* are alerted without aborting the daemon.
|
|
1966
|
+
*/
|
|
1967
|
+
runStartupIntegrityCheck() {
|
|
1968
|
+
try {
|
|
1969
|
+
this.callWasmZeroArg("worker_check_integrity");
|
|
1970
|
+
const bytes = this.readReturnSlot();
|
|
1971
|
+
const text = new TextDecoder().decode(bytes);
|
|
1972
|
+
const kv = decodeKeyValueText(text);
|
|
1973
|
+
this._integrityOk = kv["ok"] === "true";
|
|
1974
|
+
if (!this._integrityOk) {
|
|
1975
|
+
this._integrityErrors = Object.entries(kv).filter(([k]) => k !== "ok").map(([k, v]) => `${k}=${v}`);
|
|
1976
|
+
console.error(
|
|
1977
|
+
"[kbdb-worker] startup integrity check failed:",
|
|
1978
|
+
this._integrityErrors.join(", ")
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
} catch {
|
|
1982
|
+
this._integrityOk = false;
|
|
1983
|
+
this._integrityErrors = ["integrity_check_failed"];
|
|
1984
|
+
console.error(
|
|
1985
|
+
"[kbdb-worker] startup integrity check threw an error"
|
|
1986
|
+
);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1653
1989
|
// -- WASM bridge primitives --
|
|
1654
1990
|
getExport(name) {
|
|
1655
1991
|
if (this._wasmModules === null) {
|