@outputai/core 0.6.1-next.f67f4b9.0 → 0.6.1-next.f8d698e.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/core",
3
- "version": "0.6.1-next.f67f4b9.0",
3
+ "version": "0.6.1-next.f8d698e.0",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "exports": {
@@ -52,7 +52,7 @@
52
52
  "winston": "3.19.0"
53
53
  },
54
54
  "peerDependencies": {
55
- "zod": "^4.3.6"
55
+ "zod": ">=4.3 <5"
56
56
  },
57
57
  "devDependencies": {
58
58
  "zod": "4.3.6"
@@ -5,7 +5,7 @@ import { sinks } from './sinks.js';
5
5
  import { createCatalog } from './catalog_workflow/index.js';
6
6
  import { init as initTracing } from '#tracing';
7
7
  import { webpackConfigHook } from './bundler_options.js';
8
- import { initInterceptors } from './interceptors.js';
8
+ import { initInterceptors } from './interceptors/index.js';
9
9
  import { createChildLogger } from '#logger';
10
10
  import { registerShutdown } from './shutdown.js';
11
11
  import { startCatalog } from './start_catalog.js';
@@ -51,7 +51,7 @@ vi.mock( './catalog_workflow/index.js', () => ( { createCatalog: createCatalogMo
51
51
  vi.mock( './bundler_options.js', () => ( { webpackConfigHook: vi.fn() } ) );
52
52
 
53
53
  const initInterceptorsMock = vi.fn().mockReturnValue( [] );
54
- vi.mock( './interceptors.js', () => ( { initInterceptors: initInterceptorsMock } ) );
54
+ vi.mock( './interceptors/index.js', () => ( { initInterceptors: initInterceptorsMock } ) );
55
55
 
56
56
  const startCatalogMock = vi.fn().mockResolvedValue( undefined );
57
57
  vi.mock( './start_catalog.js', () => ( { startCatalog: startCatalogMock } ) );
@@ -1,7 +1,7 @@
1
1
  import { Context, activityInfo as activityInfoFn } from '@temporalio/activity';
2
2
  import { Storage } from '#async_storage';
3
3
  import * as Tracing from '#tracing';
4
- import { headersToObject } from '../sandboxed_utils.js';
4
+ import { headersToObject } from './headers.js';
5
5
  import { ACTIVITY_WRAPPER_VERSION_FIELD, BusEventType, METADATA_ACCESS_SYMBOL, Signal } from '#consts';
6
6
  import { activityHeartbeatEnabled, activityHeartbeatIntervalMs, namespace } from '../configs.js';
7
7
  import { messageBus } from '#bus';
@@ -29,37 +29,30 @@ const log = createChildLogger( 'ActivityInterceptor' );
29
29
  */
30
30
  export class ActivityExecutionInterceptor {
31
31
  constructor( { activities, workflows, connection } ) {
32
- this.activities = activities;
33
- this.workflowsMap = workflows.reduce( ( map, w ) => {
34
- map.set( w.name, w );
35
- for ( const alias of w.aliases ?? [] ) {
36
- map.set( alias, w );
37
- }
38
- return map;
39
- }, new Map() );
32
+ // convert activities{} object to a map: activityType:kind
33
+ this.activityKindMap = new Map( Object.entries( activities )
34
+ .map( ( [ type, fn ] ) => ( [ type, fn[METADATA_ACCESS_SYMBOL].type ] ) ) );
35
+
36
+ // convert workflows[] array to a map: workflowType/alias.n:path
37
+ this.workflowsPathMap = new Map( workflows.flatMap( ( { name, aliases, path } ) =>
38
+ [ name, ...aliases ?? [] ].map( a => ( [ a, path ] ) )
39
+ ) );
40
40
  this.connection = connection;
41
41
  };
42
42
 
43
- /**
44
- * Returns a workflow entry by its name or throws error
45
- * @param {string} workflowType
46
- * @returns {object} Workflow entry
47
- * @throws {Error}
48
- */
49
- getWorkflowEntry( workflowType ) {
50
- const workflowEntry = this.workflowsMap.get( workflowType );
51
- if ( !workflowEntry ) {
52
- throw new Error( `Activity interceptor: workflow "${workflowType}" not found in workflowsMap.` );
53
- }
54
- return workflowEntry;
55
- }
56
-
57
43
  async execute( input, next ) {
58
44
  const activityInfo = activityInfoFn();
59
45
  const { workflowExecution: { workflowId, runId }, activityId, activityType, workflowType } = activityInfo;
60
46
  const { traceInfo, workflowDetails } = headersToObject( input.headers );
61
- const { type: outputActivityKind } = this.activities?.[activityType]?.[METADATA_ACCESS_SYMBOL];
62
- const { path: workflowFilename } = this.getWorkflowEntry( workflowType );
47
+ const outputActivityKind = this.activityKindMap.get( activityType );
48
+ const workflowFilename = this.workflowsPathMap.get( workflowType );
49
+
50
+ if ( !outputActivityKind ) {
51
+ throw new Error( `Activity interceptor: activity "${activityType}" was not registered.` );
52
+ }
53
+ if ( !workflowFilename ) {
54
+ throw new Error( `Activity interceptor: workflow "${workflowType}" was not registered.` );
55
+ }
63
56
 
64
57
  const state = {
65
58
  heartbeat: null,
@@ -73,7 +73,7 @@ vi.mock( '#tracing', () => ( {
73
73
  addEventError: addEventErrorMock
74
74
  } ) );
75
75
 
76
- vi.mock( '../sandboxed_utils.js', () => ( {
76
+ vi.mock( './headers.js', () => ( {
77
77
  headersToObject: () => ( { traceInfo: traceInfoMock, workflowDetails: workflowDetailsMock } )
78
78
  } ) );
79
79
 
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ const toPayloadMock = vi.hoisted( () => vi.fn( value => ( { encoded: value } ) ) );
4
+ const fromPayloadMock = vi.hoisted( () => vi.fn( payload => payload.encoded ) );
5
+
6
+ vi.mock( '@temporalio/common', () => ( {
7
+ defaultPayloadConverter: {
8
+ toPayload: toPayloadMock,
9
+ fromPayload: fromPayloadMock
10
+ }
11
+ } ) );
12
+
13
+ describe( 'headers utils', () => {
14
+ beforeEach( () => {
15
+ vi.clearAllMocks();
16
+ } );
17
+
18
+ describe( 'memoToHeaders', () => {
19
+ it( 'converts memo entries into Temporal payload headers', async () => {
20
+ const { memoToHeaders } = await import( './headers.js' );
21
+ const memo = {
22
+ traceInfo: { runId: 'run-1' },
23
+ workflowDetails: { workflowId: 'workflow-1' }
24
+ };
25
+
26
+ const headers = memoToHeaders( memo );
27
+
28
+ expect( headers ).toEqual( {
29
+ traceInfo: { encoded: memo.traceInfo },
30
+ workflowDetails: { encoded: memo.workflowDetails }
31
+ } );
32
+ expect( toPayloadMock ).toHaveBeenCalledTimes( 2 );
33
+ expect( toPayloadMock ).toHaveBeenCalledWith( memo.traceInfo );
34
+ expect( toPayloadMock ).toHaveBeenCalledWith( memo.workflowDetails );
35
+ } );
36
+
37
+ it( 'returns an empty object for nullish memo', async () => {
38
+ const { memoToHeaders } = await import( './headers.js' );
39
+
40
+ expect( memoToHeaders() ).toEqual( {} );
41
+ expect( memoToHeaders( null ) ).toEqual( {} );
42
+ expect( toPayloadMock ).not.toHaveBeenCalled();
43
+ } );
44
+ } );
45
+
46
+ describe( 'headersToObject', () => {
47
+ it( 'converts Temporal payload headers into plain object values', async () => {
48
+ const { headersToObject } = await import( './headers.js' );
49
+ const headers = {
50
+ traceInfo: { encoded: { runId: 'run-1' } },
51
+ workflowDetails: { encoded: { workflowId: 'workflow-1' } }
52
+ };
53
+
54
+ const object = headersToObject( headers );
55
+
56
+ expect( object ).toEqual( {
57
+ traceInfo: headers.traceInfo.encoded,
58
+ workflowDetails: headers.workflowDetails.encoded
59
+ } );
60
+ expect( fromPayloadMock ).toHaveBeenCalledTimes( 2 );
61
+ expect( fromPayloadMock ).toHaveBeenCalledWith( headers.traceInfo );
62
+ expect( fromPayloadMock ).toHaveBeenCalledWith( headers.workflowDetails );
63
+ } );
64
+
65
+ it( 'returns an empty object for nullish headers', async () => {
66
+ const { headersToObject } = await import( './headers.js' );
67
+
68
+ expect( headersToObject() ).toEqual( {} );
69
+ expect( headersToObject( null ) ).toEqual( {} );
70
+ expect( fromPayloadMock ).not.toHaveBeenCalled();
71
+ } );
72
+ } );
73
+ } );
@@ -1,11 +1,11 @@
1
1
  import { dirname, join } from 'path';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { ActivityExecutionInterceptor } from './interceptors/activity.js';
3
+ import { ActivityExecutionInterceptor } from './activity.js';
4
4
 
5
5
  const __dirname = dirname( fileURLToPath( import.meta.url ) );
6
6
 
7
7
  export const initInterceptors = ( { activities, workflows, connection } ) => ( {
8
- workflowModules: [ join( __dirname, './interceptors/workflow.js' ) ],
8
+ workflowModules: [ join( __dirname, './workflow.js' ) ],
9
9
  activity: [
10
10
  () => ( {
11
11
  inbound: new ActivityExecutionInterceptor( { activities, workflows, connection } )
@@ -1,6 +1,6 @@
1
1
  // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
2
  import { workflowInfo, proxySinks, ApplicationFailure, ContinueAsNew, isCancellation } from '@temporalio/workflow';
3
- import { memoToHeaders } from '../sandboxed_utils.js';
3
+ import { memoToHeaders } from './headers.js';
4
4
  import { deepMerge } from '#utils';
5
5
  import { METADATA_ACCESS_SYMBOL, WorkflowSpecialOutput } from '#consts';
6
6
  // this is a dynamic generated file with activity configs overwrites
@@ -8,13 +8,9 @@ import stepOptions from '../temp/__activity_options.js';
8
8
  import { createWorkflowDetails } from '#internal_utils/temporal_context';
9
9
 
10
10
  /*
11
- This is not an AI comment!
12
-
13
- This interceptor adds information value from workflowInfo().memo as Activity invocation headers.
14
-
11
+ This interceptor adds Memo and serialized workflowInfo() to the Activity invocation headers.
15
12
  This is a strategy to share values between the workflow context and activity context.
16
-
17
- We also want to preserve existing headers that might have been inject somewhere else and
13
+ We also want to preserve existing headers that might have been inject somewhere else.
18
14
  */
19
15
  class HeadersInjectionInterceptor {
20
16
  async scheduleActivity( input, next ) {
@@ -61,7 +61,7 @@ vi.mock( '@temporalio/workflow', () => ( {
61
61
  } ) );
62
62
 
63
63
  const memoToHeadersMock = vi.fn( memo => ( memo ? { ...memo, __asHeaders: true } : {} ) );
64
- vi.mock( '../sandboxed_utils.js', () => ( { memoToHeaders: ( ...args ) => memoToHeadersMock( ...args ) } ) );
64
+ vi.mock( './headers.js', () => ( { memoToHeaders: ( ...args ) => memoToHeadersMock( ...args ) } ) );
65
65
 
66
66
  const deepMergeMock = vi.fn( ( a, b ) => ( { ...( a || {} ), ...( b || {} ) } ) );
67
67
  vi.mock( '#utils', () => ( { deepMerge: ( ...args ) => deepMergeMock( ...args ) } ) );