workflow 5.0.0-beta.53 → 5.0.0-beta.55

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.
@@ -6,4 +6,6 @@ export declare const getHookByToken: () => never;
6
6
  export declare const resumeHook: () => never;
7
7
  export declare const resumeWebhook: () => never;
8
8
  export declare const runStep: () => never;
9
+ export declare const registerLifecycleHooks: () => never;
10
+ export type { RunCompletedHookParams, RunFailedHookParams, WorkflowLifecycleHooks, } from '@workflow/core/runtime/lifecycle-hooks';
9
11
  //# sourceMappingURL=api-workflow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-workflow.d.ts","sourceRoot":"","sources":["../src/api-workflow.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,gBAAgB,EAChB,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,6BAA6B,EAC7B,WAAW,EACX,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,GAAG,EAAE,MAAM,4BAA4B,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AAQrD,eAAO,MAAM,MAAM,aAA+B,CAAC;AACnD,eAAO,MAAM,cAAc,aAAuC,CAAC;AACnE,eAAO,MAAM,UAAU,aAAmC,CAAC;AAC3D,eAAO,MAAM,aAAa,aAAsC,CAAC;AACjE,eAAO,MAAM,OAAO,aAAgC,CAAC"}
1
+ {"version":3,"file":"api-workflow.d.ts","sourceRoot":"","sources":["../src/api-workflow.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,gBAAgB,EAChB,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,6BAA6B,EAC7B,WAAW,EACX,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,GAAG,EAAE,MAAM,4BAA4B,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AAQrD,eAAO,MAAM,MAAM,aAA+B,CAAC;AACnD,eAAO,MAAM,cAAc,aAAuC,CAAC;AACnE,eAAO,MAAM,UAAU,aAAmC,CAAC;AAC3D,eAAO,MAAM,aAAa,aAAsC,CAAC;AACjE,eAAO,MAAM,OAAO,aAAgC,CAAC;AACrD,eAAO,MAAM,sBAAsB,aACK,CAAC;AACzC,YAAY,EACV,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,wCAAwC,CAAC"}
@@ -8,4 +8,5 @@ export const getHookByToken = () => workflowStub('getHookByToken');
8
8
  export const resumeHook = () => workflowStub('resumeHook');
9
9
  export const resumeWebhook = () => workflowStub('resumeWebhook');
10
10
  export const runStep = () => workflowStub('runStep');
11
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLXdvcmtmbG93LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2FwaS13b3JrZmxvdy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFXQSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDakQsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRXJELE1BQU0sWUFBWSxHQUFHLENBQUMsSUFBWSxFQUFFLEVBQUU7SUFDcEMsTUFBTSxJQUFJLEtBQUssQ0FDYixnRUFBZ0UsSUFBSSwyRkFBMkYsQ0FDaEssQ0FBQztBQUNKLENBQUMsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBRyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7QUFDbkQsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0FBQ25FLE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDM0QsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxlQUFlLENBQUMsQ0FBQztBQUNqRSxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLFNBQVMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHR5cGUge1xuICBDYW5jZWxSdW5PcHRpb25zLFxuICBFdmVudCxcbiAgU3RhcnRPcHRpb25zLFxuICBTdG9wU2xlZXBPcHRpb25zLFxuICBTdG9wU2xlZXBSZXN1bHQsXG4gIFdvcmtmbG93UmVhZGFibGVTdHJlYW1PcHRpb25zLFxuICBXb3JrZmxvd1J1bixcbiAgV29ya2Zsb3dSdW5Xcml0YWJsZVN0cmVhbU9wdGlvbnMsXG59IGZyb20gJ0B3b3JrZmxvdy9jb3JlL3J1bnRpbWUnO1xuXG5leHBvcnQgeyBSdW4gfSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lL3J1bic7XG5leHBvcnQgeyBzdGFydCB9IGZyb20gJ0B3b3JrZmxvdy9jb3JlL3J1bnRpbWUvc3RhcnQnO1xuXG5jb25zdCB3b3JrZmxvd1N0dWIgPSAoaXRlbTogc3RyaW5nKSA9PiB7XG4gIHRocm93IG5ldyBFcnJvcihcbiAgICBgVGhlIHdvcmtmbG93IGVudmlyb25tZW50IGRvZXNuJ3QgYWxsb3cgdGhpcyBydW50aW1lIHVzYWdlIG9mICR7aXRlbX0uIE1vdmUgdGhpcyBjYWxsIHRvIGEgc3RlcCBmdW5jdGlvbiAoXCJ1c2Ugc3RlcFwiKSBvciBjYWxsIGl0IG91dHNpZGUgdGhlIHdvcmtmbG93IGNvbnRleHQuYFxuICApO1xufTtcblxuZXhwb3J0IGNvbnN0IGdldFJ1biA9ICgpID0+IHdvcmtmbG93U3R1YignZ2V0UnVuJyk7XG5leHBvcnQgY29uc3QgZ2V0SG9va0J5VG9rZW4gPSAoKSA9PiB3b3JrZmxvd1N0dWIoJ2dldEhvb2tCeVRva2VuJyk7XG5leHBvcnQgY29uc3QgcmVzdW1lSG9vayA9ICgpID0+IHdvcmtmbG93U3R1YigncmVzdW1lSG9vaycpO1xuZXhwb3J0IGNvbnN0IHJlc3VtZVdlYmhvb2sgPSAoKSA9PiB3b3JrZmxvd1N0dWIoJ3Jlc3VtZVdlYmhvb2snKTtcbmV4cG9ydCBjb25zdCBydW5TdGVwID0gKCkgPT4gd29ya2Zsb3dTdHViKCdydW5TdGVwJyk7XG4iXX0=
11
+ export const registerLifecycleHooks = () => workflowStub('registerLifecycleHooks');
12
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLXdvcmtmbG93LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2FwaS13b3JrZmxvdy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFXQSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDakQsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRXJELE1BQU0sWUFBWSxHQUFHLENBQUMsSUFBWSxFQUFFLEVBQUU7SUFDcEMsTUFBTSxJQUFJLEtBQUssQ0FDYixnRUFBZ0UsSUFBSSwyRkFBMkYsQ0FDaEssQ0FBQztBQUNKLENBQUMsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBRyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7QUFDbkQsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0FBQ25FLE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDM0QsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxlQUFlLENBQUMsQ0FBQztBQUNqRSxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3JELE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLEdBQUcsRUFBRSxDQUN6QyxZQUFZLENBQUMsd0JBQXdCLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB0eXBlIHtcbiAgQ2FuY2VsUnVuT3B0aW9ucyxcbiAgRXZlbnQsXG4gIFN0YXJ0T3B0aW9ucyxcbiAgU3RvcFNsZWVwT3B0aW9ucyxcbiAgU3RvcFNsZWVwUmVzdWx0LFxuICBXb3JrZmxvd1JlYWRhYmxlU3RyZWFtT3B0aW9ucyxcbiAgV29ya2Zsb3dSdW4sXG4gIFdvcmtmbG93UnVuV3JpdGFibGVTdHJlYW1PcHRpb25zLFxufSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lJztcblxuZXhwb3J0IHsgUnVuIH0gZnJvbSAnQHdvcmtmbG93L2NvcmUvcnVudGltZS9ydW4nO1xuZXhwb3J0IHsgc3RhcnQgfSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lL3N0YXJ0JztcblxuY29uc3Qgd29ya2Zsb3dTdHViID0gKGl0ZW06IHN0cmluZykgPT4ge1xuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgYFRoZSB3b3JrZmxvdyBlbnZpcm9ubWVudCBkb2Vzbid0IGFsbG93IHRoaXMgcnVudGltZSB1c2FnZSBvZiAke2l0ZW19LiBNb3ZlIHRoaXMgY2FsbCB0byBhIHN0ZXAgZnVuY3Rpb24gKFwidXNlIHN0ZXBcIikgb3IgY2FsbCBpdCBvdXRzaWRlIHRoZSB3b3JrZmxvdyBjb250ZXh0LmBcbiAgKTtcbn07XG5cbmV4cG9ydCBjb25zdCBnZXRSdW4gPSAoKSA9PiB3b3JrZmxvd1N0dWIoJ2dldFJ1bicpO1xuZXhwb3J0IGNvbnN0IGdldEhvb2tCeVRva2VuID0gKCkgPT4gd29ya2Zsb3dTdHViKCdnZXRIb29rQnlUb2tlbicpO1xuZXhwb3J0IGNvbnN0IHJlc3VtZUhvb2sgPSAoKSA9PiB3b3JrZmxvd1N0dWIoJ3Jlc3VtZUhvb2snKTtcbmV4cG9ydCBjb25zdCByZXN1bWVXZWJob29rID0gKCkgPT4gd29ya2Zsb3dTdHViKCdyZXN1bWVXZWJob29rJyk7XG5leHBvcnQgY29uc3QgcnVuU3RlcCA9ICgpID0+IHdvcmtmbG93U3R1YigncnVuU3RlcCcpO1xuZXhwb3J0IGNvbnN0IHJlZ2lzdGVyTGlmZWN5Y2xlSG9va3MgPSAoKSA9PlxuICB3b3JrZmxvd1N0dWIoJ3JlZ2lzdGVyTGlmZWN5Y2xlSG9va3MnKTtcbmV4cG9ydCB0eXBlIHtcbiAgUnVuQ29tcGxldGVkSG9va1BhcmFtcyxcbiAgUnVuRmFpbGVkSG9va1BhcmFtcyxcbiAgV29ya2Zsb3dMaWZlY3ljbGVIb29rcyxcbn0gZnJvbSAnQHdvcmtmbG93L2NvcmUvcnVudGltZS9saWZlY3ljbGUtaG9va3MnO1xuIl19
package/dist/api.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import '@workflow/core/runtime/world-init';
2
2
  export type { CancelRunOptions, Event, StopSleepOptions, StopSleepResult, WorkflowRun, } from '@workflow/core/runtime';
3
+ export { type RunCompletedHookParams, type RunFailedHookParams, registerLifecycleHooks, type WorkflowLifecycleHooks, } from '@workflow/core/runtime/lifecycle-hooks';
3
4
  export { getHookByToken, type Hook, type ResumedHook, resumeHook, resumeWebhook, } from '@workflow/core/runtime/resume-hook';
4
5
  export { getRun, Run, type WorkflowReadableStream, type WorkflowReadableStreamOptions, type WorkflowRunWritableStreamOptions, } from '@workflow/core/runtime/run';
5
6
  export { type StartOptions, start, } from '@workflow/core/runtime/start';
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAOA,OAAO,mCAAmC,CAAC;AAE3C,YAAY,EACV,gBAAgB,EAChB,KAAK,EACL,gBAAgB,EAChB,eAAe,EACf,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,cAAc,EACd,KAAK,IAAI,EACT,KAAK,WAAW,EAChB,UAAU,EACV,aAAa,GACd,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,MAAM,EACN,GAAG,EACH,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,EAClC,KAAK,gCAAgC,GACtC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,GACN,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAOA,OAAO,mCAAmC,CAAC;AAE3C,YAAY,EACV,gBAAgB,EAChB,KAAK,EACL,gBAAgB,EAChB,eAAe,EACf,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,sBAAsB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,cAAc,EACd,KAAK,IAAI,EACT,KAAK,WAAW,EAChB,UAAU,EACV,aAAa,GACd,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,MAAM,EACN,GAAG,EACH,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,EAClC,KAAK,gCAAgC,GACtC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,GACN,MAAM,8BAA8B,CAAC"}
package/dist/api.js CHANGED
@@ -6,7 +6,8 @@
6
6
  // stays host-only.
7
7
  // See `@workflow/core/src/runtime/world-init.ts` for the full rationale.
8
8
  import '@workflow/core/runtime/world-init';
9
+ export { registerLifecycleHooks, } from '@workflow/core/runtime/lifecycle-hooks';
9
10
  export { getHookByToken, resumeHook, resumeWebhook, } from '@workflow/core/runtime/resume-hook';
10
11
  export { getRun, Run, } from '@workflow/core/runtime/run';
11
12
  export { start, } from '@workflow/core/runtime/start';
12
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2FwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxxRUFBcUU7QUFDckUseUVBQXlFO0FBQ3pFLDJFQUEyRTtBQUMzRSw0RUFBNEU7QUFDNUUsNkVBQTZFO0FBQzdFLG1CQUFtQjtBQUNuQix5RUFBeUU7QUFDekUsT0FBTyxtQ0FBbUMsQ0FBQztBQVMzQyxPQUFPLEVBQ0wsY0FBYyxFQUdkLFVBQVUsRUFDVixhQUFhLEdBQ2QsTUFBTSxvQ0FBb0MsQ0FBQztBQUM1QyxPQUFPLEVBQ0wsTUFBTSxFQUNOLEdBQUcsR0FJSixNQUFNLDRCQUE0QixDQUFDO0FBQ3BDLE9BQU8sRUFFTCxLQUFLLEdBQ04sTUFBTSw4QkFBOEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIFNpZGUtZWZmZWN0IGltcG9ydDogZW5zdXJlIGB3b3JsZC50c2AgaXMgbG9hZGVkIHNvIGl0cyBtb2R1bGUtbG9hZFxuLy8gYGdsb2JhbFRoaXNbR2V0V29ybGRGbktleV0gPz89IGdldFdvcmxkYCByZWdpc3RyYXRpb24gZmlyZXMgYmVmb3JlIGFueVxuLy8gaG9zdCByb3V0ZSByZWFjaGVzIGBnZXRXb3JsZExhenkoKWAuIFdpdGhvdXQgdGhpcywgd2VicGFjay90dXJib3BhY2sgY2FuXG4vLyB0cmVlLXNoYWtlIGB3b3JsZC50c2Agb3V0IG9mIHJvdXRlcyB0aGF0IG9ubHkgdXNlIGBzdGFydGAuIFJlc29sdmVkIHRvIGFuXG4vLyBlbXB0eSBzdHViIHZpYSB0aGUgYHdvcmtmbG93YCBleHBvcnQgY29uZGl0aW9uIGluIFZNL3N0ZXAgYnVuZGxlcywgc28gdGhpc1xuLy8gc3RheXMgaG9zdC1vbmx5LlxuLy8gU2VlIGBAd29ya2Zsb3cvY29yZS9zcmMvcnVudGltZS93b3JsZC1pbml0LnRzYCBmb3IgdGhlIGZ1bGwgcmF0aW9uYWxlLlxuaW1wb3J0ICdAd29ya2Zsb3cvY29yZS9ydW50aW1lL3dvcmxkLWluaXQnO1xuXG5leHBvcnQgdHlwZSB7XG4gIENhbmNlbFJ1bk9wdGlvbnMsXG4gIEV2ZW50LFxuICBTdG9wU2xlZXBPcHRpb25zLFxuICBTdG9wU2xlZXBSZXN1bHQsXG4gIFdvcmtmbG93UnVuLFxufSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lJztcbmV4cG9ydCB7XG4gIGdldEhvb2tCeVRva2VuLFxuICB0eXBlIEhvb2ssXG4gIHR5cGUgUmVzdW1lZEhvb2ssXG4gIHJlc3VtZUhvb2ssXG4gIHJlc3VtZVdlYmhvb2ssXG59IGZyb20gJ0B3b3JrZmxvdy9jb3JlL3J1bnRpbWUvcmVzdW1lLWhvb2snO1xuZXhwb3J0IHtcbiAgZ2V0UnVuLFxuICBSdW4sXG4gIHR5cGUgV29ya2Zsb3dSZWFkYWJsZVN0cmVhbSxcbiAgdHlwZSBXb3JrZmxvd1JlYWRhYmxlU3RyZWFtT3B0aW9ucyxcbiAgdHlwZSBXb3JrZmxvd1J1bldyaXRhYmxlU3RyZWFtT3B0aW9ucyxcbn0gZnJvbSAnQHdvcmtmbG93L2NvcmUvcnVudGltZS9ydW4nO1xuZXhwb3J0IHtcbiAgdHlwZSBTdGFydE9wdGlvbnMsXG4gIHN0YXJ0LFxufSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lL3N0YXJ0JztcbiJdfQ==
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2FwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxxRUFBcUU7QUFDckUseUVBQXlFO0FBQ3pFLDJFQUEyRTtBQUMzRSw0RUFBNEU7QUFDNUUsNkVBQTZFO0FBQzdFLG1CQUFtQjtBQUNuQix5RUFBeUU7QUFDekUsT0FBTyxtQ0FBbUMsQ0FBQztBQVMzQyxPQUFPLEVBR0wsc0JBQXNCLEdBRXZCLE1BQU0sd0NBQXdDLENBQUM7QUFDaEQsT0FBTyxFQUNMLGNBQWMsRUFHZCxVQUFVLEVBQ1YsYUFBYSxHQUNkLE1BQU0sb0NBQW9DLENBQUM7QUFDNUMsT0FBTyxFQUNMLE1BQU0sRUFDTixHQUFHLEdBSUosTUFBTSw0QkFBNEIsQ0FBQztBQUNwQyxPQUFPLEVBRUwsS0FBSyxHQUNOLE1BQU0sOEJBQThCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBTaWRlLWVmZmVjdCBpbXBvcnQ6IGVuc3VyZSBgd29ybGQudHNgIGlzIGxvYWRlZCBzbyBpdHMgbW9kdWxlLWxvYWRcbi8vIGBnbG9iYWxUaGlzW0dldFdvcmxkRm5LZXldID8/PSBnZXRXb3JsZGAgcmVnaXN0cmF0aW9uIGZpcmVzIGJlZm9yZSBhbnlcbi8vIGhvc3Qgcm91dGUgcmVhY2hlcyBgZ2V0V29ybGRMYXp5KClgLiBXaXRob3V0IHRoaXMsIHdlYnBhY2svdHVyYm9wYWNrIGNhblxuLy8gdHJlZS1zaGFrZSBgd29ybGQudHNgIG91dCBvZiByb3V0ZXMgdGhhdCBvbmx5IHVzZSBgc3RhcnRgLiBSZXNvbHZlZCB0byBhblxuLy8gZW1wdHkgc3R1YiB2aWEgdGhlIGB3b3JrZmxvd2AgZXhwb3J0IGNvbmRpdGlvbiBpbiBWTS9zdGVwIGJ1bmRsZXMsIHNvIHRoaXNcbi8vIHN0YXlzIGhvc3Qtb25seS5cbi8vIFNlZSBgQHdvcmtmbG93L2NvcmUvc3JjL3J1bnRpbWUvd29ybGQtaW5pdC50c2AgZm9yIHRoZSBmdWxsIHJhdGlvbmFsZS5cbmltcG9ydCAnQHdvcmtmbG93L2NvcmUvcnVudGltZS93b3JsZC1pbml0JztcblxuZXhwb3J0IHR5cGUge1xuICBDYW5jZWxSdW5PcHRpb25zLFxuICBFdmVudCxcbiAgU3RvcFNsZWVwT3B0aW9ucyxcbiAgU3RvcFNsZWVwUmVzdWx0LFxuICBXb3JrZmxvd1J1bixcbn0gZnJvbSAnQHdvcmtmbG93L2NvcmUvcnVudGltZSc7XG5leHBvcnQge1xuICB0eXBlIFJ1bkNvbXBsZXRlZEhvb2tQYXJhbXMsXG4gIHR5cGUgUnVuRmFpbGVkSG9va1BhcmFtcyxcbiAgcmVnaXN0ZXJMaWZlY3ljbGVIb29rcyxcbiAgdHlwZSBXb3JrZmxvd0xpZmVjeWNsZUhvb2tzLFxufSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lL2xpZmVjeWNsZS1ob29rcyc7XG5leHBvcnQge1xuICBnZXRIb29rQnlUb2tlbixcbiAgdHlwZSBIb29rLFxuICB0eXBlIFJlc3VtZWRIb29rLFxuICByZXN1bWVIb29rLFxuICByZXN1bWVXZWJob29rLFxufSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lL3Jlc3VtZS1ob29rJztcbmV4cG9ydCB7XG4gIGdldFJ1bixcbiAgUnVuLFxuICB0eXBlIFdvcmtmbG93UmVhZGFibGVTdHJlYW0sXG4gIHR5cGUgV29ya2Zsb3dSZWFkYWJsZVN0cmVhbU9wdGlvbnMsXG4gIHR5cGUgV29ya2Zsb3dSdW5Xcml0YWJsZVN0cmVhbU9wdGlvbnMsXG59IGZyb20gJ0B3b3JrZmxvdy9jb3JlL3J1bnRpbWUvcnVuJztcbmV4cG9ydCB7XG4gIHR5cGUgU3RhcnRPcHRpb25zLFxuICBzdGFydCxcbn0gZnJvbSAnQHdvcmtmbG93L2NvcmUvcnVudGltZS9zdGFydCc7XG4iXX0=
@@ -5,6 +5,18 @@ description: Vitest plugin and test helpers for integration testing workflows in
5
5
 
6
6
  The `@workflow/vitest` package provides a Vitest plugin and test helpers for running full workflow integration tests in-process, no server required.
7
7
 
8
+ ## Installation
9
+
10
+ ```package-install
11
+ npm i -D @workflow/vitest@beta
12
+ ```
13
+
14
+ <Callout type="warn">
15
+ `@workflow/vitest@latest` is still the 4.x line, so a Workflow 5 app has to install the `beta` tag (or pin the matching beta, for example `@workflow/vitest@5.0.0-beta.53`). The package carries its own copy of `@workflow/core` and runs your workflows against it, so it has to move with `workflow`.
16
+
17
+ `globalSetup` compares the two copies once per run: a different major fails the run with the install command that fixes it, and any other difference logs a warning. Set [`WORKFLOW_VITEST_VERSION_CHECK=off`](/docs/configuration/build-and-diagnostics#workflow_vitest_version_check) to skip the check.
18
+ </Callout>
19
+
8
20
  ## Plugin
9
21
 
10
22
  ### `workflow()`
@@ -108,6 +120,53 @@ Tears down the workflow test world. Clears the global world and closes the Local
108
120
  | `dataDir` | `string` | `<rootDir>/.workflow-data` | Directory for workflow runtime data written by the test world. Relative paths resolve against `cwd`. |
109
121
  | `outDir` | `string` | `<rootDir>/.workflow-vitest` | Directory for generated workflow and step bundles. Relative paths resolve against `cwd`. |
110
122
 
123
+ ## Workflow references
124
+
125
+ The test build writes the same `manifest.json` the other Workflow builders write into `outDir`. These helpers read it, so a test can name a workflow instead of hand-writing the generated `workflow//...` id. Importing the workflow function is still preferable when a test can do it, because it keeps the argument and return types.
126
+
127
+ ### `getWorkflowRef()`
128
+
129
+ Looks up one workflow in the test build and returns it in the shape [`start()`](/docs/api-reference/workflow-api/start) accepts.
130
+
131
+ ```typescript
132
+ import { getWorkflowRef } from "@workflow/vitest"; // [!code highlight]
133
+ import { start } from "workflow/api";
134
+
135
+ const run = await start(getWorkflowRef("approvalWorkflow"), ["doc-1"]); // [!code highlight]
136
+ ```
137
+
138
+ `query` is an exported workflow name, or a file-qualified name when the same name appears in more than one file: `getWorkflowRef("workflows/approval.ts#approvalWorkflow")`. The file part also matches by path suffix, so `"approval.ts#approvalWorkflow"` resolves the same entry.
139
+
140
+ **Parameters:**
141
+
142
+ | Parameter | Type | Description |
143
+ | --- | --- | --- |
144
+ | `query` | `string` | Workflow name, or `<file>#<name>` |
145
+
146
+ **Returns:** [`WorkflowRef`](#workflowref).
147
+
148
+ **Throws** when the manifest is missing (the plugin or `buildWorkflowTests()` has not run), when nothing matches, or when the name is ambiguous. The message lists the workflows the build contains.
149
+
150
+ ### `listWorkflowRefs()`
151
+
152
+ Returns every workflow in the test build, sorted by file and then name.
153
+
154
+ ```typescript
155
+ import { listWorkflowRefs } from "@workflow/vitest"; // [!code highlight]
156
+
157
+ const names = listWorkflowRefs().map((ref) => ref.name); // [!code highlight]
158
+ ```
159
+
160
+ **Returns:** [`WorkflowRef`](#workflowref)`[]`.
161
+
162
+ ### `WorkflowRef`
163
+
164
+ | Property | Type | Description |
165
+ | --- | --- | --- |
166
+ | `name` | `string` | Exported name of the workflow function |
167
+ | `file` | `string` | Project-relative path of the file it was compiled from |
168
+ | `workflowId` | `string` | Generated workflow id, the field `start()` reads |
169
+
111
170
  ## Test helpers
112
171
 
113
172
  ### `waitForSleep()`
@@ -54,7 +54,9 @@ export async function cleanupAttributes() {
54
54
 
55
55
  Attribute keys must be 1-256 characters, values must be strings up to 256 bytes, and each run can have up to 64 attributes. Keys that start with `$` are reserved for framework and library code.
56
56
 
57
- Validation errors throw [`FatalError`](/docs/api-reference/workflow/fatal-error) and fail the run before an attribute write is attempted.
57
+ Each call's complete `attr_set` event data must also fit in **8192 UTF-8 JSON bytes (8KiB)**. This includes the change keys and values, JSON escaping and structure, writer metadata (including the step ID and attempt for step calls), and the reserved-key option when enabled. It is not a limit on values alone. Split large updates into smaller calls; updates across multiple calls are not atomic and still share the 64-attribute per-run limit.
58
+
59
+ Validation errors reject `setAttributes` with [`FatalError`](/docs/api-reference/workflow/fatal-error) before a new attribute write is attempted. Catch the error if the metadata is best-effort; an uncaught error fails the workflow or step. Previously persisted attribute events remain replayable.
58
60
 
59
61
  Calls from both workflow and step bodies append a native `attr_set` event, which the World materializes onto `run.attributes`. Workflow-originated events record a workflow writer; step-originated events record the originating step ID and attempt.
60
62
 
@@ -25,6 +25,9 @@ The `workflow/api` package provides runtime functions to inspect runs, start new
25
25
  <Card href="/docs/api-reference/workflow-api/get-run" title="getRun()">
26
26
  Get workflow run status and metadata without waiting for completion.
27
27
  </Card>
28
+ <Card href="/docs/api-reference/workflow-api/register-lifecycle-hooks" title="registerLifecycleHooks()">
29
+ Observe run completions and failures with global handlers.
30
+ </Card>
28
31
  </Cards>
29
32
 
30
33
  <Callout type="info">
@@ -0,0 +1,85 @@
1
+ ---
2
+ title: registerLifecycleHooks
3
+ description: Register global handlers that observe workflow runs completing or failing.
4
+ type: reference
5
+ summary: Use registerLifecycleHooks to observe run completions and failures from one central place.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/observability/lifecycle-hooks
10
+ - /docs/api-reference/workflow-api/get-run
11
+ ---
12
+
13
+ Registers global workflow lifecycle handlers, invoked by the runtime on the compute that records a run's terminal transition. Use it for best-effort centralized reporting, such as forwarding failed runs to Sentry, without wrapping each workflow body.
14
+
15
+ Register early in the process lifecycle (in Next.js, `instrumentation.ts`) so handlers exist before the first run finishes. See the [lifecycle hooks guide](/docs/observability/lifecycle-hooks) for semantics and a full Sentry example.
16
+
17
+ ```typescript title="instrumentation.ts" lineNumbers
18
+ export async function register() {
19
+ if (process.env.NEXT_RUNTIME === "nodejs") {
20
+ const { registerLifecycleHooks } = await import("workflow/api");
21
+
22
+ registerLifecycleHooks({
23
+ async onRunCompleted({ run, workflowName }) {
24
+ console.log(`Run ${run.runId} (${workflowName}) completed`);
25
+ },
26
+ async onRunFailed({ run, workflowName, error }) {
27
+ console.error(
28
+ `Run ${run.runId} (${workflowName}) failed (${error.errorCode})`,
29
+ error.cause
30
+ );
31
+ },
32
+ });
33
+ }
34
+ }
35
+ ```
36
+
37
+ Keep the dynamic import inside the `NEXT_RUNTIME === "nodejs"` guard. Next.js also compiles `instrumentation.ts` for the Edge runtime. A top-level static import of `workflow/api` pulls Node.js-only dependencies into that compilation and breaks webpack Edge builds, even if the registration call is guarded.
38
+
39
+ ## API Signature
40
+
41
+ ### Parameters
42
+
43
+ <TSDoc
44
+ definition={`
45
+ import { registerLifecycleHooks } from "workflow/api";
46
+ export default registerLifecycleHooks;`}
47
+ showSections={["parameters"]}
48
+ />
49
+
50
+ ### Returns
51
+
52
+ Returns a function that unregisters these hooks.
53
+
54
+ ## Handlers
55
+
56
+ Both handlers receive a `workflowName` string and a lazily hydrated [`Run`](/docs/api-reference/workflow-api/get-run) instance. Use the `workflowName` parameter to filter without a backend read; `run.runId` also requires no read. Accessors such as `run.workflowName`, `run.status`, and `run.returnValue` still fetch from the backend when used. Lazy access defers those reads rather than eliminating them.
57
+
58
+ ### `onRunCompleted`
59
+
60
+ Invoked when a workflow run completes successfully.
61
+
62
+ | Parameter | Type | Description |
63
+ | --- | --- | --- |
64
+ | `params.run` | `Run` | The completed run. |
65
+ | `params.workflowName` | `string` | The machine-readable workflow identifier, such as `workflow//./src/workflows/order//processOrder`. Available without a backend read. |
66
+
67
+ ### `onRunFailed`
68
+
69
+ Invoked when a workflow run fails terminally (after any retries).
70
+
71
+ | Parameter | Type | Description |
72
+ | --- | --- | --- |
73
+ | `params.run` | `Run` | The failed run. |
74
+ | `params.workflowName` | `string` | The machine-readable workflow identifier, such as `workflow//./src/workflows/order//processOrder`. Available without a backend read. |
75
+ | `params.error` | `WorkflowRunFailedError` | The failure, in the same shape `run.returnValue` rejects with: `error.errorCode` carries the classification (e.g. `USER_ERROR`) and `error.cause` is the hydrated thrown value. |
76
+
77
+ `error.cause` is hydrated from the persisted error data, with streamed values loaded lazily when consumed. Abort signals reflect their persisted state without live subscriptions. If hydration fails, the cause is a generic `Error`, matching `run.returnValue`'s fallback. In `onRunFailed`, `run.returnValue` rejects because the run failed. Use `error.cause` to inspect or report the thrown value instead.
78
+
79
+ ## Behavior
80
+
81
+ - Handlers run on the host (full Node.js), never inside the workflow VM. Calling `registerLifecycleHooks` from workflow code throws.
82
+ - Handlers are fire-and-forget. They cannot delay or change the run's outcome, and the runtime logs and swallows a throwing handler. On Vercel, `waitUntil` keeps the invocation alive while handlers finish, subject to the invocation's duration limit. On other hosts, handlers run as detached work and may not complete if the host freezes or terminates the process after the response.
83
+ - Delivery is best effort. Each registered handler is invoked at most once by the invocation that writes the terminal event. Callbacks are not retried if they throw or the process dies, so they may never run or may stop before completing. The [event log](/docs/how-it-works/event-sourcing) is the system of record.
84
+ - Handlers fire only on the invocation that wrote the terminal event. Transitions recorded outside your app's compute (e.g. a run cancelled from the CLI or dashboard) do not fire handlers.
85
+ - You can register multiple hook sets, and handlers run in registration order.
@@ -23,7 +23,7 @@ These APIs are available but are **seeded or fixed** to ensure deterministic beh
23
23
  | API | Behavior |
24
24
  |-----|----------|
25
25
  | [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) | Seeded random number generator: same seed produces the same sequence every replay |
26
- | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) / `Date.now()` / `new Date()` | Returns a fixed timestamp that advances with the workflow's logical clock |
26
+ | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) / `Date.now()` / `new Date()` | Returns the workflow's logical clock: the run's creation time until the first step result, hook payload, hook registration, wait completion or abort reaches the workflow, then the time that event was recorded, advancing as each later one is delivered. Identical on every replay of the same log |
27
27
  | [`crypto.getRandomValues()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) | Seeded: produces deterministic output for a given workflow run |
28
28
  | [`crypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID) | Seeded: produces deterministic UUIDs for a given workflow run |
29
29
  | [`crypto.subtle.digest()`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest) | Computed synchronously via `node:crypto` (values are byte-identical to WebCrypto), so the promise settles at a deterministic point during replay |
@@ -9,7 +9,11 @@ prerequisites:
9
9
 
10
10
  Configures the output directory that [`WorkflowController`](/docs/api-reference/workflow-nest/workflow-controller) loads the generated workflow bundles (`steps.mjs`, `workflows.mjs`, `webhook.mjs`, `manifest.json`) from.
11
11
 
12
- [`WorkflowModule.forRoot()`](/docs/api-reference/workflow-nest/workflow-module) calls this for you with its resolved `outDir`. Call it yourself only when registering `WorkflowController` manually. The controller's route handlers throw if no directory has been configured.
12
+ <Callout type="warn">
13
+ Deprecated. [`WorkflowModule.forRoot()`](/docs/api-reference/workflow-nest/workflow-module) provides the output directory through dependency injection, which this function predates. It writes process-global state, so two applications in one process (the usual `Test.createTestingModule` setup) overwrite each other's configuration. It is kept only so existing callers keep working.
14
+ </Callout>
15
+
16
+ `WorkflowModule.forRoot()` still calls this for you, and injected options take precedence over it. Call it yourself only when registering `WorkflowController` without the module. A controller with no configuration at all answers `503` with an explanatory message.
13
17
 
14
18
  ## Usage
15
19
 
@@ -9,6 +9,10 @@ prerequisites:
9
9
 
10
10
  NestJS controller that handles the well-known workflow endpoints under `.well-known/workflow/v1`. It dynamically imports the generated workflow bundles and converts between Express/Fastify requests and the Web API `Request`/`Response` objects the workflow runtime expects. Both the Express and Fastify HTTP adapters are supported.
11
11
 
12
+ The conversion preserves bytes in both directions: request bodies come from `req.rawBody` when the app is created with `{ rawBody: true }`, from a `Buffer`/string body left by a parser, or read directly from the request stream when no parser claimed the content type. Responses are written as bytes, and every `set-cookie` value is kept. See [Raw request bodies](/docs/getting-started/nestjs#raw-request-bodies).
13
+
14
+ Handlers take `@Res()`, so the application's interceptors and exception filters do not wrap these routes. That is deliberate: the queue and third-party webhook senders key off the exact status and body the workflow runtime produces.
15
+
12
16
  [`WorkflowModule.forRoot()`](/docs/api-reference/workflow-nest/workflow-module) registers this controller automatically. You only register it yourself if you are not using `WorkflowModule`.
13
17
 
14
18
  ## Usage
@@ -35,6 +39,8 @@ export class AppModule {}
35
39
 
36
40
  | Route | Method | Description |
37
41
  | --- | --- | --- |
38
- | `/.well-known/workflow/v1/flow` | `POST` | Executes workflow and step work items via the combined handler in `workflows.mjs` (step registrations are imported from `steps.mjs` first). |
42
+ | `/.well-known/workflow/v1/flow` | `POST`, `GET`, `HEAD`, `OPTIONS` | Executes workflow and step work items via the combined handler in `workflows.mjs` (step registrations are imported from `steps.mjs` first). `HEAD` is what local port detection probes to identify a workflow server, so all four methods are served. |
39
43
  | `/.well-known/workflow/v1/webhook/:token` | Any | Forwards webhook requests to the handler in `webhook.mjs`. |
40
44
  | `/.well-known/workflow/v1/manifest.json` | `GET` | Serves the workflow manifest. Responds with `404` unless the `WORKFLOW_PUBLIC_MANIFEST=1` environment variable is set. |
45
+
46
+ A route whose bundle cannot be loaded answers `503` with a message naming the missing file and the build command that produces it, rather than surfacing a raw `ERR_MODULE_NOT_FOUND`.
@@ -46,7 +46,29 @@ export class AppModule {}
46
46
 
47
47
  #### `forRoot(options?)`
48
48
 
49
- Configures the module and returns a NestJS `DynamicModule` registered as `global`. It calls [`configureWorkflowController`](/docs/api-reference/workflow-nest/configure-workflow-controller) with the resolved output directory, and (unless `skipBuild` is set) creates a [`NestLocalBuilder`](/docs/api-reference/workflow-nest/nest-local-builder) that builds the workflow bundles when the module initializes.
49
+ Configures the module and returns a NestJS `DynamicModule` registered as `global`. It provides the resolved options under the `WORKFLOW_MODULE_OPTIONS` token, and (unless `skipBuild` is set) creates a [`NestLocalBuilder`](/docs/api-reference/workflow-nest/nest-local-builder) that builds the workflow bundles when the module initializes.
50
+
51
+ #### `forRootAsync(options)`
52
+
53
+ Same as `forRoot`, with the options produced by a factory so they can come from other providers.
54
+
55
+ {/* @skip-typecheck - config snippet, WorkflowModule imported above */}
56
+
57
+ ```typescript title="src/app.module.ts" lineNumbers
58
+ WorkflowModule.forRootAsync({
59
+ imports: [ConfigModule],
60
+ inject: [ConfigService],
61
+ useFactory: (config: ConfigService) => ({
62
+ basePath: config.get("API_PREFIX"),
63
+ }),
64
+ });
65
+ ```
66
+
67
+ | Parameter | Type | Description |
68
+ | --- | --- | --- |
69
+ | `imports` | `unknown[]` | Optional. Modules whose exported providers the factory injects. |
70
+ | `inject` | `unknown[]` | Optional. Providers passed to `useFactory`, in order. |
71
+ | `useFactory` | `(...args) => WorkflowModuleOptions \| Promise<WorkflowModuleOptions>` | Returns the module options. |
50
72
 
51
73
  ### Parameters
52
74
 
@@ -60,15 +82,53 @@ Extends [`NestBuilderOptions`](/docs/api-reference/workflow-nest/nest-local-buil
60
82
 
61
83
  | Option | Type | Default | Description |
62
84
  | --- | --- | --- | --- |
63
- | `skipBuild` | `boolean` | `false` | Skip building workflow bundles on startup. Useful in production when the bundles are pre-built. |
85
+ | `skipBuild` | `boolean` | `true` when `VERCEL` is set, else `false` | Skip building workflow bundles on startup. The bundles must already exist; startup fails with an explicit error if they do not. |
86
+ | `basePath` | `string` | adopted from `app.setGlobalPrefix()` | Route prefix the workflow endpoints are served under, applied to generated callback and webhook URLs. Set it when a reverse proxy mounts the app on a sub-path NestJS cannot see. |
87
+ | `manageWorldLifecycle` | `boolean` | `false` | Start the target World's background workers with the app and close them on shutdown. Required for self-hosted Worlds, which otherwise never pick up runs. |
88
+ | `preloadBundles` | `boolean` | `false` when `VERCEL` is set, else `true` | Load the generated bundles during startup instead of on the first request. On Vercel, dedicated functions serve the bundles, so there is nothing to preload. |
64
89
  | `workingDir` | `string` | `process.cwd()` | Working directory for the NestJS application. |
65
90
  | `dirs` | `string[]` | `['src']` | Directories to scan for workflow files. |
66
91
  | `outDir` | `string` | `'.nestjs/workflow'` (relative to `workingDir`) | Output directory for generated workflow bundles. |
67
- | `watch` | `boolean` | `false` | Enable watch mode for development. |
92
+ | `watch` | `boolean` | `false` | Deprecated and ignored. Watch mode is not implemented for this builder. Use `nest start --watch`, which re-runs the startup build. |
68
93
  | `moduleType` | `'es6' \| 'commonjs'` | `'es6'` | SWC module compilation type. Set to `'commonjs'` if your NestJS project compiles to CommonJS (CJS) through SWC. |
69
94
  | `distDir` | `string` | `'dist'` | Directory where NestJS compiles `.ts` source files to `.js` (relative to `workingDir`). Used when `moduleType` is `'commonjs'`. Should match the `outDir` in your `tsconfig.json`. |
70
95
  | `sourcemap` | `boolean \| 'inline' \| 'linked' \| 'external' \| 'both'` | `'inline'` (dev) / `false` (prod) | Controls source maps on generated workflow bundles. Defaults to `'inline'` in development and `false` in production. Can also be set via the `WORKFLOW_SOURCEMAP` environment variable. |
71
96
 
72
97
  ### Returns
73
98
 
74
- `forRoot()` returns a `DynamicModule` to include in the `imports` array of your root module.
99
+ `forRoot()` and `forRootAsync()` return a `DynamicModule` to include in the `imports` array of your root module.
100
+
101
+ ## Injecting the resolved options
102
+
103
+ Both factories export the resolved options under `WORKFLOW_MODULE_OPTIONS`:
104
+
105
+ {/* @skip-typecheck - NestJS decorators require special TypeScript config */}
106
+
107
+ ```typescript title="src/some.service.ts" lineNumbers
108
+ import { Inject, Injectable } from "@nestjs/common";
109
+ import {
110
+ WORKFLOW_MODULE_OPTIONS,
111
+ type WorkflowModuleOptions,
112
+ } from "workflow/nest";
113
+
114
+ @Injectable()
115
+ export class SomeService {
116
+ constructor(
117
+ @Inject(WORKFLOW_MODULE_OPTIONS)
118
+ private readonly options: WorkflowModuleOptions
119
+ ) {}
120
+ }
121
+ ```
122
+
123
+ The older `WORKFLOW_OPTIONS` token resolves to the same value and is kept for compatibility.
124
+
125
+ ## Lifecycle
126
+
127
+ | Hook | Behaviour |
128
+ | --- | --- |
129
+ | `onModuleInit` | Reconciles the base path against `app.setGlobalPrefix()`, builds the bundles (or verifies they exist when `skipBuild` is set), optionally starts the World, and preloads the bundles. |
130
+ | `onApplicationShutdown` | Closes the World when `manageWorldLifecycle` is set. Call `app.enableShutdownHooks()` so this runs on a signal. |
131
+
132
+ <Callout type="warn">
133
+ Workflows and steps run outside the NestJS injector, so providers cannot be injected into `"use workflow"` or `"use step"` code. See [NestJS dependency injection is not available in workflows and steps](/docs/getting-started/nestjs#nestjs-dependency-injection-is-not-available-in-workflows-and-steps).
134
+ </Callout>
@@ -189,12 +189,15 @@ a normal return, not an error, and a missing run throws
189
189
 
190
190
  ```typescript lineNumbers
191
191
  const result = await world.runs.list({ // [!code highlight]
192
+ status: "running", // [!code highlight]
192
193
  pagination: { cursor },
193
194
  }); // [!code highlight]
194
195
  ```
195
196
 
196
197
  | Parameter | Type | Description |
197
198
  |-----------|------|-------------|
199
+ | `params.workflowName` | `string` | Only return runs of this workflow |
200
+ | `params.status` | `WorkflowRunStatus \| WorkflowRunStatus[]` | Only return runs in this status. Pass an array to match any of the listed statuses; an empty array matches no runs. `@workflow/world-vercel` accepts a single status only and throws `WorkflowWorldError` (`INVALID_ARGUMENT`) for an array |
198
201
  | `params.pagination.cursor` | `string` | Cursor for the next page |
199
202
  | `params.resolveData` | `'all' \| 'none'` | Whether to include input/output data |
200
203
 
@@ -219,7 +222,7 @@ To cancel a batch in one call, a world may implement the optional `runs.cancelMa
219
222
  | Field | Type | Description |
220
223
  |-------|------|-------------|
221
224
  | `runId` | `string` | Unique run identifier |
222
- | `status` | `string` | `'running'`, `'completed'`, `'failed'`, `'cancelled'` |
225
+ | `status` | `string` | `'pending'`, `'running'`, `'completed'`, `'failed'`, `'cancelled'` |
223
226
  | `workflowName` | `string` | Machine-readable workflow identifier |
224
227
  | `input` | `any` | Workflow input data (when `resolveData: 'all'`) |
225
228
  | `output` | `any` | Workflow output data (when `resolveData: 'all'`) |
@@ -53,6 +53,15 @@ Accepted values:
53
53
  - The SDK's own runtime serde classes (for example, `Run`) stay registered because they are reached through a seeded entry point, and imports *within* `node_modules` are still followed.
54
54
  - Explicit framework config wins over this environment variable.
55
55
 
56
+ ## Testing
57
+
58
+ ### `WORKFLOW_VITEST_VERSION_CHECK`
59
+
60
+ - Default: enabled
61
+ - Read by [`@workflow/vitest`](/docs/api-reference/vitest) in `globalSetup`, once per test run.
62
+ - The plugin builds and runs your workflows against the copy of `@workflow/core` it was installed with. Before building, it compares that copy with the one your app resolves: a different major fails the run with the install command that fixes it, and any other difference logs a warning.
63
+ - Set `off` (or `0` / `false`) to skip the check, for example in a setup that resolves two copies on purpose.
64
+
56
65
  ## Development diagnostics
57
66
 
58
67
  ### `WORKFLOW_DEV_HMR_LOGS`
@@ -27,6 +27,8 @@ Broader signals are deliberately ignored. `vercel env pull` writes `VERCEL=1` in
27
27
 
28
28
  A deployment that pins `WORKFLOW_TARGET_WORLD=local` warns at startup and fails on its first write, because a Vercel deployment's filesystem is read-only.
29
29
 
30
+ A deployment can land on the Local World without pinning anything, and without that warning, if the project has cleared **Enable access to System Environment Variables** under **Settings**, then **Environment Variables**. That checkbox is what makes Vercel expose `VERCEL_DEPLOYMENT_ID` to your build and your functions; with it off, there is no signal to detect, so detection and the warning both see an ordinary non-Vercel process. See [System environment variables](/worlds/vercel#system-environment-variables) for how to confirm and fix it.
31
+
30
32
  Set `WORKFLOW_TARGET_WORLD` only when you want to use a custom or self-hosted World:
31
33
 
32
34
  - `local`: Alias for `@workflow/world-local`.
@@ -197,7 +199,7 @@ The Vercel World is configured automatically inside Vercel deployments. The plat
197
199
 
198
200
  Most applications should not set `WORKFLOW_VERCEL_*` variables on Vercel. They configure tooling that talks to a Vercel Workflow project from outside a deployment, such as the Workflow CLI, the web user interface (UI), continuous integration (CI), or tests. The runtime warns if these variables are set in a deployed Vercel Function because they do not control runtime configuration there.
199
201
 
200
- Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, and `VERCEL_DEPLOYMENT_KEY` are read by the runtime inside Vercel deployments. Do not set them yourself.
202
+ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, and `VERCEL_DEPLOYMENT_KEY` are read by the runtime inside Vercel deployments. Do not set them yourself; keep [system environment variables](/worlds/vercel#system-environment-variables) enabled for the project so that Vercel provides them.
201
203
 
202
204
  ### `token`
203
205
 
@@ -51,6 +51,17 @@ vercel deploy
51
51
 
52
52
  <FluidComputeCallout />
53
53
 
54
+ <Callout type="warn">
55
+ **Enable access to System Environment Variables before deploying.** Workflow
56
+ recognizes a Vercel deployment by `VERCEL_DEPLOYMENT_ID`, which Vercel exposes
57
+ to your build and your functions only when the **Enable access to System
58
+ Environment Variables** checkbox is selected under **Settings**, then
59
+ **Environment Variables**, in your project. With it cleared, the deployment
60
+ falls back to the Local World and every run fails on the read-only
61
+ filesystem. See
62
+ [System environment variables](/worlds/vercel#system-environment-variables).
63
+ </Callout>
64
+
54
65
  <Callout>
55
66
  Learn more about the [Vercel World](/worlds/vercel) and its capabilities, including [multi-region](/worlds/vercel#multi-region).
56
67
  </Callout>
@@ -398,15 +398,11 @@ export default async function handler(req: express.Request, res: express.Respons
398
398
  }
399
399
  ```
400
400
 
401
- Skip the in-process build on Vercel (the bundles are pre-built) by passing `skipBuild` when
402
- `VERCEL` is set:
401
+ No module change is needed for the in-process build: `skipBuild` defaults to `true` when the
402
+ `VERCEL` environment variable is set, because the Build Output already contains the bundles and
403
+ the deployed filesystem is read-only.
403
404
 
404
- {/* @skip-typecheck - config snippet, WorkflowModule imported above */}
405
- ```typescript title="src/app.module.ts"
406
- WorkflowModule.forRoot({ skipBuild: Boolean(process.env.VERCEL) });
407
- ```
408
-
409
- Then set your build command so the Build Output is produced after `nest build`:
405
+ Set your build command so the Build Output is produced after `nest build`:
410
406
 
411
407
  ```json title="package.json" lineNumbers
412
408
  {
@@ -440,7 +436,9 @@ WorkflowModule.forRoot({
440
436
  // Output directory for generated bundles (default: '.nestjs/workflow')
441
437
  outDir: '.nestjs/workflow',
442
438
 
443
- // Skip building in production when bundles are pre-built
439
+ // Skip building when bundles are pre-built with `workflow-nest build`
440
+ // (default: true when VERCEL is set, false otherwise). Startup fails if the
441
+ // bundles are missing.
444
442
  skipBuild: false,
445
443
 
446
444
  // SWC module type: 'es6' (default) or 'commonjs'
@@ -460,9 +458,187 @@ WorkflowModule.forRoot({
460
458
  // size limit) at the cost of stack traces pointing at generated code.
461
459
  // Can also be set via the WORKFLOW_SOURCEMAP environment variable.
462
460
  sourcemap: 'inline',
461
+
462
+ // Route prefix the workflow endpoints are served under. Leave unset to adopt
463
+ // app.setGlobalPrefix() automatically; set it when a reverse proxy mounts the
464
+ // app on a sub-path NestJS does not know about. See "Global prefixes" below.
465
+ basePath: '/api',
466
+
467
+ // Start the target World's background workers with the app and close them on
468
+ // shutdown. Self-hosted Worlds (for example @workflow/world-postgres) need
469
+ // this or runs are created and never picked up. Leave off on Vercel.
470
+ manageWorldLifecycle: false,
471
+
472
+ // Load the generated bundles during startup instead of on the first request
473
+ // (default: true, or false when VERCEL is set because dedicated functions
474
+ // serve the bundles there).
475
+ preloadBundles: true,
463
476
  });
464
477
  ```
465
478
 
479
+ Options can also come from other providers with `forRootAsync`:
480
+
481
+ {/*@skip-typecheck - Configuration snippet, imports shown above*/}
482
+
483
+ ```typescript
484
+ WorkflowModule.forRootAsync({
485
+ imports: [ConfigModule],
486
+ inject: [ConfigService],
487
+ useFactory: (config: ConfigService) => ({
488
+ basePath: config.get('API_PREFIX'),
489
+ }),
490
+ });
491
+ ```
492
+
493
+ ---
494
+
495
+ ## Global prefixes and sub-paths
496
+
497
+ `app.setGlobalPrefix()` moves the workflow routes. The SDK has to generate its
498
+ queue callback and webhook URLs under the same prefix, or every delivery 404s and
499
+ runs stay `pending`.
500
+
501
+ `WorkflowModule` reads the global prefix during startup and adopts it, so this
502
+ works with no configuration:
503
+
504
+ {/*@skip-typecheck - Bootstrap snippet*/}
505
+
506
+ ```typescript
507
+ const app = await NestFactory.create(AppModule);
508
+ app.setGlobalPrefix('api'); // workflow URLs become /api/.well-known/workflow/v1/...
509
+ await app.listen(3000);
510
+ ```
511
+
512
+ Set `basePath` explicitly when the prefix is applied outside NestJS, for example
513
+ by a reverse proxy that strips it before the request reaches your app. An
514
+ explicit `basePath` wins over the global prefix, and a disagreement between the
515
+ two is logged at startup.
516
+
517
+ For Vercel, pass the same value to the build so the deployed queue-consumer
518
+ function generates matching URLs:
519
+
520
+ ```bash
521
+ workflow-nest build --vercel --base-path /api
522
+ ```
523
+
524
+ <Callout type="warn">
525
+ `app.enableVersioning()` moves the workflow routes the same way, and is not
526
+ handled automatically. Exclude the workflow controller from versioning, or mount
527
+ it where the SDK expects it.
528
+ </Callout>
529
+
530
+ ---
531
+
532
+ ## Raw request bodies
533
+
534
+ The workflow routes are a byte pipe. A webhook that verifies a signature over its
535
+ raw body (Stripe, GitHub, Shopify, Slack) only works if the bytes the sender
536
+ signed reach the workflow unchanged, and a body parser that has already turned
537
+ the request into an object destroys them.
538
+
539
+ Create the app with `rawBody` so the original bytes stay available:
540
+
541
+ {/*@skip-typecheck - Bootstrap snippet*/}
542
+
543
+ ```typescript
544
+ const app = await NestFactory.create(AppModule, { rawBody: true });
545
+ ```
546
+
547
+ Without it, `@workflow/nest` falls back to re-serializing the parsed body with
548
+ `JSON.stringify` and logs a warning once. That changes whitespace and key order,
549
+ so signature verification fails.
550
+
551
+ Content types no body parser claims (XML, `application/x-www-form-urlencoded`
552
+ without the parser registered, custom media types) are read straight from the
553
+ request stream and need no configuration.
554
+
555
+ <Callout type="info">
556
+ On Vercel the webhook route is served by its own function rather than by your
557
+ NestJS app, so this only affects self-hosted deployments.
558
+ </Callout>
559
+
560
+ ---
561
+
562
+ ## NestJS dependency injection is not available in workflows and steps
563
+
564
+ Workflows and steps do not run inside your NestJS application. They are compiled
565
+ into separate bundles, so **the Nest injector, your providers, and anything
566
+ built on the request context are out of reach from `"use workflow"` and
567
+ `"use step"` code.**
568
+
569
+ Concretely, none of these work inside a step:
570
+
571
+ - injecting a provider, or resolving one with `app.get(MyService)`
572
+ - `@Injectable()` classes reached through a module-level reference to the app
573
+ - request-scoped providers, and `AsyncLocalStorage` context such as `nestjs-cls`
574
+ - guards, interceptors, pipes, and filters
575
+ - the Nest `Logger`
576
+
577
+ Two mechanics cause this, and neither has a workaround:
578
+
579
+ 1. A step's bundle gets **its own copy** of any application file it imports.
580
+ Module-level state is therefore duplicated, and a class imported into a step
581
+ is a different class object from the one your module registered. Nest uses the
582
+ class itself as the injection token, so `app.get(MyService)` from a step
583
+ raises `UnknownElementException` even when it can reach the app.
584
+ 2. On Vercel, workflows and steps run in the queue-consumer function, a separate
585
+ function from the one serving your NestJS app. There is no shared process to
586
+ reach into.
587
+
588
+ Write steps as plain functions over their arguments, and keep the wiring in your
589
+ controllers and providers:
590
+
591
+ {/*@skip-typecheck - Illustrates the pattern, not a complete app*/}
592
+
593
+ ```typescript
594
+ // A step takes what it needs as arguments and builds its own clients.
595
+ async function chargeCustomer(customerId: string, cents: number) {
596
+ 'use step';
597
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
598
+ return await stripe.charges.create({ customer: customerId, amount: cents });
599
+ }
600
+
601
+ // The controller stays a normal Nest controller with normal DI.
602
+ @Controller('billing')
603
+ export class BillingController {
604
+ constructor(private readonly customers: CustomerService) {}
605
+
606
+ @Post('charge')
607
+ async charge(@Body() body: { id: string }) {
608
+ const customer = await this.customers.find(body.id);
609
+ // Pass plain, serializable values into the workflow.
610
+ await start(billingWorkflow, [customer.id, customer.planCents]);
611
+ return { started: true };
612
+ }
613
+ }
614
+ ```
615
+
616
+ Configuration a step needs should come from the environment rather than
617
+ `ConfigService`, and shared logic should live in plain modules that a step can
618
+ import without pulling in `@nestjs/common`.
619
+
620
+ <Callout type="info">
621
+ Importing a file that uses `@nestjs/common` into a step is supported and builds
622
+ correctly, it simply gives you a class with no injected dependencies. Prefer
623
+ plain functions so the intent is clear.
624
+ </Callout>
625
+
626
+ ---
627
+
628
+ ## Production checklist
629
+
630
+ - Run `workflow-nest build` in your build step and set `skipBuild: true`, or
631
+ leave `skipBuild` unset so the bundles are built during startup. With
632
+ `skipBuild` set and no bundles present, startup fails with an explicit error
633
+ rather than serving broken workflow routes.
634
+ - Create the app with `{ rawBody: true }` if you receive signed webhooks.
635
+ - Set `basePath` (or rely on the adopted global prefix) so generated URLs match
636
+ the routes NestJS serves.
637
+ - Set `manageWorldLifecycle: true` for a self-hosted World, and call
638
+ `app.enableShutdownHooks()` so the World is closed on a signal.
639
+ - `skipBuild` defaults to `true` when the `VERCEL` environment variable is set,
640
+ so no Vercel-specific branch is needed in your module configuration.
641
+
466
642
  ## Troubleshooting
467
643
 
468
644
  ### `start()` says it received an invalid workflow function
@@ -57,6 +57,8 @@ export async function cleanupAttributes() {
57
57
 
58
58
  Attribute keys must be 1-256 characters, values must be strings up to 256 bytes, and each run can have up to 64 attributes. Keys that start with `$` are reserved for framework and library code.
59
59
 
60
+ Each `setAttributes` call also writes one `attr_set` event, and that event's complete data (keys, values, and writer metadata) must fit in 8192 UTF-8 JSON bytes (8KiB). Split large updates across several calls; the calls still share the 64-attribute limit. A call that exceeds a limit rejects with [`FatalError`](/docs/api-reference/workflow/fatal-error) before anything is written, so catch it when the metadata is best-effort.
61
+
60
62
  ## Reserved keys
61
63
 
62
64
  When `start()` is called from inside a running workflow or step, the new run is automatically tagged with two reserved attributes:
@@ -0,0 +1,93 @@
1
+ ---
2
+ title: Lifecycle Hooks
3
+ description: Register global handlers that observe workflow runs completing or failing, for centralized reporting to services like Sentry.
4
+ type: guide
5
+ summary: Observe run completions and failures from a single place with registerLifecycleHooks.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/observability
10
+ - /docs/observability/tracing
11
+ - /docs/errors
12
+ ---
13
+
14
+ Lifecycle hooks let you register global handlers that observe workflow runs completing or failing on your app's compute. They can observe even the failures that never reach a `try/catch` in workflow code, such as a replay timing out or a run exhausting its queue deliveries. Use them for best-effort centralized error reporting, such as forwarding failed runs to Sentry without wrapping each workflow body.
15
+
16
+ ## Registering hooks
17
+
18
+ Call `registerLifecycleHooks` from `workflow/api` early in your application's lifecycle, so the handlers exist before the first run finishes. In Next.js, [`instrumentation.ts`](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation) is the natural place; in any other app, any module that loads at startup works.
19
+
20
+ ```typescript title="instrumentation.ts" lineNumbers
21
+ export async function register() {
22
+ if (process.env.NEXT_RUNTIME === "nodejs") {
23
+ const { registerLifecycleHooks } = await import("workflow/api")
24
+
25
+ registerLifecycleHooks({
26
+ async onRunCompleted({ run, workflowName }) {
27
+ console.log(`Run ${run.runId} (${workflowName}) completed`)
28
+ },
29
+ async onRunFailed({ run, workflowName, error }) {
30
+ console.error(
31
+ `Run ${run.runId} (${workflowName}) failed with ${error.errorCode}:`,
32
+ error.cause
33
+ )
34
+ },
35
+ })
36
+ }
37
+ }
38
+ ```
39
+
40
+ Keep the dynamic `workflow/api` import inside the `NEXT_RUNTIME === "nodejs"` guard. Next.js also compiles `instrumentation.ts` for the Edge runtime. A top-level static import pulls Node.js-only dependencies into that compilation and breaks webpack Edge builds, even if the registration call is guarded.
41
+
42
+ `registerLifecycleHooks` returns an unregister function. You can register multiple hook sets, and handlers run in registration order.
43
+
44
+ ## Handler parameters
45
+
46
+ Both handlers receive a `workflowName` string and the [`Run`](/docs/api-reference/workflow-api/get-run) instance for the transitioned run. `workflowName` is the machine-readable workflow identifier, such as `workflow//./src/workflows/order//processOrder`. Use this parameter to filter runs without a backend read. `run.runId` is also available without a read.
47
+
48
+ The `Run` instance hydrates lazily. Accessors such as `run.workflowName`, `run.status`, and `run.returnValue` still fetch from the backend when used. Lazy access defers those reads; it does not make them free.
49
+
50
+ `onRunFailed` additionally receives the failure as a `WorkflowRunFailedError`, the same shape `run.returnValue` rejects with:
51
+
52
+ - `error.errorCode`: the failure classification (`USER_ERROR`, `RUNTIME_ERROR`, `MAX_DELIVERIES_EXCEEDED`, and more). See [error codes](/docs/errors) for the full list.
53
+ - `error.cause`: the thrown value hydrated from the persisted error data, with registered Error subclass identity, message, stack, and cause chain preserved. Streamed values load lazily when consumed, and abort signals reflect their persisted state without live subscriptions. If hydration fails, the cause is a generic `Error`, matching `run.returnValue`'s fallback. Any JavaScript value can be thrown, so this is typed `unknown`.
54
+
55
+ In `onRunFailed`, `run.returnValue` rejects because the run failed. Use `error.cause` to inspect or report the thrown value instead of awaiting `run.returnValue`.
56
+
57
+ ## Reporting failed runs to Sentry
58
+
59
+ This example reports failures for workflows named `processOrder`. Remove the filter to report failures from all workflows.
60
+
61
+ ```typescript title="instrumentation.ts" lineNumbers
62
+ export async function register() {
63
+ if (process.env.NEXT_RUNTIME === "nodejs") {
64
+ const { registerLifecycleHooks } = await import("workflow/api")
65
+ const Sentry = await import("@sentry/nextjs")
66
+
67
+ Sentry.init({ dsn: process.env.SENTRY_DSN })
68
+
69
+ registerLifecycleHooks({
70
+ async onRunFailed({ run, workflowName, error }) {
71
+ if (!workflowName.endsWith("//processOrder")) return
72
+
73
+ Sentry.captureException(error.cause ?? error, {
74
+ tags: {
75
+ workflowRunId: run.runId,
76
+ workflowName,
77
+ errorCode: error.errorCode,
78
+ },
79
+ })
80
+ await Sentry.flush(2000)
81
+ },
82
+ })
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## How handlers behave
88
+
89
+ - **Host-only.** Handlers run with full Node.js access, never inside the workflow's sandboxed VM. Calling `registerLifecycleHooks` from workflow code throws.
90
+ - **Fire-and-forget.** Handlers cannot delay or change the run's outcome. The runtime logs and swallows a throwing handler, and the remaining handlers still run. On Vercel, `waitUntil` keeps the invocation alive while handlers finish, subject to the invocation's duration limit. On other hosts, handlers run as detached work. If the host freezes or terminates the process after the response, handlers may not complete.
91
+ - **Best-effort delivery.** Each registered handler is invoked at most once by the invocation that writes the terminal event. For a failure, this happens after any workflow or step retries are exhausted. Callbacks are not retried if they throw or the process dies, so a callback may never run or may stop before completing. The [event log](/docs/how-it-works/event-sourcing) is the system of record, not lifecycle callbacks.
92
+ - **Fires where the transition is recorded.** Terminal transitions recorded outside your app's compute do **not** fire handlers. For example, when you cancel a run from the CLI or the Vercel dashboard, the backend writes that transition, so no handler runs.
93
+ - **Register everywhere your workflows run.** The terminal write can happen in any function invocation that processes the run's queue messages, so registration must run at startup in every instance of the app (which `instrumentation.ts` guarantees).
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "title": "Observability",
3
- "pages": ["tracing", "attributes", "retention"]
3
+ "pages": ["tracing", "attributes", "retention", "lifecycle-hooks"]
4
4
  }
@@ -85,7 +85,19 @@ Unit testing works well for individual steps. A workflow that only calls steps c
85
85
  For workflows that rely on runtime features like [hooks](/docs/foundations/hooks), [webhooks](/docs/foundations/hooks#understanding-webhooks), [`sleep()`](/docs/api-reference/workflow/sleep), or error retries, you need to test against a real workflow setup. The `@workflow/vitest` plugin handles everything automatically: it compiles your workflow directives, builds the runtime bundles, and executes workflows entirely in-process. No server required.
86
86
 
87
87
  <Callout type="warn">
88
- `vi.mock()` and related calls do _not_ work inside workflow functions, only step functions. Your workflow functions cannot import third party code that needs to be mocked. Mocking works for npm packages imported in step functions. If something needs to be mocked, it likely belongs inside a step function either way.
88
+ `vi.mock()` cannot reach code inside workflow functions at all, and reaches step code only under specific conditions. Read [Mocking](#mocking) before reaching for it.
89
+ </Callout>
90
+
91
+ ### Installation
92
+
93
+ ```package-install
94
+ npm i -D @workflow/vitest@beta
95
+ ```
96
+
97
+ <Callout type="warn">
98
+ **Workflow 5 needs the `beta` tag.** `@workflow/vitest@latest` is still the 4.x line, so a plain `npm i -D @workflow/vitest` installs a v4 plugin next to a v5 app. Install `@workflow/vitest@beta`, or pin the beta your app is on (for example `@workflow/vitest@5.0.0-beta.53`), and upgrade it together with `workflow`.
99
+
100
+ The plugin builds and runs your workflows against the copy of `@workflow/core` it was installed with, so it checks this for you. At the start of each run it compares that copy with the one your app resolves: a different major fails the run with the install command that fixes it, and any other difference logs a warning. Set [`WORKFLOW_VITEST_VERSION_CHECK=off`](/docs/configuration/build-and-diagnostics#workflow_vitest_version_check) to skip the check.
89
101
  </Callout>
90
102
 
91
103
  ### Vitest configuration
@@ -303,6 +315,72 @@ describe("ingestWorkflow", () => {
303
315
  });
304
316
  ```
305
317
 
318
+ ### Referencing workflows by name
319
+
320
+ Importing the workflow function is the best way to start it: [`start()`](/docs/api-reference/workflow-api/start) keeps its argument and return types. When a test cannot import it, because the workflow lives in a module the test does not pull in or because the test drives runs by name, look it up with [`getWorkflowRef()`](/docs/api-reference/vitest#getworkflowref) instead of hand-writing the generated `workflow//...` id:
321
+
322
+ ```typescript title="workflows/approval.integration.test.ts" lineNumbers
323
+ import { describe, it, expect } from "vitest";
324
+ import { start } from "workflow/api";
325
+ import { getWorkflowRef, listWorkflowRefs } from "@workflow/vitest"; // [!code highlight]
326
+
327
+ describe("approvalWorkflow", () => {
328
+ it("is part of the test build", () => {
329
+ expect(listWorkflowRefs().map((ref) => ref.name)).toContain( // [!code highlight]
330
+ "approvalWorkflow"
331
+ );
332
+ });
333
+
334
+ it("can be started by name", async () => {
335
+ const run = await start(getWorkflowRef("approvalWorkflow"), ["doc-1"]); // [!code highlight]
336
+ expect(run.runId).toMatch(/^wrun_/);
337
+ });
338
+ });
339
+ ```
340
+
341
+ The names come from the manifest the test build writes next to its bundles, so they follow your code when files move. When one exported name appears in several files, qualify it with the file, `getWorkflowRef("workflows/approval.ts#approvalWorkflow")`, which also matches by path suffix. An unknown or ambiguous name throws with the workflows the build does contain.
342
+
343
+ <Callout type="info">
344
+ A reference is matched by name, so the run is typed as `Run<unknown>`. Import the workflow function when you want the argument and return types checked.
345
+ </Callout>
346
+
347
+ ### Mocking
348
+
349
+ Steps do not run from your test file's module graph. They run from the bundles the plugin builds, and workflow bodies run from a code string inside the sandboxed VM, so `vi.mock()` reaches less here than in an ordinary Vitest test:
350
+
351
+ | What you mock | Step code sees it | Workflow body sees it |
352
+ | --- | --- | --- |
353
+ | An npm package a step file imports | Only when the generated bundles load through Vitest's module runner (see below) | No |
354
+ | An npm package imported by a local module that a step file imports | Same as above | No |
355
+ | A project-local module a step file imports | No: it is bundled into the step bundle, so there is no module left to replace | No |
356
+ | A step function imported and called directly, with no `workflow()` plugin | Yes, ordinary Vitest rules | n/a |
357
+
358
+ Workflow bodies execute in a VM that has no module registry, so nothing can intercept their imports. That is the same reason side effects belong in steps: if a dependency needs mocking, it belongs on the step side.
359
+
360
+ Whether step code sees an npm mock depends on how Vitest loaded the plugin. In a normal install `@workflow/vitest` is external to Vitest, Node loads the generated bundle directly, and steps get the real package. Ask for the other behavior explicitly:
361
+
362
+ ```typescript title="vitest.integration.config.ts" lineNumbers
363
+ import { defineConfig } from "vitest/config";
364
+ import { workflow } from "@workflow/vitest";
365
+
366
+ export default defineConfig({
367
+ plugins: [workflow()],
368
+ test: {
369
+ // Route the generated bundles through Vitest's module runner, so vi.mock()
370
+ // applies to the npm packages your steps import.
371
+ server: { deps: { inline: [/@workflow\/vitest/] } }, // [!code highlight]
372
+ },
373
+ });
374
+ ```
375
+
376
+ The cost is that the generated bundle goes through Vite's transform pipeline in every test worker.
377
+
378
+ Three approaches work regardless of how the plugin was loaded:
379
+
380
+ - **Unit test the step directly.** Without the plugin, `"use step"` is a no-op and the function is an ordinary async function, so `vi.mock()` behaves normally.
381
+ - **Pass the dependency in.** A step that takes its collaborator as an argument is controlled by the caller, with no module interception involved.
382
+ - **Feed values through hooks.** Resume a [hook](/docs/foundations/hooks) with the data you want instead of mocking whatever would have produced it.
383
+
306
384
  ### Manual setup
307
385
 
308
386
  If you need more control over the test lifecycle, the plugin also exports the individual setup functions:
@@ -403,6 +481,10 @@ Workflows may take longer to execute than typical unit tests, especially when th
403
481
 
404
482
  Integration tests are the right place to verify that your workflows handle errors correctly, including retryable errors, fatal errors, and timeout scenarios.
405
483
 
484
+ ### Upgrade `@workflow/vitest` with the SDK
485
+
486
+ `@workflow/vitest` carries its own copy of the Workflow runtime, so treat it as part of the same upgrade as `workflow`. While Workflow 5 is in beta, that means the `beta` tag on both. See [Installation](#installation).
487
+
406
488
  ## Further reading
407
489
 
408
490
  - [Hooks & Webhooks](/docs/foundations/hooks) - Pausing and resuming workflows with external data
@@ -410,7 +492,7 @@ Integration tests are the right place to verify that your workflows handle error
410
492
  - [`resumeHook()` API Reference](/docs/api-reference/workflow-api/resume-hook) - Resume hooks with data
411
493
  - [`resumeWebhook()` API Reference](/docs/api-reference/workflow-api/resume-webhook) - Resume webhooks with Request objects
412
494
  - [`getRun()` API Reference](/docs/api-reference/workflow-api/get-run) - Check workflow run status and wake up sleeping runs
413
- - [`@workflow/vitest` API Reference](/docs/api-reference/vitest) - Test helpers: `waitForSleep()`, `waitForHook()`, and plugin setup
495
+ - [`@workflow/vitest` API Reference](/docs/api-reference/vitest) - Test helpers: `waitForSleep()`, `waitForHook()`, `getWorkflowRef()`, and plugin setup
414
496
  - [Vite Integration](/docs/getting-started/vite) - Set up the Vite plugin
415
497
  - [Observability](/docs/observability) - Inspect and debug workflow runs with the CLI and Web UI
416
498
  - [Server-based testing](/docs/testing/server-based) - Integration testing with a running server
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow",
3
- "version": "5.0.0-beta.53",
3
+ "version": "5.0.0-beta.55",
4
4
  "description": "Workflow SDK - Build durable, resilient, and observable workflows",
5
5
  "main": "dist/typescript-plugin.cjs",
6
6
  "type": "module",
@@ -58,19 +58,19 @@
58
58
  }
59
59
  },
60
60
  "dependencies": {
61
- "@workflow/astro": "5.0.0-beta.53",
62
- "@workflow/cli": "5.0.0-beta.53",
63
- "@workflow/core": "5.0.0-beta.53",
64
- "@workflow/errors": "5.0.0-beta.21",
61
+ "@workflow/astro": "5.0.0-beta.55",
62
+ "@workflow/cli": "5.0.0-beta.55",
63
+ "@workflow/core": "5.0.0-beta.55",
64
+ "@workflow/errors": "5.0.0-beta.22",
65
65
  "@workflow/typescript-plugin": "5.0.0-beta.5",
66
66
  "@workflow/utils": "5.0.0-beta.10",
67
67
  "ms": "2.1.3",
68
- "@workflow/next": "5.0.0-beta.53",
69
- "@workflow/nest": "5.0.0-beta.53",
70
- "@workflow/nitro": "5.0.0-beta.53",
71
- "@workflow/nuxt": "5.0.0-beta.53",
72
- "@workflow/sveltekit": "5.0.0-beta.53",
73
- "@workflow/rollup": "5.0.0-beta.53"
68
+ "@workflow/next": "5.0.0-beta.55",
69
+ "@workflow/nest": "5.0.0-beta.55",
70
+ "@workflow/nitro": "5.0.0-beta.55",
71
+ "@workflow/nuxt": "5.0.0-beta.55",
72
+ "@workflow/sveltekit": "5.0.0-beta.55",
73
+ "@workflow/rollup": "5.0.0-beta.55"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@types/ms": "2.1.0",