@outputai/core 0.8.1-next.e92f632.0 → 0.8.2-dev.e78f6b4.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.
Files changed (102) hide show
  1. package/package.json +9 -10
  2. package/src/bus.js +1 -1
  3. package/src/consts.js +5 -2
  4. package/src/helpers/component.js +12 -0
  5. package/src/helpers/component.spec.js +54 -0
  6. package/src/helpers/fetch.js +105 -0
  7. package/src/helpers/fetch.spec.js +203 -0
  8. package/src/helpers/function.js +15 -0
  9. package/src/helpers/function.spec.js +48 -0
  10. package/src/helpers/object.js +98 -0
  11. package/src/helpers/object.spec.js +377 -0
  12. package/src/helpers/promise.js +29 -0
  13. package/src/helpers/promise.spec.js +35 -0
  14. package/src/helpers/string.js +30 -0
  15. package/src/helpers/string.spec.js +64 -0
  16. package/src/interface/evaluator.js +14 -12
  17. package/src/interface/evaluator.spec.js +10 -6
  18. package/src/interface/index.d.ts +7 -6
  19. package/src/interface/index.js +2 -0
  20. package/src/interface/logger.d.ts +53 -0
  21. package/src/interface/logger.js +68 -0
  22. package/src/interface/logger.spec.js +138 -0
  23. package/src/interface/step.js +15 -12
  24. package/src/interface/step.spec.js +10 -6
  25. package/src/interface/webhook.d.ts +21 -2
  26. package/src/interface/workflow.js +85 -78
  27. package/src/interface/workflow.spec.js +11 -4
  28. package/src/internal_activities/index.js +38 -35
  29. package/src/internal_activities/index.spec.js +27 -4
  30. package/src/logger/development.js +2 -2
  31. package/src/logger/development.spec.js +19 -2
  32. package/src/logger/production.js +1 -1
  33. package/src/logger/production.spec.js +24 -5
  34. package/src/sdk/README.md +47 -0
  35. package/src/sdk/helpers/component_metadata.d.ts +17 -0
  36. package/src/sdk/helpers/component_metadata.js +6 -0
  37. package/src/sdk/helpers/component_metadata.spec.js +30 -0
  38. package/src/sdk/helpers/index.d.ts +12 -0
  39. package/src/sdk/helpers/index.js +3 -0
  40. package/src/sdk/helpers/objects.d.ts +51 -0
  41. package/src/sdk/helpers/objects.js +8 -0
  42. package/src/sdk/helpers/objects.spec.js +16 -0
  43. package/src/sdk/helpers/path.d.ts +11 -0
  44. package/src/sdk/helpers/path.js +32 -0
  45. package/src/{utils/resolve_invocation_dir.spec.js → sdk/helpers/path.spec.js} +9 -9
  46. package/src/sdk/runtime/context.d.ts +30 -0
  47. package/src/sdk/runtime/context.js +15 -0
  48. package/src/{activity_integration → sdk/runtime}/context.spec.js +5 -5
  49. package/src/sdk/runtime/events.d.ts +15 -0
  50. package/src/sdk/runtime/events.js +18 -0
  51. package/src/{activity_integration → sdk/runtime}/events.spec.js +8 -9
  52. package/src/sdk/runtime/index.d.ts +12 -0
  53. package/src/sdk/runtime/index.js +3 -0
  54. package/src/sdk/runtime/tracing.d.ts +46 -0
  55. package/src/sdk/runtime/tracing.js +11 -0
  56. package/src/tracing/processors/s3/redis_client.spec.js +0 -6
  57. package/src/tracing/processors/s3/s3_client.spec.js +0 -6
  58. package/src/tracing/trace_engine.js +1 -1
  59. package/src/worker/catalog_workflow/catalog_job.js +1 -1
  60. package/src/worker/catalog_workflow/index.spec.js +8 -11
  61. package/src/worker/configs.js +14 -1
  62. package/src/worker/configs.spec.js +34 -1
  63. package/src/worker/connection_monitor.js +3 -14
  64. package/src/worker/connection_monitor.spec.js +4 -21
  65. package/src/worker/global_functions.js +14 -0
  66. package/src/worker/global_functions.spec.js +55 -0
  67. package/src/worker/index.js +11 -3
  68. package/src/worker/index.spec.js +29 -1
  69. package/src/worker/interceptors/activity.js +2 -2
  70. package/src/worker/interceptors/workflow.js +3 -3
  71. package/src/worker/interceptors/workflow.spec.js +1 -1
  72. package/src/worker/loader/matchers.js +1 -1
  73. package/src/worker/loader/tools.js +1 -1
  74. package/src/worker/loader/tools.spec.js +1 -1
  75. package/src/worker/log_hooks.js +14 -0
  76. package/src/worker/log_hooks.spec.js +83 -2
  77. package/src/worker/sinks.js +7 -1
  78. package/src/worker/sinks.spec.js +203 -0
  79. package/src/activity_integration/context.d.ts +0 -23
  80. package/src/activity_integration/context.js +0 -18
  81. package/src/activity_integration/event_id_integration.spec.js +0 -52
  82. package/src/activity_integration/events.d.ts +0 -10
  83. package/src/activity_integration/events.js +0 -15
  84. package/src/activity_integration/index.d.ts +0 -9
  85. package/src/activity_integration/index.js +0 -3
  86. package/src/activity_integration/tracing.d.ts +0 -40
  87. package/src/activity_integration/tracing.js +0 -48
  88. package/src/utils/index.d.ts +0 -180
  89. package/src/utils/index.js +0 -2
  90. package/src/utils/resolve_invocation_dir.js +0 -34
  91. package/src/utils/utils.js +0 -334
  92. package/src/utils/utils.spec.js +0 -723
  93. /package/src/{internal_utils → helpers}/aggregations.js +0 -0
  94. /package/src/{internal_utils → helpers}/aggregations.spec.js +0 -0
  95. /package/src/{internal_utils → helpers}/errors.js +0 -0
  96. /package/src/{internal_utils → helpers}/errors.spec.js +0 -0
  97. /package/src/{internal_utils → helpers}/temporal_context.js +0 -0
  98. /package/src/{internal_utils → helpers}/temporal_context.spec.ts +0 -0
  99. /package/src/{internal_utils → helpers}/trace_info.js +0 -0
  100. /package/src/{internal_utils → helpers}/trace_info.spec.js +0 -0
  101. /package/src/{internal_utils → helpers}/workflow_context.js +0 -0
  102. /package/src/{internal_utils → helpers}/workflow_context.spec.js +0 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Additional structured fields attached to a log record.
