@uuxxx/fsm 1.2.4 → 1.3.1

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
@@ -2,7 +2,16 @@
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/@uuxxx%2Ffsm.svg)](https://badge.fury.io/js/@uuxxx%2Ffsm)
4
4
 
5
- A lightweight, type-safe finite state machine library for JavaScript/TypeScript with plugin support and lifecycle hooks.
5
+ A lightweight, type-safe finite state machine library for TypeScript with plugin support, lifecycle hooks, and full type inference.
6
+
7
+ ## Features
8
+
9
+ - **Full type inference** — transition methods, states, and plugin APIs are auto-generated from config
10
+ - **Multiple transition types** — static, dynamic, async, wildcard (`*`), and multi-source
11
+ - **Lifecycle hooks** — `onBeforeTransition` (with veto) and `onAfterTransition`
12
+ - **Plugin system** — extend your FSM with custom APIs
13
+ - **Custom error handling** — provide an `onError` callback or let errors throw
14
+ - **Zero dependencies** aside from `@uuxxx/utils`
6
15
 
7
16
  ## Installation
8
17
 
@@ -21,11 +30,9 @@ import { makeFsm } from '@uuxxx/fsm';
21
30
 
22
31
  type State = 'idle' | 'loading' | 'success' | 'error';
23
32
 
24
- const STATES: State[] = ['idle', 'loading', 'success', 'error'];
25
-
26
33
  const fsm = makeFsm({
27
34
  init: 'idle',
28
- states: STATES,
35
+ states: ['idle', 'loading', 'success', 'error'],
29
36
  transitions: {
30
37
  start: {
31
38
  from: 'idle',
@@ -50,41 +57,39 @@ const fsm = makeFsm({
50
57
  },
51
58
  });
52
59
 
53
- // Check current state
54
- console.log(fsm.state()); // 'idle'
55
-
56
- // Perform transitions
57
- fsm.start();
58
- console.log(fsm.state()); // 'loading'
59
-
60
- fsm.succeed();
61
- console.log(fsm.state()); // 'success'
62
-
63
- fsm.reset();
64
- console.log(fsm.state()); // 'idle'
65
-
66
- fsm.goto('error');
67
- console.log(fsm.state()); // 'error'
60
+ fsm.state(); // 'idle'
61
+ fsm.start(); // 'loading'
62
+ fsm.succeed(); // 'success'
63
+ fsm.reset(); // 'idle'
64
+ fsm.goto('error'); // 'error'
68
65
  ```
69
66
 
67
+ Each transition key becomes a method on the FSM instance with the correct type signature inferred from config.
68
+
70
69
  ## API Reference
71
70
 
72
71
  ### `makeFsm(config)`
73
72
 
74
73
  Creates a new finite state machine instance.
75
74
 
76
- #### Parameters
75
+ #### Config
77
76
 
78
- - `config`: Configuration object with the following properties:
79
- - `init`: Initial state
80
- - `states`: Array of all possible states
81
- - `transitions`: Object defining state transitions
82
- - `methods?`: Optional lifecycle methods
83
- - `plugins?`: Optional array of plugins
77
+ | Property | Type | Required | Description |
78
+ | ------------- | --------------------------------------------- | -------- | ----------------------------------------------------------------------------- |
79
+ | `init` | `TState` | Yes | Initial state |
80
+ | `states` | `TState[]` | Yes | All valid states |
81
+ | `transitions` | `Record<string, Transition<TState>>` | Yes | Transition definitions (keys become methods) |
82
+ | `methods` | `{ onBeforeTransition?, onAfterTransition? }` | No | Lifecycle hooks |
83
+ | `plugins` | `Plugin[]` | No | Array of plugins |
84
+ | `onError` | `(msg: string) => void` | No | Custom error handler. By default, errors throw `Error` with a `[FSM]:` prefix |
84
85
 
85
86
  #### Returns
86
87
 
87
- An FSM instance with transition methods, state methods, and plugin APIs.
88
+ An FSM instance combining:
89
+
90
+ - **State methods** — `state()`, `allStates()`
91
+ - **Transition methods** — one per key in `transitions`
92
+ - **Plugin APIs** — one namespace per plugin
88
93
 
89
94
  ### State Methods
90
95
 
@@ -92,17 +97,9 @@ An FSM instance with transition methods, state methods, and plugin APIs.
92
97
 
93
98
  Returns the current state.
94
99
 
95
- ```typescript
96
- const currentState = fsm.state();
97
- ```
98
-
99
100
  #### `fsm.allStates()`
100
101
 
101
- Returns an array of all possible states.
102
-
103
- ```typescript
104
- const allStates = fsm.allStates();
105
- ```
102
+ Returns an array of all valid states.
106
103
 
107
104
  ### Transitions
108
105
 
@@ -115,21 +112,36 @@ type Transition<TState> = {
115
112
  };
116
113
  ```
117
114
 
118
- - `from`: The state(s) this transition can occur from
119
- - Single state: `'idle'`
120
- - Multiple states: `['loading', 'error']`
121
- - Any state: `'*'`
122
- - `to`: The target state or a function returning the target state
123
- - Static: `'loading'`
124
- - Dynamic: `(userId: string) => \`user\_\${userId}\``
125
- - Async: `async (data) => await apiCall(data)`
115
+ #### `from` source state(s)
116
+
117
+ | Form | Example | Description |
118
+ | --------------- | ---------------------- | --------------------- |
119
+ | Single state | `'idle'` | Only from this state |
120
+ | Multiple states | `['loading', 'error']` | From any listed state |
121
+ | Wildcard | `'*'` | From any state |
122
+
123
+ #### `to` — target state
124
+
125
+ | Form | Example | Description |
126
+ | ------- | --------------------------------- | -------------------------------- |
127
+ | Static | `'loading'` | Always transitions to this state |
128
+ | Dynamic | `(id: string) => \`user\_${id}\`` | Compute target from arguments |
129
+ | Async | `async () => await fetchState()` | Returns `Promise<TState>` |
130
+
131
+ #### Transition behavior
132
+
133
+ - **Circular transitions are skipped** — if `from === to`, the transition is silently canceled with a warning.
134
+ - **Concurrent async transitions are blocked** — starting a new transition while an async one is pending triggers an error.
135
+ - **Invalid target states** — transitioning to a state not in `states` triggers an error.
136
+ - **Forbidden transitions** — calling a transition from a state not matching `from` triggers an error.
137
+ - **Return value** — every transition method returns the new state (or `Promise<TState>` for async transitions).
126
138
 
127
139
  #### Examples
128
140
 
129
141
  ```typescript
130
142
  const transitions = {
131
- // Simple transition
132
- 'idle -> loading': {
143
+ // Static transition
144
+ start: {
133
145
  from: 'idle',
134
146
  to: 'loading',
135
147
  },
@@ -143,84 +155,113 @@ const transitions = {
143
155
  // Wildcard (from any state)
144
156
  goto: {
145
157
  from: '*',
146
- to: (targetState: State) => targetState,
158
+ to: (target: State) => target,
147
159
  },
148
160
 
149
161
  // Async transition
150
- 'async fetch': {
162
+ fetch: {
151
163
  from: 'idle',
152
164
  to: async () => {
153
165
  const result = await fetchData();
154
- return result.success ? 'success' : 'error';
166
+ return result.ok ? 'success' : 'error';
155
167
  },
156
168
  },
157
169
  };
158
170
  ```
159
171
 
172
+ ### Error Handling
173
+
174
+ By default, the FSM throws on errors (forbidden transitions, invalid states, concurrent transitions). You can provide a custom `onError` handler to change this behavior:
175
+
176
+ ```typescript
177
+ const fsm = makeFsm({
178
+ init: 'idle',
179
+ states: ['idle', 'loading'],
180
+ transitions: {
181
+ start: { from: 'idle', to: 'loading' },
182
+ stop: { from: 'loading', to: 'idle' },
183
+ },
184
+ onError: (msg) => {
185
+ console.warn(msg); // Handle gracefully instead of throwing
186
+ },
187
+ });
188
+
189
+ // Won't throw — calls onError instead
190
+ fsm.stop(); // "idle" → "idle" via "stop" is forbidden (from doesn't match)
191
+ ```
192
+
193
+ When `onError` is provided, the FSM state remains unchanged after an error.
194
+
160
195
  ### Lifecycle Methods
161
196
 
162
- Lifecycle methods can be attached to the FSM configuration:
197
+ Lifecycle methods hook into the transition process:
163
198
 
164
199
  ```typescript
165
- const config = {
166
- // ... other config
200
+ const fsm = makeFsm({
201
+ // ...
167
202
  methods: {
168
203
  onBeforeTransition: (event) => {
169
- console.log('About to transition:', event);
170
- // Return false to cancel the transition
171
- return true;
204
+ console.log(`${event.from} → ${event.to} via ${event.transition}`);
205
+ return false; // Return false to cancel the transition
172
206
  },
173
207
  onAfterTransition: (event) => {
174
- console.log('Transition completed:', event);
208
+ console.log('Transition complete:', event.transition);
175
209
  },
176
210
  },
177
- };
211
+ });
178
212
  ```
179
213
 
180
- #### `onBeforeTransition(event)`
214
+ #### Lifecycle event object
181
215
 
182
- Called before a transition occurs. Return `false` to cancel the transition.
216
+ | Property | Type | Description |
217
+ | ------------ | -------------------- | --------------------------------------- |
218
+ | `transition` | `string` | Name of the transition (the config key) |
219
+ | `from` | `TState` | State before the transition |
220
+ | `to` | `TState` | Target state |
221
+ | `args` | `any[] \| undefined` | Arguments passed to dynamic transitions |
183
222
 
184
- **Parameters:**
223
+ #### `onBeforeTransition(event)`
185
224
 
186
- - `event`: Object with `transition`, `from`, `to`, and optional `args`
225
+ Called before a transition. Return `false` to veto (cancel) the transition.
187
226
 
188
227
  #### `onAfterTransition(event)`
189
228
 
190
- Called after a successful transition.
191
-
192
- **Parameters:**
193
-
194
- - `event`: Object with `transition`, `from`, `to`, and optional `args`
229
+ Called after a successful transition. The FSM state is already updated at this point.
195
230
 
196
231
  ## Plugins
197
232
 
198
- Plugins extend the FSM with additional functionality. Each plugin receives an API object and returns a plugin definition.
233
+ Plugins extend the FSM with additional methods, grouped under a namespace.
199
234
 
200
235
  ### Plugin API
201
236
 
202
- Plugins have access to:
237
+ Each plugin receives an `api` object with:
203
238
 
204
- - `api.state()`: Get current state
205
- - `api.allStates()`: Get all states
206
- - `api.init(callback)`: Register initialization callback
207
- - `api.onBeforeTransition(callback)`: Register before transition callback
208
- - `api.onAfterTransition(callback)`: Register after transition callback
239
+ | Method | Description |
240
+ | ---------------------------------- | ----------------------------------------------------------------- |
241
+ | `api.state()` | Get current state |
242
+ | `api.allStates()` | Get all valid states |
243
+ | `api.init(callback)` | Run callback when FSM is created (receives initial state) |
244
+ | `api.onBeforeTransition(callback)` | Register before-transition listener. Returns unsubscribe function |
245
+ | `api.onAfterTransition(callback)` | Register after-transition listener. Returns unsubscribe function |
246
+ | `api.onError(callback)` | Register error listener. Returns unsubscribe function |
209
247
 
210
248
  ### Creating a Plugin
211
249
 
212
250
  ```typescript
213
251
  import type { FsmLabel, FsmPlugin, FsmTransition } from '@uuxxx/fsm';
214
252
 
215
- export const somePlugin = <TState extends FsmLabel, TTransitions extends Record<string, FsmTransition<TState>>>() =>
253
+ export const myPlugin = <TState extends FsmLabel, TTransitions extends Record<string, FsmTransition<TState>>>() =>
216
254
  ((api) => {
217
- // ... plugin code
255
+ let count = 0;
256
+
257
+ api.onAfterTransition(() => {
258
+ count++;
259
+ });
218
260
 
219
261
  return {
220
- // replace with your plugin name
221
- name: 'plugin-name' as const,
262
+ name: 'counter' as const,
222
263
  api: {
223
- // ... plugin methods
264
+ getCount: () => count,
224
265
  },
225
266
  };
226
267
  }) satisfies FsmPlugin<TState, TTransitions>;
@@ -229,54 +270,79 @@ export const somePlugin = <TState extends FsmLabel, TTransitions extends Record<
229
270
  ### Using Plugins
230
271
 
231
272
  ```typescript
232
- const config = {
233
- // ... other config
234
- plugins: [somePlugin({ someOption: true })],
235
- };
236
-
237
- const fsm = makeFsm(config);
273
+ const fsm = makeFsm({
274
+ // ...
275
+ plugins: [myPlugin()],
276
+ });
238
277
 
239
- // Access plugin API
240
- const currentState = fsm['plugin-name'].doSomething();
278
+ fsm.start();
279
+ fsm.counter.getCount(); // 1
241
280
  ```
242
281
 
282
+ Plugin names must be unique — registering two plugins with the same name triggers an error.
283
+
243
284
  ## Built-in Plugins
244
285
 
245
286
  ### History Plugin
246
287
 
247
- Tracks state history and provides navigation methods.
288
+ Read-only state history tracking with pointer-based navigation.
289
+
290
+ `back()` and `forward()` move an internal pointer and return the state at that position — they do **not** change the FSM state. Use transition methods to actually navigate (e.g. `fsm.goto(fsm.history.back(1))`).
248
291
 
249
292
  ```typescript
250
293
  import { makeFsm } from '@uuxxx/fsm';
251
- import { fsmHistoryPlugin } from '@uuxxx/fsm/history-plugin';
294
+ import { historyPlugin } from '@uuxxx/fsm-plugins/history';
252
295
 
253
- const config = {
254
- // ... config
255
- plugins: [fsmHistoryPlugin()],
256
- };
296
+ const fsm = makeFsm({
297
+ init: 'a',
298
+ states: ['a', 'b', 'c'],
299
+ transitions: {
300
+ goto: { from: '*', to: (s: 'a' | 'b' | 'c') => s },
301
+ },
302
+ plugins: [historyPlugin()],
303
+ });
257
304
 
258
- const fsm = makeFsm(config);
305
+ fsm.goto('b');
306
+ fsm.goto('c');
307
+ fsm.history.get(); // ['a', 'b', 'c'] (returns a copy)
259
308
 
260
- // Navigate
261
- fsm.goto('state1');
262
- fsm.goto('state2');
309
+ fsm.history.back(1); // returns 'b' (pointer moved, FSM state unchanged)
310
+ fsm.history.current(); // 'b'
311
+ fsm.history.canBack(); // true
312
+ fsm.history.canForward(); // true
313
+ fsm.history.forward(1); // returns 'c'
314
+ fsm.goto(fsm.history.current()); // actually transition to 'c'
315
+ ```
263
316
 
264
- // History API
265
- console.log(fsm.history.get()); // Get all history
317
+ #### History API
266
318
 
267
- fsm.history.back(1); // Get 1 step back
268
- fsm.history.forward(1); // Get 1 step forward
269
- ```
319
+ | Method | Returns | Description |
320
+ | ---------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------- |
321
+ | `fsm.history.get()` | `TState[]` | Returns a copy of the full history array |
322
+ | `fsm.history.current()` | `TState` | Returns the state at the current pointer position |
323
+ | `fsm.history.back(steps)` | `TState` | Move pointer back by `steps`, returns the state at that position. Clamps to start. Ignores non-positive values |
324
+ | `fsm.history.forward(steps)` | `TState` | Move pointer forward by `steps`, returns the state at that position. Clamps to end. Ignores non-positive values |
325
+ | `fsm.history.canBack()` | `boolean` | Whether the pointer can move back (pointer > 0) |
326
+ | `fsm.history.canForward()` | `boolean` | Whether the pointer can move forward (pointer < end) |
270
327
 
271
- #### History API Methods
328
+ When a transition occurs, any forward history after the current pointer is discarded (like browser navigation).
272
329
 
273
- - `fsm.history.get()`: Get the full history array
274
- - `fsm.history.back(steps?)`: Get N step back (default: 1)
275
- - `fsm.history.forward(steps?)`: Get N step forward (default: 1)
330
+ ## Exported Types
331
+
332
+ The library exports the following types for use in plugins and generic code:
333
+
334
+ ```typescript
335
+ import type {
336
+ FsmConfig, // Config<TState, TTransitions, TPlugins>
337
+ FsmTransition, // Transition<TState>
338
+ FsmPlugin, // Plugin<TState, TTransitions>
339
+ FsmLabel, // string (state label type)
340
+ } from '@uuxxx/fsm';
341
+ ```
276
342
 
277
343
  ## TypeScript Support
278
344
 
279
- The library is fully typed. Type inference works automatically:
345
+ The library is built with TypeScript-first design. All types are inferred from config — no manual type annotations needed:
280
346
 
281
347
  ```typescript
282
348
  const fsm = makeFsm({
@@ -287,5 +353,12 @@ const fsm = makeFsm({
287
353
  stop: { from: 'running', to: 'stopped' },
288
354
  },
289
355
  });
290
- // fsm is fully typed - autocomplete works for transitions and states
356
+
357
+ fsm.start(); // ✓ typed — only callable from 'idle'
358
+ fsm.stop(); // ✓ typed — only callable from 'running'
359
+ fsm.state(); // ✓ returns 'idle' | 'running' | 'stopped'
291
360
  ```
361
+
362
+ ## License
363
+
364
+ [MIT](./LICENSE)
package/dist/index.d.ts CHANGED
@@ -1,10 +1,61 @@
1
- import { a as Transition, c as KeyOf, i as LifecycleMethods, l as Rec, n as Plugin, o as Label, r as StateMethods, s as EmptyArray } from "./Plugin-Dx6AAZEC.js";
1
+ type AnyFn = (...args: any[]) => any; //#endregion
2
+ type Rec<T = unknown> = Record<string, T>; //#endregion
3
+ type KeyOf<T extends Rec> = keyof T; //#endregion
4
+ type Noop = () => void; //#endregion
5
+ type Key = string | number | symbol; //#endregion
6
+ type Vdx<T> = T | void; //#endregion
7
+ type EmptyArray = []; //#endregion
8
+ type Entries<T extends Rec> = { [K in KeyOf<T>]: [K, T[K]] }[KeyOf<T>]; //#endregion
9
+ type Label = string;
10
+ /**
11
+ * Defines a single state transition.
12
+ *
13
+ * - `from` — source state(s): a single state, an array of states, or `'*'` for any state.
14
+ * - `to` — target: a static state, a function returning a state, or an async function returning `Promise<TState>`.
15
+ */
16
+ type Transition<TState extends Label> = {
17
+ from: '*' | TState | TState[];
18
+ to: TState | ((...args: any[]) => TState | Promise<TState>);
19
+ };
20
+ /** Event object passed to lifecycle hooks during a transition. */
21
+ type Lifecycle<TState extends Label, TEntry extends [Key, Transition<Label>]> = {
22
+ args?: Parameters<Extract<TEntry[1]['to'], AnyFn>>;
23
+ transition: TEntry[0];
24
+ from: TState;
25
+ to: TState;
26
+ };
27
+ type LifecycleMethod<TState extends Label, TTransitions extends Rec<Transition<TState>>> = (lifecycle: Lifecycle<TState, Entries<TTransitions>>) => void;
28
+ type CancelableLifecycleMethod<TState extends Label, TTransitions extends Rec<Transition<TState>>> = (lifecycle: Lifecycle<TState, Entries<TTransitions>>) => Vdx<boolean>;
29
+ type LifecycleMethods<TState extends Label, TTransitions extends Rec<Transition<TState>>> = {
30
+ onBeforeTransition?: CancelableLifecycleMethod<TState, TTransitions>;
31
+ onAfterTransition?: LifecycleMethod<TState, TTransitions>;
32
+ };
33
+ type StateMethods<TState extends Label> = {
34
+ state: () => TState;
35
+ allStates: () => TState[];
36
+ };
37
+ /** API object passed to each plugin during registration. Provides state access, lifecycle hooks, and error listeners. */
38
+ type ApiForPlugin<TState extends Label, TTransitions extends Rec<Transition<TState>>> = {
39
+ init: (listener: (state: TState) => void) => void;
40
+ onError: (listener: (msg: string) => void) => Noop;
41
+ } & StateMethods<TState> & { [K in KeyOf<LifecycleMethods<TState, TTransitions>>]-?: (listener: LifecycleMethods<TState, TTransitions>[K]) => Noop };
42
+ type PluginApi = {
43
+ name: string;
44
+ api: Rec<AnyFn>;
45
+ };
46
+ /**
47
+ * A plugin is a function that receives the {@link ApiForPlugin} and returns
48
+ * `{ name, api }` — the name becomes a namespace on the FSM instance,
49
+ * and `api` methods are accessible under that namespace.
50
+ */
51
+ type Plugin<TState extends Label = Label, TTransitions extends Rec<Transition<TState>> = Rec<Transition<TState>>> = (api: ApiForPlugin<TState, TTransitions>) => PluginApi;
52
+ /** Configuration object for {@link makeFsm}. */
2
53
  type Config<TState extends Label, TTransitions extends Rec<Transition<TState>>, TPlugins extends Array<Plugin<TState, TTransitions>> = EmptyArray> = {
3
- init: TState;
4
- states: TState[];
5
- transitions: TTransitions;
6
- methods?: LifecycleMethods<TState, TTransitions>;
7
- plugins?: TPlugins;
54
+ /** Initial state of the FSM. */init: TState; /** All valid states. The FSM will reject transitions to states not in this list. */
55
+ states: TState[]; /** Transition definitions. Each key becomes a method on the FSM instance. */
56
+ transitions: TTransitions; /** Optional lifecycle hooks (`onBeforeTransition`, `onAfterTransition`). */
57
+ methods?: LifecycleMethods<TState, TTransitions>; /** Optional plugins to extend the FSM with additional APIs. */
58
+ plugins?: TPlugins; /** Custom error handler. By default, errors throw with a `[FSM]:` prefix. */
8
59
  onError?: (msg: string) => void;
9
60
  };
10
61
  type TransitionMethods<TTransitions extends Rec<Transition<Label>>> = { [K in KeyOf<TTransitions>]: TTransitions[K]['to'] extends Label ? () => TTransitions[K]['to'] : TTransitions[K]['to'] };
@@ -12,5 +63,27 @@ type PluginsMethods<TState extends Label, TTransitions extends Rec<Transition<TS
12
63
  name: K;
13
64
  }>['api'] };
14
65
  type Methods<TState extends Label, TTransitions extends Rec<Transition<TState>>, TPlugins extends Array<Plugin<TState, TTransitions>>> = TransitionMethods<TTransitions> & StateMethods<TState> & PluginsMethods<TState, TTransitions, TPlugins>;
66
+ /**
67
+ * Creates a finite state machine instance from the given configuration.
68
+ *
69
+ * Returns an object combining state methods (`state()`, `allStates()`),
70
+ * auto-generated transition methods (one per key in `config.transitions`),
71
+ * and plugin APIs (one namespace per plugin).
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const fsm = makeFsm({
76
+ * init: 'idle',
77
+ * states: ['idle', 'loading', 'done'],
78
+ * transitions: {
79
+ * start: { from: 'idle', to: 'loading' },
80
+ * finish: { from: 'loading', to: 'done' },
81
+ * },
82
+ * });
83
+ *
84
+ * fsm.start(); // 'loading'
85
+ * fsm.finish(); // 'done'
86
+ * ```
87
+ */
15
88
  declare const makeFsm: <TState extends Label, TTransitions extends Rec<Transition<TState>>, TPlugins extends Array<Plugin<TState, TTransitions>>>(config: Config<TState, TTransitions, TPlugins>) => Methods<TState, TTransitions, TPlugins>;
16
89
  export { type Config as FsmConfig, type Label as FsmLabel, type Plugin as FsmPlugin, type Transition as FsmTransition, makeFsm };
package/package.json CHANGED
@@ -1,17 +1,23 @@
1
1
  {
2
2
  "name": "@uuxxx/fsm",
3
- "version": "1.2.4",
4
- "description": "Javascript library for creating finite state machine",
3
+ "version": "1.3.1",
4
+ "description": "Lightweight, type-safe finite state machine for TypeScript with plugin support and lifecycle hooks",
5
5
  "keywords": [
6
6
  "finite state machine",
7
- "fsm"
7
+ "fsm",
8
+ "lifecycle",
9
+ "plugins",
10
+ "state machine",
11
+ "type-safe",
12
+ "typescript"
8
13
  ],
9
- "homepage": "https://github.com/uuxxx/fsm/blob/main/README.md",
14
+ "homepage": "https://github.com/uuxxx/fsm/blob/main/packages/core/README.md",
10
15
  "license": "MIT",
11
16
  "author": "Artem Tryapichnikov <golysheeet@gmail.com>",
12
17
  "repository": {
13
18
  "type": "git",
14
- "url": "git+https://github.com/uuxxx/fsm.git"
19
+ "url": "git+https://github.com/uuxxx/fsm.git",
20
+ "directory": "packages/core"
15
21
  },
16
22
  "files": [
17
23
  "dist",
@@ -23,49 +29,20 @@
23
29
  ".": {
24
30
  "types": "./dist/index.d.ts",
25
31
  "default": "./dist/index.js"
26
- },
27
- "./history-plugin": {
28
- "types": "./dist/history-plugin.d.ts",
29
- "default": "./dist/history-plugin.js"
30
32
  }
31
33
  },
32
34
  "publishConfig": {
33
35
  "access": "public"
34
36
  },
35
- "dependencies": {
36
- "@uuxxx/utils": "^0.0.3"
37
- },
38
37
  "devDependencies": {
39
- "@babel/core": "^7.28.4",
40
- "@babel/preset-env": "^7.28.3",
41
- "@babel/preset-typescript": "^7.27.1",
42
- "@changesets/cli": "^2.29.7",
43
38
  "@swc/core": "^1.13.5",
44
- "lefthook": "^2.0.2",
45
- "oxfmt": "^0.41.0",
46
- "oxlint": "^1.0.0",
47
- "oxlint-tsgolint": "^0.17.0",
48
39
  "rolldown": "^1.0.0-beta.41",
49
40
  "rolldown-plugin-dts": "^0.22.1",
50
41
  "rollup-plugin-swc3": "^0.12.1",
51
- "ts-node": "^10.9.2",
52
- "tslib": "^2.8.1",
53
- "typescript": "^5.9.3",
54
- "vitest": "^4.0.18"
55
- },
56
- "engines": {
57
- "node": ">= 20",
58
- "pnpm": ">= 10"
42
+ "tslib": "^2.8.1"
59
43
  },
60
44
  "scripts": {
61
45
  "build": "rolldown -c rolldown.config.ts",
62
- "changeset:version": "changeset version && git add --all && git commit -m 'chore: bump'",
63
- "changeset:publish": "changeset publish",
64
- "test": "vitest run",
65
- "lint": "oxlint",
66
- "lint:fix": "oxlint --fix",
67
- "fmt": "oxfmt --write .",
68
- "fmt:check": "oxfmt --check .",
69
46
  "check:types": "tsc --noEmit"
70
47
  }
71
48
  }
@@ -1,39 +0,0 @@
1
- type AnyFn = (...args: any[]) => any; //#endregion
2
- type Rec<T = unknown> = Record<string, T>; //#endregion
3
- type KeyOf<T extends Rec> = keyof T; //#endregion
4
- type Noop = () => void; //#endregion
5
- type Key = string | number | symbol; //#endregion
6
- type Vdx<T> = T | void; //#endregion
7
- type EmptyArray = []; //#endregion
8
- type Entries<T extends Rec> = { [K in KeyOf<T>]: [K, T[K]] }[KeyOf<T>]; //#endregion
9
- type Label = string;
10
- type Transition<TState extends Label> = {
11
- from: '*' | TState | TState[];
12
- to: TState | ((...args: any[]) => TState | Promise<TState>);
13
- };
14
- type Lifecycle<TState extends Label, TEntry extends [Key, Transition<Label>]> = {
15
- args?: Parameters<Extract<TEntry[1]['to'], AnyFn>>;
16
- transition: TEntry[0];
17
- from: TState;
18
- to: TState;
19
- };
20
- type LifecycleMethod<TState extends Label, TTransitions extends Rec<Transition<TState>>> = (lifecycle: Lifecycle<TState, Entries<TTransitions>>) => void;
21
- type CancelableLifecycleMethod<TState extends Label, TTransitions extends Rec<Transition<TState>>> = (lifecycle: Lifecycle<TState, Entries<TTransitions>>) => Vdx<boolean>;
22
- type LifecycleMethods<TState extends Label, TTransitions extends Rec<Transition<TState>>> = {
23
- onBeforeTransition?: CancelableLifecycleMethod<TState, TTransitions>;
24
- onAfterTransition?: LifecycleMethod<TState, TTransitions>;
25
- };
26
- type StateMethods<TState extends Label> = {
27
- state: () => TState;
28
- allStates: () => TState[];
29
- };
30
- type ApiForPlugin<TState extends Label, TTransitions extends Rec<Transition<TState>>> = {
31
- init: (listener: (state: TState) => void) => void;
32
- onError: (listener: (msg: string) => void) => Noop;
33
- } & StateMethods<TState> & { [K in KeyOf<LifecycleMethods<TState, TTransitions>>]-?: (listener: LifecycleMethods<TState, TTransitions>[K]) => Noop };
34
- type PluginApi = {
35
- name: string;
36
- api: Rec<AnyFn>;
37
- };
38
- type Plugin<TState extends Label = Label, TTransitions extends Rec<Transition<TState>> = Rec<Transition<TState>>> = (api: ApiForPlugin<TState, TTransitions>) => PluginApi;
39
- export { Transition as a, KeyOf as c, LifecycleMethods as i, Rec as l, Plugin as n, Label as o, StateMethods as r, EmptyArray as s, ApiForPlugin as t };
@@ -1,10 +0,0 @@
1
- import { a as Transition, l as Rec, o as Label, t as ApiForPlugin } from "./Plugin-Dx6AAZEC.js";
2
- declare const historyPlugin: <TState extends Label, TTransitions extends Rec<Transition<TState>>>() => (api: ApiForPlugin<TState, TTransitions>) => {
3
- name: "history";
4
- api: {
5
- get(): TState[];
6
- back(steps: number): TState;
7
- forward(steps: number): TState;
8
- };
9
- };
10
- export { historyPlugin as fsmHistoryPlugin };
@@ -1 +0,0 @@
1
- let t=()=>t=>{let i=[],n=0;return t.init(t=>i.push(t)),t.onAfterTransition(({to:t})=>{n++,i.splice(n,i.length-n,t)}),{name:"history",api:{get:()=>i,back:t=>i[n=Math.max(0,n-t)],forward:t=>(n=Math.min(i.length-1,n+t),i[n])}}};export{t as fsmHistoryPlugin};