@cyanheads/mcp-ts-core 0.8.11 → 0.8.13

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 (57) hide show
  1. package/CLAUDE.md +2 -1
  2. package/README.md +1 -1
  3. package/changelog/0.8.x/0.8.12.md +31 -0
  4. package/changelog/0.8.x/0.8.13.md +27 -0
  5. package/dist/config/index.d.ts.map +1 -1
  6. package/dist/config/index.js +6 -1
  7. package/dist/config/index.js.map +1 -1
  8. package/dist/logs/combined.log +4 -4
  9. package/dist/logs/error.log +4 -4
  10. package/dist/services/canvas/core/CanvasInstance.d.ts +16 -5
  11. package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -1
  12. package/dist/services/canvas/core/CanvasInstance.js +22 -4
  13. package/dist/services/canvas/core/CanvasInstance.js.map +1 -1
  14. package/dist/services/canvas/core/CanvasRegistry.d.ts +5 -11
  15. package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
  16. package/dist/services/canvas/core/CanvasRegistry.js +5 -13
  17. package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
  18. package/dist/services/canvas/core/DataCanvas.d.ts +2 -3
  19. package/dist/services/canvas/core/DataCanvas.d.ts.map +1 -1
  20. package/dist/services/canvas/core/DataCanvas.js +3 -8
  21. package/dist/services/canvas/core/DataCanvas.js.map +1 -1
  22. package/dist/services/canvas/core/IDataCanvasProvider.d.ts +24 -6
  23. package/dist/services/canvas/core/IDataCanvasProvider.d.ts.map +1 -1
  24. package/dist/services/canvas/core/IDataCanvasProvider.js +4 -4
  25. package/dist/services/canvas/core/canvasFactory.d.ts +7 -15
  26. package/dist/services/canvas/core/canvasFactory.d.ts.map +1 -1
  27. package/dist/services/canvas/core/canvasFactory.js +7 -16
  28. package/dist/services/canvas/core/canvasFactory.js.map +1 -1
  29. package/dist/services/canvas/core/sqlGate.d.ts +90 -60
  30. package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
  31. package/dist/services/canvas/core/sqlGate.js +231 -84
  32. package/dist/services/canvas/core/sqlGate.js.map +1 -1
  33. package/dist/services/canvas/index.d.ts +2 -2
  34. package/dist/services/canvas/index.d.ts.map +1 -1
  35. package/dist/services/canvas/index.js +1 -1
  36. package/dist/services/canvas/index.js.map +1 -1
  37. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +59 -17
  38. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
  39. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +364 -222
  40. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
  41. package/dist/services/canvas/providers/duckdb/exportWriter.d.ts +13 -25
  42. package/dist/services/canvas/providers/duckdb/exportWriter.d.ts.map +1 -1
  43. package/dist/services/canvas/providers/duckdb/exportWriter.js +15 -29
  44. package/dist/services/canvas/providers/duckdb/exportWriter.js.map +1 -1
  45. package/dist/services/canvas/providers/duckdb/schemaSniffer.d.ts +19 -26
  46. package/dist/services/canvas/providers/duckdb/schemaSniffer.d.ts.map +1 -1
  47. package/dist/services/canvas/providers/duckdb/schemaSniffer.js +30 -56
  48. package/dist/services/canvas/providers/duckdb/schemaSniffer.js.map +1 -1
  49. package/dist/services/canvas/types.d.ts +33 -6
  50. package/dist/services/canvas/types.d.ts.map +1 -1
  51. package/dist/services/canvas/types.js +1 -2
  52. package/dist/services/canvas/types.js.map +1 -1
  53. package/dist/utils/internal/requestContext.d.ts +9 -4
  54. package/dist/utils/internal/requestContext.d.ts.map +1 -1
  55. package/dist/utils/internal/requestContext.js.map +1 -1
  56. package/package.json +2 -2
  57. package/skills/api-canvas/SKILL.md +42 -8
