@teleporthq/teleport-plugin-next-workflows 0.43.35 → 0.43.37
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/__tests__/ai-error-contract.test.ts +149 -0
- package/__tests__/data-api-safe-query.test.ts +16 -16
- package/__tests__/node-handler-file-inline-map.test.ts +114 -0
- package/__tests__/raw-query-param-binding.test.ts +18 -17
- package/__tests__/streaming-on-end-env-filter.test.ts +405 -0
- package/dist/cjs/api-route-generator.d.ts.map +1 -1
- package/dist/cjs/api-route-generator.js +14 -8
- package/dist/cjs/api-route-generator.js.map +1 -1
- package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
- package/dist/cjs/data-api-route-generator.js +13 -2
- package/dist/cjs/data-api-route-generator.js.map +1 -1
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +47 -5
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/cjs/nodes/general/general-custom-js.js +7 -0
- package/dist/cjs/nodes/general/general-custom-js.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/workflow-component-plugin.js +9 -3
- package/dist/cjs/workflow-component-plugin.js.map +1 -1
- package/dist/cjs/workflow-project-plugin.d.ts +1 -0
- package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
- package/dist/cjs/workflow-project-plugin.js +83 -5
- package/dist/cjs/workflow-project-plugin.js.map +1 -1
- package/dist/esm/api-route-generator.d.ts.map +1 -1
- package/dist/esm/api-route-generator.js +14 -8
- package/dist/esm/api-route-generator.js.map +1 -1
- package/dist/esm/data-api-route-generator.d.ts.map +1 -1
- package/dist/esm/data-api-route-generator.js +13 -2
- package/dist/esm/data-api-route-generator.js.map +1 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +47 -5
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/esm/nodes/general/general-custom-js.js +7 -0
- package/dist/esm/nodes/general/general-custom-js.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/workflow-component-plugin.js +9 -3
- package/dist/esm/workflow-component-plugin.js.map +1 -1
- package/dist/esm/workflow-project-plugin.d.ts +1 -0
- package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
- package/dist/esm/workflow-project-plugin.js +83 -5
- package/dist/esm/workflow-project-plugin.js.map +1 -1
- package/package.json +2 -2
- package/src/api-route-generator.ts +14 -8
- package/src/data-api-route-generator.ts +13 -2
- package/src/executor-generator.ts +47 -5
- package/src/nodes/general/general-custom-js.ts +8 -0
- package/src/workflow-component-plugin.ts +15 -10
- package/src/workflow-project-plugin.ts +87 -4
|
@@ -331,8 +331,8 @@ module.exports = async function handler(req, res) {
|
|
|
331
331
|
res.status(earlyRes.status || 500).json(earlyRes.body || {});
|
|
332
332
|
return;
|
|
333
333
|
}
|
|
334
|
-
if (
|
|
335
|
-
throw new Error(result
|
|
334
|
+
if (utils.isFatalNodeResult(result)) {
|
|
335
|
+
throw new Error(utils.fatalNodeResultMessage(result));
|
|
336
336
|
}
|
|
337
337
|
context[node.id] = result;
|
|
338
338
|
if (result && result.__terminal) break;
|
|
@@ -680,6 +680,12 @@ module.exports = async function handler(req, res) {
|
|
|
680
680
|
res.write('data: ' + JSON.stringify({ type: 'node-result', nodeId: sn.id, result: snResult }) + '\\n\\n');
|
|
681
681
|
}
|
|
682
682
|
});
|
|
683
|
+
// A provider/auth failure surfaces as { error: true, message, code }.
|
|
684
|
+
// Without this gate the on-end branch would run against a failed AI
|
|
685
|
+
// result (e.g. persist a NULL chat answer → NOT NULL 500 downstream).
|
|
686
|
+
if (utils.isFatalNodeResult(result)) {
|
|
687
|
+
throw new Error(utils.fatalNodeResultMessage(result));
|
|
688
|
+
}
|
|
683
689
|
context[node.id] = result;
|
|
684
690
|
res.write('data: ' + JSON.stringify({ type: 'node-result', nodeId: node.id, result: result }) + '\\n\\n');
|
|
685
691
|
|
|
@@ -712,8 +718,8 @@ module.exports = async function handler(req, res) {
|
|
|
712
718
|
}
|
|
713
719
|
return;
|
|
714
720
|
}
|
|
715
|
-
if (
|
|
716
|
-
throw new Error(result
|
|
721
|
+
if (utils.isFatalNodeResult(result)) {
|
|
722
|
+
throw new Error(utils.fatalNodeResultMessage(result));
|
|
717
723
|
}
|
|
718
724
|
context[node.id] = result;
|
|
719
725
|
if (streamStarted) {
|
|
@@ -925,8 +931,8 @@ module.exports = async function handler(req, res) {
|
|
|
925
931
|
res.status(earlyRes.status || 500).json(earlyRes.body || {});
|
|
926
932
|
return;
|
|
927
933
|
}
|
|
928
|
-
if (
|
|
929
|
-
throw new Error(result
|
|
934
|
+
if (utils.isFatalNodeResult(result)) {
|
|
935
|
+
throw new Error(utils.fatalNodeResultMessage(result));
|
|
930
936
|
}
|
|
931
937
|
context[node.id] = result;
|
|
932
938
|
if (result && result.__terminal) break;
|
|
@@ -1225,8 +1231,8 @@ const generateNodeExecutionLoop = (
|
|
|
1225
1231
|
res.status(earlyRes.status || 500).json(earlyRes.body || {});
|
|
1226
1232
|
return;
|
|
1227
1233
|
}${customNodeBlock}
|
|
1228
|
-
if (
|
|
1229
|
-
throw new Error(result
|
|
1234
|
+
if (utils.isFatalNodeResult(result)) {
|
|
1235
|
+
throw new Error(utils.fatalNodeResultMessage(result));
|
|
1230
1236
|
}
|
|
1231
1237
|
context[node.id] = result;
|
|
1232
1238
|
if (result && result.__terminal) break;
|
|
@@ -159,12 +159,23 @@ function validateFilters(filters) {
|
|
|
159
159
|
// 22003 (numeric value out of range), 22023 (invalid parameter value).
|
|
160
160
|
// We pass the original query / params back to the caller as a debug
|
|
161
161
|
// label so log output stays diagnosable.
|
|
162
|
-
|
|
162
|
+
// Also covers 22007 (invalid_datetime_format — an empty "Filter by date" value
|
|
163
|
+
// hitting a date column: invalid input syntax for type date). Beyond the code
|
|
164
|
+
// list we ALSO suppress ANY "invalid input syntax for type" message: every one
|
|
165
|
+
// means the bound value could never coerce to the column type, so no row can
|
|
166
|
+
// match and an empty result is the correct semantics — never a 500 the user sees
|
|
167
|
+
// as "Failed to update. Please try again".
|
|
168
|
+
var SAFE_COERCION_ERROR_CODES = { '22P02': 1, '22007': 1, '22008': 1, '22003': 1, '22023': 1 };
|
|
169
|
+
function isSafeCoercionError(err) {
|
|
170
|
+
if (!err) return false;
|
|
171
|
+
if (SAFE_COERCION_ERROR_CODES[err.code]) return true;
|
|
172
|
+
return typeof err.message === 'string' && /invalid input syntax for type/i.test(err.message);
|
|
173
|
+
}
|
|
163
174
|
async function safeQuery(client, sql, params, mode) {
|
|
164
175
|
try {
|
|
165
176
|
return await client.query(sql, params);
|
|
166
177
|
} catch (err) {
|
|
167
|
-
if (err
|
|
178
|
+
if (isSafeCoercionError(err)) {
|
|
168
179
|
console.warn('[data-api] suppressed Postgres coercion error (' + err.code + '): ' + (err.message || err) +
|
|
169
180
|
' — returning empty result for ' + (mode || 'query') + '. SQL=' + sql + ' params=' + JSON.stringify(params));
|
|
170
181
|
if (mode === 'count') {
|
|
@@ -586,6 +586,24 @@ function topoSortNodes(nodes, edges) {
|
|
|
586
586
|
return sorted;
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
+
// A node result signals failure either through the legacy string contract
|
|
590
|
+
// ({ error: '...' } / { success: false }) or through the AI-node contract
|
|
591
|
+
// ({ error: true, message, code } — provider/auth failures). Both must halt
|
|
592
|
+
// the workflow; treating error:true as success used to let the pipeline limp
|
|
593
|
+
// into downstream NOT NULL violations.
|
|
594
|
+
function isFatalNodeResult(result) {
|
|
595
|
+
if (!result) return false;
|
|
596
|
+
return result.success === false ||
|
|
597
|
+
(typeof result.error === 'string' && result.error) ||
|
|
598
|
+
result.error === true;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function fatalNodeResultMessage(result) {
|
|
602
|
+
if (typeof result.error === 'string' && result.error) return result.error;
|
|
603
|
+
if (typeof result.message === 'string' && result.message) return result.message;
|
|
604
|
+
return 'Node execution failed';
|
|
605
|
+
}
|
|
606
|
+
|
|
589
607
|
async function executeNodes(nodes, edges, context, nodeHandlers, workflowConfig, callServerSegment, executionId) {
|
|
590
608
|
const executed = {};
|
|
591
609
|
const sortedNodes = topoSortNodes(nodes, edges);
|
|
@@ -684,6 +702,9 @@ async function executeNodes(nodes, edges, context, nodeHandlers, workflowConfig,
|
|
|
684
702
|
await executeNodes(onStreamNodes, edges, context, nodeHandlers, workflowConfig, callServerSegment, executionId);
|
|
685
703
|
}
|
|
686
704
|
});
|
|
705
|
+
if (isFatalNodeResult(streamResult)) {
|
|
706
|
+
throw new Error(fatalNodeResultMessage(streamResult));
|
|
707
|
+
}
|
|
687
708
|
context[node.id] = streamResult;
|
|
688
709
|
|
|
689
710
|
if (onEndNodes.length > 0) {
|
|
@@ -726,8 +747,8 @@ async function executeNodes(nodes, edges, context, nodeHandlers, workflowConfig,
|
|
|
726
747
|
throw earlyErr;
|
|
727
748
|
}
|
|
728
749
|
|
|
729
|
-
if (
|
|
730
|
-
throw new Error(result
|
|
750
|
+
if (isFatalNodeResult(result)) {
|
|
751
|
+
throw new Error(fatalNodeResultMessage(result));
|
|
731
752
|
}
|
|
732
753
|
|
|
733
754
|
context[node.id] = result;
|
|
@@ -1006,7 +1027,9 @@ module.exports = {
|
|
|
1006
1027
|
executeParallel,
|
|
1007
1028
|
collectBranchNodes,
|
|
1008
1029
|
markAllBranchNodes,
|
|
1009
|
-
isStreamingAINode
|
|
1030
|
+
isStreamingAINode,
|
|
1031
|
+
isFatalNodeResult,
|
|
1032
|
+
fatalNodeResultMessage
|
|
1010
1033
|
};
|
|
1011
1034
|
`
|
|
1012
1035
|
}
|
|
@@ -1200,6 +1223,19 @@ function findStreamingAINodes(allNodes, allEdges) {
|
|
|
1200
1223
|
return map;
|
|
1201
1224
|
}
|
|
1202
1225
|
|
|
1226
|
+
// A streaming AI node's on-stream / on-end branches are collected from the
|
|
1227
|
+
// FULL workflow node list, so they may contain server nodes (e.g. the SQL
|
|
1228
|
+
// insert persisting a chat answer). Those already execute inside their own
|
|
1229
|
+
// server segments with full configs; the browser bundle only ships their
|
|
1230
|
+
// config redacted down to the client-safe whitelist (segment-splitter's
|
|
1231
|
+
// redactServerNodeConfig), so executing them here with CLIENT handlers both
|
|
1232
|
+
// double-executes them and crashes on the missing config. Emitted nodes carry
|
|
1233
|
+
// executionEnv for exactly this filter; nodes without the marker (older
|
|
1234
|
+
// bundles) keep executing client-side as before.
|
|
1235
|
+
function clientExecutableBranchNodes(branchNodes) {
|
|
1236
|
+
return branchNodes.filter(function(n) { return !n || n.executionEnv !== 'server'; });
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1203
1239
|
async function callStreamingServerSegment(segmentUrl, context, streamingInfo, allNodes, allEdges, clientHandlers, workflowConfig, executionId) {
|
|
1204
1240
|
const prunedContext = pruneContext(context);
|
|
1205
1241
|
const handledNodeIds = {};
|
|
@@ -1254,7 +1290,10 @@ async function callStreamingServerSegment(segmentUrl, context, streamingInfo, al
|
|
|
1254
1290
|
context[data.nodeId] = { chunk: data.chunk, fullResponse: data.fullResponse, model: data.model };
|
|
1255
1291
|
const info = streamingInfo[data.nodeId];
|
|
1256
1292
|
if (info && info.onStreamNodes.length > 0) {
|
|
1257
|
-
|
|
1293
|
+
const runnableStreamNodes = clientExecutableBranchNodes(info.onStreamNodes);
|
|
1294
|
+
if (runnableStreamNodes.length > 0) {
|
|
1295
|
+
await utils.executeNodes(runnableStreamNodes, allEdges, context, clientHandlers, workflowConfig, null, executionId);
|
|
1296
|
+
}
|
|
1258
1297
|
for (let sni = 0; sni < info.onStreamNodes.length; sni++) {
|
|
1259
1298
|
handledNodeIds[info.onStreamNodes[sni].id] = true;
|
|
1260
1299
|
}
|
|
@@ -1269,7 +1308,10 @@ async function callStreamingServerSegment(segmentUrl, context, streamingInfo, al
|
|
|
1269
1308
|
for (let sk = 0; sk < streamedKeys.length; sk++) {
|
|
1270
1309
|
const endInfo = streamingInfo[streamedKeys[sk]];
|
|
1271
1310
|
if (endInfo && endInfo.onEndNodes.length > 0) {
|
|
1272
|
-
|
|
1311
|
+
const runnableEndNodes = clientExecutableBranchNodes(endInfo.onEndNodes);
|
|
1312
|
+
if (runnableEndNodes.length > 0) {
|
|
1313
|
+
await utils.executeNodes(runnableEndNodes, allEdges, context, clientHandlers, workflowConfig, null, executionId);
|
|
1314
|
+
}
|
|
1273
1315
|
for (let eni = 0; eni < endInfo.onEndNodes.length; eni++) {
|
|
1274
1316
|
handledNodeIds[endInfo.onEndNodes[eni].id] = true;
|
|
1275
1317
|
}
|
|
@@ -23,6 +23,14 @@ import { NodeHandlerGenerator, handlerToString } from '../types'
|
|
|
23
23
|
async function general_custom_js(config: any, context: Record<string, unknown>) {
|
|
24
24
|
const code = config.code
|
|
25
25
|
|
|
26
|
+
// A server-classified custom-js node's config is redacted to the client-safe
|
|
27
|
+
// whitelist ({} here) before it ships to the browser. If such a node ever
|
|
28
|
+
// reaches a client handler (defense-in-depth — the runtime filters them),
|
|
29
|
+
// no-op cleanly instead of crashing the whole workflow on `code.length`.
|
|
30
|
+
if (typeof code !== 'string' || code.length === 0) {
|
|
31
|
+
return {}
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
// Reserved context keys that must never leak into params / innerParams.
|
|
27
35
|
// Anything starting with "__" is internal scaffolding; these named entries
|
|
28
36
|
// are existing well-known runtime keys.
|
|
@@ -1308,16 +1308,21 @@ function __normalizeAdminFormRow(row, defaults) {
|
|
|
1308
1308
|
}))
|
|
1309
1309
|
)
|
|
1310
1310
|
const nodesJson = JSON.stringify(
|
|
1311
|
-
wf.nodes.map((n: UIDLWorkflowNode) =>
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1311
|
+
wf.nodes.map((n: UIDLWorkflowNode) => {
|
|
1312
|
+
// Runtime marker consumed by clientExecutableBranchNodes (client
|
|
1313
|
+
// runtime): a streaming AI node's on-stream/on-end branches are
|
|
1314
|
+
// collected from this full node list, and server nodes in them must
|
|
1315
|
+
// never execute with client handlers (their config is redacted below).
|
|
1316
|
+
const env = nodeEnvById.get(n.id) ?? resolveNodeExecutionEnv(n)
|
|
1317
|
+
return {
|
|
1318
|
+
id: n.id,
|
|
1319
|
+
type: n.type,
|
|
1320
|
+
config: redactServerNodeConfig(n.config, env),
|
|
1321
|
+
stepNumber: n.stepNumber,
|
|
1322
|
+
label: n.label,
|
|
1323
|
+
executionEnv: env,
|
|
1324
|
+
}
|
|
1325
|
+
})
|
|
1321
1326
|
)
|
|
1322
1327
|
const edgesJson = JSON.stringify(
|
|
1323
1328
|
wf.edges.map((e: UIDLWorkflowEdge) => ({
|
|
@@ -887,18 +887,97 @@ export class NextWorkflowProjectPlugin implements ProjectPlugin {
|
|
|
887
887
|
return ''
|
|
888
888
|
}
|
|
889
889
|
|
|
890
|
-
|
|
890
|
+
// Inline each handler as its `module.exports` map value inside a
|
|
891
|
+
// self-invoking function that returns the handler's entry-point, rather
|
|
892
|
+
// than emitting separate top-level `async function <name>` declarations
|
|
893
|
+
// plus a map that references them by name. `handlers` and `exportNames`
|
|
894
|
+
// are populated together in the loop above, so `handlers[index]` is
|
|
895
|
+
// exactly the handler for `exportNames[index]`.
|
|
896
|
+
//
|
|
897
|
+
// Why the IIFE (not a bare inline expression): a handler is NOT always a
|
|
898
|
+
// single function expression. AI nodes emit shared provider utils
|
|
899
|
+
// (`var __ai_… = …`) BEFORE their entry function, and payment/rate-limiter
|
|
900
|
+
// nodes append helper functions AFTER it. Splicing such a multi-statement
|
|
901
|
+
// string directly as an object-literal value (`'ai-custom-prompt': var … `)
|
|
902
|
+
// is a SyntaxError. Wrapping in `(function () { <handler>; return <fn>; })()`
|
|
903
|
+
// runs every statement in an isolated scope and yields the entry function as
|
|
904
|
+
// the value — so single- AND multi-statement handlers are both valid.
|
|
905
|
+
//
|
|
906
|
+
// Why not top-level declarations + a name reference: that forward-reference
|
|
907
|
+
// shape is fragile in production. If a bundler/minifier drops a single-use
|
|
908
|
+
// declaration (or a handler's emitted name ever drifts from its
|
|
909
|
+
// `nodeType`-derived key), the map is left referencing an undefined
|
|
910
|
+
// identifier — `ReferenceError: <name> is not defined` at MODULE-EVAL time,
|
|
911
|
+
// which fails the Next.js/Vercel build during "Collecting page data". The
|
|
912
|
+
// returned identifier is derived from the same `t` that keys the map and is
|
|
913
|
+
// defined by the handler string right above the `return`, so there is no
|
|
914
|
+
// cross-statement forward reference to dangle. The entry-function naming
|
|
915
|
+
// contract (`handlerToString(fn)` preserves `fn.name`, and every handler
|
|
916
|
+
// names its entry `nodeType.replace(/-/g, '_')`) is enforced by
|
|
917
|
+
// node-handler-file-inline-map.test.ts across the whole node registry.
|
|
918
|
+
//
|
|
919
|
+
// That contract holds for the handler's OWN source (checked below via
|
|
920
|
+
// resolveHandlerEntryName), but not necessarily for the RUNTIME name a
|
|
921
|
+
// caller's bundler assigns it. Handlers built from `handlerToString(fn)`
|
|
922
|
+
// embed `fn.toString()` — a snapshot of whatever `fn` was actually named
|
|
923
|
+
// at the moment it's read. When this package is bundled and minified by a
|
|
924
|
+
// consumer (e.g. teleport-gui's browser packer worker, built with
|
|
925
|
+
// Next.js/Terser), the minifier freely renames `fn`'s declaration: nothing
|
|
926
|
+
// in the bundle calls it by name, only `.toString()` reads it at runtime,
|
|
927
|
+
// which is invisible to the minifier. The naive
|
|
928
|
+
// `nodeType.replace(/-/g, '_')` name would then reference an identifier
|
|
929
|
+
// that no longer exists in the embedded source — a Vercel-only
|
|
930
|
+
// "Collecting page data" ReferenceError, since local dev never runs a
|
|
931
|
+
// minifier. resolveHandlerEntryName reads the name the source text
|
|
932
|
+
// ACTUALLY declares instead of assuming it matches the convention.
|
|
933
|
+
const entries = exportNames
|
|
934
|
+
.map((t, index) => {
|
|
935
|
+
const source = handlers[index].trim()
|
|
936
|
+
const entryFn = this.resolveHandlerEntryName(source, t)
|
|
937
|
+
return ` '${t}': (function () {\n${source}\nreturn ${entryFn};\n})()`
|
|
938
|
+
})
|
|
939
|
+
.join(',\n')
|
|
891
940
|
|
|
892
941
|
return `// Auto-generated workflow node handlers (${env})
|
|
893
942
|
|
|
894
|
-
${handlers.join('\n\n')}
|
|
895
|
-
|
|
896
943
|
module.exports = {
|
|
897
|
-
${
|
|
944
|
+
${entries}
|
|
898
945
|
};
|
|
899
946
|
`
|
|
900
947
|
}
|
|
901
948
|
|
|
949
|
+
// Resolves the name a handler's entry function is ACTUALLY declared with in
|
|
950
|
+
// `source`, rather than assuming it always equals the
|
|
951
|
+
// `nodeType.replace(/-/g, '_')` convention name (see the comment above
|
|
952
|
+
// `generateNodeHandlerFile`'s `entries` for why that assumption can break
|
|
953
|
+
// under minification).
|
|
954
|
+
//
|
|
955
|
+
// Handlers composed from string-literal templates (not `fn.toString()`) are
|
|
956
|
+
// immune to that class of breakage — a minifier never rewrites the contents
|
|
957
|
+
// of a string literal — so the convention name is still correct for those;
|
|
958
|
+
// this only needs to look further when it's genuinely absent from `source`.
|
|
959
|
+
private resolveHandlerEntryName(source: string, nodeType: string): string {
|
|
960
|
+
const conventionName = nodeType.replace(/-/g, '_')
|
|
961
|
+
const conventionNameUsed = new RegExp(`(?:^|[^\\w$])${conventionName}\\s*\\(`).test(source)
|
|
962
|
+
if (conventionNameUsed) {
|
|
963
|
+
return conventionName
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// `source` came from `handlerToString(fn)` on a real, possibly-minified
|
|
967
|
+
// function — its ENTIRE text is that one function's declaration, so
|
|
968
|
+
// whatever name follows the leading `function`/`async function` keyword
|
|
969
|
+
// is the name it was actually assigned at runtime.
|
|
970
|
+
const leadingDeclaration = source.match(/^(?:async\s+)?function\s*([A-Za-z0-9_$]+)\s*\(/)
|
|
971
|
+
if (leadingDeclaration) {
|
|
972
|
+
return leadingDeclaration[1]
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
throw new Error(
|
|
976
|
+
`generateNodeHandlerFile: could not resolve the entry function for workflow node type "${nodeType}" — ` +
|
|
977
|
+
`expected a "${conventionName}" declaration or a single toString()'d function, found neither.`
|
|
978
|
+
)
|
|
979
|
+
}
|
|
980
|
+
|
|
902
981
|
private getDefaultRouteUrl(uidl: ProjectUIDL, strategy: ProjectStrategy): string | null {
|
|
903
982
|
const routeDef = uidl.root?.stateDefinitions?.route
|
|
904
983
|
if (!routeDef?.values || !routeDef.defaultValue) {
|
|
@@ -972,6 +1051,10 @@ ${exports}
|
|
|
972
1051
|
(cn.nodes || []).map((n: any) => ({
|
|
973
1052
|
...n,
|
|
974
1053
|
config: redactServerNodeConfig(n.config, resolveNodeExecutionEnv(n)),
|
|
1054
|
+
// Runtime marker consumed by clientExecutableBranchNodes (client
|
|
1055
|
+
// runtime) so streaming on-stream/on-end branches never execute
|
|
1056
|
+
// server nodes (whose config was just redacted) client-side.
|
|
1057
|
+
executionEnv: resolveNodeExecutionEnv(n),
|
|
975
1058
|
}))
|
|
976
1059
|
)
|
|
977
1060
|
const edgesJson = JSON.stringify(cn.edges)
|