@kaelio/ktx 0.13.1 → 0.14.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 (241) hide show
  1. package/assets/python/{kaelio_ktx-0.13.1-py3-none-any.whl → kaelio_ktx-0.14.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 +14 -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/artifact-gates.d.ts +32 -1
  86. package/dist/context/ingest/artifact-gates.js +85 -18
  87. package/dist/context/ingest/final-gate-prune.d.ts +35 -0
  88. package/dist/context/ingest/final-gate-prune.js +229 -0
  89. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  90. package/dist/context/ingest/ingest-bundle.runner.js +459 -240
  91. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
  92. package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
  93. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  94. package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
  95. package/dist/context/ingest/local-adapters.js +6 -0
  96. package/dist/context/ingest/local-bundle-runtime.js +7 -2
  97. package/dist/context/ingest/local-ingest.js +1 -1
  98. package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
  99. package/dist/context/ingest/local-stage-ingest.js +3 -1
  100. package/dist/context/ingest/ports.d.ts +3 -0
  101. package/dist/context/ingest/report-snapshot.js +13 -3
  102. package/dist/context/ingest/reports.d.ts +3 -3
  103. package/dist/context/ingest/reports.js +3 -1
  104. package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
  105. package/dist/context/ingest/stages/build-wu-context.js +2 -1
  106. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
  107. package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
  108. package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
  109. package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
  110. package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
  111. package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
  112. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
  113. package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
  114. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
  115. package/dist/context/ingest/types.d.ts +3 -0
  116. package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
  117. package/dist/context/ingest/wiki-body-refs.js +37 -8
  118. package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
  119. package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
  120. package/dist/context/ingest/work-unit-cache.d.ts +67 -0
  121. package/dist/context/ingest/work-unit-cache.js +230 -0
  122. package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
  123. package/dist/context/llm/ai-sdk-runtime.js +5 -0
  124. package/dist/context/llm/claude-code-runtime.d.ts +4 -1
  125. package/dist/context/llm/claude-code-runtime.js +38 -8
  126. package/dist/context/llm/codex-runtime.d.ts +4 -1
  127. package/dist/context/llm/codex-runtime.js +48 -35
  128. package/dist/context/llm/runtime-port.d.ts +26 -0
  129. package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
  130. package/dist/context/llm/subprocess-generate-object-child.js +34 -0
  131. package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
  132. package/dist/context/llm/subprocess-generate-object.js +122 -0
  133. package/dist/context/mcp/context-tools.d.ts +2 -0
  134. package/dist/context/mcp/context-tools.js +111 -17
  135. package/dist/context/mcp/local-project-ports.d.ts +7 -0
  136. package/dist/context/mcp/local-project-ports.js +29 -0
  137. package/dist/context/mcp/logger.d.ts +24 -0
  138. package/dist/context/mcp/logger.js +49 -0
  139. package/dist/context/mcp/server.js +2 -0
  140. package/dist/context/mcp/types.d.ts +17 -0
  141. package/dist/context/memory/memory-agent.service.js +1 -1
  142. package/dist/context/project/config.d.ts +42 -0
  143. package/dist/context/project/config.js +5 -0
  144. package/dist/context/project/driver-schemas.d.ts +20 -0
  145. package/dist/context/project/driver-schemas.js +50 -1
  146. package/dist/context/project/project.js +1 -1
  147. package/dist/context/project/setup-config.js +1 -0
  148. package/dist/context/scan/description-generation.d.ts +4 -0
  149. package/dist/context/scan/description-generation.js +100 -13
  150. package/dist/context/scan/enabled-tables.d.ts +5 -0
  151. package/dist/context/scan/enabled-tables.js +13 -1
  152. package/dist/context/scan/enrichment-state.d.ts +50 -7
  153. package/dist/context/scan/enrichment-state.js +30 -13
  154. package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
  155. package/dist/context/scan/local-enrichment-artifacts.js +155 -32
  156. package/dist/context/scan/local-enrichment.d.ts +39 -4
  157. package/dist/context/scan/local-enrichment.js +345 -92
  158. package/dist/context/scan/local-scan.d.ts +4 -1
  159. package/dist/context/scan/local-scan.js +54 -13
  160. package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
  161. package/dist/context/scan/local-structural-artifacts.js +5 -0
  162. package/dist/context/scan/object-introspection.d.ts +21 -0
  163. package/dist/context/scan/object-introspection.js +35 -0
  164. package/dist/context/scan/relationship-benchmarks.js +5 -3
  165. package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
  166. package/dist/context/scan/relationship-composite-candidates.js +13 -1
  167. package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
  168. package/dist/context/scan/relationship-detection-budget.js +53 -0
  169. package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
  170. package/dist/context/scan/relationship-diagnostics.js +2 -0
  171. package/dist/context/scan/relationship-discovery.d.ts +9 -3
  172. package/dist/context/scan/relationship-discovery.js +27 -4
  173. package/dist/context/scan/relationship-llm-proposal.js +36 -27
  174. package/dist/context/scan/relationship-profiling.d.ts +7 -3
  175. package/dist/context/scan/relationship-profiling.js +53 -41
  176. package/dist/context/scan/relationship-validation.d.ts +6 -4
  177. package/dist/context/scan/relationship-validation.js +46 -32
  178. package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
  179. package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
  180. package/dist/context/scan/types.d.ts +5 -3
  181. package/dist/context/scan/types.js +2 -0
  182. package/dist/context/sl/local-query.js +5 -0
  183. package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
  184. package/dist/context/sl/semantic-layer.service.js +1 -1
  185. package/dist/context/sl/source-files.d.ts +5 -0
  186. package/dist/context/sl/source-files.js +17 -11
  187. package/dist/context/sl/tools/connection-id-schema.js +1 -1
  188. package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
  189. package/dist/context/sql-analysis/dialect-notes.js +40 -0
  190. package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
  191. package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
  192. package/dist/context/sql-analysis/dialects/mysql.md +9 -0
  193. package/dist/context/sql-analysis/dialects/postgres.md +10 -0
  194. package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
  195. package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
  196. package/dist/context/sql-analysis/dialects/tsql.md +10 -0
  197. package/dist/context/wiki/keys.js +1 -1
  198. package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
  199. package/dist/context/wiki/knowledge-wiki.service.js +2 -1
  200. package/dist/context/wiki/local-knowledge.d.ts +13 -0
  201. package/dist/context/wiki/local-knowledge.js +50 -6
  202. package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
  203. package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
  204. package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
  205. package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
  206. package/dist/context/wiki/types.d.ts +6 -0
  207. package/dist/context-build-view.d.ts +3 -0
  208. package/dist/context-build-view.js +1 -0
  209. package/dist/knowledge.d.ts +2 -0
  210. package/dist/knowledge.js +12 -1
  211. package/dist/local-adapters.js +11 -0
  212. package/dist/mcp-http-server.js +15 -1
  213. package/dist/mcp-server-factory.d.ts +2 -0
  214. package/dist/mcp-server-factory.js +15 -1
  215. package/dist/mcp-stdio-server.js +10 -2
  216. package/dist/notion-page-picker.js +1 -1
  217. package/dist/prompts/memory_agent_external_ingest.md +2 -0
  218. package/dist/public-ingest.d.ts +3 -1
  219. package/dist/public-ingest.js +3 -0
  220. package/dist/scan.d.ts +3 -1
  221. package/dist/scan.js +7 -0
  222. package/dist/setup-databases.d.ts +1 -1
  223. package/dist/setup-databases.js +43 -1
  224. package/dist/setup-sources.d.ts +5 -1
  225. package/dist/setup-sources.js +124 -48
  226. package/dist/setup.d.ts +3 -0
  227. package/dist/setup.js +6 -1
  228. package/dist/skills/analytics/SKILL.md +200 -2
  229. package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
  230. package/dist/skills/wiki_capture/SKILL.md +24 -0
  231. package/dist/sql.js +4 -0
  232. package/dist/status-project.d.ts +4 -0
  233. package/dist/status-project.js +54 -2
  234. package/dist/telemetry/events.d.ts +2 -2
  235. package/dist/text-ingest.d.ts +4 -0
  236. package/dist/text-ingest.js +58 -29
  237. package/dist/verbatim-ingest.d.ts +46 -0
  238. package/dist/verbatim-ingest.js +193 -0
  239. package/package.json +5 -1
  240. package/dist/context/ingest/final-gate-repair.d.ts +0 -28
  241. package/dist/context/ingest/final-gate-repair.js +0 -91
@@ -1,5 +1,6 @@
1
1
  import { resolveStringReference } from '../shared/string-reference.js';
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 { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
4
5
  import { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
5
6
  import { scopedTableNames } from '../../context/scan/table-ref.js';
@@ -74,6 +75,10 @@ function isDeniedError(error) {
74
75
  const code = error.code;
75
76
  return code === '42501' || code === '42P01';
76
77
  }
78
+ // 57014 = query_canceled, which is how statement_timeout surfaces.
79
+ function isPostgresTimeoutError(error) {
80
+ return Boolean(error) && typeof error === 'object' && error.code === '57014';
81
+ }
77
82
  function queryRows(result) {
78
83
  const headers = (result.fields ?? []).map((field) => field.name);
79
84
  return result.rows.map((row) => headers.map((header) => row[header]));
@@ -169,9 +174,13 @@ export function postgresPoolConfigFromConfig(input) {
169
174
  : { host, port: numberValue(merged.port) ?? 5432, database, user, password }),
170
175
  };
171
176
  const searchPathSchemas = searchPathSchemasFromConnection(merged);
177
+ // statement_timeout (ms) bounds every query on connections from this pool, so
178
+ // the server itself aborts a runaway query and frees the connection cleanly.
179
+ const serverOptions = [`-c statement_timeout=${resolveQueryDeadlineMs(merged)}`];
172
180
  if (searchPathSchemas.length > 0) {
173
- config.options = `-c search_path=${searchPathSchemas.join(',')}`;
181
+ serverOptions.unshift(`-c search_path=${searchPathSchemas.join(',')}`);
174
182
  }
183
+ config.options = serverOptions.join(' ');
175
184
  if (merged.ssl && sslmode !== 'prefer' && sslmode !== 'disable') {
176
185
  config.ssl = { rejectUnauthorized: merged.rejectUnauthorized ?? true };
177
186
  }
@@ -195,7 +204,8 @@ export class KtxPostgresScanConnector {
195
204
  poolFactory;
196
205
  endpointResolver;
197
206
  now;
198
- dialect = getDialectForDriver('postgres');
207
+ deadlineMs;
208
+ dialect = getSqlDialectForDriver('postgres');
199
209
  pool = null;
200
210
  lastIdlePoolError = null;
201
211
  resolvedEndpoint = null;
@@ -210,6 +220,7 @@ export class KtxPostgresScanConnector {
210
220
  this.poolFactory = options.poolFactory ?? new DefaultPostgresPoolFactory();
211
221
  this.endpointResolver = options.endpointResolver;
212
222
  this.now = options.now ?? (() => new Date());
223
+ this.deadlineMs = resolveQueryDeadlineMs(this.connection);
213
224
  this.id = `postgres:${options.connectionId}`;
214
225
  }
215
226
  async testConnection() {
@@ -533,6 +544,12 @@ export class KtxPostgresScanConnector {
533
544
  rowCount: result.rows.length,
534
545
  };
535
546
  }
547
+ catch (error) {
548
+ if (isPostgresTimeoutError(error)) {
549
+ throw queryDeadlineExceededError(this.deadlineMs, { cause: error });
550
+ }
551
+ throw error;
552
+ }
536
553
  finally {
537
554
  client.release();
538
555
  }
@@ -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 PostgresTableNameRef = Pick<KtxTableRef, 'name'> & Partial<Pick<KtxTableRef, 'catalog' | 'db'>>;
4
4
  /** @internal */
5
- export declare class KtxPostgresDialect implements KtxDialect {
5
+ export declare class KtxPostgresDialect implements KtxSqlDialect {
6
6
  readonly type: "postgres";
7
7
  private readonly typeMappings;
8
8
  quoteIdentifier(identifier: string): string;
@@ -27,6 +27,7 @@ export interface KtxSnowflakeResolvedConnectionConfig {
27
27
  passphrase?: string;
28
28
  role?: string;
29
29
  maxConnections: number;
30
+ deadlineMs: number;
30
31
  }
31
32
  export interface KtxSnowflakeRawColumnMetadata {
32
33
  name: string;
@@ -1,5 +1,6 @@
1
1
  import { createPrivateKey } from 'node:crypto';
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 { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
@@ -43,6 +44,19 @@ function isDeniedError(error) {
43
44
  }
44
45
  return false;
45
46
  }
47
+ // Snowflake cancels with code 604 and a "reached its statement ... timeout"
48
+ // message once STATEMENT_TIMEOUT_IN_SECONDS elapses.
49
+ function isSnowflakeTimeoutError(error) {
50
+ if (!error || typeof error !== 'object') {
51
+ return false;
52
+ }
53
+ const code = error.code;
54
+ const message = error.message;
55
+ return (code === 604 ||
56
+ code === '604' ||
57
+ code === '000604' ||
58
+ (typeof message === 'string' && /reached its (statement|warehouse) .*timeout/i.test(message)));
59
+ }
46
60
  function normalizeSnowflakeValue(value, columnType) {
47
61
  if (columnType && DATE_TYPES.some((type) => columnType.toUpperCase().includes(type))) {
48
62
  if (typeof value === 'number') {
@@ -131,6 +145,7 @@ export function snowflakeConnectionConfigFromConfig(input) {
131
145
  connectionId: input.connectionId,
132
146
  defaultValue: 4,
133
147
  }),
148
+ deadlineMs: resolveQueryDeadlineMs(input.connection),
134
149
  };
135
150
  const role = stringConfigValue(input.connection, 'role', env);
136
151
  if (role) {
@@ -178,12 +193,21 @@ class SnowflakeSdkDriver {
178
193
  }
179
194
  async query(sql, params) {
180
195
  const binds = Array.isArray(params) ? toSnowflakeBinds(params) : undefined;
196
+ const statementTimeoutSeconds = Math.ceil(this.resolved.deadlineMs / 1000);
181
197
  try {
182
198
  const pool = await this.getPool();
183
- const result = await pool.use(async (connection) => this.executeSnowflakeQuery(connection, sql, binds));
199
+ const result = await pool.use(async (connection) => {
200
+ // Bound the statement server-side; Snowflake cancels and frees the
201
+ // warehouse slot when STATEMENT_TIMEOUT_IN_SECONDS is reached.
202
+ await this.executeSnowflakeQuery(connection, `ALTER SESSION SET STATEMENT_TIMEOUT_IN_SECONDS = ${statementTimeoutSeconds}`);
203
+ return this.executeSnowflakeQuery(connection, sql, binds);
204
+ });
184
205
  return { ...result, totalRows: result.rows.length, rowCount: result.rows.length };
185
206
  }
186
207
  catch (error) {
208
+ if (isSnowflakeTimeoutError(error)) {
209
+ throw queryDeadlineExceededError(this.resolved.deadlineMs, { cause: error });
210
+ }
187
211
  const message = error instanceof Error ? error.message : String(error);
188
212
  if (/timeout/i.test(message) && /pool|acquire/i.test(message)) {
189
213
  throw new Error("Snowflake session pool exhausted after 60s - consider lowering maxConnections or increasing your account's concurrent-statement limit.");
@@ -355,7 +379,7 @@ export class KtxSnowflakeScanConnector {
355
379
  });
356
380
  resolved;
357
381
  driverFactory;
358
- dialect = getDialectForDriver('snowflake');
382
+ dialect = getSqlDialectForDriver('snowflake');
359
383
  now;
360
384
  driverInstance = null;
361
385
  constructor(options) {
@@ -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 SnowflakeTableNameRef = Pick<KtxTableRef, 'name'> & Partial<Pick<KtxTableRef, 'catalog' | 'db'>>;
4
4
  /** @internal */
5
- export declare class KtxSnowflakeDialect implements KtxDialect {
5
+ export declare class KtxSnowflakeDialect implements KtxSqlDialect {
6
6
  readonly type: "snowflake";
7
7
  private readonly typeMappings;
8
8
  quoteIdentifier(identifier: string): string;
@@ -1,11 +1,15 @@
1
+ import { type ChildProcess } from 'node:child_process';
1
2
  import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
2
3
  export interface KtxSqliteConnectionConfig {
3
4
  driver?: string;
4
5
  path?: string;
5
6
  url?: string;
7
+ query_timeout_ms?: number;
6
8
  [key: string]: unknown;
7
9
  }
8
10
  /** @internal */
11
+ export declare function forkReadQueryChild(): ChildProcess;
12
+ /** @internal */
9
13
  export interface SqliteDatabasePathInput {
10
14
  connectionId: string;
11
15
  projectDir?: string;
@@ -13,6 +17,8 @@ export interface SqliteDatabasePathInput {
13
17
  }
14
18
  export interface KtxSqliteScanConnectorOptions extends SqliteDatabasePathInput {
15
19
  now?: () => Date;
20
+ /** @internal Test seam: spawn the read-query child so tests can observe its lifecycle. */
21
+ spawnReadQueryChild?: () => ChildProcess;
16
22
  }
17
23
  export interface KtxSqliteReadOnlyQueryInput extends KtxReadOnlyQueryInput {
18
24
  params?: Record<string, unknown> | unknown[];
@@ -36,6 +42,8 @@ export declare class KtxSqliteScanConnector implements KtxScanConnector {
36
42
  private readonly connectionId;
37
43
  private readonly dbPath;
38
44
  private readonly now;
45
+ private readonly deadlineMs;
46
+ private readonly spawnReadQueryChild;
39
47
  private readonly dialect;
40
48
  private db;
41
49
  constructor(options: KtxSqliteScanConnectorOptions);
@@ -46,7 +54,8 @@ export declare class KtxSqliteScanConnector implements KtxScanConnector {
46
54
  sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxTableSampleResult>;
47
55
  sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise<KtxColumnSampleResult>;
48
56
  columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise<KtxColumnStatsResult | null>;
49
- executeReadOnly(input: KtxSqliteReadOnlyQueryInput, _ctx: KtxScanContext): Promise<KtxQueryResult>;
57
+ executeReadOnly(input: KtxSqliteReadOnlyQueryInput, ctx: KtxScanContext): Promise<KtxQueryResult>;
58
+ private runReadQueryOffProcess;
50
59
  getColumnDistinctValues(table: KtxTableRef, columnName: string, options: KtxSqliteColumnDistinctValuesOptions): Promise<KtxSqliteColumnDistinctValuesResult | null>;
51
60
  getTableRowCount(tableName: string): Promise<number>;
52
61
  qTableName(table: Pick<KtxTableRef, 'name'>): string;
@@ -55,6 +64,7 @@ export declare class KtxSqliteScanConnector implements KtxScanConnector {
55
64
  private database;
56
65
  private query;
57
66
  private readTable;
67
+ private readRowCount;
58
68
  private mapForeignKeys;
59
69
  private assertConnection;
60
70
  }
@@ -3,11 +3,29 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { isAbsolute, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
- import { getDialectForDriver } from '../../context/connections/dialects.js';
6
+ import { fork } from 'node:child_process';
7
+ import { getSqlDialectForDriver } from '../../context/connections/dialects.js';
8
+ import { resolveQueryDeadlineMs, queryDeadlineExceededError } from '../../context/connections/query-deadline.js';
7
9
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
8
10
  import { normalizeQueryRows } from '../../context/connections/query-executor.js';
9
11
  import { connectorTestFailure, createKtxConnectorCapabilities } from '../../context/scan/types.js';
10
12
  import { scopedTableNames } from '../../context/scan/table-ref.js';
13
+ import { tryIntrospectObject } from '../../context/scan/object-introspection.js';
14
+ // In dist, connector.js and read-query-child.js are siblings; under vitest the
15
+ // compiled .js is absent and Node strips types from the .ts when forking it.
16
+ const readQueryChildUrl = existsSync(fileURLToPath(new URL('./read-query-child.js', import.meta.url)))
17
+ ? new URL('./read-query-child.js', import.meta.url)
18
+ : new URL('./read-query-child.ts', import.meta.url);
19
+ /** @internal */
20
+ export function forkReadQueryChild() {
21
+ // Empty execArgv so the child is a clean Node process (no inherited vitest /
22
+ // inspector flags); advanced serialization preserves BigInt/Buffer in rows.
23
+ return fork(readQueryChildUrl, {
24
+ execArgv: [],
25
+ serialization: 'advanced',
26
+ stdio: ['ignore', 'ignore', 'inherit', 'ipc'],
27
+ });
28
+ }
11
29
  function stringConfigValue(connection, key) {
12
30
  const value = connection?.[key];
13
31
  return typeof value === 'string' && value.trim().length > 0 ? resolveStringReference(key, value.trim()) : undefined;
@@ -66,12 +84,16 @@ export class KtxSqliteScanConnector {
66
84
  connectionId;
67
85
  dbPath;
68
86
  now;
69
- dialect = getDialectForDriver('sqlite');
87
+ deadlineMs;
88
+ spawnReadQueryChild;
89
+ dialect = getSqlDialectForDriver('sqlite');
70
90
  db = null;
71
91
  constructor(options) {
72
92
  this.connectionId = options.connectionId;
73
93
  this.dbPath = sqliteDatabasePathFromConfig(options);
74
94
  this.now = options.now ?? (() => new Date());
95
+ this.deadlineMs = resolveQueryDeadlineMs(options.connection);
96
+ this.spawnReadQueryChild = options.spawnReadQueryChild ?? forkReadQueryChild;
75
97
  this.id = `sqlite:${options.connectionId}`;
76
98
  }
77
99
  async testConnection() {
@@ -89,14 +111,24 @@ export class KtxSqliteScanConnector {
89
111
  async introspect(input, _ctx) {
90
112
  this.assertConnection(input.connectionId);
91
113
  const database = this.database();
92
- const scopedNames = input.tableScope ? scopedTableNames(input.tableScope, { catalog: null, db: null }) : null;
93
- const scopeClause = scopedNames ? `AND name IN (${scopedNames.map(() => '?').join(', ')})` : '';
94
- const rawTables = scopedNames && scopedNames.length === 0
95
- ? []
96
- : database
97
- .prepare(`SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ${scopeClause} ORDER BY name`)
98
- .all(...(scopedNames ?? []));
99
- const tables = rawTables.map((table) => this.readTable(database, table));
114
+ const allObjects = database
115
+ .prepare(`SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name`)
116
+ .all();
117
+ const scopedNames = input.tableScope
118
+ ? new Set(scopedTableNames(input.tableScope, { catalog: null, db: null }))
119
+ : null;
120
+ const selectedObjects = scopedNames ? allObjects.filter((object) => scopedNames.has(object.name)) : allObjects;
121
+ const tables = [];
122
+ const warnings = [];
123
+ for (const object of selectedObjects) {
124
+ const outcome = await tryIntrospectObject({ object: object.name }, () => this.readTable(database, object));
125
+ if (outcome.ok) {
126
+ tables.push(outcome.table);
127
+ }
128
+ else {
129
+ warnings.push(outcome.warning);
130
+ }
131
+ }
100
132
  const fileStats = existsSync(this.dbPath) ? statSync(this.dbPath) : null;
101
133
  return {
102
134
  connectionId: this.connectionId,
@@ -108,8 +140,12 @@ export class KtxSqliteScanConnector {
108
140
  file_size: fileStats ? fileStats.size : 0,
109
141
  table_count: tables.length,
110
142
  total_columns: tables.reduce((sum, table) => sum + table.columns.length, 0),
143
+ // Carries the full object inventory so a zero-match enabled_tables scope
144
+ // can report which objects were actually available.
145
+ ...(scopedNames ? { discovered_object_names: allObjects.map((object) => object.name) } : {}),
111
146
  },
112
147
  tables,
148
+ ...(warnings.length > 0 ? { warnings } : {}),
113
149
  };
114
150
  }
115
151
  async listSchemas() {
@@ -146,11 +182,71 @@ export class KtxSqliteScanConnector {
146
182
  async columnStats(_input, _ctx) {
147
183
  return null;
148
184
  }
149
- async executeReadOnly(input, _ctx) {
185
+ async executeReadOnly(input, ctx) {
150
186
  this.assertConnection(input.connectionId);
151
- const result = this.query(limitSqlForExecution(input.sql, input.maxRows), input.params);
187
+ // Validate and row-limit on the main thread so invalid SQL fails instantly
188
+ // without spawning a process and read-only enforcement stays at the boundary.
189
+ const sql = limitSqlForExecution(input.sql, input.maxRows);
190
+ const result = await this.runReadQueryOffProcess(sql, input.params, ctx.signal);
152
191
  return { ...result, rowCount: result.rows.length };
153
192
  }
193
+ // The LLM-SQL path runs off the event loop in a short-lived child process so a
194
+ // pathological scan cannot freeze the MCP server, and the deadline is enforced
195
+ // by SIGKILL-ing that process. A synchronous better-sqlite3 scan never yields,
196
+ // so a worker-thread terminate cannot interrupt it — only the OS reclaiming the
197
+ // whole process frees the CPU. One short-lived process per call; killed on
198
+ // completion, deadline, or external abort.
199
+ runReadQueryOffProcess(sql, params, signal) {
200
+ const deadlineMs = this.deadlineMs;
201
+ const dbPath = this.dbPath;
202
+ return new Promise((resolvePromise, rejectPromise) => {
203
+ const child = this.spawnReadQueryChild();
204
+ let settled = false;
205
+ const onDeadline = () => settle(() => rejectPromise(queryDeadlineExceededError(deadlineMs)));
206
+ const timer = setTimeout(onDeadline, deadlineMs);
207
+ function settle(finish) {
208
+ if (settled) {
209
+ return;
210
+ }
211
+ settled = true;
212
+ clearTimeout(timer);
213
+ signal?.removeEventListener('abort', onDeadline);
214
+ if (child.exitCode === null && child.signalCode === null) {
215
+ child.kill('SIGKILL');
216
+ }
217
+ finish();
218
+ }
219
+ child.on('message', (message) => {
220
+ if (message.ok) {
221
+ settle(() => resolvePromise({
222
+ headers: message.headers,
223
+ rows: normalizeQueryRows(message.rows),
224
+ totalRows: message.totalRows,
225
+ }));
226
+ }
227
+ else {
228
+ settle(() => rejectPromise(new Error(message.message)));
229
+ }
230
+ });
231
+ child.on('error', (error) => settle(() => rejectPromise(error)));
232
+ child.on('exit', (code, processSignal) => {
233
+ if (!settled) {
234
+ settle(() => rejectPromise(new Error(`SQLite read process exited before returning a result (code ${code}, signal ${processSignal}).`)));
235
+ }
236
+ });
237
+ if (signal?.aborted) {
238
+ onDeadline();
239
+ return;
240
+ }
241
+ signal?.addEventListener('abort', onDeadline, { once: true });
242
+ try {
243
+ child.send({ dbPath, sql, params });
244
+ }
245
+ catch (error) {
246
+ settle(() => rejectPromise(error instanceof Error ? error : new Error(String(error))));
247
+ }
248
+ });
249
+ }
154
250
  async getColumnDistinctValues(table, columnName, options) {
155
251
  const sampleSize = options.sampleSize ?? 10000;
156
252
  const tableName = this.qTableName(table);
@@ -213,11 +309,7 @@ export class KtxSqliteScanConnector {
213
309
  const foreignKeys = database
214
310
  .prepare(`PRAGMA foreign_key_list(${this.dialect.quoteIdentifier(table.name)})`)
215
311
  .all();
216
- const estimatedRows = table.type === 'table'
217
- ? Number(database
218
- .prepare(`SELECT COUNT(*) AS count FROM ${this.dialect.quoteIdentifier(table.name)}`)
219
- .get().count)
220
- : null;
312
+ const estimatedRows = table.type === 'table' ? this.readRowCount(database, table.name) : null;
221
313
  return {
222
314
  catalog: null,
223
315
  db: null,
@@ -237,6 +329,17 @@ export class KtxSqliteScanConnector {
237
329
  foreignKeys: this.mapForeignKeys(foreignKeys),
238
330
  };
239
331
  }
332
+ // A row-count read is profiling, not structure: a failure here leaves the
333
+ // object's structure intact rather than skipping the whole object.
334
+ readRowCount(database, name) {
335
+ try {
336
+ const row = database.prepare(`SELECT COUNT(*) AS count FROM ${this.dialect.quoteIdentifier(name)}`).get();
337
+ return Number(row.count);
338
+ }
339
+ catch {
340
+ return null;
341
+ }
342
+ }
240
343
  mapForeignKeys(rows) {
241
344
  return rows
242
345
  .sort((a, b) => a.id - b.id || a.seq - b.seq)
@@ -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 SqliteTableNameRef = Pick<KtxTableRef, 'name'> & Partial<Pick<KtxTableRef, 'catalog' | 'db'>>;
4
4
  /** @internal */
5
- export declare class KtxSqliteDialect implements KtxDialect {
5
+ export declare class KtxSqliteDialect implements KtxSqlDialect {
6
6
  readonly type: "sqlite";
7
7
  private readonly typeMappings;
8
8
  quoteIdentifier(identifier: string): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import Database from 'better-sqlite3';
2
+ process.once('message', (request) => {
3
+ let db;
4
+ let response;
5
+ try {
6
+ db = new Database(request.dbPath, { readonly: true, fileMustExist: true });
7
+ const statement = db.prepare(request.sql);
8
+ const rows = (request.params ? statement.all(request.params) : statement.all());
9
+ response = {
10
+ ok: true,
11
+ headers: statement.columns().map((column) => column.name),
12
+ rows,
13
+ totalRows: rows.length,
14
+ };
15
+ }
16
+ catch (error) {
17
+ response = { ok: false, message: error instanceof Error ? error.message : String(error) };
18
+ }
19
+ finally {
20
+ db?.close();
21
+ }
22
+ process.send?.(response, () => process.exit(0));
23
+ });
@@ -20,6 +20,7 @@ export interface KtxSqlServerPoolConfig {
20
20
  database: string;
21
21
  user: string;
22
22
  password?: string;
23
+ requestTimeout: number;
23
24
  options: {
24
25
  encrypt: true;
25
26
  trustServerCertificate: boolean;
@@ -109,6 +110,7 @@ export declare class KtxSqlServerScanConnector implements KtxScanConnector {
109
110
  private readonly poolFactory;
110
111
  private readonly endpointResolver?;
111
112
  private readonly now;
113
+ private readonly deadlineMs;
112
114
  private readonly dialect;
113
115
  private pool;
114
116
  private resolvedEndpoint;
@@ -1,5 +1,6 @@
1
1
  import { assertReadOnlySql, hoistLeadingCte, stripTrailingSqlNoise } from '../../context/connections/read-only-sql.js';
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 { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
4
5
  import { scopedTableNames } from '../../context/scan/table-ref.js';
5
6
  import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
@@ -128,6 +129,10 @@ function isDeniedError(error) {
128
129
  const number = error.number;
129
130
  return number === 229 || number === 230 || number === 297;
130
131
  }
132
+ // mssql raises a RequestError with code 'ETIMEOUT' once requestTimeout elapses.
133
+ function isSqlServerTimeoutError(error) {
134
+ return Boolean(error) && typeof error === 'object' && error.code === 'ETIMEOUT';
135
+ }
131
136
  function limitSqlForSqlServerExecution(sqlText, maxRows) {
132
137
  const trimmed = stripTrailingSqlNoise(assertReadOnlySql(sqlText));
133
138
  if (!maxRows) {
@@ -176,6 +181,7 @@ export function sqlServerConnectionPoolConfigFromConfig(input) {
176
181
  database,
177
182
  user,
178
183
  password: stringConfigValue(merged, 'password', env),
184
+ requestTimeout: resolveQueryDeadlineMs(merged),
179
185
  options: { encrypt: true, trustServerCertificate: merged.trustServerCertificate ?? true },
180
186
  pool: { max: maxConnections, min: 0, idleTimeoutMillis: 30000 },
181
187
  };
@@ -199,7 +205,8 @@ export class KtxSqlServerScanConnector {
199
205
  poolFactory;
200
206
  endpointResolver;
201
207
  now;
202
- dialect = getDialectForDriver('sqlserver');
208
+ deadlineMs;
209
+ dialect = getSqlDialectForDriver('sqlserver');
203
210
  pool = null;
204
211
  resolvedEndpoint = null;
205
212
  constructor(options) {
@@ -215,6 +222,7 @@ export class KtxSqlServerScanConnector {
215
222
  this.poolFactory = options.poolFactory ?? new DefaultSqlServerPoolFactory();
216
223
  this.endpointResolver = options.endpointResolver;
217
224
  this.now = options.now ?? (() => new Date());
225
+ this.deadlineMs = resolveQueryDeadlineMs(this.connection);
218
226
  this.id = `sqlserver:${options.connectionId}`;
219
227
  }
220
228
  async testConnection() {
@@ -548,7 +556,16 @@ export class KtxSqlServerScanConnector {
548
556
  request.input(key, value);
549
557
  }
550
558
  }
551
- const result = await request.query(assertReadOnlySql(query));
559
+ let result;
560
+ try {
561
+ result = await request.query(assertReadOnlySql(query));
562
+ }
563
+ catch (error) {
564
+ if (isSqlServerTimeoutError(error)) {
565
+ throw queryDeadlineExceededError(this.deadlineMs, { cause: error });
566
+ }
567
+ throw error;
568
+ }
552
569
  const recordset = result.recordset ?? [];
553
570
  const columnMetadata = recordset.columns ?? {};
554
571
  const metadataHeaders = Object.keys(columnMetadata);
@@ -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 SqlServerTableNameRef = Pick<KtxTableRef, 'name'> & Partial<Pick<KtxTableRef, 'catalog' | 'db'>>;
4
4
  /** @internal */
5
- export declare class KtxSqlServerDialect implements KtxDialect {
5
+ export declare class KtxSqlServerDialect implements KtxSqlDialect {
6
6
  readonly type: "sqlserver";
7
7
  private readonly typeMappings;
8
8
  quoteIdentifier(identifier: string): string;
@@ -0,0 +1,36 @@
1
+ type ContentCacheMetadata = Record<string, unknown>;
2
+ export interface ContentResultCacheLookup {
3
+ namespace: string;
4
+ scopeKey: string;
5
+ inputHash: string;
6
+ }
7
+ export interface ContentResultCacheCompleted<TOutput = unknown> extends ContentResultCacheLookup {
8
+ runId: string;
9
+ status: 'completed';
10
+ output: TOutput;
11
+ errorMessage: null;
12
+ metadata: ContentCacheMetadata;
13
+ updatedAt: string;
14
+ }
15
+ export interface ContentResultCacheFailed extends ContentResultCacheLookup {
16
+ runId: string;
17
+ status: 'failed';
18
+ output: null;
19
+ errorMessage: string;
20
+ metadata: ContentCacheMetadata;
21
+ updatedAt: string;
22
+ }
23
+ export type ContentResultCacheRecord<TOutput = unknown> = ContentResultCacheCompleted<TOutput> | ContentResultCacheFailed;
24
+ export interface ContentResultCache {
25
+ findCompletedResult<TOutput = unknown>(input: ContentResultCacheLookup): Promise<ContentResultCacheCompleted<TOutput> | null>;
26
+ findLatestCompletedResult(input: {
27
+ namespace: string;
28
+ scopeKey: string;
29
+ }): Promise<ContentResultCacheCompleted | null>;
30
+ saveCompletedResult<TOutput = unknown>(input: Omit<ContentResultCacheCompleted<TOutput>, 'status' | 'errorMessage'>): Promise<void>;
31
+ saveFailedResult(input: Omit<ContentResultCacheFailed, 'status' | 'output'>): Promise<void>;
32
+ deleteResult(input: ContentResultCacheLookup): Promise<void>;
33
+ listRunResults(runId: string): Promise<ContentResultCacheRecord[]>;
34
+ }
35
+ export declare function stableContentHash(value: unknown): string;
36
+ export {};
@@ -0,0 +1,14 @@
1
+ import { createHash } from 'node:crypto';
2
+ function stableJson(value) {
3
+ if (Array.isArray(value)) {
4
+ return `[${value.map(stableJson).join(',')}]`;
5
+ }
6
+ if (value && typeof value === 'object') {
7
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
8
+ return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(',')}}`;
9
+ }
10
+ return JSON.stringify(value) ?? 'undefined';
11
+ }
12
+ export function stableContentHash(value) {
13
+ return createHash('sha256').update(stableJson(value)).digest('hex');
14
+ }
@@ -0,0 +1,18 @@
1
+ import type { ContentResultCache, ContentResultCacheCompleted, ContentResultCacheFailed, ContentResultCacheLookup, ContentResultCacheRecord } from './content-result-cache.js';
2
+ export interface SqliteContentResultCacheOptions {
3
+ dbPath: string;
4
+ }
5
+ export declare class SqliteContentResultCache implements ContentResultCache {
6
+ private readonly db;
7
+ constructor(options: SqliteContentResultCacheOptions);
8
+ private dropResultsTableIfPrimaryKeyDiffers;
9
+ findCompletedResult<TOutput = unknown>(input: ContentResultCacheLookup): Promise<ContentResultCacheCompleted<TOutput> | null>;
10
+ findLatestCompletedResult(input: {
11
+ namespace: string;
12
+ scopeKey: string;
13
+ }): Promise<ContentResultCacheCompleted | null>;
14
+ saveCompletedResult<TOutput = unknown>(input: Omit<ContentResultCacheCompleted<TOutput>, 'status' | 'errorMessage'>): Promise<void>;
15
+ saveFailedResult(input: Omit<ContentResultCacheFailed, 'status' | 'output'>): Promise<void>;
16
+ deleteResult(input: ContentResultCacheLookup): Promise<void>;
17
+ listRunResults(runId: string): Promise<ContentResultCacheRecord[]>;
18
+ }