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/README.md +26 -21
- package/dist/index.d.mts +41 -325
- package/dist/index.mjs +52 -712
- package/dist/persist.d.mts +126 -0
- package/dist/persist.mjs +240 -0
- package/dist/sigma-CJibGQ6g.mjs +383 -0
- package/dist/sigma-DD7HfTvw.d.mts +162 -0
- package/docs/context.md +81 -55
- package/docs/migrations/v5-to-v6.md +273 -0
- package/docs/persist.md +67 -0
- package/examples/async-commit.ts +38 -31
- package/examples/basic-counter.ts +21 -16
- package/examples/command-palette.tsx +114 -104
- package/examples/observe-and-restore.ts +25 -17
- package/examples/persist-search-draft.ts +70 -0
- package/examples/setup-act.ts +17 -9
- package/examples/sigma-target.ts +17 -8
- package/package.json +5 -1
- package/examples/signal-access.ts +0 -28
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { action, batch, computed, signal } from "@preact/signals";
|
|
2
|
+
import { createDraft as createDraft$1, finishDraft, freeze, isDraft, isDraftable, original, setAutoFreeze as setAutoFreeze$1 } from "immer";
|
|
3
|
+
//#region src/internal/symbols.ts
|
|
4
|
+
const instanceSymbol = Symbol.for("sigma:instance");
|
|
5
|
+
const listenersSymbol = Symbol.for("sigma:listeners");
|
|
6
|
+
const snapshotSymbol = Symbol.for("sigma:snapshot");
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/internal/listener.ts
|
|
9
|
+
/** Listener registry used by sigma targets and sigma states for typed event delivery. */
|
|
10
|
+
var SigmaListenerMap = class extends Map {
|
|
11
|
+
/** Delivers one event payload to the current listeners for `name`. */
|
|
12
|
+
emit(name, detail) {
|
|
13
|
+
const listeners = this.get(name);
|
|
14
|
+
if (!listeners?.size) return;
|
|
15
|
+
for (const listener of [...listeners]) listener(detail);
|
|
16
|
+
}
|
|
17
|
+
/** Adds one listener for `name`, creating the listener set on first use. */
|
|
18
|
+
addListener(name, listener) {
|
|
19
|
+
let listeners = this.get(name);
|
|
20
|
+
if (!listeners) {
|
|
21
|
+
listeners = /* @__PURE__ */ new Set();
|
|
22
|
+
this.set(name, listeners);
|
|
23
|
+
}
|
|
24
|
+
listeners.add(listener);
|
|
25
|
+
}
|
|
26
|
+
/** Removes one listener for `name` and prunes the empty listener set. */
|
|
27
|
+
removeListener(name, listener) {
|
|
28
|
+
const listeners = this.get(name);
|
|
29
|
+
if (!listeners) return;
|
|
30
|
+
listeners.delete(listener);
|
|
31
|
+
if (!listeners.size) this.delete(name);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/internal/utils.ts
|
|
36
|
+
function isPlainObject(value) {
|
|
37
|
+
if (typeof value !== "object" || value === null) return false;
|
|
38
|
+
const prototype = Object.getPrototypeOf(value);
|
|
39
|
+
return prototype === Object.prototype || prototype === null;
|
|
40
|
+
}
|
|
41
|
+
function isPromiseLike(value) {
|
|
42
|
+
return value != null && typeof value.then === "function";
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/sigma.ts
|
|
46
|
+
let autoFreezeEnabled = true;
|
|
47
|
+
/**
|
|
48
|
+
* Configures Immer auto-freezing for values published through sigma state.
|
|
49
|
+
*
|
|
50
|
+
* Auto-freezing is enabled by default, so draftable public values are deeply frozen after publish.
|
|
51
|
+
*/
|
|
52
|
+
function setAutoFreeze(autoFreeze) {
|
|
53
|
+
setAutoFreeze$1(autoFreeze);
|
|
54
|
+
autoFreezeEnabled = autoFreeze;
|
|
55
|
+
}
|
|
56
|
+
const signalSuffix = "$";
|
|
57
|
+
const changeListenersMap = /* @__PURE__ */ new WeakMap();
|
|
58
|
+
const patchListeners = /* @__PURE__ */ new WeakSet();
|
|
59
|
+
const initializedPrototypes = /* @__PURE__ */ new WeakSet();
|
|
60
|
+
const queries = /* @__PURE__ */ new WeakSet();
|
|
61
|
+
const emptySentinel = {};
|
|
62
|
+
let activeInstance = null;
|
|
63
|
+
let activeDraft;
|
|
64
|
+
let activeDerivedReadDepth = 0;
|
|
65
|
+
function ensureActiveInstance(instance) {
|
|
66
|
+
if (!activeInstance) {
|
|
67
|
+
activeInstance = instance;
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (instance !== activeInstance) throw new Error("Draft was not committed before an external action was invoked.");
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function clearActiveInstance() {
|
|
74
|
+
activeInstance = null;
|
|
75
|
+
activeDraft = null;
|
|
76
|
+
}
|
|
77
|
+
function isStateKey(instance, key) {
|
|
78
|
+
return Object.hasOwn(instance, key + signalSuffix);
|
|
79
|
+
}
|
|
80
|
+
function getStateSignal(instance, key) {
|
|
81
|
+
return instance[key + signalSuffix];
|
|
82
|
+
}
|
|
83
|
+
function createSnapshot(instance) {
|
|
84
|
+
if (instance[snapshotSymbol]) return instance[snapshotSymbol];
|
|
85
|
+
const state = {};
|
|
86
|
+
for (const key in instance) if (isStateKey(instance, key)) state[key] = instance[key];
|
|
87
|
+
return state;
|
|
88
|
+
}
|
|
89
|
+
function createDraft(instance) {
|
|
90
|
+
return createDraft$1(createSnapshot(instance));
|
|
91
|
+
}
|
|
92
|
+
function runDerivedRead(callback) {
|
|
93
|
+
activeDerivedReadDepth += 1;
|
|
94
|
+
try {
|
|
95
|
+
return callback();
|
|
96
|
+
} finally {
|
|
97
|
+
activeDerivedReadDepth -= 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function assertActionResult(key, result) {
|
|
101
|
+
if (isDraft(result)) throw new Error(`[preact-sigma] Action named "${key}" returned an active draft.`);
|
|
102
|
+
}
|
|
103
|
+
function hasPatchListeners(instance) {
|
|
104
|
+
const listeners = changeListenersMap.get(instance);
|
|
105
|
+
if (!listeners) return false;
|
|
106
|
+
for (const sub of listeners) if (patchListeners.has(sub)) return true;
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
function publishState(instance, nextState, baseState, patches, inversePatches) {
|
|
110
|
+
instance[snapshotSymbol] = nextState;
|
|
111
|
+
batch(() => {
|
|
112
|
+
const missingKeys = new Set(Object.keys(baseState));
|
|
113
|
+
for (const key in nextState) {
|
|
114
|
+
const nextValue = nextState[key];
|
|
115
|
+
if (autoFreezeEnabled) freeze(nextValue, true);
|
|
116
|
+
const signal = getStateSignal(instance, key);
|
|
117
|
+
if (signal) signal.value = nextValue;
|
|
118
|
+
else defineSignalProperty(instance, key, nextValue);
|
|
119
|
+
missingKeys.delete(key);
|
|
120
|
+
}
|
|
121
|
+
for (const key of missingKeys) {
|
|
122
|
+
const signal = getStateSignal(instance, key);
|
|
123
|
+
if (signal) signal.value = void 0;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
changeListenersMap.get(instance)?.forEach((listener) => {
|
|
127
|
+
listener(nextState, baseState, patches, inversePatches);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
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
|
+
function getActionInstance(context) {
|
|
160
|
+
return context[instanceSymbol];
|
|
161
|
+
}
|
|
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;
|
|
165
|
+
if (!activeDraft) return false;
|
|
166
|
+
const draft = activeDraft;
|
|
167
|
+
activeDraft = null;
|
|
168
|
+
let patches;
|
|
169
|
+
let inversePatches;
|
|
170
|
+
let patchListener;
|
|
171
|
+
if (hasPatchListeners(instance)) patchListener = (nextPatches, nextInversePatches) => {
|
|
172
|
+
patches = nextPatches;
|
|
173
|
+
inversePatches = nextInversePatches;
|
|
174
|
+
};
|
|
175
|
+
const baseState = original(draft);
|
|
176
|
+
const nextState = finishDraft(draft, patchListener);
|
|
177
|
+
const changed = baseState !== nextState;
|
|
178
|
+
if (changed) publishState(instance, nextState, baseState, patches, inversePatches);
|
|
179
|
+
return changed;
|
|
180
|
+
}
|
|
181
|
+
function initializePrototype(prototype) {
|
|
182
|
+
const descriptors = Object.getOwnPropertyDescriptors(prototype);
|
|
183
|
+
for (const key in descriptors) {
|
|
184
|
+
if (key === "constructor" || key === "onSetup") continue;
|
|
185
|
+
const { get, value } = descriptors[key];
|
|
186
|
+
if (get) descriptors[key].get = function() {
|
|
187
|
+
const instance = getActionInstance(this);
|
|
188
|
+
return (instance[key + signalSuffix] ??= computed(() => runDerivedRead(() => get.call(instance)))).value;
|
|
189
|
+
};
|
|
190
|
+
else if (typeof value === "function" && !queries.has(value)) {
|
|
191
|
+
const actionFn = action(value);
|
|
192
|
+
descriptors[key].value = function(...args) {
|
|
193
|
+
if (activeDerivedReadDepth) throw new Error("[preact-sigma] Computeds and queries cannot call actions.");
|
|
194
|
+
const instance = getActionInstance(this);
|
|
195
|
+
if (ensureActiveInstance(instance)) {
|
|
196
|
+
const result = value.apply(this, args);
|
|
197
|
+
assertActionResult(key, result);
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
let result;
|
|
201
|
+
try {
|
|
202
|
+
const actionContext = createActionContext(instance);
|
|
203
|
+
result = actionFn.apply(actionContext, args);
|
|
204
|
+
assertActionResult(key, result);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
clearActiveInstance();
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
const changed = ensureDraftCommitted(instance);
|
|
210
|
+
if (isPromiseLike(result)) {
|
|
211
|
+
if (changed) throw new Error(`[preact-sigma] Action named "${key}" forgot to commit() its draft before returning a promise.`);
|
|
212
|
+
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.`);
|
|
215
|
+
}
|
|
216
|
+
return promiseResult;
|
|
217
|
+
};
|
|
218
|
+
return result.then(onResolveAsyncAction);
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
Object.defineProperties(prototype, descriptors);
|
|
225
|
+
}
|
|
226
|
+
function initializeType(type) {
|
|
227
|
+
for (let prototype = type.prototype; prototype && prototype !== Sigma.prototype && prototype !== SigmaTarget.prototype; prototype = Object.getPrototypeOf(prototype)) {
|
|
228
|
+
if (initializedPrototypes.has(prototype)) break;
|
|
229
|
+
initializePrototype(prototype);
|
|
230
|
+
initializedPrototypes.add(prototype);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function disposeCleanupResource(resource) {
|
|
234
|
+
if (typeof resource === "function") resource();
|
|
235
|
+
else if ("dispose" in resource) resource.dispose();
|
|
236
|
+
else resource[Symbol.dispose]();
|
|
237
|
+
}
|
|
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
|
+
function defineSignalProperty(instance, key, value) {
|
|
252
|
+
Object.defineProperty(instance, key + signalSuffix, { value: signal(value) });
|
|
253
|
+
if (!Object.hasOwn(instance.constructor.prototype, key)) Object.defineProperty(instance.constructor.prototype, key, {
|
|
254
|
+
get() {
|
|
255
|
+
return this[key + signalSuffix].value;
|
|
256
|
+
},
|
|
257
|
+
enumerable: true
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Base class for signal-backed state models.
|
|
262
|
+
*
|
|
263
|
+
* `TState` is the source of typing for top-level state keys, subscriptions, signals, and replacement snapshots.
|
|
264
|
+
* Merge a same-named interface with the class when direct property reads should be typed on the instance.
|
|
265
|
+
*/
|
|
266
|
+
var Sigma = class {
|
|
267
|
+
get [instanceSymbol]() {
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
270
|
+
constructor(initialState) {
|
|
271
|
+
initializeType(this.constructor);
|
|
272
|
+
if (initialState === emptySentinel) return;
|
|
273
|
+
for (const key in initialState) {
|
|
274
|
+
const initialValue = initialState[key];
|
|
275
|
+
if (autoFreezeEnabled) freeze(initialValue, true);
|
|
276
|
+
defineSignalProperty(this, key, initialValue);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/** Runs `onSetup(...)` and returns a cleanup that disposes returned resources in reverse order. */
|
|
280
|
+
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);
|
|
287
|
+
return () => {
|
|
288
|
+
for (let i = resources.length - 1; i >= 0; i--) disposeCleanupResource(resources[i]);
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Publishes the current action draft.
|
|
293
|
+
*
|
|
294
|
+
* Use this before unpublished changes cross an async, event, or external-action boundary.
|
|
295
|
+
* A callback runs after publish in an action context.
|
|
296
|
+
*/
|
|
297
|
+
commit(callback) {
|
|
298
|
+
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
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/** Runs a synchronous setup-owned callback with action semantics from an `onSetup(...)` context. */
|
|
310
|
+
act(fn) {
|
|
311
|
+
throw new Error("Cannot act() from outside an onSetup() context.");
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
/** Casts a sigma instance to its readonly public consumer view. */
|
|
315
|
+
function castProtected(instance) {
|
|
316
|
+
return instance;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Sigma state model that can emit typed events.
|
|
320
|
+
*
|
|
321
|
+
* `TEvents` maps event names to payload types, and `TState` types reactive state.
|
|
322
|
+
*/
|
|
323
|
+
var SigmaTarget = class extends Sigma {
|
|
324
|
+
[listenersSymbol] = new SigmaListenerMap();
|
|
325
|
+
constructor(state) {
|
|
326
|
+
super(state ?? emptySentinel);
|
|
327
|
+
}
|
|
328
|
+
/** Emits a typed event from an action after unpublished draft changes are committed. */
|
|
329
|
+
emit(name, ...[detail]) {
|
|
330
|
+
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.");
|
|
333
|
+
this[listenersSymbol].emit(name, detail);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
/** Helpers for observing, accessing, capturing, and replacing committed sigma state. */
|
|
337
|
+
const sigma = /* @__PURE__ */ Object.freeze({
|
|
338
|
+
subscribe: ((instance, keyOrListener, listenerOrOptions) => {
|
|
339
|
+
if (typeof keyOrListener === "string") {
|
|
340
|
+
const signal = getStateSignal(instance, keyOrListener);
|
|
341
|
+
if (!signal) throw new Error(`[preact-sigma] Property named "${keyOrListener}" is not signal-backed.`);
|
|
342
|
+
return signal.subscribe(listenerOrOptions);
|
|
343
|
+
}
|
|
344
|
+
const listener = keyOrListener;
|
|
345
|
+
if (listenerOrOptions?.patches) patchListeners.add(listener);
|
|
346
|
+
let subscriptions = changeListenersMap.get(instance);
|
|
347
|
+
if (!subscriptions) {
|
|
348
|
+
subscriptions = /* @__PURE__ */ new Set();
|
|
349
|
+
changeListenersMap.set(instance, subscriptions);
|
|
350
|
+
}
|
|
351
|
+
subscriptions.add(listener);
|
|
352
|
+
return () => {
|
|
353
|
+
subscriptions.delete(listener);
|
|
354
|
+
if (!subscriptions.size) changeListenersMap.delete(instance);
|
|
355
|
+
};
|
|
356
|
+
}),
|
|
357
|
+
getSignal(instance, key) {
|
|
358
|
+
return getStateSignal(instance, key);
|
|
359
|
+
},
|
|
360
|
+
captureState(instance) {
|
|
361
|
+
return Object.freeze(createSnapshot(instance));
|
|
362
|
+
},
|
|
363
|
+
replaceState(target, nextState) {
|
|
364
|
+
if (!isPlainObject(nextState)) throw new Error("[preact-sigma] replaceState() requires a plain object snapshot");
|
|
365
|
+
if (activeDraft) throw new Error(`[preact-sigma] replaceState() cannot run while an action has unpublished changes.`);
|
|
366
|
+
const instance = getActionInstance(target);
|
|
367
|
+
const baseState = createSnapshot(instance);
|
|
368
|
+
const patches = hasPatchListeners(instance) ? [] : void 0;
|
|
369
|
+
publishState(instance, nextState, baseState, patches, patches ? [] : void 0);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
/** Marks a class method as a committed-state reactive read with arguments instead of an action. */
|
|
373
|
+
function query(method) {
|
|
374
|
+
queries.add(method);
|
|
375
|
+
function queryMethod(...args) {
|
|
376
|
+
const instance = getActionInstance(this);
|
|
377
|
+
return computed(() => runDerivedRead(() => method.apply(instance, args))).value;
|
|
378
|
+
}
|
|
379
|
+
queries.add(queryMethod);
|
|
380
|
+
return queryMethod;
|
|
381
|
+
}
|
|
382
|
+
//#endregion
|
|
383
|
+
export { setAutoFreeze as a, listenersSymbol as c, query as i, SigmaTarget as n, sigma as o, castProtected as r, isPlainObject as s, Sigma as t };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { ReadonlySignal } from "@preact/signals";
|
|
2
|
+
import { Patch } from "immer";
|
|
3
|
+
|
|
4
|
+
//#region src/internal/utils.d.ts
|
|
5
|
+
type AnyFunction = (...args: any[]) => any;
|
|
6
|
+
type Cleanup = () => void;
|
|
7
|
+
type AnyResource = Cleanup | {
|
|
8
|
+
dispose(): void;
|
|
9
|
+
} | {
|
|
10
|
+
[Symbol.dispose](): void;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/internal/listener.d.ts
|
|
14
|
+
/** Untyped listener shape stored internally by `SigmaListenerMap`. */
|
|
15
|
+
type RawSigmaListener = (detail: unknown) => void;
|
|
16
|
+
/** Listener registry used by sigma targets and sigma states for typed event delivery. */
|
|
17
|
+
declare class SigmaListenerMap extends Map<string, Set<RawSigmaListener>> {
|
|
18
|
+
/** Delivers one event payload to the current listeners for `name`. */
|
|
19
|
+
emit(name: string, detail: unknown): void;
|
|
20
|
+
/** Adds one listener for `name`, creating the listener set on first use. */
|
|
21
|
+
addListener(name: string, listener: RawSigmaListener): void;
|
|
22
|
+
/** Removes one listener for `name` and prunes the empty listener set. */
|
|
23
|
+
removeListener(name: string, listener: RawSigmaListener): void;
|
|
24
|
+
}
|
|
25
|
+
/** Infers the detail parameter for a typed emit. */
|
|
26
|
+
type EventParameters<T> = [void] extends [T] ? [detail?: T extends void ? undefined : T] : [undefined] extends [T] ? [detail?: T] : [detail: T];
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/internal/symbols.d.ts
|
|
29
|
+
declare const instanceSymbol: unique symbol;
|
|
30
|
+
declare const listenersSymbol: unique symbol;
|
|
31
|
+
declare const refSymbol: unique symbol;
|
|
32
|
+
declare const snapshotSymbol: unique symbol;
|
|
33
|
+
declare const typeSymbol: unique symbol;
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/immer.d.ts
|
|
36
|
+
type PrimitiveType = number | string | boolean;
|
|
37
|
+
/** Object types that should never be mapped */
|
|
38
|
+
type AtomicObject = AbortController | Date | EventTarget | Function | Promise<any> | RegExp | Sigma<any>;
|
|
39
|
+
/**
|
|
40
|
+
* If the lib "ES2015.Collection" is not included in tsconfig.json,
|
|
41
|
+
* types like ReadonlyArray, WeakMap etc. fall back to `any` (specified nowhere)
|
|
42
|
+
* or `{}` (from the node types), in both cases entering an infinite recursion in
|
|
43
|
+
* pattern matching type mappings
|
|
44
|
+
* This type can be used to cast these types to `void` in these cases.
|
|
45
|
+
*/
|
|
46
|
+
type IfAvailable<T, Fallback = void> = true | false extends (T extends never ? true : false) ? Fallback : keyof T extends never ? Fallback : T;
|
|
47
|
+
/**
|
|
48
|
+
* These should also never be mapped but must be tested after regular Map and
|
|
49
|
+
* Set
|
|
50
|
+
*/
|
|
51
|
+
type WeakReferences = IfAvailable<WeakMap<any, any>> | IfAvailable<WeakSet<any>>;
|
|
52
|
+
type WritableDraft<T> = T extends any[] ? number extends T["length"] ? Draft<T[number]>[] : WritableNonArrayDraft<T> : WritableNonArrayDraft<T>;
|
|
53
|
+
type WritableNonArrayDraft<T> = { -readonly [K in keyof T]: T[K] extends infer V ? (V extends object ? Draft<V> : V) : never };
|
|
54
|
+
/**
|
|
55
|
+
* Convert a readonly type into a mutable type, if possible.
|
|
56
|
+
*
|
|
57
|
+
* Use this instead of `immer.Draft`
|
|
58
|
+
*/
|
|
59
|
+
type Draft<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : keyof SigmaRef extends keyof T ? T : T extends ReadonlyMap<infer K, infer V> ? Map<Draft<K>, Draft<V>> : T extends ReadonlySet<infer V> ? Set<Draft<V>> : T extends WeakReferences ? T : T extends object ? WritableDraft<T> : T;
|
|
60
|
+
/**
|
|
61
|
+
* Convert a mutable type into a readonly type.
|
|
62
|
+
*
|
|
63
|
+
* Use this instead of `immer.Immutable`
|
|
64
|
+
*/
|
|
65
|
+
type Immutable<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : keyof SigmaRef extends keyof T ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<Immutable<K>, Immutable<V>> : T extends ReadonlySet<infer V> ? ReadonlySet<Immutable<V>> : T extends WeakReferences ? T : T extends object ? { readonly [K in keyof T]: Immutable<T[K]> } : T;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/sigma.d.ts
|
|
68
|
+
/**
|
|
69
|
+
* Configures Immer auto-freezing for values published through sigma state.
|
|
70
|
+
*
|
|
71
|
+
* Auto-freezing is enabled by default, so draftable public values are deeply frozen after publish.
|
|
72
|
+
*/
|
|
73
|
+
declare function setAutoFreeze(autoFreeze: boolean): void;
|
|
74
|
+
/** Marks object values that should keep their reference-like type in `Draft` and `Immutable` mappings. */
|
|
75
|
+
type SigmaRef<T extends object = {}> = T & {
|
|
76
|
+
[refSymbol]?: true;
|
|
77
|
+
};
|
|
78
|
+
/** Definition shape used by helper types that need both state and event maps. */
|
|
79
|
+
type SigmaDefinition = {
|
|
80
|
+
state: object;
|
|
81
|
+
events?: object;
|
|
82
|
+
};
|
|
83
|
+
/** Instance type for a sigma definition with state typing preserved for public helpers. */
|
|
84
|
+
type SigmaState<T extends SigmaDefinition> = Sigma<T["state"]> & {
|
|
85
|
+
[typeSymbol]: T;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Base class for signal-backed state models.
|
|
89
|
+
*
|
|
90
|
+
* `TState` is the source of typing for top-level state keys, subscriptions, signals, and replacement snapshots.
|
|
91
|
+
* Merge a same-named interface with the class when direct property reads should be typed on the instance.
|
|
92
|
+
*/
|
|
93
|
+
declare abstract class Sigma<TState extends object> {
|
|
94
|
+
[typeSymbol]: {
|
|
95
|
+
state: TState;
|
|
96
|
+
events: unknown;
|
|
97
|
+
};
|
|
98
|
+
[snapshotSymbol]: Record<string, unknown> | undefined;
|
|
99
|
+
protected get [instanceSymbol](): this;
|
|
100
|
+
constructor(initialState: TState);
|
|
101
|
+
/** Optional setup hook that owns side effects and returns cleanup resources. */
|
|
102
|
+
onSetup?(...args: any[]): readonly AnyResource[];
|
|
103
|
+
/** Runs `onSetup(...)` and returns a cleanup that disposes returned resources in reverse order. */
|
|
104
|
+
setup(...args: Parameters<Extract<this["onSetup"], AnyFunction>>): () => void;
|
|
105
|
+
/**
|
|
106
|
+
* Publishes the current action draft.
|
|
107
|
+
*
|
|
108
|
+
* Use this before unpublished changes cross an async, event, or external-action boundary.
|
|
109
|
+
* A callback runs after publish in an action context.
|
|
110
|
+
*/
|
|
111
|
+
commit<T = void>(callback?: (this: typeof this) => T): T | undefined;
|
|
112
|
+
/** Runs a synchronous setup-owned callback with action semantics from an `onSetup(...)` context. */
|
|
113
|
+
act(fn: (this: typeof this) => void): void;
|
|
114
|
+
}
|
|
115
|
+
/** Casts a sigma instance to its readonly public consumer view. */
|
|
116
|
+
declare function castProtected<T extends Sigma<any>>(instance: T): Protected<T>;
|
|
117
|
+
/**
|
|
118
|
+
* Sigma state model that can emit typed events.
|
|
119
|
+
*
|
|
120
|
+
* `TEvents` maps event names to payload types, and `TState` types reactive state.
|
|
121
|
+
*/
|
|
122
|
+
declare class SigmaTarget<TEvents extends object = {}, TState extends object = {}> extends Sigma<TState> {
|
|
123
|
+
[typeSymbol]: {
|
|
124
|
+
state: TState;
|
|
125
|
+
events: TEvents;
|
|
126
|
+
};
|
|
127
|
+
protected [listenersSymbol]: SigmaListenerMap;
|
|
128
|
+
constructor(state?: TState);
|
|
129
|
+
/** Emits a typed event from an action after unpublished draft changes are committed. */
|
|
130
|
+
emit<TEvent extends string & keyof TEvents>(name: TEvent, ...[detail]: EventParameters<TEvents[TEvent]>): void;
|
|
131
|
+
}
|
|
132
|
+
/** Helpers for observing, accessing, capturing, and replacing committed sigma state. */
|
|
133
|
+
declare const sigma: Readonly<{
|
|
134
|
+
/** Subscribes to committed state publishes or to one signal-backed top-level state key. */subscribe: {
|
|
135
|
+
<TState extends object>(instance: Sigma<TState>, listener: (nextState: Immutable<TState>, baseState: Immutable<TState>, patches: Patch[], inversePatches: Patch[]) => void, options: {
|
|
136
|
+
patches: true;
|
|
137
|
+
}): Cleanup;
|
|
138
|
+
<TState extends object>(instance: Sigma<TState>, listener: (nextState: Immutable<TState>, baseState: Immutable<TState>, patches: Patch[] | undefined, inversePatches: Patch[] | undefined) => void, options: {
|
|
139
|
+
patches: boolean;
|
|
140
|
+
}): Cleanup;
|
|
141
|
+
<TState extends object>(instance: Sigma<TState>, listener: (nextState: Immutable<TState>, baseState: Immutable<TState>) => void): Cleanup;
|
|
142
|
+
<TState extends object, TKey extends Extract<keyof TState, string>>(instance: Sigma<TState>, key: TKey, listener: (value: Immutable<TState[TKey]>) => void): Cleanup;
|
|
143
|
+
}; /** Returns the readonly signal backing one top-level state key. */
|
|
144
|
+
getSignal<TState extends object, TKey extends Extract<keyof TState, string>>(instance: Sigma<TState>, key: TKey): ReadonlySignal<Immutable<TState[TKey]>>; /** Captures the current committed top-level state snapshot. */
|
|
145
|
+
captureState<TState extends object>(instance: Sigma<TState>): Immutable<TState>; /** Publishes a plain-object snapshot as the current committed state. */
|
|
146
|
+
replaceState<TState extends object>(target: Sigma<TState>, nextState: TState): void;
|
|
147
|
+
}>;
|
|
148
|
+
/** Marks a class method as a committed-state reactive read with arguments instead of an action. */
|
|
149
|
+
declare function query<TThis extends object, TArgs extends any[], TReturn>(method: (this: TThis, ...args: TArgs) => TReturn): (this: TThis, ...args: TArgs) => TReturn;
|
|
150
|
+
declare const protectedSymbol: unique symbol;
|
|
151
|
+
type ProtectedKey = typeof listenersSymbol | typeof snapshotSymbol | "act" | "commit" | "emit" | "onSetup";
|
|
152
|
+
type BrandProtected<T> = T & {
|
|
153
|
+
[protectedSymbol]: true;
|
|
154
|
+
};
|
|
155
|
+
/** Readonly public view returned by `castProtected(...)` and `useSigma(...)`. */
|
|
156
|
+
type Protected<T extends Sigma<any>> = BrandProtected<T extends {
|
|
157
|
+
[typeSymbol]: {
|
|
158
|
+
state: infer TState extends object;
|
|
159
|
+
};
|
|
160
|
+
} ? { [K in keyof T as K extends ProtectedKey ? never : K]: K extends typeof typeSymbol ? T[K] : K extends keyof TState ? Immutable<T[K]> : T[K] extends AnyFunction ? (...params: Parameters<T[K]>) => Immutable<ReturnType<T[K]>> : Immutable<T[K]> } : never>;
|
|
161
|
+
//#endregion
|
|
162
|
+
export { SigmaState as a, query as c, Draft as d, Immutable as f, SigmaRef as i, setAutoFreeze as l, Cleanup as m, Sigma as n, SigmaTarget as o, typeSymbol as p, SigmaDefinition as r, castProtected as s, Protected as t, sigma as u };
|