@mastra/core 0.4.2 → 0.4.3-alpha.1

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.
@@ -1,8 +1,10 @@
1
1
  import { MastraBase } from './chunk-OZ4XVJ6F.js';
2
+ import { setTimeout } from 'node:timers/promises';
2
3
  import { context, trace } from '@opentelemetry/api';
4
+ import EventEmitter from 'node:events';
3
5
  import { get } from 'radash';
4
6
  import sift from 'sift';
5
- import { setup, createActor, assign, fromPromise } from 'xstate';
7
+ import { createActor, assign, fromPromise, setup } from 'xstate';
6
8
 
7
9
  // src/workflows/utils.ts
8
10
  function isErrorEvent(stateEvent) {
@@ -18,185 +20,144 @@ function getStepResult(result) {
18
20
  if (result?.status === "success") return result.output;
19
21
  return void 0;
20
22
  }
21
-
22
- // src/workflows/workflow.ts
23
- var Workflow = class extends MastraBase {
24
- name;
25
- triggerSchema;
26
- /** XState machine instance that orchestrates the workflow execution */
27
- #machine;
28
- /** XState actor instance that manages the workflow execution */
29
- #actor = null;
30
- #runId;
31
- #retryConfig;
32
- #mastra;
33
- // registers stepIds on `after` calls
34
- #afterStepStack = [];
35
- #lastStepStack = [];
36
- #stepGraph = { initial: [] };
37
- #stepSubscriberGraph = {};
38
- #steps = {};
39
- #onStepTransition = /* @__PURE__ */ new Set();
40
- #executionSpan;
41
- /**
42
- * Creates a new Workflow instance
43
- * @param name - Identifier for the workflow (not necessarily unique)
44
- * @param logger - Optional logger instance
45
- */
46
- constructor({ name, triggerSchema, retryConfig, mastra }) {
47
- super({ component: "WORKFLOW", name });
48
- this.name = name;
49
- this.#retryConfig = retryConfig;
50
- this.triggerSchema = triggerSchema;
51
- this.#runId = crypto.randomUUID();
52
- this.#mastra = mastra;
53
- if (mastra?.logger) {
54
- this.logger = mastra?.logger;
23
+ function getSuspendedPaths({
24
+ value,
25
+ path,
26
+ suspendedPaths
27
+ }) {
28
+ if (typeof value === "string") {
29
+ if (value === "suspended") {
30
+ suspendedPaths.add(path);
55
31
  }
56
- this.initializeMachine();
32
+ } else {
33
+ Object.keys(value).forEach(
34
+ (key) => getSuspendedPaths({ value: value[key], path: path ? `${path}.${key}` : key, suspendedPaths })
35
+ );
57
36
  }
58
- /**
59
- * Initializes the XState machine for the workflow
60
- *
61
- * Registers the machine's types, actions, actors, initial context, entry actions, initial state, and states
62
- * @returns The initialized machine
63
- */
64
- initializeMachine() {
65
- const machine = setup({
66
- types: {},
67
- delays: this.#makeDelayMap(),
68
- actions: this.#getDefaultActions(),
69
- actors: this.#getDefaultActors()
70
- }).createMachine({
71
- id: this.name,
72
- type: "parallel",
73
- context: ({ input }) => ({
74
- ...input
75
- }),
76
- states: this.#buildStateHierarchy(this.#stepGraph)
77
- });
78
- this.#machine = machine;
79
- return machine;
37
+ }
38
+ function isFinalState(status) {
39
+ return ["completed", "failed"].includes(status);
40
+ }
41
+ function recursivelyCheckForFinalState({
42
+ value,
43
+ suspendedPaths,
44
+ path
45
+ }) {
46
+ if (typeof value === "string") {
47
+ return isFinalState(value) || suspendedPaths.has(path);
80
48
  }
81
- step(step, config) {
82
- const { variables = {} } = config || {};
83
- const requiredData = {};
84
- for (const [key, variable] of Object.entries(variables)) {
85
- if (variable && isVariableReference(variable)) {
86
- requiredData[key] = variable;
87
- }
88
- }
89
- const stepKey = this.#makeStepKey(step);
90
- const graphEntry = {
91
- step,
92
- config: {
93
- ...this.#makeStepDef(stepKey),
94
- ...config,
95
- data: requiredData
96
- }
97
- };
98
- this.#steps[stepKey] = step;
99
- const parentStepKey = this.#afterStepStack[this.#afterStepStack.length - 1];
100
- const stepGraph = this.#stepSubscriberGraph[parentStepKey || ""];
101
- if (parentStepKey && stepGraph) {
102
- if (!stepGraph.initial.some((step2) => step2.step.id === stepKey)) {
103
- stepGraph.initial.push(graphEntry);
49
+ return Object.keys(value).every(
50
+ (key) => recursivelyCheckForFinalState({ value: value[key], suspendedPaths, path: path ? `${path}.${key}` : key })
51
+ );
52
+ }
53
+ function getActivePathsAndStatus(value) {
54
+ const paths = [];
55
+ const traverse = (current, path = []) => {
56
+ for (const [key, value2] of Object.entries(current)) {
57
+ const currentPath = [...path, key];
58
+ if (typeof value2 === "string") {
59
+ paths.push({
60
+ stepPath: currentPath,
61
+ stepId: key,
62
+ status: value2
63
+ });
64
+ } else if (typeof value2 === "object" && value2 !== null) {
65
+ traverse(value2, currentPath);
104
66
  }
105
- stepGraph[stepKey] = [];
106
- } else {
107
- if (!this.#stepGraph[stepKey]) this.#stepGraph[stepKey] = [];
108
- this.#stepGraph.initial.push(graphEntry);
109
67
  }
110
- this.#lastStepStack.push(stepKey);
111
- return this;
112
- }
113
- then(step, config) {
114
- const { variables = {} } = config || {};
115
- const requiredData = {};
116
- for (const [key, variable] of Object.entries(variables)) {
117
- if (variable && isVariableReference(variable)) {
118
- requiredData[key] = variable;
68
+ };
69
+ traverse(value);
70
+ return paths;
71
+ }
72
+ function mergeChildValue(startStepId, parent, child) {
73
+ const traverse = (current) => {
74
+ const obj = {};
75
+ for (const [key, value] of Object.entries(current)) {
76
+ if (key === startStepId) {
77
+ obj[key] = { ...child };
78
+ } else if (typeof value === "string") {
79
+ obj[key] = value;
80
+ } else if (typeof value === "object" && value !== null) {
81
+ obj[key] = traverse(value);
119
82
  }
120
83
  }
121
- const lastStepKey = this.#lastStepStack[this.#lastStepStack.length - 1];
122
- const stepKey = this.#makeStepKey(step);
123
- const graphEntry = {
124
- step,
125
- config: {
126
- ...this.#makeStepDef(stepKey),
127
- ...config,
128
- data: requiredData
129
- }
130
- };
131
- this.#steps[stepKey] = step;
132
- if (!lastStepKey) return this;
133
- const parentStepKey = this.#afterStepStack[this.#afterStepStack.length - 1];
134
- const stepGraph = this.#stepSubscriberGraph[parentStepKey || ""];
135
- if (parentStepKey && stepGraph && stepGraph[lastStepKey]) {
136
- stepGraph[lastStepKey].push(graphEntry);
84
+ return obj;
85
+ };
86
+ return traverse(parent);
87
+ }
88
+ var updateStepInHierarchy = (value, targetStepId) => {
89
+ const result = {};
90
+ for (const key of Object.keys(value)) {
91
+ const currentValue = value[key];
92
+ if (key === targetStepId) {
93
+ result[key] = "pending";
94
+ } else if (typeof currentValue === "object" && currentValue !== null) {
95
+ result[key] = updateStepInHierarchy(currentValue, targetStepId);
137
96
  } else {
138
- if (!this.#stepGraph[lastStepKey]) this.#stepGraph[lastStepKey] = [];
139
- this.#stepGraph[lastStepKey].push(graphEntry);
97
+ result[key] = currentValue;
140
98
  }
141
- return this;
142
- }
143
- after(step) {
144
- const stepKey = this.#makeStepKey(step);
145
- this.#afterStepStack.push(stepKey);
146
- if (!this.#stepSubscriberGraph[stepKey]) {
147
- this.#stepSubscriberGraph[stepKey] = { initial: [] };
148
- }
149
- return this;
150
99
  }
151
- /**
152
- * Executes the workflow with the given trigger data
153
- * @param triggerData - Initial data to start the workflow with
154
- * @returns Promise resolving to workflow results or rejecting with error
155
- * @throws Error if trigger schema validation fails
156
- */
157
- createRun() {
158
- const runId = crypto.randomUUID();
100
+ return result;
101
+ };
102
+ var Machine = class extends EventEmitter {
103
+ logger;
104
+ #mastra;
105
+ #workflowInstance;
106
+ #executionSpan;
107
+ #stepGraph;
108
+ #machine;
109
+ #runId;
110
+ #startStepId;
111
+ name;
112
+ #actor = null;
113
+ #steps = {};
114
+ #retryConfig;
115
+ constructor({
116
+ logger,
117
+ mastra,
118
+ workflowInstance,
119
+ executionSpan,
120
+ name,
121
+ runId,
122
+ steps,
123
+ stepGraph,
124
+ retryConfig,
125
+ startStepId
126
+ }) {
127
+ super();
128
+ this.#mastra = mastra;
129
+ this.#workflowInstance = workflowInstance;
130
+ this.#executionSpan = executionSpan;
131
+ this.logger = logger;
159
132
  this.#runId = runId;
160
- return {
161
- runId,
162
- start: async ({ triggerData } = {}) => this.execute({ triggerData })
163
- };
133
+ this.#startStepId = startStepId;
134
+ this.name = name;
135
+ this.#stepGraph = stepGraph;
136
+ this.#steps = steps;
137
+ this.#retryConfig = retryConfig;
138
+ this.initializeMachine();
139
+ }
140
+ get startStepId() {
141
+ return this.#startStepId;
164
142
  }
165
143
  async execute({
166
- triggerData,
167
- snapshot,
168
- runId,
169
- stepId
144
+ stepId,
145
+ input,
146
+ snapshot
170
147
  } = {}) {
171
- if (runId) {
172
- this.#runId = runId;
148
+ if (snapshot) {
173
149
  this.logger.debug(`Workflow snapshot received`, { runId: this.#runId, snapshot });
174
150
  }
175
- this.#executionSpan = this.#mastra?.telemetry?.tracer.startSpan(`workflow.${this.name}.execute`, {
176
- attributes: { componentName: this.name, runId: this.#runId }
177
- });
178
- const machineInput = snapshot ? snapshot.context : {
179
- // Maintain the original step results and their output
180
- steps: {},
181
- triggerData: triggerData || {},
182
- attempts: Object.keys(this.#steps).reduce(
183
- (acc, stepKey) => {
184
- acc[stepKey] = this.#steps[stepKey]?.retryConfig?.attempts || this.#retryConfig?.attempts || 3;
185
- return acc;
186
- },
187
- {}
188
- )
189
- };
190
- this.logger.debug(`Machine input prepared`, { runId: this.#runId, machineInput });
151
+ this.logger.debug(`Machine input prepared`, { runId: this.#runId, input });
191
152
  const actorSnapshot = snapshot ? {
192
153
  ...snapshot,
193
- context: machineInput
154
+ context: input
194
155
  } : void 0;
195
156
  this.logger.debug(`Creating actor with configuration`, {
196
- machineInput,
157
+ input,
197
158
  actorSnapshot,
198
- machineStates: this.#machine.config.states,
199
- runId: this.#runId
159
+ runId: this.#runId,
160
+ machineStates: this.#machine.config.states
200
161
  });
201
162
  this.#actor = createActor(this.#machine, {
202
163
  inspect: (inspectionEvent) => {
@@ -206,7 +167,7 @@ var Workflow = class extends MastraBase {
206
167
  runId: this.#runId
207
168
  });
208
169
  },
209
- input: machineInput,
170
+ input,
210
171
  snapshot: actorSnapshot
211
172
  });
212
173
  this.#actor.start();
@@ -224,25 +185,14 @@ var Workflow = class extends MastraBase {
224
185
  }
225
186
  const suspendedPaths = /* @__PURE__ */ new Set();
226
187
  this.#actor.subscribe(async (state) => {
227
- if (this.#onStepTransition) {
228
- this.#onStepTransition.forEach((onTransition) => {
229
- onTransition({
230
- runId: this.#runId,
231
- value: state.value,
232
- context: state.context,
233
- activePaths: this.#getActivePathsAndStatus(state.value),
234
- timestamp: Date.now()
235
- })?.catch(() => {
236
- });
237
- });
238
- }
239
- this.#getSuspendedPaths({
188
+ this.emit("state-update", this.#startStepId, state.value, state.context);
189
+ getSuspendedPaths({
240
190
  value: state.value,
241
191
  path: "",
242
192
  suspendedPaths
243
193
  });
244
194
  const allStatesValue = state.value;
245
- const allStatesComplete = this.#recursivelyCheckForFinalState({
195
+ const allStatesComplete = recursivelyCheckForFinalState({
246
196
  value: allStatesValue,
247
197
  suspendedPaths,
248
198
  path: ""
@@ -254,86 +204,298 @@ var Workflow = class extends MastraBase {
254
204
  });
255
205
  if (!allStatesComplete) return;
256
206
  try {
257
- await this.#persistWorkflowSnapshot();
207
+ await this.#workflowInstance.persistWorkflowSnapshot();
258
208
  this.#cleanup();
259
209
  this.#executionSpan?.end();
260
210
  resolve({
261
- triggerData,
262
- results: state.context.steps,
263
- runId: this.#runId
211
+ results: state.context.steps
264
212
  });
265
213
  } catch (error) {
266
214
  this.logger.debug("Failed to persist final snapshot", { error });
267
215
  this.#cleanup();
268
216
  this.#executionSpan?.end();
269
217
  resolve({
270
- triggerData,
271
- results: state.context.steps,
272
- runId: this.#runId
218
+ results: state.context.steps
273
219
  });
274
220
  }
275
221
  });
276
222
  });
277
223
  }
278
- /**
279
- * Rebuilds the machine with the current steps configuration and validates the workflow
280
- *
281
- * This is the last step of a workflow builder method chain
282
- * @throws Error if validation fails
283
- *
284
- * @returns this instance for method chaining
285
- */
286
- commit() {
287
- this.initializeMachine();
288
- return this;
289
- }
290
- // record all object paths that leads to a suspended state
291
- #getSuspendedPaths({
292
- value,
293
- path,
294
- suspendedPaths
295
- }) {
296
- if (typeof value === "string") {
297
- if (value === "suspended") {
298
- suspendedPaths.add(path);
299
- }
300
- } else {
301
- Object.keys(value).forEach(
302
- (key) => this.#getSuspendedPaths({ value: value[key], path: path ? `${path}.${key}` : key, suspendedPaths })
303
- );
224
+ #cleanup() {
225
+ if (this.#actor) {
226
+ this.#actor.stop();
227
+ this.#actor = null;
304
228
  }
229
+ this.removeAllListeners();
305
230
  }
306
- #isFinalState(status) {
307
- return ["completed", "failed"].includes(status);
308
- }
309
- #recursivelyCheckForFinalState({
310
- value,
311
- suspendedPaths,
312
- path
313
- }) {
314
- if (typeof value === "string") {
315
- return this.#isFinalState(value) || suspendedPaths.has(path);
316
- }
317
- return Object.keys(value).every(
318
- (key) => this.#recursivelyCheckForFinalState({ value: value[key], suspendedPaths, path: path ? `${path}.${key}` : key })
319
- );
231
+ #makeDelayMap() {
232
+ const delayMap = {};
233
+ Object.keys(this.#steps).forEach((stepId) => {
234
+ delayMap[stepId] = this.#steps[stepId]?.retryConfig?.delay || this.#retryConfig?.delay || 1e3;
235
+ });
236
+ return delayMap;
320
237
  }
321
- #buildBaseState(stepNode, nextSteps = []) {
322
- const nextStep = nextSteps.shift();
238
+ #getDefaultActions() {
323
239
  return {
324
- initial: "pending",
325
- on: {
326
- RESET_TO_PENDING: {
327
- target: ".pending"
328
- // Note the dot to target child state
240
+ updateStepResult: assign({
241
+ steps: ({ context, event }) => {
242
+ if (!isTransitionEvent(event)) return context.steps;
243
+ const { stepId, result } = event.output;
244
+ return {
245
+ ...context.steps,
246
+ [stepId]: {
247
+ status: "success",
248
+ output: result
249
+ }
250
+ };
251
+ }
252
+ }),
253
+ setStepError: assign({
254
+ steps: ({ context, event }, params) => {
255
+ if (!isErrorEvent(event)) return context.steps;
256
+ const { stepId } = params;
257
+ if (!stepId) return context.steps;
258
+ return {
259
+ ...context.steps,
260
+ [stepId]: {
261
+ status: "failed",
262
+ error: event.error.message
263
+ }
264
+ };
329
265
  }
266
+ }),
267
+ notifyStepCompletion: async (_, params) => {
268
+ const { stepId } = params;
269
+ this.logger.debug(`Step ${stepId} completed`);
330
270
  },
331
- states: {
332
- pending: {
333
- entry: () => {
334
- this.logger.debug(`Step ${stepNode.step.id} pending`, {
335
- stepId: stepNode.step.id,
336
- runId: this.#runId
271
+ snapshotStep: assign({
272
+ _snapshot: ({}, params) => {
273
+ const { stepId } = params;
274
+ return { stepId };
275
+ }
276
+ }),
277
+ persistSnapshot: async ({ context }) => {
278
+ if (context._snapshot) {
279
+ await this.#workflowInstance.persistWorkflowSnapshot();
280
+ }
281
+ return;
282
+ },
283
+ decrementAttemptCount: assign({
284
+ attempts: ({ context, event }, params) => {
285
+ if (!isTransitionEvent(event)) return context.attempts;
286
+ const { stepId } = params;
287
+ const attemptCount = context.attempts[stepId];
288
+ if (attemptCount === void 0) return context.attempts;
289
+ return { ...context.attempts, [stepId]: attemptCount - 1 };
290
+ }
291
+ })
292
+ };
293
+ }
294
+ #getDefaultActors() {
295
+ return {
296
+ resolverFunction: fromPromise(async ({ input }) => {
297
+ const { stepNode, context } = input;
298
+ const resolvedData = this.#resolveVariables({
299
+ stepConfig: stepNode.config,
300
+ context,
301
+ stepId: stepNode.step.id
302
+ });
303
+ this.logger.debug(`Resolved variables for ${stepNode.step.id}`, {
304
+ resolvedData,
305
+ runId: this.#runId
306
+ });
307
+ const result = await stepNode.config.handler({
308
+ context: resolvedData,
309
+ suspend: async () => {
310
+ await this.#workflowInstance.suspend(stepNode.step.id, this);
311
+ if (this.#actor) {
312
+ context.steps[stepNode.step.id] = {
313
+ status: "suspended"
314
+ };
315
+ this.logger.debug(`Sending SUSPENDED event for step ${stepNode.step.id}`);
316
+ this.#actor?.send({ type: "SUSPENDED", stepId: stepNode.step.id });
317
+ } else {
318
+ this.logger.debug(`Actor not available for step ${stepNode.step.id}`);
319
+ }
320
+ },
321
+ runId: this.#runId,
322
+ mastra: this.#mastra
323
+ });
324
+ this.logger.debug(`Step ${stepNode.step.id} result`, {
325
+ stepId: stepNode.step.id,
326
+ result,
327
+ runId: this.#runId
328
+ });
329
+ return {
330
+ stepId: stepNode.step.id,
331
+ result
332
+ };
333
+ }),
334
+ conditionCheck: fromPromise(async ({ input }) => {
335
+ const { context, stepNode } = input;
336
+ const stepConfig = stepNode.config;
337
+ const attemptCount = context.attempts[stepNode.step.id];
338
+ this.logger.debug(`Checking conditions for step ${stepNode.step.id}`, {
339
+ stepId: stepNode.step.id,
340
+ runId: this.#runId
341
+ });
342
+ this.logger.debug(`Attempt count for step ${stepNode.step.id}`, {
343
+ attemptCount,
344
+ attempts: context.attempts,
345
+ runId: this.#runId,
346
+ stepId: stepNode.step.id
347
+ });
348
+ if (!attemptCount || attemptCount < 0) {
349
+ if (stepConfig?.snapshotOnTimeout) {
350
+ return { type: "SUSPENDED", stepId: stepNode.step.id };
351
+ }
352
+ return { type: "CONDITION_FAILED", error: `Step:${stepNode.step.id} condition check failed` };
353
+ }
354
+ if (!stepConfig?.when) {
355
+ return { type: "CONDITIONS_MET" };
356
+ }
357
+ this.logger.debug(`Checking conditions for step ${stepNode.step.id}`, {
358
+ stepId: stepNode.step.id,
359
+ runId: this.#runId
360
+ });
361
+ if (typeof stepConfig?.when === "function") {
362
+ const conditionMet = await stepConfig.when({
363
+ context: {
364
+ ...context,
365
+ getStepResult: (stepId) => {
366
+ if (stepId === "trigger") {
367
+ return context.triggerData;
368
+ }
369
+ const result = context.steps[stepId];
370
+ if (result && result.status === "success") {
371
+ return result.output;
372
+ }
373
+ return void 0;
374
+ }
375
+ },
376
+ mastra: this.#mastra
377
+ });
378
+ if (conditionMet) {
379
+ this.logger.debug(`Condition met for step ${stepNode.step.id}`, {
380
+ stepId: stepNode.step.id,
381
+ runId: this.#runId
382
+ });
383
+ return { type: "CONDITIONS_MET" };
384
+ }
385
+ if (!attemptCount || attemptCount < 0) {
386
+ return { type: "CONDITION_FAILED", error: `Step:${stepNode.step.id} condition check failed` };
387
+ }
388
+ return { type: "WAITING", stepId: stepNode.step.id };
389
+ } else {
390
+ const conditionMet = this.#evaluateCondition(stepConfig.when, context);
391
+ if (!conditionMet) {
392
+ return {
393
+ type: "CONDITION_FAILED",
394
+ error: `Step:${stepNode.step.id} condition check failed`
395
+ };
396
+ }
397
+ }
398
+ return { type: "CONDITIONS_MET" };
399
+ }),
400
+ spawnSubscriberFunction: fromPromise(
401
+ async ({
402
+ input
403
+ }) => {
404
+ const { parentStepId, context } = input;
405
+ const result = await this.#workflowInstance.runMachine(parentStepId, context);
406
+ return Promise.resolve({ steps: result?.results });
407
+ }
408
+ )
409
+ };
410
+ }
411
+ #resolveVariables({
412
+ stepConfig,
413
+ context,
414
+ stepId
415
+ }) {
416
+ this.logger.debug(`Resolving variables for step ${stepId}`, {
417
+ stepId,
418
+ runId: this.#runId
419
+ });
420
+ const resolvedData = {
421
+ ...context,
422
+ getStepResult: (stepId2) => {
423
+ if (stepId2 === "trigger") {
424
+ return context.triggerData;
425
+ }
426
+ const result = context.steps[stepId2];
427
+ if (result && result.status === "success") {
428
+ return result.output;
429
+ }
430
+ return void 0;
431
+ }
432
+ };
433
+ for (const [key, variable] of Object.entries(stepConfig.data)) {
434
+ const sourceData = variable.step === "trigger" ? context.triggerData : getStepResult(context.steps[variable.step.id]);
435
+ this.logger.debug(
436
+ `Got source data for ${key} variable from ${variable.step === "trigger" ? "trigger" : variable.step.id}`,
437
+ {
438
+ sourceData,
439
+ path: variable.path,
440
+ runId: this.#runId
441
+ }
442
+ );
443
+ if (!sourceData && variable.step !== "trigger") {
444
+ resolvedData[key] = void 0;
445
+ continue;
446
+ }
447
+ const value = variable.path === "" || variable.path === "." ? sourceData : get(sourceData, variable.path);
448
+ this.logger.debug(`Resolved variable ${key}`, {
449
+ value,
450
+ runId: this.#runId
451
+ });
452
+ resolvedData[key] = value;
453
+ }
454
+ return resolvedData;
455
+ }
456
+ initializeMachine() {
457
+ const machine = setup({
458
+ types: {},
459
+ delays: this.#makeDelayMap(),
460
+ actions: this.#getDefaultActions(),
461
+ actors: this.#getDefaultActors()
462
+ }).createMachine({
463
+ id: this.name,
464
+ type: "parallel",
465
+ context: ({ input }) => ({
466
+ ...input
467
+ }),
468
+ states: this.#buildStateHierarchy(this.#stepGraph)
469
+ });
470
+ this.#machine = machine;
471
+ return machine;
472
+ }
473
+ #buildStateHierarchy(stepGraph) {
474
+ const states = {};
475
+ stepGraph.initial.forEach((stepNode) => {
476
+ const nextSteps = [...stepGraph[stepNode.step.id] || []];
477
+ states[stepNode.step.id] = {
478
+ ...this.#buildBaseState(stepNode, nextSteps)
479
+ };
480
+ });
481
+ return states;
482
+ }
483
+ #buildBaseState(stepNode, nextSteps = []) {
484
+ const nextStep = nextSteps.shift();
485
+ return {
486
+ initial: "pending",
487
+ on: {
488
+ RESET_TO_PENDING: {
489
+ target: ".pending"
490
+ // Note the dot to target child state
491
+ }
492
+ },
493
+ states: {
494
+ pending: {
495
+ entry: () => {
496
+ this.logger.debug(`Step ${stepNode.step.id} pending`, {
497
+ stepId: stepNode.step.id,
498
+ runId: this.#runId
337
499
  });
338
500
  },
339
501
  exit: () => {
@@ -466,30 +628,6 @@ var Workflow = class extends MastraBase {
466
628
  })
467
629
  })
468
630
  ]
469
- // after: {
470
- // [stepNode.step.id]: {
471
- // target: 'pending',
472
- // actions: [
473
- // assign({
474
- // attempts: ({ context }: { context: WorkflowContext }) => ({
475
- // ...context.attempts,
476
- // [stepNode.step.id]: this.#steps[stepNode.step.id]?.retryConfig?.attempts || 3,
477
- // }),
478
- // }),
479
- // ],
480
- // }
481
- // },
482
- // entry: () => {
483
- // this.logger.debug(`Step ${stepNode.step.id} suspended ${new Date().toISOString()}`);
484
- // },
485
- // exit: () => {
486
- // this.logger.debug(`Step ${stepNode.step.id} finished suspended ${new Date().toISOString()}`);
487
- // },
488
- // after: {
489
- // [stepNode.step.id]: {
490
- // target: 'suspended',
491
- // },
492
- // },
493
631
  },
494
632
  executing: {
495
633
  entry: () => {
@@ -600,400 +738,530 @@ var Workflow = class extends MastraBase {
600
738
  }
601
739
  };
602
740
  }
603
- #makeStepKey(step) {
604
- return `${step.id}`;
605
- }
606
- /**
607
- * Builds the state hierarchy for the workflow
608
- * @returns Object representing the state hierarchy
609
- */
610
- #buildStateHierarchy(stepGraph) {
611
- const states = {};
612
- stepGraph.initial.forEach((stepNode) => {
613
- const nextSteps = [...stepGraph[stepNode.step.id] || []];
614
- states[stepNode.step.id] = {
615
- ...this.#buildBaseState(stepNode, nextSteps)
616
- };
617
- });
618
- return states;
619
- }
620
- #getDefaultActions() {
621
- return {
622
- updateStepResult: assign({
623
- steps: ({ context, event }) => {
624
- if (!isTransitionEvent(event)) return context.steps;
625
- const { stepId, result } = event.output;
626
- return {
627
- ...context.steps,
628
- [stepId]: {
629
- status: "success",
630
- output: result
631
- }
632
- };
633
- }
634
- }),
635
- setStepError: assign({
636
- steps: ({ context, event }, params) => {
637
- if (!isErrorEvent(event)) return context.steps;
638
- const { stepId } = params;
639
- if (!stepId) return context.steps;
640
- return {
641
- ...context.steps,
642
- [stepId]: {
643
- status: "failed",
644
- error: event.error.message
645
- }
646
- };
647
- }
648
- }),
649
- notifyStepCompletion: async (_, params) => {
650
- const { stepId } = params;
651
- this.logger.debug(`Step ${stepId} completed`);
652
- },
653
- snapshotStep: assign({
654
- _snapshot: ({}, params) => {
655
- const { stepId } = params;
656
- return { stepId };
657
- }
658
- }),
659
- persistSnapshot: async ({ context }) => {
660
- if (context._snapshot) {
661
- return await this.#persistWorkflowSnapshot();
662
- }
663
- return;
664
- },
665
- decrementAttemptCount: assign({
666
- attempts: ({ context, event }, params) => {
667
- if (!isTransitionEvent(event)) return context.attempts;
668
- const { stepId } = params;
669
- const attemptCount = context.attempts[stepId];
670
- if (attemptCount === void 0) return context.attempts;
671
- return { ...context.attempts, [stepId]: attemptCount - 1 };
672
- }
673
- })
674
- };
741
+ #evaluateCondition(condition, context) {
742
+ let andBranchResult = true;
743
+ let baseResult = true;
744
+ let orBranchResult = true;
745
+ const simpleCondition = Object.entries(condition).find(([key]) => key.includes("."));
746
+ if (simpleCondition) {
747
+ const [key, queryValue] = simpleCondition;
748
+ const [stepId, ...pathParts] = key.split(".");
749
+ const path = pathParts.join(".");
750
+ const sourceData = stepId === "trigger" ? context.triggerData : getStepResult(context.steps[stepId]);
751
+ this.logger.debug(`Got condition data from step ${stepId}`, {
752
+ stepId,
753
+ sourceData,
754
+ runId: this.#runId
755
+ });
756
+ if (!sourceData) {
757
+ return false;
758
+ }
759
+ let value = get(sourceData, path);
760
+ if (stepId !== "trigger" && path === "status" && !value) {
761
+ value = "success";
762
+ }
763
+ if (typeof queryValue === "object" && queryValue !== null) {
764
+ baseResult = sift(queryValue)(value);
765
+ } else {
766
+ baseResult = value === queryValue;
767
+ }
768
+ }
769
+ if ("ref" in condition) {
770
+ const { ref, query } = condition;
771
+ const sourceData = ref.step === "trigger" ? context.triggerData : getStepResult(context.steps[ref.step.id]);
772
+ this.logger.debug(`Got condition data from ${ref.step === "trigger" ? "trigger" : ref.step.id}`, {
773
+ sourceData,
774
+ runId: this.#runId
775
+ });
776
+ if (!sourceData) {
777
+ return false;
778
+ }
779
+ let value = get(sourceData, ref.path);
780
+ if (ref.step !== "trigger" && ref.path === "status" && !value) {
781
+ value = "success";
782
+ }
783
+ baseResult = sift(query)(value);
784
+ }
785
+ if ("and" in condition) {
786
+ andBranchResult = condition.and.every((cond) => this.#evaluateCondition(cond, context));
787
+ this.logger.debug(`Evaluated AND condition`, {
788
+ andBranchResult,
789
+ runId: this.#runId
790
+ });
791
+ }
792
+ if ("or" in condition) {
793
+ orBranchResult = condition.or.some((cond) => this.#evaluateCondition(cond, context));
794
+ this.logger.debug(`Evaluated OR condition`, {
795
+ orBranchResult,
796
+ runId: this.#runId
797
+ });
798
+ }
799
+ const finalResult = baseResult && andBranchResult && orBranchResult;
800
+ this.logger.debug(`Evaluated condition`, {
801
+ finalResult,
802
+ runId: this.#runId
803
+ });
804
+ return finalResult;
805
+ }
806
+ getSnapshot() {
807
+ const snapshot = this.#actor?.getSnapshot();
808
+ return snapshot;
675
809
  }
676
- #getInjectables() {
810
+ };
811
+
812
+ // src/workflows/workflow-instance.ts
813
+ var WorkflowInstance = class {
814
+ name;
815
+ #mastra;
816
+ #machines = {};
817
+ logger;
818
+ #steps = {};
819
+ #stepGraph;
820
+ #stepSubscriberGraph = {};
821
+ #retryConfig;
822
+ #runId;
823
+ #state = null;
824
+ #executionSpan;
825
+ #onStepTransition = /* @__PURE__ */ new Set();
826
+ #onFinish;
827
+ // indexed by stepId
828
+ #suspendedMachines = {};
829
+ constructor({
830
+ name,
831
+ logger,
832
+ steps,
833
+ runId,
834
+ retryConfig,
835
+ mastra,
836
+ stepGraph,
837
+ stepSubscriberGraph,
838
+ onStepTransition,
839
+ onFinish
840
+ }) {
841
+ this.name = name;
842
+ this.logger = logger;
843
+ this.#steps = steps;
844
+ this.#stepGraph = stepGraph;
845
+ this.#stepSubscriberGraph = stepSubscriberGraph;
846
+ this.#retryConfig = retryConfig;
847
+ this.#mastra = mastra;
848
+ this.#runId = runId ?? crypto.randomUUID();
849
+ this.#onStepTransition = onStepTransition;
850
+ this.#onFinish = onFinish;
851
+ }
852
+ setState(state) {
853
+ this.#state = state;
854
+ }
855
+ get runId() {
856
+ return this.#runId;
857
+ }
858
+ get executionSpan() {
859
+ return this.#executionSpan;
860
+ }
861
+ async start({ triggerData } = {}) {
862
+ const results = await this.execute({ triggerData });
863
+ if (this.#onFinish) {
864
+ this.#onFinish();
865
+ }
677
866
  return {
678
- runId: this.#runId,
679
- mastra: this.#mastra
867
+ ...results,
868
+ runId: this.runId
680
869
  };
681
870
  }
682
- #getDefaultActors() {
683
- return {
684
- resolverFunction: fromPromise(async ({ input }) => {
685
- const { stepNode, context } = input;
686
- const injectables = this.#getInjectables();
687
- const resolvedData = this.#resolveVariables({
688
- stepConfig: stepNode.config,
689
- context,
690
- stepId: stepNode.step.id
691
- });
692
- this.logger.debug(`Resolved variables for ${stepNode.step.id}`, {
693
- resolvedData,
694
- runId: this.#runId
695
- });
696
- const result = await stepNode.config.handler({
697
- context: resolvedData,
698
- suspend: async () => {
699
- if (this.#actor) {
700
- context.steps[stepNode.step.id] = {
701
- status: "suspended"
702
- };
703
- await this.#persistWorkflowSnapshot();
704
- this.logger.debug(`Sending SUSPENDED event for step ${stepNode.step.id}`);
705
- this.#actor?.send({ type: "SUSPENDED", stepId: stepNode.step.id });
706
- } else {
707
- this.logger.debug(`Actor not available for step ${stepNode.step.id}`);
708
- }
709
- },
710
- ...injectables
711
- });
712
- this.logger.debug(`Step ${stepNode.step.id} result`, {
713
- stepId: stepNode.step.id,
714
- result,
715
- runId: this.#runId
716
- });
717
- return {
718
- stepId: stepNode.step.id,
719
- result
720
- };
721
- }),
722
- conditionCheck: fromPromise(async ({ input }) => {
723
- const { context, stepNode } = input;
724
- const stepConfig = stepNode.config;
725
- const attemptCount = context.attempts[stepNode.step.id];
726
- this.logger.debug(`Checking conditions for step ${stepNode.step.id}`, {
727
- stepId: stepNode.step.id,
728
- runId: this.#runId
729
- });
730
- this.logger.debug(`Attempt count for step ${stepNode.step.id}`, {
731
- attemptCount,
732
- attempts: context.attempts,
733
- runId: this.#runId,
734
- stepId: stepNode.step.id
735
- });
736
- if (!attemptCount || attemptCount < 0) {
737
- if (stepConfig?.snapshotOnTimeout) {
738
- return { type: "SUSPENDED", stepId: stepNode.step.id };
739
- }
740
- return { type: "CONDITION_FAILED", error: `Step:${stepNode.step.id} condition check failed` };
741
- }
742
- if (!stepConfig?.when) {
743
- return { type: "CONDITIONS_MET" };
744
- }
745
- this.logger.debug(`Checking conditions for step ${stepNode.step.id}`, {
746
- stepId: stepNode.step.id,
747
- runId: this.#runId
748
- });
749
- if (typeof stepConfig?.when === "function") {
750
- const conditionMet = await stepConfig.when({
751
- context: {
752
- ...context,
753
- getStepResult: (stepId) => {
754
- if (stepId === "trigger") {
755
- return context.triggerData;
756
- }
757
- const result = context.steps[stepId];
758
- if (result && result.status === "success") {
759
- return result.output;
760
- }
761
- return void 0;
762
- }
763
- },
764
- ...this.#getInjectables()
765
- });
766
- if (conditionMet) {
767
- this.logger.debug(`Condition met for step ${stepNode.step.id}`, {
768
- stepId: stepNode.step.id,
769
- runId: this.#runId
770
- });
771
- return { type: "CONDITIONS_MET" };
772
- }
773
- if (!attemptCount || attemptCount < 0) {
774
- return { type: "CONDITION_FAILED", error: `Step:${stepNode.step.id} condition check failed` };
775
- }
776
- return { type: "WAITING", stepId: stepNode.step.id };
777
- } else {
778
- const conditionMet = this.#evaluateCondition(stepConfig.when, context);
779
- if (!conditionMet) {
780
- return {
781
- type: "CONDITION_FAILED",
782
- error: `Step:${stepNode.step.id} condition check failed`
783
- };
784
- }
785
- }
786
- return { type: "CONDITIONS_MET" };
787
- }),
788
- spawnSubscriberFunction: fromPromise(
789
- async ({
790
- input
791
- }) => {
792
- const { parentStepId, context } = input;
793
- const stepGraph = this.#stepSubscriberGraph[parentStepId];
794
- if (!stepGraph) {
795
- return {
796
- steps: {}
797
- };
798
- }
799
- const subscriberMachine = setup({
800
- types: {},
801
- delays: this.#makeDelayMap(),
802
- actions: this.#getDefaultActions(),
803
- actors: this.#getDefaultActors()
804
- }).createMachine({
805
- id: `${this.name}-subscriber-${parentStepId}`,
871
+ async execute({
872
+ triggerData,
873
+ snapshot,
874
+ stepId
875
+ } = {}) {
876
+ this.#executionSpan = this.#mastra?.telemetry?.tracer.startSpan(`workflow.${this.name}.execute`, {
877
+ attributes: { componentName: this.name, runId: this.runId }
878
+ });
879
+ let machineInput = {
880
+ // Maintain the original step results and their output
881
+ steps: {},
882
+ triggerData: triggerData || {},
883
+ attempts: Object.keys(this.#steps).reduce(
884
+ (acc, stepKey) => {
885
+ acc[stepKey] = this.#steps[stepKey]?.retryConfig?.attempts || this.#retryConfig?.attempts || 3;
886
+ return acc;
887
+ },
888
+ {}
889
+ )
890
+ };
891
+ let stepGraph = this.#stepGraph;
892
+ let startStepId = "trigger";
893
+ if (snapshot) {
894
+ const runState = snapshot;
895
+ machineInput = runState.context;
896
+ if (stepId && runState?.suspendedSteps?.[stepId]) {
897
+ startStepId = runState.suspendedSteps[stepId];
898
+ stepGraph = this.#stepSubscriberGraph[startStepId] ?? this.#stepGraph;
899
+ }
900
+ }
901
+ const defaultMachine = new Machine({
902
+ logger: this.logger,
903
+ mastra: this.#mastra,
904
+ workflowInstance: this,
905
+ name: this.name,
906
+ runId: this.runId,
907
+ steps: this.#steps,
908
+ stepGraph,
909
+ executionSpan: this.#executionSpan,
910
+ startStepId
911
+ });
912
+ this.#machines[startStepId] = defaultMachine;
913
+ const stateUpdateHandler = (startStepId2, state, context) => {
914
+ if (startStepId2 === "trigger") {
915
+ this.#state = state;
916
+ } else {
917
+ this.#state = mergeChildValue(startStepId2, this.#state, state);
918
+ }
919
+ const now = Date.now();
920
+ if (this.#onStepTransition) {
921
+ this.#onStepTransition.forEach((onTransition) => {
922
+ void onTransition({
923
+ runId: this.#runId,
924
+ value: this.#state,
806
925
  context,
807
- type: "parallel",
808
- states: this.#buildStateHierarchy(stepGraph)
926
+ activePaths: getActivePathsAndStatus(this.#state),
927
+ timestamp: now
809
928
  });
810
- const actor = createActor(subscriberMachine, { input: context });
811
- actor.start();
812
- return new Promise((resolve) => {
813
- const suspendedPaths = /* @__PURE__ */ new Set();
814
- actor.subscribe((state) => {
815
- this.#getSuspendedPaths({
816
- value: state.value,
817
- path: "",
818
- suspendedPaths
819
- });
820
- const allStatesValue = state.value;
821
- const allStatesComplete = this.#recursivelyCheckForFinalState({
822
- value: allStatesValue,
823
- suspendedPaths,
824
- path: ""
825
- });
826
- if (allStatesComplete) {
827
- actor.stop();
828
- resolve({
829
- steps: state.context.steps
830
- });
831
- }
832
- });
929
+ });
930
+ }
931
+ };
932
+ defaultMachine.on("state-update", stateUpdateHandler);
933
+ const { results } = await defaultMachine.execute({ snapshot, stepId, input: machineInput });
934
+ await this.persistWorkflowSnapshot();
935
+ return { results };
936
+ }
937
+ async runMachine(parentStepId, input) {
938
+ if (!this.#stepSubscriberGraph[parentStepId]) {
939
+ return;
940
+ }
941
+ const stateUpdateHandler = (startStepId, state, context) => {
942
+ if (startStepId === "trigger") {
943
+ this.#state = state;
944
+ } else {
945
+ this.#state = mergeChildValue(startStepId, this.#state, state);
946
+ }
947
+ const now = Date.now();
948
+ if (this.#onStepTransition) {
949
+ this.#onStepTransition.forEach((onTransition) => {
950
+ void onTransition({
951
+ runId: this.#runId,
952
+ value: this.#state,
953
+ context,
954
+ activePaths: getActivePathsAndStatus(this.#state),
955
+ timestamp: now
833
956
  });
834
- }
957
+ });
958
+ }
959
+ };
960
+ const machine = new Machine({
961
+ logger: this.logger,
962
+ mastra: this.#mastra,
963
+ workflowInstance: this,
964
+ name: parentStepId === "trigger" ? this.name : `${this.name}-${parentStepId}`,
965
+ runId: this.runId,
966
+ steps: this.#steps,
967
+ stepGraph: this.#stepSubscriberGraph[parentStepId],
968
+ executionSpan: this.#executionSpan,
969
+ startStepId: parentStepId
970
+ });
971
+ machine.on("state-update", stateUpdateHandler);
972
+ this.#machines[parentStepId] = machine;
973
+ return await machine.execute({ input });
974
+ }
975
+ async suspend(stepId, machine) {
976
+ this.#suspendedMachines[stepId] = machine;
977
+ }
978
+ /**
979
+ * Persists the workflow state to the database
980
+ */
981
+ async persistWorkflowSnapshot() {
982
+ const existingSnapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
983
+ workflowName: this.name,
984
+ runId: this.#runId
985
+ });
986
+ const machineSnapshots = {};
987
+ for (const [stepId, machine] of Object.entries(this.#machines)) {
988
+ const machineSnapshot = machine?.getSnapshot();
989
+ if (machineSnapshot) {
990
+ machineSnapshots[stepId] = { ...machineSnapshot };
991
+ }
992
+ }
993
+ let snapshot = machineSnapshots["trigger"];
994
+ delete machineSnapshots["trigger"];
995
+ const suspendedSteps = Object.entries(this.#suspendedMachines).reduce(
996
+ (acc, [stepId, machine]) => {
997
+ acc[stepId] = machine.startStepId;
998
+ return acc;
999
+ },
1000
+ {}
1001
+ );
1002
+ if (!snapshot && existingSnapshot) {
1003
+ existingSnapshot.childStates = { ...existingSnapshot.childStates, ...machineSnapshots };
1004
+ existingSnapshot.suspendedSteps = { ...existingSnapshot.suspendedSteps, ...suspendedSteps };
1005
+ await this.#mastra?.storage?.persistWorkflowSnapshot({
1006
+ workflowName: this.name,
1007
+ runId: this.#runId,
1008
+ snapshot: existingSnapshot
1009
+ });
1010
+ return;
1011
+ } else if (snapshot && !existingSnapshot) {
1012
+ snapshot.suspendedSteps = suspendedSteps;
1013
+ snapshot.childStates = { ...machineSnapshots };
1014
+ await this.#mastra?.storage?.persistWorkflowSnapshot({
1015
+ workflowName: this.name,
1016
+ runId: this.#runId,
1017
+ snapshot
1018
+ });
1019
+ return;
1020
+ } else if (!snapshot) {
1021
+ this.logger.debug("Snapshot cannot be persisted. No snapshot received.", { runId: this.#runId });
1022
+ return;
1023
+ }
1024
+ snapshot.suspendedSteps = { ...existingSnapshot.suspendedSteps, ...suspendedSteps };
1025
+ if (!existingSnapshot || snapshot === existingSnapshot) {
1026
+ await this.#mastra?.storage?.persistWorkflowSnapshot({
1027
+ workflowName: this.name,
1028
+ runId: this.#runId,
1029
+ snapshot
1030
+ });
1031
+ return;
1032
+ }
1033
+ if (existingSnapshot?.childStates) {
1034
+ snapshot.childStates = { ...existingSnapshot.childStates, ...machineSnapshots };
1035
+ } else {
1036
+ snapshot.childStates = machineSnapshots;
1037
+ }
1038
+ await this.#mastra?.storage?.persistWorkflowSnapshot({
1039
+ workflowName: this.name,
1040
+ runId: this.#runId,
1041
+ snapshot
1042
+ });
1043
+ }
1044
+ async getState() {
1045
+ const storedSnapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
1046
+ workflowName: this.name,
1047
+ runId: this.runId
1048
+ });
1049
+ const prevSnapshot = storedSnapshot ? {
1050
+ trigger: storedSnapshot,
1051
+ ...Object.entries(storedSnapshot?.childStates ?? {}).reduce(
1052
+ (acc, [stepId, snapshot2]) => ({ ...acc, [stepId]: snapshot2 }),
1053
+ {}
835
1054
  )
1055
+ } : {};
1056
+ const currentSnapshot = Object.entries(this.#machines).reduce(
1057
+ (acc, [stepId, machine]) => {
1058
+ const snapshot2 = machine.getSnapshot();
1059
+ if (!snapshot2) {
1060
+ return acc;
1061
+ }
1062
+ return {
1063
+ ...acc,
1064
+ [stepId]: snapshot2
1065
+ };
1066
+ },
1067
+ {}
1068
+ );
1069
+ Object.assign(prevSnapshot, currentSnapshot);
1070
+ const trigger = prevSnapshot.trigger;
1071
+ delete prevSnapshot.trigger;
1072
+ const snapshot = { ...trigger};
1073
+ const m = getActivePathsAndStatus(prevSnapshot.value);
1074
+ return {
1075
+ runId: this.runId,
1076
+ value: snapshot.value,
1077
+ context: snapshot.context,
1078
+ activePaths: m,
1079
+ timestamp: Date.now()
1080
+ };
1081
+ }
1082
+ };
1083
+
1084
+ // src/workflows/workflow.ts
1085
+ var Workflow = class extends MastraBase {
1086
+ name;
1087
+ triggerSchema;
1088
+ #retryConfig;
1089
+ #mastra;
1090
+ #runs = /* @__PURE__ */ new Map();
1091
+ // registers stepIds on `after` calls
1092
+ #afterStepStack = [];
1093
+ #lastStepStack = [];
1094
+ #stepGraph = { initial: [] };
1095
+ #stepSubscriberGraph = {};
1096
+ #steps = {};
1097
+ #onStepTransition = /* @__PURE__ */ new Set();
1098
+ /**
1099
+ * Creates a new Workflow instance
1100
+ * @param name - Identifier for the workflow (not necessarily unique)
1101
+ * @param logger - Optional logger instance
1102
+ */
1103
+ constructor({ name, triggerSchema, retryConfig, mastra }) {
1104
+ super({ component: "WORKFLOW", name });
1105
+ this.name = name;
1106
+ this.#retryConfig = retryConfig;
1107
+ this.triggerSchema = triggerSchema;
1108
+ this.#mastra = mastra;
1109
+ if (mastra?.logger) {
1110
+ this.logger = mastra?.logger;
1111
+ }
1112
+ }
1113
+ step(step, config) {
1114
+ const { variables = {} } = config || {};
1115
+ const requiredData = {};
1116
+ for (const [key, variable] of Object.entries(variables)) {
1117
+ if (variable && isVariableReference(variable)) {
1118
+ requiredData[key] = variable;
1119
+ }
1120
+ }
1121
+ const stepKey = this.#makeStepKey(step);
1122
+ const graphEntry = {
1123
+ step,
1124
+ config: {
1125
+ ...this.#makeStepDef(stepKey),
1126
+ ...config,
1127
+ data: requiredData
1128
+ }
836
1129
  };
837
- }
838
- /**
839
- * Persists the workflow state to the database
840
- */
841
- async #persistWorkflowSnapshot() {
842
- const snapshotFromActor = this.#actor?.getPersistedSnapshot();
843
- if (!this.#mastra?.storage) {
844
- this.logger.debug("Snapshot cannot be persisted. Mastra engine is not initialized", {
845
- runId: this.#runId
846
- });
847
- return;
1130
+ this.#steps[stepKey] = step;
1131
+ const parentStepKey = this.#afterStepStack[this.#afterStepStack.length - 1];
1132
+ const stepGraph = this.#stepSubscriberGraph[parentStepKey || ""];
1133
+ if (parentStepKey && stepGraph) {
1134
+ if (!stepGraph.initial.some((step2) => step2.step.id === stepKey)) {
1135
+ stepGraph.initial.push(graphEntry);
1136
+ }
1137
+ stepGraph[stepKey] = [];
1138
+ } else {
1139
+ if (!this.#stepGraph[stepKey]) this.#stepGraph[stepKey] = [];
1140
+ this.#stepGraph.initial.push(graphEntry);
848
1141
  }
849
- if (!snapshotFromActor) {
850
- this.logger.debug("Snapshot cannot be persisted. No snapshot received.", { runId: this.#runId });
851
- return;
1142
+ this.#lastStepStack.push(stepKey);
1143
+ return this;
1144
+ }
1145
+ #makeStepKey(step) {
1146
+ return `${step.id}`;
1147
+ }
1148
+ then(step, config) {
1149
+ const { variables = {} } = config || {};
1150
+ const requiredData = {};
1151
+ for (const [key, variable] of Object.entries(variables)) {
1152
+ if (variable && isVariableReference(variable)) {
1153
+ requiredData[key] = variable;
1154
+ }
852
1155
  }
853
- if (this.#mastra?.storage) {
854
- await this.#mastra.storage.persistWorkflowSnapshot({
855
- workflowName: this.name,
856
- runId: this.#runId,
857
- snapshot: snapshotFromActor
858
- });
1156
+ const lastStepKey = this.#lastStepStack[this.#lastStepStack.length - 1];
1157
+ const stepKey = this.#makeStepKey(step);
1158
+ const graphEntry = {
1159
+ step,
1160
+ config: {
1161
+ ...this.#makeStepDef(stepKey),
1162
+ ...config,
1163
+ data: requiredData
1164
+ }
1165
+ };
1166
+ this.#steps[stepKey] = step;
1167
+ if (!lastStepKey) return this;
1168
+ const parentStepKey = this.#afterStepStack[this.#afterStepStack.length - 1];
1169
+ const stepGraph = this.#stepSubscriberGraph[parentStepKey || ""];
1170
+ if (parentStepKey && stepGraph && stepGraph[lastStepKey]) {
1171
+ stepGraph[lastStepKey].push(graphEntry);
1172
+ } else {
1173
+ if (!this.#stepGraph[lastStepKey]) this.#stepGraph[lastStepKey] = [];
1174
+ this.#stepGraph[lastStepKey].push(graphEntry);
859
1175
  }
860
- return this.#runId;
1176
+ return this;
861
1177
  }
862
- async #loadWorkflowSnapshot(runId) {
863
- if (!this.#mastra?.storage) {
864
- this.logger.debug("Snapshot cannot be loaded. Mastra engine is not initialized", { runId });
865
- return;
1178
+ after(step) {
1179
+ const stepKey = this.#makeStepKey(step);
1180
+ this.#afterStepStack.push(stepKey);
1181
+ if (!this.#stepSubscriberGraph[stepKey]) {
1182
+ this.#stepSubscriberGraph[stepKey] = { initial: [] };
866
1183
  }
867
- return this.#mastra.storage.loadWorkflowSnapshot({ runId, workflowName: this.name });
1184
+ return this;
868
1185
  }
869
1186
  /**
870
- * Resolves variables for a step from trigger data or previous step results
871
- * @param stepConfig - Configuration of the step needing variable resolution
872
- * @param context - Current workflow context containing results and trigger data
873
- * @returns Object containing resolved variable values
1187
+ * Executes the workflow with the given trigger data
1188
+ * @param triggerData - Initial data to start the workflow with
1189
+ * @returns Promise resolving to workflow results or rejecting with error
1190
+ * @throws Error if trigger schema validation fails
874
1191
  */
875
- #resolveVariables({
876
- stepConfig,
877
- context,
878
- stepId
879
- }) {
880
- this.logger.debug(`Resolving variables for step ${stepId}`, {
881
- stepId,
882
- runId: this.#runId
883
- });
884
- const resolvedData = {
885
- ...context,
886
- getStepResult: (stepId2) => {
887
- if (stepId2 === "trigger") {
888
- return context.triggerData;
889
- }
890
- const result = context.steps[stepId2];
891
- if (result && result.status === "success") {
892
- return result.output;
893
- }
894
- return void 0;
1192
+ createRun() {
1193
+ const run = new WorkflowInstance({
1194
+ logger: this.logger,
1195
+ name: this.name,
1196
+ mastra: this.#mastra,
1197
+ retryConfig: this.#retryConfig,
1198
+ steps: this.#steps,
1199
+ stepGraph: this.#stepGraph,
1200
+ stepSubscriberGraph: this.#stepSubscriberGraph,
1201
+ onStepTransition: this.#onStepTransition,
1202
+ onFinish: () => {
1203
+ this.#runs.delete(run.runId);
895
1204
  }
1205
+ });
1206
+ this.#runs.set(run.runId, run);
1207
+ return {
1208
+ start: run.start.bind(run),
1209
+ runId: run.runId
896
1210
  };
897
- for (const [key, variable] of Object.entries(stepConfig.data)) {
898
- const sourceData = variable.step === "trigger" ? context.triggerData : getStepResult(context.steps[variable.step.id]);
899
- this.logger.debug(
900
- `Got source data for ${key} variable from ${variable.step === "trigger" ? "trigger" : variable.step.id}`,
901
- {
902
- sourceData,
903
- path: variable.path,
904
- runId: this.#runId
905
- }
906
- );
907
- if (!sourceData && variable.step !== "trigger") {
908
- resolvedData[key] = void 0;
909
- continue;
910
- }
911
- const value = variable.path === "" || variable.path === "." ? sourceData : get(sourceData, variable.path);
912
- this.logger.debug(`Resolved variable ${key}`, {
913
- value,
914
- runId: this.#runId
915
- });
916
- resolvedData[key] = value;
917
- }
918
- return resolvedData;
919
1211
  }
920
1212
  /**
921
- * Evaluates a single condition against workflow context
1213
+ * Rebuilds the machine with the current steps configuration and validates the workflow
1214
+ *
1215
+ * This is the last step of a workflow builder method chain
1216
+ * @throws Error if validation fails
1217
+ *
1218
+ * @returns this instance for method chaining
922
1219
  */
923
- #evaluateCondition(condition, context) {
924
- let andBranchResult = true;
925
- let baseResult = true;
926
- let orBranchResult = true;
927
- const simpleCondition = Object.entries(condition).find(([key]) => key.includes("."));
928
- if (simpleCondition) {
929
- const [key, queryValue] = simpleCondition;
930
- const [stepId, ...pathParts] = key.split(".");
931
- const path = pathParts.join(".");
932
- const sourceData = stepId === "trigger" ? context.triggerData : getStepResult(context.steps[stepId]);
933
- this.logger.debug(`Got condition data from step ${stepId}`, {
934
- stepId,
935
- sourceData,
936
- runId: this.#runId
937
- });
938
- if (!sourceData) {
939
- return false;
940
- }
941
- let value = get(sourceData, path);
942
- if (stepId !== "trigger" && path === "status" && !value) {
943
- value = "success";
944
- }
945
- if (typeof queryValue === "object" && queryValue !== null) {
946
- baseResult = sift(queryValue)(value);
947
- } else {
948
- baseResult = value === queryValue;
949
- }
950
- }
951
- if ("ref" in condition) {
952
- const { ref, query } = condition;
953
- const sourceData = ref.step === "trigger" ? context.triggerData : getStepResult(context.steps[ref.step.id]);
954
- this.logger.debug(`Got condition data from ${ref.step === "trigger" ? "trigger" : ref.step.id}`, {
955
- sourceData,
956
- runId: this.#runId
957
- });
958
- if (!sourceData) {
959
- return false;
960
- }
961
- let value = get(sourceData, ref.path);
962
- if (ref.step !== "trigger" && ref.path === "status" && !value) {
963
- value = "success";
1220
+ commit() {
1221
+ return this;
1222
+ }
1223
+ // record all object paths that leads to a suspended state
1224
+ #getSuspendedPaths({
1225
+ value,
1226
+ path,
1227
+ suspendedPaths
1228
+ }) {
1229
+ if (typeof value === "string") {
1230
+ if (value === "suspended") {
1231
+ suspendedPaths.add(path);
964
1232
  }
965
- baseResult = sift(query)(value);
1233
+ } else {
1234
+ Object.keys(value).forEach(
1235
+ (key) => this.#getSuspendedPaths({ value: value[key], path: path ? `${path}.${key}` : key, suspendedPaths })
1236
+ );
966
1237
  }
967
- if ("and" in condition) {
968
- andBranchResult = condition.and.every((cond) => this.#evaluateCondition(cond, context));
969
- this.logger.debug(`Evaluated AND condition`, {
970
- andBranchResult,
971
- runId: this.#runId
972
- });
1238
+ }
1239
+ async #loadWorkflowSnapshot(runId) {
1240
+ if (!this.#mastra?.storage) {
1241
+ this.logger.debug("Snapshot cannot be loaded. Mastra engine is not initialized", { runId });
1242
+ return;
973
1243
  }
974
- if ("or" in condition) {
975
- orBranchResult = condition.or.some((cond) => this.#evaluateCondition(cond, context));
976
- this.logger.debug(`Evaluated OR condition`, {
977
- orBranchResult,
978
- runId: this.#runId
979
- });
1244
+ const activeRun = this.#runs.get(runId);
1245
+ if (activeRun) {
1246
+ await activeRun.persistWorkflowSnapshot();
980
1247
  }
981
- const finalResult = baseResult && andBranchResult && orBranchResult;
982
- this.logger.debug(`Evaluated condition`, {
983
- finalResult,
984
- runId: this.#runId
985
- });
986
- return finalResult;
1248
+ return this.#mastra.storage.loadWorkflowSnapshot({ runId, workflowName: this.name });
1249
+ }
1250
+ getExecutionSpan(runId) {
1251
+ return this.#runs.get(runId)?.executionSpan;
987
1252
  }
988
1253
  #makeStepDef(stepId) {
989
1254
  const executeStep = (handler2, spanName, attributes) => {
990
1255
  return async (data) => {
991
- return await context.with(trace.setSpan(context.active(), this.#executionSpan), async () => {
992
- return this.#mastra.telemetry.traceMethod(handler2, {
993
- spanName,
994
- attributes
995
- })(data);
996
- });
1256
+ return await context.with(
1257
+ trace.setSpan(context.active(), this.getExecutionSpan(attributes?.runId ?? data?.runId)),
1258
+ async () => {
1259
+ return this.#mastra.telemetry.traceMethod(handler2, {
1260
+ spanName,
1261
+ attributes
1262
+ })(data);
1263
+ }
1264
+ );
997
1265
  };
998
1266
  };
999
1267
  const handler = async ({ context, ...rest }) => {
@@ -1007,15 +1275,15 @@ var Workflow = class extends MastraBase {
1007
1275
  };
1008
1276
  const finalAction = this.#mastra?.telemetry ? executeStep(execute, `workflow.${this.name}.action.${stepId}`, {
1009
1277
  componentName: this.name,
1010
- runId: context.runId ?? this.#runId
1278
+ runId: rest.runId
1011
1279
  }) : execute;
1012
1280
  return finalAction ? await finalAction({ context: mergedData, ...rest }) : {};
1013
1281
  };
1014
1282
  const finalHandler = ({ context, ...rest }) => {
1015
- if (this.#executionSpan) {
1283
+ if (this.getExecutionSpan(rest?.runId)) {
1016
1284
  return executeStep(handler, `workflow.${this.name}.step.${stepId}`, {
1017
1285
  componentName: this.name,
1018
- runId: context.runId ?? this.#runId
1286
+ runId: rest?.runId
1019
1287
  })({ context, ...rest });
1020
1288
  }
1021
1289
  return handler({ context, ...rest });
@@ -1025,26 +1293,6 @@ var Workflow = class extends MastraBase {
1025
1293
  data: {}
1026
1294
  };
1027
1295
  }
1028
- /**
1029
- * Creates a map of step IDs to their respective delay values
1030
- * @returns Object mapping step IDs to delay values
1031
- */
1032
- #makeDelayMap() {
1033
- const delayMap = {};
1034
- Object.keys(this.#steps).forEach((stepId) => {
1035
- delayMap[stepId] = this.#steps[stepId]?.retryConfig?.delay || this.#retryConfig?.delay || 1e3;
1036
- });
1037
- return delayMap;
1038
- }
1039
- /**
1040
- * Cleans up the actor instance
1041
- */
1042
- #cleanup() {
1043
- if (this.#actor) {
1044
- this.#actor.stop();
1045
- this.#actor = null;
1046
- }
1047
- }
1048
1296
  #getActivePathsAndStatus(value) {
1049
1297
  const paths = [];
1050
1298
  const traverse = (current, path = []) => {
@@ -1065,16 +1313,9 @@ var Workflow = class extends MastraBase {
1065
1313
  return paths;
1066
1314
  }
1067
1315
  async getState(runId) {
1068
- if (this.#runId === runId && this.#actor) {
1069
- const snapshot = this.#actor.getSnapshot();
1070
- const m = this.#getActivePathsAndStatus(snapshot.value);
1071
- return {
1072
- runId,
1073
- value: snapshot.value,
1074
- context: snapshot.context,
1075
- activePaths: m,
1076
- timestamp: Date.now()
1077
- };
1316
+ const run = this.#runs.get(runId);
1317
+ if (run) {
1318
+ return run.getState();
1078
1319
  }
1079
1320
  const storedSnapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
1080
1321
  runId,
@@ -1103,6 +1344,14 @@ var Workflow = class extends MastraBase {
1103
1344
  runId,
1104
1345
  stepId,
1105
1346
  context: resumeContext
1347
+ }) {
1348
+ await setTimeout(0);
1349
+ return this._resume({ runId, stepId, context: resumeContext });
1350
+ }
1351
+ async _resume({
1352
+ runId,
1353
+ stepId,
1354
+ context: resumeContext
1106
1355
  }) {
1107
1356
  const snapshot = await this.#loadWorkflowSnapshot(runId);
1108
1357
  if (!snapshot) {
@@ -1115,6 +1364,15 @@ var Workflow = class extends MastraBase {
1115
1364
  this.logger.debug("Failed to parse workflow snapshot for resume", { error, runId });
1116
1365
  throw new Error("Failed to parse workflow snapshot");
1117
1366
  }
1367
+ const origSnapshot = parsedSnapshot;
1368
+ const startStepId = parsedSnapshot.suspendedSteps?.[stepId];
1369
+ if (!startStepId) {
1370
+ return;
1371
+ }
1372
+ parsedSnapshot = startStepId === "trigger" ? parsedSnapshot : { ...parsedSnapshot?.childStates?.[startStepId], ...{ suspendedSteps: parsedSnapshot.suspendedSteps } };
1373
+ if (!parsedSnapshot) {
1374
+ throw new Error(`No snapshot found for step: ${stepId} starting at ${startStepId}`);
1375
+ }
1118
1376
  if (resumeContext) {
1119
1377
  parsedSnapshot.context.steps[stepId] = {
1120
1378
  status: "success",
@@ -1136,20 +1394,6 @@ var Workflow = class extends MastraBase {
1136
1394
  }
1137
1395
  });
1138
1396
  }
1139
- const updateStepInHierarchy = (value, targetStepId) => {
1140
- const result = {};
1141
- for (const key of Object.keys(value)) {
1142
- const currentValue = value[key];
1143
- if (key === targetStepId) {
1144
- result[key] = "pending";
1145
- } else if (typeof currentValue === "object" && currentValue !== null) {
1146
- result[key] = updateStepInHierarchy(currentValue, targetStepId);
1147
- } else {
1148
- result[key] = currentValue;
1149
- }
1150
- }
1151
- return result;
1152
- };
1153
1397
  parsedSnapshot.value = updateStepInHierarchy(parsedSnapshot.value, stepId);
1154
1398
  if (parsedSnapshot.context?.attempts) {
1155
1399
  parsedSnapshot.context.attempts[stepId] = this.#steps[stepId]?.retryConfig?.attempts || this.#retryConfig?.attempts || 3;
@@ -1159,9 +1403,24 @@ var Workflow = class extends MastraBase {
1159
1403
  runId,
1160
1404
  stepId
1161
1405
  });
1162
- return this.execute({
1163
- snapshot: parsedSnapshot,
1406
+ const run = this.#runs.get(runId) ?? new WorkflowInstance({
1407
+ logger: this.logger,
1408
+ name: this.name,
1409
+ mastra: this.#mastra,
1410
+ retryConfig: this.#retryConfig,
1411
+ steps: this.#steps,
1412
+ stepGraph: this.#stepGraph,
1413
+ stepSubscriberGraph: this.#stepSubscriberGraph,
1414
+ onStepTransition: this.#onStepTransition,
1164
1415
  runId,
1416
+ onFinish: () => {
1417
+ this.#runs.delete(run.runId);
1418
+ }
1419
+ });
1420
+ run.setState(origSnapshot?.value);
1421
+ this.#runs.set(run.runId, run);
1422
+ return run?.execute({
1423
+ snapshot: parsedSnapshot,
1165
1424
  stepId
1166
1425
  });
1167
1426
  }
@@ -1217,4 +1476,4 @@ function createStep(opts) {
1217
1476
  return new Step(opts);
1218
1477
  }
1219
1478
 
1220
- export { Step, Workflow, createStep, getStepResult, isErrorEvent, isTransitionEvent, isVariableReference };
1479
+ export { Step, Workflow, createStep, getActivePathsAndStatus, getStepResult, getSuspendedPaths, isErrorEvent, isFinalState, isTransitionEvent, isVariableReference, mergeChildValue, recursivelyCheckForFinalState, updateStepInHierarchy };