@oscharko-dev/keiko-local-knowledge 0.2.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/dist/.tsbuildinfo +1 -0
- package/dist/bounded-document-extraction.d.ts +27 -0
- package/dist/bounded-document-extraction.d.ts.map +1 -0
- package/dist/bounded-document-extraction.js +214 -0
- package/dist/capsule-lifecycle.d.ts +33 -0
- package/dist/capsule-lifecycle.d.ts.map +1 -0
- package/dist/capsule-lifecycle.js +292 -0
- package/dist/capsule-set-lifecycle.d.ts +15 -0
- package/dist/capsule-set-lifecycle.d.ts.map +1 -0
- package/dist/capsule-set-lifecycle.js +158 -0
- package/dist/chunking/chunker-persist.d.ts +36 -0
- package/dist/chunking/chunker-persist.d.ts.map +1 -0
- package/dist/chunking/chunker-persist.js +74 -0
- package/dist/chunking/chunker-runner.d.ts +9 -0
- package/dist/chunking/chunker-runner.d.ts.map +1 -0
- package/dist/chunking/chunker-runner.js +218 -0
- package/dist/chunking/chunker.d.ts +7 -0
- package/dist/chunking/chunker.d.ts.map +1 -0
- package/dist/chunking/chunker.js +139 -0
- package/dist/chunking/citation-mapper.d.ts +4 -0
- package/dist/chunking/citation-mapper.d.ts.map +1 -0
- package/dist/chunking/citation-mapper.js +180 -0
- package/dist/chunking/index.d.ts +6 -0
- package/dist/chunking/index.d.ts.map +1 -0
- package/dist/chunking/index.js +8 -0
- package/dist/chunking/token-estimator.d.ts +3 -0
- package/dist/chunking/token-estimator.d.ts.map +1 -0
- package/dist/chunking/token-estimator.js +26 -0
- package/dist/chunking/types.d.ts +49 -0
- package/dist/chunking/types.d.ts.map +1 -0
- package/dist/chunking/types.js +26 -0
- package/dist/composition.d.ts +57 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +310 -0
- package/dist/conversation/citation-attacher.d.ts +8 -0
- package/dist/conversation/citation-attacher.d.ts.map +1 -0
- package/dist/conversation/citation-attacher.js +55 -0
- package/dist/conversation/citation-excerpts.d.ts +4 -0
- package/dist/conversation/citation-excerpts.d.ts.map +1 -0
- package/dist/conversation/citation-excerpts.js +41 -0
- package/dist/conversation/grounded-answer-runner.d.ts +9 -0
- package/dist/conversation/grounded-answer-runner.d.ts.map +1 -0
- package/dist/conversation/grounded-answer-runner.js +61 -0
- package/dist/conversation/index.d.ts +5 -0
- package/dist/conversation/index.d.ts.map +1 -0
- package/dist/conversation/index.js +7 -0
- package/dist/conversation/model-gateway-answer-generator.d.ts +28 -0
- package/dist/conversation/model-gateway-answer-generator.d.ts.map +1 -0
- package/dist/conversation/model-gateway-answer-generator.js +105 -0
- package/dist/conversation/types.d.ts +35 -0
- package/dist/conversation/types.d.ts.map +1 -0
- package/dist/conversation/types.js +24 -0
- package/dist/discovery/discovery-runner.d.ts +23 -0
- package/dist/discovery/discovery-runner.d.ts.map +1 -0
- package/dist/discovery/discovery-runner.js +109 -0
- package/dist/discovery/extract-progressive.d.ts +17 -0
- package/dist/discovery/extract-progressive.d.ts.map +1 -0
- package/dist/discovery/extract-progressive.js +522 -0
- package/dist/discovery/extract.d.ts +26 -0
- package/dist/discovery/extract.d.ts.map +1 -0
- package/dist/discovery/extract.js +906 -0
- package/dist/discovery/glob.d.ts +10 -0
- package/dist/discovery/glob.d.ts.map +1 -0
- package/dist/discovery/glob.js +72 -0
- package/dist/discovery/index.d.ts +6 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +8 -0
- package/dist/discovery/media-type.d.ts +4 -0
- package/dist/discovery/media-type.d.ts.map +1 -0
- package/dist/discovery/media-type.js +62 -0
- package/dist/discovery/persist.d.ts +63 -0
- package/dist/discovery/persist.d.ts.map +1 -0
- package/dist/discovery/persist.js +345 -0
- package/dist/discovery/test-support.d.ts +16 -0
- package/dist/discovery/test-support.d.ts.map +1 -0
- package/dist/discovery/test-support.js +127 -0
- package/dist/discovery/types.d.ts +63 -0
- package/dist/discovery/types.d.ts.map +1 -0
- package/dist/discovery/types.js +28 -0
- package/dist/discovery/walk.d.ts +12 -0
- package/dist/discovery/walk.d.ts.map +1 -0
- package/dist/discovery/walk.js +302 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +22 -0
- package/dist/evaluations/dimensions.d.ts +14 -0
- package/dist/evaluations/dimensions.d.ts.map +1 -0
- package/dist/evaluations/dimensions.js +191 -0
- package/dist/evaluations/fixtures.d.ts +18 -0
- package/dist/evaluations/fixtures.d.ts.map +1 -0
- package/dist/evaluations/fixtures.js +858 -0
- package/dist/evaluations/index.d.ts +7 -0
- package/dist/evaluations/index.d.ts.map +1 -0
- package/dist/evaluations/index.js +10 -0
- package/dist/evaluations/report.d.ts +3 -0
- package/dist/evaluations/report.d.ts.map +1 -0
- package/dist/evaluations/report.js +31 -0
- package/dist/evaluations/runner-seed.d.ts +12 -0
- package/dist/evaluations/runner-seed.d.ts.map +1 -0
- package/dist/evaluations/runner-seed.js +175 -0
- package/dist/evaluations/runner.d.ts +8 -0
- package/dist/evaluations/runner.d.ts.map +1 -0
- package/dist/evaluations/runner.js +205 -0
- package/dist/evaluations/scripted-embedding-adapter.d.ts +13 -0
- package/dist/evaluations/scripted-embedding-adapter.d.ts.map +1 -0
- package/dist/evaluations/scripted-embedding-adapter.js +163 -0
- package/dist/evaluations/types.d.ts +116 -0
- package/dist/evaluations/types.d.ts.map +1 -0
- package/dist/evaluations/types.js +27 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/indexing/bounded-indexing.d.ts +41 -0
- package/dist/indexing/bounded-indexing.d.ts.map +1 -0
- package/dist/indexing/bounded-indexing.js +240 -0
- package/dist/indexing/checkpoint-persist.d.ts +8 -0
- package/dist/indexing/checkpoint-persist.d.ts.map +1 -0
- package/dist/indexing/checkpoint-persist.js +135 -0
- package/dist/indexing/checkpoint-resume.d.ts +20 -0
- package/dist/indexing/checkpoint-resume.d.ts.map +1 -0
- package/dist/indexing/checkpoint-resume.js +50 -0
- package/dist/indexing/embedding-batcher.d.ts +3 -0
- package/dist/indexing/embedding-batcher.d.ts.map +1 -0
- package/dist/indexing/embedding-batcher.js +390 -0
- package/dist/indexing/index.d.ts +7 -0
- package/dist/indexing/index.d.ts.map +1 -0
- package/dist/indexing/index.js +11 -0
- package/dist/indexing/job-persist.d.ts +46 -0
- package/dist/indexing/job-persist.d.ts.map +1 -0
- package/dist/indexing/job-persist.js +157 -0
- package/dist/indexing/job-resume.d.ts +4 -0
- package/dist/indexing/job-resume.d.ts.map +1 -0
- package/dist/indexing/job-resume.js +14 -0
- package/dist/indexing/orchestrator.d.ts +3 -0
- package/dist/indexing/orchestrator.d.ts.map +1 -0
- package/dist/indexing/orchestrator.js +1151 -0
- package/dist/indexing/types.d.ts +156 -0
- package/dist/indexing/types.d.ts.map +1 -0
- package/dist/indexing/types.js +30 -0
- package/dist/indexing/vector-persist.d.ts +32 -0
- package/dist/indexing/vector-persist.d.ts.map +1 -0
- package/dist/indexing/vector-persist.js +105 -0
- package/dist/parsers/_internal.d.ts +20 -0
- package/dist/parsers/_internal.d.ts.map +1 -0
- package/dist/parsers/_internal.js +122 -0
- package/dist/parsers/csv-parser.d.ts +3 -0
- package/dist/parsers/csv-parser.d.ts.map +1 -0
- package/dist/parsers/csv-parser.js +202 -0
- package/dist/parsers/docx-parser.d.ts +3 -0
- package/dist/parsers/docx-parser.d.ts.map +1 -0
- package/dist/parsers/docx-parser.js +390 -0
- package/dist/parsers/html-parser.d.ts +3 -0
- package/dist/parsers/html-parser.d.ts.map +1 -0
- package/dist/parsers/html-parser.js +310 -0
- package/dist/parsers/index.d.ts +15 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +41 -0
- package/dist/parsers/json-parser.d.ts +3 -0
- package/dist/parsers/json-parser.d.ts.map +1 -0
- package/dist/parsers/json-parser.js +192 -0
- package/dist/parsers/large-document/capability-discovery.d.ts +27 -0
- package/dist/parsers/large-document/capability-discovery.d.ts.map +1 -0
- package/dist/parsers/large-document/capability-discovery.js +76 -0
- package/dist/parsers/large-document/diagnostics.d.ts +3 -0
- package/dist/parsers/large-document/diagnostics.d.ts.map +1 -0
- package/dist/parsers/large-document/diagnostics.js +11 -0
- package/dist/parsers/large-document/index.d.ts +15 -0
- package/dist/parsers/large-document/index.d.ts.map +1 -0
- package/dist/parsers/large-document/index.js +10 -0
- package/dist/parsers/large-document/legacy-format.d.ts +5 -0
- package/dist/parsers/large-document/legacy-format.d.ts.map +1 -0
- package/dist/parsers/large-document/legacy-format.js +25 -0
- package/dist/parsers/large-document/preflight.d.ts +9 -0
- package/dist/parsers/large-document/preflight.d.ts.map +1 -0
- package/dist/parsers/large-document/preflight.js +43 -0
- package/dist/parsers/large-document/progressive-extraction.d.ts +55 -0
- package/dist/parsers/large-document/progressive-extraction.d.ts.map +1 -0
- package/dist/parsers/large-document/progressive-extraction.js +123 -0
- package/dist/parsers/large-document/progressive-pdf.d.ts +20 -0
- package/dist/parsers/large-document/progressive-pdf.d.ts.map +1 -0
- package/dist/parsers/large-document/progressive-pdf.js +145 -0
- package/dist/parsers/large-document/synthetic-source.d.ts +9 -0
- package/dist/parsers/large-document/synthetic-source.d.ts.map +1 -0
- package/dist/parsers/large-document/synthetic-source.js +101 -0
- package/dist/parsers/large-document/window-builder.d.ts +24 -0
- package/dist/parsers/large-document/window-builder.d.ts.map +1 -0
- package/dist/parsers/large-document/window-builder.js +75 -0
- package/dist/parsers/ocr/index.d.ts +4 -0
- package/dist/parsers/ocr/index.d.ts.map +1 -0
- package/dist/parsers/ocr/index.js +4 -0
- package/dist/parsers/ocr/null-ocr-adapter.d.ts +3 -0
- package/dist/parsers/ocr/null-ocr-adapter.d.ts.map +1 -0
- package/dist/parsers/ocr/null-ocr-adapter.js +14 -0
- package/dist/parsers/ocr/ocr-pipeline-parser.d.ts +8 -0
- package/dist/parsers/ocr/ocr-pipeline-parser.d.ts.map +1 -0
- package/dist/parsers/ocr/ocr-pipeline-parser.js +147 -0
- package/dist/parsers/ocr/types.d.ts +16 -0
- package/dist/parsers/ocr/types.d.ts.map +1 -0
- package/dist/parsers/ocr/types.js +4 -0
- package/dist/parsers/parser-test-fixtures.d.ts +28 -0
- package/dist/parsers/parser-test-fixtures.d.ts.map +1 -0
- package/dist/parsers/parser-test-fixtures.js +139 -0
- package/dist/parsers/pdf-parser.d.ts +43 -0
- package/dist/parsers/pdf-parser.d.ts.map +1 -0
- package/dist/parsers/pdf-parser.js +388 -0
- package/dist/parsers/registry.d.ts +8 -0
- package/dist/parsers/registry.d.ts.map +1 -0
- package/dist/parsers/registry.js +57 -0
- package/dist/parsers/text-parser.d.ts +3 -0
- package/dist/parsers/text-parser.d.ts.map +1 -0
- package/dist/parsers/text-parser.js +214 -0
- package/dist/parsers/types.d.ts +53 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/types.js +21 -0
- package/dist/parsers/unsupported-parser.d.ts +4 -0
- package/dist/parsers/unsupported-parser.d.ts.map +1 -0
- package/dist/parsers/unsupported-parser.js +97 -0
- package/dist/parsers/xlsx-parser.d.ts +3 -0
- package/dist/parsers/xlsx-parser.d.ts.map +1 -0
- package/dist/parsers/xlsx-parser.js +425 -0
- package/dist/privacy/audit-emitter.d.ts +5 -0
- package/dist/privacy/audit-emitter.d.ts.map +1 -0
- package/dist/privacy/audit-emitter.js +93 -0
- package/dist/privacy/diagnostic-redactor.d.ts +2 -0
- package/dist/privacy/diagnostic-redactor.d.ts.map +1 -0
- package/dist/privacy/diagnostic-redactor.js +153 -0
- package/dist/privacy/index.d.ts +5 -0
- package/dist/privacy/index.d.ts.map +1 -0
- package/dist/privacy/index.js +6 -0
- package/dist/privacy/retention-applier.d.ts +5 -0
- package/dist/privacy/retention-applier.d.ts.map +1 -0
- package/dist/privacy/retention-applier.js +88 -0
- package/dist/privacy/types.d.ts +98 -0
- package/dist/privacy/types.d.ts.map +1 -0
- package/dist/privacy/types.js +12 -0
- package/dist/qualityIntelligence/capsuleCorpus.d.ts +27 -0
- package/dist/qualityIntelligence/capsuleCorpus.d.ts.map +1 -0
- package/dist/qualityIntelligence/capsuleCorpus.js +58 -0
- package/dist/qualityIntelligence/index.d.ts +3 -0
- package/dist/qualityIntelligence/index.d.ts.map +1 -0
- package/dist/qualityIntelligence/index.js +5 -0
- package/dist/qualityIntelligence/qiHandoff.d.ts +36 -0
- package/dist/qualityIntelligence/qiHandoff.d.ts.map +1 -0
- package/dist/qualityIntelligence/qiHandoff.js +82 -0
- package/dist/retrieval/answer-grounding.d.ts +9 -0
- package/dist/retrieval/answer-grounding.d.ts.map +1 -0
- package/dist/retrieval/answer-grounding.js +31 -0
- package/dist/retrieval/context-pack-assembler.d.ts +24 -0
- package/dist/retrieval/context-pack-assembler.d.ts.map +1 -0
- package/dist/retrieval/context-pack-assembler.js +50 -0
- package/dist/retrieval/index.d.ts +6 -0
- package/dist/retrieval/index.d.ts.map +1 -0
- package/dist/retrieval/index.js +9 -0
- package/dist/retrieval/retrieval-runner.d.ts +10 -0
- package/dist/retrieval/retrieval-runner.d.ts.map +1 -0
- package/dist/retrieval/retrieval-runner.js +163 -0
- package/dist/retrieval/scoped-vector-search.d.ts +24 -0
- package/dist/retrieval/scoped-vector-search.d.ts.map +1 -0
- package/dist/retrieval/scoped-vector-search.js +864 -0
- package/dist/retrieval/types.d.ts +28 -0
- package/dist/retrieval/types.d.ts.map +1 -0
- package/dist/retrieval/types.js +33 -0
- package/dist/section-path-hash.d.ts +3 -0
- package/dist/section-path-hash.d.ts.map +1 -0
- package/dist/section-path-hash.js +9 -0
- package/dist/source-lifecycle.d.ts +14 -0
- package/dist/source-lifecycle.d.ts.map +1 -0
- package/dist/source-lifecycle.js +155 -0
- package/dist/source-routing-validation.d.ts +11 -0
- package/dist/source-routing-validation.d.ts.map +1 -0
- package/dist/source-routing-validation.js +140 -0
- package/dist/store-content-cipher.d.ts +11 -0
- package/dist/store-content-cipher.d.ts.map +1 -0
- package/dist/store-content-cipher.js +67 -0
- package/dist/store-content-encryption.d.ts +12 -0
- package/dist/store-content-encryption.d.ts.map +1 -0
- package/dist/store-content-encryption.js +275 -0
- package/dist/store-paths.d.ts +6 -0
- package/dist/store-paths.d.ts.map +1 -0
- package/dist/store-paths.js +61 -0
- package/dist/store.d.ts +30 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +219 -0
- package/dist/testing.d.ts +47 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +170 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/package.json +43 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// capsule-lifecycle.ts — typed CRUD over the `capsules` table plus its FK-driven cascade.
|
|
2
|
+
//
|
|
3
|
+
// Invariants:
|
|
4
|
+
// * Every write runs inside `BEGIN`/`COMMIT` so partial state never lands on disk.
|
|
5
|
+
// * createCapsule rejects duplicate ids via the PRIMARY KEY — raised as KnowledgeStoreError.
|
|
6
|
+
// * Reads compose `sourceIds` from the live `capsule_sources` table — the capsule row
|
|
7
|
+
// does NOT denormalise the source list, so the reverse FK is the single truth.
|
|
8
|
+
// * deleteCapsule relies on the schema's ON DELETE CASCADE chain (#265). Removing the
|
|
9
|
+
// CASCADE clause from any dependent table will either throw an FK violation (when
|
|
10
|
+
// foreign_keys=ON catches a leftover) or leave orphan rows the cascade test catches.
|
|
11
|
+
import { DELETE_CAPSULE_SQL, isSafeStorageReference, isSafeDisplaySummary, } from "@oscharko-dev/keiko-contracts";
|
|
12
|
+
import { KnowledgeNotFoundError, KnowledgeStoreError } from "./errors.js";
|
|
13
|
+
const INSERT_CAPSULE_SQL = [
|
|
14
|
+
"INSERT INTO capsules (",
|
|
15
|
+
" id, display_name, description, tags_json, source_routing_instructions, always_query,",
|
|
16
|
+
" retrieval_effort, output_mode, answer_grounding_policy,",
|
|
17
|
+
" embedding_model_provider, embedding_model_id, embedding_model_revision,",
|
|
18
|
+
" vector_dimensions, vector_metric, lifecycle_state, storage_reference,",
|
|
19
|
+
" created_at, updated_at",
|
|
20
|
+
") VALUES (",
|
|
21
|
+
" :id, :display_name, :description, :tags_json, :source_routing_instructions, :always_query,",
|
|
22
|
+
" :retrieval_effort, :output_mode, :answer_grounding_policy,",
|
|
23
|
+
" :embedding_model_provider, :embedding_model_id, :embedding_model_revision,",
|
|
24
|
+
" :vector_dimensions, :vector_metric, :lifecycle_state, :storage_reference,",
|
|
25
|
+
" :created_at, :updated_at",
|
|
26
|
+
")",
|
|
27
|
+
].join(" ");
|
|
28
|
+
const SELECT_CAPSULE_BY_ID_SQL = "SELECT * FROM capsules WHERE id = :id";
|
|
29
|
+
const SELECT_ALL_CAPSULES_SQL = "SELECT * FROM capsules ORDER BY created_at ASC, id ASC";
|
|
30
|
+
const SELECT_SOURCE_IDS_FOR_CAPSULE_SQL = "SELECT id FROM capsule_sources WHERE capsule_id = :c ORDER BY created_at ASC, id ASC";
|
|
31
|
+
const UPDATE_STATE_SQL = "UPDATE capsules SET lifecycle_state = :state, updated_at = :now WHERE id = :id";
|
|
32
|
+
const SELECT_AFFECTED_CAPSULE_SETS_SQL = "SELECT set_id FROM capsule_set_members WHERE capsule_id = :c ORDER BY set_id ASC";
|
|
33
|
+
const DELETE_VERIFICATION_TABLES = [
|
|
34
|
+
"capsule_sources",
|
|
35
|
+
"capsule_set_members",
|
|
36
|
+
"documents",
|
|
37
|
+
"document_texts",
|
|
38
|
+
"document_text_windows",
|
|
39
|
+
"extraction_checkpoints",
|
|
40
|
+
"pages",
|
|
41
|
+
"sections",
|
|
42
|
+
"parsed_units",
|
|
43
|
+
"chunks",
|
|
44
|
+
"vectors",
|
|
45
|
+
"parser_diagnostics",
|
|
46
|
+
"indexing_jobs",
|
|
47
|
+
];
|
|
48
|
+
function jsonOrEmpty(value) {
|
|
49
|
+
return JSON.stringify(value);
|
|
50
|
+
}
|
|
51
|
+
function parseTags(json) {
|
|
52
|
+
const parsed = JSON.parse(json);
|
|
53
|
+
if (!Array.isArray(parsed))
|
|
54
|
+
return [];
|
|
55
|
+
return parsed.filter((entry) => typeof entry === "string");
|
|
56
|
+
}
|
|
57
|
+
function assertSafeDisplayField(field, value) {
|
|
58
|
+
if (value.trim().length === 0 || !isSafeDisplaySummary(value)) {
|
|
59
|
+
throw new KnowledgeStoreError(`${field} must be a browser-safe non-empty string`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function assertSafeOptionalDisplayField(field, value) {
|
|
63
|
+
if (value !== undefined && !isSafeDisplaySummary(value)) {
|
|
64
|
+
throw new KnowledgeStoreError(`${field} must be browser-safe when set`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function assertSafeCreateCapsuleInput(input) {
|
|
68
|
+
assertSafeDisplayField("displayName", input.displayName);
|
|
69
|
+
assertSafeOptionalDisplayField("description", input.description);
|
|
70
|
+
assertSafeOptionalDisplayField("sourceRoutingInstructions", input.sourceRoutingInstructions);
|
|
71
|
+
for (const tag of input.tags) {
|
|
72
|
+
assertSafeDisplayField("tag", tag);
|
|
73
|
+
}
|
|
74
|
+
if (!isSafeStorageReference(input.storageReference)) {
|
|
75
|
+
throw new KnowledgeStoreError("storageReference must be a safe relative path");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function listSourceIdsFor(store, capsuleId) {
|
|
79
|
+
const rows = store._internal.db.prepare(SELECT_SOURCE_IDS_FOR_CAPSULE_SQL).all({ c: capsuleId });
|
|
80
|
+
return rows.map((row) => row.id);
|
|
81
|
+
}
|
|
82
|
+
function buildEmbeddingIdentity(row) {
|
|
83
|
+
const base = {
|
|
84
|
+
provider: row.embedding_model_provider,
|
|
85
|
+
modelId: row.embedding_model_id,
|
|
86
|
+
vectorDimensions: row.vector_dimensions,
|
|
87
|
+
vectorMetric: row.vector_metric,
|
|
88
|
+
};
|
|
89
|
+
if (row.embedding_model_revision === null)
|
|
90
|
+
return base;
|
|
91
|
+
return { ...base, modelRevision: row.embedding_model_revision };
|
|
92
|
+
}
|
|
93
|
+
function rowToCapsule(row, sourceIds) {
|
|
94
|
+
const base = {
|
|
95
|
+
id: row.id,
|
|
96
|
+
displayName: row.display_name,
|
|
97
|
+
tags: parseTags(row.tags_json),
|
|
98
|
+
sourceIds,
|
|
99
|
+
retrievalEffort: row.retrieval_effort,
|
|
100
|
+
outputMode: row.output_mode,
|
|
101
|
+
answerGroundingPolicy: row.answer_grounding_policy,
|
|
102
|
+
embeddingModelIdentity: buildEmbeddingIdentity(row),
|
|
103
|
+
lifecycleState: row.lifecycle_state,
|
|
104
|
+
storageReference: row.storage_reference,
|
|
105
|
+
createdAt: row.created_at,
|
|
106
|
+
updatedAt: row.updated_at,
|
|
107
|
+
};
|
|
108
|
+
return withOptionalCapsuleFields(base, row);
|
|
109
|
+
}
|
|
110
|
+
function withOptionalCapsuleFields(base, row) {
|
|
111
|
+
// exactOptionalPropertyTypes requires us to OMIT absent optionals rather than set them
|
|
112
|
+
// to undefined. Spread the optionals only when they are present in the row.
|
|
113
|
+
let result = base;
|
|
114
|
+
if (row.description !== null) {
|
|
115
|
+
result = { ...result, description: row.description };
|
|
116
|
+
}
|
|
117
|
+
if (row.source_routing_instructions !== null) {
|
|
118
|
+
result = { ...result, sourceRoutingInstructions: row.source_routing_instructions };
|
|
119
|
+
}
|
|
120
|
+
if (row.always_query === 1) {
|
|
121
|
+
result = { ...result, alwaysQuery: true };
|
|
122
|
+
}
|
|
123
|
+
else if (row.always_query === 0) {
|
|
124
|
+
// alwaysQuery defaults to undefined when stored as 0; we mirror "no" as omitted.
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
export function createCapsule(store, input, auditSink) {
|
|
129
|
+
assertSafeCreateCapsuleInput(input);
|
|
130
|
+
const now = store._internal.now();
|
|
131
|
+
const params = {
|
|
132
|
+
id: input.id,
|
|
133
|
+
display_name: input.displayName,
|
|
134
|
+
description: input.description ?? null,
|
|
135
|
+
tags_json: jsonOrEmpty(input.tags),
|
|
136
|
+
source_routing_instructions: input.sourceRoutingInstructions ?? null,
|
|
137
|
+
always_query: input.alwaysQuery === true ? 1 : 0,
|
|
138
|
+
retrieval_effort: input.retrievalEffort,
|
|
139
|
+
output_mode: input.outputMode,
|
|
140
|
+
answer_grounding_policy: input.answerGroundingPolicy,
|
|
141
|
+
embedding_model_provider: input.embeddingModelIdentity.provider,
|
|
142
|
+
embedding_model_id: input.embeddingModelIdentity.modelId,
|
|
143
|
+
embedding_model_revision: input.embeddingModelIdentity.modelRevision ?? null,
|
|
144
|
+
vector_dimensions: input.embeddingModelIdentity.vectorDimensions,
|
|
145
|
+
vector_metric: input.embeddingModelIdentity.vectorMetric,
|
|
146
|
+
lifecycle_state: input.lifecycleState,
|
|
147
|
+
storage_reference: input.storageReference,
|
|
148
|
+
created_at: now,
|
|
149
|
+
updated_at: now,
|
|
150
|
+
};
|
|
151
|
+
const db = store._internal.db;
|
|
152
|
+
db.exec("BEGIN");
|
|
153
|
+
try {
|
|
154
|
+
db.prepare(INSERT_CAPSULE_SQL).run(params);
|
|
155
|
+
db.exec("COMMIT");
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
db.exec("ROLLBACK");
|
|
159
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
160
|
+
if (/UNIQUE|PRIMARY KEY/i.test(msg)) {
|
|
161
|
+
throw new KnowledgeStoreError("capsule already exists", { cause: error });
|
|
162
|
+
}
|
|
163
|
+
throw new KnowledgeStoreError("failed to create capsule", { cause: error });
|
|
164
|
+
}
|
|
165
|
+
const capsule = getCapsule(store, input.id);
|
|
166
|
+
if (capsule === undefined) {
|
|
167
|
+
// Defensive: a successful INSERT must be readable. This branch indicates a serious
|
|
168
|
+
// store-level inconsistency (e.g. concurrent DELETE) and the caller cannot continue
|
|
169
|
+
// safely with a synthesised value.
|
|
170
|
+
throw new KnowledgeStoreError(`createCapsule: insert succeeded but row not found for ${String(input.id)}`);
|
|
171
|
+
}
|
|
172
|
+
auditSink?.emit({ kind: "capsule-created", capsuleId: capsule.id, occurredAt: now });
|
|
173
|
+
return capsule;
|
|
174
|
+
}
|
|
175
|
+
export function getCapsule(store, id) {
|
|
176
|
+
const row = store._internal.db.prepare(SELECT_CAPSULE_BY_ID_SQL).get({ id });
|
|
177
|
+
if (row === undefined)
|
|
178
|
+
return undefined;
|
|
179
|
+
const sourceIds = listSourceIdsFor(store, id);
|
|
180
|
+
return rowToCapsule(row, sourceIds);
|
|
181
|
+
}
|
|
182
|
+
export function listCapsules(store) {
|
|
183
|
+
const rows = store._internal.db.prepare(SELECT_ALL_CAPSULES_SQL).all();
|
|
184
|
+
return rows.map((row) => {
|
|
185
|
+
const typed = row;
|
|
186
|
+
const sourceIds = listSourceIdsFor(store, typed.id);
|
|
187
|
+
return rowToCapsule(typed, sourceIds);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
export function updateCapsuleState(store, id, state) {
|
|
191
|
+
const db = store._internal.db;
|
|
192
|
+
const now = store._internal.now();
|
|
193
|
+
db.exec("BEGIN");
|
|
194
|
+
try {
|
|
195
|
+
const result = db.prepare(UPDATE_STATE_SQL).run({ state, now, id });
|
|
196
|
+
if (Number(result.changes) === 0) {
|
|
197
|
+
db.exec("ROLLBACK");
|
|
198
|
+
throw new KnowledgeNotFoundError(`Capsule not found: ${String(id)}`);
|
|
199
|
+
}
|
|
200
|
+
db.exec("COMMIT");
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (!(error instanceof KnowledgeNotFoundError)) {
|
|
204
|
+
db.exec("ROLLBACK");
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
const capsule = getCapsule(store, id);
|
|
209
|
+
if (capsule === undefined) {
|
|
210
|
+
throw new KnowledgeNotFoundError(`Capsule not found after update: ${String(id)}`);
|
|
211
|
+
}
|
|
212
|
+
return capsule;
|
|
213
|
+
}
|
|
214
|
+
// Slice 4 (#189): update a capsule's display name / description. The SET clause is built only
|
|
215
|
+
// from the columns present in the patch; column fragments are fixed literals (no user input),
|
|
216
|
+
// so values stay fully parameterised. Metadata persistence is a separate schema migration and is
|
|
217
|
+
// intentionally NOT handled here.
|
|
218
|
+
export function updateCapsuleDetails(store, id, patch) {
|
|
219
|
+
const assignments = [];
|
|
220
|
+
const params = { id, now: store._internal.now() };
|
|
221
|
+
if (patch.displayName !== undefined) {
|
|
222
|
+
assertSafeDisplayField("displayName", patch.displayName);
|
|
223
|
+
assignments.push("display_name = :displayName");
|
|
224
|
+
params.displayName = patch.displayName;
|
|
225
|
+
}
|
|
226
|
+
if (patch.description !== undefined) {
|
|
227
|
+
assertSafeOptionalDisplayField("description", patch.description);
|
|
228
|
+
assignments.push("description = :description");
|
|
229
|
+
params.description = patch.description;
|
|
230
|
+
}
|
|
231
|
+
if (assignments.length === 0) {
|
|
232
|
+
throw new KnowledgeStoreError("updateCapsuleDetails requires at least one field to change.");
|
|
233
|
+
}
|
|
234
|
+
const sql = `UPDATE capsules SET ${assignments.join(", ")}, updated_at = :now WHERE id = :id`;
|
|
235
|
+
const db = store._internal.db;
|
|
236
|
+
db.exec("BEGIN");
|
|
237
|
+
try {
|
|
238
|
+
const result = db.prepare(sql).run(params);
|
|
239
|
+
if (Number(result.changes) === 0) {
|
|
240
|
+
db.exec("ROLLBACK");
|
|
241
|
+
throw new KnowledgeNotFoundError(`Capsule not found: ${String(id)}`);
|
|
242
|
+
}
|
|
243
|
+
db.exec("COMMIT");
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
if (!(error instanceof KnowledgeNotFoundError)) {
|
|
247
|
+
db.exec("ROLLBACK");
|
|
248
|
+
}
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
const capsule = getCapsule(store, id);
|
|
252
|
+
if (capsule === undefined) {
|
|
253
|
+
throw new KnowledgeNotFoundError(`Capsule not found after update: ${String(id)}`);
|
|
254
|
+
}
|
|
255
|
+
return capsule;
|
|
256
|
+
}
|
|
257
|
+
export function deleteCapsule(store, id, auditSink) {
|
|
258
|
+
const db = store._internal.db;
|
|
259
|
+
const occurredAt = store._internal.now();
|
|
260
|
+
const affectedCapsuleSetIds = db
|
|
261
|
+
.prepare(SELECT_AFFECTED_CAPSULE_SETS_SQL)
|
|
262
|
+
.all({ c: id })
|
|
263
|
+
.map((row) => row.set_id);
|
|
264
|
+
db.exec("BEGIN");
|
|
265
|
+
try {
|
|
266
|
+
const result = db.prepare(DELETE_CAPSULE_SQL).run({ capsule_id: id });
|
|
267
|
+
if (Number(result.changes) === 0) {
|
|
268
|
+
db.exec("ROLLBACK");
|
|
269
|
+
throw new KnowledgeNotFoundError(`Capsule not found: ${String(id)}`);
|
|
270
|
+
}
|
|
271
|
+
verifyDeleteCleanup(db, id);
|
|
272
|
+
db.exec("COMMIT");
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
if (!(error instanceof KnowledgeNotFoundError)) {
|
|
276
|
+
db.exec("ROLLBACK");
|
|
277
|
+
}
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
auditSink?.emit({ kind: "capsule-deleted", capsuleId: id, occurredAt });
|
|
281
|
+
return { capsuleId: id, affectedCapsuleSetIds, cleanupVerified: true };
|
|
282
|
+
}
|
|
283
|
+
function verifyDeleteCleanup(db, capsuleId) {
|
|
284
|
+
for (const table of DELETE_VERIFICATION_TABLES) {
|
|
285
|
+
const row = db
|
|
286
|
+
.prepare(`SELECT COUNT(*) AS n FROM ${table} WHERE capsule_id = :c`)
|
|
287
|
+
.get({ c: capsuleId });
|
|
288
|
+
if (row.n !== 0) {
|
|
289
|
+
throw new KnowledgeStoreError(`capsule delete left residual rows in ${table} for ${String(capsuleId)}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CapsuleSet, type CapsuleSetId, type KnowledgeCapsuleId } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
import type { KnowledgeStore } from "./store.js";
|
|
3
|
+
export interface CreateCapsuleSetInput {
|
|
4
|
+
readonly id: CapsuleSetId;
|
|
5
|
+
readonly displayName: string;
|
|
6
|
+
readonly description?: string;
|
|
7
|
+
readonly tags: readonly string[];
|
|
8
|
+
readonly capsuleIds: readonly KnowledgeCapsuleId[];
|
|
9
|
+
}
|
|
10
|
+
export declare function createCapsuleSetWithinTxn(store: KnowledgeStore, input: CreateCapsuleSetInput, now: number): void;
|
|
11
|
+
export declare function createCapsuleSet(store: KnowledgeStore, input: CreateCapsuleSetInput): CapsuleSet;
|
|
12
|
+
export declare function getCapsuleSet(store: KnowledgeStore, id: CapsuleSetId): CapsuleSet | undefined;
|
|
13
|
+
export declare function listCapsuleSets(store: KnowledgeStore): readonly CapsuleSet[];
|
|
14
|
+
export declare function deleteCapsuleSet(store: KnowledgeStore, id: CapsuleSetId): void;
|
|
15
|
+
//# sourceMappingURL=capsule-set-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capsule-set-lifecycle.d.ts","sourceRoot":"","sources":["../src/capsule-set-lifecycle.ts"],"names":[],"mappings":"AAYA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACxB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACpD;AAwFD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,qBAAqB,EAC5B,GAAG,EAAE,MAAM,GACV,IAAI,CAuBN;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,qBAAqB,GAAG,UAAU,CAsBhG;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,YAAY,GAAG,UAAU,GAAG,SAAS,CAQ7F;AAOD,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,UAAU,EAAE,CAc5E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,YAAY,GAAG,IAAI,CAiB9E"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// capsule-set-lifecycle.ts — CapsuleSets are LOGICAL compositions: they hold capsule IDs
|
|
2
|
+
// only, never vectors/chunks/documents (Foundry-IQ "no global pool" invariant).
|
|
3
|
+
//
|
|
4
|
+
// The #265 schema declares `capsule_set_members(set_id, capsule_id, ordinal, composed_at)`
|
|
5
|
+
// but does NOT declare a top-level `capsule_sets` table; #265 explicitly defers the set's
|
|
6
|
+
// metadata storage to consumers because the contract layer only knows the membership
|
|
7
|
+
// relation. We persist the metadata (display name, description, tags) as a single row in
|
|
8
|
+
// the existing `schema_meta` key-value table under key `capsule_set:<id>`, with the JSON
|
|
9
|
+
// payload `{displayName, description?, tags, composedAt}`. Members live in their declared
|
|
10
|
+
// table. Deleting a capsule cascades to the membership row via the FK from #265; this
|
|
11
|
+
// module does not need to chase it.
|
|
12
|
+
import { isSafeDisplaySummary, } from "@oscharko-dev/keiko-contracts";
|
|
13
|
+
import { KnowledgeNotFoundError, KnowledgeStoreError } from "./errors.js";
|
|
14
|
+
const META_PREFIX = "capsule_set:";
|
|
15
|
+
function metaKey(id) {
|
|
16
|
+
return `${META_PREFIX}${String(id)}`;
|
|
17
|
+
}
|
|
18
|
+
function assertSafeDisplayField(field, value) {
|
|
19
|
+
if (value.trim().length === 0 || !isSafeDisplaySummary(value)) {
|
|
20
|
+
throw new KnowledgeStoreError(`${field} must be a browser-safe non-empty string`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function assertSafeOptionalDisplayField(field, value) {
|
|
24
|
+
if (value !== undefined && !isSafeDisplaySummary(value)) {
|
|
25
|
+
throw new KnowledgeStoreError(`${field} must be browser-safe when set`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function parseMeta(value, id) {
|
|
29
|
+
const parsed = JSON.parse(value);
|
|
30
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
31
|
+
throw new KnowledgeStoreError(`Corrupt schema_meta payload for ${String(id)}.`);
|
|
32
|
+
}
|
|
33
|
+
const obj = parsed;
|
|
34
|
+
const displayName = obj.displayName;
|
|
35
|
+
const description = obj.description;
|
|
36
|
+
const tags = obj.tags;
|
|
37
|
+
const composedAt = obj.composedAt;
|
|
38
|
+
if (typeof displayName !== "string") {
|
|
39
|
+
throw new KnowledgeStoreError(`schema_meta payload missing displayName for ${String(id)}.`);
|
|
40
|
+
}
|
|
41
|
+
if (typeof composedAt !== "number") {
|
|
42
|
+
throw new KnowledgeStoreError(`schema_meta payload missing composedAt for ${String(id)}.`);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
displayName,
|
|
46
|
+
description: typeof description === "string" ? description : null,
|
|
47
|
+
tags: Array.isArray(tags) ? tags.filter((t) => typeof t === "string") : [],
|
|
48
|
+
composedAt,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function readMembers(store, setId) {
|
|
52
|
+
const rows = store._internal.db
|
|
53
|
+
.prepare("SELECT capsule_id, ordinal FROM capsule_set_members WHERE set_id = :s ORDER BY ordinal ASC")
|
|
54
|
+
.all({ s: setId });
|
|
55
|
+
return rows.map((row) => row.capsule_id);
|
|
56
|
+
}
|
|
57
|
+
function metaToCapsuleSet(id, payload, capsuleIds) {
|
|
58
|
+
const base = {
|
|
59
|
+
id,
|
|
60
|
+
displayName: payload.displayName,
|
|
61
|
+
tags: payload.tags,
|
|
62
|
+
capsuleIds,
|
|
63
|
+
composedAt: payload.composedAt,
|
|
64
|
+
};
|
|
65
|
+
return payload.description === null ? base : { ...base, description: payload.description };
|
|
66
|
+
}
|
|
67
|
+
// Inserts the schema_meta row and all member rows WITHOUT bracketing a BEGIN/COMMIT.
|
|
68
|
+
// Must be called from within an already-open transaction. Used by createCapsuleSet (which
|
|
69
|
+
// wraps it in its own transaction) and by runComposeTransaction (which needs to combine the
|
|
70
|
+
// set creation and audit writes in one atomic outer transaction).
|
|
71
|
+
export function createCapsuleSetWithinTxn(store, input, now) {
|
|
72
|
+
assertSafeDisplayField("displayName", input.displayName);
|
|
73
|
+
assertSafeOptionalDisplayField("description", input.description);
|
|
74
|
+
for (const tag of input.tags) {
|
|
75
|
+
assertSafeDisplayField("tag", tag);
|
|
76
|
+
}
|
|
77
|
+
const db = store._internal.db;
|
|
78
|
+
const payload = {
|
|
79
|
+
displayName: input.displayName,
|
|
80
|
+
description: input.description ?? null,
|
|
81
|
+
tags: input.tags,
|
|
82
|
+
composedAt: now,
|
|
83
|
+
};
|
|
84
|
+
db.prepare("INSERT INTO schema_meta (key, value) VALUES (:k, :v)").run({
|
|
85
|
+
k: metaKey(input.id),
|
|
86
|
+
v: JSON.stringify(payload),
|
|
87
|
+
});
|
|
88
|
+
const insertMember = db.prepare("INSERT INTO capsule_set_members (set_id, capsule_id, ordinal, composed_at) VALUES (:s, :c, :o, :now)");
|
|
89
|
+
input.capsuleIds.forEach((capsuleId, index) => {
|
|
90
|
+
insertMember.run({ s: input.id, c: capsuleId, o: index, now });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function createCapsuleSet(store, input) {
|
|
94
|
+
const db = store._internal.db;
|
|
95
|
+
const now = store._internal.now();
|
|
96
|
+
db.exec("BEGIN");
|
|
97
|
+
try {
|
|
98
|
+
createCapsuleSetWithinTxn(store, input, now);
|
|
99
|
+
db.exec("COMMIT");
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
db.exec("ROLLBACK");
|
|
103
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
104
|
+
if (/UNIQUE|PRIMARY KEY/i.test(msg)) {
|
|
105
|
+
throw new KnowledgeStoreError("capsule set already exists", { cause: error });
|
|
106
|
+
}
|
|
107
|
+
throw new KnowledgeStoreError("failed to create capsule set", { cause: error });
|
|
108
|
+
}
|
|
109
|
+
const fetched = getCapsuleSet(store, input.id);
|
|
110
|
+
if (fetched === undefined) {
|
|
111
|
+
throw new KnowledgeStoreError(`createCapsuleSet: insert succeeded but row not found for ${String(input.id)}`);
|
|
112
|
+
}
|
|
113
|
+
return fetched;
|
|
114
|
+
}
|
|
115
|
+
export function getCapsuleSet(store, id) {
|
|
116
|
+
const row = store._internal.db
|
|
117
|
+
.prepare("SELECT value FROM schema_meta WHERE key = :k")
|
|
118
|
+
.get({ k: metaKey(id) });
|
|
119
|
+
if (row === undefined)
|
|
120
|
+
return undefined;
|
|
121
|
+
const payload = parseMeta(row.value, id);
|
|
122
|
+
const members = readMembers(store, id);
|
|
123
|
+
return metaToCapsuleSet(id, payload, members);
|
|
124
|
+
}
|
|
125
|
+
export function listCapsuleSets(store) {
|
|
126
|
+
// Fetch (key, value) in one query so we avoid a getCapsuleSet re-query per row (N+1).
|
|
127
|
+
// Members still need a per-set query because capsule_set_members is a separate table.
|
|
128
|
+
const rows = store._internal.db
|
|
129
|
+
.prepare("SELECT key, value FROM schema_meta WHERE key LIKE :prefix")
|
|
130
|
+
.all({ prefix: `${META_PREFIX}%` });
|
|
131
|
+
const sets = rows.map((row) => {
|
|
132
|
+
const { key, value } = row;
|
|
133
|
+
const id = key.slice(META_PREFIX.length);
|
|
134
|
+
const payload = parseMeta(value, id);
|
|
135
|
+
const capsuleIds = readMembers(store, id);
|
|
136
|
+
return metaToCapsuleSet(id, payload, capsuleIds);
|
|
137
|
+
});
|
|
138
|
+
return sets.slice().sort((a, b) => a.composedAt - b.composedAt);
|
|
139
|
+
}
|
|
140
|
+
export function deleteCapsuleSet(store, id) {
|
|
141
|
+
const db = store._internal.db;
|
|
142
|
+
db.exec("BEGIN");
|
|
143
|
+
try {
|
|
144
|
+
const result = db.prepare("DELETE FROM schema_meta WHERE key = :k").run({ k: metaKey(id) });
|
|
145
|
+
if (Number(result.changes) === 0) {
|
|
146
|
+
db.exec("ROLLBACK");
|
|
147
|
+
throw new KnowledgeNotFoundError(`Capsule set not found: ${String(id)}`);
|
|
148
|
+
}
|
|
149
|
+
db.prepare("DELETE FROM capsule_set_members WHERE set_id = :s").run({ s: id });
|
|
150
|
+
db.exec("COMMIT");
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (!(error instanceof KnowledgeNotFoundError)) {
|
|
154
|
+
db.exec("ROLLBACK");
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ChunkId, DocumentId, KnowledgeCapsuleId, KnowledgeSourceId } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
3
|
+
export interface ChunkInsertRow {
|
|
4
|
+
readonly id: ChunkId;
|
|
5
|
+
readonly capsuleId: KnowledgeCapsuleId;
|
|
6
|
+
readonly sourceId: KnowledgeSourceId;
|
|
7
|
+
readonly documentId: DocumentId;
|
|
8
|
+
readonly parsedUnitId: string;
|
|
9
|
+
readonly orderIndex: number;
|
|
10
|
+
readonly tokenCount: number;
|
|
11
|
+
readonly safeExcerptHash: string;
|
|
12
|
+
readonly chunkingStrategyVersion: string;
|
|
13
|
+
readonly characterStart: number;
|
|
14
|
+
readonly characterEnd: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function insertChunkRow(db: DatabaseSync, row: ChunkInsertRow): void;
|
|
17
|
+
export declare function deleteChunksForDocument(db: DatabaseSync, capsuleId: KnowledgeCapsuleId, documentId: DocumentId): void;
|
|
18
|
+
export declare function countChunksForDocument(db: DatabaseSync, capsuleId: KnowledgeCapsuleId, documentId: DocumentId): number;
|
|
19
|
+
export declare function hasStaleChunksForDocument(db: DatabaseSync, capsuleId: KnowledgeCapsuleId, documentId: DocumentId, chunkingStrategyVersion?: string): boolean;
|
|
20
|
+
export declare function selectDocumentSourceId(db: DatabaseSync, capsuleId: KnowledgeCapsuleId, documentId: DocumentId): KnowledgeSourceId | undefined;
|
|
21
|
+
export interface ParsedUnitRow {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly kind: string;
|
|
24
|
+
readonly page_number: number | null;
|
|
25
|
+
readonly page_label: string | null;
|
|
26
|
+
readonly section_path_json: string | null;
|
|
27
|
+
readonly json_pointer: string | null;
|
|
28
|
+
readonly table_name: string | null;
|
|
29
|
+
readonly row_index: number | null;
|
|
30
|
+
readonly heading_path_json: string | null;
|
|
31
|
+
readonly unsupported_reason: string | null;
|
|
32
|
+
readonly character_start: number | null;
|
|
33
|
+
readonly character_end: number | null;
|
|
34
|
+
}
|
|
35
|
+
export declare function selectParsedUnitsForDocument(db: DatabaseSync, capsuleId: KnowledgeCapsuleId, documentId: DocumentId): readonly ParsedUnitRow[];
|
|
36
|
+
//# sourceMappingURL=chunker-persist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker-persist.d.ts","sourceRoot":"","sources":["../../src/chunking/chunker-persist.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAqChD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IAIzC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAc1E;AAED,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,UAAU,GACrB,IAAI,CAEN;AAMD,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,UAAU,GACrB,MAAM,CAKR;AAED,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,UAAU,EACtB,uBAAuB,GAAE,MAAsC,GAC9D,OAAO,CAOT;AAMD,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,UAAU,GACrB,iBAAiB,GAAG,SAAS,CAK/B;AAKD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED,wBAAgB,4BAA4B,CAC1C,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,UAAU,GACrB,SAAS,aAAa,EAAE,CAK1B"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Prepared-statement helpers for the `chunks` table (Epic #189, Issue #195). Every helper
|
|
2
|
+
// wraps a single statement so the runner can compose them inside a transaction in
|
|
3
|
+
// `chunker-runner.ts`. The runner is the only module that issues transactions for chunks —
|
|
4
|
+
// mirrors the `discovery/persist.ts` boundary convention so a per-document failure rolls
|
|
5
|
+
// back exactly the rows from that document.
|
|
6
|
+
import { DEFAULT_CHUNKING_STRATEGY_KEY } from "./types.js";
|
|
7
|
+
const INSERT_CHUNK_SQL = [
|
|
8
|
+
"INSERT INTO chunks (",
|
|
9
|
+
" id, capsule_id, source_id, document_id, parsed_unit_id,",
|
|
10
|
+
" order_index, token_count, safe_excerpt_hash, chunking_strategy_version,",
|
|
11
|
+
" character_start, character_end",
|
|
12
|
+
") VALUES (",
|
|
13
|
+
" :id, :capsule_id, :source_id, :document_id, :parsed_unit_id,",
|
|
14
|
+
" :order_index, :token_count, :safe_excerpt_hash, :chunking_strategy_version,",
|
|
15
|
+
" :character_start, :character_end",
|
|
16
|
+
")",
|
|
17
|
+
].join(" ");
|
|
18
|
+
const DELETE_CHUNKS_FOR_DOCUMENT_SQL = "DELETE FROM chunks WHERE capsule_id = :c AND document_id = :d";
|
|
19
|
+
const COUNT_CHUNKS_FOR_DOCUMENT_SQL = "SELECT COUNT(*) AS n FROM chunks WHERE capsule_id = :c AND document_id = :d";
|
|
20
|
+
const COUNT_STALE_CHUNKS_FOR_DOCUMENT_SQL = [
|
|
21
|
+
"SELECT COUNT(*) AS n FROM chunks",
|
|
22
|
+
"WHERE capsule_id = :c AND document_id = :d",
|
|
23
|
+
" AND (chunking_strategy_version IS NULL OR chunking_strategy_version <> :v)",
|
|
24
|
+
].join(" ");
|
|
25
|
+
const SELECT_PARSED_UNITS_FOR_DOCUMENT_SQL = [
|
|
26
|
+
"SELECT id, kind, page_number, page_label, section_path_json,",
|
|
27
|
+
" json_pointer, table_name, row_index, heading_path_json,",
|
|
28
|
+
" unsupported_reason, character_start, character_end",
|
|
29
|
+
"FROM parsed_units",
|
|
30
|
+
"WHERE capsule_id = :c AND document_id = :d",
|
|
31
|
+
"ORDER BY rowid ASC",
|
|
32
|
+
].join(" ");
|
|
33
|
+
export function insertChunkRow(db, row) {
|
|
34
|
+
db.prepare(INSERT_CHUNK_SQL).run({
|
|
35
|
+
id: String(row.id),
|
|
36
|
+
capsule_id: String(row.capsuleId),
|
|
37
|
+
source_id: String(row.sourceId),
|
|
38
|
+
document_id: String(row.documentId),
|
|
39
|
+
parsed_unit_id: row.parsedUnitId,
|
|
40
|
+
order_index: row.orderIndex,
|
|
41
|
+
token_count: row.tokenCount,
|
|
42
|
+
safe_excerpt_hash: row.safeExcerptHash,
|
|
43
|
+
chunking_strategy_version: row.chunkingStrategyVersion,
|
|
44
|
+
character_start: row.characterStart,
|
|
45
|
+
character_end: row.characterEnd,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function deleteChunksForDocument(db, capsuleId, documentId) {
|
|
49
|
+
db.prepare(DELETE_CHUNKS_FOR_DOCUMENT_SQL).run({ c: capsuleId, d: documentId });
|
|
50
|
+
}
|
|
51
|
+
export function countChunksForDocument(db, capsuleId, documentId) {
|
|
52
|
+
const row = db.prepare(COUNT_CHUNKS_FOR_DOCUMENT_SQL).get({ c: capsuleId, d: documentId });
|
|
53
|
+
return typeof row?.n === "number" ? row.n : 0;
|
|
54
|
+
}
|
|
55
|
+
export function hasStaleChunksForDocument(db, capsuleId, documentId, chunkingStrategyVersion = DEFAULT_CHUNKING_STRATEGY_KEY) {
|
|
56
|
+
const row = db.prepare(COUNT_STALE_CHUNKS_FOR_DOCUMENT_SQL).get({
|
|
57
|
+
c: capsuleId,
|
|
58
|
+
d: documentId,
|
|
59
|
+
v: chunkingStrategyVersion,
|
|
60
|
+
});
|
|
61
|
+
return (row?.n ?? 0) > 0;
|
|
62
|
+
}
|
|
63
|
+
export function selectDocumentSourceId(db, capsuleId, documentId) {
|
|
64
|
+
const row = db
|
|
65
|
+
.prepare("SELECT source_id FROM documents WHERE capsule_id = :c AND id = :d")
|
|
66
|
+
.get({ c: capsuleId, d: documentId });
|
|
67
|
+
return row === undefined ? undefined : row.source_id;
|
|
68
|
+
}
|
|
69
|
+
export function selectParsedUnitsForDocument(db, capsuleId, documentId) {
|
|
70
|
+
const rows = db
|
|
71
|
+
.prepare(SELECT_PARSED_UNITS_FOR_DOCUMENT_SQL)
|
|
72
|
+
.all({ c: capsuleId, d: documentId });
|
|
73
|
+
return rows;
|
|
74
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChunkId, DocumentId, ParsedUnit } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
import { type ParsedUnitRow } from "./chunker-persist.js";
|
|
3
|
+
import type { KnowledgeStore } from "../store.js";
|
|
4
|
+
import type { StoreContentCipher } from "../store-content-cipher.js";
|
|
5
|
+
import type { ChunkDocumentParams, ChunkDocumentResult, ChunkingOptions } from "./types.js";
|
|
6
|
+
export declare function rowToParsedUnit(row: ParsedUnitRow, documentId: DocumentId, cipher: StoreContentCipher): ParsedUnit;
|
|
7
|
+
export declare function composeChunkId(documentId: DocumentId, parsedUnitRowId: string, orderIndex: number): ChunkId;
|
|
8
|
+
export declare function chunkDocument(store: KnowledgeStore, params: ChunkDocumentParams, options?: ChunkingOptions): ChunkDocumentResult;
|
|
9
|
+
//# sourceMappingURL=chunker-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker-runner.d.ts","sourceRoot":"","sources":["../../src/chunking/chunker-runner.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EAGV,UAAU,EACX,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAsG5F,wBAAgB,eAAe,CAC7B,GAAG,EAAE,aAAa,EAClB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,kBAAkB,GACzB,UAAU,CAqBZ;AAcD,wBAAgB,cAAc,CAC5B,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,GACjB,OAAO,CAET;AAoHD,wBAAgB,aAAa,CAC3B,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,mBAAmB,EAC3B,OAAO,CAAC,EAAE,eAAe,GACxB,mBAAmB,CAkCrB"}
|