@mastra/temporal 0.6.0-alpha.0 → 0.6.0-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,11 @@
1
1
  import {
2
+ ApplicationFailure,
3
+ TemporalFailure,
2
4
  condition,
3
5
  defineQuery,
4
6
  defineSignal,
5
7
  executeChild,
8
+ isCancellation,
6
9
  proxyActivities,
7
10
  log,
8
11
  setHandler,
@@ -12,6 +15,39 @@ import {
12
15
  const resumeSignal = defineSignal('mastra.resume');
13
16
  const statusQuery = defineQuery('mastra.status');
14
17
 
18
+ // The step error an Activity or child Workflow put in its failure details. Failures without them
19
+ // (timeouts, Activities from an older Worker) give the innermost cause's type and message.
20
+ function readStepFailure(error) {
21
+ let current = error;
22
+ while (current) {
23
+ const details = current.details?.[0];
24
+
25
+ if (details?.error) {
26
+ return { error: details.error, ...(details.tripwire && { tripwire: details.tripwire }) };
27
+ }
28
+
29
+ if (!current.cause) {
30
+ break;
31
+ }
32
+
33
+ current = current.cause;
34
+ }
35
+
36
+ return { error: { name: current?.type ?? current?.name ?? 'Error', message: current?.message ?? String(error) } };
37
+ }
38
+
39
+ // Records a step as failed, in the core StepResult shape, before its failure ends the run.
40
+ function recordFailure(stepResults, id, payload, startedAt, error) {
41
+ stepResults[id] = {
42
+ __temporalStepResult: true,
43
+ status: 'failed',
44
+ payload,
45
+ ...readStepFailure(error),
46
+ startedAt,
47
+ endedAt: Date.now(),
48
+ };
49
+ }
50
+
15
51
  export class TemporalExecutionEngine {
16
52
  startToCloseTimeout;
17
53
  activityHandle;
@@ -56,8 +92,26 @@ export class TemporalExecutionEngine {
56
92
  steps: stepResults,
57
93
  };
58
94
 
59
- for (const entry of params.graph.steps) {
60
- result = await this.executeEntry(entry, result, stepResults);
95
+ try {
96
+ for (const entry of params.graph.steps) {
97
+ result = await this.executeEntry(entry, result, stepResults);
98
+ }
99
+ } catch (error) {
100
+ // An error that isn't a Temporal failure comes from Workflow code. It fails only the Workflow
101
+ // Task, which Temporal retries, so a fixed Worker can still finish the run.
102
+ if (isCancellation(error) || !(error instanceof TemporalFailure)) {
103
+ throw error;
104
+ }
105
+
106
+ // The run still fails in Temporal. The details let start() return step records and the
107
+ // step's own error, which a thrown ActivityFailure would lose.
108
+ const failure = readStepFailure(error);
109
+ throw ApplicationFailure.create({
110
+ message: failure.error.message,
111
+ type: failure.error.name,
112
+ cause: error,
113
+ details: [{ ...failure, steps: stepResults }],
114
+ });
61
115
  }
62
116
 
63
117
  this.currentResult = {
@@ -82,9 +136,17 @@ export class TemporalExecutionEngine {
82
136
  }
83
137
 
84
138
  // Record steps in the core StepResult shape ({ status, payload, output, startedAt, endedAt }).
85
- async recordStep(stepResults, id, payload, run) {
139
+ async recordStep(stepResults, id, payload, run, group) {
86
140
  const startedAt = Date.now();
87
- let output = await run();
141
+ const attempt = async resumeData => {
142
+ try {
143
+ return await run(resumeData);
144
+ } catch (error) {
145
+ recordFailure(stepResults, id, payload, startedAt, error);
146
+ throw error;
147
+ }
148
+ };
149
+ let output = await attempt();
88
150
  let suspendPayload;
89
151
  let resumeData;
90
152
 
@@ -114,7 +176,11 @@ export class TemporalExecutionEngine {
114
176
 
115
177
  this.suspendedSteps.add(id);
116
178
  try {
117
- await condition(() => this.resumePayloads.has(id));
179
+ await condition(() => this.resumePayloads.has(id) || group?.failed === true);
180
+ // A failed sibling branch ends the run, so the step stays suspended in the records.
181
+ if (!this.resumePayloads.has(id)) {
182
+ return output;
183
+ }
118
184
  resumeData = this.resumePayloads.get(id)?.resumeData;
119
185
  this.resumePayloads.delete(id);
120
186
  } finally {
@@ -131,7 +197,7 @@ export class TemporalExecutionEngine {
131
197
  state: this.initialState,
132
198
  steps: stepResults,
133
199
  };
134
- output = await run(resumeData);
200
+ output = await attempt(resumeData);
135
201
  }
136
202
 
137
203
  stepResults[id] = {
@@ -157,17 +223,22 @@ export class TemporalExecutionEngine {
157
223
  return { inputData, ...extra, ...this.executionContext };
158
224
  }
159
225
 
160
- async executeEntry(entry, inputData, stepResults) {
226
+ async executeEntry(entry, inputData, stepResults, group) {
161
227
  switch (entry.type) {
162
228
  case 'step': {
163
229
  log.info('step', { stepId: entry.step.id });
164
- return this.recordStep(stepResults, entry.step.id, inputData, resumeData =>
165
- this.activityHandle[entry.step.id](
166
- this.activityParams(inputData, {
167
- initData: this.initData,
168
- ...(resumeData !== undefined && { resumeData }),
169
- }),
170
- ),
230
+ return this.recordStep(
231
+ stepResults,
232
+ entry.step.id,
233
+ inputData,
234
+ resumeData =>
235
+ this.activityHandle[entry.step.id](
236
+ this.activityParams(inputData, {
237
+ initData: this.initData,
238
+ ...(resumeData !== undefined && { resumeData }),
239
+ }),
240
+ ),
241
+ group,
171
242
  );
172
243
  }
173
244
 
@@ -213,11 +284,34 @@ export class TemporalExecutionEngine {
213
284
  const entryId = parallelEntry =>
214
285
  parallelEntry.type === 'childWorkflow' ? parallelEntry.workflowType : parallelEntry.step.id;
215
286
  log.info('parallel', { steps: entry.steps.map(entryId) });
216
- const results = await Promise.all(entry.steps.map(step => this.executeEntry(step, inputData, stepResults)));
287
+ // Waits for every branch, as a local run does, so a failed run keeps the others' records.
288
+ // A failed branch also ends the wait of a suspended one, which would otherwise keep the run
289
+ // open until a resume. The flag is per group so a later parallel starts clean.
290
+ const group = { failed: false };
291
+ const settled = await Promise.allSettled(
292
+ entry.steps.map(step =>
293
+ this.executeEntry(step, inputData, stepResults, group).catch(error => {
294
+ group.failed = true;
295
+ throw error;
296
+ }),
297
+ ),
298
+ );
299
+ const failures = settled.filter(outcome => outcome.status === 'rejected');
300
+ // Same order as in execute(), whatever the branch order: a cancelled run ends Canceled, and
301
+ // an error from Workflow code wins so Temporal retries the Workflow Task.
302
+ const rejected =
303
+ failures.find(outcome => isCancellation(outcome.reason)) ??
304
+ failures.find(outcome => !(outcome.reason instanceof TemporalFailure)) ??
305
+ failures[0];
306
+
307
+ if (rejected) {
308
+ throw rejected.reason;
309
+ }
310
+
217
311
  const out = {};
218
312
 
219
313
  entry.steps.forEach((step, i) => {
220
- out[entryId(step)] = results[i];
314
+ out[entryId(step)] = settled[i].value;
221
315
  });
222
316
 
223
317
  return out;
@@ -301,7 +395,12 @@ export class TemporalExecutionEngine {
301
395
  }
302
396
  });
303
397
 
304
- await Promise.all(workers);
398
+ try {
399
+ await Promise.all(workers);
400
+ } catch (error) {
401
+ recordFailure(stepResults, entry.step.id, inputData, startedAt, error);
402
+ throw error;
403
+ }
305
404
  stepResults[entry.step.id] = {
306
405
  __temporalStepResult: true,
307
406
  status: 'success',
@@ -17,7 +17,7 @@ export declare class TemporalExecutionEngine {
17
17
  constructor(params: any);
18
18
  execute(params: any): Promise<any>;
19
19
  resumeTarget(payload: any): any;
20
- recordStep(stepResults: any, id: any, payload: any, run: any): Promise<any>;
20
+ recordStep(stepResults: any, id: any, payload: any, run: any, group: any): Promise<any>;
21
21
  activityParams(inputData: any, extra?: {}): {
22
22
  requestContext?: any;
23
23
  runId?: any;
@@ -26,7 +26,7 @@ export declare class TemporalExecutionEngine {
26
26
  workflowId?: any;
27
27
  inputData: any;
28
28
  };
29
- executeEntry(entry: any, inputData: any, stepResults: any): Promise<any>;
29
+ executeEntry(entry: any, inputData: any, stepResults: any, group: any): Promise<any>;
30
30
  }
31
31
  export declare function createWorkflow(workflowId: any, options: any): ((startArgs: any) => Promise<any>) & {
32
32
  then(stepId: any): (startArgs: any) => Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"temporal-workflow-runtime.d.mts","sourceRoot":"","sources":["../../src/transforms/temporal-workflow-runtime.mjs"],"names":[],"mappings":"AAcA,qBAAa,uBAAuB;IA4B3B,gBAAgB;;;;;;;IA3BvB,mBAAmB,MAAC;IACpB,cAAc,wGAAC;IACf,cAAc,gBAAa;IAC3B,cAAc,WAAa;IAC3B,YAAY,gBAAa;IACzB,aAAa,MAAC;IACd,QAAQ,MAAC;IACT,YAAY,MAAC;IAEb,YAAY,MAAM,KAAA,EAajB;IAEK,OAAO,CAAC,MAAM,KAAA,gBA+BnB;IAED,YAAY,CAAC,OAAO,KAAA,OASnB;IAGK,UAAU,CAAC,WAAW,KAAA,EAAE,EAAE,KAAA,EAAE,OAAO,KAAA,EAAE,GAAG,KAAA,gBAqE7C;IAED,cAAc,CAAC,SAAS,KAAA,EAAE,KAAK,KAAK;;;;;;;MAEnC;IAEK,YAAY,CAAC,KAAK,KAAA,EAAE,SAAS,KAAA,EAAE,WAAW,KAAA,gBA+J/C;CACF;AAED,wBAAgB,cAAc,CAAC,UAAU,KAAA,EAAE,OAAO,KAAA;;;;;;;;;;;;EAuJjD"}
1
+ {"version":3,"file":"temporal-workflow-runtime.d.mts","sourceRoot":"","sources":["../../src/transforms/temporal-workflow-runtime.mjs"],"names":[],"mappings":"AAkDA,qBAAa,uBAAuB;IA4B3B,gBAAgB;;;;;;;IA3BvB,mBAAmB,MAAC;IACpB,cAAc,wGAAC;IACf,cAAc,gBAAa;IAC3B,cAAc,WAAa;IAC3B,YAAY,gBAAa;IACzB,aAAa,MAAC;IACd,QAAQ,MAAC;IACT,YAAY,MAAC;IAEb,YAAY,MAAM,KAAA,EAajB;IAEK,OAAO,CAAC,MAAM,KAAA,gBAiDnB;IAED,YAAY,CAAC,OAAO,KAAA,OASnB;IAGK,UAAU,CAAC,WAAW,KAAA,EAAE,EAAE,KAAA,EAAE,OAAO,KAAA,EAAE,GAAG,KAAA,EAAE,KAAK,KAAA,gBAiFpD;IAED,cAAc,CAAC,SAAS,KAAA,EAAE,KAAK,KAAK;;;;;;;MAEnC;IAEK,YAAY,CAAC,KAAK,KAAA,EAAE,SAAS,KAAA,EAAE,WAAW,KAAA,EAAE,KAAK,KAAA,gBAgMtD;CACF;AAED,wBAAgB,cAAc,CAAC,UAAU,KAAA,EAAE,OAAO,KAAA;;;;;;;;;;;;EAuJjD"}
package/dist/worker.cjs CHANGED
@@ -23,12 +23,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  const require_utils = require("./utils-BVE1AkTt.cjs");
24
24
  let _mastra_core_workflows = require("@mastra/core/workflows");
25
25
  let _mastra_core_di = require("@mastra/core/di");
26
+ let _mastra_core_error = require("@mastra/core/error");
26
27
  let fs = require("fs");
27
28
  let fs_promises = require("fs/promises");
28
29
  let path = require("path");
29
30
  path = __toESM(path, 1);
30
31
  let url = require("url");
31
- let _mastra_core_error = require("@mastra/core/error");
32
32
  let _mastra_core_schema = require("@mastra/core/schema");
33
33
  let _temporalio_activity = require("@temporalio/activity");
34
34
  let _babel_generator = require("@babel/generator");
@@ -1212,6 +1212,44 @@ function getActivityBindingsPath(outputDir) {
1212
1212
  return path.default.join(outputDir, ACTIVITY_BINDINGS_FILE_NAME);
1213
1213
  }
1214
1214
  /**
1215
+ * Temporal reduces a thrown error to its class name and message, so the step's own fields go in
1216
+ * the failure's `details` for start() to rebuild the result a local run returns.
1217
+ */
1218
+ function toStepFailureDetails(error) {
1219
+ const serialized = (0, _mastra_core_error.getErrorFromUnknown)(error, { serializeStack: false }).toJSON();
1220
+ let details;
1221
+ try {
1222
+ details = JSON.parse(JSON.stringify(serialized));
1223
+ } catch {
1224
+ details = {
1225
+ name: serialized.name,
1226
+ message: serialized.message
1227
+ };
1228
+ }
1229
+ if (error instanceof Error && error.name === "TripWire") {
1230
+ const { options, processorId } = error;
1231
+ return {
1232
+ error: details,
1233
+ tripwire: {
1234
+ reason: error.message,
1235
+ retry: options?.retry,
1236
+ metadata: options?.metadata,
1237
+ processorId
1238
+ }
1239
+ };
1240
+ }
1241
+ return { error: details };
1242
+ }
1243
+ /** Fails as Temporal's own conversion would (same type, message and retries), plus the details. */
1244
+ async function runStep(execute) {
1245
+ try {
1246
+ return await execute();
1247
+ } catch (error) {
1248
+ if (error instanceof _temporalio_activity.ApplicationFailure || error instanceof _temporalio_activity.CancelledFailure) throw error;
1249
+ throw _temporalio_activity.ApplicationFailure.fromError(error, { details: [toStepFailureDetails(error)] });
1250
+ }
1251
+ }
1252
+ /**
1215
1253
  * Activities queued by a Workflow built before `validateInputs` existed carry no flag and run
1216
1254
  * unchecked. A mismatch is non-retryable because a retry gets the same input.
1217
1255
  */
@@ -1234,7 +1272,8 @@ async function checkStepParams(stepId, { inputSchema, requestContextSchema }, {
1234
1272
  type: error instanceof _mastra_core_error.MastraError ? error.id : error.name,
1235
1273
  message: error.message,
1236
1274
  nonRetryable: true,
1237
- cause: error
1275
+ cause: error,
1276
+ details: [toStepFailureDetails(error)]
1238
1277
  });
1239
1278
  return {
1240
1279
  ...params,
@@ -1298,9 +1337,10 @@ var MastraPlugin = class {
1298
1337
  if (generatedActivities[binding.stepId]) continue;
1299
1338
  generatedActivities[binding.stepId] = async (params) => {
1300
1339
  const activity = (await this.#loadCompiledActivitiesModule(compiledActivitiesPath))[binding.exportName];
1301
- if (typeof activity === "function") return activity(params);
1340
+ if (typeof activity === "function") return runStep(() => activity(params));
1302
1341
  if (typeof activity?.execute !== "function") throw new Error(`Unable to load activity '${binding.exportName}' from ${compiledActivitiesPath}`);
1303
- return activity.execute(await checkStepParams(binding.stepId, activity, params));
1342
+ const checkedParams = await checkStepParams(binding.stepId, activity, params);
1343
+ return runStep(() => activity.execute(checkedParams));
1304
1344
  };
1305
1345
  }
1306
1346
  return generatedActivities;