@menapo/machine 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +135 -2
- package/dist/chart-BWjU2VF4.js +395 -0
- package/dist/compiled.d.ts +58 -0
- package/dist/compiled.js +43 -0
- package/dist/component-DDXOyYUI.d.ts +147 -0
- package/dist/dev-BAz6e3W-.js +79 -0
- package/dist/dom.d.ts +187 -0
- package/dist/dom.js +659 -0
- package/dist/glue/preact.d.ts +4 -0
- package/dist/glue/preact.js +54 -0
- package/dist/glue/react.d.ts +4 -0
- package/dist/glue/react.js +50 -0
- package/dist/glue/solid.d.ts +4 -0
- package/dist/glue/solid.js +42 -0
- package/dist/glue/svelte.svelte.d.ts +4 -0
- package/dist/glue/svelte.svelte.js +57 -0
- package/dist/glue/vanilla.d.ts +4 -0
- package/dist/glue/vanilla.js +83 -0
- package/dist/glue/vue.d.ts +4 -0
- package/dist/glue/vue.js +41 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +582 -0
- package/dist/inspect-BmyLUhcy.js +74 -0
- package/dist/inspect-C3qosuCm.d.ts +22 -0
- package/dist/instance-BM2UqeXW.js +418 -0
- package/dist/machine-BstWHtWL.d.ts +335 -0
- package/dist/naming-Csj5186j.js +492 -0
- package/dist/records-7FFepzo9.js +10 -0
- package/dist/source-DdFTPjID.js +15 -0
- package/dist/test.d.ts +63 -0
- package/dist/test.js +262 -0
- package/dist/types-Bk8svtO6.d.ts +580 -0
- package/dist/vicss.d.ts +19 -0
- package/dist/vicss.js +51 -0
- package/dist/vite.d.ts +27 -0
- package/dist/vite.js +4281 -0
- package/package.json +87 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { i as suggest, n as checkParts, r as fail, t as checkKeys } from "./dev-BAz6e3W-.js";
|
|
2
|
+
import { a as start, d as CHECK, f as DEV, l as track, o as derivedClass, p as dev, r as run, s as storeClass, u as bit } from "./instance-BM2UqeXW.js";
|
|
3
|
+
import { a as release, c as own, i as activate, n as wireParallel, o as BOUND, r as wireHistory, s as bind, t as DONE } from "./chart-BWjU2VF4.js";
|
|
4
|
+
import { t as inspect } from "./inspect-BmyLUhcy.js";
|
|
5
|
+
//#region src/core/bindable.ts
|
|
6
|
+
/**
|
|
7
|
+
* `bindable`: a prop the owner may control or leave to the machine (D9, as
|
|
8
|
+
* spike S2 refined it). One transition serves both modes:
|
|
9
|
+
*
|
|
10
|
+
* - uncontrolled, the machine moves or stores the value and tells the owner
|
|
11
|
+
* through `on…Change`;
|
|
12
|
+
* - controlled, a transition that would change the value runs its actions
|
|
13
|
+
* when the event is handled and asks the owner; only the move waits. When
|
|
14
|
+
* the owner's new value arrives, the machine takes the waiting transition's
|
|
15
|
+
* target, and the target's entry sees the event that asked. Props that do
|
|
16
|
+
* not grant it drop it (a veto); an owner that changes the value unasked
|
|
17
|
+
* moves the machine to the matching state.
|
|
18
|
+
*
|
|
19
|
+
* All of it lives here and reaches the core only through the
|
|
20
|
+
* {@link Control} a `bindable()` value carries, so an app that never imports
|
|
21
|
+
* `bindable` ships none of it.
|
|
22
|
+
*/
|
|
23
|
+
/** What entry actions see when the owner moves a bound boolean unasked. */
|
|
24
|
+
const PROPS = { type: "machine.props" };
|
|
25
|
+
/**
|
|
26
|
+
* A controlled-or-uncontrolled prop: `open: bindable(false, { states: {
|
|
27
|
+
* true: "open", false: "closed" } })` gives the owner `open`, `defaultOpen`
|
|
28
|
+
* and `onOpenChange`; `value: bindable("")` is data that actions write as
|
|
29
|
+
* `props.value = next`.
|
|
30
|
+
*/
|
|
31
|
+
const bindable = (value, options) => ({
|
|
32
|
+
[BOUND]: control,
|
|
33
|
+
value,
|
|
34
|
+
...options
|
|
35
|
+
});
|
|
36
|
+
const capital = (name) => name[0].toUpperCase() + name.slice(1);
|
|
37
|
+
/** True when leaf `at` is inside `state`. */
|
|
38
|
+
function inside(at, state) {
|
|
39
|
+
for (; at; at = at.up) if (at === state) return true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const bounds = (machine) => machine.bd;
|
|
43
|
+
const control = {
|
|
44
|
+
bind(machine, prop, name, bindable, proto) {
|
|
45
|
+
const { states, changed } = bindable;
|
|
46
|
+
const find = (path) => {
|
|
47
|
+
const state = machine.ss.find((s) => s.path === path);
|
|
48
|
+
if (CHECK && !state) fail("UNKNOWN_TARGET", machine.name, `bindable "${name}" names state "${path}", which does not exist; use a path from the root: ${machine.ss.map((s) => `"${s.path}"`).join(", ")}.`);
|
|
49
|
+
return state;
|
|
50
|
+
};
|
|
51
|
+
if (CHECK && changed !== void 0 && !machine.handled.has(changed)) {
|
|
52
|
+
const handled = [...machine.handled];
|
|
53
|
+
fail("UNKNOWN_EVENT", machine.name, `bindable "${name}" sends "${changed}" when the owner changes it, but no state handles it${suggest(changed, handled) || "."} Handle it in a state's \`on\`, or name an event one does.`);
|
|
54
|
+
}
|
|
55
|
+
const b = {
|
|
56
|
+
prop,
|
|
57
|
+
name,
|
|
58
|
+
bit: bit(1 + machine.nm.length - machine.pn.length + prop),
|
|
59
|
+
default: `default${capital(name)}`,
|
|
60
|
+
change: `on${capital(name)}Change`,
|
|
61
|
+
yes: states && find(states.true),
|
|
62
|
+
no: states && find(states.false),
|
|
63
|
+
changed
|
|
64
|
+
};
|
|
65
|
+
bounds(machine).push(b);
|
|
66
|
+
Object.defineProperty(proto, name, {
|
|
67
|
+
get() {
|
|
68
|
+
const instance = this.i;
|
|
69
|
+
track(b.bit);
|
|
70
|
+
const given = instance.o[name];
|
|
71
|
+
if (given !== void 0) return given;
|
|
72
|
+
if (!b.yes) return this.v[prop];
|
|
73
|
+
track(1);
|
|
74
|
+
return inside(instance.s, b.yes);
|
|
75
|
+
},
|
|
76
|
+
set(value) {
|
|
77
|
+
const owner = this.i.o;
|
|
78
|
+
if (DEV) {
|
|
79
|
+
if (b.yes) fail("BOUND_WRITE", machine.name, `props.${name} follows the state; move to "${b.yes.path}" or "${b.no.path}" instead of writing it.`);
|
|
80
|
+
if (!dev.acting) fail("CONTEXT_WRITE", machine.name, `props.${name} was written outside an action. Write it in a transition's \`do\`, \`entry\` or \`exit\`, and send an event from here instead.`);
|
|
81
|
+
}
|
|
82
|
+
const controlled = owner[name] !== void 0;
|
|
83
|
+
if ((controlled ? owner[name] : this.v[prop]) === value) return;
|
|
84
|
+
if (!controlled) {
|
|
85
|
+
this.v[prop] = value;
|
|
86
|
+
this.m |= b.bit;
|
|
87
|
+
}
|
|
88
|
+
owner[b.change]?.(value);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
initial(instance, leaf) {
|
|
93
|
+
const owner = instance.o;
|
|
94
|
+
for (const b of bounds(instance.M)) {
|
|
95
|
+
const given = owner[b.name] !== void 0 ? owner[b.name] : owner[b.default];
|
|
96
|
+
if (given === void 0) continue;
|
|
97
|
+
if (!b.yes) instance.props.v[b.prop] = given;
|
|
98
|
+
else if (!!given !== inside(leaf, b.yes)) leaf = (given ? b.yes : b.no).lf;
|
|
99
|
+
}
|
|
100
|
+
return leaf;
|
|
101
|
+
},
|
|
102
|
+
hold(instance, rule) {
|
|
103
|
+
for (const b of bounds(instance.M)) {
|
|
104
|
+
if (!b.yes || instance.o[b.name] === void 0) continue;
|
|
105
|
+
const next = inside(rule.to.lf, b.yes);
|
|
106
|
+
if (inside(instance.s, b.yes) === next) continue;
|
|
107
|
+
run(rule.do, instance);
|
|
108
|
+
instance.p = {
|
|
109
|
+
to: rule.to,
|
|
110
|
+
event: instance.event,
|
|
111
|
+
b
|
|
112
|
+
};
|
|
113
|
+
instance.o[b.change]?.(next);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
},
|
|
118
|
+
moved(instance, from) {
|
|
119
|
+
for (const b of bounds(instance.M)) {
|
|
120
|
+
if (!b.yes || instance.o[b.name] !== void 0) continue;
|
|
121
|
+
const now = inside(instance.s, b.yes);
|
|
122
|
+
if (now !== inside(from, b.yes)) {
|
|
123
|
+
instance.props.m |= b.bit;
|
|
124
|
+
instance.o[b.change]?.(now);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
own(instance, previous) {
|
|
129
|
+
for (const b of bounds(instance.M)) {
|
|
130
|
+
const value = instance.o[b.name];
|
|
131
|
+
if (value !== previous[b.name]) {
|
|
132
|
+
instance.props.m |= b.bit;
|
|
133
|
+
if (b.changed && value !== void 0) instance.dispatch({
|
|
134
|
+
type: b.changed,
|
|
135
|
+
value
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (!b.yes) continue;
|
|
139
|
+
const pending = instance.p?.b === b ? instance.p : void 0;
|
|
140
|
+
if (pending) instance.p = void 0;
|
|
141
|
+
if (value === void 0 || !!value === inside(instance.s, b.yes)) continue;
|
|
142
|
+
if (pending && inside(pending.to.lf, b.yes) === !!value) {
|
|
143
|
+
instance.event = pending.event;
|
|
144
|
+
instance.move(pending.to, void 0);
|
|
145
|
+
} else {
|
|
146
|
+
instance.event = PROPS;
|
|
147
|
+
instance.move(value ? b.yes : b.no, void 0);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/core/machine.ts
|
|
154
|
+
/**
|
|
155
|
+
* `defineMachine`: the config normalised once into tables (PLAN §5.2).
|
|
156
|
+
* Each state becomes a record linked to its parent, numbered depth first
|
|
157
|
+
* (so an ancestor's index is below its descendants'), with the leaf it
|
|
158
|
+
* settles in when entered, its entry/exit/effect lists, `after` timers,
|
|
159
|
+
* `always` rules and one `Map` from event name to what it does, its
|
|
160
|
+
* ancestors' rules folded in after its own (D20).
|
|
161
|
+
*/
|
|
162
|
+
const STATE_KEYS = [
|
|
163
|
+
"initial",
|
|
164
|
+
"states",
|
|
165
|
+
"on",
|
|
166
|
+
"entry",
|
|
167
|
+
"exit",
|
|
168
|
+
"effect",
|
|
169
|
+
"after",
|
|
170
|
+
"always",
|
|
171
|
+
"type",
|
|
172
|
+
"done"
|
|
173
|
+
];
|
|
174
|
+
const MACHINE_KEYS = [
|
|
175
|
+
"name",
|
|
176
|
+
"context",
|
|
177
|
+
"props",
|
|
178
|
+
"events",
|
|
179
|
+
"derived",
|
|
180
|
+
"actions",
|
|
181
|
+
"guards",
|
|
182
|
+
"effects",
|
|
183
|
+
"initial",
|
|
184
|
+
"states",
|
|
185
|
+
"on",
|
|
186
|
+
"parts",
|
|
187
|
+
"expose"
|
|
188
|
+
];
|
|
189
|
+
const RULE_KEYS = [
|
|
190
|
+
"to",
|
|
191
|
+
"if",
|
|
192
|
+
"do"
|
|
193
|
+
];
|
|
194
|
+
const list = (value) => value === void 0 || Array.isArray(value) ? value : [value];
|
|
195
|
+
const NOTHING = /* @__PURE__ */ new Map();
|
|
196
|
+
/** Config → tables: what `defineMachine` does at runtime, and the plugin at build time ({@link prepared}). */
|
|
197
|
+
function normalise(config) {
|
|
198
|
+
const name = CHECK ? config.name ?? "machine" : "";
|
|
199
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
200
|
+
const states = [];
|
|
201
|
+
const root = {
|
|
202
|
+
i: -1,
|
|
203
|
+
path: ""
|
|
204
|
+
};
|
|
205
|
+
/** The raw config of `root`, then of each state. */
|
|
206
|
+
const raws = [config];
|
|
207
|
+
if (CHECK) checkKeys(config, MACHINE_KEYS, name, "the machine");
|
|
208
|
+
const where = (from) => from === root ? "the machine" : `"${from.path}"`;
|
|
209
|
+
const below = (up, key) => up === root ? key : `${up.path}.${key}`;
|
|
210
|
+
/** Dev only: the state and target being resolved, for `find`'s message; set before each call. */
|
|
211
|
+
let asked = [root, ""];
|
|
212
|
+
const find = (path) => {
|
|
213
|
+
const found = byPath.get(path);
|
|
214
|
+
if (CHECK && !found && asked[1] !== void 0) {
|
|
215
|
+
const [from, target] = asked;
|
|
216
|
+
const last = (p) => p.slice(p.lastIndexOf(".") + 1);
|
|
217
|
+
fail("UNKNOWN_TARGET", name, `${where(from)} → "${target}" does not exist${suggest(last(path), [...byPath.keys()].map(last)) || `; the states are ${[...byPath.keys()].map((p) => `"${p}"`).join(", ")}.`}`);
|
|
218
|
+
}
|
|
219
|
+
return found;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Named implementations (D8), in one map: the types keep each kind's
|
|
223
|
+
* names apart, and development rejects a name used in the wrong kind or
|
|
224
|
+
* declared in two.
|
|
225
|
+
*/
|
|
226
|
+
const named = {
|
|
227
|
+
...config.actions,
|
|
228
|
+
...config.guards,
|
|
229
|
+
...config.effects
|
|
230
|
+
};
|
|
231
|
+
if (CHECK) {
|
|
232
|
+
const seen = /* @__PURE__ */ new Set();
|
|
233
|
+
for (const kind of [
|
|
234
|
+
"actions",
|
|
235
|
+
"guards",
|
|
236
|
+
"effects"
|
|
237
|
+
]) for (const key of Object.keys(config[kind] ?? {})) {
|
|
238
|
+
if (seen.has(key)) fail("NAME_CLASH", name, `"${key}" is declared in \`${kind}\` and in another of \`actions\`, \`guards\` and \`effects\`; give each its own name.`);
|
|
239
|
+
seen.add(key);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/** Functions as written, or the ones they name. */
|
|
243
|
+
const fns = (value) => value && list(value).map((f) => typeof f === "string" ? named[f] : f);
|
|
244
|
+
/** Dev only: each name in `value` is one of `config[kind]`. */
|
|
245
|
+
const known = (value, kind, from) => {
|
|
246
|
+
for (const f of list(value) ?? []) if (typeof f !== "function" && !config[kind]?.[f]) fail("UNKNOWN_NAME", name, `${where(from)} names "${String(f)}", which is not in \`${kind}\`${suggest(String(f), Object.keys(config[kind] ?? {})) || "."} Declare it in \`${kind}\`, or write the function in place.`);
|
|
247
|
+
};
|
|
248
|
+
/** Numbers `up`'s children depth first, then sets the leaf entering `up` settles in. */
|
|
249
|
+
const walk = (up, raw) => {
|
|
250
|
+
const children = raw.states;
|
|
251
|
+
if (children) {
|
|
252
|
+
for (const key of Object.keys(children)) {
|
|
253
|
+
const child = children[key];
|
|
254
|
+
const state = {
|
|
255
|
+
i: states.length,
|
|
256
|
+
path: below(up, key),
|
|
257
|
+
up,
|
|
258
|
+
fi: child.type === "final"
|
|
259
|
+
};
|
|
260
|
+
if (CHECK) {
|
|
261
|
+
if (/[.#]/.test(key)) fail("STATE_NAME", name, `state "${key}" has a "." or "#" in its name; targets use them as separators, so rename it.`);
|
|
262
|
+
checkKeys(child, STATE_KEYS, name, `state "${state.path}"`);
|
|
263
|
+
const type = child.type;
|
|
264
|
+
if (type !== void 0 && type !== "final" && typeof type !== "object") fail("BAD_TYPE", name, `"${state.path}" has \`type: ${JSON.stringify(type)}\`; a state's type is "final", or \`parallel\` imported from @menapo/machine (\`type: parallel\`).`);
|
|
265
|
+
}
|
|
266
|
+
byPath.set(state.path, state);
|
|
267
|
+
states.push(state);
|
|
268
|
+
raws.push(child);
|
|
269
|
+
walk(state, child);
|
|
270
|
+
}
|
|
271
|
+
if (CHECK && raw.initial === void 0 && typeof raw.type !== "object") fail("NO_INITIAL", name, `${up === root ? "the machine has no" : `"${up.path}" has child states but no`} \`initial\`; name one of: ${Object.keys(children).join(", ")}.`);
|
|
272
|
+
}
|
|
273
|
+
if (CHECK) asked = [up, raw.initial];
|
|
274
|
+
up.lf = children ? find(below(up, raw.initial))?.lf : up;
|
|
275
|
+
};
|
|
276
|
+
walk(root, config);
|
|
277
|
+
/** `#path` from the root, `.child` below `from`, else a sibling of `from` or of its nearest ancestor that has one. */
|
|
278
|
+
const resolve = (target, from) => {
|
|
279
|
+
if (CHECK) asked = [from, target];
|
|
280
|
+
if (target[0] === "#") return find(target.slice(1));
|
|
281
|
+
if (target[0] === ".") return find(below(from, target.slice(1)));
|
|
282
|
+
for (let up = from.up; up && up !== root; up = up.up) {
|
|
283
|
+
const found = byPath.get(`${up.path}.${target}`);
|
|
284
|
+
if (found) return found;
|
|
285
|
+
}
|
|
286
|
+
return find(target);
|
|
287
|
+
};
|
|
288
|
+
const rules = (transitions, from) => list(transitions).map((item) => {
|
|
289
|
+
if (CHECK && (typeof item !== "object" || item === null) && typeof item !== "string" && typeof item !== "function") fail("BAD_TRANSITION", name, `${where(from)} has a transition that is ${String(item)}; write a target name, an action, or { to, if, do }.`);
|
|
290
|
+
const rule = typeof item === "string" ? { to: item } : typeof item === "function" ? { do: item } : item;
|
|
291
|
+
if (CHECK) {
|
|
292
|
+
checkKeys(rule, RULE_KEYS, name, `a transition in ${where(from)}`);
|
|
293
|
+
known(rule.if, "guards", from);
|
|
294
|
+
known(rule.do, "actions", from);
|
|
295
|
+
if (rule.to === "") resolve("", from);
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
to: rule.to && resolve(rule.to, from),
|
|
299
|
+
if: rule.if && fns(rule.if)[0],
|
|
300
|
+
do: fns(rule.do)
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
const handled = /* @__PURE__ */ new Set();
|
|
304
|
+
[root, ...states].forEach((state, k) => {
|
|
305
|
+
const raw = raws[k];
|
|
306
|
+
const up = state.up;
|
|
307
|
+
const inherited = up?.on ?? NOTHING;
|
|
308
|
+
let on = state.fi ? NOTHING : inherited;
|
|
309
|
+
if (raw.on && !state.fi) {
|
|
310
|
+
const folded = on = new Map(inherited);
|
|
311
|
+
for (const type of Object.keys(raw.on)) {
|
|
312
|
+
if (CHECK) {
|
|
313
|
+
const declared = config.events;
|
|
314
|
+
if (declared && !(type in declared)) fail("UNKNOWN_EVENT", name, `${where(state)} handles "${type}", which is not in \`events\`${suggest(type, Object.keys(declared)) || "."} Declare it in \`events\`.`);
|
|
315
|
+
handled.add(type);
|
|
316
|
+
}
|
|
317
|
+
const mine = rules(raw.on[type], state);
|
|
318
|
+
const outer = inherited.get(type);
|
|
319
|
+
folded.set(type, {
|
|
320
|
+
ev: outer?.ev ?? { type },
|
|
321
|
+
ru: outer ? mine.concat(outer.ru) : mine
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (CHECK) {
|
|
326
|
+
if (raw.done && raw.type?.kind !== "parallel") fail("DONE_KEY", name, `${where(state)} has \`done\`, which is taken when every region of a parallel state is final; give the state \`type: parallel\`, or use \`always\`.`);
|
|
327
|
+
}
|
|
328
|
+
if (CHECK && state.fi) {
|
|
329
|
+
const region = up !== root && (raws[up.up.i + 1]?.type)?.kind;
|
|
330
|
+
if (up !== root && region !== "parallel") fail("FINAL_STATE", name, `"${state.path}" is final but not top-level; a final state ends the machine at the top level, or a region of a parallel state, so move it or drop \`type: "final"\`.`);
|
|
331
|
+
for (const key of [
|
|
332
|
+
"states",
|
|
333
|
+
"on",
|
|
334
|
+
"effect",
|
|
335
|
+
"after",
|
|
336
|
+
"always"
|
|
337
|
+
]) if (raw[key]) fail("FINAL_STATE", name, `final state "${state.path}" has \`${key}\`, but the machine stops there; move it to another state.`);
|
|
338
|
+
}
|
|
339
|
+
const always = raw.always && rules(raw.always, state);
|
|
340
|
+
const after = raw.after;
|
|
341
|
+
state.on = on;
|
|
342
|
+
if (CHECK) {
|
|
343
|
+
known(raw.entry, "actions", state);
|
|
344
|
+
known(raw.exit, "actions", state);
|
|
345
|
+
known(raw.effect, "effects", state);
|
|
346
|
+
}
|
|
347
|
+
state.en = fns(raw.entry);
|
|
348
|
+
state.ex = fns(raw.exit);
|
|
349
|
+
state.ef = fns(raw.effect);
|
|
350
|
+
state.af = after && Object.keys(after).map((ms) => {
|
|
351
|
+
if (CHECK && !(Number(ms) >= 0)) fail("BAD_DELAY", name, `"${state.path}" has \`after: { ${ms}: … }\`; a delay is a number of milliseconds.`);
|
|
352
|
+
return [+ms, rules(after[ms], state)];
|
|
353
|
+
});
|
|
354
|
+
state.al = always && up?.al ? always.concat(up.al) : always || up?.al;
|
|
355
|
+
});
|
|
356
|
+
if (CHECK) checkParts(config, name, handled);
|
|
357
|
+
const context = config.context ?? {};
|
|
358
|
+
const fields = Object.keys(context);
|
|
359
|
+
const defaults = Object.values(context);
|
|
360
|
+
if (CHECK) {
|
|
361
|
+
for (const value of defaults) if (Array.isArray(value) || Object.getPrototypeOf(value ?? 0) === Object.prototype) Object.freeze(value);
|
|
362
|
+
}
|
|
363
|
+
const props = config.props ?? {};
|
|
364
|
+
const propNames = Object.keys(props);
|
|
365
|
+
const Props = propNames.length ? storeClass(propNames, 1 + fields.length, false) : void 0;
|
|
366
|
+
const derived = config.derived;
|
|
367
|
+
const tables = {
|
|
368
|
+
ss: states,
|
|
369
|
+
i0: root.lf,
|
|
370
|
+
df: defaults,
|
|
371
|
+
C: fields.length ? storeClass(fields, 1, true) : void 0,
|
|
372
|
+
pn: propNames,
|
|
373
|
+
pd: [],
|
|
374
|
+
Q: Props,
|
|
375
|
+
pl: [],
|
|
376
|
+
nm: fields.concat(propNames),
|
|
377
|
+
D: derived && derivedClass(Object.keys(derived), Object.values(derived)),
|
|
378
|
+
k: void 0,
|
|
379
|
+
bd: [],
|
|
380
|
+
c: config,
|
|
381
|
+
r: normalise,
|
|
382
|
+
A: activate,
|
|
383
|
+
X: release,
|
|
384
|
+
P: own
|
|
385
|
+
};
|
|
386
|
+
if (CHECK) Object.assign(tables, {
|
|
387
|
+
name,
|
|
388
|
+
handled
|
|
389
|
+
});
|
|
390
|
+
bind(tables, props);
|
|
391
|
+
for (const raw of raws) raw.type?.f?.(tables, raws, rules);
|
|
392
|
+
return tables;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Defines a machine (PLAN §4.1): states, transitions, context, props and
|
|
396
|
+
* events in one object, normalised once into tables. Context, props,
|
|
397
|
+
* events, derived values, named implementations and the state tree are
|
|
398
|
+
* inferred from it.
|
|
399
|
+
*/
|
|
400
|
+
const defineMachine = normalise;
|
|
401
|
+
/**
|
|
402
|
+
* The machine with some of its named `actions`, `guards` and `effects`
|
|
403
|
+
* replaced (D8), for tests or reuse; `machine` itself is unchanged. Each
|
|
404
|
+
* function is typed from the machine, as the ones it replaces.
|
|
405
|
+
*/
|
|
406
|
+
function provide(machine, implementations) {
|
|
407
|
+
const tables = machine;
|
|
408
|
+
const config = { ...tables.c };
|
|
409
|
+
for (const kind of [
|
|
410
|
+
"actions",
|
|
411
|
+
"guards",
|
|
412
|
+
"effects"
|
|
413
|
+
]) {
|
|
414
|
+
const given = implementations[kind];
|
|
415
|
+
if (DEV && given) {
|
|
416
|
+
for (const key of Object.keys(given)) if (!(key in (config[kind] ?? {}))) fail("UNKNOWN_NAME", config.name ?? "machine", `provide() was given ${kind}.${key}, which the machine does not declare${suggest(key, Object.keys(config[kind] ?? {})) || "."} Declare it in the machine's \`${kind}\` first.`);
|
|
417
|
+
}
|
|
418
|
+
config[kind] = {
|
|
419
|
+
...config[kind],
|
|
420
|
+
...given
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return tables.r(config);
|
|
424
|
+
}
|
|
425
|
+
/** Declares an event's data in `events` (D7): `ADD: payload<{ by: number }>()`. */
|
|
426
|
+
const payload = () => null;
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/features/marks.ts
|
|
429
|
+
/** The feature's mark on a raw state config, if it has one. */
|
|
430
|
+
const markOf = (raw) => typeof raw?.type === "object" ? raw.type : void 0;
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region src/features/history.ts
|
|
433
|
+
/**
|
|
434
|
+
* `history()` (PLAN §4.9): a pseudo-state that stands for where its parent
|
|
435
|
+
* was when last exited. A transition to it enters that again: the parent's
|
|
436
|
+
* active child (shallow, its own initial states below it) or its active
|
|
437
|
+
* leaves (`history("deep")`); before the parent was ever exited, the
|
|
438
|
+
* parent's initial state. It is never active itself.
|
|
439
|
+
*
|
|
440
|
+
* Its mark carries what prepares a machine's tables, which the normaliser
|
|
441
|
+
* calls when it meets the mark: each history state is marked (`h`: deep or
|
|
442
|
+
* not) and checked; `resume.ts` wires them. Importing it has no effect. A
|
|
443
|
+
* machine with parallel states leaves both to `parallel`'s configurations,
|
|
444
|
+
* which read the same marker and the same records. A compiled machine (M7)
|
|
445
|
+
* has its tables prepared at build time, so it ships `resume.ts` alone.
|
|
446
|
+
*/
|
|
447
|
+
/** The machines whose history states are prepared. */
|
|
448
|
+
const done = /* @__PURE__ */ new WeakSet();
|
|
449
|
+
/** Prepares a machine's history states, then wires them: what the marks carry. */
|
|
450
|
+
const prepare$1 = (tables, raws) => {
|
|
451
|
+
if (done.has(tables) || raws.some((raw) => markOf(raw)?.kind === "parallel")) return;
|
|
452
|
+
done.add(tables);
|
|
453
|
+
tables.ss.forEach((state, k) => {
|
|
454
|
+
const mark = markOf(raws[k + 1]);
|
|
455
|
+
if (mark?.kind !== "history") return;
|
|
456
|
+
const parent = state.up;
|
|
457
|
+
if (CHECK) {
|
|
458
|
+
if (parent.i < 0) fail("HISTORY", tables.name, `"${state.path}" is a history state of the machine itself, which is never exited; put it inside the state whose history it keeps.`);
|
|
459
|
+
if (`${parent.path}.${raws[parent.i + 1].initial}` === state.path) fail("HISTORY", tables.name, `"${parent.path}" starts in its history state "${state.path}"; make another state its \`initial\`.`);
|
|
460
|
+
}
|
|
461
|
+
state.h = !!mark.deep;
|
|
462
|
+
});
|
|
463
|
+
wireHistory(tables);
|
|
464
|
+
};
|
|
465
|
+
const SHALLOW = /* @__PURE__ */ Object.freeze({
|
|
466
|
+
kind: "history",
|
|
467
|
+
deep: false,
|
|
468
|
+
f: prepare$1
|
|
469
|
+
});
|
|
470
|
+
const DEEP = /* @__PURE__ */ Object.freeze({
|
|
471
|
+
kind: "history",
|
|
472
|
+
deep: true,
|
|
473
|
+
f: prepare$1
|
|
474
|
+
});
|
|
475
|
+
/**
|
|
476
|
+
* A history pseudo-state: `resume: history()` returns to the child its
|
|
477
|
+
* parent was last in, `history("deep")` to the very states inside it.
|
|
478
|
+
*/
|
|
479
|
+
const history = (type) => ({ type: type === "deep" ? DEEP : SHALLOW });
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/features/job.ts
|
|
482
|
+
/**
|
|
483
|
+
* Starts `run(signal)` for the state whose effect calls it and returns the
|
|
484
|
+
* cleanup that cancels it. `view` is the effect's view; `done` and
|
|
485
|
+
* `error` name events the machine declares.
|
|
486
|
+
*/
|
|
487
|
+
function job(view, run, events) {
|
|
488
|
+
const controller = new AbortController();
|
|
489
|
+
let live = true;
|
|
490
|
+
const send = view.send;
|
|
491
|
+
const tell = (type, payload) => {
|
|
492
|
+
if (live && type) send(type, payload);
|
|
493
|
+
};
|
|
494
|
+
const work = run(controller.signal);
|
|
495
|
+
let stop;
|
|
496
|
+
if (typeof work === "function") stop = work;
|
|
497
|
+
else if (work && typeof work.stop === "function") {
|
|
498
|
+
const child = work;
|
|
499
|
+
stop = () => child.stop();
|
|
500
|
+
child.done.then(() => tell(events?.done, { data: child.context }));
|
|
501
|
+
} else if (work) work.then((data) => tell(events?.done, { data }), (error) => {
|
|
502
|
+
if (!live) return;
|
|
503
|
+
if (!events?.error) throw error;
|
|
504
|
+
tell(events.error, { error });
|
|
505
|
+
});
|
|
506
|
+
return (() => {
|
|
507
|
+
live = false;
|
|
508
|
+
controller.abort();
|
|
509
|
+
stop?.();
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region src/features/parallel.ts
|
|
514
|
+
/**
|
|
515
|
+
* `type: parallel` (PLAN §4.9, D12): a state whose children, its regions,
|
|
516
|
+
* are all active at once. Only a machine that declares one ships this
|
|
517
|
+
* module: `parallel` is imported to be written, and the mark carries what
|
|
518
|
+
* prepares the machine's tables for the configurations of `chart.ts` (the
|
|
519
|
+
* regions, initial children and subtrees of each state, the state that
|
|
520
|
+
* declares each rule, a parallel state's `done`) and wires them, which the
|
|
521
|
+
* normaliser calls when it meets the mark. Importing it has no effect.
|
|
522
|
+
* A compiled machine (M7) has its tables prepared at build time, so it ships
|
|
523
|
+
* `chart.ts` without this module.
|
|
524
|
+
*/
|
|
525
|
+
/** Prepares a machine with parallel states, then wires it: what the mark carries. */
|
|
526
|
+
const prepare = (tables, raws, rules) => {
|
|
527
|
+
const states = tables.ss;
|
|
528
|
+
const root = states[0].up;
|
|
529
|
+
if (root.k) return;
|
|
530
|
+
const all = [root, ...states];
|
|
531
|
+
const byPath = new Map(states.map((s) => [s.path, s]));
|
|
532
|
+
for (const s of all) {
|
|
533
|
+
const k = s.i + 1;
|
|
534
|
+
const mark = markOf(raws[k]);
|
|
535
|
+
const first = raws[k].initial;
|
|
536
|
+
s.k = [];
|
|
537
|
+
s.hs = [];
|
|
538
|
+
s.p = mark?.kind === "parallel";
|
|
539
|
+
s.h = mark?.kind === "history" ? !!mark.deep : void 0;
|
|
540
|
+
s.n = first === void 0 ? void 0 : byPath.get(s === root ? first : `${s.path}.${first}`);
|
|
541
|
+
for (let up = s; up; up = up.up) up.e = s.i;
|
|
542
|
+
if (s.up) (s.h === void 0 ? s.up.k : s.up.hs).push(s);
|
|
543
|
+
}
|
|
544
|
+
if (CHECK) {
|
|
545
|
+
for (const s of states) if (s.h !== void 0 && s.up.n === s) fail("HISTORY", tables.name, `"${s.up.path}" starts in its history state "${s.path}"; make another state its \`initial\`.`);
|
|
546
|
+
if (tables.bd.some((b) => b.yes)) fail("PARALLEL", tables.name, "a bindable prop follows states (`bindable(…, { states })`) in a machine with parallel states, which is not supported yet; bind the value to data and move with events.");
|
|
547
|
+
}
|
|
548
|
+
/** Marks the first `count` rules of `list` as declared by `s`. */
|
|
549
|
+
const own = (list, count, s) => {
|
|
550
|
+
for (let k = 0; k < count; k++) list[k].s = s;
|
|
551
|
+
};
|
|
552
|
+
for (const s of all) {
|
|
553
|
+
for (const [type, handler] of s.on) {
|
|
554
|
+
const outer = s.up?.on.get(type);
|
|
555
|
+
if (outer !== handler) own(handler.ru, handler.ru.length - (outer?.ru.length ?? 0), s);
|
|
556
|
+
}
|
|
557
|
+
if (s.al && s.al !== s.up?.al) own(s.al, s.al.length - (s.up?.al?.length ?? 0), s);
|
|
558
|
+
for (const [, list] of s.af ?? []) own(list, list.length, s);
|
|
559
|
+
const done = raws[s.i + 1]?.done;
|
|
560
|
+
if (done && s.p) {
|
|
561
|
+
const list = rules(done, s);
|
|
562
|
+
own(list, list.length, s);
|
|
563
|
+
s.dn = {
|
|
564
|
+
rules: list,
|
|
565
|
+
event: { type: DONE.type }
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
wireParallel(tables);
|
|
570
|
+
};
|
|
571
|
+
/** Marks a state whose regions are all active at once: `editor: { type: parallel, states: { … } }`. */
|
|
572
|
+
const parallel = /* @__PURE__ */ Object.freeze({
|
|
573
|
+
kind: "parallel",
|
|
574
|
+
f: prepare
|
|
575
|
+
});
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/parts/use.ts
|
|
578
|
+
function useMachine() {
|
|
579
|
+
throw new Error("[machine:NO_PLUGIN] useMachine is resolved per framework by the Vite plugin; add machine() from @menapo/machine/vite to the Vite config's plugins. With it, reach useMachine from a module the plugin transforms (a script, a .vue or .svelte file), by import, re-export or import(), not through an object passed around.");
|
|
580
|
+
}
|
|
581
|
+
//#endregion
|
|
582
|
+
export { bindable, defineMachine, history, inspect, job, parallel, payload, provide, start, useMachine };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { i as setHook } from "./instance-BM2UqeXW.js";
|
|
2
|
+
//#region src/inspect.ts
|
|
3
|
+
/**
|
|
4
|
+
* `inspect` (PLAN D17, §5.8): how DevTools and `/test` watch machines. Each
|
|
5
|
+
* event an instance takes becomes one step once it is handled: the event,
|
|
6
|
+
* the state after it, and the context fields and props it changed, with
|
|
7
|
+
* their new values.
|
|
8
|
+
*
|
|
9
|
+
* A step's changes are found by comparing each field with its value after
|
|
10
|
+
* the previous step, so a queued event that changes a field again reports it
|
|
11
|
+
* too. Only the last values are kept, never a snapshot per event (§9): with
|
|
12
|
+
* context changed in place, snapshots would cost memory on every event and
|
|
13
|
+
* still not make time travel safe.
|
|
14
|
+
*
|
|
15
|
+
* This module is the only one that sets the core's hook, so a bundle that
|
|
16
|
+
* never imports `inspect` (every production build: DevTools add it in dev
|
|
17
|
+
* only) drops the hook's calls along with it.
|
|
18
|
+
*/
|
|
19
|
+
const PROPS = { type: "machine.props" };
|
|
20
|
+
let open = /* @__PURE__ */ new WeakMap();
|
|
21
|
+
const watchers = /* @__PURE__ */ new Set();
|
|
22
|
+
/** Context fields, then props, as the instance reads them (a controlled prop is the owner's). */
|
|
23
|
+
function values(instance) {
|
|
24
|
+
const { nm: names, pn: propNames } = instance.M;
|
|
25
|
+
const fields = names.length - propNames.length;
|
|
26
|
+
const context = instance.context;
|
|
27
|
+
const props = instance.props;
|
|
28
|
+
return names.map((name, n) => (n < fields ? context : props)[name]);
|
|
29
|
+
}
|
|
30
|
+
function close(instance, record) {
|
|
31
|
+
const event = record.event;
|
|
32
|
+
if (!event) return;
|
|
33
|
+
record.event = void 0;
|
|
34
|
+
const now = values(instance);
|
|
35
|
+
const changed = {};
|
|
36
|
+
now.forEach((value, n) => {
|
|
37
|
+
if (!Object.is(value, record.values[n])) changed[instance.M.nm[n]] = value;
|
|
38
|
+
});
|
|
39
|
+
record.values = now;
|
|
40
|
+
const step = {
|
|
41
|
+
instance,
|
|
42
|
+
event,
|
|
43
|
+
state: instance.s.path,
|
|
44
|
+
changed
|
|
45
|
+
};
|
|
46
|
+
for (const watch of watchers) watch(step);
|
|
47
|
+
}
|
|
48
|
+
/** The hook: an event begins (`null`: owner props), or, with none, the instance is done. */
|
|
49
|
+
function observe(instance, event) {
|
|
50
|
+
let record = open.get(instance);
|
|
51
|
+
if (!record) open.set(instance, record = {
|
|
52
|
+
values: values(instance),
|
|
53
|
+
event: void 0
|
|
54
|
+
});
|
|
55
|
+
else close(instance, record);
|
|
56
|
+
if (event !== void 0) record.event = event ?? PROPS;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Calls `watch` with a {@link Step} for every event any instance takes from
|
|
60
|
+
* now on, in order. Returns the function that stops watching.
|
|
61
|
+
*/
|
|
62
|
+
function inspect(watch) {
|
|
63
|
+
watchers.add(watch);
|
|
64
|
+
setHook(observe);
|
|
65
|
+
return () => {
|
|
66
|
+
watchers.delete(watch);
|
|
67
|
+
if (!watchers.size) {
|
|
68
|
+
setHook(void 0);
|
|
69
|
+
open = /* @__PURE__ */ new WeakMap();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { values as n, inspect as t };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { g as Instance, n as AnyEvent } from "./types-Bk8svtO6.js";
|
|
2
|
+
//#region src/inspect.d.ts
|
|
3
|
+
/** One event an instance took, once handled. */
|
|
4
|
+
interface Step {
|
|
5
|
+
readonly instance: Instance;
|
|
6
|
+
/**
|
|
7
|
+
* `machine.start` first, `machine.after` for a timer, `machine.props` for
|
|
8
|
+
* new owner props; otherwise the event as sent.
|
|
9
|
+
*/
|
|
10
|
+
readonly event: AnyEvent;
|
|
11
|
+
/** The active path after it. */
|
|
12
|
+
readonly state: string;
|
|
13
|
+
/** The context fields and props it changed, with their new values. */
|
|
14
|
+
readonly changed: Readonly<Record<string, unknown>>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Calls `watch` with a {@link Step} for every event any instance takes from
|
|
18
|
+
* now on, in order. Returns the function that stops watching.
|
|
19
|
+
*/
|
|
20
|
+
declare function inspect(watch: (step: Step) => void): () => void;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { inspect as n, Step as t };
|