@pyreon/machine 0.11.5 → 0.11.6

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/README.md CHANGED
@@ -23,13 +23,17 @@ const machine = createMachine({
23
23
  },
24
24
  })
25
25
 
26
- machine() // 'idle' — reads like a signal
27
- machine.send('FETCH') // transition to 'loading'
26
+ machine() // 'idle' — reads like a signal
27
+ machine.send('FETCH') // transition to 'loading'
28
28
  machine.matches('loading') // true — reactive in effects/JSX
29
29
 
30
30
  // Reactive in JSX
31
- {() => machine.matches('loading') && <Spinner />}
32
- {() => machine.matches('done') && <Results />}
31
+ {
32
+ ;() => machine.matches('loading') && <Spinner />
33
+ }
34
+ {
35
+ ;() => machine.matches('done') && <Results />
36
+ }
33
37
  ```
34
38
 
35
39
  ## Guards
@@ -57,16 +61,16 @@ Create a reactive state machine. Config: `initial` (starting state) and `states`
57
61
 
58
62
  **Returns `Machine`:**
59
63
 
60
- | Property | Description |
61
- | --- | --- |
62
- | `machine()` | Read current state (reactive) |
63
- | `send(event, payload?)` | Trigger a transition |
64
- | `matches(...states)` | Check if in one of the given states (reactive) |
65
- | `can(event)` | Check if event would trigger a valid transition |
66
- | `nextEvents()` | Available events from current state |
67
- | `reset()` | Return to initial state |
68
- | `onEnter(state, callback)` | Fire callback when entering a state |
69
- | `onTransition(callback)` | Fire on any transition |
64
+ | Property | Description |
65
+ | -------------------------- | ----------------------------------------------- |
66
+ | `machine()` | Read current state (reactive) |
67
+ | `send(event, payload?)` | Trigger a transition |
68
+ | `matches(...states)` | Check if in one of the given states (reactive) |
69
+ | `can(event)` | Check if event would trigger a valid transition |
70
+ | `nextEvents()` | Available events from current state |
71
+ | `reset()` | Return to initial state |
72
+ | `onEnter(state, callback)` | Fire callback when entering a state |
73
+ | `onTransition(callback)` | Fire on any transition |
70
74
 
71
75
  Use signals alongside machines for data. The machine manages transitions, signals manage data.
72
76
 
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/machine.ts"],"sourcesContent":["import { signal } from \"@pyreon/reactivity\"\nimport type {\n EnterCallback,\n InferEvents,\n InferStates,\n Machine,\n MachineConfig,\n MachineEvent,\n TransitionCallback,\n TransitionConfig,\n} from \"./types\"\n\n/**\n * Create a reactive state machine — a constrained signal with type-safe transitions.\n *\n * The returned instance is callable (reads like a signal) and exposes\n * `send()`, `matches()`, `can()`, and listeners for state changes.\n *\n * @param config - Machine definition with initial state and state configs\n * @returns A reactive machine instance\n *\n * @example\n * ```tsx\n * const machine = createMachine({\n * initial: 'idle',\n * states: {\n * idle: { on: { FETCH: 'loading' } },\n * loading: { on: { SUCCESS: 'done', ERROR: 'error' } },\n * done: {},\n * error: { on: { RETRY: 'loading' } },\n * },\n * })\n *\n * machine() // 'idle'\n * machine.send('FETCH')\n * machine() // 'loading'\n *\n * // Reactive in JSX\n * {() => machine.matches('loading') && <Spinner />}\n * ```\n */\nexport function createMachine<const TConfig extends MachineConfig<string, string>>(\n config: TConfig,\n): Machine<InferStates<TConfig>, InferEvents<TConfig>> {\n type TState = InferStates<TConfig>\n type TEvent = InferEvents<TConfig>\n\n const { initial, states } = config as unknown as MachineConfig<TState, TEvent>\n\n // Validate initial state\n if (!(initial in states)) {\n throw new Error(`[@pyreon/machine] Initial state '${initial}' is not defined in states`)\n }\n\n const current = signal<TState>(initial)\n const enterListeners = new Map<TState, Set<EnterCallback<TEvent>>>()\n const transitionListeners = new Set<TransitionCallback<TState, TEvent>>()\n\n function resolveTransition(event: TEvent, payload?: unknown): TState | null {\n const stateConfig = states[current.peek()]\n if (!stateConfig?.on) return null\n\n const transition = stateConfig.on[event] as TransitionConfig<TState> | undefined\n if (!transition) return null\n\n if (typeof transition === \"string\") {\n return transition\n }\n\n // Guarded transition\n if (transition.guard && !transition.guard(payload)) {\n return null\n }\n\n return transition.target\n }\n\n // The machine instance — callable like a signal\n function machine(): TState {\n return current()\n }\n\n machine.send = (event: TEvent, payload?: unknown): void => {\n const target = resolveTransition(event, payload)\n if (target === null) return\n\n const from = current.peek()\n const machineEvent: MachineEvent<TEvent> = { type: event, payload }\n\n current.set(target)\n\n // Fire transition listeners\n for (const cb of transitionListeners) {\n cb(from, target, machineEvent)\n }\n\n // Fire enter listeners for the target state\n const listeners = enterListeners.get(target)\n if (listeners) {\n for (const cb of listeners) {\n cb(machineEvent)\n }\n }\n }\n\n machine.matches = (...matchStates: TState[]): boolean => {\n const state = current()\n return matchStates.includes(state)\n }\n\n machine.can = (event: TEvent): boolean => {\n const stateConfig = states[current()]\n if (!stateConfig?.on) return false\n\n const transition = stateConfig.on[event]\n if (!transition) return false\n\n // For guarded transitions, we can't know without payload\n // Return true if the event exists (guard may still reject)\n return true\n }\n\n machine.nextEvents = (): TEvent[] => {\n const stateConfig = states[current()]\n if (!stateConfig?.on) return []\n return Object.keys(stateConfig.on) as TEvent[]\n }\n\n machine.reset = (): void => {\n current.set(initial)\n }\n\n machine.onEnter = (state: TState, callback: EnterCallback<TEvent>): (() => void) => {\n if (!enterListeners.has(state)) {\n enterListeners.set(state, new Set())\n }\n enterListeners.get(state)!.add(callback)\n\n return () => {\n enterListeners.get(state)?.delete(callback)\n }\n }\n\n machine.onTransition = (callback: TransitionCallback<TState, TEvent>): (() => void) => {\n transitionListeners.add(callback)\n return () => {\n transitionListeners.delete(callback)\n }\n }\n\n machine.dispose = (): void => {\n enterListeners.clear()\n transitionListeners.clear()\n }\n\n return machine as Machine<TState, TEvent>\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,cACd,QACqD;CAIrD,MAAM,EAAE,SAAS,WAAW;AAG5B,KAAI,EAAE,WAAW,QACf,OAAM,IAAI,MAAM,oCAAoC,QAAQ,4BAA4B;CAG1F,MAAM,UAAU,OAAe,QAAQ;CACvC,MAAM,iCAAiB,IAAI,KAAyC;CACpE,MAAM,sCAAsB,IAAI,KAAyC;CAEzE,SAAS,kBAAkB,OAAe,SAAkC;EAC1E,MAAM,cAAc,OAAO,QAAQ,MAAM;AACzC,MAAI,CAAC,aAAa,GAAI,QAAO;EAE7B,MAAM,aAAa,YAAY,GAAG;AAClC,MAAI,CAAC,WAAY,QAAO;AAExB,MAAI,OAAO,eAAe,SACxB,QAAO;AAIT,MAAI,WAAW,SAAS,CAAC,WAAW,MAAM,QAAQ,CAChD,QAAO;AAGT,SAAO,WAAW;;CAIpB,SAAS,UAAkB;AACzB,SAAO,SAAS;;AAGlB,SAAQ,QAAQ,OAAe,YAA4B;EACzD,MAAM,SAAS,kBAAkB,OAAO,QAAQ;AAChD,MAAI,WAAW,KAAM;EAErB,MAAM,OAAO,QAAQ,MAAM;EAC3B,MAAM,eAAqC;GAAE,MAAM;GAAO;GAAS;AAEnE,UAAQ,IAAI,OAAO;AAGnB,OAAK,MAAM,MAAM,oBACf,IAAG,MAAM,QAAQ,aAAa;EAIhC,MAAM,YAAY,eAAe,IAAI,OAAO;AAC5C,MAAI,UACF,MAAK,MAAM,MAAM,UACf,IAAG,aAAa;;AAKtB,SAAQ,WAAW,GAAG,gBAAmC;EACvD,MAAM,QAAQ,SAAS;AACvB,SAAO,YAAY,SAAS,MAAM;;AAGpC,SAAQ,OAAO,UAA2B;EACxC,MAAM,cAAc,OAAO,SAAS;AACpC,MAAI,CAAC,aAAa,GAAI,QAAO;AAG7B,MAAI,CADe,YAAY,GAAG,OACjB,QAAO;AAIxB,SAAO;;AAGT,SAAQ,mBAA6B;EACnC,MAAM,cAAc,OAAO,SAAS;AACpC,MAAI,CAAC,aAAa,GAAI,QAAO,EAAE;AAC/B,SAAO,OAAO,KAAK,YAAY,GAAG;;AAGpC,SAAQ,cAAoB;AAC1B,UAAQ,IAAI,QAAQ;;AAGtB,SAAQ,WAAW,OAAe,aAAkD;AAClF,MAAI,CAAC,eAAe,IAAI,MAAM,CAC5B,gBAAe,IAAI,uBAAO,IAAI,KAAK,CAAC;AAEtC,iBAAe,IAAI,MAAM,CAAE,IAAI,SAAS;AAExC,eAAa;AACX,kBAAe,IAAI,MAAM,EAAE,OAAO,SAAS;;;AAI/C,SAAQ,gBAAgB,aAA+D;AACrF,sBAAoB,IAAI,SAAS;AACjC,eAAa;AACX,uBAAoB,OAAO,SAAS;;;AAIxC,SAAQ,gBAAsB;AAC5B,iBAAe,OAAO;AACtB,sBAAoB,OAAO;;AAG7B,QAAO"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/machine.ts"],"sourcesContent":["import { signal } from '@pyreon/reactivity'\nimport type {\n EnterCallback,\n InferEvents,\n InferStates,\n Machine,\n MachineConfig,\n MachineEvent,\n TransitionCallback,\n TransitionConfig,\n} from './types'\n\n/**\n * Create a reactive state machine — a constrained signal with type-safe transitions.\n *\n * The returned instance is callable (reads like a signal) and exposes\n * `send()`, `matches()`, `can()`, and listeners for state changes.\n *\n * @param config - Machine definition with initial state and state configs\n * @returns A reactive machine instance\n *\n * @example\n * ```tsx\n * const machine = createMachine({\n * initial: 'idle',\n * states: {\n * idle: { on: { FETCH: 'loading' } },\n * loading: { on: { SUCCESS: 'done', ERROR: 'error' } },\n * done: {},\n * error: { on: { RETRY: 'loading' } },\n * },\n * })\n *\n * machine() // 'idle'\n * machine.send('FETCH')\n * machine() // 'loading'\n *\n * // Reactive in JSX\n * {() => machine.matches('loading') && <Spinner />}\n * ```\n */\nexport function createMachine<const TConfig extends MachineConfig<string, string>>(\n config: TConfig,\n): Machine<InferStates<TConfig>, InferEvents<TConfig>> {\n type TState = InferStates<TConfig>\n type TEvent = InferEvents<TConfig>\n\n const { initial, states } = config as unknown as MachineConfig<TState, TEvent>\n\n // Validate initial state\n if (!(initial in states)) {\n throw new Error(`[@pyreon/machine] Initial state '${initial}' is not defined in states`)\n }\n\n const current = signal<TState>(initial)\n const enterListeners = new Map<TState, Set<EnterCallback<TEvent>>>()\n const transitionListeners = new Set<TransitionCallback<TState, TEvent>>()\n\n function resolveTransition(event: TEvent, payload?: unknown): TState | null {\n const stateConfig = states[current.peek()]\n if (!stateConfig?.on) return null\n\n const transition = stateConfig.on[event] as TransitionConfig<TState> | undefined\n if (!transition) return null\n\n if (typeof transition === 'string') {\n return transition\n }\n\n // Guarded transition\n if (transition.guard && !transition.guard(payload)) {\n return null\n }\n\n return transition.target\n }\n\n // The machine instance — callable like a signal\n function machine(): TState {\n return current()\n }\n\n machine.send = (event: TEvent, payload?: unknown): void => {\n const target = resolveTransition(event, payload)\n if (target === null) return\n\n const from = current.peek()\n const machineEvent: MachineEvent<TEvent> = { type: event, payload }\n\n current.set(target)\n\n // Fire transition listeners\n for (const cb of transitionListeners) {\n cb(from, target, machineEvent)\n }\n\n // Fire enter listeners for the target state\n const listeners = enterListeners.get(target)\n if (listeners) {\n for (const cb of listeners) {\n cb(machineEvent)\n }\n }\n }\n\n machine.matches = (...matchStates: TState[]): boolean => {\n const state = current()\n return matchStates.includes(state)\n }\n\n machine.can = (event: TEvent): boolean => {\n const stateConfig = states[current()]\n if (!stateConfig?.on) return false\n\n const transition = stateConfig.on[event]\n if (!transition) return false\n\n // For guarded transitions, we can't know without payload\n // Return true if the event exists (guard may still reject)\n return true\n }\n\n machine.nextEvents = (): TEvent[] => {\n const stateConfig = states[current()]\n if (!stateConfig?.on) return []\n return Object.keys(stateConfig.on) as TEvent[]\n }\n\n machine.reset = (): void => {\n current.set(initial)\n }\n\n machine.onEnter = (state: TState, callback: EnterCallback<TEvent>): (() => void) => {\n if (!enterListeners.has(state)) {\n enterListeners.set(state, new Set())\n }\n enterListeners.get(state)!.add(callback)\n\n return () => {\n enterListeners.get(state)?.delete(callback)\n }\n }\n\n machine.onTransition = (callback: TransitionCallback<TState, TEvent>): (() => void) => {\n transitionListeners.add(callback)\n return () => {\n transitionListeners.delete(callback)\n }\n }\n\n machine.dispose = (): void => {\n enterListeners.clear()\n transitionListeners.clear()\n }\n\n return machine as Machine<TState, TEvent>\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,cACd,QACqD;CAIrD,MAAM,EAAE,SAAS,WAAW;AAG5B,KAAI,EAAE,WAAW,QACf,OAAM,IAAI,MAAM,oCAAoC,QAAQ,4BAA4B;CAG1F,MAAM,UAAU,OAAe,QAAQ;CACvC,MAAM,iCAAiB,IAAI,KAAyC;CACpE,MAAM,sCAAsB,IAAI,KAAyC;CAEzE,SAAS,kBAAkB,OAAe,SAAkC;EAC1E,MAAM,cAAc,OAAO,QAAQ,MAAM;AACzC,MAAI,CAAC,aAAa,GAAI,QAAO;EAE7B,MAAM,aAAa,YAAY,GAAG;AAClC,MAAI,CAAC,WAAY,QAAO;AAExB,MAAI,OAAO,eAAe,SACxB,QAAO;AAIT,MAAI,WAAW,SAAS,CAAC,WAAW,MAAM,QAAQ,CAChD,QAAO;AAGT,SAAO,WAAW;;CAIpB,SAAS,UAAkB;AACzB,SAAO,SAAS;;AAGlB,SAAQ,QAAQ,OAAe,YAA4B;EACzD,MAAM,SAAS,kBAAkB,OAAO,QAAQ;AAChD,MAAI,WAAW,KAAM;EAErB,MAAM,OAAO,QAAQ,MAAM;EAC3B,MAAM,eAAqC;GAAE,MAAM;GAAO;GAAS;AAEnE,UAAQ,IAAI,OAAO;AAGnB,OAAK,MAAM,MAAM,oBACf,IAAG,MAAM,QAAQ,aAAa;EAIhC,MAAM,YAAY,eAAe,IAAI,OAAO;AAC5C,MAAI,UACF,MAAK,MAAM,MAAM,UACf,IAAG,aAAa;;AAKtB,SAAQ,WAAW,GAAG,gBAAmC;EACvD,MAAM,QAAQ,SAAS;AACvB,SAAO,YAAY,SAAS,MAAM;;AAGpC,SAAQ,OAAO,UAA2B;EACxC,MAAM,cAAc,OAAO,SAAS;AACpC,MAAI,CAAC,aAAa,GAAI,QAAO;AAG7B,MAAI,CADe,YAAY,GAAG,OACjB,QAAO;AAIxB,SAAO;;AAGT,SAAQ,mBAA6B;EACnC,MAAM,cAAc,OAAO,SAAS;AACpC,MAAI,CAAC,aAAa,GAAI,QAAO,EAAE;AAC/B,SAAO,OAAO,KAAK,YAAY,GAAG;;AAGpC,SAAQ,cAAoB;AAC1B,UAAQ,IAAI,QAAQ;;AAGtB,SAAQ,WAAW,OAAe,aAAkD;AAClF,MAAI,CAAC,eAAe,IAAI,MAAM,CAC5B,gBAAe,IAAI,uBAAO,IAAI,KAAK,CAAC;AAEtC,iBAAe,IAAI,MAAM,CAAE,IAAI,SAAS;AAExC,eAAa;AACX,kBAAe,IAAI,MAAM,EAAE,OAAO,SAAS;;;AAI/C,SAAQ,gBAAgB,aAA+D;AACrF,sBAAoB,IAAI,SAAS;AACjC,eAAa;AACX,uBAAoB,OAAO,SAAS;;;AAIxC,SAAQ,gBAAsB;AAC5B,iBAAe,OAAO;AACtB,sBAAoB,OAAO;;AAG7B,QAAO"}
package/package.json CHANGED
@@ -1,20 +1,17 @@
1
1
  {
2
2
  "name": "@pyreon/machine",
3
- "version": "0.11.5",
3
+ "version": "0.11.6",
4
4
  "description": "Reactive state machines for Pyreon — constrained signals with type-safe transitions",
5
+ "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/machine#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/pyreon/pyreon/issues"
8
+ },
5
9
  "license": "MIT",
