@mantaq/sugar 1.0.0 → 2.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/README.md CHANGED
@@ -387,6 +387,36 @@ withPromise(fetchData(), input.signal, input.emit, {
387
387
  });
388
388
  ```
389
389
 
390
+ #### Errors as values
391
+
392
+ When a port returns `Promise<Either<PortError, Loaded>>`, map the result with `Either.match` inside `success`. No new helper is needed, and both branches stay typed without a cast:
393
+
394
+ ```ts
395
+ import { Either } from "@mantaq/utils";
396
+ import { withPromise } from "@mantaq/sugar";
397
+
398
+ m.effect(loading, {
399
+ name: "load",
400
+ sends: [loaded, failed, loadRejected],
401
+ fn: (input) =>
402
+ withPromise({
403
+ promise: port.load(),
404
+ signal: input.signal,
405
+ emit: input.emit,
406
+ events: {
407
+ success: (result) =>
408
+ Either.match(result, {
409
+ onLeft: (e): LoadEvent => failed.create(e), // e: PortError
410
+ onRight: (v): LoadEvent => loaded.create(v), // v: Loaded
411
+ }),
412
+ error: (err) => loadRejected.create({ message: String(err) }),
413
+ },
414
+ }),
415
+ });
416
+ ```
417
+
418
+ `LoadEvent` is the union of the two event shapes; annotating both handlers with it lets `Either.match` infer one result type. A rejected promise still goes through `error`, and an effect aborted before the promise settles emits nothing. See `src/effects/promise.either.test.ts`.
419
+
390
420
  ### `withTimeout(ms, input, eventFn)`
391
421
 
392
422
  Schedule a timeout event through the actor's clock. Aborts cleanly if the actor is destroyed before timeout fires.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Actor, ActorBuilder, ActorOptions, AnyActor, AnyEventRef, AnyStateRef, Clock, EffectInput, EventRef, InitialState, InternalEvent, Snapshot, StateRef } from "@mantaq/core";
2
- //#region src/actors/matches.d.ts
3
2
  interface MatchTarget {
4
3
  snapshot(): Snapshot;
5
4
  }
@@ -8,9 +7,7 @@ interface MatchTarget {
8
7
  * pattern as one dotted string or as separate segments. A segment containing
9
8
  * dots matches states whose own name contains dots.
10
9
  */
11
- declare function matches(actor: MatchTarget, ...pattern: [pattern: string]): boolean;
12
- //#endregion
13
- //#region src/transitions/broadcast.d.ts
10
+ export declare function matches(actor: MatchTarget, ...pattern: [pattern: string]): boolean;
14
11
  /**
15
12
  * Plain event shape sugar helpers accept next to core event refs.
16
13
  */
@@ -30,9 +27,7 @@ interface SendableMap<T extends SendableEvent = SendableEvent> {
30
27
  * Fan every given event out to all children of the map, once per key, in
31
28
  * key order.
32
29
  */
33
- declare function broadcast<const T extends SendableEvent>(map: SendableMap<T>, ...events: [event: T]): void;
34
- //#endregion
35
- //#region src/actors/actor-map.d.ts
30
+ export declare function broadcast<const T extends SendableEvent>(map: SendableMap<T>, ...events: [event: T]): void;
36
31
  /**
37
32
  * Minimal actor surface the map needs. Real actors satisfy this
38
33
  * structurally — tests can substitute tiny fakes.
@@ -63,11 +58,9 @@ interface ActorMap extends SendableMap<SendableEvent> {
63
58
  snapshot(key: string): Snapshot | undefined;
64
59
  readonly size: number;
65
60
  }
66
- declare function createActorMap(factory: (id: string) => ActorMapChild, { autoReap }?: {
61
+ export declare function createActorMap(factory: (id: string) => ActorMapChild, { autoReap }?: {
67
62
  autoReap?: boolean;
68
63
  }): ActorMap;
69
- //#endregion
70
- //#region src/state.d.ts
71
64
  interface StatesEntry {
72
65
  name: string;
73
66
  final?: boolean;
@@ -80,12 +73,8 @@ type FinalOf<T> = T extends {
80
73
  final: true;
81
74
  } ? true : false;
82
75
  type StatesRecord<T extends StatesArg[]> = { [K in T[number] as NameOf<K>]: StateRef<NameOf<K>, unknown, FinalOf<K>>; };
83
- declare function states<const T extends StatesArg[]>(...entries: T): StatesRecord<T>;
84
- //#endregion
85
- //#region src/event.d.ts
86
- declare function events<T extends string>(...names: T[]): { [K in T]: EventRef<K>; };
87
- //#endregion
88
- //#region src/output.d.ts
76
+ export declare function states<const T extends StatesArg[]>(...entries: T): StatesRecord<T>;
77
+ export declare function events<T extends string>(...names: T[]): { [K in T]: EventRef<K>; };
89
78
  /**
90
79
  * Route an actor's emitted outputs to one or more handlers.
91
80
  *
@@ -94,43 +83,30 @@ declare function events<T extends string>(...names: T[]): { [K in T]: EventRef<K
94
83
  * factory connects a child's declared outputs to a receiver — pass several
95
84
  * handlers to feed multiple receivers.
96
85
  */
97
- declare function onOutput(actor: AnyActor, ...handlers: [handler: (event: InternalEvent) => void]): void;
98
- //#endregion
99
- //#region src/effects/promise.d.ts
100
- type EmitFn = (event: {
86
+ export declare function onOutput(actor: AnyActor, ...handlers: [handler: (event: InternalEvent) => void]): void;
87
+ type AnyEvent = {
101
88
  type: string;
102
89
  payload?: unknown;
103
- }) => void;
104
- interface WithPromiseOptions<T> {
90
+ };
91
+ /** `Emit`: events the effect may send; follows the effect's `sends` list when declared. */
92
+ interface WithPromiseOptions<T, Emit = AnyEvent> {
105
93
  promise: Promise<T>;
106
94
  signal: AbortSignal;
107
- emit: EmitFn;
95
+ emit: (event: Emit) => void;
108
96
  events: {
109
- success: (data: T) => {
110
- type: string;
111
- payload?: unknown;
112
- };
113
- error: (err: unknown) => {
114
- type: string;
115
- payload?: unknown;
116
- };
97
+ success: (data: T) => NoInfer<Emit>;
98
+ error: (err: unknown) => NoInfer<Emit>;
117
99
  };
118
100
  }
119
- declare function withPromise<T>(options: WithPromiseOptions<T>): Promise<void>;
120
- //#endregion
121
- //#region src/effects/timeout.d.ts
122
- declare function withTimeout<ActorContext>(durationMs: number, options: {
123
- input: EffectInput<ActorContext>;
124
- event: () => InternalEvent;
101
+ export declare function withPromise<T, Emit = AnyEvent>(options: WithPromiseOptions<T, Emit>): Promise<void>;
102
+ export declare function withTimeout<ActorContext, Payload = unknown, Emit extends InternalEvent = InternalEvent>(durationMs: number, options: {
103
+ input: EffectInput<ActorContext, Payload, Emit>;
104
+ event: () => NoInfer<Emit>;
125
105
  }): void;
126
- //#endregion
127
- //#region src/tags.d.ts
128
106
  interface Tag {
129
107
  has(snapshot: Snapshot): boolean;
130
108
  }
131
- declare function tag(...stateRefs: StateRef<string, unknown>[]): Tag;
132
- //#endregion
133
- //#region src/parts.d.ts
109
+ export declare function tag(...stateRefs: StateRef<string, unknown>[]): Tag;
134
110
  interface ActorSpec {
135
111
  states: readonly AnyStateRef[];
136
112
  inputs: readonly AnyEventRef[];
@@ -151,9 +127,9 @@ type OutputsOf<S extends ActorSpec> = "outputs" extends keyof S ? [NotUndef<S["o
151
127
  type ContextOf<S extends ActorSpec> = "context" extends keyof S ? [NotUndef<S["context"]>] extends [never] ? Record<string, unknown> : NotUndef<S["context"]> : Record<string, unknown>;
152
128
  type BuilderOf<S extends ActorSpec> = ActorBuilder<S["states"], S["inputs"], InternalOf<S>, OutputsOf<S>, ContextOf<S>>;
153
129
  type Fragment<S extends ActorSpec> = (builder: BuilderOf<S>) => void;
154
- declare function definePart<S extends ActorSpec = never>(part: (builder: BuilderOf<S>) => void): Fragment<S>;
130
+ export declare function definePart<S extends ActorSpec = never>(part: (builder: BuilderOf<S>) => void): Fragment<S>;
155
131
  /** Apply one or more parts to a builder, in order. */
156
- declare function use<S extends ActorSpec>(builder: BuilderOf<S>, ...parts: [Fragment<S>, ...Fragment<S>[]]): void;
132
+ export declare function use<S extends ActorSpec>(builder: BuilderOf<S>, ...parts: [Fragment<S>, ...Fragment<S>[]]): void;
157
133
  type Part<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext> = (builder: ActorBuilder<States, Inputs, Internal, Outputs, ActorContext>) => void;
158
134
  type SpecValue<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext, Initial> = {
159
135
  states: States;
@@ -172,18 +148,15 @@ type SpecValue<States extends readonly AnyStateRef[], Inputs extends readonly An
172
148
  * the initial ref narrow to literals while the context stays mutable, so
173
149
  * `definePart` handlers read and write it with no `as` casts.
174
150
  */
175
- 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>;
151
+ export 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>;
176
152
  /**
177
153
  * Build an actor whose setup applies every given part in order. Pass parts
178
154
  * as separate arguments — single part or many.
179
155
  */
180
- 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>[]): Actor<States, Inputs, Internal, Outputs, ActorContext>;
181
- //#endregion
182
- //#region src/snapshot.d.ts
156
+ export 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>[]): Actor<States, Inputs, Internal, Outputs, ActorContext>;
183
157
  /**
184
158
  * True when any of the named states is active anywhere in the snapshot tree.
185
159
  */
186
- declare function isIn(snapshot: Snapshot, ...stateNames: [stateName: string]): boolean;
187
- declare function activeLeaves(snapshot: Snapshot): string[];
188
- //#endregion
189
- export { type ActorMap, type ActorMapChild, type ActorSpec, type BuilderOf, type EventLike, type Fragment, type Part, type SendableEvent, type SendableMap, type WithPromiseOptions, activeLeaves, actorSpec, broadcast, createActorMap, definePart, events, isIn, matches, onOutput, states, tag, use, withParts, withPromise, withTimeout };
160
+ export declare function isIn(snapshot: Snapshot, ...stateNames: [stateName: string]): boolean;
161
+ export declare function activeLeaves(snapshot: Snapshot): string[];
162
+ export type { ActorMap, ActorMapChild, ActorSpec, BuilderOf, EventLike, Fragment, Part, SendableEvent, SendableMap, WithPromiseOptions };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Actor, event, state } from "@mantaq/core";
2
- //#region src/actors/matches.ts
2
+ // src/actors/matches.ts
3
3
  function matchFrom(patternParts) {
4
4
  function matchRegion(options) {
5
5
  const stateName = options.snapshot.path[0];
@@ -31,8 +31,7 @@ function matches(actor, ...pattern) {
31
31
  index: 0
32
32
  });
33
33
  }
34
- //#endregion
35
- //#region src/actors/actor-map.ts
34
+ // src/actors/actor-map.ts
36
35
  function createActorMap(factory, { autoReap } = {}) {
37
36
  const actors = /* @__PURE__ */ new Map();
38
37
  const reapers = /* @__PURE__ */ new Map();
@@ -100,8 +99,7 @@ function createActorMap(factory, { autoReap } = {}) {
100
99
  }
101
100
  };
102
101
  }
103
- //#endregion
104
- //#region src/parse-refs.ts
102
+ // src/parse-refs.ts
105
103
  /**
106
104
  * Boundary utility for the `states()` and `events()` helpers. Ref records are
107
105
  * built dynamically, keyed by a literal name tuple. TypeScript cannot express
@@ -114,8 +112,7 @@ function parseStateRefs(record) {
114
112
  function parseEventRefs(record) {
115
113
  return record;
116
114
  }
117
- //#endregion
118
- //#region src/state.ts
115
+ // src/state.ts
119
116
  function states(...entries) {
120
117
  const result = {};
121
118
  for (const entry of entries) {
@@ -125,15 +122,13 @@ function states(...entries) {
125
122
  }
126
123
  return parseStateRefs(result);
127
124
  }
128
- //#endregion
129
- //#region src/event.ts
125
+ // src/event.ts
130
126
  function events(...names) {
131
127
  const result = {};
132
128
  for (const name of names) result[name] = event(name)();
133
129
  return parseEventRefs(result);
134
130
  }
135
- //#endregion
136
- //#region src/output.ts
131
+ // src/output.ts
137
132
  /**
138
133
  * Route an actor's emitted outputs to one or more handlers.
139
134
  *
@@ -145,8 +140,7 @@ function events(...names) {
145
140
  function onOutput(actor, ...handlers) {
146
141
  for (const handler of handlers) actor.on("output", { fn: handler });
147
142
  }
148
- //#endregion
149
- //#region src/effects/promise.ts
143
+ // src/effects/promise.ts
150
144
  function withPromise(options) {
151
145
  const { promise, signal, emit, events } = options;
152
146
  return promise.then((data) => {
@@ -155,20 +149,19 @@ function withPromise(options) {
155
149
  if (!signal.aborted) emit(events.error(err));
156
150
  });
157
151
  }
158
- //#endregion
159
- //#region src/effects/timeout.ts
152
+ // src/effects/timeout.ts
160
153
  function withTimeout(durationMs, options) {
161
154
  const { input, event } = options;
162
155
  input.clock.setTimeout(durationMs, {
163
156
  signal: input.signal,
157
+ eventName: event().type,
164
158
  cb: () => {
165
159
  if (input.signal.aborted) return;
166
160
  input.emit(event());
167
161
  }
168
162
  });
169
163
  }
170
- //#endregion
171
- //#region src/transitions/broadcast.ts
164
+ // src/transitions/broadcast.ts
172
165
  /**
173
166
  * Fan every given event out to all children of the map, once per key, in
174
167
  * key order.
@@ -176,8 +169,7 @@ function withTimeout(durationMs, options) {
176
169
  function broadcast(map, ...events) {
177
170
  for (const event of events) for (const key of map.keys()) map.send(key, event);
178
171
  }
179
- //#endregion
180
- //#region src/snapshot.ts
172
+ // src/snapshot.ts
181
173
  function isInState(options) {
182
174
  if (options.snapshot.path[0] === options.stateName) return true;
183
175
  for (const region of Object.values(options.snapshot.regions)) if (isInState({
@@ -201,14 +193,12 @@ function activeLeaves(snapshot) {
201
193
  for (const [regionName, region] of Object.entries(snapshot.regions)) for (const leaf of activeLeaves(region)) leaves.push(`${snapshot.path[0]}.${regionName}.${leaf}`);
202
194
  return leaves;
203
195
  }
204
- //#endregion
205
- //#region src/tags.ts
196
+ // src/tags.ts
206
197
  function tag(...stateRefs) {
207
198
  const names = stateRefs.map((ref) => ref.name);
208
199
  return { has: (snapshot) => names.some((name) => isIn(snapshot, name)) };
209
200
  }
210
- //#endregion
211
- //#region src/parts.ts
201
+ // src/parts.ts
212
202
  function definePart(part) {
213
203
  return part;
214
204
  }
@@ -237,5 +227,4 @@ function withParts(base, ...parts) {
237
227
  }
238
228
  });
239
229
  }
240
- //#endregion
241
230
  export { activeLeaves, actorSpec, broadcast, createActorMap, 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": "1.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Userland sugar for mantaq actors — matches, effect helpers, transition utilities.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,19 +20,20 @@
20
20
  "access": "public"
21
21
  },
22
22
  "dependencies": {
23
- "@mantaq/core": "0.4.0"
23
+ "@mantaq/core": "0.5.1"
24
24
  },
25
25
  "devDependencies": {
26
- "@stryker-mutator/core": "^9",
27
- "@stryker-mutator/vitest-runner": "^9",
26
+ "@stryker-mutator/core": "^10",
27
+ "@stryker-mutator/vitest-runner": "^10",
28
28
  "@types/node": "^26.2.0",
29
29
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
30
- "@vitest/coverage-v8": "4.1.10",
30
+ "@vitest/coverage-v8": "5.0.5",
31
31
  "typescript": "6.0.3",
32
- "vite": "npm:@voidzero-dev/vite-plus-core@0.2.8",
33
- "vite-plus": "0.2.8",
34
- "vitest": "4.1.10",
35
- "@mantaq/pbt": "0.0.3"
32
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
33
+ "vite-plus": "1.4.0",
34
+ "vitest": "5.0.5",
35
+ "@mantaq/pbt": "0.0.3",
36
+ "@mantaq/utils": "0.0.4"
36
37
  },
37
38
  "scripts": {
38
39
  "build": "vp pack",