@cyanheads/mcp-ts-core 0.8.8 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -1
- package/README.md +4 -1
- package/biome.json +1 -1
- package/changelog/0.8.x/0.8.10.md +23 -0
- package/changelog/0.8.x/0.8.9.md +34 -0
- package/dist/config/index.d.ts +51 -15
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +44 -0
- package/dist/config/index.js.map +1 -1
- package/dist/core/app.d.ts +8 -0
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +11 -0
- package/dist/core/app.js.map +1 -1
- package/dist/core/worker.d.ts +7 -0
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js +1 -0
- package/dist/core/worker.js.map +1 -1
- package/dist/logs/combined.log +4 -4
- package/dist/logs/error.log +4 -4
- package/dist/services/canvas/core/CanvasInstance.d.ts +43 -0
- package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -0
- package/dist/services/canvas/core/CanvasInstance.js +63 -0
- package/dist/services/canvas/core/CanvasInstance.js.map +1 -0
- package/dist/services/canvas/core/CanvasRegistry.d.ts +96 -0
- package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -0
- package/dist/services/canvas/core/CanvasRegistry.js +250 -0
- package/dist/services/canvas/core/CanvasRegistry.js.map +1 -0
- package/dist/services/canvas/core/DataCanvas.d.ts +49 -0
- package/dist/services/canvas/core/DataCanvas.d.ts.map +1 -0
- package/dist/services/canvas/core/DataCanvas.js +85 -0
- package/dist/services/canvas/core/DataCanvas.js.map +1 -0
- package/dist/services/canvas/core/IDataCanvasProvider.d.ts +47 -0
- package/dist/services/canvas/core/IDataCanvasProvider.d.ts.map +1 -0
- package/dist/services/canvas/core/IDataCanvasProvider.js +10 -0
- package/dist/services/canvas/core/IDataCanvasProvider.js.map +1 -0
- package/dist/services/canvas/core/canvasFactory.d.ts +26 -0
- package/dist/services/canvas/core/canvasFactory.d.ts.map +1 -0
- package/dist/services/canvas/core/canvasFactory.js +63 -0
- package/dist/services/canvas/core/canvasFactory.js.map +1 -0
- package/dist/services/canvas/core/sqlGate.d.ts +107 -0
- package/dist/services/canvas/core/sqlGate.d.ts.map +1 -0
- package/dist/services/canvas/core/sqlGate.js +267 -0
- package/dist/services/canvas/core/sqlGate.js.map +1 -0
- package/dist/services/canvas/index.d.ts +21 -0
- package/dist/services/canvas/index.d.ts.map +1 -0
- package/dist/services/canvas/index.js +19 -0
- package/dist/services/canvas/index.js.map +1 -0
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +56 -0
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -0
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +600 -0
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -0
- package/dist/services/canvas/providers/duckdb/exportWriter.d.ts +48 -0
- package/dist/services/canvas/providers/duckdb/exportWriter.d.ts.map +1 -0
- package/dist/services/canvas/providers/duckdb/exportWriter.js +119 -0
- package/dist/services/canvas/providers/duckdb/exportWriter.js.map +1 -0
- package/dist/services/canvas/providers/duckdb/schemaSniffer.d.ts +44 -0
- package/dist/services/canvas/providers/duckdb/schemaSniffer.d.ts.map +1 -0
- package/dist/services/canvas/providers/duckdb/schemaSniffer.js +134 -0
- package/dist/services/canvas/providers/duckdb/schemaSniffer.js.map +1 -0
- package/dist/services/canvas/types.d.ts +134 -0
- package/dist/services/canvas/types.d.ts.map +1 -0
- package/dist/services/canvas/types.js +9 -0
- package/dist/services/canvas/types.js.map +1 -0
- package/package.json +14 -5
- package/skills/add-tool/SKILL.md +1 -0
- package/skills/api-canvas/SKILL.md +260 -0
- package/skills/api-config/SKILL.md +18 -0
- package/skills/api-workers/SKILL.md +6 -0
- package/skills/design-mcp-server/SKILL.md +3 -0
- package/skills/report-issue-framework/SKILL.md +1 -0
- package/skills/report-issue-local/SKILL.md +2 -0
- package/skills/security-pass/SKILL.md +24 -0
- package/templates/.github/ISSUE_TEMPLATE/bug_report.yml +1 -0
- package/templates/.github/ISSUE_TEMPLATE/feature_request.yml +1 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Canvas lifecycle registry. Keys canvases by (tenantId, canvasId),
|
|
3
|
+
* generates crypto-secure 10-char URL-safe IDs, enforces a sliding 24h TTL with
|
|
4
|
+
* a 7-day absolute cap, and runs a periodic sweeper that destroys expired
|
|
5
|
+
* canvases via the underlying provider. Also enforces the per-tenant active
|
|
6
|
+
* canvas cap (default 100).
|
|
7
|
+
*
|
|
8
|
+
* Public access is gated by {@link DataCanvas} — callers never see this class
|
|
9
|
+
* directly. Tests construct it with a fake provider and a mock `Date.now` to
|
|
10
|
+
* exercise expiry/sweep logic.
|
|
11
|
+
* @module src/services/canvas/core/CanvasRegistry
|
|
12
|
+
*/
|
|
13
|
+
import { conflict, notFound, rateLimited } from '../../../types-global/errors.js';
|
|
14
|
+
import { logger } from '../../../utils/internal/logger.js';
|
|
15
|
+
import { requestContextService } from '../../../utils/internal/requestContext.js';
|
|
16
|
+
import { IdGenerator } from '../../../utils/security/idGenerator.js';
|
|
17
|
+
/**
|
|
18
|
+
* Canvas ID character set — URL-safe alphabet matching `nanoid`'s default
|
|
19
|
+
* (A-Z, a-z, 0-9, `-`, `_`). 10 chars × 64 alphabet ≈ 1.15 × 10^18 keyspace.
|
|
20
|
+
*/
|
|
21
|
+
const CANVAS_ID_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
22
|
+
const CANVAS_ID_LENGTH = 10;
|
|
23
|
+
const CANVAS_ID_REGEX = /^[A-Za-z0-9_-]{10}$/;
|
|
24
|
+
/**
|
|
25
|
+
* Default lifecycle constants. Mirrored in the config schema; exported so
|
|
26
|
+
* tests and downstream tooling can reference the same numbers.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_CANVAS_REGISTRY_OPTIONS = {
|
|
29
|
+
ttlMs: 24 * 60 * 60 * 1000,
|
|
30
|
+
absoluteCapMs: 7 * 24 * 60 * 60 * 1000,
|
|
31
|
+
maxCanvasesPerTenant: 100,
|
|
32
|
+
sweeperIntervalMs: 60 * 1000,
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Tracks the active canvases for a single process. Not multi-process safe —
|
|
36
|
+
* tokens issued by one process are not portable to another (matches v1 scope
|
|
37
|
+
* in the issue).
|
|
38
|
+
*/
|
|
39
|
+
export class CanvasRegistry {
|
|
40
|
+
provider;
|
|
41
|
+
options;
|
|
42
|
+
clock;
|
|
43
|
+
idGenerator = new IdGenerator();
|
|
44
|
+
canvases = new Map();
|
|
45
|
+
/** Per-tenant index for cap enforcement and listing. */
|
|
46
|
+
byTenant = new Map();
|
|
47
|
+
sweeperTimer;
|
|
48
|
+
isShuttingDown = false;
|
|
49
|
+
constructor(provider, options = DEFAULT_CANVAS_REGISTRY_OPTIONS,
|
|
50
|
+
/** Injected for tests. Defaults to `Date.now`. */
|
|
51
|
+
clock = Date.now) {
|
|
52
|
+
this.provider = provider;
|
|
53
|
+
this.options = options;
|
|
54
|
+
this.clock = clock;
|
|
55
|
+
if (options.sweeperIntervalMs > 0) {
|
|
56
|
+
this.sweeperTimer = setInterval(() => void this.sweep(), options.sweeperIntervalMs);
|
|
57
|
+
// Don't keep the event loop alive solely for the sweeper.
|
|
58
|
+
this.sweeperTimer.unref?.();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve an existing canvas or create a new one when `maybeId` is omitted
|
|
63
|
+
* or the supplied id is unknown for the caller's tenant.
|
|
64
|
+
*
|
|
65
|
+
* - Omitted id → create fresh, return `isNew: true`.
|
|
66
|
+
* - Unknown id → throw `NotFound` (caller should retry without an id).
|
|
67
|
+
* - Known id under wrong tenant → throw `NotFound` (uniform with unknown
|
|
68
|
+
* to avoid leaking existence across tenants).
|
|
69
|
+
* - Known + own tenant → touch (extend TTL), return `isNew: false`.
|
|
70
|
+
*/
|
|
71
|
+
async acquire(maybeId, tenantId, context) {
|
|
72
|
+
if (this.isShuttingDown) {
|
|
73
|
+
throw notFound('Canvas registry is shutting down.', { tenantId });
|
|
74
|
+
}
|
|
75
|
+
if (maybeId !== undefined) {
|
|
76
|
+
const record = this.lookup(maybeId, tenantId);
|
|
77
|
+
if (!record) {
|
|
78
|
+
throw notFound('Canvas not found or expired. Omit canvas_id to start a new canvas.', {
|
|
79
|
+
canvasId: maybeId,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
this.touch(record);
|
|
83
|
+
return {
|
|
84
|
+
canvasId: record.canvasId,
|
|
85
|
+
tenantId: record.tenantId,
|
|
86
|
+
isNew: false,
|
|
87
|
+
expiresAt: new Date(record.expiresAt).toISOString(),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
this.enforceTenantCap(tenantId);
|
|
91
|
+
const canvasId = this.mintId();
|
|
92
|
+
const now = this.clock();
|
|
93
|
+
const record = {
|
|
94
|
+
canvasId,
|
|
95
|
+
tenantId,
|
|
96
|
+
createdAt: now,
|
|
97
|
+
lastAccessedAt: now,
|
|
98
|
+
expiresAt: now + this.options.ttlMs,
|
|
99
|
+
};
|
|
100
|
+
this.canvases.set(canvasId, record);
|
|
101
|
+
this.indexByTenant(tenantId, canvasId);
|
|
102
|
+
await this.provider.initCanvas(canvasId, context);
|
|
103
|
+
logger.debug('Canvas created.', {
|
|
104
|
+
...context,
|
|
105
|
+
canvasId,
|
|
106
|
+
tenantId,
|
|
107
|
+
provider: this.provider.name,
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
canvasId,
|
|
111
|
+
tenantId,
|
|
112
|
+
isNew: true,
|
|
113
|
+
expiresAt: new Date(record.expiresAt).toISOString(),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Validate that `canvasId` belongs to `tenantId` and is not expired, then
|
|
118
|
+
* extend its TTL and return the resolved expiry. Used by {@link CanvasInstance}
|
|
119
|
+
* before every op so individual operations slide the window.
|
|
120
|
+
*/
|
|
121
|
+
touchOrThrow(canvasId, tenantId) {
|
|
122
|
+
const record = this.lookup(canvasId, tenantId);
|
|
123
|
+
if (!record) {
|
|
124
|
+
throw notFound('Canvas not found or expired.', { canvasId });
|
|
125
|
+
}
|
|
126
|
+
this.touch(record);
|
|
127
|
+
return new Date(record.expiresAt).toISOString();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Drop a canvas explicitly (e.g. tenant-initiated cleanup). Returns true
|
|
131
|
+
* when the canvas existed and was destroyed.
|
|
132
|
+
*/
|
|
133
|
+
async drop(canvasId, tenantId, context) {
|
|
134
|
+
const record = this.lookup(canvasId, tenantId);
|
|
135
|
+
if (!record)
|
|
136
|
+
return false;
|
|
137
|
+
await this.destroy(record, context);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
/** Active canvas count for a tenant (used by tests and metrics surfaces). */
|
|
141
|
+
countForTenant(tenantId) {
|
|
142
|
+
return this.byTenant.get(tenantId)?.size ?? 0;
|
|
143
|
+
}
|
|
144
|
+
/** Total active canvases (used by tests and metrics surfaces). */
|
|
145
|
+
totalActive() {
|
|
146
|
+
return this.canvases.size;
|
|
147
|
+
}
|
|
148
|
+
/** Stop the sweeper and tear down every active canvas. Idempotent. */
|
|
149
|
+
async shutdown(context) {
|
|
150
|
+
if (this.isShuttingDown)
|
|
151
|
+
return;
|
|
152
|
+
this.isShuttingDown = true;
|
|
153
|
+
if (this.sweeperTimer) {
|
|
154
|
+
clearInterval(this.sweeperTimer);
|
|
155
|
+
this.sweeperTimer = undefined;
|
|
156
|
+
}
|
|
157
|
+
const records = Array.from(this.canvases.values());
|
|
158
|
+
await Promise.allSettled(records.map((r) => this.destroy(r, context)));
|
|
159
|
+
await this.provider.shutdown();
|
|
160
|
+
}
|
|
161
|
+
/** @internal Test/diagnostic hook — runs one sweep pass synchronously. */
|
|
162
|
+
async sweep() {
|
|
163
|
+
if (this.isShuttingDown)
|
|
164
|
+
return;
|
|
165
|
+
const now = this.clock();
|
|
166
|
+
const expired = [];
|
|
167
|
+
for (const record of this.canvases.values()) {
|
|
168
|
+
if (now >= record.expiresAt || now - record.createdAt >= this.options.absoluteCapMs) {
|
|
169
|
+
expired.push(record);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (expired.length === 0)
|
|
173
|
+
return;
|
|
174
|
+
const sweepContext = requestContextService.createRequestContext({
|
|
175
|
+
operation: 'CanvasRegistry.sweep',
|
|
176
|
+
});
|
|
177
|
+
await Promise.allSettled(expired.map((r) => this.destroy(r, sweepContext)));
|
|
178
|
+
logger.debug('Canvas sweeper destroyed expired canvases.', {
|
|
179
|
+
...sweepContext,
|
|
180
|
+
destroyedCount: expired.length,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// ---------------------------------------------------------------------
|
|
184
|
+
// Internals
|
|
185
|
+
// ---------------------------------------------------------------------
|
|
186
|
+
lookup(canvasId, tenantId) {
|
|
187
|
+
if (!CANVAS_ID_REGEX.test(canvasId))
|
|
188
|
+
return;
|
|
189
|
+
const record = this.canvases.get(canvasId);
|
|
190
|
+
if (!record)
|
|
191
|
+
return;
|
|
192
|
+
if (record.tenantId !== tenantId)
|
|
193
|
+
return;
|
|
194
|
+
const now = this.clock();
|
|
195
|
+
if (now >= record.expiresAt || now - record.createdAt >= this.options.absoluteCapMs) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
return record;
|
|
199
|
+
}
|
|
200
|
+
touch(record) {
|
|
201
|
+
const now = this.clock();
|
|
202
|
+
record.lastAccessedAt = now;
|
|
203
|
+
const slidingExpiry = now + this.options.ttlMs;
|
|
204
|
+
const absoluteExpiry = record.createdAt + this.options.absoluteCapMs;
|
|
205
|
+
record.expiresAt = Math.min(slidingExpiry, absoluteExpiry);
|
|
206
|
+
}
|
|
207
|
+
enforceTenantCap(tenantId) {
|
|
208
|
+
const count = this.byTenant.get(tenantId)?.size ?? 0;
|
|
209
|
+
if (count >= this.options.maxCanvasesPerTenant) {
|
|
210
|
+
throw rateLimited(`Tenant has reached the active canvas cap (${this.options.maxCanvasesPerTenant}). Drop unused canvases or wait for the sliding TTL to expire them.`, { tenantId, activeCount: count, cap: this.options.maxCanvasesPerTenant });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
mintId() {
|
|
214
|
+
// Loop-mint to avoid the (vanishingly rare) collision case.
|
|
215
|
+
for (let i = 0; i < 5; i += 1) {
|
|
216
|
+
const id = this.idGenerator.generateRandomString(CANVAS_ID_LENGTH, CANVAS_ID_CHARSET);
|
|
217
|
+
if (!this.canvases.has(id))
|
|
218
|
+
return id;
|
|
219
|
+
}
|
|
220
|
+
throw conflict('Failed to generate a unique canvas ID after 5 attempts.');
|
|
221
|
+
}
|
|
222
|
+
indexByTenant(tenantId, canvasId) {
|
|
223
|
+
let set = this.byTenant.get(tenantId);
|
|
224
|
+
if (!set) {
|
|
225
|
+
set = new Set();
|
|
226
|
+
this.byTenant.set(tenantId, set);
|
|
227
|
+
}
|
|
228
|
+
set.add(canvasId);
|
|
229
|
+
}
|
|
230
|
+
async destroy(record, context) {
|
|
231
|
+
this.canvases.delete(record.canvasId);
|
|
232
|
+
const set = this.byTenant.get(record.tenantId);
|
|
233
|
+
if (set) {
|
|
234
|
+
set.delete(record.canvasId);
|
|
235
|
+
if (set.size === 0)
|
|
236
|
+
this.byTenant.delete(record.tenantId);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
await this.provider.destroyCanvas(record.canvasId, context);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
logger.warning('Provider destroyCanvas failed during sweep.', {
|
|
243
|
+
...context,
|
|
244
|
+
canvasId: record.canvasId,
|
|
245
|
+
error: err instanceof Error ? err.message : String(err),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=CanvasRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CanvasRegistry.js","sourceRoot":"","sources":["../../../../src/services/canvas/core/CanvasRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
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/services/canvas/core/DataCanvas
|
|
7
|
+
*/
|
|
8
|
+
import type { RequestContext } from '../../../utils/internal/requestContext.js';
|
|
9
|
+
import type { AcquireOptions } from '../types.js';
|
|
10
|
+
import { CanvasInstance } from './CanvasInstance.js';
|
|
11
|
+
import type { CanvasRegistry } from './CanvasRegistry.js';
|
|
12
|
+
import type { IDataCanvasProvider } from './IDataCanvasProvider.js';
|
|
13
|
+
/**
|
|
14
|
+
* Service entry point for the DataCanvas primitive. Returned from
|
|
15
|
+
* `core.canvas` on `CoreServices` when a canvas provider is configured.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const canvas = await core.canvas!.acquire(input.canvas_id, ctx);
|
|
20
|
+
* await canvas.registerTable('germplasm', rows);
|
|
21
|
+
* const result = await canvas.query('SELECT name FROM germplasm LIMIT 10');
|
|
22
|
+
* return { canvas_id: canvas.canvasId, expires_at: canvas.expiresAt, ... };
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare class DataCanvas {
|
|
26
|
+
private readonly provider;
|
|
27
|
+
private readonly registry;
|
|
28
|
+
constructor(provider: IDataCanvasProvider, registry: CanvasRegistry);
|
|
29
|
+
/**
|
|
30
|
+
* Resolve an existing canvas or create a new one. The returned
|
|
31
|
+
* {@link CanvasInstance} captures `(canvasId, tenantId)` so subsequent
|
|
32
|
+
* operations don't need to repeat them.
|
|
33
|
+
*/
|
|
34
|
+
acquire(maybeId: string | undefined, context: RequestContext, _options?: AcquireOptions): Promise<CanvasInstance>;
|
|
35
|
+
/**
|
|
36
|
+
* Drop a canvas explicitly. Returns true when the canvas existed and was
|
|
37
|
+
* destroyed.
|
|
38
|
+
*/
|
|
39
|
+
drop(canvasId: string, context: RequestContext): Promise<boolean>;
|
|
40
|
+
/** Active canvas count for the calling tenant. */
|
|
41
|
+
countForTenant(context: RequestContext): number;
|
|
42
|
+
/** Liveness check on the underlying provider. */
|
|
43
|
+
healthCheck(): Promise<boolean>;
|
|
44
|
+
/** Tear down the registry and provider. Called from `ServerHandle.shutdown()`. */
|
|
45
|
+
shutdown(context: RequestContext): Promise<void>;
|
|
46
|
+
/** @internal Surface the underlying provider for advanced use cases. */
|
|
47
|
+
getProvider(): IDataCanvasProvider;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=DataCanvas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataCanvas.d.ts","sourceRoot":"","sources":["../../../../src/services/canvas/core/DataCanvas.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAmBpE;;;;;;;;;;;GAWG;AACH,qBAAa,UAAU;IAEnB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,QAAQ,EAAE,mBAAmB,EAC7B,QAAQ,EAAE,cAAc;IAK3C;;;;OAIG;IACG,OAAO,CACX,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,cAAc,EACvB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,cAAc,CAAC;IAoB1B;;;OAGG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAKvE,kDAAkD;IAClD,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM;IAK/C,iDAAiD;IACjD,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,kFAAkF;IAC5E,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD,wEAAwE;IACxE,WAAW,IAAI,mBAAmB;CAGnC"}
|
|
@@ -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/services/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/services/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/services/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/services/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/services/canvas/core/IDataCanvasProvider
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=IDataCanvasProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IDataCanvasProvider.js","sourceRoot":"","sources":["../../../../src/services/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/services/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/services/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/services/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/services/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"}
|