next-workflow-builder 0.4.7 → 0.5.0

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.
@@ -1038,6 +1038,142 @@ ${code}
1038
1038
  `;
1039
1039
  }
1040
1040
 
1041
+ // src/plugins/condition/operators.ts
1042
+ var DATA_TYPE_OPTIONS = [
1043
+ { value: "string", label: "String" },
1044
+ { value: "number", label: "Number" },
1045
+ { value: "boolean", label: "Boolean" },
1046
+ { value: "datetime", label: "Date & Time" }
1047
+ ];
1048
+ var OPERATORS = {
1049
+ string: [
1050
+ { value: "exists", label: "exists", unary: true },
1051
+ { value: "doesNotExist", label: "does not exist", unary: true },
1052
+ { value: "isEmpty", label: "is empty", unary: true },
1053
+ { value: "isNotEmpty", label: "is not empty", unary: true },
1054
+ { value: "equals", label: "equals", unary: false },
1055
+ { value: "notEquals", label: "does not equal", unary: false },
1056
+ { value: "contains", label: "contains", unary: false },
1057
+ { value: "doesNotContain", label: "does not contain", unary: false },
1058
+ { value: "startsWith", label: "starts with", unary: false },
1059
+ { value: "doesNotStartWith", label: "does not start with", unary: false },
1060
+ { value: "endsWith", label: "ends with", unary: false },
1061
+ { value: "doesNotEndWith", label: "does not end with", unary: false },
1062
+ { value: "matchesRegex", label: "matches regex", unary: false },
1063
+ { value: "doesNotMatchRegex", label: "does not match regex", unary: false }
1064
+ ],
1065
+ number: [
1066
+ { value: "equals", label: "equals", unary: false },
1067
+ { value: "notEquals", label: "does not equal", unary: false },
1068
+ { value: "greaterThan", label: "greater than", unary: false },
1069
+ { value: "lessThan", label: "less than", unary: false },
1070
+ { value: "greaterThanOrEqual", label: "greater than or equal", unary: false },
1071
+ { value: "lessThanOrEqual", label: "less than or equal", unary: false }
1072
+ ],
1073
+ boolean: [
1074
+ { value: "isTrue", label: "is true", unary: true },
1075
+ { value: "isFalse", label: "is false", unary: true },
1076
+ { value: "exists", label: "exists", unary: true },
1077
+ { value: "doesNotExist", label: "does not exist", unary: true }
1078
+ ],
1079
+ datetime: [
1080
+ { value: "isBefore", label: "is before", unary: false },
1081
+ { value: "isAfter", label: "is after", unary: false },
1082
+ { value: "equals", label: "equals", unary: false }
1083
+ ]
1084
+ };
1085
+ function evaluateOperator(dataType, operator, leftValue, rightValue) {
1086
+ switch (operator) {
1087
+ case "exists":
1088
+ return leftValue !== null && leftValue !== void 0;
1089
+ case "doesNotExist":
1090
+ return leftValue === null || leftValue === void 0;
1091
+ case "isEmpty":
1092
+ return leftValue === null || leftValue === void 0 || String(leftValue) === "";
1093
+ case "isNotEmpty":
1094
+ return leftValue !== null && leftValue !== void 0 && String(leftValue) !== "";
1095
+ case "isTrue":
1096
+ return Boolean(leftValue) === true;
1097
+ case "isFalse":
1098
+ return Boolean(leftValue) === false;
1099
+ }
1100
+ switch (dataType) {
1101
+ case "string": {
1102
+ const left = String(leftValue ?? "");
1103
+ const right = String(rightValue ?? "");
1104
+ switch (operator) {
1105
+ case "equals":
1106
+ return left === right;
1107
+ case "notEquals":
1108
+ return left !== right;
1109
+ case "contains":
1110
+ return left.includes(right);
1111
+ case "doesNotContain":
1112
+ return !left.includes(right);
1113
+ case "startsWith":
1114
+ return left.startsWith(right);
1115
+ case "doesNotStartWith":
1116
+ return !left.startsWith(right);
1117
+ case "endsWith":
1118
+ return left.endsWith(right);
1119
+ case "doesNotEndWith":
1120
+ return !left.endsWith(right);
1121
+ case "matchesRegex":
1122
+ try {
1123
+ return new RegExp(right).test(left);
1124
+ } catch {
1125
+ return false;
1126
+ }
1127
+ case "doesNotMatchRegex":
1128
+ try {
1129
+ return !new RegExp(right).test(left);
1130
+ } catch {
1131
+ return false;
1132
+ }
1133
+ }
1134
+ break;
1135
+ }
1136
+ case "number": {
1137
+ const left = Number(leftValue);
1138
+ const right = Number(rightValue);
1139
+ if (Number.isNaN(left) || Number.isNaN(right)) return false;
1140
+ switch (operator) {
1141
+ case "equals":
1142
+ return left === right;
1143
+ case "notEquals":
1144
+ return left !== right;
1145
+ case "greaterThan":
1146
+ return left > right;
1147
+ case "lessThan":
1148
+ return left < right;
1149
+ case "greaterThanOrEqual":
1150
+ return left >= right;
1151
+ case "lessThanOrEqual":
1152
+ return left <= right;
1153
+ }
1154
+ break;
1155
+ }
1156
+ case "boolean":
1157
+ break;
1158
+ case "datetime": {
1159
+ const left = new Date(leftValue).getTime();
1160
+ const right = new Date(rightValue).getTime();
1161
+ if (Number.isNaN(left) || Number.isNaN(right)) return false;
1162
+ switch (operator) {
1163
+ case "isBefore":
1164
+ return left < right;
1165
+ case "isAfter":
1166
+ return left > right;
1167
+ case "equals":
1168
+ return left === right;
1169
+ }
1170
+ break;
1171
+ }
1172
+ }
1173
+ console.warn(`[Condition] Unknown operator "${operator}" for data type "${dataType}"`);
1174
+ return false;
1175
+ }
1176
+
1041
1177
  // src/plugins/condition/index.tsx
1042
1178
  import { GitBranch } from "lucide-react";
1043
1179
  import { jsx } from "react/jsx-runtime";
@@ -1049,10 +1185,14 @@ var conditionAction = {
1049
1185
  icon: /* @__PURE__ */ jsx(GitBranch, { className: "size-12 text-pink-300", strokeWidth: 1.5 }),
1050
1186
  codeGenerator: `export async function conditionStep(input: {
1051
1187
  condition: boolean;
1188
+ dataType?: string;
1189
+ operator?: string;
1190
+ leftValue?: unknown;
1191
+ rightValue?: unknown;
1052
1192
  }) {
1053
1193
  "use step";
1054
-
1055
- // Evaluate condition
1194
+
1195
+ // Evaluate structured condition
1056
1196
  return { condition: input.condition };
1057
1197
  }`
1058
1198
  };
@@ -1156,6 +1296,9 @@ export {
1156
1296
  sanitizeVarName,
1157
1297
  generateWorkflowCode,
1158
1298
  generateWorkflowModule,
1299
+ DATA_TYPE_OPTIONS,
1300
+ OPERATORS,
1301
+ evaluateOperator,
1159
1302
  conditionAction,
1160
1303
  databaseQueryAction,
1161
1304
  httpRequestAction
@@ -1,10 +1,12 @@
1
1
  "use client";
2
2
  import {
3
+ DATA_TYPE_OPTIONS,
4
+ OPERATORS,
3
5
  conditionAction,
4
6
  databaseQueryAction,
5
7
  generateWorkflowCode,
6
8
  httpRequestAction
7
- } from "../chunk-MIBRBQIJ.js";
9
+ } from "../chunk-EMCA7GLF.js";
8
10
  import {
9
11
  cn
10
12
  } from "../chunk-5YYA34YV.js";
@@ -1176,6 +1178,166 @@ import { useAtomValue as useAtomValue5, useSetAtom as useSetAtom5 } from "jotai"
1176
1178
  import { HelpCircle as HelpCircle2, Plus as Plus3, Settings as Settings2 } from "lucide-react";
1177
1179
  import { useEffect as useEffect8, useMemo as useMemo4, useState as useState11 } from "react";
1178
1180
 
1181
+ // src/client/components/ui/select.tsx
1182
+ import { Select as SelectPrimitive } from "radix-ui";
1183
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
1184
+ import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
1185
+ function Select({
1186
+ ...props
1187
+ }) {
1188
+ return /* @__PURE__ */ jsx7(SelectPrimitive.Root, { "data-slot": "select", ...props });
1189
+ }
1190
+ function SelectGroup({
1191
+ ...props
1192
+ }) {
1193
+ return /* @__PURE__ */ jsx7(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
1194
+ }
1195
+ function SelectValue({
1196
+ ...props
1197
+ }) {
1198
+ return /* @__PURE__ */ jsx7(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
1199
+ }
1200
+ function SelectTrigger({
1201
+ className,
1202
+ size = "default",
1203
+ children,
1204
+ ...props
1205
+ }) {
1206
+ return /* @__PURE__ */ jsxs2(
1207
+ SelectPrimitive.Trigger,
1208
+ {
1209
+ "data-slot": "select-trigger",
1210
+ "data-size": size,
1211
+ className: cn(
1212
+ "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive bg-transparent hover:bg-accent/50 flex w-fit items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1213
+ className
1214
+ ),
1215
+ ...props,
1216
+ children: [
1217
+ children,
1218
+ /* @__PURE__ */ jsx7(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx7(ChevronDownIcon, { className: "size-4 opacity-50" }) })
1219
+ ]
1220
+ }
1221
+ );
1222
+ }
1223
+ function SelectContent({
1224
+ className,
1225
+ children,
1226
+ position = "popper",
1227
+ align = "center",
1228
+ ...props
1229
+ }) {
1230
+ return /* @__PURE__ */ jsx7(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs2(
1231
+ SelectPrimitive.Content,
1232
+ {
1233
+ "data-slot": "select-content",
1234
+ className: cn(
1235
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
1236
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
1237
+ className
1238
+ ),
1239
+ position,
1240
+ align,
1241
+ ...props,
1242
+ children: [
1243
+ /* @__PURE__ */ jsx7(SelectScrollUpButton, {}),
1244
+ /* @__PURE__ */ jsx7(
1245
+ SelectPrimitive.Viewport,
1246
+ {
1247
+ className: cn(
1248
+ "p-1",
1249
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
1250
+ ),
1251
+ children
1252
+ }
1253
+ ),
1254
+ /* @__PURE__ */ jsx7(SelectScrollDownButton, {})
1255
+ ]
1256
+ }
1257
+ ) });
1258
+ }
1259
+ function SelectLabel({
1260
+ className,
1261
+ ...props
1262
+ }) {
1263
+ return /* @__PURE__ */ jsx7(
1264
+ SelectPrimitive.Label,
1265
+ {
1266
+ "data-slot": "select-label",
1267
+ className: cn("text-muted-foreground px-2 py-1.5 text-xs", className),
1268
+ ...props
1269
+ }
1270
+ );
1271
+ }
1272
+ function SelectItem({
1273
+ className,
1274
+ children,
1275
+ ...props
1276
+ }) {
1277
+ return /* @__PURE__ */ jsxs2(
1278
+ SelectPrimitive.Item,
1279
+ {
1280
+ "data-slot": "select-item",
1281
+ className: cn(
1282
+ "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
1283
+ className
1284
+ ),
1285
+ ...props,
1286
+ children: [
1287
+ /* @__PURE__ */ jsx7("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx7(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx7(CheckIcon, { className: "size-4" }) }) }),
1288
+ /* @__PURE__ */ jsx7(SelectPrimitive.ItemText, { children })
1289
+ ]
1290
+ }
1291
+ );
1292
+ }
1293
+ function SelectSeparator({
1294
+ className,
1295
+ ...props
1296
+ }) {
1297
+ return /* @__PURE__ */ jsx7(
1298
+ SelectPrimitive.Separator,
1299
+ {
1300
+ "data-slot": "select-separator",
1301
+ className: cn("bg-border pointer-events-none -mx-1 my-1 h-px", className),
1302
+ ...props
1303
+ }
1304
+ );
1305
+ }
1306
+ function SelectScrollUpButton({
1307
+ className,
1308
+ ...props
1309
+ }) {
1310
+ return /* @__PURE__ */ jsx7(
1311
+ SelectPrimitive.ScrollUpButton,
1312
+ {
1313
+ "data-slot": "select-scroll-up-button",
1314
+ className: cn(
1315
+ "flex cursor-default items-center justify-center py-1",
1316
+ className
1317
+ ),
1318
+ ...props,
1319
+ children: /* @__PURE__ */ jsx7(ChevronUpIcon, { className: "size-4" })
1320
+ }
1321
+ );
1322
+ }
1323
+ function SelectScrollDownButton({
1324
+ className,
1325
+ ...props
1326
+ }) {
1327
+ return /* @__PURE__ */ jsx7(
1328
+ SelectPrimitive.ScrollDownButton,
1329
+ {
1330
+ "data-slot": "select-scroll-down-button",
1331
+ className: cn(
1332
+ "flex cursor-default items-center justify-center py-1",
1333
+ className
1334
+ ),
1335
+ ...props,
1336
+ children: /* @__PURE__ */ jsx7(ChevronDownIcon, { className: "size-4" })
1337
+ }
1338
+ );
1339
+ }
1340
+
1179
1341
  // src/client/components/ui/template-badge-input.tsx
1180
1342
  import { useAtom as useAtom2 } from "jotai";
1181
1343
  import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
@@ -1185,7 +1347,7 @@ import { useAtom } from "jotai";
1185
1347
  import { Check } from "lucide-react";
1186
1348
  import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
1187
1349
  import { createPortal } from "react-dom";
1188
- import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
1350
+ import { Fragment, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
1189
1351
  var getNodeDisplayName = (node) => {
1190
1352
  if (node.data.label) {
1191
1353
  return node.data.label;
@@ -1404,7 +1566,7 @@ function TemplateAutocomplete({
1404
1566
  left: Math.min(position.left, window.innerWidth - 320)
1405
1567
  // Keep menu (320px wide) within viewport
1406
1568
  };
1407
- const menuContent = /* @__PURE__ */ jsx7(
1569
+ const menuContent = /* @__PURE__ */ jsx8(
1408
1570
  "div",
1409
1571
  {
1410
1572
  className: "fixed z-[9999] w-80 rounded-lg border bg-popover p-1 text-popover-foreground shadow-md",
@@ -1413,7 +1575,7 @@ function TemplateAutocomplete({
1413
1575
  top: `${adjustedPosition.top}px`,
1414
1576
  left: `${adjustedPosition.left}px`
1415
1577
  },
1416
- children: /* @__PURE__ */ jsx7("div", { className: "max-h-60 overflow-y-auto", children: filteredOptions.map((option, index) => /* @__PURE__ */ jsxs2(
1578
+ children: /* @__PURE__ */ jsx8("div", { className: "max-h-60 overflow-y-auto", children: filteredOptions.map((option, index) => /* @__PURE__ */ jsxs3(
1417
1579
  "div",
1418
1580
  {
1419
1581
  className: cn(
@@ -1423,17 +1585,17 @@ function TemplateAutocomplete({
1423
1585
  onClick: () => onSelect(option.template),
1424
1586
  onMouseEnter: () => setSelectedIndex(index),
1425
1587
  children: [
1426
- /* @__PURE__ */ jsxs2("div", { className: "flex-1", children: [
1427
- /* @__PURE__ */ jsx7("div", { className: "font-medium", children: option.type === "node" ? option.nodeName : /* @__PURE__ */ jsxs2(Fragment, { children: [
1428
- /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground", children: [
1588
+ /* @__PURE__ */ jsxs3("div", { className: "flex-1", children: [
1589
+ /* @__PURE__ */ jsx8("div", { className: "font-medium", children: option.type === "node" ? option.nodeName : /* @__PURE__ */ jsxs3(Fragment, { children: [
1590
+ /* @__PURE__ */ jsxs3("span", { className: "text-muted-foreground", children: [
1429
1591
  option.nodeName,
1430
1592
  "."
1431
1593
  ] }),
1432
1594
  option.field
1433
1595
  ] }) }),
1434
- option.description && /* @__PURE__ */ jsx7("div", { className: "text-muted-foreground text-xs", children: option.description })
1596
+ option.description && /* @__PURE__ */ jsx8("div", { className: "text-muted-foreground text-xs", children: option.description })
1435
1597
  ] }),
1436
- index === selectedIndex && /* @__PURE__ */ jsx7(Check, { className: "h-4 w-4" })
1598
+ index === selectedIndex && /* @__PURE__ */ jsx8(Check, { className: "h-4 w-4" })
1437
1599
  ]
1438
1600
  },
1439
1601
  `${option.nodeId}-${option.field || "root"}`
@@ -1444,7 +1606,7 @@ function TemplateAutocomplete({
1444
1606
  }
1445
1607
 
1446
1608
  // src/client/components/ui/template-badge-input.tsx
1447
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
1609
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
1448
1610
  function doesNodeExist(template, nodes) {
1449
1611
  const match = template.match(/\{\{@([^:]+):([^}]+)\}\}/);
1450
1612
  if (!match) return false;
@@ -1795,8 +1957,8 @@ function TemplateBadgeInput({
1795
1957
  updateDisplay();
1796
1958
  }
1797
1959
  }, [internalValue, isFocused]);
1798
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1799
- /* @__PURE__ */ jsx8(
1960
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1961
+ /* @__PURE__ */ jsx9(
1800
1962
  "div",
1801
1963
  {
1802
1964
  className: cn(
@@ -1804,7 +1966,7 @@ function TemplateBadgeInput({
1804
1966
  disabled && "cursor-not-allowed opacity-50",
1805
1967
  className
1806
1968
  ),
1807
- children: /* @__PURE__ */ jsx8(
1969
+ children: /* @__PURE__ */ jsx9(
1808
1970
  "div",
1809
1971
  {
1810
1972
  className: "w-full outline-none",
@@ -1821,7 +1983,7 @@ function TemplateBadgeInput({
1821
1983
  )
1822
1984
  }
1823
1985
  ),
1824
- /* @__PURE__ */ jsx8(
1986
+ /* @__PURE__ */ jsx9(
1825
1987
  TemplateAutocomplete,
1826
1988
  {
1827
1989
  currentNodeId: selectedNodeId || void 0,
@@ -1836,193 +1998,88 @@ function TemplateBadgeInput({
1836
1998
  }
1837
1999
 
1838
2000
  // src/plugins/condition/fields.tsx
1839
- import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
2001
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
1840
2002
  function ConditionFields({
1841
2003
  config,
1842
2004
  onUpdateConfig,
1843
2005
  disabled
1844
2006
  }) {
1845
- return /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
1846
- /* @__PURE__ */ jsx9(Label, { htmlFor: "condition", children: "Condition Expression" }),
1847
- /* @__PURE__ */ jsx9(
1848
- TemplateBadgeInput,
1849
- {
1850
- disabled,
1851
- id: "condition",
1852
- onChange: (value) => onUpdateConfig("condition", value),
1853
- placeholder: "e.g., 5 > 3, status === 200, {{PreviousNode.value}} > 100",
1854
- value: config?.condition || ""
1855
- }
1856
- ),
1857
- /* @__PURE__ */ jsx9("p", { className: "text-muted-foreground text-xs", children: "Enter a JavaScript expression that evaluates to true or false. You can use @ to reference previous node outputs." })
2007
+ const dataType = config?.dataType || "string";
2008
+ const operator = config?.operator || OPERATORS[dataType][0].value;
2009
+ const operatorDefs = OPERATORS[dataType];
2010
+ const selectedOp = operatorDefs.find((op) => op.value === operator);
2011
+ const isUnary = selectedOp?.unary ?? false;
2012
+ function handleDataTypeChange(value) {
2013
+ onUpdateConfig("dataType", value);
2014
+ const newOps = OPERATORS[value];
2015
+ if (newOps?.[0]) {
2016
+ onUpdateConfig("operator", newOps[0].value);
2017
+ }
2018
+ }
2019
+ return /* @__PURE__ */ jsxs5("div", { className: "space-y-4", children: [
2020
+ /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
2021
+ /* @__PURE__ */ jsx10(Label, { htmlFor: "leftValue", children: "Value to Test" }),
2022
+ /* @__PURE__ */ jsx10(
2023
+ TemplateBadgeInput,
2024
+ {
2025
+ disabled,
2026
+ id: "leftValue",
2027
+ onChange: (value) => onUpdateConfig("leftValue", value),
2028
+ placeholder: "e.g., {{PreviousNode.status}}",
2029
+ value: config?.leftValue || ""
2030
+ }
2031
+ ),
2032
+ /* @__PURE__ */ jsx10("p", { className: "text-muted-foreground text-xs", children: "Use @ to reference previous node outputs." })
2033
+ ] }),
2034
+ /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
2035
+ /* @__PURE__ */ jsx10(Label, { htmlFor: "dataType", children: "Data Type" }),
2036
+ /* @__PURE__ */ jsxs5(
2037
+ Select,
2038
+ {
2039
+ disabled,
2040
+ onValueChange: handleDataTypeChange,
2041
+ value: dataType,
2042
+ children: [
2043
+ /* @__PURE__ */ jsx10(SelectTrigger, { id: "dataType", children: /* @__PURE__ */ jsx10(SelectValue, { placeholder: "Select data type" }) }),
2044
+ /* @__PURE__ */ jsx10(SelectContent, { children: DATA_TYPE_OPTIONS.map((opt) => /* @__PURE__ */ jsx10(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
2045
+ ]
2046
+ }
2047
+ )
2048
+ ] }),
2049
+ /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
2050
+ /* @__PURE__ */ jsx10(Label, { htmlFor: "operator", children: "Operator" }),
2051
+ /* @__PURE__ */ jsxs5(
2052
+ Select,
2053
+ {
2054
+ disabled,
2055
+ onValueChange: (value) => onUpdateConfig("operator", value),
2056
+ value: operator,
2057
+ children: [
2058
+ /* @__PURE__ */ jsx10(SelectTrigger, { id: "operator", children: /* @__PURE__ */ jsx10(SelectValue, { placeholder: "Select operator" }) }),
2059
+ /* @__PURE__ */ jsx10(SelectContent, { children: operatorDefs.map((op) => /* @__PURE__ */ jsx10(SelectItem, { value: op.value, children: op.label }, op.value)) })
2060
+ ]
2061
+ }
2062
+ )
2063
+ ] }),
2064
+ !isUnary && /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
2065
+ /* @__PURE__ */ jsx10(Label, { htmlFor: "rightValue", children: "Compare Value" }),
2066
+ /* @__PURE__ */ jsx10(
2067
+ TemplateBadgeInput,
2068
+ {
2069
+ disabled,
2070
+ id: "rightValue",
2071
+ onChange: (value) => onUpdateConfig("rightValue", value),
2072
+ placeholder: "e.g., 200 or {{OtherNode.field}}",
2073
+ value: config?.rightValue || ""
2074
+ }
2075
+ )
2076
+ ] })
1858
2077
  ] });
1859
2078
  }
1860
2079
 
1861
2080
  // src/client/components/workflow/config/schema-builder.tsx
1862
2081
  import { Plus, Trash2 } from "lucide-react";
1863
2082
  import { nanoid } from "nanoid";
1864
-
1865
- // src/client/components/ui/select.tsx
1866
- import { Select as SelectPrimitive } from "radix-ui";
1867
- import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
1868
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
1869
- function Select({
1870
- ...props
1871
- }) {
1872
- return /* @__PURE__ */ jsx10(SelectPrimitive.Root, { "data-slot": "select", ...props });
1873
- }
1874
- function SelectGroup({
1875
- ...props
1876
- }) {
1877
- return /* @__PURE__ */ jsx10(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
1878
- }
1879
- function SelectValue({
1880
- ...props
1881
- }) {
1882
- return /* @__PURE__ */ jsx10(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
1883
- }
1884
- function SelectTrigger({
1885
- className,
1886
- size = "default",
1887
- children,
1888
- ...props
1889
- }) {
1890
- return /* @__PURE__ */ jsxs5(
1891
- SelectPrimitive.Trigger,
1892
- {
1893
- "data-slot": "select-trigger",
1894
- "data-size": size,
1895
- className: cn(
1896
- "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive bg-transparent hover:bg-accent/50 flex w-fit items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1897
- className
1898
- ),
1899
- ...props,
1900
- children: [
1901
- children,
1902
- /* @__PURE__ */ jsx10(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx10(ChevronDownIcon, { className: "size-4 opacity-50" }) })
1903
- ]
1904
- }
1905
- );
1906
- }
1907
- function SelectContent({
1908
- className,
1909
- children,
1910
- position = "popper",
1911
- align = "center",
1912
- ...props
1913
- }) {
1914
- return /* @__PURE__ */ jsx10(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs5(
1915
- SelectPrimitive.Content,
1916
- {
1917
- "data-slot": "select-content",
1918
- className: cn(
1919
- "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
1920
- position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
1921
- className
1922
- ),
1923
- position,
1924
- align,
1925
- ...props,
1926
- children: [
1927
- /* @__PURE__ */ jsx10(SelectScrollUpButton, {}),
1928
- /* @__PURE__ */ jsx10(
1929
- SelectPrimitive.Viewport,
1930
- {
1931
- className: cn(
1932
- "p-1",
1933
- position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
1934
- ),
1935
- children
1936
- }
1937
- ),
1938
- /* @__PURE__ */ jsx10(SelectScrollDownButton, {})
1939
- ]
1940
- }
1941
- ) });
1942
- }
1943
- function SelectLabel({
1944
- className,
1945
- ...props
1946
- }) {
1947
- return /* @__PURE__ */ jsx10(
1948
- SelectPrimitive.Label,
1949
- {
1950
- "data-slot": "select-label",
1951
- className: cn("text-muted-foreground px-2 py-1.5 text-xs", className),
1952
- ...props
1953
- }
1954
- );
1955
- }
1956
- function SelectItem({
1957
- className,
1958
- children,
1959
- ...props
1960
- }) {
1961
- return /* @__PURE__ */ jsxs5(
1962
- SelectPrimitive.Item,
1963
- {
1964
- "data-slot": "select-item",
1965
- className: cn(
1966
- "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
1967
- className
1968
- ),
1969
- ...props,
1970
- children: [
1971
- /* @__PURE__ */ jsx10("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx10(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx10(CheckIcon, { className: "size-4" }) }) }),
1972
- /* @__PURE__ */ jsx10(SelectPrimitive.ItemText, { children })
1973
- ]
1974
- }
1975
- );
1976
- }
1977
- function SelectSeparator({
1978
- className,
1979
- ...props
1980
- }) {
1981
- return /* @__PURE__ */ jsx10(
1982
- SelectPrimitive.Separator,
1983
- {
1984
- "data-slot": "select-separator",
1985
- className: cn("bg-border pointer-events-none -mx-1 my-1 h-px", className),
1986
- ...props
1987
- }
1988
- );
1989
- }
1990
- function SelectScrollUpButton({
1991
- className,
1992
- ...props
1993
- }) {
1994
- return /* @__PURE__ */ jsx10(
1995
- SelectPrimitive.ScrollUpButton,
1996
- {
1997
- "data-slot": "select-scroll-up-button",
1998
- className: cn(
1999
- "flex cursor-default items-center justify-center py-1",
2000
- className
2001
- ),
2002
- ...props,
2003
- children: /* @__PURE__ */ jsx10(ChevronUpIcon, { className: "size-4" })
2004
- }
2005
- );
2006
- }
2007
- function SelectScrollDownButton({
2008
- className,
2009
- ...props
2010
- }) {
2011
- return /* @__PURE__ */ jsx10(
2012
- SelectPrimitive.ScrollDownButton,
2013
- {
2014
- "data-slot": "select-scroll-down-button",
2015
- className: cn(
2016
- "flex cursor-default items-center justify-center py-1",
2017
- className
2018
- ),
2019
- ...props,
2020
- children: /* @__PURE__ */ jsx10(ChevronDownIcon, { className: "size-4" })
2021
- }
2022
- );
2023
- }
2024
-
2025
- // src/client/components/workflow/config/schema-builder.tsx
2026
2083
  import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2027
2084
  function SchemaBuilder({
2028
2085
  schema,
@@ -5,13 +5,14 @@ import {
5
5
  conditionAction,
6
6
  databaseQueryAction,
7
7
  escapeForTemplateLiteral,
8
+ evaluateOperator,
8
9
  findTriggerNodes,
9
10
  generateWorkflowModule,
10
11
  httpRequestAction,
11
12
  sanitizeFunctionName,
12
13
  sanitizeStepName,
13
14
  sanitizeVarName
14
- } from "../../chunk-MIBRBQIJ.js";
15
+ } from "../../chunk-EMCA7GLF.js";
15
16
  import {
16
17
  auth,
17
18
  getAuthConfig
@@ -157,243 +158,6 @@ import { eq as eq2 } from "drizzle-orm";
157
158
  import { readdir, readFile } from "fs/promises";
158
159
  import { join } from "path";
159
160
 
160
- // src/server/lib/condition-validator.ts
161
- var DANGEROUS_PATTERNS = [
162
- // Assignment operators
163
- /(?<![=!<>])=(?!=)/g,
164
- // = but not ==, ===, !=, !==, <=, >=
165
- /\+=|-=|\*=|\/=|%=|\^=|\|=|&=/g,
166
- // Code execution
167
- /\beval\s*\(/gi,
168
- /\bFunction\s*\(/gi,
169
- /\bimport\s*\(/gi,
170
- /\brequire\s*\(/gi,
171
- /\bnew\s+\w/gi,
172
- // Dangerous globals
173
- /\bprocess\b/gi,
174
- /\bglobal\b/gi,
175
- /\bwindow\b/gi,
176
- /\bdocument\b/gi,
177
- /\bconstructor\b/gi,
178
- /\b__proto__\b/gi,
179
- /\bprototype\b/gi,
180
- // Control flow that could be exploited
181
- /\bwhile\s*\(/gi,
182
- /\bfor\s*\(/gi,
183
- /\bdo\s*\{/gi,
184
- /\bswitch\s*\(/gi,
185
- /\btry\s*\{/gi,
186
- /\bcatch\s*\(/gi,
187
- /\bfinally\s*\{/gi,
188
- /\bthrow\s+/gi,
189
- /\breturn\s+/gi,
190
- // Template literals with expressions (could execute code)
191
- /`[^`]*\$\{/g,
192
- // Object literals (but NOT bracket property access)
193
- /\{\s*\w+\s*:/g,
194
- // Increment/decrement
195
- /\+\+|--/g,
196
- // Bitwise operators (rarely needed, often used in exploits)
197
- /<<|>>|>>>/g,
198
- // Comma operator (can chain expressions)
199
- /,(?![^(]*\))/g,
200
- // Comma not inside function call parentheses
201
- // Semicolons (statement separator)
202
- /;/g
203
- ];
204
- var ALLOWED_METHODS = /* @__PURE__ */ new Set([
205
- "includes",
206
- "startsWith",
207
- "endsWith",
208
- "toString",
209
- "toLowerCase",
210
- "toUpperCase",
211
- "trim",
212
- "length"
213
- // Actually a property, but accessed like .length
214
- ]);
215
- var METHOD_CALL_PATTERN = /\.(\w+)\s*\(/g;
216
- var BRACKET_EXPRESSION_PATTERN = /(\w+)\s*\[([^\]]+)\]/g;
217
- var VALID_BRACKET_ACCESS_PATTERN = /^__v\d+$/;
218
- var VALID_BRACKET_CONTENT_PATTERN = /^(\d+|'[^']*'|"[^"]*")$/;
219
- var WHITESPACE_SPLIT_PATTERN = /\s+/;
220
- var VARIABLE_TOKEN_PATTERN = /^__v\d+/;
221
- var STRING_TOKEN_PATTERN = /^['"]/;
222
- var NUMBER_TOKEN_PATTERN = /^\d/;
223
- var LITERAL_TOKEN_PATTERN = /^(true|false|null|undefined)$/;
224
- var OPERATOR_TOKEN_PATTERN = /^(===|!==|==|!=|>=|<=|>|<|&&|\|\||!|\(|\))$/;
225
- var IDENTIFIER_TOKEN_PATTERN = /^[a-zA-Z_]\w*$/;
226
- function checkDangerousPatterns(expression) {
227
- for (const pattern of DANGEROUS_PATTERNS) {
228
- pattern.lastIndex = 0;
229
- if (pattern.test(expression)) {
230
- pattern.lastIndex = 0;
231
- const match = expression.match(pattern);
232
- return {
233
- valid: false,
234
- error: `Condition contains disallowed syntax: "${match?.[0] || "unknown"}"`
235
- };
236
- }
237
- }
238
- return { valid: true };
239
- }
240
- function checkBracketExpressions(expression) {
241
- BRACKET_EXPRESSION_PATTERN.lastIndex = 0;
242
- let match = null;
243
- while (true) {
244
- match = BRACKET_EXPRESSION_PATTERN.exec(expression);
245
- if (match === null) {
246
- break;
247
- }
248
- const beforeBracket = match[1];
249
- const insideBracket = match[2].trim();
250
- if (!VALID_BRACKET_ACCESS_PATTERN.test(beforeBracket)) {
251
- return {
252
- valid: false,
253
- error: `Bracket notation is only allowed on workflow variables. Found: "${beforeBracket}[...]"`
254
- };
255
- }
256
- if (!VALID_BRACKET_CONTENT_PATTERN.test(insideBracket)) {
257
- return {
258
- valid: false,
259
- error: `Invalid bracket content: "[${insideBracket}]". Only numeric indices or string literals are allowed.`
260
- };
261
- }
262
- }
263
- const standaloneArrayPattern = /(?:^|[=!<>&|(\s])\s*\[/g;
264
- standaloneArrayPattern.lastIndex = 0;
265
- if (standaloneArrayPattern.test(expression)) {
266
- return {
267
- valid: false,
268
- error: "Array literals are not allowed in conditions. Use workflow variables instead."
269
- };
270
- }
271
- return { valid: true };
272
- }
273
- function checkMethodCalls(expression) {
274
- METHOD_CALL_PATTERN.lastIndex = 0;
275
- let match = null;
276
- while (true) {
277
- match = METHOD_CALL_PATTERN.exec(expression);
278
- if (match === null) {
279
- break;
280
- }
281
- const methodName = match[1];
282
- if (!ALLOWED_METHODS.has(methodName)) {
283
- return {
284
- valid: false,
285
- error: `Method "${methodName}" is not allowed in conditions. Allowed methods: ${Array.from(ALLOWED_METHODS).join(", ")}`
286
- };
287
- }
288
- }
289
- return { valid: true };
290
- }
291
- function checkParentheses(expression) {
292
- let parenDepth = 0;
293
- for (const char of expression) {
294
- if (char === "(") {
295
- parenDepth += 1;
296
- }
297
- if (char === ")") {
298
- parenDepth -= 1;
299
- }
300
- if (parenDepth < 0) {
301
- return { valid: false, error: "Unbalanced parentheses in condition" };
302
- }
303
- }
304
- if (parenDepth !== 0) {
305
- return { valid: false, error: "Unbalanced parentheses in condition" };
306
- }
307
- return { valid: true };
308
- }
309
- function isValidToken(token) {
310
- if (VARIABLE_TOKEN_PATTERN.test(token)) {
311
- return true;
312
- }
313
- if (STRING_TOKEN_PATTERN.test(token)) {
314
- return true;
315
- }
316
- if (NUMBER_TOKEN_PATTERN.test(token)) {
317
- return true;
318
- }
319
- if (LITERAL_TOKEN_PATTERN.test(token)) {
320
- return true;
321
- }
322
- if (OPERATOR_TOKEN_PATTERN.test(token)) {
323
- return true;
324
- }
325
- return false;
326
- }
327
- function checkUnauthorizedIdentifiers(expression) {
328
- const tokens = expression.split(WHITESPACE_SPLIT_PATTERN).filter(Boolean);
329
- for (const token of tokens) {
330
- if (isValidToken(token)) {
331
- continue;
332
- }
333
- if (IDENTIFIER_TOKEN_PATTERN.test(token) && !token.startsWith("__v")) {
334
- return {
335
- valid: false,
336
- error: `Unknown identifier "${token}" in condition. Use template variables like {{@nodeId:Label.field}} to reference workflow data.`
337
- };
338
- }
339
- }
340
- return { valid: true };
341
- }
342
- function validateConditionExpression(expression) {
343
- if (!expression || expression.trim() === "") {
344
- return { valid: false, error: "Condition expression cannot be empty" };
345
- }
346
- const dangerousCheck = checkDangerousPatterns(expression);
347
- if (!dangerousCheck.valid) {
348
- return dangerousCheck;
349
- }
350
- const bracketCheck = checkBracketExpressions(expression);
351
- if (!bracketCheck.valid) {
352
- return bracketCheck;
353
- }
354
- const methodCheck = checkMethodCalls(expression);
355
- if (!methodCheck.valid) {
356
- return methodCheck;
357
- }
358
- const parenCheck = checkParentheses(expression);
359
- if (!parenCheck.valid) {
360
- return parenCheck;
361
- }
362
- const identifierCheck = checkUnauthorizedIdentifiers(expression);
363
- if (!identifierCheck.valid) {
364
- return identifierCheck;
365
- }
366
- return { valid: true };
367
- }
368
- function preValidateConditionExpression(expression) {
369
- if (!expression || typeof expression !== "string") {
370
- return { valid: false, error: "Condition must be a non-empty string" };
371
- }
372
- const dangerousKeywords = [
373
- "eval",
374
- "Function",
375
- "import",
376
- "require",
377
- "process",
378
- "global",
379
- "window",
380
- "document",
381
- "__proto__",
382
- "constructor",
383
- "prototype"
384
- ];
385
- const lowerExpression = expression.toLowerCase();
386
- for (const keyword of dangerousKeywords) {
387
- if (lowerExpression.includes(keyword.toLowerCase())) {
388
- return {
389
- valid: false,
390
- error: `Condition contains disallowed keyword: "${keyword}"`
391
- };
392
- }
393
- }
394
- return { valid: true };
395
- }
396
-
397
161
  // src/server/lib/steps/trigger.ts
398
162
  import "server-only";
399
163
  function executeTrigger(input) {
@@ -445,20 +209,18 @@ var SYSTEM_ACTIONS = {
445
209
  stepFunction: "mergeStep"
446
210
  }
447
211
  };
448
- function replaceTemplateVariable(match, nodeId, rest, outputs, evalContext, varCounter) {
449
- const sanitizedNodeId = nodeId.replace(/[^a-zA-Z0-9]/g, "_");
450
- const output = outputs[sanitizedNodeId];
451
- if (!output) {
452
- console.log("[Condition] Output not found for node:", sanitizedNodeId);
453
- return match;
454
- }
455
- const dotIndex = rest.indexOf(".");
456
- let value;
457
- if (dotIndex === -1) {
458
- value = output.data;
459
- } else if (output.data === null || output.data === void 0) {
460
- value = void 0;
461
- } else {
212
+ function resolveTemplateValue(value, outputs) {
213
+ if (value === void 0 || value === "") return void 0;
214
+ const templatePattern = /\{\{@([^:]+):([^}]+)\}\}/g;
215
+ const matches = [...value.matchAll(templatePattern)];
216
+ if (matches.length === 0) return value;
217
+ function extractValue(nodeId, rest) {
218
+ const sanitizedNodeId = nodeId.replace(/[^a-zA-Z0-9]/g, "_");
219
+ const output = outputs[sanitizedNodeId];
220
+ if (!output) return void 0;
221
+ const dotIndex = rest.indexOf(".");
222
+ if (dotIndex === -1) return output.data;
223
+ if (output.data === null || output.data === void 0) return void 0;
462
224
  const fieldPath = rest.substring(dotIndex + 1);
463
225
  const fields = fieldPath.split(".");
464
226
  let current = output.data;
@@ -470,78 +232,37 @@ function replaceTemplateVariable(match, nodeId, rest, outputs, evalContext, varC
470
232
  if (current && typeof current === "object") {
471
233
  current = current[field];
472
234
  } else {
473
- console.log("[Condition] Field access failed:", fieldPath);
474
- value = void 0;
475
- break;
235
+ return void 0;
476
236
  }
477
237
  }
478
- if (value === void 0 && current !== void 0) {
479
- value = current;
480
- }
238
+ return current;
481
239
  }
482
- const varName = `__v${varCounter.value}`;
483
- varCounter.value += 1;
484
- evalContext[varName] = value;
485
- return varName;
486
- }
487
- function evaluateConditionExpression(conditionExpression, outputs) {
488
- console.log("[Condition] Original expression:", conditionExpression);
489
- if (typeof conditionExpression === "boolean") {
490
- return { result: conditionExpression, resolvedValues: {} };
240
+ if (matches.length === 1 && matches[0][0] === value) {
241
+ return extractValue(matches[0][1], matches[0][2]);
491
242
  }
492
- if (typeof conditionExpression === "string") {
493
- const preValidation = preValidateConditionExpression(conditionExpression);
494
- if (!preValidation.valid) {
495
- console.error("[Condition] Pre-validation failed:", preValidation.error);
496
- console.error("[Condition] Expression was:", conditionExpression);
497
- return { result: false, resolvedValues: {} };
498
- }
499
- try {
500
- const evalContext = {};
501
- const resolvedValues = {};
502
- let transformedExpression = conditionExpression;
503
- const templatePattern = /\{\{@([^:]+):([^}]+)\}\}/g;
504
- const varCounter = { value: 0 };
505
- transformedExpression = transformedExpression.replace(
506
- templatePattern,
507
- (match, nodeId, rest) => {
508
- const varName = replaceTemplateVariable(
509
- match,
510
- nodeId,
511
- rest,
512
- outputs,
513
- evalContext,
514
- varCounter
515
- );
516
- resolvedValues[rest] = evalContext[varName];
517
- return varName;
518
- }
519
- );
520
- const validation = validateConditionExpression(transformedExpression);
521
- if (!validation.valid) {
522
- console.error("[Condition] Validation failed:", validation.error);
523
- console.error("[Condition] Original expression:", conditionExpression);
524
- console.error(
525
- "[Condition] Transformed expression:",
526
- transformedExpression
527
- );
528
- return { result: false, resolvedValues };
529
- }
530
- const varNames = Object.keys(evalContext);
531
- const varValues = Object.values(evalContext);
532
- const evalFunc = new Function(
533
- ...varNames,
534
- `return (${transformedExpression});`
535
- );
536
- const result = evalFunc(...varValues);
537
- return { result: Boolean(result), resolvedValues };
538
- } catch (error) {
539
- console.error("[Condition] Failed to evaluate condition:", error);
540
- console.error("[Condition] Expression was:", conditionExpression);
541
- return { result: false, resolvedValues: {} };
243
+ return value.replace(templatePattern, (match, nodeId, rest) => {
244
+ const val = extractValue(nodeId, rest);
245
+ if (val === null || val === void 0) return "";
246
+ if (typeof val === "object") return JSON.stringify(val);
247
+ return String(val);
248
+ });
249
+ }
250
+ function evaluateStructuredCondition(config, outputs) {
251
+ const dataType = config.dataType || "string";
252
+ const operator = config.operator;
253
+ const leftRaw = config.leftValue;
254
+ const rightRaw = config.rightValue;
255
+ const leftResolved = resolveTemplateValue(leftRaw, outputs);
256
+ const rightResolved = resolveTemplateValue(rightRaw, outputs);
257
+ console.log("[Condition] Evaluating:", { dataType, operator, leftResolved, rightResolved });
258
+ const result = evaluateOperator(dataType, operator, leftResolved, rightResolved);
259
+ return {
260
+ result,
261
+ resolvedValues: {
262
+ leftValue: leftResolved,
263
+ rightValue: rightResolved
542
264
  }
543
- }
544
- return { result: Boolean(conditionExpression), resolvedValues: {} };
265
+ };
545
266
  }
546
267
  async function executeActionStep(input) {
547
268
  const { actionType, config, outputs, context } = input;
@@ -552,14 +273,14 @@ async function executeActionStep(input) {
552
273
  if (actionType === "Condition") {
553
274
  const systemAction2 = SYSTEM_ACTIONS.Condition;
554
275
  const module = await systemAction2.importer();
555
- const originalExpression = stepInput.condition;
556
- const { result: evaluatedCondition, resolvedValues } = evaluateConditionExpression(originalExpression, outputs);
276
+ const { result: evaluatedCondition, resolvedValues } = evaluateStructuredCondition(config, outputs);
557
277
  console.log("[Condition] Final result:", evaluatedCondition);
558
278
  return await module[systemAction2.stepFunction]({
559
279
  condition: evaluatedCondition,
560
- // Include original expression and resolved values for logging purposes
561
- expression: typeof originalExpression === "string" ? originalExpression : void 0,
562
- values: Object.keys(resolvedValues).length > 0 ? resolvedValues : void 0,
280
+ dataType: config.dataType,
281
+ operator: config.operator,
282
+ leftValue: resolvedValues.leftValue,
283
+ rightValue: resolvedValues.rightValue,
563
284
  _context: context
564
285
  });
565
286
  }
@@ -776,16 +497,7 @@ async function executeWorkflow(input) {
776
497
  results[nodeId] = result;
777
498
  return;
778
499
  }
779
- const configWithoutCondition = { ...config };
780
- const originalCondition = config.condition;
781
- configWithoutCondition.condition = void 0;
782
- const processedConfig = processTemplates(
783
- configWithoutCondition,
784
- outputs
785
- );
786
- if (originalCondition !== void 0) {
787
- processedConfig.condition = originalCondition;
788
- }
500
+ const processedConfig = processTemplates(config, outputs);
789
501
  const stepContext = {
790
502
  executionId,
791
503
  nodeId: node.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-workflow-builder",
3
- "version": "0.4.7",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Next.js plugin for Workflow Builder",
6
6
  "repository": "https://github.com/emulienfou/next-workflow-builder",