@output.ai/core 0.0.7 → 0.0.9

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.
Files changed (45) hide show
  1. package/README.md +85 -59
  2. package/package.json +10 -3
  3. package/src/configs.js +1 -1
  4. package/src/consts.js +4 -3
  5. package/src/errors.js +11 -0
  6. package/src/index.d.ts +302 -30
  7. package/src/index.js +3 -2
  8. package/src/interface/metadata.js +3 -3
  9. package/src/interface/step.js +18 -4
  10. package/src/interface/utils.js +41 -4
  11. package/src/interface/utils.spec.js +71 -0
  12. package/src/interface/validations/ajv_provider.js +3 -0
  13. package/src/interface/validations/runtime.js +69 -0
  14. package/src/interface/validations/runtime.spec.js +50 -0
  15. package/src/interface/validations/static.js +67 -0
  16. package/src/interface/validations/static.spec.js +101 -0
  17. package/src/interface/webhook.js +15 -14
  18. package/src/interface/workflow.js +45 -40
  19. package/src/internal_activities/index.js +16 -5
  20. package/src/worker/catalog_workflow/catalog.js +105 -0
  21. package/src/worker/catalog_workflow/index.js +21 -0
  22. package/src/worker/catalog_workflow/index.spec.js +139 -0
  23. package/src/worker/catalog_workflow/workflow.js +13 -0
  24. package/src/worker/index.js +41 -5
  25. package/src/worker/interceptors/activity.js +3 -2
  26. package/src/worker/internal_utils.js +60 -0
  27. package/src/worker/internal_utils.spec.js +134 -0
  28. package/src/worker/loader.js +30 -44
  29. package/src/worker/loader.spec.js +68 -0
  30. package/src/worker/sinks.js +2 -1
  31. package/src/worker/tracer/index.js +35 -3
  32. package/src/worker/tracer/index.test.js +115 -0
  33. package/src/worker/tracer/tracer_tree.js +29 -5
  34. package/src/worker/tracer/tracer_tree.test.js +116 -0
  35. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +133 -0
  36. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +77 -0
  37. package/src/worker/webpack_loaders/workflow_rewriter/consts.js +3 -0
  38. package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +58 -0
  39. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +129 -0
  40. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +70 -0
  41. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +33 -0
  42. package/src/worker/webpack_loaders/workflow_rewriter/tools.js +245 -0
  43. package/src/worker/webpack_loaders/workflow_rewriter/tools.spec.js +144 -0
  44. package/src/errors.d.ts +0 -3
  45. package/src/worker/temp/__workflows_entrypoint.js +0 -6
@@ -1,9 +1,46 @@
1
- // This is rigged to return which folder had the source of calls for both interface methods (step workflow) functions
2
- // Important, if to refactor, pay attention to the depth in the stack trace to extract the info
3
- // now is 3 cause (1 line is name, 2 line is this function, 3 line is step/workflow, 4 line is caller)
4
- export const getInvocationDir = _ => new Error()
1
+ /**
2
+ * Function rigged to return the folder path of the source of calls for the interface methods (step/workflow)
3
+ *
4
+ * IMPORTANT!!!
5
+ * If to refactor this, pay attention to the depth in the stack trace to extract the info.
6
+ * Currently it is 3:
7
+ * - 1st line is the name of the function;
8
+ * - 2nd line is this function;
9
+ * - 3rd line is step/workflow;
10
+ * - 4th line is caller;
11
+ *
12
+ * @returns {string} The folder path of the caller
13
+ */
14
+ export const getInvocationDir = () => new Error()
5
15
  .stack.split( '\n' )[3]
6
16
  .split( ' ' )
7
17
  .at( -1 )
8
18
  .replace( /\((.+):\d+:\d+\)/, '$1' )
9
19
  .split( '/' ).slice( 0, -1 ).join( '/' );
