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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/workflow/engine.mjs +89 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codehooks-js",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "type": "module",
5
5
  "description": "Codehooks.io official library - provides express.JS like syntax",
6
6
  "main": "index.js",
@@ -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
- // Handle array of next steps
156
- if (Array.isArray(nextStep)) {
157
- const results = [];
158
- for (const step of nextStep) {
159
- const qid = await this.handleNextStep(stepsName, step, newState, instanceId, options);
160
- results.push(qid);
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
- return results;
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
- console.debug('enqueuing step', nextStep, instanceId);
225
- await connection.enqueue(`${StepsEngine.queuePrefix}_${stepsName}_${nextStep}`, {
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
- } catch (error) {
242
- throw error;
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('worker job', stepName, instanceId);
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({ qId: ID });
436
+ resolve({ instanceId });
397
437
  });
398
438
  }
399
439