3
+ */
4
+ export type LogMetadata = Record<string, unknown>;
5
+
6
+ /**
7
+ * Log an error (level 0)
8
+ * @param message Log message
9
+ * @param metadata Additional information to be displayed
10
+ */
11
+ export declare function error( message: string, metadata?: LogMetadata ) : void;
12
+
13
+ /**
14
+ * Log a warn (level 1)
15
+ * @param message Log message
16
+ * @param metadata Additional information to be displayed
17
+ */
18
+ export declare function warn( message: string, metadata?: LogMetadata ) : void;
19
+
20
+ /**
21
+ * Log an info (level 2)
22
+ * @param message Log message
23
+ * @param metadata Additional information to be displayed
24
+ */
25
+ export declare function info( message: string, metadata?: LogMetadata ) : void;
26
+
27
+ /**
28
+ * Log http (level 3)
29
+ * @param message Log message
30
+ * @param metadata Additional information to be displayed
31
+ */
32
+ export declare function http( message: string, metadata?: LogMetadata ) : void;
33
+
34
+ /**
35
+ * Log verbose (level 4)
36
+ * @param message Log message
37
+ * @param metadata Additional information to be displayed
38
+ */
39
+ export declare function verbose( message: string, metadata?: LogMetadata ) : void;
40
+
41
+ /**
42
+ * Log debug (level 5)
43
+ * @param message Log message
44
+ * @param metadata Additional information to be displayed
45
+ */
46
+ export declare function debug( message: string, metadata?: LogMetadata ) : void;
47
+
48
+ /**
49
+ * Log silly (level 6)
50
+ * @param message Log message
51
+ * @param metadata Additional information to be displayed
52
+ */
53
+ export declare function silly( message: string, metadata?: LogMetadata ) : void;
@@ -0,0 +1,68 @@
1
+ import { inWorkflowContext } from '@temporalio/workflow';
2
+ import { proxySinks } from '@temporalio/workflow';
3
+ import { ACTIVITY_LOGGER_SYMBOL } from '#consts';
4
+ import { isPlainObject } from '#helpers/object';
5
+
6
+ const reservedMetadataFields = new Set( [
7
+ // Winston fields
8
+ 'label',
9
+ 'level',
10
+ 'message',
11
+ 'metadata',
12
+ 'namespace',
13
+ 'splat',
14
+ 'stack',
15
+ 'timestamp',
16
+ // reserved fields enriched by us
17
+ 'workflowId',
18
+ 'workflowType',
19
+ 'runId',
20
+ 'activityId',
21
+ 'activityType',
22
+ 'service',
23
+ 'environment'
24
+ ] );
25
+
26
+ // This is inoffensive and can be used outside workflow sandbox
27
+ const sinks = proxySinks();
28
+
29
+ // Convert npm log levels to console levels
30
+ const levelToConsole = {
31
+ error: 'error',
32
+ warn: 'warn',
33
+ info: 'info',
34
+ http: 'log',
35
+ verbose: 'log',
36
+ debug: 'debug',
37
+ silly: 'log'
38
+ };
39
+
40
+ /** Drops reserved keys from object */
41
+ const removeReservedFields = obj => Object.fromEntries( Object.entries( obj ).filter( ( [ k ] ) => !reservedMetadataFields.has( k ) ) );
42
+
43
+ const log = ( level, message, metadata ) => {
44
+ const sanitized = {
45
+ message: String( message ),
46
+ ...( isPlainObject( metadata ) && { metadata: removeReservedFields( metadata ) } )
47
+ };
48
+
49
+ // When inside workflow, use sinks to send logs out
50
+ if ( inWorkflowContext() ) {
51
+ sinks.workflow.log( { level, ...sanitized } );
52
+ // When inside activities, use the global function to ship logs
53
+ } else if ( typeof globalThis[ACTIVITY_LOGGER_SYMBOL] === 'function' ) {
54
+ globalThis[ACTIVITY_LOGGER_SYMBOL]( { level, ...sanitized } );
55
+ // This fallback is used on unit tests
56
+ } else {
57
+ console[levelToConsole[level]]( sanitized.message, sanitized.metadata );
58
+ }
59
+ };
60
+
61
+ // Winston uses npm levels by default: https://github.com/winstonjs/winston#logging-levels
62
+ export const error = log.bind( null, 'error' );
63
+ export const warn = log.bind( null, 'warn' );
64
+ export const info = log.bind( null, 'info' );
65
+ export const http = log.bind( null, 'http' );
66
+ export const verbose = log.bind( null, 'verbose' );
67
+ export const debug = log.bind( null, 'debug' );
68
+ export const silly = log.bind( null, 'silly' );
@@ -0,0 +1,138 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { ACTIVITY_LOGGER_SYMBOL } from '#consts';
3
+
4
+ const workflowLogMock = vi.fn();
5
+ const proxySinksMock = vi.fn( () => ( {
6
+ workflow: {
7
+ log: workflowLogMock
8
+ }
9
+ } ) );
10
+ const inWorkflowContextMock = vi.fn( () => false );
11
+
12
+ vi.mock( '@temporalio/workflow', () => ( {
13
+ inWorkflowContext: inWorkflowContextMock,
14
+ proxySinks: proxySinksMock
15
+ } ) );
16
+
17
+ const logLevels = [
18
+ 'error',
19
+ 'warn',
20
+ 'info',
21
+ 'http',
22
+ 'verbose',
23
+ 'debug',
24
+ 'silly'
25
+ ];
26
+
27
+ const consoleMethodsByLevel = {
28
+ error: 'error',
29
+ warn: 'warn',
30
+ info: 'info',
31
+ http: 'log',
32
+ verbose: 'log',
33
+ debug: 'debug',
34
+ silly: 'log'
35
+ };
36
+
37
+ const loadLogger = async () => import( './logger.js' );
38
+
39
+ describe( 'interface/logger', () => {
40
+ beforeEach( () => {
41
+ vi.clearAllMocks();
42
+ inWorkflowContextMock.mockReturnValue( false );
43
+ delete globalThis[ACTIVITY_LOGGER_SYMBOL];
44
+ } );
45
+
46
+ afterEach( () => {
47
+ delete globalThis[ACTIVITY_LOGGER_SYMBOL];
48
+ vi.restoreAllMocks();
49
+ } );
50
+
51
+ it( 'proxies sinks once when the module is loaded', async () => {
52
+ await loadLogger();
53
+
54
+ expect( proxySinksMock ).toHaveBeenCalledTimes( 1 );
55
+ } );
56
+
57
+ it( 'logs every level through workflow sinks inside workflow context', async () => {
58
+ const logger = await loadLogger();
59
+ inWorkflowContextMock.mockReturnValue( true );
60
+
61
+ logLevels.forEach( level => {
62
+ logger[level]( `${level} message`, { requestId: level } );
63
+ } );
64
+
65
+ logLevels.forEach( ( level, index ) => {
66
+ const payload = {
67
+ level,
68
+ message: `${level} message`,
69
+ metadata: { requestId: level }
70
+ };
71
+
72
+ expect( workflowLogMock ).toHaveBeenNthCalledWith( index + 1, payload );
73
+ } );
74
+ } );
75
+
76
+ it( 'sanitizes messages and metadata before logging', async () => {
77
+ const logger = await loadLogger();
78
+ inWorkflowContextMock.mockReturnValue( true );
79
+
80
+ logger.info( 123, {
81
+ requestId: 'req-1',
82
+ level: 'error',
83
+ message: 'metadata message'
84
+ } );
85
+
86
+ expect( workflowLogMock ).toHaveBeenCalledWith( {
87
+ level: 'info',
88
+ message: '123',
89
+ metadata: { requestId: 'req-1' }
90
+ } );
91
+ } );
92
+
93
+ it( 'logs every level through the activity global logger when it is set outside workflow context', async () => {
94
+ const logger = await loadLogger();
95
+ const activityLoggerMock = vi.fn();
96
+ globalThis[ACTIVITY_LOGGER_SYMBOL] = activityLoggerMock;
97
+
98
+ logLevels.forEach( level => {
99
+ logger[level]( `${level} message`, { requestId: level } );
100
+ } );
101
+
102
+ logLevels.forEach( ( level, index ) => {
103
+ expect( activityLoggerMock ).toHaveBeenNthCalledWith( index + 1, {
104
+ level,
105
+ message: `${level} message`,
106
+ metadata: { requestId: level }
107
+ } );
108
+ } );
109
+ expect( workflowLogMock ).not.toHaveBeenCalled();
110
+ } );
111
+
112
+ it( 'logs every level to its native console method when no workflow or activity logger is available', async () => {
113
+ const logger = await loadLogger();
114
+ const consoleMocks = Object.fromEntries(
115
+ [ 'debug', 'error', 'info', 'log', 'warn' ].map( method => [
116
+ method,
117
+ vi.spyOn( console, method ).mockImplementation( () => {} )
118
+ ] )
119
+ );
120
+
121
+ logLevels.forEach( level => {
122
+ logger[level]( `${level} message`, { requestId: level } );
123
+ } );
124
+
125
+ logLevels.forEach( level => {
126
+ expect( consoleMocks[consoleMethodsByLevel[level]] ).toHaveBeenCalledWith(
127
+ `${level} message`,
128
+ { requestId: level }
129
+ );
130
+ } );
131
+ expect( consoleMocks.error ).toHaveBeenCalledTimes( 1 );
132
+ expect( consoleMocks.warn ).toHaveBeenCalledTimes( 1 );
133
+ expect( consoleMocks.info ).toHaveBeenCalledTimes( 1 );
134
+ expect( consoleMocks.debug ).toHaveBeenCalledTimes( 1 );
135
+ expect( consoleMocks.log ).toHaveBeenCalledTimes( 3 );
136
+ expect( workflowLogMock ).not.toHaveBeenCalled();
137
+ } );
138
+ } );
@@ -1,6 +1,5 @@
1
1
  import { StepValidator } from './validations/index.js';