6
10
  "repository": {
7
11
  "type": "git",
8
12
  "url": "https://github.com/pyreon/pyreon.git",
9
13
  "directory": "packages/fundamentals/machine"
10
14
  },
11
- "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/machine#readme",
12
- "bugs": {
13
- "url": "https://github.com/pyreon/pyreon/issues"
14
- },
15
- "publishConfig": {
16
- "access": "public"
17
- },
18
15
  "files": [
19
16
  "lib",
20
17
  "src",
@@ -22,6 +19,7 @@
22
19
  "LICENSE"
23
20
  ],
24
21
  "type": "module",
22
+ "sideEffects": false,
25
23
  "main": "./lib/index.js",
26
24
  "module": "./lib/index.js",
27
25
  "types": "./lib/types/index.d.ts",
@@ -32,20 +30,22 @@
32
30
  "types": "./lib/types/index.d.ts"
33
31
  }
34
32
  },
35
- "sideEffects": false,
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
36
  "scripts": {
37
37
  "build": "vl_rolldown_build",
38
38
  "dev": "vl_rolldown_build-watch",
39
39
  "test": "vitest run",
40
40
  "typecheck": "tsc --noEmit",
41
- "lint": "biome check ."
42
- },
43
- "peerDependencies": {
44
- "@pyreon/reactivity": "^0.11.5"
41
+ "lint": "oxlint ."
45
42
  },
