ts-edge
Version:
A strongly-typed graph-based workflow engine for building flexible, composable data processing pipelines with TypeScript
5 lines (4 loc) • 13.2 kB
JavaScript
var pe=Object.defineProperty;var he=(t,e,r)=>e in t?pe(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var I=(t,e,r)=>he(t,typeof e!="symbol"?e+"":e,r);var le=t=>t;function ge(...t){return typeof t[0]=="function"?t[0]:{possibleTargets:t[0],router:t[1]}}var J=t=>t,me=t=>t,fe=t=>J({branch:t.branch,name:t.name,metadata:t.metadata,execute:(e,r)=>t.execute(Object.values(e)[0],r)});var Q=t=>typeof t=="function",R=t=>Q(t?.then),xe=t=>{try{return t instanceof Error?t:typeof t=="string"?new Error(t):new Error(JSON.stringify(t))}catch(e){return e}},k=t=>({isOk:!0,error:void 0,value:t}),_=t=>({isOk:!1,error:xe(t),value:void 0}),Ee=(t,e)=>{if(R(t))return t.then(e).then(k,_);try{let r=e(t);return R(r)?r.then(n=>Promise.resolve(k(n)),n=>Promise.resolve(_(n))):k(r)}catch(r){return _(r)}},$={ok:k,fail:_,update:Ee},F=t=>{let e=r=>F($.update(t,r));return{map(r){return e(n=>{if(n.isOk)return r(n.value);throw n.error})},flatMap(r){return e(n=>{if(n.isOk)return r(n.value).unwrap();throw n.error})},effect(r){return e(n=>{if(!n.isOk)throw n.error;let a=r(n.value);return R(a)?a.then(()=>n.value):n.value})},ifOk(r){return e(n=>{if(!n.isOk)throw n.error;let a=r(n.value);return R(a)?a.then(()=>n.value):n.value})},watch(r){return e(n=>{try{r({...n})}catch{}if(n.isOk)return n.value;throw n.error})},catch(r){return e(n=>n.isOk?n.value:r(n.error))},ifFail(r){return e(n=>n.isOk?n.value:r(n.error))},unwrap(){if(R(t))return t.then(r=>{if(r.isOk)return r.value;throw r.error});if(t.isOk)return t.value;throw t.error},isOk:R(t)?t.then(r=>Promise.resolve(r.isOk)):t.isOk,orElse(r){return e(n=>n.isOk?n.value:r).unwrap()}}};function q(t){let e=F($.ok(void 0));try{return e.map(()=>t)}catch(r){return e.map(()=>{throw r})}}function Ne(t){let e=F($.ok(void 0));try{return e.map(()=>t())}catch(r){return e.map(()=>{throw r})}}function ye(){return F($.ok(void 0))}function Ge(...t){return e=>{let r=q(e);return t.reduce((n,a)=>n.map(a),r)}}function E(t){return t===void 0?ye():Q(t)?Ne(t):q(t)}E.pipe=Ge;var C=t=>t==null,D=()=>"ts-edge-xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)}),Z=(t,e,r)=>{let n;return new Promise((a,o)=>{n=setTimeout(async()=>{let i=r;typeof r=="function"&&(i=await E(r).ifFail(s=>s).unwrap()),o(i??new Error(`Execution aborted: Timeout of ${e}ms exceeded`))},e),t.then(i=>a(i),i=>o(i))}).finally(()=>{clearTimeout(n)})},ee=()=>{let t=[];return{publish(e){t.forEach(async r=>r(e))},subscribe(e){t.push(e)},unsubscribe(e){let r=t.findIndex(n=>n===e);r!==-1&&t.splice(r,1)}}},te=()=>{let t,e;return{promise:new Promise((n,a)=>{t=n,e=a}),reject:e,resolve:t}};var U=()=>{let t=Promise.resolve();return e=>(t=t.then(()=>e()),t)};var L=(c=>(c.INVALID_NODE_NAME="Node name cannot be empty or is invalid",c.DUPLICATE_NODE_NAME="Node with this name already exists in the graph",c.NODE_NOT_FOUND="Node not found in the graph",c.CIRCULAR_DEPENDENCY="Circular dependency detected in graph",c.INVALID_EDGE="Invalid edge configuration",c.MISSING_SOURCE_NODE="Source node not found for edge",c.DUPLICATE_EDGE="Node already has an outgoing connection",c.MERGE_NODE_MISSING_BRANCH="Merge node references non-existent source nodes",c.MAX_NODE_VISITS_EXCEEDED="Maximum node visits exceeded",c.EXECUTION_TIMEOUT="Graph execution timed out",c.NODE_EXECUTION_FAILED="Node execution failed",c.INVALID_DYNAMIC_EDGE_RESULT="Invalid result from dynamic edge router",c.THREAD_POOL_FAILURE="Thread pool execution failed",c.EXECUTION_ABORTED="Graph execution was aborted",c.MIDDLEWARE_FAIL="Error thrown in graph middleware",c.EXIT="EXIT",c.INVALID_INPUT="Invalid input data provided",c.INVALID_OUTPUT="Node produced invalid output data",c.TYPE_MISMATCH="Data type mismatch between nodes",c.UNKNOWN_ERROR="Unknown error occurred in graph execution",c))(L||{}),M=class extends Error{constructor(r,n){let a=n?.message||r;super(a);I(this,"code");I(this,"nodeName");I(this,"context");I(this,"cause");this.code=r,this.nodeName=n?.nodeName,this.context=n?.context,this.cause=n?.cause,this.name="GraphError"}toString(){let r=`[${this.name}] ${this.message}`;return this.nodeName&&(r+=`
Node: ${this.nodeName}`),this.context&&Object.keys(this.context).length>0&&(r+=`
Context: ${JSON.stringify(this.context,null,2)}`),this.cause&&(r+=`
Caused by: ${this.cause}`),r}formatWithNode(r){if(!r&&!this.nodeName)return this.message;let n=r||this.nodeName;return`${this.message} (Node: ${n})`}},m=class t extends M{constructor(e,r){super(e,r),this.name="GraphConfigurationError"}static nodeNotFound(e){return new t("Node not found in the graph",{message:`Node "${e}" not found in the graph`,nodeName:e})}static duplicateNode(e){return new t("Node with this name already exists in the graph",{message:`Node with name "${e}" already exists in the graph`,nodeName:e})}static duplicateEdge(e){return new t("Node already has an outgoing connection",{message:`Node "${e}" already has an outgoing connection`,nodeName:e})}static invalidMergeBranch(e,r){return new t("Merge node references non-existent source nodes",{message:`Merge node "${e}" references non-existent source node(s): ${r.join(", ")}`,nodeName:e,context:{missingBranch:r}})}},N=class t extends M{constructor(e,r){super(e,r),this.name="GraphExecutionError"}static nodeExecutionFailed(e,r,n){return new t("Node execution failed",{message:`Execution of node "${e}" failed: ${r.message}`,nodeName:e,cause:r,context:n?{input:n}:void 0})}static timeout(e){return new t("Graph execution timed out",{message:`Graph execution timed out after ${e}ms`,context:{timeoutMs:e}})}static maxVisitsExceeded(e,r){return new t("Maximum node visits exceeded",{message:`Maximum node visits (${r}) exceeded for node "${e}"`,nodeName:e,context:{maxVisits:r}})}static invalidDynamicEdgeResult(e,r){return new t("Invalid result from dynamic edge router",{message:`Node "${e}" returned invalid dynamic edge result`,nodeName:e,context:{result:r}})}},X=class t extends M{constructor(e,r){super(e,r),this.name="GraphDataError"}static invalidInput(e,r,n){return new t("Invalid input data provided",{message:`Invalid input for node "${e}": ${r}`,nodeName:e,context:n?{input:n}:void 0})}static invalidOutput(e,r,n){return new t("Node produced invalid output data",{message:`Invalid output from node "${e}": ${r}`,nodeName:e,context:n?{output:n}:void 0})}static typeMismatch(e,r,n){return new t("Data type mismatch between nodes",{message:`Type mismatch between nodes "${e}" and "${r}": ${n}`,context:{sourceNode:e,targetNode:r}})}};var re=({executionId:t,name:e,node:r,end:n,baseBranch:a,threadId:o,recordExecution:i,publishEvent:s})=>async h=>{let g=Date.now(),l=D();return E(r).watch(s.bind(null,{nodeExecutionId:l,executionId:t,threadId:o,eventType:"NODE_START",startedAt:g,node:{name:e,input:h}})).ifOk(u=>{if(!u)throw N.nodeExecutionFailed(e,new Error(`Node not found: "${e}"`),h)}).map(u=>{let x={stream:w=>s({eventType:"NODE_STREAM",nodeExecutionId:l,executionId:t,threadId:o,timestamp:Date.now(),node:{name:e,chunk:w}}),metadata:u.metadata??{}};return u.execute(h,x)}).map(async u=>{if(!C(n)&&e==n||!r.edge)return{name:[],output:u,exit:!0};if(r.edge.type=="direct")return{output:u,name:r.edge.next};let x=r.edge.router,O=[await x(u)].flat().filter(P=>{if(C(P))return!1;if(typeof P!="string")throw N.invalidDynamicEdgeResult(e,O);return!0});return{name:O,output:u}}).map(u=>!u.name.length&&a.length&&!u.exit?{name:a,output:u.output}:u).watch(u=>{let x={startedAt:g,endedAt:Date.now(),threadId:o,error:u.error,nodeExecutionId:l,node:{input:h,name:e,output:u.value?.output},isOk:u.isOk};i(x),s({eventType:"NODE_END",executionId:t,...x})}).unwrap()};var ne=()=>{let t=new Map,e={error:void 0,...te()},r=()=>{for(let o of t.values())if(o.tasks.some(i=>!i.isCompleted))return!1;return!0},n=(o,i)=>{let s=t.get(o);if(!s)return;let h=s.tasks.findIndex(g=>g.promise===i);h!==-1&&(s.tasks[h].isCompleted=!0)},a=o=>{r()&&(e.error?e.reject(e.error):e.resolve(o))};return{waitForCompletion(){return e.promise},scheduleTask(o,i){if(e.error)return;t.has(o)||t.set(o,{chain:U(),tasks:[]});let s=t.get(o),h=s.chain(async()=>{try{let g=await Promise.resolve().then(()=>i());return n(o,h),a(g),g}catch(g){throw n(o,h),e.error||(e.error=g),a(),e.error}});s.tasks.push({promise:h,isCompleted:!1})},abort(o="Thread pool execution aborted"){e.error=new Error(o),e.reject(e.error)}}};var oe=({start:t,end:e,registry:r})=>{let{publish:n,subscribe:a,unsubscribe:o}=ee(),i=[],s=[],h,g={isRunning:()=>s.length>0,subscribe:a,unsubscribe:o,exit(l){s.length&&(h=E(()=>String(l)||"").ifFail(u=>u?.name||"stop-error").unwrap())},use(l){return i.push(l),g},getStructure(){return Array.from(r.entries()).map(([l,u])=>({name:l,metadata:u.metadata,isMergeNode:u.isMergeNode,edge:u.edge?{name:u.edge.next,type:u.edge.type}:void 0}))},async run(l,u){let x={timeout:6e5,maxNodeVisits:100,disableHistory:!1,...u};h=void 0;let w=D();s.push(w);let O=Date.now(),P=Array.from(r.entries()).reduce((d,[f,y])=>(y.branch?.forEach(p=>{d[p]??(d[p]=[]),d[p].push(f)}),d),{}),b=[],se=d=>{x.disableHistory||b.push(d)},ie=(d,f,y)=>{let p=B.get(d),S=p.find(T=>T.source===f);return S.pending=!1,S.output=y,p.some(T=>T.pending)?null:p.reduce((T,A)=>(T[A.source]=A.output,T),{})},B=Array.from(r.entries()).reduce((d,[f,y])=>(y.isMergeNode&&d.set(f,y.branch.map(p=>({source:p,output:void 0,pending:!0}))),d),new Map),c=ne(),V=(d,f,y)=>{c.scheduleTask(d,async()=>{let p=f,S=y,z=U();await Promise.all(i.map(v=>z(async()=>{try{await v({name:p,input:S},W=>{W&&(p=W.name,S=W.input)})}catch(G){throw new N("Error thrown in graph middleware",{message:`Middleware error for node "${p}": ${G instanceof Error?G.message:String(G)}`,nodeName:p,cause:G instanceof Error?G:new Error(String(G)),context:{input:S}})}})));let T=r.get(p);if(!T)throw N.nodeExecutionFailed(p,new Error(`Node not found: "${p}"`),S);if(x.maxNodeVisits--<=0)throw N.maxVisitsExceeded(p,u?.maxNodeVisits||100);let A=re({executionId:w,name:p,end:e,baseBranch:P[p]??[],threadId:d,node:T,recordExecution:se,publishEvent:n}),{name:Y,output:H}=await A(S),ce=[...Y.map(()=>D()),d],K=()=>ce.pop();return Y.forEach(async v=>{if(!B.has(v))return V(K(),v,H);let G=ie(v,p,H);if(G)return V(K(),v,G)}),H})},ue=()=>{n({eventType:"WORKFLOW_START",executionId:w,startedAt:O,input:l})},de=d=>{n({eventType:"WORKFLOW_END",executionId:w,startedAt:O,endedAt:Date.now(),histories:b,isOk:d.isOk,error:d.error,output:d.value})},j=d=>f=>{if(e&&b.at(-1)?.node.name!=e){let y=[...b].reverse().find(p=>p.node.name==e)?.node;y&&(f=y.output)}return{startedAt:O,endedAt:Date.now(),histories:b,error:d?void 0:f instanceof Error?f:new Error(String(f)),output:d?f:void 0,isOk:d}};return E().map(()=>Z(E().watch(ue).map(V.bind(null,D(),t,l)).map(c.waitForCompletion).watch(de).unwrap(),Math.max(0,x.timeout),()=>{let d=N.timeout(x.timeout);return c.abort(d.message),d})).map(j(!0)).ifFail(j(!1)).watch(()=>{s=s.filter(d=>d!=w)}).unwrap()}};return g.use(l=>{if(h!=null)throw new N("EXIT",{message:h||"Exit",nodeName:l.name})}),g};var ae=()=>{let t=new Map,e=()=>{Array.from(t.entries()).forEach(([n,a])=>{if(a.isMergeNode){let o=a.branch.filter(i=>!t.has(i));if(o.length>0)throw m.invalidMergeBranch(n,o);a.branch.forEach(i=>{let s=t.get(i);if(s&&s.edge?.type!="dynamic"){let h={type:"direct",next:Array.from(new Set([...s.edge?.next??[],n]))};s.edge=h}})}else if(a.edge?.type==="direct"){let o=a.edge.next.filter(i=>!t.has(i));if(o.length>0)throw new m("Source node not found for edge",{message:`Node "${n}" has direct edge to non-existent node(s): ${o.join(", ")}`,nodeName:n,context:{invalidTargets:o}})}})},r={addNode({name:n,execute:a,metadata:o}){if(t.has(n))throw m.duplicateNode(n);return t.set(n,{execute:a,isMergeNode:!1,metadata:o??{}}),r},addMergeNode({branch:n,execute:a,name:o,metadata:i}){if(t.has(o))throw m.duplicateNode(o);return t.set(o,{execute:a,isMergeNode:!0,branch:n,metadata:i??{}}),r},edge(n,a){let o=t.get(n);if(!o)throw m.nodeNotFound(n);if(o.edge)throw m.duplicateEdge(n);return o.edge={type:"direct",next:[a].flat()},r},dynamicEdge(n,a){let o=t.get(n);if(!o)throw m.nodeNotFound(n);if(o.edge)throw m.duplicateEdge(n);return o.edge={type:"dynamic",next:typeof a=="function"?[]:a.possibleTargets,router:typeof a=="function"?a:a.router},r},compile(n,a){if(!t.has(n))throw m.nodeNotFound(n);if(a&&!t.has(a))throw m.nodeNotFound(a);return e(),oe({start:n,end:a,registry:t})}};return r},Te=t=>{let e=ae(),r=e.addNode,n=e.addMergeNode,a=e.compile;return e.addNode=o=>(r({...o,execute:(i,s)=>E(()=>o.execute(t.get(),s)).map(()=>t.get()).unwrap()}),e),e.addMergeNode=o=>(n({...o,execute:(i,s)=>E(()=>o.execute(i,s)).map(()=>t.get()).unwrap()}),e),e.compile=(o,i)=>{let s=a(o,i),h=s.run;return s.run=(g,l)=>(l?.noResetState||t.reset(),C(g)||t.set(g),h(void 0,l)),s},e};var we=(t,e)=>{let r=typeof e!="function"?e:e(t);return Object.assign(t??{},r)},Se=t=>{let e,r=()=>e,n=o=>{e=we(e,o)};e=t(n,r);let a={...e};return r.get=r,r.set=n,r.reset=()=>n({...a}),r};export{m as GraphConfigurationError,X as GraphDataError,M as GraphError,L as GraphErrorCode,N as GraphExecutionError,ae as createGraph,Te as createStateGraph,J as graphMergeNode,le as graphNode,ge as graphNodeRouter,fe as graphStateMergeNode,me as graphStateNode,Se as graphStore};