2
- import { setMetadata } from '#utils';
3
- import { ComponentType } from '#consts';
2
+ import { createStep } from '#helpers/component';
4
3
 
5
4
  /**
6
5
  * Create a new step (activity flavor) and return a wrapper function around its fn handler
@@ -9,13 +8,17 @@ export function step( { name, description, inputSchema, outputSchema, fn, option
9
8
  StepValidator.validateDefinition( { name, description, inputSchema, outputSchema, fn, options } );
10
9
  const validator = new StepValidator( { name, inputSchema, outputSchema } );
11
10
 
12
- const wrapper = async input => {
13
- validator.validateInput( input );
14
- const output = await fn( input );
15
- validator.validateOutput( output );
16
- return output;
17
- };
18
-
19
- setMetadata( wrapper, { name, description, inputSchema, outputSchema, type: ComponentType.STEP, options } );
20
- return wrapper;
21
- };
11
+ return createStep( {
12
+ name,
13
+ description,
14
+ inputSchema,
15
+ outputSchema,
16
+ options,
17
+ handler: async input => {
18
+ validator.validateInput( input );
19
+ const output = await fn( input );
20
+ validator.validateOutput( output );
21
+ return output;
22
+ }
23
+ } );
24
+ }
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { ValidationError } from '#errors';
3
- import { ComponentType } from '#consts';
4
3
 
5
4
  const validateDefinitionMock = vi.hoisted( () => vi.fn() );
6
5
  const validateInputMock = vi.hoisted( () => vi.fn() );
7
6
  const validateOutputMock = vi.hoisted( () => vi.fn() );
8
7
  const validatorConstructorMock = vi.hoisted( () => vi.fn() );
8
+ const createStepMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
9
9
 
10
10
  vi.mock( './validations/index.js', () => {
11
11
  class StepValidator {
@@ -23,12 +23,16 @@ vi.mock( './validations/index.js', () => {
23
23
  return { StepValidator };
24
24
  } );
25
25
 
26
+ vi.mock( '#helpers/component', () => ( {
27
+ createStep: createStepMock
28
+ } ) );
29
+
26
30
  describe( 'step()', () => {
27
31
  beforeEach( () => {
28
32
  vi.clearAllMocks();
29
33
  } );
30
34
 
31
- it( 'validates the definition, creates a runtime validator, and attaches metadata', async () => {
35
+ it( 'validates the definition, creates a runtime validator, and creates a step component', async () => {
32
36
  const { step } = await import( './step.js' );
33
37
  const inputSchema = { safeParse: vi.fn() };
34
38
  const outputSchema = { safeParse: vi.fn() };
@@ -58,15 +62,15 @@ describe( 'step()', () => {
58
62
  outputSchema
59
63
  } );
60
64
 
61
- const [ metadataSymbol ] = Object.getOwnPropertySymbols( wrapper );
62
- expect( wrapper[metadataSymbol] ).toEqual( {
65
+ expect( createStepMock ).toHaveBeenCalledWith( {
63
66
  name: 'test_step',
64
67
  description: 'Test step',
65
68
  inputSchema,
66
69
  outputSchema,
67
- type: ComponentType.STEP,
68
- options
70
+ options,
71
+ handler: expect.any( Function )
69
72
  } );
73
+ expect( wrapper ).toBe( createStepMock.mock.calls[0][0].handler );
70
74
  } );
71
75
 
72
76
  it( 'validates input and output around the step function', async () => {
@@ -1,10 +1,29 @@
1
- import type { SerializedFetchResponse } from '../utils/index.d.ts';
2
-
3
1
  /**
4
2
  * Allowed HTTP methods for request helpers.
5
3
  */
