preact-sigma 4.0.0 → 6.0.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.mjs CHANGED
@@ -1,727 +1,34 @@
1
- import { batch, computed, signal, untracked } from "@preact/signals";
2
- import * as immer from "immer";
3
- import { useEffect, useRef, useState } from "preact/hooks";
4
- //#region src/internal/context.ts
5
- const disabledContextOptions = {
6
- allowAct: false,
7
- allowActions: false,
8
- allowCommit: false,
9
- allowEmit: false,
10
- allowQueries: false,
11
- allowWrites: false,
12
- draftAware: false,
13
- draftOnRead: false,
14
- liveComputeds: false,
15
- reactiveReads: false
16
- };
17
- const publicContextOptions = {
18
- computedReadonly: {
19
- ...disabledContextOptions,
20
- reactiveReads: true
21
- },
22
- observe: {
23
- ...disabledContextOptions,
24
- allowQueries: true
25
- },
26
- queryCommitted: {
27
- ...disabledContextOptions,
28
- allowQueries: true,
29
- reactiveReads: true
30
- },
31
- setup: {
32
- ...disabledContextOptions,
33
- allowAct: true,
34
- allowActions: true,
35
- allowEmit: true,
36
- allowQueries: true
37
- }
38
- };
39
- const ownerContextOptions = {
40
- action: {
41
- ...disabledContextOptions,
42
- allowActions: true,
43
- allowCommit: true,
44
- allowEmit: true,
45
- allowQueries: true,
46
- allowWrites: true,
47
- draftAware: true,
48
- draftOnRead: true,
49
- liveComputeds: true
50
- },
51
- computedDraftAware: {
52
- ...disabledContextOptions,
53
- draftAware: true,
54
- liveComputeds: true
55
- },
56
- queryDraftAware: {
57
- ...disabledContextOptions,
58
- allowQueries: true,
59
- draftAware: true,
60
- liveComputeds: true
61
- }
62
- };
63
- const dirtyContexts = {
64
- computedReadonly: /* @__PURE__ */ new Set(),
65
- observe: /* @__PURE__ */ new Set(),
66
- queryCommitted: /* @__PURE__ */ new Set(),
67
- setup: /* @__PURE__ */ new Set()
68
- };
69
- const contextKinds = Object.keys(dirtyContexts);
70
- const contextCache = {
71
- computedReadonly: /* @__PURE__ */ new WeakMap(),
72
- observe: /* @__PURE__ */ new WeakMap(),
73
- queryCommitted: /* @__PURE__ */ new WeakMap(),
74
- setup: /* @__PURE__ */ new WeakMap()
75
- };
76
- const contextOwnerMap = /* @__PURE__ */ new WeakMap();
77
- let contextCacheFlushScheduled = false;
78
- function getContext(target, kind) {
79
- if (isOwnerContextKind(kind)) return getOwnerContext(target, kind);
80
- return getPublicContext(target, kind);
81
- }
82
- function getContextOwner(context) {
83
- return contextOwnerMap.get(context);
84
- }
85
- function registerContextOwner(context, owner) {
86
- contextOwnerMap.set(context, owner);
87
- }
88
- function createContext(instance, options, owner) {
89
- const publicPrototype = Object.getPrototypeOf(instance.publicInstance);
90
- return new Proxy(publicPrototype, {
91
- get(_target, key, receiver) {
92
- if (typeof key !== "string") return Reflect.get(publicPrototype, key, owner?.actionContext ?? instance.publicInstance);
93
- if (key === "act") return options.allowAct ? (actionFn) => {
94
- if (typeof actionFn !== "function") throw new Error("[preact-sigma] act() requires a function");
95
- return runAdHocAction(receiver, actionFn);
96
- } : void 0;
97
- if (key === "commit") return options.allowCommit && owner ? () => commitActionOwner(owner) : void 0;
98
- if (key === "emit") return options.allowEmit && owner ? (name, detail) => {
99
- handleActionBoundary(owner, "emit");
100
- instance.publicInstance.dispatchEvent(new CustomEvent(name, { detail }));
101
- } : void 0;
102
- if (instance.stateKeys.has(key)) {
103
- if (owner && options.draftAware) return readActionStateValue(owner, key, options);
104
- const signal = getSignal(instance, key);
105
- return options.reactiveReads ? signal.value : signal.peek();
106
- }
107
- if (key in instance.type._computeFunctions) {
108
- if (owner && options.liveComputeds) return readActionComputedValue(owner, key);
109
- const signal = getSignal(instance, key);
110
- return options.reactiveReads ? signal.value : signal.peek();
111
- }
112
- if (options.allowQueries && key in instance.type._queryFunctions) return Reflect.get(instance.publicInstance, key);
113
- if (options.allowActions && key in instance.type._actionFunctions) return Reflect.get(instance.publicInstance, key);
114
- if (Reflect.has(publicPrototype, key)) return Reflect.get(publicPrototype, key, owner?.actionContext ?? instance.publicInstance);
115
- },
116
- set(_target, key, value) {
117
- if (!owner || !options.allowWrites || typeof key !== "string" || !instance.stateKeys.has(key)) return false;
118
- setActionStateValue(owner, key, value);
119
- return true;
120
- },
121
- apply: unsupportedOperation,
122
- construct: unsupportedOperation,
123
- defineProperty: unsupportedOperation,
124
- deleteProperty: unsupportedOperation,
125
- getOwnPropertyDescriptor: unsupportedOperation,
126
- has: unsupportedOperation,
127
- isExtensible: unsupportedOperation,
128
- ownKeys: unsupportedOperation,
129
- preventExtensions: unsupportedOperation,
130
- setPrototypeOf: unsupportedOperation
131
- });
132
- }
133
- function unsupportedOperation() {
134
- throw new Error("[preact-sigma] This operation is not supported by context proxies");
135
- }
136
- const kindToOwnerContextKey = {
137
- action: "actionContext",
138
- computedDraftAware: "computedContext",
139
- queryDraftAware: "queryContext"
140
- };
141
- function isOwnerContextKind(kind) {
142
- return kind in kindToOwnerContextKey;
143
- }
144
- function getOwnerContext(owner, kind) {
145
- const contextKey = kindToOwnerContextKey[kind];
146
- if (owner[contextKey]) return owner[contextKey];
147
- const context = createContext(owner.instance, ownerContextOptions[kind], owner);
148
- registerSigmaInternals(context, owner.instance);
149
- registerContextOwner(context, owner);
150
- owner[contextKey] = context;
151
- return context;
152
- }
153
- function getPublicContext(instance, kind) {
154
- const cachedContext = contextCache[kind].get(instance);
155
- if (cachedContext) return cachedContext;
156
- const context = createContext(instance, publicContextOptions[kind], void 0);
157
- registerSigmaInternals(context, instance);
158
- contextCache[kind].set(instance, context);
159
- dirtyContexts[kind].add(instance);
160
- if (!contextCacheFlushScheduled) {
161
- contextCacheFlushScheduled = true;
162
- setTimeout(() => {
163
- for (const queuedKind of contextKinds) {
164
- for (const queuedInstance of dirtyContexts[queuedKind]) contextCache[queuedKind].delete(queuedInstance);
165
- dirtyContexts[queuedKind].clear();
166
- }
167
- contextCacheFlushScheduled = false;
168
- }, 0);
169
- }
170
- return context;
171
- }
172
- //#endregion
173
- //#region src/internal/symbols.ts
174
- const sigmaStateBrand = Symbol("sigma.state");
175
- const reservedKeys = new Set([
176
- "act",
177
- "get",
178
- "emit",
179
- "commit",
180
- "on",
181
- "setup"
182
- ]);
183
- //#endregion
184
- //#region src/internal/runtime.ts
185
- const sigmaInternalsMap = /* @__PURE__ */ new WeakMap();
186
- let autoFreezeEnabled = true;
187
- let nextActionOwnerId = 1;
188
- let currentDraftOwner;
189
- function registerSigmaInternals(context, instance) {
190
- sigmaInternalsMap.set(context, instance);
191
- }
192
- function getSigmaInternals(context) {
193
- const instance = sigmaInternalsMap.get(context);
194
- if (!instance) throw new Error("[preact-sigma] Invalid sigma context");
195
- return instance;
196
- }
197
- function isAutoFreeze() {
198
- return autoFreezeEnabled;
199
- }
200
- /** Controls whether sigma deep-freezes published public state. Auto-freezing starts enabled and the setting is shared across instances. */
201
- function setAutoFreeze(autoFreeze) {
202
- autoFreezeEnabled = autoFreeze;
203
- immer.setAutoFreeze(autoFreeze);
204
- }
205
- function isPlainObject(value) {
206
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
207
- const prototype = Object.getPrototypeOf(value);
208
- return prototype === Object.prototype || prototype === null;
209
- }
210
- function getSignal(instance, key) {
211
- return instance.publicInstance["#" + key];
212
- }
213
- function initializeSigmaInstance(publicInstance, type, initialState) {
214
- const stateKeys = new Set(type._defaultStateKeys);
215
- if (initialState) for (const key in initialState) stateKeys.add(key);
216
- const instance = {
217
- currentSetupCleanup: void 0,
218
- publicInstance,
219
- stateKeys,
220
- type,
221
- disposed: false
222
- };
223
- for (const key of stateKeys) {
224
- if (reservedKeys.has(key)) throw new Error(`[preact-sigma] Reserved property name: ${key}`);
225
- let value = initialState?.[key];
226
- if (value === void 0) value = typeof type._defaultState[key] === "function" ? type._defaultState[key].call(void 0) : type._defaultState[key];
227
- const container = signal(value);
228
- Object.defineProperty(publicInstance, "#" + key, { value: container });
229
- Object.defineProperty(publicInstance, key, {
230
- get: () => container.value,
231
- enumerable: true
232
- });
233
- }
234
- for (const key in type._computeFunctions) Object.defineProperty(publicInstance, "#" + key, { value: computed(() => type._computeFunctions[key].call(getContext(instance, "computedReadonly"))) });
235
- registerSigmaInternals(publicInstance, instance);
236
- }
237
- function buildQueryMethod(queryFunction) {
238
- return function(...args) {
239
- const instance = getSigmaInternals(this);
240
- const owner = getContextOwner(this);
241
- if (owner) return queryFunction.apply(getContext(owner, "queryDraftAware"), args);
242
- return computed(() => queryFunction.apply(getContext(instance, "queryCommitted"), args)).value;
243
- };
244
- }
245
- function buildActionMethod(actionName, actionFn) {
246
- return function(...args) {
247
- return runActionInvocation(this, actionName, actionFn, args);
248
- };
249
- }
250
- function runAdHocAction(context, actionFn) {
251
- return runActionInvocation(context, "act()", actionFn, []);
252
- }
253
- function readActionStateValue(owner, key, options) {
254
- if (owner.currentDraft) return owner.currentDraft[key];
255
- const signal = getSignal(owner.instance, key);
256
- const committedValue = options.reactiveReads ? signal.value : signal.peek();
257
- if (options.draftOnRead && immer.isDraftable(committedValue)) return ensureOwnerDraft(owner)[key];
258
- return committedValue;
259
- }
260
- function readActionComputedValue(owner, key) {
261
- return owner.instance.type._computeFunctions[key].call(getContext(owner, "computedDraftAware"));
262
- }
263
- function setActionStateValue(owner, key, value) {
264
- ensureOwnerDraft(owner)[key] = value;
265
- }
266
- function commitActionOwner(owner) {
267
- const finalized = finalizeOwnerDraft(owner);
268
- if (finalized?.changed) publishState(owner.instance, finalized);
269
- }
270
- function handleActionBoundary(owner, boundary, actionName) {
271
- const draftOwner = currentDraftOwner;
272
- if (!draftOwner?.currentDraft) return;
273
- if (!finalizeOwnerDraft(draftOwner)?.changed) return;
274
- if (draftOwner === owner) {
275
- const message = boundary === "emit" ? `[preact-sigma] Action "${draftOwner.actionName}" has unpublished changes. Call this.commit() before emit().` : `[preact-sigma] Action "${draftOwner.actionName}" has unpublished changes. Call this.commit() before calling another action.`;
276
- throw new Error(message);
277
- }
278
- if (boundary === "emit") throw new Error("[preact-sigma] Unexpected emit boundary. This is a bug.");
279
- console.warn(`[preact-sigma] Discarded unpublished action changes from "${draftOwner.actionName}" before running "${actionName ?? "another action"}".`, createDraftMetadata(draftOwner));
280
- }
281
- function assertDefinitionKeyAvailable(builder, key, kind) {
282
- if (reservedKeys.has(key)) throw new Error(`[preact-sigma] Reserved property name: ${key}`);
283
- if (key in builder._computeFunctions || key in builder._queryFunctions || key in builder._actionFunctions) throw new Error(`[preact-sigma] Duplicate key for ${kind}: ${key}`);
284
- }
285
- function shouldSetup(publicInstance) {
286
- return getSigmaInternals(publicInstance).type._setupFunction !== null;
287
- }
288
- function clearCurrentDraft(owner) {
289
- owner.currentDraft = void 0;
290
- owner.currentBase = void 0;
291
- if (currentDraftOwner === owner) currentDraftOwner = void 0;
292
- }
293
- function createActionOwner(instance, actionName, actionFn, args) {
294
- const owner = {
295
- actionFn,
296
- actionName,
297
- args,
298
- id: nextActionOwnerId++,
299
- instance,
300
- publicInstance: instance.publicInstance
301
- };
302
- owner.actionContext = getContext(owner, "action");
303
- registerSigmaInternals(owner.actionContext, instance);
304
- registerContextOwner(owner.actionContext, owner);
305
- return owner;
306
- }
307
- function runActionInvocation(context, actionName, actionFn, args) {
308
- const instance = getSigmaInternals(context);
309
- if (instance.disposed) throw new Error("[preact-sigma] Cannot run an action on a disposed sigma state");
310
- const isAdHocAction = actionName === "act()";
311
- const actionIsAsync = isAsyncFunction(actionFn);
312
- if (actionIsAsync && isAdHocAction) throw new Error("[preact-sigma] act() callbacks must stay synchronous");
313
- return untracked(() => {
314
- let owner;
315
- const callerOwner = getContextOwner(context);
316
- if (callerOwner && callerOwner.instance === instance && !actionIsAsync) owner = callerOwner;
317
- else {
318
- handleActionBoundary(callerOwner, "action", actionName);
319
- owner = createActionOwner(instance, actionName, actionFn, args);
320
- }
321
- let result;
322
- try {
323
- result = actionFn.apply(owner.actionContext, args);
324
- } catch (error) {
325
- clearCurrentDraft(owner);
326
- throw error;
327
- }
328
- if (isAdHocAction && isPromiseLike(result)) {
329
- clearCurrentDraft(owner);
330
- Promise.resolve(result).catch(() => {});
331
- throw new Error("[preact-sigma] act() callbacks must stay synchronous");
332
- }
333
- if (!actionIsAsync && isPromiseLike(result)) {
334
- clearCurrentDraft(owner);
335
- Promise.resolve(result).catch(() => {});
336
- throw new Error(`[preact-sigma] Action "${actionName}" must use native async-await syntax to return a promise.`);
337
- }
338
- if (owner === callerOwner) return result;
339
- const finalized = finalizeOwnerDraft(owner);
340
- if (finalized?.changed) publishState(instance, finalized);
341
- if (isPromiseLike(result)) return resolveAsyncActionResult(owner, result);
342
- return result;
343
- });
344
- }
345
- function createCommitError(owner) {
346
- return /* @__PURE__ */ new Error(`[preact-sigma] Async action "${owner.actionName}" finished with unpublished changes. Call this.commit() before await or return.`);
347
- }
348
- function createDraftMetadata(owner) {
349
- return {
350
- action: owner.actionFn,
351
- actionArgs: owner.args,
352
- actionId: owner.id,
353
- actionName: owner.actionName,
354
- draftedInstance: currentDraftOwner?.publicInstance ?? owner.publicInstance,
355
- instance: owner.instance.publicInstance
356
- };
357
- }
358
- function disposeCleanupResource(resource) {
359
- if (typeof resource === "function") resource();
360
- else if (resource instanceof AbortController) resource.abort();
361
- else if ("dispose" in resource) resource.dispose();
362
- else resource[Symbol.dispose]();
363
- }
364
- function assertExactStateKeys(instance, nextState) {
365
- const extraKeys = Object.keys(nextState).filter((key) => !instance.stateKeys.has(key));
366
- const missingKeys = [...instance.stateKeys].filter((key) => !Object.prototype.hasOwnProperty.call(nextState, key));
367
- if (!extraKeys.length && !missingKeys.length) return;
368
- let message = "[preact-sigma] replaceState() requires exactly the instance's state keys";
369
- if (missingKeys.length) message += `. Missing: ${missingKeys.join(", ")}`;
370
- if (extraKeys.length) message += `. Extra: ${extraKeys.join(", ")}`;
371
- throw new Error(message);
372
- }
373
- function assertNoPendingDraft(operationName) {
374
- const owner = currentDraftOwner;
375
- if (!owner?.currentDraft) return;
376
- throw new Error(`[preact-sigma] ${operationName}() cannot run while action "${owner.actionName}" has unpublished changes. Call this.commit() before ${operationName}().`);
377
- }
378
- function snapshotState(instance) {
379
- const snapshot = Object.create(null);
380
- for (const key of instance.stateKeys) snapshot[key] = getSignal(instance, key).peek();
381
- return snapshot;
382
- }
383
- function ensureOwnerDraft(owner) {
384
- if (owner.currentDraft) return owner.currentDraft;
385
- handleActionBoundary(owner, "action", owner.actionName);
386
- owner.currentBase = snapshotState(owner.instance);
387
- owner.currentDraft = immer.createDraft(owner.currentBase);
388
- currentDraftOwner = owner;
389
- return owner.currentDraft;
390
- }
391
- function finalizeOwnerDraft(owner) {
392
- const currentDraft = owner.currentDraft;
393
- const oldState = owner.currentBase;
394
- if (!currentDraft || !oldState) return;
395
- clearCurrentDraft(owner);
396
- let patches;
397
- let inversePatches;
398
- const newState = owner.instance.type._patchesEnabled ? immer.finishDraft(currentDraft, (nextPatches, nextInversePatches) => {
399
- patches = nextPatches;
400
- inversePatches = nextInversePatches;
401
- }) : immer.finishDraft(currentDraft);
402
- return {
403
- changed: newState !== oldState,
404
- inversePatches,
405
- newState,
406
- oldState,
407
- patches
408
- };
409
- }
410
- function finalizeReplacementState(instance, oldState, nextState) {
411
- const draft = immer.createDraft(oldState);
412
- for (const key of instance.stateKeys) draft[key] = nextState[key];
413
- let patches;
414
- let inversePatches;
415
- const newState = instance.type._patchesEnabled ? immer.finishDraft(draft, (nextPatches, nextInversePatches) => {
416
- patches = nextPatches;
417
- inversePatches = nextInversePatches;
418
- }) : immer.finishDraft(draft);
419
- return {
420
- changed: newState !== oldState,
421
- inversePatches,
422
- newState,
423
- oldState,
424
- patches
425
- };
426
- }
427
- function isAsyncFunction(fn) {
428
- return fn.constructor.name === "AsyncFunction";
429
- }
430
- function isPromiseLike(value) {
431
- return value != null && typeof value.then === "function";
432
- }
433
- function publishState(instance, finalized) {
434
- batch(() => {
435
- for (const key of instance.stateKeys) {
436
- const nextValue = finalized.newState[key];
437
- if (isAutoFreeze()) immer.freeze(nextValue, true);
438
- const signal = getSignal(instance, key);
439
- signal.value = nextValue;
440
- }
441
- });
442
- instance.type._observeFunction?.call(getContext(instance, "observe"), finalized);
443
- }
444
- /**
445
- * Returns a shallow snapshot of an instance's committed public state.
446
- *
447
- * The snapshot includes one own property for each top-level state key and reads
448
- * the current committed value for that key. Nested sigma states remain as
449
- * referenced values. Its type is inferred from the instance's sigma-state
450
- * definition.
451
- */
452
- function snapshot(publicInstance) {
453
- return snapshotState(getSigmaInternals(publicInstance));
454
- }
455
- /**
456
- * Replaces an instance's committed public state from a snapshot object.
457
- *
458
- * The replacement snapshot must be a plain object with exactly the instance's
459
- * top-level state keys. It updates committed state outside action semantics and
460
- * notifies observers when the committed state changes. Its type is inferred
461
- * from the instance's sigma-state definition.
462
- */
463
- function replaceState(publicInstance, nextState) {
464
- const instance = getSigmaInternals(publicInstance);
465
- if (!isPlainObject(nextState)) throw new Error("[preact-sigma] replaceState() requires a plain object snapshot");
466
- assertNoPendingDraft("replaceState");
467
- assertExactStateKeys(instance, nextState);
468
- const finalized = finalizeReplacementState(instance, snapshot(publicInstance), nextState);
469
- if (finalized.changed) publishState(instance, finalized);
470
- }
471
- async function resolveAsyncActionResult(owner, result) {
472
- let settledValue;
473
- let settledError;
474
- let rejected = false;
475
- try {
476
- settledValue = await result;
477
- } catch (error) {
478
- rejected = true;
479
- settledError = error;
480
- }
481
- if (currentDraftOwner === owner && owner.currentDraft) {
482
- if (finalizeOwnerDraft(owner)?.changed) {
483
- const commitError = createCommitError(owner);
484
- if (rejected) throw new AggregateError([settledError, commitError], `[preact-sigma] Async action "${owner.actionName}" rejected and left unpublished changes`);
485
- throw commitError;
486
- }
487
- }
488
- if (rejected) throw settledError;
489
- return settledValue;
490
- }
491
- var Sigma = class extends EventTarget {
492
- setup(...args) {
493
- const instance = getSigmaInternals(this);
494
- if (!instance.type._setupFunction) throw new Error("[preact-sigma] Setup is undefined for this sigma state");
495
- if (instance.disposed) throw new Error("[preact-sigma] Cannot set up a disposed sigma state");
496
- instance.currentSetupCleanup?.();
497
- instance.currentSetupCleanup = void 0;
498
- const resources = instance.type._setupFunction.apply(getContext(instance, "setup"), args);
499
- if (!Array.isArray(resources)) throw new Error("[preact-sigma] Sigma setup handlers must return an array");
500
- let cleanup;
501
- if (resources.length) {
502
- let cleaned = false;
503
- cleanup = () => {
504
- if (instance.currentSetupCleanup === cleanup) instance.currentSetupCleanup = void 0;
505
- if (cleaned) return;
506
- cleaned = true;
507
- let errors;
508
- for (let index = resources.length - 1; index >= 0; index -= 1) try {
509
- disposeCleanupResource(resources[index]);
510
- } catch (error) {
511
- errors ||= [];
512
- errors.push(error);
513
- }
514
- if (errors) throw new AggregateError(errors, "Failed to dispose one or more sigma resources");
515
- };
516
- instance.currentSetupCleanup = cleanup;
517
- } else cleanup = () => {};
518
- return cleanup;
519
- }
520
- get(key) {
521
- return getSignal(getSigmaInternals(this), key);
522
- }
523
- on(name, listener) {
524
- const adapter = (event) => {
525
- const payload = event.detail;
526
- if (payload === void 0) listener();
527
- else listener(payload);
528
- };
529
- this.addEventListener(name, adapter);
530
- return () => {
531
- this.removeEventListener(name, adapter);
532
- };
533
- }
534
- };
535
- Object.defineProperty(Sigma.prototype, sigmaStateBrand, { value: true });
536
- //#endregion
537
- //#region src/framework.ts
538
- /** Checks whether a value is an instance created by a configured sigma type. */
539
- function isSigmaState(value) {
540
- return Boolean(value[sigmaStateBrand]);
541
- }
542
- /**
543
- * Creates a standalone tracked query helper with the same signature as `fn`.
544
- *
545
- * Each call is reactive at the call site and does not memoize results across
546
- * invocations, which makes `query(fn)` a good fit for local tracked helpers
547
- * that do not need to live on the sigma-state instance.
548
- */
549
- function query(fn) {
550
- return ((...args) => computed(() => fn(...args)).value);
551
- }
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";
2
+ import { useEffect, useRef } from "preact/hooks";
3
+ //#region src/defaults.ts
552
4
  /**
553
- * Builds sigma-state constructors by accumulating default state, computeds,
554
- * queries, observers, actions, and setup handlers.
5
+ * Shallowly applies defined initial values over a defaults object.
555
6
  *
556
- * State and event inference starts from `new SigmaType<TState, TEvents>()`.
557
- * Later builder methods infer names and types from the objects you pass to them.
7
+ * `undefined` initial values leave their matching default value in place.
558
8
  */
