ts2workflows 0.1.0 → 0.2.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 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transpiler/index.ts"],"names":[],"mappings":"AAyBA,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAa9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transpiler/index.ts"],"names":[],"mappings":"AA0BA,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAa9C"}
@@ -7,7 +7,7 @@ import { WorkflowSyntaxError } from '../errors.js';
7
7
  import { generateStepNames } from '../ast/stepnames.js';
8
8
  import { assertType } from './asserts.js';
9
9
  import { parseBlockStatement } from './statements.js';
10
- const { AssignmentPattern, ExportNamedDeclaration, FunctionDeclaration, Identifier, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, Literal, Program, TSTypeAliasDeclaration, TSInterfaceDeclaration, } = AST_NODE_TYPES;
10
+ const { AssignmentPattern, ExportNamedDeclaration, FunctionDeclaration, Identifier, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, Literal, Program, TSDeclareFunction, TSTypeAliasDeclaration, TSInterfaceDeclaration, } = AST_NODE_TYPES;
11
11
  export function transpile(code) {
12
12
  const parserOptions = {
13
13
  jsDocParsingMode: 'none',
@@ -40,10 +40,11 @@ function parseTopLevelStatement(node) {
40
40
  }
41
41
  case TSInterfaceDeclaration:
42
42
  case TSTypeAliasDeclaration:
43
- // Ignore "type" and "interface" declarations at the top-level
43
+ case TSDeclareFunction:
44
+ // Ignore "type", "interface" and "declare function" at the top-level
44
45
  return [];
45
46
  default:
46
- throw new WorkflowSyntaxError(`Only function declarations, imports and type aliases allowed at the top level, encountered ${node?.type}`, node?.loc);
47
+ throw new WorkflowSyntaxError(`Only function definitions, imports and type aliases allowed at the top level, encountered ${node?.type}`, node?.loc);
47
48
  }
48
49
  }
49
50
  function parseSubworkflows(node) {
@@ -1 +1 @@
1
- {"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../../src/transpiler/statements.ts"],"names":[],"mappings":"AAEA,OAAO,EASL,QAAQ,EAOR,eAAe,EAEhB,MAAM,iBAAiB,CAAA;AAwDxB,MAAM,WAAW,cAAc;IAE7B,WAAW,CAAC,EAAE,QAAQ,CAAA;IAEtB,cAAc,CAAC,EAAE,QAAQ,CAAA;CAC1B;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,GAAG,EACT,GAAG,EAAE,cAAc,GAClB,eAAe,EAAE,CAKnB"}
1
+ {"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../../src/transpiler/statements.ts"],"names":[],"mappings":"AAEA,OAAO,EASL,QAAQ,EAOR,eAAe,EAEhB,MAAM,iBAAiB,CAAA;AAyDxB,MAAM,WAAW,cAAc;IAE7B,WAAW,CAAC,EAAE,QAAQ,CAAA;IAEtB,cAAc,CAAC,EAAE,QAAQ,CAAA;CAC1B;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,GAAG,EACT,GAAG,EAAE,cAAc,GAClB,eAAe,EAAE,CAKnB"}
@@ -8,7 +8,7 @@ import { transformAST } from './transformations.js';
8
8
  import { assertType } from './asserts.js';
9
9
  import { convertExpression, convertMemberExpression, convertObjectExpression, convertObjectAsExpressionValues, } from './expressions.js';
10
10
  import { blockingFunctions } from './generated/functionMetadata.js';
