preact-sigma 6.0.1 → 6.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/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as SigmaState, c as query, d as Draft, f as Immutable, i as SigmaRef, l as setAutoFreeze, m as Cleanup, n as Sigma, o as SigmaTarget, p as typeSymbol, r as SigmaDefinition, s as castProtected, t as Protected, u as sigma } from "./sigma-D1V3m1xk.mjs";
1
+ import { a as SigmaState, c as query, d as Draft, f as Immutable, i as SigmaRef, l as setAutoFreeze, m as Cleanup, n as Sigma, o as SigmaTarget, p as typeSymbol, r as SigmaDefinition, s as castProtected, t as Protected, u as sigma } from "./sigma-CadB76LN.mjs";
2
2
 
3
3
  //#region src/defaults.d.ts
4
4
  /**
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as setAutoFreeze, c as listenersSymbol, i as query, n as SigmaTarget, o as sigma, r as castProtected, t as Sigma } from "./sigma-DTMODzf8.mjs";
1
+ import { a as setAutoFreeze, c as listenersSymbol, i as query, n as SigmaTarget, o as sigma, r as castProtected, t as Sigma } from "./sigma-0X8yJrUR.mjs";
2
2
  import { useEffect, useRef } from "preact/hooks";
3
3
  //#region src/defaults.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { f as Immutable, n as Sigma } from "./sigma-D1V3m1xk.mjs";
1
+ import { f as Immutable, n as Sigma } from "./sigma-CadB76LN.mjs";
2
2
 
3
3
  //#region src/persist.d.ts
4
4
  type MaybePromise<T> = T | Promise<T>;
package/dist/persist.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { o as sigma, s as isPlainObject } from "./sigma-DTMODzf8.mjs";
1
+ import { o as sigma, s as isPlainObject } from "./sigma-0X8yJrUR.mjs";
2
2
  //#region src/persist.ts
3
3
  function createIdentityCodec() {
4
4
  return {
@@ -322,7 +322,8 @@ function defineSignalProperty(instance, key, value) {
322
322
  * Base class for signal-backed state models.
323
323
  *
324
324
  * `TState` is the source of typing for top-level state keys, subscriptions, signals, and replacement snapshots.
325
- * Private class fields stay ordinary instance storage and are not signal-backed, captured, or persisted.
325
+ * Private class fields stay ordinary instance storage and are not signal-backed, captured,
326
+ * persisted, or used for reactive invalidation by themselves.
326
327
  * Merge a same-named interface with the class when direct property reads should be typed on the instance.
327
328
  */