559
- var SigmaType = class SigmaType extends Function {
560
- constructor(name = "Sigma") {
561
- super();
562
- const { [name]: type } = { [name]: class extends Sigma {
563
- static _actionFunctions = Object.create(null);
564
- static _computeFunctions = Object.create(null);
565
- static _defaultState = Object.create(null);
566
- static _defaultStateKeys = [];
567
- static _observeFunction = null;
568
- static _patchesEnabled = false;
569
- static _queryFunctions = Object.create(null);
570
- static _setupFunction = null;
571
- constructor(initialState) {
572
- super();
573
- initializeSigmaInstance(this, type, initialState);
574
- }
575
- } };
576
- Object.defineProperties(type, Object.getOwnPropertyDescriptors(SigmaType.prototype));
577
- return type;
578
- }
579
- /**
580
- * Adds top-level public state and default values to the builder.
581
- *
582
- * Each property becomes a reactive public state property on instances. Use a
583
- * zero-argument function when each instance needs a fresh object or array.
584
- */
585
- defaultState(defaultState) {
586
- const type = getTypeInternals(this);
587
- for (const key in defaultState) {
588
- if (defaultState[key] === void 0) continue;
589
- type._defaultState[key] = defaultState[key];
590
- type._defaultStateKeys.push(key);
591
- }
592
- return this;
593
- }
594
- /**
595
- * Adds reactive getter properties for derived values that take no arguments.
596
- *
597
- * Computed names and return types are inferred from the object you pass.
598
- * `this` exposes readonly state plus computeds that are already on the builder.
599
- */
600
- computed(computeds) {
601
- const type = getTypeInternals(this);
602
- for (const key in computeds) {
603
- assertDefinitionKeyAvailable(type, key, "computed");
604
- type._computeFunctions[key] = computeds[key];
605
- Object.defineProperty(this.prototype, key, { get: function() {
606
- return this["#" + key].value;
607
- } });
608
- }
609
- return this;
610
- }
611
- /**
612
- * Adds reactive read methods that accept arguments.
613
- *
614
- * Query names, parameters, and return types are inferred from the object you
615
- * pass. Each call tracks reactively at the call site and does not memoize
616
- * results across invocations.
617
- */
618
- queries(queries) {
619
- const type = getTypeInternals(this);
620
- for (const key in queries) {
621
- assertDefinitionKeyAvailable(type, key, "query");
622
- const queryFunction = queries[key];
623
- type._queryFunctions[key] = queryFunction;
624
- Object.defineProperty(this.prototype, key, { value: buildQueryMethod(queryFunction) });
625
- }
626
- return this;
627
- }
628
- observe(listener, options) {
629
- const type = getTypeInternals(this);
630
- type._observeFunction = listener;
631
- if (options?.patches) type._patchesEnabled = true;
632
- return this;
633
- }
634
- /**
635
- * Adds action methods whose `this` receives draft state, typed events, `commit()`,
636
- * and the computeds, queries, and actions already defined on the builder.
637
- *
638
- * Actions create drafts lazily as they need them. Sync actions on the same
639
- * instance reuse the current draft, so they can compose and publish once when
640
- * the outer action returns. Declared async actions publish their initial
641
- * synchronous work on return, then require `this.commit()` to publish later
642
- * writes made after `await`. Non-async actions stay synchronous; if one
643
- * returns a promise, sigma throws so async boundaries stay explicit.
644
- */
645
- actions(actions) {
646
- const type = getTypeInternals(this);
647
- for (const key in actions) {
648
- assertDefinitionKeyAvailable(type, key, "action");
649
- const actionFunction = actions[key];
650
- type._actionFunctions[key] = actionFunction;
651
- Object.defineProperty(this.prototype, key, { value: buildActionMethod(key, actionFunction) });
652
- }
653
- return this;
654
- }
655
- /**
656
- * Adds an explicit setup handler for side effects and owned resources.
657
- *
658
- * Every registered handler runs when `instance.setup(...)` is called, and the
659
- * setup argument list is inferred from the first handler you add.
660
- */
661
- setup(setup) {
662
- const type = getTypeInternals(this);
663
- type._setupFunction = setup;
664
- return this;
665
- }
666
- };
667
- function getTypeInternals(type) {
668
- return type;
9
+ function mergeDefaults(initial, defaults) {
10
+ if (!initial) return defaults;
11
+ const merged = { ...defaults };
12
+ for (const key in initial) if (initial[key] !== void 0) merged[key] = initial[key];
13
+ return merged;
669
14
  }
670
15
  //#endregion
671
16
  //#region src/listener.ts
672
- /**
673
- * A standalone typed event hub with `emit(...)` and `on(...)` methods and full
674
- * `EventTarget`, `listen(...)`, and `useListener(...)` compatibility.
675
- */
676
- var SigmaTarget = class extends EventTarget {
677
- /**
678
- * Emits a typed event from the hub.
679
- *
680
- * Void events dispatch a plain `Event`. Payload events dispatch a
681
- * `CustomEvent` whose `detail` holds the payload.
682
- */
683
- emit(name, ...[detail]) {
684
- this.dispatchEvent(detail === void 0 ? new Event(name) : new CustomEvent(name, { detail }));
685
- }
686
- /**
687
- * Registers a typed event listener and returns an unsubscribe function.
688
- *
689
- * Payload events pass their payload directly to the listener. Void events
690
- * call the listener with no arguments.
691
- */
692
- on(name, listener) {
693
- const adapter = (event) => listener(event.detail);
694
- this.addEventListener(name, adapter);
17
+ function listen(target, name, listener) {
18
+ if (target instanceof SigmaTarget) {
19
+ target[listenersSymbol].addListener(name, listener);
695
20
  return () => {
696
- this.removeEventListener(name, adapter);
21
+ target[listenersSymbol].removeListener(name, listener);
697
22
  };
698
23
  }
699
- };
700
- /** Adds a listener to a sigma state or DOM target and returns a cleanup function that removes it. */
701
- function listen(target, name, listener) {
702
- const adapter = isSigmaState(target) || target instanceof SigmaTarget ? (event) => listener(event.detail) : listener;
703
- target.addEventListener(name, adapter);
24
+ const eventTarget = target;
25
+ eventTarget.addEventListener(name, listener);
704
26
  return () => {
705
- target.removeEventListener(name, adapter);
27
+ eventTarget.removeEventListener(name, listener);
706
28
  };
707
29
  }
708
30
  //#endregion
709
- //#region src/hooks.ts
710
- /**
711
- * Creates one sigma-state instance for a component.
712
- *
713
- * `create()` runs once per mounted component instance. When the sigma state
714
- * defines setup, `useSigma(...)` also runs `setup(...setupArgs)` in an effect
715
- * and cleans it up when the setup arguments change or the component unmounts.
716
- */
717
- function useSigma(create, setupArgs) {
718
- const sigmaState = useState(create)[0];
719
- if (shouldSetup(sigmaState)) {
720
- const args = setupArgs ?? [];
721
- useEffect(() => sigmaState.setup(...args), [sigmaState, ...args]);
722
- }
723
- return sigmaState;
724
- }
31
+ //#region src/hooks/useListener.ts
725
32
  /**
726
33
  * Attaches an event listener in a component and cleans it up automatically.
727
34
  *
@@ -737,4 +44,37 @@ function useListener(target, name, listener) {
737
44
  }, [target, name]);
738
45
  }
739
46
  //#endregion
740
- export { SigmaTarget, SigmaType, isSigmaState, listen, query, replaceState, setAutoFreeze, snapshot, useListener, useSigma };
47
+ //#region src/hooks/useSigma.ts
48
+ const isArray = Array.isArray;
49
+ const depsCache = /* @__PURE__ */ new WeakMap();
50
+ function depsChanged(container, deps) {
51
+ const cachedDeps = depsCache.get(container);
52
+ if (!deps && !cachedDeps) return false;
53
+ if (!deps || !cachedDeps) return true;
54
+ if (deps.length !== cachedDeps.length || deps.some((dep, index) => !Object.is(dep, cachedDeps[index]))) return true;
55
+ return false;
56
+ }
57
+ function useSigma(create, optionsOrDeps) {
58
+ const container = useRef(null);
59
+ let setup;
60
+ let deps;
61
+ if (isArray(optionsOrDeps)) deps = optionsOrDeps;
62
+ else {
63
+ setup = optionsOrDeps?.setup;
64
+ deps = optionsOrDeps?.deps;
65
+ }
66
+ if (!container.current || depsChanged(container, deps)) {
67
+ depsCache.set(container, deps);
68
+ container.current = create();
69
+ }
70
+ const instance = container.current;
71
+ useEffect(() => {
72
+ if ("onSetup" in instance) {
73
+ const setupArgs = setup ? isArray(setup) ? setup : setup() : [];
74
+ return instance.setup(...setupArgs);
75
+ }
76
+ }, [instance, ...isArray(setup) ? setup : []]);
77
+ return castProtected(instance);
78
+ }
79
+ //#endregion
80
+ export { Sigma, SigmaTarget, castProtected, listen, mergeDefaults, query, setAutoFreeze, sigma, useListener, useSigma };