@outputai/core 0.9.2-next.40ae759.0 → 0.9.2-next.52c7f0a.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 +4 -1
- package/src/worker/catalog_workflow/catalog.js +10 -0
- package/src/worker/catalog_workflow/catalog_job.js +7 -3
- package/src/worker/catalog_workflow/catalog_job.spec.js +24 -38
- package/src/worker/catalog_workflow/index.spec.js +6 -0
- package/src/worker/catalog_workflow/workflow.js +1 -4
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/core",
|
|
3
|
-
"version": "0.9.2-next.
|
|
3
|
+
"version": "0.9.2-next.52c7f0a.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.
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|