@kaelio/ktx 0.13.1 → 0.15.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/assets/python/{kaelio_ktx-0.13.1-py3-none-any.whl → kaelio_ktx-0.15.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli-program.js +1 -1
- package/dist/commands/ingest-commands.d.ts +9 -0
- package/dist/commands/ingest-commands.js +37 -1
- package/dist/commands/knowledge-commands.js +3 -0
- package/dist/commands/setup-commands.js +15 -1
- package/dist/connection-drivers.d.ts +2 -0
- package/dist/connection-drivers.js +7 -3
- package/dist/connection.d.ts +3 -0
- package/dist/connection.js +27 -0
- package/dist/connectors/bigquery/connector.d.ts +17 -6
- package/dist/connectors/bigquery/connector.js +152 -76
- package/dist/connectors/bigquery/dialect.d.ts +2 -2
- package/dist/connectors/clickhouse/connector.d.ts +1 -0
- package/dist/connectors/clickhouse/connector.js +45 -16
- package/dist/connectors/clickhouse/dialect.d.ts +2 -2
- package/dist/connectors/mongodb/connector.d.ts +69 -0
- package/dist/connectors/mongodb/connector.js +307 -0
- package/dist/connectors/mongodb/dialect.d.ts +17 -0
- package/dist/connectors/mongodb/dialect.js +50 -0
- package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
- package/dist/connectors/mongodb/live-database-introspection.js +24 -0
- package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
- package/dist/connectors/mongodb/schema-inference.js +110 -0
- package/dist/connectors/mysql/connector.d.ts +1 -0
- package/dist/connectors/mysql/connector.js +18 -2
- package/dist/connectors/mysql/dialect.d.ts +2 -2
- package/dist/connectors/postgres/connector.d.ts +1 -0
- package/dist/connectors/postgres/connector.js +20 -3
- package/dist/connectors/postgres/dialect.d.ts +2 -2
- package/dist/connectors/snowflake/connector.d.ts +1 -0
- package/dist/connectors/snowflake/connector.js +27 -3
- package/dist/connectors/snowflake/dialect.d.ts +2 -2
- package/dist/connectors/sqlite/connector.d.ts +11 -1
- package/dist/connectors/sqlite/connector.js +120 -17
- package/dist/connectors/sqlite/dialect.d.ts +2 -2
- package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
- package/dist/connectors/sqlite/read-query-child.js +23 -0
- package/dist/connectors/sqlserver/connector.d.ts +2 -0
- package/dist/connectors/sqlserver/connector.js +20 -3
- package/dist/connectors/sqlserver/dialect.d.ts +2 -2
- package/dist/context/cache/content-result-cache.d.ts +36 -0
- package/dist/context/cache/content-result-cache.js +14 -0
- package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
- package/dist/context/cache/sqlite-content-result-cache.js +223 -0
- package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
- package/dist/context/connections/bigquery-identifiers.js +7 -0
- package/dist/context/connections/configured-connections.d.ts +9 -0
- package/dist/context/connections/configured-connections.js +18 -0
- package/dist/context/connections/dialects.d.ts +30 -4
- package/dist/context/connections/dialects.js +38 -11
- package/dist/context/connections/drivers.js +21 -0
- package/dist/context/connections/gdrive-config.d.ts +19 -0
- package/dist/context/connections/gdrive-config.js +57 -0
- package/dist/context/connections/query-deadline.d.ts +31 -0
- package/dist/context/connections/query-deadline.js +37 -0
- package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
- package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
- package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
- package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
- package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
- package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
- package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
- package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
- package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
- package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
- package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
- package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
- package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
- package/dist/context/ingest/adapters/gdrive/types.js +36 -0
- package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
- package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
- package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
- package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
- package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
- package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
- package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
- package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
- package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
- package/dist/context/ingest/adapters/live-database/stage.js +5 -5
- package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/types.js +1 -1
- package/dist/context/ingest/adapters/sigma/chunk.d.ts +6 -0
- package/dist/context/ingest/adapters/sigma/chunk.js +119 -0
- package/dist/context/ingest/adapters/sigma/client-port.d.ts +45 -0
- package/dist/context/ingest/adapters/sigma/client-port.js +1 -0
- package/dist/context/ingest/adapters/sigma/client.d.ts +33 -0
- package/dist/context/ingest/adapters/sigma/client.js +176 -0
- package/dist/context/ingest/adapters/sigma/detect.d.ts +1 -0
- package/dist/context/ingest/adapters/sigma/detect.js +23 -0
- package/dist/context/ingest/adapters/sigma/fetch.d.ts +14 -0
- package/dist/context/ingest/adapters/sigma/fetch.js +191 -0
- package/dist/context/ingest/adapters/sigma/local-sigma.adapter.d.ts +17 -0
- package/dist/context/ingest/adapters/sigma/local-sigma.adapter.js +41 -0
- package/dist/context/ingest/adapters/sigma/project.d.ts +8 -0
- package/dist/context/ingest/adapters/sigma/project.js +186 -0
- package/dist/context/ingest/adapters/sigma/sigma.adapter.d.ts +18 -0
- package/dist/context/ingest/adapters/sigma/sigma.adapter.js +43 -0
- package/dist/context/ingest/adapters/sigma/types.d.ts +80 -0
- package/dist/context/ingest/adapters/sigma/types.js +82 -0
- package/dist/context/ingest/artifact-gates.d.ts +32 -1
- package/dist/context/ingest/artifact-gates.js +85 -18
- package/dist/context/ingest/final-gate-prune.d.ts +35 -0
- package/dist/context/ingest/final-gate-prune.js +229 -0
- package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
- package/dist/context/ingest/ingest-bundle.runner.js +459 -240
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
- package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
- package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
- package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
- package/dist/context/ingest/local-adapters.d.ts +2 -1
- package/dist/context/ingest/local-adapters.js +28 -0
- package/dist/context/ingest/local-bundle-runtime.js +7 -2
- package/dist/context/ingest/local-ingest.js +1 -1
- package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
- package/dist/context/ingest/local-stage-ingest.js +3 -1
- package/dist/context/ingest/ports.d.ts +3 -0
- package/dist/context/ingest/report-snapshot.js +13 -3
- package/dist/context/ingest/reports.d.ts +3 -3
- package/dist/context/ingest/reports.js +3 -1
- package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
- package/dist/context/ingest/stages/build-wu-context.js +2 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
- package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
- package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
- package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
- package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
- package/dist/context/ingest/types.d.ts +3 -0
- package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
- package/dist/context/ingest/wiki-body-refs.js +37 -8
- package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
- package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
- package/dist/context/ingest/work-unit-cache.d.ts +67 -0
- package/dist/context/ingest/work-unit-cache.js +230 -0
- package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
- package/dist/context/llm/ai-sdk-runtime.js +5 -0
- package/dist/context/llm/claude-code-runtime.d.ts +4 -1
- package/dist/context/llm/claude-code-runtime.js +38 -8
- package/dist/context/llm/codex-runtime.d.ts +4 -1
- package/dist/context/llm/codex-runtime.js +48 -35
- package/dist/context/llm/runtime-port.d.ts +26 -0
- package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
- package/dist/context/llm/subprocess-generate-object-child.js +34 -0
- package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
- package/dist/context/llm/subprocess-generate-object.js +122 -0
- package/dist/context/mcp/context-tools.d.ts +2 -0
- package/dist/context/mcp/context-tools.js +111 -17
- package/dist/context/mcp/local-project-ports.d.ts +7 -0
- package/dist/context/mcp/local-project-ports.js +29 -0
- package/dist/context/mcp/logger.d.ts +24 -0
- package/dist/context/mcp/logger.js +49 -0
- package/dist/context/mcp/server.js +2 -0
- package/dist/context/mcp/types.d.ts +17 -0
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +72 -0
- package/dist/context/project/config.js +5 -0
- package/dist/context/project/driver-schemas.d.ts +35 -0
- package/dist/context/project/driver-schemas.js +89 -1
- package/dist/context/project/project.js +1 -1
- package/dist/context/project/setup-config.js +1 -0
- package/dist/context/scan/description-generation.d.ts +4 -0
- package/dist/context/scan/description-generation.js +100 -13
- package/dist/context/scan/enabled-tables.d.ts +5 -0
- package/dist/context/scan/enabled-tables.js +13 -1
- package/dist/context/scan/enrichment-state.d.ts +50 -7
- package/dist/context/scan/enrichment-state.js +30 -13
- package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
- package/dist/context/scan/local-enrichment-artifacts.js +155 -32
- package/dist/context/scan/local-enrichment.d.ts +39 -4
- package/dist/context/scan/local-enrichment.js +345 -92
- package/dist/context/scan/local-scan.d.ts +4 -1
- package/dist/context/scan/local-scan.js +54 -13
- package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
- package/dist/context/scan/local-structural-artifacts.js +5 -0
- package/dist/context/scan/object-introspection.d.ts +21 -0
- package/dist/context/scan/object-introspection.js +35 -0
- package/dist/context/scan/relationship-benchmarks.js +5 -3
- package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
- package/dist/context/scan/relationship-composite-candidates.js +13 -1
- package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
- package/dist/context/scan/relationship-detection-budget.js +53 -0
- package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
- package/dist/context/scan/relationship-diagnostics.js +2 -0
- package/dist/context/scan/relationship-discovery.d.ts +9 -3
- package/dist/context/scan/relationship-discovery.js +27 -4
- package/dist/context/scan/relationship-llm-proposal.js +36 -27
- package/dist/context/scan/relationship-profiling.d.ts +7 -3
- package/dist/context/scan/relationship-profiling.js +53 -41
- package/dist/context/scan/relationship-validation.d.ts +6 -4
- package/dist/context/scan/relationship-validation.js +46 -32
- package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
- package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
- package/dist/context/scan/types.d.ts +5 -3
- package/dist/context/scan/types.js +2 -0
- package/dist/context/sl/local-query.js +5 -0
- package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
- package/dist/context/sl/semantic-layer.service.js +1 -1
- package/dist/context/sl/source-files.d.ts +5 -0
- package/dist/context/sl/source-files.js +17 -11
- package/dist/context/sl/tools/connection-id-schema.js +1 -1
- package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
- package/dist/context/sql-analysis/dialect-notes.js +40 -0
- package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
- package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
- package/dist/context/sql-analysis/dialects/mysql.md +9 -0
- package/dist/context/sql-analysis/dialects/postgres.md +10 -0
- package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
- package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
- package/dist/context/sql-analysis/dialects/tsql.md +10 -0
- package/dist/context/wiki/keys.js +1 -1
- package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
- package/dist/context/wiki/knowledge-wiki.service.js +2 -1
- package/dist/context/wiki/local-knowledge.d.ts +13 -0
- package/dist/context/wiki/local-knowledge.js +50 -6
- package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
- package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
- package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
- package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
- package/dist/context/wiki/types.d.ts +6 -0
- package/dist/context-build-view.d.ts +3 -0
- package/dist/context-build-view.js +1 -0
- package/dist/knowledge.d.ts +2 -0
- package/dist/knowledge.js +12 -1
- package/dist/local-adapters.js +11 -0
- package/dist/mcp-http-server.js +15 -1
- package/dist/mcp-server-factory.d.ts +2 -0
- package/dist/mcp-server-factory.js +15 -1
- package/dist/mcp-stdio-server.js +10 -2
- package/dist/notion-page-picker.js +1 -1
- package/dist/prompts/memory_agent_external_ingest.md +2 -0
- package/dist/public-ingest.d.ts +3 -1
- package/dist/public-ingest.js +4 -0
- package/dist/scan.d.ts +3 -1
- package/dist/scan.js +7 -0
- package/dist/setup-databases.d.ts +1 -1
- package/dist/setup-databases.js +43 -1
- package/dist/setup-sources.d.ts +6 -1
- package/dist/setup-sources.js +208 -48
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +6 -1
- package/dist/skills/analytics/SKILL.md +200 -2
- package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
- package/dist/skills/sigma_ingest/SKILL.md +189 -0
- package/dist/skills/wiki_capture/SKILL.md +24 -0
- package/dist/sql.js +4 -0
- package/dist/status-project.d.ts +4 -0
- package/dist/status-project.js +54 -2
- package/dist/telemetry/events.d.ts +2 -2
- package/dist/text-ingest.d.ts +4 -0
- package/dist/text-ingest.js +58 -29
- package/dist/verbatim-ingest.d.ts +46 -0
- package/dist/verbatim-ingest.js +193 -0
- package/package.json +5 -1
- package/dist/context/ingest/final-gate-repair.d.ts +0 -28
- package/dist/context/ingest/final-gate-repair.js +0 -91
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import Database from 'better-sqlite3';
|
|
4
|
+
const RESULTS_TABLE = 'local_content_results';
|
|
5
|
+
const RESULTS_PRIMARY_KEY = ['namespace', 'scope_key', 'input_hash'];
|
|
6
|
+
function isSafeRunId(runId) {
|
|
7
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(runId);
|
|
8
|
+
}
|
|
9
|
+
function parseResultRow(row) {
|
|
10
|
+
const base = {
|
|
11
|
+
runId: row.run_id,
|
|
12
|
+
namespace: row.namespace,
|
|
13
|
+
scopeKey: row.scope_key,
|
|
14
|
+
inputHash: row.input_hash,
|
|
15
|
+
metadata: JSON.parse(row.metadata_json || '{}'),
|
|
16
|
+
updatedAt: row.updated_at,
|
|
17
|
+
};
|
|
18
|
+
if (row.status === 'completed') {
|
|
19
|
+
return {
|
|
20
|
+
...base,
|
|
21
|
+
status: 'completed',
|
|
22
|
+
output: JSON.parse(row.output_json ?? 'null'),
|
|
23
|
+
errorMessage: null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
status: 'failed',
|
|
29
|
+
output: null,
|
|
30
|
+
errorMessage: row.error_message ?? 'Unknown content result failure',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export class SqliteContentResultCache {
|
|
34
|
+
db;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
mkdirSync(dirname(options.dbPath), { recursive: true });
|
|
37
|
+
this.db = new Database(options.dbPath);
|
|
38
|
+
this.db.pragma('journal_mode = WAL');
|
|
39
|
+
this.db.exec('DROP TABLE IF EXISTS local_scan_enrichment_stages');
|
|
40
|
+
this.dropResultsTableIfPrimaryKeyDiffers();
|
|
41
|
+
this.db.exec(`
|
|
42
|
+
CREATE TABLE IF NOT EXISTS local_content_results (
|
|
43
|
+
run_id TEXT NOT NULL,
|
|
44
|
+
namespace TEXT NOT NULL,
|
|
45
|
+
scope_key TEXT NOT NULL,
|
|
46
|
+
input_hash TEXT NOT NULL,
|
|
47
|
+
status TEXT NOT NULL,
|
|
48
|
+
output_json TEXT,
|
|
49
|
+
error_message TEXT,
|
|
50
|
+
metadata_json TEXT NOT NULL,
|
|
51
|
+
updated_at TEXT NOT NULL,
|
|
52
|
+
PRIMARY KEY (namespace, scope_key, input_hash)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE INDEX IF NOT EXISTS local_content_results_lookup_idx
|
|
56
|
+
ON local_content_results (namespace, scope_key, input_hash, updated_at);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS local_content_results_run_idx
|
|
58
|
+
ON local_content_results (run_id, updated_at, namespace);
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
dropResultsTableIfPrimaryKeyDiffers() {
|
|
62
|
+
const columns = this.db.prepare(`PRAGMA table_info(${RESULTS_TABLE})`).all();
|
|
63
|
+
if (columns.length === 0) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const primaryKey = columns
|
|
67
|
+
.filter((column) => column.pk > 0)
|
|
68
|
+
.sort((left, right) => left.pk - right.pk)
|
|
69
|
+
.map((column) => column.name);
|
|
70
|
+
const matches = primaryKey.length === RESULTS_PRIMARY_KEY.length &&
|
|
71
|
+
primaryKey.every((name, index) => name === RESULTS_PRIMARY_KEY[index]);
|
|
72
|
+
if (!matches) {
|
|
73
|
+
this.db.exec(`DROP TABLE ${RESULTS_TABLE}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async findCompletedResult(input) {
|
|
77
|
+
const row = this.db
|
|
78
|
+
.prepare(`
|
|
79
|
+
SELECT *
|
|
80
|
+
FROM local_content_results
|
|
81
|
+
WHERE namespace = ?
|
|
82
|
+
AND scope_key = ?
|
|
83
|
+
AND input_hash = ?
|
|
84
|
+
AND status = 'completed'
|
|
85
|
+
ORDER BY updated_at DESC
|
|
86
|
+
LIMIT 1
|
|
87
|
+
`)
|
|
88
|
+
.get(input.namespace, input.scopeKey, input.inputHash);
|
|
89
|
+
if (!row) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const parsed = parseResultRow(row);
|
|
93
|
+
return parsed.status === 'completed' ? parsed : null;
|
|
94
|
+
}
|
|
95
|
+
async findLatestCompletedResult(input) {
|
|
96
|
+
const row = this.db
|
|
97
|
+
.prepare(`
|
|
98
|
+
SELECT *
|
|
99
|
+
FROM local_content_results
|
|
100
|
+
WHERE namespace = ?
|
|
101
|
+
AND scope_key = ?
|
|
102
|
+
AND status = 'completed'
|
|
103
|
+
ORDER BY updated_at DESC
|
|
104
|
+
LIMIT 1
|
|
105
|
+
`)
|
|
106
|
+
.get(input.namespace, input.scopeKey);
|
|
107
|
+
if (!row) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const parsed = parseResultRow(row);
|
|
111
|
+
return parsed.status === 'completed' ? parsed : null;
|
|
112
|
+
}
|
|
113
|
+
async saveCompletedResult(input) {
|
|
114
|
+
this.db
|
|
115
|
+
.prepare(`
|
|
116
|
+
INSERT INTO local_content_results (
|
|
117
|
+
run_id,
|
|
118
|
+
namespace,
|
|
119
|
+
scope_key,
|
|
120
|
+
input_hash,
|
|
121
|
+
status,
|
|
122
|
+
output_json,
|
|
123
|
+
error_message,
|
|
124
|
+
metadata_json,
|
|
125
|
+
updated_at
|
|
126
|
+
)
|
|
127
|
+
VALUES (
|
|
128
|
+
@runId,
|
|
129
|
+
@namespace,
|
|
130
|
+
@scopeKey,
|
|
131
|
+
@inputHash,
|
|
132
|
+
'completed',
|
|
133
|
+
@outputJson,
|
|
134
|
+
NULL,
|
|
135
|
+
@metadataJson,
|
|
136
|
+
@updatedAt
|
|
137
|
+
)
|
|
138
|
+
ON CONFLICT(namespace, scope_key, input_hash) DO UPDATE SET
|
|
139
|
+
run_id = excluded.run_id,
|
|
140
|
+
status = excluded.status,
|
|
141
|
+
output_json = excluded.output_json,
|
|
142
|
+
error_message = excluded.error_message,
|
|
143
|
+
metadata_json = excluded.metadata_json,
|
|
144
|
+
updated_at = excluded.updated_at
|
|
145
|
+
`)
|
|
146
|
+
.run({
|
|
147
|
+
runId: input.runId,
|
|
148
|
+
namespace: input.namespace,
|
|
149
|
+
scopeKey: input.scopeKey,
|
|
150
|
+
inputHash: input.inputHash,
|
|
151
|
+
outputJson: JSON.stringify(input.output),
|
|
152
|
+
metadataJson: JSON.stringify(input.metadata),
|
|
153
|
+
updatedAt: input.updatedAt,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async saveFailedResult(input) {
|
|
157
|
+
this.db
|
|
158
|
+
.prepare(`
|
|
159
|
+
INSERT INTO local_content_results (
|
|
160
|
+
run_id,
|
|
161
|
+
namespace,
|
|
162
|
+
scope_key,
|
|
163
|
+
input_hash,
|
|
164
|
+
status,
|
|
165
|
+
output_json,
|
|
166
|
+
error_message,
|
|
167
|
+
metadata_json,
|
|
168
|
+
updated_at
|
|
169
|
+
)
|
|
170
|
+
VALUES (
|
|
171
|
+
@runId,
|
|
172
|
+
@namespace,
|
|
173
|
+
@scopeKey,
|
|
174
|
+
@inputHash,
|
|
175
|
+
'failed',
|
|
176
|
+
NULL,
|
|
177
|
+
@errorMessage,
|
|
178
|
+
@metadataJson,
|
|
179
|
+
@updatedAt
|
|
180
|
+
)
|
|
181
|
+
ON CONFLICT(namespace, scope_key, input_hash) DO UPDATE SET
|
|
182
|
+
run_id = excluded.run_id,
|
|
183
|
+
status = excluded.status,
|
|
184
|
+
output_json = excluded.output_json,
|
|
185
|
+
error_message = excluded.error_message,
|
|
186
|
+
metadata_json = excluded.metadata_json,
|
|
187
|
+
updated_at = excluded.updated_at
|
|
188
|
+
`)
|
|
189
|
+
.run({
|
|
190
|
+
runId: input.runId,
|
|
191
|
+
namespace: input.namespace,
|
|
192
|
+
scopeKey: input.scopeKey,
|
|
193
|
+
inputHash: input.inputHash,
|
|
194
|
+
errorMessage: input.errorMessage,
|
|
195
|
+
metadataJson: JSON.stringify(input.metadata),
|
|
196
|
+
updatedAt: input.updatedAt,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
async deleteResult(input) {
|
|
200
|
+
this.db
|
|
201
|
+
.prepare(`
|
|
202
|
+
DELETE FROM local_content_results
|
|
203
|
+
WHERE namespace = ?
|
|
204
|
+
AND scope_key = ?
|
|
205
|
+
AND input_hash = ?
|
|
206
|
+
`)
|
|
207
|
+
.run(input.namespace, input.scopeKey, input.inputHash);
|
|
208
|
+
}
|
|
209
|
+
async listRunResults(runId) {
|
|
210
|
+
if (!isSafeRunId(runId)) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
const rows = this.db
|
|
214
|
+
.prepare(`
|
|
215
|
+
SELECT *
|
|
216
|
+
FROM local_content_results
|
|
217
|
+
WHERE run_id = ?
|
|
218
|
+
ORDER BY updated_at ASC, namespace ASC
|
|
219
|
+
`)
|
|
220
|
+
.all(runId);
|
|
221
|
+
return rows.map((row) => parseResultRow(row));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export declare function normalizeBigQueryProjectId(value: string, context?: string): string;
|
|
2
|
+
export declare function normalizeBigQueryDatasetId(value: string, context?: string): string;
|
|
2
3
|
export declare function normalizeBigQueryRegion(value: string, context?: string): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const BIGQUERY_PROJECT_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
2
|
+
const BIGQUERY_DATASET_ID_PATTERN = /^[A-Za-z0-9_]+$/;
|
|
2
3
|
const BIGQUERY_REGION_PATTERN = /^[a-z0-9-]+$/;
|
|
3
4
|
export function normalizeBigQueryProjectId(value, context = 'historic-SQL ingest') {
|
|
4
5
|
if (!BIGQUERY_PROJECT_ID_PATTERN.test(value)) {
|
|
@@ -6,6 +7,12 @@ export function normalizeBigQueryProjectId(value, context = 'historic-SQL ingest
|
|
|
6
7
|
}
|
|
7
8
|
return value;
|
|
8
9
|
}
|
|
10
|
+
export function normalizeBigQueryDatasetId(value, context = 'historic-SQL ingest') {
|
|
11
|
+
if (!BIGQUERY_DATASET_ID_PATTERN.test(value)) {
|
|
12
|
+
throw new Error(`Invalid BigQuery dataset id for ${context}: ${value}`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
9
16
|
export function normalizeBigQueryRegion(value, context = 'historic-SQL ingest') {
|
|
10
17
|
const normalized = value.trim().toLowerCase().replace(/^region-/, '');
|
|
11
18
|
if (!BIGQUERY_REGION_PATTERN.test(normalized)) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { KtxProjectConnectionConfig } from '../project/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validate a connection id supplied as an explicit command/tool argument against
|
|
4
|
+
* the canonical `ktx.yaml` connections map. Returns the id when configured;
|
|
5
|
+
* otherwise throws an error that lists the configured ids so the caller can fix
|
|
6
|
+
* the typo. Use for explicit arguments only — persisted page frontmatter that
|
|
7
|
+
* references a since-removed connection must warn, not fail.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertConfiguredConnectionId(connections: Record<string, KtxProjectConnectionConfig>, connectionId: string): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function listConfiguredConnectionIds(connections) {
|
|
2
|
+
return Object.keys(connections).sort();
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Validate a connection id supplied as an explicit command/tool argument against
|
|
6
|
+
* the canonical `ktx.yaml` connections map. Returns the id when configured;
|
|
7
|
+
* otherwise throws an error that lists the configured ids so the caller can fix
|
|
8
|
+
* the typo. Use for explicit arguments only — persisted page frontmatter that
|
|
9
|
+
* references a since-removed connection must warn, not fail.
|
|
10
|
+
*/
|
|
11
|
+
export function assertConfiguredConnectionId(connections, connectionId) {
|
|
12
|
+
if (Object.hasOwn(connections, connectionId)) {
|
|
13
|
+
return connectionId;
|
|
14
|
+
}
|
|
15
|
+
const ids = listConfiguredConnectionIds(connections);
|
|
16
|
+
const configured = ids.length > 0 ? ids.join(', ') : '(none configured)';
|
|
17
|
+
throw new Error(`Unknown connection "${connectionId}". Configured connections: ${configured}.`);
|
|
18
|
+
}
|
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import type { KtxConnectionDriver, KtxSchemaDimensionType, KtxTableRef } from '../scan/types.js';
|
|
2
2
|
import type { KtxDialectTableRef } from './dialect-helpers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Driver-agnostic dialect surface every connection implements, including
|
|
5
|
+
* non-SQL sources like MongoDB: display/ref formatting and type mapping. The
|
|
6
|
+
* catalog and entity-details paths resolve this for any snapshot driver, so it
|
|
7
|
+
* must stay free of SQL generation.
|
|
8
|
+
*/
|
|
3
9
|
export interface KtxDialect {
|
|
4
10
|
readonly type: KtxConnectionDriver;
|
|
5
|
-
quoteIdentifier(identifier: string): string;
|
|
6
|
-
formatTableName(table: KtxDialectTableRef): string;
|
|
7
11
|
formatDisplayRef(table: KtxDialectTableRef): string;
|
|
8
12
|
parseDisplayRef(display: string): KtxTableRef | null;
|
|
9
13
|
columnDisplayTablePartCount(): 1 | 2 | 3;
|
|
14
|
+
mapToDimensionType(nativeType: string): KtxSchemaDimensionType;
|
|
15
|
+
mapDataType(nativeType: string): string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* SQL query generation, implemented only by SQL warehouse drivers. The relationship
|
|
19
|
+
* profiling/validation pipeline is the sole caller and is gated on the
|
|
20
|
+
* `readOnlySql` capability, so these methods are unreachable for a non-SQL source.
|
|
21
|
+
*/
|
|
22
|
+
export interface KtxSqlDialect extends KtxDialect {
|
|
23
|
+
quoteIdentifier(identifier: string): string;
|
|
24
|
+
formatTableName(table: KtxDialectTableRef): string;
|
|
10
25
|
getLimitOffsetClause(limit: number, offset?: number): string;
|
|
11
26
|
getTopClause(limit: number): string;
|
|
12
27
|
getRandomSampleFilter(samplePct: number): string;
|
|
@@ -22,7 +37,18 @@ export interface KtxDialect {
|
|
|
22
37
|
getDistinctCountExpression(column: string): string;
|
|
23
38
|
textLengthExpression(columnSql: string): string;
|
|
24
39
|
castToText(columnSql: string): string;
|
|
25
|
-
mapToDimensionType(nativeType: string): KtxSchemaDimensionType;
|
|
26
|
-
mapDataType(nativeType: string): string;
|
|
27
40
|
}
|
|
28
41
|
export declare function getDialectForDriver(driver: string): KtxDialect;
|
|
42
|
+
export declare function getSqlDialectForDriver(driver: string): KtxSqlDialect;
|
|
43
|
+
/**
|
|
44
|
+
* Whether a driver can generate and execute SQL. Single source of truth for the
|
|
45
|
+
* SQL/non-SQL boundary: a driver is SQL-queryable iff it has a SQL dialect, so
|
|
46
|
+
* non-SQL sources (e.g. mongodb) are excluded without a hand-maintained list.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isSqlQueryableDriver(driver: string | undefined): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Refuse a non-SQL connection (e.g. mongodb) at a read-only-SQL entry point before
|
|
51
|
+
* any dialect selection or parser/daemon work, so it is never validated as Postgres.
|
|
52
|
+
* The federated `duckdb` connection has no driver — callers skip this guard for it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function assertSqlQueryableConnection(connectionId: string, driver: string | undefined): void;
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { KtxBigQueryDialect } from '../../connectors/bigquery/dialect.js';
|
|
2
2
|
import { KtxClickHouseDialect } from '../../connectors/clickhouse/dialect.js';
|
|
3
|
+
import { KtxMongoDbDialect } from '../../connectors/mongodb/dialect.js';
|
|
3
4
|
import { KtxMysqlDialect } from '../../connectors/mysql/dialect.js';
|
|
4
5
|
import { KtxPostgresDialect } from '../../connectors/postgres/dialect.js';
|
|
5
6
|
import { KtxSqliteDialect } from '../../connectors/sqlite/dialect.js';
|
|
6
7
|
import { KtxSnowflakeDialect } from '../../connectors/snowflake/dialect.js';
|
|
7
8
|
import { KtxSqlServerDialect } from '../../connectors/sqlserver/dialect.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'clickhouse',
|
|
11
|
-
'mysql',
|
|
12
|
-
'postgres',
|
|
13
|
-
'sqlite',
|
|
14
|
-
'snowflake',
|
|
15
|
-
'sqlserver',
|
|
16
|
-
];
|
|
17
|
-
const dialectFactories = {
|
|
9
|
+
import { KtxExpectedError } from '../../errors.js';
|
|
10
|
+
const sqlDialectFactories = {
|
|
18
11
|
bigquery: () => new KtxBigQueryDialect(),
|
|
19
12
|
clickhouse: () => new KtxClickHouseDialect(),
|
|
20
13
|
mysql: () => new KtxMysqlDialect(),
|
|
@@ -23,11 +16,45 @@ const dialectFactories = {
|
|
|
23
16
|
snowflake: () => new KtxSnowflakeDialect(),
|
|
24
17
|
sqlserver: () => new KtxSqlServerDialect(),
|
|
25
18
|
};
|
|
19
|
+
const dialectFactories = {
|
|
20
|
+
...sqlDialectFactories,
|
|
21
|
+
mongodb: () => new KtxMongoDbDialect(),
|
|
22
|
+
};
|
|
23
|
+
const supportedSqlDrivers = Object.keys(sqlDialectFactories).sort();
|
|
26
24
|
export function getDialectForDriver(driver) {
|
|
27
25
|
const normalized = driver.toLowerCase().trim();
|
|
28
26
|
const factory = dialectFactories[normalized];
|
|
29
27
|
if (factory) {
|
|
30
28
|
return factory();
|
|
31
29
|
}
|
|
32
|
-
throw new Error(`Unsupported
|
|
30
|
+
throw new Error(`Unsupported driver "${driver}". Supported drivers: ${Object.keys(dialectFactories).sort().join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
export function getSqlDialectForDriver(driver) {
|
|
33
|
+
const normalized = driver.toLowerCase().trim();
|
|
34
|
+
const factory = sqlDialectFactories[normalized];
|
|
35
|
+
if (factory) {
|
|
36
|
+
return factory();
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`Driver "${driver}" has no SQL dialect. SQL drivers: ${supportedSqlDrivers.join(', ')}`);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether a driver can generate and execute SQL. Single source of truth for the
|
|
42
|
+
* SQL/non-SQL boundary: a driver is SQL-queryable iff it has a SQL dialect, so
|
|
43
|
+
* non-SQL sources (e.g. mongodb) are excluded without a hand-maintained list.
|
|
44
|
+
*/
|
|
45
|
+
export function isSqlQueryableDriver(driver) {
|
|
46
|
+
const normalized = (driver ?? '').toLowerCase().trim();
|
|
47
|
+
return Object.prototype.hasOwnProperty.call(sqlDialectFactories, normalized);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Refuse a non-SQL connection (e.g. mongodb) at a read-only-SQL entry point before
|
|
51
|
+
* any dialect selection or parser/daemon work, so it is never validated as Postgres.
|
|
52
|
+
* The federated `duckdb` connection has no driver — callers skip this guard for it.
|
|
53
|
+
*/
|
|
54
|
+
export function assertSqlQueryableConnection(connectionId, driver) {
|
|
55
|
+
if (!isSqlQueryableDriver(driver)) {
|
|
56
|
+
throw new KtxExpectedError(`Connection '${connectionId}' uses the non-SQL driver '${driver ?? 'unknown'}'. ` +
|
|
57
|
+
'Read-only SQL (ktx sql, the sql_execution tool) requires a SQL warehouse connection; ' +
|
|
58
|
+
'MongoDB and other context-only sources are searchable and ingestable, not SQL-queryable.');
|
|
59
|
+
}
|
|
33
60
|
}
|
|
@@ -45,6 +45,27 @@ export const driverRegistrations = {
|
|
|
45
45
|
};
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
|
+
mongodb: {
|
|
49
|
+
driver: 'mongodb',
|
|
50
|
+
scopeConfigKey: 'databases',
|
|
51
|
+
hasHistoricSqlReader: false,
|
|
52
|
+
load: async () => {
|
|
53
|
+
const m = await import('../../connectors/mongodb/connector.js');
|
|
54
|
+
return {
|
|
55
|
+
isConnectionConfig: (connection) => {
|
|
56
|
+
const typedConnection = connection;
|
|
57
|
+
return m.isKtxMongoDbConnectionConfig(typedConnection);
|
|
58
|
+
},
|
|
59
|
+
createScanConnector: ({ connectionId, connection }) => {
|
|
60
|
+
const typedConnection = connection;
|
|
61
|
+
if (!m.isKtxMongoDbConnectionConfig(typedConnection)) {
|
|
62
|
+
throw invalidConnectionConfig('mongodb');
|
|
63
|
+
}
|
|
64
|
+
return new m.KtxMongoDbScanConnector({ connectionId, connection: typedConnection });
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
},
|
|
48
69
|
mysql: {
|
|
49
70
|
driver: 'mysql',
|
|
50
71
|
scopeConfigKey: 'schemas',
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { KtxProjectConnectionConfig } from '../project/config.js';
|
|
2
|
+
import type { GdrivePullConfig } from '../ingest/adapters/gdrive/types.js';
|
|
3
|
+
type RawKtxGdriveConnectionConfig = Extract<KtxProjectConnectionConfig, {
|
|
4
|
+
driver: 'gdrive';
|
|
5
|
+
}>;
|
|
6
|
+
export type KtxGdriveConnectionConfig = Omit<RawKtxGdriveConnectionConfig, 'service_account_key_ref' | 'folder_id' | 'recursive'> & {
|
|
7
|
+
driver: 'gdrive';
|
|
8
|
+
service_account_key_ref: string;
|
|
9
|
+
folder_id: string;
|
|
10
|
+
recursive: boolean;
|
|
11
|
+
};
|
|
12
|
+
interface ResolveKeyOptions {
|
|
13
|
+
readTextFile?: (path: string) => Promise<string>;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseGdriveConnectionConfig(raw: unknown): KtxGdriveConnectionConfig;
|
|
16
|
+
/** @internal */
|
|
17
|
+
export declare function resolveGdriveServiceAccountKey(serviceAccountKeyRef: string, options?: ResolveKeyOptions): Promise<string>;
|
|
18
|
+
export declare function gdriveConnectionToPullConfig(config: KtxGdriveConnectionConfig, options?: ResolveKeyOptions): Promise<GdrivePullConfig>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { gdrivePullConfigSchema } from '../ingest/adapters/gdrive/types.js';
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function expandHome(path) {
|
|
9
|
+
return path === '~' || path.startsWith('~/') ? resolve(homedir(), path.slice(2)) : path;
|
|
10
|
+
}
|
|
11
|
+
export function parseGdriveConnectionConfig(raw) {
|
|
12
|
+
if (!isRecord(raw)) {
|
|
13
|
+
throw new Error('gdrive connection config must be an object');
|
|
14
|
+
}
|
|
15
|
+
if (raw.driver !== 'gdrive') {
|
|
16
|
+
throw new Error('gdrive connection config requires driver: gdrive');
|
|
17
|
+
}
|
|
18
|
+
const keyRef = typeof raw.service_account_key_ref === 'string' && raw.service_account_key_ref.trim().length > 0 // pragma: allowlist secret
|
|
19
|
+
? raw.service_account_key_ref.trim()
|
|
20
|
+
: null;
|
|
21
|
+
if (!keyRef) {
|
|
22
|
+
throw new Error('gdrive connection config requires service_account_key_ref');
|
|
23
|
+
}
|
|
24
|
+
if (!keyRef.startsWith('file:')) {
|
|
25
|
+
throw new Error('gdrive service_account_key_ref must use file:/path/to/key.json');
|
|
26
|
+
}
|
|
27
|
+
const folderId = typeof raw.folder_id === 'string' && raw.folder_id.trim().length > 0 ? raw.folder_id.trim() : null;
|
|
28
|
+
if (!folderId) {
|
|
29
|
+
throw new Error('gdrive connection config requires folder_id');
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
driver: 'gdrive',
|
|
33
|
+
service_account_key_ref: keyRef,
|
|
34
|
+
folder_id: folderId,
|
|
35
|
+
recursive: raw.recursive === true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** @internal */
|
|
39
|
+
export async function resolveGdriveServiceAccountKey(serviceAccountKeyRef, options = {}) {
|
|
40
|
+
if (!serviceAccountKeyRef.startsWith('file:')) {
|
|
41
|
+
throw new Error('gdrive service_account_key_ref must use file:/path/to/key.json');
|
|
42
|
+
}
|
|
43
|
+
const path = expandHome(serviceAccountKeyRef.slice('file:'.length));
|
|
44
|
+
const readTextFile = options.readTextFile ?? ((filePath) => readFile(filePath, 'utf-8'));
|
|
45
|
+
const value = (await readTextFile(path)).trim();
|
|
46
|
+
if (!value) {
|
|
47
|
+
throw new Error(`gdrive service account key file is empty: ${path}`);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
export async function gdriveConnectionToPullConfig(config, options = {}) {
|
|
52
|
+
return gdrivePullConfigSchema.parse({
|
|
53
|
+
serviceAccountKey: await resolveGdriveServiceAccountKey(config.service_account_key_ref, options),
|
|
54
|
+
folderId: config.folder_id,
|
|
55
|
+
recursive: config.recursive,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { KtxQueryError } from '../../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical default bound on read-query execution time. Generous headroom over
|
|
4
|
+
* any indexed aggregate or normal profiling probe; a pathological nested-loop
|
|
5
|
+
* scan blows past it immediately. Overridable per-connection via
|
|
6
|
+
* `query_timeout_ms`. Production reads it through {@link resolveQueryDeadlineMs};
|
|
7
|
+
* exported for the resolver's own unit tests.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_QUERY_TIMEOUT_MS = 30000;
|
|
11
|
+
interface QueryTimeoutConnectionConfig {
|
|
12
|
+
query_timeout_ms?: unknown;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Single source of truth for the read-query deadline: the per-connection
|
|
17
|
+
* `query_timeout_ms` override (milliseconds) when present, else the default.
|
|
18
|
+
* Every connector resolves through here so the default and override precedence
|
|
19
|
+
* live in exactly one place. A malformed override (zero, negative, non-integer,
|
|
20
|
+
* non-number) is a config error — surfaced here even though `ktx.yaml`
|
|
21
|
+
* validation also rejects it, so programmatically-built connectors cannot
|
|
22
|
+
* silently run unbounded.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveQueryDeadlineMs(connection: QueryTimeoutConnectionConfig | undefined): number;
|
|
25
|
+
/**
|
|
26
|
+
* The canonical, driver-independent timeout error an agent sees regardless of
|
|
27
|
+
* which connector enforced the deadline. Reads in whole seconds. Remote
|
|
28
|
+
* connectors pass the driver's own timeout error as `cause`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function queryDeadlineExceededError(deadlineMs: number, options?: ErrorOptions): KtxQueryError;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { KtxQueryError } from '../../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical default bound on read-query execution time. Generous headroom over
|
|
4
|
+
* any indexed aggregate or normal profiling probe; a pathological nested-loop
|
|
5
|
+
* scan blows past it immediately. Overridable per-connection via
|
|
6
|
+
* `query_timeout_ms`. Production reads it through {@link resolveQueryDeadlineMs};
|
|
7
|
+
* exported for the resolver's own unit tests.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_QUERY_TIMEOUT_MS = 30_000;
|
|
11
|
+
/**
|
|
12
|
+
* Single source of truth for the read-query deadline: the per-connection
|
|
13
|
+
* `query_timeout_ms` override (milliseconds) when present, else the default.
|
|
14
|
+
* Every connector resolves through here so the default and override precedence
|
|
15
|
+
* live in exactly one place. A malformed override (zero, negative, non-integer,
|
|
16
|
+
* non-number) is a config error — surfaced here even though `ktx.yaml`
|
|
17
|
+
* validation also rejects it, so programmatically-built connectors cannot
|
|
18
|
+
* silently run unbounded.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveQueryDeadlineMs(connection) {
|
|
21
|
+
const raw = connection?.query_timeout_ms;
|
|
22
|
+
if (raw === undefined || raw === null) {
|
|
23
|
+
return DEFAULT_QUERY_TIMEOUT_MS;
|
|
24
|
+
}
|
|
25
|
+
if (typeof raw !== 'number' || !Number.isInteger(raw) || raw <= 0) {
|
|
26
|
+
throw new Error(`query_timeout_ms must be a positive integer in milliseconds, received ${JSON.stringify(raw)}.`);
|
|
27
|
+
}
|
|
28
|
+
return raw;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The canonical, driver-independent timeout error an agent sees regardless of
|
|
32
|
+
* which connector enforced the deadline. Reads in whole seconds. Remote
|
|
33
|
+
* connectors pass the driver's own timeout error as `cause`.
|
|
34
|
+
*/
|
|
35
|
+
export function queryDeadlineExceededError(deadlineMs, options) {
|
|
36
|
+
return new KtxQueryError(`query exceeded ${Math.round(deadlineMs / 1000)}s`, options);
|
|
37
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ChunkResult, DiffSet, ScopeDescriptor } from '../../types.js';
|
|
2
|
+
export declare function chunkGdriveStagedDir(stagedDir: string, diffSet?: DiffSet): Promise<ChunkResult>;
|
|
3
|
+
export declare function describeGdriveScope(stagedDir: string): Promise<ScopeDescriptor>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, relative } from 'node:path';
|
|
4
|
+
import { gdriveManifestSchema, gdriveMetadataSchema } from './types.js';
|
|
5
|
+
const GDRIVE_RECONCILE_GUIDANCE = 'Synthesize durable wiki knowledge from this Google Doc. Preserve product definitions, process documentation, and operating rules as wiki pages. Do not create semantic-layer sources from gdrive content in v1.';
|
|
6
|
+
function normalizeRawPath(path) {
|
|
7
|
+
return path.replace(/\\/g, '/');
|
|
8
|
+
}
|
|
9
|
+
async function walk(root) {
|
|
10
|
+
const entries = await readdir(root, { withFileTypes: true, recursive: true });
|
|
11
|
+
return entries
|
|
12
|
+
.filter((entry) => entry.isFile())
|
|
13
|
+
.map((entry) => normalizeRawPath(relative(root, join(entry.parentPath, entry.name))))
|
|
14
|
+
.sort();
|
|
15
|
+
}
|
|
16
|
+
function safeUnitKey(path) {
|
|
17
|
+
return `gdrive-${path.replace(/^docs\//, '').replace(/\/page\.md$/, '').replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-+|-+$/g, '')}`;
|
|
18
|
+
}
|
|
19
|
+
async function readManifest(stagedDir) {
|
|
20
|
+
try {
|
|
21
|
+
return gdriveManifestSchema.parse(JSON.parse(await readFile(join(stagedDir, 'manifest.json'), 'utf-8')));
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new Error(`Invalid gdrive manifest: ${error instanceof Error ? error.message : String(error)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function chunkGdriveStagedDir(stagedDir, diffSet) {
|
|
28
|
+
const files = await walk(stagedDir);
|
|
29
|
+
const manifest = await readManifest(stagedDir);
|
|
30
|
+
const touched = diffSet
|
|
31
|
+
? new Set([...diffSet.added, ...diffSet.modified].map((path) => normalizeRawPath(path)))
|
|
32
|
+
: null;
|
|
33
|
+
const workUnits = [];
|
|
34
|
+
for (const pagePath of files.filter((path) => path.endsWith('/page.md'))) {
|
|
35
|
+
const metadataPath = pagePath.replace(/\/page\.md$/, '/metadata.json');
|
|
36
|
+
const primary = [metadataPath, pagePath].filter((path) => files.includes(path));
|
|
37
|
+
if (touched && !primary.some((path) => touched.has(path))) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const metadata = gdriveMetadataSchema.parse(JSON.parse(await readFile(join(stagedDir, metadataPath), 'utf-8')));
|
|
41
|
+
const rawFiles = touched ? primary.filter((path) => touched.has(path)).sort() : primary.sort();
|
|
42
|
+
const dependencyPaths = ['manifest.json'].filter((path) => !rawFiles.includes(path));
|
|
43
|
+
const excluded = new Set([...rawFiles, ...dependencyPaths]);
|
|
44
|
+
const peerFileIndex = files.filter((path) => !excluded.has(path)).sort();
|
|
45
|
+
workUnits.push({
|
|
46
|
+
unitKey: safeUnitKey(pagePath),
|
|
47
|
+
displayLabel: metadata.path,
|
|
48
|
+
rawFiles,
|
|
49
|
+
dependencyPaths,
|
|
50
|
+
peerFileIndex,
|
|
51
|
+
notes: GDRIVE_RECONCILE_GUIDANCE,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
workUnits,
|
|
56
|
+
eviction: diffSet && diffSet.deleted.length > 0
|
|
57
|
+
? { deletedRawPaths: diffSet.deleted.map((path) => normalizeRawPath(path)).sort() }
|
|
58
|
+
: undefined,
|
|
59
|
+
reconcileNotes: ['Google Drive docs are knowledge-only in v1; keep output in wiki pages unless later follow-up work expands scope.'],
|
|
60
|
+
contextReport: { capped: false, warnings: manifest.warnings },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export async function describeGdriveScope(stagedDir) {
|
|
64
|
+
const manifest = await readManifest(stagedDir);
|
|
65
|
+
const scopeKey = JSON.stringify({
|
|
66
|
+
folderId: manifest.folderId,
|
|
67
|
+
recursive: manifest.recursive,
|
|
68
|
+
});
|
|
69
|
+
const fingerprint = createHash('sha256').update(scopeKey).digest('hex');
|
|
70
|
+
return {
|
|
71
|
+
fingerprint,
|
|
72
|
+
isPathInScope: (rawPath) => rawPath === 'manifest.json' || rawPath.startsWith('docs/'),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function detectGdriveStagedDir(stagedDir: string): Promise<boolean>;
|