@lunora/workflow 1.0.0-alpha.13 → 1.0.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/do/index.mjs +1 -44
- package/dist/index.mjs +1 -9
- package/dist/packem_shared/MAX_BRANCHES-DC-bbfah.mjs +1 -0
- package/dist/packem_shared/NonRetryableError-CXD-mZe4.mjs +1 -0
- package/dist/packem_shared/WorkflowsRestError--PIoe9uw.mjs +1 -0
- package/dist/packem_shared/createRunStep-CzGKC1eZ.mjs +1 -0
- package/dist/packem_shared/createWorkflowContext-Dc-ECx9X.mjs +1 -0
- package/dist/packem_shared/createWorkflowRunContext-BTtqNIwl.mjs +1 -0
- package/dist/packem_shared/createWorkflows-UPBTcUJt.mjs +1 -0
- package/dist/packem_shared/defineStep-rJ0XPdAx.mjs +1 -0
- package/dist/packem_shared/defineWorkflow-fBuCDkGT.mjs +1 -0
- package/package.json +3 -3
- package/dist/packem_shared/MAX_BRANCHES-EO5xYS_3.mjs +0 -170
- package/dist/packem_shared/NonRetryableError-Dn2dTyBS.mjs +0 -27
- package/dist/packem_shared/WorkflowsRestError-zmjOxTR1.mjs +0 -117
- package/dist/packem_shared/createRunStep-8jOXxP2o.mjs +0 -54
- package/dist/packem_shared/createWorkflowContext-oxZ5zj3x.mjs +0 -14
- package/dist/packem_shared/createWorkflowRunContext-D62MbkFK.mjs +0 -134
- package/dist/packem_shared/createWorkflows-wq4fs72q.mjs +0 -40
- package/dist/packem_shared/defineStep-DJQtLw7g.mjs +0 -28
- package/dist/packem_shared/defineWorkflow-DbUC-oCN.mjs +0 -15
package/dist/do/index.mjs
CHANGED
|
@@ -1,44 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { NonRetryableError } from 'cloudflare:workflows';
|
|
3
|
-
import { convertNonRetryableError } from '../packem_shared/NonRetryableError-Dn2dTyBS.mjs';
|
|
4
|
-
import { extractBranchMarker, stripBranchMarker, signalBranchParentSafe, errorOutcome, okOutcome } from '../packem_shared/MAX_BRANCHES-EO5xYS_3.mjs';
|
|
5
|
-
import { createWorkflowRunContext } from '../packem_shared/createWorkflowRunContext-D62MbkFK.mjs';
|
|
6
|
-
|
|
7
|
-
class LunoraWorkflow extends WorkflowEntrypoint {
|
|
8
|
-
/** The `lunora/workflows.ts` export name, for log correlation. */
|
|
9
|
-
#lunoraName;
|
|
10
|
-
/** The `defineWorkflow` result this entrypoint runs. */
|
|
11
|
-
#definition;
|
|
12
|
-
constructor(context, env, definition, exportName) {
|
|
13
|
-
super(context, env);
|
|
14
|
-
this.#definition = definition;
|
|
15
|
-
this.#lunoraName = exportName ?? "workflow";
|
|
16
|
-
}
|
|
17
|
-
async run(event, step) {
|
|
18
|
-
const nativeStep = step;
|
|
19
|
-
const marker = extractBranchMarker(event.payload);
|
|
20
|
-
const handlerEvent = marker ? { ...event, payload: stripBranchMarker(event.payload) } : event;
|
|
21
|
-
const context = createWorkflowRunContext({
|
|
22
|
-
env: this.env,
|
|
23
|
-
event: handlerEvent,
|
|
24
|
-
exportName: this.#lunoraName,
|
|
25
|
-
nonRetryableErrorClass: NonRetryableError,
|
|
26
|
-
step: nativeStep
|
|
27
|
-
});
|
|
28
|
-
let output;
|
|
29
|
-
try {
|
|
30
|
-
output = await this.#definition.handler(context);
|
|
31
|
-
} catch (error) {
|
|
32
|
-
if (marker) {
|
|
33
|
-
await signalBranchParentSafe({ env: this.env, log: context.log, step: nativeStep }, marker, errorOutcome(error));
|
|
34
|
-
}
|
|
35
|
-
return convertNonRetryableError(error, NonRetryableError);
|
|
36
|
-
}
|
|
37
|
-
if (marker) {
|
|
38
|
-
await signalBranchParentSafe({ env: this.env, log: context.log, step: nativeStep }, marker, okOutcome(output));
|
|
39
|
-
}
|
|
40
|
-
return output;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export { LunoraWorkflow as default };
|
|
1
|
+
import{WorkflowEntrypoint as c}from"cloudflare:workers";import{NonRetryableError as i}from"cloudflare:workflows";import{convertNonRetryableError as m}from"../packem_shared/NonRetryableError-CXD-mZe4.mjs";import{extractBranchMarker as h,stripBranchMarker as f,signalBranchParentSafe as l,errorOutcome as u,okOutcome as v}from"../packem_shared/MAX_BRANCHES-DC-bbfah.mjs";import{createWorkflowRunContext as y}from"../packem_shared/createWorkflowRunContext-BTtqNIwl.mjs";class R extends c{#r;#t;constructor(t,e,a,r){super(t,e),this.#t=a,this.#r=r??"workflow"}async run(t,e){const a=e,r=h(t.payload),p=r?{...t,payload:f(t.payload)}:t,o=y({env:this.env,event:p,exportName:this.#r,nonRetryableErrorClass:i,step:a});let n;try{n=await this.#t.handler(o)}catch(s){return r&&await l({env:this.env,log:o.log,step:a},r,u(s)),m(s,i)}return r&&await l({env:this.env,log:o.log,step:a},r,v(n)),n}}export{R as default};
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { default as createWorkflows } from './packem_shared/createWorkflows-wq4fs72q.mjs';
|
|
3
|
-
export { defineStep, isStepDefinition } from './packem_shared/defineStep-DJQtLw7g.mjs';
|
|
4
|
-
export { defineWorkflow, isWorkflowDefinition, workflowBindingName, workflowClassName, workflowDefaultName } from './packem_shared/defineWorkflow-DbUC-oCN.mjs';
|
|
5
|
-
export { NonRetryableError, convertNonRetryableError, isNonRetryableError, toNativeNonRetryableError } from './packem_shared/NonRetryableError-Dn2dTyBS.mjs';
|
|
6
|
-
export { MAX_BRANCHES, branch } from './packem_shared/MAX_BRANCHES-EO5xYS_3.mjs';
|
|
7
|
-
export { WorkflowsRestError, createWorkflowsRestClient } from './packem_shared/WorkflowsRestError-zmjOxTR1.mjs';
|
|
8
|
-
export { createWorkflowRunContext } from './packem_shared/createWorkflowRunContext-D62MbkFK.mjs';
|
|
9
|
-
export { createRunStep, validateStepArgs } from './packem_shared/createRunStep-8jOXxP2o.mjs';
|
|
1
|
+
import{createWorkflowContext as e}from"./packem_shared/createWorkflowContext-Dc-ECx9X.mjs";import{default as f}from"./packem_shared/createWorkflows-UPBTcUJt.mjs";import{defineStep as l,isStepDefinition as n}from"./packem_shared/defineStep-rJ0XPdAx.mjs";import{defineWorkflow as p,isWorkflowDefinition as w,workflowBindingName as m,workflowClassName as s,workflowDefaultName as x}from"./packem_shared/defineWorkflow-fBuCDkGT.mjs";import{NonRetryableError as N,convertNonRetryableError as R,isNonRetryableError as c,toNativeNonRetryableError as W}from"./packem_shared/NonRetryableError-CXD-mZe4.mjs";import{MAX_BRANCHES as b,branch as d}from"./packem_shared/MAX_BRANCHES-DC-bbfah.mjs";import{WorkflowsRestError as S,createWorkflowsRestClient as u}from"./packem_shared/WorkflowsRestError--PIoe9uw.mjs";import{createWorkflowRunContext as v}from"./packem_shared/createWorkflowRunContext-BTtqNIwl.mjs";import{createRunStep as D,validateStepArgs as g}from"./packem_shared/createRunStep-CzGKC1eZ.mjs";export{b as MAX_BRANCHES,N as NonRetryableError,S as WorkflowsRestError,d as branch,R as convertNonRetryableError,D as createRunStep,e as createWorkflowContext,v as createWorkflowRunContext,f as createWorkflows,u as createWorkflowsRestClient,l as defineStep,p as defineWorkflow,c as isNonRetryableError,n as isStepDefinition,w as isWorkflowDefinition,W as toNativeNonRetryableError,g as validateStepArgs,m as workflowBindingName,s as workflowClassName,x as workflowDefaultName};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as g}from"@lunora/errors";import{NonRetryableError as p}from"./NonRetryableError-CXD-mZe4.mjs";const h=100,c="__lunoraBranch",w="lunora:spawn:",y="lunora:await:",$="lunora:signal:",v="lunora:compensate:",f="lunora:branch:",b=(e,t,a)=>({compensateWith:a?.compensateWith,id:a?.id,params:t,timeout:a?.timeout,workflow:e}),m=e=>e instanceof Error?{message:e.message,name:e.name}:{message:String(e),name:"Error"},k=e=>({status:"ok",value:e}),S=e=>({error:m(e),status:"error"}),u=async(e,t,a)=>{for(let i=t.length-1;i>=0;i-=1){const n=t[i],l=n?.plan.item.compensateWith;if(!(n===void 0||l===void 0))try{const r=e.resolveBinding(l);await e.step.do(`${v}${n.plan.childId}`,async()=>{const o=`${n.plan.childId}:compensate`,s={branch:n.plan.item.workflow,error:a,index:n.plan.index,output:n.output};return await r.create({id:o,params:s}),o})}catch(r){e.log?.error(`ctx.parallel: group-saga compensation "${l}" for branch "${n.plan.item.workflow}" (#${String(n.plan.index)}) failed`,r)}}},E=e=>async t=>{if(t.length===0)return[];if(t.length>h)throw new p(`ctx.parallel: ${String(t.length)} branches exceeds the cap of ${String(h)} — split the fan-out or raise the work into fewer child workflows`);const a=t.map((r,o)=>{const s=e.nextChildId(r.id);return{childId:s,eventType:`${f}${s}`,index:o,item:r}}),i=new Set;for(const r of a){if(i.has(r.childId))throw new p(`ctx.parallel: duplicate branch id "${r.childId}" — each branch in a group must resolve to a unique child instance id (check explicit \`id\` options)`);i.add(r.childId)}await Promise.all(a.map(r=>e.step.do(`${w}${r.childId}`,async()=>{const o=e.resolveBinding(r.item.workflow),s={eventType:r.eventType,index:r.index,parentBinding:e.parentBinding,parentId:e.instanceId};return await o.create({id:r.childId,params:{...r.item.params,[c]:s}}),r.childId})));const n=[],l=[];for(const r of a){let o;try{o=(await e.step.waitForEvent(`${y}${r.childId}`,{timeout:r.item.timeout,type:r.eventType})).payload}catch(s){const d=m(s);throw await u(e,l,d),new p(`ctx.parallel: branch "${r.item.workflow}" (#${String(r.index)}) join failed: ${d.message}`)}if(o.status==="error")throw await u(e,l,o.error),new p(`ctx.parallel: branch "${r.item.workflow}" (#${String(r.index)}) failed: ${o.error.message}`);l.push({output:o.value,plan:r}),n.push(o.value)}return n},T=e=>async(t,a,i)=>{if(a!==void 0&&Object.hasOwn(a,c))throw new g("BAD_REQUEST",`@lunora/workflow: params may not contain the reserved key "${c}"`);const n=e.nextChildId(i?.id);return await e.step.do(`${w}${n}`,async()=>(await e.resolveBinding(t).create({id:n,params:a}),n)),e.resolveBinding(t).get(n)},R=e=>{if(typeof e!="object"||e===null)return;const t=e[c];if(typeof t!="object"||t===null)return;const a=t;if(!(typeof a.eventType!="string"||typeof a.parentBinding!="string"||typeof a.parentId!="string"||typeof a.index!="number")&&!(!a.parentBinding.startsWith("WORKFLOW_")||!a.eventType.startsWith(f)))return{eventType:a.eventType,index:a.index,parentBinding:a.parentBinding,parentId:a.parentId}},W=e=>{if(typeof e!="object"||e===null)return e;const t={...e};return Reflect.deleteProperty(t,c),t},x=async(e,t,a)=>{const i=e.env[t.parentBinding];if(!i||typeof i.get!="function")return;const n=i.get.bind(i);await e.step.do(`${$}${String(t.index)}`,async()=>(await(await n(t.parentId)).sendEvent({payload:a,type:t.eventType}),t.eventType))},O=async(e,t,a)=>{try{await x(e,t,a)}catch(i){e.log?.error(`@lunora/workflow: failed to signal branch parent "${t.parentId}" (event "${t.eventType}")`,i)}};export{c as BRANCH_MARKER_KEY,h as MAX_BRANCHES,b as branch,E as createParallel,T as createSpawn,S as errorOutcome,R as extractBranchMarker,k as okOutcome,x as signalBranchParent,O as signalBranchParentSafe,W as stripBranchMarker};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t="__lunoraNonRetryable";class c extends Error{constructor(e,o="NonRetryableError"){super(e),this.name=o,this[t]=!0}}const s=r=>r instanceof Error&&r[t]===!0,a=(r,e)=>{const o=new e(r.message,r.name);return r.stack!==void 0&&(o.stack=r.stack),r.cause!==void 0&&o.cause===void 0&&(o.cause=r.cause),o},n=(r,e)=>{throw e!==void 0&&s(r)?a(r,e):r};export{c as NonRetryableError,n as convertNonRetryableError,s as isNonRetryableError,a as toNativeNonRetryableError};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as h}from"@lunora/errors";const $="https://api.cloudflare.com/client/v4/accounts",I={complete:!0,errored:!0,paused:!0,queued:!0,running:!0,terminated:!0,unknown:!0,waiting:!0,waitingForPause:!0},w=e=>typeof e=="string"&&Object.hasOwn(I,e)?e:"unknown",c=e=>typeof e=="string"&&e!==""?e:void 0,f=(e,a)=>typeof e=="string"?e:a,S=e=>typeof e=="boolean"?e:void 0,k=e=>Array.isArray(e)?e.length:typeof e=="number"?e:void 0,m=e=>({createdOn:c(e.created_on),endedOn:c(e.ended_on),id:f(e.id,""),startedOn:c(e.started_on),status:w(e.status)}),b=e=>({attempts:k(e.attempts),end:c(e.end),error:e.error,name:f(e.name,""),output:e.output,start:c(e.start),success:S(e.success),type:c(e.type)});class g extends h{constructor(a,i){super("WORKFLOWS_REST_ERROR",`Cloudflare Workflows REST API returned ${String(a)}: ${i}`,{name:"WorkflowsRestError",status:a})}}const C=e=>{const a=e.fetch??globalThis.fetch.bind(globalThis),i=`${$}/${e.accountId}/workflows`,p=async(r,s)=>{const t=await a(`${i}${r}`,{...s,headers:{Authorization:`Bearer ${e.apiToken}`,"Content-Type":"application/json"}}),o=await t.text();let n;try{n=JSON.parse(o)}catch{throw new g(t.status,o)}if(!t.ok||n.success===!1)throw new g(t.status,o);return n};return{getInstance:async({instanceId:r,workflowName:s})=>{const t=(await p(`/${encodeURIComponent(s)}/instances/${encodeURIComponent(r)}`)).result??{},o=Array.isArray(t.steps)?t.steps:[];return{...m(t),error:t.error,output:t.output,params:t.params,steps:o.map(n=>b(n))}},listInstances:async({page:r,perPage:s,status:t,workflowName:o})=>{const n=new URLSearchParams;t!==void 0&&n.set("status",t),r!==void 0&&n.set("page",String(r)),s!==void 0&&n.set("per_page",String(s));const y=n.toString()===""?"":`?${n.toString()}`,d=await p(`/${encodeURIComponent(o)}/instances${y}`),l=Array.isArray(d.result)?d.result:[],u=d.result_info??{};return{instances:l.map(R=>m(R)),page:typeof u.page=="number"?u.page:r??1,perPage:typeof u.per_page=="number"?u.per_page:s??l.length,totalCount:typeof u.total_count=="number"?u.total_count:void 0}},setInstanceStatus:async({action:r,instanceId:s,workflowName:t})=>{const o=(await p(`/${encodeURIComponent(t)}/instances/${encodeURIComponent(s)}`,{body:JSON.stringify({status:r}),method:"PATCH"})).result??{};return{status:w(o.status)}}}};export{g as WorkflowsRestError,C as createWorkflowsRestClient};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{parseValidatorMap as v}from"@lunora/values";import{convertNonRetryableError as p,NonRetryableError as y}from"./NonRetryableError-CXD-mZe4.mjs";const b=(r,t)=>v(r,t,"step args"),E=r=>async(t,g,f)=>{const o=f?.config??t.config,s=b(t.args,g),l=async n=>{const m={attempt:n.attempt,config:n.config,env:r.env,log:r.log,run:r.run,step:n.step};let e;try{e=await t.handler(m,s)}catch(a){return p(a,r.nonRetryableErrorClass)}if(!t.returns)return e;try{return t.returns.parse(e)}catch(a){const d=a instanceof Error?a.message:String(a),u=new y(`step "${t.name}" returns validation failed: ${d}`);return a!==void 0&&(u.cause=a),p(u,r.nonRetryableErrorClass)}},c=t.rollback,i=c?{rollback:async n=>{await c({args:s,env:r.env,error:n.error,log:r.log,output:n.output,run:r.run})},rollbackConfig:t.rollbackConfig}:void 0;return o===void 0?r.step.do(t.name,l,i):r.step.do(t.name,o,l,i)};export{E as createRunStep,b as validateStepArgs};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import r from"./createWorkflows-UPBTcUJt.mjs";const i=(e,c)=>{const o={};for(const n of c){const t=e[n.binding];t&&typeof t.create=="function"&&typeof t.createBatch=="function"&&typeof t.get=="function"&&(o[n.exportName]=t)}return r({bindings:o})};export{i as createWorkflowContext};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as f}from"@lunora/errors";import{workflowBindingName as h}from"./defineWorkflow-fBuCDkGT.mjs";import{createSpawn as w,createParallel as g}from"./MAX_BRANCHES-DC-bbfah.mjs";import{createRunStep as y}from"./createRunStep-CzGKC1eZ.mjs";const N=t=>({debug:(n,...e)=>{console.debug(t,n,...e)},error:(n,...e)=>{console.error(t,n,...e)},info:(n,...e)=>{console.info(t,n,...e)},warn:(n,...e)=>{console.warn(t,n,...e)}}),v="/_lunora/scheduler/dispatch",m=t=>{let n=t.length;for(;n>0&&t[n-1]==="/";)n-=1;return t.slice(0,n)},I=(t,n,e)=>{try{const o=JSON.parse(e)?.error;if(typeof o=="object"&&o!==null&&typeof o.code=="string"){const{code:i,data:u,message:s}=o;return new f(i,typeof s=="string"?s:void 0,{data:u,status:n})}}catch{}return new f("INTERNAL",`${t}: function dispatch failed (${String(n)}): ${e}`,{status:n})},R=t=>{const{label:n}=t,e=globalThis.fetch,o=t.fetchImpl??(typeof e=="function"?e.bind(globalThis):void 0);return async(i,u,s={})=>{if(typeof o!="function")throw new TypeError(`${n}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);const r=t.env.LUNORA_ORIGIN_URL;if(typeof r!="string"||r.length===0)throw new f("INTERNAL",`${n}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);const a=t.env.LUNORA_ADMIN_TOKEN;if(typeof a!="string"||a.length===0)throw new f("INTERNAL",`${n}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);const l=`${m(r)}${v}`,p={authorization:`Bearer ${a}`,"content-type":"application/json"};t.identity?.userId!==void 0&&(p["x-lunora-userid"]=t.identity.userId),t.identity?.claims!==void 0&&(p["x-lunora-identity"]=JSON.stringify(t.identity.claims));const c=await o(l,{body:JSON.stringify({args:u??{},functionPath:i.__lunoraRef,shardKey:s.shardKey}),headers:p,method:"POST"});if(!c.ok)throw I(n,c.status,await c.text());const d=await c.text();if(d.length!==0)try{return JSON.parse(d)}catch{throw new f("INTERNAL",`${n}: function dispatch returned a non-JSON body (${String(c.status)}): ${d}`,{status:c.status})}}},S=t=>{const n=N(`[workflow:${t.exportName}]`),e=R({env:t.env,fetchImpl:t.fetchImpl,label:"@lunora/workflow"}),o=r=>{const a=h(r),l=t.env[a];if(!l||typeof l.create!="function"||typeof l.get!="function")throw new f("INTERNAL",`@lunora/workflow: cannot spawn child workflow "${r}" — no Workflow binding "${a}" on env (is it declared in lunora/workflows.ts?)`);return l};let i=0;const u=r=>{if(r!==void 0)return r;const a=`${t.event.instanceId}-c${String(i)}`;return i+=1,a},s={env:t.env,instanceId:t.event.instanceId,log:n,nextChildId:u,parentBinding:h(t.exportName),resolveBinding:o,step:t.step};return{env:t.env,event:t.event,log:n,parallel:g(s),params:t.event.payload,run:e,runStep:y({env:t.env,log:n,nonRetryableErrorClass:t.nonRetryableErrorClass,run:e,step:t.step}),spawn:w(s),step:t.step}};export{S as createWorkflowRunContext};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as s}from"@lunora/errors";import{BRANCH_MARKER_KEY as a}from"./MAX_BRANCHES-DC-bbfah.mjs";const c=r=>{const{params:o}=r??{};if(o!==void 0&&Object.hasOwn(o,a))throw new s("BAD_REQUEST",`@lunora/workflow: params may not contain the reserved key "${a}"`)},f=r=>({create:async o=>(c(o),r.create(o)),createBatch:async o=>{for(const e of o)c(e);return r.createBatch(o)},get:async o=>r.get(o)}),k=r=>{const o=r.bindings??{};return{get:e=>{const n=o[e];if(n===void 0){const t=Object.keys(o),w=t.length===0?"no workflows are declared":`known workflows: ${t.join(", ")}`;throw new s("INTERNAL",`@lunora/workflow: no workflow named "${e}" (${w})`)}return f(n)}}};export{k as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=(r,e)=>{if(typeof r!="string"||r.length===0)throw new TypeError("defineStep: `name` must be a non-empty string (the durable step label)");const n=e.args;if(typeof n!="object"||n===null)throw new TypeError("defineStep: `args` must be a validator map (e.g. `{ id: v.string() }`)");if(typeof e.handler!="function")throw new TypeError("defineStep: `handler` must be a function (the step body)");if(e.rollback!==void 0&&typeof e.rollback!="function")throw new TypeError("defineStep: `rollback` must be a function when provided");return{args:e.args,config:e.config,handler:e.handler,isLunoraStep:!0,name:r,returns:e.returns,rollback:e.rollback,rollbackConfig:e.rollbackConfig}},t=r=>typeof r=="object"&&r!==null&&r.isLunoraStep===!0;export{o as defineStep,t as isStepDefinition};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}Workflow`,r=e=>`WORKFLOW_${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"_").toUpperCase()}`,n=e=>e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"-").toLowerCase(),a=e=>{if(typeof e.handler!="function")throw new TypeError("defineWorkflow: `handler` must be a function (the workflow body)");if(e.name!==void 0&&(typeof e.name!="string"||e.name.length===0))throw new TypeError("defineWorkflow: `name` must be a non-empty string when provided");return{...e,isLunoraWorkflow:!0}},t=e=>typeof e=="object"&&e!==null&&e.isLunoraWorkflow===!0;export{a as defineWorkflow,t as isWorkflowDefinition,r as workflowBindingName,o as workflowClassName,n as workflowDefaultName};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/workflow",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.14",
|
|
4
4
|
"description": "Durable workflows for Lunora: defineWorkflow over Cloudflare Workflows, generated WorkflowEntrypoint classes, and the ctx.workflows surface",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
52
|
-
"@lunora/values": "1.0.0-alpha.
|
|
51
|
+
"@lunora/errors": "1.0.0-alpha.9",
|
|
52
|
+
"@lunora/values": "1.0.0-alpha.12"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": "^22.15.0 || >=24.11.0"
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { NonRetryableError } from './NonRetryableError-Dn2dTyBS.mjs';
|
|
3
|
-
|
|
4
|
-
const MAX_BRANCHES = 100;
|
|
5
|
-
const BRANCH_MARKER_KEY = "__lunoraBranch";
|
|
6
|
-
const SPAWN_STEP_PREFIX = "lunora:spawn:";
|
|
7
|
-
const AWAIT_STEP_PREFIX = "lunora:await:";
|
|
8
|
-
const SIGNAL_STEP_PREFIX = "lunora:signal:";
|
|
9
|
-
const COMPENSATE_STEP_PREFIX = "lunora:compensate:";
|
|
10
|
-
const BRANCH_EVENT_PREFIX = "lunora:branch:";
|
|
11
|
-
const branch = (workflow, params, options) => {
|
|
12
|
-
return { compensateWith: options?.compensateWith, id: options?.id, params, timeout: options?.timeout, workflow };
|
|
13
|
-
};
|
|
14
|
-
const serializeError = (error) => {
|
|
15
|
-
if (error instanceof Error) {
|
|
16
|
-
return { message: error.message, name: error.name };
|
|
17
|
-
}
|
|
18
|
-
return { message: String(error), name: "Error" };
|
|
19
|
-
};
|
|
20
|
-
const okOutcome = (value) => {
|
|
21
|
-
return { status: "ok", value };
|
|
22
|
-
};
|
|
23
|
-
const errorOutcome = (error) => {
|
|
24
|
-
return { error: serializeError(error), status: "error" };
|
|
25
|
-
};
|
|
26
|
-
const compensateCompleted = async (deps, completed, error) => {
|
|
27
|
-
for (let cursor = completed.length - 1; cursor >= 0; cursor -= 1) {
|
|
28
|
-
const done = completed[cursor];
|
|
29
|
-
const compensateWith = done?.plan.item.compensateWith;
|
|
30
|
-
if (done === void 0 || compensateWith === void 0) {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
const compensation = deps.resolveBinding(compensateWith);
|
|
35
|
-
await deps.step.do(`${COMPENSATE_STEP_PREFIX}${done.plan.childId}`, async () => {
|
|
36
|
-
const compensateId = `${done.plan.childId}:compensate`;
|
|
37
|
-
const compensationParams = {
|
|
38
|
-
branch: done.plan.item.workflow,
|
|
39
|
-
error,
|
|
40
|
-
index: done.plan.index,
|
|
41
|
-
output: done.output
|
|
42
|
-
};
|
|
43
|
-
await compensation.create({ id: compensateId, params: compensationParams });
|
|
44
|
-
return compensateId;
|
|
45
|
-
});
|
|
46
|
-
} catch (compensationError) {
|
|
47
|
-
deps.log?.error(
|
|
48
|
-
`ctx.parallel: group-saga compensation "${compensateWith}" for branch "${done.plan.item.workflow}" (#${String(done.plan.index)}) failed`,
|
|
49
|
-
compensationError
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const createParallel = (deps) => {
|
|
55
|
-
const run = async (branches) => {
|
|
56
|
-
if (branches.length === 0) {
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
if (branches.length > MAX_BRANCHES) {
|
|
60
|
-
throw new NonRetryableError(
|
|
61
|
-
`ctx.parallel: ${String(branches.length)} branches exceeds the cap of ${String(MAX_BRANCHES)} — split the fan-out or raise the work into fewer child workflows`
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
const planned = branches.map((item, index) => {
|
|
65
|
-
const childId = deps.nextChildId(item.id);
|
|
66
|
-
return { childId, eventType: `${BRANCH_EVENT_PREFIX}${childId}`, index, item };
|
|
67
|
-
});
|
|
68
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
69
|
-
for (const plan of planned) {
|
|
70
|
-
if (seenIds.has(plan.childId)) {
|
|
71
|
-
throw new NonRetryableError(
|
|
72
|
-
`ctx.parallel: duplicate branch id "${plan.childId}" — each branch in a group must resolve to a unique child instance id (check explicit \`id\` options)`
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
seenIds.add(plan.childId);
|
|
76
|
-
}
|
|
77
|
-
await Promise.all(
|
|
78
|
-
planned.map(
|
|
79
|
-
(plan) => deps.step.do(`${SPAWN_STEP_PREFIX}${plan.childId}`, async () => {
|
|
80
|
-
const binding = deps.resolveBinding(plan.item.workflow);
|
|
81
|
-
const marker = { eventType: plan.eventType, index: plan.index, parentBinding: deps.parentBinding, parentId: deps.instanceId };
|
|
82
|
-
await binding.create({ id: plan.childId, params: { ...plan.item.params, [BRANCH_MARKER_KEY]: marker } });
|
|
83
|
-
return plan.childId;
|
|
84
|
-
})
|
|
85
|
-
)
|
|
86
|
-
);
|
|
87
|
-
const results = [];
|
|
88
|
-
const completed = [];
|
|
89
|
-
for (const plan of planned) {
|
|
90
|
-
let outcome;
|
|
91
|
-
try {
|
|
92
|
-
const event = await deps.step.waitForEvent(`${AWAIT_STEP_PREFIX}${plan.childId}`, {
|
|
93
|
-
timeout: plan.item.timeout,
|
|
94
|
-
type: plan.eventType
|
|
95
|
-
});
|
|
96
|
-
outcome = event.payload;
|
|
97
|
-
} catch (joinError) {
|
|
98
|
-
const joinFailure = serializeError(joinError);
|
|
99
|
-
await compensateCompleted(deps, completed, joinFailure);
|
|
100
|
-
throw new NonRetryableError(`ctx.parallel: branch "${plan.item.workflow}" (#${String(plan.index)}) join failed: ${joinFailure.message}`);
|
|
101
|
-
}
|
|
102
|
-
if (outcome.status === "error") {
|
|
103
|
-
await compensateCompleted(deps, completed, outcome.error);
|
|
104
|
-
throw new NonRetryableError(`ctx.parallel: branch "${plan.item.workflow}" (#${String(plan.index)}) failed: ${outcome.error.message}`);
|
|
105
|
-
}
|
|
106
|
-
completed.push({ output: outcome.value, plan });
|
|
107
|
-
results.push(outcome.value);
|
|
108
|
-
}
|
|
109
|
-
return results;
|
|
110
|
-
};
|
|
111
|
-
return run;
|
|
112
|
-
};
|
|
113
|
-
const createSpawn = (deps) => async (workflow, params, options) => {
|
|
114
|
-
if (params !== void 0 && Object.hasOwn(params, BRANCH_MARKER_KEY)) {
|
|
115
|
-
throw new LunoraError("BAD_REQUEST", `@lunora/workflow: params may not contain the reserved key "${BRANCH_MARKER_KEY}"`);
|
|
116
|
-
}
|
|
117
|
-
const childId = deps.nextChildId(options?.id);
|
|
118
|
-
await deps.step.do(`${SPAWN_STEP_PREFIX}${childId}`, async () => {
|
|
119
|
-
const binding = deps.resolveBinding(workflow);
|
|
120
|
-
await binding.create({ id: childId, params });
|
|
121
|
-
return childId;
|
|
122
|
-
});
|
|
123
|
-
return deps.resolveBinding(workflow).get(childId);
|
|
124
|
-
};
|
|
125
|
-
const extractBranchMarker = (payload) => {
|
|
126
|
-
if (typeof payload !== "object" || payload === null) {
|
|
127
|
-
return void 0;
|
|
128
|
-
}
|
|
129
|
-
const marker = payload[BRANCH_MARKER_KEY];
|
|
130
|
-
if (typeof marker !== "object" || marker === null) {
|
|
131
|
-
return void 0;
|
|
132
|
-
}
|
|
133
|
-
const candidate = marker;
|
|
134
|
-
if (typeof candidate.eventType !== "string" || typeof candidate.parentBinding !== "string" || typeof candidate.parentId !== "string" || typeof candidate.index !== "number") {
|
|
135
|
-
return void 0;
|
|
136
|
-
}
|
|
137
|
-
if (!candidate.parentBinding.startsWith("WORKFLOW_") || !candidate.eventType.startsWith(BRANCH_EVENT_PREFIX)) {
|
|
138
|
-
return void 0;
|
|
139
|
-
}
|
|
140
|
-
return { eventType: candidate.eventType, index: candidate.index, parentBinding: candidate.parentBinding, parentId: candidate.parentId };
|
|
141
|
-
};
|
|
142
|
-
const stripBranchMarker = (payload) => {
|
|
143
|
-
if (typeof payload !== "object" || payload === null) {
|
|
144
|
-
return payload;
|
|
145
|
-
}
|
|
146
|
-
const rest = { ...payload };
|
|
147
|
-
Reflect.deleteProperty(rest, BRANCH_MARKER_KEY);
|
|
148
|
-
return rest;
|
|
149
|
-
};
|
|
150
|
-
const signalBranchParent = async (deps, marker, outcome) => {
|
|
151
|
-
const binding = deps.env[marker.parentBinding];
|
|
152
|
-
if (!binding || typeof binding.get !== "function") {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const getParent = binding.get.bind(binding);
|
|
156
|
-
await deps.step.do(`${SIGNAL_STEP_PREFIX}${String(marker.index)}`, async () => {
|
|
157
|
-
const parent = await getParent(marker.parentId);
|
|
158
|
-
await parent.sendEvent({ payload: outcome, type: marker.eventType });
|
|
159
|
-
return marker.eventType;
|
|
160
|
-
});
|
|
161
|
-
};
|
|
162
|
-
const signalBranchParentSafe = async (deps, marker, outcome) => {
|
|
163
|
-
try {
|
|
164
|
-
await signalBranchParent(deps, marker, outcome);
|
|
165
|
-
} catch (signalError) {
|
|
166
|
-
deps.log?.error(`@lunora/workflow: failed to signal branch parent "${marker.parentId}" (event "${marker.eventType}")`, signalError);
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
export { BRANCH_MARKER_KEY, MAX_BRANCHES, branch, createParallel, createSpawn, errorOutcome, extractBranchMarker, okOutcome, signalBranchParent, signalBranchParentSafe, stripBranchMarker };
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
const NON_RETRYABLE_BRAND = "__lunoraNonRetryable";
|
|
2
|
-
class NonRetryableError extends Error {
|
|
3
|
-
constructor(message, name = "NonRetryableError") {
|
|
4
|
-
super(message);
|
|
5
|
-
this.name = name;
|
|
6
|
-
this[NON_RETRYABLE_BRAND] = true;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
const isNonRetryableError = (value) => value instanceof Error && value[NON_RETRYABLE_BRAND] === true;
|
|
10
|
-
const toNativeNonRetryableError = (error, NativeNonRetryableError) => {
|
|
11
|
-
const native = new NativeNonRetryableError(error.message, error.name);
|
|
12
|
-
if (error.stack !== void 0) {
|
|
13
|
-
native.stack = error.stack;
|
|
14
|
-
}
|
|
15
|
-
if (error.cause !== void 0 && native.cause === void 0) {
|
|
16
|
-
native.cause = error.cause;
|
|
17
|
-
}
|
|
18
|
-
return native;
|
|
19
|
-
};
|
|
20
|
-
const convertNonRetryableError = (error, NativeNonRetryableError) => {
|
|
21
|
-
if (NativeNonRetryableError !== void 0 && isNonRetryableError(error)) {
|
|
22
|
-
throw toNativeNonRetryableError(error, NativeNonRetryableError);
|
|
23
|
-
}
|
|
24
|
-
throw error;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export { NonRetryableError, convertNonRetryableError, isNonRetryableError, toNativeNonRetryableError };
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
|
|
3
|
-
const API_BASE = "https://api.cloudflare.com/client/v4/accounts";
|
|
4
|
-
const KNOWN_STATUSES = {
|
|
5
|
-
complete: true,
|
|
6
|
-
errored: true,
|
|
7
|
-
paused: true,
|
|
8
|
-
queued: true,
|
|
9
|
-
running: true,
|
|
10
|
-
terminated: true,
|
|
11
|
-
unknown: true,
|
|
12
|
-
waiting: true,
|
|
13
|
-
waitingForPause: true
|
|
14
|
-
};
|
|
15
|
-
const toStatus = (value) => typeof value === "string" && Object.hasOwn(KNOWN_STATUSES, value) ? value : "unknown";
|
|
16
|
-
const asString = (value) => typeof value === "string" && value !== "" ? value : void 0;
|
|
17
|
-
const stringOr = (value, fallback) => typeof value === "string" ? value : fallback;
|
|
18
|
-
const asBoolean = (value) => typeof value === "boolean" ? value : void 0;
|
|
19
|
-
const countAttempts = (value) => {
|
|
20
|
-
if (Array.isArray(value)) {
|
|
21
|
-
return value.length;
|
|
22
|
-
}
|
|
23
|
-
return typeof value === "number" ? value : void 0;
|
|
24
|
-
};
|
|
25
|
-
const toSummary = (raw) => {
|
|
26
|
-
return {
|
|
27
|
-
createdOn: asString(raw["created_on"]),
|
|
28
|
-
endedOn: asString(raw["ended_on"]),
|
|
29
|
-
id: stringOr(raw["id"], ""),
|
|
30
|
-
startedOn: asString(raw["started_on"]),
|
|
31
|
-
status: toStatus(raw["status"])
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
const toStep = (raw) => {
|
|
35
|
-
return {
|
|
36
|
-
attempts: countAttempts(raw["attempts"]),
|
|
37
|
-
end: asString(raw["end"]),
|
|
38
|
-
error: raw["error"],
|
|
39
|
-
name: stringOr(raw["name"], ""),
|
|
40
|
-
output: raw["output"],
|
|
41
|
-
start: asString(raw["start"]),
|
|
42
|
-
success: asBoolean(raw["success"]),
|
|
43
|
-
type: asString(raw["type"])
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
class WorkflowsRestError extends LunoraError {
|
|
47
|
-
constructor(status, body) {
|
|
48
|
-
super("WORKFLOWS_REST_ERROR", `Cloudflare Workflows REST API returned ${String(status)}: ${body}`, { name: "WorkflowsRestError", status });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const createWorkflowsRestClient = (config) => {
|
|
52
|
-
const fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
53
|
-
const base = `${API_BASE}/${config.accountId}/workflows`;
|
|
54
|
-
const request = async (path, init) => {
|
|
55
|
-
const response = await fetchImpl(`${base}${path}`, {
|
|
56
|
-
...init,
|
|
57
|
-
headers: { Authorization: `Bearer ${config.apiToken}`, "Content-Type": "application/json" }
|
|
58
|
-
});
|
|
59
|
-
const text = await response.text();
|
|
60
|
-
let body;
|
|
61
|
-
try {
|
|
62
|
-
body = JSON.parse(text);
|
|
63
|
-
} catch {
|
|
64
|
-
throw new WorkflowsRestError(response.status, text);
|
|
65
|
-
}
|
|
66
|
-
if (!response.ok || body["success"] === false) {
|
|
67
|
-
throw new WorkflowsRestError(response.status, text);
|
|
68
|
-
}
|
|
69
|
-
return body;
|
|
70
|
-
};
|
|
71
|
-
return {
|
|
72
|
-
getInstance: async ({ instanceId, workflowName }) => {
|
|
73
|
-
const body = await request(`/${encodeURIComponent(workflowName)}/instances/${encodeURIComponent(instanceId)}`);
|
|
74
|
-
const result = body["result"] ?? {};
|
|
75
|
-
const steps = Array.isArray(result["steps"]) ? result["steps"] : [];
|
|
76
|
-
return {
|
|
77
|
-
...toSummary(result),
|
|
78
|
-
error: result["error"],
|
|
79
|
-
output: result["output"],
|
|
80
|
-
params: result["params"],
|
|
81
|
-
steps: steps.map((step) => toStep(step))
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
listInstances: async ({ page, perPage, status, workflowName }) => {
|
|
85
|
-
const query = new URLSearchParams();
|
|
86
|
-
if (status !== void 0) {
|
|
87
|
-
query.set("status", status);
|
|
88
|
-
}
|
|
89
|
-
if (page !== void 0) {
|
|
90
|
-
query.set("page", String(page));
|
|
91
|
-
}
|
|
92
|
-
if (perPage !== void 0) {
|
|
93
|
-
query.set("per_page", String(perPage));
|
|
94
|
-
}
|
|
95
|
-
const suffix = query.toString() === "" ? "" : `?${query.toString()}`;
|
|
96
|
-
const body = await request(`/${encodeURIComponent(workflowName)}/instances${suffix}`);
|
|
97
|
-
const result = Array.isArray(body["result"]) ? body["result"] : [];
|
|
98
|
-
const info = body["result_info"] ?? {};
|
|
99
|
-
return {
|
|
100
|
-
instances: result.map((instance) => toSummary(instance)),
|
|
101
|
-
page: typeof info["page"] === "number" ? info["page"] : page ?? 1,
|
|
102
|
-
perPage: typeof info["per_page"] === "number" ? info["per_page"] : perPage ?? result.length,
|
|
103
|
-
totalCount: typeof info["total_count"] === "number" ? info["total_count"] : void 0
|
|
104
|
-
};
|
|
105
|
-
},
|
|
106
|
-
setInstanceStatus: async ({ action, instanceId, workflowName }) => {
|
|
107
|
-
const body = await request(`/${encodeURIComponent(workflowName)}/instances/${encodeURIComponent(instanceId)}`, {
|
|
108
|
-
body: JSON.stringify({ status: action }),
|
|
109
|
-
method: "PATCH"
|
|
110
|
-
});
|
|
111
|
-
const result = body["result"] ?? {};
|
|
112
|
-
return { status: toStatus(result["status"]) };
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export { WorkflowsRestError, createWorkflowsRestClient };
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { parseValidatorMap } from '@lunora/values';
|
|
2
|
-
import { convertNonRetryableError, NonRetryableError } from './NonRetryableError-Dn2dTyBS.mjs';
|
|
3
|
-
|
|
4
|
-
const validateStepArgs = (validators, source) => parseValidatorMap(validators, source, "step args");
|
|
5
|
-
const createRunStep = (deps) => async (step, args, options) => {
|
|
6
|
-
const config = options?.config ?? step.config;
|
|
7
|
-
const validatedArgs = validateStepArgs(step.args, args);
|
|
8
|
-
const callback = async (nativeContext) => {
|
|
9
|
-
const stepContext = {
|
|
10
|
-
attempt: nativeContext.attempt,
|
|
11
|
-
config: nativeContext.config,
|
|
12
|
-
env: deps.env,
|
|
13
|
-
log: deps.log,
|
|
14
|
-
run: deps.run,
|
|
15
|
-
step: nativeContext.step
|
|
16
|
-
};
|
|
17
|
-
let result;
|
|
18
|
-
try {
|
|
19
|
-
result = await step.handler(stepContext, validatedArgs);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
return convertNonRetryableError(error, deps.nonRetryableErrorClass);
|
|
22
|
-
}
|
|
23
|
-
if (!step.returns) {
|
|
24
|
-
return result;
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
return step.returns.parse(result);
|
|
28
|
-
} catch (error) {
|
|
29
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
-
const nonRetryable = new NonRetryableError(`step "${step.name}" returns validation failed: ${message}`);
|
|
31
|
-
if (error !== void 0) {
|
|
32
|
-
nonRetryable.cause = error;
|
|
33
|
-
}
|
|
34
|
-
return convertNonRetryableError(nonRetryable, deps.nonRetryableErrorClass);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
const rollbackHandler = step.rollback;
|
|
38
|
-
const rollbackOptions = rollbackHandler ? {
|
|
39
|
-
rollback: async (rollbackContext) => {
|
|
40
|
-
await rollbackHandler({
|
|
41
|
-
args: validatedArgs,
|
|
42
|
-
env: deps.env,
|
|
43
|
-
error: rollbackContext.error,
|
|
44
|
-
log: deps.log,
|
|
45
|
-
output: rollbackContext.output,
|
|
46
|
-
run: deps.run
|
|
47
|
-
});
|
|
48
|
-
},
|
|
49
|
-
rollbackConfig: step.rollbackConfig
|
|
50
|
-
} : void 0;
|
|
51
|
-
return config === void 0 ? deps.step.do(step.name, callback, rollbackOptions) : deps.step.do(step.name, config, callback, rollbackOptions);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export { createRunStep, validateStepArgs };
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import createWorkflows from './createWorkflows-wq4fs72q.mjs';
|
|
2
|
-
|
|
3
|
-
const createWorkflowContext = (env, specs) => {
|
|
4
|
-
const bindings = {};
|
|
5
|
-
for (const spec of specs) {
|
|
6
|
-
const binding = env[spec.binding];
|
|
7
|
-
if (binding && typeof binding.create === "function" && typeof binding.createBatch === "function" && typeof binding.get === "function") {
|
|
8
|
-
bindings[spec.exportName] = binding;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
return createWorkflows({ bindings });
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export { createWorkflowContext };
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { workflowBindingName } from './defineWorkflow-DbUC-oCN.mjs';
|
|
3
|
-
import { createSpawn, createParallel } from './MAX_BRANCHES-EO5xYS_3.mjs';
|
|
4
|
-
import { createRunStep } from './createRunStep-8jOXxP2o.mjs';
|
|
5
|
-
|
|
6
|
-
const createDispatchLogger = (prefix) => {
|
|
7
|
-
return {
|
|
8
|
-
debug: (message, ...rest) => {
|
|
9
|
-
console.debug(prefix, message, ...rest);
|
|
10
|
-
},
|
|
11
|
-
error: (message, ...rest) => {
|
|
12
|
-
console.error(prefix, message, ...rest);
|
|
13
|
-
},
|
|
14
|
-
info: (message, ...rest) => {
|
|
15
|
-
console.info(prefix, message, ...rest);
|
|
16
|
-
},
|
|
17
|
-
warn: (message, ...rest) => {
|
|
18
|
-
console.warn(prefix, message, ...rest);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const SCHEDULER_DISPATCH_PATH = "/_lunora/scheduler/dispatch";
|
|
24
|
-
const trimTrailingSlashes = (value) => {
|
|
25
|
-
let end = value.length;
|
|
26
|
-
while (end > 0 && value[end - 1] === "/") {
|
|
27
|
-
end -= 1;
|
|
28
|
-
}
|
|
29
|
-
return value.slice(0, end);
|
|
30
|
-
};
|
|
31
|
-
const toDispatchError = (label, status, rawBody) => {
|
|
32
|
-
try {
|
|
33
|
-
const parsed = JSON.parse(rawBody);
|
|
34
|
-
const errorBody = parsed?.error;
|
|
35
|
-
if (typeof errorBody === "object" && errorBody !== null && typeof errorBody.code === "string") {
|
|
36
|
-
const { code, data, message } = errorBody;
|
|
37
|
-
return new LunoraError(code, typeof message === "string" ? message : void 0, { data, status });
|
|
38
|
-
}
|
|
39
|
-
} catch {
|
|
40
|
-
}
|
|
41
|
-
return new LunoraError("INTERNAL", `${label}: function dispatch failed (${String(status)}): ${rawBody}`, { status });
|
|
42
|
-
};
|
|
43
|
-
const createDispatchRunner = (options) => {
|
|
44
|
-
const { label } = options;
|
|
45
|
-
const globalFetch = globalThis.fetch;
|
|
46
|
-
const fetchImpl = options.fetchImpl ?? (typeof globalFetch === "function" ? globalFetch.bind(globalThis) : void 0);
|
|
47
|
-
return async (function_, args, runOptions = {}) => {
|
|
48
|
-
if (typeof fetchImpl !== "function") {
|
|
49
|
-
throw new TypeError(`${label}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);
|
|
50
|
-
}
|
|
51
|
-
const origin = options.env.LUNORA_ORIGIN_URL;
|
|
52
|
-
if (typeof origin !== "string" || origin.length === 0) {
|
|
53
|
-
throw new LunoraError("INTERNAL", `${label}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);
|
|
54
|
-
}
|
|
55
|
-
const token = options.env.LUNORA_ADMIN_TOKEN;
|
|
56
|
-
if (typeof token !== "string" || token.length === 0) {
|
|
57
|
-
throw new LunoraError("INTERNAL", `${label}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);
|
|
58
|
-
}
|
|
59
|
-
const url = `${trimTrailingSlashes(origin)}${SCHEDULER_DISPATCH_PATH}`;
|
|
60
|
-
const headers = { authorization: `Bearer ${token}`, "content-type": "application/json" };
|
|
61
|
-
if (options.identity?.userId !== void 0) {
|
|
62
|
-
headers["x-lunora-userid"] = options.identity.userId;
|
|
63
|
-
}
|
|
64
|
-
if (options.identity?.claims !== void 0) {
|
|
65
|
-
headers["x-lunora-identity"] = JSON.stringify(options.identity.claims);
|
|
66
|
-
}
|
|
67
|
-
const response = await fetchImpl(url, {
|
|
68
|
-
body: JSON.stringify({ args: args ?? {}, functionPath: function_.__lunoraRef, shardKey: runOptions.shardKey }),
|
|
69
|
-
headers,
|
|
70
|
-
method: "POST"
|
|
71
|
-
});
|
|
72
|
-
if (!response.ok) {
|
|
73
|
-
throw toDispatchError(label, response.status, await response.text());
|
|
74
|
-
}
|
|
75
|
-
const text = await response.text();
|
|
76
|
-
if (text.length === 0) {
|
|
77
|
-
return void 0;
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
return JSON.parse(text);
|
|
81
|
-
} catch {
|
|
82
|
-
throw new LunoraError("INTERNAL", `${label}: function dispatch returned a non-JSON body (${String(response.status)}): ${text}`, {
|
|
83
|
-
status: response.status
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const createWorkflowRunContext = (options) => {
|
|
90
|
-
const log = createDispatchLogger(`[workflow:${options.exportName}]`);
|
|
91
|
-
const run = createDispatchRunner({ env: options.env, fetchImpl: options.fetchImpl, label: "@lunora/workflow" });
|
|
92
|
-
const resolveBinding = (workflow) => {
|
|
93
|
-
const bindingName = workflowBindingName(workflow);
|
|
94
|
-
const binding = options.env[bindingName];
|
|
95
|
-
if (!binding || typeof binding.create !== "function" || typeof binding.get !== "function") {
|
|
96
|
-
throw new LunoraError(
|
|
97
|
-
"INTERNAL",
|
|
98
|
-
`@lunora/workflow: cannot spawn child workflow "${workflow}" — no Workflow binding "${bindingName}" on env (is it declared in lunora/workflows.ts?)`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
return binding;
|
|
102
|
-
};
|
|
103
|
-
let childCounter = 0;
|
|
104
|
-
const nextChildId = (explicit) => {
|
|
105
|
-
if (explicit !== void 0) {
|
|
106
|
-
return explicit;
|
|
107
|
-
}
|
|
108
|
-
const id = `${options.event.instanceId}-c${String(childCounter)}`;
|
|
109
|
-
childCounter += 1;
|
|
110
|
-
return id;
|
|
111
|
-
};
|
|
112
|
-
const fanOutDeps = {
|
|
113
|
-
env: options.env,
|
|
114
|
-
instanceId: options.event.instanceId,
|
|
115
|
-
log,
|
|
116
|
-
nextChildId,
|
|
117
|
-
parentBinding: workflowBindingName(options.exportName),
|
|
118
|
-
resolveBinding,
|
|
119
|
-
step: options.step
|
|
120
|
-
};
|
|
121
|
-
return {
|
|
122
|
-
env: options.env,
|
|
123
|
-
event: options.event,
|
|
124
|
-
log,
|
|
125
|
-
parallel: createParallel(fanOutDeps),
|
|
126
|
-
params: options.event.payload,
|
|
127
|
-
run,
|
|
128
|
-
runStep: createRunStep({ env: options.env, log, nonRetryableErrorClass: options.nonRetryableErrorClass, run, step: options.step }),
|
|
129
|
-
spawn: createSpawn(fanOutDeps),
|
|
130
|
-
step: options.step
|
|
131
|
-
};
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
export { createWorkflowRunContext };
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { BRANCH_MARKER_KEY } from './MAX_BRANCHES-EO5xYS_3.mjs';
|
|
3
|
-
|
|
4
|
-
const rejectReservedParams = (options) => {
|
|
5
|
-
const { params } = options ?? {};
|
|
6
|
-
if (params !== void 0 && Object.hasOwn(params, BRANCH_MARKER_KEY)) {
|
|
7
|
-
throw new LunoraError("BAD_REQUEST", `@lunora/workflow: params may not contain the reserved key "${BRANCH_MARKER_KEY}"`);
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
const handleFor = (binding) => {
|
|
11
|
-
return {
|
|
12
|
-
create: async (options) => {
|
|
13
|
-
rejectReservedParams(options);
|
|
14
|
-
return binding.create(options);
|
|
15
|
-
},
|
|
16
|
-
createBatch: async (batch) => {
|
|
17
|
-
for (const options of batch) {
|
|
18
|
-
rejectReservedParams(options);
|
|
19
|
-
}
|
|
20
|
-
return binding.createBatch(batch);
|
|
21
|
-
},
|
|
22
|
-
get: async (id) => binding.get(id)
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
const createWorkflows = (options) => {
|
|
26
|
-
const bindings = options.bindings ?? {};
|
|
27
|
-
return {
|
|
28
|
-
get: (name) => {
|
|
29
|
-
const binding = bindings[name];
|
|
30
|
-
if (binding === void 0) {
|
|
31
|
-
const known = Object.keys(bindings);
|
|
32
|
-
const suffix = known.length === 0 ? "no workflows are declared" : `known workflows: ${known.join(", ")}`;
|
|
33
|
-
throw new LunoraError("INTERNAL", `@lunora/workflow: no workflow named "${name}" (${suffix})`);
|
|
34
|
-
}
|
|
35
|
-
return handleFor(binding);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export { createWorkflows as default };
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const defineStep = (name, config) => {
|
|
2
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
3
|
-
throw new TypeError("defineStep: `name` must be a non-empty string (the durable step label)");
|
|
4
|
-
}
|
|
5
|
-
const declaredArgs = config.args;
|
|
6
|
-
if (typeof declaredArgs !== "object" || declaredArgs === null) {
|
|
7
|
-
throw new TypeError("defineStep: `args` must be a validator map (e.g. `{ id: v.string() }`)");
|
|
8
|
-
}
|
|
9
|
-
if (typeof config.handler !== "function") {
|
|
10
|
-
throw new TypeError("defineStep: `handler` must be a function (the step body)");
|
|
11
|
-
}
|
|
12
|
-
if (config.rollback !== void 0 && typeof config.rollback !== "function") {
|
|
13
|
-
throw new TypeError("defineStep: `rollback` must be a function when provided");
|
|
14
|
-
}
|
|
15
|
-
return {
|
|
16
|
-
args: config.args,
|
|
17
|
-
config: config.config,
|
|
18
|
-
handler: config.handler,
|
|
19
|
-
isLunoraStep: true,
|
|
20
|
-
name,
|
|
21
|
-
returns: config.returns,
|
|
22
|
-
rollback: config.rollback,
|
|
23
|
-
rollbackConfig: config.rollbackConfig
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
const isStepDefinition = (value) => typeof value === "object" && value !== null && value.isLunoraStep === true;
|
|
27
|
-
|
|
28
|
-
export { defineStep, isStepDefinition };
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const workflowClassName = (exportName) => `${exportName.charAt(0).toUpperCase()}${exportName.slice(1)}Workflow`;
|
|
2
|
-
const workflowBindingName = (exportName) => `WORKFLOW_${exportName.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g, "_").toUpperCase()}`;
|
|
3
|
-
const workflowDefaultName = (exportName) => exportName.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g, "-").toLowerCase();
|
|
4
|
-
const defineWorkflow = (config) => {
|
|
5
|
-
if (typeof config.handler !== "function") {
|
|
6
|
-
throw new TypeError("defineWorkflow: `handler` must be a function (the workflow body)");
|
|
7
|
-
}
|
|
8
|
-
if (config.name !== void 0 && (typeof config.name !== "string" || config.name.length === 0)) {
|
|
9
|
-
throw new TypeError("defineWorkflow: `name` must be a non-empty string when provided");
|
|
10
|
-
}
|
|
11
|
-
return { ...config, isLunoraWorkflow: true };
|
|
12
|
-
};
|
|
13
|
-
const isWorkflowDefinition = (value) => typeof value === "object" && value !== null && value.isLunoraWorkflow === true;
|
|
14
|
-
|
|
15
|
-
export { defineWorkflow, isWorkflowDefinition, workflowBindingName, workflowClassName, workflowDefaultName };
|