next-workflow-builder 0.7.4 → 0.7.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # next-workflow-builder
2
2
 
3
+ ## 0.7.6
4
+
5
+ ### Features
6
+
7
+ - Add **Enum** field type to the Webhook payload schema builder — allows defining a fixed set of allowed values with a configurable value type (`string`, `number`, or `boolean`). Enum values are displayed in template autocomplete as `enum<value1 | value2 | ...>`
8
+ - Make **Switch** plugin routes fully dynamic — users can now add and remove routes with no upper limit (minimum 1). Previously hardcoded to exactly 4 routes. Existing workflows with 4 routes continue to work without migration
9
+ - Add **Switch node routing** — Switch nodes now display multiple output handles (one per route + Default) on the right side. Each handle can be connected to a different downstream node. The executor routes execution only to the matched route's connected nodes based on `sourceHandle` IDs on edges
10
+ - Add **Condition node routing** — Condition nodes now display two output handles: **True** and **False**. Each can be connected to different downstream nodes for branching. Backwards compatible with existing workflows (legacy edges without handles still work as before)
11
+ - Add **Switch route condition evaluation** — route conditions in rules mode are now properly evaluated as expressions (supports `===`, `!==`, `>`, `<`, `>=`, `<=`) instead of being passed as raw strings
12
+ - Fix **Switch** code generation showing `unknownStep` — Switch was missing from `SYSTEM_ACTION_TEMPLATES`, now correctly generates `switchStep` code
13
+ - Make **Merge** plugin inputs fully dynamic — users can now add and remove inputs (minimum 2, no upper limit). Previously hardcoded to exactly 2 inputs. Merge nodes display multiple labeled target handles on the left side, one per input. The executor maps incoming edges by `targetHandle` to the correct input slot. Existing workflows with 2 inputs continue to work without migration
14
+
15
+ ### Bug Fixes
16
+
17
+ - Fix **Condition** Data Type select being stuck — changing the data type called `onUpdateConfig` twice in rapid succession, causing the second call to overwrite the first with stale state. Now batches both `dataType` and `operator` updates into a single atomic config change via `onUpdateMultipleConfig`
18
+
19
+ ## 0.7.5
20
+
21
+ ### Bug Fixes
22
+
23
+ - Fix **Clear** and **Delete** buttons on the Workflow Panel doing nothing when clicked — they were setting unused Jotai atoms instead of opening confirmation overlays. Now uses the same `ConfirmOverlay` pattern as the toolbar
24
+ - Fix **Clear Workflow** not persisting — `clearWorkflowAtom` was missing the `autosaveAtom` trigger, so cleared state was never saved to the database
25
+ - Fix **Clear Workflow** removing the Trigger node — now preserves trigger nodes, consistent with delete-node and multi-select delete behavior
26
+ - Fix deleting a workflow failing with a foreign key constraint error when the workflow has execution history — execution logs and executions are now deleted before the workflow itself
27
+
3
28
  ## 0.7.4
4
29
 
5
30
  ### Features
