@outputai/core 0.9.2-next.d8fa1ad.0 → 0.9.3-next.01f20d3.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,8 +1,11 @@
1
1
  {
2
2
  "name": "@outputai/core",
3
- "version": "0.9.2-next.d8fa1ad.0",
3
+ "version": "0.9.3-next.01f20d3.0",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=24.15.0"
8
+ },
6
9
  "exports": {
7
10
  ".": {
8
11
  "types": "./src/index.d.ts",
@@ -8,6 +8,11 @@ export class Catalog {
8
8
  */
9
9
  workflows;
10
10
 
11
+ /**
12
+ * Object, where the key is each workflow name and each workflow alias, the value is the resolved workflow name.
13
+ */
14
+ workflowNames = {};
15
+
11
16
  constructor() {
12
17
  this.workflows = [];
13
18
  };
@@ -20,6 +25,11 @@ export class Catalog {
20
25
  */
21
26
  addWorkflow( workflow ) {
22
27
  this.workflows.push( workflow );
28
+ this.workflowNames[workflow.name] = workflow.name;
29
+ for ( const alias of workflow.aliases ) {
30
+ this.workflowNames[alias] = workflow.name;
31
+ }
32
+
23
33
  return this;
24
34
  }
25
35
  }
@@ -33,12 +33,12 @@ export class CatalogJob {
33
33
  /** Check if the currently running catalog has the same hash as instance. */
34
34
  async #checkCatalogIsTheSame( handle ) {
35
35
  log.info( 'Checking running catalog hash against worker hash...' );
36
- return this.#runCancellable( handle.query( 'get_hash' ) ).then( h => h === this.#catalogHash ).catch( e => {
36
+ return this.#runCancellable( handle.describe() ).then( d => d.memo?.hash === this.#catalogHash ).catch( e => {
37
37
  if ( e instanceof CancellationError ) {
38
38
  throw e;
39
39
  }
40
40
  if ( !( e instanceof WorkflowNotFoundError ) ) {
41
- log.warn( 'Error retrieving catalog hash', { error: e } );
41
+ log.warn( 'Error describing catalog workflow', { error: e } );
42
42
  }
43
43
  return false;
44
44
  } );
@@ -88,7 +88,11 @@ export class CatalogJob {
88
88
  taskQueue,
89
89
  workflowId: catalogId,
90
90
  workflowIdConflictPolicy: WorkflowIdConflictPolicy.FAIL,
91
- args: [ this.#catalog, this.#catalogHash ]
91
+ args: [ this.#catalog ],
92
+ memo: {
93
+ workflowNames: this.#catalog.workflowNames,
94
+ hash: this.#catalogHash
95
+ }
92
96
  };
93
97
 
94
98
  log.info( 'Starting catalog workflow...' );
@@ -8,7 +8,6 @@ const {
8
8
  describeMock,
9
9
  executeUpdateMock,
10
10
  mockLog,
11
- queryMock,
12
11
  taskQueue,
13
12
  workflowStartMock
14
13
  } = vi.hoisted( () => ( {
@@ -16,7 +15,6 @@ const {
16
15
  describeMock: vi.fn(),
17
16
  executeUpdateMock: vi.fn(),
18
17
  mockLog: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
19
- queryMock: vi.fn(),
20
18
  taskQueue: 'test-queue',
21
19
  workflowStartMock: vi.fn()
22
20
  } ) );
@@ -32,7 +30,7 @@ vi.mock( '@temporalio/client', async importOriginal => {
32
30
  return {
33
31
  workflow: {
34
32
  start: workflowStartMock,
35
- getHandle: () => ( { describe: describeMock, query: queryMock, executeUpdate: executeUpdateMock } )
33
+ getHandle: () => ( { describe: describeMock, executeUpdate: executeUpdateMock } )
36
34
  }
37
35
  };
38
36
  } )
@@ -41,8 +39,18 @@ vi.mock( '@temporalio/client', async importOriginal => {
41
39
 
42
40
  const mockConnection = {};
43
41
  const namespace = 'default';
44
- const catalog = { workflows: [], activities: {} };
42
+ const catalog = { workflows: [], workflowNames: { workflow: 'workflow', alias: 'workflow' }, activities: {} };
45
43
  const catalogHash = 'catalog-hash';
44
+ const startArguments = {
45
+ taskQueue,
46
+ workflowId: catalogId,
47
+ workflowIdConflictPolicy: WorkflowIdConflictPolicy.FAIL,
48
+ args: [ catalog ],
49
+ memo: {
50
+ workflowNames: catalog.workflowNames,
51
+ hash: catalogHash
52
+ }
53
+ };
46
54
 
47
55
  const createJob = () => new CatalogJob( { connection: mockConnection, namespace, catalog, catalogHash } );
48
56
 
@@ -55,38 +63,28 @@ describe( 'CatalogJob', () => {
55
63
  vi.clearAllMocks();
56
64
  describeMock.mockResolvedValue( { closeTime: '2024-01-01T00:00:00Z' } );
57
65
  executeUpdateMock.mockResolvedValue( undefined );
58
- queryMock.mockResolvedValue( catalogHash );
59
66
  workflowStartMock.mockResolvedValue( undefined );
60
67
  } );
61
68
 
62
69
  it( 'completes a previous running stale catalog before starting the new workflow', async () => {
63
- describeMock.mockResolvedValue( { closeTime: undefined } );
64
- queryMock.mockResolvedValue( 'old-catalog-hash' );
70
+ describeMock.mockResolvedValue( { closeTime: undefined, memo: { hash: 'old-catalog-hash' } } );
65
71
  const job = createJob();
66
72
 
67
73
  await job.run();
68
74
 
69
75
  expect( describeMock ).toHaveBeenCalled();
70
- expect( queryMock ).toHaveBeenCalledWith( 'get_hash' );
71
76
  expect( executeUpdateMock ).toHaveBeenCalledWith( 'complete', { args: [] } );
72
- expect( workflowStartMock ).toHaveBeenCalledWith( 'catalog', {
73
- taskQueue,
74
- workflowId: catalogId,
75
- workflowIdConflictPolicy: WorkflowIdConflictPolicy.FAIL,
76
- args: [ catalog, catalogHash ]
77
- } );
77
+ expect( workflowStartMock ).toHaveBeenCalledWith( 'catalog', startArguments );
78
78
  expect( job.error ).toBeNull();
79
79
  expect( job.running ).toBe( false );
80
80
  } );
81
81
 
82
82
  it( 'keeps the existing catalog workflow when the running hash matches', async () => {
83
- describeMock.mockResolvedValue( { closeTime: undefined } );
84
- queryMock.mockResolvedValue( catalogHash );
83
+ describeMock.mockResolvedValue( { closeTime: undefined, memo: { hash: catalogHash } } );
85
84
  const job = createJob();
86
85
 
87
86
  await job.run();
88
87
 
89
- expect( queryMock ).toHaveBeenCalledWith( 'get_hash' );
90
88
  expect( executeUpdateMock ).not.toHaveBeenCalled();
91
89
  expect( workflowStartMock ).not.toHaveBeenCalled();
92
90
  expect( mockLog.info ).toHaveBeenCalledWith( 'Current catalog workflow hash matches worker, restart skipped' );
@@ -100,14 +98,8 @@ describe( 'CatalogJob', () => {
100
98
  await job.run();
101
99
 
102
100
  expect( describeMock ).toHaveBeenCalled();
103
- expect( queryMock ).not.toHaveBeenCalled();
104
101
  expect( executeUpdateMock ).not.toHaveBeenCalled();
105
- expect( workflowStartMock ).toHaveBeenCalledWith( 'catalog', {
106
- taskQueue,
107
- workflowId: catalogId,
108
- workflowIdConflictPolicy: WorkflowIdConflictPolicy.FAIL,
109
- args: [ catalog, catalogHash ]
110
- } );
102
+ expect( workflowStartMock ).toHaveBeenCalledWith( 'catalog', startArguments );
111
103
  expect( mockLog.warn ).not.toHaveBeenCalled();
112
104
  } );
113
105
 
@@ -117,20 +109,13 @@ describe( 'CatalogJob', () => {
117
109
  await job.run();
118
110
 
119
111
  expect( describeMock ).toHaveBeenCalled();
120
- expect( queryMock ).not.toHaveBeenCalled();
121
112
  expect( executeUpdateMock ).not.toHaveBeenCalled();
122
- expect( workflowStartMock ).toHaveBeenCalledWith( 'catalog', {
123
- taskQueue,
124
- workflowId: catalogId,
125
- workflowIdConflictPolicy: WorkflowIdConflictPolicy.FAIL,
126
- args: [ catalog, catalogHash ]
127
- } );
113
+ expect( workflowStartMock ).toHaveBeenCalledWith( 'catalog', startArguments );
128
114
  } );
129
115
 
130
116
  it( 'warns and continues when describing or completing the previous catalog fails', async () => {
131
117
  const completeError = new Error( 'complete failed' );
132
- describeMock.mockResolvedValue( { closeTime: undefined } );
133
- queryMock.mockResolvedValue( 'old-catalog-hash' );
118
+ describeMock.mockResolvedValue( { closeTime: undefined, memo: { hash: 'old-catalog-hash' } } );
134
119
  executeUpdateMock.mockRejectedValue( completeError );
135
120
  const job = createJob();
136
121
 
@@ -143,15 +128,15 @@ describe( 'CatalogJob', () => {
143
128
 
144
129
  it( 'ignores an already-started error when the running catalog hash matches', async () => {
145
130
  const alreadyStartedError = new WorkflowExecutionAlreadyStartedError( 'already started', catalogId, 'catalog' );
146
- describeMock.mockRejectedValue( new WorkflowNotFoundError( 'not found' ) );
131
+ describeMock
132
+ .mockRejectedValueOnce( new WorkflowNotFoundError( 'not found' ) )
133
+ .mockResolvedValueOnce( { closeTime: undefined, memo: { hash: catalogHash } } );
147
134
  workflowStartMock.mockRejectedValue( alreadyStartedError );
148
- queryMock.mockResolvedValue( catalogHash );
149
135
  const job = createJob();
150
136
 
151
137
  await job.run();
152
138
 
153
139
  expect( workflowStartMock ).toHaveBeenCalledWith( 'catalog', expect.any( Object ) );
154
- expect( queryMock ).toHaveBeenCalledWith( 'get_hash' );
155
140
  expect( mockLog.info ).toHaveBeenCalledWith(
156
141
  'Ignoring start error: it failed because execution already started but catalog hash matches worker'
157
142
  );
@@ -175,9 +160,10 @@ describe( 'CatalogJob', () => {
175
160
  it( 'stores stale already-started errors and calls the error callback', async () => {
176
161
  const alreadyStartedError = new WorkflowExecutionAlreadyStartedError( 'already started', catalogId, 'catalog' );
177
162
  const onError = vi.fn();
178
- describeMock.mockRejectedValue( new WorkflowNotFoundError( 'not found' ) );
163
+ describeMock
164
+ .mockRejectedValueOnce( new WorkflowNotFoundError( 'not found' ) )
165
+ .mockResolvedValueOnce( { closeTime: undefined, memo: { hash: 'old-catalog-hash' } } );
179
166
  workflowStartMock.mockRejectedValue( alreadyStartedError );
180
- queryMock.mockResolvedValue( 'old-catalog-hash' );
181
167
  const job = createJob();
182
168
 
183
169
  job.onError( onError );
@@ -212,5 +212,11 @@ describe( 'createCatalog', () => {
212
212
 
213
213
  expect( catalog.workflows[0].aliases ).toEqual( [ 'flow1_old', 'flow1_legacy' ] );
214
214
  expect( catalog.workflows[1].aliases ).toEqual( [] );
215
+ expect( catalog.workflowNames ).toEqual( {
216
+ flow1: 'flow1',
217
+ flow1_old: 'flow1',
218
+ flow1_legacy: 'flow1',
219
+ flow2: 'flow2'
220
+ } );
215
221
  } );
216
222
  } );
@@ -7,15 +7,12 @@ import { defineQuery, setHandler, condition, defineUpdate } from '@temporalio/wo
7
7
  *
8
8
  * @param {object} catalog - The catalog information
9
9
  */
10
- export default async function catalogWorkflow( catalog, catalogHash ) {
10
+ export default async function catalogWorkflow( catalog ) {
11
11
  const state = { canEnd: false };
12
12
 
13
13
  // Returns the catalog
14
14
  setHandler( defineQuery( 'get' ), () => catalog );
15
15
 
16
- // Returns the catalog hash
17
- setHandler( defineQuery( 'get_hash' ), () => catalogHash );
18
-
19
16
  // Politely respond to a ping
20
17
  setHandler( defineQuery( 'ping' ), () => 'pong' );
21
18