codehooks-js 1.3.6 → 1.3.7
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/workflow/engine.mjs +89 -49
package/package.json
CHANGED
package/workflow/engine.mjs
CHANGED
|
@@ -151,43 +151,37 @@ class StepsEngine extends EventEmitter {
|
|
|
151
151
|
* @throws {Error} If step execution fails
|
|
152
152
|
*/
|
|
153
153
|
async handleNextStep(stepsName, nextStep, newState, instanceId, options) {
|
|
154
|
+
|
|
155
|
+
// open the connection to the database
|
|
156
|
+
const connection = await Datastore.open();
|
|
157
|
+
|
|
158
|
+
// Handle single next step
|
|
159
|
+
StepsEngine.getInstance().emit('stepStarted', { workflowName: stepsName, step: nextStep, state: newState, instanceId });
|
|
160
|
+
|
|
161
|
+
// remove the _id from the newState
|
|
162
|
+
delete newState._id;
|
|
163
|
+
// increment the step count
|
|
164
|
+
newState.stepCount[nextStep] = (newState.stepCount[nextStep] || 0) + 1;
|
|
165
|
+
// Update the existing steps state in the database
|
|
166
|
+
newState = await connection.updateOne(StepsEngine.collectionName,
|
|
167
|
+
{ _id: instanceId },
|
|
168
|
+
{ $set: { ...newState, nextStep: nextStep, updatedAt: new Date().toISOString(), stepCount: newState.stepCount } });
|
|
169
|
+
|
|
170
|
+
StepsEngine.getInstance().emit('stateUpdated', { workflowName: stepsName, state: newState, instanceId });
|
|
171
|
+
|
|
154
172
|
try {
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
173
|
+
// Get the next step function
|
|
174
|
+
const func = StepsEngine.getInstance().getDefinition(stepsName, nextStep);
|
|
175
|
+
|
|
176
|
+
// Wrap the callback in a Promise to ensure proper async handling
|
|
177
|
+
await new Promise(async (resolve, reject) => {
|
|
178
|
+
// check if the step count is greater than the max step count
|
|
179
|
+
if (newState.stepCount[nextStep] > StepsEngine.maxStepCount) {
|
|
180
|
+
reject(new Error(`Step ${nextStep} has been executed ${newState.stepCount[nextStep]} times, which is greater than the maximum step count of ${StepsEngine.maxStepCount}`));
|
|
181
|
+
return;
|
|
161
182
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// Handle single next step
|
|
166
|
-
StepsEngine.getInstance().emit('stepStarted', { workflowName: stepsName, step: nextStep, state: newState, instanceId });
|
|
167
|
-
const connection = await Datastore.open();
|
|
168
|
-
// remove the _id from the newState
|
|
169
|
-
delete newState._id;
|
|
170
|
-
// increment the step count
|
|
171
|
-
newState.stepCount[nextStep] = (newState.stepCount[nextStep] || 0) + 1;
|
|
172
|
-
// Update the existing steps state in the database
|
|
173
|
-
newState = await connection.updateOne(StepsEngine.collectionName,
|
|
174
|
-
{ _id: instanceId },
|
|
175
|
-
{ $set: { ...newState, nextStep: nextStep, updatedAt: new Date().toISOString(), stepCount: newState.stepCount } });
|
|
176
|
-
|
|
177
|
-
StepsEngine.getInstance().emit('stateUpdated', { workflowName: stepsName, state: newState, instanceId });
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
// Get the next step function
|
|
181
|
-
const func = StepsEngine.getInstance().getDefinition(stepsName, nextStep);
|
|
182
|
-
|
|
183
|
-
// Wrap the callback in a Promise to ensure proper async handling
|
|
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
|
-
}
|
|
190
|
-
func({...newState, instanceId: newState._id}, async (nextStep, userState, options) => {
|
|
183
|
+
try {
|
|
184
|
+
await func({...newState, instanceId: newState._id}, async (nextStep, userState, options) => {
|
|
191
185
|
try {
|
|
192
186
|
// Protect system-level properties
|
|
193
187
|
const protectedState = {
|
|
@@ -197,7 +191,9 @@ class StepsEngine extends EventEmitter {
|
|
|
197
191
|
updatedAt: newState.updatedAt,
|
|
198
192
|
instanceId: newState.instanceId,
|
|
199
193
|
workflowName: newState.workflowName,
|
|
200
|
-
stepCount: newState.stepCount
|
|
194
|
+
stepCount: newState.stepCount,
|
|
195
|
+
parallelSteps: newState.parallelSteps,
|
|
196
|
+
previousStep: newState.nextStep
|
|
201
197
|
};
|
|
202
198
|
|
|
203
199
|
// Merge states with userState taking precedence, but protecting system fields
|
|
@@ -206,6 +202,27 @@ class StepsEngine extends EventEmitter {
|
|
|
206
202
|
...protectedState
|
|
207
203
|
};
|
|
208
204
|
|
|
205
|
+
// update the parallel steps metadata
|
|
206
|
+
if (mergedState.parallelSteps && mergedState.parallelSteps[mergedState.nextStep]) {
|
|
207
|
+
// get a fresh copy of the parallel steps
|
|
208
|
+
const fresh = await connection.findOne(StepsEngine.collectionName, { _id: instanceId });
|
|
209
|
+
fresh.parallelSteps[mergedState.nextStep].done = true;
|
|
210
|
+
fresh.parallelSteps[mergedState.nextStep].nextStep = nextStep;
|
|
211
|
+
delete fresh._id;
|
|
212
|
+
await connection.updateOne(StepsEngine.collectionName,
|
|
213
|
+
{ _id: instanceId },
|
|
214
|
+
{ $set: { ...fresh, parallelSteps: fresh.parallelSteps } });
|
|
215
|
+
// Check if all parallel steps are done
|
|
216
|
+
const allStepsDone = Object.values(fresh.parallelSteps).every(step => step.done);
|
|
217
|
+
if (!allStepsDone) {
|
|
218
|
+
console.debug('Waiting for other parallel steps to complete');
|
|
219
|
+
resolve();
|
|
220
|
+
return;
|
|
221
|
+
} else {
|
|
222
|
+
console.debug('All parallel steps are done');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
209
226
|
// If there is no next step, the workflow is completed
|
|
210
227
|
if (nextStep === null) {
|
|
211
228
|
delete mergedState._id;
|
|
@@ -220,16 +237,38 @@ class StepsEngine extends EventEmitter {
|
|
|
220
237
|
return;
|
|
221
238
|
}
|
|
222
239
|
|
|
223
|
-
// Enqueue the next step
|
|
224
|
-
|
|
225
|
-
|
|
240
|
+
// Enqueue the next step, single or parallel
|
|
241
|
+
if (Array.isArray(nextStep)) {
|
|
242
|
+
const now = new Date().toISOString();
|
|
243
|
+
const metadata = nextStep.reduce((acc, step) => {
|
|
244
|
+
acc[step] = { done: false, startTime: now };
|
|
245
|
+
return acc;
|
|
246
|
+
}, {});
|
|
247
|
+
const metadataDoc = await connection.updateOne(StepsEngine.collectionName,
|
|
248
|
+
{ _id: instanceId },
|
|
249
|
+
{ $set: { parallelSteps: metadata } });
|
|
250
|
+
//console.log('metadataDoc', metadataDoc);
|
|
251
|
+
// enqueue all steps in parallel
|
|
252
|
+
for (const step of nextStep) {
|
|
253
|
+
console.debug('enqueue step', step, instanceId);
|
|
254
|
+
await connection.enqueue(`${StepsEngine.queuePrefix}_${stepsName}_${step}`, {
|
|
255
|
+
stepsName: stepsName,
|
|
256
|
+
goto: step,
|
|
257
|
+
state: mergedState,
|
|
258
|
+
options: options,
|
|
259
|
+
instanceId: instanceId
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
console.debug('enqueue step', nextStep, instanceId);
|
|
264
|
+
await connection.enqueue(`${StepsEngine.queuePrefix}_${stepsName}_${nextStep}`, {
|
|
226
265
|
stepsName: stepsName,
|
|
227
266
|
goto: nextStep,
|
|
228
267
|
state: mergedState,
|
|
229
268
|
options: options,
|
|
230
269
|
instanceId: instanceId
|
|
231
270
|
});
|
|
232
|
-
|
|
271
|
+
}
|
|
233
272
|
StepsEngine.getInstance().emit('stepEnqueued', { workflowName: stepsName, step: nextStep, state: newState, instanceId });
|
|
234
273
|
resolve();
|
|
235
274
|
} catch (error) {
|
|
@@ -237,14 +276,15 @@ class StepsEngine extends EventEmitter {
|
|
|
237
276
|
reject(error);
|
|
238
277
|
}
|
|
239
278
|
});
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
catch (error) {
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('Error executing step function:', error);
|
|
281
|
+
reject(error);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.error('error in handleNextStep outer', error.message);
|
|
246
286
|
throw error;
|
|
247
|
-
}
|
|
287
|
+
}
|
|
248
288
|
}
|
|
249
289
|
|
|
250
290
|
/**
|
|
@@ -267,7 +307,7 @@ class StepsEngine extends EventEmitter {
|
|
|
267
307
|
app.worker(`${StepsEngine.queuePrefix}_${name}_${stepName}`, async function(req, res) {
|
|
268
308
|
try {
|
|
269
309
|
const { stepsName, goto, state, instanceId, options } = req.body.payload;
|
|
270
|
-
console.debug('
|
|
310
|
+
console.debug('dequeue step', stepName, instanceId);
|
|
271
311
|
const qid = await StepsEngine.getInstance().handleNextStep(stepsName, goto, state, instanceId, options);
|
|
272
312
|
} catch (error) {
|
|
273
313
|
const { stepsName, goto, state, instanceId, options } = req.body.payload;
|
|
@@ -393,7 +433,7 @@ class StepsEngine extends EventEmitter {
|
|
|
393
433
|
instanceId: instanceId
|
|
394
434
|
});
|
|
395
435
|
|
|
396
|
-
resolve({
|
|
436
|
+
resolve({ instanceId });
|
|
397
437
|
});
|
|
398
438
|
}
|
|
399
439
|
|