@alwatr/fsm 9.25.0 → 9.28.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/README.md CHANGED
@@ -1,306 +1,466 @@
1
- # Alwatr FSM
1
+ # 🤖 Alwatr FSM
2
2
 
3
- [](https://www.google.com/search?q=alwatr+flux)
4
- [](https://www.google.com/search?q=alwatr+fsm)
5
- [](https://www.google.com/search?q=alwatr+signal)
6
- [](https://www.google.com/search?q=alwatr)
7
- [](https://www.npmjs.com/package/%40alwatr/flux)
8
- [](https://www.npmjs.com/package/%40alwatr/fsm)
9
- [](https://www.npmjs.com/package/%40alwatr/signal)
3
+ **Declarative, Reactive, and Type-Safe Statechart Engine for High-Performance Applications**
10
4
 
11
- A tiny, type-safe, declarative, and reactive finite state machine (FSM) library for modern TypeScript applications, built on top of [Alwatr Signals](https://github.com/Alwatr/flux/tree/next/packages/signal).
5
+ [![npm version](https://img.shields.io/npm/v/@alwatr/fsm?color=4CAF50&label=%40alwatr%2Ffsm)](https://www.npmjs.com/package/@alwatr/fsm)
6
+ [![license](https://img.shields.io/github/license/Alwatr/alwatr?color=4CAF50)](https://github.com/Alwatr/alwatr/blob/next/LICENSE)
12
7
 
13
- یک کتابخانه کوچک، تایپ-سیف، اعلانی و واکنش‌گرا (reactive) برای مدیریت وضعیت به روش ماشین حالت متناهی (FSM) در اپلیکیشن‌های مدرن TypeScript که بر پایه [Alwatr Signals](https://github.com/Alwatr/flux/tree/next/packages/signal) ساخته شده است.
8
+ > A tiny, production-grade, and highly reactive Finite State Machine (FSM) engine built on top of `@alwatr/signal`. It natively enforces Run-To-Completion (RTC) safety, state persistence, nested transitions, and dynamic state actors—allowing you to model complex user flows as deterministic, bulletproof statecharts.
14
9
 
15
- ## Philosophy
16
-
17
- Managing state in complex applications can be challenging. As features grow, state transitions can become unpredictable, leading to bugs and difficult-to-maintain code. Finite State Machines provide a powerful model to solve this problem by formalizing application logic.
10
+ ---
18
11
 
19
- An FSM describes a system that can be in **exactly one** of a finite number of **states** at any given time. It transitions from one state to another in response to **events**, following predefined rules. This approach makes state changes predictable, visualizable, and robust.
12
+ ## 🎯 What is Alwatr FSM?
20
13
 
21
- This library is designed to be:
14
+ `@alwatr/fsm` is a type-safe, lightweight engine that replaces loose, error-prone boolean flags (`isLoading`, `isError`, etc.) with a **declarative, mathematically sound statechart**.
22
15
 
23
- - **Declarative**: Define your entire machine logic in a single, easy-to-read configuration object.
24
- - **Type-Safe**: Leverage TypeScript to catch errors at compile time, not runtime.
25
- - **Reactive**: Built around signals for seamless integration with modern UI frameworks and reactive codebases.
26
- - **Resilient**: Guarantees Run-to-Completion (RTC) and handles errors in user-defined functions gracefully without crashing.
16
+ It serves as the core logic engine (the "Brain") for the **Actor Model** within unidirectional data flow architectures. By defining states, transitions, guards, and side-effects in a single structured configuration, you ensure your application can never enter an undefined or invalid state.
27
17
 
28
18
  ---
29
19
 
30
- ## مفاهیم و فلسفه
20
+ ## 🏗️ Architecture & Actor Model Flow
31
21
 
32
- مدیریت وضعیت در اپلیکیشن‌های پیچیده یک چالش است. با رشد برنامه، جریان تغییر وضعیت‌ها می‌تواند غیرقابل‌پیش‌بینی شده و منجر به باگ‌ها و کدهای غیرقابل نگهداری شود. ماشین‌های حالت متناهی (FSM) یک مدل قدرتمند برای حل این مشکل از طریق ساختارمند کردن منطق برنامه ارائه می‌دهند.
22
+ Alwatr FSM acts as a self-contained Actor. It receives external messages via its private mailbox (the `dispatch` method), evaluates them atomically, mutates its internal context, triggers side-effects, and broadcasts state updates to the rest of the application.
33
23
 
34
- یک FSM سیستمی را توصیف می‌کند که در هر لحظه **دقیقا در یکی** از تعداد محدودی **وضعیت (state)** قرار دارد. این سیستم در پاسخ به **رویدادها (events)** و بر اساس قوانین از پیش تعریف‌شده، از یک وضعیت به وضعیت دیگر **گذار (transition)** می‌کند. این رویکرد، تغییرات وضعیت را قابل‌پیش‌بینی، قابل ترسیم و مستحکم می‌سازد.
24
+ ```mermaid
25
+ graph TD
26
+ %% Styling
27
+ classDef signal fill:#E1F5FE,stroke:#03A9F4,stroke-width:2px,color:#01579B;
28
+ classDef core fill:#E8F5E9,stroke:#4CAF50,stroke-width:2px,color:#1B5E20;
29
+ classDef side fill:#FFF3E0,stroke:#FF9800,stroke-width:2px,color:#E65100;
30
+ classDef actor fill:#F3E5F5,stroke:#9C27B0,stroke-width:2px,color:#4A148C;
35
31
 
36
- این کتابخانه با اهداف زیر طراحی شده است:
32
+ Mailbox[dispatch Event] -->|Inbound Queue| Queue[Run-to-Completion Loop]
37
33
 
38
- - **اعلانی (Declarative)**: تمام منطق ماشین خود را در یک آبجکت پیکربندی واحد و خوانا تعریف کنید.
39
- - **تایپ-سیف (Type-Safe)**: با بهره‌گیری از قدرت TypeScript، خطاها را در زمان کامپایل پیدا کنید، نه در زمان اجرا.
40
- - **واکنش‌گرا (Reactive)**: مبتنی بر سیگنال‌ها برای یکپارچه‌سازی آسان با فریم‌ورک‌های مدرن و کدهای واکنش‌گرا.
41
- - **مستحکم و تاب‌آور (Resilient)**: مدل اجرای کامل تا انتها (RTC) را تضمین کرده و خطاهای توابع تعریف‌شده توسط کاربر را بدون متوقف کردن سیستم مدیریت می‌کند.
34
+ subgraph Engine ["FsmService (Atomic Evaluation)"]
35
+ Queue -->|Evaluate Guard Predicates| Guards{Guard Met?}
36
+ Guards -->|Yes| Transition[Apply Transition]
37
+ Guards -->|No| Ignored[Ignore Event]
42
38
 
43
- ## Installation
39
+ Transition -->|1. Run Exit Effects| Exit[State Exit Effects]
40
+ Transition -->|2. Pure Assigner Mutation| Context[(Compute Next Context)]
41
+ Transition -->|3. Run Entry Effects| Entry[State Entry Effects]
42
+ Transition -->|4. Update stateSignal| Sig[State Signal Update]
43
+ Transition -->|5. Spawn State Actors| Spawn[State Actors Active]
44
+ end
44
45
 
45
- ```bash
46
- npm i @alwatr/fsm
46
+ Sig -->|Reactivity| UI[View Layer / Subscribers]
47
+ Spawn -->|Async Actions| Mailbox
48
+
49
+ class Sig,UI signal;
50
+ class Queue,Guards,Transition,Context core;
51
+ class Exit,Entry side;
52
+ class Spawn actor;
47
53
  ```
48
54
 
49
- ## Core Concepts / مفاهیم کلیدی
55
+ ---
50
56
 
51
- | Term | Description |
52
- | :------------- | :------------------------------------------------------------------------------------------------------------------ |
53
- | **State** | The current finite state of the machine (e.g., `'idle'`, `'loading'`). |
54
- | **Context** | An object holding the "extended state"—any quantitative or non-finite data (e.g., `{retries: 2, data: null}`). |
55
- | **Event** | An object that triggers a potential state transition (e.g., `{type: 'FETCH', id: '123'}`). |
56
- | **Transition** | A rule defining the path from a source state to a target state for a given event. It can be guarded by a condition. |
57
- | **Assigner** | A **pure function** that synchronously updates the `context` during a transition. |
58
- | **Effect** | A function for **side effects** (e.g., API calls, logging) that runs upon entering or exiting a state. |
59
- | **Condition** | A **predicate function** that must return `true` for its associated transition to be taken. |
57
+ ## 🛡️ Core Philosophy
60
58
 
61
- | اصطلاح | توضیحات |
62
- | :------------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
63
- | **وضعیت (State)** | وضعیت متناهی فعلی ماشین (مثلاً `'idle'`, `'loading'`). |
64
- | **زمینه (Context)** | یک آبجکت برای نگهداری "وضعیت گسترده"—هرگونه داده کمی یا نامتناهی (مثلاً `{retries: 2, data: null}`). |
65
- | **رویداد (Event)** | آبجکتی که یک گذار وضعیت بالقوه را آغاز می‌کند (مثلاً `{type: 'FETCH', id: '123'}`). |
66
- | **گذار (Transition)** | قانونی که مسیر از یک وضعیت مبدأ به یک وضعیت مقصد را برای یک رویداد خاص تعریف می‌کند. این گذار می‌تواند توسط یک شرط محافظت شود. |
67
- | **تخصیص‌دهنده (Assigner)** | یک **تابع خالص** که به صورت همزمان (synchronously) `context` را در طول یک گذار به‌روزرسانی می‌کند. |
68
- | **اثر جانبی (Effect)** | تابعی برای **عملیات‌های جانبی** (مانند فراخوانی API یا لاگ کردن) که هنگام ورود یا خروج از یک وضعیت اجرا می‌شود. |
69
- | **شرط (Condition)** | یک **تابع گزاره‌ای** که باید `true` برگرداند تا گذار مرتبط با آن انجام شود. |
59
+ - **Declarative Topology**: Describe your entire state machine logic in a single configuration matrix. Avoid nested `if/else` checks and scattered state variables.
60
+ - **Run-To-Completion (RTC)**: Transitions are evaluated synchronously in microtasks. Concurrent inputs are queued and evaluated sequentially to prevent state-sync bugs and race conditions.
61
+ - **Deep Compile-Time Safety**: Leverage TypeScript's advanced type inference to validate event payloads, contextual mutations, and conditional transitions at build time.
62
+ - **Resilient & Guarded Boundaries**: Assigner, guard, and effect closures run inside try/catch blocks. If a user function throws an error, the machine catches it, logs a diagnostic accident, and safely reverts context updates.
70
63
 
71
- ## Example 1: A Simple Light Switch
64
+ ---
72
65
 
73
- Let's model a simple light switch that can be turned on and off.
66
+ ## 🧠 Glossary
74
67
 
75
- ### ۱. تعریف انواع
68
+ | Term | Domain Scope | Description |
69
+ | :------------- | :-------------- | :-------------------------------------------------------------------------------------------------------- |
70
+ | **State** | Finite | A discrete behavioral mode of the system (e.g., `'idle'`, `'uploading'`). |
71
+ | **Context** | Infinite | The extended quantitative state holding quantitative data (e.g., `{ retries: 0 }`). |
72
+ | **Event** | Message | An object with a unique `type` dispatched to trigger state machine evaluation. |
73
+ | **Transition** | Structural Rule | A predefined path establishing how the machine moves from state A to B on receiving an event. |
74
+ | **Assigner** | Pure Mutation | A synchronous, deterministic function that updates a slice of the machine's extended `context`. |
75
+ | **Effect** | Side-Effect | A fire-and-forget synchronous or asynchronous side-effect run on entering or leaving a state. |
76
+ | **Actor** | Spawned Process | An async lifecycle process spawned on state entry that can dispatch events back and runs cleanup on exit. |
77
+ | **Guard** | Gatekeeper | A boolean evaluator that must return `true` for a transition branch to be authorized. |
76
78
 
77
- First, define the types for the states, events, and context.
78
- ابتدا انواع مربوط به وضعیت‌ها، رویدادها و زمینه را تعریف می‌کنیم.
79
+ ---
79
80
 
80
- ```ts
81
- import type {StateMachineConfig} from '@alwatr/fsm';
81
+ ## 📦 Installation
82
+
83
+ ```bash
84
+ # npm
85
+ npm install @alwatr/fsm
86
+
87
+ # bun
88
+ bun add @alwatr/fsm
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 🚀 Quick Start Blueprint
82
94
 
83
- // The context stores the brightness level.
84
- type LightContext = {brightness: number};
95
+ ### 1. Define Typed Schemas
85
96
 
86
- // The machine can only be in one of these two states.
87
- type LightState = 'on' | 'off';
97
+ ```typescript
98
+ // types.ts
99
+ import type {MachineEvent} from '@alwatr/fsm';
88
100
 
89
- // Define the events that can be sent to the machine.
90
- type LightEvent = {type: 'TOGGLE'} | {type: 'SET_BRIGHTNESS'; level: number};
101
+ export type FileState = 'idle' | 'uploading' | 'success' | 'failed';
102
+
103
+ export interface FileContext {
104
+ fileId: string | null;
105
+ progress: number;
106
+ errorMessage: string | null;
107
+ }
108
+
109
+ export type FileEvent =
110
+ | {type: 'START_UPLOAD'; fileId: string}
111
+ | {type: 'PROGRESS_UPDATE'; percent: number}
112
+ | {type: 'UPLOAD_SUCCESS'}
113
+ | {type: 'UPLOAD_FAILURE'; error: string}
114
+ | {type: 'RETRY'};
91
115
  ```
92
116
 
93
- ### ۲. پیکربندی ماشین
117
+ ### 2. Configure the State Machine
94
118
 
95
- Define the entire machine logic in a configuration object.
96
- کل منطق ماشین را در یک آبجکت پیکربندی تعریف می‌کنیم.
119
+ ```typescript
120
+ // config.ts
121
+ import type {StateMachineConfig} from '@alwatr/fsm';
122
+ import type {FileState, FileEvent, FileContext} from './types.js';
97
123
 
98
- ```ts
99
- const lightMachineConfig: StateMachineConfig<LightState, LightEvent, LightContext> = {
100
- name: 'light-switch',
101
- initial: 'off',
102
- context: {brightness: 0},
124
+ export const fileUploadConfig: StateMachineConfig<FileState, FileEvent, FileContext> = {
125
+ name: 'file-upload-lifecycle',
126
+ initial: 'idle',
127
+ context: {fileId: null, progress: 0, errorMessage: null},
103
128
  states: {
104
- off: {
129
+ idle: {
105
130
  on: {
106
- // When in the 'off' state and a 'TOGGLE' event occurs...
107
- TOGGLE: {
108
- target: 'on', // ...transition to the 'on' state.
109
- assigners: [() => ({brightness: 100})], // ...and set brightness to 100.
131
+ START_UPLOAD: {
132
+ target: 'uploading',
133
+ // Pure assigner updates context slice
134
+ assigners: [({context, event}) => ({...context, fileId: event.fileId, progress: 0, errorMessage: null})],
110
135
  },
111
136
  },
112
137
  },
113
- on: {
138
+ uploading: {
114
139
  on: {
115
- // When in the 'on' state and a 'TOGGLE' event occurs...
116
- TOGGLE: {
117
- target: 'off', // ...transition to 'off'.
118
- assigners: [() => ({brightness: 0})], // ...and reset brightness.
140
+ PROGRESS_UPDATE: {
141
+ // No 'target' means an internal transition: context changes, state remains 'uploading'
142
+ assigners: [({context, event}) => ({...context, progress: event.percent})],
143
+ },
144
+ UPLOAD_SUCCESS: {target: 'success'},
145
+ UPLOAD_FAILURE: {
146
+ target: 'failed',
147
+ assigners: [({context, event}) => ({...context, errorMessage: event.error})],
119
148
  },
120
- // An internal transition that only updates context without changing the state.
121
- SET_BRIGHTNESS: {
122
- // No 'target' means it's an internal transition.
123
- assigners: [(event) => ({brightness: event.level})],
149
+ },
150
+ },
151
+ failed: {
152
+ on: {
153
+ RETRY: {
154
+ target: 'uploading',
155
+ guard: ({context}) => context.fileId !== null, // Guard evaluates transition authorization
124
156
  },
125
157
  },
126
158
  },
159
+ success: {},
127
160
  },
128
161
  };
129
162
  ```
130
163
 
131
- ### ۳. ساخت و استفاده از سرویس
164
+ ### 3. Initialize & Use
132
165
 
133
- Create the service and interact with it using signals.
134
- سرویس را ایجاد کرده و با استفاده از سیگنال‌ها با آن تعامل می‌کنیم.
135
-
136
- ```ts
166
+ ```typescript
167
+ // main.ts
137
168
  import {createFsmService} from '@alwatr/fsm';
169
+ import {fileUploadConfig} from './config.js';
138
170
 
139
- // Create the FSM service instance.
140
- const lightService = createFsmService(lightMachineConfig);
171
+ // Instantiate the FSM service using the factory function
172
+ export const fileUploadService = createFsmService(fileUploadConfig);
141
173
 
142
- // Subscribe to state changes.
143
- lightService.stateSignal.subscribe((state) => {
144
- console.log(`Light is ${state.name} with brightness ${state.context.brightness}`);
174
+ // Subscribe to state and context changes (fine-grained reactivity)
175
+ fileUploadService.stateSignal.subscribe((state) => {
176
+ console.log(`Current FSM State: ${state.name}`);
177
+ console.log(`Context Progress: ${state.context.progress}%`);
145
178
  });
146
179
 
147
- // Dispatch events to trigger transitions.
148
- lightService.eventSignal.dispatch({type: 'TOGGLE'});
149
- // Logs: Light is on with brightness 100
150
-
151
- lightService.eventSignal.dispatch({type: 'SET_BRIGHTNESS', level: 50});
152
- // Logs: Light is on with brightness 50
153
-
154
- lightService.eventSignal.dispatch({type: 'TOGGLE'});
155
- // Logs: Light is off with brightness 0
180
+ // Dispatch events into the machine mailbox
181
+ fileUploadService.dispatch({type: 'START_UPLOAD', fileId: 'doc_102'});
156
182
  ```
157
183
 
158
- ## Example 2: Async Data Fetching
184
+ ---
159
185
 
160
- A more advanced example showing side effects (`effects`) and conditional transitions (`condition`).
186
+ ## Advanced Features
161
187
 
162
- ```ts
163
- import {createFsmService} from '@alwatr/fsm';
164
- import type {StateMachineConfig} from '@alwatr/fsm';
188
+ ### 1. Local / Session State Persistence
165
189
 
166
- // Types
167
- type User = {id: string; name: string};
168
- type FetchContext = {user: User | null; error: string | null};
169
- type FetchState = 'idle' | 'pending' | 'success' | 'error';
170
- type FetchEvent = {type: 'FETCH'; id: string} | {type: 'RESOLVE'; user: User} | {type: 'REJECT'; error: string} | {type: 'RETRY'};
190
+ Make your state machine crash-resilient by syncing both state and context automatically with `localStorage` or `sessionStorage`.
171
191
 
172
- // FSM Configuration
173
- const fetchMachineConfig: StateMachineConfig<FetchState, FetchEvent, FetchContext> = {
174
- name: 'fetch-user',
192
+ ```typescript
193
+ export const fileUploadConfig: StateMachineConfig<FileState, FileEvent, FileContext> = {
194
+ name: 'file-upload-lifecycle',
175
195
  initial: 'idle',
176
- context: {
177
- user: null,
178
- error: null,
196
+ context: {fileId: null, progress: 0, errorMessage: null},
197
+ persistent: {
198
+ schemaVersion: 1, // Automatic clear and reset if version bumps
199
+ storageKey: 'file-upload-state', // LocalStorage item key
179
200
  },
180
201
  states: {
181
- idle: {
182
- on: {
183
- FETCH: {target: 'pending'},
184
- },
202
+ // ...
203
+ },
204
+ };
205
+ ```
206
+
207
+ ### 2. Nested Transitions (Guards & Fallbacks)
208
+
209
+ You can define an array of transitions for a single event. The engine evaluates guards in order and executes the first valid one. A transition without a guard acts as a fallback.
210
+
211
+ ```typescript
212
+ states: {
213
+ idle: {
214
+ on: {
215
+ VERIFY: [
216
+ {target: 'high_priority', guard: ({context}) => context.score > 90},
217
+ {target: 'medium_priority', guard: ({context}) => context.score > 50},
218
+ {target: 'low_priority'}, // Fallback path
219
+ ],
185
220
  },
186
- pending: {
187
- // On entering 'pending' state, execute the fetchUser effect.
188
- entry: [
189
- async (event, context) => {
190
- if (event.type !== 'FETCH') return; // Type guard
221
+ },
222
+ }
223
+ ```
224
+
225
+ ### 3. Multiple Entry/Exit Effects
226
+
227
+ You can run multiple side-effects (synchronous or asynchronous fire-and-forget) in order when entering or leaving a state by specifying an array of effects. Note that these are executed synchronously by the FSM (the FSM does not wait for any returned Promises to resolve).
228
+
229
+ ```typescript
230
+ states: {
231
+ active: {
232
+ entry: [
233
+ ({event}) => console.log('First entry effect', event),
234
+ async ({context}) => {
235
+ await saveProgress(context);
236
+ },
237
+ ],
238
+ exit: [
239
+ () => console.log('Exit effect'),
240
+ ],
241
+ },
242
+ }
243
+ ```
244
+
245
+ > [!WARNING]
246
+ > **Initial State Entry Events**:
247
+ > When the FSM is initialized, the entry effects and actors of the initial state are executed with a mock event `{ type: '__init__' }` (casted to `TEvent`).
248
+ > If your initial state's entry effects or actors expect specific custom payload properties on the triggering event, they might throw runtime errors. Ensure your initial state's entry logic checks the event type or handles `{ type: '__init__' }` safely.
249
+
250
+ ### 4. State Actors (Invoked Actors)
251
+
252
+ State Actors are async lifecycle processes spawned automatically when entering a state. In contrast to **Effects** (which are fire-and-forget synchronous actions), an **Actor**:
253
+
254
+ 1. Is instantiated dynamically upon state entry.
255
+ 2. Receives a `dispatch(event)` callback to asynchronously send events back to the parent FSM.
256
+ 3. Can return a synchronous cleanup/teardown function that is executed automatically when the FSM exits the state or is destroyed.
257
+
258
+ This conforms to XState v5 patterns and is extremely useful for running side-effects with a defined lifecycle, such as polling intervals, websocket listeners, or async fetch requests.
259
+
260
+ ```typescript
261
+ states: {
262
+ uploading: {
263
+ actors: [
264
+ ({context, dispatch}) => {
265
+ console.log('Spawning polling actor...');
266
+
267
+ const intervalId = setInterval(async () => {
191
268
  try {
192
- console.log(`Fetching user with id: ${event.id}...`);
193
- const response = await fetch(`https://api.example.com/users/${event.id}`);
194
- if (!response.ok) throw new Error('User not found');
195
- const user = (await response.json()) as User;
196
- // An effect can return a new event to be dispatched back to the machine.
197
- return {type: 'RESOLVE', user};
269
+ const status = await getStatus(context.fileId);
270
+ if (status.done) {
271
+ dispatch({type: 'UPLOAD_SUCCESS'});
272
+ } else {
273
+ dispatch({type: 'PROGRESS_UPDATE', percent: status.progress});
274
+ }
198
275
  } catch (err) {
199
- // If an error occurs, dispatch a 'REJECT' event.
200
- return {type: 'REJECT', error: (err as Error).message};
276
+ dispatch({type: 'UPLOAD_FAILURE', error: err.message});
201
277
  }
202
- },
203
- ],
204
- on: {
205
- RESOLVE: {
206
- target: 'success',
207
- assigners: [(event) => ({user: event.user})],
208
- },
209
- REJECT: {
210
- target: 'error',
211
- assigners: [(event) => ({error: event.error})],
212
- },
278
+ }, 1000);
279
+
280
+ // Return a cleanup callback run automatically on state exit
281
+ return () => {
282
+ console.log('Cleaning up polling actor...');
283
+ clearInterval(intervalId);
284
+ };
213
285
  },
286
+ ],
287
+ on: {
288
+ PROGRESS_UPDATE: {assigners: [({context, event}) => ({...context, progress: event.percent})]},
289
+ UPLOAD_SUCCESS: {target: 'success'},
290
+ UPLOAD_FAILURE: {target: 'failed'},
214
291
  },
215
- success: {
292
+ },
293
+ }
294
+ ```
295
+
296
+ ### 5. Internal vs. External Transitions (Self-Transitions)
297
+
298
+ Transitions are categorized into two types based on the presence of the `target` property:
299
+
300
+ 1. **Internal Transitions** (No `target` specified):
301
+ - Triggered when a transition updates the machine's `context` (via `assigners`) but keeps the machine in the current state.
302
+ - **Behavior**: State entry/exit effects are **not** executed, and active state actors are **not** restarted.
303
+ - **Use Case**: Simple data updates (e.g., updating progress percentages or input values).
304
+
305
+ 2. **External Transitions** (With `target` specified):
306
+ - Triggered when transitioning to a target state, **even if the target state is the same as the current state** (a self-transition).
307
+ - **Behavior**: Exits the current state (running `exit` effects and cleaning up active actors) and re-enters the state (running `entry` effects and spawning actors).
308
+ - **Use Case**: Resetting state-scoped processes, restarting animations, or refetching initial state data upon self-referential triggers.
309
+
310
+ ---
311
+
312
+ ## FSM as an Actor Model
313
+
314
+ In highly scalable architectures, complex user interface elements or backend workflows are best structured using the **Actor Model**. Under this paradigm, every specialized system is treated as an isolated, sovereign entity called an **Actor**.
315
+
316
+ An Actor complies strictly with three architectural rules:
317
+
318
+ 1. It maintains isolated local state that nobody else can mutate directly.
319
+ 2. It processes events via an inbound mailbox sequentially.
320
+ 3. It sends asynchronous messages to other Actors to notify them of shifts in reality.
321
+
322
+ ### The Alwatr Synergy: FSM + Flux Action Bus
323
+
324
+ `@alwatr/fsm` acts as the perfect structural core for an Actor.
325
+
326
+ - **The Brain**: `FsmService` encapsulates your local state (`stateSignal`) and contextual calculations.
327
+ - **The Mailbox**: The FSM's internal `eventSignal` serves as the private inbound message queue, exposed via the `dispatch` method.
328
+ - **The Global Nervous System**: The global `@alwatr/flux` Action Bus (`actionService.dispatch` / `actionService.on`) handles asynchronous messaging between different dockets of the system.
329
+
330
+ ### Concrete Actor Workflow Implementation
331
+
332
+ By combining an Input Controller, an FsmService, and Output Effects, you construct a pure Actor that is completely decoupled from other business domains.
333
+
334
+ #### Step A: The Input Gate (The Controller Box)
335
+
336
+ The Input Controller intercepts generic global intents from the `@alwatr/flux` ecosystem, confirms target context keys, and queues them into the Actor's private FSM mailbox.
337
+
338
+ ```typescript
339
+ // account-actor-controller.ts
340
+ import {actionService} from '@alwatr/flux';
341
+ import {accountFsmService} from './account-actor-service.js';
342
+
343
+ /**
344
+ * @context AI: Input Mailbox Router for the Account Actor.
345
+ * Intercepts generic UI actions and converts them into specific internal FSM events.
346
+ */
347
+ export function setupAccountActorController() {
348
+ // Catching global dialog resolutions without importing the Dialog Service directly
349
+ actionService.on('ui_confirm_dialog_resolved', (action) => {
350
+ // Structural Guard Boundary check: Ensure this confirmation belongs to this actor
351
+ if (action.context !== 'account_purging_flow') return;
352
+
353
+ const isApproved = action.payload === 'approve';
354
+
355
+ // Map the external system intent safely to the internal actor machine
356
+ accountFsmService.dispatch(isApproved ? {type: 'CONFIRM'} : {type: 'ABORT'});
357
+ });
358
+ }
359
+ ```
360
+
361
+ #### Step B: The Core Logic (The Actor Brain Config)
362
+
363
+ The internal machine processes the transition cleanly. When it shifts state, it uses entry/exit array hooks to broadcast outgoing signals to foreign actors.
364
+
365
+ ```typescript
366
+ // account-actor-config.ts
367
+ import type {StateMachineConfig} from '@alwatr/fsm';
368
+ import {actionService} from '@alwatr/flux';
369
+
370
+ export const accountActorConfig: StateMachineConfig<any, any, any> = {
371
+ name: 'account-actor-core',
372
+ initial: 'active',
373
+ context: {userId: 'usr_882'},
374
+ states: {
375
+ active: {
216
376
  on: {
217
- FETCH: {target: 'pending'}, // Allow re-fetching
377
+ TRIGGER_DELETE: {target: 'awaiting_confirmation'},
218
378
  },
219
379
  },
220
- error: {
221
- on: {
222
- RETRY: {
223
- target: 'pending',
224
- // A condition that must be met for the transition to occur.
225
- condition: (event, context) => {
226
- // This is just an example; you might have retry logic in the context.
227
- console.log('Checking retry condition...');
228
- return true;
229
- },
380
+ awaiting_confirmation: {
381
+ entry: [
382
+ // AI Note: Outbound Messaging. The Actor sends an instruction out over the nervous system.
383
+ () => {
384
+ actionService.dispatch({
385
+ type: 'confirm_dialog_requested',
386
+ payload: {
387
+ targetContext: 'account_purging_flow',
388
+ title: 'Purge Records',
389
+ message: 'Are you sure you want to completely erase your historical profile?',
390
+ },
391
+ });
230
392
  },
393
+ ],
394
+ on: {
395
+ CONFIRM: {target: 'wiping_data'},
396
+ ABORT: {target: 'active'},
231
397
  },
232
398
  },
399
+ wiping_data: {
400
+ entry: [
401
+ async () => {
402
+ // Perform isolated backend calls...
403
+ actionService.dispatch({type: 'confirm_dialog_close_requested'});
404
+ },
405
+ ],
406
+ },
233
407
  },
234
408
  };
409
+ ```
235
410
 
236
- // --- Usage ---
237
- const fetchService = createFsmService(fetchMachineConfig);
411
+ ### Strategic Benefits of the Actor Pattern
238
412
 
239
- fetchService.stateSignal.subscribe((state) => {
240
- console.log('Current State:', state.name);
241
- if (state.name === 'success') console.log('User:', state.context.user);
242
- if (state.name === 'error') console.log('Error:', state.context.error);
243
- });
413
+ 1. **Absolute Multi-Domain Decoupling**: Services never invoke each other directly. They speak exclusively via event packets, guaranteeing that any single domain can be refactored or swapped out without causing regressions.
414
+ 2. **Deterministic Parallelism**: Race conditions are structurally impossible. State transitions happen inside isolated microtasks while network responses queue outside the gate.
415
+ 3. **AI-Agent Optimization**: The explicit segregation of Input Routing (Controllers), Context Transitions (FSM), and Outbound Signals (Effects) provides rigid, understandable boundaries that LLMs can parse and write code for with near-perfect reliability.
244
416
 
245
- fetchService.eventSignal.dispatch({type: 'FETCH', id: '1'});
246
- ```
417
+ ---
247
418
 
248
- ## Architectural Advantages / مزیت‌های معماری
419
+ ## API Reference
249
420
 
250
- - **Predictable & Visualizable**: By constraining how and when state can change, your application logic becomes deterministic and easy to reason about. The declarative nature of the config allows for easy visualization of all possible states and transitions.
251
- <br>
252
- **قابل پیش‌بینی و ترسیم**: با محدود کردن زمان و چگونگی تغییر وضعیت، منطق برنامه شما قطعی و قابل درک می‌شود. ماهیت اعلانی پیکربندی، امکان ترسیم و مشاهده تمام وضعیت‌ها و گذارهای ممکن را فراهم می‌کند.
421
+ ### `createFsmService(config)`
253
422
 
254
- - **Type-Safe by Design**: The library is built from the ground up with TypeScript. It ensures that event payloads are correctly typed for each transition and that assigners update the context with valid data, preventing a wide class of bugs at compile time.
255
- <br>
256
- **ذاتاً تایپ-سیف**: این کتابخانه از پایه با TypeScript ساخته شده است. این امر تضمین می‌کند که پارامترهای رویدادها در هر گذار به درستی تایپ‌دهی شده و `assigner`ها زمینه (`context`) را با داده‌های معتبر به‌روزرسانی می‌کنند، که از بروز دسته وسیعی از باگ‌ها در زمان کامپایل جلوگیری می‌کند.
423
+ The primary factory utility to initiate a reactive FSM.
257
424
 
258
- - **Decouples Logic from UI**: The `FsmService` encapsulates your application's logic, keeping it completely independent of your UI framework. Your UI simply subscribes to the `stateSignal` and dispatches events to the `eventSignal`. This improves testability and makes it easy to refactor or even replace the UI layer.
259
- <br>
260
- **جداسازی منطق از UI**: سرویس FSM منطق برنامه شما را کپسوله کرده و آن را کاملاً مستقل از فریم‌ورک UI نگه می‌دارد. لایه UI شما تنها به `stateSignal` گوش می‌دهد و رویدادها را به `eventSignal` ارسال می‌کند. این رویکرد آزمون‌پذیری را بهبود بخشیده و بازنویسی یا حتی تعویض لایه UI را آسان می‌کند.
425
+ - **`config`**: `StateMachineConfig<TState, TEvent, TContext>` Declarative system matrix detailing properties and state hooks.
426
+ - **Returns**: `FsmService<TState, TEvent, TContext>` — The assigned runtime manager.
427
+ - `stateSignal`: An `IReadonlySignal` broadcasting atomic state shifts.
428
+ - `dispatch(event)`: Dispatches a typed event to the state machine.
429
+ - `destroy()`: Cleans up local allocations to completely prevent memory retention issues.
261
430
 
262
- - **Robust & Resilient**: The machine operates on a Run-to-Completion (RTC) model, ensuring that each event is fully processed before the next one begins, preventing race conditions. Furthermore, any errors thrown inside user-defined functions (`condition`, `assigner`, `effect`) are caught and logged, preventing the entire machine from crashing.
263
- <br>
264
- **مستحکم و انعطاف‌پذیر**: ماشین بر اساس مدل اجرای کامل تا انتها (RTC) عمل می‌کند، که تضمین می‌کند هر رویداد به طور کامل پردازش شده و سپس رویداد بعدی آغاز می‌شود. این از بروز race condition جلوگیری می‌کند. علاوه بر این، هرگونه خطای ایجاد شده در توابع تعریف‌شده توسط کاربر (`condition`, `assigner`, `effect`) مدیریت و لاگ می‌شود و از کرش کردن کل ماشین جلوگیری می‌کند.
431
+ ### Key Types
265
432
 
266
- ## API Reference / مرجع API
433
+ | Type | Description |
434
+ | :------------------------- | :--------------------------------------------------------------------------------------------------------------- |
435
+ | **`MachineState<S, C>`** | Represents the complete state, containing `name: S` and `context: C`. |
436
+ | **`MachineEvent<T>`** | The base interface for events. Must have a `type: T` property. |
437
+ | **`StateMachineConfig`** | The main configuration object defining `initial`, `context`, and `states`. |
438
+ | **`FsmPersistenceConfig`** | Configuration options for FSM state persistence (`schemaVersion`, `storageKey`). |
439
+ | **`Transition`** | Defines a transition with an optional `target`, `guard`, and `assigners`. |
440
+ | **`Assigner<E, C>`** | A synchronous function that returns the next context object or void (if mutated directly). |
441
+ | **`Effect<E, C>`** | A synchronous or asynchronous fire-and-forget function executed on state entry/exit (returns `Awaitable<void>`). |
442
+ | **`Actor<E, C>`** | A function spawned on state entry that can call `dispatch(event)` and return a cleanup function. |
443
+ | **`Guard<E, C>`** | A boolean guard function that must return `true` for a transition branch to be taken. |
267
444
 
268
- ### `createFsmService(config)`
445
+ ---
269
446
 
270
- The main factory function to create a new FSM service.
271
- تابع اصلی برای ساخت یک سرویس FSM جدید.
447
+ ## 🏛️ Part of the Alwatr Ecosystem
272
448
 
273
- - **`config`**: `StateMachineConfig<TState, TEvent, TContext>`
274
- - The declarative configuration object for the state machine.
275
- - آبجکت پیکربندی اعلانی برای ماشین حالت.
276
- - **Returns**: `FsmService<TState, TEvent, TContext>`
277
- - An instance of the FSM service with two main properties:
278
- - `stateSignal`: A readable signal that emits the current `MachineState`.
279
- - `eventSignal`: A signal to `dispatch` new events to the machine.
280
- - یک نمونه از سرویس FSM با دو پراپرتی اصلی:
281
- - `stateSignal`: یک سیگنال خواندنی که `MachineState` فعلی را منتشر می‌کند.
282
- - `eventSignal`: سیگنالی برای ارسال (`dispatch`) رویدادهای جدید به ماشین.
449
+ `@alwatr/fsm` is part of the **Alwatr Developer Kit**, a monorepo of lightweight TypeScript libraries designed for modularity and performance.
283
450
 
284
- ### Key Types / انواع کلیدی
451
+ - **[@alwatr/flux](https://github.com/Alwatr/alwatr/tree/next/pkg/flux)** UI and reactive bundle (aggregates signals, actions, and FSM).
452
+ - **[@alwatr/signal](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/signal)** — Fine-grained reactive signal bus.
453
+ - **[@alwatr/action](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/action)** — Global event delegation action bus.
454
+ - **[@alwatr/directive](https://github.com/Alwatr/alwatr/tree/next/pkg/nanolib/directive)** — Attribute-based DOM directives.
285
455
 
286
- | Type | Description |
287
- | :----------------------- | :---------------------------------------------------------------------------- |
288
- | **`MachineState<S, C>`** | Represents the complete state, containing `name: S` and `context: C`. |
289
- | **`MachineEvent<T>`** | The base interface for events. Must have a `type: T` property. |
290
- | **`StateMachineConfig`** | The main configuration object defining `initial`, `context`, and `states`. |
291
- | **`Transition`** | Defines a transition with an optional `target`, `condition`, and `assigners`. |
456
+ ---
292
457
 
293
- | نوع | توضیحات |
294
- | :----------------------- | :------------------------------------------------------------------------------------------------------------- |
295
- | **`MachineState<S, C>`** | وضعیت کامل ماشین را نمایش می‌دهد که شامل `name: S` (نام وضعیت) و `context: C` (زمینه) است. |
296
- | **`MachineEvent<T>`** | اینترفیس پایه برای رویدادها. باید یک پراپرتی `type: T` داشته باشد. |
297
- | **`StateMachineConfig`** | آبجکت اصلی پیکربندی که `initial` (وضعیت اولیه)، `context` (زمینه اولیه) و `states` (وضعیت‌ها) را تعریف می‌کند. |
298
- | **`Transition`** | یک گذار را با `target` (مقصد)، `condition` (شرط) و `assigners` (تخصیص‌دهنده‌ها)ی اختیاری تعریف می‌کند. |
458
+ ## 🤝 Contributing
299
459
 
300
- ## Sponsors
460
+ We welcome contributions! Please see our [Contributing Guide](https://github.com/Alwatr/alwatr/blob/next/CONTRIBUTING.md).
301
461
 
302
- The following companies, organizations, and individuals support flux ongoing maintenance and development. Become a Sponsor to get your logo on our README and website.
462
+ ---
303
463
 
304
- ## Contributing
464
+ ## 📄 License
305
465
 
306
- Contributions are welcome! Please read our [contribution guidelines](https://github.com/Alwatr/.github/blob/next/CONTRIBUTING.md) before submitting a pull request.
466
+ [MPL-2.0](https://github.com/Alwatr/alwatr/blob/next/LICENSE) © [S. Ali Mihandoost](https://ali.mihandoost.com)