first commit
This commit is contained in:
107
docs/foundation/flow/fsm/src/utils/context-helpers.md
Normal file
107
docs/foundation/flow/fsm/src/utils/context-helpers.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# context-helpers
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides ergonomic factory helpers for building context-update and side-effect actions that can be slotted directly into `TransitionDefinition.actions` arrays. Without these helpers, every action must be written as an explicit arrow function returning a new context, which becomes verbose for simple partial-update or logging scenarios.
|
||||
|
||||
The two helpers mirror the `assign` and `pure` patterns popularised by XState, adapted to the `@suaveplan/fsm` action signature.
|
||||
|
||||
## Features
|
||||
|
||||
- `assign(patch)` — shallow-merges a static partial object into the context
|
||||
- `assign(fn)` — delegates to a context-mapping function for complex derivations
|
||||
- `pure(fn)` — wraps a side-effect function ensuring its return value never corrupts the context
|
||||
- Both helpers are fully typed against the machine's `TContext` generic parameter
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { assign, pure, createMachine } from '@suaveplan/fsm';
|
||||
|
||||
interface Ctx { count: number; lastEvent: string }
|
||||
|
||||
const machine = createMachine<Ctx>({
|
||||
initial: 'idle',
|
||||
context: { count: 0, lastEvent: '' },
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
INCREMENT: {
|
||||
target: 'idle',
|
||||
actions: [
|
||||
assign<Ctx>(ctx => ({ ...ctx, count: ctx.count + 1 })),
|
||||
pure<Ctx>((ctx, evt) => { console.log('incremented', ctx, evt); }),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { assign } from '@suaveplan/fsm';
|
||||
|
||||
interface WizardCtx {
|
||||
step: number;
|
||||
formData: Record<string, string>;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
// Static patch (shallow merge)
|
||||
const resetWizard = assign<WizardCtx>({ step: 1, completed: false });
|
||||
|
||||
// Functional update with derived logic
|
||||
const advanceStep = assign<WizardCtx>(ctx => ({
|
||||
...ctx,
|
||||
step: ctx.step + 1,
|
||||
completed: ctx.step + 1 >= 5,
|
||||
}));
|
||||
|
||||
// Compose them in a transition
|
||||
const machine = createMachine<WizardCtx>({
|
||||
initial: 'wizard',
|
||||
context: { step: 1, formData: {}, completed: false },
|
||||
states: {
|
||||
wizard: {
|
||||
on: {
|
||||
NEXT: { target: 'wizard', actions: [advanceStep] },
|
||||
RESET: { target: 'wizard', actions: [resetWizard] },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `assign<TContext>(updater): (context: TContext, event: FSMEvent) => TContext`
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `updater` — either a `Partial<TContext>` patch object or a function `(ctx: TContext) => TContext`
|
||||
|
||||
**Returns:** An action function compatible with `TransitionDefinition.actions`.
|
||||
|
||||
**Behaviour:**
|
||||
|
||||
- If `updater` is a plain object, it performs `{ ...context, ...updater }`.
|
||||
- If `updater` is a function, it calls `updater(context)` and returns the result.
|
||||
|
||||
### `pure<TContext>(fn): (context: TContext, event: FSMEvent) => void`
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `fn` — a `(context: TContext, event: FSMEvent) => void` side-effect function
|
||||
|
||||
**Returns:** A wrapped action function whose return value is always `void`.
|
||||
|
||||
**Behaviour:** Calls `fn(context, event)` and discards any return value. This prevents accidental context mutations from side-effect functions that inadvertently return a value.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`assign` accepts both object and function forms by checking `typeof updater === 'function'`. The cast `updater as (ctx: TContext) => TContext` is required because TypeScript cannot narrow a union of `Partial<TContext>` and `(ctx) => TContext` cleanly at the call site without the explicit cast.
|
||||
|
||||
`pure` exists as a safety wrapper rather than a direct alias because some developers write side-effect actions as `(ctx, evt) => doSomething()` where `doSomething` returns a non-void value. Without `pure`, that non-void value would be treated as a context replacement by the interpreter.
|
||||
Reference in New Issue
Block a user