@mantaq/sugar 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -307,6 +307,29 @@ broadcast(map, { type: "ping" });
307
307
 
308
308
  Works with `ActorMap` or any object implementing `{ keys(): string[]; send(key, event): void }`.
309
309
 
310
+ ### `onOutput(actor, handler)`
311
+
312
+ Route an actor's emitted outputs to a handler. `regions` auto-wire child
313
+ outputs into the parent; ActorMap children do not — this is the public
314
+ wrapper for that wiring seam, no internal import needed:
315
+
316
+ ```ts
317
+ import { ActorMap, onOutput } from "@mantaq/sugar";
318
+
319
+ const requests = new ActorMap(
320
+ (id) => {
321
+ const child = createHandler(id);
322
+ onOutput(child, (e) => {
323
+ if (result.is(e)) parent.send(e);
324
+ });
325
+ return child;
326
+ },
327
+ { autoReap: true },
328
+ );
329
+ ```
330
+
331
+ The `is()` guard narrows the output to the receiver's declared input.
332
+
310
333
  ### `tag(...stateRefs)`
311
334
 
312
335
  Group multiple `StateRef`s and test if a snapshot matches any of them. Useful for UI state grouping.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnyActor, AnyEventRef, EffectInput, EventRef, InternalEvent, Snapshot, StateRef } from "@mantaq/core";
1
+ import { Actor, ActorBuilder, ActorOptions, AnyActor, AnyEventRef, AnyStateRef, Clock, EffectInput, EventRef, InitialState, InternalEvent, Snapshot, StateRef } from "@mantaq/core";
2
2
  //#region src/actors/matches.d.ts
