@bobtail.software/b-durable 1.0.10 → 1.0.11
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/compiler/cli.mjs +37 -45
- package/dist/index.d.mts +12 -22
- package/dist/index.mjs +3 -3
- package/package.json +1 -1
package/dist/compiler/cli.mjs
CHANGED
|
@@ -1,30 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import G from"path";import{createHash as Y}from"crypto";import{existsSync as
|
|
3
|
-
Procesando archivo: ${
|
|
2
|
+
import G from"path";import{createHash as Y}from"crypto";import{existsSync as z,mkdirSync as X,readFileSync as Q,rmSync as Z,writeFileSync as ee}from"fs";import A from"path";import*as j from"prettier";import{Node as a,Project as te,SyntaxKind as $,ts as _,VariableDeclarationKind as U}from"ts-morph";var ne="bDurable",L=_.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope|_.TypeFormatFlags.NoTruncation;function se(e){return Y("sha256").update(e).digest("hex")}async function re(e){let t=e.getFilePath(),r=e.getFullText(),n=await j.resolveConfig(t),o=await j.format(r,{...n,parser:"typescript"});e.replaceWithText(o)}async function H(e){console.log("Iniciando compilador de workflows duraderos...");let{inputDir:t,outputDir:r,packageName:n,mode:o}=e,c=A.resolve(process.cwd(),"durable.lock.json"),i={};if(z(c))try{i=JSON.parse(Q(c,"utf-8"))}catch(g){console.warn("Advertencia: durable.lock.json corrupto, iniciando uno nuevo.",g)}let s=new te({tsConfigFilePath:A.resolve(process.cwd(),"tsconfig.json")}),u=s.addSourceFilesAtPaths(`${t}/**/*.ts`);z(r)&&(console.log(`Limpiando directorio de salida: ${r}`),Z(r,{recursive:!0,force:!0})),X(r,{recursive:!0});let l=s.createDirectory(r);console.log(`Encontrados ${u.length} archivos de workflow para procesar.`);let p=[],S=[],f=!1;for(let g of u){console.log(`
|
|
3
|
+
Procesando archivo: ${g.getBaseName()}`);let d=g.getDescendantsOfKind($.CallExpression).filter(x=>x.getExpression().getText()===ne);if(d.length!==0)for(let x of d){let m=x.getParentIfKind($.VariableDeclaration);if(!m)continue;let h=m.getName();console.log(` -> Transformando workflow: ${h}`);let[y]=x.getArguments();if(!a.isObjectLiteralExpression(y))continue;let v=y.getProperty("workflow");if(!v||!a.isPropertyAssignment(v))continue;let T=v.getInitializer();if(!T||!a.isArrowFunction(T))continue;let w=y.getProperty("version"),b="unknown";if(w&&a.isPropertyAssignment(w)){let R=w.getInitializer();R&&a.isStringLiteral(R)&&(b=R.getLiteralValue())}let E=g.getBaseName().replace(/\.ts$/,".compiled.mts"),P=A.join(l.getPath(),E),C=s.createSourceFile(P,"",{overwrite:!0});S.push(C),oe(h,T,x,C,n);let V=C.getFullText(),O=se(V),N=i[h]?.[b];if(N)if(N!==O){if(o==="prod")throw new Error(`
|
|
4
4
|
\u{1F6D1} ERROR DE INTEGRIDAD (PROD) \u{1F6D1}
|
|
5
|
-
El workflow '${
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
\
|
|
14
|
-
`),
|
|
15
|
-
`),u.addVariableStatement({declarationKind:U.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}=v(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",I=f.length>2?f[2].getText():"Record<string, never>",A=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(`
|
|
5
|
+
El workflow '${h}' (v${b}) ha cambiado, pero el lockfile no se actualiz\xF3.
|
|
6
|
+
`);console.log(` \u26A0\uFE0F v${b} modificada. Actualizando hash en lockfile.`),i[h]||(i[h]={}),i[h][b]=O,f=!0}else console.log(` \u2705 v${b} verificada.`);else console.log(` \u{1F195} v${b} registrada.`),i[h]||(i[h]={}),i[h][b]=O,f=!0;console.log(` -> Archivo generado: ${A.relative(process.cwd(),P)}`);let I=E;p.push({name:h,importPath:`./${I}`})}}if(f){if(o==="prod")throw new Error("El lockfile ha cambiado durante un build de PROD. Esto no est\xE1 permitido.");ee(c,JSON.stringify(i,null,2)),console.log(`
|
|
7
|
+
\u{1F512} durable.lock.json actualizado.`)}if(p.length>0){let g=A.join(l.getPath(),"index.mts"),d=s.createSourceFile(g,"",{overwrite:!0});S.push(d),d.addStatements(`// Este archivo fue generado autom\xE1ticamente. NO EDITAR MANUALMENTE.
|
|
8
|
+
`),d.addImportDeclaration({isTypeOnly:!0,moduleSpecifier:n,namedImports:["DurableFunction"]});for(let m of p)d.addImportDeclaration({moduleSpecifier:m.importPath,namedImports:[m.name]});d.addExportDeclaration({namedExports:p.map(m=>m.name)}),d.addStatements(`
|
|
9
|
+
`),d.addVariableStatement({declarationKind:U.Const,declarations:[{name:"durableFunctions",type:"Map<string, DurableFunction<any, any, any, any>>",initializer:"new Map()"}]});let x=p.map(m=>`durableFunctions.set(${m.name}.name, ${m.name});`);d.addStatements(x),d.addStatements(`
|
|
10
|
+
`),d.addExportAssignment({isExportEquals:!1,expression:"durableFunctions"}),console.log(`
|
|
11
|
+
-> Archivo de \xEDndice generado: ${A.basename(g)}`)}console.log(`
|
|
12
|
+
Formateando archivos generados con Prettier...`);for(let g of S)await re(g);await s.save(),console.log(`
|
|
13
|
+
Compilaci\xF3n completada exitosamente.`)}function oe(e,t,r,n,o){let c=t.getBody();if(!a.isBlock(c))throw new Error(`El cuerpo del workflow '${e}' debe ser un bloque {}.`);let[i]=r.getArguments();if(!a.isObjectLiteralExpression(i))throw new Error("El argumento de bDurable debe ser un objeto.");let s=i.getProperty("version");if(!s||!a.isPropertyAssignment(s))throw new Error(`El workflow '${e}' debe tener una propiedad 'version'.`);let u=s.getInitializer();if(!u||!a.isStringLiteral(u))throw new Error(`La versi\xF3n del workflow '${e}' debe ser un string literal.`);let l=i.getProperty("retryOptions"),p="undefined";l&&a.isPropertyAssignment(l)&&(p=l.getInitializer()?.getText()||"undefined");let{clauses:S}=D(c.getStatements(),{step:0,persistedVariables:new Map}),f=t.getReturnType();f.getSymbol()?.getName()==="Promise"&&f.isObject()&&(f=f.getTypeArguments()[0]||f);let g=f.getText(void 0,L),d=new Set,x=t.getSourceFile(),m=r.getTypeArguments(),h=m.length>0?m[0].getText():"unknown",y=m.length>2?m[2].getText():"Record<string, never>",v=m.length>3?m[3].getText():"Record<string, never>";x.getImportDeclarations().forEach(E=>{if(E.getModuleSpecifierValue()===o)return;let P=E.getModuleSpecifierValue();if(P.includes(".workflow")){let N=A.parse(P),I=A.join(N.dir,N.base+".compiled.mts");!I.startsWith(".")&&!A.isAbsolute(I)&&(I="./"+I),P=I.replace(/\\/g,"/")}else P.startsWith(".")&&A.extname(P)===""&&(P+=".mjs");let C=[],V=[];E.getNamedImports().forEach(N=>{let I=N.getName(),R=N.getAliasNode()?.getText(),W=R?`${I} as ${R}`:I,K=(N.getNameNode().getSymbol()?.getAliasedSymbol()??N.getNameNode().getSymbol())?.getDeclarations()??[],J=K.some(B=>a.isEnumDeclaration(B));N.isTypeOnly()||!J&&K.every(B=>a.isInterfaceDeclaration(B)||a.isTypeAliasDeclaration(B))?V.push(W):C.push(W)}),C.length>0&&n.addImportDeclaration({moduleSpecifier:P,namedImports:C}),V.length>0&&n.addImportDeclaration({isTypeOnly:!0,moduleSpecifier:P,namedImports:V});let O=E.getDefaultImport();O&&n.addImportDeclaration({moduleSpecifier:P,defaultImport:O.getText()})}),x.getInterfaces().forEach(E=>{d.add(E.getText().startsWith("export")?E.getText():`export ${E.getText()}`)}),x.getTypeAliases().forEach(E=>{d.add(E.getText().startsWith("export")?E.getText():`export ${E.getText()}`)});let[T]=t.getParameters(),w="";if(T){let E=T.getNameNode().getText();E!=="input"&&(a.isObjectBindingPattern(T.getNameNode())?w=`const ${T.getNameNode().getText()} = input;`:w=`const ${E} = input;`)}n.addImportDeclaration({isTypeOnly:!0,moduleSpecifier:o,namedImports:["DurableFunction","WorkflowContext","Instruction"]}),d.size>0&&(n.addStatements(`
|
|
14
|
+
`),n.addStatements(Array.from(d))),n.addStatements(`
|
|
21
15
|
// Este archivo fue generado autom\xE1ticamente. NO EDITAR MANUALMENTE.
|
|
22
|
-
`);let
|
|
16
|
+
`);let b=u.getLiteralValue(),M=`{
|
|
23
17
|
__isDurable: true,
|
|
24
18
|
name: '${e}',
|
|
25
|
-
version: '${
|
|
26
|
-
retryOptions: ${
|
|
27
|
-
async execute(context: WorkflowContext<${
|
|
19
|
+
version: '${b}',
|
|
20
|
+
retryOptions: ${p},
|
|
21
|
+
async execute(context: WorkflowContext<${h}>): Promise<Instruction<${g}>> {
|
|
28
22
|
const { input, state, result, log, workflowId } = context;
|
|
29
23
|
${w}
|
|
30
24
|
while (true) {
|
|
@@ -36,39 +30,37 @@ Compilaci\xF3n completada exitosamente.`)}function oe(e,t,o,n,i){let c=t.getBody
|
|
|
36
30
|
}
|
|
37
31
|
}
|
|
38
32
|
}
|
|
39
|
-
}`;n.addVariableStatement({isExported:!0,declarationKind:U.Const,declarations:[{name:e,type:`DurableFunction<${
|
|
33
|
+
}`;n.addVariableStatement({isExported:!0,declarationKind:U.Const,declarations:[{name:e,type:`DurableFunction<${h}, ${g}, ${y}, ${v}>`,initializer:M}]}),n.organizeImports()}function D(e,t){if(e.length===0){let d=[];if(t.pendingStateAssignment){let x=`case ${t.step}: {
|
|
40
34
|
${t.pendingStateAssignment}
|
|
41
35
|
return { type: 'COMPLETE', result: undefined };
|
|
42
|
-
}`;
|
|
43
|
-
`),
|
|
36
|
+
}`;d.push(x)}return{clauses:d,nextStep:t.step+1}}let{syncBlock:r,durableStatement:n,nextStatements:o}=de(e),{rewrittenSyncStatements:c,newlyPersistedVariables:i}=ce(r,n?[n,...o]:[],t.persistedVariables);t.pendingStateAssignment&&c.unshift(t.pendingStateAssignment);let s=new Map([...t.persistedVariables,...i]);if(!n){let d=c.join(`
|
|
37
|
+
`),m=r.length>0&&a.isReturnStatement(r[r.length-1])?"":`
|
|
44
38
|
return { type: 'COMPLETE', result: undefined };`;return{clauses:[`case ${t.step}: {
|
|
45
|
-
${
|
|
46
|
-
}`],nextStep:t.step+1}}if(a.isIfStatement(n))return ae(n,
|
|
39
|
+
${d}${m}
|
|
40
|
+
}`],nextStep:t.step+1}}if(a.isIfStatement(n))return ae(n,o,{...t,persistedVariables:s},c);if(a.isTryStatement(n))return ie(n,o,{...t,persistedVariables:s},c);let{instruction:u,nextPendingStateAssignment:l}=le(n,s);c.push(u);let p=c.join(`
|
|
47
41
|
`),S=`case ${t.step}: {
|
|
48
|
-
${
|
|
49
|
-
}`,
|
|
50
|
-
`),
|
|
51
|
-
case ${
|
|
52
|
-
${
|
|
53
|
-
if (${
|
|
54
|
-
context.step = ${
|
|
42
|
+
${p}
|
|
43
|
+
}`,f={step:t.step+1,persistedVariables:s,pendingStateAssignment:l},g=D(o,f);return{clauses:[S,...g.clauses],nextStep:g.nextStep}}function ae(e,t,r,n){let o=k(e.getExpression(),r.persistedVariables),c=e.getThenStatement(),i=a.isBlock(c)?c.getStatements():[c],s=D(i,{step:r.step+1,persistedVariables:new Map(r.persistedVariables)}),u,l=e.getElseStatement();if(l){let x=a.isBlock(l)?l.getStatements():[l];u=D(x,{step:s.nextStep,persistedVariables:new Map(r.persistedVariables)})}let p=u?u.nextStep:s.nextStep,S=D(t,{step:p,persistedVariables:r.persistedVariables}),f=n.join(`
|
|
44
|
+
`),g=s.nextStep;return{clauses:[`
|
|
45
|
+
case ${r.step}: {
|
|
46
|
+
${f}
|
|
47
|
+
if (${o}) {
|
|
48
|
+
context.step = ${r.step+1};
|
|
55
49
|
} else {
|
|
56
|
-
${
|
|
50
|
+
${l?`context.step = ${g};`:`context.step = ${p};`}
|
|
57
51
|
}
|
|
58
52
|
break;
|
|
59
53
|
}
|
|
60
|
-
`,...
|
|
61
|
-
case ${
|
|
54
|
+
`,...s.clauses,...u?u.clauses:[],...S.clauses],nextStep:S.nextStep}}function ie(e,t,r,n){let{step:o,persistedVariables:c}=r,i=e.getTryBlock(),s=e.getCatchClause(),u=e.getFinallyBlock(),l=D(i.getStatements(),{step:o+1,persistedVariables:new Map(c)}),p,S,f=l.nextStep;if(s){let w=s.getBlock(),b=s.getVariableDeclaration();b&&(S=b.getName()),p=D(w.getStatements(),{step:f,persistedVariables:new Map(c)})}let g,d=p?p.nextStep:f;u&&(g=D(u.getStatements(),{step:d,persistedVariables:new Map(c)}));let x=g?g.nextStep:d,m=D(t,{step:x,persistedVariables:c}),h=`{ catchStep: ${s?f:"undefined"}, finallyStep: ${u?d:"undefined"} }`,y=`
|
|
55
|
+
case ${o}: {
|
|
62
56
|
${n.join(`
|
|
63
57
|
`)}
|
|
64
58
|
state.tryCatchStack = state.tryCatchStack || [];
|
|
65
|
-
state.tryCatchStack.push(${
|
|
66
|
-
context.step = ${
|
|
59
|
+
state.tryCatchStack.push(${h});
|
|
60
|
+
context.step = ${o+1}; // Salta al inicio del bloque try
|
|
67
61
|
break;
|
|
68
62
|
}
|
|
69
|
-
`,
|
|
70
|
-
const ${S} = result as unknown;`)}let w=
|
|
71
|
-
Please split them into separate lines/variables to ensure safe state persistence.`);let n,
|
|
72
|
-
The 'await' keyword must be the direct value of the assignment.
|
|
73
|
-
Invalid: const x = 1 + await foo();
|
|
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,I=a.isInterfaceDeclaration(s)&&s.getNameNode()===r,A=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&&!I&&!A&&!$&&!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"bWaitForAny":return`{ type: 'WAIT_FOR_ANY_SIGNAL', signalNames: ${c} }`;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 q=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=q("--in"),t=q("--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=G.resolve(process.cwd(),e),c=G.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)});
|
|
63
|
+
`,v=l.clauses.pop()||"",T=u?d:x;if(l.clauses.push(v.replace(/return { type: 'COMPLETE'.* };/,`context.step = ${T}; break;`)),p){if(S){let b=p.clauses[0]||`case ${f}: {}`;p.clauses[0]=b.replace("{",`{
|
|
64
|
+
const ${S} = result as unknown;`)}let w=p.clauses.pop()||"";p.clauses.push(w.replace(/return { type: 'COMPLETE'.* };/,`context.step = ${T}; break;`))}if(g){let w=g.clauses.pop()||"";g.clauses.push(w.replace(/return { type: 'COMPLETE'.* };/,`state.tryCatchStack?.pop(); context.step = ${x}; break;`))}return{clauses:[y,...l.clauses,...p?p.clauses:[],...g?g.clauses:[],...m.clauses],nextStep:m.nextStep}}function ce(e,t,r){let n=[],o=new Map,c=pe(t),i=new Map(r);for(let s of e){let u=!1;if(a.isVariableStatement(s)){let p=s.getDeclarationList().getDeclarations(),S=!0;for(let f of p){let g=f.getInitializer();if(!g){S=!1;continue}let d=f.getNameNode(),x=k(g,i);if(a.isObjectBindingPattern(d)){let m=d.getElements(),h=[];for(let y of m){let v=y.getNameNode(),T=y.getPropertyNameNode(),w=T?T.getText():v.getText(),b=v.getText();if(c.has(b)){let M=y.getType().getText(y,L);h.push({name:b,type:M,propertyName:w})}}if(h.length===m.length&&m.length>0)for(let y of h)o.set(y.name,{type:y.type}),i.set(y.name,{type:y.type}),n.push(`state.${y.name} = (${x} as any).${y.propertyName};`);else{if(h.length>0)for(let y of h)o.set(y.name,{type:y.type}),i.set(y.name,{type:y.type}),n.push(`state.${y.name} = (${x} as any).${y.propertyName};`);S=!1}}else if(a.isIdentifier(d)){let m=d.getText();if(c.has(m)){let h=f.getType().getText(f,L);o.set(m,{type:h}),i.set(m,{type:h}),n.push(`state.${m} = ${x};`)}else S=!1}else S=!1}S&&p.length>0&&(u=!0)}u||n.push(k(s,i))}return{rewrittenSyncStatements:n,newlyPersistedVariables:o}}function le(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($.AwaitExpression).length>1)throw new Error(`[b-durable Compiler Error] Multiple 'await' expressions found in a single statement at line ${e.getStartLineNumber()}.
|
|
65
|
+
Please split them into separate lines/variables to ensure safe state persistence.`);let n,o=e.getFirstDescendantByKind($.VariableDeclaration);if(o){let i=o.getName(),s=o.getType().getText(o,L);t.set(i,{type:s}),n=`state.${i} = result;`}let c=e.getFirstDescendantByKind($.AwaitExpression);if(c){let i=c.getParent();if(!(a.isExpressionStatement(i)||a.isVariableDeclaration(i)||a.isCallExpression(i)&&a.isExpressionStatement(i.getParent()))&&o&&o.getInitializer()!==c)throw new Error(`[b-durable Compiler Error] Complex 'await' usage detected at line ${e.getStartLineNumber()}.
|
|
66
|
+
The 'await' keyword must be the direct value of the assignment.`);let u=c.getExpression();if(a.isCallExpression(u))return{instruction:`return ${ue(u,t)};`,nextPendingStateAssignment:n}}return{instruction:k(e,t),nextPendingStateAssignment:n}}function pe(e){let t=new Set;for(let r of e)r.getDescendantsOfKind($.Identifier).forEach(n=>{t.add(n.getText())});return t}function k(e,t){let r=e.getText(),n=e.getStart(),o=[],c=e.getDescendantsOfKind($.Identifier);a.isIdentifier(e)&&c.push(e);for(let s of c){let u=s.getText();if(!t.has(u))continue;let l=s.getParent(),p=a.isShorthandPropertyAssignment(l)&&l.getNameNode()===s,S=s.getSymbol();if(S&&!p&&S.getDeclarations().some(y=>y.getStart()>=e.getStart()&&y.getEnd()<=e.getEnd())||a.isVariableDeclaration(l)&&l.getNameNode()===s||a.isBindingElement(l)&&l.getNameNode()===s||a.isParameterDeclaration(l)&&l.getNameNode()===s||a.isFunctionDeclaration(l)&&l.getNameNode()===s||a.isPropertyAccessExpression(l)&&l.getNameNode()===s||a.isPropertyAssignment(l)&&l.getNameNode()===s)continue;let f=t.get(u),g=s.getStart()-n,d=s.getEnd()-n,x=`(state.${u} as ${f.type})`;p&&(x=`${u}: ${x}`),o.push({start:g,end:d,text:x})}o.sort((s,u)=>u.start-s.start);let i=r;for(let{start:s,end:u,text:l}of o)i=i.substring(0,s)+l+i.substring(u);return i}function ue(e,t){let r=e.getExpression(),n,o=!1;a.isPropertyAccessExpression(r)?(r.getExpression().getText()==="context"&&(o=!0),n=r.getName()):n=r.getText();let c=e.getArguments().map(p=>k(p,t)).join(", ");if(o)switch(n){case"bSleep":return`{ type: 'SCHEDULE_SLEEP', duration: ${c} }`;case"bWaitForEvent":return`{ type: 'WAIT_FOR_SIGNAL', signalName: ${c} }`;case"bWaitForAny":return`{ type: 'WAIT_FOR_ANY_SIGNAL', signalNames: ${c} }`;case"bExecute":{let[p,S]=e.getArguments(),f=p.getText(),g=S?k(S,t):"undefined";return`{ type: 'EXECUTE_SUBWORKFLOW', workflowName: ${f}.name, input: ${g} }`}case"bSignal":{let[p,S]=e.getArguments().map(f=>k(f,t));return`{ type: 'EMIT_EVENT', eventName: ${p}, payload: ${S} }`}default:throw new Error(`Funci\xF3n de contexto durable desconocida: '${n}'.`)}let i=r.getSymbol();if(!i)throw new Error(`S\xEDmbolo no encontrado para '${n}'.`);let s=i.getDeclarations()[0]?.asKind($.ImportSpecifier);if(!s)throw new Error(`'${n}' debe ser importada.`);let u=s.getImportDeclaration().getModuleSpecifierSourceFileOrThrow();return`{ type: 'SCHEDULE_TASK', modulePath: '${A.relative(process.cwd(),u.getFilePath()).replace(/\\/g,"/")}', exportName: '${n}', args: [${c}] }`}function F(e){for(let t of e.getDescendantsOfKind($.AwaitExpression)){let r=t.getExpressionIfKind($.CallExpression);if(r){let n=r.getExpression();if(a.isPropertyAccessExpression(n)){let o=n.getName();if(n.getExpression().getText()==="context"&&(o==="bSleep"||o==="bWaitForEvent"||o==="bWaitForAny"||o==="bExecute"||o==="bSignal")||n.getSymbol()?.getDeclarations()[0]?.isKind($.ImportSpecifier))return!0}else if(n.getSymbol()?.getDeclarations()[0]?.isKind($.ImportSpecifier))return!0}}if(a.isTryStatement(e)&&(F(e.getTryBlock())||e.getCatchClause()&&F(e.getCatchClause().getBlock())||e.getFinallyBlock()&&F(e.getFinallyBlock())))return!0;if(a.isIfStatement(e)){let t=F(e.getThenStatement()),r=e.getElseStatement()?F(e.getElseStatement()):!1;return t||r}return a.isBlock(e)?e.getStatements().some(F):!1}function de(e){for(let t=0;t<e.length;t++){let r=e[t];if(a.isReturnStatement(r)||F(r)||a.isTryStatement(r))return{syncBlock:e.slice(0,t),durableStatement:r,nextStatements:e.slice(t+1)}}return{syncBlock:e,durableStatement:null,nextStatements:[]}}var q=e=>{let t=process.argv.indexOf(e);if(t!==-1&&process.argv.length>t+1)return process.argv[t+1]};async function ge(){let e=q("--in"),t=q("--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 o=G.resolve(process.cwd(),e),c=G.resolve(process.cwd(),t);await H({inputDir:o,outputDir:c,packageName:"@bobtail.software/b-durable",mode:n})}ge().catch(e=>{console.error("Error durante la compilaci\xF3n:",e),process.exit(1)});
|
package/dist/index.d.mts
CHANGED
|
@@ -76,7 +76,7 @@ type Instruction<TOutput = unknown> = {
|
|
|
76
76
|
type: 'COMPLETE';
|
|
77
77
|
result: TOutput;
|
|
78
78
|
};
|
|
79
|
-
interface DurableFunction<TInput = unknown, TOutput = unknown, TSignals = Record<string, never>, // INPUT
|
|
79
|
+
interface DurableFunction<TInput = unknown, TOutput = unknown, TSignals = Record<string, never>, // Signals (INPUT)
|
|
80
80
|
TEvents = Record<string, never>> {
|
|
81
81
|
__isDurable: true;
|
|
82
82
|
name: string;
|
|
@@ -204,7 +204,7 @@ declare class DurableRuntime {
|
|
|
204
204
|
/**
|
|
205
205
|
* El contexto de ejecución proporcionado a cada workflow, con métodos de durabilidad tipados.
|
|
206
206
|
*/
|
|
207
|
-
interface DurableContext<
|
|
207
|
+
interface DurableContext<TSignals = Record<string, never>, TEvents = Record<string, never>> extends Pick<WorkflowContext, 'log' | 'workflowId'> {
|
|
208
208
|
/**
|
|
209
209
|
* Pausa la ejecución del workflow de manera duradera.
|
|
210
210
|
* @param duration Una cadena de tiempo como '2 days', '10h', '7s'.
|
|
@@ -213,17 +213,10 @@ interface DurableContext<TEvents = Record<string, never>, TSignals = Record<stri
|
|
|
213
213
|
/**
|
|
214
214
|
* Pausa la ejecución del workflow hasta que se reciba un evento externo.
|
|
215
215
|
* El tipo del payload retornado se infiere automáticamente del contrato de eventos del workflow.
|
|
216
|
-
* @param
|
|
216
|
+
* @param signalName El nombre único del evento que se está esperando.
|
|
217
217
|
* @returns Una promesa que se resuelve con el payload del evento recibido.
|
|
218
218
|
*/
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Ejecuta un sub-workflow y espera de forma duradera su resultado.
|
|
222
|
-
* @param workflow La función durable a ejecutar.
|
|
223
|
-
* @param input La entrada para el sub-workflow.
|
|
224
|
-
* @returns Una promesa que se resuelve con el resultado del sub-workflow.
|
|
225
|
-
*/
|
|
226
|
-
bExecute<TInput, TOutput, TWorkflowEvents, TWorkflowSignals>(workflow: DurableFunction<TInput, TOutput, TWorkflowEvents, TWorkflowSignals>, input: TInput): Promise<TOutput>;
|
|
219
|
+
bWaitForSignal<K extends keyof TSignals>(signalName: K): Promise<TSignals[K]>;
|
|
227
220
|
/**
|
|
228
221
|
* Emite una señal o notificación con un payload desde el workflow.
|
|
229
222
|
* Esta operación es duradera pero no bloquea la ejecución. El workflow continúa inmediatamente después.
|
|
@@ -231,24 +224,21 @@ interface DurableContext<TEvents = Record<string, never>, TSignals = Record<stri
|
|
|
231
224
|
* @param signalName El nombre de la señal.
|
|
232
225
|
* @param payload Los datos a enviar con la señal.
|
|
233
226
|
*/
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Espera a que ocurra CUALQUIERA de los eventos listados.
|
|
237
|
-
* Retorna el primer evento que ocurra.
|
|
238
|
-
*/
|
|
239
|
-
bWaitForAny<K extends keyof TEvents>(eventNames: K[]): Promise<{
|
|
227
|
+
bWaitForAnySignal<K extends keyof TSignals>(signalNames: K[]): Promise<{
|
|
240
228
|
[P in K]: {
|
|
241
229
|
name: P;
|
|
242
|
-
payload:
|
|
230
|
+
payload: TSignals[P];
|
|
243
231
|
};
|
|
244
232
|
}[K]>;
|
|
233
|
+
bExecute<TInput, TOutput, TSubSignals, TSubEvents>(workflow: DurableFunction<TInput, TOutput, TSubSignals, TSubEvents>, input: TInput): Promise<TOutput>;
|
|
234
|
+
bSignal<K extends keyof TEvents>(eventName: K, payload: TEvents[K]): Promise<void>;
|
|
245
235
|
}
|
|
246
|
-
type DurableWorkflowFn<TInput, TOutput,
|
|
247
|
-
interface DurableWorkflowDef<TInput, TOutput,
|
|
236
|
+
type DurableWorkflowFn<TInput, TOutput, TSignals = Record<string, never>, TEvents = Record<string, never>> = (input: TInput, context: DurableContext<TSignals, TEvents>) => Promise<TOutput>;
|
|
237
|
+
interface DurableWorkflowDef<TInput, TOutput, TSignals = Record<string, never>, TEvents = Record<string, never>> {
|
|
248
238
|
/**
|
|
249
239
|
* La función async que contiene la lógica del workflow.
|
|
250
240
|
*/
|
|
251
|
-
workflow: DurableWorkflowFn<TInput, TOutput,
|
|
241
|
+
workflow: DurableWorkflowFn<TInput, TOutput, TSignals, TEvents>;
|
|
252
242
|
/**
|
|
253
243
|
* Versión del workflow. Se utiliza para identificar versiones de un workflow.
|
|
254
244
|
*/
|
|
@@ -263,7 +253,7 @@ interface DurableWorkflowDef<TInput, TOutput, TEvents = Record<string, never>, T
|
|
|
263
253
|
* Marcador para que el compilador identifique y transforme una función en un workflow durable.
|
|
264
254
|
* Esta función es un passthrough en tiempo de ejecución, su único propósito es para el análisis estático.
|
|
265
255
|
*/
|
|
266
|
-
declare const bDurable: <TInput = any, TOutput = any,
|
|
256
|
+
declare const bDurable: <TInput = any, TOutput = any, TSignals = Record<string, never>, TEvents = Record<string, never>>(def: DurableWorkflowDef<TInput, TOutput, TSignals, TEvents>) => DurableFunction<TInput, TOutput, TSignals, TEvents>;
|
|
267
257
|
|
|
268
258
|
interface BDurableAPI {
|
|
269
259
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{randomUUID as B}from"crypto";import q from"ioredis";var m="queue:tasks",y="durable:sleepers",A="worker:heartbeat:",k="durable:workers",O="queue:dead",L="queue:tasks:delayed",I="index:tag:";function P(l){return`workflow:${l}`}var g={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 o,b;function C(l){if(o||b){console.warn("[Persistence] Los clientes de Redis ya han sido configurados. Omitiendo.");return}o=l.commandClient,b=l.blockingClient}import{randomUUID as
|
|
1
|
+
import{randomUUID as B}from"crypto";import q from"ioredis";var m="queue:tasks",y="durable:sleepers",A="worker:heartbeat:",k="durable:workers",O="queue:dead",L="queue:tasks:delayed",I="index:tag:";function P(l){return`workflow:${l}`}var g={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 o,b;function C(l){if(o||b){console.warn("[Persistence] Los clientes de Redis ya han sido configurados. Omitiendo.");return}o=l.commandClient,b=l.blockingClient}import{randomUUID as N}from"crypto";import v from"ms";import{resolve as Y}from"path";var S=class extends Error{isCancellation=!0;constructor(e){super(e),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
|
-
`,
|
|
13
|
+
`,K=`
|
|
14
14
|
local lockKey = KEYS[1]
|
|
15
15
|
local workflowKey = KEYS[2]
|
|
16
16
|
local token = ARGV[1]
|
|
@@ -88,4 +88,4 @@ end
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
return #tasks
|
|
91
|
-
`;import V from"superjson";function h(l){return V.stringify(l)}function E(l){try{return V.parse(l)}catch(e){try{return JSON.parse(l)}catch(t){throw new Error(`Failed to deserialize data: ${e} ${t}`)}}}var H={info:(l,e)=>console.log(`[INFO] ${l}`,e||""),error:(l,e)=>console.error(`[ERROR] ${l}`,e||""),warn:(l,e)=>console.warn(`[WARN] ${l}`,e||""),debug:(l,e)=>console.debug(`[DEBUG] ${l}`,e||"")},N=class{constructor(e){this.retention=e}getKey(e){return`workflow:${e}`}getLockKey(e){return`workflow:${e}:lock`}async acquireLock(e,t=10){let a=this.getLockKey(e),n=K();return await o.set(a,n,"EX",t,"NX")==="OK"?n:null}async releaseLock(e,t){await o.eval(_,1,this.getLockKey(e),t)}async renewLock(e,t,a){return await o.eval(D,1,this.getLockKey(e),t,a)===1}async get(e){let t=await o.hgetall(this.getKey(e));return!t||Object.keys(t).length===0?null:{workflowId:t.workflowId,name:t.name,version:t.version,status:t.status,step:parseInt(t.step,10),input:E(t.input),state:E(t.state),result:t.result?E(t.result):void 0,error:t.error,parentId:t.parentId,subWorkflowId:t.subWorkflowId,awaitingSignal:t.awaitingSignal||t.awaitingEvent,awaitingSignals:t.awaitingSignals?JSON.parse(t.awaitingSignals):[],createdAt:t.createdAt?parseInt(t.createdAt,10):0,updatedAt:t.updatedAt?parseInt(t.updatedAt,10):0,tags:t.tags?JSON.parse(t.tags):[]}}async create(e){let t=Date.now(),a={...e,step:0,state:{},createdAt:t,updatedAt:t,tags:e.tags||[]},n={...a,input:h(a.input),state:h(a.state),tags:JSON.stringify(a.tags)};n.version===void 0&&delete n.version;let r=o.pipeline();if(r.hset(this.getKey(a.workflowId),n),a.tags&&a.tags.length>0)for(let s of a.tags)r.sadd(`${I}${s}`,a.workflowId);await r.exec()}async getIdsByTag(e){return o.smembers(`${I}${e}`)}async removeTagIndex(e,t){if(!t.length)return;let a=o.pipeline();for(let n of t)a.srem(`${I}${n}`,e);await a.exec()}async updateState(e,t,a,n){if(await o.eval(W,2,this.getLockKey(e),this.getKey(e),n,h(t),Date.now(),a)===0)throw new Error(`Lock lost for workflow ${e}`)}async updateStatus(e,t,a={}){await o.hset(this.getKey(e),{status:t,...a,updatedAt:Date.now()})}async incrementStep(e,t){let a=await o.eval(F,2,this.getLockKey(e),this.getKey(e),t);if(a===-1)throw new Error(`Lock lost for workflow ${e}`);return a}async applyRetention(e){if(this.retention){let t=v(this.retention)/1e3;t>0&&await o.expire(this.getKey(e),t)}}async complete(e,t,a){if(await o.eval($,2,this.getLockKey(e),this.getKey(e),a,h(t??null),g.COMPLETED)===0)throw new Error(`Lock lost for workflow ${e}`);await this.applyRetention(e)}async fail(e,t,a,n=g.FAILED){a?await o.eval(G,2,this.getLockKey(e),this.getKey(e),a,t.message,n)===0&&console.warn(`Could not fail workflow ${e} safely: Lock lost.`):await o.hset(this.getKey(e),{status:n,error:t.message}),await this.applyRetention(e)}async scheduleSleep(e,t){await this.updateStatus(e,g.SLEEPING),await o.zadd(y,t,e)}async getWorkflowsToWake(e=100){let t=Date.now();return await o.eval(U,1,y,e,t)}async enqueueTask(e){await o.lpush(m,h(e))}async resumeForCatch(e,t,a,n){if(await o.eval(W,2,this.getLockKey(e),this.getKey(e),n,h(t),Date.now(),a)===0)throw new Error(`Lock lost for workflow ${e}`);await o.hset(this.getKey(e),{status:g.RUNNING})}async moveToDLQ(e,t){let a={...e,failedAt:Date.now(),error:t.message,stack:t.stack};await o.lpush(O,h(a))}async scheduleTaskRetry(e,t){let a=Date.now()+t;await o.zadd(L,a,h(e))}async moveDueTasksToQueue(e=100){return await o.eval(M,2,L,m,Date.now(),e)}},R=class{durableFns=new Map;repo;workerId=K();isRunning=!1;schedulerInterval=null;heartbeatInterval=null;sourceRoot;pollingInterval;logger;maxTaskRetries=3;constructor(e){this.sourceRoot=e.sourceRoot,this.repo=new N(e.retention),this.pollingInterval=e.pollingInterval||5e3,this.logger=e.logger||H}async getState(e){let t=await this.repo.get(e);return t?{workflowId:t.workflowId,name:t.name,version:t.version,status:t.status,step:t.step,input:t.input,output:t.result,state:t.state,error:t.error,createdAt:t.createdAt,updatedAt:t.updatedAt,tags:t.tags}:null}async start(e,t,a){let n=t.workflowId||K();if(t.workflowId){let r=await this.repo.get(t.workflowId);if(r&&r.status!==g.COMPLETED&&r.status!==g.FAILED)throw new Error(`Workflow with ID '${t.workflowId}' already exists and is in a running state (${r.status}).`)}return this.logger.info(`[RUNTIME] Iniciando workflow '${e.name}' v${e.version} con ID: ${n}`),await this.repo.create({workflowId:n,name:e.name,version:e.version,status:g.RUNNING,input:t.input,parentId:a,tags:t.tags||[]}),setImmediate(()=>{this._executeStep(n,e).catch(r=>{this.logger.error("Error fatal en ejecuci\xF3n inicial",{error:r,workflowId:n})})}),{workflowId:n,unsubscribe:async()=>{}}}async scheduleExecution(e,t,a,n){setImmediate(()=>{this._executeStep(e,t,a,n).catch(r=>{this.logger.error("Error no manejado en scheduleExecution",{error:r,workflowId:e})})})}async _executeStep(e,t,a,n){let r=await this.repo.acquireLock(e);if(!r)return;let s=setInterval(()=>{this.repo.renewLock(e,r,10).catch(i=>this.logger.warn(`Error renovando lock para ${e}`,{error:i}))},5e3);try{if(n)throw n;let i=await this.repo.get(e);if(!i)return;if(i.status===g.CANCELLING)throw new S(i.error||"Workflow cancelled");if(i.status!==g.RUNNING)return;let c=i.version==="undefined"?void 0:i.version,p=t.version==="undefined"?void 0:t.version;if(String(c??"")!==String(p??"")){let w=new Error(`Version mismatch: DB=${c}, Code=${p}`);await this.repo.fail(e,w,r,g.VERSION_MISMATCH);return}let u={workflowId:e,step:i.step,input:i.input,state:i.state,result:a,log:(w,T)=>this.logger.info(w,{...T,workflowId:e,step:i.step})},d=await t.execute(u);await this.repo.updateState(e,u.state,u.step,r),await this.handleInstruction(d,u,i.name,r)&&(await this.repo.incrementStep(e,r),this.scheduleExecution(e,t,void 0))}catch(i){let c=i instanceof Error?i:new Error(String(i));this.logger.error("Error en workflow",{workflowId:e,error:c.message}),await this.handleFailure(e,c,t,r)}finally{clearInterval(s),await this.repo.releaseLock(e,r)}}async handleInstruction(e,t,a,n){let{workflowId:r}=t;switch(e.type){case"SCHEDULE_TASK":return await this.repo.enqueueTask({workflowId:r,durableFunctionName:a,...e}),!1;case"SCHEDULE_SLEEP":{let s=v(e.duration);if(typeof s!="number")throw new Error(`Invalid time value provided to bSleep: "${e.duration}"`);let i=Date.now()+s;return await this.repo.scheduleSleep(r,i),!1}case"WAIT_FOR_SIGNAL":return await this.repo.updateStatus(r,g.AWAITING_SIGNAL,{awaitingSignal:e.signalName,awaitingSignals:JSON.stringify([e.signalName])}),await o.sadd(`signals:awaiting:${e.signalName}`,r),!1;case"WAIT_FOR_ANY_SIGNAL":{await this.repo.updateStatus(r,g.AWAITING_SIGNAL,{awaitingSignal:"",awaitingSignals:JSON.stringify(e.signalNames)});let s=o.pipeline();for(let i of e.signalNames)s.sadd(`signals:awaiting:${i}`,r);return await s.exec(),!1}case"EXECUTE_SUBWORKFLOW":{let s=this.durableFns.get(e.workflowName);if(!s)throw new Error(`Sub-workflow '${e.workflowName}' no encontrado.`);let{workflowId:i}=await this.start(s,{input:e.input},r);return await this.repo.updateStatus(r,g.AWAITING_SUBWORKFLOW,{subWorkflowId:i}),!1}case"EMIT_EVENT":{let s=`event:${r}`,i=h({eventName:e.eventName,payload:e.payload});return await o.publish(s,i),!0}case"COMPLETE":{let s=`event:${r}`,i=h({eventName:"workflow:completed",payload:e.result});return await o.publish(s,i),await this.repo.complete(r,e.result,n),await this.resumeParentWorkflow(r),!1}}}async handleFailure(e,t,a,n){let r=n;if(!r&&(r=await this.repo.acquireLock(e,20),!r)){this.logger.warn(`No se pudo adquirir lock para fallo en ${e}`);return}try{if(t instanceof S){await this.repo.fail(e,t,r,g.CANCELLED);let u=await this.repo.get(e);u?.subWorkflowId&&await this.cancel(u.subWorkflowId,`Parent workflow ${e} was cancelled`);return}let s=await this.repo.get(e);if(!s||s.status===g.FAILED||s.status===g.COMPLETED)return;let i=s.state.tryCatchStack;if(i&&i.length>0){let d=i.pop()?.catchStep;if(d!==void 0){this.logger.info(`Capturando error en step ${d}`,{workflowId:e}),await this.repo.resumeForCatch(e,s.state,d,r),this.scheduleExecution(e,a,{name:t.name,message:t.message,stack:t.stack});return}}let c=`event:${e}`,p=h({eventName:"workflow:failed",payload:{message:t.message}});await o.publish(c,p),await this.repo.fail(e,t,r),await this.propagateFailureToParent(e,t)}finally{!n&&r&&await this.repo.releaseLock(e,r)}}async resumeParentWorkflow(e){let t=await this.repo.get(e);if(!t?.parentId)return;let a=t.parentId,n=await this.repo.get(a);if(!n||n.status!==g.AWAITING_SUBWORKFLOW||n.subWorkflowId!==e)return;let r=this.durableFns.get(n.name);if(!r){await this.repo.fail(a,new Error(`Definici\xF3n del workflow '${n.name}' no encontrada.`),null);return}await this.repo.updateStatus(a,g.RUNNING,{subWorkflowId:""});let s=await this.repo.acquireLock(a);if(s)try{await this.repo.incrementStep(a,s),this.scheduleExecution(a,r,t.result)}finally{await this.repo.releaseLock(a,s)}else throw this.logger.warn(`Could not lock parent ${a} to resume. Retrying later...`),new Error(`Temporary Lock Failure: Could not acquire parent lock for ${a}`)}async propagateFailureToParent(e,t){let a=await this.repo.get(e);if(!a?.parentId)return;let n=a.parentId,r=await this.repo.get(n);if(!r||r.status!==g.AWAITING_SUBWORKFLOW||r.subWorkflowId!==e)return;let s=this.durableFns.get(r.name);if(!s){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,g.RUNNING,{subWorkflowId:""});let i=new Error(`Sub-workflow '${a.name}' (${e}) fall\xF3: ${t.message}`);i.stack=t.stack,this.scheduleExecution(n,s,void 0,i)}async signal(e,t,a){let n=null;for(let r=0;r<3&&(n=await this.repo.acquireLock(e),!n);r++)await new Promise(s=>setTimeout(s,50));if(!n)return this.logger.warn("Lock timeout en signal",{workflowId:e});try{let r=await this.repo.get(e);if(!r)return this.logger.warn("Se\xF1al para workflow inexistente",{workflowId:e});if(r.status!==g.AWAITING_SIGNAL||r.awaitingSignal!==t)return this.logger.warn("Workflow no esperaba esta se\xF1al",{workflowId:e,expected:r.awaitingSignal,received:t});let s=r.awaitingSignals&&r.awaitingSignals.length>0?r.awaitingSignals:r.awaitingSignal?[r.awaitingSignal]:[];if(!s.includes(t))return this.logger.warn("Workflow no esperaba esta se\xF1al",{workflowId:e,expected:s,received:t});let i=this.durableFns.get(r.name);if(!i){await this.repo.fail(e,new Error(`Funci\xF3n durable '${r.name}' no encontrada.`),n);return}await this.repo.updateStatus(e,g.RUNNING,{awaitingSignal:"",awaitingSignals:"[]"});let c=o.pipeline();for(let u of s)c.srem(`signals:awaiting:${u}`,e);await c.exec(),await this.repo.incrementStep(e,n);let p=a;r.awaitingSignals&&r.awaitingSignals.length>0&&r.awaitingSignal===""&&(p={name:t,payload:a}),this.scheduleExecution(e,i,p)}catch(r){let s=r instanceof Error?r:new Error(String(r)),i=(await this.repo.get(e))?.name||"",c=this.durableFns.get(i);await this.handleFailure(e,s,c,n)}finally{n&&await this.repo.releaseLock(e,n)}}async cancel(e,t){let a=await this.repo.acquireLock(e);if(!a)return await new Promise(n=>setTimeout(n,100)),this.cancel(e,t);try{let n=await this.repo.get(e);if(!n||[g.COMPLETED,g.FAILED,g.CANCELLED].includes(n.status))return;if(await this.repo.updateStatus(e,g.CANCELLING,{error:t}),n.status===g.SLEEPING){await o.zrem(y,e);let r=this.durableFns.get(n.name);this.scheduleExecution(e,r)}if(n.status===g.AWAITING_SIGNAL){let r=this.durableFns.get(n.name);this.scheduleExecution(e,r)}}finally{await this.repo.releaseLock(e,a)}}async cancelByTag(e,t){this.logger.info(`[RUNTIME] Cancelando grupo de workflows por tag: ${e}`);let a=await this.repo.getIdsByTag(e);if(a.length===0){this.logger.debug(`No se encontraron workflows para el tag: ${e}`);return}let n=a.map(r=>this.cancel(r,t).catch(s=>{this.logger.error(`Error cancelando workflow ${r} del grupo ${e}`,{error:s})}));await Promise.all(n),this.logger.info(`[RUNTIME] Se enviaron se\xF1ales de cancelaci\xF3n a ${a.length} workflows del tag: ${e}`)}startScheduler(){if(this.schedulerInterval)return;this.logger.info(`Scheduler iniciado (${this.pollingInterval}ms)`);let e=async()=>{await this.checkSleepers(),await this.checkDelayedTasks(),await this.reapDeadWorkers()};this.schedulerInterval=setInterval(e,this.pollingInterval)}async checkDelayedTasks(){try{let e=await this.repo.moveDueTasksToQueue(50);e>0&&this.logger.debug(`Scheduler movi\xF3 ${e} tareas diferidas a la cola activa`)}catch(e){this.logger.error("Error chequeando tareas diferidas",{error:e})}}async checkSleepers(){let t=await this.repo.getWorkflowsToWake(50);t.length!==0&&await Promise.all(t.map(async a=>{let n=await this.repo.acquireLock(a);if(n)try{let r=await this.repo.get(a);if(r){let s=this.durableFns.get(r.name);s&&(this.logger.info("Despertando workflow",{workflowId:a}),await this.repo.updateStatus(a,g.RUNNING),await this.repo.incrementStep(a,n),this.scheduleExecution(a,s,void 0))}}finally{await this.repo.releaseLock(a,n)}}))}async reapDeadWorkers(){let e="0";do{let[t,a]=await o.sscan(k,e,"COUNT",100);e=t;for(let n of a){if(await o.exists(`${A}${n}`))continue;this.logger.warn(`Worker muerto ${n}. Recuperando tareas.`);let r=`${m}:processing:${n}`,s=await o.rpoplpush(r,m);for(;s;)s=await o.rpoplpush(r,m);await o.del(r),await o.srem(k,n)}}while(e!=="0")}startHeartbeat(){let e=`${A}${this.workerId}`,t=Math.max(Math.ceil(this.pollingInterval*3/1e3),5),a=()=>{this.isRunning&&o.set(e,Date.now().toString(),"EX",t).catch(()=>{})};this.heartbeatInterval=setInterval(a,this.pollingInterval),a()}startWorker(){if(this.isRunning)return;this.isRunning=!0;let e=`${m}:processing:${this.workerId}`;this.logger.info(`Worker ${this.workerId} iniciado`),this.startHeartbeat(),(async()=>{for(await o.sadd(k,this.workerId);this.isRunning;)try{let a=await b.brpoplpush(m,e,2);if(!a)continue;let n=E(a);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(Y(this.sourceRoot,n.modulePath));let s=r[n.exportName];if(typeof s!="function")throw new Error(`'${n.exportName}' no es una funci\xF3n.`);let i=await s(...n.args),c=this.durableFns.get(n.durableFunctionName);if(c){let p=await this.repo.acquireLock(n.workflowId);if(p)try{await this.repo.incrementStep(n.workflowId,p),this.scheduleExecution(n.workflowId,c,i)}finally{await this.repo.releaseLock(n.workflowId,p)}else this.logger.warn(`No se pudo adquirir lock para avanzar workflow ${n.workflowId} tras tarea`,{task:n.exportName})}await o.lrem(e,1,a)}catch(r){let s=r instanceof Error?r:new Error(String(r));this.logger.error(`Fallo en tarea ${n.exportName}`,{workflowId:n.workflowId,error:s.message});let i=this.durableFns.get(n.durableFunctionName),c=i?.retryOptions||{},p=c.maxAttempts??3,u=(n.attempts||0)+1;if(n.attempts=u,u<=p){let d=c.initialInterval?v(c.initialInterval):1e3,f=c.backoffCoefficient??2,w=c.maxInterval?v(c.maxInterval):36e5,T=d*Math.pow(f,u-1);T>w&&(T=w),this.logger.warn(`Reintentando tarea en ${v(T)} (intento ${u}/${p===1/0?"Inf":p})`,{workflowId:n.workflowId}),T>0?await this.repo.scheduleTaskRetry(n,T):await o.lpush(m,h(n)),await o.lrem(e,1,a)}else this.logger.error("Reintentos agotados. Moviendo a DLQ.",{workflowId:n.workflowId}),await this.repo.moveToDLQ(n,s),i?await this.handleFailure(n.workflowId,s,i,null):await this.repo.fail(n.workflowId,new Error(`Def missing for ${n.durableFunctionName}`),null),await o.lrem(e,1,a)}}catch(a){if(!this.isRunning)break;this.logger.error("Error infraestructura worker",{error:a}),await new Promise(n=>setTimeout(n,5e3))}})()}run(e){this.durableFns=e,this.startWorker(),this.startScheduler()}async stop(){this.isRunning=!1,this.schedulerInterval&&clearInterval(this.schedulerInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),await o.srem(k,this.workerId),this.logger.info("Runtime detenido")}};var z=l=>({...l,__isDurable:!0});var Q={info:(l,e)=>console.log(`[INFO] ${l}`,e||""),error:(l,e)=>console.error(`[ERROR] ${l}`,e||""),warn:(l,e)=>console.warn(`[WARN] ${l}`,e||""),debug:(l,e)=>console.debug(`[DEBUG] ${l}`,e||"")},j=l=>{if(!l.startsWith("on")||l.length<=2)return null;let e=l.slice(2);return e.charAt(0).toLowerCase()+e.slice(1)};function Ae(l){let e=l.logger||Q;e.info("--- Inicializando Sistema Durable ---"),C({commandClient:l.redisClient,blockingClient:l.blockingRedisClient});let t=new R({sourceRoot:l.sourceRoot,retention:l.retention,pollingInterval:l.pollingInterval,logger:e});t.run(l.durableFunctions);let a=new q(l.redisClient.options),n=new Map;a.psubscribe("event:*",s=>{s&&e.error("Error fatal al suscribirse a los canales de eventos:",{error:s})}),a.on("pmessage",(s,i,c)=>{let p=n.get(i);if(p&&p.length>0)try{let u=E(c),d={name:u.eventName||u.signalName,payload:u.payload};[...p].forEach(f=>f(d))}catch(u){e.error(`Error al parsear evento en ${i}`,{error:u})}});let r=(s,i)=>{let c=`event:${i}`,p=u=>(n.has(c)||n.set(c,[]),n.get(c)?.push(u),()=>{let d=n.get(c);if(d){let f=d.indexOf(u);f>-1&&d.splice(f,1),d.length===0&&n.delete(c)}});return{workflowId:i,signal:async(u,d)=>{await t.signal(i,u,d)},on:async(u,d)=>{let f=P(i),w=await l.redisClient.hgetall(f);return u==="workflow:completed"&&w.status===g.COMPLETED?(d(E(w.result||"null")),{unsubscribe:()=>{}}):u==="workflow:failed"&&w.status===g.FAILED?(d({message:w.error||"Unknown"}),{unsubscribe:()=>{}}):{unsubscribe:p(x=>{x.name===u&&d(x.payload)})}},subscribe:async u=>({unsubscribe:p(f=>{u(f)})})}};return{start:async(s,i)=>{let c=i.workflowId||B(),p=[],u=`event:${c}`,d={};if(Object.keys(i).forEach(f=>{let w=j(f);w&&typeof i[f]=="function"&&(d[w]=i[f])}),Object.keys(d).length>0){n.has(u)||n.set(u,[]);let f=w=>{let T=d[w.name];T&&T(w.payload)};n.get(u)?.push(f),p.push(()=>{let w=n.get(u);if(w){let T=w.indexOf(f);T>-1&&w.splice(T,1)}})}return await t.start(s,{workflowId:c,input:i.input,tags:i.tags}),{workflowId:c,unsubscribe:async()=>{p.forEach(f=>f())}}},stop:()=>{t.stop(),a.quit().catch(()=>{})},runtime:t,cancel:(s,i)=>t.cancel(s,i),getState:s=>t.getState(s),getHandle:(s,i)=>r(s,i),cancelByTag:(s,i)=>t.cancelByTag(s,i)}}export{S as WorkflowCancellationError,z as bDurable,Ae as bDurableInitialize};
|
|
91
|
+
`;import V from"superjson";function h(l){return V.stringify(l)}function E(l){try{return V.parse(l)}catch(e){try{return JSON.parse(l)}catch(t){throw new Error(`Failed to deserialize data: ${e} ${t}`)}}}var H={info:(l,e)=>console.log(`[INFO] ${l}`,e||""),error:(l,e)=>console.error(`[ERROR] ${l}`,e||""),warn:(l,e)=>console.warn(`[WARN] ${l}`,e||""),debug:(l,e)=>console.debug(`[DEBUG] ${l}`,e||"")},W=class{constructor(e){this.retention=e}getKey(e){return`workflow:${e}`}getLockKey(e){return`workflow:${e}:lock`}async acquireLock(e,t=10){let a=this.getLockKey(e),n=N();return await o.set(a,n,"EX",t,"NX")==="OK"?n:null}async releaseLock(e,t){await o.eval(_,1,this.getLockKey(e),t)}async renewLock(e,t,a){return await o.eval(D,1,this.getLockKey(e),t,a)===1}async get(e){let t=await o.hgetall(this.getKey(e));return!t||Object.keys(t).length===0?null:{workflowId:t.workflowId,name:t.name,version:t.version,status:t.status,step:parseInt(t.step,10),input:E(t.input),state:E(t.state),result:t.result?E(t.result):void 0,error:t.error,parentId:t.parentId,subWorkflowId:t.subWorkflowId,awaitingSignal:t.awaitingSignal||t.awaitingEvent,awaitingSignals:t.awaitingSignals?JSON.parse(t.awaitingSignals):[],createdAt:t.createdAt?parseInt(t.createdAt,10):0,updatedAt:t.updatedAt?parseInt(t.updatedAt,10):0,tags:t.tags?JSON.parse(t.tags):[]}}async create(e){let t=Date.now(),a={...e,step:0,state:{},createdAt:t,updatedAt:t,tags:e.tags||[]},n={...a,input:h(a.input),state:h(a.state),tags:JSON.stringify(a.tags)};n.version===void 0&&delete n.version;let r=o.pipeline();if(r.hset(this.getKey(a.workflowId),n),a.tags&&a.tags.length>0)for(let s of a.tags)r.sadd(`${I}${s}`,a.workflowId);await r.exec()}async getIdsByTag(e){return o.smembers(`${I}${e}`)}async removeTagIndex(e,t){if(!t.length)return;let a=o.pipeline();for(let n of t)a.srem(`${I}${n}`,e);await a.exec()}async updateState(e,t,a,n){if(await o.eval(K,2,this.getLockKey(e),this.getKey(e),n,h(t),Date.now(),a)===0)throw new Error(`Lock lost for workflow ${e}`)}async updateStatus(e,t,a={}){await o.hset(this.getKey(e),{status:t,...a,updatedAt:Date.now()})}async incrementStep(e,t){let a=await o.eval(F,2,this.getLockKey(e),this.getKey(e),t);if(a===-1)throw new Error(`Lock lost for workflow ${e}`);return a}async applyRetention(e){if(this.retention){let t=v(this.retention)/1e3;t>0&&await o.expire(this.getKey(e),t)}}async complete(e,t,a){if(await o.eval($,2,this.getLockKey(e),this.getKey(e),a,h(t??null),g.COMPLETED)===0)throw new Error(`Lock lost for workflow ${e}`);await this.applyRetention(e)}async fail(e,t,a,n=g.FAILED){a?await o.eval(G,2,this.getLockKey(e),this.getKey(e),a,t.message,n)===0&&console.warn(`Could not fail workflow ${e} safely: Lock lost.`):await o.hset(this.getKey(e),{status:n,error:t.message}),await this.applyRetention(e)}async scheduleSleep(e,t){await this.updateStatus(e,g.SLEEPING),await o.zadd(y,t,e)}async getWorkflowsToWake(e=100){let t=Date.now();return await o.eval(U,1,y,e,t)}async enqueueTask(e){await o.lpush(m,h(e))}async resumeForCatch(e,t,a,n){if(await o.eval(K,2,this.getLockKey(e),this.getKey(e),n,h(t),Date.now(),a)===0)throw new Error(`Lock lost for workflow ${e}`);await o.hset(this.getKey(e),{status:g.RUNNING})}async moveToDLQ(e,t){let a={...e,failedAt:Date.now(),error:t.message,stack:t.stack};await o.lpush(O,h(a))}async scheduleTaskRetry(e,t){let a=Date.now()+t;await o.zadd(L,a,h(e))}async moveDueTasksToQueue(e=100){return await o.eval(M,2,L,m,Date.now(),e)}},R=class{durableFns=new Map;repo;workerId=N();isRunning=!1;schedulerInterval=null;heartbeatInterval=null;sourceRoot;pollingInterval;logger;maxTaskRetries=3;constructor(e){this.sourceRoot=e.sourceRoot,this.repo=new W(e.retention),this.pollingInterval=e.pollingInterval||5e3,this.logger=e.logger||H}async getState(e){let t=await this.repo.get(e);return t?{workflowId:t.workflowId,name:t.name,version:t.version,status:t.status,step:t.step,input:t.input,output:t.result,state:t.state,error:t.error,createdAt:t.createdAt,updatedAt:t.updatedAt,tags:t.tags}:null}async start(e,t,a){let n=t.workflowId||N();if(t.workflowId){let r=await this.repo.get(t.workflowId);if(r&&r.status!==g.COMPLETED&&r.status!==g.FAILED)throw new Error(`Workflow with ID '${t.workflowId}' already exists and is in a running state (${r.status}).`)}return this.logger.info(`[RUNTIME] Iniciando workflow '${e.name}' v${e.version} con ID: ${n}`),await this.repo.create({workflowId:n,name:e.name,version:e.version,status:g.RUNNING,input:t.input,parentId:a,tags:t.tags||[]}),setImmediate(()=>{this._executeStep(n,e).catch(r=>{this.logger.error("Error fatal en ejecuci\xF3n inicial",{error:r,workflowId:n})})}),{workflowId:n,unsubscribe:async()=>{}}}async scheduleExecution(e,t,a,n){setImmediate(()=>{this._executeStep(e,t,a,n).catch(r=>{this.logger.error("Error no manejado en scheduleExecution",{error:r,workflowId:e})})})}async _executeStep(e,t,a,n){let r=await this.repo.acquireLock(e);if(!r)return;let s=setInterval(()=>{this.repo.renewLock(e,r,10).catch(i=>this.logger.warn(`Error renovando lock para ${e}`,{error:i}))},5e3);try{if(n)throw n;let i=await this.repo.get(e);if(!i)return;if(i.status===g.CANCELLING)throw new S(i.error||"Workflow cancelled");if(i.status!==g.RUNNING)return;let c=i.version==="undefined"?void 0:i.version,p=t.version==="undefined"?void 0:t.version;if(String(c??"")!==String(p??"")){let w=new Error(`Version mismatch: DB=${c}, Code=${p}`);await this.repo.fail(e,w,r,g.VERSION_MISMATCH);return}let u={workflowId:e,step:i.step,input:i.input,state:i.state,result:a,log:(w,T)=>this.logger.info(w,{...T,workflowId:e,step:i.step})},d=await t.execute(u);await this.repo.updateState(e,u.state,u.step,r),await this.handleInstruction(d,u,i.name,r)&&(await this.repo.incrementStep(e,r),this.scheduleExecution(e,t,void 0))}catch(i){let c=i instanceof Error?i:new Error(String(i));this.logger.error("Error en workflow",{workflowId:e,error:c.message}),await this.handleFailure(e,c,t,r)}finally{clearInterval(s),await this.repo.releaseLock(e,r)}}async handleInstruction(e,t,a,n){let{workflowId:r}=t;switch(e.type){case"SCHEDULE_TASK":return await this.repo.enqueueTask({workflowId:r,durableFunctionName:a,...e}),!1;case"SCHEDULE_SLEEP":{let s=v(e.duration);if(typeof s!="number")throw new Error(`Invalid time value provided to bSleep: "${e.duration}"`);let i=Date.now()+s;return await this.repo.scheduleSleep(r,i),!1}case"WAIT_FOR_SIGNAL":return await this.repo.updateStatus(r,g.AWAITING_SIGNAL,{awaitingSignal:e.signalName,awaitingSignals:JSON.stringify([e.signalName])}),await o.sadd(`signals:awaiting:${e.signalName}`,r),!1;case"WAIT_FOR_ANY_SIGNAL":{await this.repo.updateStatus(r,g.AWAITING_SIGNAL,{awaitingSignal:"",awaitingSignals:JSON.stringify(e.signalNames)});let s=o.pipeline();for(let i of e.signalNames)s.sadd(`signals:awaiting:${i}`,r);return await s.exec(),!1}case"EXECUTE_SUBWORKFLOW":{let s=this.durableFns.get(e.workflowName);if(!s)throw new Error(`Sub-workflow '${e.workflowName}' no encontrado.`);let{workflowId:i}=await this.start(s,{input:e.input},r);return await this.repo.updateStatus(r,g.AWAITING_SUBWORKFLOW,{subWorkflowId:i}),!1}case"EMIT_EVENT":{let s=`event:${r}`,i=h({eventName:e.eventName,payload:e.payload});return await o.publish(s,i),!0}case"COMPLETE":{let s=`event:${r}`,i=h({eventName:"workflow:completed",payload:e.result});return await o.publish(s,i),await this.repo.complete(r,e.result,n),await this.resumeParentWorkflow(r),!1}}}async handleFailure(e,t,a,n){let r=n;if(!r&&(r=await this.repo.acquireLock(e,20),!r)){this.logger.warn(`No se pudo adquirir lock para fallo en ${e}`);return}try{if(t instanceof S){await this.repo.fail(e,t,r,g.CANCELLED);let u=await this.repo.get(e);u?.subWorkflowId&&await this.cancel(u.subWorkflowId,`Parent workflow ${e} was cancelled`);return}let s=await this.repo.get(e);if(!s||s.status===g.FAILED||s.status===g.COMPLETED)return;let i=s.state.tryCatchStack;if(i&&i.length>0){let d=i.pop()?.catchStep;if(d!==void 0){this.logger.info(`Capturando error en step ${d}`,{workflowId:e}),await this.repo.resumeForCatch(e,s.state,d,r),this.scheduleExecution(e,a,{name:t.name,message:t.message,stack:t.stack});return}}let c=`event:${e}`,p=h({eventName:"workflow:failed",payload:{message:t.message}});await o.publish(c,p),await this.repo.fail(e,t,r),await this.propagateFailureToParent(e,t)}finally{!n&&r&&await this.repo.releaseLock(e,r)}}async resumeParentWorkflow(e){let t=await this.repo.get(e);if(!t?.parentId)return;let a=t.parentId,n=await this.repo.get(a);if(!n||n.status!==g.AWAITING_SUBWORKFLOW||n.subWorkflowId!==e)return;let r=this.durableFns.get(n.name);if(!r){await this.repo.fail(a,new Error(`Definici\xF3n del workflow '${n.name}' no encontrada.`),null);return}await this.repo.updateStatus(a,g.RUNNING,{subWorkflowId:""});let s=await this.repo.acquireLock(a);if(s)try{await this.repo.incrementStep(a,s),this.scheduleExecution(a,r,t.result)}finally{await this.repo.releaseLock(a,s)}else throw this.logger.warn(`Could not lock parent ${a} to resume. Retrying later...`),new Error(`Temporary Lock Failure: Could not acquire parent lock for ${a}`)}async propagateFailureToParent(e,t){let a=await this.repo.get(e);if(!a?.parentId)return;let n=a.parentId,r=await this.repo.get(n);if(!r||r.status!==g.AWAITING_SUBWORKFLOW||r.subWorkflowId!==e)return;let s=this.durableFns.get(r.name);if(!s){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,g.RUNNING,{subWorkflowId:""});let i=new Error(`Sub-workflow '${a.name}' (${e}) fall\xF3: ${t.message}`);i.stack=t.stack,this.scheduleExecution(n,s,void 0,i)}async signal(e,t,a){let n=null;for(let r=0;r<3&&(n=await this.repo.acquireLock(e),!n);r++)await new Promise(s=>setTimeout(s,50));if(!n)return this.logger.warn("Lock timeout en signal",{workflowId:e});try{let r=await this.repo.get(e);if(!r)return this.logger.warn("Se\xF1al para workflow inexistente",{workflowId:e});if(r.status!==g.AWAITING_SIGNAL||r.awaitingSignal!==t)return this.logger.warn("Workflow no esperaba esta se\xF1al",{workflowId:e,expected:r.awaitingSignal,received:t});let s=r.awaitingSignals&&r.awaitingSignals.length>0?r.awaitingSignals:r.awaitingSignal?[r.awaitingSignal]:[];if(!s.includes(t))return this.logger.warn("Workflow no esperaba esta se\xF1al",{workflowId:e,expected:s,received:t});let i=this.durableFns.get(r.name);if(!i){await this.repo.fail(e,new Error(`Funci\xF3n durable '${r.name}' no encontrada.`),n);return}await this.repo.updateStatus(e,g.RUNNING,{awaitingSignal:"",awaitingSignals:"[]"});let c=o.pipeline();for(let u of s)c.srem(`signals:awaiting:${u}`,e);await c.exec(),await this.repo.incrementStep(e,n);let p=a;r.awaitingSignals&&r.awaitingSignals.length>0&&r.awaitingSignal===""&&(p={name:t,payload:a}),this.scheduleExecution(e,i,p)}catch(r){let s=r instanceof Error?r:new Error(String(r)),i=(await this.repo.get(e))?.name||"",c=this.durableFns.get(i);await this.handleFailure(e,s,c,n)}finally{n&&await this.repo.releaseLock(e,n)}}async cancel(e,t){let a=await this.repo.acquireLock(e);if(!a)return await new Promise(n=>setTimeout(n,100)),this.cancel(e,t);try{let n=await this.repo.get(e);if(!n||[g.COMPLETED,g.FAILED,g.CANCELLED].includes(n.status))return;if(await this.repo.updateStatus(e,g.CANCELLING,{error:t}),n.status===g.SLEEPING){await o.zrem(y,e);let r=this.durableFns.get(n.name);this.scheduleExecution(e,r)}if(n.status===g.AWAITING_SIGNAL){let r=this.durableFns.get(n.name);this.scheduleExecution(e,r)}}finally{await this.repo.releaseLock(e,a)}}async cancelByTag(e,t){this.logger.info(`[RUNTIME] Cancelando grupo de workflows por tag: ${e}`);let a=await this.repo.getIdsByTag(e);if(a.length===0){this.logger.debug(`No se encontraron workflows para el tag: ${e}`);return}let n=a.map(r=>this.cancel(r,t).catch(s=>{this.logger.error(`Error cancelando workflow ${r} del grupo ${e}`,{error:s})}));await Promise.all(n),this.logger.info(`[RUNTIME] Se enviaron se\xF1ales de cancelaci\xF3n a ${a.length} workflows del tag: ${e}`)}startScheduler(){if(this.schedulerInterval)return;this.logger.info(`Scheduler iniciado (${this.pollingInterval}ms)`);let e=async()=>{await this.checkSleepers(),await this.checkDelayedTasks(),await this.reapDeadWorkers()};this.schedulerInterval=setInterval(e,this.pollingInterval)}async checkDelayedTasks(){try{let e=await this.repo.moveDueTasksToQueue(50);e>0&&this.logger.debug(`Scheduler movi\xF3 ${e} tareas diferidas a la cola activa`)}catch(e){this.logger.error("Error chequeando tareas diferidas",{error:e})}}async checkSleepers(){let t=await this.repo.getWorkflowsToWake(50);t.length!==0&&await Promise.all(t.map(async a=>{let n=await this.repo.acquireLock(a);if(n)try{let r=await this.repo.get(a);if(r){let s=this.durableFns.get(r.name);s&&(this.logger.info("Despertando workflow",{workflowId:a}),await this.repo.updateStatus(a,g.RUNNING),await this.repo.incrementStep(a,n),this.scheduleExecution(a,s,void 0))}}finally{await this.repo.releaseLock(a,n)}}))}async reapDeadWorkers(){let e="0";do{let[t,a]=await o.sscan(k,e,"COUNT",100);e=t;for(let n of a){if(await o.exists(`${A}${n}`))continue;this.logger.warn(`Worker muerto ${n}. Recuperando tareas.`);let r=`${m}:processing:${n}`,s=await o.rpoplpush(r,m);for(;s;)s=await o.rpoplpush(r,m);await o.del(r),await o.srem(k,n)}}while(e!=="0")}startHeartbeat(){let e=`${A}${this.workerId}`,t=Math.max(Math.ceil(this.pollingInterval*3/1e3),5),a=()=>{this.isRunning&&o.set(e,Date.now().toString(),"EX",t).catch(()=>{})};this.heartbeatInterval=setInterval(a,this.pollingInterval),a()}startWorker(){if(this.isRunning)return;this.isRunning=!0;let e=`${m}:processing:${this.workerId}`;this.logger.info(`Worker ${this.workerId} iniciado`),this.startHeartbeat(),(async()=>{for(await o.sadd(k,this.workerId);this.isRunning;)try{let a=await b.brpoplpush(m,e,2);if(!a)continue;let n=E(a);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(Y(this.sourceRoot,n.modulePath));let s=r[n.exportName];if(typeof s!="function")throw new Error(`'${n.exportName}' no es una funci\xF3n.`);let i=await s(...n.args),c=this.durableFns.get(n.durableFunctionName);if(c){let p=await this.repo.acquireLock(n.workflowId);if(p)try{await this.repo.incrementStep(n.workflowId,p),this.scheduleExecution(n.workflowId,c,i)}finally{await this.repo.releaseLock(n.workflowId,p)}else this.logger.warn(`No se pudo adquirir lock para avanzar workflow ${n.workflowId} tras tarea`,{task:n.exportName})}await o.lrem(e,1,a)}catch(r){let s=r instanceof Error?r:new Error(String(r));this.logger.error(`Fallo en tarea ${n.exportName}`,{workflowId:n.workflowId,error:s.message});let i=this.durableFns.get(n.durableFunctionName),c=i?.retryOptions||{},p=c.maxAttempts??3,u=(n.attempts||0)+1;if(n.attempts=u,u<=p){let d=c.initialInterval?v(c.initialInterval):1e3,f=c.backoffCoefficient??2,w=c.maxInterval?v(c.maxInterval):36e5,T=d*Math.pow(f,u-1);T>w&&(T=w),this.logger.warn(`Reintentando tarea en ${v(T)} (intento ${u}/${p===1/0?"Inf":p})`,{workflowId:n.workflowId}),T>0?await this.repo.scheduleTaskRetry(n,T):await o.lpush(m,h(n)),await o.lrem(e,1,a)}else this.logger.error("Reintentos agotados. Moviendo a DLQ.",{workflowId:n.workflowId}),await this.repo.moveToDLQ(n,s),i?await this.handleFailure(n.workflowId,s,i,null):await this.repo.fail(n.workflowId,new Error(`Def missing for ${n.durableFunctionName}`),null),await o.lrem(e,1,a)}}catch(a){if(!this.isRunning)break;this.logger.error("Error infraestructura worker",{error:a}),await new Promise(n=>setTimeout(n,5e3))}})()}run(e){this.durableFns=e,this.startWorker(),this.startScheduler()}async stop(){this.isRunning=!1,this.schedulerInterval&&clearInterval(this.schedulerInterval),this.heartbeatInterval&&clearInterval(this.heartbeatInterval),await o.srem(k,this.workerId),this.logger.info("Runtime detenido")}};var z=l=>({...l,__isDurable:!0});var Q={info:(l,e)=>console.log(`[INFO] ${l}`,e||""),error:(l,e)=>console.error(`[ERROR] ${l}`,e||""),warn:(l,e)=>console.warn(`[WARN] ${l}`,e||""),debug:(l,e)=>console.debug(`[DEBUG] ${l}`,e||"")},j=l=>{if(!l.startsWith("on")||l.length<=2)return null;let e=l.slice(2);return e.charAt(0).toLowerCase()+e.slice(1)};function Ae(l){let e=l.logger||Q;e.info("--- Inicializando Sistema Durable ---"),C({commandClient:l.redisClient,blockingClient:l.blockingRedisClient});let t=new R({sourceRoot:l.sourceRoot,retention:l.retention,pollingInterval:l.pollingInterval,logger:e});t.run(l.durableFunctions);let a=new q(l.redisClient.options),n=new Map;a.psubscribe("event:*",s=>{s&&e.error("Error fatal al suscribirse a los canales de eventos:",{error:s})}),a.on("pmessage",(s,i,c)=>{let p=n.get(i);if(p&&p.length>0)try{let u=E(c),d={name:u.eventName||u.signalName,payload:u.payload};[...p].forEach(f=>f(d))}catch(u){e.error(`Error al parsear evento en ${i}`,{error:u})}});let r=(s,i)=>{let c=`event:${i}`,p=u=>(n.has(c)||n.set(c,[]),n.get(c)?.push(u),()=>{let d=n.get(c);if(d){let f=d.indexOf(u);f>-1&&d.splice(f,1),d.length===0&&n.delete(c)}});return{workflowId:i,signal:async(u,d)=>{await t.signal(i,u,d)},on:async(u,d)=>{let f=P(i),w=await l.redisClient.hgetall(f);return u==="workflow:completed"&&w.status===g.COMPLETED?(d(E(w.result||"null")),{unsubscribe:()=>{}}):u==="workflow:failed"&&w.status===g.FAILED?(d({message:w.error||"Unknown"}),{unsubscribe:()=>{}}):{unsubscribe:p(x=>{x.name===u&&d(x.payload)})}},subscribe:async u=>({unsubscribe:p(f=>{u(f)})})}};return{start:async(s,i)=>{let c=i.workflowId||B(),p=[],u=`event:${c}`,d={};if(Object.keys(i).forEach(f=>{let w=j(f);w&&typeof i[f]=="function"&&(d[w]=i[f])}),Object.keys(d).length>0){n.has(u)||n.set(u,[]);let f=w=>{let T=d[w.name];T&&T(w.payload)};n.get(u)?.push(f),p.push(()=>{let w=n.get(u);if(w){let T=w.indexOf(f);T>-1&&w.splice(T,1)}})}return await t.start(s,{workflowId:c,input:i.input,tags:i.tags}),{workflowId:c,unsubscribe:async()=>{p.forEach(f=>f())}}},stop:()=>{t.stop(),a.quit().catch(()=>{})},runtime:t,cancel:(s,i)=>t.cancel(s,i),getState:s=>t.getState(s),getHandle:(s,i)=>r(s,i),cancelByTag:(s,i)=>t.cancelByTag(s,i)}}export{S as WorkflowCancellationError,z as bDurable,Ae as bDurableInitialize};
|
package/package.json
CHANGED