@mantaq/sugar 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anders Can
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,418 @@
1
+ # @mantaq/sugar
2
+
3
+ Convenience helpers for [@mantaq/core](../core) actors. State/event batching, pattern matching, effect utilities, and dynamic child management.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @mantaq/sugar
9
+ # or
10
+ pnpm add @mantaq/sugar
11
+ # or
12
+ yarn add @mantaq/sugar
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```ts
18
+ import { states, events, matches, withTimeout } from "@mantaq/sugar";
19
+ import { Actor, VirtualClock } from "@mantaq/core";
20
+
21
+ const s = states("idle", "loading", "success", "error");
22
+ const e = events("fetch", "resolve", "fail");
23
+
24
+ const clock = new VirtualClock();
25
+
26
+ const machine = new Actor({
27
+ inputs: [e.fetch],
28
+ internal: [e.resolve, e.fail],
29
+ context: {},
30
+ states: [s.idle, s.loading, s.success, s.error],
31
+ initial: s.idle,
32
+ clock,
33
+ setup: (m) => {
34
+ m.on(s.idle, e.fetch, () => ({ state: s.loading }));
35
+ m.effect(s.loading, (input) => withTimeout(2000, input, () => e.resolve.create()));
36
+ m.on(s.loading, e.resolve, () => ({ state: s.success }));
37
+ m.on(s.loading, e.fail, () => ({ state: s.error }));
38
+ },
39
+ });
40
+
41
+ matches(machine, "idle"); // true
42
+ machine.send(e.fetch.create());
43
+ matches(machine, "loading"); // true
44
+ clock.advance(2000);
45
+ matches(machine, "success"); // true
46
+ ```
47
+
48
+ ## Patterns
49
+
50
+ ### Type-safe state groups with `states()`
51
+
52
+ Use `states()` to create a record of `StateRef`s with full type safety. The returned keys match the input strings exactly.
53
+
54
+ ```ts
55
+ import { states } from "@mantaq/sugar";
56
+
57
+ const s = states("idle", "loading", "success", "error");
58
+
59
+ s.idle.name; // "idle"
60
+ s.success.final(); // StateRef with isFinal = true
61
+
62
+ const actor = new Actor({
63
+ states: [s.idle, s.loading, s.success, s.error],
64
+ initial: s.idle,
65
+ // ...
66
+ });
67
+ ```
68
+
69
+ **Anti-pattern:** Creating states manually without `states()` and losing type safety:
70
+
71
+ ```ts
72
+ import { state } from "@mantaq/core";
73
+
74
+ const idle = state("idle")();
75
+ const loading = state("loading")();
76
+ const success = state("success")();
77
+ const error = state("error")();
78
+ // No single record — easy to miss one, no autocomplete, no compile check
79
+ ```
80
+
81
+ `states()` guarantees all names exist as typed keys. Manual creation requires you to keep references in sync yourself.
82
+
83
+ ### Type-safe event groups with `events()`
84
+
85
+ Same as `states()` but for events. Creates a typed record with `.create()`, `.is()`, and other `EventRef` methods.
86
+
87
+ ```ts
88
+ import { events } from "@mantaq/sugar";
89
+
90
+ const e = events("fetch", "resolve", "fail");
91
+
92
+ e.fetch.create(); // { id: "fetch" }
93
+ e.resolve.is(emittedEvent); // boolean
94
+
95
+ const actor = new Actor({
96
+ inputs: [e.fetch],
97
+ internal: [e.resolve],
98
+ // ...
99
+ });
100
+ ```
101
+
102
+ **Anti-pattern:** Scattering event refs across files without a shared group:
103
+
104
+ ```ts
105
+ import { event } from "@mantaq/core";
106
+
107
+ const fetchEvent = event("fetch")();
108
+ const resolveEvent = event("resolve")();
109
+ const failEvent = event("fail")();
110
+ // No single source of truth — naming drift, typos not caught
111
+ ```
112
+
113
+ `events()` creates a single typed object. Any typo in a key is a compile error.
114
+
115
+ ### Pattern matching with `matches()`
116
+
117
+ `matches()` uses dot-separated paths. The format alternates between state names and region keys: `"state.regionKey.state"`.
118
+
119
+ ```ts
120
+ import { matches } from "@mantaq/sugar";
121
+
122
+ // flat
123
+ matches(actor, "idle"); // true if in idle
124
+
125
+ // hierarchical — state, region key, nested state
126
+ matches(actor, "connected.default.active");
127
+
128
+ // parallel — match any region
129
+ matches(actor, "player.playing");
130
+ ```
131
+
132
+ **Anti-pattern:** Using wrong path format:
133
+
134
+ ```ts
135
+ // WRONG — region key must come after parent state
136
+ matches(actor, "active.connected.default");
137
+
138
+ // WRONG — trailing dot always returns false
139
+ matches(actor, "idle.");
140
+
141
+ // WRONG — empty string returns false
142
+ matches(actor, "");
143
+ ```
144
+
145
+ Path format: `"parentState.regionKey.childState"`. The region key is the key you passed to the actor's `regions` option, not the nested state's name.
146
+
147
+ ### Using `tag()` for state grouping
148
+
149
+ `tag()` groups multiple `StateRef`s and tests if a snapshot matches any of them. Useful for UI that needs to branch on state categories.
150
+
151
+ ```ts
152
+ import { tag } from "@mantaq/sugar";
153
+
154
+ const s = states("idle", "loading", "success", "error");
155
+ const busy = tag(s.idle, s.loading);
156
+ const terminal = tag(s.success, s.error);
157
+
158
+ // In UI
159
+ if (busy.has(snapshot)) {
160
+ showSpinner();
161
+ } else if (terminal.has(snapshot)) {
162
+ showResult();
163
+ }
164
+ ```
165
+
166
+ **Anti-pattern:** Testing individual states manually with `matches()`:
167
+
168
+ ```ts
169
+ if (matches(actor, "idle") || matches(actor, "loading")) {
170
+ showSpinner();
171
+ }
172
+ // Breaks when hierarchy changes, verbose, error-prone
173
+ ```
174
+
175
+ `tag()` searches recursively through parallel regions and nested hierarchies — works regardless of depth.
176
+
177
+ ### ActorMap patterns
178
+
179
+ `ActorMap` manages dynamic children by string key. Always use `ensure()` to avoid duplicate spawns.
180
+
181
+ ```ts
182
+ import { ActorMap, isIn } from "@mantaq/sugar";
183
+
184
+ const map = new ActorMap(parentActor);
185
+
186
+ // Safe spawn — no-op if key exists
187
+ map.ensure("child1", () => createChild());
188
+
189
+ // Send events
190
+ map.send("child1", someEvent);
191
+
192
+ // Snapshot a child
193
+ const snap = map.snapshot("child1");
194
+ if (snap && isIn(snap, "active")) {
195
+ // child is active
196
+ }
197
+
198
+ // Kill when done
199
+ map.kill("child1");
200
+ ```
201
+
202
+ **Anti-pattern:** Using `spawn()` without checking for duplicates:
203
+
204
+ ```ts
205
+ map.spawn("child1", () => createChild());
206
+ map.spawn("child1", () => createChild()); // overwrites silently — previous child lost
207
+ ```
208
+
209
+ `ensure()` is idempotent — safe to call repeatedly. `spawn()` always replaces.
210
+
211
+ ### Effect utilities: `withPromise` vs manual handling
212
+
213
+ Always use `withPromise()` for promise-to-event bridging. It handles abort signals automatically.
214
+
215
+ ```ts
216
+ import { withPromise } from "@mantaq/sugar";
217
+
218
+ // Correct — abort-aware
219
+ withPromise(fetchData(), input.signal, input.emit, {
220
+ success: (data) => ({ id: "loaded", data }),
221
+ error: (err) => ({ id: "loadFailed", error: String(err) }),
222
+ });
223
+ ```
224
+
225
+ **Anti-pattern:** Manual `.then()/.catch()` without abort handling:
226
+
227
+ ```ts
228
+ fetchData()
229
+ .then((data) => {
230
+ input.emit({ id: "loaded", data }); // fires even if actor destroyed
231
+ })
232
+ .catch((err) => {
233
+ input.emit({ id: "loadFailed", error: String(err) }); // same problem
234
+ });
235
+ ```
236
+
237
+ `withPromise` checks `isAborted(signal)` before each emit. Manual chains fire events into destroyed actors.
238
+
239
+ ## Helpers
240
+
241
+ ### `matches(actor, pattern)`
242
+
243
+ Dot-notation state pattern matching against an actor's snapshot. Works with flat, hierarchical, and parallel states.
244
+
245
+ ```ts
246
+ import { matches } from "@mantaq/sugar";
247
+
248
+ // flat
249
+ matches(actor, "idle"); // true/false
250
+
251
+ // hierarchical — region path
252
+ matches(actor, "connected.default.active");
253
+
254
+ // parallel — any region matches
255
+ matches(actor, "player.playback.playing");
256
+ ```
257
+
258
+ Pattern format: `"state.regionKey.state"` or just `"state"` for flat. Empty string or trailing dots return `false`.
259
+
260
+ ### `ActorMap`
261
+
262
+ Dynamic child actor registry. Spawns, sends to, kills, and snapshots children by string key. Optionally wires child output to a parent actor.
263
+
264
+ ```ts
265
+ import { ActorMap, isIn } from "@mantaq/sugar";
266
+
267
+ const map = new ActorMap(); // or new ActorMap(parentActor)
268
+
269
+ map.spawn("child1", () => childActor);
270
+ map.spawn("child2", () => otherActor);
271
+
272
+ map.send("child1", someEvent);
273
+ map.kill("child2");
274
+ map.keys(); // ["child1"]
275
+
276
+ const snap = map.snapshot("child1"); // Snapshot | undefined
277
+ if (snap && isIn(snap, "active")) {
278
+ // child is active
279
+ }
280
+ ```
281
+
282
+ **`ensure(key, factory)`** — spawn only if key missing:
283
+
284
+ ```ts
285
+ map.ensure("child1", () => childActor); // no-op if exists
286
+ ```
287
+
288
+ **Typing children:** `ActorMap` is untyped by design. Access children through your own typed references or use `matches()` / `snapshot()` for type-safe checks.
289
+
290
+ **Parent wiring:** When constructed with a parent actor, child outputs are automatically forwarded to the parent:
291
+
292
+ ```ts
293
+ const map = new ActorMap(parentActor);
294
+ map.spawn("child", () => childActor);
295
+ // child emits → parent receives
296
+ ```
297
+
298
+ ### `broadcast(map, event)`
299
+
300
+ Send an event to every key in an `ActorMap` (or any `SendableMap`):
301
+
302
+ ```ts
303
+ import { broadcast } from "@mantaq/sugar";
304
+
305
+ broadcast(map, { id: "ping" });
306
+ ```
307
+
308
+ Works with `ActorMap` or any object implementing `{ keys(): string[]; send(key, event): void }`.
309
+
310
+ ### `tag(...stateRefs)`
311
+
312
+ Group multiple `StateRef`s and test if a snapshot matches any of them. Useful for UI state grouping.
313
+
314
+ ```ts
315
+ import { tag } from "@mantaq/sugar";
316
+ import { state } from "@mantaq/core";
317
+
318
+ const idle = state("idle")();
319
+ const loading = state("loading")();
320
+ const busy = tag(idle, loading);
321
+
322
+ busy.has(snapshot); // true if snapshot is idle or loading (any depth)
323
+ ```
324
+
325
+ Searches recursively through parallel regions and nested hierarchies.
326
+
327
+ ### `states(...names)`
328
+
329
+ Batch-create `StateRef`s as a typed record:
330
+
331
+ ```ts
332
+ import { states } from "@mantaq/sugar";
333
+
334
+ const s = states("idle", "loading", "success");
335
+ s.idle.name; // "idle"
336
+ s.success.final(); // StateRef with isFinal = true
337
+ ```
338
+
339
+ ### `events(...names)`
340
+
341
+ Batch-create `EventRef`s as a typed record:
342
+
343
+ ```ts
344
+ import { events } from "@mantaq/sugar";
345
+
346
+ const e = events("click", "submit");
347
+ e.click.create(); // { id: "click" }
348
+ e.click.is(emittedEvent); // boolean
349
+ ```
350
+
351
+ ### `withPromise(promise, signal, emit, events)`
352
+
353
+ Bridge a promise into actor events. Emits `success` on resolve, `error` on reject. Respects `AbortSignal` — skips emit if aborted before settlement.
354
+
355
+ ```ts
356
+ import { withPromise } from "@mantaq/sugar";
357
+
358
+ withPromise(fetchData(), input.signal, input.emit, {
359
+ success: (data) => ({ id: "loaded", data }),
360
+ error: (err) => ({ id: "loadFailed", error: String(err) }),
361
+ });
362
+ ```
363
+
364
+ ### `withTimeout(ms, input, eventFn)`
365
+
366
+ Schedule a timeout event through the actor's clock. Aborts cleanly if the actor is destroyed before timeout fires.
367
+
368
+ ```ts
369
+ import { withTimeout } from "@mantaq/sugar";
370
+
371
+ withTimeout(5000, input, () => ({ id: "timeout", reason: "exceeded" }));
372
+ ```
373
+
374
+ ## Migration from Core
375
+
376
+ Starting with `@mantaq/core`? Here's what sugar adds.
377
+
378
+ | Core | Sugar | Benefit |
379
+ | --------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------- |
380
+ | `state("idle")()` × N | `states("idle", "loading")` | Single call. Typed record. No missed names. |
381
+ | `event("FETCH")()` × N | `events("FETCH", "RESOLVE")` | Single call. Typed record. Shared source of truth. |
382
+ | `snapshot().path.includes("active")` | `matches(actor, "active")` | Works on actor directly. Dot-notation for hierarchies: `"idle.region.a"`. |
383
+ | Manual `.then()/.catch()` + abort check | `withPromise(promise, signal, emit, events)` | Auto abort-aware. No forgotten `signal.aborted` guard. |
384
+ | Group states by variable naming | `tag(stateA, stateB).has(snapshot)` | Recursive matching. Works through parallel regions. |
385
+
386
+ **Core alone:**
387
+
388
+ ```ts
389
+ import { state, event } from "@mantaq/core";
390
+
391
+ const idle = state("idle")();
392
+ const loading = state("loading")();
393
+ const success = state("success")();
394
+ const failEvent = event("FAIL")();
395
+
396
+ actor.snapshot().path.includes("idle"); // flat name only
397
+ ```
398
+
399
+ **With sugar:**
400
+
401
+ ```ts
402
+ import { states, events, matches, tag } from "@mantaq/sugar";
403
+
404
+ const s = states("idle", "loading", "success");
405
+ const e = events("FAIL");
406
+
407
+ matches(actor, "idle"); // actor, not snapshot
408
+ matches(actor, "connected.default.active"); // hierarchical
409
+
410
+ const busy = tag(s.idle, s.loading);
411
+ busy.has(actor.snapshot()); // recursive
412
+ ```
413
+
414
+ No migration needed. Sugar wraps core — use both side by side.
415
+
416
+ ## License
417
+
418
+ MIT
@@ -0,0 +1,68 @@
1
+ import { AnyActor, AnyEventRef, EffectInput, EventRef, InternalEvent, Snapshot, StateRef } from "@mantaq/core";
2
+ //#region src/actors/matches.d.ts
3
+ declare function matches(actor: {
4
+ snapshot(): Snapshot;
5
+ }, pattern: string): boolean;
6
+ //#endregion
7
+ //#region src/transitions/broadcast.d.ts
8
+ type EventLike = {
9
+ id: string;
10
+ [key: string]: unknown;
11
+ };
12
+ type SendableEvent = AnyEventRef | EventLike;
13
+ interface SendableMap<T extends SendableEvent = SendableEvent> {
14
+ keys(): string[];
15
+ send(key: string, event: T): void;
16
+ }
17
+ declare function broadcast<const T extends SendableEvent>(map: SendableMap<T>, event: T): void;
18
+ //#endregion
19
+ //#region src/actors/actor-map.d.ts
20
+ declare class ActorMap implements SendableMap<SendableEvent> {
21
+ #private;
22
+ constructor(parent?: AnyActor);
23
+ spawn(key: string, factory: () => AnyActor): void;
24
+ send(key: string, event: SendableEvent): void;
25
+ kill(key: string): void;
26
+ get size(): number;
27
+ has(key: string): boolean;
28
+ keys(): string[];
29
+ ensure(key: string, factory: () => AnyActor): void;
30
+ snapshot(key: string): Snapshot | undefined;
31
+ }
32
+ //#endregion
33
+ //#region src/state.d.ts
34
+ declare function states<T extends string>(...names: T[]): { [K in T]: StateRef<K>; };
35
+ //#endregion
36
+ //#region src/event.d.ts
37
+ declare function events<T extends string>(...names: T[]): { [K in T]: EventRef<K>; };
38
+ //#endregion
39
+ //#region src/effects/promise.d.ts
40
+ type EmitFn = (event: {
41
+ id: string;
42
+ [key: string]: unknown;
43
+ }) => void;
44
+ declare function withPromise<T>(promise: Promise<T>, signal: AbortSignal, emit: EmitFn, events: {
45
+ success: (data: T) => {
46
+ id: string;
47
+ [key: string]: unknown;
48
+ };
49
+ error: (err: unknown) => {
50
+ id: string;
51
+ [key: string]: unknown;
52
+ };
53
+ }): void;
54
+ //#endregion
55
+ //#region src/effects/timeout.d.ts
56
+ declare function withTimeout<ActorContext>(ms: number, input: EffectInput<ActorContext>, event: () => InternalEvent): void;
57
+ //#endregion
58
+ //#region src/tags.d.ts
59
+ interface Tag {
60
+ has(snapshot: Snapshot): boolean;
61
+ }
62
+ declare function tag(...stateNames: StateRef<string, unknown>[]): Tag;
63
+ //#endregion
64
+ //#region src/snapshot.d.ts
65
+ declare function isIn(snapshot: Snapshot, stateRefName: string): boolean;
66
+ declare function activeLeaves(snapshot: Snapshot): string[];
67
+ //#endregion
68
+ export { ActorMap, type EventLike, type SendableEvent, type SendableMap, activeLeaves, broadcast, events, isIn, matches, states, tag, withPromise, withTimeout };
package/dist/index.mjs ADDED
@@ -0,0 +1,122 @@
1
+ import { abortEffects, setOutputHandler } from "@mantaq/core/internal";
2
+ import { Either } from "@mantaq/utils";
3
+ import { event, state } from "@mantaq/core";
4
+ //#region src/actors/matches.ts
5
+ function matchSnapshot(snapshot, parts, index) {
6
+ const stateName = snapshot.path[0];
7
+ for (let end = index; end < parts.length; end++) {
8
+ if (parts.slice(index, end + 1).join(".") !== stateName) continue;
9
+ if (end === parts.length - 1) return true;
10
+ const regionSnap = snapshot.regions[parts[end + 1]];
11
+ if (!regionSnap) return false;
12
+ if (end + 1 === parts.length - 1) return true;
13
+ return matchSnapshot(regionSnap, parts, end + 2);
14
+ }
15
+ return false;
16
+ }
17
+ function matches(actor, pattern) {
18
+ if (!pattern || pattern.endsWith(".") || pattern.startsWith(".") || pattern.includes("..")) return false;
19
+ const parts = pattern.split(".");
20
+ return matchSnapshot(actor.snapshot(), parts, 0);
21
+ }
22
+ //#endregion
23
+ //#region src/actors/actor-map.ts
24
+ var ActorMap = class {
25
+ #actors = /* @__PURE__ */ new Map();
26
+ #parent;
27
+ constructor(parent) {
28
+ this.#parent = parent ?? null;
29
+ }
30
+ spawn(key, factory) {
31
+ if (this.#actors.has(key)) {
32
+ console.warn(`[ActorMap] spawning over existing key "${key}". Old actor will be aborted.`);
33
+ this.kill(key);
34
+ }
35
+ const child = factory();
36
+ if (this.#parent) Either.match(setOutputHandler(child, (event) => {
37
+ this.#parent.send(event);
38
+ }), (err) => console.error(err.message), () => {});
39
+ this.#actors.set(key, child);
40
+ }
41
+ send(key, event) {
42
+ this.#actors.get(key)?.send(event);
43
+ }
44
+ kill(key) {
45
+ const actor = this.#actors.get(key);
46
+ if (actor) Either.match(abortEffects(actor), (err) => console.error(err.message), () => {});
47
+ this.#actors.delete(key);
48
+ }
49
+ get size() {
50
+ return this.#actors.size;
51
+ }
52
+ has(key) {
53
+ return this.#actors.has(key);
54
+ }
55
+ keys() {
56
+ return [...this.#actors.keys()];
57
+ }
58
+ ensure(key, factory) {
59
+ if (!this.#actors.has(key)) this.spawn(key, factory);
60
+ }
61
+ snapshot(key) {
62
+ return this.#actors.get(key)?.snapshot();
63
+ }
64
+ };
65
+ //#endregion
66
+ //#region src/state.ts
67
+ function states(...names) {
68
+ const result = {};
69
+ for (const name of names) result[name] = state(name)();
70
+ return result;
71
+ }
72
+ //#endregion
73
+ //#region src/event.ts
74
+ function events(...names) {
75
+ const result = {};
76
+ for (const name of names) result[name] = event(name)();
77
+ return result;
78
+ }
79
+ //#endregion
80
+ //#region src/effects/promise.ts
81
+ function withPromise(promise, signal, emit, events) {
82
+ promise.then((data) => {
83
+ if (!signal.aborted) emit(events.success(data));
84
+ }).catch((err) => {
85
+ if (!signal.aborted) emit(events.error(err));
86
+ });
87
+ }
88
+ //#endregion
89
+ //#region src/effects/timeout.ts
90
+ function withTimeout(ms, input, event) {
91
+ if (typeof ms !== "number" || ms < 0 || !Number.isFinite(ms)) console.warn(`[withTimeout] invalid ms value: ${ms}. Timeout may not fire.`);
92
+ input.clock.setTimeout(ms, () => {
93
+ if (input.signal.aborted) return;
94
+ input.emit(event());
95
+ });
96
+ }
97
+ //#endregion
98
+ //#region src/transitions/broadcast.ts
99
+ function broadcast(map, event) {
100
+ for (const key of map.keys()) map.send(key, event);
101
+ }
102
+ //#endregion
103
+ //#region src/snapshot.ts
104
+ function isIn(snapshot, stateRefName) {
105
+ if (snapshot.path[0] === stateRefName) return true;
106
+ for (const region of Object.values(snapshot.regions)) if (isIn(region, stateRefName)) return true;
107
+ return false;
108
+ }
109
+ function activeLeaves(snapshot) {
110
+ if (Object.keys(snapshot.regions).length === 0) return [snapshot.path.join(".")];
111
+ const leaves = [];
112
+ for (const [regionName, region] of Object.entries(snapshot.regions)) for (const leaf of activeLeaves(region)) leaves.push(`${snapshot.path[0]}.${regionName}.${leaf}`);
113
+ return leaves;
114
+ }
115
+ //#endregion
116
+ //#region src/tags.ts
117
+ function tag(...stateNames) {
118
+ const names = stateNames.map((s) => s.name);
119
+ return { has: (snapshot) => names.some((name) => isIn(snapshot, name)) };
120
+ }
121
+ //#endregion
122
+ export { ActorMap, activeLeaves, broadcast, events, isIn, matches, states, tag, withPromise, withTimeout };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@mantaq/sugar",
3
+ "version": "0.0.1",
4
+ "description": "Userland sugar for mantaq actors — matches, effect helpers, transition utilities.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/AndersCan/mantaq",
9
+ "directory": "packages/sugar"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "type": "module",
15
+ "exports": {
16
+ ".": "./dist/index.mjs",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "dependencies": {
23
+ "@mantaq/core": "0.0.1",
24
+ "@mantaq/utils": "0.0.1"
25
+ },
26
+ "devDependencies": {
27
+ "@stryker-mutator/core": "^9",
28
+ "@stryker-mutator/vitest-runner": "^9",
29
+ "@types/node": "^26.2.0",
30
+ "@typescript/native-preview": "7.0.0-dev.20260707.2",
31
+ "@vitest/coverage-v8": "4.1.10",
32
+ "typescript": "^7.0.2",
33
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.2.8",
34
+ "vite-plus": "0.2.8",
35
+ "vitest": "4.1.10",
36
+ "@mantaq/pbt": "0.0.1"
37
+ },
38
+ "scripts": {
39
+ "build": "vp pack",
40
+ "dev": "vp pack --watch",
41
+ "test": "vp test",
42
+ "check": "vp check",
43
+ "mutation": "stryker run",
44
+ "coverage": "vp test run --coverage"
45
+ }
46
+ }