@cyanheads/mcp-ts-core 0.8.7 → 0.8.9

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 (77) hide show
  1. package/CLAUDE.md +2 -0
  2. package/README.md +1 -1
  3. package/biome.json +1 -1
  4. package/changelog/0.8.x/0.8.8.md +11 -0
  5. package/changelog/0.8.x/0.8.9.md +34 -0
  6. package/dist/canvas/core/CanvasInstance.d.ts +43 -0
  7. package/dist/canvas/core/CanvasInstance.d.ts.map +1 -0
  8. package/dist/canvas/core/CanvasInstance.js +63 -0
  9. package/dist/canvas/core/CanvasInstance.js.map +1 -0
  10. package/dist/canvas/core/CanvasRegistry.d.ts +96 -0
  11. package/dist/canvas/core/CanvasRegistry.d.ts.map +1 -0
  12. package/dist/canvas/core/CanvasRegistry.js +250 -0
  13. package/dist/canvas/core/CanvasRegistry.js.map +1 -0
  14. package/dist/canvas/core/DataCanvas.d.ts +49 -0
  15. package/dist/canvas/core/DataCanvas.d.ts.map +1 -0
  16. package/dist/canvas/core/DataCanvas.js +85 -0
  17. package/dist/canvas/core/DataCanvas.js.map +1 -0
  18. package/dist/canvas/core/IDataCanvasProvider.d.ts +47 -0
  19. package/dist/canvas/core/IDataCanvasProvider.d.ts.map +1 -0
  20. package/dist/canvas/core/IDataCanvasProvider.js +10 -0
  21. package/dist/canvas/core/IDataCanvasProvider.js.map +1 -0
  22. package/dist/canvas/core/canvasFactory.d.ts +26 -0
  23. package/dist/canvas/core/canvasFactory.d.ts.map +1 -0
  24. package/dist/canvas/core/canvasFactory.js +63 -0
  25. package/dist/canvas/core/canvasFactory.js.map +1 -0
  26. package/dist/canvas/core/sqlGate.d.ts +107 -0
  27. package/dist/canvas/core/sqlGate.d.ts.map +1 -0
  28. package/dist/canvas/core/sqlGate.js +267 -0
  29. package/dist/canvas/core/sqlGate.js.map +1 -0
  30. package/dist/canvas/index.d.ts +21 -0
  31. package/dist/canvas/index.d.ts.map +1 -0
  32. package/dist/canvas/index.js +19 -0
  33. package/dist/canvas/index.js.map +1 -0
  34. package/dist/canvas/providers/duckdb/DuckdbProvider.d.ts +56 -0
  35. package/dist/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -0
  36. package/dist/canvas/providers/duckdb/DuckdbProvider.js +600 -0
  37. package/dist/canvas/providers/duckdb/DuckdbProvider.js.map +1 -0
  38. package/dist/canvas/providers/duckdb/exportWriter.d.ts +48 -0
  39. package/dist/canvas/providers/duckdb/exportWriter.d.ts.map +1 -0
  40. package/dist/canvas/providers/duckdb/exportWriter.js +119 -0
  41. package/dist/canvas/providers/duckdb/exportWriter.js.map +1 -0
  42. package/dist/canvas/providers/duckdb/schemaSniffer.d.ts +44 -0
  43. package/dist/canvas/providers/duckdb/schemaSniffer.d.ts.map +1 -0
  44. package/dist/canvas/providers/duckdb/schemaSniffer.js +134 -0
  45. package/dist/canvas/providers/duckdb/schemaSniffer.js.map +1 -0
  46. package/dist/canvas/types.d.ts +134 -0
  47. package/dist/canvas/types.d.ts.map +1 -0
  48. package/dist/canvas/types.js +9 -0
  49. package/dist/canvas/types.js.map +1 -0
  50. package/dist/config/index.d.ts +51 -15
  51. package/dist/config/index.d.ts.map +1 -1
  52. package/dist/config/index.js +44 -0
  53. package/dist/config/index.js.map +1 -1
  54. package/dist/core/app.d.ts +8 -0
  55. package/dist/core/app.d.ts.map +1 -1
  56. package/dist/core/app.js +11 -0
  57. package/dist/core/app.js.map +1 -1
  58. package/dist/core/worker.d.ts +7 -0
  59. package/dist/core/worker.d.ts.map +1 -1
  60. package/dist/core/worker.js +1 -0
  61. package/dist/core/worker.js.map +1 -1
  62. package/dist/logs/combined.log +4 -4
  63. package/dist/logs/error.log +4 -4
  64. package/dist/storage/core/storageValidation.d.ts.map +1 -1
  65. package/dist/storage/core/storageValidation.js +7 -1
  66. package/dist/storage/core/storageValidation.js.map +1 -1
  67. package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
  68. package/dist/utils/internal/error-handler/errorHandler.js +2 -1
  69. package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
  70. package/package.json +18 -8
  71. package/skills/api-canvas/SKILL.md +260 -0
  72. package/skills/api-config/SKILL.md +18 -0
  73. package/skills/api-workers/SKILL.md +6 -0
  74. package/skills/report-issue-framework/SKILL.md +1 -0
  75. package/skills/report-issue-local/SKILL.md +2 -0
  76. package/templates/.github/ISSUE_TEMPLATE/bug_report.yml +1 -0
  77. package/templates/.github/ISSUE_TEMPLATE/feature_request.yml +1 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @fileoverview User-facing DataCanvas service. Wraps the provider and registry
