ts2workflows 0.3.0 → 0.5.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.
@@ -1,6 +1,6 @@
1
1
  import { AssignStepAST, CallStepAST, ForStepAST, RaiseStepAST, ReturnStepAST, SwitchStepAST, TryStepAST, } from '../ast/steps.js';
2
2
  import { InternalTranspilingError } from '../errors.js';
3
- import { isRecord } from '../utils.js';
3
+ import { isRecord, mapRecordValues } from '../utils.js';
4
4
  import { BinaryExpression, FunctionInvocationExpression, MemberExpression, PrimitiveExpression, UnaryExpression, VariableReferenceExpression, expressionToLiteralValueOrLiteralExpression, isExpression, isFullyQualifiedName, isLiteral, } from '../ast/expressions.js';
5
5
  import { blockingFunctions } from './generated/functionMetadata.js';
6
6
  const Unmodified = Symbol();
@@ -23,8 +23,9 @@ function mergeAssignSteps(steps) {
23
23
  const prev = acc.length > 0 ? acc[acc.length - 1] : null;
24
24
  if (current.tag === 'assign' &&
25
25
  prev?.tag === 'assign' &&
26
- prev.assignments.length < 50) {
27
- const merged = new AssignStepAST(prev.assignments.concat(current.assignments));
26
+ prev.assignments.length < 50 &&
27
+ !prev.next) {
28
+ const merged = new AssignStepAST(prev.assignments.concat(current.assignments), current.next, prev.label ?? current.label);
28
29
  acc.pop();
29
30
  acc.push(merged);
30
31
  }
@@ -142,26 +143,29 @@ function parseRetryPolicyNumber(record, keyName) {
142
143
  * next: target1
143
144
  */
144
145
  export function flattenPlainNextConditions(steps) {
145
- /*
146
- const res = steps.reduce((acc: WorkflowStepAST[], step: WorkflowStepAST) => {
147
- if (acc.length > 0) {
148
- if (step.tag === 'next') {
149
- const prev = acc[-1]
150
-
151
- if (prev.tag === 'assign') {
152
-
153
- }
154
-
146
+ return steps.reduce((acc, step) => {
147
+ if (acc.length > 0 && step.tag === 'next') {
148
+ // Merge a "next" to the previous "assign" step
149
+ const prev = acc[acc.length - 1];
150
+ if (prev.tag === 'assign' && !prev.next) {
151
+ acc.pop();
152
+ acc.push(prev.withNext(step.target));
153
+ }
154
+ else {
155
+ acc.push(step);
156
+ }
157
+ }
158
+ else if (step.tag === 'switch') {
159
+ // If the condition steps consists of a single "next", merge it with the condition
160
+ acc.push(flattenNextToCondition(step));
161
+ }
162
+ else {
163
+ acc.push(step);
155
164
  }
156
- }
157
-
158
- return acc
159
- }, [])
160
-
161
- */
162
- return steps.map((step) => (step.tag === 'switch' ? flattenNext(step) : step));
163
- }
164
- function flattenNext(step) {
165
+ return acc;
166
+ }, []);
167
+ }
168
+ function flattenNextToCondition(step) {
165
169
  const transformedBranches = step.branches.map((cond) => {
166
170
  if (!cond.next && cond.steps.length === 1 && cond.steps[0].tag === 'next') {
167
171
  const nextStep = cond.steps[0];
@@ -287,7 +291,7 @@ function transformExpressionsAssign(step, transform) {
287
291
  newSteps.push(...steps2);
288
292
  return [name, ex2];
289
293
  });
290
- newSteps.push(new AssignStepAST(newAssignments, step.label));
294
+ newSteps.push(new AssignStepAST(newAssignments, step.next, step.label));
291
295
  return newSteps;
292
296
  }
293
297
  else {
@@ -297,11 +301,11 @@ function transformExpressionsAssign(step, transform) {
297
301
  function transformExpressionsCall(step, transform) {
298
302
  if (step.args) {
299
303
  const newSteps = [];
300
- const newArgs = Object.fromEntries(Object.entries(step.args).map(([name, ex]) => {
304
+ const newArgs = mapRecordValues(step.args, (ex) => {
301
305
  const [steps2, ex2] = transform(ex);
302
306
  newSteps.push(...steps2);
303
- return [name, ex2];
304
- }));
307
+ return ex2;
308
+ });
305
309
  newSteps.push(new CallStepAST(step.call, newArgs, step.result, step.label));
306
310
  return newSteps;
307
311
  }
@@ -353,8 +357,8 @@ function transformExpressionsSwitch(step, transform) {
353
357
  newSteps.push(new SwitchStepAST(newBranches, step.label));
354
358
  return newSteps;
355
359
  }
356
- function transformExpression(ex, transformer) {
357
- const transformed = transformer(ex);
360
+ function transformExpression(ex, transform) {
361
+ const transformed = transform(ex);
358
362
  if (transformed !== Unmodified) {
359
363
  // Use the transformed version of this term
360
364
  return transformed;
@@ -363,28 +367,76 @@ function transformExpression(ex, transformer) {
363
367
  // Otherwise, recurse into the nested expression
364
368
  switch (ex.expressionType) {
365
369
  case 'primitive':
366
- case 'variableReference':
367
- return ex;
370
+ if (isLiteral(ex)) {
371
+ return ex;
372
+ }
373
+ else {
374
+ const newPrimitive = transformPrimitive(ex.value, transform);
375
+ return newPrimitive === ex.value
376
+ ? ex
377
+ : new PrimitiveExpression(newPrimitive);
378
+ }
368
379
  case 'binary':
369
- return transformBinaryExpression(ex, transformer);
380
+ return transformBinaryExpression(ex, transform);
370
381
  case 'functionInvocation':
371
- return transformFunctionInvocationExpression(ex, transformer);
382
+ return transformFunctionInvocationExpression(ex, transform);
372
383
  case 'member':
373
- return new MemberExpression(transformExpression(ex.object, transformer), transformExpression(ex.property, transformer), ex.computed);
384
+ return transformMemberExpression(ex, transform);
374
385
  case 'unary':
375
- return new UnaryExpression(ex.operator, transformExpression(ex.value, transformer));
386
+ return transformUnaryExpression(ex, transform);
387
+ case 'variableReference':
388
+ return ex;
376
389
  }
377
390
  }
378
391
  }
379
- function transformBinaryExpression(ex, transformer) {
392
+ function transformPrimitive(val, transform) {
393
+ if (Array.isArray(val)) {
394
+ return val.map((x) => isExpression(x)
395
+ ? transformExpression(x, transform)
396
+ : transformPrimitive(x, transform));
397
+ }
398
+ else if (isRecord(val)) {
399
+ return mapRecordValues(val, (x) => isExpression(x)
400
+ ? transformExpression(x, transform)
401
+ : transformPrimitive(x, transform));
402
+ }
403
+ else {
404
+ return val;
405
+ }
406
+ }
407
+ function transformBinaryExpression(ex, transform) {
380
408
  // Transform left first to keep the correct order of execution of sub-expressions
381
- const newLeft = transformExpression(ex.left, transformer);
382
- const newRight = transformExpression(ex.right, transformer);
383
- return new BinaryExpression(newLeft, ex.binaryOperator, newRight);
409
+ const newLeft = transformExpression(ex.left, transform);
410
+ const newRight = transformExpression(ex.right, transform);
411
+ if (newLeft === ex.left && newRight === ex.right) {
412
+ return ex;
413
+ }
414
+ else {
415
+ return new BinaryExpression(newLeft, ex.binaryOperator, newRight);
416
+ }
417
+ }
418
+ function transformFunctionInvocationExpression(ex, transform) {
419
+ const newArguments = ex.arguments.map((x) => transformExpression(x, transform));
420
+ if (newArguments.every((x, i) => x === ex.arguments[i])) {
421
+ return ex;
422
+ }
423
+ else {
424
+ return new FunctionInvocationExpression(ex.functionName, newArguments);
425
+ }
426
+ }
427
+ function transformMemberExpression(ex, transform) {
428
+ const newObject = transformExpression(ex.object, transform);
429
+ const newProperty = transformExpression(ex.property, transform);
430
+ if (newObject === ex.object && newProperty === ex.property) {
431
+ return ex;
432
+ }
433
+ else {
434
+ return new MemberExpression(newObject, newProperty, ex.computed);
435
+ }
384
436
  }
385
- function transformFunctionInvocationExpression(ex, transformer) {
386
- const newArguments = ex.arguments.map((x) => transformExpression(x, transformer));
387
- return new FunctionInvocationExpression(ex.functionName, newArguments);
437
+ function transformUnaryExpression(ex, transform) {
438
+ const newValue = transformExpression(ex.value, transform);
439
+ return newValue === ex.value ? ex : new UnaryExpression(ex.operator, newValue);
388
440
  }
389
441
  /**
390
442
  * Search for map literals in expressions and replace them with assign step + variable.
@@ -406,89 +458,143 @@ function transformFunctionInvocationExpression(ex, transformer) {
406
458
  * return: ${__temp0.value}
407
459
  */
408
460
  function mapLiteralsAsAssignSteps(steps) {
409
- function transformer(ex) {
410
- const generateTemporaryVariableName = createTempVariableGenerator();
411
- const { transformedExpression, assignSteps } = replaceMapLiterals(ex, generateTemporaryVariableName);
412
- return [assignSteps, transformedExpression];
413
- }
414
- return steps.reduce((acc, current) => {
415
- let needsTransformation = true;
416
- // These steps are allowed to contain map literals if the map is the
417
- // main expression
418
- if (current.tag === 'assign') {
419
- // This does the transformation a bit too eagerly: If any of the
420
- // assignments need the map literal extraction, it is done on all of
421
- // the variables.
422
- needsTransformation = !current.assignments.every(([, value]) => {
423
- return value.expressionType === 'primitive';
424
- });
425
- }
426
- else if (current.tag === 'raise' || current.tag === 'return') {
427
- needsTransformation =
428
- current.value !== undefined &&
429
- includesExtractableMapLiteral(current.value, true);
430
- }
431
- else if (current.tag === 'call') {
432
- if (current.args) {
433
- needsTransformation = Object.values(current.args).some((ex) => includesExtractableMapLiteral(ex, true));
434
- }
435
- }
436
- if (needsTransformation) {
437
- const transformedSteps = transformStepExpressions(current, transformer);
438
- acc.push(...transformedSteps);
439
- }
440
- else {
441
- acc.push(current);
442
- }
443
- return acc;
444
- }, []);
461
+ return steps.flatMap((step) => transformStepExpressions(step, transformNestedMaps));
462
+ }
463
+ function transformNestedMaps(ex) {
464
+ const generateTemporaryVariableName = createTempVariableGenerator();
465
+ const { transformedExpression, tempVariables } = extractNestedMaps(ex, generateTemporaryVariableName, 0);
466
+ const assignments = tempVariables.length > 0 ? [new AssignStepAST(tempVariables)] : [];
467
+ return [assignments, transformedExpression];
445
468
  }
446
- // Return true if the string representation of ex would include {}
447
- function includesExtractableMapLiteral(ex, parentAllowsMaps) {
469
+ function extractNestedMaps(ex, generateName, nestingLevel) {
448
470
  switch (ex.expressionType) {
449
471
  case 'primitive':
450
- if (isRecord(ex.value)) {
451
- return (!parentAllowsMaps ||
452
- Object.values(ex.value).some((x) => isExpression(x) &&
453
- includesExtractableMapLiteral(x, parentAllowsMaps)));
454
- }
455
- else if (Array.isArray(ex.value)) {
456
- return ex.value.some((x) => isExpression(x) &&
457
- includesExtractableMapLiteral(x, parentAllowsMaps));
458
- }
459
- else {
460
- return false;
461
- }
472
+ return extractNestedMapPrimitive(ex.value, generateName, nestingLevel);
462
473
  case 'binary':
463
- return (includesExtractableMapLiteral(ex.left, parentAllowsMaps) ||
464
- includesExtractableMapLiteral(ex.right, parentAllowsMaps));
474
+ return extractNestedMapBinary(ex, generateName, nestingLevel);
465
475
  case 'variableReference':
466
- return false;
467
- case 'unary':
468
- return includesExtractableMapLiteral(ex.value, parentAllowsMaps);
476
+ return { transformedExpression: ex, tempVariables: [] };
469
477
  case 'functionInvocation':
470
- return ex.arguments.some((x) => includesExtractableMapLiteral(x, false));
478
+ return extractNestedMapFunctionInvocation(ex, generateName, nestingLevel);
471
479
  case 'member':
472
- return (includesExtractableMapLiteral(ex.object, false) ||
473
- includesExtractableMapLiteral(ex.property, false));
480
+ return extractNestedMapMember(ex, generateName, nestingLevel);
481
+ case 'unary':
482
+ return extractNestedMapUnary(ex, generateName, nestingLevel);
474
483
  }
475
484
  }
476
- function replaceMapLiterals(expression, generateName) {
477
- function replace(ex) {
478
- if (ex.expressionType === 'primitive' && isRecord(ex.value)) {
479
- const tempVariable = generateName();
480
- assignSteps.push(new AssignStepAST([[tempVariable, new PrimitiveExpression(ex.value)]]));
481
- // replace the map literal with a reference to the temporary variable
482
- return new VariableReferenceExpression(tempVariable);
485
+ function extractNestedMapPrimitive(primitiveEx, generateName, nestingLevel) {
486
+ const { transformed, tempVariables } = extractNestedMapPrimitiveRecursive(primitiveEx, generateName, nestingLevel);
487
+ const ex = isExpression(transformed)
488
+ ? transformed
489
+ : new PrimitiveExpression(transformed);
490
+ return {
491
+ transformedExpression: ex,
492
+ tempVariables,
493
+ };
494
+ }
495
+ function extractNestedMapPrimitiveRecursive(primitiveEx, generateName, nestingLevel) {
496
+ if (typeof primitiveEx === 'string' ||
497
+ typeof primitiveEx === 'number' ||
498
+ typeof primitiveEx === 'boolean' ||
499
+ primitiveEx === null) {
500
+ return {
501
+ transformed: primitiveEx,
502
+ tempVariables: [],
503
+ };
504
+ }
505
+ else if (Array.isArray(primitiveEx)) {
506
+ return extractMapsInList(primitiveEx, generateName, nestingLevel);
507
+ }
508
+ else {
509
+ return extractMapsInMap(primitiveEx, generateName, nestingLevel);
510
+ }
511
+ }
512
+ function extractMapsInList(primitiveEx, generateName, nestingLevel) {
513
+ const { tempVariables, elements } = primitiveEx.reduce((acc, val) => {
514
+ if (isExpression(val)) {
515
+ const { transformedExpression, tempVariables: temps } = extractNestedMaps(val, generateName, nestingLevel);
516
+ acc.tempVariables.push(...temps);
517
+ acc.elements.push(transformedExpression);
483
518
  }
484
519
  else {
485
- return Unmodified;
520
+ const { transformed, tempVariables: temps } = extractNestedMapPrimitiveRecursive(val, generateName, nestingLevel);
521
+ acc.tempVariables.push(...temps);
522
+ acc.elements.push(transformed);
486
523
  }
524
+ return acc;
525
+ }, {
526
+ tempVariables: [],
527
+ elements: [],
528
+ });
529
+ return {
530
+ tempVariables,
531
+ transformed: elements,
532
+ };
533
+ }
534
+ function extractMapsInMap(primitiveEx, generateName, nestingLevel) {
535
+ const { tempVariables, properties } = Object.entries(primitiveEx).reduce((acc, [key, val]) => {
536
+ if (isExpression(val)) {
537
+ const { transformedExpression, tempVariables: temps } = extractNestedMaps(val, generateName, 0);
538
+ acc.tempVariables.push(...temps);
539
+ acc.properties[key] = transformedExpression;
540
+ }
541
+ else {
542
+ const { transformed, tempVariables: temps } = extractNestedMapPrimitiveRecursive(val, generateName, 0);
543
+ acc.tempVariables.push(...temps);
544
+ acc.properties[key] = transformed;
545
+ }
546
+ return acc;
547
+ }, {
548
+ tempVariables: [],
549
+ properties: {},
550
+ });
551
+ let newValue;
552
+ if (nestingLevel === 0) {
553
+ newValue = properties;
554
+ }
555
+ else {
556
+ const name = generateName();
557
+ tempVariables.push([name, new PrimitiveExpression(properties)]);
558
+ newValue = new VariableReferenceExpression(name);
487
559
  }
488
- const assignSteps = [];
489
560
  return {
490
- transformedExpression: transformExpression(expression, replace),
491
- assignSteps,
561
+ transformed: newValue,
562
+ tempVariables,
563
+ };
564
+ }
565
+ function extractNestedMapFunctionInvocation(ex, generateName, nestingLevel) {
566
+ const { expressions, temps } = ex.arguments.reduce((acc, arg) => {
567
+ const { transformedExpression, tempVariables } = extractNestedMaps(arg, generateName, nestingLevel + 1);
568
+ acc.expressions.push(transformedExpression);
569
+ acc.temps.push(...tempVariables);
570
+ return acc;
571
+ }, { expressions: [], temps: [] });
572
+ return {
573
+ transformedExpression: new FunctionInvocationExpression(ex.functionName, expressions),
574
+ tempVariables: temps,
575
+ };
576
+ }
577
+ function extractNestedMapBinary(ex, generateName, nestingLevel) {
578
+ const left = extractNestedMaps(ex.left, generateName, nestingLevel + 1);
579
+ const right = extractNestedMaps(ex.right, generateName, nestingLevel + 1);
580
+ return {
581
+ transformedExpression: new BinaryExpression(left.transformedExpression, ex.binaryOperator, right.transformedExpression),
582
+ tempVariables: left.tempVariables.concat(right.tempVariables),
583
+ };
584
+ }
585
+ function extractNestedMapMember(ex, generateName, nestingLevel) {
586
+ const obj = extractNestedMaps(ex.object, generateName, nestingLevel + 1);
587
+ const pr = extractNestedMaps(ex.property, generateName, nestingLevel + 1);
588
+ return {
589
+ transformedExpression: new MemberExpression(obj.transformedExpression, pr.transformedExpression, ex.computed),
590
+ tempVariables: obj.tempVariables.concat(pr.tempVariables),
591
+ };
592
+ }
593
+ function extractNestedMapUnary(ex, generateName, nestingLevel) {
594
+ const { transformedExpression, tempVariables } = extractNestedMaps(ex.value, generateName, nestingLevel);
595
+ return {
596
+ transformedExpression: new UnaryExpression(ex.operator, transformedExpression),
597
+ tempVariables,
492
598
  };
493
599
  }
494
600
  /**
package/dist/utils.d.ts CHANGED
@@ -1,2 +1,6 @@
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>;
2
6
  //# sourceMappingURL=utils.d.ts.map
@@ -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"}
package/dist/utils.js CHANGED
@@ -1,3 +1,9 @@
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
+ }
@@ -40,6 +40,12 @@ It is not possible to construct a bytes object expect by calling a function that
40
40
 
41
41
  In addition to the literal `null`, the Typescript `undefined` value is also treated as `null` in Workflows YAML.
42
42
 
43
+ ### Implicit type conversions
44
+
45
+ Expressions that combine variables with operators such as `+`, `>`, `==` perform implict type conversions according to the [rules listed on GCP Workflows documentation](https://cloud.google.com/workflows/docs/reference/syntax/datatypes#implicit-conversions). For example, applying `+` to a string and a number concatenates the values into a string.
46
+
47
+ ⚠️ Checking if a variable is null or not must be done by an explicit comparison: `if (var != null) {...}`. Relying in an implicit conversion, such as `if (var) {...}`, results in a TypeError at runtime.
48
+
43
49
  ## Expressions
44
50
 
45
51
  Most Typescript expressions work as expected.
@@ -56,7 +62,7 @@ name === 'Bean'
56
62
  sys.get_env('GOOGLE_CLOUD_PROJECT_ID')
57
63
  ```
58
64
 
59
- Operators:
65
+ ## Operators
60
66
 
61
67
  | Operator | Description |
62
68
  | ------------ | -------------------------------------------- |
@@ -71,11 +77,59 @@ Operators:
71
77
  | &&, \|\|, ! | logical operators |
72
78
  | in | check if a property is present in an object |
73
79
  | ?? | nullish coalescing |
80
+ | ?. | optional chaining |
81
+ | ? : | conditional operator |
74
82
 
75
83
  The [precendence order of operators](https://cloud.google.com/workflows/docs/reference/syntax/datatypes#order-operations) is the same as in GCP Workflows.
76
84
 
77
85
  See [expression in GCP Workflows](https://cloud.google.com/workflows/docs/reference/syntax/expressions) for more information.
78
86
 
87
+ ### Conditional (ternary) operator
88
+
89
+ The expression
90
+
91
+ ```javascript
92
+ x > 0 ? 'positive' : 'not positive'
93
+ ```
94
+
95
+ is converted to an [if() expression](https://cloud.google.com/workflows/docs/reference/stdlib/expression-helpers#conditional_functions):
96
+
97
+ ```yaml
98
+ ${if(x > 0, "positive", "not positive")}
99
+ ```
100
+
101
+ ⚠️ Note that Workflows always evaluates both expression branches unlike Typescript which evaluates only the branch that gets executed.
102
+
103
+ ### Nullish coalescing operator
104
+
105
+ The expression
106
+
107
+ ```javascript
108
+ x ?? 'default value'
109
+ ```
110
+
111
+ is converted to a [default() expression](https://cloud.google.com/workflows/docs/reference/stdlib/expression-helpers#conditional_functions):
112
+
113
+ ```yaml
114
+ ${default(x, "default value")}
115
+ ```
116
+
117
+ ⚠️ Note that Workflows always evaluates the right-hand side expression unlike Typescript which evaluates the right-hand side only if the left-hand side is `null` or `undefined`.
118
+
119
+ ### Optional chaining
120
+
121
+ The optional chaining expression
122
+
123
+ ```javascript
124
+ data.user?.name
125
+ ```
126
+
127
+ is converted to a [map.get() expression](https://cloud.google.com/workflows/docs/reference/stdlib/map/get):
128
+
129
+ ```yaml
130
+ ${map.get(data, ["user", "name"])}
131
+ ```
132
+
79
133
  ## Template literals
80
134
 
81
135
  Template literals are strings that support string interpolation. For example, `Hello ${name}`.
@@ -307,38 +361,6 @@ steps:
307
361
  return: ${b}
308
362
  ```
309
363
 
310
- ## Conditional (ternary) operator
311
-
312
- The expression
313
-
314
- ```javascript
315
- x > 0 ? 'positive' : 'not positive'
316
- ```
317
-
318
- is converted to an [if() expression](https://cloud.google.com/workflows/docs/reference/stdlib/expression-helpers#conditional_functions):
319
-
320
- ```yaml
321
- ${if(x > 0, "positive", "not positive")}
322
- ```
323
-
324
- ⚠️ Note that Workflows always evaluates both expression branches unlike Typescript which evaluates only the branch that gets executed.
325
-
326
- ## Nullish coalescing operator
327
-
328
- The expression
329
-
330
- ```javascript
331
- x ?? 'default value'
332
- ```
333
-
334
- is converted to an [default() expression](https://cloud.google.com/workflows/docs/reference/stdlib/expression-helpers#conditional_functions):
335
-
336
- ```yaml
337
- ${default(x, "default value")}
338
- ```
339
-
340
- ⚠️ Note that Workflows always evaluates the right-hand side expression unlike Typescript which evaluates the right-hand side only if the left-hand side is `null` or `undefined`.
341
-
342
364
  ## Loops
343
365
 
344
366
  The fragment
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts2workflows",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Transpile Typescript code to GCP Workflows programs",
5
5
  "homepage": "https://github.com/aajanki/ts2workflows",
6
6
  "repository": {
@@ -23,7 +23,15 @@
23
23
  "prepare": "husky"
24
24
  },
25
25
  "lint-staged": {
26
- "*.ts": [
26
+ "src/**/*.ts": [
27
+ "prettier --write",
28
+ "eslint"
29
+ ],
30
+ "test/**/*.ts": [
31
+ "prettier --write",
32
+ "eslint"
33
+ ],
34
+ "scripts/**/*.ts": [
27
35
  "prettier --write",
28
36
  "eslint"
29
37
  ],
@@ -49,7 +57,7 @@
49
57
  ],
50
58
  "devDependencies": {
51
59
  "@eslint/js": "^9.10.0",
52
- "@types/chai": "^4.3.16",
60
+ "@types/chai": "^5.0.1",
53
61
  "@types/mocha": "^10.0.6",
54
62
  "@types/node": "^18",
55
63
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -66,7 +74,6 @@
66
74
  },
67
75
  "dependencies": {
68
76
  "@typescript-eslint/typescript-estree": "^8.0.0",
69
- "@typescript-eslint/utils": "^8.0.0",
70
77
  "commander": "^12.1.0",
71
78
  "typescript": "^5.0.0",
72
79
  "yaml": "^2.4.2"