11
- const { ArrayExpression, ArrowFunctionExpression, AssignmentExpression, BlockStatement, BreakStatement, CallExpression, ContinueStatement, DoWhileStatement, EmptyStatement, ExpressionStatement, ForInStatement, ForOfStatement, FunctionDeclaration, Identifier, IfStatement, LabeledStatement, MemberExpression, ObjectExpression, ReturnStatement, SwitchCase, SwitchStatement, ThrowStatement, TryStatement, TSTypeAliasDeclaration, TSInterfaceDeclaration, VariableDeclaration, VariableDeclarator, WhileStatement, } = AST_NODE_TYPES;
11
+ const { ArrayExpression, ArrowFunctionExpression, AssignmentExpression, BlockStatement, BreakStatement, CallExpression, ContinueStatement, DoWhileStatement, EmptyStatement, ExpressionStatement, ForInStatement, ForOfStatement, FunctionDeclaration, Identifier, IfStatement, LabeledStatement, MemberExpression, ObjectExpression, ReturnStatement, SwitchCase, SwitchStatement, ThrowStatement, TryStatement, TSDeclareFunction, TSTypeAliasDeclaration, TSInterfaceDeclaration, VariableDeclaration, VariableDeclarator, WhileStatement, } = AST_NODE_TYPES;
12
12
  export function parseBlockStatement(node, ctx) {
13
13
  assertType(node, BlockStatement);
14
14
  const body = node.body;
@@ -58,7 +58,8 @@ function parseStep(node, ctx) {
58
58
  throw new WorkflowSyntaxError('Functions must be defined at the top level of a source file', node.loc);
59
59
  case TSInterfaceDeclaration:
60
60
  case TSTypeAliasDeclaration:
61
- // Ignore "type" and "interface" declarations
61
+ case TSDeclareFunction:
62
+ // Ignore "type", "interface" and "declare function"
62
63
  return [];
63
64
  default:
64
65
  throw new WorkflowSyntaxError(`TODO: encountered unsupported type: ${node.type}`, node.loc);
@@ -1 +1 @@
1
- {"version":3,"file":"transformations.d.ts","sourceRoot":"","sources":["../../src/transpiler/transformations.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,eAAe,EAChB,MAAM,iBAAiB,CAAA;AAoBxB;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAQxE;AA0JD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,eAAe,EAAE,GACvB,eAAe,EAAE,CAEnB"}
1
+ {"version":3,"file":"transformations.d.ts","sourceRoot":"","sources":["../../src/transpiler/transformations.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,eAAe,EAChB,MAAM,iBAAiB,CAAA;AAuBxB;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAUxE;AA0JD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,eAAe,EAAE,GACvB,eAAe,EAAE,CAEnB"}
@@ -3,6 +3,7 @@ import { InternalTranspilingError } from '../errors.js';
3
3
  import { isRecord } 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
+ const Unmodified = Symbol();
6
7
  /**
7
8
  * Performs various transformations on the AST.
8
9
  *
@@ -10,7 +11,7 @@ import { blockingFunctions } from './generated/functionMetadata.js';
10
11
  * called on each nesting level separately.
11
12
  */
12
13
  export function transformAST(steps) {
13
- return blockingCallsAsCallSteps(flattenPlainNextConditions(combineRetryBlocksToTry(mergeAssignSteps(mapLiteralsAsAssignSteps(steps)))));
14
+ return blockingCallsAsCallSteps(runtimeFunctionImplementation(flattenPlainNextConditions(combineRetryBlocksToTry(mergeAssignSteps(mapLiteralsAsAssignSteps(steps))))));
14
15
  }
15
16
  /**
16
17
  * Merge consecutive assign steps into one assign step
@@ -196,7 +197,6 @@ function createTempVariableGenerator() {
196
197
  const generator = () => `__temp${i++}`;
197
198
  return generator;
198
199
  }
199
- const Unmodified = Symbol();
200
200
  function replaceBlockingCalls(expression, generateName) {
201
201
  function replaceBlockingFunctionInvocations(ex) {
202
202
  if (ex.expressionType === 'functionInvocation') {
@@ -406,13 +406,14 @@ function mapLiteralsAsAssignSteps(steps) {
406
406
  return value.expressionType === 'primitive';
407
407
  });
408
408
  }
409
- else if (current.tag === 'raise') {
409
+ else if (current.tag === 'raise' || current.tag === 'return') {
410
410
  needsTransformation =
411
- !isLiteral(current.value) && includesMapLiteral(current.value);
411
+ current.value !== undefined &&
412
+ includesExtractableMapLiteral(current.value, true);
412
413
  }
413
414
  else if (current.tag === 'call') {
414
415
  if (current.args) {
415
- needsTransformation = Object.values(current.args).some((ex) => !isLiteral(ex) && includesMapLiteral(ex));
416
+ needsTransformation = Object.values(current.args).some((ex) => includesExtractableMapLiteral(ex, true));
416
417
  }
417
418
  }
418
419
  if (needsTransformation) {
@@ -425,20 +426,34 @@ function mapLiteralsAsAssignSteps(steps) {
425
426
  return acc;
426
427
  }, []);
427
428
  }
428
- function includesMapLiteral(ex) {
429
+ // Return true if the string representation of ex would include {}
430
+ function includesExtractableMapLiteral(ex, parentAllowsMaps) {
429
431
  switch (ex.expressionType) {
430
432
  case 'primitive':
431
- return isRecord(ex.value);
433
+ if (isRecord(ex.value)) {
434
+ return (!parentAllowsMaps ||
435
+ Object.values(ex.value).some((x) => isExpression(x) &&
436
+ includesExtractableMapLiteral(x, parentAllowsMaps)));
437
+ }
438
+ else if (Array.isArray(ex.value)) {
439
+ return ex.value.some((x) => isExpression(x) &&
440
+ includesExtractableMapLiteral(x, parentAllowsMaps));
441
+ }
442
+ else {
443
+ return false;
444
+ }
432
445
  case 'binary':
433
- return includesMapLiteral(ex.left) || includesMapLiteral(ex.right);
446
+ return (includesExtractableMapLiteral(ex.left, parentAllowsMaps) ||
447
+ includesExtractableMapLiteral(ex.right, parentAllowsMaps));
434
448
  case 'variableReference':
435
449
  return false;
436
450
  case 'unary':
437
- return includesMapLiteral(ex.value);
451
+ return includesExtractableMapLiteral(ex.value, parentAllowsMaps);
438
452
  case 'functionInvocation':
439
- return ex.arguments.some(includesMapLiteral);
453
+ return ex.arguments.some((x) => includesExtractableMapLiteral(x, false));
440
454
  case 'member':
441
- return includesMapLiteral(ex.object) || includesMapLiteral(ex.property);
455
+ return (includesExtractableMapLiteral(ex.object, false) ||
456
+ includesExtractableMapLiteral(ex.property, false));
442
457
  }
443
458
  }
444
459
  function replaceMapLiterals(expression, generateName) {
@@ -459,3 +474,25 @@ function replaceMapLiterals(expression, generateName) {
459
474
  assignSteps,
460
475
  };
461
476
  }
477
+ /**
478
+ * Replace `Array.isArray(x)` with `get_type(x) == "list"`
479
+ */
480
+ function runtimeFunctionImplementation(steps) {
481
+ return steps.reduce((acc, current) => {
482
+ const transformedSteps = transformStepExpressions(current, (ex) => [
483
+ [],
484
+ transformExpression(ex, replaceIsArray),
485
+ ]);
486
+ acc.push(...transformedSteps);
487
+ return acc;
488
+ }, []);
489
+ }
490
+ function replaceIsArray(ex) {
491
+ if (ex.expressionType === 'functionInvocation' &&
492
+ ex.functionName === 'Array.isArray') {
493
+ return new BinaryExpression(new FunctionInvocationExpression('get_type', ex.arguments), '==', new PrimitiveExpression('list'));
494
+ }
495
+ else {
496
+ return Unmodified;
497
+ }
498
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  ts2workflow converts Typescript source code to GCP Workflows YAML syntax. Only a subset of Typescript language features are supported. This page documents supported Typescript features and shows examples of the generted Workflows YAML output.
4
4
 
5
- Functions provided by a Javascript runtime (`console.log`, `setInterval`, etc) are not available.
5
+ Most functions provided by a Javascript runtime (`console.log()`, `setInterval()`, etc) are not available.
6
6
 
7
7
  Type annotations are allowed. Type checking is done by the compiler but the types dont't affect the generated Workflows code.
8
8
 
@@ -22,7 +22,7 @@ Semicolon can be used as optional statement delimitter.
22
22
 
23
23
  ### Array type
24
24
 
25
- ⚠️ Arrays are not objects. In particular, methods like `array.map()` and `array.concat()` are not available.
25
+ ⚠️ Arrays are not objects. In particular, methods like `[].map()` and `[].concat()` are not available.
26
26
 
27
27
  ⚠️ Accessing out-of-bounds index will cause an IndexError at runtime unlike in Typescript where out-of-bounds access would return `undefined`.
28
28
 
@@ -689,7 +689,19 @@ function read_from_env() {
689
689
 
690
690
  At the moment, type annotations are provided for some [connectors](https://cloud.google.com/workflows/docs/reference/googleapis) but not for all of them.
691
691
 
692
- ## Special run-time functions
692
+ ### Runtime functions
693
+
694
+ This section describes the few standard Javascript runtime functions that are available. Most are not.
695
+
696
+ ### Array.isArray()
697
+
698
+ ```typescript
699
+ Array.isArray(arg: any): arg is any[]
700
+ ```
701
+
702
+ Gets converted to the comparison `get_type(arg) == "list"`. Unlike a direct call to `get_type()`, `Array.isArray()` allows the type inference to learn if `arg` is array or not.
703
+
704
+ ## Language extension functions
693
705
 
694
706
  ts2workflows provides some special functions for implementing features that are not directly supported by Typescript language features. The type annotations for these functions can be imported from ts2workflows/types/workflowslib:
695
707
 
@@ -701,15 +713,18 @@ import {
701
713
  } from 'ts2workflows/types/workflowslib'
702
714
  ```
703
715
 
704
- ### call_step
716
+ ### call_step()
705
717
 
706
718
  ```typescript
707
- function call_step(func: Function, args: Record<string, unknown>): unknown
719
+ function call_step<T, A extends any[]>(
720
+ func: (...args: A) => T,
721
+ arguments: Record<string, unknown>,
722
+ ): T
708
723
  ```
709
724
 
710
725
  The `call_step` function outputs a [call step](https://cloud.google.com/workflows/docs/reference/syntax/calls).
711
726
 
712
- ### parallel
727
+ ### parallel()
713
728
 
714
729
  ```typescript
715
730
  function parallel(
@@ -724,7 +739,7 @@ function parallel(
724
739
 
725
740
  The `parallel` function executes code blocks in parallel (using [parallel step](https://cloud.google.com/workflows/docs/reference/syntax/parallel-steps)). See the previous sections covering parallel branches and iteration.
726
741
 
727
- ### retry_policy
742
+ ### retry_policy()
728
743
 
729
744
  ```typescript
730
745
  function retry_policy(
@@ -760,9 +775,9 @@ const var1 = 1 // This is a comment
760
775
 
761
776
  ts2workflows supports only a subset of all Typescript language features. Some examples that are not (yet) supported by ts2workflows:
762
777
 
763
- - Functions provided by a Javascript runtime (`console.log`, `setInterval`, etc) are not available. Only the [GCP Workflows standard library functions](https://cloud.google.com/workflows/docs/reference/stdlib/overview) and [connectors](https://cloud.google.com/workflows/docs/reference/googleapis) are available.
778
+ - Most functions provided by a Javascript runtime (`console.log()`, `setInterval()`, etc) are not available. Only the [GCP Workflows standard library functions](https://cloud.google.com/workflows/docs/reference/stdlib/overview) and [connectors](https://cloud.google.com/workflows/docs/reference/googleapis) are available.
764
779
  - Classes (`class`) are not supported
765
- - Arrays and maps are not objects. In particular, arrays don't have methods such as `array.push()`, `array.map()`, etc.
780
+ - Arrays and maps are not objects. In particular, arrays don't have methods such as `[].push()`, `[].map()`, etc.
766
781
  - Functions (subworkflows) are not first-class objects. Functions can not be assigned to a variable or passed to other functions
767
782
  - Update expressions (`x++` and similar) are not supported
768
783
  - Destructuring (`[a, b] = func()`) is not supported
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts2workflows",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Transpile Typescript code to GCP Workflows programs",
5
5
  "homepage": "https://github.com/aajanki/ts2workflows",
6
6
  "repository": {
@@ -188,16 +188,19 @@ export declare namespace list {
188
188
  }
189
189
 
190
190
  export declare namespace map {
191
- function _delete<T1>(map: Record<string, T1>, key: string): Record<string, T1>
192
- export function get<T2>(map: Record<string, T2>, keys: string | string[]): T2
193
- export function merge<S1, S2>(
194
- first: Record<string, S1>,
195
- second: Record<string, S2>,
196
- ): Record<string, S1 | S2>
197
- export function merge_nested<S3, S4>(
198
- first: Record<string, S3>,
199
- second: Record<string, S4>,
200
- ): Record<string, S3 | S4>
191
+ function _delete<T>(map: Record<string, T>, key: string): Record<string, T>
192
+ export function get<T, K extends string | string[]>(
193
+ map: Record<string, T>,
194
+ keys: K,
195
+ ): K extends string ? T : unknown
196
+ export function merge<T, U>(
197
+ first: Record<string, T>,
198
+ second: Record<string, U>,
199
+ ): Record<string, T | U>
200
+ export function merge_nested<T, U>(
201
+ first: Record<string, T>,
202
+ second: Record<string, U>,
203
+ ): Record<string, T | U>
201
204
  export { _delete as delete }
202
205
  }
203
206
 
@@ -708,7 +711,7 @@ export declare function retry_policy(
708
711
  },
709
712
  ): void
710
713
 
711
- export declare function call_step(
712
- func: Function,
714
+ export declare function call_step<T, A extends any[]>(
715
+ func: (...args: A) => T,
713
716
  arguments: Record<string, unknown>,
714
- ): unknown
717
+ ): T