6
4
  export type HttpMethod = 'HEAD' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
7
5
 
6
+ /** Represents a {Response} serialized to plain object */
7
+ export type SerializedFetchResponse = {
8
+ /** The response url */
9
+ url: string,
10
+
11
+ /** The response status code */
12
+ status: number,
13
+
14
+ /** The response status text */
15
+ statusText: string,
16
+
17
+ /** Flag indicating if the request succeeded */
18
+ ok: boolean,
19
+
20
+ /** Object with response headers */
21
+ headers: Record<string, string>,
22
+
23
+ /** Response body, either JSON, text, or an arrayBuffer converted to base64 */
24
+ body: object | string
25
+ };
26
+
8
27
  /**
9
28
  * Send an POST HTTP request to a URL, optionally with a payload, then wait for a webhook response.
10
29
  *
@@ -1,10 +1,12 @@
1
1
  // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
2
  import { proxyActivities, inWorkflowContext, executeChild, workflowInfo, uuid4, ParentClosePolicy } from '@temporalio/workflow';
3
3
  import { WorkflowValidator } from './validations/index.js';
4
- import { deepMerge, setMetadata, toUrlSafeBase64 } from '#utils';
5
- import { WorkflowContext } from '#internal_utils/workflow_context';
6
- import { TraceInfo } from '#internal_utils/trace_info';
4
+ import { toUrlSafeBase64 } from '#helpers/string';
5
+ import { WorkflowContext } from '#helpers/workflow_context';
6
+ import { TraceInfo } from '#helpers/trace_info';
7
+ import { deepMerge } from '#helpers/object';
7
8
  import { defaultOptions } from './workflow_activity_options.js';
9
+ import { createWorkflow } from '#helpers/component';
8
10
  import {
9
11
  ACTIVITY_WRAPPER_VERSION_FIELD,
10
12
  ACTIVITY_GET_TRACE_DESTINATIONS,
@@ -34,84 +36,89 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
34
36
  const { disableTrace, activityOptions } = deepMerge( defaultOptions, options );
35
37
  const validator = new WorkflowValidator( { name, inputSchema, outputSchema } );
36
38
 
37
- const wrapper = async ( input, extra = {} ) => {
38
- validator.validateInvocationOptions( extra );
39
-
40
- // this returns a plain function, for example, in unit tests
41
- if ( !inWorkflowContext() ) {
42
- validator.validateInput( input );
43
- const output = await fn( input, deepMerge( WorkflowContext.build(), extra?.context ) );
44
- validator.validateOutput( output );
45
- return output;
46
- }
39
+ return createWorkflow( {
40
+ name,
41
+ description,
42
+ inputSchema,
43
+ outputSchema,
44
+ options,
45
+ aliases,
46
+ handler: async ( input, extra = {} ) => {
47
+ validator.validateInvocationOptions( extra );
48
+
49
+ // this returns a plain function, for example, in unit tests
50
+ if ( !inWorkflowContext() ) {
51
+ validator.validateInput( input );
52
+ const output = await fn( input, deepMerge( WorkflowContext.build(), extra?.context ) );
53
+ validator.validateOutput( output );
54
+ return output;
55
+ }
47
56
 
48
- const { workflowId, workflowType, memo, root } = workflowInfo();
49
-
50
- // if the stack already includes this workflowId, means the workflow() function was called
51
- // from within a running workflow, meaning it is suppose to start a child workflow
52
- const isChild = Array.isArray( memo.stack ) ? memo.stack.includes( workflowId ) :
53
- checkChildFallback( { workflowType, aliases, name } );
54
-
55
- if ( isChild ) {
56
- const result = await executeChild( name, {
57
- args: undefined === input ? [] : [ input ],
58
- workflowId: `${workflowId}-${toUrlSafeBase64( uuid4() )}`,
59
- parentClosePolicy: ParentClosePolicy[extra?.detached ? 'ABANDON' : 'TERMINATE'],
60
- memo: {
61
- ...memo, // Preserve memo and mix activityOptions, if provided
62
- ...( extra?.activityOptions && {
63
- activityOptions: deepMerge( memo?.activityOptions ?? {}, extra?.activityOptions )
64
- } )
65
- }
66
- } );
67
- return result.output;
68
- }
57
+ const { workflowId, workflowType, memo, root } = workflowInfo();
58
+
59
+ // if the stack already includes this workflowId, means the workflow() function was called
60
+ // from within a running workflow, meaning it is suppose to start a child workflow
61
+ const isChild = Array.isArray( memo.stack ) ? memo.stack.includes( workflowId ) :
62
+ checkChildFallback( { workflowType, aliases, name } );
63
+
64
+ if ( isChild ) {
65
+ const result = await executeChild( name, {
66
+ args: undefined === input ? [] : [ input ],
67
+ workflowId: `${workflowId}-${toUrlSafeBase64( uuid4() )}`,
68
+ parentClosePolicy: ParentClosePolicy[extra?.detached ? 'ABANDON' : 'TERMINATE'],
69
+ memo: {
70
+ ...memo, // Preserve memo and mix activityOptions, if provided
71
+ ...( extra?.activityOptions && {
72
+ activityOptions: deepMerge( memo?.activityOptions ?? {}, extra?.activityOptions )
73
+ } )
74
+ }
75
+ } );
76
+ return result.output;
77
+ }
69
78
 
70
- const isRoot = !root;
79
+ const isRoot = !root;
71
80
 
72
- memo.stack = [ ...memo.stack ?? [], workflowId ];
73
- // Parent options have prevalence on nested calls, child will be overwritten
74
- memo.activityOptions = deepMerge( activityOptions, memo.activityOptions );
75
- // Trace info is only added in the root workflow
76
- if ( isRoot ) {
77
- memo.traceInfo = TraceInfo.build( { disableTrace } );
78
- }
81
+ memo.stack = [ ...memo.stack ?? [], workflowId ];
82
+ // Parent options have prevalence on nested calls, child will be overwritten
83
+ memo.activityOptions = deepMerge( activityOptions, memo.activityOptions );
84
+ // Trace info is only added in the root workflow
85
+ if ( isRoot ) {
86
+ memo.traceInfo = TraceInfo.build( { disableTrace } );
87
+ }
79
88
 
80
- const steps = proxyActivities( memo.activityOptions );
81
- const traceDest = isRoot && parseActivityOutput( await steps[ACTIVITY_GET_TRACE_DESTINATIONS]( memo.traceInfo ) );
82
-
83
- try {
84
- validator.validateInput( input );
85
-
86
- // Creates an activity caller based on a prefix
87
- const createCaller = prefix => async ( t, ...args ) => parseActivityOutput( await steps[`${prefix}#${t}`]( ...args ) );
88
-
89
- // This are functions used by the AST to replace direct activity (step/evaluator) calls
90
- const dispatchers = {
91
- invokeStep: createCaller( name ),
92
- invokeSharedStep: createCaller( SHARED_STEP_PREFIX ),
93
- invokeEvaluator: createCaller( name ),
94
- invokeSharedEvaluator: createCaller( SHARED_STEP_PREFIX )
95
- };
96
-
97
- // The workflow function execution with "this" set with the dispatchers
98
- const output = await fn.call( dispatchers, input, WorkflowContext.build() );
99
- validator.validateOutput( output );
100
-
101
- return {
102
- [WORKFLOW_WRAPPER_VERSION_FIELD]: 1,
103
- output,
104
- ...( traceDest && { trace: { destinations: traceDest } } )
105
- };
106
- } catch ( error ) {
107
- if ( isRoot && traceDest ) {
108
- // Append the trace destinations so it is carried to interceptor
109
- error[METADATA_ACCESS_SYMBOL] = { trace: { destinations: traceDest } };
89
+ const steps = proxyActivities( memo.activityOptions );
90
+ const traceDest = isRoot && parseActivityOutput( await steps[ACTIVITY_GET_TRACE_DESTINATIONS]( memo.traceInfo ) );
91
+
92
+ try {
93
+ validator.validateInput( input );
94
+
95
+ // Creates an activity caller based on a prefix
96
+ const createCaller = prefix => async ( t, ...args ) => parseActivityOutput( await steps[`${prefix}#${t}`]( ...args ) );
97
+
98
+ // This are functions used by the AST to replace direct activity (step/evaluator) calls
99
+ const dispatchers = {
100
+ invokeStep: createCaller( name ),
101
+ invokeSharedStep: createCaller( SHARED_STEP_PREFIX ),
102
+ invokeEvaluator: createCaller( name ),
103
+ invokeSharedEvaluator: createCaller( SHARED_STEP_PREFIX )
104
+ };
105
+
106
+ // The workflow function execution with "this" set with the dispatchers
107
+ const output = await fn.call( dispatchers, input, WorkflowContext.build() );
108
+ validator.validateOutput( output );
109
+
110
+ return {
111
+ [WORKFLOW_WRAPPER_VERSION_FIELD]: 1,
112
+ output,
113
+ ...( traceDest && { trace: { destinations: traceDest } } )
114
+ };
115
+ } catch ( error ) {
116
+ if ( isRoot && traceDest ) {
117
+ // Append the trace destinations so it is carried to interceptor
118
+ error[METADATA_ACCESS_SYMBOL] = { trace: { destinations: traceDest } };
119
+ }
120
+ throw error;
110
121
  }
111
- throw error;
112
122
  }
113
- };
114
-
115
- setMetadata( wrapper, { name, description, inputSchema, outputSchema, aliases } );
116
- return wrapper;
117
- };
123
+ } );
124
+ }
@@ -19,6 +19,7 @@ const validateInputMock = vi.hoisted( () => vi.fn() );
19
19
  const validateOutputMock = vi.hoisted( () => vi.fn() );
20
20
  const validateInvocationOptionsMock = vi.hoisted( () => vi.fn() );
21
21
  const validatorConstructorMock = vi.hoisted( () => vi.fn() );
22
+ const createWorkflowMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
22
23
 
23
24
  vi.mock( './validations/index.js', () => {
24
25
  class WorkflowValidator {
@@ -37,6 +38,10 @@ vi.mock( './validations/index.js', () => {
37
38
  return { WorkflowValidator };
38
39
  } );
39
40
 
41
+ vi.mock( '#helpers/component', () => ( {
42
+ createWorkflow: createWorkflowMock
43
+ } ) );
44
+
40
45
  vi.mock( '@temporalio/workflow', async importOriginal => {
41
46
  const actual = await importOriginal();
42
47
  return {
@@ -151,7 +156,7 @@ describe( 'workflow()', () => {
151
156
  expect( () => workflow( workflowDefinition( { name: 'invalid_name' } ) ) ).toThrow( error );
152
157
  } );
153
158
 
154
- it( 'attaches workflow metadata to the wrapper', async () => {
159
+ it( 'creates a workflow component with definition metadata', async () => {
155
160
  const { workflow } = await import( './workflow.js' );
156
161
  const inputSchema = z.object( { value: z.string() } );
157
162
  const outputSchema = z.object( { ok: z.boolean() } );
@@ -165,14 +170,16 @@ describe( 'workflow()', () => {
165
170
  fn: async () => ( { ok: true } )
166
171
  } ) );
167
172
 
168
- const [ metadataSymbol ] = Object.getOwnPropertySymbols( wf );
169
- expect( wf[metadataSymbol] ).toEqual( {
173
+ expect( createWorkflowMock ).toHaveBeenCalledWith( {
170
174
  name: 'metadata_wf',
171
175
  description: 'Metadata workflow',
172
176
  inputSchema,
173
177
  outputSchema,
174
- aliases: [ 'metadata_alias' ]
178
+ options: {},
179
+ aliases: [ 'metadata_alias' ],
180
+ handler: expect.any( Function )
175
181
  } );
182
+ expect( wf ).toBe( createWorkflowMock.mock.calls[0][0].handler );
176
183
  } );
177
184
 
178
185
  describe( 'outside Temporal workflow context', () => {