@vsirotin/ts-stop 0.5.1 → 0.6.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 +2 -164
- package/lib/FiniteStateMachine.d.ts +6 -1
- package/lib/FiniteStateMachine.d.ts.map +1 -1
- package/lib/FiniteStateMachine.js +114 -6
- package/lib/FiniteStateMachine.js.map +1 -1
- package/lib/esm/FiniteStateMachine.js +114 -6
- package/lib/esm/fa/DefaultState.js +10 -0
- package/lib/esm/fa/FiniteStateMachine.js +405 -0
- package/lib/esm/fa/IStateWithActions.js +1 -0
- package/lib/esm/fa/IStateWithOutputSignal.js +1 -0
- package/lib/esm/fa/MatrixBasedStateMachine.js +127 -0
- package/lib/esm/fa/TransitionMatrix.js +98 -0
- package/lib/esm/index.js +6 -6
- package/lib/esm/sfsm/ExternalWorldHub.js +80 -0
- package/lib/esm/sfsm/FaLoader.js +30 -0
- package/lib/esm/sfsm/FaReducer.js +36 -0
- package/lib/esm/sfsm/FaResolver.js +115 -0
- package/lib/esm/sfsm/FaUpdater.js +101 -0
- package/lib/esm/sfsm/Sfsm.js +225 -0
- package/lib/esm/sfsm/index.js +6 -0
- package/lib/esm/sfsm/interfaces.js +1 -0
- package/lib/esm/sfsm/types.js +1 -0
- package/lib/fa/DefaultState.d.ts +9 -0
- package/lib/fa/DefaultState.d.ts.map +1 -0
- package/lib/fa/DefaultState.js +15 -0
- package/lib/fa/DefaultState.js.map +1 -0
- package/lib/fa/FiniteStateMachine.d.ts +227 -0
- package/lib/fa/FiniteStateMachine.d.ts.map +1 -0
- package/lib/fa/FiniteStateMachine.js +410 -0
- package/lib/fa/FiniteStateMachine.js.map +1 -0
- package/lib/fa/IStateWithActions.d.ts +18 -0
- package/lib/fa/IStateWithActions.d.ts.map +1 -0
- package/lib/fa/IStateWithActions.js +3 -0
- package/lib/fa/IStateWithActions.js.map +1 -0
- package/lib/fa/IStateWithOutputSignal.d.ts +4 -0
- package/lib/fa/IStateWithOutputSignal.d.ts.map +1 -0
- package/lib/fa/IStateWithOutputSignal.js +3 -0
- package/lib/fa/IStateWithOutputSignal.js.map +1 -0
- package/lib/fa/MatrixBasedStateMachine.d.ts +108 -0
- package/lib/fa/MatrixBasedStateMachine.d.ts.map +1 -0
- package/lib/fa/MatrixBasedStateMachine.js +132 -0
- package/lib/fa/MatrixBasedStateMachine.js.map +1 -0
- package/lib/fa/TransitionMatrix.d.ts +61 -0
- package/lib/fa/TransitionMatrix.d.ts.map +1 -0
- package/lib/fa/TransitionMatrix.js +104 -0
- package/lib/fa/TransitionMatrix.js.map +1 -0
- package/lib/index.d.ts +6 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -6
- package/lib/index.js.map +1 -1
- package/lib/sfsm/ExternalWorldHub.d.ts +52 -0
- package/lib/sfsm/ExternalWorldHub.d.ts.map +1 -0
- package/lib/sfsm/ExternalWorldHub.js +85 -0
- package/lib/sfsm/ExternalWorldHub.js.map +1 -0
- package/lib/sfsm/FaLoader.d.ts +22 -0
- package/lib/sfsm/FaLoader.d.ts.map +1 -0
- package/lib/sfsm/FaLoader.js +68 -0
- package/lib/sfsm/FaLoader.js.map +1 -0
- package/lib/sfsm/FaReducer.d.ts +12 -0
- package/lib/sfsm/FaReducer.d.ts.map +1 -0
- package/lib/sfsm/FaReducer.js +40 -0
- package/lib/sfsm/FaReducer.js.map +1 -0
- package/lib/sfsm/FaResolver.d.ts +37 -0
- package/lib/sfsm/FaResolver.d.ts.map +1 -0
- package/lib/sfsm/FaResolver.js +120 -0
- package/lib/sfsm/FaResolver.js.map +1 -0
- package/lib/sfsm/FaUpdater.d.ts +25 -0
- package/lib/sfsm/FaUpdater.d.ts.map +1 -0
- package/lib/sfsm/FaUpdater.js +106 -0
- package/lib/sfsm/FaUpdater.js.map +1 -0
- package/lib/sfsm/Sfsm.d.ts +60 -0
- package/lib/sfsm/Sfsm.d.ts.map +1 -0
- package/lib/sfsm/Sfsm.js +230 -0
- package/lib/sfsm/Sfsm.js.map +1 -0
- package/lib/sfsm/index.d.ts +9 -0
- package/lib/sfsm/index.d.ts.map +1 -0
- package/lib/sfsm/index.js +18 -0
- package/lib/sfsm/index.js.map +1 -0
- package/lib/sfsm/interfaces.d.ts +22 -0
- package/lib/sfsm/interfaces.d.ts.map +1 -0
- package/lib/sfsm/interfaces.js +3 -0
- package/lib/sfsm/interfaces.js.map +1 -0
- package/lib/sfsm/types.d.ts +78 -0
- package/lib/sfsm/types.d.ts.map +1 -0
- package/lib/sfsm/types.js +3 -0
- package/lib/sfsm/types.js.map +1 -0
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -2,171 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
State Oriented Programming library for TypeScript.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
See [DEVELOPMENT.md](./DEVELOPMENT.md) for development instructions.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### From NPM (when published)
|
|
10
|
-
```bash
|
|
11
|
-
npm install @vsirotin/ts-stop
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
### Local Installation for Another Project
|
|
15
|
-
|
|
16
|
-
If you want to use this package in another project without publishing to NPM:
|
|
17
|
-
|
|
18
|
-
#### Step 1: Build and Pack the Library
|
|
19
|
-
```bash
|
|
20
|
-
# In the ts-stop directory
|
|
21
|
-
cd /path/to/StOP/ts/ts-stop
|
|
22
|
-
npm run build
|
|
23
|
-
npm pack
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
This creates a file `vsirotin-ts-stop-<version>.tgz` in the ts-stop directory.
|
|
27
|
-
|
|
28
|
-
#### Step 2: Install in Your Project
|
|
29
|
-
|
|
30
|
-
Choose one of these methods:
|
|
31
|
-
|
|
32
|
-
**Method 1: Install from Tarball (Recommended)**
|
|
33
|
-
```bash
|
|
34
|
-
# In your project directory
|
|
35
|
-
npm install /Users/viktorsirotin/VSCodeProjects/StOP/ts/ts-stop/vsirotin-ts-stop-0.0.1.tgz
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Method 2: Install from Directory**
|
|
39
|
-
```bash
|
|
40
|
-
# In your project directory
|
|
41
|
-
npm install /path/to/StOP/ts/ts-stop
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
**Method 3: Add to package.json**
|
|
45
|
-
```json
|
|
46
|
-
{
|
|
47
|
-
"dependencies": {
|
|
48
|
-
"@vsirotin/ts-stop": "file:/path/to/StOP/ts/ts-stop"
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
Then run `npm install`.
|
|
53
|
-
|
|
54
|
-
#### Step 3: Update After Changes
|
|
55
|
-
|
|
56
|
-
When you make changes to the ts-stop library:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
# In the ts-stop directory
|
|
60
|
-
npm run build
|
|
61
|
-
npm pack
|
|
62
|
-
|
|
63
|
-
# In your project directory
|
|
64
|
-
npm install /path/to/StOP/ts/ts-stop/vsirotin-ts-stop-0.0.1.tgz --force
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Or if using Method 2 or 3:
|
|
68
|
-
```bash
|
|
69
|
-
# In your project directory
|
|
70
|
-
npm install --force
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Usage
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { FiniteStateMachine, DefaultState } from '@vsirotin/ts-stop';
|
|
77
|
-
|
|
78
|
-
// Example: Simple State Machine
|
|
79
|
-
const states = [/* your states */];
|
|
80
|
-
const signals = [/* your signals */];
|
|
81
|
-
const transitions = [/* your transitions */];
|
|
82
|
-
const startState = states[0];
|
|
83
|
-
|
|
84
|
-
const machine = new FiniteStateMachine(
|
|
85
|
-
states,
|
|
86
|
-
signals,
|
|
87
|
-
transitions,
|
|
88
|
-
startState
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// Process signals
|
|
92
|
-
machine.processSignal(someSignal);
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Development
|
|
96
|
-
|
|
97
|
-
### Build
|
|
98
|
-
```bash
|
|
99
|
-
npm run build # Build both CommonJS and ESM versions
|
|
100
|
-
npm run build:cjs # Build CommonJS version only
|
|
101
|
-
npm run build:esm # Build ESM version only
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Test
|
|
105
|
-
```bash
|
|
106
|
-
npm test # Run tests
|
|
107
|
-
npm run test:coverage # Run tests with coverage
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Clean
|
|
111
|
-
```bash
|
|
112
|
-
npm run clean # Remove build artifacts
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Package
|
|
116
|
-
```bash
|
|
117
|
-
npm run pack # Build and create tarball for distribution
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Package Structure
|
|
121
|
-
|
|
122
|
-
After building, the package contains:
|
|
123
|
-
|
|
124
|
-
```
|
|
125
|
-
lib/
|
|
126
|
-
├── index.js # CommonJS entry point
|
|
127
|
-
├── index.d.ts # TypeScript declarations
|
|
128
|
-
├── index.d.ts.map # TypeScript declaration source map
|
|
129
|
-
├── index.js.map # JavaScript source map
|
|
130
|
-
├── esm/ # ES Modules
|
|
131
|
-
│ └── index.js # ESM entry point
|
|
132
|
-
└── [other modules with .js, .d.ts, and .map files]
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
The package includes:
|
|
136
|
-
- **CommonJS** modules in `lib/` for Node.js compatibility
|
|
137
|
-
- **ESM** (ES Modules) in `lib/esm/` for modern JavaScript
|
|
138
|
-
- **TypeScript declarations** (`.d.ts`) for type checking
|
|
139
|
-
- **Source maps** for debugging
|
|
140
|
-
|
|
141
|
-
## Using the Package Locally
|
|
142
|
-
|
|
143
|
-
To use this package in another project without publishing to NPM:
|
|
144
|
-
|
|
145
|
-
### Method 1: Direct Installation
|
|
146
|
-
```bash
|
|
147
|
-
# From your target project directory
|
|
148
|
-
npm install /path/to/StOP/ts/ts-stop
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Method 2: Using the Packed Tarball
|
|
152
|
-
```bash
|
|
153
|
-
# In ts-stop directory
|
|
154
|
-
npm run pack
|
|
155
|
-
|
|
156
|
-
# In your target project directory
|
|
157
|
-
npm install /path/to/StOP/ts/ts-stop/vsirotin-ts-stop-0.0.1.tgz
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Method 3: Using package.json
|
|
161
|
-
In your target project's `package.json`:
|
|
162
|
-
```json
|
|
163
|
-
{
|
|
164
|
-
"dependencies": {
|
|
165
|
-
"@vsirotin/ts-stop": "file:../StOP/ts/ts-stop"
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
Then run `npm install`.
|
|
7
|
+
For usage and API documentation, see the [main StOP documentation](../../docs/Tutorial/01-StOP-Tutorial.md).
|
|
170
8
|
|
|
171
9
|
## License
|
|
172
10
|
|
|
@@ -102,8 +102,9 @@ export declare abstract class FiniteStateMachine<STATE, SIGNAL> implements IFini
|
|
|
102
102
|
* @param signals - Array of all possible signals/events
|
|
103
103
|
* @param transitions - Array of transition rules defining how signals move between states
|
|
104
104
|
* @param startState - The initial state of the machine
|
|
105
|
+
* @param skipValidation - When true, all structural validations are skipped (default: false)
|
|
105
106
|
*/
|
|
106
|
-
constructor(states: STATE[], signals: SIGNAL[], transitions: ITransition<STATE, SIGNAL>[], startState: STATE);
|
|
107
|
+
constructor(states: STATE[], signals: SIGNAL[], transitions: ITransition<STATE, SIGNAL>[], startState: STATE, skipValidation?: boolean);
|
|
107
108
|
/**
|
|
108
109
|
* Gets the current state of the state machine.
|
|
109
110
|
*
|
|
@@ -126,6 +127,10 @@ export declare abstract class FiniteStateMachine<STATE, SIGNAL> implements IFini
|
|
|
126
127
|
*/
|
|
127
128
|
protected sendSignal(signal: SIGNAL): STATE;
|
|
128
129
|
private tryProcessStateWithOutputSignal;
|
|
130
|
+
private validateUnreachableStates;
|
|
131
|
+
private validateDanglingReferences;
|
|
132
|
+
private validateOutputSignalStates;
|
|
133
|
+
private validateOutputSignalCycles;
|
|
129
134
|
private isStateWithOutputSignal;
|
|
130
135
|
private executeStateTransition;
|
|
131
136
|
private executeStateActions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FiniteStateMachine.d.ts","sourceRoot":"","sources":["../src/FiniteStateMachine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAG7F;;;;;GAKG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,EAAE,MAAM;IACtC,IAAI,EAAE,KAAK,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,KAAK,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB,CAAC,KAAK;IACtC;;;;OAIG;IACH,eAAe,IAAI,KAAK,CAAC;CAE5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,8BAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAE,YAAW,mBAAmB,CAAC,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"FiniteStateMachine.d.ts","sourceRoot":"","sources":["../src/FiniteStateMachine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAG7F;;;;;GAKG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,EAAE,MAAM;IACtC,IAAI,EAAE,KAAK,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,KAAK,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB,CAAC,KAAK;IACtC;;;;OAIG;IACH,eAAe,IAAI,KAAK,CAAC;CAE5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,8BAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAE,YAAW,mBAAmB,CAAC,KAAK,CAAC;IAuBrF,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE;IACzB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IAC3B,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE;IACnD,SAAS,CAAC,UAAU,EAAE,KAAK;IAzB/B;;;OAGG;IACH,SAAS,CAAC,YAAY,EAAE,KAAK,CAAC;IAE1B;;;GAGD;IACH,OAAO,CAAC,YAAY,CAA6B;IAEjD;;;;;;;;OAQG;gBAEW,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,EACzC,UAAU,EAAE,KAAK,EAC3B,cAAc,GAAE,OAAe;IA2CnC;;;;OAIG;IACH,eAAe,IAAI,KAAK;IAIxB;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK;IAqB3C,OAAO,CAAC,+BAA+B;IAcvC,OAAO,CAAC,yBAAyB;IA0BjC,OAAO,CAAC,0BAA0B;IAiClC,OAAO,CAAC,0BAA0B;IAelC,OAAO,CAAC,0BAA0B;IAmDlC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,sBAAsB;IAiB9B,OAAO,CAAC,mBAAmB;IAK3B;;;;;OAKG;IACH,SAAS,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IAI/C;;;;;OAKG;IACH,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,IAAI,KAAK,GAAG,0BAA0B;IAOxF;;;;;OAKG;IACH,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,IAAI,KAAK,GAAG,0BAA0B;IAOxF;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;;;OAIG;IACH,YAAY,IAAI,KAAK,EAAE;IAIvB;;;;OAIG;IACH,aAAa,IAAI,MAAM,EAAE;IAIzB;;;;;OAKG;IACH,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAMtD;;;;OAIG;IACH,+BAA+B,IAAI,MAAM,EAAE;IAM3C;;;;OAIG;IACH,cAAc,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE;IAI9C;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIlC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;IAI3B;;;;GAID;IACH,eAAe,IAAI,YAAY,GAAG,IAAI;IAItC;;;;OAIG;IACH,eAAe,IAAI,OAAO;CAG7B"}
|
|
@@ -65,8 +65,9 @@ class FiniteStateMachine {
|
|
|
65
65
|
* @param signals - Array of all possible signals/events
|
|
66
66
|
* @param transitions - Array of transition rules defining how signals move between states
|
|
67
67
|
* @param startState - The initial state of the machine
|
|
68
|
+
* @param skipValidation - When true, all structural validations are skipped (default: false)
|
|
68
69
|
*/
|
|
69
|
-
constructor(states, signals, transitions, startState) {
|
|
70
|
+
constructor(states, signals, transitions, startState, skipValidation = false) {
|
|
70
71
|
this.states = states;
|
|
71
72
|
this.signals = signals;
|
|
72
73
|
this.transitions = transitions;
|
|
@@ -78,11 +79,21 @@ class FiniteStateMachine {
|
|
|
78
79
|
this.defaultState = null;
|
|
79
80
|
// Find states that implement IDefaultState
|
|
80
81
|
const defaultStates = states.filter(state => this.isDefaultState(state));
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
`
|
|
85
|
-
|
|
82
|
+
if (!skipValidation) {
|
|
83
|
+
// Validate default state count
|
|
84
|
+
if (defaultStates.length > 1) {
|
|
85
|
+
throw new Error(`ERROR-STOP-01: Multiple states implement IDefaultState interface. ` +
|
|
86
|
+
`Only one state can handle invalid signals. ` +
|
|
87
|
+
`Found ${defaultStates.length} states with IDefaultState.`);
|
|
88
|
+
}
|
|
89
|
+
// Validate that all transition and start-state references are defined
|
|
90
|
+
this.validateDanglingReferences();
|
|
91
|
+
// Validate that all output signal states have at least one outgoing transition
|
|
92
|
+
this.validateOutputSignalStates();
|
|
93
|
+
// Validate that output-signal states do not form cycles
|
|
94
|
+
this.validateOutputSignalCycles();
|
|
95
|
+
// Validate that all states are reachable from the start state
|
|
96
|
+
this.validateUnreachableStates();
|
|
86
97
|
}
|
|
87
98
|
// Set default state if exactly one found
|
|
88
99
|
if (defaultStates.length === 1) {
|
|
@@ -145,6 +156,103 @@ class FiniteStateMachine {
|
|
|
145
156
|
}
|
|
146
157
|
}
|
|
147
158
|
}
|
|
159
|
+
validateUnreachableStates() {
|
|
160
|
+
const reachable = new Set();
|
|
161
|
+
const queue = [this.startState];
|
|
162
|
+
reachable.add(this.startState);
|
|
163
|
+
while (queue.length > 0) {
|
|
164
|
+
const current = queue.shift();
|
|
165
|
+
for (const t of this.transitions) {
|
|
166
|
+
if (t.from === current && !reachable.has(t.to)) {
|
|
167
|
+
reachable.add(t.to);
|
|
168
|
+
queue.push(t.to);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const state of this.states) {
|
|
173
|
+
if (this.isDefaultState(state))
|
|
174
|
+
continue; // DefaultState handles invalid signals — unreachable by design
|
|
175
|
+
if (!reachable.has(state)) {
|
|
176
|
+
throw new Error(`ERROR-STOP-08: State '${state}' is unreachable from start state '${this.startState}'. ` +
|
|
177
|
+
`All non-default states must be reachable from the start state via the transition graph.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
validateDanglingReferences() {
|
|
182
|
+
const stateSet = new Set(this.states);
|
|
183
|
+
const signalSet = new Set(this.signals);
|
|
184
|
+
if (!stateSet.has(this.startState)) {
|
|
185
|
+
throw new Error(`ERROR-STOP-03: Start state '${this.startState}' is not in the states array. ` +
|
|
186
|
+
`The start state must be one of the provided states.`);
|
|
187
|
+
}
|
|
188
|
+
for (const t of this.transitions) {
|
|
189
|
+
if (!stateSet.has(t.from)) {
|
|
190
|
+
throw new Error(`ERROR-STOP-04: Transition references unknown state '${t.from}' as 'from'. ` +
|
|
191
|
+
`All transition states must be in the states array.`);
|
|
192
|
+
}
|
|
193
|
+
if (!stateSet.has(t.to)) {
|
|
194
|
+
throw new Error(`ERROR-STOP-05: Transition references unknown state '${t.to}' as 'to'. ` +
|
|
195
|
+
`All transition states must be in the states array.`);
|
|
196
|
+
}
|
|
197
|
+
if (!signalSet.has(t.signal)) {
|
|
198
|
+
throw new Error(`ERROR-STOP-06: Transition references unknown signal '${String(t.signal)}'. ` +
|
|
199
|
+
`All transition signals must be in the signals array.`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
validateOutputSignalStates() {
|
|
204
|
+
for (const state of this.states) {
|
|
205
|
+
if (this.isStateWithOutputSignal(state)) {
|
|
206
|
+
const hasOutgoing = this.transitions.some(t => t.from === state);
|
|
207
|
+
if (!hasOutgoing) {
|
|
208
|
+
throw new Error(`ERROR-STOP-02: Configuration error: State '${state}' implements IStateWithOutputSignal ` +
|
|
209
|
+
`but has no outgoing transitions. States with output signals must have ` +
|
|
210
|
+
`at least one outgoing transition.`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
validateOutputSignalCycles() {
|
|
216
|
+
const outputStateSet = new Set(this.states.filter(s => this.isStateWithOutputSignal(s)));
|
|
217
|
+
if (outputStateSet.size < 2)
|
|
218
|
+
return;
|
|
219
|
+
// Build adjacency: output state → output states reachable via one transition
|
|
220
|
+
const adj = new Map();
|
|
221
|
+
for (const state of outputStateSet) {
|
|
222
|
+
adj.set(state, this.transitions
|
|
223
|
+
.filter(t => t.from === state && outputStateSet.has(t.to))
|
|
224
|
+
.map(t => t.to));
|
|
225
|
+
}
|
|
226
|
+
// DFS-based cycle detection (colour: 0=unvisited, 1=in-stack, 2=done)
|
|
227
|
+
const visited = new Set();
|
|
228
|
+
const onStack = new Set();
|
|
229
|
+
const stackPath = [];
|
|
230
|
+
const dfs = (state) => {
|
|
231
|
+
var _a;
|
|
232
|
+
visited.add(state);
|
|
233
|
+
onStack.add(state);
|
|
234
|
+
stackPath.push(state);
|
|
235
|
+
for (const neighbor of ((_a = adj.get(state)) !== null && _a !== void 0 ? _a : [])) {
|
|
236
|
+
if (onStack.has(neighbor)) {
|
|
237
|
+
const cycleStart = stackPath.indexOf(neighbor);
|
|
238
|
+
const cyclePath = [...stackPath.slice(cycleStart), neighbor];
|
|
239
|
+
throw new Error(`ERROR-STOP-07: Circular dependency detected in output signals: ` +
|
|
240
|
+
`${cyclePath.map(s => String(s)).join(' -> ')}. ` +
|
|
241
|
+
`States with output signals cannot form cycles as this creates infinite loops.`);
|
|
242
|
+
}
|
|
243
|
+
if (!visited.has(neighbor)) {
|
|
244
|
+
dfs(neighbor);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
onStack.delete(state);
|
|
248
|
+
stackPath.pop();
|
|
249
|
+
};
|
|
250
|
+
for (const state of outputStateSet) {
|
|
251
|
+
if (!visited.has(state)) {
|
|
252
|
+
dfs(state);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
148
256
|
isStateWithOutputSignal(state) {
|
|
149
257
|
const obj = state;
|
|
150
258
|
return (typeof obj === 'object' &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FiniteStateMachine.js","sourceRoot":"","sources":["../src/FiniteStateMachine.ts"],"names":[],"mappings":";;;AAAA,iDAA8C;AAgC9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAsB,kBAAkB;IAapC
|
|
1
|
+
{"version":3,"file":"FiniteStateMachine.js","sourceRoot":"","sources":["../src/FiniteStateMachine.ts"],"names":[],"mappings":"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"}
|
|
@@ -62,8 +62,9 @@ export class FiniteStateMachine {
|
|
|
62
62
|
* @param signals - Array of all possible signals/events
|
|
63
63
|
* @param transitions - Array of transition rules defining how signals move between states
|
|
64
64
|
* @param startState - The initial state of the machine
|
|
65
|
+
* @param skipValidation - When true, all structural validations are skipped (default: false)
|
|
65
66
|
*/
|
|
66
|
-
constructor(states, signals, transitions, startState) {
|
|
67
|
+
constructor(states, signals, transitions, startState, skipValidation = false) {
|
|
67
68
|
this.states = states;
|
|
68
69
|
this.signals = signals;
|
|
69
70
|
this.transitions = transitions;
|
|
@@ -75,11 +76,21 @@ export class FiniteStateMachine {
|
|
|
75
76
|
this.defaultState = null;
|
|
76
77
|
// Find states that implement IDefaultState
|
|
77
78
|
const defaultStates = states.filter(state => this.isDefaultState(state));
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`
|
|
82
|
-
|
|
79
|
+
if (!skipValidation) {
|
|
80
|
+
// Validate default state count
|
|
81
|
+
if (defaultStates.length > 1) {
|
|
82
|
+
throw new Error(`ERROR-STOP-01: Multiple states implement IDefaultState interface. ` +
|
|
83
|
+
`Only one state can handle invalid signals. ` +
|
|
84
|
+
`Found ${defaultStates.length} states with IDefaultState.`);
|
|
85
|
+
}
|
|
86
|
+
// Validate that all transition and start-state references are defined
|
|
87
|
+
this.validateDanglingReferences();
|
|
88
|
+
// Validate that all output signal states have at least one outgoing transition
|
|
89
|
+
this.validateOutputSignalStates();
|
|
90
|
+
// Validate that output-signal states do not form cycles
|
|
91
|
+
this.validateOutputSignalCycles();
|
|
92
|
+
// Validate that all states are reachable from the start state
|
|
93
|
+
this.validateUnreachableStates();
|
|
83
94
|
}
|
|
84
95
|
// Set default state if exactly one found
|
|
85
96
|
if (defaultStates.length === 1) {
|
|
@@ -142,6 +153,103 @@ export class FiniteStateMachine {
|
|
|
142
153
|
}
|
|
143
154
|
}
|
|
144
155
|
}
|
|
156
|
+
validateUnreachableStates() {
|
|
157
|
+
const reachable = new Set();
|
|
158
|
+
const queue = [this.startState];
|
|
159
|
+
reachable.add(this.startState);
|
|
160
|
+
while (queue.length > 0) {
|
|
161
|
+
const current = queue.shift();
|
|
162
|
+
for (const t of this.transitions) {
|
|
163
|
+
if (t.from === current && !reachable.has(t.to)) {
|
|
164
|
+
reachable.add(t.to);
|
|
165
|
+
queue.push(t.to);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const state of this.states) {
|
|
170
|
+
if (this.isDefaultState(state))
|
|
171
|
+
continue; // DefaultState handles invalid signals — unreachable by design
|
|
172
|
+
if (!reachable.has(state)) {
|
|
173
|
+
throw new Error(`ERROR-STOP-08: State '${state}' is unreachable from start state '${this.startState}'. ` +
|
|
174
|
+
`All non-default states must be reachable from the start state via the transition graph.`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
validateDanglingReferences() {
|
|
179
|
+
const stateSet = new Set(this.states);
|
|
180
|
+
const signalSet = new Set(this.signals);
|
|
181
|
+
if (!stateSet.has(this.startState)) {
|
|
182
|
+
throw new Error(`ERROR-STOP-03: Start state '${this.startState}' is not in the states array. ` +
|
|
183
|
+
`The start state must be one of the provided states.`);
|
|
184
|
+
}
|
|
185
|
+
for (const t of this.transitions) {
|
|
186
|
+
if (!stateSet.has(t.from)) {
|
|
187
|
+
throw new Error(`ERROR-STOP-04: Transition references unknown state '${t.from}' as 'from'. ` +
|
|
188
|
+
`All transition states must be in the states array.`);
|
|
189
|
+
}
|
|
190
|
+
if (!stateSet.has(t.to)) {
|
|
191
|
+
throw new Error(`ERROR-STOP-05: Transition references unknown state '${t.to}' as 'to'. ` +
|
|
192
|
+
`All transition states must be in the states array.`);
|
|
193
|
+
}
|
|
194
|
+
if (!signalSet.has(t.signal)) {
|
|
195
|
+
throw new Error(`ERROR-STOP-06: Transition references unknown signal '${String(t.signal)}'. ` +
|
|
196
|
+
`All transition signals must be in the signals array.`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
validateOutputSignalStates() {
|
|
201
|
+
for (const state of this.states) {
|
|
202
|
+
if (this.isStateWithOutputSignal(state)) {
|
|
203
|
+
const hasOutgoing = this.transitions.some(t => t.from === state);
|
|
204
|
+
if (!hasOutgoing) {
|
|
205
|
+
throw new Error(`ERROR-STOP-02: Configuration error: State '${state}' implements IStateWithOutputSignal ` +
|
|
206
|
+
`but has no outgoing transitions. States with output signals must have ` +
|
|
207
|
+
`at least one outgoing transition.`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
validateOutputSignalCycles() {
|
|
213
|
+
const outputStateSet = new Set(this.states.filter(s => this.isStateWithOutputSignal(s)));
|
|
214
|
+
if (outputStateSet.size < 2)
|
|
215
|
+
return;
|
|
216
|
+
// Build adjacency: output state → output states reachable via one transition
|
|
217
|
+
const adj = new Map();
|
|
218
|
+
for (const state of outputStateSet) {
|
|
219
|
+
adj.set(state, this.transitions
|
|
220
|
+
.filter(t => t.from === state && outputStateSet.has(t.to))
|
|
221
|
+
.map(t => t.to));
|
|
222
|
+
}
|
|
223
|
+
// DFS-based cycle detection (colour: 0=unvisited, 1=in-stack, 2=done)
|
|
224
|
+
const visited = new Set();
|
|
225
|
+
const onStack = new Set();
|
|
226
|
+
const stackPath = [];
|
|
227
|
+
const dfs = (state) => {
|
|
228
|
+
var _a;
|
|
229
|
+
visited.add(state);
|
|
230
|
+
onStack.add(state);
|
|
231
|
+
stackPath.push(state);
|
|
232
|
+
for (const neighbor of ((_a = adj.get(state)) !== null && _a !== void 0 ? _a : [])) {
|
|
233
|
+
if (onStack.has(neighbor)) {
|
|
234
|
+
const cycleStart = stackPath.indexOf(neighbor);
|
|
235
|
+
const cyclePath = [...stackPath.slice(cycleStart), neighbor];
|
|
236
|
+
throw new Error(`ERROR-STOP-07: Circular dependency detected in output signals: ` +
|
|
237
|
+
`${cyclePath.map(s => String(s)).join(' -> ')}. ` +
|
|
238
|
+
`States with output signals cannot form cycles as this creates infinite loops.`);
|
|
239
|
+
}
|
|
240
|
+
if (!visited.has(neighbor)) {
|
|
241
|
+
dfs(neighbor);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
onStack.delete(state);
|
|
245
|
+
stackPath.pop();
|
|
246
|
+
};
|
|
247
|
+
for (const state of outputStateSet) {
|
|
248
|
+
if (!visited.has(state)) {
|
|
249
|
+
dfs(state);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
145
253
|
isStateWithOutputSignal(state) {
|
|
146
254
|
const obj = state;
|
|
147
255
|
return (typeof obj === 'object' &&
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker base class for states that should be used processed by invalid signals and
|
|
3
|
+
* in situation, then no transitions set for pair (current signal, current state).
|
|
4
|
+
* Only one state in a state machine can have this role.
|
|
5
|
+
*/
|
|
6
|
+
export class DefaultState {
|
|
7
|
+
isDefaultState() {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
}
|