3
+ * with debug logging and tenant-id resolution; the registry handles TTL,
4
+ * caps, and provider-keying. Mirrors the StorageService surface pattern but
5
+ * stays OTel-free in v1 (per issue #97 scope).
6
+ * @module src/canvas/core/DataCanvas
7
+ */
8
+ import { JsonRpcErrorCode, McpError } from '../../types-global/errors.js';
9
+ import { logger } from '../../utils/internal/logger.js';
10
+ import { CanvasInstance } from './CanvasInstance.js';
11
+ /**
12
+ * Resolve the effective tenant ID from the context, throwing when absent.
13
+ * Mirrors `requireTenantId` in StorageService — canvas operations are
14
+ * tenant-scoped by construction.
15
+ */
16
+ function requireTenantId(context) {
17
+ const tenantId = context.tenantId;
18
+ if (tenantId === undefined || tenantId === null || tenantId === '') {
19
+ throw new McpError(JsonRpcErrorCode.InternalError, 'Tenant ID is required for canvas operations but was not found in the request context.', { operation: context.operation || 'DataCanvas.acquire', requestId: context.requestId });
20
+ }
21
+ return tenantId;
22
+ }
23
+ /**
24
+ * Service entry point for the DataCanvas primitive. Returned from
25
+ * `core.canvas` on `CoreServices` when a canvas provider is configured.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const canvas = await core.canvas!.acquire(input.canvas_id, ctx);
30
+ * await canvas.registerTable('germplasm', rows);
31
+ * const result = await canvas.query('SELECT name FROM germplasm LIMIT 10');
32
+ * return { canvas_id: canvas.canvasId, expires_at: canvas.expiresAt, ... };
33
+ * ```
34
+ */
35
+ export class DataCanvas {
36
+ provider;
37
+ registry;
38
+ constructor(provider, registry) {
39
+ this.provider = provider;
40
+ this.registry = registry;
41
+ logger.info(`DataCanvas initialized with provider: ${provider.name}`);
42
+ }
43
+ /**
44
+ * Resolve an existing canvas or create a new one. The returned
45
+ * {@link CanvasInstance} captures `(canvasId, tenantId)` so subsequent
46
+ * operations don't need to repeat them.
47
+ */
48
+ async acquire(maybeId, context, _options) {
49
+ const tenantId = requireTenantId(context);
50
+ const result = await this.registry.acquire(maybeId, tenantId, context);
51
+ logger.debug('Canvas acquired.', {
52
+ ...context,
53
+ canvasId: result.canvasId,
54
+ tenantId,
55
+ isNew: result.isNew,
56
+ });
57
+ return new CanvasInstance(result.canvasId, result.tenantId, result.isNew, result.expiresAt, this.registry, this.provider, context);
58
+ }
59
+ /**
60
+ * Drop a canvas explicitly. Returns true when the canvas existed and was
61
+ * destroyed.
62
+ */
63
+ async drop(canvasId, context) {
64
+ const tenantId = requireTenantId(context);
65
+ return await this.registry.drop(canvasId, tenantId, context);
66
+ }
67
+ /** Active canvas count for the calling tenant. */
68
+ countForTenant(context) {
69
+ const tenantId = requireTenantId(context);
70
+ return this.registry.countForTenant(tenantId);
71
+ }
72
+ /** Liveness check on the underlying provider. */
73
+ healthCheck() {
74
+ return this.provider.healthCheck();
75
+ }
76
+ /** Tear down the registry and provider. Called from `ServerHandle.shutdown()`. */
77
+ async shutdown(context) {
78
+ await this.registry.shutdown(context);
79
+ }
80
+ /** @internal Surface the underlying provider for advanced use cases. */
81
+ getProvider() {
82
+ return this.provider;
83
+ }
84
+ }
85
+ //# sourceMappingURL=DataCanvas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DataCanvas.js","sourceRoot":"","sources":["../../../src/canvas/core/DataCanvas.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAGpD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIrD;;;;GAIG;AACH,SAAS,eAAe,CAAC,OAAuB;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACnE,MAAM,IAAI,QAAQ,CAChB,gBAAgB,CAAC,aAAa,EAC9B,uFAAuF,EACvF,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,oBAAoB,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CACvF,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,UAAU;IAEF;IACA;IAFnB,YACmB,QAA6B,EAC7B,QAAwB;QADxB,aAAQ,GAAR,QAAQ,CAAqB;QAC7B,aAAQ,GAAR,QAAQ,CAAgB;QAEzC,MAAM,CAAC,IAAI,CAAC,yCAAyC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,OAAO,CACX,OAA2B,EAC3B,OAAuB,EACvB,QAAyB;QAEzB,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QACvE,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE;YAC/B,GAAG,OAAO;YACV,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ;YACR,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CAAC;QACH,OAAO,IAAI,cAAc,CACvB,MAAM,CAAC,QAAQ,EACf,MAAM,CAAC,QAAQ,EACf,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,SAAS,EAChB,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,QAAQ,EACb,OAAO,CACR,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAC,QAAgB,EAAE,OAAuB;QAClD,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/D,CAAC;IAED,kDAAkD;IAClD,cAAc,CAAC,OAAuB;QACpC,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED,iDAAiD;IACjD,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC;IAED,kFAAkF;IAClF,KAAK,CAAC,QAAQ,CAAC,OAAuB;QACpC,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,wEAAwE;IACxE,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @fileoverview Engine-level provider interface for the DataCanvas primitive.
3
+ * Implementations own the physical resources backing each canvas (e.g. one
4
+ * DuckDB instance per canvasId) and expose register/query/export/describe
5
+ * operations keyed by canvasId. The lifecycle wrapper ({@link CanvasRegistry})
6
+ * keys these calls by `(tenantId, canvasId)` and enforces TTL/caps.
7
+ * @module src/canvas/core/IDataCanvasProvider
8
+ */
9
+ import type { RequestContext } from '../../utils/internal/requestContext.js';
10
+ import type { DescribeOptions, ExportOptions, ExportResult, ExportTarget, QueryOptions, QueryResult, RegisterRows, RegisterTableOptions, RegisterTableResult, TableInfo } from '../types.js';
11
+ /**
12
+ * Engine-level contract. The lifecycle wrapper guarantees `canvasId` is
13
+ * already validated and authorized for the caller's tenant before any of
14
+ * these methods are invoked, so providers may treat `canvasId` as opaque
15
+ * and trusted.
16
+ */
17
+ export interface IDataCanvasProvider {
18
+ /** Drop every table on the canvas. Returns the number dropped. */
19
+ clear(canvasId: string, context: RequestContext): Promise<number>;
20
+ /** Describe one or all tables on the canvas. */
21
+ describe(canvasId: string, context: RequestContext, options?: DescribeOptions): Promise<TableInfo[]>;
22
+ /**
23
+ * Release engine resources for a canvas. After this call, further ops on
24
+ * the canvas throw `NotFound`.
25
+ */
26
+ destroyCanvas(canvasId: string, context: RequestContext): Promise<void>;
27
+ /** Drop a single canvas table. Returns `true` when found and removed. */
28
+ drop(canvasId: string, name: string, context: RequestContext): Promise<boolean>;
29
+ /** Export a canvas table to a file or stream target. */
30
+ export(canvasId: string, tableName: string, target: ExportTarget, context: RequestContext, options?: ExportOptions): Promise<ExportResult>;
31
+ /** Liveness check on the underlying engine. */
32
+ healthCheck(): Promise<boolean>;
33
+ /**
34
+ * Allocate engine resources for a new canvas. Idempotent — calling twice
35
+ * with the same id is a no-op.
36
+ */
37
+ initCanvas(canvasId: string, context: RequestContext): Promise<void>;
38
+ /** Provider name (e.g. `'duckdb'`). Used in logs and health output. */
39
+ readonly name: string;
40
+ /** Run a SQL query against the canvas. Read-only enforcement is the gate's job. */
41
+ query(canvasId: string, sql: string, context: RequestContext, options?: QueryOptions): Promise<QueryResult>;
42
+ /** Register a table on the canvas from in-memory or async iterator rows. */
43
+ registerTable(canvasId: string, name: string, rows: RegisterRows, context: RequestContext, options?: RegisterTableOptions): Promise<RegisterTableResult>;
44
+ /** Tear down all engine resources. Called from `ServerHandle.shutdown()`. */
45
+ shutdown(): Promise<void>;
46
+ }
47
+ //# sourceMappingURL=IDataCanvasProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IDataCanvasProvider.d.ts","sourceRoot":"","sources":["../../../src/canvas/core/IDataCanvasProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,mBAAmB,EACnB,SAAS,EACV,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAElE,gDAAgD;IAChD,QAAQ,CACN,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAExB;;;OAGG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExE,yEAAyE;IACzE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhF,wDAAwD;IACxD,MAAM,CACJ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzB,+CAA+C;IAC/C,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhC;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,mFAAmF;IACnF,KAAK,CACH,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,WAAW,CAAC,CAAC;IAExB,4EAA4E;IAC5E,aAAa,CACX,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEhC,6EAA6E;IAC7E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @fileoverview Engine-level provider interface for the DataCanvas primitive.
3
+ * Implementations own the physical resources backing each canvas (e.g. one
4
+ * DuckDB instance per canvasId) and expose register/query/export/describe
5
+ * operations keyed by canvasId. The lifecycle wrapper ({@link CanvasRegistry})
6
+ * keys these calls by `(tenantId, canvasId)` and enforces TTL/caps.
7
+ * @module src/canvas/core/IDataCanvasProvider
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=IDataCanvasProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IDataCanvasProvider.js","sourceRoot":"","sources":["../../../src/canvas/core/IDataCanvasProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @fileoverview Factory for the optional DataCanvas service. Mirrors the
3
+ * pattern in `createStorageProvider` — switches on `config.canvas.providerType`,
4
+ * fails closed in serverless environments for `duckdb`, and returns
5
+ * `undefined` when canvas is disabled (`'none'`).
6
+ *
7
+ * The factory does NOT eager-load `@duckdb/node-api`; the provider lazy-loads
8
+ * on first use via `lazyImport`. Servers that set `CANVAS_PROVIDER_TYPE=none`
9
+ * (the default) pay zero install cost.
10
+ *
11
+ * @module src/canvas/core/canvasFactory
12
+ */
13
+ import type { AppConfig } from '../../config/index.js';
14
+ import { DataCanvas } from './DataCanvas.js';
15
+ /**
16
+ * Construct the canvas service from configuration. Returns `undefined` when
17
+ * canvas is disabled (`CANVAS_PROVIDER_TYPE=none`), keeping `core.canvas`
18
+ * `undefined` on `CoreServices` for that case.
19
+ *
20
+ * In serverless environments, an explicit `CANVAS_PROVIDER_TYPE=duckdb`
21
+ * fails closed with a clear ConfigurationError — DuckDB has no V8-isolate
22
+ * build, so canvas is unavailable on Workers. (Refinement #1 in issue #97 is
23
+ * about export-path sandboxing, separate from this Worker fail-closed.)
24
+ */
25
+ export declare function createCanvasService(config: AppConfig): DataCanvas | undefined;
26
+ //# sourceMappingURL=canvasFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasFactory.d.ts","sourceRoot":"","sources":["../../../src/canvas/core/canvasFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAOnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAO7C;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,CAkC7E"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @fileoverview Factory for the optional DataCanvas service. Mirrors the
3
+ * pattern in `createStorageProvider` — switches on `config.canvas.providerType`,
4
+ * fails closed in serverless environments for `duckdb`, and returns
5
+ * `undefined` when canvas is disabled (`'none'`).
6
+ *
7
+ * The factory does NOT eager-load `@duckdb/node-api`; the provider lazy-loads
8
+ * on first use via `lazyImport`. Servers that set `CANVAS_PROVIDER_TYPE=none`
9
+ * (the default) pay zero install cost.
10
+ *
11
+ * @module src/canvas/core/canvasFactory
12
+ */
13
+ import { configurationError } from '../../types-global/errors.js';
14
+ import { logger } from '../../utils/internal/logger.js';
15
+ import { requestContextService } from '../../utils/internal/requestContext.js';
16
+ import { DuckdbProvider } from '../providers/duckdb/DuckdbProvider.js';
17
+ import { CanvasRegistry } from './CanvasRegistry.js';
18
+ import { DataCanvas } from './DataCanvas.js';
19
+ /** Evaluated at call time so worker.ts can set IS_SERVERLESS before first use. */
20
+ function isServerless() {
21
+ return typeof process === 'undefined' || process.env.IS_SERVERLESS === 'true';
22
+ }
23
+ /**
24
+ * Construct the canvas service from configuration. Returns `undefined` when
25
+ * canvas is disabled (`CANVAS_PROVIDER_TYPE=none`), keeping `core.canvas`
26
+ * `undefined` on `CoreServices` for that case.
27
+ *
28
+ * In serverless environments, an explicit `CANVAS_PROVIDER_TYPE=duckdb`
29
+ * fails closed with a clear ConfigurationError — DuckDB has no V8-isolate
30
+ * build, so canvas is unavailable on Workers. (Refinement #1 in issue #97 is
31
+ * about export-path sandboxing, separate from this Worker fail-closed.)
32
+ */
33
+ export function createCanvasService(config) {
34
+ const providerType = config.canvas.providerType;
35
+ if (providerType === 'none')
36
+ return;
37
+ const context = requestContextService.createRequestContext({
38
+ operation: 'createCanvasService',
39
+ });
40
+ if (providerType === 'duckdb') {
41
+ if (isServerless()) {
42
+ throw configurationError('DuckDB canvas requires Node.js or Bun. Set CANVAS_PROVIDER_TYPE=none or omit it for Cloudflare Workers deployment.', context);
43
+ }
44
+ logger.info('Creating DuckDB canvas provider', context);
45
+ const provider = new DuckdbProvider({
46
+ memoryLimitMb: config.canvas.defaultMemoryLimitMb,
47
+ exportRootPath: config.canvas.exportRootPath,
48
+ defaultRowLimit: config.canvas.defaultRowLimit,
49
+ schemaSniffRows: config.canvas.schemaSniffRows,
50
+ });
51
+ const registry = new CanvasRegistry(provider, {
52
+ ttlMs: config.canvas.ttlMs,
53
+ absoluteCapMs: config.canvas.absoluteCapMs,
54
+ maxCanvasesPerTenant: config.canvas.maxCanvasesPerTenant,
55
+ sweeperIntervalMs: config.canvas.sweeperIntervalMs,
56
+ });
57
+ return new DataCanvas(provider, registry);
58
+ }
59
+ // Exhaustive check for the providerType union.
60
+ const exhaustive = providerType;
61
+ throw configurationError(`Unhandled canvas provider type: ${String(exhaustive)}`, context);
62
+ }
63
+ //# sourceMappingURL=canvasFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasFactory.js","sourceRoot":"","sources":["../../../src/canvas/core/canvasFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAE3E,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,kFAAkF;AAClF,SAAS,YAAY;IACnB,OAAO,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,MAAM,CAAC;AAChF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;IAChD,IAAI,YAAY,KAAK,MAAM;QAAE,OAAO;IAEpC,MAAM,OAAO,GAAG,qBAAqB,CAAC,oBAAoB,CAAC;QACzD,SAAS,EAAE,qBAAqB;KACjC,CAAC,CAAC;IAEH,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,YAAY,EAAE,EAAE,CAAC;YACnB,MAAM,kBAAkB,CACtB,oHAAoH,EACpH,OAAO,CACR,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE,OAAO,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC;YAClC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,oBAAoB;YACjD,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc;YAC5C,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe;YAC9C,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe;SAC/C,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,QAAQ,EAAE;YAC5C,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK;YAC1B,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa;YAC1C,oBAAoB,EAAE,MAAM,CAAC,MAAM,CAAC,oBAAoB;YACxD,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,iBAAiB;SACnD,CAAC,CAAC;QACH,OAAO,IAAI,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED,+CAA+C;IAC/C,MAAM,UAAU,GAAU,YAAY,CAAC;IACvC,MAAM,kBAAkB,CAAC,mCAAmC,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;AAC7F,CAAC"}
@@ -0,0 +1,107 @@
1
+ /**
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).
5
+ *
6
+ * Three layers of enforcement, each authoritative on its own:
7
+ *
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.
21
+ *
22
+ * Rejection paths throw `ValidationError` with a structured `data.reason`
23
+ * suitable for surfacing to the agent.
24
+ *
25
+ * @module src/canvas/core/sqlGate
26
+ */
27
+ /**
28
+ * DuckDB statement-type strings emitted by the Neo client. Only `SELECT` is
29
+ * accepted by the canvas. Other values (`INSERT`, `UPDATE`, `DELETE`,
30
+ * `CREATE`, `DROP`, `ALTER`, `COPY`, `PRAGMA`, `ATTACH`, `DETACH`, `LOAD`,
31
+ * `INSTALL`, `SET`, `RESET`, `EXECUTE`, `EXPLAIN`, `TRANSACTION`, `VACUUM`,
32
+ * `CHECKPOINT`, `CALL`, …) are rejected outright. This is a surface — not
33
+ * an enum we own — so the gate matches on the literal string emitted.
34
+ */
35
+ export type DuckdbStatementType = string;
36
+ /** Subset of statement types the gate permits. */
37
+ export declare const ALLOWED_STATEMENT_TYPES: ReadonlySet<DuckdbStatementType>;
38
+ /**
39
+ * Curated allowlist of operator names that can appear in an EXPLAIN plan.
40
+ * Sourced from DuckDB's logical/physical-plan node families (1.5.x). Not
41
+ * every member is reachable from a SELECT — but every member is read-only.
42
+ *
43
+ * Pinned by `tests/canvas/sqlGate.fixtures.test.ts` against live DuckDB
44
+ * EXPLAIN output so version bumps that add operators are caught in CI rather
45
+ * than silently widening the gate.
46
+ *
47
+ * Operators **not** in this list cause rejection. Notable exclusions:
48
+ *
49
+ * - `READ_CSV`, `READ_PARQUET`, `READ_JSON` — bypass canvas, read external files.
50
+ * - `INSERT`, `UPDATE`, `DELETE`, `MERGE`, `CREATE_*`, `DROP_*`, `ALTER_*` — writes.
51
+ * - `COPY_TO_FILE`, `BATCH_COPY_TO_FILE` — exports a SELECT to a file.
52
+ * - `ATTACH`, `DETACH`, `LOAD`, `INSTALL`, `PRAGMA`, `SET`, `RESET` — utility.
53
+ */
54
+ export declare const ALLOWED_PLAN_OPERATORS: ReadonlySet<string>;
55
+ /**
56
+ * Public entry point — validates the trio of `(statementCount, statementType,
57
+ * planJson)`. Throws on the first violation, leaving the provider to pass
58
+ * results back to the caller untouched on success.
59
+ */
60
+ export declare function assertReadOnlyQuery(input: {
61
+ /** Number of statements DuckDB extracted from the user-supplied SQL. */
62
+ statementCount: number;
63
+ /** DuckDB statement type for the (single) prepared statement. */
64
+ statementType: DuckdbStatementType;
65
+ /** Parsed `EXPLAIN (FORMAT JSON)` payload. */
66
+ planJson: unknown;
67
+ }): void;
68
+ /**
69
+ * Pre-EXPLAIN gate: validate statement count and type. Run before the EXPLAIN
70
+ * call so non-SELECT statements (which DuckDB's EXPLAIN can't always wrap —
71
+ * e.g. ATTACH/PRAGMA/COPY/INSTALL) fail with a structured ValidationError
72
+ * here rather than a confusing parser error from EXPLAIN itself.
73
+ */
74
+ export declare function assertSelectOnly(input: {
75
+ statementCount: number;
76
+ statementType: DuckdbStatementType;
77
+ }): void;
78
+ /**
79
+ * Post-EXPLAIN gate: walk the plan tree and reject any operator outside the
80
+ * curated allowlist. Defense-in-depth against future DuckDB additions that
81
+ * smuggle work into a SELECT envelope.
82
+ */
83
+ export declare function assertPlanReadOnly(planJson: unknown): void;
84
+ /**
85
+ * Walks the EXPLAIN plan and returns the set of operator names not in
86
+ * `ALLOWED_PLAN_OPERATORS`. Exported for fixture-driven tests that want
87
+ * to inspect the gate's view of a plan without throwing.
88
+ *
89
+ * Tolerant of structural variation — DuckDB emits operator identity under
90
+ * either `name` (logical plan) or `operator_type` (physical/profile plan)
91
+ * depending on the EXPLAIN flavor. We honor both. Children traversal
92
+ * supports `children`, `child`, and `inputs` arrays.
93
+ */
94
+ export declare function collectDisallowedOperators(planJson: unknown): Set<string>;
95
+ /**
96
+ * Validate an identifier for use as a canvas-local table or column name.
97
+ * Throws `ValidationError` on rejection.
98
+ */
99
+ export declare function assertValidIdentifier(value: string, kind: 'table' | 'column'): void;
100
+ /**
101
+ * Wrap an identifier in double quotes for safe inclusion in SQL. Internal
102
+ * double quotes are doubled per the SQL standard. Callers should still
103
+ * validate via {@link assertValidIdentifier} before quoting — this helper
104
+ * only escapes; it does not validate shape.
105
+ */
106
+ export declare function quoteIdentifier(value: string): string;
107
+ //# sourceMappingURL=sqlGate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlGate.d.ts","sourceRoot":"","sources":["../../../src/canvas/core/sqlGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEzC,kDAAkD;AAClD,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,mBAAmB,CAAuB,CAAC;AAE7F;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAwDrD,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,aAAa,EAAE,mBAAmB,CAAC;IACnC,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,IAAI,CAGP;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,mBAAmB,CAAC;CACpC,GAAG,IAAI,CAaP;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAW1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAIzE;AAsFD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAmBnF;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD"}
@@ -0,0 +1,267 @@
1
+ /**
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).
5
+ *
6
+ * Three layers of enforcement, each authoritative on its own:
7
+ *
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.
21
+ *
22
+ * Rejection paths throw `ValidationError` with a structured `data.reason`
23
+ * suitable for surfacing to the agent.
24
+ *
25
+ * @module src/canvas/core/sqlGate
26
+ */
27
+ import { validationError } from '../../types-global/errors.js';
28
+ /** Subset of statement types the gate permits. */
29
+ export const ALLOWED_STATEMENT_TYPES = new Set(['SELECT']);
30
+ /**
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:
40
+ *
41
+ * - `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.
45
+ */
46
+ export const ALLOWED_PLAN_OPERATORS = new Set([
47
+ // Scans (registered tables only — file scans like READ_CSV are excluded)
48
+ 'SEQ_SCAN',
49
+ 'COLUMN_DATA_SCAN',
50
+ 'CHUNK_SCAN',
51
+ 'EXPRESSION_SCAN',
52
+ 'DUMMY_SCAN',
53
+ 'EMPTY_RESULT',
54
+ 'IN_MEMORY_TABLE_SCAN',
55
+ // Projection / filter
56
+ 'PROJECTION',
57
+ 'FILTER',
58
+ // Joins
59
+ 'HASH_JOIN',
60
+ 'NESTED_LOOP_JOIN',
61
+ 'BLOCKWISE_NL_JOIN',
62
+ 'IE_JOIN',
63
+ 'PIECEWISE_MERGE_JOIN',
64
+ 'CROSS_PRODUCT',
65
+ 'POSITIONAL_JOIN',
66
+ 'ASOF_JOIN',
67
+ 'DELIM_JOIN',
68
+ // Aggregates
69
+ 'HASH_GROUP_BY',
70
+ 'PERFECT_HASH_GROUP_BY',
71
+ 'UNGROUPED_AGGREGATE',
72
+ 'SIMPLE_AGGREGATE',
73
+ 'PARTITIONED_AGGREGATE',
74
+ // Distinct / set ops
75
+ 'HASH_DISTINCT',
76
+ 'DISTINCT',
77
+ 'UNION',
78
+ // Sorting / limits
79
+ 'ORDER_BY',
80
+ 'TOP_N',
81
+ 'LIMIT',
82
+ 'LIMIT_PERCENT',
83
+ 'STREAMING_LIMIT',
84
+ // Window
85
+ 'WINDOW',
86
+ // Nested
87
+ 'UNNEST',
88
+ // CTEs
89
+ 'CTE',
90
+ 'CTE_REF',
91
+ 'RECURSIVE_CTE',
92
+ 'MATERIALIZED_CTE',
93
+ // Sampling
94
+ 'RESERVOIR_SAMPLE',
95
+ 'SAMPLE',
96
+ 'STREAMING_SAMPLE',
97
+ // Result framing
98
+ 'RESULT_COLLECTOR',
99
+ 'EXPLAIN',
100
+ // Pivot/unpivot collapsed planner forms
101
+ 'PIVOT',
102
+ ]);
103
+ /**
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.
107
+ */
108
+ export function assertReadOnlyQuery(input) {
109
+ assertSelectOnly(input);
110
+ assertPlanReadOnly(input.planJson);
111
+ }
112
+ /**
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.
117
+ */
118
+ export function assertSelectOnly(input) {
119
+ if (input.statementCount !== 1) {
120
+ throw validationError('Canvas query must contain exactly one SQL statement.', {
121
+ reason: 'multi_statement',
122
+ statementCount: input.statementCount,
123
+ });
124
+ }
125
+ 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 });
127
+ }
128
+ }
129
+ /**
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.
133
+ */
134
+ export function assertPlanReadOnly(planJson) {
135
+ const offending = collectDisallowedOperators(planJson);
136
+ if (offending.size > 0) {
137
+ throw validationError(`Canvas query contains disallowed operators: ${[...offending].sort().join(', ')}.`, {
138
+ reason: 'plan_operator_not_allowed',
139
+ operators: [...offending].sort(),
140
+ });
141
+ }
142
+ }
143
+ /**
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.
152
+ */
153
+ export function collectDisallowedOperators(planJson) {
154
+ const offending = new Set();
155
+ walk(planJson, offending);
156
+ return offending;
157
+ }
158
+ function walk(node, offending) {
159
+ if (node === null || typeof node !== 'object')
160
+ return;
161
+ if (Array.isArray(node)) {
162
+ for (const child of node)
163
+ walk(child, offending);
164
+ return;
165
+ }
166
+ const obj = node;
167
+ const operator = readOperatorName(obj);
168
+ if (operator !== undefined && !ALLOWED_PLAN_OPERATORS.has(operator)) {
169
+ offending.add(operator);
170
+ }
171
+ // Traverse known child slots; ignore string/number leaves.
172
+ for (const key of ['children', 'child', 'inputs', 'plan', 'root']) {
173
+ if (key in obj)
174
+ walk(obj[key], offending);
175
+ }
176
+ }
177
+ function readOperatorName(obj) {
178
+ const candidates = ['name', 'operator_type', 'operator', 'type'];
179
+ for (const key of candidates) {
180
+ const value = obj[key];
181
+ if (typeof value === 'string' && value !== '') {
182
+ return value.toUpperCase();
183
+ }
184
+ }
185
+ return;
186
+ }
187
+ // ---------------------------------------------------------------------------
188
+ // Identifier validation and quoting
189
+ // ---------------------------------------------------------------------------
190
+ /**
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).
194
+ */
195
+ const IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
196
+ /**
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.
201
+ */
202
+ const RESERVED_IDENTIFIERS = new Set([
203
+ 'select',
204
+ 'from',
205
+ 'where',
206
+ 'order',
207
+ 'group',
208
+ 'having',
209
+ 'limit',
210
+ 'offset',
211
+ 'union',
212
+ 'intersect',
213
+ 'except',
214
+ 'all',
215
+ 'distinct',
216
+ 'as',
217
+ 'and',
218
+ 'or',
219
+ 'not',
220
+ 'null',
221
+ 'true',
222
+ 'false',
223
+ 'case',
224
+ 'when',
225
+ 'then',
226
+ 'else',
227
+ 'end',
228
+ 'join',
229
+ 'inner',
230
+ 'outer',
231
+ 'left',
232
+ 'right',
233
+ 'full',
234
+ 'cross',
235
+ 'on',
236
+ 'using',
237
+ 'with',
238
+ 'recursive',
239
+ ]);
240
+ /**
241
+ * Validate an identifier for use as a canvas-local table or column name.
242
+ * Throws `ValidationError` on rejection.
243
+ */
244
+ export function assertValidIdentifier(value, kind) {
245
+ if (typeof value !== 'string' || value.length === 0) {
246
+ throw validationError(`Canvas ${kind} name must be a non-empty string.`, {
247
+ reason: 'identifier_empty',
248
+ kind,
249
+ });
250
+ }
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 });
253
+ }
254
+ 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 });
256
+ }
257
+ }
258
+ /**
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.
263
+ */
264
+ export function quoteIdentifier(value) {
265
+ return `"${value.replace(/"/g, '""')}"`;
266
+ }
267
+ //# sourceMappingURL=sqlGate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlGate.js","sourceRoot":"","sources":["../../../src/canvas/core/sqlGate.ts"],"names":[],"mappings":"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"}