@bobtail.software/b-durable 1.0.4 → 1.0.5
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/README.md +23 -8
- package/dist/compiler/cli.mjs +703 -40
- package/dist/index.d.mts +19 -15
- package/dist/index.mjs +551 -1
- package/package.json +2 -1
package/dist/compiler/cli.mjs
CHANGED
|
@@ -1,58 +1,721 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
|
|
3
|
+
// src/compiler/cli.ts
|
|
4
|
+
import path2 from "path";
|
|
5
|
+
|
|
6
|
+
// src/compiler/compile.ts
|
|
7
|
+
import { existsSync, mkdirSync, rmSync } from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import * as prettier from "prettier";
|
|
10
|
+
import {
|
|
11
|
+
Node,
|
|
12
|
+
Project,
|
|
13
|
+
SyntaxKind,
|
|
14
|
+
ts,
|
|
15
|
+
VariableDeclarationKind
|
|
16
|
+
} from "ts-morph";
|
|
17
|
+
var DURABLE_WRAPPER_NAME = "bDurable";
|
|
18
|
+
var TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | ts.TypeFormatFlags.NoTruncation;
|
|
19
|
+
async function formatSourceFileWithPrettier(sourceFile) {
|
|
20
|
+
const filePath = sourceFile.getFilePath();
|
|
21
|
+
const unformattedText = sourceFile.getFullText();
|
|
22
|
+
const prettierConfig = await prettier.resolveConfig(filePath);
|
|
23
|
+
const formattedText = await prettier.format(unformattedText, {
|
|
24
|
+
...prettierConfig,
|
|
25
|
+
parser: "typescript"
|
|
26
|
+
});
|
|
27
|
+
sourceFile.replaceWithText(formattedText);
|
|
28
|
+
}
|
|
29
|
+
async function compileWorkflows(options) {
|
|
30
|
+
console.log("Iniciando compilador de workflows duraderos...");
|
|
31
|
+
const { inputDir, outputDir, packageName } = options;
|
|
32
|
+
const project = new Project({
|
|
33
|
+
tsConfigFilePath: path.resolve(process.cwd(), "tsconfig.json")
|
|
34
|
+
});
|
|
35
|
+
const sourceFiles = project.addSourceFilesAtPaths(`${inputDir}/**/*.ts`);
|
|
36
|
+
if (existsSync(outputDir)) {
|
|
37
|
+
console.log(`Limpiando directorio de salida: ${outputDir}`);
|
|
38
|
+
rmSync(outputDir, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
mkdirSync(outputDir, { recursive: true });
|
|
41
|
+
const compiledDir = project.createDirectory(outputDir);
|
|
42
|
+
console.log(`Encontrados ${sourceFiles.length} archivos de workflow para procesar.`);
|
|
43
|
+
const workflowRegistry = [];
|
|
44
|
+
const generatedFiles = [];
|
|
45
|
+
for (const sourceFile of sourceFiles) {
|
|
46
|
+
console.log(`
|
|
47
|
+
Procesando archivo: ${sourceFile.getBaseName()}`);
|
|
48
|
+
const durableCalls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).filter((call) => call.getExpression().getText() === DURABLE_WRAPPER_NAME);
|
|
49
|
+
if (durableCalls.length === 0) continue;
|
|
50
|
+
for (const call of durableCalls) {
|
|
51
|
+
const workflowVarDecl = call.getParentIfKind(SyntaxKind.VariableDeclaration);
|
|
52
|
+
if (!workflowVarDecl) continue;
|
|
53
|
+
const workflowName = workflowVarDecl.getName();
|
|
54
|
+
console.log(` -> Transformando workflow: ${workflowName}`);
|
|
55
|
+
const [arg] = call.getArguments();
|
|
56
|
+
if (!Node.isObjectLiteralExpression(arg)) continue;
|
|
57
|
+
const workflowProperty = arg.getProperty("workflow");
|
|
58
|
+
if (!workflowProperty || !Node.isPropertyAssignment(workflowProperty)) continue;
|
|
59
|
+
const workflowFunc = workflowProperty.getInitializer();
|
|
60
|
+
if (!workflowFunc || !Node.isArrowFunction(workflowFunc)) continue;
|
|
61
|
+
const originalBaseName = sourceFile.getBaseName();
|
|
62
|
+
const compiledFileName = originalBaseName.replace(/\.ts$/, ".compiled.mts");
|
|
63
|
+
const newFilePath = path.join(compiledDir.getPath(), compiledFileName);
|
|
64
|
+
const newSourceFile = project.createSourceFile(newFilePath, "", {
|
|
65
|
+
overwrite: true
|
|
66
|
+
});
|
|
67
|
+
generatedFiles.push(newSourceFile);
|
|
68
|
+
transformWorkflowFunction(
|
|
69
|
+
workflowName,
|
|
70
|
+
workflowFunc,
|
|
71
|
+
call,
|
|
72
|
+
newSourceFile,
|
|
73
|
+
packageName
|
|
74
|
+
);
|
|
75
|
+
console.log(
|
|
76
|
+
` -> Archivo generado: ${path.relative(process.cwd(), newFilePath)}`
|
|
77
|
+
);
|
|
78
|
+
const importPathName = compiledFileName;
|
|
79
|
+
workflowRegistry.push({ name: workflowName, importPath: `./${importPathName}` });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (workflowRegistry.length > 0) {
|
|
83
|
+
const indexFilePath = path.join(compiledDir.getPath(), "index.mts");
|
|
84
|
+
const indexSourceFile = project.createSourceFile(indexFilePath, "", {
|
|
85
|
+
overwrite: true
|
|
86
|
+
});
|
|
87
|
+
generatedFiles.push(indexSourceFile);
|
|
88
|
+
indexSourceFile.addStatements(
|
|
89
|
+
"// Este archivo fue generado autom\xE1ticamente. NO EDITAR MANUALMENTE.\n"
|
|
90
|
+
);
|
|
91
|
+
indexSourceFile.addImportDeclaration({
|
|
92
|
+
isTypeOnly: true,
|
|
93
|
+
moduleSpecifier: packageName,
|
|
94
|
+
namedImports: ["DurableFunction"]
|
|
95
|
+
});
|
|
96
|
+
for (const wf of workflowRegistry) {
|
|
97
|
+
indexSourceFile.addImportDeclaration({
|
|
98
|
+
moduleSpecifier: wf.importPath,
|
|
99
|
+
namedImports: [wf.name]
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
indexSourceFile.addExportDeclaration({
|
|
103
|
+
namedExports: workflowRegistry.map((wf) => wf.name)
|
|
104
|
+
});
|
|
105
|
+
indexSourceFile.addStatements("\n");
|
|
106
|
+
indexSourceFile.addVariableStatement({
|
|
107
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
108
|
+
declarations: [
|
|
109
|
+
{
|
|
110
|
+
name: "durableFunctions",
|
|
111
|
+
type: "Map<string, DurableFunction<any, any, any>>",
|
|
112
|
+
initializer: "new Map()"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
});
|
|
116
|
+
const setStatements = workflowRegistry.map(
|
|
117
|
+
(wf) => `durableFunctions.set(${wf.name}.name, ${wf.name});`
|
|
118
|
+
);
|
|
119
|
+
indexSourceFile.addStatements(setStatements);
|
|
120
|
+
indexSourceFile.addStatements("\n");
|
|
121
|
+
indexSourceFile.addExportAssignment({
|
|
122
|
+
isExportEquals: false,
|
|
123
|
+
expression: "durableFunctions"
|
|
124
|
+
});
|
|
125
|
+
console.log(`
|
|
126
|
+
-> Archivo de \xEDndice generado: ${path.basename(indexFilePath)}`);
|
|
127
|
+
}
|
|
128
|
+
console.log("\nFormateando archivos generados con Prettier...");
|
|
129
|
+
for (const file of generatedFiles) {
|
|
130
|
+
await formatSourceFileWithPrettier(file);
|
|
131
|
+
}
|
|
132
|
+
await project.save();
|
|
133
|
+
console.log("\nCompilaci\xF3n completada exitosamente.");
|
|
134
|
+
}
|
|
135
|
+
function transformWorkflowFunction(workflowName, func, bDurableCall, targetSourceFile, packageName) {
|
|
136
|
+
const body = func.getBody();
|
|
137
|
+
if (!Node.isBlock(body)) {
|
|
138
|
+
throw new Error(`El cuerpo del workflow '${workflowName}' debe ser un bloque {}.`);
|
|
139
|
+
}
|
|
140
|
+
const { clauses: caseClauses } = processStatementsRecursive(body.getStatements(), {
|
|
141
|
+
step: 0,
|
|
142
|
+
persistedVariables: /* @__PURE__ */ new Map()
|
|
143
|
+
});
|
|
144
|
+
let returnTypeNode = func.getReturnType();
|
|
145
|
+
if (returnTypeNode.getSymbol()?.getName() === "Promise" && returnTypeNode.isObject()) {
|
|
146
|
+
returnTypeNode = returnTypeNode.getTypeArguments()[0] || returnTypeNode;
|
|
147
|
+
}
|
|
148
|
+
const returnType = returnTypeNode.getText(void 0, TYPE_FORMAT_FLAGS);
|
|
149
|
+
const dependencyStatements = /* @__PURE__ */ new Set();
|
|
150
|
+
const originalSourceFile = func.getSourceFile();
|
|
151
|
+
const typeArgs = bDurableCall.getTypeArguments();
|
|
152
|
+
const paramType = typeArgs.length > 0 ? typeArgs[0].getText() : "unknown";
|
|
153
|
+
const eventsType = typeArgs.length > 2 ? typeArgs[2].getText() : "Record<string, never>";
|
|
154
|
+
originalSourceFile.getImportDeclarations().forEach((importDecl) => {
|
|
155
|
+
if (importDecl.getModuleSpecifierValue() === packageName) return;
|
|
156
|
+
let moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
157
|
+
if (moduleSpecifier.includes(".workflow")) {
|
|
158
|
+
const parsedPath = path.parse(moduleSpecifier);
|
|
159
|
+
let joinedPath = path.join(parsedPath.dir, parsedPath.base + ".compiled.mts");
|
|
160
|
+
if (!joinedPath.startsWith(".") && !path.isAbsolute(joinedPath)) {
|
|
161
|
+
joinedPath = "./" + joinedPath;
|
|
162
|
+
}
|
|
163
|
+
moduleSpecifier = joinedPath.replace(/\\/g, "/");
|
|
164
|
+
} else if (moduleSpecifier.startsWith(".")) {
|
|
165
|
+
if (path.extname(moduleSpecifier) === "") {
|
|
166
|
+
moduleSpecifier += ".mjs";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const valueImports = [];
|
|
170
|
+
const typeImports = [];
|
|
171
|
+
importDecl.getNamedImports().forEach((specifier) => {
|
|
172
|
+
const name = specifier.getName();
|
|
173
|
+
const alias = specifier.getAliasNode()?.getText();
|
|
174
|
+
const importText = alias ? `${name} as ${alias}` : name;
|
|
175
|
+
const symbol = specifier.getNameNode().getSymbol()?.getAliasedSymbol() ?? specifier.getNameNode().getSymbol();
|
|
176
|
+
const declarations = symbol?.getDeclarations() ?? [];
|
|
177
|
+
const isEnum = declarations.some((d) => Node.isEnumDeclaration(d));
|
|
178
|
+
const isPurelyType = specifier.isTypeOnly() || !isEnum && declarations.every(
|
|
179
|
+
(d) => Node.isInterfaceDeclaration(d) || Node.isTypeAliasDeclaration(d)
|
|
180
|
+
);
|
|
181
|
+
if (isPurelyType) {
|
|
182
|
+
typeImports.push(importText);
|
|
183
|
+
} else {
|
|
184
|
+
valueImports.push(importText);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
if (valueImports.length > 0) {
|
|
188
|
+
targetSourceFile.addImportDeclaration({
|
|
189
|
+
moduleSpecifier,
|
|
190
|
+
namedImports: valueImports
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (typeImports.length > 0) {
|
|
194
|
+
targetSourceFile.addImportDeclaration({
|
|
195
|
+
isTypeOnly: true,
|
|
196
|
+
moduleSpecifier,
|
|
197
|
+
namedImports: typeImports
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
201
|
+
if (defaultImport) {
|
|
202
|
+
targetSourceFile.addImportDeclaration({
|
|
203
|
+
moduleSpecifier,
|
|
204
|
+
defaultImport: defaultImport.getText()
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
originalSourceFile.getInterfaces().forEach((iface) => {
|
|
209
|
+
dependencyStatements.add(
|
|
210
|
+
iface.getText().startsWith("export") ? iface.getText() : `export ${iface.getText()}`
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
originalSourceFile.getTypeAliases().forEach((typeAlias) => {
|
|
214
|
+
dependencyStatements.add(
|
|
215
|
+
typeAlias.getText().startsWith("export") ? typeAlias.getText() : `export ${typeAlias.getText()}`
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
const [params] = func.getParameters();
|
|
219
|
+
let paramAssignment = "";
|
|
220
|
+
if (params) {
|
|
221
|
+
const paramName = params.getNameNode().getText();
|
|
222
|
+
if (paramName !== "input") {
|
|
223
|
+
paramAssignment = `const ${paramName} = input;`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
targetSourceFile.addImportDeclaration({
|
|
227
|
+
isTypeOnly: true,
|
|
228
|
+
moduleSpecifier: packageName,
|
|
229
|
+
namedImports: ["DurableFunction", "WorkflowContext", "Instruction"]
|
|
230
|
+
});
|
|
231
|
+
if (dependencyStatements.size > 0) {
|
|
232
|
+
targetSourceFile.addStatements("\n");
|
|
233
|
+
targetSourceFile.addStatements(Array.from(dependencyStatements));
|
|
234
|
+
}
|
|
235
|
+
targetSourceFile.addStatements(
|
|
236
|
+
"\n// Este archivo fue generado autom\xE1ticamente. NO EDITAR MANUALMENTE.\n"
|
|
237
|
+
);
|
|
238
|
+
const initializerObjectString = `{
|
|
13
239
|
__isDurable: true,
|
|
14
|
-
name: '${
|
|
15
|
-
async execute(context: WorkflowContext<${
|
|
240
|
+
name: '${workflowName}',
|
|
241
|
+
async execute(context: WorkflowContext<${paramType}>): Promise<Instruction<${returnType}>> {
|
|
16
242
|
const { input, state, result, log, workflowId } = context;
|
|
17
|
-
${
|
|
243
|
+
${paramAssignment}
|
|
18
244
|
while (true) {
|
|
19
245
|
switch (context.step) {
|
|
20
|
-
${
|
|
21
|
-
`)}
|
|
246
|
+
${caseClauses.join("\n")}
|
|
22
247
|
default:
|
|
23
248
|
throw new Error(\`Paso desconocido: \${context.step}\`);
|
|
24
249
|
}
|
|
25
250
|
}
|
|
26
251
|
}
|
|
27
|
-
}`;
|
|
28
|
-
|
|
252
|
+
}`;
|
|
253
|
+
targetSourceFile.addVariableStatement({
|
|
254
|
+
isExported: true,
|
|
255
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
256
|
+
declarations: [
|
|
257
|
+
{
|
|
258
|
+
name: workflowName,
|
|
259
|
+
type: `DurableFunction<${paramType}, ${returnType}, ${eventsType}>`,
|
|
260
|
+
initializer: initializerObjectString
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
});
|
|
264
|
+
targetSourceFile.organizeImports();
|
|
265
|
+
}
|
|
266
|
+
function processStatementsRecursive(statements, initialState) {
|
|
267
|
+
if (statements.length === 0) {
|
|
268
|
+
const clauses = [];
|
|
269
|
+
if (initialState.pendingStateAssignment) {
|
|
270
|
+
const finalClause = `case ${initialState.step}: {
|
|
271
|
+
${initialState.pendingStateAssignment}
|
|
29
272
|
return { type: 'COMPLETE', result: undefined };
|
|
30
|
-
}`;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
273
|
+
}`;
|
|
274
|
+
clauses.push(finalClause);
|
|
275
|
+
}
|
|
276
|
+
return { clauses, nextStep: initialState.step + 1 };
|
|
277
|
+
}
|
|
278
|
+
const { syncBlock, durableStatement, nextStatements } = splitAtNextDurableCall(statements);
|
|
279
|
+
const { rewrittenSyncStatements, newlyPersistedVariables } = processSyncBlock(
|
|
280
|
+
syncBlock,
|
|
281
|
+
durableStatement ? [durableStatement, ...nextStatements] : [],
|
|
282
|
+
initialState.persistedVariables
|
|
283
|
+
);
|
|
284
|
+
if (initialState.pendingStateAssignment) {
|
|
285
|
+
rewrittenSyncStatements.unshift(initialState.pendingStateAssignment);
|
|
286
|
+
}
|
|
287
|
+
const updatedPersistedVariables = new Map([
|
|
288
|
+
...initialState.persistedVariables,
|
|
289
|
+
...newlyPersistedVariables
|
|
290
|
+
]);
|
|
291
|
+
if (!durableStatement) {
|
|
292
|
+
const syncCode2 = rewrittenSyncStatements.join("\n");
|
|
293
|
+
const lastStatementIsReturn = syncBlock.length > 0 && Node.isReturnStatement(syncBlock[syncBlock.length - 1]);
|
|
294
|
+
const finalInstruction = lastStatementIsReturn ? "" : `
|
|
295
|
+
return { type: 'COMPLETE', result: undefined };`;
|
|
296
|
+
const clause = `case ${initialState.step}: {
|
|
297
|
+
${syncCode2}${finalInstruction}
|
|
298
|
+
}`;
|
|
299
|
+
return { clauses: [clause], nextStep: initialState.step + 1 };
|
|
300
|
+
}
|
|
301
|
+
if (Node.isIfStatement(durableStatement)) {
|
|
302
|
+
return processIfStatement(
|
|
303
|
+
durableStatement,
|
|
304
|
+
nextStatements,
|
|
305
|
+
{
|
|
306
|
+
...initialState,
|
|
307
|
+
persistedVariables: updatedPersistedVariables
|
|
308
|
+
},
|
|
309
|
+
rewrittenSyncStatements
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
if (Node.isTryStatement(durableStatement)) {
|
|
313
|
+
return processTryStatement(
|
|
314
|
+
durableStatement,
|
|
315
|
+
nextStatements,
|
|
316
|
+
{ ...initialState, persistedVariables: updatedPersistedVariables },
|
|
317
|
+
rewrittenSyncStatements
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const { instruction, nextPendingStateAssignment } = generateDurableInstruction(
|
|
321
|
+
durableStatement,
|
|
322
|
+
updatedPersistedVariables
|
|
323
|
+
);
|
|
324
|
+
rewrittenSyncStatements.push(instruction);
|
|
325
|
+
const syncCode = rewrittenSyncStatements.join("\n");
|
|
326
|
+
const currentClause = `case ${initialState.step}: {
|
|
327
|
+
${syncCode}
|
|
328
|
+
}`;
|
|
329
|
+
const nextState = {
|
|
330
|
+
step: initialState.step + 1,
|
|
331
|
+
persistedVariables: updatedPersistedVariables,
|
|
332
|
+
pendingStateAssignment: nextPendingStateAssignment
|
|
333
|
+
};
|
|
334
|
+
const restResult = processStatementsRecursive(nextStatements, nextState);
|
|
335
|
+
return {
|
|
336
|
+
clauses: [currentClause, ...restResult.clauses],
|
|
337
|
+
nextStep: restResult.nextStep
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function processIfStatement(ifStatement, followingStatements, currentState, rewrittenSyncStatements) {
|
|
341
|
+
const condition = rewriteNode(ifStatement.getExpression(), currentState.persistedVariables);
|
|
342
|
+
const thenBlock = ifStatement.getThenStatement();
|
|
343
|
+
const thenStatements = Node.isBlock(thenBlock) ? thenBlock.getStatements() : [thenBlock];
|
|
344
|
+
const thenResult = processStatementsRecursive(thenStatements, {
|
|
345
|
+
step: currentState.step + 1,
|
|
346
|
+
persistedVariables: new Map(currentState.persistedVariables)
|
|
347
|
+
// Clona para aislamiento de ramas
|
|
348
|
+
});
|
|
349
|
+
let elseResult;
|
|
350
|
+
const elseStatement = ifStatement.getElseStatement();
|
|
351
|
+
if (elseStatement) {
|
|
352
|
+
const elseStatements = Node.isBlock(elseStatement) ? elseStatement.getStatements() : [elseStatement];
|
|
353
|
+
elseResult = processStatementsRecursive(elseStatements, {
|
|
354
|
+
step: thenResult.nextStep,
|
|
355
|
+
// La rama 'else' comienza donde termina la 'then'
|
|
356
|
+
persistedVariables: new Map(currentState.persistedVariables)
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const nextStepAfterIf = elseResult ? elseResult.nextStep : thenResult.nextStep;
|
|
360
|
+
const afterIfResult = processStatementsRecursive(followingStatements, {
|
|
361
|
+
step: nextStepAfterIf,
|
|
362
|
+
persistedVariables: currentState.persistedVariables
|
|
363
|
+
// Usa el mapa original
|
|
364
|
+
});
|
|
365
|
+
const syncCode = rewrittenSyncStatements.join("\n");
|
|
366
|
+
const jumpToElseStep = thenResult.nextStep;
|
|
367
|
+
const ifClause = `
|
|
368
|
+
case ${currentState.step}: {
|
|
369
|
+
${syncCode}
|
|
370
|
+
if (${condition}) {
|
|
371
|
+
context.step = ${currentState.step + 1};
|
|
43
372
|
} else {
|
|
44
|
-
${
|
|
373
|
+
${elseStatement ? `context.step = ${jumpToElseStep};` : `context.step = ${nextStepAfterIf};`}
|
|
45
374
|
}
|
|
46
375
|
break;
|
|
47
376
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
377
|
+
`;
|
|
378
|
+
return {
|
|
379
|
+
clauses: [
|
|
380
|
+
ifClause,
|
|
381
|
+
...thenResult.clauses,
|
|
382
|
+
...elseResult ? elseResult.clauses : [],
|
|
383
|
+
...afterIfResult.clauses
|
|
384
|
+
],
|
|
385
|
+
nextStep: afterIfResult.nextStep
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function processTryStatement(tryStatement, followingStatements, currentState, rewrittenSyncStatements) {
|
|
389
|
+
const { step, persistedVariables } = currentState;
|
|
390
|
+
const tryBlock = tryStatement.getTryBlock();
|
|
391
|
+
const catchClause = tryStatement.getCatchClause();
|
|
392
|
+
const finallyBlock = tryStatement.getFinallyBlock();
|
|
393
|
+
const tryResult = processStatementsRecursive(tryBlock.getStatements(), {
|
|
394
|
+
step: step + 1,
|
|
395
|
+
// El bloque try comienza en el siguiente step
|
|
396
|
+
persistedVariables: new Map(persistedVariables)
|
|
397
|
+
});
|
|
398
|
+
let catchResult;
|
|
399
|
+
let catchVarName;
|
|
400
|
+
const catchStep = tryResult.nextStep;
|
|
401
|
+
if (catchClause) {
|
|
402
|
+
const catchBlock = catchClause.getBlock();
|
|
403
|
+
const varDeclaration = catchClause.getVariableDeclaration();
|
|
404
|
+
if (varDeclaration) {
|
|
405
|
+
catchVarName = varDeclaration.getName();
|
|
406
|
+
}
|
|
407
|
+
catchResult = processStatementsRecursive(catchBlock.getStatements(), {
|
|
408
|
+
step: catchStep,
|
|
409
|
+
persistedVariables: new Map(persistedVariables)
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
let finallyResult;
|
|
413
|
+
const finallyStep = catchResult ? catchResult.nextStep : catchStep;
|
|
414
|
+
if (finallyBlock) {
|
|
415
|
+
finallyResult = processStatementsRecursive(finallyBlock.getStatements(), {
|
|
416
|
+
step: finallyStep,
|
|
417
|
+
persistedVariables: new Map(persistedVariables)
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
const endStep = finallyResult ? finallyResult.nextStep : finallyStep;
|
|
421
|
+
const afterTryResult = processStatementsRecursive(followingStatements, {
|
|
422
|
+
step: endStep,
|
|
423
|
+
persistedVariables
|
|
424
|
+
});
|
|
425
|
+
const handler = `{ catchStep: ${catchClause ? catchStep : "undefined"}, finallyStep: ${finallyBlock ? finallyStep : "undefined"} }`;
|
|
426
|
+
const pushClause = `
|
|
427
|
+
case ${step}: {
|
|
428
|
+
${rewrittenSyncStatements.join("\n")}
|
|
52
429
|
state.tryCatchStack = state.tryCatchStack || [];
|
|
53
|
-
state.tryCatchStack.push(${
|
|
54
|
-
context.step = ${
|
|
430
|
+
state.tryCatchStack.push(${handler});
|
|
431
|
+
context.step = ${step + 1}; // Salta al inicio del bloque try
|
|
55
432
|
break;
|
|
56
433
|
}
|
|
57
|
-
|
|
58
|
-
const
|
|
434
|
+
`;
|
|
435
|
+
const lastTryClause = tryResult.clauses.pop() || "";
|
|
436
|
+
const jumpToAfterTryStep = finallyBlock ? finallyStep : endStep;
|
|
437
|
+
tryResult.clauses.push(
|
|
438
|
+
lastTryClause.replace(
|
|
439
|
+
/return { type: 'COMPLETE'.* };/,
|
|
440
|
+
`context.step = ${jumpToAfterTryStep}; break;`
|
|
441
|
+
)
|
|
442
|
+
);
|
|
443
|
+
if (catchResult) {
|
|
444
|
+
if (catchVarName) {
|
|
445
|
+
const firstCatchClause = catchResult.clauses[0] || `case ${catchStep}: {}`;
|
|
446
|
+
catchResult.clauses[0] = firstCatchClause.replace(
|
|
447
|
+
"{",
|
|
448
|
+
`{
|
|
449
|
+
const ${catchVarName} = result as Error;`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const lastCatchClause = catchResult.clauses.pop() || "";
|
|
453
|
+
catchResult.clauses.push(
|
|
454
|
+
lastCatchClause.replace(
|
|
455
|
+
/return { type: 'COMPLETE'.* };/,
|
|
456
|
+
`context.step = ${jumpToAfterTryStep}; break;`
|
|
457
|
+
)
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
if (finallyResult) {
|
|
461
|
+
const lastFinallyClause = finallyResult.clauses.pop() || "";
|
|
462
|
+
finallyResult.clauses.push(
|
|
463
|
+
lastFinallyClause.replace(
|
|
464
|
+
/return { type: 'COMPLETE'.* };/,
|
|
465
|
+
`state.tryCatchStack?.pop(); context.step = ${endStep}; break;`
|
|
466
|
+
)
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
clauses: [
|
|
471
|
+
pushClause,
|
|
472
|
+
...tryResult.clauses,
|
|
473
|
+
...catchResult ? catchResult.clauses : [],
|
|
474
|
+
...finallyResult ? finallyResult.clauses : [],
|
|
475
|
+
...afterTryResult.clauses
|
|
476
|
+
],
|
|
477
|
+
nextStep: afterTryResult.nextStep
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function processSyncBlock(syncBlock, followingStatements, persistedVariables) {
|
|
481
|
+
const rewrittenSyncStatements = [];
|
|
482
|
+
const newlyPersistedVariables = /* @__PURE__ */ new Map();
|
|
483
|
+
const usedLaterIdentifiers = findUsedIdentifiers(followingStatements);
|
|
484
|
+
const currentPersistedVars = new Map(persistedVariables);
|
|
485
|
+
for (const statement of syncBlock) {
|
|
486
|
+
let handledAsStatePersistence = false;
|
|
487
|
+
if (Node.isVariableStatement(statement)) {
|
|
488
|
+
for (const decl of statement.getDeclarations()) {
|
|
489
|
+
const initializer = decl.getInitializer();
|
|
490
|
+
if (!initializer) continue;
|
|
491
|
+
const varInfos = getVarInfoFromDeclaration(decl);
|
|
492
|
+
const varsToPersist = varInfos.filter(
|
|
493
|
+
(v) => usedLaterIdentifiers.has(v.name)
|
|
494
|
+
);
|
|
495
|
+
if (varsToPersist.length > 0) {
|
|
496
|
+
const rewrittenInitializer = rewriteNode(
|
|
497
|
+
initializer,
|
|
498
|
+
persistedVariables
|
|
499
|
+
);
|
|
500
|
+
for (const { name, type } of varsToPersist) {
|
|
501
|
+
newlyPersistedVariables.set(name, { type });
|
|
502
|
+
currentPersistedVars.set(name, { type });
|
|
503
|
+
const assignmentRightHand = varInfos.length > 1 ? `${rewrittenInitializer}.${name}` : rewrittenInitializer;
|
|
504
|
+
rewrittenSyncStatements.push(
|
|
505
|
+
`state.${name} = ${assignmentRightHand};`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
if (varsToPersist.length === varInfos.length) {
|
|
509
|
+
handledAsStatePersistence = true;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (!handledAsStatePersistence) {
|
|
515
|
+
rewrittenSyncStatements.push(rewriteNode(statement, currentPersistedVars));
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return { rewrittenSyncStatements, newlyPersistedVariables };
|
|
519
|
+
}
|
|
520
|
+
function getVarInfoFromDeclaration(decl) {
|
|
521
|
+
const nameNode = decl.getNameNode();
|
|
522
|
+
const result = [];
|
|
523
|
+
if (Node.isIdentifier(nameNode)) {
|
|
524
|
+
const type = decl.getType().getText(decl, TYPE_FORMAT_FLAGS);
|
|
525
|
+
result.push({ name: nameNode.getText(), type });
|
|
526
|
+
} else if (Node.isObjectBindingPattern(nameNode)) {
|
|
527
|
+
for (const element of nameNode.getElements()) {
|
|
528
|
+
const name = element.getName();
|
|
529
|
+
const type = element.getType().getText(element, TYPE_FORMAT_FLAGS);
|
|
530
|
+
result.push({ name, type });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return result;
|
|
534
|
+
}
|
|
535
|
+
function generateDurableInstruction(statement, persistedVariables) {
|
|
536
|
+
if (Node.isReturnStatement(statement)) {
|
|
537
|
+
const returnExpr = statement.getExpression() ? rewriteNode(statement.getExpressionOrThrow(), persistedVariables) : "undefined";
|
|
538
|
+
return {
|
|
539
|
+
instruction: `return { type: 'COMPLETE', result: ${returnExpr} };`,
|
|
540
|
+
nextPendingStateAssignment: void 0
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
let nextPendingStateAssignment = void 0;
|
|
544
|
+
const varDecl = statement.getFirstDescendantByKind(SyntaxKind.VariableDeclaration);
|
|
545
|
+
if (varDecl) {
|
|
546
|
+
const varName = varDecl.getName();
|
|
547
|
+
const varType = varDecl.getType().getText(varDecl, TYPE_FORMAT_FLAGS);
|
|
548
|
+
persistedVariables.set(varName, { type: varType });
|
|
549
|
+
nextPendingStateAssignment = `state.${varName} = result;`;
|
|
550
|
+
}
|
|
551
|
+
const awaitExpression = statement.getFirstDescendantByKind(SyntaxKind.AwaitExpression);
|
|
552
|
+
if (awaitExpression) {
|
|
553
|
+
const callExpr = awaitExpression.getExpression();
|
|
554
|
+
if (Node.isCallExpression(callExpr)) {
|
|
555
|
+
return {
|
|
556
|
+
instruction: `return ${createInstructionForCall(callExpr, persistedVariables)};`,
|
|
557
|
+
nextPendingStateAssignment
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
instruction: rewriteNode(statement, persistedVariables),
|
|
563
|
+
nextPendingStateAssignment
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function findUsedIdentifiers(nodes) {
|
|
567
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
568
|
+
for (const node of nodes) {
|
|
569
|
+
node.getDescendantsOfKind(SyntaxKind.Identifier).forEach((ident) => {
|
|
570
|
+
identifiers.add(ident.getText());
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return identifiers;
|
|
574
|
+
}
|
|
575
|
+
function rewriteNode(node, persistedVariables) {
|
|
576
|
+
const tempSourceFile = node.getProject().createSourceFile(
|
|
577
|
+
`temp_rewrite_${Math.random()}.ts`,
|
|
578
|
+
`const temp = ${node.getText()};`,
|
|
579
|
+
// Se envuelve para asegurar que sea un AST válido
|
|
580
|
+
{ overwrite: true }
|
|
581
|
+
);
|
|
582
|
+
const nodeClone = tempSourceFile.getVariableDeclarationOrThrow("temp").getInitializerOrThrow();
|
|
583
|
+
const descendants = [nodeClone, ...nodeClone.getDescendants()].reverse();
|
|
584
|
+
for (const currentNode of descendants) {
|
|
585
|
+
if (Node.isIdentifier(currentNode) && !currentNode.wasForgotten() && persistedVariables.has(currentNode.getText())) {
|
|
586
|
+
const varName = currentNode.getText();
|
|
587
|
+
const parent = currentNode.getParent();
|
|
588
|
+
const isDeclaration = Node.isVariableDeclaration(parent) && parent.getNameNode() === currentNode;
|
|
589
|
+
const isProperty = Node.isPropertyAccessExpression(parent) && parent.getNameNode() === currentNode || Node.isPropertyAssignment(parent) && parent.getNameNode() === currentNode;
|
|
590
|
+
const isBindingElement = Node.isBindingElement(parent) && parent.getNameNode() === currentNode;
|
|
591
|
+
if (!isDeclaration && !isProperty && !isBindingElement) {
|
|
592
|
+
const typeInfo = persistedVariables.get(varName);
|
|
593
|
+
currentNode.replaceWithText(`(state.${varName} as ${typeInfo.type})`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const fullText = tempSourceFile.getFullText().trim();
|
|
598
|
+
tempSourceFile.forget();
|
|
599
|
+
const prefix = "const temp = ";
|
|
600
|
+
let newText = fullText;
|
|
601
|
+
if (newText.startsWith(prefix)) {
|
|
602
|
+
newText = newText.substring(prefix.length);
|
|
603
|
+
}
|
|
604
|
+
if (newText.endsWith(";")) {
|
|
605
|
+
newText = newText.slice(0, -1);
|
|
606
|
+
}
|
|
607
|
+
return newText;
|
|
608
|
+
}
|
|
609
|
+
function createInstructionForCall(callExpr, persistedVariables) {
|
|
610
|
+
const identifier = callExpr.getExpression();
|
|
611
|
+
let functionName;
|
|
612
|
+
let isContextCall = false;
|
|
613
|
+
if (Node.isPropertyAccessExpression(identifier)) {
|
|
614
|
+
const expr = identifier.getExpression();
|
|
615
|
+
if (expr.getText() === "context") {
|
|
616
|
+
isContextCall = true;
|
|
617
|
+
}
|
|
618
|
+
functionName = identifier.getName();
|
|
619
|
+
} else {
|
|
620
|
+
functionName = identifier.getText();
|
|
621
|
+
}
|
|
622
|
+
const args = callExpr.getArguments().map((arg) => rewriteNode(arg, persistedVariables)).join(", ");
|
|
623
|
+
if (isContextCall) {
|
|
624
|
+
if (functionName === "bSleep") {
|
|
625
|
+
return `{ type: 'SCHEDULE_SLEEP', duration: ${args} }`;
|
|
626
|
+
}
|
|
627
|
+
if (functionName === "bWaitForEvent") {
|
|
628
|
+
return `{ type: 'WAIT_FOR_EVENT', eventName: ${args} }`;
|
|
629
|
+
}
|
|
630
|
+
if (functionName === "bExecute") {
|
|
631
|
+
const [workflowArg, inputArg] = callExpr.getArguments();
|
|
632
|
+
const subWorkflowName = workflowArg.getText();
|
|
633
|
+
const subWorkflowInput = inputArg ? rewriteNode(inputArg, persistedVariables) : "undefined";
|
|
634
|
+
return `{ type: 'EXECUTE_SUBWORKFLOW', workflowName: ${subWorkflowName}.name, input: ${subWorkflowInput} }`;
|
|
635
|
+
}
|
|
636
|
+
throw new Error(`Funci\xF3n de contexto durable desconocida: '${functionName}'.`);
|
|
637
|
+
}
|
|
638
|
+
const symbol = identifier.getSymbol();
|
|
639
|
+
if (!symbol) throw new Error(`S\xEDmbolo no encontrado para '${functionName}'.`);
|
|
640
|
+
const importSpecifier = symbol.getDeclarations()[0]?.asKind(SyntaxKind.ImportSpecifier);
|
|
641
|
+
if (!importSpecifier) throw new Error(`'${functionName}' debe ser importada.`);
|
|
642
|
+
const sourceFile = importSpecifier.getImportDeclaration().getModuleSpecifierSourceFileOrThrow();
|
|
643
|
+
const relativeSourcePath = path.relative(process.cwd(), sourceFile.getFilePath()).replace(/\\/g, "/");
|
|
644
|
+
return `{ type: 'SCHEDULE_TASK', modulePath: '${relativeSourcePath}', exportName: '${functionName}', args: [${args}] }`;
|
|
645
|
+
}
|
|
646
|
+
function hasDurableCall(node) {
|
|
647
|
+
for (const awaitExpr of node.getDescendantsOfKind(SyntaxKind.AwaitExpression)) {
|
|
648
|
+
const callExpr = awaitExpr.getExpressionIfKind(SyntaxKind.CallExpression);
|
|
649
|
+
if (callExpr) {
|
|
650
|
+
const expression = callExpr.getExpression();
|
|
651
|
+
if (Node.isPropertyAccessExpression(expression)) {
|
|
652
|
+
const propName = expression.getName();
|
|
653
|
+
if (expression.getExpression().getText() === "context" && (propName === "bSleep" || propName === "bWaitForEvent" || propName === "bExecute") || expression.getSymbol()?.getDeclarations()[0]?.isKind(SyntaxKind.ImportSpecifier)) {
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
} else {
|
|
657
|
+
if (expression.getSymbol()?.getDeclarations()[0]?.isKind(SyntaxKind.ImportSpecifier)) {
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (Node.isTryStatement(node)) {
|
|
664
|
+
if (hasDurableCall(node.getTryBlock())) return true;
|
|
665
|
+
if (node.getCatchClause() && hasDurableCall(node.getCatchClause().getBlock()))
|
|
666
|
+
return true;
|
|
667
|
+
if (node.getFinallyBlock() && hasDurableCall(node.getFinallyBlock())) return true;
|
|
668
|
+
}
|
|
669
|
+
if (Node.isIfStatement(node)) {
|
|
670
|
+
const thenHas = hasDurableCall(node.getThenStatement());
|
|
671
|
+
const elseHas = node.getElseStatement() ? hasDurableCall(node.getElseStatement()) : false;
|
|
672
|
+
return thenHas || elseHas;
|
|
673
|
+
}
|
|
674
|
+
if (Node.isBlock(node)) {
|
|
675
|
+
return node.getStatements().some(hasDurableCall);
|
|
676
|
+
}
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
function splitAtNextDurableCall(statements) {
|
|
680
|
+
for (let i = 0; i < statements.length; i++) {
|
|
681
|
+
const statement = statements[i];
|
|
682
|
+
if (Node.isReturnStatement(statement) || hasDurableCall(statement) || Node.isTryStatement(statement)) {
|
|
683
|
+
return {
|
|
684
|
+
syncBlock: statements.slice(0, i),
|
|
685
|
+
durableStatement: statement,
|
|
686
|
+
nextStatements: statements.slice(i + 1)
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return { syncBlock: statements, durableStatement: null, nextStatements: [] };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/compiler/cli.ts
|
|
694
|
+
var getArg = (name) => {
|
|
695
|
+
const argIndex = process.argv.indexOf(name);
|
|
696
|
+
if (argIndex !== -1 && process.argv.length > argIndex + 1) {
|
|
697
|
+
return process.argv[argIndex + 1];
|
|
698
|
+
}
|
|
699
|
+
return void 0;
|
|
700
|
+
};
|
|
701
|
+
async function run() {
|
|
702
|
+
const inputDir = getArg("--in");
|
|
703
|
+
const outputDir = getArg("--out");
|
|
704
|
+
if (!inputDir || !outputDir) {
|
|
705
|
+
console.error(
|
|
706
|
+
"Uso: b-durable-compiler --in <directorio_entrada> --out <directorio_salida>"
|
|
707
|
+
);
|
|
708
|
+
process.exit(1);
|
|
709
|
+
}
|
|
710
|
+
const absInput = path2.resolve(process.cwd(), inputDir);
|
|
711
|
+
const absOutput = path2.resolve(process.cwd(), outputDir);
|
|
712
|
+
await compileWorkflows({
|
|
713
|
+
inputDir: absInput,
|
|
714
|
+
outputDir: absOutput,
|
|
715
|
+
packageName: "@bobtail.software/b-durable"
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
run().catch((error) => {
|
|
719
|
+
console.error("Error durante la compilaci\xF3n:", error);
|
|
720
|
+
process.exit(1);
|
|
721
|
+
});
|