first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View 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.