preact-sigma 6.0.0 → 6.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/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-DD7HfTvw.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-D1V3m1xk.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-CJibGQ6g.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-DTMODzf8.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-DD7HfTvw.mjs";
1
+ import { f as Immutable, n as Sigma } from "./sigma-D1V3m1xk.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-CJibGQ6g.mjs";
1
+ import { o as sigma, s as isPlainObject } from "./sigma-DTMODzf8.mjs";
2
2
  //#region src/persist.ts
3
3
  function createIdentityCodec() {
4
4
  return {
@@ -88,6 +88,7 @@ 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
92
  * Merge a same-named interface with the class when direct property reads should be typed on the instance.
92
93
  */
93
94
  declare abstract class Sigma<TState extends object> {
@@ -59,21 +59,54 @@ const patchListeners = /* @__PURE__ */ new WeakSet();
59
59
  const initializedPrototypes = /* @__PURE__ */ new WeakSet();
60
60
  const queries = /* @__PURE__ */ new WeakSet();
61
61
  const emptySentinel = {};
62
- let activeInstance = null;
62
+ let activeActionInstance = null;
63
+ let activeDraftInstance = null;
63
64
  let activeDraft;
64
65
  let activeDerivedReadDepth = 0;
65
- function ensureActiveInstance(instance) {
66
- if (!activeInstance) {
67
- activeInstance = instance;
66
+ let activeSetupInstance = null;
67
+ const pendingAsyncActions = /* @__PURE__ */ new WeakMap();
68
+ function hasPendingAsyncAction(instance) {
69
+ return pendingAsyncActions.has(instance);
70
+ }
71
+ function addPendingAsyncAction(instance) {
72
+ pendingAsyncActions.set(instance, (pendingAsyncActions.get(instance) ?? 0) + 1);
73
+ }
74
+ function removePendingAsyncAction(instance) {
75
+ const count = pendingAsyncActions.get(instance);
76
+ if (!count) return;
77
+ if (count === 1) pendingAsyncActions.delete(instance);
78
+ else pendingAsyncActions.set(instance, count - 1);
79
+ }
80
+ function hasActionContext(instance) {
81
+ if (activeActionInstance) return activeActionInstance === instance;
82
+ return hasPendingAsyncAction(instance);
83
+ }
84
+ function createExternalActionError() {
85
+ const constructorName = (activeDraftInstance ?? activeActionInstance)?.constructor.name;
86
+ const owner = constructorName ? `Draft for ${constructorName}` : "Draft";
87
+ return /* @__PURE__ */ new Error(`[preact-sigma] ${owner} was not committed before an external action was invoked.`);
88
+ }
89
+ function beginActionContext(instance) {
90
+ if (!activeActionInstance) {
91
+ if (activeDraftInstance && activeDraftInstance !== instance) throw createExternalActionError();
92
+ activeActionInstance = instance;
68
93
  return false;
69
94
  }
70
- if (instance !== activeInstance) throw new Error("Draft was not committed before an external action was invoked.");
95
+ if (instance !== activeActionInstance) throw createExternalActionError();
71
96
  return true;
72
97
  }
73
- function clearActiveInstance() {
74
- activeInstance = null;
98
+ function endActionContext(instance) {
99
+ if (activeActionInstance === instance) activeActionInstance = null;
100
+ }
101
+ function clearActiveAction() {
102
+ activeActionInstance = null;
103
+ activeDraftInstance = null;
75
104
  activeDraft = null;
76
105
  }
106
+ function assertActionContext(instance, message) {
107
+ if (activeActionInstance && activeActionInstance !== instance) throw createExternalActionError();
108
+ if (!hasActionContext(instance)) throw new Error(message);
109
+ }
77
110
  function isStateKey(instance, key) {
78
111
  return Object.hasOwn(instance, key + signalSuffix);
79
112
  }
@@ -83,12 +116,30 @@ function getStateSignal(instance, key) {
83
116
  function createSnapshot(instance) {
84
117
  if (instance[snapshotSymbol]) return instance[snapshotSymbol];
85
118
  const state = {};
86
- for (const key in instance) if (isStateKey(instance, key)) state[key] = instance[key];
119
+ for (const key in instance) if (isStateKey(instance, key)) state[key] = getStateSignal(instance, key).value;
87
120
  return state;
88
121
  }
89
122
  function createDraft(instance) {
90
123
  return createDraft$1(createSnapshot(instance));
91
124
  }
125
+ function ensureDraft(instance) {
126
+ if (activeDraftInstance && activeDraftInstance !== instance) throw createExternalActionError();
127
+ activeDraftInstance = instance;
128
+ activeDraft ??= createDraft(instance);
129
+ return activeDraft;
130
+ }
131
+ function readStateProperty(instance, key) {
132
+ const signal = getStateSignal(instance, key);
133
+ if (!activeDerivedReadDepth && hasActionContext(instance)) {
134
+ if (activeDraftInstance === instance) return activeDraft[key];
135
+ if (isDraftable(signal.value)) return ensureDraft(instance)[key];
136
+ }
137
+ return signal.value;
138
+ }
139
+ function writeStateProperty(instance, key, value) {
140
+ assertActionContext(instance, `[preact-sigma] Cannot set state property "${key}" outside an action.`);
141
+ ensureDraft(instance)[key] = value;
142
+ }
92
143
  function runDerivedRead(callback) {
93
144
  activeDerivedReadDepth += 1;
94
145
  try {
@@ -127,44 +178,15 @@ function publishState(instance, nextState, baseState, patches, inversePatches) {
127
178
  listener(nextState, baseState, patches, inversePatches);
128
179
  });
129
180
  }
130
- function createActionContext(instance) {
131
- return new Proxy(instance, {
132
- get(target, key, receiver) {
133
- if (typeof key === "string" && isStateKey(target, key)) {
134
- if (activeDraft) {
135
- ensureActiveInstance(target);
136
- return activeDraft[key];
137
- }
138
- const { value } = getStateSignal(target, key);
139
- if (isDraftable(value)) {
140
- activeDraft = createDraft(instance);
141
- return activeDraft[key];
142
- }
143
- return value;
144
- }
145
- if (key === instanceSymbol) return instance;
146
- return Reflect.get(target, key, receiver);
147
- },
148
- set(target, key, value) {
149
- if (typeof key === "string" && isStateKey(target, key)) {
150
- ensureActiveInstance(instance);
151
- activeDraft ??= createDraft(instance);
152
- activeDraft[key] = value;
153
- return true;
154
- }
155
- return Reflect.set(target, key, value);
156
- }
157
- });
158
- }
159
181
  function getActionInstance(context) {
160
182
  return context[instanceSymbol];
161
183
  }
162
- function ensureDraftCommitted(instance) {
163
- if (activeInstance && instance !== activeInstance) throw new Error("Draft was not committed before an external action was invoked.");
164
- activeInstance = null;
184
+ function commitDraft(instance) {
185
+ if (activeDraftInstance && instance !== activeDraftInstance) throw createExternalActionError();
165
186
  if (!activeDraft) return false;
166
187
  const draft = activeDraft;
167
188
  activeDraft = null;
189
+ activeDraftInstance = null;
168
190
  let patches;
169
191
  let inversePatches;
170
192
  let patchListener;
@@ -178,6 +200,32 @@ function ensureDraftCommitted(instance) {
178
200
  if (changed) publishState(instance, nextState, baseState, patches, inversePatches);
179
201
  return changed;
180
202
  }
203
+ function hasStateChanges(baseState, nextState) {
204
+ const baseKeys = Object.keys(baseState);
205
+ const nextKeys = Object.keys(nextState);
206
+ if (baseKeys.length !== nextKeys.length) return true;
207
+ for (const key of nextKeys) if (!Object.hasOwn(baseState, key) || !Object.is(baseState[key], nextState[key])) return true;
208
+ return false;
209
+ }
210
+ function createReplacementPatches(baseState, nextState) {
211
+ let patches;
212
+ let inversePatches;
213
+ const draft = createDraft$1(baseState);
214
+ const missingKeys = new Set(Object.keys(baseState));
215
+ for (const key in nextState) {
216
+ draft[key] = nextState[key];
217
+ missingKeys.delete(key);
218
+ }
219
+ for (const key of missingKeys) delete draft[key];
220
+ finishDraft(draft, (nextPatches, nextInversePatches) => {
221
+ patches = nextPatches;
222
+ inversePatches = nextInversePatches;
223
+ });
224
+ return {
225
+ inversePatches,
226
+ patches
227
+ };
228
+ }
181
229
  function initializePrototype(prototype) {
182
230
  const descriptors = Object.getOwnPropertyDescriptors(prototype);
183
231
  for (const key in descriptors) {
@@ -192,31 +240,54 @@ function initializePrototype(prototype) {
192
240
  descriptors[key].value = function(...args) {
193
241
  if (activeDerivedReadDepth) throw new Error("[preact-sigma] Computeds and queries cannot call actions.");
194
242
  const instance = getActionInstance(this);
195
- if (ensureActiveInstance(instance)) {
196
- const result = value.apply(this, args);
243
+ if (beginActionContext(instance)) {
244
+ const result = value.apply(instance, args);
197
245
  assertActionResult(key, result);
198
246
  return result;
199
247
  }
200
248
  let result;
201
249
  try {
202
- const actionContext = createActionContext(instance);
203
- result = actionFn.apply(actionContext, args);
250
+ result = actionFn.apply(instance, args);
204
251
  assertActionResult(key, result);
205
252
  } catch (error) {
206
- clearActiveInstance();
253
+ clearActiveAction();
254
+ throw error;
255
+ }
256
+ let changed;
257
+ try {
258
+ changed = commitDraft(instance);
259
+ } catch (error) {
260
+ clearActiveAction();
207
261
  throw error;
208
262
  }
209
- const changed = ensureDraftCommitted(instance);
210
263
  if (isPromiseLike(result)) {
211
- if (changed) throw new Error(`[preact-sigma] Action named "${key}" forgot to commit() its draft before returning a promise.`);
264
+ if (changed) {
265
+ endActionContext(instance);
266
+ throw new Error(`[preact-sigma] Action named "${key}" forgot to commit() its draft before returning a promise.`);
267
+ }
268
+ addPendingAsyncAction(instance);
269
+ endActionContext(instance);
212
270
  const onResolveAsyncAction = (promiseResult) => {
213
- if (activeDraft && instance === activeInstance) {
214
- if (ensureDraftCommitted(instance)) throw new Error(`[preact-sigma] Action named "${key}" forgot to commit() its draft before its promise resolved.`);
271
+ try {
272
+ if (activeDraft && instance === activeDraftInstance) {
273
+ if (commitDraft(instance)) throw new Error(`[preact-sigma] Action named "${key}" forgot to commit() its draft before its promise resolved.`);
274
+ }
275
+ return promiseResult;
276
+ } finally {
277
+ removePendingAsyncAction(instance);
278
+ }
279
+ };
280
+ const onRejectAsyncAction = (error) => {
281
+ if (activeDraftInstance === instance) {
282
+ activeDraft = null;
283
+ activeDraftInstance = null;
215
284
  }
216
- return promiseResult;
285
+ removePendingAsyncAction(instance);
286
+ throw error;
217
287
  };
218
- return result.then(onResolveAsyncAction);
288
+ return result.then(onResolveAsyncAction, onRejectAsyncAction);
219
289
  }
290
+ endActionContext(instance);
220
291
  return result;
221
292
  };
222
293
  }
@@ -235,24 +306,14 @@ function disposeCleanupResource(resource) {
235
306
  else if ("dispose" in resource) resource.dispose();
236
307
  else resource[Symbol.dispose]();
237
308
  }
238
- function act(fn) {
239
- const instance = getActionInstance(this);
240
- if (instance !== this) throw new Error("Cannot act() from inside an action.");
241
- ensureActiveInstance(instance);
242
- try {
243
- const context = createActionContext(instance);
244
- if (isPromiseLike(action(fn).call(context))) throw new Error("[preact-sigma] act() callbacks must be synchronous");
245
- } catch (error) {
246
- clearActiveInstance();
247
- throw error;
248
- }
249
- ensureDraftCommitted(instance);
250
- }
251
309
  function defineSignalProperty(instance, key, value) {
252
310
  Object.defineProperty(instance, key + signalSuffix, { value: signal(value) });
253
311
  if (!Object.hasOwn(instance.constructor.prototype, key)) Object.defineProperty(instance.constructor.prototype, key, {
254
312
  get() {
255
- return this[key + signalSuffix].value;
313
+ return readStateProperty(this, key);
314
+ },
315
+ set(value) {
316
+ writeStateProperty(this, key, value);
256
317
  },
257
318
  enumerable: true
258
319
  });
@@ -261,6 +322,7 @@ function defineSignalProperty(instance, key, value) {
261
322
  * Base class for signal-backed state models.
262
323
  *
263
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.
264
326
  * Merge a same-named interface with the class when direct property reads should be typed on the instance.
265
327
  */
266
328
  var Sigma = class {
@@ -278,12 +340,15 @@ var Sigma = class {
278
340
  }
279
341
  /** Runs `onSetup(...)` and returns a cleanup that disposes returned resources in reverse order. */
280
342
  setup(...args) {
281
- const setupContext = new Proxy(this, { get(target, key, receiver) {
282
- if (key === instanceSymbol) return target;
283
- if (key === "act") return act.bind(target);
284
- return Reflect.get(target, key, receiver);
285
- } });
286
- const resources = this.onSetup.apply(setupContext, args);
343
+ const instance = getActionInstance(this);
344
+ const previousSetupInstance = activeSetupInstance;
345
+ activeSetupInstance = instance;
346
+ let resources;
347
+ try {
348
+ resources = this.onSetup.apply(instance, args);
349
+ } finally {
350
+ activeSetupInstance = previousSetupInstance;
351
+ }
287
352
  return () => {
288
353
  for (let i = resources.length - 1; i >= 0; i--) disposeCleanupResource(resources[i]);
289
354
  };
@@ -296,19 +361,24 @@ var Sigma = class {
296
361
  */
297
362
  commit(callback) {
298
363
  const instance = getActionInstance(this);
299
- if (instance === this) throw new Error("Cannot commit() from outside an action.");
300
- ensureDraftCommitted(instance);
301
- if (callback) {
302
- const context = new Proxy(instance, { get(target, key, receiver) {
303
- if (key === instanceSymbol) return instance;
304
- return Reflect.get(target, key, receiver);
305
- } });
306
- return callback.call(context);
307
- }
364
+ assertActionContext(instance, "Cannot commit() from outside an action.");
365
+ commitDraft(instance);
366
+ if (callback) return callback.call(instance);
308
367
  }
309
368
  /** Runs a synchronous setup-owned callback with action semantics from an `onSetup(...)` context. */
310
369
  act(fn) {
311
- throw new Error("Cannot act() from outside an onSetup() context.");
370
+ const instance = getActionInstance(this);
371
+ if (activeSetupInstance !== instance) throw new Error("Cannot act() from outside an onSetup() context.");
372
+ if (activeActionInstance === instance) throw new Error("Cannot act() from inside an action.");
373
+ beginActionContext(instance);
374
+ try {
375
+ if (isPromiseLike(action(fn).call(instance))) throw new Error("[preact-sigma] act() callbacks must be synchronous");
376
+ commitDraft(instance);
377
+ } catch (error) {
378
+ clearActiveAction();
379
+ throw error;
380
+ }
381
+ endActionContext(instance);
312
382
  }
313
383
  };
314
384
  /** Casts a sigma instance to its readonly public consumer view. */
@@ -328,8 +398,8 @@ var SigmaTarget = class extends Sigma {
328
398
  /** Emits a typed event from an action after unpublished draft changes are committed. */
329
399
  emit(name, ...[detail]) {
330
400
  const instance = getActionInstance(this);
331
- if (instance === this) throw new Error("Cannot emit() from outside an action.");
332
- if (instance === activeInstance && activeDraft) throw new Error("Cannot emit() until you commit() your draft.");
401
+ assertActionContext(instance, "Cannot emit() from outside an action.");
402
+ if (instance === activeDraftInstance && activeDraft) throw new Error("Cannot emit() until you commit() your draft.");
333
403
  this[listenersSymbol].emit(name, detail);
334
404
  }
335
405
  };
@@ -365,8 +435,12 @@ const sigma = /* @__PURE__ */ Object.freeze({
365
435
  if (activeDraft) throw new Error(`[preact-sigma] replaceState() cannot run while an action has unpublished changes.`);
366
436
  const instance = getActionInstance(target);
367
437
  const baseState = createSnapshot(instance);
368
- const patches = hasPatchListeners(instance) ? [] : void 0;
369
- publishState(instance, nextState, baseState, patches, patches ? [] : void 0);
438
+ if (!hasStateChanges(baseState, nextState)) return;
439
+ const { inversePatches, patches } = hasPatchListeners(instance) ? createReplacementPatches(baseState, nextState) : {
440
+ inversePatches: void 0,
441
+ patches: void 0
442
+ };
443
+ publishState(instance, nextState, baseState, patches, inversePatches);
370
444
  }
371
445
  });
372
446
  /** Marks a class method as a committed-state reactive read with arguments instead of an action. */
package/docs/context.md CHANGED
@@ -22,6 +22,7 @@
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
23
  - Sigma target: a class that extends `SigmaTarget<TEvents, TState>` when it also emits typed events. Use `SigmaTarget<TEvents>` for 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
26
  - Computed: an argument-free derived getter on the class prototype that reads committed state.
26
27
  - Query: a reactive read method that accepts arguments, is marked with the `query` decorator, and reads committed state.
27
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.
@@ -62,6 +63,7 @@
62
63
 
63
64
  - 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.
64
65
  - 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.
65
67
  - Use getters for argument-free derived reads.
66
68
  - Use `@query` for tracked reads with arguments.
67
69
  - Derive directly from state properties inside an action when the calculation needs unpublished draft values.
@@ -94,6 +96,7 @@
94
96
  # Invariants and Constraints
95
97
 
96
98
  - 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.
97
100
  - Protected consumer views expose immutable state and callable actions.
98
101
  - Published draftable public state is deep-frozen by default. `setAutoFreeze(false)` disables that behavior globally.
99
102
  - Computeds and queries read committed state, including when called inside actions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "preact-sigma",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
4
4
  "keywords": [],
5
5
  "license": "MIT",
6
6
  "author": "Alec Larson",