@@ -1,26 +1,25 @@
1
1
  /**
2
2
  * @fileoverview Read-only SQL gate for the DataCanvas primitive. Engine-agnostic
3
- * pure validators that the provider invokes after pulling DuckDB-specific
4
- * metadata (statement extraction, prepared-statement type, EXPLAIN plan JSON).
3
+ * pure validators the provider invokes after pulling DuckDB-specific metadata.
5
4
  *
6
- * Three layers of enforcement, each authoritative on its own:
5
+ * Four layers of enforcement, each authoritative:
7
6
  *
8
- * 1. **Single-statement check.** The provider parses the input via DuckDB's
9
- * `extractStatements` and passes the count here. Anything other than 1 is
10
- * rejected comment-hidden second statements, multi-statement smuggling,
11
- * and Unicode tricks all collapse here because DuckDB's parser is the
12
- * arbiter, not a regex.
13
- * 2. **Statement-type check.** The provider prepares the single statement and
14
- * passes the resulting `statementType`. We require `SELECT`. Any DDL, DML,
15
- * or utility (PRAGMA/ATTACH/COPY/INSTALL/LOAD/SET/EXECUTE) fails to type as
16
- * SELECT and is rejected here.
17
- * 3. **Plan-walk allowlist.** The provider runs `EXPLAIN (FORMAT JSON)` and
18
- * passes the plan JSON. We walk every node and reject if any operator
19
- * name is outside the curated allowlist defense-in-depth against future
20
- * DuckDB additions that might smuggle work into a SELECT envelope.
7
+ * 1. **Function deny-list (text scan).** Pre-EXPLAIN regex against the SQL
8
+ * (string literals stripped) for file/HTTP-reading table functions like
9
+ * `read_json`, `read_parquet`. These can lower into generic SEQ_SCAN
10
+ * operators that pass the operator allowlist; catching them by name closes
11
+ * that bypass.
12
+ * 2. **Single-statement check.** Reject anything other than exactly one
13
+ * statement parsed by DuckDB. Comment-hidden second statements, Unicode
14
+ * tricks, and multi-statement smuggling collapse here.
15
+ * 3. **Statement-type check.** Require `SELECT`. DDL, DML, and utility
16
+ * statements (PRAGMA/ATTACH/COPY/INSTALL/LOAD/SET/EXECUTE) fail to type as
17
+ * SELECT.
18
+ * 4. **Plan-walk allowlist + denied-function rescan.** Walk the
19
+ * `EXPLAIN (FORMAT JSON)` tree; reject any operator outside the allowlist
20
+ * or any string field referencing a deny-listed function.
21
21
  *
22
- * Rejection paths throw `ValidationError` with a structured `data.reason`
23
- * suitable for surfacing to the agent.
22
+ * Rejection paths throw `ValidationError` with a structured `data.reason`.
24
23
  *
25
24
  * @module src/services/canvas/core/sqlGate
26
25
  */
@@ -28,27 +27,47 @@ import { validationError } from '../../../types-global/errors.js';
28
27
  /** Subset of statement types the gate permits. */
29
28
  export const ALLOWED_STATEMENT_TYPES = new Set(['SELECT']);
30
29
  /**
31
- * Curated allowlist of operator names that can appear in an EXPLAIN plan.
32
- * Sourced from DuckDB's logical/physical-plan node families (1.5.x). Not
33
- * every member is reachable from a SELECT but every member is read-only.
34
- *
35
- * Pinned by `tests/canvas/sqlGate.fixtures.test.ts` against live DuckDB
36
- * EXPLAIN output so version bumps that add operators are caught in CI rather
37
- * than silently widening the gate.
38
- *
39
- * Operators **not** in this list cause rejection. Notable exclusions:
30
+ * Reason codes set on `validationError.data.reason` by gate assertions.
31
+ * Consumers can import the {@link SqlGateReason} union to translate gate
32
+ * denials into typed contract reasons without duplicating the strings.
33
+ */
34
+ export const SQL_GATE_REASONS = {
35
+ multiStatement: 'multi_statement',
36
+ nonSelectStatement: 'non_select_statement',
37
+ planOperatorNotAllowed: 'plan_operator_not_allowed',
38
+ deniedFunction: 'denied_function',
39
+ deniedFunctionInPlan: 'denied_function_in_plan',
40
+ identifierEmpty: 'identifier_empty',
41
+ identifierShape: 'identifier_shape',
42
+ identifierReserved: 'identifier_reserved',
43
+ };
44
+ /**
45
+ * Allowlist of read-only operator names that can appear in an EXPLAIN plan.
46
+ * Anything outside this set causes rejection. Notable exclusions:
40
47
  *
41
48
  * - `READ_CSV`, `READ_PARQUET`, `READ_JSON` — bypass canvas, read external files.
42
- * - `INSERT`, `UPDATE`, `DELETE`, `MERGE`, `CREATE_*`, `DROP_*`, `ALTER_*` — writes.
43
- * - `COPY_TO_FILE`, `BATCH_COPY_TO_FILE` — exports a SELECT to a file.
44
- * - `ATTACH`, `DETACH`, `LOAD`, `INSTALL`, `PRAGMA`, `SET`, `RESET` — utility.
49
+ * - `INSERT`, `UPDATE`, `DELETE`, `MERGE_INTO`, `CREATE_*`, `DROP`, `ALTER` — writes.
50
+ * - `COPY_TO_FILE`, `BATCH_COPY_TO_FILE`, `COPY_DATABASE`write a SELECT to a file/db.
51
+ * - `ATTACH`, `DETACH`, `LOAD`, `PRAGMA`, `SET`, `SET_VARIABLE`, `RESET`, `TRANSACTION`,
52
+ * `EXECUTE`, `PREPARE`, `VACUUM`, `EXPORT`, `EXPLAIN_ANALYZE`, `CREATE_SECRET` — utility/system.
53
+ * - `INOUT_FUNCTION` — table-valued function lowering (read_json/read_parquet/...);
54
+ * gated by the function deny-list rather than the operator allowlist.
55
+ *
56
+ * Source pinned against `PhysicalOperatorToString` in DuckDB v1.5.2:
57
+ * https://github.com/duckdb/duckdb/blob/v1.5.2/src/common/enums/physical_operator_type.cpp
45
58
  */