3
3
  declare function matches(actor: {
4
4
  snapshot(): Snapshot;
@@ -60,6 +60,16 @@ declare function states<const T extends StatesArg[]>(...entries: T): StatesRecor
60
60
  //#region src/event.d.ts
61
61
  declare function events<T extends string>(...names: T[]): { [K in T]: EventRef<K>; };
62
62
  //#endregion
63
+ //#region src/output.d.ts
64
+ /**
65
+ * Route an actor's emitted outputs to a handler.
66
+ *
67
+ * `regions` auto-wire child outputs into the parent. ActorMap children do
68
+ * not — this is the public wrapper for that wiring seam. One call in the
69
+ * factory connects a child's declared outputs to a receiver.
70
+ */
71
+ declare function onOutput(actor: AnyActor, handler: (event: InternalEvent) => void): void;
72
+ //#endregion
63
73
  //#region src/effects/promise.d.ts
64
74
  type EmitFn = (event: {
65
75
  type: string;
@@ -85,8 +95,52 @@ interface Tag {
85
95
  }
86
96
  declare function tag(...stateNames: StateRef<string, unknown>[]): Tag;
87
97
  //#endregion
98
+ //#region src/parts.d.ts
99
+ interface ActorSpec {
100
+ states: readonly AnyStateRef[];
101
+ inputs: readonly AnyEventRef[];
102
+ internal?: readonly AnyEventRef[];
103
+ outputs?: readonly AnyEventRef[];
104
+ context?: unknown;
105
+ initial: AnyStateRef | {
106
+ state: AnyStateRef;
107
+ payload?: unknown;
108
+ };
109
+ clock?: Clock;
110
+ regions?: Record<string, AnyActor>;
111
+ internalBudget?: number;
112
+ }
113
+ type NotUndef<T> = [Exclude<T, undefined>] extends [never] ? never : Exclude<T, undefined>;
114
+ type InternalOf<S extends ActorSpec> = "internal" extends keyof S ? [NotUndef<S["internal"]>] extends [never] ? readonly [] : NotUndef<S["internal"]> : readonly [];
115
+ type OutputsOf<S extends ActorSpec> = "outputs" extends keyof S ? [NotUndef<S["outputs"]>] extends [never] ? readonly [] : NotUndef<S["outputs"]> : readonly [];
116
+ type ContextOf<S extends ActorSpec> = "context" extends keyof S ? [NotUndef<S["context"]>] extends [never] ? Record<string, unknown> : NotUndef<S["context"]> : Record<string, unknown>;
117
+ type BuilderOf<S extends ActorSpec> = ActorBuilder<S["states"], S["inputs"], InternalOf<S>, OutputsOf<S>, ContextOf<S>>;
118
+ type Fragment<S extends ActorSpec> = (m: BuilderOf<S>) => void;
119
+ declare function definePart<S extends ActorSpec = never>(fn: (m: BuilderOf<S>) => void): Fragment<S>;
120
+ declare function use<S extends ActorSpec>(m: BuilderOf<S>, part: Fragment<S>): void;
121
+ type Part<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext> = (m: ActorBuilder<States, Inputs, Internal, Outputs, ActorContext>) => void;
122
+ type SpecValue<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext, Initial> = {
123
+ states: States;
124
+ inputs: Inputs;
125
+ internal?: Internal;
126
+ outputs?: Outputs;
127
+ context?: ActorContext;
128
+ clock?: Clock;
129
+ regions?: Record<string, AnyActor>;
130
+ internalBudget?: number;
131
+ initial: Initial;
132
+ };
133
+ /**
134
+ * Construct the actor's static spec — states, inputs, internal/output events,
135
+ * context, initial state, clock, regions and budget. State/event tuples and
136
+ * the initial ref narrow to literals; the context stays mutable, so
137
+ * `definePart` handlers read and write it with no `as` casts.
138
+ */
139
+ declare function actorSpec<const States extends readonly AnyStateRef[], const Inputs extends readonly AnyEventRef[], const Internal extends readonly AnyEventRef[] = readonly [], const Outputs extends readonly AnyEventRef[] = readonly [], ActorContext = Record<string, unknown>, const Initial extends InitialState<States[number]> = InitialState<States[number]>>(spec: SpecValue<States, Inputs, Internal, Outputs, ActorContext, Initial>): SpecValue<States, Inputs, Internal, Outputs, ActorContext, Initial>;
140
+ declare function withParts<const States extends readonly AnyStateRef[], const Inputs extends readonly AnyEventRef[], const Internal extends readonly AnyEventRef[] = readonly [], const Outputs extends readonly AnyEventRef[] = readonly [], ActorContext = Record<string, unknown>>(base: Omit<ActorOptions<States, Inputs, Internal, Outputs, ActorContext>, "setup">, parts: Part<States, Inputs, Internal, Outputs, ActorContext> | readonly Part<States, Inputs, Internal, Outputs, ActorContext>[]): Actor<States, Inputs, Internal, Outputs, ActorContext>;
141
+ //#endregion
88
142
  //#region src/snapshot.d.ts
89
143
  declare function isIn(snapshot: Snapshot, stateRefName: string): boolean;
90
144
  declare function activeLeaves(snapshot: Snapshot): string[];
91
145
  //#endregion
92
- export { ActorMap, type EventLike, type SendableEvent, type SendableMap, activeLeaves, broadcast, events, isIn, matches, states, tag, withPromise, withTimeout };
146
+ export { ActorMap, type ActorSpec, type BuilderOf, type EventLike, type Fragment, type Part, type SendableEvent, type SendableMap, activeLeaves, actorSpec, broadcast, definePart, events, isIn, matches, onOutput, states, tag, use, withParts, withPromise, withTimeout };
package/dist/index.mjs CHANGED
@@ -1,6 +1,4 @@
1
- import { abortEffects } from "@mantaq/core/internal";
2
- import { Either } from "@mantaq/utils";
3
- import { event, state } from "@mantaq/core";
1
+ import { Actor, event, state } from "@mantaq/core";
4
2
  //#region src/actors/matches.ts
5
3
  function matchSnapshot(snapshot, parts, index) {
6
4
  const stateName = snapshot.path[0];
@@ -45,6 +43,7 @@ var ActorMap = class {
45
43
  }
46
44
  const off = child.on("done", () => {
47
45
  if (this.#actors.get(key) === child) this.#actors.delete(key);
46
+ this.#reapers.delete(key);
48
47
  });
49
48
  this.#reapers.set(key, off);
50
49
  }
@@ -53,10 +52,7 @@ var ActorMap = class {
53
52
  this.#actors.get(key)?.send(event);
54
53
  }
55
54
  kill(key) {
56
- const actor = this.#actors.get(key);
57
- if (actor) Either.match(abortEffects(actor), (err) => {
58
- throw new Error(err.message);
59
- }, () => {});
55
+ this.#actors.get(key)?.dispose();
60
56
  this.#reapers.get(key)?.();
61
57
  this.#reapers.delete(key);
62
58
  this.#actors.delete(key);
@@ -96,6 +92,18 @@ function events(...names) {
96
92
  return result;
97
93
  }
98
94
  //#endregion
95
+ //#region src/output.ts
96
+ /**
97
+ * Route an actor's emitted outputs to a handler.
98
+ *
99
+ * `regions` auto-wire child outputs into the parent. ActorMap children do
100
+ * not — this is the public wrapper for that wiring seam. One call in the
101
+ * factory connects a child's declared outputs to a receiver.
102
+ */
103
+ function onOutput(actor, handler) {
104
+ actor.on("output", handler);
105
+ }
106
+ //#endregion
99
107
  //#region src/effects/promise.ts
100
108
  function withPromise(promise, signal, emit, events) {
101
109
  promise.then((data) => {
@@ -137,4 +145,30 @@ function tag(...stateNames) {
137
145
  return { has: (snapshot) => names.some((name) => isIn(snapshot, name)) };
138
146
  }
139
147
  //#endregion
140
- export { ActorMap, activeLeaves, broadcast, events, isIn, matches, states, tag, withPromise, withTimeout };
148
+ //#region src/parts.ts
149
+ function definePart(fn) {
150
+ return fn;
151
+ }
152
+ function use(m, part) {
153
+ part(m);
154
+ }
155
+ /**
156
+ * Construct the actor's static spec — states, inputs, internal/output events,
157
+ * context, initial state, clock, regions and budget. State/event tuples and
158
+ * the initial ref narrow to literals; the context stays mutable, so
159
+ * `definePart` handlers read and write it with no `as` casts.
160
+ */
161
+ function actorSpec(spec) {
162
+ return spec;
163
+ }
164
+ function withParts(base, parts) {
165
+ const list = Array.isArray(parts) ? parts : [parts];
166
+ return new Actor({
167
+ ...base,
168
+ setup: (m) => {
169
+ for (const part of list) part(m);
170
+ }
171
+ });
172
+ }
173
+ //#endregion
174
+ export { ActorMap, activeLeaves, actorSpec, broadcast, definePart, events, isIn, matches, onOutput, states, tag, use, withParts, withPromise, withTimeout };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantaq/sugar",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Userland sugar for mantaq actors — matches, effect helpers, transition utilities.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,8 +20,7 @@
20
20
  "access": "public"
21
21
  },
22
22
  "dependencies": {
23
- "@mantaq/core": "0.2.0",
24
- "@mantaq/utils": "0.0.2"
23
+ "@mantaq/core": "0.3.0"
25
24
  },
26
25
  "devDependencies": {
27
26
  "@stryker-mutator/core": "^9",