statecharts.sh 0.0.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/dist/index.d.mts +133 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +390 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +28 -0
- package/schema.json +141 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type StringKeys<T> = Extract<keyof T, string>;
|
|
3
|
+
type AtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys];
|
|
4
|
+
type AnyContext = Record<string, unknown>;
|
|
5
|
+
interface BaseEvent {
|
|
6
|
+
type: string;
|
|
7
|
+
}
|
|
8
|
+
type EventPayload<E extends BaseEvent, T extends E["type"]> = Extract<E, {
|
|
9
|
+
type: T;
|
|
10
|
+
}>;
|
|
11
|
+
type Action<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent> = (context: TContext, event: TEvent) => Partial<TContext> | void;
|
|
12
|
+
type Actions<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent> = Action<TContext, TEvent> | Action<TContext, TEvent>[];
|
|
13
|
+
type Guard<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent> = (context: TContext, event: TEvent) => boolean;
|
|
14
|
+
interface TransitionObject<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent> {
|
|
15
|
+
target?: string;
|
|
16
|
+
guard?: Guard<TContext, TEvent>;
|
|
17
|
+
action?: Action<TContext, TEvent>;
|
|
18
|
+
}
|
|
19
|
+
type TransitionConfig<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent> = string | TransitionObject<TContext, TEvent> | TransitionObject<TContext, TEvent>[];
|
|
20
|
+
interface Subscription {
|
|
21
|
+
unsubscribe: () => void;
|
|
22
|
+
}
|
|
23
|
+
interface SubscriptionSource<T> {
|
|
24
|
+
subscribe: (observer: {
|
|
25
|
+
next: (value: T) => void;
|
|
26
|
+
error?: (err: unknown) => void;
|
|
27
|
+
}) => Subscription;
|
|
28
|
+
}
|
|
29
|
+
type InvokeFn<TContext extends AnyContext> = (context: TContext) => Promise<unknown> | SubscriptionSource<unknown>;
|
|
30
|
+
interface InvokeHandlers<TContext extends AnyContext> {
|
|
31
|
+
onDone?: TransitionConfig<TContext, BaseEvent & {
|
|
32
|
+
data: unknown;
|
|
33
|
+
}>;
|
|
34
|
+
onError?: TransitionConfig<TContext, BaseEvent & {
|
|
35
|
+
error: unknown;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
interface StateNode<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent> {
|
|
39
|
+
entry?: Actions<TContext, TEvent>;
|
|
40
|
+
exit?: Actions<TContext, TEvent>;
|
|
41
|
+
on?: Record<string, TransitionConfig<TContext, TEvent>>;
|
|
42
|
+
after?: Record<number, TransitionConfig<TContext, TEvent>>;
|
|
43
|
+
invoke?: InvokeFn<TContext>;
|
|
44
|
+
onDone?: TransitionConfig<TContext, BaseEvent & {
|
|
45
|
+
data: unknown;
|
|
46
|
+
}>;
|
|
47
|
+
onError?: TransitionConfig<TContext, BaseEvent & {
|
|
48
|
+
error: unknown;
|
|
49
|
+
}>;
|
|
50
|
+
initial?: string;
|
|
51
|
+
states?: Record<string, StateNode<TContext, TEvent>>;
|
|
52
|
+
parallel?: Record<string, {
|
|
53
|
+
initial: string;
|
|
54
|
+
states: Record<string, StateNode<TContext, TEvent>>;
|
|
55
|
+
}>;
|
|
56
|
+
final?: boolean;
|
|
57
|
+
}
|
|
58
|
+
interface ChartDefinition<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent> {
|
|
59
|
+
id?: string;
|
|
60
|
+
context: TContext;
|
|
61
|
+
initial: string;
|
|
62
|
+
states: Record<string, StateNode<TContext, TEvent>>;
|
|
63
|
+
events?: TEvent;
|
|
64
|
+
}
|
|
65
|
+
type StateValue = string | {
|
|
66
|
+
[key: string]: StateValue;
|
|
67
|
+
};
|
|
68
|
+
interface IStateSnapshot<TContext extends AnyContext> {
|
|
69
|
+
readonly value: StateValue;
|
|
70
|
+
readonly context: Readonly<TContext>;
|
|
71
|
+
readonly done: boolean;
|
|
72
|
+
readonly path: readonly string[];
|
|
73
|
+
readonly timestamp: number;
|
|
74
|
+
matches(stateValue: string): boolean;
|
|
75
|
+
}
|
|
76
|
+
interface ChartInstance<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent> {
|
|
77
|
+
readonly state: IStateSnapshot<TContext>;
|
|
78
|
+
send(event: TEvent | TEvent["type"]): void;
|
|
79
|
+
subscribe(listener: (state: IStateSnapshot<TContext>) => void): () => void;
|
|
80
|
+
onTransition(listener: (event: TEvent) => void): () => void;
|
|
81
|
+
stop(): void;
|
|
82
|
+
}
|
|
83
|
+
interface Chart<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent> {
|
|
84
|
+
readonly definition: ChartDefinition<TContext, TEvent>;
|
|
85
|
+
start(initialContext?: Partial<TContext>): ChartInstance<TContext, TEvent>;
|
|
86
|
+
export(): ExportedChart;
|
|
87
|
+
}
|
|
88
|
+
interface ExportedTransition {
|
|
89
|
+
target?: string;
|
|
90
|
+
guard: string | null;
|
|
91
|
+
actions: string[];
|
|
92
|
+
}
|
|
93
|
+
interface ExportedStateNode {
|
|
94
|
+
entry: string[];
|
|
95
|
+
exit: string[];
|
|
96
|
+
on: Record<string, ExportedTransition | ExportedTransition[]>;
|
|
97
|
+
after?: Record<number, ExportedTransition | ExportedTransition[]>;
|
|
98
|
+
invoke?: string | null;
|
|
99
|
+
onDone?: ExportedTransition | ExportedTransition[];
|
|
100
|
+
onError?: ExportedTransition | ExportedTransition[];
|
|
101
|
+
initial?: string;
|
|
102
|
+
states?: Record<string, ExportedStateNode>;
|
|
103
|
+
parallel?: Record<string, {
|
|
104
|
+
initial: string;
|
|
105
|
+
states: Record<string, ExportedStateNode>;
|
|
106
|
+
}>;
|
|
107
|
+
final?: boolean;
|
|
108
|
+
}
|
|
109
|
+
interface ExportedChart {
|
|
110
|
+
version: 1;
|
|
111
|
+
id: string;
|
|
112
|
+
initial: string;
|
|
113
|
+
context: unknown;
|
|
114
|
+
states: Record<string, ExportedStateNode>;
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/chart.d.ts
|
|
118
|
+
declare function chart<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent>(definition: ChartDefinition<TContext, TEvent>): Chart<TContext, TEvent>;
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/snapshot.d.ts
|
|
121
|
+
declare class StateSnapshot<TContext extends AnyContext> implements IStateSnapshot<TContext> {
|
|
122
|
+
readonly value: StateValue;
|
|
123
|
+
readonly context: Readonly<TContext>;
|
|
124
|
+
readonly done: boolean;
|
|
125
|
+
readonly path: readonly string[];
|
|
126
|
+
readonly timestamp: number;
|
|
127
|
+
constructor(value: StateValue, context: TContext, done: boolean, path: readonly string[], timestamp?: number);
|
|
128
|
+
matches(stateValue: string): boolean;
|
|
129
|
+
private matchesPath;
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
export { type Action, type Actions, type AnyContext, type AtLeastOne, type BaseEvent, type Chart, type ChartDefinition, type ChartInstance, type EventPayload, type ExportedChart, type ExportedStateNode, type ExportedTransition, type Guard, type IStateSnapshot, type InvokeFn, type InvokeHandlers, type StateNode, StateSnapshot, type StateValue, type StringKeys, type Subscription, type SubscriptionSource, type TransitionConfig, type TransitionObject, chart };
|
|
133
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/chart.ts","../src/snapshot.ts"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
//#region src/snapshot.ts
|
|
2
|
+
var StateSnapshot = class {
|
|
3
|
+
value;
|
|
4
|
+
context;
|
|
5
|
+
done;
|
|
6
|
+
path;
|
|
7
|
+
timestamp;
|
|
8
|
+
constructor(value, context, done, path, timestamp) {
|
|
9
|
+
this.value = value;
|
|
10
|
+
this.context = Object.freeze({ ...context });
|
|
11
|
+
this.done = done;
|
|
12
|
+
this.path = Object.freeze([...path]);
|
|
13
|
+
this.timestamp = timestamp ?? Date.now();
|
|
14
|
+
}
|
|
15
|
+
matches(stateValue) {
|
|
16
|
+
const parts = stateValue.split(".");
|
|
17
|
+
if (typeof this.value === "string") return parts.length === 1 && this.value === parts[0];
|
|
18
|
+
return this.matchesPath(parts, 0);
|
|
19
|
+
}
|
|
20
|
+
matchesPath(parts, index) {
|
|
21
|
+
if (index >= parts.length) return true;
|
|
22
|
+
const part = parts[index];
|
|
23
|
+
if (part === void 0) return true;
|
|
24
|
+
return this.path.includes(part) && this.matchesPath(parts, index + 1);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/transition.ts
|
|
30
|
+
function resolveTransition(config, context, event) {
|
|
31
|
+
if (typeof config === "string") return {
|
|
32
|
+
target: config,
|
|
33
|
+
actions: []
|
|
34
|
+
};
|
|
35
|
+
if (Array.isArray(config)) {
|
|
36
|
+
for (const item of config) {
|
|
37
|
+
const result = resolveTransitionObject(item, context, event);
|
|
38
|
+
if (result !== null) return result;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return resolveTransitionObject(config, context, event);
|
|
43
|
+
}
|
|
44
|
+
function resolveTransitionObject(config, context, event) {
|
|
45
|
+
if (config.guard && !config.guard(context, event)) return null;
|
|
46
|
+
return {
|
|
47
|
+
target: config.target,
|
|
48
|
+
actions: config.action ? [config.action] : []
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/invoke.ts
|
|
54
|
+
function isSubscriptionSource(value) {
|
|
55
|
+
return typeof value === "object" && value !== null && "subscribe" in value && typeof value.subscribe === "function";
|
|
56
|
+
}
|
|
57
|
+
function startInvoke(invokeFn, context, onDone, onError) {
|
|
58
|
+
let cancelled = false;
|
|
59
|
+
let subscription = null;
|
|
60
|
+
try {
|
|
61
|
+
const result = invokeFn(context);
|
|
62
|
+
if (isSubscriptionSource(result)) subscription = result.subscribe({
|
|
63
|
+
next: (value) => {
|
|
64
|
+
if (!cancelled) onDone(value);
|
|
65
|
+
},
|
|
66
|
+
error: (err) => {
|
|
67
|
+
if (!cancelled) onError(err);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
else result.then((data) => {
|
|
71
|
+
if (!cancelled) onDone(data);
|
|
72
|
+
}).catch((error) => {
|
|
73
|
+
if (!cancelled) onError(error);
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (!cancelled) onError(error);
|
|
77
|
+
}
|
|
78
|
+
return { cancel() {
|
|
79
|
+
cancelled = true;
|
|
80
|
+
if (subscription) subscription.unsubscribe();
|
|
81
|
+
} };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/instance.ts
|
|
86
|
+
function createInstance(definition, initialContextOverride) {
|
|
87
|
+
let context = initialContextOverride ? {
|
|
88
|
+
...definition.context,
|
|
89
|
+
...initialContextOverride
|
|
90
|
+
} : { ...definition.context };
|
|
91
|
+
let stopped = false;
|
|
92
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
93
|
+
const transitionListeners = /* @__PURE__ */ new Set();
|
|
94
|
+
const activeStates = [];
|
|
95
|
+
const initialPath = buildInitialPath(definition.states, definition.initial);
|
|
96
|
+
let currentSnapshot = createSnapshot(initialPath);
|
|
97
|
+
enterStates(initialPath);
|
|
98
|
+
function createSnapshot(path) {
|
|
99
|
+
const value = pathToValue(path);
|
|
100
|
+
const done = isPathFinal(path);
|
|
101
|
+
return new StateSnapshot(value, context, done, path);
|
|
102
|
+
}
|
|
103
|
+
function pathToValue(path) {
|
|
104
|
+
if (path.length === 0) return "";
|
|
105
|
+
if (path.length === 1) return path[0];
|
|
106
|
+
let result = path[path.length - 1];
|
|
107
|
+
for (let i = path.length - 2; i >= 0; i--) result = { [path[i]]: result };
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
function buildInitialPath(states, initial) {
|
|
111
|
+
const path = [initial];
|
|
112
|
+
let current = states[initial];
|
|
113
|
+
while (current) if (current.initial && current.states) {
|
|
114
|
+
path.push(current.initial);
|
|
115
|
+
current = current.states[current.initial];
|
|
116
|
+
} else if (current.parallel) break;
|
|
117
|
+
else break;
|
|
118
|
+
return path;
|
|
119
|
+
}
|
|
120
|
+
function getStateNodeAtPath(path) {
|
|
121
|
+
if (path.length === 0) return void 0;
|
|
122
|
+
let current = definition.states[path[0]];
|
|
123
|
+
for (let i = 1; i < path.length && current; i++) current = current.states?.[path[i]];
|
|
124
|
+
return current;
|
|
125
|
+
}
|
|
126
|
+
function isPathFinal(path) {
|
|
127
|
+
return getStateNodeAtPath(path)?.final === true;
|
|
128
|
+
}
|
|
129
|
+
function executeActions(actions, event) {
|
|
130
|
+
if (!actions) return;
|
|
131
|
+
const actionList = Array.isArray(actions) ? actions : [actions];
|
|
132
|
+
for (const action of actionList) {
|
|
133
|
+
const result = action(context, event);
|
|
134
|
+
if (result) context = {
|
|
135
|
+
...context,
|
|
136
|
+
...result
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function enterStates(path) {
|
|
141
|
+
const activeState = {
|
|
142
|
+
path: [...path],
|
|
143
|
+
timers: [],
|
|
144
|
+
invokes: []
|
|
145
|
+
};
|
|
146
|
+
activeStates.push(activeState);
|
|
147
|
+
for (let i = 0; i < path.length; i++) {
|
|
148
|
+
const currentPath = path.slice(0, i + 1);
|
|
149
|
+
const node = getStateNodeAtPath(currentPath);
|
|
150
|
+
if (!node) continue;
|
|
151
|
+
executeActions(node.entry, { type: "__entry__" });
|
|
152
|
+
if (node.after) for (const [delayStr, transitionConfig] of Object.entries(node.after)) {
|
|
153
|
+
const delay = Number(delayStr);
|
|
154
|
+
const timer = setTimeout(() => {
|
|
155
|
+
if (stopped) return;
|
|
156
|
+
handleDelayedTransition(currentPath, transitionConfig);
|
|
157
|
+
}, delay);
|
|
158
|
+
activeState.timers.push(timer);
|
|
159
|
+
}
|
|
160
|
+
if (node.invoke) {
|
|
161
|
+
const controller = startInvoke(node.invoke, context, (data) => {
|
|
162
|
+
if (stopped) return;
|
|
163
|
+
if (node.onDone) {
|
|
164
|
+
const doneEvent = {
|
|
165
|
+
type: "__invoke_done__",
|
|
166
|
+
data
|
|
167
|
+
};
|
|
168
|
+
handleInvokeResult(currentPath, node.onDone, doneEvent);
|
|
169
|
+
}
|
|
170
|
+
}, (error) => {
|
|
171
|
+
if (stopped) return;
|
|
172
|
+
if (node.onError) {
|
|
173
|
+
const errorEvent = {
|
|
174
|
+
type: "__invoke_error__",
|
|
175
|
+
error
|
|
176
|
+
};
|
|
177
|
+
handleInvokeResult(currentPath, node.onError, errorEvent);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
activeState.invokes.push(controller);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function exitStates(path) {
|
|
185
|
+
const activeIndex = activeStates.findIndex((a) => a.path.join(".") === path.join("."));
|
|
186
|
+
if (activeIndex === -1) return;
|
|
187
|
+
const activeState = activeStates[activeIndex];
|
|
188
|
+
for (const timer of activeState.timers) clearTimeout(timer);
|
|
189
|
+
for (const invoke of activeState.invokes) invoke.cancel();
|
|
190
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
191
|
+
const node = getStateNodeAtPath(path.slice(0, i + 1));
|
|
192
|
+
if (node?.exit) executeActions(node.exit, { type: "__exit__" });
|
|
193
|
+
}
|
|
194
|
+
activeStates.splice(activeIndex, 1);
|
|
195
|
+
}
|
|
196
|
+
function handleDelayedTransition(fromPath, config) {
|
|
197
|
+
const syntheticEvent = { type: "__after__" };
|
|
198
|
+
const resolved = resolveTransition(config, context, syntheticEvent);
|
|
199
|
+
if (resolved) performTransition(fromPath, resolved.target, resolved.actions, syntheticEvent);
|
|
200
|
+
}
|
|
201
|
+
function handleInvokeResult(fromPath, config, event) {
|
|
202
|
+
const resolved = resolveTransition(config, context, event);
|
|
203
|
+
if (resolved) performTransition(fromPath, resolved.target, resolved.actions, event);
|
|
204
|
+
}
|
|
205
|
+
function performTransition(fromPath, target, actions, event) {
|
|
206
|
+
if (stopped) return;
|
|
207
|
+
const targetPath = resolveTargetPath(fromPath, target);
|
|
208
|
+
findCommonAncestorLength(fromPath, targetPath);
|
|
209
|
+
const exitPath = fromPath;
|
|
210
|
+
const enterPath = targetPath;
|
|
211
|
+
exitStates(exitPath);
|
|
212
|
+
for (const action of actions) {
|
|
213
|
+
const result = action(context, event);
|
|
214
|
+
if (result) context = {
|
|
215
|
+
...context,
|
|
216
|
+
...result
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
enterStates(enterPath);
|
|
220
|
+
currentSnapshot = createSnapshot(enterPath);
|
|
221
|
+
notifySubscribers();
|
|
222
|
+
}
|
|
223
|
+
function resolveTargetPath(fromPath, target) {
|
|
224
|
+
if (!target) return [...fromPath];
|
|
225
|
+
if (target.startsWith("#")) {
|
|
226
|
+
const parts = target.slice(1).split(".");
|
|
227
|
+
if (parts[0] === definition.id) return parts.slice(1);
|
|
228
|
+
return parts;
|
|
229
|
+
}
|
|
230
|
+
return buildFullPath([...fromPath.slice(0, -1), target]);
|
|
231
|
+
}
|
|
232
|
+
function buildFullPath(basePath) {
|
|
233
|
+
const path = [...basePath];
|
|
234
|
+
let node = getStateNodeAtPath(path);
|
|
235
|
+
while (node) if (node.initial && node.states) {
|
|
236
|
+
path.push(node.initial);
|
|
237
|
+
node = node.states[node.initial];
|
|
238
|
+
} else break;
|
|
239
|
+
return path;
|
|
240
|
+
}
|
|
241
|
+
function findCommonAncestorLength(path1, path2) {
|
|
242
|
+
let i = 0;
|
|
243
|
+
while (i < path1.length && i < path2.length && path1[i] === path2[i]) i++;
|
|
244
|
+
return i;
|
|
245
|
+
}
|
|
246
|
+
function findTransitionHandler(path, eventType) {
|
|
247
|
+
for (let i = path.length; i > 0; i--) {
|
|
248
|
+
const currentPath = path.slice(0, i);
|
|
249
|
+
const node = getStateNodeAtPath(currentPath);
|
|
250
|
+
if (node?.on?.[eventType]) return {
|
|
251
|
+
path: currentPath,
|
|
252
|
+
config: node.on[eventType]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
function notifySubscribers() {
|
|
258
|
+
for (const subscriber of subscribers) subscriber(currentSnapshot);
|
|
259
|
+
}
|
|
260
|
+
function send(event) {
|
|
261
|
+
if (stopped) return;
|
|
262
|
+
const normalizedEvent = typeof event === "string" ? { type: event } : event;
|
|
263
|
+
for (const listener of transitionListeners) listener(normalizedEvent);
|
|
264
|
+
const handler = findTransitionHandler(currentSnapshot.path, normalizedEvent.type);
|
|
265
|
+
if (!handler) return;
|
|
266
|
+
const resolved = resolveTransition(handler.config, context, normalizedEvent);
|
|
267
|
+
if (!resolved) return;
|
|
268
|
+
performTransition(handler.path, resolved.target, resolved.actions, normalizedEvent);
|
|
269
|
+
}
|
|
270
|
+
function subscribe(listener) {
|
|
271
|
+
subscribers.add(listener);
|
|
272
|
+
return () => {
|
|
273
|
+
subscribers.delete(listener);
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function onTransition(listener) {
|
|
277
|
+
transitionListeners.add(listener);
|
|
278
|
+
return () => {
|
|
279
|
+
transitionListeners.delete(listener);
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function stop() {
|
|
283
|
+
stopped = true;
|
|
284
|
+
for (const activeState of [...activeStates]) {
|
|
285
|
+
for (const timer of activeState.timers) clearTimeout(timer);
|
|
286
|
+
for (const invoke of activeState.invokes) invoke.cancel();
|
|
287
|
+
}
|
|
288
|
+
activeStates.length = 0;
|
|
289
|
+
notifySubscribers();
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
get state() {
|
|
293
|
+
return currentSnapshot;
|
|
294
|
+
},
|
|
295
|
+
send,
|
|
296
|
+
subscribe,
|
|
297
|
+
onTransition,
|
|
298
|
+
stop
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/export.ts
|
|
304
|
+
function getFunctionName(fn) {
|
|
305
|
+
return fn.name || "anonymous";
|
|
306
|
+
}
|
|
307
|
+
function exportActions(actions) {
|
|
308
|
+
if (!actions) return [];
|
|
309
|
+
return (Array.isArray(actions) ? actions : [actions]).map(getFunctionName);
|
|
310
|
+
}
|
|
311
|
+
function exportTransition(config) {
|
|
312
|
+
if (typeof config === "string") return {
|
|
313
|
+
target: config,
|
|
314
|
+
guard: null,
|
|
315
|
+
actions: []
|
|
316
|
+
};
|
|
317
|
+
if (Array.isArray(config)) return config.map((item) => ({
|
|
318
|
+
target: item.target,
|
|
319
|
+
guard: item.guard ? getFunctionName(item.guard) : null,
|
|
320
|
+
actions: item.action ? [getFunctionName(item.action)] : []
|
|
321
|
+
}));
|
|
322
|
+
return {
|
|
323
|
+
target: config.target,
|
|
324
|
+
guard: config.guard ? getFunctionName(config.guard) : null,
|
|
325
|
+
actions: config.action ? [getFunctionName(config.action)] : []
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function exportStateNode(node) {
|
|
329
|
+
const exported = {
|
|
330
|
+
entry: exportActions(node.entry),
|
|
331
|
+
exit: exportActions(node.exit),
|
|
332
|
+
on: {}
|
|
333
|
+
};
|
|
334
|
+
if (node.on) for (const [eventType, transition] of Object.entries(node.on)) exported.on[eventType] = exportTransition(transition);
|
|
335
|
+
if (node.after) {
|
|
336
|
+
exported.after = {};
|
|
337
|
+
for (const [delay, transition] of Object.entries(node.after)) exported.after[Number(delay)] = exportTransition(transition);
|
|
338
|
+
}
|
|
339
|
+
if (node.invoke) exported.invoke = getFunctionName(node.invoke);
|
|
340
|
+
if (node.onDone) exported.onDone = exportTransition(node.onDone);
|
|
341
|
+
if (node.onError) exported.onError = exportTransition(node.onError);
|
|
342
|
+
if (node.initial) exported.initial = node.initial;
|
|
343
|
+
if (node.states) {
|
|
344
|
+
exported.states = {};
|
|
345
|
+
for (const [name, childNode] of Object.entries(node.states)) exported.states[name] = exportStateNode(childNode);
|
|
346
|
+
}
|
|
347
|
+
if (node.parallel) {
|
|
348
|
+
exported.parallel = {};
|
|
349
|
+
for (const [regionName, region] of Object.entries(node.parallel)) {
|
|
350
|
+
exported.parallel[regionName] = {
|
|
351
|
+
initial: region.initial,
|
|
352
|
+
states: {}
|
|
353
|
+
};
|
|
354
|
+
for (const [stateName, stateNode] of Object.entries(region.states)) exported.parallel[regionName].states[stateName] = exportStateNode(stateNode);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (node.final) exported.final = true;
|
|
358
|
+
return exported;
|
|
359
|
+
}
|
|
360
|
+
function exportChart(definition) {
|
|
361
|
+
const states = {};
|
|
362
|
+
for (const [name, node] of Object.entries(definition.states)) states[name] = exportStateNode(node);
|
|
363
|
+
return {
|
|
364
|
+
version: 1,
|
|
365
|
+
id: definition.id ?? "chart",
|
|
366
|
+
initial: definition.initial,
|
|
367
|
+
context: definition.context,
|
|
368
|
+
states
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/chart.ts
|
|
374
|
+
function chart(definition) {
|
|
375
|
+
return {
|
|
376
|
+
get definition() {
|
|
377
|
+
return definition;
|
|
378
|
+
},
|
|
379
|
+
start(initialContext) {
|
|
380
|
+
return createInstance(definition, initialContext);
|
|
381
|
+
},
|
|
382
|
+
export() {
|
|
383
|
+
return exportChart(definition);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
//#endregion
|
|
389
|
+
export { StateSnapshot, chart };
|
|
390
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/snapshot.ts","../src/transition.ts","../src/invoke.ts","../src/instance.ts","../src/export.ts","../src/chart.ts"],"sourcesContent":["import type { AnyContext, IStateSnapshot, StateValue } from './types.js';\n\nexport class StateSnapshot<TContext extends AnyContext> implements IStateSnapshot<TContext> {\n readonly value: StateValue;\n readonly context: Readonly<TContext>;\n readonly done: boolean;\n readonly path: readonly string[];\n readonly timestamp: number;\n\n constructor(\n value: StateValue,\n context: TContext,\n done: boolean,\n path: readonly string[],\n timestamp?: number\n ) {\n this.value = value;\n this.context = Object.freeze({ ...context }) as Readonly<TContext>;\n this.done = done;\n this.path = Object.freeze([...path]) as readonly string[];\n this.timestamp = timestamp ?? Date.now();\n }\n\n matches(stateValue: string): boolean {\n const parts = stateValue.split('.');\n\n // For flat states\n if (typeof this.value === 'string') {\n return parts.length === 1 && this.value === parts[0];\n }\n\n // For nested/parallel states - check if path includes all parts\n return this.matchesPath(parts, 0);\n }\n\n private matchesPath(parts: string[], index: number): boolean {\n if (index >= parts.length) return true;\n\n const part = parts[index];\n if (part === undefined) return true;\n\n // Check if this part exists in our path\n return this.path.includes(part) && this.matchesPath(parts, index + 1);\n }\n}\n","import type {\n AnyContext,\n BaseEvent,\n TransitionConfig,\n TransitionObject,\n} from './types.js';\n\nexport interface ResolvedTransition<TContext extends AnyContext, TEvent extends BaseEvent> {\n target: string | undefined;\n actions: ((context: TContext, event: TEvent) => Partial<TContext> | void)[];\n}\n\nexport function resolveTransition<TContext extends AnyContext, TEvent extends BaseEvent>(\n config: TransitionConfig<TContext, TEvent>,\n context: TContext,\n event: TEvent\n): ResolvedTransition<TContext, TEvent> | null {\n // String shorthand\n if (typeof config === 'string') {\n return { target: config, actions: [] };\n }\n\n // Array - first match wins\n if (Array.isArray(config)) {\n for (const item of config) {\n const result = resolveTransitionObject(item, context, event);\n if (result !== null) {\n return result;\n }\n }\n return null;\n }\n\n // Object form\n return resolveTransitionObject(config, context, event);\n}\n\nfunction resolveTransitionObject<TContext extends AnyContext, TEvent extends BaseEvent>(\n config: TransitionObject<TContext, TEvent>,\n context: TContext,\n event: TEvent\n): ResolvedTransition<TContext, TEvent> | null {\n // Check guard\n if (config.guard && !config.guard(context, event)) {\n return null;\n }\n\n return {\n target: config.target,\n actions: config.action ? [config.action] : [],\n };\n}\n","import type { AnyContext, InvokeFn, Subscription, SubscriptionSource } from './types.js';\n\nexport interface InvokeController {\n cancel(): void;\n}\n\nfunction isSubscriptionSource(value: unknown): value is SubscriptionSource<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'subscribe' in value &&\n typeof (value as SubscriptionSource<unknown>).subscribe === 'function'\n );\n}\n\nexport function startInvoke<TContext extends AnyContext>(\n invokeFn: InvokeFn<TContext>,\n context: TContext,\n onDone: (data: unknown) => void,\n onError: (error: unknown) => void\n): InvokeController {\n let cancelled = false;\n let subscription: Subscription | null = null;\n\n try {\n const result = invokeFn(context);\n\n if (isSubscriptionSource(result)) {\n // Subscription-based invoke\n subscription = result.subscribe({\n next: (value) => {\n if (!cancelled) {\n onDone(value);\n }\n },\n error: (err) => {\n if (!cancelled) {\n onError(err);\n }\n },\n });\n } else {\n // Promise-based invoke\n result\n .then((data) => {\n if (!cancelled) {\n onDone(data);\n }\n })\n .catch((error: unknown) => {\n if (!cancelled) {\n onError(error);\n }\n });\n }\n } catch (error) {\n if (!cancelled) {\n onError(error);\n }\n }\n\n return {\n cancel() {\n cancelled = true;\n if (subscription) {\n subscription.unsubscribe();\n }\n },\n };\n}\n","import type {\n Actions,\n AnyContext,\n BaseEvent,\n ChartDefinition,\n ChartInstance,\n IStateSnapshot,\n StateNode,\n StateValue,\n TransitionConfig,\n} from './types.js';\nimport { StateSnapshot } from './snapshot.js';\nimport { resolveTransition } from './transition.js';\nimport { startInvoke, type InvokeController } from './invoke.js';\n\ninterface ActiveState {\n path: string[];\n timers: ReturnType<typeof setTimeout>[];\n invokes: InvokeController[];\n}\n\nexport function createInstance<TContext extends AnyContext, TEvent extends BaseEvent>(\n definition: ChartDefinition<TContext, TEvent>,\n initialContextOverride?: Partial<TContext>\n): ChartInstance<TContext, TEvent> {\n let context: TContext = initialContextOverride\n ? { ...definition.context, ...initialContextOverride }\n : { ...definition.context };\n\n let stopped = false;\n const subscribers: Set<(state: IStateSnapshot<TContext>) => void> = new Set();\n const transitionListeners: Set<(event: TEvent) => void> = new Set();\n const activeStates: ActiveState[] = [];\n\n // Build initial state\n const initialPath = buildInitialPath(definition.states, definition.initial);\n let currentSnapshot = createSnapshot(initialPath);\n\n // Enter initial states\n enterStates(initialPath);\n\n function createSnapshot(path: string[]): StateSnapshot<TContext> {\n const value = pathToValue(path);\n const done = isPathFinal(path);\n return new StateSnapshot(value, context, done, path);\n }\n\n function pathToValue(path: string[]): StateValue {\n if (path.length === 0) return '';\n if (path.length === 1) return path[0]!;\n\n // Build nested object\n let result: StateValue = path[path.length - 1]!;\n for (let i = path.length - 2; i >= 0; i--) {\n result = { [path[i]!]: result };\n }\n return result;\n }\n\n function buildInitialPath(\n states: Record<string, StateNode<TContext, TEvent>>,\n initial: string\n ): string[] {\n const path: string[] = [initial];\n let current = states[initial];\n\n while (current) {\n if (current.initial && current.states) {\n path.push(current.initial);\n current = current.states[current.initial];\n } else if (current.parallel) {\n // For parallel states, we handle differently\n break;\n } else {\n break;\n }\n }\n\n return path;\n }\n\n function getStateNodeAtPath(path: string[]): StateNode<TContext, TEvent> | undefined {\n if (path.length === 0) return undefined;\n\n let current: StateNode<TContext, TEvent> | undefined = definition.states[path[0]!];\n for (let i = 1; i < path.length && current; i++) {\n current = current.states?.[path[i]!];\n }\n return current;\n }\n\n function isPathFinal(path: string[]): boolean {\n const node = getStateNodeAtPath(path);\n return node?.final === true;\n }\n\n function executeActions(\n actions: Actions<TContext, TEvent> | undefined,\n event: TEvent\n ): void {\n if (!actions) return;\n\n const actionList = Array.isArray(actions) ? actions : [actions];\n for (const action of actionList) {\n const result = action(context, event);\n if (result) {\n context = { ...context, ...result };\n }\n }\n }\n\n function enterStates(path: string[]): void {\n const activeState: ActiveState = {\n path: [...path],\n timers: [],\n invokes: [],\n };\n activeStates.push(activeState);\n\n // Enter each state in path\n for (let i = 0; i < path.length; i++) {\n const currentPath = path.slice(0, i + 1);\n const node = getStateNodeAtPath(currentPath);\n if (!node) continue;\n\n // Execute entry actions\n executeActions(node.entry, { type: '__entry__' } as TEvent);\n\n // Setup delayed transitions\n if (node.after) {\n for (const [delayStr, transitionConfig] of Object.entries(node.after)) {\n const delay = Number(delayStr);\n const timer = setTimeout(() => {\n if (stopped) return;\n handleDelayedTransition(currentPath, transitionConfig as TransitionConfig<TContext, TEvent>);\n }, delay);\n activeState.timers.push(timer);\n }\n }\n\n // Start invocations\n if (node.invoke) {\n const controller = startInvoke(\n node.invoke,\n context,\n (data) => {\n if (stopped) return;\n if (node.onDone) {\n const doneEvent = { type: '__invoke_done__', data } as TEvent & { data: unknown };\n handleInvokeResult(currentPath, node.onDone as TransitionConfig<TContext, TEvent>, doneEvent);\n }\n },\n (error) => {\n if (stopped) return;\n if (node.onError) {\n const errorEvent = { type: '__invoke_error__', error } as TEvent & { error: unknown };\n handleInvokeResult(currentPath, node.onError as TransitionConfig<TContext, TEvent>, errorEvent);\n }\n }\n );\n activeState.invokes.push(controller);\n }\n }\n }\n\n function exitStates(path: string[]): void {\n const activeIndex = activeStates.findIndex(\n (a) => a.path.join('.') === path.join('.')\n );\n if (activeIndex === -1) return;\n\n const activeState = activeStates[activeIndex]!;\n\n // Clear timers\n for (const timer of activeState.timers) {\n clearTimeout(timer);\n }\n\n // Cancel invokes\n for (const invoke of activeState.invokes) {\n invoke.cancel();\n }\n\n // Execute exit actions (in reverse order)\n for (let i = path.length - 1; i >= 0; i--) {\n const currentPath = path.slice(0, i + 1);\n const node = getStateNodeAtPath(currentPath);\n if (node?.exit) {\n executeActions(node.exit, { type: '__exit__' } as TEvent);\n }\n }\n\n activeStates.splice(activeIndex, 1);\n }\n\n function handleDelayedTransition(\n fromPath: string[],\n config: TransitionConfig<TContext, TEvent>\n ): void {\n const syntheticEvent = { type: '__after__' } as TEvent;\n const resolved = resolveTransition(config, context, syntheticEvent);\n\n if (resolved) {\n performTransition(fromPath, resolved.target, resolved.actions, syntheticEvent);\n }\n }\n\n function handleInvokeResult(\n fromPath: string[],\n config: TransitionConfig<TContext, TEvent>,\n event: TEvent\n ): void {\n const resolved = resolveTransition(config, context, event);\n\n if (resolved) {\n performTransition(fromPath, resolved.target, resolved.actions, event);\n }\n }\n\n function performTransition(\n fromPath: string[],\n target: string | undefined,\n actions: ((ctx: TContext, ev: TEvent) => Partial<TContext> | void)[],\n event: TEvent\n ): void {\n if (stopped) return;\n\n // Calculate the paths to exit and enter\n const targetPath = resolveTargetPath(fromPath, target);\n\n // Find common ancestor\n const commonLength = findCommonAncestorLength(fromPath, targetPath);\n const exitPath = fromPath;\n const enterPath = targetPath;\n\n // Exit current states (from leaf to common ancestor)\n exitStates(exitPath);\n\n // Execute transition actions\n for (const action of actions) {\n const result = action(context, event);\n if (result) {\n context = { ...context, ...result };\n }\n }\n\n // Enter new states\n enterStates(enterPath);\n\n // Update snapshot and notify\n currentSnapshot = createSnapshot(enterPath);\n notifySubscribers();\n }\n\n function resolveTargetPath(fromPath: string[], target: string | undefined): string[] {\n if (!target) {\n // Self-transition - re-enter current state\n return [...fromPath];\n }\n\n // Handle absolute reference (#id.state)\n if (target.startsWith('#')) {\n const parts = target.slice(1).split('.');\n // First part should be chart id, rest is path\n if (parts[0] === definition.id) {\n return parts.slice(1);\n }\n // Just use the path after #\n return parts;\n }\n\n // Handle relative target - find sibling state\n // The target should be a sibling of the deepest state that has that transition\n const parentPath = fromPath.slice(0, -1);\n const targetPath = [...parentPath, target];\n\n // Build full path including nested initial states\n return buildFullPath(targetPath);\n }\n\n function buildFullPath(basePath: string[]): string[] {\n const path = [...basePath];\n let node = getStateNodeAtPath(path);\n\n while (node) {\n if (node.initial && node.states) {\n path.push(node.initial);\n node = node.states[node.initial];\n } else {\n break;\n }\n }\n\n return path;\n }\n\n function findCommonAncestorLength(path1: string[], path2: string[]): number {\n let i = 0;\n while (i < path1.length && i < path2.length && path1[i] === path2[i]) {\n i++;\n }\n return i;\n }\n\n function findTransitionHandler(\n path: string[],\n eventType: string\n ): { path: string[]; config: TransitionConfig<TContext, TEvent> } | null {\n // Search from leaf to root (event bubbling)\n for (let i = path.length; i > 0; i--) {\n const currentPath = path.slice(0, i);\n const node = getStateNodeAtPath(currentPath);\n\n if (node?.on?.[eventType]) {\n return { path: currentPath, config: node.on[eventType] as TransitionConfig<TContext, TEvent> };\n }\n }\n\n return null;\n }\n\n function notifySubscribers(): void {\n for (const subscriber of subscribers) {\n subscriber(currentSnapshot);\n }\n }\n\n function send(event: TEvent | TEvent['type']): void {\n if (stopped) return;\n\n const normalizedEvent: TEvent =\n typeof event === 'string' ? ({ type: event } as TEvent) : event;\n\n // Notify transition listeners\n for (const listener of transitionListeners) {\n listener(normalizedEvent);\n }\n\n // Find handler\n const handler = findTransitionHandler(currentSnapshot.path as string[], normalizedEvent.type);\n if (!handler) return;\n\n const resolved = resolveTransition(handler.config, context, normalizedEvent);\n if (!resolved) return;\n\n performTransition(\n handler.path,\n resolved.target,\n resolved.actions,\n normalizedEvent\n );\n }\n\n function subscribe(\n listener: (state: IStateSnapshot<TContext>) => void\n ): () => void {\n subscribers.add(listener);\n return () => {\n subscribers.delete(listener);\n };\n }\n\n function onTransition(listener: (event: TEvent) => void): () => void {\n transitionListeners.add(listener);\n return () => {\n transitionListeners.delete(listener);\n };\n }\n\n function stop(): void {\n stopped = true;\n\n // Clean up all active states\n for (const activeState of [...activeStates]) {\n for (const timer of activeState.timers) {\n clearTimeout(timer);\n }\n for (const invoke of activeState.invokes) {\n invoke.cancel();\n }\n }\n activeStates.length = 0;\n\n // Notify subscribers one final time\n notifySubscribers();\n }\n\n return {\n get state() {\n return currentSnapshot;\n },\n send,\n subscribe,\n onTransition,\n stop,\n };\n}\n","import type {\n AnyContext,\n BaseEvent,\n ChartDefinition,\n ExportedChart,\n ExportedStateNode,\n ExportedTransition,\n StateNode,\n TransitionConfig,\n Actions,\n} from './types.js';\n\nfunction getFunctionName(fn: Function): string {\n return fn.name || 'anonymous';\n}\n\nfunction exportActions<TContext extends AnyContext, TEvent extends BaseEvent>(\n actions: Actions<TContext, TEvent> | undefined\n): string[] {\n if (!actions) return [];\n const list = Array.isArray(actions) ? actions : [actions];\n return list.map(getFunctionName);\n}\n\nfunction exportTransition<TContext extends AnyContext, TEvent extends BaseEvent>(\n config: TransitionConfig<TContext, TEvent>\n): ExportedTransition | ExportedTransition[] {\n if (typeof config === 'string') {\n return { target: config, guard: null, actions: [] };\n }\n\n if (Array.isArray(config)) {\n return config.map((item) => ({\n target: item.target,\n guard: item.guard ? getFunctionName(item.guard) : null,\n actions: item.action ? [getFunctionName(item.action)] : [],\n }));\n }\n\n return {\n target: config.target,\n guard: config.guard ? getFunctionName(config.guard) : null,\n actions: config.action ? [getFunctionName(config.action)] : [],\n };\n}\n\nfunction exportStateNode<TContext extends AnyContext, TEvent extends BaseEvent>(\n node: StateNode<TContext, TEvent>\n): ExportedStateNode {\n const exported: ExportedStateNode = {\n entry: exportActions(node.entry),\n exit: exportActions(node.exit),\n on: {},\n };\n\n if (node.on) {\n for (const [eventType, transition] of Object.entries(node.on)) {\n exported.on[eventType] = exportTransition(transition as TransitionConfig<TContext, TEvent>);\n }\n }\n\n if (node.after) {\n exported.after = {};\n for (const [delay, transition] of Object.entries(node.after)) {\n exported.after[Number(delay)] = exportTransition(transition as TransitionConfig<TContext, TEvent>);\n }\n }\n\n if (node.invoke) {\n exported.invoke = getFunctionName(node.invoke);\n }\n\n if (node.onDone) {\n exported.onDone = exportTransition(node.onDone as TransitionConfig<TContext, TEvent>);\n }\n\n if (node.onError) {\n exported.onError = exportTransition(node.onError as TransitionConfig<TContext, TEvent>);\n }\n\n if (node.initial) {\n exported.initial = node.initial;\n }\n\n if (node.states) {\n exported.states = {};\n for (const [name, childNode] of Object.entries(node.states)) {\n exported.states[name] = exportStateNode(childNode);\n }\n }\n\n if (node.parallel) {\n exported.parallel = {};\n for (const [regionName, region] of Object.entries(node.parallel)) {\n exported.parallel[regionName] = {\n initial: region.initial,\n states: {},\n };\n for (const [stateName, stateNode] of Object.entries(region.states)) {\n exported.parallel[regionName]!.states[stateName] = exportStateNode(stateNode);\n }\n }\n }\n\n if (node.final) {\n exported.final = true;\n }\n\n return exported;\n}\n\nexport function exportChart<TContext extends AnyContext, TEvent extends BaseEvent>(\n definition: ChartDefinition<TContext, TEvent>\n): ExportedChart {\n const states: Record<string, ExportedStateNode> = {};\n\n for (const [name, node] of Object.entries(definition.states)) {\n states[name] = exportStateNode(node);\n }\n\n return {\n version: 1,\n id: definition.id ?? 'chart',\n initial: definition.initial,\n context: definition.context,\n states,\n };\n}\n","import type {\n AnyContext,\n BaseEvent,\n Chart,\n ChartDefinition,\n ChartInstance,\n ExportedChart,\n} from './types.js';\nimport { createInstance } from './instance.js';\nimport { exportChart } from './export.js';\n\nexport function chart<TContext extends AnyContext, TEvent extends BaseEvent = BaseEvent>(\n definition: ChartDefinition<TContext, TEvent>\n): Chart<TContext, TEvent> {\n return {\n get definition(): ChartDefinition<TContext, TEvent> {\n return definition;\n },\n\n start(initialContext?: Partial<TContext>): ChartInstance<TContext, TEvent> {\n return createInstance(definition, initialContext);\n },\n\n export(): ExportedChart {\n return exportChart(definition);\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "statecharts.sh",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./schema.json": "./schema.json"
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": ["dist", "schema.json"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsdown",
|
|
18
|
+
"check-types": "tsc --noEmit",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@statecharts/typescript-config": "*",
|
|
24
|
+
"tsdown": "^0.20.1",
|
|
25
|
+
"typescript": "5.9.2",
|
|
26
|
+
"vitest": "^3.2.4"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/schema.json
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://statecharts.sh/schema.json",
|
|
4
|
+
"title": "ExportedChart",
|
|
5
|
+
"description": "JSON Schema for statechart export format",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["version", "id", "initial", "context", "states"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"version": {
|
|
10
|
+
"const": 1,
|
|
11
|
+
"description": "Schema version for forward compatibility"
|
|
12
|
+
},
|
|
13
|
+
"id": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Chart identifier"
|
|
16
|
+
},
|
|
17
|
+
"initial": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Initial state name"
|
|
20
|
+
},
|
|
21
|
+
"context": {
|
|
22
|
+
"description": "Initial context value"
|
|
23
|
+
},
|
|
24
|
+
"states": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"additionalProperties": {
|
|
27
|
+
"$ref": "#/$defs/ExportedStateNode"
|
|
28
|
+
},
|
|
29
|
+
"description": "State tree structure"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"$defs": {
|
|
33
|
+
"ExportedTransition": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"required": ["guard", "actions"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"target": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "Target state name"
|
|
40
|
+
},
|
|
41
|
+
"guard": {
|
|
42
|
+
"type": ["string", "null"],
|
|
43
|
+
"description": "Guard function name or null"
|
|
44
|
+
},
|
|
45
|
+
"actions": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": { "type": "string" },
|
|
48
|
+
"description": "Action function names"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"TransitionOrArray": {
|
|
53
|
+
"oneOf": [
|
|
54
|
+
{ "$ref": "#/$defs/ExportedTransition" },
|
|
55
|
+
{
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": { "$ref": "#/$defs/ExportedTransition" }
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"ParallelRegion": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"required": ["initial", "states"],
|
|
64
|
+
"properties": {
|
|
65
|
+
"initial": {
|
|
66
|
+
"type": "string"
|
|
67
|
+
},
|
|
68
|
+
"states": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"additionalProperties": {
|
|
71
|
+
"$ref": "#/$defs/ExportedStateNode"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"ExportedStateNode": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"required": ["entry", "exit", "on"],
|
|
79
|
+
"properties": {
|
|
80
|
+
"entry": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"items": { "type": "string" },
|
|
83
|
+
"description": "Entry action names"
|
|
84
|
+
},
|
|
85
|
+
"exit": {
|
|
86
|
+
"type": "array",
|
|
87
|
+
"items": { "type": "string" },
|
|
88
|
+
"description": "Exit action names"
|
|
89
|
+
},
|
|
90
|
+
"on": {
|
|
91
|
+
"type": "object",
|
|
92
|
+
"additionalProperties": {
|
|
93
|
+
"$ref": "#/$defs/TransitionOrArray"
|
|
94
|
+
},
|
|
95
|
+
"description": "Event transitions"
|
|
96
|
+
},
|
|
97
|
+
"after": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"additionalProperties": {
|
|
100
|
+
"$ref": "#/$defs/TransitionOrArray"
|
|
101
|
+
},
|
|
102
|
+
"description": "Delayed transitions keyed by milliseconds"
|
|
103
|
+
},
|
|
104
|
+
"invoke": {
|
|
105
|
+
"type": ["string", "null"],
|
|
106
|
+
"description": "Invoked service function name"
|
|
107
|
+
},
|
|
108
|
+
"onDone": {
|
|
109
|
+
"$ref": "#/$defs/TransitionOrArray",
|
|
110
|
+
"description": "Transition on invoke completion"
|
|
111
|
+
},
|
|
112
|
+
"onError": {
|
|
113
|
+
"$ref": "#/$defs/TransitionOrArray",
|
|
114
|
+
"description": "Transition on invoke error"
|
|
115
|
+
},
|
|
116
|
+
"initial": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"description": "Initial child state for compound states"
|
|
119
|
+
},
|
|
120
|
+
"states": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"additionalProperties": {
|
|
123
|
+
"$ref": "#/$defs/ExportedStateNode"
|
|
124
|
+
},
|
|
125
|
+
"description": "Nested state nodes"
|
|
126
|
+
},
|
|
127
|
+
"parallel": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"additionalProperties": {
|
|
130
|
+
"$ref": "#/$defs/ParallelRegion"
|
|
131
|
+
},
|
|
132
|
+
"description": "Parallel state regions"
|
|
133
|
+
},
|
|
134
|
+
"final": {
|
|
135
|
+
"type": "boolean",
|
|
136
|
+
"description": "Whether this is a final state"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|