ts2workflows 0.4.0 → 0.6.0
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 +6 -2
- package/dist/ast/expressions.d.ts.map +1 -1
- package/dist/ast/expressions.js +7 -10
- package/dist/ast/stepnames.js +4 -2
- package/dist/ast/steps.d.ts +9 -9
- package/dist/ast/steps.d.ts.map +1 -1
- package/dist/ast/steps.js +33 -20
- package/dist/ast/workflows.js +1 -1
- package/dist/transpiler/expressions.d.ts +2 -0
- package/dist/transpiler/expressions.d.ts.map +1 -1
- package/dist/transpiler/expressions.js +9 -5
- package/dist/transpiler/statements.d.ts +1 -0
- package/dist/transpiler/statements.d.ts.map +1 -1
- package/dist/transpiler/statements.js +237 -45
- package/dist/transpiler/transformations.d.ts.map +1 -1
- package/dist/transpiler/transformations.js +194 -181
- package/dist/utils.d.ts +11 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +23 -0
- package/language_reference.md +47 -10
- package/package.json +2 -2
- package/types/global.d.ts +9 -1
- package/types/workflowslib.d.ts +18 -16
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AssignStepAST, CallStepAST, ForStepAST, RaiseStepAST, ReturnStepAST, SwitchStepAST,
|
|
1
|
+
import { AssignStepAST, CallStepAST, ForStepAST, RaiseStepAST, ReturnStepAST, SwitchStepAST, } from '../ast/steps.js';
|
|
2
2
|
import { InternalTranspilingError } from '../errors.js';
|
|
3
|
-
import { isRecord } from '../utils.js';
|
|
4
|
-
import { BinaryExpression, FunctionInvocationExpression, MemberExpression, PrimitiveExpression, UnaryExpression, VariableReferenceExpression,
|
|
3
|
+
import { isRecord, mapRecordValues } from '../utils.js';
|
|
4
|
+
import { BinaryExpression, FunctionInvocationExpression, MemberExpression, PrimitiveExpression, UnaryExpression, VariableReferenceExpression, isExpression, isLiteral, } from '../ast/expressions.js';
|
|
5
5
|
import { blockingFunctions } from './generated/functionMetadata.js';
|
|
6
6
|
const Unmodified = Symbol();
|
|
7
7
|
/**
|
|
@@ -11,7 +11,7 @@ const Unmodified = Symbol();
|
|
|
11
11
|
* called on each nesting level separately.
|
|
12
12
|
*/
|
|
13
13
|
export function transformAST(steps) {
|
|
14
|
-
return blockingCallsAsCallSteps(runtimeFunctionImplementation(flattenPlainNextConditions(
|
|
14
|
+
return blockingCallsAsCallSteps(runtimeFunctionImplementation(flattenPlainNextConditions(mergeAssignSteps(mapLiteralsAsAssignSteps(steps)))));
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Merge consecutive assign steps into one assign step
|
|
@@ -25,7 +25,7 @@ function mergeAssignSteps(steps) {
|
|
|
25
25
|
prev?.tag === 'assign' &&
|
|
26
26
|
prev.assignments.length < 50 &&
|
|
27
27
|
!prev.next) {
|
|
28
|
-
const merged = new AssignStepAST(prev.assignments.concat(current.assignments), current.next);
|
|
28
|
+
const merged = new AssignStepAST(prev.assignments.concat(current.assignments), current.next, prev.label ?? current.label);
|
|
29
29
|
acc.pop();
|
|
30
30
|
acc.push(merged);
|
|
31
31
|
}
|
|
@@ -35,95 +35,6 @@ function mergeAssignSteps(steps) {
|
|
|
35
35
|
return acc;
|
|
36
36
|
}, []);
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Transform a retry_policy call step to a retry block in a preceeding try step
|
|
40
|
-
*/
|
|
41
|
-
function combineRetryBlocksToTry(steps) {
|
|
42
|
-
return steps.reduce((acc, current) => {
|
|
43
|
-
const prev = acc.length > 0 ? acc[acc.length - 1] : null;
|
|
44
|
-
if (current.tag === 'call' && current.call === 'retry_policy') {
|
|
45
|
-
if (prev?.tag === 'try') {
|
|
46
|
-
if (prev.retryPolicy) {
|
|
47
|
-
throw new InternalTranspilingError('Retry policy already assigned!');
|
|
48
|
-
}
|
|
49
|
-
let retryPolicy = undefined;
|
|
50
|
-
const retryParameters = current.args;
|
|
51
|
-
if (retryParameters) {
|
|
52
|
-
const retryPolicyEx = retryParameters.policy;
|
|
53
|
-
if (retryPolicyEx && isFullyQualifiedName(retryPolicyEx)) {
|
|
54
|
-
retryPolicy = retryPolicyEx.toString();
|
|
55
|
-
}
|
|
56
|
-
if (!retryPolicy) {
|
|
57
|
-
let predicate = '';
|
|
58
|
-
const predicateEx = retryParameters.predicate;
|
|
59
|
-
if (predicateEx) {
|
|
60
|
-
if (isFullyQualifiedName(predicateEx)) {
|
|
61
|
-
predicate = predicateEx.toString();
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
throw new InternalTranspilingError('"predicate" must be a function name');
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const maxRetries = parseRetryPolicyNumber(retryParameters, 'max_retries');
|
|
68
|
-
let initialDelay = 1;
|
|
69
|
-
let maxDelay = 1;
|
|
70
|
-
let multiplier = 1;
|
|
71
|
-
const backoffEx = retryParameters.backoff;
|
|
72
|
-
if (backoffEx &&
|
|
73
|
-
isLiteral(backoffEx) &&
|
|
74
|
-
backoffEx.expressionType === 'primitive') {
|
|
75
|
-
const backoffLit = backoffEx.value;
|
|
76
|
-
if (isRecord(backoffLit)) {
|
|
77
|
-
initialDelay = parseRetryPolicyNumber(backoffLit, 'initial_delay');
|
|
78
|
-
maxDelay = parseRetryPolicyNumber(backoffLit, 'max_delay');
|
|
79
|
-
multiplier = parseRetryPolicyNumber(backoffLit, 'multiplier');
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
retryPolicy = {
|
|
83
|
-
predicate,
|
|
84
|
-
maxRetries,
|
|
85
|
-
backoff: {
|
|
86
|
-
initialDelay,
|
|
87
|
-
maxDelay,
|
|
88
|
-
multiplier,
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const tryWithRetry = new TryStepAST(prev.trySteps, prev.exceptSteps, retryPolicy, prev.errorMap);
|
|
94
|
-
acc.pop();
|
|
95
|
-
acc.push(tryWithRetry);
|
|
96
|
-
}
|
|
97
|
-
// If prev is not a try step, "retry_policy" is ignored. Should print a warning.
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
acc.push(current);
|
|
101
|
-
}
|
|
102
|
-
return acc;
|
|
103
|
-
}, []);
|
|
104
|
-
}
|
|
105
|
-
function parseRetryPolicyNumber(record, keyName) {
|
|
106
|
-
let primitiveValue;
|
|
107
|
-
const primitiveOrExpression = record[keyName];
|
|
108
|
-
if (primitiveOrExpression && isExpression(primitiveOrExpression)) {
|
|
109
|
-
if (isLiteral(primitiveOrExpression)) {
|
|
110
|
-
primitiveValue = expressionToLiteralValueOrLiteralExpression(primitiveOrExpression);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
throw new InternalTranspilingError(`Support for non-literal "${keyName}" values not yet implemented`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
else if (primitiveOrExpression) {
|
|
117
|
-
primitiveValue = primitiveOrExpression;
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
throw new InternalTranspilingError(`"${keyName}" expected`);
|
|
121
|
-
}
|
|
122
|
-
if (typeof primitiveValue !== 'number') {
|
|
123
|
-
throw new InternalTranspilingError(`"${keyName}" must be a number`);
|
|
124
|
-
}
|
|
125
|
-
return primitiveValue;
|
|
126
|
-
}
|
|
127
38
|
/**
|
|
128
39
|
* Merge a next step to the previous step.
|
|
129
40
|
*
|
|
@@ -301,11 +212,11 @@ function transformExpressionsAssign(step, transform) {
|
|
|
301
212
|
function transformExpressionsCall(step, transform) {
|
|
302
213
|
if (step.args) {
|
|
303
214
|
const newSteps = [];
|
|
304
|
-
const newArgs =
|
|
215
|
+
const newArgs = mapRecordValues(step.args, (ex) => {
|
|
305
216
|
const [steps2, ex2] = transform(ex);
|
|
306
217
|
newSteps.push(...steps2);
|
|
307
|
-
return
|
|
308
|
-
})
|
|
218
|
+
return ex2;
|
|
219
|
+
});
|
|
309
220
|
newSteps.push(new CallStepAST(step.call, newArgs, step.result, step.label));
|
|
310
221
|
return newSteps;
|
|
311
222
|
}
|
|
@@ -357,8 +268,8 @@ function transformExpressionsSwitch(step, transform) {
|
|
|
357
268
|
newSteps.push(new SwitchStepAST(newBranches, step.label));
|
|
358
269
|
return newSteps;
|
|
359
270
|
}
|
|
360
|
-
function transformExpression(ex,
|
|
361
|
-
const transformed =
|
|
271
|
+
function transformExpression(ex, transform) {
|
|
272
|
+
const transformed = transform(ex);
|
|
362
273
|
if (transformed !== Unmodified) {
|
|
363
274
|
// Use the transformed version of this term
|
|
364
275
|
return transformed;
|
|
@@ -367,28 +278,76 @@ function transformExpression(ex, transformer) {
|
|
|
367
278
|
// Otherwise, recurse into the nested expression
|
|
368
279
|
switch (ex.expressionType) {
|
|
369
280
|
case 'primitive':
|
|
370
|
-
|
|
371
|
-
|
|
281
|
+
if (isLiteral(ex)) {
|
|
282
|
+
return ex;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const newPrimitive = transformPrimitive(ex.value, transform);
|
|
286
|
+
return newPrimitive === ex.value
|
|
287
|
+
? ex
|
|
288
|
+
: new PrimitiveExpression(newPrimitive);
|
|
289
|
+
}
|
|
372
290
|
case 'binary':
|
|
373
|
-
return transformBinaryExpression(ex,
|
|
291
|
+
return transformBinaryExpression(ex, transform);
|
|
374
292
|
case 'functionInvocation':
|
|
375
|
-
return transformFunctionInvocationExpression(ex,
|
|
293
|
+
return transformFunctionInvocationExpression(ex, transform);
|
|
376
294
|
case 'member':
|
|
377
|
-
return
|
|
295
|
+
return transformMemberExpression(ex, transform);
|
|
378
296
|
case 'unary':
|
|
379
|
-
return
|
|
297
|
+
return transformUnaryExpression(ex, transform);
|
|
298
|
+
case 'variableReference':
|
|
299
|
+
return ex;
|
|
380
300
|
}
|
|
381
301
|
}
|
|
382
302
|
}
|
|
383
|
-
function
|
|
303
|
+
function transformPrimitive(val, transform) {
|
|
304
|
+
if (Array.isArray(val)) {
|
|
305
|
+
return val.map((x) => isExpression(x)
|
|
306
|
+
? transformExpression(x, transform)
|
|
307
|
+
: transformPrimitive(x, transform));
|
|
308
|
+
}
|
|
309
|
+
else if (isRecord(val)) {
|
|
310
|
+
return mapRecordValues(val, (x) => isExpression(x)
|
|
311
|
+
? transformExpression(x, transform)
|
|
312
|
+
: transformPrimitive(x, transform));
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
return val;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function transformBinaryExpression(ex, transform) {
|
|
384
319
|
// Transform left first to keep the correct order of execution of sub-expressions
|
|
385
|
-
const newLeft = transformExpression(ex.left,
|
|
386
|
-
const newRight = transformExpression(ex.right,
|
|
387
|
-
|
|
320
|
+
const newLeft = transformExpression(ex.left, transform);
|
|
321
|
+
const newRight = transformExpression(ex.right, transform);
|
|
322
|
+
if (newLeft === ex.left && newRight === ex.right) {
|
|
323
|
+
return ex;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
return new BinaryExpression(newLeft, ex.binaryOperator, newRight);
|
|
327
|
+
}
|
|
388
328
|
}
|
|
389
|
-
function transformFunctionInvocationExpression(ex,
|
|
390
|
-
const newArguments = ex.arguments.map((x) => transformExpression(x,
|
|
391
|
-
|
|
329
|
+
function transformFunctionInvocationExpression(ex, transform) {
|
|
330
|
+
const newArguments = ex.arguments.map((x) => transformExpression(x, transform));
|
|
331
|
+
if (newArguments.every((x, i) => x === ex.arguments[i])) {
|
|
332
|
+
return ex;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
return new FunctionInvocationExpression(ex.functionName, newArguments);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function transformMemberExpression(ex, transform) {
|
|
339
|
+
const newObject = transformExpression(ex.object, transform);
|
|
340
|
+
const newProperty = transformExpression(ex.property, transform);
|
|
341
|
+
if (newObject === ex.object && newProperty === ex.property) {
|
|
342
|
+
return ex;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
return new MemberExpression(newObject, newProperty, ex.computed);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function transformUnaryExpression(ex, transform) {
|
|
349
|
+
const newValue = transformExpression(ex.value, transform);
|
|
350
|
+
return newValue === ex.value ? ex : new UnaryExpression(ex.operator, newValue);
|
|
392
351
|
}
|
|
393
352
|
/**
|
|
394
353
|
* Search for map literals in expressions and replace them with assign step + variable.
|
|
@@ -410,89 +369,143 @@ function transformFunctionInvocationExpression(ex, transformer) {
|
|
|
410
369
|
* return: ${__temp0.value}
|
|
411
370
|
*/
|
|
412
371
|
function mapLiteralsAsAssignSteps(steps) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
// These steps are allowed to contain map literals if the map is the
|
|
421
|
-
// main expression
|
|
422
|
-
if (current.tag === 'assign') {
|
|
423
|
-
// This does the transformation a bit too eagerly: If any of the
|
|
424
|
-
// assignments need the map literal extraction, it is done on all of
|
|
425
|
-
// the variables.
|
|
426
|
-
needsTransformation = !current.assignments.every(([, value]) => {
|
|
427
|
-
return value.expressionType === 'primitive';
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
else if (current.tag === 'raise' || current.tag === 'return') {
|
|
431
|
-
needsTransformation =
|
|
432
|
-
current.value !== undefined &&
|
|
433
|
-
includesExtractableMapLiteral(current.value, true);
|
|
434
|
-
}
|
|
435
|
-
else if (current.tag === 'call') {
|
|
436
|
-
if (current.args) {
|
|
437
|
-
needsTransformation = Object.values(current.args).some((ex) => includesExtractableMapLiteral(ex, true));
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
if (needsTransformation) {
|
|
441
|
-
const transformedSteps = transformStepExpressions(current, transformer);
|
|
442
|
-
acc.push(...transformedSteps);
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
acc.push(current);
|
|
446
|
-
}
|
|
447
|
-
return acc;
|
|
448
|
-
}, []);
|
|
372
|
+
return steps.flatMap((step) => transformStepExpressions(step, transformNestedMaps));
|
|
373
|
+
}
|
|
374
|
+
function transformNestedMaps(ex) {
|
|
375
|
+
const generateTemporaryVariableName = createTempVariableGenerator();
|
|
376
|
+
const { transformedExpression, tempVariables } = extractNestedMaps(ex, generateTemporaryVariableName, 0);
|
|
377
|
+
const assignments = tempVariables.length > 0 ? [new AssignStepAST(tempVariables)] : [];
|
|
378
|
+
return [assignments, transformedExpression];
|
|
449
379
|
}
|
|
450
|
-
|
|
451
|
-
function includesExtractableMapLiteral(ex, parentAllowsMaps) {
|
|
380
|
+
function extractNestedMaps(ex, generateName, nestingLevel) {
|
|
452
381
|
switch (ex.expressionType) {
|
|
453
382
|
case 'primitive':
|
|
454
|
-
|
|
455
|
-
return (!parentAllowsMaps ||
|
|
456
|
-
Object.values(ex.value).some((x) => isExpression(x) &&
|
|
457
|
-
includesExtractableMapLiteral(x, parentAllowsMaps)));
|
|
458
|
-
}
|
|
459
|
-
else if (Array.isArray(ex.value)) {
|
|
460
|
-
return ex.value.some((x) => isExpression(x) &&
|
|
461
|
-
includesExtractableMapLiteral(x, parentAllowsMaps));
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
return false;
|
|
465
|
-
}
|
|
383
|
+
return extractNestedMapPrimitive(ex.value, generateName, nestingLevel);
|
|
466
384
|
case 'binary':
|
|
467
|
-
return (
|
|
468
|
-
includesExtractableMapLiteral(ex.right, parentAllowsMaps));
|
|
385
|
+
return extractNestedMapBinary(ex, generateName, nestingLevel);
|
|
469
386
|
case 'variableReference':
|
|
470
|
-
return
|
|
471
|
-
case 'unary':
|
|
472
|
-
return includesExtractableMapLiteral(ex.value, parentAllowsMaps);
|
|
387
|
+
return { transformedExpression: ex, tempVariables: [] };
|
|
473
388
|
case 'functionInvocation':
|
|
474
|
-
return ex
|
|
389
|
+
return extractNestedMapFunctionInvocation(ex, generateName, nestingLevel);
|
|
475
390
|
case 'member':
|
|
476
|
-
return (
|
|
477
|
-
|
|
391
|
+
return extractNestedMapMember(ex, generateName, nestingLevel);
|
|
392
|
+
case 'unary':
|
|
393
|
+
return extractNestedMapUnary(ex, generateName, nestingLevel);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function extractNestedMapPrimitive(primitiveEx, generateName, nestingLevel) {
|
|
397
|
+
const { transformed, tempVariables } = extractNestedMapPrimitiveRecursive(primitiveEx, generateName, nestingLevel);
|
|
398
|
+
const ex = isExpression(transformed)
|
|
399
|
+
? transformed
|
|
400
|
+
: new PrimitiveExpression(transformed);
|
|
401
|
+
return {
|
|
402
|
+
transformedExpression: ex,
|
|
403
|
+
tempVariables,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function extractNestedMapPrimitiveRecursive(primitiveEx, generateName, nestingLevel) {
|
|
407
|
+
if (typeof primitiveEx === 'string' ||
|
|
408
|
+
typeof primitiveEx === 'number' ||
|
|
409
|
+
typeof primitiveEx === 'boolean' ||
|
|
410
|
+
primitiveEx === null) {
|
|
411
|
+
return {
|
|
412
|
+
transformed: primitiveEx,
|
|
413
|
+
tempVariables: [],
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
else if (Array.isArray(primitiveEx)) {
|
|
417
|
+
return extractMapsInList(primitiveEx, generateName, nestingLevel);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
return extractMapsInMap(primitiveEx, generateName, nestingLevel);
|
|
478
421
|
}
|
|
479
422
|
}
|
|
480
|
-
function
|
|
481
|
-
|
|
482
|
-
if (
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
return new VariableReferenceExpression(tempVariable);
|
|
423
|
+
function extractMapsInList(primitiveEx, generateName, nestingLevel) {
|
|
424
|
+
const { tempVariables, elements } = primitiveEx.reduce((acc, val) => {
|
|
425
|
+
if (isExpression(val)) {
|
|
426
|
+
const { transformedExpression, tempVariables: temps } = extractNestedMaps(val, generateName, nestingLevel);
|
|
427
|
+
acc.tempVariables.push(...temps);
|
|
428
|
+
acc.elements.push(transformedExpression);
|
|
487
429
|
}
|
|
488
430
|
else {
|
|
489
|
-
|
|
431
|
+
const { transformed, tempVariables: temps } = extractNestedMapPrimitiveRecursive(val, generateName, nestingLevel);
|
|
432
|
+
acc.tempVariables.push(...temps);
|
|
433
|
+
acc.elements.push(transformed);
|
|
434
|
+
}
|
|
435
|
+
return acc;
|
|
436
|
+
}, {
|
|
437
|
+
tempVariables: [],
|
|
438
|
+
elements: [],
|
|
439
|
+
});
|
|
440
|
+
return {
|
|
441
|
+
tempVariables,
|
|
442
|
+
transformed: elements,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function extractMapsInMap(primitiveEx, generateName, nestingLevel) {
|
|
446
|
+
const { tempVariables, properties } = Object.entries(primitiveEx).reduce((acc, [key, val]) => {
|
|
447
|
+
if (isExpression(val)) {
|
|
448
|
+
const { transformedExpression, tempVariables: temps } = extractNestedMaps(val, generateName, 0);
|
|
449
|
+
acc.tempVariables.push(...temps);
|
|
450
|
+
acc.properties[key] = transformedExpression;
|
|
490
451
|
}
|
|
452
|
+
else {
|
|
453
|
+
const { transformed, tempVariables: temps } = extractNestedMapPrimitiveRecursive(val, generateName, 0);
|
|
454
|
+
acc.tempVariables.push(...temps);
|
|
455
|
+
acc.properties[key] = transformed;
|
|
456
|
+
}
|
|
457
|
+
return acc;
|
|
458
|
+
}, {
|
|
459
|
+
tempVariables: [],
|
|
460
|
+
properties: {},
|
|
461
|
+
});
|
|
462
|
+
let newValue;
|
|
463
|
+
if (nestingLevel === 0) {
|
|
464
|
+
newValue = properties;
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
const name = generateName();
|
|
468
|
+
tempVariables.push([name, new PrimitiveExpression(properties)]);
|
|
469
|
+
newValue = new VariableReferenceExpression(name);
|
|
491
470
|
}
|
|
492
|
-
const assignSteps = [];
|
|
493
471
|
return {
|
|
494
|
-
|
|
495
|
-
|
|
472
|
+
transformed: newValue,
|
|
473
|
+
tempVariables,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function extractNestedMapFunctionInvocation(ex, generateName, nestingLevel) {
|
|
477
|
+
const { expressions, temps } = ex.arguments.reduce((acc, arg) => {
|
|
478
|
+
const { transformedExpression, tempVariables } = extractNestedMaps(arg, generateName, nestingLevel + 1);
|
|
479
|
+
acc.expressions.push(transformedExpression);
|
|
480
|
+
acc.temps.push(...tempVariables);
|
|
481
|
+
return acc;
|
|
482
|
+
}, { expressions: [], temps: [] });
|
|
483
|
+
return {
|
|
484
|
+
transformedExpression: new FunctionInvocationExpression(ex.functionName, expressions),
|
|
485
|
+
tempVariables: temps,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function extractNestedMapBinary(ex, generateName, nestingLevel) {
|
|
489
|
+
const left = extractNestedMaps(ex.left, generateName, nestingLevel + 1);
|
|
490
|
+
const right = extractNestedMaps(ex.right, generateName, nestingLevel + 1);
|
|
491
|
+
return {
|
|
492
|
+
transformedExpression: new BinaryExpression(left.transformedExpression, ex.binaryOperator, right.transformedExpression),
|
|
493
|
+
tempVariables: left.tempVariables.concat(right.tempVariables),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function extractNestedMapMember(ex, generateName, nestingLevel) {
|
|
497
|
+
const obj = extractNestedMaps(ex.object, generateName, nestingLevel + 1);
|
|
498
|
+
const pr = extractNestedMaps(ex.property, generateName, nestingLevel + 1);
|
|
499
|
+
return {
|
|
500
|
+
transformedExpression: new MemberExpression(obj.transformedExpression, pr.transformedExpression, ex.computed),
|
|
501
|
+
tempVariables: obj.tempVariables.concat(pr.tempVariables),
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function extractNestedMapUnary(ex, generateName, nestingLevel) {
|
|
505
|
+
const { transformedExpression, tempVariables } = extractNestedMaps(ex.value, generateName, nestingLevel);
|
|
506
|
+
return {
|
|
507
|
+
transformedExpression: new UnaryExpression(ex.operator, transformedExpression),
|
|
508
|
+
tempVariables,
|
|
496
509
|
};
|
|
497
510
|
}
|
|
498
511
|
/**
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
export declare function isRecord(object: unknown): object is Record<keyof never, unknown>;
|
|
2
|
+
/**
|
|
3
|
+
* Apply f to values of obj and return the result
|
|
4
|
+
*/
|
|
5
|
+
export declare function mapRecordValues<T, U>(obj: Record<string, T>, f: (t: T) => U): Record<string, U>;
|
|
6
|
+
/**
|
|
7
|
+
* Like arr.flatMap() but the callback takes two consecutive array elements.
|
|
8
|
+
*
|
|
9
|
+
* During the last execution of the callback, the second argument (which would
|
|
10
|
+
* be element after the last array element) will be undefined.
|
|
11
|
+
*/
|
|
12
|
+
export declare function flatMapPair<T, U>(arr: T[], callback: (val: T, next: T | undefined) => U[]): U[];
|
|
2
13
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,QAAQ,CACtB,MAAM,EAAE,OAAO,GACd,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE,OAAO,CAAC,CAExC"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,QAAQ,CACtB,MAAM,EAAE,OAAO,GACd,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE,OAAO,CAAC,CAExC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,EAClC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EACtB,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GACb,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAEnB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAC9B,GAAG,EAAE,CAAC,EAAE,EACR,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,EAAE,GAC7C,CAAC,EAAE,CAaL"}
|
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
1
|
export function isRecord(object) {
|
|
2
2
|
return object instanceof Object && object.constructor === Object;
|
|
3
3
|
}
|
|
4
|
+
/**
|
|
5
|
+
* Apply f to values of obj and return the result
|
|
6
|
+
*/
|
|
7
|
+
export function mapRecordValues(obj, f) {
|
|
8
|
+
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, f(v)]));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Like arr.flatMap() but the callback takes two consecutive array elements.
|
|
12
|
+
*
|
|
13
|
+
* During the last execution of the callback, the second argument (which would
|
|
14
|
+
* be element after the last array element) will be undefined.
|
|
15
|
+
*/
|
|
16
|
+
export function flatMapPair(arr, callback) {
|
|
17
|
+
if (arr.length <= 0) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const mapped = [];
|
|
21
|
+
for (let i = 0; i < arr.length - 1; i++) {
|
|
22
|
+
mapped.push(...callback(arr[i], arr[i + 1]));
|
|
23
|
+
}
|
|
24
|
+
mapped.push(...callback(arr[arr.length - 1], undefined));
|
|
25
|
+
return mapped;
|
|
26
|
+
}
|
package/language_reference.md
CHANGED
|
@@ -561,7 +561,7 @@ parallel(
|
|
|
561
561
|
)
|
|
562
562
|
```
|
|
563
563
|
|
|
564
|
-
## Try/catch statements
|
|
564
|
+
## Try/catch/finally statements
|
|
565
565
|
|
|
566
566
|
The statement
|
|
567
567
|
|
|
@@ -592,13 +592,48 @@ is compiled to the following [try/except structure](https://cloud.google.com/wor
|
|
|
592
592
|
|
|
593
593
|
The error variable and other variables created inside the catch block are accessible only in that block's scope (similar to [the variable scoping in Workflows](https://cloud.google.com/workflows/docs/reference/syntax/catching-errors#variable-scope)).
|
|
594
594
|
|
|
595
|
+
Finally block is also supported:
|
|
596
|
+
|
|
597
|
+
```javascript
|
|
598
|
+
try {
|
|
599
|
+
return readFile()
|
|
600
|
+
} catch (err) {
|
|
601
|
+
return 'Error!'
|
|
602
|
+
} finally {
|
|
603
|
+
closeFile()
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
If an exception gets thrown inside a try block, the stack trace in Workflows logs will misleadingly show the exception originating from inside the finally block. This happens because the implementation of the finally block catches the original exception and later throws an identical exception. The original source location of the exception is lost.
|
|
608
|
+
|
|
609
|
+
⚠️ At the moment, break and continue are not supported in a try or a catch block if there is a related finally block.
|
|
610
|
+
|
|
595
611
|
## Retrying on errors
|
|
596
612
|
|
|
597
613
|
It is possible to set a retry policy for a try-catch statement. Because Typescript does not have `retry` keyword, the retry is implemented by a special `retry_policy` function. It must be called immediately after a try-catch block. A call to the `retry_policy` is ignored elsewhere.
|
|
598
614
|
|
|
599
|
-
|
|
615
|
+
Finally and catch blocks are run after possible retry attempts. The following sample retries `http.get()` if it throws an exception and executes `sys.log('Error!')` and `closeConnection()` after retry attempts.
|
|
600
616
|
|
|
601
|
-
|
|
617
|
+
```javascript
|
|
618
|
+
import { http, retry_policy, sys } from 'ts2workflows/types/workflowslib'
|
|
619
|
+
|
|
620
|
+
function main() {
|
|
621
|
+
try {
|
|
622
|
+
http.get('https://visit.dreamland.test/')
|
|
623
|
+
} catch (err) {
|
|
624
|
+
sys.log('Error!')
|
|
625
|
+
} finally {
|
|
626
|
+
closeConnection()
|
|
627
|
+
}
|
|
628
|
+
retry_policy(http.default_retry)
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
The `retry_policy` function must be called with a parameter that defines the retry policy. It can be either a policy provided by GCP Workflows or a custom retry policy.
|
|
633
|
+
|
|
634
|
+
### GCP-provided retry policy
|
|
635
|
+
|
|
636
|
+
GCP retry policy must be either `http.default_retry` or `http.default_retry_non_idempotent`. Their effects are described by the [GCP documentation](https://cloud.google.com/workflows/docs/reference/syntax/retrying#default-retry-policy).
|
|
602
637
|
|
|
603
638
|
```javascript
|
|
604
639
|
import { http, retry_policy } from 'ts2workflows/types/workflowslib'
|
|
@@ -609,11 +644,15 @@ function main() {
|
|
|
609
644
|
} catch (err) {
|
|
610
645
|
return 'Error!'
|
|
611
646
|
}
|
|
612
|
-
retry_policy(
|
|
647
|
+
retry_policy(http.default_retry)
|
|
613
648
|
}
|
|
614
649
|
```
|
|
615
650
|
|
|
616
|
-
|
|
651
|
+
### Custom retry policy
|
|
652
|
+
|
|
653
|
+
A custom retry policy is an object with the properties shown in the following example. See the GCP documentation for the [explanation of the properties](https://cloud.google.com/workflows/docs/reference/syntax/retrying#try-retry).
|
|
654
|
+
|
|
655
|
+
The parameter must be a literal map object (not a variable). The values may be literals or expressions.
|
|
617
656
|
|
|
618
657
|
```javascript
|
|
619
658
|
import { http, retry_policy } from 'ts2workflows/types/workflowslib'
|
|
@@ -766,11 +805,9 @@ The `parallel` function executes code blocks in parallel (using [parallel step](
|
|
|
766
805
|
```typescript
|
|
767
806
|
function retry_policy(
|
|
768
807
|
params:
|
|
808
|
+
| ((errormap: Record<string, any>) => void)
|
|
769
809
|
| {
|
|
770
|
-
|
|
771
|
-
}
|
|
772
|
-
| {
|
|
773
|
-
predicate: (exception: unknown) => boolean
|
|
810
|
+
predicate: (errormap: Record<string, any>) => boolean
|
|
774
811
|
max_retries: number
|
|
775
812
|
backoff: {
|
|
776
813
|
initial_delay: number
|
|
@@ -781,7 +818,7 @@ function retry_policy(
|
|
|
781
818
|
): void
|
|
782
819
|
```
|
|
783
820
|
|
|
784
|
-
The `retry_policy` function can called right after a `try`-`catch` block to specify a retry policy. See the section on retrying.
|
|
821
|
+
The `retry_policy` function can called right after a `try`-`catch` block to specify a retry policy. See the section on [retrying errors](#retrying-on-errors).
|
|
785
822
|
|
|
786
823
|
## Source code comments
|
|
787
824
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts2workflows",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Transpile Typescript code to GCP Workflows programs",
|
|
5
5
|
"homepage": "https://github.com/aajanki/ts2workflows",
|
|
6
6
|
"repository": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
],
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@eslint/js": "^9.10.0",
|
|
60
|
-
"@types/chai": "^
|
|
60
|
+
"@types/chai": "^5.0.1",
|
|
61
61
|
"@types/mocha": "^10.0.6",
|
|
62
62
|
"@types/node": "^18",
|
|
63
63
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
package/types/global.d.ts
CHANGED
|
@@ -10,7 +10,15 @@ declare global {
|
|
|
10
10
|
|
|
11
11
|
var Symbol: SymbolConstructor
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Iterator types copied from lib.es2015.iterable.d.ts
|
|
15
|
+
*/
|
|
16
|
+
interface Iterator<T, TReturn = any, TNext = undefined> {
|
|
17
|
+
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
|
|
18
|
+
next(...args: [] | [TNext]): IteratorResult<T, TReturn>
|
|
19
|
+
return?(value?: TReturn): IteratorResult<T, TReturn>
|
|
20
|
+
throw?(e?: any): IteratorResult<T, TReturn>
|
|
21
|
+
}
|
|
14
22
|
|
|
15
23
|
interface Iterable<T> {
|
|
16
24
|
[Symbol.iterator](): Iterator<T>
|