@@ -0,0 +1 @@
1
+ import{c as L}from"./chunk-3YVRTDK2.js";import{b as V}from"./chunk-6UXAINJQ.js";import{k as z,o as q,p as B,q as H}from"./chunk-5J6TNMJG.js";import{eq as K}from"drizzle-orm";import"server-only";function Q(s){return{success:!0,data:s.triggerData}}async function A(s){"use step";return s._workflowComplete?(await B(s._workflowComplete),{success:!0,data:{}}):H(s,()=>Promise.resolve(Q(s)))}A.maxRetries=0;var _={"Database Query":{importer:()=>import("./database-query-OHFQUPLV.js"),stepFunction:"databaseQueryStep"},"HTTP Request":{importer:()=>import("./http-request-EHJHOTNA.js"),stepFunction:"httpRequestStep"},Condition:{importer:()=>import("./condition-CFAA7UDI.js"),stepFunction:"conditionStep"},Loop:{importer:()=>import("./loop-5LPVY452.js"),stepFunction:"loopStep"},Switch:{importer:()=>import("./switch-V4UFKFL5.js"),stepFunction:"switchStep"},Merge:{importer:()=>import("./merge-LQKF2JKM.js"),stepFunction:"mergeStep"},"Run Workflow":{importer:()=>import("./run-workflow-EAXBE7Z5.js"),stepFunction:"runWorkflowStep"},"Run Workflows in Sequence":{importer:()=>import("./run-workflows-in-sequence-X4A77MVY.js"),stepFunction:"runWorkflowsInSequenceStep"}};function Z(s,x){if(s===void 0||s==="")return;let c=/\{\{@([^:]+):([^}]+)\}\}/g,p=[...s.matchAll(c)];if(p.length===0)return s;function u(E,n){let l=E.replace(/[^a-zA-Z0-9]/g,"_"),d=x[l];if(!d)return;let a=n.indexOf(".");if(a===-1)return d.data;if(d.data===null||d.data===void 0)return;let i=n.substring(a+1).split("."),o=d.data,S=i[0];o&&typeof o=="object"&&"success"in o&&"data"in o&&S!=="success"&&S!=="data"&&S!=="error"&&!(S in o)&&(o=o.data);for(let k of i)if(o&&typeof o=="object")o=o[k];else return;return o}return p.length===1&&p[0][0]===s?u(p[0][1],p[0][2]):s.replace(c,(E,n,l)=>{let d=u(n,l);return d==null?"":typeof d=="object"?JSON.stringify(d):String(d)})}function Y(s,x){let c=s.dataType||"string",p=s.operator,u=s.leftValue,E=s.rightValue,n=Z(u,x),l=Z(E,x);return console.log("[Condition] Evaluating:",{dataType:c,operator:p,leftResolved:n,rightResolved:l}),{result:L(c,p,n,l),resolvedValues:{leftValue:n,rightValue:l}}}function G(s){let x=/^(.+?)\s*(===|!==|==|!=|>=|<=|>|<)\s*(.+)$/,c=s.trim().match(x);if(!c){let k=s.trim();return k!==""&&k!=="false"&&k!=="0"&&k!=="null"&&k!=="undefined"}let[,p,u,E]=c,n=p.trim(),l=E.trim(),d=k=>k.startsWith('"')&&k.endsWith('"')||k.startsWith("'")&&k.endsWith("'")?k.slice(1,-1):k,a=d(n),g=d(l),i=Number(a),o=Number(g),S=!Number.isNaN(i)&&!Number.isNaN(o)&&a!==""&&g!=="";switch(u){case"===":case"==":return a===g;case"!==":case"!=":return a!==g;case">":return S?i>o:a>g;case"<":return S?i<o:a<g;case">=":return S?i>=o:a>=g;case"<=":return S?i<=o:a<=g;default:return!1}}async function U(s){let{actionType:x,config:c,outputs:p,context:u}=s,E={...c,_context:u};if(x==="Condition"){let a=_.Condition,g=await a.importer(),{result:i,resolvedValues:o}=Y(c,p);return console.log("[Condition] Final result:",i),await g[a.stepFunction]({condition:i,dataType:c.dataType,operator:c.operator,leftValue:o.leftValue,rightValue:o.rightValue,_context:u})}if(x==="Switch"&&(c.mode||"rules")==="rules"){let g=Number(c.routeCount)||4;for(let i=0;i<g;i++){let o=`routeCondition${i}`,S=E[o];typeof S=="string"&&S.trim()!==""&&(E[o]=G(S))}}let n=_[x];if(n){let g=(await n.importer())[n.stepFunction];return await g(E)}let{getStepImporter:l}=await import("virtual:workflow-builder-step-registry"),d=l(x);if(d){let g=(await d.importer())[d.stepFunction];return g?await g(E):{success:!1,error:`Step function "${d.stepFunction}" not found in module for action "${x}". Check that the plugin exports the correct function name.`}}return{success:!1,error:`Unknown action type: "${x}". This action is not registered in the plugin system. Available system actions: ${Object.keys(_).join(", ")}.`}}function J(s,x){let c={};for(let[p,u]of Object.entries(s))if(typeof u=="string"){let E=u,n=/\{\{@([^:]+):([^}]+)\}\}/g;E=E.replace(n,(l,d,a)=>{let g=d.replace(/[^a-zA-Z0-9]/g,"_"),i=x[g];if(!i)return l;let o=a.indexOf(".");if(o===-1){let N=i.data;return N==null?"":typeof N=="object"?JSON.stringify(N):String(N)}if(i.data===null||i.data===void 0)return"";let k=a.substring(o+1).split("."),m=i.data,F=k[0];m&&typeof m=="object"&&"success"in m&&"data"in m&&F!=="success"&&F!=="data"&&F!=="error"&&!(F in m)&&(m=m.data);for(let N of k)if(m&&typeof m=="object")m=m[N];else return"";return m==null?"":typeof m=="object"?JSON.stringify(m):String(m)}),c[p]=E}else c[p]=u;return c}async function ce(s){"use workflow";console.log("[Workflow Executor] Starting workflow execution");let{nodes:x,edges:c,triggerInput:p={},executionId:u,workflowId:E}=s;console.log("[Workflow Executor] Input:",{nodeCount:x.length,edgeCount:c.length,hasExecutionId:!!u,workflowId:E||"none"});let n={},l={},d=new Map(x.map(e=>[e.id,e])),a=new Map,g=new Map,i=new Map;for(let e of c){let f=a.get(e.source)||[];f.push(e.target),a.set(e.source,f);let t=g.get(e.target)||[];if(t.push(e.source),g.set(e.target,t),e.sourceHandle){let r=`${e.source}:${e.sourceHandle}`,j=i.get(r)||[];j.push(e.target),i.set(r,j)}}let o=new Set(c.map(e=>e.target)),S=x.filter(e=>e.data.type==="trigger"&&!o.has(e.id));console.log("[Workflow Executor] Found",S.length,"trigger nodes");let{getActionLabel:k}=await import("virtual:workflow-builder-step-registry");function m(e){if(e.data.label)return e.data.label;if(e.data.type==="action"){let f=e.data.config?.actionType;if(f){let t=k(f);if(t)return t}return"Action"}return e.data.type==="trigger"?e.data.config?.triggerType||"Trigger":e.data.type}async function F(){return u?(await q.query.workflowExecutions.findFirst({where:K(z.id,u),columns:{status:!0}}))?.status==="cancelled":!1}async function N(e,f=new Set){if(console.log("[Workflow Executor] Executing node:",e),await F()){console.log("[Workflow Executor] Execution cancelled, stopping");return}if(f.has(e)){console.log("[Workflow Executor] Node already visited, skipping");return}f.add(e);let t=d.get(e);if(!t){console.log("[Workflow Executor] Node not found:",e);return}if(t.data.enabled===!1){console.log("[Workflow Executor] Skipping disabled node:",e);let r=e.replace(/[^a-zA-Z0-9]/g,"_");n[r]={label:t.data.label||e,data:null};let j=a.get(e)||[];await Promise.all(j.map(R=>N(R,f)));return}try{let r;if(t.data.type==="trigger"){console.log("[Workflow Executor] Executing trigger node");let R=t.data.config||{},P=R.triggerType,O={triggered:!0,timestamp:Date.now()};if(P==="Webhook"&&R.webhookMockRequest&&(!p||Object.keys(p).length===0))try{let W=JSON.parse(R.webhookMockRequest);O={...O,...W},console.log("[Workflow Executor] Using webhook mock request data:",W)}catch(W){console.error("[Workflow Executor] Failed to parse webhook mock request:",W)}else p&&Object.keys(p).length>0&&(O={...O,...p});let b={executionId:u,nodeId:t.id,nodeName:m(t),nodeType:t.data.type},w=await A({triggerData:O,_context:b});r={success:w.success,data:w.data}}else if(t.data.type==="action"){let R=t.data.config||{},P=R.actionType;if(console.log("[Workflow Executor] Executing action node:",P),!P){r={success:!1,error:`Action node "${t.data.label||t.id}" has no action type configured`},l[e]=r;return}let O=J(R,n),b={executionId:u,nodeId:t.id,nodeName:m(t),nodeType:P},w={};if(P==="Merge"){let I=c.filter(T=>T.target===e);for(let T of I){let $=T.source.replace(/[^a-zA-Z0-9]/g,"_"),M=n[$];if(!M?.data)continue;let y=M.data;if("success"in y&&"data"in y&&y.data&&(y=y.data),T.targetHandle){let C=Number.parseInt(T.targetHandle.replace("input-",""),10);Number.isNaN(C)||(w[`input${C+1}`]=y)}else if(typeof y=="object")for(let[C,v]of Object.entries(y))C!=="success"&&C!=="error"&&(w[C]=v)}}else{let I=g.get(e)||[];for(let T of I){let $=T.replace(/[^a-zA-Z0-9]/g,"_"),M=n[$];if(M?.data&&typeof M.data=="object"){let y=M.data;"success"in y&&"data"in y&&y.data&&typeof y.data=="object"&&(y=y.data);for(let[C,v]of Object.entries(y))C!=="success"&&C!=="error"&&(w[C]=v)}}}console.log("[Workflow Executor] Calling executeActionStep");let h=await U({actionType:P,config:{...w,...O},outputs:n,context:b});if(console.log("[Workflow Executor] Step result received:",{hasResult:!!h,resultType:typeof h}),h&&typeof h=="object"&&"success"in h&&h.success===!1){let I=h,T=typeof I.error=="string"?I.error:I.error?.message||`Step "${P}" in node "${t.data.label||t.id}" failed without a specific error message.`;console.error(`[Workflow Executor] Step "${P}" failed:`,T),r={success:!1,error:T}}else r={success:!0,data:h}}else console.log("[Workflow Executor] Unknown node type:",t.data.type),r={success:!1,error:`Unknown node type "${t.data.type}" in node "${t.data.label||t.id}". Expected "trigger" or "action".`};l[e]=r;let j=e.replace(/[^a-zA-Z0-9]/g,"_");if(n[j]={label:t.data.label||e,data:r.data},console.log("[Workflow Executor] Node execution completed:",{nodeId:e,success:r.success}),r.success){let R=t.data.type==="action"&&t.data.config?.actionType==="Condition",P=t.data.type==="action"&&t.data.config?.actionType==="Switch",O=t.data.type==="action"&&t.data.config?.actionType==="Loop";if(R){let b=r.data?.condition;console.log("[Workflow Executor] Condition node result:",b);let w=b?"condition-true":"condition-false",W=i.get(`${e}:${w}`)||[];if(W.length>0)console.log("[Workflow Executor] Condition routing via handle:",w,"executing",W.length,"next nodes"),await Promise.all(W.map(h=>N(h,f)));else if(b===!0){let h=a.get(e)||[];console.log("[Workflow Executor] Condition is true (legacy), executing",h.length,"next nodes in parallel"),await Promise.all(h.map(D=>N(D,f)))}else console.log("[Workflow Executor] Condition is false, skipping next nodes")}else if(P){let b=r.data,w=b?.matchedRouteIndex??-1,h=b?.isDefault??!0?"route-default":`route-${w}`,D=i.get(`${e}:${h}`)||[];console.log("[Workflow Executor] Switch node matched route:",h,"executing",D.length,"next nodes"),D.length>0&&await Promise.all(D.map(I=>N(I,f)))}else if(O){let b=a.get(e)||[],w=r.data;if(w&&b.length>0){let W=new Set(f),h=0;for(;;){console.log("[Workflow Executor] Loop iteration",h,"- batchIndex:",w.currentBatchIndex,"hasMore:",w.hasMore);let D=new Set(W);if(await Promise.all(b.map(C=>N(C,D))),!w.hasMore)break;if(await F()){console.log("[Workflow Executor] Loop cancelled between iterations");break}let I=w.currentBatchIndex+1,T=t.data.config||{},$=J(T,n),M={executionId:u,nodeId:t.id,nodeName:m(t),nodeType:"Loop"},y=await U({actionType:"Loop",config:{...$,currentBatchIndex:I},outputs:n,context:M});n[j]={label:t.data.label||e,data:y},l[e]={success:!0,data:y},w=y,h++}}else b.length>0&&await Promise.all(b.map(W=>N(W,f)))}else{let b=a.get(e)||[];console.log("[Workflow Executor] Executing",b.length,"next nodes in parallel"),await Promise.all(b.map(w=>N(w,f)))}}}catch(r){console.error("[Workflow Executor] Error executing node:",e,r);let R={success:!1,error:await V(r)};l[e]=R}}try{console.log("[Workflow Executor] Starting execution from trigger nodes");let e=Date.now();await Promise.all(S.map(r=>N(r.id)));let f=Object.values(l).every(r=>r.success),t=Date.now()-e;if(console.log("[Workflow Executor] Workflow execution completed:",{success:f,resultCount:Object.keys(l).length,duration:t}),u&&!await F())try{await A({triggerData:{},_workflowComplete:{executionId:u,status:f?"success":"error",output:Object.values(l).at(-1)?.data,error:Object.values(l).find(r=>!r.success)?.error,startTime:e}}),console.log("[Workflow Executor] Updated execution record")}catch(r){console.error("[Workflow Executor] Failed to update execution record:",r)}return{success:f,results:l,outputs:n}}catch(e){console.error("[Workflow Executor] Fatal error during workflow execution:",e);let f=await V(e);if(u)try{await A({triggerData:{},_workflowComplete:{executionId:u,status:"error",error:f,startTime:Date.now()}})}catch(t){console.error("[Workflow Executor] Failed to log error:",t)}return{success:!1,results:l,outputs:n,error:f}}}export{ce as a};
@@ -1,4 +1,4 @@
1
- import{a as k}from"./chunk-WT4BSMUD.js";import{j as l}from"./chunk-VUDOAZ3W.js";import{a as m,b as w}from"./chunk-IEOZJAW2.js";import{l as d,m as p}from"./chunk-BNX2SV7E.js";import{r as f,t as u}from"./chunk-QRG4O4PE.js";import{k as c,o as g}from"./chunk-5J6TNMJG.js";async function b(e){let r=m();if(r.getUser)return r.getUser(e);let t=await w.api.getSession({headers:e.headers});return t?.user?{id:t.user.id,email:t.user.email??void 0,name:t.user.name??void 0}:null}import{eq as x}from"drizzle-orm";import{readdir as h,readFile as R}from"fs/promises";import{join as y}from"path";var T={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization"};function _(e){let t=new URL(e.url).pathname,n="/api/workflow-builder/",s=t.indexOf(n);if(s===-1){let i=t.indexOf("/api/");return i===-1?[]:t.slice(i+5).split("/").filter(Boolean)}return t.slice(s+n.length).split("/").filter(Boolean)}function q(e,r){return new Request(r,{method:e.method,headers:e.headers,body:e.body,duplex:"half"})}async function D(e,r,t,n,s){try{await k({nodes:t,edges:n,triggerInput:s,executionId:e,workflowId:r})}catch(o){console.error("[Workflow Execute] Error during execution:",o),await g.update(c).set({status:"error",error:o instanceof Error?o.message:"Unknown error",completedAt:new Date}).where(x(c.id,e))}}function S(e){let r={updatedAt:new Date};return e.name!==void 0&&(r.name=e.name),e.description!==void 0&&(r.description=e.description),e.nodes!==void 0&&(r.nodes=e.nodes),e.edges!==void 0&&(r.edges=e.edges),e.visibility!==void 0&&(r.visibility=e.visibility),r}function B(e){return e.map(r=>{let t={...r};if(t.data&&typeof t.data=="object"&&t.data!==null){let n={...t.data};if(n.config&&typeof n.config=="object"&&n.config!==null){let{integrationId:s,...o}=n.config;n.config=o}t.data=n}return t})}async function A(e,r=e){let t={},n=await h(e,{withFileTypes:!0});for(let s of n){let o=y(e,s.name);if(s.isDirectory()){let i=await A(o,r);Object.assign(t,i)}else if(s.isFile()){let i=await R(o,"utf-8"),a=o.substring(r.length+1);t[a]=i}}return t}function z(e){let r={},n=`${e.name.replace(d,"").split(p).map((i,a)=>a===0?i.toLowerCase():i.charAt(0).toUpperCase()+i.slice(1).toLowerCase()).join("")||"execute"}Workflow`,s=l(e.name,e.nodes,e.edges,{functionName:n}),o=N(e.name);return r[`workflows/${o}.ts`]=s,r[`app/api/workflows/${o}/route.ts`]=`import { start } from 'workflow/api';
1
+ import{a as k}from"./chunk-4FVH6K2Q.js";import{j as l}from"./chunk-VUDOAZ3W.js";import{a as m,b as w}from"./chunk-IEOZJAW2.js";import{l as d,m as p}from"./chunk-BNX2SV7E.js";import{r as f,t as u}from"./chunk-QRG4O4PE.js";import{k as c,o as g}from"./chunk-5J6TNMJG.js";async function b(e){let r=m();if(r.getUser)return r.getUser(e);let t=await w.api.getSession({headers:e.headers});return t?.user?{id:t.user.id,email:t.user.email??void 0,name:t.user.name??void 0}:null}import{eq as x}from"drizzle-orm";import{readdir as h,readFile as R}from"fs/promises";import{join as y}from"path";var T={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization"};function _(e){let t=new URL(e.url).pathname,n="/api/workflow-builder/",s=t.indexOf(n);if(s===-1){let i=t.indexOf("/api/");return i===-1?[]:t.slice(i+5).split("/").filter(Boolean)}return t.slice(s+n.length).split("/").filter(Boolean)}function q(e,r){return new Request(r,{method:e.method,headers:e.headers,body:e.body,duplex:"half"})}async function D(e,r,t,n,s){try{await k({nodes:t,edges:n,triggerInput:s,executionId:e,workflowId:r})}catch(o){console.error("[Workflow Execute] Error during execution:",o),await g.update(c).set({status:"error",error:o instanceof Error?o.message:"Unknown error",completedAt:new Date}).where(x(c.id,e))}}function S(e){let r={updatedAt:new Date};return e.name!==void 0&&(r.name=e.name),e.description!==void 0&&(r.description=e.description),e.nodes!==void 0&&(r.nodes=e.nodes),e.edges!==void 0&&(r.edges=e.edges),e.visibility!==void 0&&(r.visibility=e.visibility),r}function B(e){return e.map(r=>{let t={...r};if(t.data&&typeof t.data=="object"&&t.data!==null){let n={...t.data};if(n.config&&typeof n.config=="object"&&n.config!==null){let{integrationId:s,...o}=n.config;n.config=o}t.data=n}return t})}async function A(e,r=e){let t={},n=await h(e,{withFileTypes:!0});for(let s of n){let o=y(e,s.name);if(s.isDirectory()){let i=await A(o,r);Object.assign(t,i)}else if(s.isFile()){let i=await R(o,"utf-8"),a=o.substring(r.length+1);t[a]=i}}return t}function z(e){let r={},n=`${e.name.replace(d,"").split(p).map((i,a)=>a===0?i.toLowerCase():i.charAt(0).toUpperCase()+i.slice(1).toLowerCase()).join("")||"execute"}Workflow`,s=l(e.name,e.nodes,e.edges,{functionName:n}),o=N(e.name);return r[`workflows/${o}.ts`]=s,r[`app/api/workflows/${o}/route.ts`]=`import { start } from 'workflow/api';
2
2
  import { ${n} } from '@/workflows/${o}';
3
3
  import { NextResponse } from 'next/server';
4
4