@wooksjs/event-wf 0.6.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,38 +1,16 @@
1
- import { createAsyncEventContext, useAsyncEventContext } from "@wooksjs/event-core";
1
+ import { EventContext, current, defineEventKind, key, run, slot, useLogger, useRouteParams } from "@wooksjs/event-core";
2
2
  import { StepRetriableError, Workflow, createStep } from "@prostojs/wf";
3
3
  import { WooksAdapterBase } from "wooks";
4
4
 
5
- //#region packages/event-wf/src/event-wf.ts
6
- /** Creates a new async event context for a fresh workflow execution. */
7
- function createWfContext(data, options) {
8
- return createAsyncEventContext({
9
- event: {
10
- ...data,
11
- type: "WF"
12
- },
13
- resume: false,
14
- options
15
- });
16
- }
17
- /** Creates an async event context for resuming a paused workflow. */
18
- function resumeWfContext(data, options) {
19
- return createAsyncEventContext({
20
- event: {
21
- ...data,
22
- type: "WF"
23
- },
24
- resume: true,
25
- options
26
- });
27
- }
28
- /**
29
- * Wrapper on top of useEventContext that provides
30
- * proper context types for WF event
31
- * @returns set of hooks { getCtx, restoreCtx, clearCtx, hookStore, getStore, setStore }
32
- */
33
- function useWFContext() {
34
- return useAsyncEventContext("WF");
35
- }
5
+ //#region packages/event-wf/src/wf-kind.ts
6
+ const wfKind = defineEventKind("WF", {
7
+ schemaId: slot(),
8
+ stepId: slot(),
9
+ inputContext: slot(),
10
+ indexes: slot(),
11
+ input: slot()
12
+ });
13
+ const resumeKey = key("wf.resume");
36
14
 
37
15
  //#endregion
38
16
  //#region packages/event-wf/src/composables/wf-state.ts
