@bobtail.software/b-durable 1.0.7 → 1.0.9

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.
@@ -1,64 +1,74 @@
1
1
  #!/usr/bin/env node
2
- import H from"path";import{existsSync as X,mkdirSync as Y,rmSync as J}from"fs";import T from"path";import*as M from"prettier";import{Node as i,Project as Q,SyntaxKind as b,ts as _,VariableDeclarationKind as z}from"ts-morph";var Z="bDurable",V=_.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope|_.TypeFormatFlags.NoTruncation;async function ee(e){let n=e.getFilePath(),o=e.getFullText(),s=await M.resolveConfig(n),a=await M.format(o,{...s,parser:"typescript"});e.replaceWithText(a)}async function U(e){console.log("Iniciando compilador de workflows duraderos...");let{inputDir:n,outputDir:o,packageName:s}=e,a=new Q({tsConfigFilePath:T.resolve(process.cwd(),"tsconfig.json")}),c=a.addSourceFilesAtPaths(`${n}/**/*.ts`);X(o)&&(console.log(`Limpiando directorio de salida: ${o}`),J(o,{recursive:!0,force:!0})),Y(o,{recursive:!0});let g=a.createDirectory(o);console.log(`Encontrados ${c.length} archivos de workflow para procesar.`);let r=[],l=[];for(let u of c){console.log(`
3
- Procesando archivo: ${u.getBaseName()}`);let t=u.getDescendantsOfKind(b.CallExpression).filter(m=>m.getExpression().getText()===Z);if(t.length!==0)for(let m of t){let p=m.getParentIfKind(b.VariableDeclaration);if(!p)continue;let d=p.getName();console.log(` -> Transformando workflow: ${d}`);let[f]=m.getArguments();if(!i.isObjectLiteralExpression(f))continue;let S=f.getProperty("workflow");if(!S||!i.isPropertyAssignment(S))continue;let x=S.getInitializer();if(!x||!i.isArrowFunction(x))continue;let D=u.getBaseName().replace(/\.ts$/,".compiled.mts"),I=T.join(g.getPath(),D),w=a.createSourceFile(I,"",{overwrite:!0});l.push(w),te(d,x,m,w,s),console.log(` -> Archivo generado: ${T.relative(process.cwd(),I)}`);let E=D;r.push({name:d,importPath:`./${E}`})}}if(r.length>0){let u=T.join(g.getPath(),"index.mts"),t=a.createSourceFile(u,"",{overwrite:!0});l.push(t),t.addStatements(`// Este archivo fue generado autom\xE1ticamente. NO EDITAR MANUALMENTE.
4
- `),t.addImportDeclaration({isTypeOnly:!0,moduleSpecifier:s,namedImports:["DurableFunction"]});for(let p of r)t.addImportDeclaration({moduleSpecifier:p.importPath,namedImports:[p.name]});t.addExportDeclaration({namedExports:r.map(p=>p.name)}),t.addStatements(`
5
- `),t.addVariableStatement({declarationKind:z.Const,declarations:[{name:"durableFunctions",type:"Map<string, DurableFunction<any, any, any, any>>",initializer:"new Map()"}]});let m=r.map(p=>`durableFunctions.set(${p.name}.name, ${p.name});`);t.addStatements(m),t.addStatements(`
6
- `),t.addExportAssignment({isExportEquals:!1,expression:"durableFunctions"}),console.log(`
7
- -> Archivo de \xEDndice generado: ${T.basename(u)}`)}console.log(`
8
- Formateando archivos generados con Prettier...`);for(let u of l)await ee(u);await a.save(),console.log(`
9
- Compilaci\xF3n completada exitosamente.`)}function te(e,n,o,s,a){let c=n.getBody();if(!i.isBlock(c))throw new Error(`El cuerpo del workflow '${e}' debe ser un bloque {}.`);let[g]=o.getArguments();if(!i.isObjectLiteralExpression(g))throw new Error("El argumento de bDurable debe ser un objeto.");let r=g.getProperty("version");if(!r||!i.isPropertyAssignment(r))throw new Error(`El workflow '${e}' debe tener una propiedad 'version'.`);let l=r.getInitializer();if(!l||!i.isStringLiteral(l))throw new Error(`La versi\xF3n del workflow '${e}' debe ser un string literal.`);let u=g.getProperty("retryOptions"),t="undefined";u&&i.isPropertyAssignment(u)&&(t=u.getInitializer()?.getText()||"undefined");let{clauses:m}=$(c.getStatements(),{step:0,persistedVariables:new Map}),p=n.getReturnType();p.getSymbol()?.getName()==="Promise"&&p.isObject()&&(p=p.getTypeArguments()[0]||p);let d=p.getText(void 0,V),f=new Set,S=n.getSourceFile(),x=o.getTypeArguments(),N=x.length>0?x[0].getText():"unknown",D=x.length>2?x[2].getText():"Record<string, never>",I=x.length>3?x[3].getText():"Record<string, never>";S.getImportDeclarations().forEach(y=>{if(y.getModuleSpecifierValue()===a)return;let h=y.getModuleSpecifierValue();if(h.includes(".workflow")){let A=T.parse(h),C=T.join(A.dir,A.base+".compiled.mts");!C.startsWith(".")&&!T.isAbsolute(C)&&(C="./"+C),h=C.replace(/\\/g,"/")}else h.startsWith(".")&&T.extname(h)===""&&(h+=".mjs");let R=[],B=[];y.getNamedImports().forEach(A=>{let C=A.getName(),j=A.getAliasNode()?.getText(),W=j?`${C} as ${j}`:C,K=(A.getNameNode().getSymbol()?.getAliasedSymbol()??A.getNameNode().getSymbol())?.getDeclarations()??[],G=K.some(O=>i.isEnumDeclaration(O));A.isTypeOnly()||!G&&K.every(O=>i.isInterfaceDeclaration(O)||i.isTypeAliasDeclaration(O))?B.push(W):R.push(W)}),R.length>0&&s.addImportDeclaration({moduleSpecifier:h,namedImports:R}),B.length>0&&s.addImportDeclaration({isTypeOnly:!0,moduleSpecifier:h,namedImports:B});let L=y.getDefaultImport();L&&s.addImportDeclaration({moduleSpecifier:h,defaultImport:L.getText()})}),S.getInterfaces().forEach(y=>{f.add(y.getText().startsWith("export")?y.getText():`export ${y.getText()}`)}),S.getTypeAliases().forEach(y=>{f.add(y.getText().startsWith("export")?y.getText():`export ${y.getText()}`)});let[w]=n.getParameters(),E="";if(w){let y=w.getNameNode().getText();y!=="input"&&(E=`const ${y} = input;`)}s.addImportDeclaration({isTypeOnly:!0,moduleSpecifier:a,namedImports:["DurableFunction","WorkflowContext","Instruction"]}),f.size>0&&(s.addStatements(`
10
- `),s.addStatements(Array.from(f))),s.addStatements(`
2
+ import q from"path";import{createHash as X}from"crypto";import{existsSync as K,mkdirSync as Y,readFileSync as Q,rmSync as Z,writeFileSync as ee}from"fs";import N from"path";import*as j from"prettier";import{Node as a,Project as te,SyntaxKind as P,ts as U,VariableDeclarationKind as _}from"ts-morph";var ne="bDurable",L=U.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope|U.TypeFormatFlags.NoTruncation;function se(e){return X("sha256").update(e).digest("hex")}async function re(e){let t=e.getFilePath(),o=e.getFullText(),n=await j.resolveConfig(t),i=await j.format(o,{...n,parser:"typescript"});e.replaceWithText(i)}async function H(e){console.log("Iniciando compilador de workflows duraderos...");let{inputDir:t,outputDir:o,packageName:n,mode:i}=e,c=N.resolve(process.cwd(),"durable.lock.json"),l={};if(K(c))try{l=JSON.parse(Q(c,"utf-8"))}catch(p){console.warn("Advertencia: durable.lock.json corrupto, iniciando uno nuevo.",p)}let r=new te({tsConfigFilePath:N.resolve(process.cwd(),"tsconfig.json")}),g=r.addSourceFilesAtPaths(`${t}/**/*.ts`);K(o)&&(console.log(`Limpiando directorio de salida: ${o}`),Z(o,{recursive:!0,force:!0})),Y(o,{recursive:!0});let d=r.createDirectory(o);console.log(`Encontrados ${g.length} archivos de workflow para procesar.`);let s=[],S=[],m=!1;for(let p of g){console.log(`
3
+ Procesando archivo: ${p.getBaseName()}`);let u=p.getDescendantsOfKind(P.CallExpression).filter(y=>y.getExpression().getText()===ne);if(u.length!==0)for(let y of u){let f=y.getParentIfKind(P.VariableDeclaration);if(!f)continue;let b=f.getName();console.log(` -> Transformando workflow: ${b}`);let[v]=y.getArguments();if(!a.isObjectLiteralExpression(v))continue;let I=v.getProperty("workflow");if(!I||!a.isPropertyAssignment(I))continue;let $=I.getInitializer();if(!$||!a.isArrowFunction($))continue;let w=v.getProperty("version"),h="unknown";if(w&&a.isPropertyAssignment(w)){let R=w.getInitializer();R&&a.isStringLiteral(R)&&(h=R.getLiteralValue())}let x=p.getBaseName().replace(/\.ts$/,".compiled.mts"),E=N.join(d.getPath(),x),C=r.createSourceFile(E,"",{overwrite:!0});S.push(C),oe(b,$,y,C,n);let M=C.getFullText(),F=se(M),T=l[b]?.[h];if(T)if(T!==F){if(i==="prod")throw new Error(`
4
+ \u{1F6D1} ERROR DE INTEGRIDAD (PROD) \u{1F6D1}
5
+ El workflow '${b}' (v${h}) ha cambiado, pero el lockfile no se actualiz\xF3.
6
+ Hash esperado: ${T.substring(0,8)}...
7
+ Hash actual: ${F.substring(0,8)}...
8
+
9
+ Soluci\xF3n:
10
+ 1. Incrementa la versi\xF3n en tu c\xF3digo (ej: v${h} -> v${parseFloat(h)+.1}).
11
+ 2. O ejecuta en modo dev para actualizar el lockfile: 'b-durable-compiler dev ...'
12
+ `);console.log(` \u26A0\uFE0F v${h} modificada. Actualizando hash en lockfile.`),l[b]||(l[b]={}),l[b][h]=F,m=!0}else console.log(` \u2705 v${h} verificada.`);else console.log(` \u{1F195} v${h} registrada.`),l[b]||(l[b]={}),l[b][h]=F,m=!0;console.log(` -> Archivo generado: ${N.relative(process.cwd(),E)}`);let D=x;s.push({name:b,importPath:`./${D}`})}}if(m){if(i==="prod")throw new Error("El lockfile ha cambiado durante un build de PROD. Esto no est\xE1 permitido.");ee(c,JSON.stringify(l,null,2)),console.log(`
13
+ \u{1F512} durable.lock.json actualizado.`)}if(s.length>0){let p=N.join(d.getPath(),"index.mts"),u=r.createSourceFile(p,"",{overwrite:!0});S.push(u),u.addStatements(`// Este archivo fue generado autom\xE1ticamente. NO EDITAR MANUALMENTE.
14
+ `),u.addImportDeclaration({isTypeOnly:!0,moduleSpecifier:n,namedImports:["DurableFunction"]});for(let f of s)u.addImportDeclaration({moduleSpecifier:f.importPath,namedImports:[f.name]});u.addExportDeclaration({namedExports:s.map(f=>f.name)}),u.addStatements(`
15
+ `),u.addVariableStatement({declarationKind:_.Const,declarations:[{name:"durableFunctions",type:"Map<string, DurableFunction<any, any, any, any>>",initializer:"new Map()"}]});let y=s.map(f=>`durableFunctions.set(${f.name}.name, ${f.name});`);u.addStatements(y),u.addStatements(`
16
+ `),u.addExportAssignment({isExportEquals:!1,expression:"durableFunctions"}),console.log(`
17
+ -> Archivo de \xEDndice generado: ${N.basename(p)}`)}console.log(`
18
+ Formateando archivos generados con Prettier...`);for(let p of S)await re(p);await r.save(),console.log(`
19
+ Compilaci\xF3n completada exitosamente.`)}function oe(e,t,o,n,i){let c=t.getBody();if(!a.isBlock(c))throw new Error(`El cuerpo del workflow '${e}' debe ser un bloque {}.`);let[l]=o.getArguments();if(!a.isObjectLiteralExpression(l))throw new Error("El argumento de bDurable debe ser un objeto.");let r=l.getProperty("version");if(!r||!a.isPropertyAssignment(r))throw new Error(`El workflow '${e}' debe tener una propiedad 'version'.`);let g=r.getInitializer();if(!g||!a.isStringLiteral(g))throw new Error(`La versi\xF3n del workflow '${e}' debe ser un string literal.`);let d=l.getProperty("retryOptions"),s="undefined";d&&a.isPropertyAssignment(d)&&(s=d.getInitializer()?.getText()||"undefined");let{clauses:S}=A(c.getStatements(),{step:0,persistedVariables:new Map}),m=t.getReturnType();m.getSymbol()?.getName()==="Promise"&&m.isObject()&&(m=m.getTypeArguments()[0]||m);let p=m.getText(void 0,L),u=new Set,y=t.getSourceFile(),f=o.getTypeArguments(),b=f.length>0?f[0].getText():"unknown",v=f.length>2?f[2].getText():"Record<string, never>",I=f.length>3?f[3].getText():"Record<string, never>";y.getImportDeclarations().forEach(x=>{if(x.getModuleSpecifierValue()===i)return;let E=x.getModuleSpecifierValue();if(E.includes(".workflow")){let T=N.parse(E),D=N.join(T.dir,T.base+".compiled.mts");!D.startsWith(".")&&!N.isAbsolute(D)&&(D="./"+D),E=D.replace(/\\/g,"/")}else E.startsWith(".")&&N.extname(E)===""&&(E+=".mjs");let C=[],M=[];x.getNamedImports().forEach(T=>{let D=T.getName(),R=T.getAliasNode()?.getText(),W=R?`${D} as ${R}`:D,z=(T.getNameNode().getSymbol()?.getAliasedSymbol()??T.getNameNode().getSymbol())?.getDeclarations()??[],J=z.some(B=>a.isEnumDeclaration(B));T.isTypeOnly()||!J&&z.every(B=>a.isInterfaceDeclaration(B)||a.isTypeAliasDeclaration(B))?M.push(W):C.push(W)}),C.length>0&&n.addImportDeclaration({moduleSpecifier:E,namedImports:C}),M.length>0&&n.addImportDeclaration({isTypeOnly:!0,moduleSpecifier:E,namedImports:M});let F=x.getDefaultImport();F&&n.addImportDeclaration({moduleSpecifier:E,defaultImport:F.getText()})}),y.getInterfaces().forEach(x=>{u.add(x.getText().startsWith("export")?x.getText():`export ${x.getText()}`)}),y.getTypeAliases().forEach(x=>{u.add(x.getText().startsWith("export")?x.getText():`export ${x.getText()}`)});let[$]=t.getParameters(),w="";if($){let x=$.getNameNode().getText();x!=="input"&&(w=`const ${x} = input;`)}n.addImportDeclaration({isTypeOnly:!0,moduleSpecifier:i,namedImports:["DurableFunction","WorkflowContext","Instruction"]}),u.size>0&&(n.addStatements(`
20
+ `),n.addStatements(Array.from(u))),n.addStatements(`
11
21
  // Este archivo fue generado autom\xE1ticamente. NO EDITAR MANUALMENTE.
12
- `);let P=l.getLiteralValue(),F=`{
22
+ `);let h=g.getLiteralValue(),V=`{
13
23
  __isDurable: true,
14
24
  name: '${e}',
15
- version: '${P}',
16
- retryOptions: ${t},
17
- async execute(context: WorkflowContext<${N}>): Promise<Instruction<${d}>> {
25
+ version: '${h}',
26
+ retryOptions: ${s},
27
+ async execute(context: WorkflowContext<${b}>): Promise<Instruction<${p}>> {
18
28
  const { input, state, result, log, workflowId } = context;
19
- ${E}
29
+ ${w}
20
30
  while (true) {
21
31
  switch (context.step) {
22
- ${m.join(`
32
+ ${S.join(`
23
33
  `)}
24
34
  default:
25
35
  throw new Error(\`Paso desconocido: \${context.step}\`);
26
36
  }
27
37
  }
28
38
  }
29
- }`;s.addVariableStatement({isExported:!0,declarationKind:z.Const,declarations:[{name:e,type:`DurableFunction<${N}, ${d}, ${D}, ${I}>`,initializer:F}]}),s.organizeImports()}function $(e,n){if(e.length===0){let f=[];if(n.pendingStateAssignment){let S=`case ${n.step}: {
30
- ${n.pendingStateAssignment}
39
+ }`;n.addVariableStatement({isExported:!0,declarationKind:_.Const,declarations:[{name:e,type:`DurableFunction<${b}, ${p}, ${v}, ${I}>`,initializer:V}]}),n.organizeImports()}function A(e,t){if(e.length===0){let u=[];if(t.pendingStateAssignment){let y=`case ${t.step}: {
40
+ ${t.pendingStateAssignment}
31
41
  return { type: 'COMPLETE', result: undefined };
32
- }`;f.push(S)}return{clauses:f,nextStep:n.step+1}}let{syncBlock:o,durableStatement:s,nextStatements:a}=le(e),{rewrittenSyncStatements:c,newlyPersistedVariables:g}=re(o,s?[s,...a]:[],n.persistedVariables);n.pendingStateAssignment&&c.unshift(n.pendingStateAssignment);let r=new Map([...n.persistedVariables,...g]);if(!s){let f=c.join(`
33
- `),x=o.length>0&&i.isReturnStatement(o[o.length-1])?"":`
34
- return { type: 'COMPLETE', result: undefined };`;return{clauses:[`case ${n.step}: {
35
- ${f}${x}
36
- }`],nextStep:n.step+1}}if(i.isIfStatement(s))return ne(s,a,{...n,persistedVariables:r},c);if(i.isTryStatement(s))return se(s,a,{...n,persistedVariables:r},c);let{instruction:l,nextPendingStateAssignment:u}=ae(s,r);c.push(l);let t=c.join(`
37
- `),m=`case ${n.step}: {
38
- ${t}
39
- }`,p={step:n.step+1,persistedVariables:r,pendingStateAssignment:u},d=$(a,p);return{clauses:[m,...d.clauses],nextStep:d.nextStep}}function ne(e,n,o,s){let a=k(e.getExpression(),o.persistedVariables),c=e.getThenStatement(),g=i.isBlock(c)?c.getStatements():[c],r=$(g,{step:o.step+1,persistedVariables:new Map(o.persistedVariables)}),l,u=e.getElseStatement();if(u){let S=i.isBlock(u)?u.getStatements():[u];l=$(S,{step:r.nextStep,persistedVariables:new Map(o.persistedVariables)})}let t=l?l.nextStep:r.nextStep,m=$(n,{step:t,persistedVariables:o.persistedVariables}),p=s.join(`
40
- `),d=r.nextStep;return{clauses:[`
42
+ }`;u.push(y)}return{clauses:u,nextStep:t.step+1}}let{syncBlock:o,durableStatement:n,nextStatements:i}=de(e),{rewrittenSyncStatements:c,newlyPersistedVariables:l}=ce(o,n?[n,...i]:[],t.persistedVariables);t.pendingStateAssignment&&c.unshift(t.pendingStateAssignment);let r=new Map([...t.persistedVariables,...l]);if(!n){let u=c.join(`
43
+ `),f=o.length>0&&a.isReturnStatement(o[o.length-1])?"":`
44
+ return { type: 'COMPLETE', result: undefined };`;return{clauses:[`case ${t.step}: {
45
+ ${u}${f}
46
+ }`],nextStep:t.step+1}}if(a.isIfStatement(n))return ae(n,i,{...t,persistedVariables:r},c);if(a.isTryStatement(n))return ie(n,i,{...t,persistedVariables:r},c);let{instruction:g,nextPendingStateAssignment:d}=pe(n,r);c.push(g);let s=c.join(`
47
+ `),S=`case ${t.step}: {
48
+ ${s}
49
+ }`,m={step:t.step+1,persistedVariables:r,pendingStateAssignment:d},p=A(i,m);return{clauses:[S,...p.clauses],nextStep:p.nextStep}}function ae(e,t,o,n){let i=k(e.getExpression(),o.persistedVariables),c=e.getThenStatement(),l=a.isBlock(c)?c.getStatements():[c],r=A(l,{step:o.step+1,persistedVariables:new Map(o.persistedVariables)}),g,d=e.getElseStatement();if(d){let y=a.isBlock(d)?d.getStatements():[d];g=A(y,{step:r.nextStep,persistedVariables:new Map(o.persistedVariables)})}let s=g?g.nextStep:r.nextStep,S=A(t,{step:s,persistedVariables:o.persistedVariables}),m=n.join(`
50
+ `),p=r.nextStep;return{clauses:[`
41
51
  case ${o.step}: {
42
- ${p}
43
- if (${a}) {
52
+ ${m}
53
+ if (${i}) {
44
54
  context.step = ${o.step+1};
45
55
  } else {
46
- ${u?`context.step = ${d};`:`context.step = ${t};`}
56
+ ${d?`context.step = ${p};`:`context.step = ${s};`}
47
57
  }
48
58
  break;
49
59
  }
50
- `,...r.clauses,...l?l.clauses:[],...m.clauses],nextStep:m.nextStep}}function se(e,n,o,s){let{step:a,persistedVariables:c}=o,g=e.getTryBlock(),r=e.getCatchClause(),l=e.getFinallyBlock(),u=$(g.getStatements(),{step:a+1,persistedVariables:new Map(c)}),t,m,p=u.nextStep;if(r){let E=r.getBlock(),P=r.getVariableDeclaration();P&&(m=P.getName()),t=$(E.getStatements(),{step:p,persistedVariables:new Map(c)})}let d,f=t?t.nextStep:p;l&&(d=$(l.getStatements(),{step:f,persistedVariables:new Map(c)}));let S=d?d.nextStep:f,x=$(n,{step:S,persistedVariables:c}),N=`{ catchStep: ${r?p:"undefined"}, finallyStep: ${l?f:"undefined"} }`,D=`
51
- case ${a}: {
52
- ${s.join(`
60
+ `,...r.clauses,...g?g.clauses:[],...S.clauses],nextStep:S.nextStep}}function ie(e,t,o,n){let{step:i,persistedVariables:c}=o,l=e.getTryBlock(),r=e.getCatchClause(),g=e.getFinallyBlock(),d=A(l.getStatements(),{step:i+1,persistedVariables:new Map(c)}),s,S,m=d.nextStep;if(r){let w=r.getBlock(),h=r.getVariableDeclaration();h&&(S=h.getName()),s=A(w.getStatements(),{step:m,persistedVariables:new Map(c)})}let p,u=s?s.nextStep:m;g&&(p=A(g.getStatements(),{step:u,persistedVariables:new Map(c)}));let y=p?p.nextStep:u,f=A(t,{step:y,persistedVariables:c}),b=`{ catchStep: ${r?m:"undefined"}, finallyStep: ${g?u:"undefined"} }`,v=`
61
+ case ${i}: {
62
+ ${n.join(`
53
63
  `)}
54
64
  state.tryCatchStack = state.tryCatchStack || [];
55
- state.tryCatchStack.push(${N});
56
- context.step = ${a+1}; // Salta al inicio del bloque try
65
+ state.tryCatchStack.push(${b});
66
+ context.step = ${i+1}; // Salta al inicio del bloque try
57
67
  break;
58
68
  }
59
- `,I=u.clauses.pop()||"",w=l?f:S;if(u.clauses.push(I.replace(/return { type: 'COMPLETE'.* };/,`context.step = ${w}; break;`)),t){if(m){let P=t.clauses[0]||`case ${p}: {}`;t.clauses[0]=P.replace("{",`{
60
- const ${m} = result as unknown;`)}let E=t.clauses.pop()||"";t.clauses.push(E.replace(/return { type: 'COMPLETE'.* };/,`context.step = ${w}; break;`))}if(d){let E=d.clauses.pop()||"";d.clauses.push(E.replace(/return { type: 'COMPLETE'.* };/,`state.tryCatchStack?.pop(); context.step = ${S}; break;`))}return{clauses:[D,...u.clauses,...t?t.clauses:[],...d?d.clauses:[],...x.clauses],nextStep:x.nextStep}}function re(e,n,o){let s=[],a=new Map,c=ie(n),g=new Map(o);for(let r of e){if(i.isVariableStatement(r))for(let u of r.getDeclarations()){let t=u.getInitializer();if(!t)continue;let m=oe(u),p=m.filter(d=>c.has(d.name));if(p.length>0){let d=k(t,o);for(let{name:f,type:S}of p){a.set(f,{type:S}),g.set(f,{type:S});let x=m.length>1?`${d}.${f}`:d;s.push(`state.${f} = ${x};`)}}}s.push(k(r,g))}return{rewrittenSyncStatements:s,newlyPersistedVariables:a}}function oe(e){let n=e.getNameNode(),o=[];if(i.isIdentifier(n)){let s=e.getType().getText(e,V);o.push({name:n.getText(),type:s})}else if(i.isObjectBindingPattern(n))for(let s of n.getElements()){let a=s.getName(),c=s.getType().getText(s,V);o.push({name:a,type:c})}return o}function ae(e,n){if(i.isReturnStatement(e))return{instruction:`return { type: 'COMPLETE', result: ${e.getExpression()?k(e.getExpressionOrThrow(),n):"undefined"} };`,nextPendingStateAssignment:void 0};if(e.getDescendantsOfKind(b.AwaitExpression).length>1)throw new Error(`[b-durable Compiler Error] Multiple 'await' expressions found in a single statement at line ${e.getStartLineNumber()}.
61
- Please split them into separate lines/variables to ensure safe state persistence.`);let s,a=e.getFirstDescendantByKind(b.VariableDeclaration);if(a){let g=a.getName(),r=a.getType().getText(a,V);n.set(g,{type:r}),s=`state.${g} = result;`}let c=e.getFirstDescendantByKind(b.AwaitExpression);if(c){let g=c.getParent();if(!(i.isExpressionStatement(g)||i.isVariableDeclaration(g)||i.isCallExpression(g)&&i.isExpressionStatement(g.getParent()))&&a&&a.getInitializer()!==c)throw new Error(`[b-durable Compiler Error] Complex 'await' usage detected at line ${e.getStartLineNumber()}.
69
+ `,I=d.clauses.pop()||"",$=g?u:y;if(d.clauses.push(I.replace(/return { type: 'COMPLETE'.* };/,`context.step = ${$}; break;`)),s){if(S){let h=s.clauses[0]||`case ${m}: {}`;s.clauses[0]=h.replace("{",`{
70
+ const ${S} = result as unknown;`)}let w=s.clauses.pop()||"";s.clauses.push(w.replace(/return { type: 'COMPLETE'.* };/,`context.step = ${$}; break;`))}if(p){let w=p.clauses.pop()||"";p.clauses.push(w.replace(/return { type: 'COMPLETE'.* };/,`state.tryCatchStack?.pop(); context.step = ${y}; break;`))}return{clauses:[v,...d.clauses,...s?s.clauses:[],...p?p.clauses:[],...f.clauses],nextStep:f.nextStep}}function ce(e,t,o){let n=[],i=new Map,c=ue(t),l=new Map(o);for(let r of e){if(a.isVariableStatement(r))for(let d of r.getDeclarations()){let s=d.getInitializer();if(!s)continue;let S=le(d),m=S.filter(p=>c.has(p.name));if(m.length>0){let p=k(s,o);for(let{name:u,type:y}of m){i.set(u,{type:y}),l.set(u,{type:y});let f=S.length>1?`${p}.${u}`:p;n.push(`state.${u} = ${f};`)}}}n.push(k(r,l))}return{rewrittenSyncStatements:n,newlyPersistedVariables:i}}function le(e){let t=e.getNameNode(),o=[];if(a.isIdentifier(t)){let n=e.getType().getText(e,L);o.push({name:t.getText(),type:n})}else if(a.isObjectBindingPattern(t))for(let n of t.getElements()){let i=n.getName(),c=n.getType().getText(n,L);o.push({name:i,type:c})}return o}function pe(e,t){if(a.isReturnStatement(e))return{instruction:`return { type: 'COMPLETE', result: ${e.getExpression()?k(e.getExpressionOrThrow(),t):"undefined"} };`,nextPendingStateAssignment:void 0};if(e.getDescendantsOfKind(P.AwaitExpression).length>1)throw new Error(`[b-durable Compiler Error] Multiple 'await' expressions found in a single statement at line ${e.getStartLineNumber()}.
71
+ Please split them into separate lines/variables to ensure safe state persistence.`);let n,i=e.getFirstDescendantByKind(P.VariableDeclaration);if(i){let l=i.getName(),r=i.getType().getText(i,L);t.set(l,{type:r}),n=`state.${l} = result;`}let c=e.getFirstDescendantByKind(P.AwaitExpression);if(c){let l=c.getParent();if(!(a.isExpressionStatement(l)||a.isVariableDeclaration(l)||a.isCallExpression(l)&&a.isExpressionStatement(l.getParent()))&&i&&i.getInitializer()!==c)throw new Error(`[b-durable Compiler Error] Complex 'await' usage detected at line ${e.getStartLineNumber()}.
62
72
  The 'await' keyword must be the direct value of the assignment.
63
73
  Invalid: const x = 1 + await foo();
64
- Valid: const temp = await foo(); const x = 1 + temp;`);let l=c.getExpression();if(i.isCallExpression(l))return{instruction:`return ${ce(l,n)};`,nextPendingStateAssignment:s}}return{instruction:k(e,n),nextPendingStateAssignment:s}}function ie(e){let n=new Set;for(let o of e)o.getDescendantsOfKind(b.Identifier).forEach(s=>{n.add(s.getText())});return n}function k(e,n){let o=e.getText(),s=e.getStart(),a=[],c=e.getDescendantsOfKind(b.Identifier);i.isIdentifier(e)&&c.push(e),c.forEach(r=>{let l=r.getText();if(!n.has(l))return;let u=r.getSymbol();if(u&&u.getDeclarations().some(y=>y.getStart()>=e.getStart()&&y.getEnd()<=e.getEnd()))return;let t=r.getParent(),m=i.isVariableDeclaration(t)&&t.getNameNode()===r,p=i.isPropertyAccessExpression(t)&&t.getNameNode()===r||i.isPropertyAssignment(t)&&t.getNameNode()===r,d=i.isBindingElement(t)&&t.getNameNode()===r,f=i.isShorthandPropertyAssignment(t)&&t.getNameNode()===r,S=i.isParameterDeclaration(t)&&t.getNameNode()===r,x=i.isFunctionDeclaration(t)&&t.getNameNode()===r,N=i.isClassDeclaration(t)&&t.getNameNode()===r,D=i.isInterfaceDeclaration(t)&&t.getNameNode()===r,I=i.isTypeAliasDeclaration(t)&&t.getNameNode()===r,w=i.isEnumDeclaration(t)&&t.getNameNode()===r,E=i.isMethodDeclaration(t)&&t.getNameNode()===r;if(!m&&!p&&!d&&!S&&!x&&!N&&!D&&!I&&!w&&!E){let P=n.get(l),F=r.getStart()-s,y=r.getEnd()-s,h=`(state.${l} as ${P.type})`;f&&(h=`${l}: ${h}`),a.push({start:F,end:y,text:h})}}),a.sort((r,l)=>l.start-r.start);let g=o;for(let{start:r,end:l,text:u}of a)g=g.substring(0,r)+u+g.substring(l);return g}function ce(e,n){let o=e.getExpression(),s,a=!1;i.isPropertyAccessExpression(o)?(o.getExpression().getText()==="context"&&(a=!0),s=o.getName()):s=o.getText();let c=e.getArguments().map(t=>k(t,n)).join(", ");if(a)switch(s){case"bSleep":return`{ type: 'SCHEDULE_SLEEP', duration: ${c} }`;case"bWaitForEvent":return`{ type: 'WAIT_FOR_SIGNAL', signalName: ${c} }`;case"bExecute":{let[t,m]=e.getArguments(),p=t.getText(),d=m?k(m,n):"undefined";return`{ type: 'EXECUTE_SUBWORKFLOW', workflowName: ${p}.name, input: ${d} }`}case"bSignal":{let[t,m]=e.getArguments().map(p=>k(p,n));return`{ type: 'EMIT_EVENT', eventName: ${t}, payload: ${m} }`}default:throw new Error(`Funci\xF3n de contexto durable desconocida: '${s}'.`)}let g=o.getSymbol();if(!g)throw new Error(`S\xEDmbolo no encontrado para '${s}'.`);let r=g.getDeclarations()[0]?.asKind(b.ImportSpecifier);if(!r)throw new Error(`'${s}' debe ser importada.`);let l=r.getImportDeclaration().getModuleSpecifierSourceFileOrThrow();return`{ type: 'SCHEDULE_TASK', modulePath: '${T.relative(process.cwd(),l.getFilePath()).replace(/\\/g,"/")}', exportName: '${s}', args: [${c}] }`}function v(e){for(let n of e.getDescendantsOfKind(b.AwaitExpression)){let o=n.getExpressionIfKind(b.CallExpression);if(o){let s=o.getExpression();if(i.isPropertyAccessExpression(s)){let a=s.getName();if(s.getExpression().getText()==="context"&&(a==="bSleep"||a==="bWaitForEvent"||a==="bExecute"||a==="bSignal")||s.getSymbol()?.getDeclarations()[0]?.isKind(b.ImportSpecifier))return!0}else if(s.getSymbol()?.getDeclarations()[0]?.isKind(b.ImportSpecifier))return!0}}if(i.isTryStatement(e)&&(v(e.getTryBlock())||e.getCatchClause()&&v(e.getCatchClause().getBlock())||e.getFinallyBlock()&&v(e.getFinallyBlock())))return!0;if(i.isIfStatement(e)){let n=v(e.getThenStatement()),o=e.getElseStatement()?v(e.getElseStatement()):!1;return n||o}return i.isBlock(e)?e.getStatements().some(v):!1}function le(e){for(let n=0;n<e.length;n++){let o=e[n];if(i.isReturnStatement(o)||v(o)||i.isTryStatement(o))return{syncBlock:e.slice(0,n),durableStatement:o,nextStatements:e.slice(n+1)}}return{syncBlock:e,durableStatement:null,nextStatements:[]}}var q=e=>{let n=process.argv.indexOf(e);if(n!==-1&&process.argv.length>n+1)return process.argv[n+1]};async function pe(){let e=q("--in"),n=q("--out");(!e||!n)&&(console.error("Uso: b-durable-compiler --in <directorio_entrada> --out <directorio_salida>"),process.exit(1));let o=H.resolve(process.cwd(),e),s=H.resolve(process.cwd(),n);await U({inputDir:o,outputDir:s,packageName:"@bobtail.software/b-durable"})}pe().catch(e=>{console.error("Error durante la compilaci\xF3n:",e),process.exit(1)});
74
+ Valid: const temp = await foo(); const x = 1 + temp;`);let g=c.getExpression();if(a.isCallExpression(g))return{instruction:`return ${ge(g,t)};`,nextPendingStateAssignment:n}}return{instruction:k(e,t),nextPendingStateAssignment:n}}function ue(e){let t=new Set;for(let o of e)o.getDescendantsOfKind(P.Identifier).forEach(n=>{t.add(n.getText())});return t}function k(e,t){let o=e.getText(),n=e.getStart(),i=[],c=e.getDescendantsOfKind(P.Identifier);a.isIdentifier(e)&&c.push(e),c.forEach(r=>{let g=r.getText();if(!t.has(g))return;let d=r.getSymbol();if(d&&d.getDeclarations().some(x=>x.getStart()>=e.getStart()&&x.getEnd()<=e.getEnd()))return;let s=r.getParent(),S=a.isVariableDeclaration(s)&&s.getNameNode()===r,m=a.isPropertyAccessExpression(s)&&s.getNameNode()===r||a.isPropertyAssignment(s)&&s.getNameNode()===r,p=a.isBindingElement(s)&&s.getNameNode()===r,u=a.isShorthandPropertyAssignment(s)&&s.getNameNode()===r,y=a.isParameterDeclaration(s)&&s.getNameNode()===r,f=a.isFunctionDeclaration(s)&&s.getNameNode()===r,b=a.isClassDeclaration(s)&&s.getNameNode()===r,v=a.isInterfaceDeclaration(s)&&s.getNameNode()===r,I=a.isTypeAliasDeclaration(s)&&s.getNameNode()===r,$=a.isEnumDeclaration(s)&&s.getNameNode()===r,w=a.isMethodDeclaration(s)&&s.getNameNode()===r;if(!S&&!m&&!p&&!y&&!f&&!b&&!v&&!I&&!$&&!w){let h=t.get(g),V=r.getStart()-n,x=r.getEnd()-n,E=`(state.${g} as ${h.type})`;u&&(E=`${g}: ${E}`),i.push({start:V,end:x,text:E})}}),i.sort((r,g)=>g.start-r.start);let l=o;for(let{start:r,end:g,text:d}of i)l=l.substring(0,r)+d+l.substring(g);return l}function ge(e,t){let o=e.getExpression(),n,i=!1;a.isPropertyAccessExpression(o)?(o.getExpression().getText()==="context"&&(i=!0),n=o.getName()):n=o.getText();let c=e.getArguments().map(s=>k(s,t)).join(", ");if(i)switch(n){case"bSleep":return`{ type: 'SCHEDULE_SLEEP', duration: ${c} }`;case"bWaitForEvent":return`{ type: 'WAIT_FOR_SIGNAL', signalName: ${c} }`;case"bExecute":{let[s,S]=e.getArguments(),m=s.getText(),p=S?k(S,t):"undefined";return`{ type: 'EXECUTE_SUBWORKFLOW', workflowName: ${m}.name, input: ${p} }`}case"bSignal":{let[s,S]=e.getArguments().map(m=>k(m,t));return`{ type: 'EMIT_EVENT', eventName: ${s}, payload: ${S} }`}default:throw new Error(`Funci\xF3n de contexto durable desconocida: '${n}'.`)}let l=o.getSymbol();if(!l)throw new Error(`S\xEDmbolo no encontrado para '${n}'.`);let r=l.getDeclarations()[0]?.asKind(P.ImportSpecifier);if(!r)throw new Error(`'${n}' debe ser importada.`);let g=r.getImportDeclaration().getModuleSpecifierSourceFileOrThrow();return`{ type: 'SCHEDULE_TASK', modulePath: '${N.relative(process.cwd(),g.getFilePath()).replace(/\\/g,"/")}', exportName: '${n}', args: [${c}] }`}function O(e){for(let t of e.getDescendantsOfKind(P.AwaitExpression)){let o=t.getExpressionIfKind(P.CallExpression);if(o){let n=o.getExpression();if(a.isPropertyAccessExpression(n)){let i=n.getName();if(n.getExpression().getText()==="context"&&(i==="bSleep"||i==="bWaitForEvent"||i==="bExecute"||i==="bSignal")||n.getSymbol()?.getDeclarations()[0]?.isKind(P.ImportSpecifier))return!0}else if(n.getSymbol()?.getDeclarations()[0]?.isKind(P.ImportSpecifier))return!0}}if(a.isTryStatement(e)&&(O(e.getTryBlock())||e.getCatchClause()&&O(e.getCatchClause().getBlock())||e.getFinallyBlock()&&O(e.getFinallyBlock())))return!0;if(a.isIfStatement(e)){let t=O(e.getThenStatement()),o=e.getElseStatement()?O(e.getElseStatement()):!1;return t||o}return a.isBlock(e)?e.getStatements().some(O):!1}function de(e){for(let t=0;t<e.length;t++){let o=e[t];if(a.isReturnStatement(o)||O(o)||a.isTryStatement(o))return{syncBlock:e.slice(0,t),durableStatement:o,nextStatements:e.slice(t+1)}}return{syncBlock:e,durableStatement:null,nextStatements:[]}}var G=e=>{let t=process.argv.indexOf(e);if(t!==-1&&process.argv.length>t+1)return process.argv[t+1]};async function me(){let e=G("--in"),t=G("--out"),n=process.argv.slice(2).includes("dev")?"dev":"prod";(!e||!t)&&(console.error("Uso: b-durable-compiler <dev|prod> --in <src> --out <generated>"),process.exit(1));let i=q.resolve(process.cwd(),e),c=q.resolve(process.cwd(),t);await H({inputDir:i,outputDir:c,packageName:"@bobtail.software/b-durable",mode:n})}me().catch(e=>{console.error("Error durante la compilaci\xF3n:",e),process.exit(1)});
package/dist/index.d.mts CHANGED
@@ -23,6 +23,7 @@ interface WorkflowStateInfo<TInput = unknown, TOutput = unknown> {
23
23
  name: string;
24
24
  attempts: number;
25
25
  };
26
+ tags?: string[];
26
27
  }
27
28
  interface WorkflowState {
28
29
  tryCatchStack?: {
@@ -121,6 +122,7 @@ type Compute<T> = {
121
122
  type StartOptions<TInput, TEvents> = Compute<{
122
123
  input: TInput;
123
124
  workflowId?: string;
125
+ tags?: string[];
124
126
  } & WorkflowEventListeners<TEvents>>;
125
127
  interface StartedWorkflowHandle {
126
128
  workflowId: string;
@@ -185,6 +187,7 @@ declare class DurableRuntime {
185
187
  private propagateFailureToParent;
186
188
  signal<T>(workflowId: string, signalName: string, payload: T): Promise<void>;
187
189
  cancel(workflowId: string, reason: string): Promise<void>;
190
+ cancelByTag(tag: string, reason: string): Promise<void>;
188
191
  private startScheduler;
189
192
  private checkDelayedTasks;
190
193
  private checkSleepers;
@@ -260,6 +263,7 @@ interface BDurableAPI {
260
263
  runtime: DurableRuntime;
261
264
  cancel: (workflowId: string, reason: string) => Promise<void>;
262
265
  getState: (workflowId: string) => Promise<WorkflowStateInfo | null>;
266
+ cancelByTag: (tag: string, reason: string) => Promise<void>;
263
267
  /**
264
268
  * Obtiene un "handle" para una instancia de workflow existente.
265
269
  * Permite enviar señales (Input) y escuchar eventos (Output).
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import{randomUUID as z}from"crypto";import q from"ioredis";var E="queue:tasks",I="durable:sleepers",R="worker:heartbeat:",k="durable:workers",x="queue:dead",L="queue:tasks:delayed";function C(i){return`workflow:${i}`}var c={RUNNING:"RUNNING",SLEEPING:"SLEEPING",COMPLETED:"COMPLETED",FAILED:"FAILED",AWAITING_SIGNAL:"AWAITING_SIGNAL",AWAITING_SUBWORKFLOW:"AWAITING_SUBWORKFLOW",CANCELLING:"CANCELLING",CANCELLED:"CANCELLED",VERSION_MISMATCH:"VERSION_MISMATCH"};var l,y;function N(i){if(l||y){console.warn("[Persistence] Los clientes de Redis ya han sido configurados. Omitiendo.");return}l=i.commandClient,y=i.blockingClient}import{randomUUID as W}from"crypto";import v from"ms";import{resolve as V}from"path";var S=class extends Error{isCancellation=!0;constructor(t){super(t),this.name="WorkflowCancellationError"}};var P=`
1
+ import{randomUUID as B}from"crypto";import q from"ioredis";var m="queue:tasks",y="durable:sleepers",A="worker:heartbeat:",k="durable:workers",P="queue:dead",L="queue:tasks:delayed",I="index:tag:";function N(i){return`workflow:${i}`}var c={RUNNING:"RUNNING",SLEEPING:"SLEEPING",COMPLETED:"COMPLETED",FAILED:"FAILED",AWAITING_SIGNAL:"AWAITING_SIGNAL",AWAITING_SUBWORKFLOW:"AWAITING_SUBWORKFLOW",CANCELLING:"CANCELLING",CANCELLED:"CANCELLED",VERSION_MISMATCH:"VERSION_MISMATCH"};var l,b;function C(i){if(l||b){console.warn("[Persistence] Los clientes de Redis ya han sido configurados. Omitiendo.");return}l=i.commandClient,b=i.blockingClient}import{randomUUID as K}from"crypto";import v from"ms";import{resolve as Y}from"path";var S=class extends Error{isCancellation=!0;constructor(t){super(t),this.name="WorkflowCancellationError"}};var _=`
2
2
  if redis.call("get", KEYS[1]) == ARGV[1] then
3
3
  return redis.call("del", KEYS[1])
4
4
  else
@@ -10,7 +10,7 @@ if redis.call("get", KEYS[1]) == ARGV[1] then
10
10
  else
11
11
  return 0
12
12
  end
13
- `,A=`
13
+ `,W=`
14
14
  local lockKey = KEYS[1]
15
15
  local workflowKey = KEYS[2]
16
16
  local token = ARGV[1]
@@ -24,7 +24,7 @@ if redis.call("get", lockKey) == token then
24
24
  else
25
25
  return 0
26
26
  end
27
- `,_=`
27
+ `,F=`
28
28
  local lockKey = KEYS[1]
29
29
  local workflowKey = KEYS[2]
30
30
  local token = ARGV[1]
@@ -34,7 +34,7 @@ if redis.call("get", lockKey) == token then
34
34
  else
35
35
  return -1
36
36
  end
37
- `,F=`
37
+ `,$=`
38
38
  local lockKey = KEYS[1]
39
39
  local workflowKey = KEYS[2]
40
40
  local token = ARGV[1]
@@ -47,7 +47,7 @@ if redis.call("get", lockKey) == token then
47
47
  else
48
48
  return 0
49
49
  end
50
- `,$=`
50
+ `,G=`
51
51
  local lockKey = KEYS[1]
52
52
  local workflowKey = KEYS[2]
53
53
  local token = ARGV[1]
@@ -60,7 +60,7 @@ if redis.call("get", lockKey) == token then
60
60
  else
61
61
  return 0
62
62
  end
63
- `,G=`
63
+ `,U=`
64
64
  local limit = tonumber(ARGV[1])
65
65
  local now = tonumber(ARGV[2])
66
66
  local key = KEYS[1]
@@ -70,7 +70,7 @@ end
70
70
  redis.call('zrem', key, unpack(ids))
71
71
  end
72
72
  return ids
73
- `,U=`
73
+ `,M=`
74
74
  local sourceZSet = KEYS[1]
75
75
  local destList = KEYS[2]
76
76
  local now = tonumber(ARGV[1])
@@ -88,4 +88,4 @@ end
88
88
  end
89
89
 
90
90
  return #tasks
91
- `;import M from"superjson";function h(i){return M.stringify(i)}function m(i){try{return M.parse(i)}catch(t){try{return JSON.parse(i)}catch(e){throw new Error(`Failed to deserialize data: ${t} ${e}`)}}}var Y={info:(i,t)=>console.log(`[INFO] ${i}`,t||""),error:(i,t)=>console.error(`[ERROR] ${i}`,t||""),warn:(i,t)=>console.warn(`[WARN] ${i}`,t||""),debug:(i,t)=>console.debug(`[DEBUG] ${i}`,t||"")},K=class{constructor(t){this.retention=t}getKey(t){return`workflow:${t}`}getLockKey(t){return`workflow:${t}:lock`}async acquireLock(t,e=10){let s=this.getLockKey(t),n=W();return await l.set(s,n,"EX",e,"NX")==="OK"?n:null}async releaseLock(t,e){await l.eval(P,1,this.getLockKey(t),e)}async renewLock(t,e,s){return await l.eval(D,1,this.getLockKey(t),e,s)===1}async get(t){let e=await l.hgetall(this.getKey(t));return!e||Object.keys(e).length===0?null:{workflowId:e.workflowId,name:e.name,version:e.version,status:e.status,step:parseInt(e.step,10),input:m(e.input),state:m(e.state),result:e.result?m(e.result):void 0,error:e.error,parentId:e.parentId,subWorkflowId:e.subWorkflowId,awaitingSignal:e.awaitingSignal||e.awaitingEvent,createdAt:e.createdAt?parseInt(e.createdAt,10):0,updatedAt:e.updatedAt?parseInt(e.updatedAt,10):0}}async create(t){let e=Date.now(),s={...t,step:0,state:{},createdAt:e,updatedAt:e},n={...s,input:h(s.input),state:h(s.state)};n.version===void 0&&delete n.version;let r=l.pipeline();r.hset(this.getKey(s.workflowId),n),await r.exec()}async updateState(t,e,s,n){if(await l.eval(A,2,this.getLockKey(t),this.getKey(t),n,h(e),Date.now(),s)===0)throw new Error(`Lock lost for workflow ${t}`)}async updateStatus(t,e,s={}){await l.hset(this.getKey(t),{status:e,...s,updatedAt:Date.now()})}async incrementStep(t,e){let s=await l.eval(_,2,this.getLockKey(t),this.getKey(t),e);if(s===-1)throw new Error(`Lock lost for workflow ${t}`);return s}async applyRetention(t){if(this.retention){let e=v(this.retention)/1e3;e>0&&await l.expire(this.getKey(t),e)}}async complete(t,e,s){if(await l.eval(F,2,this.getLockKey(t),this.getKey(t),s,h(e??null),c.COMPLETED)===0)throw new Error(`Lock lost for workflow ${t}`);await this.applyRetention(t)}async fail(t,e,s,n=c.FAILED){s?await l.eval($,2,this.getLockKey(t),this.getKey(t),s,e.message,n)===0&&console.warn(`Could not fail workflow ${t} safely: Lock lost.`):await l.hset(this.getKey(t),{status:n,error:e.message}),await this.applyRetention(t)}async scheduleSleep(t,e){await this.updateStatus(t,c.SLEEPING),await l.zadd(I,e,t)}async getWorkflowsToWake(t=100){let e=Date.now();return await l.eval(G,1,I,t,e)}async enqueueTask(t){await l.lpush(E,h(t))}async resumeForCatch(t,e,s,n){if(await l.eval(A,2,this.getLockKey(t),this.getKey(t),n,h(e),Date.now(),s)===0)throw new Error(`Lock lost for workflow ${t}`);await l.hset(this.getKey(t),{status:c.RUNNING})}async moveToDLQ(t,e){let s={...t,failedAt:Date.now(),error:e.message,stack:e.stack};await l.lpush(x,h(s))}async scheduleTaskRetry(t,e){let s=Date.now()+e;await l.zadd(L,s,h(t))}async moveDueTasksToQueue(t=100){return await l.eval(U,2,L,E,Date.now(),t)}},b=class{durableFns=new Map;repo;workerId=W();isRunning=!1;schedulerInterval=null;heartbeatInterval=null;sourceRoot;pollingInterval;logger;maxTaskRetries=3;constructor(t){this.sourceRoot=t.sourceRoot,this.repo=new K(t.retention),this.pollingInterval=t.pollingInterval||5e3,this.logger=t.logger||Y}async getState(t){let e=await this.repo.get(t);return e?{workflowId:e.workflowId,name:e.name,version:e.version,status:e.status,step:e.step,input:e.input,output:e.result,state:e.state,error:e.error,createdAt:e.createdAt,updatedAt:e.updatedAt}:null}async start(t,e,s){let n=e.workflowId||W();if(e.workflowId){let r=await this.repo.get(e.workflowId);if(r&&r.status!==c.COMPLETED&&r.status!==c.FAILED)throw new Error(`Workflow with ID '${e.workflowId}' already exists and is in a running state (${r.status}).`)}return this.logger.info(`[RUNTIME] Iniciando workflow '${t.name}' v${t.version} con ID: ${n}`),await this.repo.create({workflowId:n,name:t.name,version:t.version,status:c.RUNNING,input:e.input,parentId:s}),setImmediate(()=>{this._executeStep(n,t).catch(r=>{this.logger.error("Error fatal en ejecuci\xF3n inicial",{error:r,workflowId:n})})}),{workflowId:n,unsubscribe:async()=>{}}}async scheduleExecution(t,e,s,n){setImmediate(()=>{this._executeStep(t,e,s,n).catch(r=>{this.logger.error("Error no manejado en scheduleExecution",{error:r,workflowId:t})})})}async _executeStep(t,e,s,n){let r=await this.repo.acquireLock(t);if(!r)return;let o=setInterval(()=>{this.repo.renewLock(t,r,10).catch(a=>this.logger.warn(`Error renovando lock para ${t}`,{error:a}))},5e3);try{if(n)throw n;let a=await this.repo.get(t);if(!a)return;if(a.status===c.CANCELLING)throw new S(a.error||"Workflow cancelled");if(a.status!==c.RUNNING)return;let p=a.version==="undefined"?void 0:a.version,d=e.version==="undefined"?void 0:e.version;if(String(p??"")!==String(d??"")){let w=new Error(`Version mismatch: DB=${p}, Code=${d}`);await this.repo.fail(t,w,r,c.VERSION_MISMATCH);return}let u={workflowId:t,step:a.step,input:a.input,state:a.state,result:s,log:(w,T)=>this.logger.info(w,{...T,workflowId:t,step:a.step})},g=await e.execute(u);await this.repo.updateState(t,u.state,u.step,r),await this.handleInstruction(g,u,a.name,r)&&(await this.repo.incrementStep(t,r),this.scheduleExecution(t,e,void 0))}catch(a){let p=a instanceof Error?a:new Error(String(a));this.logger.error("Error en workflow",{workflowId:t,error:p.message}),await this.handleFailure(t,p,e,r)}finally{clearInterval(o),await this.repo.releaseLock(t,r)}}async handleInstruction(t,e,s,n){let{workflowId:r}=e;switch(t.type){case"SCHEDULE_TASK":return await this.repo.enqueueTask({workflowId:r,durableFunctionName:s,...t}),!1;case"SCHEDULE_SLEEP":{let o=v(t.duration);if(typeof o!="number")throw new Error(`Invalid time value provided to bSleep: "${t.duration}"`);let a=Date.now()+o;return await this.repo.scheduleSleep(r,a),!1}case"WAIT_FOR_SIGNAL":return await this.repo.updateStatus(r,c.AWAITING_SIGNAL,{awaitingSignal:t.signalName}),await l.sadd(`signals:awaiting:${t.signalName}`,r),!1;case"EXECUTE_SUBWORKFLOW":{let o=this.durableFns.get(t.workflowName);if(!o)throw new Error(`Sub-workflow '${t.workflowName}' no encontrado.`);let{workflowId:a}=await this.start(o,{input:t.input},r);return await this.repo.updateStatus(r,c.AWAITING_SUBWORKFLOW,{subWorkflowId:a}),!1}case"EMIT_EVENT":{let o=`event:${r}`,a=h({eventName:t.eventName,payload:t.payload});return await l.publish(o,a),!0}case"COMPLETE":{let o=`event:${r}`,a=h({eventName:"workflow:completed",payload:t.result});return await l.publish(o,a),await this.repo.complete(r,t.result,n),await this.resumeParentWorkflow(r),!1}}}async handleFailure(t,e,s,n){let r=n;if(!r&&(r=await this.repo.acquireLock(t,20),!r)){this.logger.warn(`No se pudo adquirir lock para fallo en ${t}`);return}try{if(e instanceof S){await this.repo.fail(t,e,r,c.CANCELLED);let u=await this.repo.get(t);u?.subWorkflowId&&await this.cancel(u.subWorkflowId,`Parent workflow ${t} was cancelled`);return}let o=await this.repo.get(t);if(!o||o.status===c.FAILED||o.status===c.COMPLETED)return;let a=o.state.tryCatchStack;if(a&&a.length>0){let g=a.pop()?.catchStep;if(g!==void 0){this.logger.info(`Capturando error en step ${g}`,{workflowId:t}),await this.repo.resumeForCatch(t,o.state,g,r),this.scheduleExecution(t,s,{name:e.name,message:e.message,stack:e.stack});return}}let p=`event:${t}`,d=h({eventName:"workflow:failed",payload:{message:e.message}});await l.publish(p,d),await this.repo.fail(t,e,r),await this.propagateFailureToParent(t,e)}finally{!n&&r&&await this.repo.releaseLock(t,r)}}async resumeParentWorkflow(t){let e=await this.repo.get(t);if(!e?.parentId)return;let s=e.parentId,n=await this.repo.get(s);if(!n||n.status!==c.AWAITING_SUBWORKFLOW||n.subWorkflowId!==t)return;let r=this.durableFns.get(n.name);if(!r){await this.repo.fail(s,new Error(`Definici\xF3n del workflow '${n.name}' no encontrada.`),null);return}await this.repo.updateStatus(s,c.RUNNING,{subWorkflowId:""});let o=await this.repo.acquireLock(s);if(o)try{await this.repo.incrementStep(s,o),this.scheduleExecution(s,r,e.result)}finally{await this.repo.releaseLock(s,o)}else throw this.logger.warn(`Could not lock parent ${s} to resume. Retrying later...`),new Error(`Temporary Lock Failure: Could not acquire parent lock for ${s}`)}async propagateFailureToParent(t,e){let s=await this.repo.get(t);if(!s?.parentId)return;let n=s.parentId,r=await this.repo.get(n);if(!r||r.status!==c.AWAITING_SUBWORKFLOW||r.subWorkflowId!==t)return;let o=this.durableFns.get(r.name);if(!o){await this.repo.fail(n,new Error(`Definici\xF3n del workflow '${r.name}' no encontrada al propagar fallo.`),null);return}await this.repo.updateStatus(n,c.RUNNING,{subWorkflowId:""});let a=new Error(`Sub-workflow '${s.name}' (${t}) fall\xF3: ${e.message}`);a.stack=e.stack,this.scheduleExecution(n,o,void 0,a)}async signal(t,e,s){let n=null;for(let r=0;r<3&&(n=await this.repo.acquireLock(t),!n);r++)await new Promise(o=>setTimeout(o,50));if(!n)return this.logger.warn("Lock timeout en signal",{workflowId:t});try{let r=await this.repo.get(t);if(!r)return this.logger.warn("Se\xF1al para workflow inexistente",{workflowId:t});if(r.status!==c.AWAITING_SIGNAL||r.awaitingSignal!==e)return this.logger.warn("Workflow no esperaba esta se\xF1al",{workflowId:t,expected:r.awaitingSignal,received:e});let o=this.durableFns.get(r.name);if(!o){await this.repo.fail(t,new Error(`Funci\xF3n durable '${r.name}' no encontrada.`),n);return}await this.repo.updateStatus(t,c.RUNNING,{awaitingSignal:""}),await l.srem(`signals:awaiting:${e}`,t),await this.repo.incrementStep(t,n),this.scheduleExecution(t,o,s)}catch(r){let o=r instanceof Error?r:new Error(String(r)),a=(await this.repo.get(t))?.name||"",p=this.durableFns.get(a);await this.handleFailure(t,o,p,n)}finally{n&&await this.repo.releaseLock(t,n)}}async cancel(t,e){let s=await this.repo.acquireLock(t);if(!s)return await new Promise(n=>setTimeout(n,100)),this.cancel(t,e);try{let n=await this.repo.get(t);if(!n||[c.COMPLETED,c.FAILED,c.CANCELLED].includes(n.status))return;if(await this.repo.updateStatus(t,c.CANCELLING,{error:e}),n.status===c.SLEEPING){await l.zrem(I,t);let r=this.durableFns.get(n.name);this.scheduleExecution(t,r)}if(n.status===c.AWAITING_SIGNAL){let r=this.durableFns.get(n.name);this.scheduleExecution(t,r)}}finally{await this.repo.releaseLock(t,s)}}startScheduler(){if(this.schedulerInterval)return;this.logger.info(`Scheduler iniciado (${this.pollingInterval}ms)`);let t=async()=>{await this.checkSleepers(),await this.checkDelayedTasks(),await this.reapDeadWorkers()};this.schedulerInterval=setInterval(t,this.pollingInterval)}async checkDelayedTasks(){try{let t=await this.repo.moveDueTasksToQueue(50);t>0&&this.logger.debug(`Scheduler movi\xF3 ${t} tareas diferidas a la cola activa`)}catch(t){this.logger.error("Error chequeando tareas diferidas",{error:t})}}async checkSleepers(){let e=await this.repo.getWorkflowsToWake(50);e.length!==0&&await Promise.all(e.map(async s=>{let n=await this.repo.acquireLock(s);if(n)try{let r=await this.repo.get(s);if(r){let o=this.durableFns.get(r.name);o&&(this.logger.info("Despertando workflow",{workflowId:s}),await this.repo.updateStatus(s,c.RUNNING),await this.repo.incrementStep(s,n),this.scheduleExecution(s,o,void 0))}}finally{await this.repo.releaseLock(s,n)}}))}async reapDeadWorkers(){let t="0";do{let[e,s]=await l.sscan(k,t,"COUNT",100);t=e;for(let n of s){if(await l.exists(`${R}${n}`))continue;this.logger.warn(`Worker muerto ${n}. Recuperando tareas.`);let r=`${E}:processing:${n}`,o=await l.rpoplpush(r,E);for(;o;)o=await l.rpoplpush(r,E);await l.del(r),await l.srem(k,n)}}while(t!=="0")}startHeartbeat(){let t=`${R}${this.workerId}`,e=Math.max(Math.ceil(this.pollingInterval*3/1e3),5),s=()=>{this.isRunning&&l.set(t,Date.now().toString(),"EX",e).catch(()=>{})};this.heartbeatInterval=setInterval(s,this.pollingInterval),s()}startWorker(){if(this.isRunning)return;this.isRunning=!0;let t=`${E}:processing:${this.workerId}`;this.logger.info(`Worker ${this.workerId} iniciado`),this.startHeartbeat(),(async()=>{for(await l.sadd(k,this.workerId);this.isRunning;)try{let s=await y.brpoplpush(E,t,2);if(!s)continue;let n=m(s);this.logger.debug(`Ejecutando tarea: ${n.exportName}`,{workflowId:n.workflowId});try{let r;n.modulePath.startsWith("virtual:")?r=await import(n.modulePath):r=await import(V(this.sourceRoot,n.modulePath));let o=r[n.exportName];if(typeof o!="function")throw new Error(`'${n.exportName}' no es una funci\xF3n.`);let a=await o(...n.args),p=this.durableFns.get(n.durableFunctionName);if(p){let d=await this.repo.acquireLock(n.workflowId);if(d)try{await this.repo.incrementStep(n.workflowId,d),this.scheduleExecution(n.workflowId,p,a)}finally{await this.repo.releaseLock(n.workflowId,d)}else this.logger.warn(`No se pudo adquirir lock para avanzar workflow ${n.workflowId} tras tarea`,{task:n.exportName})}await l.lrem(t,1,s)}catch(r){let o=r instanceof Error?r:new Error(String(r));this.logger.error(`Fallo en tarea ${n.exportName}`,{workflowId:n.workflowId,error:o.message});let a=this.durableFns.get(n.durableFunctionName),p=a?.retryOptions||{},d=p.maxAttempts??3,u=(n.attempts||0)+1;if(n.attempts=u,u<=d){let g=p.initialInterval?v(p.initialInterval):1e3,f=p.backoffCoefficient??2,w=p.maxInterval?v(p.maxInterval):36e5,T=g*Math.pow(f,u-1);T>w&&(T=w),this.logger.warn(`Reintentando tarea en ${v(T)} (intento ${u}/${d===1/0?"Inf":d})`,{workflowId:n.workflowId}),T>0?await this.repo.scheduleTaskRetry(n,T):await l.lpush(E,h(n)),await l.lrem(t,1,s)}else this.logger.error("Reintentos agotados. Moviendo a DLQ.",{workflowId:n.workflowId}),await this.repo.moveToDLQ(n,o),a?await this.handleFailure(n.workflowId,o,a,null):await this.repo.fail(n.workflowId,new Error(`Def missing for ${n.durableFunctionName}`),null),await l.lrem(t,1,s)}}catch(s){if(!this.isRunning)break;this.logger.error("Error infraestructura worker",{error:s}),await new Promise(n=>setTimeout(n,5e3))}})()}run(t){this.durableFns=t,this.startWorker(),this.startScheduler()}async stop(){this.isRunning=!1,this.schedulerInterval&&clearInterval(this.schedulerInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),await l.srem(k,this.workerId),this.logger.info("Runtime detenido")}};var H=i=>({...i,__isDurable:!0});var B={info:(i,t)=>console.log(`[INFO] ${i}`,t||""),error:(i,t)=>console.error(`[ERROR] ${i}`,t||""),warn:(i,t)=>console.warn(`[WARN] ${i}`,t||""),debug:(i,t)=>console.debug(`[DEBUG] ${i}`,t||"")},Q=i=>{if(!i.startsWith("on")||i.length<=2)return null;let t=i.slice(2);return t.charAt(0).toLowerCase()+t.slice(1)};function Rt(i){let t=i.logger||B;t.info("--- Inicializando Sistema Durable ---"),N({commandClient:i.redisClient,blockingClient:i.blockingRedisClient});let e=new b({sourceRoot:i.sourceRoot,retention:i.retention,pollingInterval:i.pollingInterval,logger:t});e.run(i.durableFunctions);let s=new q(i.redisClient.options),n=new Map;s.psubscribe("event:*",o=>{o&&t.error("Error fatal al suscribirse a los canales de eventos:",{error:o})}),s.on("pmessage",(o,a,p)=>{let d=n.get(a);if(d&&d.length>0)try{let u=m(p),g={name:u.eventName||u.signalName,payload:u.payload};[...d].forEach(f=>f(g))}catch(u){t.error(`Error al parsear evento en ${a}`,{error:u})}});let r=(o,a)=>{let p=`event:${a}`,d=u=>(n.has(p)||n.set(p,[]),n.get(p)?.push(u),()=>{let g=n.get(p);if(g){let f=g.indexOf(u);f>-1&&g.splice(f,1),g.length===0&&n.delete(p)}});return{workflowId:a,signal:async(u,g)=>{await e.signal(a,u,g)},on:async(u,g)=>{let f=C(a),w=await i.redisClient.hgetall(f);return u==="workflow:completed"&&w.status===c.COMPLETED?(g(m(w.result||"null")),{unsubscribe:()=>{}}):u==="workflow:failed"&&w.status===c.FAILED?(g({message:w.error||"Unknown"}),{unsubscribe:()=>{}}):{unsubscribe:d(O=>{O.name===u&&g(O.payload)})}},subscribe:async u=>({unsubscribe:d(f=>{u(f)})})}};return{start:async(o,a)=>{let p=a.workflowId||z(),d=[],u=`event:${p}`,g={};if(Object.keys(a).forEach(f=>{let w=Q(f);w&&typeof a[f]=="function"&&(g[w]=a[f])}),Object.keys(g).length>0){n.has(u)||n.set(u,[]);let f=w=>{let T=g[w.name];T&&T(w.payload)};n.get(u)?.push(f),d.push(()=>{let w=n.get(u);if(w){let T=w.indexOf(f);T>-1&&w.splice(T,1)}})}return await e.start(o,{workflowId:p,input:a.input}),{workflowId:p,unsubscribe:async()=>{d.forEach(f=>f())}}},stop:()=>{e.stop(),s.quit().catch(()=>{})},runtime:e,cancel:(o,a)=>e.cancel(o,a),getState:o=>e.getState(o),getHandle:(o,a)=>r(o,a)}}export{S as WorkflowCancellationError,H as bDurable,Rt as bDurableInitialize};
91
+ `;import V from"superjson";function h(i){return V.stringify(i)}function E(i){try{return V.parse(i)}catch(t){try{return JSON.parse(i)}catch(e){throw new Error(`Failed to deserialize data: ${t} ${e}`)}}}var H={info:(i,t)=>console.log(`[INFO] ${i}`,t||""),error:(i,t)=>console.error(`[ERROR] ${i}`,t||""),warn:(i,t)=>console.warn(`[WARN] ${i}`,t||""),debug:(i,t)=>console.debug(`[DEBUG] ${i}`,t||"")},x=class{constructor(t){this.retention=t}getKey(t){return`workflow:${t}`}getLockKey(t){return`workflow:${t}:lock`}async acquireLock(t,e=10){let r=this.getLockKey(t),n=K();return await l.set(r,n,"EX",e,"NX")==="OK"?n:null}async releaseLock(t,e){await l.eval(_,1,this.getLockKey(t),e)}async renewLock(t,e,r){return await l.eval(D,1,this.getLockKey(t),e,r)===1}async get(t){let e=await l.hgetall(this.getKey(t));return!e||Object.keys(e).length===0?null:{workflowId:e.workflowId,name:e.name,version:e.version,status:e.status,step:parseInt(e.step,10),input:E(e.input),state:E(e.state),result:e.result?E(e.result):void 0,error:e.error,parentId:e.parentId,subWorkflowId:e.subWorkflowId,awaitingSignal:e.awaitingSignal||e.awaitingEvent,createdAt:e.createdAt?parseInt(e.createdAt,10):0,updatedAt:e.updatedAt?parseInt(e.updatedAt,10):0,tags:e.tags?JSON.parse(e.tags):[]}}async create(t){let e=Date.now(),r={...t,step:0,state:{},createdAt:e,updatedAt:e,tags:t.tags||[]},n={...r,input:h(r.input),state:h(r.state),tags:JSON.stringify(r.tags)};n.version===void 0&&delete n.version;let s=l.pipeline();if(s.hset(this.getKey(r.workflowId),n),r.tags&&r.tags.length>0)for(let a of r.tags)s.sadd(`${I}${a}`,r.workflowId);await s.exec()}async getIdsByTag(t){return l.smembers(`${I}${t}`)}async removeTagIndex(t,e){if(!e.length)return;let r=l.pipeline();for(let n of e)r.srem(`${I}${n}`,t);await r.exec()}async updateState(t,e,r,n){if(await l.eval(W,2,this.getLockKey(t),this.getKey(t),n,h(e),Date.now(),r)===0)throw new Error(`Lock lost for workflow ${t}`)}async updateStatus(t,e,r={}){await l.hset(this.getKey(t),{status:e,...r,updatedAt:Date.now()})}async incrementStep(t,e){let r=await l.eval(F,2,this.getLockKey(t),this.getKey(t),e);if(r===-1)throw new Error(`Lock lost for workflow ${t}`);return r}async applyRetention(t){if(this.retention){let e=v(this.retention)/1e3;e>0&&await l.expire(this.getKey(t),e)}}async complete(t,e,r){if(await l.eval($,2,this.getLockKey(t),this.getKey(t),r,h(e??null),c.COMPLETED)===0)throw new Error(`Lock lost for workflow ${t}`);await this.applyRetention(t)}async fail(t,e,r,n=c.FAILED){r?await l.eval(G,2,this.getLockKey(t),this.getKey(t),r,e.message,n)===0&&console.warn(`Could not fail workflow ${t} safely: Lock lost.`):await l.hset(this.getKey(t),{status:n,error:e.message}),await this.applyRetention(t)}async scheduleSleep(t,e){await this.updateStatus(t,c.SLEEPING),await l.zadd(y,e,t)}async getWorkflowsToWake(t=100){let e=Date.now();return await l.eval(U,1,y,t,e)}async enqueueTask(t){await l.lpush(m,h(t))}async resumeForCatch(t,e,r,n){if(await l.eval(W,2,this.getLockKey(t),this.getKey(t),n,h(e),Date.now(),r)===0)throw new Error(`Lock lost for workflow ${t}`);await l.hset(this.getKey(t),{status:c.RUNNING})}async moveToDLQ(t,e){let r={...t,failedAt:Date.now(),error:e.message,stack:e.stack};await l.lpush(P,h(r))}async scheduleTaskRetry(t,e){let r=Date.now()+e;await l.zadd(L,r,h(t))}async moveDueTasksToQueue(t=100){return await l.eval(M,2,L,m,Date.now(),t)}},R=class{durableFns=new Map;repo;workerId=K();isRunning=!1;schedulerInterval=null;heartbeatInterval=null;sourceRoot;pollingInterval;logger;maxTaskRetries=3;constructor(t){this.sourceRoot=t.sourceRoot,this.repo=new x(t.retention),this.pollingInterval=t.pollingInterval||5e3,this.logger=t.logger||H}async getState(t){let e=await this.repo.get(t);return e?{workflowId:e.workflowId,name:e.name,version:e.version,status:e.status,step:e.step,input:e.input,output:e.result,state:e.state,error:e.error,createdAt:e.createdAt,updatedAt:e.updatedAt,tags:e.tags}:null}async start(t,e,r){let n=e.workflowId||K();if(e.workflowId){let s=await this.repo.get(e.workflowId);if(s&&s.status!==c.COMPLETED&&s.status!==c.FAILED)throw new Error(`Workflow with ID '${e.workflowId}' already exists and is in a running state (${s.status}).`)}return this.logger.info(`[RUNTIME] Iniciando workflow '${t.name}' v${t.version} con ID: ${n}`),await this.repo.create({workflowId:n,name:t.name,version:t.version,status:c.RUNNING,input:e.input,parentId:r,tags:e.tags||[]}),setImmediate(()=>{this._executeStep(n,t).catch(s=>{this.logger.error("Error fatal en ejecuci\xF3n inicial",{error:s,workflowId:n})})}),{workflowId:n,unsubscribe:async()=>{}}}async scheduleExecution(t,e,r,n){setImmediate(()=>{this._executeStep(t,e,r,n).catch(s=>{this.logger.error("Error no manejado en scheduleExecution",{error:s,workflowId:t})})})}async _executeStep(t,e,r,n){let s=await this.repo.acquireLock(t);if(!s)return;let a=setInterval(()=>{this.repo.renewLock(t,s,10).catch(o=>this.logger.warn(`Error renovando lock para ${t}`,{error:o}))},5e3);try{if(n)throw n;let o=await this.repo.get(t);if(!o)return;if(o.status===c.CANCELLING)throw new S(o.error||"Workflow cancelled");if(o.status!==c.RUNNING)return;let p=o.version==="undefined"?void 0:o.version,d=e.version==="undefined"?void 0:e.version;if(String(p??"")!==String(d??"")){let w=new Error(`Version mismatch: DB=${p}, Code=${d}`);await this.repo.fail(t,w,s,c.VERSION_MISMATCH);return}let u={workflowId:t,step:o.step,input:o.input,state:o.state,result:r,log:(w,T)=>this.logger.info(w,{...T,workflowId:t,step:o.step})},g=await e.execute(u);await this.repo.updateState(t,u.state,u.step,s),await this.handleInstruction(g,u,o.name,s)&&(await this.repo.incrementStep(t,s),this.scheduleExecution(t,e,void 0))}catch(o){let p=o instanceof Error?o:new Error(String(o));this.logger.error("Error en workflow",{workflowId:t,error:p.message}),await this.handleFailure(t,p,e,s)}finally{clearInterval(a),await this.repo.releaseLock(t,s)}}async handleInstruction(t,e,r,n){let{workflowId:s}=e;switch(t.type){case"SCHEDULE_TASK":return await this.repo.enqueueTask({workflowId:s,durableFunctionName:r,...t}),!1;case"SCHEDULE_SLEEP":{let a=v(t.duration);if(typeof a!="number")throw new Error(`Invalid time value provided to bSleep: "${t.duration}"`);let o=Date.now()+a;return await this.repo.scheduleSleep(s,o),!1}case"WAIT_FOR_SIGNAL":return await this.repo.updateStatus(s,c.AWAITING_SIGNAL,{awaitingSignal:t.signalName}),await l.sadd(`signals:awaiting:${t.signalName}`,s),!1;case"EXECUTE_SUBWORKFLOW":{let a=this.durableFns.get(t.workflowName);if(!a)throw new Error(`Sub-workflow '${t.workflowName}' no encontrado.`);let{workflowId:o}=await this.start(a,{input:t.input},s);return await this.repo.updateStatus(s,c.AWAITING_SUBWORKFLOW,{subWorkflowId:o}),!1}case"EMIT_EVENT":{let a=`event:${s}`,o=h({eventName:t.eventName,payload:t.payload});return await l.publish(a,o),!0}case"COMPLETE":{let a=`event:${s}`,o=h({eventName:"workflow:completed",payload:t.result});return await l.publish(a,o),await this.repo.complete(s,t.result,n),await this.resumeParentWorkflow(s),!1}}}async handleFailure(t,e,r,n){let s=n;if(!s&&(s=await this.repo.acquireLock(t,20),!s)){this.logger.warn(`No se pudo adquirir lock para fallo en ${t}`);return}try{if(e instanceof S){await this.repo.fail(t,e,s,c.CANCELLED);let u=await this.repo.get(t);u?.subWorkflowId&&await this.cancel(u.subWorkflowId,`Parent workflow ${t} was cancelled`);return}let a=await this.repo.get(t);if(!a||a.status===c.FAILED||a.status===c.COMPLETED)return;let o=a.state.tryCatchStack;if(o&&o.length>0){let g=o.pop()?.catchStep;if(g!==void 0){this.logger.info(`Capturando error en step ${g}`,{workflowId:t}),await this.repo.resumeForCatch(t,a.state,g,s),this.scheduleExecution(t,r,{name:e.name,message:e.message,stack:e.stack});return}}let p=`event:${t}`,d=h({eventName:"workflow:failed",payload:{message:e.message}});await l.publish(p,d),await this.repo.fail(t,e,s),await this.propagateFailureToParent(t,e)}finally{!n&&s&&await this.repo.releaseLock(t,s)}}async resumeParentWorkflow(t){let e=await this.repo.get(t);if(!e?.parentId)return;let r=e.parentId,n=await this.repo.get(r);if(!n||n.status!==c.AWAITING_SUBWORKFLOW||n.subWorkflowId!==t)return;let s=this.durableFns.get(n.name);if(!s){await this.repo.fail(r,new Error(`Definici\xF3n del workflow '${n.name}' no encontrada.`),null);return}await this.repo.updateStatus(r,c.RUNNING,{subWorkflowId:""});let a=await this.repo.acquireLock(r);if(a)try{await this.repo.incrementStep(r,a),this.scheduleExecution(r,s,e.result)}finally{await this.repo.releaseLock(r,a)}else throw this.logger.warn(`Could not lock parent ${r} to resume. Retrying later...`),new Error(`Temporary Lock Failure: Could not acquire parent lock for ${r}`)}async propagateFailureToParent(t,e){let r=await this.repo.get(t);if(!r?.parentId)return;let n=r.parentId,s=await this.repo.get(n);if(!s||s.status!==c.AWAITING_SUBWORKFLOW||s.subWorkflowId!==t)return;let a=this.durableFns.get(s.name);if(!a){await this.repo.fail(n,new Error(`Definici\xF3n del workflow '${s.name}' no encontrada al propagar fallo.`),null);return}await this.repo.updateStatus(n,c.RUNNING,{subWorkflowId:""});let o=new Error(`Sub-workflow '${r.name}' (${t}) fall\xF3: ${e.message}`);o.stack=e.stack,this.scheduleExecution(n,a,void 0,o)}async signal(t,e,r){let n=null;for(let s=0;s<3&&(n=await this.repo.acquireLock(t),!n);s++)await new Promise(a=>setTimeout(a,50));if(!n)return this.logger.warn("Lock timeout en signal",{workflowId:t});try{let s=await this.repo.get(t);if(!s)return this.logger.warn("Se\xF1al para workflow inexistente",{workflowId:t});if(s.status!==c.AWAITING_SIGNAL||s.awaitingSignal!==e)return this.logger.warn("Workflow no esperaba esta se\xF1al",{workflowId:t,expected:s.awaitingSignal,received:e});let a=this.durableFns.get(s.name);if(!a){await this.repo.fail(t,new Error(`Funci\xF3n durable '${s.name}' no encontrada.`),n);return}await this.repo.updateStatus(t,c.RUNNING,{awaitingSignal:""}),await l.srem(`signals:awaiting:${e}`,t),await this.repo.incrementStep(t,n),this.scheduleExecution(t,a,r)}catch(s){let a=s instanceof Error?s:new Error(String(s)),o=(await this.repo.get(t))?.name||"",p=this.durableFns.get(o);await this.handleFailure(t,a,p,n)}finally{n&&await this.repo.releaseLock(t,n)}}async cancel(t,e){let r=await this.repo.acquireLock(t);if(!r)return await new Promise(n=>setTimeout(n,100)),this.cancel(t,e);try{let n=await this.repo.get(t);if(!n||[c.COMPLETED,c.FAILED,c.CANCELLED].includes(n.status))return;if(await this.repo.updateStatus(t,c.CANCELLING,{error:e}),n.status===c.SLEEPING){await l.zrem(y,t);let s=this.durableFns.get(n.name);this.scheduleExecution(t,s)}if(n.status===c.AWAITING_SIGNAL){let s=this.durableFns.get(n.name);this.scheduleExecution(t,s)}}finally{await this.repo.releaseLock(t,r)}}async cancelByTag(t,e){this.logger.info(`[RUNTIME] Cancelando grupo de workflows por tag: ${t}`);let r=await this.repo.getIdsByTag(t);if(r.length===0){this.logger.debug(`No se encontraron workflows para el tag: ${t}`);return}let n=r.map(s=>this.cancel(s,e).catch(a=>{this.logger.error(`Error cancelando workflow ${s} del grupo ${t}`,{error:a})}));await Promise.all(n),this.logger.info(`[RUNTIME] Se enviaron se\xF1ales de cancelaci\xF3n a ${r.length} workflows del tag: ${t}`)}startScheduler(){if(this.schedulerInterval)return;this.logger.info(`Scheduler iniciado (${this.pollingInterval}ms)`);let t=async()=>{await this.checkSleepers(),await this.checkDelayedTasks(),await this.reapDeadWorkers()};this.schedulerInterval=setInterval(t,this.pollingInterval)}async checkDelayedTasks(){try{let t=await this.repo.moveDueTasksToQueue(50);t>0&&this.logger.debug(`Scheduler movi\xF3 ${t} tareas diferidas a la cola activa`)}catch(t){this.logger.error("Error chequeando tareas diferidas",{error:t})}}async checkSleepers(){let e=await this.repo.getWorkflowsToWake(50);e.length!==0&&await Promise.all(e.map(async r=>{let n=await this.repo.acquireLock(r);if(n)try{let s=await this.repo.get(r);if(s){let a=this.durableFns.get(s.name);a&&(this.logger.info("Despertando workflow",{workflowId:r}),await this.repo.updateStatus(r,c.RUNNING),await this.repo.incrementStep(r,n),this.scheduleExecution(r,a,void 0))}}finally{await this.repo.releaseLock(r,n)}}))}async reapDeadWorkers(){let t="0";do{let[e,r]=await l.sscan(k,t,"COUNT",100);t=e;for(let n of r){if(await l.exists(`${A}${n}`))continue;this.logger.warn(`Worker muerto ${n}. Recuperando tareas.`);let s=`${m}:processing:${n}`,a=await l.rpoplpush(s,m);for(;a;)a=await l.rpoplpush(s,m);await l.del(s),await l.srem(k,n)}}while(t!=="0")}startHeartbeat(){let t=`${A}${this.workerId}`,e=Math.max(Math.ceil(this.pollingInterval*3/1e3),5),r=()=>{this.isRunning&&l.set(t,Date.now().toString(),"EX",e).catch(()=>{})};this.heartbeatInterval=setInterval(r,this.pollingInterval),r()}startWorker(){if(this.isRunning)return;this.isRunning=!0;let t=`${m}:processing:${this.workerId}`;this.logger.info(`Worker ${this.workerId} iniciado`),this.startHeartbeat(),(async()=>{for(await l.sadd(k,this.workerId);this.isRunning;)try{let r=await b.brpoplpush(m,t,2);if(!r)continue;let n=E(r);this.logger.debug(`Ejecutando tarea: ${n.exportName}`,{workflowId:n.workflowId});try{let s;n.modulePath.startsWith("virtual:")?s=await import(n.modulePath):s=await import(Y(this.sourceRoot,n.modulePath));let a=s[n.exportName];if(typeof a!="function")throw new Error(`'${n.exportName}' no es una funci\xF3n.`);let o=await a(...n.args),p=this.durableFns.get(n.durableFunctionName);if(p){let d=await this.repo.acquireLock(n.workflowId);if(d)try{await this.repo.incrementStep(n.workflowId,d),this.scheduleExecution(n.workflowId,p,o)}finally{await this.repo.releaseLock(n.workflowId,d)}else this.logger.warn(`No se pudo adquirir lock para avanzar workflow ${n.workflowId} tras tarea`,{task:n.exportName})}await l.lrem(t,1,r)}catch(s){let a=s instanceof Error?s:new Error(String(s));this.logger.error(`Fallo en tarea ${n.exportName}`,{workflowId:n.workflowId,error:a.message});let o=this.durableFns.get(n.durableFunctionName),p=o?.retryOptions||{},d=p.maxAttempts??3,u=(n.attempts||0)+1;if(n.attempts=u,u<=d){let g=p.initialInterval?v(p.initialInterval):1e3,f=p.backoffCoefficient??2,w=p.maxInterval?v(p.maxInterval):36e5,T=g*Math.pow(f,u-1);T>w&&(T=w),this.logger.warn(`Reintentando tarea en ${v(T)} (intento ${u}/${d===1/0?"Inf":d})`,{workflowId:n.workflowId}),T>0?await this.repo.scheduleTaskRetry(n,T):await l.lpush(m,h(n)),await l.lrem(t,1,r)}else this.logger.error("Reintentos agotados. Moviendo a DLQ.",{workflowId:n.workflowId}),await this.repo.moveToDLQ(n,a),o?await this.handleFailure(n.workflowId,a,o,null):await this.repo.fail(n.workflowId,new Error(`Def missing for ${n.durableFunctionName}`),null),await l.lrem(t,1,r)}}catch(r){if(!this.isRunning)break;this.logger.error("Error infraestructura worker",{error:r}),await new Promise(n=>setTimeout(n,5e3))}})()}run(t){this.durableFns=t,this.startWorker(),this.startScheduler()}async stop(){this.isRunning=!1,this.schedulerInterval&&clearInterval(this.schedulerInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),await l.srem(k,this.workerId),this.logger.info("Runtime detenido")}};var z=i=>({...i,__isDurable:!0});var Q={info:(i,t)=>console.log(`[INFO] ${i}`,t||""),error:(i,t)=>console.error(`[ERROR] ${i}`,t||""),warn:(i,t)=>console.warn(`[WARN] ${i}`,t||""),debug:(i,t)=>console.debug(`[DEBUG] ${i}`,t||"")},j=i=>{if(!i.startsWith("on")||i.length<=2)return null;let t=i.slice(2);return t.charAt(0).toLowerCase()+t.slice(1)};function At(i){let t=i.logger||Q;t.info("--- Inicializando Sistema Durable ---"),C({commandClient:i.redisClient,blockingClient:i.blockingRedisClient});let e=new R({sourceRoot:i.sourceRoot,retention:i.retention,pollingInterval:i.pollingInterval,logger:t});e.run(i.durableFunctions);let r=new q(i.redisClient.options),n=new Map;r.psubscribe("event:*",a=>{a&&t.error("Error fatal al suscribirse a los canales de eventos:",{error:a})}),r.on("pmessage",(a,o,p)=>{let d=n.get(o);if(d&&d.length>0)try{let u=E(p),g={name:u.eventName||u.signalName,payload:u.payload};[...d].forEach(f=>f(g))}catch(u){t.error(`Error al parsear evento en ${o}`,{error:u})}});let s=(a,o)=>{let p=`event:${o}`,d=u=>(n.has(p)||n.set(p,[]),n.get(p)?.push(u),()=>{let g=n.get(p);if(g){let f=g.indexOf(u);f>-1&&g.splice(f,1),g.length===0&&n.delete(p)}});return{workflowId:o,signal:async(u,g)=>{await e.signal(o,u,g)},on:async(u,g)=>{let f=N(o),w=await i.redisClient.hgetall(f);return u==="workflow:completed"&&w.status===c.COMPLETED?(g(E(w.result||"null")),{unsubscribe:()=>{}}):u==="workflow:failed"&&w.status===c.FAILED?(g({message:w.error||"Unknown"}),{unsubscribe:()=>{}}):{unsubscribe:d(O=>{O.name===u&&g(O.payload)})}},subscribe:async u=>({unsubscribe:d(f=>{u(f)})})}};return{start:async(a,o)=>{let p=o.workflowId||B(),d=[],u=`event:${p}`,g={};if(Object.keys(o).forEach(f=>{let w=j(f);w&&typeof o[f]=="function"&&(g[w]=o[f])}),Object.keys(g).length>0){n.has(u)||n.set(u,[]);let f=w=>{let T=g[w.name];T&&T(w.payload)};n.get(u)?.push(f),d.push(()=>{let w=n.get(u);if(w){let T=w.indexOf(f);T>-1&&w.splice(T,1)}})}return await e.start(a,{workflowId:p,input:o.input,tags:o.tags}),{workflowId:p,unsubscribe:async()=>{d.forEach(f=>f())}}},stop:()=>{e.stop(),r.quit().catch(()=>{})},runtime:e,cancel:(a,o)=>e.cancel(a,o),getState:a=>e.getState(a),getHandle:(a,o)=>s(a,o),cancelByTag:(a,o)=>e.cancelByTag(a,o)}}export{S as WorkflowCancellationError,z as bDurable,At as bDurableInitialize};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobtail.software/b-durable",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "main": "dist/index.mjs",
5
5
  "types": "dist/index.d.mts",
6
6
  "description": "A system for creating durable, resilient, and type-safe workflows in JavaScript/TypeScript.",