@mantaq/sugar 0.0.2 → 0.2.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
@@ -89,7 +89,7 @@ import { events } from "@mantaq/sugar";
89
89
 
90
90
  const e = events("fetch", "resolve", "fail");
91
91
 
92
- e.fetch.create(); // { id: "fetch" }
92
+ e.fetch.create(); // { type: "fetch" }
93
93
  e.resolve.is(emittedEvent); // boolean
94
94
 
95
95
  const actor = new Actor({
@@ -203,7 +203,7 @@ map.kill("child1");
203
203
 
204
204
  ```ts
205
205
  map.spawn("child1", () => createChild());
206
- map.spawn("child1", () => createChild()); // overwrites silently — previous child lost
206
+ map.spawn("child1", () => createChild()); // warns, aborts the previous child, replaces
207
207
  ```
208
208
 
209
209
  `ensure()` is idempotent — safe to call repeatedly. `spawn()` always replaces.
@@ -217,8 +217,8 @@ import { withPromise } from "@mantaq/sugar";
217
217
 
218
218
  // Correct — abort-aware
219
219
  withPromise(fetchData(), input.signal, input.emit, {
220
- success: (data) => ({ id: "loaded", data }),
221
- error: (err) => ({ id: "loadFailed", error: String(err) }),
220
+ success: (data) => ({ type: "loaded", payload: data }),
221
+ error: (err) => ({ type: "loadFailed", payload: String(err) }),
222
222
  });
223
223
  ```
224
224
 
@@ -227,14 +227,14 @@ withPromise(fetchData(), input.signal, input.emit, {
227
227
  ```ts
228
228
  fetchData()
229
229
  .then((data) => {
230
- input.emit({ id: "loaded", data }); // fires even if actor destroyed
230
+ input.emit({ type: "loaded", payload: data }); // fires even if actor destroyed
231
231
  })
232
232
  .catch((err) => {
233
- input.emit({ id: "loadFailed", error: String(err) }); // same problem
233
+ input.emit({ type: "loadFailed", payload: String(err) }); // same problem
234
234
  });
235
235
  ```
236
236
 
237
- `withPromise` checks `isAborted(signal)` before each emit. Manual chains fire events into destroyed actors.
237
+ `withPromise` checks `signal.aborted` before each emit. Manual chains fire events into destroyed actors.
238
238
 
239
239
  ## Helpers
240
240
 
@@ -302,7 +302,7 @@ Send an event to every key in an `ActorMap` (or any `SendableMap`):
302
302
  ```ts
303
303
  import { broadcast } from "@mantaq/sugar";
304
304
 
305
- broadcast(map, { id: "ping" });
305
+ broadcast(map, { type: "ping" });
306
306
  ```
307
307
 
308
308
  Works with `ActorMap` or any object implementing `{ keys(): string[]; send(key, event): void }`.
@@ -344,7 +344,7 @@ Batch-create `EventRef`s as a typed record:
344
344
  import { events } from "@mantaq/sugar";
345
345
 
346
346
  const e = events("click", "submit");
347
- e.click.create(); // { id: "click" }
347
+ e.click.create(); // { type: "click" }
348
348
  e.click.is(emittedEvent); // boolean
349
349
  ```
350
350
 
@@ -356,8 +356,8 @@ Bridge a promise into actor events. Emits `success` on resolve, `error` on rejec
356
356
  import { withPromise } from "@mantaq/sugar";
357
357
 
358
358
  withPromise(fetchData(), input.signal, input.emit, {
359
- success: (data) => ({ id: "loaded", data }),
360
- error: (err) => ({ id: "loadFailed", error: String(err) }),
359
+ success: (data) => ({ type: "loaded", payload: data }),
360
+ error: (err) => ({ type: "loadFailed", payload: String(err) }),
361
361
  });
362
362
  ```
363
363
 
@@ -368,7 +368,7 @@ Schedule a timeout event through the actor's clock. Aborts cleanly if the actor
368
368
  ```ts
369
369
  import { withTimeout } from "@mantaq/sugar";
370
370
 
371
- withTimeout(5000, input, () => ({ id: "timeout", reason: "exceeded" }));
371
+ withTimeout(5000, input, () => ({ type: "timeout", payload: { reason: "exceeded" } }));
372
372
  ```
373
373
 
374
374
  ## Migration from Core
package/dist/index.d.mts CHANGED
@@ -6,8 +6,8 @@ declare function matches(actor: {
6
6
  //#endregion
7
7
  //#region src/transitions/broadcast.d.ts
8
8
  type EventLike = {
9
- id: string;
10
- [key: string]: unknown;
9
+ type: string;
10
+ payload?: unknown;
11
11
  };
12
12
  type SendableEvent = AnyEventRef | EventLike;
13
13
  interface SendableMap<T extends SendableEvent = SendableEvent> {
@@ -17,38 +17,62 @@ interface SendableMap<T extends SendableEvent = SendableEvent> {
17
17
  declare function broadcast<const T extends SendableEvent>(map: SendableMap<T>, event: T): void;
18
18
  //#endregion
19
19
  //#region src/actors/actor-map.d.ts
20
+ interface ActorMapOptions {
21
+ /**
22
+ * When true, a child that reaches a final state (or dies into `__error`)
23
+ * is removed from the map automatically. Without it, completed children
24
+ * linger until explicitly `kill`ed.
25
+ */
26
+ autoReap?: boolean;
27
+ }
28
+ /**
29
+ * Keyed registry of one actor type. `spawn(key)` builds a fresh instance via
30
+ * the factory — same actor shape, keyed by id. The factory receives the key.
31
+ */
20
32
  declare class ActorMap implements SendableMap<SendableEvent> {
21
33
  #private;
22
- constructor(parent?: AnyActor);
23
- spawn(key: string, factory: () => AnyActor): void;
34
+ constructor(factory: (id: string) => AnyActor, options?: ActorMapOptions);
35
+ spawn(key: string): void;
24
36
  send(key: string, event: SendableEvent): void;
25
37
  kill(key: string): void;
26
38
  get size(): number;
27
39
  has(key: string): boolean;
28
40
  keys(): string[];
29
- ensure(key: string, factory: () => AnyActor): void;
41
+ ensure(key: string): void;
30
42
  snapshot(key: string): Snapshot | undefined;
31
43
  }
32
44
  //#endregion
33
45
  //#region src/state.d.ts
34
- declare function states<T extends string>(...names: T[]): { [K in T]: StateRef<K>; };
46
+ interface StatesEntry {
47
+ name: string;
48
+ final?: boolean;
49
+ }
50
+ type StatesArg = string | StatesEntry;
51
+ type NameOf<T> = T extends string ? T : T extends {
52
+ name: infer N extends string;
53
+ } ? N : never;
54
+ type FinalOf<T> = T extends {
55
+ final: true;
56
+ } ? true : false;
57
+ type StatesRecord<T extends StatesArg[]> = { [K in T[number] as NameOf<K>]: StateRef<NameOf<K>, unknown, FinalOf<K>>; };
58
+ declare function states<const T extends StatesArg[]>(...entries: T): StatesRecord<T>;
35
59
  //#endregion
36
60
  //#region src/event.d.ts
37
61
  declare function events<T extends string>(...names: T[]): { [K in T]: EventRef<K>; };
38
62
  //#endregion
39
63
  //#region src/effects/promise.d.ts
40
64
  type EmitFn = (event: {
41
- id: string;
42
- [key: string]: unknown;
65
+ type: string;
66
+ payload?: unknown;
43
67
  }) => void;
44
68
  declare function withPromise<T>(promise: Promise<T>, signal: AbortSignal, emit: EmitFn, events: {
45
69
  success: (data: T) => {
46
- id: string;
47
- [key: string]: unknown;
70
+ type: string;
71
+ payload?: unknown;
48
72
  };
49
73
  error: (err: unknown) => {
50
- id: string;
51
- [key: string]: unknown;
74
+ type: string;
75
+ payload?: unknown;
52
76
  };
53
77
  }): void;
54
78
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { abortEffects, setOutputHandler } from "@mantaq/core/internal";
1
+ import { abortEffects } from "@mantaq/core/internal";
2
2
  import { Either } from "@mantaq/utils";
3
3
  import { event, state } from "@mantaq/core";
4
4
  //#region src/actors/matches.ts
@@ -21,29 +21,44 @@ function matches(actor, pattern) {
21
21
  }
22
22
  //#endregion
23
23
  //#region src/actors/actor-map.ts
24
+ /**
25
+ * Keyed registry of one actor type. `spawn(key)` builds a fresh instance via
26
+ * the factory — same actor shape, keyed by id. The factory receives the key.
27
+ */
24
28
  var ActorMap = class {
25
29
  #actors = /* @__PURE__ */ new Map();
26
- #parent;
27
- constructor(parent) {
28
- this.#parent = parent ?? null;
30
+ #reapers = /* @__PURE__ */ new Map();
31
+ #factory;
32
+ #autoReap;
33
+ constructor(factory, options = {}) {
34
+ this.#factory = factory;
35
+ this.#autoReap = options.autoReap === true;
29
36
  }
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), () => {});
37
+ spawn(key) {
38
+ if (this.#actors.has(key)) this.kill(key);
39
+ const child = this.#factory(key);
39
40
  this.#actors.set(key, child);
41
+ if (this.#autoReap) {
42
+ if (child.snapshot().done) {
43
+ this.#actors.delete(key);
44
+ return;
45
+ }
46
+ const off = child.on("done", () => {
47
+ if (this.#actors.get(key) === child) this.#actors.delete(key);
48
+ });
49
+ this.#reapers.set(key, off);
50
+ }
40
51
  }
41
52
  send(key, event) {
42
53
  this.#actors.get(key)?.send(event);
43
54
  }
44
55
  kill(key) {
45
56
  const actor = this.#actors.get(key);
46
- if (actor) Either.match(abortEffects(actor), (err) => console.error(err.message), () => {});
57
+ if (actor) Either.match(abortEffects(actor), (err) => {
58
+ throw new Error(err.message);
59
+ }, () => {});
60
+ this.#reapers.get(key)?.();
61
+ this.#reapers.delete(key);
47
62
  this.#actors.delete(key);
48
63
  }
49
64
  get size() {
@@ -55,8 +70,8 @@ var ActorMap = class {
55
70
  keys() {
56
71
  return [...this.#actors.keys()];
57
72
  }
58
- ensure(key, factory) {
59
- if (!this.#actors.has(key)) this.spawn(key, factory);
73
+ ensure(key) {
74
+ if (!this.#actors.has(key)) this.spawn(key);
60
75
  }
61
76
  snapshot(key) {
62
77
  return this.#actors.get(key)?.snapshot();
@@ -64,9 +79,13 @@ var ActorMap = class {
64
79
  };
65
80
  //#endregion
66
81
  //#region src/state.ts
67
- function states(...names) {
82
+ function states(...entries) {
68
83
  const result = {};
69
- for (const name of names) result[name] = state(name)();
84
+ for (const entry of entries) {
85
+ const name = typeof entry === "string" ? entry : entry.name;
86
+ const ref = state(name)();
87
+ result[name] = typeof entry === "object" && entry.final ? ref.final() : ref;
88
+ }
70
89
  return result;
71
90
  }
72
91
  //#endregion
@@ -88,11 +107,10 @@ function withPromise(promise, signal, emit, events) {
88
107
  //#endregion
89
108
  //#region src/effects/timeout.ts
90
109
  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
110
  input.clock.setTimeout(ms, () => {
93
111
  if (input.signal.aborted) return;
94
112
  input.emit(event());
95
- });
113
+ }, { signal: input.signal });
96
114
  }
97
115
  //#endregion
98
116
  //#region src/transitions/broadcast.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantaq/sugar",
3
- "version": "0.0.2",
3
+ "version": "0.2.0",
4
4
  "description": "Userland sugar for mantaq actors — matches, effect helpers, transition utilities.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,8 +20,8 @@
20
20
  "access": "public"
21
21
  },
22
22
  "dependencies": {
23
- "@mantaq/core": "0.0.2",
24
- "@mantaq/utils": "0.0.1"
23
+ "@mantaq/core": "0.2.0",
24
+ "@mantaq/utils": "0.0.2"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@stryker-mutator/core": "^9",