46
59
  export const ALLOWED_PLAN_OPERATORS = new Set([
47
- // Scans (registered tables only — file scans like READ_CSV are excluded)
60
+ // Scans (registered tables only)
61
+ 'TABLE_SCAN',
48
62
  'SEQ_SCAN',
49
63
  'COLUMN_DATA_SCAN',
50
64
  'CHUNK_SCAN',
65
+ 'CTE_SCAN',
66
+ 'REC_CTE_SCAN',
67
+ 'REC_REC_CTE_SCAN',
68
+ 'DELIM_SCAN',
51
69
  'EXPRESSION_SCAN',
70
+ 'POSITIONAL_SCAN',
52
71
  'DUMMY_SCAN',
53
72
  'EMPTY_RESULT',
54
73
  'IN_MEMORY_TABLE_SCAN',
@@ -64,6 +83,8 @@ export const ALLOWED_PLAN_OPERATORS = new Set([
64
83
  'CROSS_PRODUCT',
65
84
  'POSITIONAL_JOIN',
66
85
  'ASOF_JOIN',
86
+ 'LEFT_DELIM_JOIN',
87
+ 'RIGHT_DELIM_JOIN',
67
88
  'DELIM_JOIN',
68
89
  // Aggregates
69
90
  'HASH_GROUP_BY',
@@ -74,6 +95,7 @@ export const ALLOWED_PLAN_OPERATORS = new Set([
74
95
  // Distinct / set ops
75
96
  'HASH_DISTINCT',
76
97
  'DISTINCT',
98
+ 'LIMITED_DISTINCT',
77
99
  'UNION',
78
100
  // Sorting / limits
79
101
  'ORDER_BY',
@@ -83,11 +105,14 @@ export const ALLOWED_PLAN_OPERATORS = new Set([
83
105
  'STREAMING_LIMIT',
84
106
  // Window
85
107
  'WINDOW',
108
+ 'STREAMING_WINDOW',
86
109
  // Nested
87
110
  'UNNEST',
88
- // CTEs
111
+ // CTEs (DuckDB stringifies RECURSIVE_* as REC_*; long names kept for older versions)
89
112
  'CTE',
90
113
  'CTE_REF',
114
+ 'REC_CTE',
115
+ 'REC_KEY_CTE',
91
116
  'RECURSIVE_CTE',
92
117
  'MATERIALIZED_CTE',
93
118
  // Sampling
@@ -99,68 +124,176 @@ export const ALLOWED_PLAN_OPERATORS = new Set([
99
124
  'EXPLAIN',
100
125
  // Pivot/unpivot collapsed planner forms
101
126
  'PIVOT',
127
+ // Spatial — pre-staged, dormant until the `spatial` extension loads.
128
+ // See https://github.com/cyanheads/mcp-ts-core/issues/106.
129
+ 'RTREE_INDEX_SCAN',
102
130
  ]);
103
131
  /**
104
- * Public entry point validates the trio of `(statementCount, statementType,
105
- * planJson)`. Throws on the first violation, leaving the provider to pass
106
- * results back to the caller untouched on success.
132
+ * External-data table functions (files, HTTP, S3, lakehouse formats). These
133
+ * lower into generic scan operators that pass the operator allowlist, so the
134
+ * text-scan and plan-rescan defenses in this module catch them by name
135
+ * regardless of how DuckDB lowers them.
136
+ */
137
+ export const DENIED_TABLE_FUNCTIONS = new Set([
138
+ // CSV
139
+ 'read_csv',
140
+ 'read_csv_auto',
141
+ 'sniff_csv',
142
+ // JSON
143
+ 'read_json',
144
+ 'read_json_auto',
145
+ 'read_json_objects',
146
+ 'read_json_objects_auto',
147
+ 'read_ndjson',
148
+ 'read_ndjson_auto',
149
+ 'read_ndjson_objects',
150
+ // Parquet
151
+ 'read_parquet',
152
+ 'parquet_scan',
153
+ 'parquet_metadata',
154
+ 'parquet_schema',
155
+ 'parquet_file_metadata',
156
+ 'parquet_kv_metadata',
157
+ // Text / blob / glob
158
+ 'read_text',
159
+ 'read_blob',
160
+ 'glob',
161
+ // Iceberg / Delta
162
+ 'iceberg_scan',
163
+ 'iceberg_metadata',
164
+ 'iceberg_snapshots',
165
+ 'delta_scan',
166
+ // Postgres / MySQL / SQLite scanners (extension-loaded; defense-in-depth)
167
+ 'postgres_scan',
168
+ 'postgres_query',
169
+ 'mysql_scan',
170
+ 'mysql_query',
171
+ 'sqlite_scan',
172
+ 'sqlite_query',
173
+ // Spatial — pre-staged, dormant until the `spatial` extension loads.
174
+ // ST_Read is a GDAL-backed reader for ~50 vector formats; ST_Drivers exposes
175
+ // the bundled GDAL driver surface; rtree_index_dump leaks index internals.
176
+ // See https://github.com/cyanheads/mcp-ts-core/issues/106.
177
+ 'st_read',
178
+ 'st_drivers',
179
+ 'rtree_index_dump',
180
+ ]);
181
+ /**
182
+ * Call-shape regex (`name(`). Caller strips comments and string literals first
183
+ * so quoted text and comment-injected separators between a name and its `(`
184
+ * can't false-positive or bypass.
185
+ */
186
+ const DENIED_FUNCTION_CALL_REGEX = new RegExp(String.raw `\b(${[...DENIED_TABLE_FUNCTIONS].join('|')})\s*\(`, 'gi');
187
+ /**
188
+ * Bare-name regex for the plan-walk rescan only. DuckDB EXPLAIN metadata can
189
+ * spell out a function as `Function: read_json` without parens. Restricted to
190
+ * known function-name fields to avoid scanning user-projected string literals.
191
+ */
192
+ const DENIED_FUNCTION_BARE_REGEX = new RegExp(String.raw `\b(${[...DENIED_TABLE_FUNCTIONS].join('|')})\b`, 'gi');
193
+ /** Plan-node string fields where DuckDB stores lowered table-function names. */
194
+ const FUNCTION_METADATA_KEYS = new Set([
195
+ 'extra_info',
196
+ 'function',
197
+ 'function_name',
198
+ 'table_function',
199
+ 'source',
200
+ ]);
201
+ /** Strip SQL block and line comments. */
202
+ function stripSqlComments(sql) {
203
+ return sql.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/--[^\n]*/g, '');
204
+ }
205
+ /**
206
+ * Strip standard SQL string literals. Doesn't handle E-strings or DuckDB
207
+ * dollar-quoting; the plan-walk rescan catches anything that survives.
208
+ */
209
+ function stripSqlStringLiterals(sql) {
210
+ return sql.replace(/'(?:[^']|'')*'/g, "''");
211
+ }
212
+ /**
213
+ * Layer 1: pre-EXPLAIN function deny-list. Scans the SQL (string literals
214
+ * stripped) for calls to any function in {@link DENIED_TABLE_FUNCTIONS} so a
215
+ * malicious `read_json('/etc/passwd')` is rejected before reaching the planner.
216
+ */
217
+ export function assertNoDeniedFunctions(sql) {
218
+ if (typeof sql !== 'string' || sql.length === 0)
219
+ return;
220
+ const stripped = stripSqlStringLiterals(stripSqlComments(sql));
221
+ DENIED_FUNCTION_CALL_REGEX.lastIndex = 0;
222
+ const match = DENIED_FUNCTION_CALL_REGEX.exec(stripped);
223
+ if (match?.[1]) {
224
+ const fn = match[1].toLowerCase();
225
+ throw validationError(`Canvas query references disallowed table function: ${fn}. File-reading and external-data functions are not permitted.`, { reason: SQL_GATE_REASONS.deniedFunction, function: fn });
226
+ }
227
+ }
228
+ /**
229
+ * Layers 2-4. Throws on the first violation. Layer 1
230
+ * ({@link assertNoDeniedFunctions}) runs separately before `extractStatements`.
107
231
  */
108
232
  export function assertReadOnlyQuery(input) {
109
233
  assertSelectOnly(input);
110
234
  assertPlanReadOnly(input.planJson);
111
235
  }
112
236
  /**
113
- * Pre-EXPLAIN gate: validate statement count and type. Run before the EXPLAIN
114
- * call so non-SELECT statements (which DuckDB's EXPLAIN can't always wrap —
115
- * e.g. ATTACH/PRAGMA/COPY/INSTALL) fail with a structured ValidationError
116
- * here rather than a confusing parser error from EXPLAIN itself.
237
+ * Layers 2-3: validate statement count and type before EXPLAIN. Non-SELECT
238
+ * statements (ATTACH/PRAGMA/COPY/INSTALL/...) fail here with a structured
239
+ * ValidationError rather than a confusing parser error from EXPLAIN itself.
117
240
  */
118
241
  export function assertSelectOnly(input) {
119
242
  if (input.statementCount !== 1) {
120
243
  throw validationError('Canvas query must contain exactly one SQL statement.', {
121
- reason: 'multi_statement',
244
+ reason: SQL_GATE_REASONS.multiStatement,
122
245
  statementCount: input.statementCount,
123
246
  });
124
247
  }
125
248
  if (!ALLOWED_STATEMENT_TYPES.has(input.statementType)) {
126
- throw validationError(`Canvas query must be SELECT; got ${input.statementType}. Mutations must use registerTable, drop, or clear.`, { reason: 'non_select_statement', statementType: input.statementType });
249
+ throw validationError(`Canvas query must be SELECT; got ${input.statementType}. Mutations must use registerTable, drop, or clear.`, { reason: SQL_GATE_REASONS.nonSelectStatement, statementType: input.statementType });
127
250
  }
128
251
  }
129
252
  /**
130
- * Post-EXPLAIN gate: walk the plan tree and reject any operator outside the
131
- * curated allowlist. Defense-in-depth against future DuckDB additions that
132
- * smuggle work into a SELECT envelope.
253
+ * Layer 4: walk the plan tree and reject any operator outside the allowlist
254
+ * or any deny-listed table function smuggled into a generic scan operator.
133
255
  */
134
256
  export function assertPlanReadOnly(planJson) {
135
- const offending = collectDisallowedOperators(planJson);
257
+ const { offending, deniedFunctions } = collectPlanViolations(planJson);
258
+ if (deniedFunctions.size > 0) {
259
+ throw validationError(`Canvas query references disallowed table function in plan: ${[...deniedFunctions].sort().join(', ')}.`, {
260
+ reason: SQL_GATE_REASONS.deniedFunctionInPlan,
261
+ functions: [...deniedFunctions].sort(),
262
+ });
263
+ }
136
264
  if (offending.size > 0) {
137
265
  throw validationError(`Canvas query contains disallowed operators: ${[...offending].sort().join(', ')}.`, {
138
- reason: 'plan_operator_not_allowed',
266
+ reason: SQL_GATE_REASONS.planOperatorNotAllowed,
139
267
  operators: [...offending].sort(),
140
268
  });
141
269
  }
142
270
  }
143
271
  /**
144
- * Walks the EXPLAIN plan and returns the set of operator names not in
145
- * `ALLOWED_PLAN_OPERATORS`. Exported for fixture-driven tests that want
146
- * to inspect the gate's view of a plan without throwing.
147
- *
148
- * Tolerant of structural variation — DuckDB emits operator identity under
149
- * either `name` (logical plan) or `operator_type` (physical/profile plan)
150
- * depending on the EXPLAIN flavor. We honor both. Children traversal
151
- * supports `children`, `child`, and `inputs` arrays.
272
+ * Returns operator names not in `ALLOWED_PLAN_OPERATORS`. Exported for
273
+ * fixture-driven tests that want to inspect the gate's view without throwing.
274
+ * Use {@link collectPlanViolations} to also surface deny-listed function
275
+ * references in scan-operator metadata.
152
276
  */
153
277
  export function collectDisallowedOperators(planJson) {
278
+ return collectPlanViolations(planJson).offending;
279
+ }
280
+ /**
281
+ * Combined plan-walk: disallowed operators and deny-listed table-function
282
+ * references in string-valued fields, returned separately so callers can word
283
+ * errors appropriately.
284
+ */
285
+ export function collectPlanViolations(planJson) {
154
286
  const offending = new Set();
155
- walk(planJson, offending);
156
- return offending;
287
+ const deniedFunctions = new Set();
288
+ walk(planJson, offending, deniedFunctions);
289
+ return { offending, deniedFunctions };
157
290
  }
158
- function walk(node, offending) {
291
+ function walk(node, offending, deniedFunctions) {
159
292
  if (node === null || typeof node !== 'object')
160
293
  return;
161
294
  if (Array.isArray(node)) {
162
295
  for (const child of node)
163
- walk(child, offending);
296
+ walk(child, offending, deniedFunctions);
164
297
  return;
165
298
  }
166
299
  const obj = node;
@@ -168,19 +301,38 @@ function walk(node, offending) {
168
301
  if (operator !== undefined && !ALLOWED_PLAN_OPERATORS.has(operator)) {
169
302
  offending.add(operator);
170
303
  }
171
- // Traverse known child slots; ignore string/number leaves.
304
+ // read_json/read_parquet lower into generic scan operators whose source
305
+ // function appears in plan metadata, not the operator name. Use the bare
306
+ // regex on known function-name fields, the call-shape regex elsewhere.
307
+ for (const [key, value] of Object.entries(obj)) {
308
+ if (typeof value !== 'string')
309
+ continue;
310
+ const regex = FUNCTION_METADATA_KEYS.has(key)
311
+ ? DENIED_FUNCTION_BARE_REGEX
312
+ : DENIED_FUNCTION_CALL_REGEX;
313
+ collectDeniedFunctionMatches(value, regex, deniedFunctions);
314
+ }
172
315
  for (const key of ['children', 'child', 'inputs', 'plan', 'root']) {
173
316
  if (key in obj)
174
- walk(obj[key], offending);
317
+ walk(obj[key], offending, deniedFunctions);
318
+ }
319
+ }
320
+ function collectDeniedFunctionMatches(value, regex, sink) {
321
+ regex.lastIndex = 0;
322
+ let match = regex.exec(value);
323
+ while (match !== null) {
324
+ if (match[1])
325
+ sink.add(match[1].toLowerCase());
326
+ match = regex.exec(value);
175
327
  }
176
328
  }
177
329
  function readOperatorName(obj) {
178
- const candidates = ['name', 'operator_type', 'operator', 'type'];
179
- for (const key of candidates) {
330
+ // DuckDB emits operator identity under different keys depending on the
331
+ // EXPLAIN flavor (logical/physical/profile).
332
+ for (const key of ['name', 'operator_type', 'operator', 'type']) {
180
333
  const value = obj[key];
181
- if (typeof value === 'string' && value !== '') {
334
+ if (typeof value === 'string' && value !== '')
182
335
  return value.toUpperCase();
183
- }
184
336
  }
185
337
  return;
186
338
  }
@@ -188,16 +340,15 @@ function readOperatorName(obj) {
188
340
  // Identifier validation and quoting
189
341
  // ---------------------------------------------------------------------------
190
342
  /**
191
- * Allowed shape for canvas-local table and column names. Matches the
192
- * conservative SQL identifier convention: starts with letter/underscore,
193
- * followed by letters/digits/underscores, max 63 chars (PostgreSQL/DuckDB cap).
343
+ * Allowed shape for canvas table/column names. SQL identifier convention:
344
+ * letter/underscore start, letters/digits/underscores after, max 63 chars
345
+ * (PostgreSQL/DuckDB cap). Exported so consumers can reuse it in Zod schemas
346
+ * (`z.string().regex(CANVAS_IDENTIFIER_REGEX)`).
194
347
  */
195
- const IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
348
+ export const CANVAS_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
196
349
  /**
197
- * DuckDB reserved words that must not be used as bare identifiers. Not
198
- * exhaustive this is a courtesy guard so misnamed tables fail at register
199
- * time rather than confusing-error time. The `IDENTIFIER_REGEX` is the
200
- * authoritative shape gate.
350
+ * Courtesy guard against bare reserved words. Not exhaustive the shape gate
351
+ * is authoritative; this just produces a friendlier error at register time.
201
352
  */
202
353
  const RESERVED_IDENTIFIERS = new Set([
203
354
  'select',
@@ -237,29 +388,25 @@ const RESERVED_IDENTIFIERS = new Set([
237
388
  'with',
238
389
  'recursive',
239
390
  ]);
240
- /**
241
- * Validate an identifier for use as a canvas-local table or column name.
242
- * Throws `ValidationError` on rejection.
243
- */
391
+ /** Validate a canvas table or column name. Throws `ValidationError` on rejection. */
244
392
  export function assertValidIdentifier(value, kind) {
245
393
  if (typeof value !== 'string' || value.length === 0) {
246
394
  throw validationError(`Canvas ${kind} name must be a non-empty string.`, {
247
- reason: 'identifier_empty',
395
+ reason: SQL_GATE_REASONS.identifierEmpty,
248
396
  kind,
249
397
  });
250
398
  }
251
- if (!IDENTIFIER_REGEX.test(value)) {
252
- throw validationError(`Canvas ${kind} name "${value}" is invalid. Use letters, digits, and underscores; must start with a letter or underscore; max 63 chars.`, { reason: 'identifier_shape', kind, value });
399
+ if (!CANVAS_IDENTIFIER_REGEX.test(value)) {
400
+ throw validationError(`Canvas ${kind} name "${value}" is invalid. Use letters, digits, and underscores; must start with a letter or underscore; max 63 chars.`, { reason: SQL_GATE_REASONS.identifierShape, kind, value });
253
401
  }
254
402
  if (RESERVED_IDENTIFIERS.has(value.toLowerCase())) {
255
- throw validationError(`Canvas ${kind} name "${value}" is a reserved SQL keyword. Choose another name.`, { reason: 'identifier_reserved', kind, value });
403
+ throw validationError(`Canvas ${kind} name "${value}" is a reserved SQL keyword. Choose another name.`, { reason: SQL_GATE_REASONS.identifierReserved, kind, value });
256
404
  }
257
405
  }
258
406
  /**
259
- * Wrap an identifier in double quotes for safe inclusion in SQL. Internal
260
- * double quotes are doubled per the SQL standard. Callers should still
261
- * validate via {@link assertValidIdentifier} before quoting — this helper
262
- * only escapes; it does not validate shape.
407
+ * Double-quote-escape an identifier for SQL embedding. Validate via
408
+ * {@link assertValidIdentifier} first this helper only escapes, it does not
409
+ * check shape.
263
410
  */
264
411
  export function quoteIdentifier(value) {
265
412
  return `"${value.replace(/"/g, '""')}"`;
@@ -1 +1 @@
1
- {"version":3,"file":"sqlGate.js","sourceRoot":"","sources":["../../../../src/services/canvas/core/sqlGate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlGate.js","sourceRoot":"","sources":["../../../../src/services/canvas/core/sqlGate.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,7 @@ export { type AcquireResult, CanvasRegistry, type CanvasRegistryOptions, DEFAULT
15
15
  export { createCanvasService } from './core/canvasFactory.js';
16
16
  export { DataCanvas } from './core/DataCanvas.js';
17
17
  export type { IDataCanvasProvider } from './core/IDataCanvasProvider.js';
18
- export { ALLOWED_PLAN_OPERATORS, ALLOWED_STATEMENT_TYPES, assertReadOnlyQuery, assertValidIdentifier, collectDisallowedOperators, type DuckdbStatementType, quoteIdentifier, } from './core/sqlGate.js';
18
+ export { ALLOWED_PLAN_OPERATORS, ALLOWED_STATEMENT_TYPES, assertNoDeniedFunctions, assertReadOnlyQuery, assertValidIdentifier, CANVAS_IDENTIFIER_REGEX, collectDisallowedOperators, collectPlanViolations, DENIED_TABLE_FUNCTIONS, type DuckdbStatementType, quoteIdentifier, SQL_GATE_REASONS, type SqlGateReason, } from './core/sqlGate.js';
19
19
  export { DuckdbProvider, type DuckdbProviderOptions, } from './providers/duckdb/DuckdbProvider.js';
20
- export type { AcquireOptions, ColumnSchema, ColumnType, DescribeOptions, ExportFormat, ExportOptions, ExportResult, ExportTarget, QueryOptions, QueryResult, RegisterRows, RegisterTableOptions, RegisterTableResult, TableInfo, } from './types.js';
20
+ export type { AcquireOptions, CanvasObjectKind, ColumnSchema, ColumnType, DescribeOptions, ExportFormat, ExportOptions, ExportResult, ExportTarget, ImportFromOptions, QueryOptions, QueryResult, RegisterRows, RegisterTableOptions, RegisterTableResult, RegisterViewOptions, RegisterViewResult, TableInfo, } from './types.js';
21
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EACL,KAAK,aAAa,EAClB,cAAc,EACd,KAAK,qBAAqB,EAC1B,+BAA+B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,KAAK,mBAAmB,EACxB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,EACd,KAAK,qBAAqB,GAC3B,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,cAAc,EACd,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,mBAAmB,EACnB,SAAS,GACV,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EACL,KAAK,aAAa,EAClB,cAAc,EACd,KAAK,qBAAqB,EAC1B,+BAA+B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,eAAe,EACf,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,EACd,KAAK,qBAAqB,GAC3B,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,SAAS,GACV,MAAM,YAAY,CAAC"}
@@ -14,6 +14,6 @@ export { CanvasInstance } from './core/CanvasInstance.js';
14
14
  export { CanvasRegistry, DEFAULT_CANVAS_REGISTRY_OPTIONS, } from './core/CanvasRegistry.js';
15
15
  export { createCanvasService } from './core/canvasFactory.js';
16
16
  export { DataCanvas } from './core/DataCanvas.js';
17
- export { ALLOWED_PLAN_OPERATORS, ALLOWED_STATEMENT_TYPES, assertReadOnlyQuery, assertValidIdentifier, collectDisallowedOperators, quoteIdentifier, } from './core/sqlGate.js';
17
+ export { ALLOWED_PLAN_OPERATORS, ALLOWED_STATEMENT_TYPES, assertNoDeniedFunctions, assertReadOnlyQuery, assertValidIdentifier, CANVAS_IDENTIFIER_REGEX, collectDisallowedOperators, collectPlanViolations, DENIED_TABLE_FUNCTIONS, quoteIdentifier, SQL_GATE_REASONS, } from './core/sqlGate.js';
18
18
  export { DuckdbProvider, } from './providers/duckdb/DuckdbProvider.js';
19
19
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAEL,cAAc,EAEd,+BAA+B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAE1B,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,GAEf,MAAM,sCAAsC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAEL,cAAc,EAEd,+BAA+B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EAEtB,eAAe,EACf,gBAAgB,GAEjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,GAEf,MAAM,sCAAsC,CAAC"}
@@ -1,18 +1,14 @@
1
1
  /**
2
- * @fileoverview DuckDB-backed implementation of {@link IDataCanvasProvider}.
3
- * One DuckDB instance per canvasId for memory isolation; a shared connection
4
- * for control-plane work (DDL, describe, drop) and per-query connections for
5
- * data-plane work so that {@link DuckDBConnection.interrupt} cancels exactly
6
- * the in-flight query without disturbing concurrent ops on the same canvas.
7
- *
8
- * Lazy-loaded via {@link lazyImport} so `@duckdb/node-api` stays a true peer
9
- * dependency — servers that don't enable canvas pay no install cost.
10
- *
2
+ * @fileoverview DuckDB-backed {@link IDataCanvasProvider}. One in-memory
3
+ * DuckDB instance per canvasId; a long-lived control connection for DDL and
4
+ * describe operations, plus a per-query connection so cancellation interrupts
5
+ * exactly the in-flight query. `@duckdb/node-api` is lazy-loaded so it stays
6
+ * a true peer dependency.
11
7
  * @module src/services/canvas/providers/duckdb/DuckdbProvider
12
8
  */
13
9
  import { type RequestContext } from '../../../../utils/internal/requestContext.js';
14
10
  import type { IDataCanvasProvider } from '../../core/IDataCanvasProvider.js';
15
- import type { DescribeOptions, ExportOptions, ExportResult, ExportTarget, QueryOptions, QueryResult, RegisterRows, RegisterTableOptions, RegisterTableResult, TableInfo } from '../../types.js';
11
+ import type { DescribeOptions, ExportOptions, ExportResult, ExportTarget, ImportFromOptions, QueryOptions, QueryResult, RegisterRows, RegisterTableOptions, RegisterTableResult, RegisterViewOptions, RegisterViewResult, TableInfo } from '../../types.js';
16
12
  /** Configuration for {@link DuckdbProvider}. Mirrors the AppConfig.canvas block. */
17
13
  export interface DuckdbProviderOptions {
18
14
  /** Default row cap for `query()` results. */
@@ -24,12 +20,9 @@ export interface DuckdbProviderOptions {
24
20
  /** Number of rows to sniff for schema inference. */
25
21
  schemaSniffRows: number;
26
22
  }
27
- /** DuckDB SELECT statement-type id (matches `StatementType.SELECT === 1`). */
28
- declare const STATEMENT_TYPE_SELECT_ID = 1;
29
23
  export declare class DuckdbProvider implements IDataCanvasProvider {
30
24
  private readonly options;
31
25
  readonly name = "duckdb";
32
- private duck;
33
26
  private readonly canvases;
34
27
  constructor(options: DuckdbProviderOptions);
35
28
  initCanvas(canvasId: string, _context: RequestContext): Promise<void>;
@@ -39,18 +32,67 @@ export declare class DuckdbProvider implements IDataCanvasProvider {
39
32
  registerTable(canvasId: string, name: string, rows: RegisterRows, _context: RequestContext, options?: RegisterTableOptions): Promise<RegisterTableResult>;
40
33
  query(canvasId: string, sql: string, _context: RequestContext, options?: QueryOptions): Promise<QueryResult>;
41
34
  export(canvasId: string, tableName: string, target: ExportTarget, _context: RequestContext, options?: ExportOptions): Promise<ExportResult>;
35
+ registerView(canvasId: string, name: string, selectSql: string, _context: RequestContext, options?: RegisterViewOptions): Promise<RegisterViewResult>;
36
+ importFrom(targetCanvasId: string, sourceCanvasId: string, sourceTableName: string, asName: string, _context: RequestContext, options?: ImportFromOptions): Promise<RegisterTableResult>;
42
37
  describe(canvasId: string, _context: RequestContext, options?: DescribeOptions): Promise<TableInfo[]>;
38
+ private describeOne;
43
39
  drop(canvasId: string, name: string, _context: RequestContext): Promise<boolean>;
44
40
  clear(canvasId: string, _context: RequestContext): Promise<number>;
45
41
  private requireCanvas;
46
- private getModule;
42
+ /**
43
+ * Run the same four-layer read-only gate `query()` enforces, against an
44
+ * arbitrary SELECT string. Used by `query()` and `registerView()` so view
45
+ * definitions inherit query-level safety.
46
+ */
47
+ private assertReadOnlySql;
48
+ /**
49
+ * Resolve whether a name on the canvas refers to a base table, a view, or
50
+ * nothing. Returns `undefined` when absent; used by drop/registerView/
51
+ * importFrom to dispatch the right DDL.
52
+ */
53
+ private lookupKind;
54
+ /**
55
+ * Materialize `COUNT(*)` against a (validated) table or view name. DuckDB
56
+ * returns BIGINT as a JSON string; this helper centralizes the `Number(...)`
57
+ * coercion so callers see a plain `number`.
58
+ */
59
+ private countRows;
47
60
  private runExplain;
48
61
  }
49
- /** Re-export for tests and consumer-side parsing. */
50
- export { STATEMENT_TYPE_SELECT_ID };
62
+ /**
63
+ * Coerce a value to BigInt without precision loss. `BigInt(Number(value))`
64
+ * round-trips through JS Number and truncates outside the 53-bit safe range,
65
+ * silently corrupting BIGINT IDs returned as numeric strings.
66
+ *
67
+ * @internal Exported for unit testing.
68
+ */
69
+ export declare function toBigInt(value: unknown): bigint;
70
+ /**
71
+ * Coerce to DuckDB's TIMESTAMP unit (micros since 1970-01-01 UTC, as `bigint`).
72
+ * Accepts `Date`, `bigint` (already-micros), `number` (ms-since-epoch matching
73
+ * `Date.getTime()`), and ISO 8601 strings. Throws `validationError` otherwise.
74
+ *
75
+ * @internal Exported for unit testing.
76
+ */
77
+ export declare function toTimestampMicros(value: unknown, columnName: string): bigint;
78
+ /**
79
+ * Coerce to DuckDB's DATE unit (days since 1970-01-01 UTC, as `number`).
80
+ * Accepts the same shapes as {@link toTimestampMicros}; throws otherwise.
81
+ *
82
+ * @internal Exported for unit testing.
83
+ */
84
+ export declare function toDateDays(value: unknown, columnName: string): number;
85
+ /**
86
+ * Coerce to `Uint8Array` for BLOB appends. Accepts `Uint8Array` (Node's
87
+ * `Buffer` passes through as a subclass), `ArrayBuffer`, and any other
88
+ * `ArrayBufferView`. Throws `validationError` for non-binary inputs.
89
+ *
90
+ * @internal Exported for unit testing.
91
+ */
92
+ export declare function toUint8Array(value: unknown, columnName: string): Uint8Array;
51
93
  /**
52
94
  * Map a DuckDB-thrown error to a framework error class.
53
- * @internal Exported for unit testing — not re-exported from the canvas barrel.
95
+ * @internal Exported for unit testing.
54
96
  */
55
97
  export declare function classifyDuckdbError(err: unknown): Error;
56
98
  //# sourceMappingURL=DuckdbProvider.d.ts.map