@superatomai/sdk-node 0.0.8-s → 0.0.9-dsp
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/dist/index.d.mts +109 -3
- package/dist/index.d.ts +109 -3
- package/dist/index.js +3285 -1521
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3285 -1521
- package/dist/index.mjs.map +1 -1
- package/dist/userResponse/scripts/script-bootstrap.js +61 -65
- package/dist/userResponse/scripts/script-bootstrap.js.map +1 -1
- package/dist/userResponse/scripts/script-bootstrap.mjs +61 -65
- package/dist/userResponse/scripts/script-bootstrap.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -48,9 +48,6 @@ var LineSplitter = class {
|
|
|
48
48
|
return rest;
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
-
function shortId() {
|
|
52
|
-
return Math.random().toString(36).slice(2, 10);
|
|
53
|
-
}
|
|
54
51
|
|
|
55
52
|
// src/userResponse/scripts/script-bootstrap.ts
|
|
56
53
|
var SCRIPT_ARG = process.argv[2];
|
|
@@ -58,8 +55,20 @@ if (!SCRIPT_ARG) {
|
|
|
58
55
|
sendError("Bootstrap was invoked without a script path.", "compile");
|
|
59
56
|
process.exit(1);
|
|
60
57
|
}
|
|
61
|
-
var
|
|
58
|
+
var BRIDGE_URL = process.env.SCRIPT_BRIDGE_URL;
|
|
62
59
|
var executedQueries = [];
|
|
60
|
+
var PREVIEW_MAX_ROWS = 10;
|
|
61
|
+
var PREVIEW_MAX_CHARS = 200;
|
|
62
|
+
function previewRows(data) {
|
|
63
|
+
return data.slice(0, PREVIEW_MAX_ROWS).map((row) => {
|
|
64
|
+
if (!row || typeof row !== "object") return row;
|
|
65
|
+
const out = {};
|
|
66
|
+
for (const [k, v] of Object.entries(row)) {
|
|
67
|
+
out[k] = typeof v === "string" && v.length > PREVIEW_MAX_CHARS ? v.slice(0, PREVIEW_MAX_CHARS) + "\u2026" : v;
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
63
72
|
var initResolve = null;
|
|
64
73
|
var initPromise = new Promise((resolve) => {
|
|
65
74
|
initResolve = resolve;
|
|
@@ -68,31 +77,58 @@ function buildCtx(init) {
|
|
|
68
77
|
return {
|
|
69
78
|
now: init.now,
|
|
70
79
|
async query(toolId, sql) {
|
|
71
|
-
|
|
80
|
+
if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
|
|
72
81
|
const startedAt = Date.now();
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
send({ type: "stream", chunk: `
|
|
83
|
+
\u{1F4DD} **Querying ${toolId}:**
|
|
84
|
+
\`\`\`sql
|
|
85
|
+
${sql}
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
` });
|
|
89
|
+
send({ type: "stream", chunk: `__QUERY_TIMER_START_Executing query__` });
|
|
90
|
+
const res = await fetch(`${BRIDGE_URL}/query`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: { "content-type": "application/json" },
|
|
93
|
+
body: JSON.stringify({ toolId, sql })
|
|
75
94
|
});
|
|
76
|
-
|
|
77
|
-
const result = await promise;
|
|
95
|
+
const json = await res.json();
|
|
78
96
|
const executionTimeMs = Date.now() - startedAt;
|
|
97
|
+
send({ type: "stream", chunk: `__QUERY_TIMER_DONE_${(executionTimeMs / 1e3).toFixed(1)}__
|
|
98
|
+
|
|
99
|
+
` });
|
|
100
|
+
if (!res.ok || !json.success) {
|
|
101
|
+
const errMsg = json.error || `Bridge query failed (${res.status})`;
|
|
102
|
+
send({ type: "stream", chunk: `\u274C **Query failed on ${toolId}:** ${errMsg}
|
|
103
|
+
|
|
104
|
+
` });
|
|
105
|
+
throw new Error(errMsg);
|
|
106
|
+
}
|
|
107
|
+
const data = json.data ?? [];
|
|
108
|
+
const count = json.count ?? data.length;
|
|
109
|
+
send({ type: "stream", chunk: `\u2705 **${count} rows from ${toolId}**
|
|
110
|
+
|
|
111
|
+
` });
|
|
112
|
+
if (data.length > 0) {
|
|
113
|
+
send({ type: "stream", chunk: `<DataTable>${JSON.stringify(previewRows(data))}</DataTable>
|
|
114
|
+
|
|
115
|
+
` });
|
|
116
|
+
}
|
|
79
117
|
executedQueries.push({
|
|
80
118
|
sourceId: toolId,
|
|
81
119
|
sourceName: toolId,
|
|
82
|
-
//
|
|
120
|
+
// enriched by runner after result arrives
|
|
83
121
|
sql,
|
|
84
|
-
data
|
|
85
|
-
count
|
|
86
|
-
totalCount: result.metadata?.totalCount,
|
|
122
|
+
data,
|
|
123
|
+
count,
|
|
87
124
|
executionTimeMs
|
|
88
125
|
});
|
|
89
|
-
return { data
|
|
126
|
+
return { data, count };
|
|
90
127
|
},
|
|
91
128
|
/**
|
|
92
129
|
* Call a tool with structured params (typically a direct tool whose
|
|
93
130
|
* fn(params) does not expect a SQL string). Returns whatever the tool
|
|
94
|
-
* returned — shape is up to the tool.
|
|
95
|
-
* against the authorized externalTools list before executing.
|
|
131
|
+
* returned — shape is up to the tool.
|
|
96
132
|
*
|
|
97
133
|
* Use this when you need a pre-built business-logic tool (e.g.,
|
|
98
134
|
* "Calculate Shelf Life") that takes structured input rather than SQL.
|
|
@@ -103,18 +139,15 @@ function buildCtx(init) {
|
|
|
103
139
|
* the array you want to chart.
|
|
104
140
|
*/
|
|
105
141
|
async runTool(toolId, params = {}) {
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
toolId,
|
|
112
|
-
sql: `-- runTool: ${toolId}`,
|
|
113
|
-
startedAt: Date.now()
|
|
114
|
-
});
|
|
142
|
+
if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
|
|
143
|
+
const res = await fetch(`${BRIDGE_URL}/run-tool`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: { "content-type": "application/json" },
|
|
146
|
+
body: JSON.stringify({ toolId, params })
|
|
115
147
|
});
|
|
116
|
-
|
|
117
|
-
|
|
148
|
+
const json = await res.json();
|
|
149
|
+
if (!res.ok || !json.success) throw new Error(json.error || `Bridge run-tool failed (${res.status})`);
|
|
150
|
+
return json.result;
|
|
118
151
|
},
|
|
119
152
|
/**
|
|
120
153
|
* Register a computed dataset. Flows into executedQueries alongside
|
|
@@ -147,12 +180,6 @@ function send(msg) {
|
|
|
147
180
|
function sendError(message, phase, stack) {
|
|
148
181
|
send({ type: "error", message, stack, phase });
|
|
149
182
|
}
|
|
150
|
-
function rejectAllPending(reason) {
|
|
151
|
-
for (const [id, entry] of Array.from(pending.entries())) {
|
|
152
|
-
pending.delete(id);
|
|
153
|
-
entry.reject(new Error(reason));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
183
|
var splitter = new LineSplitter();
|
|
157
184
|
process.stdin.setEncoding("utf-8");
|
|
158
185
|
process.stdin.on("data", (chunk) => {
|
|
@@ -161,7 +188,6 @@ process.stdin.on("data", (chunk) => {
|
|
|
161
188
|
lines = splitter.push(chunk);
|
|
162
189
|
} catch (err) {
|
|
163
190
|
const msg = err instanceof Error ? err.message : String(err);
|
|
164
|
-
rejectAllPending(msg);
|
|
165
191
|
sendError(msg, "runtime");
|
|
166
192
|
process.exit(1);
|
|
167
193
|
}
|
|
@@ -175,12 +201,6 @@ process.stdin.on("data", (chunk) => {
|
|
|
175
201
|
handleMessage(msg);
|
|
176
202
|
}
|
|
177
203
|
});
|
|
178
|
-
process.stdin.on("end", () => {
|
|
179
|
-
rejectAllPending("Parent closed the IPC channel (stdin end) before a query completed.");
|
|
180
|
-
});
|
|
181
|
-
process.stdin.on("error", (err) => {
|
|
182
|
-
rejectAllPending(`IPC channel error before a query completed: ${err.message}`);
|
|
183
|
-
});
|
|
184
204
|
function handleMessage(msg) {
|
|
185
205
|
switch (msg.type) {
|
|
186
206
|
case "init": {
|
|
@@ -192,37 +212,13 @@ function handleMessage(msg) {
|
|
|
192
212
|
}
|
|
193
213
|
break;
|
|
194
214
|
}
|
|
195
|
-
case "query_result": {
|
|
196
|
-
const entry = pending.get(msg.id);
|
|
197
|
-
if (entry) {
|
|
198
|
-
pending.delete(msg.id);
|
|
199
|
-
entry.resolve({ data: msg.data, count: msg.count, metadata: msg.metadata });
|
|
200
|
-
}
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
203
|
-
case "run_tool_result": {
|
|
204
|
-
const entry = pending.get(msg.id);
|
|
205
|
-
if (entry) {
|
|
206
|
-
pending.delete(msg.id);
|
|
207
|
-
entry.resolve({ result: msg.result });
|
|
208
|
-
}
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
case "query_error": {
|
|
212
|
-
const entry = pending.get(msg.id);
|
|
213
|
-
if (entry) {
|
|
214
|
-
pending.delete(msg.id);
|
|
215
|
-
entry.reject(new Error(msg.error));
|
|
216
|
-
}
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
215
|
}
|
|
220
216
|
}
|
|
221
217
|
(async () => {
|
|
222
218
|
let getData;
|
|
223
219
|
try {
|
|
224
220
|
const mod = await import((0, import_url.pathToFileURL)(SCRIPT_ARG).href);
|
|
225
|
-
getData = mod.getData ?? mod.default;
|
|
221
|
+
getData = mod.getData ?? (typeof mod.default === "function" ? mod.default : mod.default?.getData);
|
|
226
222
|
if (typeof getData !== "function") {
|
|
227
223
|
throw new Error(
|
|
228
224
|
`Script at ${SCRIPT_ARG} does not export getData \u2014 expected \`export async function getData(ctx, params)\``
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/userResponse/scripts/script-bootstrap.ts","../../../src/userResponse/scripts/script-ipc.ts"],"sourcesContent":["/**\n * script-bootstrap.ts — Child-side entrypoint for subprocess script execution.\n *\n * Runs as a separate process spawned by ScriptRunner via tsx. Reads the\n * target user-script path from argv[2], listens for INIT on stdin, builds\n * the ctx object, dynamic-imports the user script's `getData` export,\n * and reports the result (or error) back via stdout.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\nimport { pathToFileURL } from 'url';\nimport {\n\tencodeMessage,\n\tLineSplitter,\n\tshortId,\n\ttype ChildToParentMessage,\n\ttype ParentToChildMessage,\n\ttype ExecutedQuery,\n\ttype QueryResultMessage,\n\ttype QueryErrorMessage,\n} from './script-ipc';\n\nconst SCRIPT_ARG = process.argv[2];\nif (!SCRIPT_ARG) {\n\tsendError('Bootstrap was invoked without a script path.', 'compile');\n\tprocess.exit(1);\n}\n\n/**\n * Pending queries await a QUERY_RESULT or QUERY_ERROR from the parent.\n * Keyed by the short uuid we generated when sending the QUERY message.\n */\n/**\n * One pending entry per in-flight ctx.query or ctx.runTool. The resolve\n * payload differs by call kind ({data, count, metadata} for query,\n * {result} for runTool) — each caller's resolve handler unwraps the shape\n * it expects, so the map uses `any` rather than a discriminated union to\n * keep the call sites simple.\n */\nconst pending = new Map<string, {\n\tresolve: (value: any) => void;\n\treject: (err: Error) => void;\n\ttoolId: string;\n\tsql: string;\n\tstartedAt: number;\n}>();\n\n/** Completed queries — reported to the parent as part of RESULT */\nconst executedQueries: ExecutedQuery[] = [];\n\n/** Resolvers for the INIT handshake — getData waits for this */\nlet initResolve: ((init: { params: Record<string, any>; now: Date }) => void) | null = null;\nconst initPromise = new Promise<{ params: Record<string, any>; now: Date }>((resolve) => {\n\tinitResolve = resolve;\n});\n\n/**\n * Source name lookup is not available in the child (the parent holds the\n * ExternalTool list). We use toolId as sourceName and let the parent fill\n * it in when building the final ScriptResult, if needed.\n */\nfunction buildCtx(init: { params: Record<string, any>; now: Date }) {\n\treturn {\n\t\tnow: init.now,\n\n\t\tasync query(toolId: string, sql: string): Promise<{ data: any[]; count: number }> {\n\t\t\tconst id = shortId();\n\t\t\tconst startedAt = Date.now();\n\t\t\tconst promise = new Promise<{ data: any[]; count: number; metadata?: any }>((resolve, reject) => {\n\t\t\t\tpending.set(id, { resolve, reject, toolId, sql, startedAt });\n\t\t\t});\n\n\t\t\tsend({ type: 'query', id, toolId, sql });\n\n\t\t\tconst result = await promise;\n\t\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: toolId,\n\t\t\t\tsourceName: toolId, // parent overwrites with the real name\n\t\t\t\tsql,\n\t\t\t\tdata: result.data,\n\t\t\t\tcount: result.count,\n\t\t\t\ttotalCount: result.metadata?.totalCount,\n\t\t\t\texecutionTimeMs,\n\t\t\t});\n\n\t\t\treturn { data: result.data, count: result.count };\n\t\t},\n\n\t\t/**\n\t\t * Call a tool with structured params (typically a direct tool whose\n\t\t * fn(params) does not expect a SQL string). Returns whatever the tool\n\t\t * returned — shape is up to the tool. The parent verifies toolId\n\t\t * against the authorized externalTools list before executing.\n\t\t *\n\t\t * Use this when you need a pre-built business-logic tool (e.g.,\n\t\t * \"Calculate Shelf Life\") that takes structured input rather than SQL.\n\t\t * For SQL source tools, keep using ctx.query.\n\t\t *\n\t\t * runTool results do NOT auto-flow into executedQueries. If you want\n\t\t * the result visualised, register it via ctx.emit() after extracting\n\t\t * the array you want to chart.\n\t\t */\n\t\tasync runTool<T = any>(toolId: string, params: Record<string, any> = {}): Promise<T> {\n\t\t\tconst id = shortId();\n\t\t\tconst promise = new Promise<any>((resolveResult, rejectResult) => {\n\t\t\t\tpending.set(id, {\n\t\t\t\t\tresolve: (payload: any) => resolveResult(payload?.result ?? payload),\n\t\t\t\t\treject: rejectResult,\n\t\t\t\t\ttoolId,\n\t\t\t\t\tsql: `-- runTool: ${toolId}`,\n\t\t\t\t\tstartedAt: Date.now(),\n\t\t\t\t});\n\t\t\t});\n\t\t\tsend({ type: 'run_tool', id, toolId, params });\n\t\t\treturn promise;\n\t\t},\n\n\t\t/**\n\t\t * Register a computed dataset. Flows into executedQueries alongside\n\t\t * real SQL query results so component generation can render post-SQL\n\t\t * transforms (weighted averages, clustering, joins) as charts.\n\t\t */\n\t\temit(name: string, rows: any[], meta?: { description?: string }): void {\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: `computed:${name}`,\n\t\t\t\tsourceName: meta?.description || name,\n\t\t\t\tsql: `-- computed dataset: ${name}`,\n\t\t\t\tdata: Array.isArray(rows) ? rows : [],\n\t\t\t\tcount: Array.isArray(rows) ? rows.length : 0,\n\t\t\t\texecutionTimeMs: 0,\n\t\t\t\tvirtual: true,\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Forward a short progress message to the parent's StreamBuffer so the UI\n\t\t * continues to see per-script feedback during execution.\n\t\t */\n\t\tlog(chunk: string): void {\n\t\t\tsend({ type: 'stream', chunk });\n\t\t},\n\t};\n}\n\nfunction send(msg: ChildToParentMessage): void {\n\tprocess.stdout.write(encodeMessage(msg));\n}\n\nfunction sendError(message: string, phase: 'compile' | 'runtime', stack?: string): void {\n\tsend({ type: 'error', message, stack, phase });\n}\n\n/**\n * Reject every in-flight ctx.query / ctx.runTool with the same reason. Used when\n * the IPC channel goes away (stdin end/error) so a pending query fails fast\n * instead of hanging until the parent's wall-clock SIGKILL. Idempotent — a\n * second call finds the map already drained.\n */\nfunction rejectAllPending(reason: string): void {\n\tfor (const [id, entry] of Array.from(pending.entries())) {\n\t\tpending.delete(id);\n\t\tentry.reject(new Error(reason));\n\t}\n}\n\n// ============================================\n// stdin reader — parse NDJSON messages from the parent\n// ============================================\n\nconst splitter = new LineSplitter();\n\nprocess.stdin.setEncoding('utf-8');\nprocess.stdin.on('data', (chunk: string) => {\n\tlet lines: string[];\n\ttry {\n\t\tlines = splitter.push(chunk);\n\t} catch (err) {\n\t\t// Oversized parent message (#9) — abort cleanly rather than buffer to OOM.\n\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\trejectAllPending(msg);\n\t\tsendError(msg, 'runtime');\n\t\tprocess.exit(1);\n\t}\n\tfor (const line of lines) {\n\t\tlet msg: ParentToChildMessage;\n\t\ttry {\n\t\t\tmsg = JSON.parse(line);\n\t\t} catch {\n\t\t\tcontinue; // ignore malformed input\n\t\t}\n\t\thandleMessage(msg);\n\t}\n});\n\nprocess.stdin.on('end', () => {\n\t// Parent closed our stdin — no further query replies can arrive. Reject any\n\t// in-flight queries now so getData fails fast (and we report ERROR) instead\n\t// of awaiting a reply that will never come, until the parent's wall-clock kill.\n\trejectAllPending('Parent closed the IPC channel (stdin end) before a query completed.');\n});\n\nprocess.stdin.on('error', (err: Error) => {\n\trejectAllPending(`IPC channel error before a query completed: ${err.message}`);\n});\n\nfunction handleMessage(msg: ParentToChildMessage): void {\n\tswitch (msg.type) {\n\t\tcase 'init': {\n\t\t\tif (initResolve) {\n\t\t\t\tconst now = new Date(msg.now || new Date().toISOString());\n\t\t\t\tconst params = restoreParamTypes(msg.params || {}, msg.paramSchema);\n\t\t\t\tinitResolve({ params, now });\n\t\t\t\tinitResolve = null;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'query_result': {\n\t\t\tconst entry = pending.get(msg.id);\n\t\t\tif (entry) {\n\t\t\t\tpending.delete(msg.id);\n\t\t\t\tentry.resolve({ data: msg.data, count: msg.count, metadata: msg.metadata });\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'run_tool_result': {\n\t\t\tconst entry = pending.get(msg.id);\n\t\t\tif (entry) {\n\t\t\t\tpending.delete(msg.id);\n\t\t\t\t// runTool's resolve unwraps payload.result — wrap here so the\n\t\t\t\t// same pending entry shape works for both query and runTool.\n\t\t\t\tentry.resolve({ result: msg.result });\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'query_error': {\n\t\t\tconst entry = pending.get(msg.id);\n\t\t\tif (entry) {\n\t\t\t\tpending.delete(msg.id);\n\t\t\t\tentry.reject(new Error(msg.error));\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n// ============================================\n// Main — import the user script and run getData\n// ============================================\n\n(async () => {\n\tlet getData: (ctx: any, params: any) => Promise<any>;\n\n\ttry {\n\t\t// Dynamic import — tsx transpiles the user .ts file on the fly\n\t\tconst mod = await import(pathToFileURL(SCRIPT_ARG).href);\n\t\tgetData = mod.getData ?? mod.default;\n\t\tif (typeof getData !== 'function') {\n\t\t\tthrow new Error(\n\t\t\t\t`Script at ${SCRIPT_ARG} does not export getData — expected \\`export async function getData(ctx, params)\\``,\n\t\t\t);\n\t\t}\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'compile', e.stack);\n\t\tprocess.exit(1);\n\t\treturn;\n\t}\n\n\tconst init = await initPromise;\n\tconst ctx = buildCtx(init);\n\tconst startedAt = Date.now();\n\n\ttry {\n\t\tconst raw = await getData(ctx, init.params);\n\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\tconst data = normalizeResultData(raw);\n\t\tconst count = typeof raw?.count === 'number' ? raw.count : data.length;\n\t\tconst sampleRows = data.slice(0, 5);\n\n\t\tconst result: ChildToParentMessage = {\n\t\t\ttype: 'result',\n\t\t\tdata,\n\t\t\tcount,\n\t\t\texecutedQueries,\n\t\t\texecutionTimeMs,\n\t\t\tsampleRows,\n\t\t};\n\t\tsend(result);\n\t\t// Give stdout a tick to flush before we exit\n\t\tprocess.stdout.once('drain', () => process.exit(0));\n\t\tsetImmediate(() => process.exit(0));\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'runtime', e.stack);\n\t\tprocess.exit(1);\n\t}\n})();\n\n/**\n * Normalize whatever getData returned into an array. Mirrors the old\n * ScriptRunner semantics: direct array, { data: [] }, or { data: {...} }\n * wrapped as single-element array.\n */\nfunction normalizeResultData(raw: any): any[] {\n\tif (!raw) return [];\n\tif (Array.isArray(raw)) return raw;\n\tif (Array.isArray(raw.data)) return raw.data;\n\tif (raw.data && typeof raw.data === 'object') return [raw.data];\n\treturn [];\n}\n\n/**\n * Restore typed values that JSON stripped during IPC transport. Date and\n * date_range params arrive as ISO strings; re-wrap them so scripts can call\n * `.toISOString()` and other Date methods the same way they would in-process.\n */\nfunction restoreParamTypes(\n\tparams: Record<string, any>,\n\tschema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>,\n): Record<string, any> {\n\tif (!schema) return params;\n\tconst out = { ...params };\n\tfor (const [name, type] of Object.entries(schema)) {\n\t\tconst v = out[name];\n\t\tif (v === undefined || v === null) continue;\n\t\tif (type === 'date' && typeof v === 'string') {\n\t\t\tconst d = new Date(v);\n\t\t\tif (!isNaN(d.getTime())) out[name] = d;\n\t\t} else if (type === 'date_range' && typeof v === 'object') {\n\t\t\tconst from = v.from;\n\t\t\tconst to = v.to;\n\t\t\tout[name] = {\n\t\t\t\tfrom: typeof from === 'string' ? new Date(from) : from,\n\t\t\t\tto: typeof to === 'string' ? new Date(to) : to,\n\t\t\t};\n\t\t}\n\t}\n\treturn out;\n}\n","/**\n * script-ipc.ts — Protocol definitions for parent/child IPC.\n *\n * Transport: newline-delimited JSON over the child's stdin/stdout.\n * stderr is passed through for uncaught exceptions and compile errors.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\n// ============================================\n// Parent → Child\n// ============================================\n\nexport interface InitMessage {\n\ttype: 'init';\n\t/**\n\t * Params passed to getData(). Date/date_range values cross the IPC boundary\n\t * as ISO strings and are restored to Date objects in the child via paramSchema.\n\t */\n\tparams: Record<string, any>;\n\t/** Frozen timestamp for ctx.now — ISO string */\n\tnow: string;\n\t/**\n\t * Maps each parameter name to its declared type so the child can restore\n\t * Date objects (lost during JSON serialization).\n\t */\n\tparamSchema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>;\n}\n\nexport interface QueryResultMessage {\n\ttype: 'query_result';\n\t/** Matches the `id` from the child's QUERY message */\n\tid: string;\n\tdata: any[];\n\tcount: number;\n\tmetadata?: {\n\t\ttotalCount?: number;\n\t\texecutionTimeMs?: number;\n\t};\n}\n\nexport interface QueryErrorMessage {\n\ttype: 'query_error';\n\tid: string;\n\terror: string;\n}\n\n/**\n * Response to a `run_tool` request — carries whatever the direct-tool's fn()\n * returned. Shape is arbitrary; the script is expected to know what its chosen\n * tool yields and unwrap accordingly.\n */\nexport interface RunToolResultMessage {\n\ttype: 'run_tool_result';\n\tid: string;\n\tresult: any;\n}\n\nexport type ParentToChildMessage =\n\t| InitMessage\n\t| QueryResultMessage\n\t| QueryErrorMessage\n\t| RunToolResultMessage;\n\n// ============================================\n// Child → Parent\n// ============================================\n\nexport interface QueryMessage {\n\ttype: 'query';\n\t/** Short uuid — parent uses this to correlate its response */\n\tid: string;\n\ttoolId: string;\n\tsql: string;\n}\n\n/**\n * Run an arbitrary tool with structured params. Used by ctx.runTool() to call\n * direct tools (and any tool whose `fn(params)` doesn't expect a SQL string).\n * Reply is RunToolResultMessage or QueryErrorMessage.\n */\nexport interface RunToolMessage {\n\ttype: 'run_tool';\n\tid: string;\n\ttoolId: string;\n\tparams: Record<string, any>;\n}\n\nexport interface StreamMessage {\n\ttype: 'stream';\n\tchunk: string;\n}\n\nexport interface ExecutedQuery {\n\tsourceId: string;\n\tsourceName: string;\n\tsql: string;\n\tdata: any[];\n\tcount: number;\n\ttotalCount?: number;\n\texecutionTimeMs: number;\n\t/**\n\t * When the script registered a computed dataset via ctx.emit(), this is\n\t * marked virtual so component generation can distinguish it from a real SQL query.\n\t */\n\tvirtual?: boolean;\n}\n\nexport interface ResultMessage {\n\ttype: 'result';\n\tdata: any[];\n\tcount: number;\n\texecutedQueries: ExecutedQuery[];\n\texecutionTimeMs: number;\n\t/** First 5 rows of the final `data` — lets the parent judge intent-match cheaply */\n\tsampleRows: any[];\n}\n\nexport interface ErrorMessage {\n\ttype: 'error';\n\tmessage: string;\n\tstack?: string;\n\t/** \"compile\" → tsx couldn't parse; \"runtime\" → thrown during getData */\n\tphase?: 'compile' | 'runtime';\n}\n\nexport type ChildToParentMessage =\n\t| QueryMessage\n\t| RunToolMessage\n\t| StreamMessage\n\t| ResultMessage\n\t| ErrorMessage;\n\n// ============================================\n// Utilities\n// ============================================\n\n/**\n * Encode a message for transport: JSON + newline.\n */\nexport function encodeMessage(msg: ParentToChildMessage | ChildToParentMessage): string {\n\treturn JSON.stringify(msg) + '\\n';\n}\n\n/**\n * Thrown when a single NDJSON line grows past the byte ceiling without a\n * newline — a runaway writer or a pathologically large message. Callers abort\n * (kill the child / exit) instead of buffering toward OOM. See #9 in\n * backend/docs/SCRIPT-FLOW-SCALING-ISSUES.md.\n */\nexport class LineOverflowError extends Error {\n\tconstructor(public readonly buffered: number, public readonly limit: number) {\n\t\tsuper(`IPC message exceeded ${limit} bytes without a newline (${buffered} buffered) — aborting to avoid unbounded memory.`);\n\t\tthis.name = 'LineOverflowError';\n\t}\n}\n\n/** Default per-message ceiling — generous enough for large result sets, low\n * enough to catch a runaway/infinite writer. Override via SCRIPT_MAX_IPC_BYTES. */\nconst DEFAULT_MAX_IPC_BYTES = (() => {\n\tconst envVal = Number(process.env.SCRIPT_MAX_IPC_BYTES);\n\treturn Number.isFinite(envVal) && envVal > 0 ? Math.floor(envVal) : 64 * 1024 * 1024;\n})();\n\n/**\n * Split a stream of data chunks on newlines and emit each complete JSON line.\n * Partial lines are buffered until the next chunk arrives, up to `maxBytes` —\n * past which `push` throws `LineOverflowError` rather than grow unbounded.\n */\nexport class LineSplitter {\n\tprivate buffer = '';\n\n\tconstructor(private readonly maxBytes: number = DEFAULT_MAX_IPC_BYTES) {}\n\n\tpush(chunk: string): string[] {\n\t\tthis.buffer += chunk;\n\t\tconst lines: string[] = [];\n\t\tlet idx: number;\n\t\twhile ((idx = this.buffer.indexOf('\\n')) !== -1) {\n\t\t\tconst line = this.buffer.slice(0, idx);\n\t\t\tthis.buffer = this.buffer.slice(idx + 1);\n\t\t\tif (line.length > 0) lines.push(line);\n\t\t}\n\t\t// A partial line past the ceiling means no newline is coming (or the\n\t\t// message is pathologically large). Drop the buffer and signal abort.\n\t\tif (this.buffer.length > this.maxBytes) {\n\t\t\tconst buffered = this.buffer.length;\n\t\t\tthis.buffer = '';\n\t\t\tthrow new LineOverflowError(buffered, this.maxBytes);\n\t\t}\n\t\treturn lines;\n\t}\n\n\t/** Flush any remaining partial data (useful on stream close) */\n\tflush(): string | null {\n\t\tif (this.buffer.length === 0) return null;\n\t\tconst rest = this.buffer;\n\t\tthis.buffer = '';\n\t\treturn rest;\n\t}\n}\n\n/**\n * Generate a short correlation id for in-flight queries.\n * Collision within a single execution is astronomically unlikely.\n */\nexport function shortId(): string {\n\treturn Math.random().toString(36).slice(2, 10);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/userResponse/scripts/script-bootstrap.ts","../../../src/userResponse/scripts/script-ipc.ts"],"sourcesContent":["/**\n * script-bootstrap.ts — Child-side entrypoint for subprocess script execution.\n *\n * Runs as a separate process spawned by ScriptRunner via tsx. Reads the\n * target user-script path from argv[2], listens for INIT on stdin, builds\n * the ctx object, dynamic-imports the user script's `getData` export,\n * and reports the result (or error) back via stdout.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\nimport { pathToFileURL } from 'url';\nimport {\n\tencodeMessage,\n\tLineSplitter,\n\ttype ChildToParentMessage,\n\ttype ParentToChildMessage,\n\ttype ExecutedQuery,\n} from './script-ipc';\n\nconst SCRIPT_ARG = process.argv[2];\nif (!SCRIPT_ARG) {\n\tsendError('Bootstrap was invoked without a script path.', 'compile');\n\tprocess.exit(1);\n}\n\nconst BRIDGE_URL = process.env.SCRIPT_BRIDGE_URL;\n\n/** Completed queries — reported to the parent as part of RESULT */\nconst executedQueries: ExecutedQuery[] = [];\n\n// Stream-preview limits — kept in sync with the SDK's STREAM_PREVIEW_* constants.\n// Inlined (not imported) so this child-process bundle stays minimal.\nconst PREVIEW_MAX_ROWS = 10;\nconst PREVIEW_MAX_CHARS = 200;\n\n/** Bounded, char-truncated preview of result rows for the live <DataTable> stream. */\nfunction previewRows(data: any[]): any[] {\n\treturn data.slice(0, PREVIEW_MAX_ROWS).map((row) => {\n\t\tif (!row || typeof row !== 'object') return row;\n\t\tconst out: Record<string, any> = {};\n\t\tfor (const [k, v] of Object.entries(row)) {\n\t\t\tout[k] = (typeof v === 'string' && v.length > PREVIEW_MAX_CHARS)\n\t\t\t\t? v.slice(0, PREVIEW_MAX_CHARS) + '…'\n\t\t\t\t: v;\n\t\t}\n\t\treturn out;\n\t});\n}\n\n/** Resolvers for the INIT handshake — getData waits for this */\nlet initResolve: ((init: { params: Record<string, any>; now: Date }) => void) | null = null;\nconst initPromise = new Promise<{ params: Record<string, any>; now: Date }>((resolve) => {\n\tinitResolve = resolve;\n});\n\n/**\n * Source name lookup is not available in the child (the parent holds the\n * ExternalTool list). We use toolId as sourceName and let the parent fill\n * it in when building the final ScriptResult, if needed.\n */\nfunction buildCtx(init: { params: Record<string, any>; now: Date }) {\n\treturn {\n\t\tnow: init.now,\n\n\t\tasync query(toolId: string, sql: string): Promise<{ data: any[]; count: number }> {\n\t\t\tif (!BRIDGE_URL) throw new Error('SCRIPT_BRIDGE_URL is not set — script bridge server is not running');\n\n\t\t\tconst startedAt = Date.now();\n\n\t\t\tsend({ type: 'stream', chunk: `\\n📝 **Querying ${toolId}:**\\n\\`\\`\\`sql\\n${sql}\\n\\`\\`\\`\\n\\n` });\n\t\t\tsend({ type: 'stream', chunk: `__QUERY_TIMER_START_Executing query__` });\n\n\t\t\tconst res = await fetch(`${BRIDGE_URL}/query`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ toolId, sql }),\n\t\t\t});\n\n\t\t\tconst json = await res.json() as any;\n\t\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\t\tsend({ type: 'stream', chunk: `__QUERY_TIMER_DONE_${(executionTimeMs / 1000).toFixed(1)}__\\n\\n` });\n\n\t\t\tif (!res.ok || !json.success) {\n\t\t\t\tconst errMsg = json.error || `Bridge query failed (${res.status})`;\n\t\t\t\tsend({ type: 'stream', chunk: `❌ **Query failed on ${toolId}:** ${errMsg}\\n\\n` });\n\t\t\t\tthrow new Error(errMsg);\n\t\t\t}\n\n\t\t\tconst data: any[] = json.data ?? [];\n\t\t\tconst count: number = json.count ?? data.length;\n\n\t\t\tsend({ type: 'stream', chunk: `✅ **${count} rows from ${toolId}**\\n\\n` });\n\t\t\tif (data.length > 0) {\n\t\t\t\tsend({ type: 'stream', chunk: `<DataTable>${JSON.stringify(previewRows(data))}</DataTable>\\n\\n` });\n\t\t\t}\n\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: toolId,\n\t\t\t\tsourceName: toolId, // enriched by runner after result arrives\n\t\t\t\tsql,\n\t\t\t\tdata,\n\t\t\t\tcount,\n\t\t\t\texecutionTimeMs,\n\t\t\t});\n\n\t\t\treturn { data, count };\n\t\t},\n\n\t\t/**\n\t\t * Call a tool with structured params (typically a direct tool whose\n\t\t * fn(params) does not expect a SQL string). Returns whatever the tool\n\t\t * returned — shape is up to the tool.\n\t\t *\n\t\t * Use this when you need a pre-built business-logic tool (e.g.,\n\t\t * \"Calculate Shelf Life\") that takes structured input rather than SQL.\n\t\t * For SQL source tools, keep using ctx.query.\n\t\t *\n\t\t * runTool results do NOT auto-flow into executedQueries. If you want\n\t\t * the result visualised, register it via ctx.emit() after extracting\n\t\t * the array you want to chart.\n\t\t */\n\t\tasync runTool<T = any>(toolId: string, params: Record<string, any> = {}): Promise<T> {\n\t\t\tif (!BRIDGE_URL) throw new Error('SCRIPT_BRIDGE_URL is not set — script bridge server is not running');\n\n\t\t\tconst res = await fetch(`${BRIDGE_URL}/run-tool`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ toolId, params }),\n\t\t\t});\n\n\t\t\tconst json = await res.json() as any;\n\t\t\tif (!res.ok || !json.success) throw new Error(json.error || `Bridge run-tool failed (${res.status})`);\n\n\t\t\treturn json.result as T;\n\t\t},\n\n\t\t/**\n\t\t * Register a computed dataset. Flows into executedQueries alongside\n\t\t * real SQL query results so component generation can render post-SQL\n\t\t * transforms (weighted averages, clustering, joins) as charts.\n\t\t */\n\t\temit(name: string, rows: any[], meta?: { description?: string }): void {\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: `computed:${name}`,\n\t\t\t\tsourceName: meta?.description || name,\n\t\t\t\tsql: `-- computed dataset: ${name}`,\n\t\t\t\tdata: Array.isArray(rows) ? rows : [],\n\t\t\t\tcount: Array.isArray(rows) ? rows.length : 0,\n\t\t\t\texecutionTimeMs: 0,\n\t\t\t\tvirtual: true,\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Forward a short progress message to the parent's StreamBuffer so the UI\n\t\t * continues to see per-script feedback during execution.\n\t\t */\n\t\tlog(chunk: string): void {\n\t\t\tsend({ type: 'stream', chunk });\n\t\t},\n\t};\n}\n\nfunction send(msg: ChildToParentMessage): void {\n\tprocess.stdout.write(encodeMessage(msg));\n}\n\nfunction sendError(message: string, phase: 'compile' | 'runtime', stack?: string): void {\n\tsend({ type: 'error', message, stack, phase });\n}\n\n// ============================================\n// stdin reader — parse NDJSON messages from the parent (INIT only)\n// ============================================\n\nconst splitter = new LineSplitter();\n\nprocess.stdin.setEncoding('utf-8');\nprocess.stdin.on('data', (chunk: string) => {\n\tlet lines: string[];\n\ttry {\n\t\tlines = splitter.push(chunk);\n\t} catch (err) {\n\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\tsendError(msg, 'runtime');\n\t\tprocess.exit(1);\n\t}\n\tfor (const line of lines) {\n\t\tlet msg: ParentToChildMessage;\n\t\ttry {\n\t\t\tmsg = JSON.parse(line);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\thandleMessage(msg);\n\t}\n});\n\nfunction handleMessage(msg: ParentToChildMessage): void {\n\tswitch (msg.type) {\n\t\tcase 'init': {\n\t\t\tif (initResolve) {\n\t\t\t\tconst now = new Date(msg.now || new Date().toISOString());\n\t\t\t\tconst params = restoreParamTypes(msg.params || {}, msg.paramSchema);\n\t\t\t\tinitResolve({ params, now });\n\t\t\t\tinitResolve = null;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n// ============================================\n// Main — import the user script and run getData\n// ============================================\n\n(async () => {\n\tlet getData: (ctx: any, params: any) => Promise<any>;\n\n\ttry {\n\t\t// Dynamic import — tsx transpiles the user .ts file on the fly\n\t\tconst mod = await import(pathToFileURL(SCRIPT_ARG).href);\n\t\tgetData = mod.getData ?? (typeof mod.default === 'function' ? mod.default : mod.default?.getData);\n\t\tif (typeof getData !== 'function') {\n\t\t\tthrow new Error(\n\t\t\t\t`Script at ${SCRIPT_ARG} does not export getData — expected \\`export async function getData(ctx, params)\\``,\n\t\t\t);\n\t\t}\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'compile', e.stack);\n\t\tprocess.exit(1);\n\t\treturn;\n\t}\n\n\tconst init = await initPromise;\n\tconst ctx = buildCtx(init);\n\tconst startedAt = Date.now();\n\n\ttry {\n\t\tconst raw = await getData(ctx, init.params);\n\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\tconst data = normalizeResultData(raw);\n\t\tconst count = typeof raw?.count === 'number' ? raw.count : data.length;\n\t\tconst sampleRows = data.slice(0, 5);\n\n\t\tconst result: ChildToParentMessage = {\n\t\t\ttype: 'result',\n\t\t\tdata,\n\t\t\tcount,\n\t\t\texecutedQueries,\n\t\t\texecutionTimeMs,\n\t\t\tsampleRows,\n\t\t};\n\t\tsend(result);\n\t\t// Give stdout a tick to flush before we exit\n\t\tprocess.stdout.once('drain', () => process.exit(0));\n\t\tsetImmediate(() => process.exit(0));\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'runtime', e.stack);\n\t\tprocess.exit(1);\n\t}\n})();\n\n/**\n * Normalize whatever getData returned into an array. Mirrors the old\n * ScriptRunner semantics: direct array, { data: [] }, or { data: {...} }\n * wrapped as single-element array.\n */\nfunction normalizeResultData(raw: any): any[] {\n\tif (!raw) return [];\n\tif (Array.isArray(raw)) return raw;\n\tif (Array.isArray(raw.data)) return raw.data;\n\tif (raw.data && typeof raw.data === 'object') return [raw.data];\n\treturn [];\n}\n\n/**\n * Restore typed values that JSON stripped during IPC transport. Date and\n * date_range params arrive as ISO strings; re-wrap them so scripts can call\n * `.toISOString()` and other Date methods the same way they would in-process.\n */\nfunction restoreParamTypes(\n\tparams: Record<string, any>,\n\tschema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>,\n): Record<string, any> {\n\tif (!schema) return params;\n\tconst out = { ...params };\n\tfor (const [name, type] of Object.entries(schema)) {\n\t\tconst v = out[name];\n\t\tif (v === undefined || v === null) continue;\n\t\tif (type === 'date' && typeof v === 'string') {\n\t\t\tconst d = new Date(v);\n\t\t\tif (!isNaN(d.getTime())) out[name] = d;\n\t\t} else if (type === 'date_range' && typeof v === 'object') {\n\t\t\tconst from = v.from;\n\t\t\tconst to = v.to;\n\t\t\tout[name] = {\n\t\t\t\tfrom: typeof from === 'string' ? new Date(from) : from,\n\t\t\t\tto: typeof to === 'string' ? new Date(to) : to,\n\t\t\t};\n\t\t}\n\t}\n\treturn out;\n}\n","/**\n * script-ipc.ts — Protocol definitions for parent/child IPC.\n *\n * Transport: newline-delimited JSON over the child's stdin/stdout.\n * stderr is passed through for uncaught exceptions and compile errors.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\n// ============================================\n// Parent → Child\n// ============================================\n\nexport interface InitMessage {\n\ttype: 'init';\n\t/**\n\t * Params passed to getData(). Date/date_range values cross the IPC boundary\n\t * as ISO strings and are restored to Date objects in the child via paramSchema.\n\t */\n\tparams: Record<string, any>;\n\t/** Frozen timestamp for ctx.now — ISO string */\n\tnow: string;\n\t/**\n\t * Maps each parameter name to its declared type so the child can restore\n\t * Date objects (lost during JSON serialization).\n\t */\n\tparamSchema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>;\n}\n\nexport interface QueryResultMessage {\n\ttype: 'query_result';\n\t/** Matches the `id` from the child's QUERY message */\n\tid: string;\n\tdata: any[];\n\tcount: number;\n\tmetadata?: {\n\t\ttotalCount?: number;\n\t\texecutionTimeMs?: number;\n\t};\n}\n\nexport interface QueryErrorMessage {\n\ttype: 'query_error';\n\tid: string;\n\terror: string;\n}\n\n/**\n * Response to a `run_tool` request — carries whatever the direct-tool's fn()\n * returned. Shape is arbitrary; the script is expected to know what its chosen\n * tool yields and unwrap accordingly.\n */\nexport interface RunToolResultMessage {\n\ttype: 'run_tool_result';\n\tid: string;\n\tresult: any;\n}\n\nexport type ParentToChildMessage =\n\t| InitMessage\n\t| QueryResultMessage\n\t| QueryErrorMessage\n\t| RunToolResultMessage;\n\n// ============================================\n// Child → Parent\n// ============================================\n\nexport interface QueryMessage {\n\ttype: 'query';\n\t/** Short uuid — parent uses this to correlate its response */\n\tid: string;\n\ttoolId: string;\n\tsql: string;\n}\n\n/**\n * Run an arbitrary tool with structured params. Used by ctx.runTool() to call\n * direct tools (and any tool whose `fn(params)` doesn't expect a SQL string).\n * Reply is RunToolResultMessage or QueryErrorMessage.\n */\nexport interface RunToolMessage {\n\ttype: 'run_tool';\n\tid: string;\n\ttoolId: string;\n\tparams: Record<string, any>;\n}\n\nexport interface StreamMessage {\n\ttype: 'stream';\n\tchunk: string;\n}\n\nexport interface ExecutedQuery {\n\tsourceId: string;\n\tsourceName: string;\n\tsql: string;\n\tdata: any[];\n\tcount: number;\n\ttotalCount?: number;\n\texecutionTimeMs: number;\n\t/**\n\t * When the script registered a computed dataset via ctx.emit(), this is\n\t * marked virtual so component generation can distinguish it from a real SQL query.\n\t */\n\tvirtual?: boolean;\n}\n\nexport interface ResultMessage {\n\ttype: 'result';\n\tdata: any[];\n\tcount: number;\n\texecutedQueries: ExecutedQuery[];\n\texecutionTimeMs: number;\n\t/** First 5 rows of the final `data` — lets the parent judge intent-match cheaply */\n\tsampleRows: any[];\n}\n\nexport interface ErrorMessage {\n\ttype: 'error';\n\tmessage: string;\n\tstack?: string;\n\t/** \"compile\" → tsx couldn't parse; \"runtime\" → thrown during getData */\n\tphase?: 'compile' | 'runtime';\n}\n\nexport type ChildToParentMessage =\n\t| QueryMessage\n\t| RunToolMessage\n\t| StreamMessage\n\t| ResultMessage\n\t| ErrorMessage;\n\n// ============================================\n// Utilities\n// ============================================\n\n/**\n * Encode a message for transport: JSON + newline.\n */\nexport function encodeMessage(msg: ParentToChildMessage | ChildToParentMessage): string {\n\treturn JSON.stringify(msg) + '\\n';\n}\n\n/**\n * Thrown when a single NDJSON line grows past the byte ceiling without a\n * newline — a runaway writer or a pathologically large message. Callers abort\n * (kill the child / exit) instead of buffering toward OOM. See #9 in\n * backend/docs/SCRIPT-FLOW-SCALING-ISSUES.md.\n */\nexport class LineOverflowError extends Error {\n\tconstructor(public readonly buffered: number, public readonly limit: number) {\n\t\tsuper(`IPC message exceeded ${limit} bytes without a newline (${buffered} buffered) — aborting to avoid unbounded memory.`);\n\t\tthis.name = 'LineOverflowError';\n\t}\n}\n\n/** Default per-message ceiling — generous enough for large result sets, low\n * enough to catch a runaway/infinite writer. Override via SCRIPT_MAX_IPC_BYTES. */\nconst DEFAULT_MAX_IPC_BYTES = (() => {\n\tconst envVal = Number(process.env.SCRIPT_MAX_IPC_BYTES);\n\treturn Number.isFinite(envVal) && envVal > 0 ? Math.floor(envVal) : 64 * 1024 * 1024;\n})();\n\n/**\n * Split a stream of data chunks on newlines and emit each complete JSON line.\n * Partial lines are buffered until the next chunk arrives, up to `maxBytes` —\n * past which `push` throws `LineOverflowError` rather than grow unbounded.\n */\nexport class LineSplitter {\n\tprivate buffer = '';\n\n\tconstructor(private readonly maxBytes: number = DEFAULT_MAX_IPC_BYTES) {}\n\n\tpush(chunk: string): string[] {\n\t\tthis.buffer += chunk;\n\t\tconst lines: string[] = [];\n\t\tlet idx: number;\n\t\twhile ((idx = this.buffer.indexOf('\\n')) !== -1) {\n\t\t\tconst line = this.buffer.slice(0, idx);\n\t\t\tthis.buffer = this.buffer.slice(idx + 1);\n\t\t\tif (line.length > 0) lines.push(line);\n\t\t}\n\t\t// A partial line past the ceiling means no newline is coming (or the\n\t\t// message is pathologically large). Drop the buffer and signal abort.\n\t\tif (this.buffer.length > this.maxBytes) {\n\t\t\tconst buffered = this.buffer.length;\n\t\t\tthis.buffer = '';\n\t\t\tthrow new LineOverflowError(buffered, this.maxBytes);\n\t\t}\n\t\treturn lines;\n\t}\n\n\t/** Flush any remaining partial data (useful on stream close) */\n\tflush(): string | null {\n\t\tif (this.buffer.length === 0) return null;\n\t\tconst rest = this.buffer;\n\t\tthis.buffer = '';\n\t\treturn rest;\n\t}\n}\n\n/**\n * Generate a short correlation id for in-flight queries.\n * Collision within a single execution is astronomically unlikely.\n */\nexport function shortId(): string {\n\treturn Math.random().toString(36).slice(2, 10);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -46,9 +46,6 @@ var LineSplitter = class {
|
|
|
46
46
|
return rest;
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
|
-
function shortId() {
|
|
50
|
-
return Math.random().toString(36).slice(2, 10);
|
|
51
|
-
}
|
|
52
49
|
|
|
53
50
|
// src/userResponse/scripts/script-bootstrap.ts
|
|
54
51
|
var SCRIPT_ARG = process.argv[2];
|
|
@@ -56,8 +53,20 @@ if (!SCRIPT_ARG) {
|
|
|
56
53
|
sendError("Bootstrap was invoked without a script path.", "compile");
|
|
57
54
|
process.exit(1);
|
|
58
55
|
}
|
|
59
|
-
var
|
|
56
|
+
var BRIDGE_URL = process.env.SCRIPT_BRIDGE_URL;
|
|
60
57
|
var executedQueries = [];
|
|
58
|
+
var PREVIEW_MAX_ROWS = 10;
|
|
59
|
+
var PREVIEW_MAX_CHARS = 200;
|
|
60
|
+
function previewRows(data) {
|
|
61
|
+
return data.slice(0, PREVIEW_MAX_ROWS).map((row) => {
|
|
62
|
+
if (!row || typeof row !== "object") return row;
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [k, v] of Object.entries(row)) {
|
|
65
|
+
out[k] = typeof v === "string" && v.length > PREVIEW_MAX_CHARS ? v.slice(0, PREVIEW_MAX_CHARS) + "\u2026" : v;
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
61
70
|
var initResolve = null;
|
|
62
71
|
var initPromise = new Promise((resolve) => {
|
|
63
72
|
initResolve = resolve;
|
|
@@ -66,31 +75,58 @@ function buildCtx(init) {
|
|
|
66
75
|
return {
|
|
67
76
|
now: init.now,
|
|
68
77
|
async query(toolId, sql) {
|
|
69
|
-
|
|
78
|
+
if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
|
|
70
79
|
const startedAt = Date.now();
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
send({ type: "stream", chunk: `
|
|
81
|
+
\u{1F4DD} **Querying ${toolId}:**
|
|
82
|
+
\`\`\`sql
|
|
83
|
+
${sql}
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
` });
|
|
87
|
+
send({ type: "stream", chunk: `__QUERY_TIMER_START_Executing query__` });
|
|
88
|
+
const res = await fetch(`${BRIDGE_URL}/query`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "content-type": "application/json" },
|
|
91
|
+
body: JSON.stringify({ toolId, sql })
|
|
73
92
|
});
|
|
74
|
-
|
|
75
|
-
const result = await promise;
|
|
93
|
+
const json = await res.json();
|
|
76
94
|
const executionTimeMs = Date.now() - startedAt;
|
|
95
|
+
send({ type: "stream", chunk: `__QUERY_TIMER_DONE_${(executionTimeMs / 1e3).toFixed(1)}__
|
|
96
|
+
|
|
97
|
+
` });
|
|
98
|
+
if (!res.ok || !json.success) {
|
|
99
|
+
const errMsg = json.error || `Bridge query failed (${res.status})`;
|
|
100
|
+
send({ type: "stream", chunk: `\u274C **Query failed on ${toolId}:** ${errMsg}
|
|
101
|
+
|
|
102
|
+
` });
|
|
103
|
+
throw new Error(errMsg);
|
|
104
|
+
}
|
|
105
|
+
const data = json.data ?? [];
|
|
106
|
+
const count = json.count ?? data.length;
|
|
107
|
+
send({ type: "stream", chunk: `\u2705 **${count} rows from ${toolId}**
|
|
108
|
+
|
|
109
|
+
` });
|
|
110
|
+
if (data.length > 0) {
|
|
111
|
+
send({ type: "stream", chunk: `<DataTable>${JSON.stringify(previewRows(data))}</DataTable>
|
|
112
|
+
|
|
113
|
+
` });
|
|
114
|
+
}
|
|
77
115
|
executedQueries.push({
|
|
78
116
|
sourceId: toolId,
|
|
79
117
|
sourceName: toolId,
|
|
80
|
-
//
|
|
118
|
+
// enriched by runner after result arrives
|
|
81
119
|
sql,
|
|
82
|
-
data
|
|
83
|
-
count
|
|
84
|
-
totalCount: result.metadata?.totalCount,
|
|
120
|
+
data,
|
|
121
|
+
count,
|
|
85
122
|
executionTimeMs
|
|
86
123
|
});
|
|
87
|
-
return { data
|
|
124
|
+
return { data, count };
|
|
88
125
|
},
|
|
89
126
|
/**
|
|
90
127
|
* Call a tool with structured params (typically a direct tool whose
|
|
91
128
|
* fn(params) does not expect a SQL string). Returns whatever the tool
|
|
92
|
-
* returned — shape is up to the tool.
|
|
93
|
-
* against the authorized externalTools list before executing.
|
|
129
|
+
* returned — shape is up to the tool.
|
|
94
130
|
*
|
|
95
131
|
* Use this when you need a pre-built business-logic tool (e.g.,
|
|
96
132
|
* "Calculate Shelf Life") that takes structured input rather than SQL.
|
|
@@ -101,18 +137,15 @@ function buildCtx(init) {
|
|
|
101
137
|
* the array you want to chart.
|
|
102
138
|
*/
|
|
103
139
|
async runTool(toolId, params = {}) {
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
toolId,
|
|
110
|
-
sql: `-- runTool: ${toolId}`,
|
|
111
|
-
startedAt: Date.now()
|
|
112
|
-
});
|
|
140
|
+
if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
|
|
141
|
+
const res = await fetch(`${BRIDGE_URL}/run-tool`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "content-type": "application/json" },
|
|
144
|
+
body: JSON.stringify({ toolId, params })
|
|
113
145
|
});
|
|
114
|
-
|
|
115
|
-
|
|
146
|
+
const json = await res.json();
|
|
147
|
+
if (!res.ok || !json.success) throw new Error(json.error || `Bridge run-tool failed (${res.status})`);
|
|
148
|
+
return json.result;
|
|
116
149
|
},
|
|
117
150
|
/**
|
|
118
151
|
* Register a computed dataset. Flows into executedQueries alongside
|
|
@@ -145,12 +178,6 @@ function send(msg) {
|
|
|
145
178
|
function sendError(message, phase, stack) {
|
|
146
179
|
send({ type: "error", message, stack, phase });
|
|
147
180
|
}
|
|
148
|
-
function rejectAllPending(reason) {
|
|
149
|
-
for (const [id, entry] of Array.from(pending.entries())) {
|
|
150
|
-
pending.delete(id);
|
|
151
|
-
entry.reject(new Error(reason));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
181
|
var splitter = new LineSplitter();
|
|
155
182
|
process.stdin.setEncoding("utf-8");
|
|
156
183
|
process.stdin.on("data", (chunk) => {
|
|
@@ -159,7 +186,6 @@ process.stdin.on("data", (chunk) => {
|
|
|
159
186
|
lines = splitter.push(chunk);
|
|
160
187
|
} catch (err) {
|
|
161
188
|
const msg = err instanceof Error ? err.message : String(err);
|
|
162
|
-
rejectAllPending(msg);
|
|
163
189
|
sendError(msg, "runtime");
|
|
164
190
|
process.exit(1);
|
|
165
191
|
}
|
|
@@ -173,12 +199,6 @@ process.stdin.on("data", (chunk) => {
|
|
|
173
199
|
handleMessage(msg);
|
|
174
200
|
}
|
|
175
201
|
});
|
|
176
|
-
process.stdin.on("end", () => {
|
|
177
|
-
rejectAllPending("Parent closed the IPC channel (stdin end) before a query completed.");
|
|
178
|
-
});
|
|
179
|
-
process.stdin.on("error", (err) => {
|
|
180
|
-
rejectAllPending(`IPC channel error before a query completed: ${err.message}`);
|
|
181
|
-
});
|
|
182
202
|
function handleMessage(msg) {
|
|
183
203
|
switch (msg.type) {
|
|
184
204
|
case "init": {
|
|
@@ -190,37 +210,13 @@ function handleMessage(msg) {
|
|
|
190
210
|
}
|
|
191
211
|
break;
|
|
192
212
|
}
|
|
193
|
-
case "query_result": {
|
|
194
|
-
const entry = pending.get(msg.id);
|
|
195
|
-
if (entry) {
|
|
196
|
-
pending.delete(msg.id);
|
|
197
|
-
entry.resolve({ data: msg.data, count: msg.count, metadata: msg.metadata });
|
|
198
|
-
}
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
case "run_tool_result": {
|
|
202
|
-
const entry = pending.get(msg.id);
|
|
203
|
-
if (entry) {
|
|
204
|
-
pending.delete(msg.id);
|
|
205
|
-
entry.resolve({ result: msg.result });
|
|
206
|
-
}
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
case "query_error": {
|
|
210
|
-
const entry = pending.get(msg.id);
|
|
211
|
-
if (entry) {
|
|
212
|
-
pending.delete(msg.id);
|
|
213
|
-
entry.reject(new Error(msg.error));
|
|
214
|
-
}
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
213
|
}
|
|
218
214
|
}
|
|
219
215
|
(async () => {
|
|
220
216
|
let getData;
|
|
221
217
|
try {
|
|
222
218
|
const mod = await import(pathToFileURL(SCRIPT_ARG).href);
|
|
223
|
-
getData = mod.getData ?? mod.default;
|
|
219
|
+
getData = mod.getData ?? (typeof mod.default === "function" ? mod.default : mod.default?.getData);
|
|
224
220
|
if (typeof getData !== "function") {
|
|
225
221
|
throw new Error(
|
|
226
222
|
`Script at ${SCRIPT_ARG} does not export getData \u2014 expected \`export async function getData(ctx, params)\``
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/userResponse/scripts/script-bootstrap.ts","../../../src/userResponse/scripts/script-ipc.ts"],"sourcesContent":["/**\n * script-bootstrap.ts — Child-side entrypoint for subprocess script execution.\n *\n * Runs as a separate process spawned by ScriptRunner via tsx. Reads the\n * target user-script path from argv[2], listens for INIT on stdin, builds\n * the ctx object, dynamic-imports the user script's `getData` export,\n * and reports the result (or error) back via stdout.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\nimport { pathToFileURL } from 'url';\nimport {\n\tencodeMessage,\n\tLineSplitter,\n\tshortId,\n\ttype ChildToParentMessage,\n\ttype ParentToChildMessage,\n\ttype ExecutedQuery,\n\ttype QueryResultMessage,\n\ttype QueryErrorMessage,\n} from './script-ipc';\n\nconst SCRIPT_ARG = process.argv[2];\nif (!SCRIPT_ARG) {\n\tsendError('Bootstrap was invoked without a script path.', 'compile');\n\tprocess.exit(1);\n}\n\n/**\n * Pending queries await a QUERY_RESULT or QUERY_ERROR from the parent.\n * Keyed by the short uuid we generated when sending the QUERY message.\n */\n/**\n * One pending entry per in-flight ctx.query or ctx.runTool. The resolve\n * payload differs by call kind ({data, count, metadata} for query,\n * {result} for runTool) — each caller's resolve handler unwraps the shape\n * it expects, so the map uses `any` rather than a discriminated union to\n * keep the call sites simple.\n */\nconst pending = new Map<string, {\n\tresolve: (value: any) => void;\n\treject: (err: Error) => void;\n\ttoolId: string;\n\tsql: string;\n\tstartedAt: number;\n}>();\n\n/** Completed queries — reported to the parent as part of RESULT */\nconst executedQueries: ExecutedQuery[] = [];\n\n/** Resolvers for the INIT handshake — getData waits for this */\nlet initResolve: ((init: { params: Record<string, any>; now: Date }) => void) | null = null;\nconst initPromise = new Promise<{ params: Record<string, any>; now: Date }>((resolve) => {\n\tinitResolve = resolve;\n});\n\n/**\n * Source name lookup is not available in the child (the parent holds the\n * ExternalTool list). We use toolId as sourceName and let the parent fill\n * it in when building the final ScriptResult, if needed.\n */\nfunction buildCtx(init: { params: Record<string, any>; now: Date }) {\n\treturn {\n\t\tnow: init.now,\n\n\t\tasync query(toolId: string, sql: string): Promise<{ data: any[]; count: number }> {\n\t\t\tconst id = shortId();\n\t\t\tconst startedAt = Date.now();\n\t\t\tconst promise = new Promise<{ data: any[]; count: number; metadata?: any }>((resolve, reject) => {\n\t\t\t\tpending.set(id, { resolve, reject, toolId, sql, startedAt });\n\t\t\t});\n\n\t\t\tsend({ type: 'query', id, toolId, sql });\n\n\t\t\tconst result = await promise;\n\t\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: toolId,\n\t\t\t\tsourceName: toolId, // parent overwrites with the real name\n\t\t\t\tsql,\n\t\t\t\tdata: result.data,\n\t\t\t\tcount: result.count,\n\t\t\t\ttotalCount: result.metadata?.totalCount,\n\t\t\t\texecutionTimeMs,\n\t\t\t});\n\n\t\t\treturn { data: result.data, count: result.count };\n\t\t},\n\n\t\t/**\n\t\t * Call a tool with structured params (typically a direct tool whose\n\t\t * fn(params) does not expect a SQL string). Returns whatever the tool\n\t\t * returned — shape is up to the tool. The parent verifies toolId\n\t\t * against the authorized externalTools list before executing.\n\t\t *\n\t\t * Use this when you need a pre-built business-logic tool (e.g.,\n\t\t * \"Calculate Shelf Life\") that takes structured input rather than SQL.\n\t\t * For SQL source tools, keep using ctx.query.\n\t\t *\n\t\t * runTool results do NOT auto-flow into executedQueries. If you want\n\t\t * the result visualised, register it via ctx.emit() after extracting\n\t\t * the array you want to chart.\n\t\t */\n\t\tasync runTool<T = any>(toolId: string, params: Record<string, any> = {}): Promise<T> {\n\t\t\tconst id = shortId();\n\t\t\tconst promise = new Promise<any>((resolveResult, rejectResult) => {\n\t\t\t\tpending.set(id, {\n\t\t\t\t\tresolve: (payload: any) => resolveResult(payload?.result ?? payload),\n\t\t\t\t\treject: rejectResult,\n\t\t\t\t\ttoolId,\n\t\t\t\t\tsql: `-- runTool: ${toolId}`,\n\t\t\t\t\tstartedAt: Date.now(),\n\t\t\t\t});\n\t\t\t});\n\t\t\tsend({ type: 'run_tool', id, toolId, params });\n\t\t\treturn promise;\n\t\t},\n\n\t\t/**\n\t\t * Register a computed dataset. Flows into executedQueries alongside\n\t\t * real SQL query results so component generation can render post-SQL\n\t\t * transforms (weighted averages, clustering, joins) as charts.\n\t\t */\n\t\temit(name: string, rows: any[], meta?: { description?: string }): void {\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: `computed:${name}`,\n\t\t\t\tsourceName: meta?.description || name,\n\t\t\t\tsql: `-- computed dataset: ${name}`,\n\t\t\t\tdata: Array.isArray(rows) ? rows : [],\n\t\t\t\tcount: Array.isArray(rows) ? rows.length : 0,\n\t\t\t\texecutionTimeMs: 0,\n\t\t\t\tvirtual: true,\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Forward a short progress message to the parent's StreamBuffer so the UI\n\t\t * continues to see per-script feedback during execution.\n\t\t */\n\t\tlog(chunk: string): void {\n\t\t\tsend({ type: 'stream', chunk });\n\t\t},\n\t};\n}\n\nfunction send(msg: ChildToParentMessage): void {\n\tprocess.stdout.write(encodeMessage(msg));\n}\n\nfunction sendError(message: string, phase: 'compile' | 'runtime', stack?: string): void {\n\tsend({ type: 'error', message, stack, phase });\n}\n\n/**\n * Reject every in-flight ctx.query / ctx.runTool with the same reason. Used when\n * the IPC channel goes away (stdin end/error) so a pending query fails fast\n * instead of hanging until the parent's wall-clock SIGKILL. Idempotent — a\n * second call finds the map already drained.\n */\nfunction rejectAllPending(reason: string): void {\n\tfor (const [id, entry] of Array.from(pending.entries())) {\n\t\tpending.delete(id);\n\t\tentry.reject(new Error(reason));\n\t}\n}\n\n// ============================================\n// stdin reader — parse NDJSON messages from the parent\n// ============================================\n\nconst splitter = new LineSplitter();\n\nprocess.stdin.setEncoding('utf-8');\nprocess.stdin.on('data', (chunk: string) => {\n\tlet lines: string[];\n\ttry {\n\t\tlines = splitter.push(chunk);\n\t} catch (err) {\n\t\t// Oversized parent message (#9) — abort cleanly rather than buffer to OOM.\n\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\trejectAllPending(msg);\n\t\tsendError(msg, 'runtime');\n\t\tprocess.exit(1);\n\t}\n\tfor (const line of lines) {\n\t\tlet msg: ParentToChildMessage;\n\t\ttry {\n\t\t\tmsg = JSON.parse(line);\n\t\t} catch {\n\t\t\tcontinue; // ignore malformed input\n\t\t}\n\t\thandleMessage(msg);\n\t}\n});\n\nprocess.stdin.on('end', () => {\n\t// Parent closed our stdin — no further query replies can arrive. Reject any\n\t// in-flight queries now so getData fails fast (and we report ERROR) instead\n\t// of awaiting a reply that will never come, until the parent's wall-clock kill.\n\trejectAllPending('Parent closed the IPC channel (stdin end) before a query completed.');\n});\n\nprocess.stdin.on('error', (err: Error) => {\n\trejectAllPending(`IPC channel error before a query completed: ${err.message}`);\n});\n\nfunction handleMessage(msg: ParentToChildMessage): void {\n\tswitch (msg.type) {\n\t\tcase 'init': {\n\t\t\tif (initResolve) {\n\t\t\t\tconst now = new Date(msg.now || new Date().toISOString());\n\t\t\t\tconst params = restoreParamTypes(msg.params || {}, msg.paramSchema);\n\t\t\t\tinitResolve({ params, now });\n\t\t\t\tinitResolve = null;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'query_result': {\n\t\t\tconst entry = pending.get(msg.id);\n\t\t\tif (entry) {\n\t\t\t\tpending.delete(msg.id);\n\t\t\t\tentry.resolve({ data: msg.data, count: msg.count, metadata: msg.metadata });\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'run_tool_result': {\n\t\t\tconst entry = pending.get(msg.id);\n\t\t\tif (entry) {\n\t\t\t\tpending.delete(msg.id);\n\t\t\t\t// runTool's resolve unwraps payload.result — wrap here so the\n\t\t\t\t// same pending entry shape works for both query and runTool.\n\t\t\t\tentry.resolve({ result: msg.result });\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'query_error': {\n\t\t\tconst entry = pending.get(msg.id);\n\t\t\tif (entry) {\n\t\t\t\tpending.delete(msg.id);\n\t\t\t\tentry.reject(new Error(msg.error));\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n// ============================================\n// Main — import the user script and run getData\n// ============================================\n\n(async () => {\n\tlet getData: (ctx: any, params: any) => Promise<any>;\n\n\ttry {\n\t\t// Dynamic import — tsx transpiles the user .ts file on the fly\n\t\tconst mod = await import(pathToFileURL(SCRIPT_ARG).href);\n\t\tgetData = mod.getData ?? mod.default;\n\t\tif (typeof getData !== 'function') {\n\t\t\tthrow new Error(\n\t\t\t\t`Script at ${SCRIPT_ARG} does not export getData — expected \\`export async function getData(ctx, params)\\``,\n\t\t\t);\n\t\t}\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'compile', e.stack);\n\t\tprocess.exit(1);\n\t\treturn;\n\t}\n\n\tconst init = await initPromise;\n\tconst ctx = buildCtx(init);\n\tconst startedAt = Date.now();\n\n\ttry {\n\t\tconst raw = await getData(ctx, init.params);\n\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\tconst data = normalizeResultData(raw);\n\t\tconst count = typeof raw?.count === 'number' ? raw.count : data.length;\n\t\tconst sampleRows = data.slice(0, 5);\n\n\t\tconst result: ChildToParentMessage = {\n\t\t\ttype: 'result',\n\t\t\tdata,\n\t\t\tcount,\n\t\t\texecutedQueries,\n\t\t\texecutionTimeMs,\n\t\t\tsampleRows,\n\t\t};\n\t\tsend(result);\n\t\t// Give stdout a tick to flush before we exit\n\t\tprocess.stdout.once('drain', () => process.exit(0));\n\t\tsetImmediate(() => process.exit(0));\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'runtime', e.stack);\n\t\tprocess.exit(1);\n\t}\n})();\n\n/**\n * Normalize whatever getData returned into an array. Mirrors the old\n * ScriptRunner semantics: direct array, { data: [] }, or { data: {...} }\n * wrapped as single-element array.\n */\nfunction normalizeResultData(raw: any): any[] {\n\tif (!raw) return [];\n\tif (Array.isArray(raw)) return raw;\n\tif (Array.isArray(raw.data)) return raw.data;\n\tif (raw.data && typeof raw.data === 'object') return [raw.data];\n\treturn [];\n}\n\n/**\n * Restore typed values that JSON stripped during IPC transport. Date and\n * date_range params arrive as ISO strings; re-wrap them so scripts can call\n * `.toISOString()` and other Date methods the same way they would in-process.\n */\nfunction restoreParamTypes(\n\tparams: Record<string, any>,\n\tschema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>,\n): Record<string, any> {\n\tif (!schema) return params;\n\tconst out = { ...params };\n\tfor (const [name, type] of Object.entries(schema)) {\n\t\tconst v = out[name];\n\t\tif (v === undefined || v === null) continue;\n\t\tif (type === 'date' && typeof v === 'string') {\n\t\t\tconst d = new Date(v);\n\t\t\tif (!isNaN(d.getTime())) out[name] = d;\n\t\t} else if (type === 'date_range' && typeof v === 'object') {\n\t\t\tconst from = v.from;\n\t\t\tconst to = v.to;\n\t\t\tout[name] = {\n\t\t\t\tfrom: typeof from === 'string' ? new Date(from) : from,\n\t\t\t\tto: typeof to === 'string' ? new Date(to) : to,\n\t\t\t};\n\t\t}\n\t}\n\treturn out;\n}\n","/**\n * script-ipc.ts — Protocol definitions for parent/child IPC.\n *\n * Transport: newline-delimited JSON over the child's stdin/stdout.\n * stderr is passed through for uncaught exceptions and compile errors.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\n// ============================================\n// Parent → Child\n// ============================================\n\nexport interface InitMessage {\n\ttype: 'init';\n\t/**\n\t * Params passed to getData(). Date/date_range values cross the IPC boundary\n\t * as ISO strings and are restored to Date objects in the child via paramSchema.\n\t */\n\tparams: Record<string, any>;\n\t/** Frozen timestamp for ctx.now — ISO string */\n\tnow: string;\n\t/**\n\t * Maps each parameter name to its declared type so the child can restore\n\t * Date objects (lost during JSON serialization).\n\t */\n\tparamSchema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>;\n}\n\nexport interface QueryResultMessage {\n\ttype: 'query_result';\n\t/** Matches the `id` from the child's QUERY message */\n\tid: string;\n\tdata: any[];\n\tcount: number;\n\tmetadata?: {\n\t\ttotalCount?: number;\n\t\texecutionTimeMs?: number;\n\t};\n}\n\nexport interface QueryErrorMessage {\n\ttype: 'query_error';\n\tid: string;\n\terror: string;\n}\n\n/**\n * Response to a `run_tool` request — carries whatever the direct-tool's fn()\n * returned. Shape is arbitrary; the script is expected to know what its chosen\n * tool yields and unwrap accordingly.\n */\nexport interface RunToolResultMessage {\n\ttype: 'run_tool_result';\n\tid: string;\n\tresult: any;\n}\n\nexport type ParentToChildMessage =\n\t| InitMessage\n\t| QueryResultMessage\n\t| QueryErrorMessage\n\t| RunToolResultMessage;\n\n// ============================================\n// Child → Parent\n// ============================================\n\nexport interface QueryMessage {\n\ttype: 'query';\n\t/** Short uuid — parent uses this to correlate its response */\n\tid: string;\n\ttoolId: string;\n\tsql: string;\n}\n\n/**\n * Run an arbitrary tool with structured params. Used by ctx.runTool() to call\n * direct tools (and any tool whose `fn(params)` doesn't expect a SQL string).\n * Reply is RunToolResultMessage or QueryErrorMessage.\n */\nexport interface RunToolMessage {\n\ttype: 'run_tool';\n\tid: string;\n\ttoolId: string;\n\tparams: Record<string, any>;\n}\n\nexport interface StreamMessage {\n\ttype: 'stream';\n\tchunk: string;\n}\n\nexport interface ExecutedQuery {\n\tsourceId: string;\n\tsourceName: string;\n\tsql: string;\n\tdata: any[];\n\tcount: number;\n\ttotalCount?: number;\n\texecutionTimeMs: number;\n\t/**\n\t * When the script registered a computed dataset via ctx.emit(), this is\n\t * marked virtual so component generation can distinguish it from a real SQL query.\n\t */\n\tvirtual?: boolean;\n}\n\nexport interface ResultMessage {\n\ttype: 'result';\n\tdata: any[];\n\tcount: number;\n\texecutedQueries: ExecutedQuery[];\n\texecutionTimeMs: number;\n\t/** First 5 rows of the final `data` — lets the parent judge intent-match cheaply */\n\tsampleRows: any[];\n}\n\nexport interface ErrorMessage {\n\ttype: 'error';\n\tmessage: string;\n\tstack?: string;\n\t/** \"compile\" → tsx couldn't parse; \"runtime\" → thrown during getData */\n\tphase?: 'compile' | 'runtime';\n}\n\nexport type ChildToParentMessage =\n\t| QueryMessage\n\t| RunToolMessage\n\t| StreamMessage\n\t| ResultMessage\n\t| ErrorMessage;\n\n// ============================================\n// Utilities\n// ============================================\n\n/**\n * Encode a message for transport: JSON + newline.\n */\nexport function encodeMessage(msg: ParentToChildMessage | ChildToParentMessage): string {\n\treturn JSON.stringify(msg) + '\\n';\n}\n\n/**\n * Thrown when a single NDJSON line grows past the byte ceiling without a\n * newline — a runaway writer or a pathologically large message. Callers abort\n * (kill the child / exit) instead of buffering toward OOM. See #9 in\n * backend/docs/SCRIPT-FLOW-SCALING-ISSUES.md.\n */\nexport class LineOverflowError extends Error {\n\tconstructor(public readonly buffered: number, public readonly limit: number) {\n\t\tsuper(`IPC message exceeded ${limit} bytes without a newline (${buffered} buffered) — aborting to avoid unbounded memory.`);\n\t\tthis.name = 'LineOverflowError';\n\t}\n}\n\n/** Default per-message ceiling — generous enough for large result sets, low\n * enough to catch a runaway/infinite writer. Override via SCRIPT_MAX_IPC_BYTES. */\nconst DEFAULT_MAX_IPC_BYTES = (() => {\n\tconst envVal = Number(process.env.SCRIPT_MAX_IPC_BYTES);\n\treturn Number.isFinite(envVal) && envVal > 0 ? Math.floor(envVal) : 64 * 1024 * 1024;\n})();\n\n/**\n * Split a stream of data chunks on newlines and emit each complete JSON line.\n * Partial lines are buffered until the next chunk arrives, up to `maxBytes` —\n * past which `push` throws `LineOverflowError` rather than grow unbounded.\n */\nexport class LineSplitter {\n\tprivate buffer = '';\n\n\tconstructor(private readonly maxBytes: number = DEFAULT_MAX_IPC_BYTES) {}\n\n\tpush(chunk: string): string[] {\n\t\tthis.buffer += chunk;\n\t\tconst lines: string[] = [];\n\t\tlet idx: number;\n\t\twhile ((idx = this.buffer.indexOf('\\n')) !== -1) {\n\t\t\tconst line = this.buffer.slice(0, idx);\n\t\t\tthis.buffer = this.buffer.slice(idx + 1);\n\t\t\tif (line.length > 0) lines.push(line);\n\t\t}\n\t\t// A partial line past the ceiling means no newline is coming (or the\n\t\t// message is pathologically large). Drop the buffer and signal abort.\n\t\tif (this.buffer.length > this.maxBytes) {\n\t\t\tconst buffered = this.buffer.length;\n\t\t\tthis.buffer = '';\n\t\t\tthrow new LineOverflowError(buffered, this.maxBytes);\n\t\t}\n\t\treturn lines;\n\t}\n\n\t/** Flush any remaining partial data (useful on stream close) */\n\tflush(): string | null {\n\t\tif (this.buffer.length === 0) return null;\n\t\tconst rest = this.buffer;\n\t\tthis.buffer = '';\n\t\treturn rest;\n\t}\n}\n\n/**\n * Generate a short correlation id for in-flight queries.\n * Collision within a single execution is astronomically unlikely.\n */\nexport function shortId(): string {\n\treturn Math.random().toString(36).slice(2, 10);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/userResponse/scripts/script-bootstrap.ts","../../../src/userResponse/scripts/script-ipc.ts"],"sourcesContent":["/**\n * script-bootstrap.ts — Child-side entrypoint for subprocess script execution.\n *\n * Runs as a separate process spawned by ScriptRunner via tsx. Reads the\n * target user-script path from argv[2], listens for INIT on stdin, builds\n * the ctx object, dynamic-imports the user script's `getData` export,\n * and reports the result (or error) back via stdout.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\nimport { pathToFileURL } from 'url';\nimport {\n\tencodeMessage,\n\tLineSplitter,\n\ttype ChildToParentMessage,\n\ttype ParentToChildMessage,\n\ttype ExecutedQuery,\n} from './script-ipc';\n\nconst SCRIPT_ARG = process.argv[2];\nif (!SCRIPT_ARG) {\n\tsendError('Bootstrap was invoked without a script path.', 'compile');\n\tprocess.exit(1);\n}\n\nconst BRIDGE_URL = process.env.SCRIPT_BRIDGE_URL;\n\n/** Completed queries — reported to the parent as part of RESULT */\nconst executedQueries: ExecutedQuery[] = [];\n\n// Stream-preview limits — kept in sync with the SDK's STREAM_PREVIEW_* constants.\n// Inlined (not imported) so this child-process bundle stays minimal.\nconst PREVIEW_MAX_ROWS = 10;\nconst PREVIEW_MAX_CHARS = 200;\n\n/** Bounded, char-truncated preview of result rows for the live <DataTable> stream. */\nfunction previewRows(data: any[]): any[] {\n\treturn data.slice(0, PREVIEW_MAX_ROWS).map((row) => {\n\t\tif (!row || typeof row !== 'object') return row;\n\t\tconst out: Record<string, any> = {};\n\t\tfor (const [k, v] of Object.entries(row)) {\n\t\t\tout[k] = (typeof v === 'string' && v.length > PREVIEW_MAX_CHARS)\n\t\t\t\t? v.slice(0, PREVIEW_MAX_CHARS) + '…'\n\t\t\t\t: v;\n\t\t}\n\t\treturn out;\n\t});\n}\n\n/** Resolvers for the INIT handshake — getData waits for this */\nlet initResolve: ((init: { params: Record<string, any>; now: Date }) => void) | null = null;\nconst initPromise = new Promise<{ params: Record<string, any>; now: Date }>((resolve) => {\n\tinitResolve = resolve;\n});\n\n/**\n * Source name lookup is not available in the child (the parent holds the\n * ExternalTool list). We use toolId as sourceName and let the parent fill\n * it in when building the final ScriptResult, if needed.\n */\nfunction buildCtx(init: { params: Record<string, any>; now: Date }) {\n\treturn {\n\t\tnow: init.now,\n\n\t\tasync query(toolId: string, sql: string): Promise<{ data: any[]; count: number }> {\n\t\t\tif (!BRIDGE_URL) throw new Error('SCRIPT_BRIDGE_URL is not set — script bridge server is not running');\n\n\t\t\tconst startedAt = Date.now();\n\n\t\t\tsend({ type: 'stream', chunk: `\\n📝 **Querying ${toolId}:**\\n\\`\\`\\`sql\\n${sql}\\n\\`\\`\\`\\n\\n` });\n\t\t\tsend({ type: 'stream', chunk: `__QUERY_TIMER_START_Executing query__` });\n\n\t\t\tconst res = await fetch(`${BRIDGE_URL}/query`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ toolId, sql }),\n\t\t\t});\n\n\t\t\tconst json = await res.json() as any;\n\t\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\t\tsend({ type: 'stream', chunk: `__QUERY_TIMER_DONE_${(executionTimeMs / 1000).toFixed(1)}__\\n\\n` });\n\n\t\t\tif (!res.ok || !json.success) {\n\t\t\t\tconst errMsg = json.error || `Bridge query failed (${res.status})`;\n\t\t\t\tsend({ type: 'stream', chunk: `❌ **Query failed on ${toolId}:** ${errMsg}\\n\\n` });\n\t\t\t\tthrow new Error(errMsg);\n\t\t\t}\n\n\t\t\tconst data: any[] = json.data ?? [];\n\t\t\tconst count: number = json.count ?? data.length;\n\n\t\t\tsend({ type: 'stream', chunk: `✅ **${count} rows from ${toolId}**\\n\\n` });\n\t\t\tif (data.length > 0) {\n\t\t\t\tsend({ type: 'stream', chunk: `<DataTable>${JSON.stringify(previewRows(data))}</DataTable>\\n\\n` });\n\t\t\t}\n\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: toolId,\n\t\t\t\tsourceName: toolId, // enriched by runner after result arrives\n\t\t\t\tsql,\n\t\t\t\tdata,\n\t\t\t\tcount,\n\t\t\t\texecutionTimeMs,\n\t\t\t});\n\n\t\t\treturn { data, count };\n\t\t},\n\n\t\t/**\n\t\t * Call a tool with structured params (typically a direct tool whose\n\t\t * fn(params) does not expect a SQL string). Returns whatever the tool\n\t\t * returned — shape is up to the tool.\n\t\t *\n\t\t * Use this when you need a pre-built business-logic tool (e.g.,\n\t\t * \"Calculate Shelf Life\") that takes structured input rather than SQL.\n\t\t * For SQL source tools, keep using ctx.query.\n\t\t *\n\t\t * runTool results do NOT auto-flow into executedQueries. If you want\n\t\t * the result visualised, register it via ctx.emit() after extracting\n\t\t * the array you want to chart.\n\t\t */\n\t\tasync runTool<T = any>(toolId: string, params: Record<string, any> = {}): Promise<T> {\n\t\t\tif (!BRIDGE_URL) throw new Error('SCRIPT_BRIDGE_URL is not set — script bridge server is not running');\n\n\t\t\tconst res = await fetch(`${BRIDGE_URL}/run-tool`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({ toolId, params }),\n\t\t\t});\n\n\t\t\tconst json = await res.json() as any;\n\t\t\tif (!res.ok || !json.success) throw new Error(json.error || `Bridge run-tool failed (${res.status})`);\n\n\t\t\treturn json.result as T;\n\t\t},\n\n\t\t/**\n\t\t * Register a computed dataset. Flows into executedQueries alongside\n\t\t * real SQL query results so component generation can render post-SQL\n\t\t * transforms (weighted averages, clustering, joins) as charts.\n\t\t */\n\t\temit(name: string, rows: any[], meta?: { description?: string }): void {\n\t\t\texecutedQueries.push({\n\t\t\t\tsourceId: `computed:${name}`,\n\t\t\t\tsourceName: meta?.description || name,\n\t\t\t\tsql: `-- computed dataset: ${name}`,\n\t\t\t\tdata: Array.isArray(rows) ? rows : [],\n\t\t\t\tcount: Array.isArray(rows) ? rows.length : 0,\n\t\t\t\texecutionTimeMs: 0,\n\t\t\t\tvirtual: true,\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Forward a short progress message to the parent's StreamBuffer so the UI\n\t\t * continues to see per-script feedback during execution.\n\t\t */\n\t\tlog(chunk: string): void {\n\t\t\tsend({ type: 'stream', chunk });\n\t\t},\n\t};\n}\n\nfunction send(msg: ChildToParentMessage): void {\n\tprocess.stdout.write(encodeMessage(msg));\n}\n\nfunction sendError(message: string, phase: 'compile' | 'runtime', stack?: string): void {\n\tsend({ type: 'error', message, stack, phase });\n}\n\n// ============================================\n// stdin reader — parse NDJSON messages from the parent (INIT only)\n// ============================================\n\nconst splitter = new LineSplitter();\n\nprocess.stdin.setEncoding('utf-8');\nprocess.stdin.on('data', (chunk: string) => {\n\tlet lines: string[];\n\ttry {\n\t\tlines = splitter.push(chunk);\n\t} catch (err) {\n\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\tsendError(msg, 'runtime');\n\t\tprocess.exit(1);\n\t}\n\tfor (const line of lines) {\n\t\tlet msg: ParentToChildMessage;\n\t\ttry {\n\t\t\tmsg = JSON.parse(line);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\thandleMessage(msg);\n\t}\n});\n\nfunction handleMessage(msg: ParentToChildMessage): void {\n\tswitch (msg.type) {\n\t\tcase 'init': {\n\t\t\tif (initResolve) {\n\t\t\t\tconst now = new Date(msg.now || new Date().toISOString());\n\t\t\t\tconst params = restoreParamTypes(msg.params || {}, msg.paramSchema);\n\t\t\t\tinitResolve({ params, now });\n\t\t\t\tinitResolve = null;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n// ============================================\n// Main — import the user script and run getData\n// ============================================\n\n(async () => {\n\tlet getData: (ctx: any, params: any) => Promise<any>;\n\n\ttry {\n\t\t// Dynamic import — tsx transpiles the user .ts file on the fly\n\t\tconst mod = await import(pathToFileURL(SCRIPT_ARG).href);\n\t\tgetData = mod.getData ?? (typeof mod.default === 'function' ? mod.default : mod.default?.getData);\n\t\tif (typeof getData !== 'function') {\n\t\t\tthrow new Error(\n\t\t\t\t`Script at ${SCRIPT_ARG} does not export getData — expected \\`export async function getData(ctx, params)\\``,\n\t\t\t);\n\t\t}\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'compile', e.stack);\n\t\tprocess.exit(1);\n\t\treturn;\n\t}\n\n\tconst init = await initPromise;\n\tconst ctx = buildCtx(init);\n\tconst startedAt = Date.now();\n\n\ttry {\n\t\tconst raw = await getData(ctx, init.params);\n\t\tconst executionTimeMs = Date.now() - startedAt;\n\n\t\tconst data = normalizeResultData(raw);\n\t\tconst count = typeof raw?.count === 'number' ? raw.count : data.length;\n\t\tconst sampleRows = data.slice(0, 5);\n\n\t\tconst result: ChildToParentMessage = {\n\t\t\ttype: 'result',\n\t\t\tdata,\n\t\t\tcount,\n\t\t\texecutedQueries,\n\t\t\texecutionTimeMs,\n\t\t\tsampleRows,\n\t\t};\n\t\tsend(result);\n\t\t// Give stdout a tick to flush before we exit\n\t\tprocess.stdout.once('drain', () => process.exit(0));\n\t\tsetImmediate(() => process.exit(0));\n\t} catch (err) {\n\t\tconst e = err as Error;\n\t\tsendError(e.message || String(err), 'runtime', e.stack);\n\t\tprocess.exit(1);\n\t}\n})();\n\n/**\n * Normalize whatever getData returned into an array. Mirrors the old\n * ScriptRunner semantics: direct array, { data: [] }, or { data: {...} }\n * wrapped as single-element array.\n */\nfunction normalizeResultData(raw: any): any[] {\n\tif (!raw) return [];\n\tif (Array.isArray(raw)) return raw;\n\tif (Array.isArray(raw.data)) return raw.data;\n\tif (raw.data && typeof raw.data === 'object') return [raw.data];\n\treturn [];\n}\n\n/**\n * Restore typed values that JSON stripped during IPC transport. Date and\n * date_range params arrive as ISO strings; re-wrap them so scripts can call\n * `.toISOString()` and other Date methods the same way they would in-process.\n */\nfunction restoreParamTypes(\n\tparams: Record<string, any>,\n\tschema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>,\n): Record<string, any> {\n\tif (!schema) return params;\n\tconst out = { ...params };\n\tfor (const [name, type] of Object.entries(schema)) {\n\t\tconst v = out[name];\n\t\tif (v === undefined || v === null) continue;\n\t\tif (type === 'date' && typeof v === 'string') {\n\t\t\tconst d = new Date(v);\n\t\t\tif (!isNaN(d.getTime())) out[name] = d;\n\t\t} else if (type === 'date_range' && typeof v === 'object') {\n\t\t\tconst from = v.from;\n\t\t\tconst to = v.to;\n\t\t\tout[name] = {\n\t\t\t\tfrom: typeof from === 'string' ? new Date(from) : from,\n\t\t\t\tto: typeof to === 'string' ? new Date(to) : to,\n\t\t\t};\n\t\t}\n\t}\n\treturn out;\n}\n","/**\n * script-ipc.ts — Protocol definitions for parent/child IPC.\n *\n * Transport: newline-delimited JSON over the child's stdin/stdout.\n * stderr is passed through for uncaught exceptions and compile errors.\n *\n * See backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md § IPC Bridge.\n */\n\n// ============================================\n// Parent → Child\n// ============================================\n\nexport interface InitMessage {\n\ttype: 'init';\n\t/**\n\t * Params passed to getData(). Date/date_range values cross the IPC boundary\n\t * as ISO strings and are restored to Date objects in the child via paramSchema.\n\t */\n\tparams: Record<string, any>;\n\t/** Frozen timestamp for ctx.now — ISO string */\n\tnow: string;\n\t/**\n\t * Maps each parameter name to its declared type so the child can restore\n\t * Date objects (lost during JSON serialization).\n\t */\n\tparamSchema?: Record<string, 'string' | 'number' | 'date' | 'date_range' | 'enum' | 'boolean'>;\n}\n\nexport interface QueryResultMessage {\n\ttype: 'query_result';\n\t/** Matches the `id` from the child's QUERY message */\n\tid: string;\n\tdata: any[];\n\tcount: number;\n\tmetadata?: {\n\t\ttotalCount?: number;\n\t\texecutionTimeMs?: number;\n\t};\n}\n\nexport interface QueryErrorMessage {\n\ttype: 'query_error';\n\tid: string;\n\terror: string;\n}\n\n/**\n * Response to a `run_tool` request — carries whatever the direct-tool's fn()\n * returned. Shape is arbitrary; the script is expected to know what its chosen\n * tool yields and unwrap accordingly.\n */\nexport interface RunToolResultMessage {\n\ttype: 'run_tool_result';\n\tid: string;\n\tresult: any;\n}\n\nexport type ParentToChildMessage =\n\t| InitMessage\n\t| QueryResultMessage\n\t| QueryErrorMessage\n\t| RunToolResultMessage;\n\n// ============================================\n// Child → Parent\n// ============================================\n\nexport interface QueryMessage {\n\ttype: 'query';\n\t/** Short uuid — parent uses this to correlate its response */\n\tid: string;\n\ttoolId: string;\n\tsql: string;\n}\n\n/**\n * Run an arbitrary tool with structured params. Used by ctx.runTool() to call\n * direct tools (and any tool whose `fn(params)` doesn't expect a SQL string).\n * Reply is RunToolResultMessage or QueryErrorMessage.\n */\nexport interface RunToolMessage {\n\ttype: 'run_tool';\n\tid: string;\n\ttoolId: string;\n\tparams: Record<string, any>;\n}\n\nexport interface StreamMessage {\n\ttype: 'stream';\n\tchunk: string;\n}\n\nexport interface ExecutedQuery {\n\tsourceId: string;\n\tsourceName: string;\n\tsql: string;\n\tdata: any[];\n\tcount: number;\n\ttotalCount?: number;\n\texecutionTimeMs: number;\n\t/**\n\t * When the script registered a computed dataset via ctx.emit(), this is\n\t * marked virtual so component generation can distinguish it from a real SQL query.\n\t */\n\tvirtual?: boolean;\n}\n\nexport interface ResultMessage {\n\ttype: 'result';\n\tdata: any[];\n\tcount: number;\n\texecutedQueries: ExecutedQuery[];\n\texecutionTimeMs: number;\n\t/** First 5 rows of the final `data` — lets the parent judge intent-match cheaply */\n\tsampleRows: any[];\n}\n\nexport interface ErrorMessage {\n\ttype: 'error';\n\tmessage: string;\n\tstack?: string;\n\t/** \"compile\" → tsx couldn't parse; \"runtime\" → thrown during getData */\n\tphase?: 'compile' | 'runtime';\n}\n\nexport type ChildToParentMessage =\n\t| QueryMessage\n\t| RunToolMessage\n\t| StreamMessage\n\t| ResultMessage\n\t| ErrorMessage;\n\n// ============================================\n// Utilities\n// ============================================\n\n/**\n * Encode a message for transport: JSON + newline.\n */\nexport function encodeMessage(msg: ParentToChildMessage | ChildToParentMessage): string {\n\treturn JSON.stringify(msg) + '\\n';\n}\n\n/**\n * Thrown when a single NDJSON line grows past the byte ceiling without a\n * newline — a runaway writer or a pathologically large message. Callers abort\n * (kill the child / exit) instead of buffering toward OOM. See #9 in\n * backend/docs/SCRIPT-FLOW-SCALING-ISSUES.md.\n */\nexport class LineOverflowError extends Error {\n\tconstructor(public readonly buffered: number, public readonly limit: number) {\n\t\tsuper(`IPC message exceeded ${limit} bytes without a newline (${buffered} buffered) — aborting to avoid unbounded memory.`);\n\t\tthis.name = 'LineOverflowError';\n\t}\n}\n\n/** Default per-message ceiling — generous enough for large result sets, low\n * enough to catch a runaway/infinite writer. Override via SCRIPT_MAX_IPC_BYTES. */\nconst DEFAULT_MAX_IPC_BYTES = (() => {\n\tconst envVal = Number(process.env.SCRIPT_MAX_IPC_BYTES);\n\treturn Number.isFinite(envVal) && envVal > 0 ? Math.floor(envVal) : 64 * 1024 * 1024;\n})();\n\n/**\n * Split a stream of data chunks on newlines and emit each complete JSON line.\n * Partial lines are buffered until the next chunk arrives, up to `maxBytes` —\n * past which `push` throws `LineOverflowError` rather than grow unbounded.\n */\nexport class LineSplitter {\n\tprivate buffer = '';\n\n\tconstructor(private readonly maxBytes: number = DEFAULT_MAX_IPC_BYTES) {}\n\n\tpush(chunk: string): string[] {\n\t\tthis.buffer += chunk;\n\t\tconst lines: string[] = [];\n\t\tlet idx: number;\n\t\twhile ((idx = this.buffer.indexOf('\\n')) !== -1) {\n\t\t\tconst line = this.buffer.slice(0, idx);\n\t\t\tthis.buffer = this.buffer.slice(idx + 1);\n\t\t\tif (line.length > 0) lines.push(line);\n\t\t}\n\t\t// A partial line past the ceiling means no newline is coming (or the\n\t\t// message is pathologically large). Drop the buffer and signal abort.\n\t\tif (this.buffer.length > this.maxBytes) {\n\t\t\tconst buffered = this.buffer.length;\n\t\t\tthis.buffer = '';\n\t\t\tthrow new LineOverflowError(buffered, this.maxBytes);\n\t\t}\n\t\treturn lines;\n\t}\n\n\t/** Flush any remaining partial data (useful on stream close) */\n\tflush(): string | null {\n\t\tif (this.buffer.length === 0) return null;\n\t\tconst rest = this.buffer;\n\t\tthis.buffer = '';\n\t\treturn rest;\n\t}\n}\n\n/**\n * Generate a short correlation id for in-flight queries.\n * Collision within a single execution is astronomically unlikely.\n */\nexport function shortId(): string {\n\treturn Math.random().toString(36).slice(2, 10);\n}\n"],"mappings":"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","names":[]}
|