20
+
21
+ /**
22
+ * This mouthful function will invoke a function with given arguments, and validate its return
23
+ * using a given validator.
24
+ *
25
+ * It will preserver the execution model (asynchronous vs synchronous), so if the function is
26
+ * sync the validation happens here, if it is async (returns Promise) the validation is attached
27
+ * to a .then().
28
+ *
29
+ *
30
+ * @param {Function} fn - The function to execute
31
+ * @param {any} input - The payload to call the function
32
+ * @param {Function} validate - The validator function
33
+ * @returns {any} Function result (Promise or not)
34
+ */
35
+ export const invokeFnAndValidateOutputPreservingExecutionModel = ( fn, input, validate ) => {
36
+ const uniformReturn = output => {
37
+ validate( output );
38
+ return output;
39
+ };
40
+
41
+ const output = fn( input );
42
+ if ( output?.constructor === Promise ) {
43
+ return output.then( resolvedOutput => uniformReturn( resolvedOutput ) );
44
+ }
45
+ return uniformReturn( output );
46
+ };
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { getInvocationDir, invokeFnAndValidateOutputPreservingExecutionModel } from './utils.js';
3
+
4
+ describe( 'interface/utils', () => {
5
+ describe( 'getInvocationDir', () => {
6
+ it( 'returns the caller directory from stack trace', () => {
7
+ const fakeCaller = '/tmp/project/src/caller/file.js';
8
+ const OriginalError = Error;
9
+ // Provide a deterministic stack shape for the function under test
10
+ // Lines: 1) Error, 2) getInvocationDir, 3) step/workflow, 4) actual caller
11
+ // Include typical V8 formatting with leading spaces and without function name
12
+ // for the caller line
13
+
14
+ Error = class extends OriginalError {
15
+ constructor( ...args ) {
16
+ super( ...args );
17
+ this.stack = [
18
+ 'Error',
19
+ ' at getInvocationDir (a:1:1)',
20
+ ' at step (b:1:1)',
21
+ ` at ${fakeCaller}:10:20`
22
+ ].join( '\n' );
23
+ }
24
+ };
25
+ try {
26
+ const dir = getInvocationDir();
27
+ expect( dir ).toBe( '/tmp/project/src/caller' );
28
+ } finally {
29
+
30
+ Error = OriginalError;
31
+ }
32
+ } );
33
+ } );
34
+
35
+ describe( 'invokeFnAndValidateOutputPreservingExecutionModel', () => {
36
+ it( 'validates and returns sync output', () => {
37
+ const fn = vi.fn( x => x * 2 );
38
+ const validate = vi.fn();
39
+ const result = invokeFnAndValidateOutputPreservingExecutionModel( fn, 3, validate );
40
+ expect( result ).toBe( 6 );
41
+ expect( validate ).toHaveBeenCalledWith( 6 );
42
+ } );
43
+
44
+ it( 'validates and returns async output preserving promise', async () => {
45
+ const fn = vi.fn( async x => x + 1 );
46
+ const validate = vi.fn();
47
+ const resultPromise = invokeFnAndValidateOutputPreservingExecutionModel( fn, 4, validate );
48
+ expect( resultPromise ).toBeInstanceOf( Promise );
49
+ const result = await resultPromise;
50
+ expect( result ).toBe( 5 );
51
+ expect( validate ).toHaveBeenCalledWith( 5 );
52
+ } );
53
+
54
+ it( 'propagates validator errors (sync)', () => {
55
+ const fn = vi.fn( x => x );
56
+ const validate = vi.fn( () => {
57
+ throw new Error( 'invalid' );
58
+ } );
59
+ expect( () => invokeFnAndValidateOutputPreservingExecutionModel( fn, 'a', validate ) ).toThrow( 'invalid' );
60
+ } );
61
+
62
+ it( 'propagates validator errors (async)', async () => {
63
+ const fn = vi.fn( async x => x );
64
+ const validate = vi.fn( () => {
65
+ throw new Error( 'invalid' );
66
+ } );
67
+ await expect( invokeFnAndValidateOutputPreservingExecutionModel( fn, 'a', validate ) ).rejects.toThrow( 'invalid' );
68
+ } );
69
+ } );
70
+ } );
71
+
@@ -0,0 +1,3 @@
1
+ import Ajv from 'ajv';
2
+
3
+ export const ajv = new Ajv();
@@ -0,0 +1,69 @@
1
+ import { FatalError } from '#errors';
2
+ import { ajv } from './ajv_provider.js';
3
+
4
+ /**
5
+ * Error type for when the input/output of a step/workflow doesn't match its input/output schema, respectively
6
+ */
7
+ export class MismatchSchemaError extends FatalError {}
8
+ /**
9
+ * Error type for when the input of a step/workflow doesn't match its input schema
10
+ * @extends MismatchSchemaError
11
+ */
12
+ export class InvalidInputError extends MismatchSchemaError {}
13
+ /**
14
+ * Error type for when the output of a step/workflow doesn't match its output schema
15
+ * @extends MismatchSchemaError
16
+ */
17
+ export class InvalidOutputError extends MismatchSchemaError {}
18
+
19
+ const validate = ( ErrorClass, type, name, schema, payload ) => {
20
+ const validate = ajv.compile( schema );
21
+ const valid = validate( payload );
22
+
23
+ if ( !valid ) {
24
+ throw new ErrorClass( `Invalid input at ${type} "${name}": ${ajv.errorsText( validate.errors )}` );
25
+ }
26
+ };
27
+
28
+ const validateInput = validate.bind( null, InvalidInputError );
29
+ const validateOutput = validate.bind( null, InvalidOutputError );
30
+
31
+ /**
32
+ * Validates step input
33
+ *
34
+ * @param {name} name - step's name
35
+ * @param {object} schema - step's input schema
36
+ * @param {any} - the input to validate
37
+ * @throws InvalidInputError
38
+ */
39
+ export const validateStepInput = validateInput.bind( null, 'step' );
40
+
41
+ /**
42
+ * Validates step output
43
+ *
44
+ * @param {name} name - step's name
45
+ * @param {object} schema - step's output schema
46
+ * @param {any} - the output to validate
47
+ * @throws InvalidOutputError
48
+ */
49
+ export const validateStepOutput = validateOutput.bind( null, 'step' );
50
+
51
+ /**
52
+ * Validates workflow input
53
+ *
54
+ * @param {name} name - workflow's name
55
+ * @param {object} schema - workflow's input schema
56
+ * @param {any} - the input to validate
57
+ * @throws InvalidInputError
58
+ */
59
+ export const validateWorkflowInput = validateInput.bind( null, 'workflow' );
60
+
61
+ /**
62
+ * Validates workflow output
63
+ *
64
+ * @param {name} name - workflow's name
65
+ * @param {object} schema - workflow's output schema
66
+ * @param {any} - the output to validate
67
+ * @throws InvalidOutputError
68
+ */
69
+ export const validateWorkflowOutput = validateOutput.bind( null, 'workflow' );
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateStepInput, validateWorkflowInput, InvalidInputError } from './runtime.js';
3
+
4
+ describe( 'Runtime validations spec', () => {
5
+ describe( 'validateStepInput', () => {
6
+ it( 'passes with matching payload', () => {
7
+ const schema = {
8
+ type: 'object',
9
+ properties: { id: { type: 'string' }, count: { type: 'integer', minimum: 0 } },
10
+ required: [ 'id' ],
11
+ additionalProperties: false
12
+ };
13
+ expect( () => validateStepInput( 'myStep', schema, { id: 'x', count: 2 } ) ).not.toThrow();
14
+ } );
15
+
16
+ it( 'rejects invalid payload', () => {
17
+ const schema = {
18
+ type: 'object',
19
+ properties: { id: { type: 'string' }, count: { type: 'integer', minimum: 0 } },
20
+ required: [ 'id' ],
21
+ additionalProperties: false
22
+ };
23
+ const error = new InvalidInputError( 'Invalid input at step "myStep": data must have required property \'id\'' );
24
+ expect( () => validateStepInput( 'myStep', schema, { count: -1 } ) ).toThrow( error );
25
+ } );
26
+ } );
27
+
28
+ describe( 'validateWorkflowInput', () => {
29
+ it( 'passes with matching payload', () => {
30
+ const schema = {
31
+ type: 'object',
32
+ properties: { name: { type: 'string' }, enabled: { type: 'boolean' } },
33
+ required: [ 'name' ],
34
+ additionalProperties: false
35
+ };
36
+ expect( () => validateWorkflowInput( 'myWorkflow', schema, { name: 'wf', enabled: true } ) ).not.toThrow();
37
+ } );
38
+
39
+ it( 'rejects invalid payload', () => {
40
+ const schema = {
41
+ type: 'object',
42
+ properties: { name: { type: 'string' }, enabled: { type: 'boolean' } },
43
+ required: [ 'name' ],
44
+ additionalProperties: false
45
+ };
46
+ const error = new InvalidInputError( 'Invalid input at workflow "myWorkflow": data must have required property \'name\'' );
47
+ expect( () => validateWorkflowInput( 'myWorkflow', schema, { enabled: 'yes' } ) ).toThrow( error );
48
+ } );
49
+ } );
50
+ } );
@@ -0,0 +1,67 @@
1
+ import { ajv } from './ajv_provider.js';
2
+ import * as z from 'zod';
3
+
4
+ /**
5
+ * Error is thrown when the definition of a step/workflow has problems
6
+ */
7
+ export class StaticValidationError extends Error {};
8
+
9
+ // Custom validation for zod, to be used when validating JSONSchema def with ajv
10
+ const refineJsonSchema = ( value, ctx ) => {
11
+ if ( value && !ajv.validateSchema( value ) ) {
12
+ ctx.addIssue( {
13
+ code: 'invalid_format',
14
+ message: ajv.errorsText( ajv.errors )
15
+ } );
16
+ }
17
+ };
18
+
19
+ const stepAndWorkflowSchema = z.object( {
20
+ name: z.string().regex( /^[a-z_][a-z0-9_]*$/i ),
21
+ description: z.string().optional(),
22
+ inputSchema: z.looseObject( {} ).optional().superRefine( refineJsonSchema ),
23
+ outputSchema: z.looseObject( {} ).optional().superRefine( refineJsonSchema ),
24
+ fn: z.function()
25
+ } );
26
+
27
+ const webhookSchema = z.object( {
28
+ url: z.url( { protocol: /^https?$/ } ),
29
+ payload: z.any().optional()
30
+ } );
31
+
32
+ const validateAgainstSchema = ( schema, args ) => {
33
+ const result = schema.safeParse( args );
34
+ if ( !result.success ) {
35
+ throw new StaticValidationError( z.prettifyError( result.error ) );
36
+ }
37
+ };
38
+
39
+ /**
40
+ * Validate step payload
41
+ *
42
+ * @param {object} args - The step arguments
43
+ * @throws {StaticValidationError} Throws if args are invalid
44
+ */
45
+ export function validateStep( args ) {
46
+ validateAgainstSchema( stepAndWorkflowSchema, args );
47
+ };
48
+
49
+ /**
50
+ * Validate workflow payload
51
+ *
52
+ * @param {object} args - The workflow arguments
53
+ * @throws {StaticValidationError} Throws if args are invalid
54
+ */
55
+ export function validateWorkflow( args ) {
56
+ validateAgainstSchema( stepAndWorkflowSchema, args );
57
+ };
58
+
59
+ /**
60
+ * Validate createWebhook payload
61
+ *
62
+ * @param {object} args - The createWebhook arguments
63
+ * @throws {StaticValidationError} Throws if args are invalid
64
+ */
65
+ export function validateCreateWebhook( args ) {
66
+ validateAgainstSchema( webhookSchema, args );
67
+ };
@@ -0,0 +1,101 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateStep, validateWorkflow, validateCreateWebhook, StaticValidationError } from './static.js';
3
+
4
+ const validArgs = Object.freeze( {
5
+ name: 'valid_name',
6
+ description: 'desc',
7
+ inputSchema: { type: 'object' },
8
+ outputSchema: { type: 'object' },
9
+ fn: () => {}
10
+ } );
11
+
12
+ describe( 'interface/validator', () => {
13
+ describe( 'validateStep', () => {
14
+ it( 'passes for valid args', () => {
15
+ expect( () => validateStep( { ...validArgs } ) ).not.toThrow();
16
+ } );
17
+
18
+ it( 'rejects missing name', () => {
19
+ const error = new StaticValidationError( '✖ Invalid input: expected string, received undefined\n → at name' );
20
+ expect( () => validateStep( { ...validArgs, name: undefined } ) ).toThrow( error );
21
+ } );
22
+
23
+ it( 'rejects non-string name', () => {
24
+ const error = new StaticValidationError( '✖ Invalid input: expected string, received number\n → at name' );
25
+ expect( () => validateStep( { ...validArgs, name: 123 } ) ).toThrow( error );
26
+ } );
27
+
28
+ it( 'rejects invalid name pattern', () => {
29
+ const error = new StaticValidationError( '✖ Invalid string: must match pattern /^[a-z_][a-z0-9_]*$/i\n → at name' );
30
+ expect( () => validateStep( { ...validArgs, name: '-bad' } ) ).toThrow( error );
31
+ } );
32
+
33
+ it( 'rejects non-string description', () => {
34
+ const error = new StaticValidationError( '✖ Invalid input: expected string, received number\n → at description' );
35
+ expect( () => validateStep( { ...validArgs, description: 10 } ) ).toThrow( error );
36
+ } );
37
+
38
+ it( 'rejects non-object inputSchema', () => {
39
+ const error = new StaticValidationError( '✖ Invalid input: expected object, received string\n → at inputSchema' );
40
+ expect( () => validateStep( { ...validArgs, inputSchema: 'not-an-object' } ) ).toThrow( error );
41
+ } );
42
+
43
+ it( 'rejects invalid inputSchema structure', () => {
44
+ const error = new StaticValidationError( '✖ data/type must be equal to one of the allowed values, \
45
+ data/type must be array, data/type must match a schema in anyOf\n → at inputSchema' );
46
+ expect( () => validateStep( { ...validArgs, inputSchema: { type: 1 } } ) ).toThrow( error );
47
+ } );
48
+
49
+ it( 'rejects non-object outputSchema', () => {
50
+ const error = new StaticValidationError( '✖ Invalid input: expected object, received number\n → at outputSchema' );
51
+ expect( () => validateStep( { ...validArgs, outputSchema: 10 } ) ).toThrow( error );
52
+ } );
53
+
54
+ it( 'rejects invalid outputSchema structure', () => {
55
+ const error = new StaticValidationError( '✖ data/type must be equal to one of the allowed values, \
56
+ data/type must be array, data/type must match a schema in anyOf\n → at outputSchema' );
57
+ expect( () => validateStep( { ...validArgs, outputSchema: { type: 1 } } ) ).toThrow( error );
58
+ } );
59
+
60
+ it( 'rejects missing fn', () => {
61
+ const error = new StaticValidationError( '✖ Invalid input: expected function, received undefined\n → at fn' );
62
+ expect( () => validateStep( { ...validArgs, fn: undefined } ) ).toThrow( error );
63
+ } );
64
+
65
+ it( 'rejects non-function fn', () => {
66
+ const error = new StaticValidationError( '✖ Invalid input: expected function, received string\n → at fn' );
67
+ expect( () => validateStep( { ...validArgs, fn: 'not-fn' } ) ).toThrow( error );
68
+ } );
69
+ } );
70
+
71
+ describe( 'validateWorkflow', () => {
72
+ it( 'passes for valid args', () => {
73
+ expect( () => validateWorkflow( { ...validArgs } ) ).not.toThrow();
74
+ } );
75
+ } );
76
+
77
+ describe( 'validate webhook', () => {
78
+ it( 'passes with valid http url', () => {
79
+ expect( () => validateCreateWebhook( { url: 'http://example.com' } ) ).not.toThrow();
80
+ } );
81
+
82
+ it( 'passes with valid https url', () => {
83
+ expect( () => validateCreateWebhook( { url: 'https://example.com/path?q=1' } ) ).not.toThrow();
84
+ } );
85
+
86
+ it( 'rejects missing url', () => {
87
+ const error = new StaticValidationError( '✖ Invalid input: expected string, received undefined\n → at url' );
88
+ expect( () => validateCreateWebhook( { } ) ).toThrow( error );
89
+ } );
90
+
91
+ it( 'rejects invalid scheme', () => {
92
+ const error = new StaticValidationError( '✖ Invalid URL\n → at url' );
93
+ expect( () => validateCreateWebhook( { url: 'ftp://example.com' } ) ).toThrow( error );
94
+ } );
95
+
96
+ it( 'rejects malformed url', () => {
97
+ const error = new StaticValidationError( '✖ Invalid URL\n → at url' );
98
+ expect( () => validateCreateWebhook( { url: 'http:////' } ) ).toThrow( error );
99
+ } );
100
+ } );
101
+ } );
@@ -1,18 +1,19 @@
1
- import { defineSignal, setHandler } from '@temporalio/workflow';
1
+ // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
+ import { defineSignal, setHandler, proxyActivities, workflowInfo } from '@temporalio/workflow';
3
+ import { SEND_WEBHOOK_ACTIVITY_NAME } from '#consts';
4
+ import { validateCreateWebhook } from './validations/static.js';
2
5
 
3
- export function createWebhook( workflowId, requestDispatcher ) {
4
- return async function webhook( { name: _name, description: _description, url, payload } ) {
5
- const { error } = await requestDispatcher( { url, workflowId, payload } );
6
- if ( error ) {
7
- throw new Error( 'Webhook call failed' );
8
- }
6
+ export async function createWebhook( { url, payload } ) {
7
+ validateCreateWebhook( { url, payload } );
8
+ const workflowId = workflowInfo();
9
9
 
10
- const resumeSignal = defineSignal( 'resume' );
10
+ await proxyActivities( temporalActivityConfigs )[SEND_WEBHOOK_ACTIVITY_NAME]( { url, workflowId, payload } );
11
11
 
12
- return new Promise( resolve => {
13
- setHandler( resumeSignal, responsePayload => {
14
- resolve( responsePayload );
15
- } );
16
- } );
17
- };
12
+ const resumeSignal = defineSignal( 'resume' );
13
+
14
+ return new Promise( resolve =>
15
+ setHandler( resumeSignal, responsePayload => {
16
+ resolve( responsePayload );
17
+ } )
18
+ );
18
19
  };
@@ -1,10 +1,10 @@
1
1
  // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
- import { proxyActivities, IllegalStateError, executeChild, workflowInfo } from '@temporalio/workflow';
3
- import { getInvocationDir } from './utils.js';
4
- import { createWebhook } from './webhook.js';
5
- import { setName } from './metadata.js';
6
- import { sendWebhookPostName } from '#consts';
2
+ import { proxyActivities, inWorkflowContext, executeChild, workflowInfo, ApplicationFailure } from '@temporalio/workflow';
3
+ import { getInvocationDir, invokeFnAndValidateOutputPreservingExecutionModel } from './utils.js';
4
+ import { setMetadata } from './metadata.js';
7
5
  import { FatalError, ValidationError } from '../errors.js';
6
+ import { validateWorkflow } from './validations/static.js';
7
+ import { validateWorkflowInput, validateWorkflowOutput } from './validations/runtime.js';
8
8
 
9
9
  const temporalActivityConfigs = {
10
10
  startToCloseTimeout: '20 minute',
@@ -17,53 +17,58 @@ const temporalActivityConfigs = {
17
17
  }
18
18
  };
19
19
 
20
- export function workflow( { name, description: _description, fn } ) {
20
+ export function workflow( { name, description, inputSchema, outputSchema, fn } ) {
21
+ validateWorkflow( { name, description, inputSchema, outputSchema, fn } );
21
22
  const workflowPath = getInvocationDir();
22
23
 
23
- // This wraps the actually function call so before being invoked it creates the activity proxies and export the context
24
+ const steps = proxyActivities( temporalActivityConfigs );
25
+
24
26
  const wrapper = async input => {
25
27
  try {
26
- const { workflowId } = workflowInfo();
28
+ if ( inputSchema ) {
29
+ validateWorkflowInput( name, inputSchema, input );
30
+ }
31
+
32
+ // this returns a plain function, for example, in unit tests
33
+ if ( !inWorkflowContext() ) {
34
+ if ( outputSchema ) {
35
+ return invokeFnAndValidateOutputPreservingExecutionModel( fn, input, validateWorkflowOutput.bind( null, name, outputSchema ) );
36
+ }
37
+ return fn( input );
38
+ }
27
39
 
28
- // enrich context information needed for tracking
29
40
  Object.assign( workflowInfo().memo, { workflowPath } );
30
41
 
31
- const proxies = proxyActivities( temporalActivityConfigs );
32
- const context = {
33
- steps: new Proxy( {}, {
34
- get( _target, name ) {
35
- return proxies[`${workflowPath}#${name}`];
36
- }
37
- } ),
38
- tools: {
39
- webhook: createWebhook( workflowId, proxies[sendWebhookPostName] )
40
- }
41
- };
42
+ // binds the methods called in the code that Webpack loader will add, they will exposed via "this"
43
+ const boundFn = fn.bind( {
44
+ invokeStep: async ( stepName, input ) => steps[`${workflowPath}#${stepName}`]( input ),
42
45
 
43
- return fn( input, context );
46
+ startWorkflow: async ( name, input ) => {
47
+ const { memo, workflowId, workflowType } = workflowInfo();
44
48
 
45
- } catch ( error ) {
46
- // IllegalStateError is thrown when temporal is not running and one try to access workflowInfo()
47
- if ( error instanceof IllegalStateError ) {
48
- return fn( input, { steps: null, workflowId: null } );
49
- }
49
+ // Checks if current memo has rootWorkflowId, which means current execution is already a child
50
+ // Then it sets the memory for the child execution passing along who's the original workflow is and its type
51
+ const workflowMemory = memo.rootWorkflowId ?
52
+ { parentWorkflowId: workflowId, rootWorkflowType: memo.rootWorkflowType, rootWorkflowId: memo.rootWorkflowId } :
53
+ { parentWorkflowId: workflowId, rootWorkflowId: workflowId, rootWorkflowType: workflowType };
50
54
 
51
- throw error;
55
+ return executeChild( name, { args: input ? [ input ] : [], memo: workflowMemory } );
56
+ }
57
+ } );
58
+
59
+ if ( outputSchema ) {
60
+ return invokeFnAndValidateOutputPreservingExecutionModel( boundFn, input, validateWorkflowOutput.bind( null, name, outputSchema ) );
61
+ }
62
+ return boundFn( input );
63
+ } catch ( error ) {
64
+ /*
65
+ * Any errors in the workflow will interrupt its execution since the workflow is designed to orchestrate and
66
+ * IOs should be made in steps
67
+ */
68
+ throw new ApplicationFailure( error.message, error.constructor.name );
52
69
  }
53
70
  };
54
71
 
55
- setName( wrapper, name );
72
+ setMetadata( wrapper, { name, description, inputSchema, outputSchema } );
56
73
  return wrapper;
57
74
  };
58
-
59
- export async function startWorkflow( name, { input } = {} ) {
60
- const { memo, workflowId, workflowType } = workflowInfo();
61
-
62
- // Checks if current memo has rootWorkflowId, which means current execution is already a child
63
- // Then it sets the memory for the child execution passing along who's the original workflow is and its type
64
- const workflowMemory = memo.rootWorkflowId ?
65
- { parentWorkflowId: workflowId, rootWorkflowType: memo.rootWorkflowType, rootWorkflowId: memo.rootWorkflowId } :
66
- { parentWorkflowId: workflowId, rootWorkflowId: workflowId, rootWorkflowType: workflowType };
67
-
68
- return executeChild( name, { args: input ? [ input ] : [], memo: workflowMemory } );
69
- }
@@ -1,17 +1,28 @@
1
- import { fetch } from 'undici';
2
1
  import { api as apiConfig } from '#configs';
2
+ import { FatalError } from '#errors';
3
3
 
4
- export async function sendWebhookPost( { url, workflowId, payload } ) {
5
- const res = await fetch( url, {
4
+ export const sendWebhookPost = async ( { url, workflowId, payload } ) => {
5
+ const request = fetch( url, {
6
6
  method: 'POST',
7
7
  headers: {
8
8
  'Content-Type': 'application/json',
9
9
  Authentication: `Basic ${apiConfig.authKey}`
10
10
  },
11
- body: JSON.stringify( { workflowId, payload } )
11
+ body: JSON.stringify( { workflowId, payload } ),
12
+ signal: AbortSignal.timeout( 5000 )
12
13
  } );
13
14
 
15
+ const res = await ( async () => {
16
+ try {
17
+ return await request;
18
+ } catch {
19
+ throw new FatalError( 'Webhook fail: timeout' );
20
+ }
21
+ } )();
22
+
14
23
  console.log( '[Core.SendWebhookPost]', res.status, res.statusText );
15
24
 
16
- return { error: !res.ok };
25
+ if ( !res.ok ) {
26
+ throw new FatalError( `Webhook fail: ${res.status}` );
27
+ }
17
28
  };