codehooks-js 1.3.5 → 1.3.6
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 +1 -1
- package/types/index.d.ts +4 -0
- package/types/workflow/engine.d.mts +23 -0
- package/types/workflow/index.d.mts +1 -0
- package/workflow/engine.mjs +81 -17
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ declare class Codehooks {
|
|
|
67
67
|
configure(config: {
|
|
68
68
|
collectionName: string;
|
|
69
69
|
queuePrefix: string;
|
|
70
|
+
timeout: number;
|
|
70
71
|
}): void;
|
|
71
72
|
getDefinition(stepsName: string, stepName: string): Function;
|
|
72
73
|
validateStepDefinition(step: Function): void;
|
|
@@ -80,6 +81,9 @@ declare class Codehooks {
|
|
|
80
81
|
continue(stepsName: string, instanceId: string): Promise<{
|
|
81
82
|
qId: string;
|
|
82
83
|
}>;
|
|
84
|
+
continueAllTimedOut(): Promise<Array<{
|
|
85
|
+
qId: string;
|
|
86
|
+
}>>;
|
|
83
87
|
getStepsStatus(id: string): Promise<any>;
|
|
84
88
|
getInstances(filter: any): Promise<any[]>;
|
|
85
89
|
cancelSteps(id: string): Promise<any>;
|
|
@@ -11,6 +11,8 @@ declare class StepsEngine extends EventEmitter<[never]> {
|
|
|
11
11
|
static instance: any;
|
|
12
12
|
static collectionName: string;
|
|
13
13
|
static queuePrefix: string;
|
|
14
|
+
static timeout: number;
|
|
15
|
+
static maxStepCount: number;
|
|
14
16
|
/**
|
|
15
17
|
* Set the collection name for storing steps data
|
|
16
18
|
* @param {string} name - Collection name
|
|
@@ -23,6 +25,18 @@ declare class StepsEngine extends EventEmitter<[never]> {
|
|
|
23
25
|
* @throws {Error} If prefix is not a non-empty string
|
|
24
26
|
*/
|
|
25
27
|
static setQueuePrefix(prefix: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Set the timeout for steps jobs
|
|
30
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
31
|
+
* @throws {Error} If timeout is not a positive number
|
|
32
|
+
*/
|
|
33
|
+
static setTimeout(timeout: number): void;
|
|
34
|
+
/**
|
|
35
|
+
* Set the maximum step count for a steps workflow
|
|
36
|
+
* @param {number} maxStepCount - Maximum step count
|
|
37
|
+
* @throws {Error} If maxStepCount is not a positive number
|
|
38
|
+
*/
|
|
39
|
+
static setMaxStepCount(maxStepCount: number): void;
|
|
26
40
|
/**
|
|
27
41
|
* Get the singleton instance of StepsEngine
|
|
28
42
|
* @returns {StepsEngine} The singleton instance
|
|
@@ -35,10 +49,12 @@ declare class StepsEngine extends EventEmitter<[never]> {
|
|
|
35
49
|
* @param {Object} config - Configuration object
|
|
36
50
|
* @param {string} config.collectionName - Collection name
|
|
37
51
|
* @param {string} config.queuePrefix - Queue prefix
|
|
52
|
+
* @param {number} config.timeout - Timeout in milliseconds
|
|
38
53
|
*/
|
|
39
54
|
configure(config: {
|
|
40
55
|
collectionName: string;
|
|
41
56
|
queuePrefix: string;
|
|
57
|
+
timeout: number;
|
|
42
58
|
}): void;
|
|
43
59
|
/**
|
|
44
60
|
* Get the step definition for a specific step
|
|
@@ -111,6 +127,13 @@ declare class StepsEngine extends EventEmitter<[never]> {
|
|
|
111
127
|
continue(stepsName: string, instanceId: string): Promise<{
|
|
112
128
|
qId: string;
|
|
113
129
|
}>;
|
|
130
|
+
/**
|
|
131
|
+
* Continue all timed out steps instances
|
|
132
|
+
* @returns {Promise<Array<{qId: string}>>} Array of results containing queue IDs for continued workflows
|
|
133
|
+
*/
|
|
134
|
+
continueAllTimedOut(): Promise<Array<{
|
|
135
|
+
qId: string;
|
|
136
|
+
}>>;
|
|
114
137
|
/**
|
|
115
138
|
* Get the status of a steps instance
|
|
116
139
|
* @param {string} id - ID of the steps instance
|
package/workflow/engine.mjs
CHANGED
|
@@ -22,6 +22,8 @@ class StepsEngine extends EventEmitter {
|
|
|
22
22
|
// Configuration
|
|
23
23
|
static collectionName = 'workflowdata';
|
|
24
24
|
static queuePrefix = 'workflowqueue';
|
|
25
|
+
static timeout = 30000;
|
|
26
|
+
static maxStepCount = 3;
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Set the collection name for storing steps data
|
|
@@ -47,11 +49,36 @@ class StepsEngine extends EventEmitter {
|
|
|
47
49
|
StepsEngine.queuePrefix = prefix.trim();
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Set the timeout for steps jobs
|
|
54
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
55
|
+
* @throws {Error} If timeout is not a positive number
|
|
56
|
+
*/
|
|
57
|
+
static setTimeout(timeout) {
|
|
58
|
+
if (typeof timeout !== 'number' || timeout <= 0) {
|
|
59
|
+
throw new Error('Timeout must be a positive number');
|
|
60
|
+
}
|
|
61
|
+
StepsEngine.timeout = timeout;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Set the maximum step count for a steps workflow
|
|
66
|
+
* @param {number} maxStepCount - Maximum step count
|
|
67
|
+
* @throws {Error} If maxStepCount is not a positive number
|
|
68
|
+
*/
|
|
69
|
+
static setMaxStepCount(maxStepCount) {
|
|
70
|
+
if (typeof maxStepCount !== 'number' || maxStepCount <= 0) {
|
|
71
|
+
throw new Error('Maximum step count must be a positive number');
|
|
72
|
+
}
|
|
73
|
+
StepsEngine.maxStepCount = maxStepCount;
|
|
74
|
+
}
|
|
75
|
+
|
|
50
76
|
/**
|
|
51
77
|
* Configure the steps engine
|
|
52
78
|
* @param {Object} config - Configuration object
|
|
53
79
|
* @param {string} config.collectionName - Collection name
|
|
54
80
|
* @param {string} config.queuePrefix - Queue prefix
|
|
81
|
+
* @param {number} config.timeout - Timeout in milliseconds
|
|
55
82
|
*/
|
|
56
83
|
configure(config) {
|
|
57
84
|
if (config.collectionName) {
|
|
@@ -60,6 +87,12 @@ class StepsEngine extends EventEmitter {
|
|
|
60
87
|
if (config.queuePrefix) {
|
|
61
88
|
StepsEngine.setQueuePrefix(config.queuePrefix);
|
|
62
89
|
}
|
|
90
|
+
if (config.timeout) {
|
|
91
|
+
StepsEngine.setTimeout(config.timeout);
|
|
92
|
+
}
|
|
93
|
+
if (config.maxStepCount) {
|
|
94
|
+
StepsEngine.setMaxStepCount(config.maxStepCount);
|
|
95
|
+
}
|
|
63
96
|
}
|
|
64
97
|
|
|
65
98
|
/**
|
|
@@ -134,15 +167,14 @@ class StepsEngine extends EventEmitter {
|
|
|
134
167
|
const connection = await Datastore.open();
|
|
135
168
|
// remove the _id from the newState
|
|
136
169
|
delete newState._id;
|
|
170
|
+
// increment the step count
|
|
171
|
+
newState.stepCount[nextStep] = (newState.stepCount[nextStep] || 0) + 1;
|
|
137
172
|
// Update the existing steps state in the database
|
|
138
173
|
newState = await connection.updateOne(StepsEngine.collectionName,
|
|
139
174
|
{ _id: instanceId },
|
|
140
|
-
{ $set: { ...newState, nextStep: nextStep, updatedAt: new Date().toISOString() } });
|
|
141
|
-
|
|
142
|
-
StepsEngine.getInstance().emit('stateUpdated', { workflowName: stepsName, state: newState, instanceId });
|
|
143
|
-
|
|
144
|
-
|
|
175
|
+
{ $set: { ...newState, nextStep: nextStep, updatedAt: new Date().toISOString(), stepCount: newState.stepCount } });
|
|
145
176
|
|
|
177
|
+
StepsEngine.getInstance().emit('stateUpdated', { workflowName: stepsName, state: newState, instanceId });
|
|
146
178
|
|
|
147
179
|
try {
|
|
148
180
|
// Get the next step function
|
|
@@ -150,6 +182,11 @@ class StepsEngine extends EventEmitter {
|
|
|
150
182
|
|
|
151
183
|
// Wrap the callback in a Promise to ensure proper async handling
|
|
152
184
|
await new Promise((resolve, reject) => {
|
|
185
|
+
// check if the step count is greater than the max step count
|
|
186
|
+
if (newState.stepCount[nextStep] > StepsEngine.maxStepCount) {
|
|
187
|
+
reject(new Error(`Step ${nextStep} has been executed ${newState.stepCount[nextStep]} times, which is greater than the maximum step count of ${StepsEngine.maxStepCount}`));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
153
190
|
func({...newState, instanceId: newState._id}, async (nextStep, userState, options) => {
|
|
154
191
|
try {
|
|
155
192
|
// Protect system-level properties
|
|
@@ -159,7 +196,8 @@ class StepsEngine extends EventEmitter {
|
|
|
159
196
|
createdAt: newState.createdAt,
|
|
160
197
|
updatedAt: newState.updatedAt,
|
|
161
198
|
instanceId: newState.instanceId,
|
|
162
|
-
workflowName: newState.workflowName
|
|
199
|
+
workflowName: newState.workflowName,
|
|
200
|
+
stepCount: newState.stepCount
|
|
163
201
|
};
|
|
164
202
|
|
|
165
203
|
// Merge states with userState taking precedence, but protecting system fields
|
|
@@ -201,12 +239,6 @@ class StepsEngine extends EventEmitter {
|
|
|
201
239
|
});
|
|
202
240
|
});
|
|
203
241
|
} catch (error) {
|
|
204
|
-
console.error('Error in step function: '+nextStep, error.message);
|
|
205
|
-
StepsEngine.getInstance().emit('error', { workflowName: stepsName, step: nextStep, state: newState, instanceId, error: error.message });
|
|
206
|
-
const connection = await Datastore.open();
|
|
207
|
-
await connection.updateOne(StepsEngine.collectionName,
|
|
208
|
-
{ _id: instanceId },
|
|
209
|
-
{ $set: { lastError: error.message, updatedAt: new Date().toISOString() } });
|
|
210
242
|
throw error;
|
|
211
243
|
}
|
|
212
244
|
}
|
|
@@ -233,13 +265,18 @@ class StepsEngine extends EventEmitter {
|
|
|
233
265
|
if (stepName !== undefined) {
|
|
234
266
|
//console.log('registering queue for step', `${StepsEngine.queuePrefix}_${name}_${stepName}`);
|
|
235
267
|
app.worker(`${StepsEngine.queuePrefix}_${name}_${stepName}`, async function(req, res) {
|
|
236
|
-
try {
|
|
237
|
-
|
|
268
|
+
try {
|
|
238
269
|
const { stepsName, goto, state, instanceId, options } = req.body.payload;
|
|
239
270
|
console.debug('worker job', stepName, instanceId);
|
|
240
271
|
const qid = await StepsEngine.getInstance().handleNextStep(stepsName, goto, state, instanceId, options);
|
|
241
272
|
} catch (error) {
|
|
242
|
-
|
|
273
|
+
const { stepsName, goto, state, instanceId, options } = req.body.payload;
|
|
274
|
+
const connection = await Datastore.open();
|
|
275
|
+
await connection.updateOne(StepsEngine.collectionName,
|
|
276
|
+
{ _id: instanceId },
|
|
277
|
+
{ $set: { lastError: error, updatedAt: new Date().toISOString() } });
|
|
278
|
+
console.error('Error in function: ' + stepName, error);
|
|
279
|
+
StepsEngine.getInstance().emit('error', error);
|
|
243
280
|
} finally {
|
|
244
281
|
res.end();
|
|
245
282
|
}
|
|
@@ -277,7 +314,7 @@ class StepsEngine extends EventEmitter {
|
|
|
277
314
|
const connection = await Datastore.open();
|
|
278
315
|
// Create a new steps state in the database
|
|
279
316
|
const newState = await connection.insertOne(StepsEngine.collectionName,
|
|
280
|
-
{ ...initialState, nextStep: firstStepName, createdAt: new Date().toISOString(), workflowName: name });
|
|
317
|
+
{ ...initialState, nextStep: firstStepName, createdAt: new Date().toISOString(), workflowName: name, stepCount: {} });
|
|
281
318
|
const { _id: ID } = await connection.enqueue(`${StepsEngine.queuePrefix}_${name}_${firstStepName}`, {
|
|
282
319
|
stepsName: name,
|
|
283
320
|
goto: firstStepName,
|
|
@@ -339,7 +376,11 @@ class StepsEngine extends EventEmitter {
|
|
|
339
376
|
if (!state) {
|
|
340
377
|
throw new Error(`No steps found with instanceId: ${instanceId}`);
|
|
341
378
|
}
|
|
342
|
-
|
|
379
|
+
// update all step counts to 0
|
|
380
|
+
for (const step in state.stepCount) {
|
|
381
|
+
state.stepCount[step] = 0;
|
|
382
|
+
}
|
|
383
|
+
await connection.updateOne(StepsEngine.collectionName, { _id: instanceId }, { $set: { stepCount: state.stepCount } });
|
|
343
384
|
console.debug('continue state', state);
|
|
344
385
|
StepsEngine.getInstance().emit('workflowContinued', { stepsName, step: state.nextStep, instanceId });
|
|
345
386
|
|
|
@@ -356,6 +397,29 @@ class StepsEngine extends EventEmitter {
|
|
|
356
397
|
});
|
|
357
398
|
}
|
|
358
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Continue all timed out steps instances
|
|
402
|
+
* @returns {Promise<Array<{qId: string}>>} Array of results containing queue IDs for continued workflows
|
|
403
|
+
*/
|
|
404
|
+
async continueAllTimedOut() {
|
|
405
|
+
const db = await Datastore.open();
|
|
406
|
+
const timedOutWorkflows = await db.collection(StepsEngine.collectionName).find({nextStep: {$ne: null}}).toArray();
|
|
407
|
+
const now = new Date();
|
|
408
|
+
const results = [];
|
|
409
|
+
for (const workflow of timedOutWorkflows) {
|
|
410
|
+
const createdAt = new Date(workflow.createdAt);
|
|
411
|
+
const diffMillis = now.getTime() - createdAt.getTime();
|
|
412
|
+
if (diffMillis > StepsEngine.timeout) {
|
|
413
|
+
const diffMinutes = diffMillis / (1000 * 60);
|
|
414
|
+
console.log('Timed out:', workflow._id, workflow.nextStep, `(${diffMinutes.toFixed(1)} minutes old)`);
|
|
415
|
+
const result = await this.continue(workflow.workflowName, workflow._id);
|
|
416
|
+
console.log('Continued:', result._id);
|
|
417
|
+
results.push(result);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return results;
|
|
421
|
+
}
|
|
422
|
+
|
|
359
423
|
/**
|
|
360
424
|
* Get the status of a steps instance
|
|
361
425
|
* @param {string} id - ID of the steps instance
|