@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.
Files changed (261) hide show
  1. package/assets/python/{kaelio_ktx-0.13.1-py3-none-any.whl → kaelio_ktx-0.15.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cli-program.js +1 -1
  5. package/dist/commands/ingest-commands.d.ts +9 -0
  6. package/dist/commands/ingest-commands.js +37 -1
  7. package/dist/commands/knowledge-commands.js +3 -0
  8. package/dist/commands/setup-commands.js +15 -1
  9. package/dist/connection-drivers.d.ts +2 -0
  10. package/dist/connection-drivers.js +7 -3
  11. package/dist/connection.d.ts +3 -0
  12. package/dist/connection.js +27 -0
  13. package/dist/connectors/bigquery/connector.d.ts +17 -6
  14. package/dist/connectors/bigquery/connector.js +152 -76
  15. package/dist/connectors/bigquery/dialect.d.ts +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +1 -0
  17. package/dist/connectors/clickhouse/connector.js +45 -16
  18. package/dist/connectors/clickhouse/dialect.d.ts +2 -2
  19. package/dist/connectors/mongodb/connector.d.ts +69 -0
  20. package/dist/connectors/mongodb/connector.js +307 -0
  21. package/dist/connectors/mongodb/dialect.d.ts +17 -0
  22. package/dist/connectors/mongodb/dialect.js +50 -0
  23. package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
  24. package/dist/connectors/mongodb/live-database-introspection.js +24 -0
  25. package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
  26. package/dist/connectors/mongodb/schema-inference.js +110 -0
  27. package/dist/connectors/mysql/connector.d.ts +1 -0
  28. package/dist/connectors/mysql/connector.js +18 -2
  29. package/dist/connectors/mysql/dialect.d.ts +2 -2
  30. package/dist/connectors/postgres/connector.d.ts +1 -0
  31. package/dist/connectors/postgres/connector.js +20 -3
  32. package/dist/connectors/postgres/dialect.d.ts +2 -2
  33. package/dist/connectors/snowflake/connector.d.ts +1 -0
  34. package/dist/connectors/snowflake/connector.js +27 -3
  35. package/dist/connectors/snowflake/dialect.d.ts +2 -2
  36. package/dist/connectors/sqlite/connector.d.ts +11 -1
  37. package/dist/connectors/sqlite/connector.js +120 -17
  38. package/dist/connectors/sqlite/dialect.d.ts +2 -2
  39. package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
  40. package/dist/connectors/sqlite/read-query-child.js +23 -0
  41. package/dist/connectors/sqlserver/connector.d.ts +2 -0
  42. package/dist/connectors/sqlserver/connector.js +20 -3
  43. package/dist/connectors/sqlserver/dialect.d.ts +2 -2
  44. package/dist/context/cache/content-result-cache.d.ts +36 -0
  45. package/dist/context/cache/content-result-cache.js +14 -0
  46. package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
  47. package/dist/context/cache/sqlite-content-result-cache.js +223 -0
  48. package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
  49. package/dist/context/connections/bigquery-identifiers.js +7 -0
  50. package/dist/context/connections/configured-connections.d.ts +9 -0
  51. package/dist/context/connections/configured-connections.js +18 -0
  52. package/dist/context/connections/dialects.d.ts +30 -4
  53. package/dist/context/connections/dialects.js +38 -11
  54. package/dist/context/connections/drivers.js +21 -0
  55. package/dist/context/connections/gdrive-config.d.ts +19 -0
  56. package/dist/context/connections/gdrive-config.js +57 -0
  57. package/dist/context/connections/query-deadline.d.ts +31 -0
  58. package/dist/context/connections/query-deadline.js +37 -0
  59. package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
  60. package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
  61. package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
  62. package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
  63. package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
  64. package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
  65. package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
  66. package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
  67. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
  68. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
  69. package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
  70. package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
  71. package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
  72. package/dist/context/ingest/adapters/gdrive/types.js +36 -0
  73. package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
  74. package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
  75. package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
  76. package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
  77. package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
  78. package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
  79. package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
  80. package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
  81. package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
  82. package/dist/context/ingest/adapters/live-database/stage.js +5 -5
  83. package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
  84. package/dist/context/ingest/adapters/metabase/types.js +1 -1
  85. package/dist/context/ingest/adapters/sigma/chunk.d.ts +6 -0
  86. package/dist/context/ingest/adapters/sigma/chunk.js +119 -0
  87. package/dist/context/ingest/adapters/sigma/client-port.d.ts +45 -0
  88. package/dist/context/ingest/adapters/sigma/client-port.js +1 -0
  89. package/dist/context/ingest/adapters/sigma/client.d.ts +33 -0
  90. package/dist/context/ingest/adapters/sigma/client.js +176 -0
  91. package/dist/context/ingest/adapters/sigma/detect.d.ts +1 -0
  92. package/dist/context/ingest/adapters/sigma/detect.js +23 -0
  93. package/dist/context/ingest/adapters/sigma/fetch.d.ts +14 -0
  94. package/dist/context/ingest/adapters/sigma/fetch.js +191 -0
  95. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.d.ts +17 -0
  96. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.js +41 -0
  97. package/dist/context/ingest/adapters/sigma/project.d.ts +8 -0
  98. package/dist/context/ingest/adapters/sigma/project.js +186 -0
  99. package/dist/context/ingest/adapters/sigma/sigma.adapter.d.ts +18 -0
  100. package/dist/context/ingest/adapters/sigma/sigma.adapter.js +43 -0
  101. package/dist/context/ingest/adapters/sigma/types.d.ts +80 -0
  102. package/dist/context/ingest/adapters/sigma/types.js +82 -0
  103. package/dist/context/ingest/artifact-gates.d.ts +32 -1
  104. package/dist/context/ingest/artifact-gates.js +85 -18
  105. package/dist/context/ingest/final-gate-prune.d.ts +35 -0
  106. package/dist/context/ingest/final-gate-prune.js +229 -0
  107. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  108. package/dist/context/ingest/ingest-bundle.runner.js +459 -240
  109. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
  110. package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
  111. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  112. package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
  113. package/dist/context/ingest/local-adapters.d.ts +2 -1
  114. package/dist/context/ingest/local-adapters.js +28 -0
  115. package/dist/context/ingest/local-bundle-runtime.js +7 -2
  116. package/dist/context/ingest/local-ingest.js +1 -1
  117. package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
  118. package/dist/context/ingest/local-stage-ingest.js +3 -1
  119. package/dist/context/ingest/ports.d.ts +3 -0
  120. package/dist/context/ingest/report-snapshot.js +13 -3
  121. package/dist/context/ingest/reports.d.ts +3 -3
  122. package/dist/context/ingest/reports.js +3 -1
  123. package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
  124. package/dist/context/ingest/stages/build-wu-context.js +2 -1
  125. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
  126. package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
  127. package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
  128. package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
  129. package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
  130. package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
  131. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
  132. package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
  133. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
  134. package/dist/context/ingest/types.d.ts +3 -0
  135. package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
  136. package/dist/context/ingest/wiki-body-refs.js +37 -8
  137. package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
  138. package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
  139. package/dist/context/ingest/work-unit-cache.d.ts +67 -0
  140. package/dist/context/ingest/work-unit-cache.js +230 -0
  141. package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
  142. package/dist/context/llm/ai-sdk-runtime.js +5 -0
  143. package/dist/context/llm/claude-code-runtime.d.ts +4 -1
  144. package/dist/context/llm/claude-code-runtime.js +38 -8
  145. package/dist/context/llm/codex-runtime.d.ts +4 -1
  146. package/dist/context/llm/codex-runtime.js +48 -35
  147. package/dist/context/llm/runtime-port.d.ts +26 -0
  148. package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
  149. package/dist/context/llm/subprocess-generate-object-child.js +34 -0
  150. package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
  151. package/dist/context/llm/subprocess-generate-object.js +122 -0
  152. package/dist/context/mcp/context-tools.d.ts +2 -0
  153. package/dist/context/mcp/context-tools.js +111 -17
  154. package/dist/context/mcp/local-project-ports.d.ts +7 -0
  155. package/dist/context/mcp/local-project-ports.js +29 -0
  156. package/dist/context/mcp/logger.d.ts +24 -0
  157. package/dist/context/mcp/logger.js +49 -0
  158. package/dist/context/mcp/server.js +2 -0
  159. package/dist/context/mcp/types.d.ts +17 -0
  160. package/dist/context/memory/memory-agent.service.js +1 -1
  161. package/dist/context/project/config.d.ts +72 -0
  162. package/dist/context/project/config.js +5 -0
  163. package/dist/context/project/driver-schemas.d.ts +35 -0
  164. package/dist/context/project/driver-schemas.js +89 -1
  165. package/dist/context/project/project.js +1 -1
  166. package/dist/context/project/setup-config.js +1 -0
  167. package/dist/context/scan/description-generation.d.ts +4 -0
  168. package/dist/context/scan/description-generation.js +100 -13
  169. package/dist/context/scan/enabled-tables.d.ts +5 -0
  170. package/dist/context/scan/enabled-tables.js +13 -1
  171. package/dist/context/scan/enrichment-state.d.ts +50 -7
  172. package/dist/context/scan/enrichment-state.js +30 -13
  173. package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
  174. package/dist/context/scan/local-enrichment-artifacts.js +155 -32
  175. package/dist/context/scan/local-enrichment.d.ts +39 -4
  176. package/dist/context/scan/local-enrichment.js +345 -92
  177. package/dist/context/scan/local-scan.d.ts +4 -1
  178. package/dist/context/scan/local-scan.js +54 -13
  179. package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
  180. package/dist/context/scan/local-structural-artifacts.js +5 -0
  181. package/dist/context/scan/object-introspection.d.ts +21 -0
  182. package/dist/context/scan/object-introspection.js +35 -0
  183. package/dist/context/scan/relationship-benchmarks.js +5 -3
  184. package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
  185. package/dist/context/scan/relationship-composite-candidates.js +13 -1
  186. package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
  187. package/dist/context/scan/relationship-detection-budget.js +53 -0
  188. package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
  189. package/dist/context/scan/relationship-diagnostics.js +2 -0
  190. package/dist/context/scan/relationship-discovery.d.ts +9 -3
  191. package/dist/context/scan/relationship-discovery.js +27 -4
  192. package/dist/context/scan/relationship-llm-proposal.js +36 -27
  193. package/dist/context/scan/relationship-profiling.d.ts +7 -3
  194. package/dist/context/scan/relationship-profiling.js +53 -41
  195. package/dist/context/scan/relationship-validation.d.ts +6 -4
  196. package/dist/context/scan/relationship-validation.js +46 -32
  197. package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
  198. package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
  199. package/dist/context/scan/types.d.ts +5 -3
  200. package/dist/context/scan/types.js +2 -0
  201. package/dist/context/sl/local-query.js +5 -0
  202. package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
  203. package/dist/context/sl/semantic-layer.service.js +1 -1
  204. package/dist/context/sl/source-files.d.ts +5 -0
  205. package/dist/context/sl/source-files.js +17 -11
  206. package/dist/context/sl/tools/connection-id-schema.js +1 -1
  207. package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
  208. package/dist/context/sql-analysis/dialect-notes.js +40 -0
  209. package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
  210. package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
  211. package/dist/context/sql-analysis/dialects/mysql.md +9 -0
  212. package/dist/context/sql-analysis/dialects/postgres.md +10 -0
  213. package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
  214. package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
  215. package/dist/context/sql-analysis/dialects/tsql.md +10 -0
  216. package/dist/context/wiki/keys.js +1 -1
  217. package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
  218. package/dist/context/wiki/knowledge-wiki.service.js +2 -1
  219. package/dist/context/wiki/local-knowledge.d.ts +13 -0
  220. package/dist/context/wiki/local-knowledge.js +50 -6
  221. package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
  222. package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
  223. package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
  224. package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
  225. package/dist/context/wiki/types.d.ts +6 -0
  226. package/dist/context-build-view.d.ts +3 -0
  227. package/dist/context-build-view.js +1 -0
  228. package/dist/knowledge.d.ts +2 -0
  229. package/dist/knowledge.js +12 -1
  230. package/dist/local-adapters.js +11 -0
  231. package/dist/mcp-http-server.js +15 -1
  232. package/dist/mcp-server-factory.d.ts +2 -0
  233. package/dist/mcp-server-factory.js +15 -1
  234. package/dist/mcp-stdio-server.js +10 -2
  235. package/dist/notion-page-picker.js +1 -1
  236. package/dist/prompts/memory_agent_external_ingest.md +2 -0
  237. package/dist/public-ingest.d.ts +3 -1
  238. package/dist/public-ingest.js +4 -0
  239. package/dist/scan.d.ts +3 -1
  240. package/dist/scan.js +7 -0
  241. package/dist/setup-databases.d.ts +1 -1
  242. package/dist/setup-databases.js +43 -1
  243. package/dist/setup-sources.d.ts +6 -1
  244. package/dist/setup-sources.js +208 -48
  245. package/dist/setup.d.ts +3 -0
  246. package/dist/setup.js +6 -1
  247. package/dist/skills/analytics/SKILL.md +200 -2
  248. package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
  249. package/dist/skills/sigma_ingest/SKILL.md +189 -0
  250. package/dist/skills/wiki_capture/SKILL.md +24 -0
  251. package/dist/sql.js +4 -0
  252. package/dist/status-project.d.ts +4 -0
  253. package/dist/status-project.js +54 -2
  254. package/dist/telemetry/events.d.ts +2 -2
  255. package/dist/text-ingest.d.ts +4 -0
  256. package/dist/text-ingest.js +58 -29
  257. package/dist/verbatim-ingest.d.ts +46 -0
  258. package/dist/verbatim-ingest.js +193 -0
  259. package/package.json +5 -1
  260. package/dist/context/ingest/final-gate-repair.d.ts +0 -28
  261. package/dist/context/ingest/final-gate-repair.js +0 -91
@@ -0,0 +1,307 @@
1
+ import { MongoClient } from 'mongodb';
2
+ import { resolveKtxConfigReference } from '../../context/core/config-reference.js';
3
+ import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
4
+ import { scopedTableNames } from '../../context/scan/table-ref.js';
5
+ import { getDialectForDriver } from '../../context/connections/dialects.js';
6
+ import { inferKtxMongoCollectionColumns, MONGO_ID_FIELD } from './schema-inference.js';
7
+ const DEFAULT_SAMPLE_SIZE = 1000;
8
+ const SAMPLE_MAX_TIME_MS = 30_000;
9
+ class DefaultMongoClient {
10
+ client;
11
+ connected = false;
12
+ constructor(url) {
13
+ this.client = new MongoClient(url);
14
+ }
15
+ async connectedClient() {
16
+ if (!this.connected) {
17
+ await this.client.connect();
18
+ this.connected = true;
19
+ }
20
+ return this.client;
21
+ }
22
+ async listCollections(databaseName) {
23
+ const client = await this.connectedClient();
24
+ const collections = await client.db(databaseName).listCollections({}, { nameOnly: false }).toArray();
25
+ return collections.map((collection) => ({ name: collection.name, type: collection.type }));
26
+ }
27
+ async estimatedDocumentCount(databaseName, collectionName) {
28
+ const client = await this.connectedClient();
29
+ return client.db(databaseName).collection(collectionName).estimatedDocumentCount();
30
+ }
31
+ async find(databaseName, collectionName, options) {
32
+ const client = await this.connectedClient();
33
+ return client
34
+ .db(databaseName)
35
+ .collection(collectionName)
36
+ .find({}, { sort: options.sort, limit: options.limit, maxTimeMS: SAMPLE_MAX_TIME_MS, ...(options.projection ? { projection: options.projection } : {}) })
37
+ .toArray();
38
+ }
39
+ async ping(databaseName) {
40
+ const client = await this.connectedClient();
41
+ await client.db(databaseName).command({ ping: 1 });
42
+ }
43
+ async close() {
44
+ if (this.connected) {
45
+ await this.client.close();
46
+ this.connected = false;
47
+ }
48
+ }
49
+ }
50
+ class DefaultMongoClientFactory {
51
+ create(url) {
52
+ return new DefaultMongoClient(url);
53
+ }
54
+ }
55
+ export function isKtxMongoDbConnectionConfig(connection) {
56
+ return String(connection?.driver ?? '').toLowerCase() === 'mongodb';
57
+ }
58
+ function databaseFromUrl(url) {
59
+ try {
60
+ const path = new URL(url).pathname.replace(/^\/+/, '');
61
+ const database = path.split('/')[0];
62
+ return database && database.length > 0 ? decodeURIComponent(database) : undefined;
63
+ }
64
+ catch {
65
+ return undefined;
66
+ }
67
+ }
68
+ function configuredDatabases(connection, fallback) {
69
+ if (Array.isArray(connection.databases)) {
70
+ const selected = connection.databases
71
+ .filter((database) => typeof database === 'string' && database.trim().length > 0)
72
+ .map((database) => database.trim());
73
+ if (selected.length > 0) {
74
+ return [...new Set(selected)];
75
+ }
76
+ }
77
+ const single = typeof connection.database === 'string' && connection.database.trim().length > 0
78
+ ? connection.database.trim()
79
+ : fallback;
80
+ return single ? [single] : [];
81
+ }
82
+ function positiveInteger(value, fallback) {
83
+ return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : fallback;
84
+ }
85
+ function normalizeSampleValue(value) {
86
+ if (value === null || value === undefined) {
87
+ return null;
88
+ }
89
+ if (value instanceof Date) {
90
+ return value.toISOString();
91
+ }
92
+ if (typeof value === 'object') {
93
+ const bsontype = value._bsontype;
94
+ return typeof bsontype === 'string' ? String(value) : JSON.stringify(value);
95
+ }
96
+ return value;
97
+ }
98
+ function unionDocumentKeys(documents) {
99
+ const keys = [];
100
+ const seen = new Set();
101
+ for (const document of documents) {
102
+ for (const key of Object.keys(document)) {
103
+ if (!seen.has(key)) {
104
+ seen.add(key);
105
+ keys.push(key);
106
+ }
107
+ }
108
+ }
109
+ return keys;
110
+ }
111
+ export class KtxMongoDbScanConnector {
112
+ id;
113
+ driver = 'mongodb';
114
+ capabilities = createKtxConnectorCapabilities({
115
+ tableSampling: true,
116
+ columnSampling: true,
117
+ columnStats: false,
118
+ readOnlySql: false,
119
+ nestedAnalysis: true,
120
+ formalForeignKeys: false,
121
+ estimatedRowCounts: true,
122
+ });
123
+ connectionId;
124
+ connection;
125
+ url;
126
+ databases;
127
+ sampleSize;
128
+ orderBy;
129
+ enabledTables;
130
+ clientFactory;
131
+ now;
132
+ dialect = getDialectForDriver('mongodb');
133
+ client = null;
134
+ constructor(options) {
135
+ const connection = options.connection ?? {};
136
+ const inputDriver = connection.driver ?? 'unknown';
137
+ if (!isKtxMongoDbConnectionConfig(connection)) {
138
+ throw new Error(`Native MongoDB connector cannot run driver "${inputDriver}"`);
139
+ }
140
+ const env = options.env ?? process.env;
141
+ const url = resolveKtxConfigReference(typeof connection.url === 'string' ? connection.url.trim() : undefined, env);
142
+ if (!url) {
143
+ throw new Error(`Native MongoDB connector requires connections.${options.connectionId}.url`);
144
+ }
145
+ const databases = configuredDatabases(connection, databaseFromUrl(url));
146
+ if (databases.length === 0) {
147
+ throw new Error(`Native MongoDB connector requires connections.${options.connectionId}.databases (or a database in the URL)`);
148
+ }
149
+ const enabledTables = Array.isArray(connection.enabled_tables)
150
+ ? new Set(connection.enabled_tables
151
+ .filter((table) => typeof table === 'string' && table.trim().length > 0)
152
+ .map((table) => table.trim()))
153
+ : null;
154
+ this.connectionId = options.connectionId;
155
+ this.connection = connection;
156
+ this.url = url;
157
+ this.databases = databases;
158
+ this.sampleSize = positiveInteger(connection.sample_size, DEFAULT_SAMPLE_SIZE);
159
+ this.orderBy = typeof connection.order_by === 'string' && connection.order_by.trim().length > 0
160
+ ? connection.order_by.trim()
161
+ : MONGO_ID_FIELD;
162
+ this.enabledTables = enabledTables && enabledTables.size > 0 ? enabledTables : null;
163
+ this.clientFactory = options.clientFactory ?? new DefaultMongoClientFactory();
164
+ this.now = options.now ?? (() => new Date());
165
+ this.id = `mongodb:${options.connectionId}`;
166
+ }
167
+ async testConnection() {
168
+ try {
169
+ await this.clientForQuery().ping(this.databases[0]);
170
+ return { success: true };
171
+ }
172
+ catch (error) {
173
+ return connectorTestFailure(error);
174
+ }
175
+ }
176
+ async introspect(input, _ctx) {
177
+ this.assertConnection(input.connectionId);
178
+ const client = this.clientForQuery();
179
+ const tables = [];
180
+ for (const database of this.databases) {
181
+ const scopedNames = input.tableScope
182
+ ? new Set(scopedTableNames(input.tableScope, { catalog: null, db: database }))
183
+ : null;
184
+ const collections = await client.listCollections(database);
185
+ for (const collection of collections) {
186
+ if (collection.name.startsWith('system.')) {
187
+ continue;
188
+ }
189
+ if (scopedNames && !scopedNames.has(collection.name)) {
190
+ continue;
191
+ }
192
+ if (this.enabledTables && !this.enabledTables.has(`${database}.${collection.name}`)) {
193
+ continue;
194
+ }
195
+ tables.push(await this.introspectCollection(client, database, collection));
196
+ }
197
+ }
198
+ return {
199
+ connectionId: this.connectionId,
200
+ driver: 'mongodb',
201
+ extractedAt: this.now().toISOString(),
202
+ scope: { schemas: this.databases },
203
+ metadata: {
204
+ databases: this.databases,
205
+ sample_size: this.sampleSize,
206
+ order_by: this.orderBy,
207
+ table_count: tables.length,
208
+ total_columns: tables.reduce((sum, table) => sum + table.columns.length, 0),
209
+ },
210
+ tables,
211
+ };
212
+ }
213
+ async introspectCollection(client, database, collection) {
214
+ const isView = collection.type === 'view';
215
+ // estimatedDocumentCount issues a count command, which MongoDB rejects on a
216
+ // view (CommandNotSupportedOnView); only count real collections.
217
+ const estimatedRows = isView ? null : await client.estimatedDocumentCount(database, collection.name);
218
+ const documents = await client.find(database, collection.name, {
219
+ sort: { [this.orderBy]: -1 },
220
+ limit: this.sampleSize,
221
+ });
222
+ return {
223
+ catalog: null,
224
+ db: database,
225
+ name: collection.name,
226
+ kind: isView ? 'view' : 'table',
227
+ comment: null,
228
+ estimatedRows,
229
+ columns: inferKtxMongoCollectionColumns(documents, this.dialect),
230
+ foreignKeys: [],
231
+ };
232
+ }
233
+ async sampleTable(input, _ctx) {
234
+ this.assertConnection(input.connectionId);
235
+ const { database, collection } = this.resolveTableRef(input.table);
236
+ const documents = await this.clientForQuery().find(database, collection, {
237
+ sort: { [this.orderBy]: -1 },
238
+ limit: input.limit,
239
+ });
240
+ const headers = input.columns && input.columns.length > 0 ? input.columns : unionDocumentKeys(documents);
241
+ const rows = documents.map((document) => headers.map((header) => normalizeSampleValue(document[header])));
242
+ return { headers, rows, totalRows: documents.length };
243
+ }
244
+ async sampleColumn(input, _ctx) {
245
+ this.assertConnection(input.connectionId);
246
+ const { database, collection } = this.resolveTableRef(input.table);
247
+ const documents = await this.clientForQuery().find(database, collection, {
248
+ sort: { [this.orderBy]: -1 },
249
+ limit: input.limit,
250
+ projection: { [input.column]: 1 },
251
+ });
252
+ const values = [];
253
+ let nullCount = 0;
254
+ for (const document of documents) {
255
+ const value = document[input.column];
256
+ if (value === null || value === undefined) {
257
+ nullCount += 1;
258
+ continue;
259
+ }
260
+ values.push(normalizeSampleValue(value));
261
+ }
262
+ return { values, nullCount, distinctCount: null };
263
+ }
264
+ async listSchemas() {
265
+ return [...this.databases];
266
+ }
267
+ async listTables(schemas) {
268
+ const client = this.clientForQuery();
269
+ const databases = schemas && schemas.length > 0 ? schemas : this.databases;
270
+ const entries = [];
271
+ for (const database of databases) {
272
+ const collections = await client.listCollections(database);
273
+ for (const collection of collections) {
274
+ if (collection.name.startsWith('system.')) {
275
+ continue;
276
+ }
277
+ entries.push({
278
+ catalog: null,
279
+ schema: database,
280
+ name: collection.name,
281
+ kind: collection.type === 'view' ? 'view' : 'table',
282
+ });
283
+ }
284
+ }
285
+ return entries;
286
+ }
287
+ async cleanup() {
288
+ if (this.client) {
289
+ await this.client.close();
290
+ this.client = null;
291
+ }
292
+ }
293
+ resolveTableRef(table) {
294
+ return { database: table.db ?? this.databases[0], collection: table.name };
295
+ }
296
+ clientForQuery() {
297
+ if (!this.client) {
298
+ this.client = this.clientFactory.create(this.url);
299
+ }
300
+ return this.client;
301
+ }
302
+ assertConnection(connectionId) {
303
+ if (connectionId !== this.connectionId) {
304
+ throw new Error(`ktx MongoDB connector ${this.id} cannot serve connection ${connectionId}`);
305
+ }
306
+ }
307
+ }
@@ -0,0 +1,17 @@
1
+ import type { KtxDialect } from '../../context/connections/dialects.js';
2
+ import type { KtxDialectTableRef } from '../../context/connections/dialect-helpers.js';
3
+ import type { KtxSchemaDimensionType, KtxTableRef } from '../../context/scan/types.js';
4
+ /**
5
+ * Display/type-mapping half of the dialect contract for MongoDB. Collections map
6
+ * to `db.collection` display refs (ansi two-part shape). MongoDB is a non-SQL
7
+ * source, so it implements {@link KtxDialect} only — never {@link KtxSqlDialect}.
8
+ */
9
+ export declare class KtxMongoDbDialect implements KtxDialect {
10
+ readonly type: "mongodb";
11
+ private readonly typeMappings;
12
+ formatDisplayRef(table: KtxDialectTableRef): string;
13
+ parseDisplayRef(display: string): KtxTableRef | null;
14
+ columnDisplayTablePartCount(): 1 | 2 | 3;
15
+ mapDataType(nativeType: string): string;
16
+ mapToDimensionType(nativeType: string): KtxSchemaDimensionType;
17
+ }
@@ -0,0 +1,50 @@
1
+ import { columnDisplayPartCount, formatDialectDisplayRef, parseDialectDisplayRef, } from '../../context/connections/dialect-helpers.js';
2
+ /**
3
+ * Display/type-mapping half of the dialect contract for MongoDB. Collections map
4
+ * to `db.collection` display refs (ansi two-part shape). MongoDB is a non-SQL
5
+ * source, so it implements {@link KtxDialect} only — never {@link KtxSqlDialect}.
6
+ */
7
+ export class KtxMongoDbDialect {
8
+ type = 'mongodb';
9
+ typeMappings = {
10
+ objectid: 'string',
11
+ string: 'string',
12
+ uuid: 'string',
13
+ binary: 'string',
14
+ regex: 'string',
15
+ int: 'number',
16
+ long: 'number',
17
+ double: 'number',
18
+ decimal: 'number',
19
+ bool: 'boolean',
20
+ date: 'time',
21
+ timestamp: 'time',
22
+ object: 'string',
23
+ array: 'string',
24
+ json: 'string',
25
+ null: 'string',
26
+ mixed: 'string',
27
+ };
28
+ formatDisplayRef(table) {
29
+ return formatDialectDisplayRef(table, 'ansi');
30
+ }
31
+ parseDisplayRef(display) {
32
+ return parseDialectDisplayRef(display, 'ansi');
33
+ }
34
+ columnDisplayTablePartCount() {
35
+ return columnDisplayPartCount('ansi');
36
+ }
37
+ mapDataType(nativeType) {
38
+ const normalized = nativeType.toLowerCase().trim();
39
+ if (normalized === 'object' || normalized === 'array') {
40
+ return 'json';
41
+ }
42
+ return normalized || 'mixed';
43
+ }
44
+ mapToDimensionType(nativeType) {
45
+ if (!nativeType) {
46
+ return 'string';
47
+ }
48
+ return this.typeMappings[nativeType.toLowerCase().trim()] ?? 'string';
49
+ }
50
+ }
@@ -0,0 +1,10 @@
1
+ import type { LiveDatabaseIntrospectionPort } from '../../context/ingest/adapters/live-database/types.js';
2
+ import type { KtxProjectConnectionConfig } from '../../context/project/config.js';
3
+ import { type KtxMongoClientFactory } from './connector.js';
4
+ interface CreateMongoDbLiveDatabaseIntrospectionOptions {
5
+ connections: Record<string, KtxProjectConnectionConfig>;
6
+ clientFactory?: KtxMongoClientFactory;
7
+ now?: () => Date;
8
+ }
9
+ export declare function createMongoDbLiveDatabaseIntrospection(options: CreateMongoDbLiveDatabaseIntrospectionOptions): LiveDatabaseIntrospectionPort;
10
+ export {};
@@ -0,0 +1,24 @@
1
+ import { KtxMongoDbScanConnector, } from './connector.js';
2
+ export function createMongoDbLiveDatabaseIntrospection(options) {
3
+ return {
4
+ async extractSchema(connectionId, introspectionOptions) {
5
+ const connection = options.connections[connectionId];
6
+ const connector = new KtxMongoDbScanConnector({
7
+ connectionId,
8
+ connection,
9
+ clientFactory: options.clientFactory,
10
+ now: options.now,
11
+ });
12
+ try {
13
+ return await connector.introspect({
14
+ connectionId,
15
+ driver: 'mongodb',
16
+ ...(introspectionOptions?.tableScope ? { tableScope: introspectionOptions.tableScope } : {}),
17
+ }, { runId: `mongodb-${connectionId}` });
18
+ }
19
+ finally {
20
+ await connector.cleanup();
21
+ }
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,20 @@
1
+ import type { KtxSchemaColumn } from '../../context/scan/types.js';
2
+ import type { KtxDialect } from '../../context/connections/dialects.js';
3
+ export type KtxMongoDocument = Record<string, unknown>;
4
+ /** Top-level field name MongoDB guarantees on every document; used as the primary key. */
5
+ export declare const MONGO_ID_FIELD = "_id";
6
+ /**
7
+ * Canonical BSON type name for a sampled value as the `mongodb` driver hydrates
8
+ * it: BSON wrapper objects expose `_bsontype`; everything else maps from the JS
9
+ * runtime type. Sub-documents and arrays collapse to opaque `object`/`array`.
10
+ * @internal
11
+ */
12
+ export declare function bsonTypeOf(value: unknown): string;
13
+ /**
14
+ * Infer a flat column schema from sampled documents. Each top-level field becomes
15
+ * one column: BSON types are unioned (a field seen with >1 type is `mixed` and
16
+ * treated as a string), nullability is derived from field presence and observed
17
+ * nulls, and sub-documents/arrays remain a single opaque `json` column. `_id` is
18
+ * the non-nullable primary key.
19
+ */
20
+ export declare function inferKtxMongoCollectionColumns(documents: readonly KtxMongoDocument[], dialect: KtxDialect): KtxSchemaColumn[];
@@ -0,0 +1,110 @@
1
+ /** Top-level field name MongoDB guarantees on every document; used as the primary key. */
2
+ export const MONGO_ID_FIELD = '_id';
3
+ const BSON_TYPE_NAMES = {
4
+ objectid: 'objectId',
5
+ int32: 'int',
6
+ long: 'long',
7
+ double: 'double',
8
+ decimal128: 'decimal',
9
+ binary: 'binary',
10
+ uuid: 'uuid',
11
+ timestamp: 'timestamp',
12
+ bsonregexp: 'regex',
13
+ bsonsymbol: 'string',
14
+ };
15
+ /**
16
+ * Canonical BSON type name for a sampled value as the `mongodb` driver hydrates
17
+ * it: BSON wrapper objects expose `_bsontype`; everything else maps from the JS
18
+ * runtime type. Sub-documents and arrays collapse to opaque `object`/`array`.
19
+ * @internal
20
+ */
21
+ export function bsonTypeOf(value) {
22
+ if (value === null || value === undefined) {
23
+ return 'null';
24
+ }
25
+ if (typeof value === 'string') {
26
+ return 'string';
27
+ }
28
+ if (typeof value === 'boolean') {
29
+ return 'bool';
30
+ }
31
+ if (typeof value === 'number') {
32
+ return Number.isInteger(value) ? 'int' : 'double';
33
+ }
34
+ if (typeof value === 'bigint') {
35
+ return 'long';
36
+ }
37
+ if (value instanceof Date) {
38
+ return 'date';
39
+ }
40
+ if (Array.isArray(value)) {
41
+ return 'array';
42
+ }
43
+ if (typeof value === 'object') {
44
+ const bsontype = value._bsontype;
45
+ if (typeof bsontype === 'string') {
46
+ return BSON_TYPE_NAMES[bsontype.toLowerCase()] ?? bsontype;
47
+ }
48
+ return 'object';
49
+ }
50
+ return 'mixed';
51
+ }
52
+ function resolveNativeType(types) {
53
+ if (types.size === 0) {
54
+ return 'null';
55
+ }
56
+ if (types.size > 1) {
57
+ return 'mixed';
58
+ }
59
+ return [...types][0];
60
+ }
61
+ /**
62
+ * Infer a flat column schema from sampled documents. Each top-level field becomes
63
+ * one column: BSON types are unioned (a field seen with >1 type is `mixed` and
64
+ * treated as a string), nullability is derived from field presence and observed
65
+ * nulls, and sub-documents/arrays remain a single opaque `json` column. `_id` is
66
+ * the non-nullable primary key.
67
+ */
68
+ export function inferKtxMongoCollectionColumns(documents, dialect) {
69
+ const total = documents.length;
70
+ const order = [];
71
+ const fields = new Map();
72
+ for (const document of documents) {
73
+ if (!document || typeof document !== 'object') {
74
+ continue;
75
+ }
76
+ for (const [name, value] of Object.entries(document)) {
77
+ let accumulator = fields.get(name);
78
+ if (!accumulator) {
79
+ accumulator = { types: new Set(), present: 0, nullSeen: false };
80
+ fields.set(name, accumulator);
81
+ order.push(name);
82
+ }
83
+ accumulator.present += 1;
84
+ const bsonType = bsonTypeOf(value);
85
+ if (bsonType === 'null') {
86
+ accumulator.nullSeen = true;
87
+ }
88
+ else {
89
+ accumulator.types.add(bsonType);
90
+ }
91
+ }
92
+ }
93
+ return order.map((name) => {
94
+ const accumulator = fields.get(name);
95
+ const nativeType = resolveNativeType(accumulator.types);
96
+ const isId = name === MONGO_ID_FIELD;
97
+ const nullable = isId
98
+ ? false
99
+ : accumulator.present < total || accumulator.nullSeen || accumulator.types.size === 0;
100
+ return {
101
+ name,
102
+ nativeType,
103
+ normalizedType: dialect.mapDataType(nativeType),
104
+ dimensionType: dialect.mapToDimensionType(nativeType),
105
+ nullable,
106
+ primaryKey: isId,
107
+ comment: null,
108
+ };
109
+ });
110
+ }
@@ -96,6 +96,7 @@ export declare class KtxMysqlScanConnector implements KtxScanConnector {
96
96
  private readonly poolFactory;
97
97
  private readonly endpointResolver?;
98
98
  private readonly now;
99
+ private readonly deadlineMs;
99
100
  private readonly dialect;
100
101
  private pool;
101
102
  private resolvedEndpoint;
@@ -1,5 +1,6 @@
1
1
  import mysql from 'mysql2/promise';
2
- import { getDialectForDriver } from '../../context/connections/dialects.js';
2
+ import { getSqlDialectForDriver } from '../../context/connections/dialects.js';
3
+ import { resolveQueryDeadlineMs, queryDeadlineExceededError } from '../../context/connections/query-deadline.js';
3
4
  import { resolveStringReference } from '../shared/string-reference.js';
4
5
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
5
6
  import { constraintDiscoveryWarning, tryConstraintQuery, } from '../../context/scan/constraint-discovery.js';
@@ -96,6 +97,10 @@ function isDeniedError(error) {
96
97
  code === 'ER_SPECIFIC_ACCESS_DENIED_ERROR' ||
97
98
  code === 'ER_DBACCESS_DENIED_ERROR');
98
99
  }
100
+ // errno 3024 = ER_QUERY_TIMEOUT, raised when max_execution_time is exceeded.
101
+ function isMysqlTimeoutError(error) {
102
+ return Boolean(error) && typeof error === 'object' && error.errno === 3024;
103
+ }
99
104
  function pushConstraintWarnings(warnings, schemas, kind) {
100
105
  for (const schema of schemas) {
101
106
  warnings.push(constraintDiscoveryWarning({ schema, kind }));
@@ -183,7 +188,8 @@ export class KtxMysqlScanConnector {
183
188
  poolFactory;
184
189
  endpointResolver;
185
190
  now;
186
- dialect = getDialectForDriver('mysql');
191
+ deadlineMs;
192
+ dialect = getSqlDialectForDriver('mysql');
187
193
  pool = null;
188
194
  resolvedEndpoint = null;
189
195
  constructor(options) {
@@ -197,6 +203,7 @@ export class KtxMysqlScanConnector {
197
203
  this.poolFactory = options.poolFactory ?? new DefaultMysqlPoolFactory();
198
204
  this.endpointResolver = options.endpointResolver;
199
205
  this.now = options.now ?? (() => new Date());
206
+ this.deadlineMs = resolveQueryDeadlineMs(this.connection);
200
207
  this.id = `mysql:${options.connectionId}`;
201
208
  }
202
209
  async testConnection() {
@@ -478,6 +485,9 @@ export class KtxMysqlScanConnector {
478
485
  const pool = await this.poolForQuery();
479
486
  const connection = await pool.getConnection();
480
487
  try {
488
+ // max_execution_time (ms) bounds read-only SELECTs server-side; our path
489
+ // only runs SELECT/WITH, so the session setting always applies.
490
+ await connection.query('SET SESSION max_execution_time = ?', [this.deadlineMs]);
481
491
  const [rows, fields] = await connection.query(assertReadOnlySql(sql), queryParams(params));
482
492
  const headers = fields.map((field) => field.name);
483
493
  const headerTypes = fields.map((field) => String(field.type ?? 'unknown'));
@@ -488,6 +498,12 @@ export class KtxMysqlScanConnector {
488
498
  totalRows: rows.length,
489
499
  };
490
500
  }
501
+ catch (error) {
502
+ if (isMysqlTimeoutError(error)) {
503
+ throw queryDeadlineExceededError(this.deadlineMs, { cause: error });
504
+ }
505
+ throw error;
506
+ }
491
507
  finally {
492
508
  connection.release();
493
509
  }
@@ -1,8 +1,8 @@
1
- import type { KtxDialect } from '../../context/connections/dialects.js';
1
+ import type { KtxSqlDialect } from '../../context/connections/dialects.js';
2
2
  import type { KtxSchemaDimensionType, KtxTableRef } from '../../context/scan/types.js';
3
3
  type MysqlTableNameRef = Pick<KtxTableRef, 'name'> & Partial<Pick<KtxTableRef, 'catalog' | 'db'>>;
4
4
  /** @internal */
5
- export declare class KtxMysqlDialect implements KtxDialect {
5
+ export declare class KtxMysqlDialect implements KtxSqlDialect {
6
6
  readonly type: "mysql";
7
7
  private readonly typeMappings;
8
8
  quoteIdentifier(identifier: string): string;
@@ -112,6 +112,7 @@ export declare class KtxPostgresScanConnector implements KtxScanConnector {
112
112
  private readonly poolFactory;
113
113
  private readonly endpointResolver?;
114
114
  private readonly now;
115
+ private readonly deadlineMs;
115
116
  private readonly dialect;
116
117
  private pool;
117
118
  private lastIdlePoolError;