46
43
  "devDependencies": {
47
44
  "@happy-dom/global-registrator": "^20.8.3",
48
- "@pyreon/reactivity": "^0.11.5",
45
+ "@pyreon/reactivity": "^0.11.6",
49
46
  "@vitus-labs/tools-lint": "^1.11.0"
47
+ },
48
+ "peerDependencies": {
49
+ "@pyreon/reactivity": "^0.11.6"
50
50
  }
51
51
  }
package/src/index.ts CHANGED
@@ -25,7 +25,7 @@
25
25
  * ```
26
26
  */
27
27
 
28
- export { createMachine } from "./machine"
28
+ export { createMachine } from './machine'
29
29
 
30
30
  // Types
31
31
  export type {
@@ -38,4 +38,4 @@ export type {
38
38
  StateConfig,
39
39
  TransitionCallback,
40
40
  TransitionConfig,
41
- } from "./types"
41
+ } from './types'
package/src/machine.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { signal } from "@pyreon/reactivity"
1
+ import { signal } from '@pyreon/reactivity'
2
2
  import type {
3
3
  EnterCallback,
4
4
  InferEvents,
@@ -8,7 +8,7 @@ import type {
8
8
  MachineEvent,
9
9
  TransitionCallback,
10
10
  TransitionConfig,
11
- } from "./types"
11
+ } from './types'
12
12
 
13
13
  /**
14
14
  * Create a reactive state machine — a constrained signal with type-safe transitions.
@@ -63,7 +63,7 @@ export function createMachine<const TConfig extends MachineConfig<string, string
63
63
  const transition = stateConfig.on[event] as TransitionConfig<TState> | undefined
64
64
  if (!transition) return null
65
65
 
66
- if (typeof transition === "string") {
66
+ if (typeof transition === 'string') {
67
67
  return transition
68
68
  }
69
69