328
329
  var Sigma = class {
@@ -390,15 +391,22 @@ function castProtected(instance) {
390
391
  *
391
392
  * `TEvents` maps event names to payload types, and `TState` types reactive state.
392
393
  */
393
- var SigmaTarget = class extends Sigma {
394
+ var SigmaTarget = class SigmaTarget extends Sigma {
394
395
  [listenersSymbol] = new SigmaListenerMap();
395
396
  constructor(state) {
396
397
  super(state ?? emptySentinel);
397
398
  }
398
- /** Emits a typed event from an action after unpublished draft changes are committed. */
399
+ /**
400
+ * Emits a typed event.
401
+ *
402
+ * Directly constructed targets can emit from ordinary code. Subclasses emit from action context
403
+ * after unpublished draft changes are committed.
404
+ */
399
405
  emit(name, ...[detail]) {
400
406
  const instance = getActionInstance(this);
401
- assertActionContext(instance, "Cannot emit() from outside an action.");
407
+ if (Object.getPrototypeOf(instance) === SigmaTarget.prototype) {
408
+ if (activeDraft) throw createExternalActionError();
409
+ } else assertActionContext(instance, "Cannot emit() from outside an action.");
402
410
  if (instance === activeDraftInstance && activeDraft) throw new Error("Cannot emit() until you commit() your draft.");
403
411
  this[listenersSymbol].emit(name, detail);
404
412
  }
@@ -443,7 +451,11 @@ const sigma = /* @__PURE__ */ Object.freeze({
443
451
  publishState(instance, nextState, baseState, patches, inversePatches);
444
452
  }
445
453
  });
446
- /** Marks a class method as a committed-state reactive read with arguments instead of an action. */
454
+ /**
455
+ * Marks a class method as a committed-state reactive read with arguments instead of an action.
456
+ *
457
+ * Each call creates a reactive read at the call site. Query calls do not memoize results across invocations.
458
+ */
447
459
  function query(method) {
448
460
  queries.add(method);
449
461
  function queryMethod(...args) {
@@ -88,7 +88,8 @@ type SigmaState<T extends SigmaDefinition> = Sigma<T["state"]> & {
88
88
  * Base class for signal-backed state models.
89
89
  *
90
90
  * `TState` is the source of typing for top-level state keys, subscriptions, signals, and replacement snapshots.
91
- * Private class fields stay ordinary instance storage and are not signal-backed, captured, or persisted.
91
+ * Private class fields stay ordinary instance storage and are not signal-backed, captured,
92
+ * persisted, or used for reactive invalidation by themselves.
92
93
  * Merge a same-named interface with the class when direct property reads should be typed on the instance.
93
94
  */
94
95
  declare abstract class Sigma<TState extends object> {
@@ -127,7 +128,12 @@ declare class SigmaTarget<TEvents extends object = {}, TState extends object = {
127
128
  };
128
129
  protected [listenersSymbol]: SigmaListenerMap;
129
130
  constructor(state?: TState);
130
- /** Emits a typed event from an action after unpublished draft changes are committed. */
131
+ /**
132
+ * Emits a typed event.
133
+ *
134
+ * Directly constructed targets can emit from ordinary code. Subclasses emit from action context
135
+ * after unpublished draft changes are committed.
136
+ */
131
137
  emit<TEvent extends string & keyof TEvents>(name: TEvent, ...[detail]: EventParameters<TEvents[TEvent]>): void;
132
138
  }
133
139
  /** Helpers for observing, accessing, capturing, and replacing committed sigma state. */
@@ -146,7 +152,11 @@ declare const sigma: Readonly<{
146
152
  captureState<TState extends object>(instance: Sigma<TState>): Immutable<TState>; /** Publishes a plain-object snapshot as the current committed state. */
147
153
  replaceState<TState extends object>(target: Sigma<TState>, nextState: TState): void;
148
154
  }>;
149
- /** Marks a class method as a committed-state reactive read with arguments instead of an action. */
155
+ /**
156
+ * Marks a class method as a committed-state reactive read with arguments instead of an action.
157
+ *
158
+ * Each call creates a reactive read at the call site. Query calls do not memoize results across invocations.
159
+ */
150
160
  declare function query<TThis extends object, TArgs extends any[], TReturn>(method: (this: TThis, ...args: TArgs) => TReturn): (this: TThis, ...args: TArgs) => TReturn;
151
161
  declare const protectedSymbol: unique symbol;
152
162
  type ProtectedKey = typeof listenersSymbol | typeof snapshotSymbol | "act" | "commit" | "emit" | "onSetup";
package/docs/context.md CHANGED
@@ -20,14 +20,14 @@
20
20
  # Core Abstractions
21
21
 
22
22
  - Sigma class: a class that extends `Sigma<TState>` and passes its initial top-level state to `super(...)`. The `TState` argument drives helper typing for subscriptions, signals, and replacement snapshots; a same-named merged interface gives direct property reads their instance types.
23
- - Sigma target: a class that extends `SigmaTarget<TEvents, TState>` when it also emits typed events. Use `SigmaTarget<TEvents>` for event-only targets.
23
+ - Sigma target: a class that extends `SigmaTarget<TEvents>` for typed event actions or `SigmaTarget<TEvents, TState>` when it also owns state. Use `new SigmaTarget<TEvents>()` for standalone event-only targets.
24
24
  - State property: a top-level key from `TState`. Each key becomes a reactive public property and has its own signal.
25
- - Private field: an ECMAScript `#field` on the model class. Private fields are ordinary instance storage, not signal-backed state.
25
+ - Private field: an ECMAScript `#field` on the model class. Private fields are ordinary instance storage. They can be read from model members, but they do not create signals or invalidate reactive reads by themselves.
26
26
  - Computed: an argument-free derived getter on the class prototype that reads committed state.
27
27
  - Query: a reactive read method that accepts arguments, is marked with the `query` decorator, and reads committed state.
28
28
  - Action: a prototype method that is not marked as a query. Actions read and write state properties through sigma's draft and commit semantics.
29
29
  - Setup handler: an optional `onSetup(...)` method that owns side effects and returns cleanup resources.
30
- - Event: a typed notification emitted with `this.emit(...)` inside an action and observed through `listen(...)` or `useListener(...)`.
30
+ - Event: a typed notification emitted with `this.emit(...)` inside an action, or with `emit(...)` on a directly constructed `SigmaTarget`, and observed through `listen(...)` or `useListener(...)`.
31
31
  - Protected view: the readonly consumer view returned by `castProtected(...)` and `useSigma(...)`.
32
32
 
33
33
  # Data Flow / Lifecycle
@@ -45,6 +45,7 @@
45
45
 
46
46
  - Define reusable model state: `class Model extends Sigma<TState>`.
47
47
  - Define reusable model state with events: `class Model extends SigmaTarget<TEvents, TState>`.
48
+ - Define a standalone typed event target: `new SigmaTarget<TEvents>()`.
48
49
  - Merge partial constructor input with defaults: `mergeDefaults(initial, defaults)`.
49
50
  - Derive an argument-free value: a class getter.
50
51
  - Derive a reactive read with arguments: an `@query` class method.
@@ -63,12 +64,12 @@
63
64
 
64
65
  - Put the state shape in a named `State` type, pass it to `Sigma<TState>` or `SigmaTarget<TEvents, TState>`, then merge a same-named interface with the class for direct property typing.
65
66
  - Keep frequently read values as separate top-level state properties. Each top-level key gets its own signal.
66
- - Use private fields for ephemeral caches, handles, or bookkeeping that should not be captured, restored, persisted, or used as subscription keys.
67
+ - Use private fields for ephemeral caches, handles, or bookkeeping when private-only changes should not be captured, restored, persisted, used as subscription keys, or published reactively.
67
68
  - Use getters for argument-free derived reads.
68
69
  - Use `@query` for tracked reads with arguments.
69
70
  - Derive directly from state properties inside an action when the calculation needs unpublished draft values.
70
71
  - Use ordinary actions for routine writes. Reserve `sigma.captureState(...)` and `sigma.replaceState(...)` for replay, reset, restore, or undo-like flows on committed top-level state.
71
- - Emit directly from actions that have no unpublished draft changes. After mutating state, publish first with `this.commit(); this.emit(...)`.
72
+ - Emit directly from standalone `SigmaTarget` instances. In subclasses, emit from actions that have no unpublished draft changes. After mutating state, publish first with `this.commit(); this.emit(...)`.
72
73
  - Prefer `listen(...)` for external event subscriptions. It works with sigma targets and DOM targets.
73
74
  - Put owned side effects in `onSetup(...)`.
74
75
  - Use `sigma.subscribe(this, ...)` inside `onSetup(...)` when a setup-owned side effect should react to future committed publishes. Return that cleanup so the subscription stops with setup.
@@ -96,7 +97,7 @@
96
97
  # Invariants and Constraints
97
98
 
98
99
  - Sigma tracks top-level state properties. Each top-level key gets its own signal.
99
- - Private fields are not top-level state properties. They do not create signals, appear in committed snapshots, participate in persistence helpers, or drive subscriptions by themselves.
100
+ - Private fields are not top-level state properties. They do not create signals, appear in committed snapshots, participate in persistence helpers, or drive subscriptions by themselves. Computeds and queries that read private fields update when their signal-backed state dependencies change; private-only changes do not invalidate those reads.
100
101
  - Protected consumer views expose immutable state and callable actions.
101
102
  - Published draftable public state is deep-frozen by default. `setAutoFreeze(false)` disables that behavior globally.
102
103
  - Computeds and queries read committed state, including when called inside actions.
@@ -104,14 +105,14 @@
104
105
  - Setup handlers return arrays of cleanup resources, and cleanup runs in reverse order.
105
106
  - Call Immer's `enablePatches()` before relying on `sigma.subscribe(instance, handler, { patches: true })`.
106
107
  - `sigma.replaceState(...)` works on committed top-level state and requires a plain object snapshot.
107
- - `SigmaTarget.emit(...)` runs from an action and requires no active unpublished draft. It does not need a `commit(...)` callback.
108
+ - `SigmaTarget.emit(...)` runs directly on standalone targets. In subclasses, it runs from an action and requires no active unpublished draft.
108
109
 
109
110
  # Error Model
110
111
 
111
112
  - Crossing an action boundary with unpublished changes throws until `this.commit()` publishes them. Async actions also reject when they finish with unpublished changes.
112
113
  - Calling `commit(...)` outside an action throws.
113
114
  - Calling `act(...)` outside an `onSetup(...)` setup context throws.
114
- - Calling `emit(...)` outside an action or before committing the active draft throws.
115
+ - Calling `emit(...)` outside an action on a subclass, or before committing the active draft, throws.
115
116
  - Calling an action from a computed or query throws.
116
117
  - Returning an active draft from an action throws.
117
118
  - `sigma.replaceState(...)` throws when the replacement value is not a plain object or when an action still owns unpublished changes.
@@ -116,7 +116,7 @@ interface TodoList extends TodoListState {}
116
116
 
117
117
  ## Events
118
118
 
119
- `SigmaTarget` now takes event types first. Use `SigmaTarget<TEvents>` for event-only targets and `SigmaTarget<TEvents, TState>` for targets that also own state.
119
+ `SigmaTarget` now takes event types first. Use `new SigmaTarget<TEvents>()` for standalone event-only targets, `class Model extends SigmaTarget<TEvents>` for event-only action classes, and `SigmaTarget<TEvents, TState>` for class targets that also own state.
120
120
 
121
121
  ```ts
122
122
  import { listen, SigmaTarget } from "preact-sigma";
@@ -140,7 +140,7 @@ const stop = listen(notifications, "saved", ({ id }) => {
140
140
  });
141
141
  ```
142
142
 
143
- `emit(...)` runs inside actions. If an action mutates state before emitting, publish first with `this.commit()`.
143
+ Directly constructed `SigmaTarget` instances can emit from ordinary code. Subclasses emit inside actions. If an action mutates state before emitting, publish first with `this.commit()`.
144
144
 
145
145
  ## Commit Boundaries
146
146
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "preact-sigma",
3
- "version": "6.0.1",
3
+ "version": "6.1.0",
4
4
  "keywords": [],
5
5
  "license": "MIT",
6
6
  "author": "Alec Larson",