@@ -46,18 +24,58 @@ function useWFContext() {
46
24
  * ```
47
25
  */
48
26
  function useWfState() {
49
- const { store, getCtx } = useWFContext();
50
- const event = store("event");
27
+ const c = current();
51
28
  return {
52
- ctx: () => event.get("inputContext"),
53
- input: () => event.get("input"),
54
- schemaId: event.get("schemaId"),
55
- stepId: () => event.get("stepId"),
56
- indexes: () => event.get("indexes"),
57
- resume: getCtx().resume
29
+ ctx: () => c.get(wfKind.keys.inputContext),
30
+ input: () => c.get(wfKind.keys.input),
31
+ schemaId: c.get(wfKind.keys.schemaId),
32
+ stepId: () => c.get(wfKind.keys.stepId),
33
+ indexes: () => c.get(wfKind.keys.indexes),
34
+ resume: c.get(resumeKey)
58
35
  };
59
36
  }
60
37
 
38
+ //#endregion
39
+ //#region packages/event-wf/src/event-wf.ts
40
+ const wfSeeds = (data) => ({
41
+ schemaId: data.schemaId,
42
+ stepId: data.stepId,
43
+ inputContext: data.inputContext,
44
+ indexes: data.indexes,
45
+ input: data.input
46
+ });
47
+ /**
48
+ * Creates a new event context for a fresh workflow execution.
49
+ * When `parentCtx` is provided, the workflow creates a child context
50
+ * linked to the parent, so step handlers can transparently access
51
+ * composables from the parent scope (e.g. HTTP) via the parent chain.
52
+ */
53
+ function createWfContext(data, options, parentCtx) {
54
+ const ctx = new EventContext(parentCtx ? {
55
+ ...options,
56
+ parent: parentCtx
57
+ } : options);
58
+ return (fn) => run(ctx, () => {
59
+ ctx.set(resumeKey, false);
60
+ return ctx.seed(wfKind, wfSeeds(data), fn);
61
+ });
62
+ }
63
+ /**
64
+ * Creates an event context for resuming a paused workflow.
65
+ * When `parentCtx` is provided, the workflow creates a child context
66
+ * linked to the parent.
67
+ */
68
+ function resumeWfContext(data, options, parentCtx) {
69
+ const ctx = new EventContext(parentCtx ? {
70
+ ...options,
71
+ parent: parentCtx
72
+ } : options);
73
+ return (fn) => run(ctx, () => {
74
+ ctx.set(resumeKey, true);
75
+ return ctx.seed(wfKind, wfSeeds(data), fn);
76
+ });
77
+ }
78
+
61
79
  //#endregion
62
80
  //#region packages/event-wf/src/workflow.ts
63
81
  /** Workflow engine that resolves steps via Wooks router lookup. */
@@ -69,10 +87,10 @@ var WooksWorkflow = class extends Workflow {
69
87
  resolveStep(stepId) {
70
88
  const stepIdNorm = `/${stepId}`.replace(/\/{2,}/gu, "/");
71
89
  try {
72
- const store = useWFContext().store("event");
90
+ const ctx = current();
73
91
  const found = this.wooks.lookup("WF_STEP", stepIdNorm);
74
92
  if (found.handlers?.length) {
75
- store.set("stepId", stepIdNorm);
93
+ ctx.set(wfKind.keys.stepId, stepIdNorm);
76
94
  return found.handlers[0]();
77
95
  }
78
96
  } catch {
@@ -122,15 +140,40 @@ var WooksWf = class extends WooksAdapterBase {
122
140
  id
123
141
  }));
124
142
  }
125
- /** Starts a new workflow execution from the beginning. */
126
- start(schemaId, inputContext, input, spy, cleanup) {
127
- return this._start(schemaId, inputContext, void 0, input, spy, cleanup);
143
+ /**
144
+ * Starts a new workflow execution from the beginning.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * // Simple
149
+ * await app.start('my-flow', { result: 0 })
150
+ *
151
+ * // With options
152
+ * await app.start('my-flow', { result: 0 }, { input: 5, eventContext: current() })
153
+ * ```
154
+ */
155
+ start(schemaId, inputContext, opts) {
156
+ const parentCtx = opts?.eventContext;
157
+ return this._start(schemaId, inputContext, void 0, opts, parentCtx);
128
158
  }
129
- /** Resumes a previously paused workflow from saved state. */
130
- resume(state, input, spy, cleanup) {
131
- return this._start(state.schemaId, state.context, state.indexes, input, spy, cleanup);
159
+ /**
160
+ * Resumes a previously paused workflow from saved state.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * // With user input
165
+ * await app.resume(output.state, { input: userInput })
166
+ *
167
+ * // Simple retry (no input)
168
+ * await app.resume(output.state)
169
+ * ```
170
+ */
171
+ resume(state, opts) {
172
+ const parentCtx = opts?.eventContext;
173
+ return this._start(state.schemaId, state.context, state.indexes, opts, parentCtx);
132
174
  }
133
- async _start(schemaId, inputContext, indexes, input, spy, cleanup) {
175
+ async _start(schemaId, inputContext, indexes, opts, parentCtx) {
176
+ const { input, spy, cleanup } = opts ?? {};
134
177
  const resume = !!indexes?.length;
135
178
  const runInContext = (resume ? resumeWfContext : createWfContext)({
136
179
  inputContext,
@@ -138,7 +181,7 @@ var WooksWf = class extends WooksAdapterBase {
138
181
  stepId: null,
139
182
  indexes,
140
183
  input
141
- }, this.mergeEventOptions(this.opts?.eventOptions));
184
+ }, this.getEventContextOptions(), parentCtx);
142
185
  return runInContext(async () => {
143
186
  const { handlers: foundHandlers } = this.wooks.lookup("WF_FLOW", `/${schemaId}`.replace(/^\/+/u, "/"));
144
187
  const handlers = foundHandlers || this.opts?.onNotFound && [this.opts.onNotFound] || null;
@@ -149,8 +192,8 @@ var WooksWf = class extends WooksAdapterBase {
149
192
  if (spy) spy(...args);
150
193
  if (firstStep && args[0] === "step") {
151
194
  firstStep = false;
152
- const { store } = useWFContext();
153
- store("event").set("input", void 0);
195
+ const ctx = current();
196
+ ctx.set(wfKind.keys.input, void 0);
154
197
  }
155
198
  };
156
199
  try {
@@ -174,7 +217,11 @@ var WooksWf = class extends WooksAdapterBase {
174
217
  throw error;
175
218
  }
176
219
  clean();
177
- if (result.resume) result.resume = (_input) => this.resume(result.state, _input, spy, cleanup);
220
+ if (result.resume) result.resume = (_input) => this.resume(result.state, {
221
+ input: _input,
222
+ spy,
223
+ cleanup
224
+ });
178
225
  return result;
179
226
  }
180
227
  clean();
@@ -205,7 +252,7 @@ var WooksWf = class extends WooksAdapterBase {
205
252
  * ```ts
206
253
  * const app = createWfApp()
207
254
  * app.step('process', { handler: (ctx) => ctx })
208
- * app.flow('my-flow', [{ step: 'process' }])
255
+ * app.flow('my-flow', ['process'])
209
256
  * await app.start('my-flow', { data: 'hello' })
210
257
  * ```
211
258
  */
@@ -214,4 +261,4 @@ function createWfApp(opts, wooks) {
214
261
  }
215
262
 
216
263
  //#endregion
217
- export { StepRetriableError, WooksWf, createWfApp, createWfContext, resumeWfContext, useWFContext, useWfState, wfShortcuts };
264
+ export { StepRetriableError, WooksWf, createWfApp, createWfContext, resumeKey, resumeWfContext, useLogger, useRouteParams, useWfState, wfKind, wfShortcuts };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wooksjs/event-wf",
3
- "version": "0.6.6",
3
+ "version": "0.7.0",
4
4
  "description": "@wooksjs/event-wf",
5
5
  "keywords": [
6
6
  "app",
@@ -23,7 +23,7 @@
23
23
  "directory": "packages/event-wf"
24
24
  },
25
25
  "bin": {
26
- "setup-skills": "scripts/setup-skills.js"
26
+ "wooksjs-event-wf-skill": "scripts/setup-skills.js"
27
27
  },
28
28
  "files": [
29
29
  "dist",
@@ -47,13 +47,13 @@
47
47
  "devDependencies": {
48
48
  "typescript": "^5.9.3",
49
49
  "vitest": "^3.2.4",
50
- "@wooksjs/event-core": "^0.6.6",
51
- "wooks": "^0.6.6"
50
+ "@wooksjs/event-core": "^0.7.0",
51
+ "wooks": "^0.7.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@prostojs/logger": "^0.4.3",
55
- "@wooksjs/event-core": "^0.6.6",
56
- "wooks": "^0.6.6"
55
+ "wooks": "^0.7.0",
56
+ "@wooksjs/event-core": "^0.7.0"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "rolldown -c ../../rolldown.config.mjs",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: wooksjs-event-wf
3
- description: Wooks Workflow framework — composable, step-based workflow engine for Node.js. Load when building workflow/process automation with wooks; defining workflow steps and flows; using workflow composables (useWfState, useRouteParams); working with @wooksjs/event-core context store (init, get, set, hook); creating conditional and looping flows; resuming paused workflows; handling user input requirements; using string-based step handlers; attaching workflow spies; working with StepRetriableError; creating custom event context composables for workflows.
3
+ description: Wooks Workflow framework — composable, step-based workflow engine for Node.js. Load when building workflow/process automation with wooks; defining workflow steps and flows; using workflow composables (useWfState, useRouteParams, useLogger); working with @wooksjs/event-core context (key, cached, defineWook, defineEventKind); creating conditional and looping flows; resuming paused workflows; handling user input requirements; using string-based step handlers; attaching workflow spies; working with StepRetriableError; creating custom event context composables for workflows; sharing parent event context (eventContext) for HTTP integration; accessing HTTP composables from workflow steps.
4
4
  ---
5
5
 
6
6
  # @wooksjs/event-wf
@@ -11,11 +11,11 @@ A composable workflow framework for Node.js built on async context (AsyncLocalSt
11
11
 
12
12
  Read the domain file that matches the task. Do not load all files — only what you need.
13
13
 
14
- | Domain | File | Load when... |
15
- |--------|------|------------|
16
- | Event context (core machinery) | [event-core.md](event-core.md) | Understanding the context store API (`init`/`get`/`set`/`hook`), creating custom composables, lazy evaluation and caching, building your own `use*()` functions |
17
- | Workflow app setup | [core.md](core.md) | Creating a workflow app, `createWfApp`, starting/resuming workflows, error handling, spies, testing, logging |
18
- | Steps & flows | [workflows.md](workflows.md) | Defining steps (`app.step`), defining flows (`app.flow`), workflow schemas, conditions, loops, user input, parametric steps, `useWfState`, `StepRetriableError`, string-based handlers |
14
+ | Domain | File | Load when... |
15
+ | ------------------------------ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | Event context (core machinery) | [event-core.md](event-core.md) | Understanding `EventContext`, `key()`/`cached()`/`defineWook()`/`defineEventKind()`/`slot()`, creating custom composables, lazy evaluation and caching, building your own `use*()` functions |
17
+ | Workflow app setup | [core.md](core.md) | Creating a workflow app, `createWfApp`, starting/resuming workflows, error handling, spies, testing, logging, sharing parent event context (`eventContext`), HTTP integration |
18
+ | Steps & flows | [workflows.md](workflows.md) | Defining steps (`app.step`), defining flows (`app.flow`), workflow schemas, conditions, loops, user input, parametric steps, `useWfState`, `StepRetriableError`, string-based handlers |
19
19
 
20
20
  ## Quick reference
21
21
 
@@ -39,4 +39,4 @@ const output = await app.start('calculate', { result: 0 })
39
39
  console.log(output.state.context) // { result: 10 }
40
40
  ```
41
41
 
42
- Key exports: `createWfApp()`, `useWfState()`, `useWFContext()`, `useRouteParams()`, `StepRetriableError`.
42
+ Key exports: `createWfApp()`, `useWfState()`, `useRouteParams()`, `useLogger()`, `StepRetriableError`.
@@ -9,6 +9,7 @@ For the underlying event context store API (`init`, `get`, `set`, `hook`, etc.)
9
9
  `@wooksjs/event-wf` is the workflow adapter for Wooks. It wraps the `@prostojs/wf` workflow engine, adding composable context management via `AsyncLocalStorage`. Each workflow execution gets its own isolated context store, and step handlers can call composable functions (`useWfState()`, `useRouteParams()`, etc.) from anywhere.
10
10
 
11
11
  Key principles:
12
+
12
13
  1. **Steps are route handlers** — Steps are registered with IDs that are resolved via the Wooks router, supporting parametric step IDs (`:param`), wildcards, and regex constraints.
13
14
  2. **Flows are schemas** — Flows define the execution order of steps, with conditions, loops, and branching.
14
15
  3. **Pause and resume** — Workflows can pause for user input and resume from saved state.
@@ -28,7 +29,9 @@ import { createWfApp } from '@wooksjs/event-wf'
28
29
  const app = createWfApp<{ result: number }>()
29
30
 
30
31
  app.step('increment', {
31
- handler: (ctx) => { ctx.result++ },
32
+ handler: (ctx) => {
33
+ ctx.result++
34
+ },
32
35
  })
33
36
 
34
37
  app.flow('my-flow', [{ id: 'increment' }])
@@ -43,11 +46,11 @@ Options:
43
46
 
44
47
  ```ts
45
48
  interface TWooksWfOptions {
46
- onError?: (e: Error) => void // custom error handler
47
- onNotFound?: TWooksHandler // handler when flow not found
49
+ onError?: (e: Error) => void // custom error handler
50
+ onNotFound?: TWooksHandler // handler when flow not found
48
51
  onUnknownFlow?: (schemaId: string, raiseError: () => void) => unknown
49
- logger?: TConsoleBase // custom logger
50
- eventOptions?: TEventOptions // event-level logger config
52
+ logger?: TConsoleBase // custom logger
53
+ eventOptions?: EventContextOptions // event context options (logger, parent)
51
54
  router?: {
52
55
  ignoreTrailingSlash?: boolean
53
56
  ignoreCase?: boolean
@@ -58,37 +61,51 @@ interface TWooksWfOptions {
58
61
 
59
62
  ## Starting a Workflow
60
63
 
61
- ### `app.start(schemaId, inputContext, input?, spy?, cleanup?)`
64
+ ### `app.start(schemaId, inputContext, opts?)`
62
65
 
63
66
  Starts a new workflow execution from the beginning:
64
67
 
65
68
  ```ts
66
69
  const output = await app.start('my-flow', { result: 0 })
70
+
71
+ // With options
72
+ const output = await app.start(
73
+ 'my-flow',
74
+ { result: 0 },
75
+ {
76
+ input: 5,
77
+ eventContext: current(),
78
+ },
79
+ )
67
80
  ```
68
81
 
69
82
  **Parameters:**
83
+
70
84
  - `schemaId` — The flow ID registered with `app.flow()`
71
85
  - `inputContext` — The initial context object (`T`)
72
- - `input` — Optional input for the first step
73
- - `spy` — Optional spy function to observe step execution
74
- - `cleanup` — Optional cleanup function called when execution ends
86
+ - `opts` — Optional `TWfRunOptions` object:
87
+ - `input` — Input for the first step (consumed after execution)
88
+ - `spy` — Spy function to observe step execution
89
+ - `cleanup` — Cleanup function called when execution ends
90
+ - `eventContext` — Parent `EventContext` to link to. Pass `current()` from within an active event scope (e.g. HTTP handler). The workflow creates a child context with `parent: current()`, so step handlers can access parent composables transparently via parent chain traversal.
75
91
 
76
92
  **Return value (`TFlowOutput<T, I, IR>`):**
77
93
 
78
94
  ```ts
79
95
  interface TFlowOutput<T, I, IR> {
80
- finished: boolean // true if workflow completed
96
+ finished: boolean // true if workflow completed
81
97
  state: {
82
- schemaId: string // flow ID
83
- indexes: number[] // position in schema (for resume)
84
- context: T // final context state
98
+ schemaId: string // flow ID
99
+ indexes: number[] // position in schema (for resume)
100
+ context: T // final context state
85
101
  }
86
- inputRequired?: { // present if paused for input
87
- type: string // expected input type
88
- schemaId: string // step requiring input
102
+ inputRequired?: {
103
+ // present if paused for input
104
+ type: string // expected input type
105
+ schemaId: string // step requiring input
89
106
  }
90
- stepResult?: IR // last step's return value
91
- resume?: (input?: I) => Promise<TFlowOutput<T, I, IR>> // resume function
107
+ stepResult?: IR // last step's return value
108
+ resume?: (input?: I) => Promise<TFlowOutput<T, I, IR>> // resume function
92
109
  }
93
110
  ```
94
111
 
@@ -107,15 +124,20 @@ if (output.finished) {
107
124
 
108
125
  ## Resuming a Workflow
109
126
 
110
- ### `app.resume(state, input?, spy?, cleanup?)`
127
+ ### `app.resume(state, opts?)`
111
128
 
112
129
  Resumes a previously paused workflow from saved state:
113
130
 
114
131
  ```ts
115
132
  // Resume with user-provided input
116
- const resumed = await app.resume(output.state, userInput)
133
+ const resumed = await app.resume(output.state, { input: userInput })
134
+
135
+ // Simple retry (no input)
136
+ const retried = await app.resume(output.state)
117
137
  ```
118
138
 
139
+ The `opts` parameter accepts the same `TWfRunOptions` as `start()` — including `eventContext` to link to the active event context via a parent chain.
140
+
119
141
  ### Using the `resume()` function on output
120
142
 
121
143
  The output object includes a convenience `resume()` method:
@@ -144,10 +166,7 @@ app.step('welcome', {
144
166
  handler: (ctx) => console.log(`Welcome, ${ctx.username}!`),
145
167
  })
146
168
 
147
- app.flow('login', [
148
- { id: 'get-credentials' },
149
- { id: 'welcome' },
150
- ])
169
+ app.flow('login', [{ id: 'get-credentials' }, { id: 'welcome' }])
151
170
 
152
171
  // Start — pauses at get-credentials because input is required
153
172
  const output = await app.start('login', {})
@@ -159,7 +178,7 @@ const savedState = JSON.stringify(output.state)
159
178
 
160
179
  // Later, resume with user input
161
180
  const state = JSON.parse(savedState)
162
- const final = await app.resume(state, { username: 'alice', password: 'secret' })
181
+ const final = await app.resume(state, { input: { username: 'alice', password: 'secret' } })
163
182
  // final.finished === true
164
183
  ```
165
184
 
@@ -174,52 +193,50 @@ app.start(schemaId, inputContext)
174
193
  → router matches flow ID → handler runs
175
194
  → workflow engine executes steps sequentially
176
195
  → each step can call useWfState(), useRouteParams(), etc.
177
- → composables call useWFContext()
178
- → reads/writes the WF context store
196
+ → composables call current() from @wooksjs/event-core
197
+ → reads/writes the event context via key/cached accessors
179
198
  ```
180
199
 
181
200
  ### The WF Context Store
182
201
 
183
202
  ```ts
184
203
  interface TWFContextStore {
185
- resume: boolean // true if this is a resumed execution
204
+ resume: boolean // true if this is a resumed execution
186
205
  }
187
206
 
188
207
  interface TWFEventData {
189
- schemaId: string // flow ID being executed
190
- stepId: string | null // current step ID (set during step execution)
191
- inputContext: unknown // the workflow context object (T)
192
- indexes?: number[] // position for resume
193
- input?: unknown // input for current step
208
+ schemaId: string // flow ID being executed
209
+ stepId: string | null // current step ID (set during step execution)
210
+ inputContext: unknown // the workflow context object (T)
211
+ indexes?: number[] // position for resume
212
+ input?: unknown // input for current step
194
213
  type: 'WF'
195
214
  }
196
215
  ```
197
216
 
198
- ### Extending the WF Store for Custom Composables
217
+ ### Custom Composables for Workflows
199
218
 
200
- ```ts
201
- import { useWFContext } from '@wooksjs/event-wf'
219
+ Use `defineWook` and `key()` from `@wooksjs/event-core` to create custom composables that store data in the event context:
202
220
 
203
- interface TMyStore {
204
- metrics?: {
205
- startTime?: number
206
- stepCount?: number
207
- }
208
- }
221
+ ```ts
222
+ import { defineWook, key } from '@wooksjs/event-core'
209
223
 
210
- export function useWorkflowMetrics() {
211
- const { store } = useWFContext<TMyStore>()
212
- const { init, get, set } = store('metrics')
224
+ const startTimeKey = key<number>('wf.metrics.startTime')
225
+ const stepCountKey = key<number>('wf.metrics.stepCount')
213
226
 
214
- const startTimer = () => init('startTime', () => Date.now())
215
- const incrementSteps = () => set('stepCount', (get('stepCount') || 0) + 1)
216
- const getElapsed = () => Date.now() - (get('startTime') || Date.now())
227
+ export const useWorkflowMetrics = defineWook((ctx) => {
228
+ ctx.set(startTimeKey, Date.now())
229
+ ctx.set(stepCountKey, 0)
217
230
 
218
- return { startTimer, incrementSteps, getElapsed }
219
- }
231
+ return {
232
+ incrementSteps: () => ctx.set(stepCountKey, ctx.get(stepCountKey) + 1),
233
+ getElapsed: () => Date.now() - ctx.get(startTimeKey),
234
+ getStepCount: () => ctx.get(stepCountKey),
235
+ }
236
+ })
220
237
  ```
221
238
 
222
- For the full context store API and composable patterns, see [event-core.md](event-core.md).
239
+ For the full context store API and composable patterns, see the `@wooksjs/event-core` skill.
223
240
 
224
241
  ## Workflow Spies
225
242
 
@@ -241,14 +258,21 @@ app.detachSpy(spy)
241
258
  ### Per-execution spy
242
259
 
243
260
  ```ts
244
- const output = await app.start('my-flow', { result: 0 }, undefined, (event, ...args) => {
245
- if (event === 'step') {
246
- console.log('Step executed:', args)
247
- }
248
- })
261
+ const output = await app.start(
262
+ 'my-flow',
263
+ { result: 0 },
264
+ {
265
+ spy: (event, ...args) => {
266
+ if (event === 'step') {
267
+ console.log('Step executed:', args)
268
+ }
269
+ },
270
+ },
271
+ )
249
272
  ```
250
273
 
251
274
  The spy function receives:
275
+
252
276
  - `event` — Event type (e.g., `'step'`)
253
277
  - Additional arguments vary by event type
254
278
 
@@ -299,6 +323,49 @@ app.step('validate', {
299
323
  })
300
324
  ```
301
325
 
326
+ ## Sharing the Parent Event Context
327
+
328
+ By default, `start()` and `resume()` create an isolated event context — step handlers cannot access composables from the calling scope (e.g., HTTP composables). When `eventContext` is passed, the workflow creates a **child context** with a parent link (`parent: current()`) instead of sharing the parent context directly. The child context seeds its own WF slots locally, and slot lookups that are not found in the child automatically traverse the parent chain. This means both WF composables and parent composables (e.g., HTTP) work transparently inside step handlers.
329
+
330
+ ### Use case: accessing HTTP auth data in workflow steps
331
+
332
+ ```ts
333
+ import { current } from '@wooksjs/event-core'
334
+ import { createHttpApp, useRequest } from '@wooksjs/event-http'
335
+ import { createWfApp, useWfState } from '@wooksjs/event-wf'
336
+
337
+ const wf = createWfApp<{ userId: string; role: string }>()
338
+
339
+ wf.step('check-permissions', {
340
+ handler: () => {
341
+ const { ctx } = useWfState()
342
+ // useRequest() works because the child context traverses the parent chain
343
+ const { headers } = useRequest()
344
+ const user = decodeToken(headers.authorization)
345
+ ctx<{ userId: string; role: string }>().userId = user.id
346
+ ctx<{ userId: string; role: string }>().role = user.role
347
+ },
348
+ })
349
+
350
+ wf.flow('secure-action', ['check-permissions', 'do-work'])
351
+
352
+ const http = createHttpApp()
353
+
354
+ http.post('/actions/run', async () => {
355
+ const output = await wf.start(
356
+ 'secure-action',
357
+ { userId: '', role: '' },
358
+ { eventContext: current() },
359
+ )
360
+ return output.state.context
361
+ })
362
+ ```
363
+
364
+ ### When to inherit vs isolate
365
+
366
+ - **Inherit** (`eventContext: current()`) when the workflow runs entirely within a single HTTP request and steps need parent composables (auth, headers, cached user data). The child context links to the parent via a parent chain, keeping WF-specific slots isolated while providing transparent access to parent slots.
367
+ - **Isolate** (default) when the workflow may pause and resume across different requests, or when it should be testable without a parent context.
368
+
302
369
  ## Sharing Router Between Adapters
303
370
 
304
371
  Multiple adapters can share the same Wooks router:
@@ -309,7 +376,7 @@ import { createWfApp } from '@wooksjs/event-wf'
309
376
 
310
377
  const wooks = new Wooks()
311
378
  const app1 = createWfApp({}, wooks)
312
- const app2 = createWfApp({}, wooks) // shares the same routes
379
+ const app2 = createWfApp({}, wooks) // shares the same routes
313
380
  ```
314
381
 
315
382
  Or share with another adapter (e.g., HTTP):
@@ -319,7 +386,7 @@ import { createHttpApp } from '@wooksjs/event-http'
319
386
  import { createWfApp } from '@wooksjs/event-wf'
320
387
 
321
388
  const httpApp = createHttpApp()
322
- const wfApp = createWfApp({}, httpApp) // shares httpApp's router
389
+ const wfApp = createWfApp({}, httpApp) // shares httpApp's router
323
390
  ```
324
391
 
325
392
  ## Testing
@@ -332,13 +399,12 @@ import { createWfApp } from '@wooksjs/event-wf'
332
399
  const app = createWfApp<{ count: number }>()
333
400
 
334
401
  app.step('increment', {
335
- handler: (ctx) => { ctx.count++ },
402
+ handler: (ctx) => {
403
+ ctx.count++
404
+ },
336
405
  })
337
406
 
338
- app.flow('test-flow', [
339
- { id: 'increment' },
340
- { id: 'increment' },
341
- ])
407
+ app.flow('test-flow', [{ id: 'increment' }, { id: 'increment' }])
342
408
 
343
409
  // Test:
344
410
  const output = await app.start('test-flow', { count: 0 })
@@ -359,7 +425,7 @@ app.flow('resume-flow', [{ id: 'needs-input' }])
359
425
  const output = await app.start('resume-flow', { count: 0 })
360
426
  expect(output.finished).toBe(false)
361
427
 
362
- const final = await app.resume(output.state, 42)
428
+ const final = await app.resume(output.state, { input: 42 })
363
429
  expect(final.state.context.count).toBe(42)
364
430
  expect(final.finished).toBe(true)
365
431
  ```
@@ -369,12 +435,12 @@ expect(final.finished).toBe(true)
369
435
  Inside a step handler, use the event-scoped logger:
370
436
 
371
437
  ```ts
372
- import { useEventLogger } from '@wooksjs/event-core'
438
+ import { useLogger } from '@wooksjs/event-core'
373
439
 
374
440
  app.step('process', {
375
441
  handler: (ctx) => {
376
- const logger = useEventLogger('process-step')
377
- logger.log('Processing...')
442
+ const logger = useLogger()
443
+ logger.info('Processing...')
378
444
  ctx.processed = true
379
445
  },
380
446
  })