preact-sigma 5.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/README.md +24 -18
- package/dist/index.d.mts +50 -9
- package/dist/index.mjs +58 -140
- package/dist/persist.d.mts +69 -44
- package/dist/persist.mjs +40 -48
- package/dist/sigma-D1V3m1xk.d.mts +163 -0
- package/dist/sigma-DTMODzf8.mjs +457 -0
- package/docs/context.md +68 -58
- package/docs/migrations/v5-to-v6.md +273 -0
- package/docs/persist.md +19 -18
- 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 +21 -15
- package/examples/persist-search-draft.ts +33 -30
- package/examples/setup-act.ts +17 -9
- package/examples/sigma-target.ts +16 -7
- package/package.json +9 -10
- package/dist/framework-GgPzRfff.d.mts +0 -331
- package/dist/runtime-nX4Aygb8.mjs +0 -595
- package/examples/signal-access.ts +0 -31
package/dist/persist.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
@@ -11,12 +11,31 @@ function createIdentityCodec() {
|
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
+
function createPickCodec(keys) {
|
|
15
|
+
return {
|
|
16
|
+
version: 1,
|
|
17
|
+
encode(state) {
|
|
18
|
+
const stored = {};
|
|
19
|
+
for (const key of keys) stored[key] = state[key];
|
|
20
|
+
return stored;
|
|
21
|
+
},
|
|
22
|
+
decode(stored, context) {
|
|
23
|
+
if (!isPlainObject(stored)) throw new Error("[preact-sigma/persist] pick requires a plain object payload");
|
|
24
|
+
const partialStored = stored;
|
|
25
|
+
const restored = { ...context.baseState };
|
|
26
|
+
for (const key of keys) if (key in partialStored) restored[key] = partialStored[key];
|
|
27
|
+
return restored;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
14
31
|
function getCodec(options) {
|
|
15
|
-
|
|
32
|
+
if (options.codec) return options.codec;
|
|
33
|
+
if (options.pick) return createPickCodec(options.pick);
|
|
34
|
+
return createIdentityCodec();
|
|
16
35
|
}
|
|
17
36
|
function applyRecord(instance, key, record, codec) {
|
|
18
37
|
if (!record) return { status: "missing" };
|
|
19
|
-
const baseState = sigma.
|
|
38
|
+
const baseState = sigma.captureState(instance);
|
|
20
39
|
const nextState = codec.decode(record.value, {
|
|
21
40
|
baseState,
|
|
22
41
|
key,
|
|
@@ -29,46 +48,21 @@ function applyRecord(instance, key, record, codec) {
|
|
|
29
48
|
storedVersion: record.version
|
|
30
49
|
};
|
|
31
50
|
}
|
|
32
|
-
function
|
|
33
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
34
|
-
const prototype = Object.getPrototypeOf(value);
|
|
35
|
-
return prototype === Object.prototype || prototype === null;
|
|
36
|
-
}
|
|
37
|
-
/** Restores committed state from a persisted record through an async store. */
|
|
38
|
-
async function restoreState(instance, options) {
|
|
51
|
+
async function restore(instance, options) {
|
|
39
52
|
const codec = getCodec(options);
|
|
40
|
-
const record = await options.store.
|
|
53
|
+
const record = await options.store.get(options.key);
|
|
41
54
|
return applyRecord(instance, options.key, record, codec);
|
|
42
55
|
}
|
|
43
|
-
|
|
44
|
-
function restoreStateSync(instance, options) {
|
|
56
|
+
function restoreSync(instance, options) {
|
|
45
57
|
const codec = getCodec(options);
|
|
46
|
-
const record = options.store.
|
|
58
|
+
const record = options.store.get(options.key);
|
|
47
59
|
return applyRecord(instance, options.key, record, codec);
|
|
48
60
|
}
|
|
49
|
-
|
|
50
|
-
function pickStateCodec(keys) {
|
|
51
|
-
return {
|
|
52
|
-
version: 1,
|
|
53
|
-
encode(state) {
|
|
54
|
-
const stored = {};
|
|
55
|
-
for (const key of keys) stored[key] = state[key];
|
|
56
|
-
return stored;
|
|
57
|
-
},
|
|
58
|
-
decode(stored, context) {
|
|
59
|
-
if (!isPlainObject(stored)) throw new Error("[preact-sigma/persist] pickStateCodec() requires a plain object payload");
|
|
60
|
-
const partialStored = stored;
|
|
61
|
-
const restored = { ...context.baseState };
|
|
62
|
-
for (const key of keys) if (key in partialStored) restored[key] = partialStored[key];
|
|
63
|
-
return restored;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
/** Persists future committed state changes for one sigma-state instance. */
|
|
68
|
-
function persistState(instance, options) {
|
|
61
|
+
function persist(instance, options) {
|
|
69
62
|
const codec = getCodec(options);
|
|
70
63
|
const schedule = options.schedule ?? "microtask";
|
|
71
64
|
const key = options.key;
|
|
65
|
+
const store = options.store;
|
|
72
66
|
let stopped = false;
|
|
73
67
|
let suspended = false;
|
|
74
68
|
let hasPendingState = false;
|
|
@@ -124,7 +118,7 @@ function persistState(instance, options) {
|
|
|
124
118
|
}, schedule.debounceMs);
|
|
125
119
|
};
|
|
126
120
|
const queueStateWrite = () => {
|
|
127
|
-
pendingState = sigma.
|
|
121
|
+
pendingState = sigma.captureState(instance);
|
|
128
122
|
hasPendingState = true;
|
|
129
123
|
scheduleWrite();
|
|
130
124
|
};
|
|
@@ -135,7 +129,7 @@ function persistState(instance, options) {
|
|
|
135
129
|
while (hasPendingState && !stopped && !suspended) {
|
|
136
130
|
const state = pendingState;
|
|
137
131
|
hasPendingState = false;
|
|
138
|
-
await
|
|
132
|
+
await store.set(key, createRecord(state));
|
|
139
133
|
}
|
|
140
134
|
})();
|
|
141
135
|
try {
|
|
@@ -163,7 +157,7 @@ function persistState(instance, options) {
|
|
|
163
157
|
pendingState = void 0;
|
|
164
158
|
try {
|
|
165
159
|
await runningWrite;
|
|
166
|
-
await
|
|
160
|
+
await store.delete(key);
|
|
167
161
|
} finally {
|
|
168
162
|
suspended = false;
|
|
169
163
|
if (hasPendingState && !stopped) scheduleWrite();
|
|
@@ -184,13 +178,12 @@ function persistState(instance, options) {
|
|
|
184
178
|
}
|
|
185
179
|
};
|
|
186
180
|
}
|
|
187
|
-
|
|
188
|
-
function bindPersistence(instance, options) {
|
|
181
|
+
function hydrate(instance, options) {
|
|
189
182
|
let stopped = false;
|
|
190
183
|
let handle;
|
|
191
184
|
const restored = (async () => {
|
|
192
|
-
const result = await
|
|
193
|
-
if (!stopped) handle =
|
|
185
|
+
const result = await restore(instance, options);
|
|
186
|
+
if (!stopped) handle = persist(instance, options);
|
|
194
187
|
return result;
|
|
195
188
|
})();
|
|
196
189
|
return {
|
|
@@ -203,14 +196,14 @@ function bindPersistence(instance, options) {
|
|
|
203
196
|
try {
|
|
204
197
|
await restored;
|
|
205
198
|
} catch {
|
|
206
|
-
await options.store.
|
|
199
|
+
await options.store.delete(options.key);
|
|
207
200
|
return;
|
|
208
201
|
}
|
|
209
202
|
if (handle) {
|
|
210
203
|
await handle.clear();
|
|
211
204
|
return;
|
|
212
205
|
}
|
|
213
|
-
await options.store.
|
|
206
|
+
await options.store.delete(options.key);
|
|
214
207
|
},
|
|
215
208
|
async stop() {
|
|
216
209
|
if (stopped) {
|
|
@@ -227,10 +220,9 @@ function bindPersistence(instance, options) {
|
|
|
227
220
|
}
|
|
228
221
|
};
|
|
229
222
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
const handle = persistState(instance, options);
|
|
223
|
+
function hydrateSync(instance, options) {
|
|
224
|
+
const restored = restoreSync(instance, options);
|
|
225
|
+
const handle = persist(instance, options);
|
|
234
226
|
return {
|
|
235
227
|
restored,
|
|
236
228
|
clear() {
|
|
@@ -245,4 +237,4 @@ function bindPersistenceSync(instance, options) {
|
|
|
245
237
|
};
|
|
246
238
|
}
|
|
247
239
|
//#endregion
|
|
248
|
-
export {
|
|
240
|
+
export { hydrate, hydrateSync, persist, restore, restoreSync };
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
* Private class fields stay ordinary instance storage and are not signal-backed, captured, or persisted.
|
|
92
|
+
* Merge a same-named interface with the class when direct property reads should be typed on the instance.
|
|
93
|
+
*/
|
|
94
|
+
declare abstract class Sigma<TState extends object> {
|
|
95
|
+
[typeSymbol]: {
|
|
96
|
+
state: TState;
|
|
97
|
+
events: unknown;
|
|
98
|
+
};
|
|
99
|
+
[snapshotSymbol]: Record<string, unknown> | undefined;
|
|
100
|
+
protected get [instanceSymbol](): this;
|
|
101
|
+
constructor(initialState: TState);
|
|
102
|
+
/** Optional setup hook that owns side effects and returns cleanup resources. */
|
|
103
|
+
onSetup?(...args: any[]): readonly AnyResource[];
|
|
104
|
+
/** Runs `onSetup(...)` and returns a cleanup that disposes returned resources in reverse order. */
|
|
105
|
+
setup(...args: Parameters<Extract<this["onSetup"], AnyFunction>>): () => void;
|
|
106
|
+
/**
|
|
107
|
+
* Publishes the current action draft.
|
|
108
|
+
*
|
|
109
|
+
* Use this before unpublished changes cross an async, event, or external-action boundary.
|
|
110
|
+
* A callback runs after publish in an action context.
|
|
111
|
+
*/
|
|
112
|
+
commit<T = void>(callback?: (this: typeof this) => T): T | undefined;
|
|
113
|
+
/** Runs a synchronous setup-owned callback with action semantics from an `onSetup(...)` context. */
|
|
114
|
+
act(fn: (this: typeof this) => void): void;
|
|
115
|
+
}
|
|
116
|
+
/** Casts a sigma instance to its readonly public consumer view. */
|
|
117
|
+
declare function castProtected<T extends Sigma<any>>(instance: T): Protected<T>;
|
|
118
|
+
/**
|
|
119
|
+
* Sigma state model that can emit typed events.
|
|
120
|
+
*
|
|
121
|
+
* `TEvents` maps event names to payload types, and `TState` types reactive state.
|
|
122
|
+
*/
|
|
123
|
+
declare class SigmaTarget<TEvents extends object = {}, TState extends object = {}> extends Sigma<TState> {
|
|
124
|
+
[typeSymbol]: {
|
|
125
|
+
state: TState;
|
|
126
|
+
events: TEvents;
|
|
127
|
+
};
|
|
128
|
+
protected [listenersSymbol]: SigmaListenerMap;
|
|
129
|
+
constructor(state?: TState);
|
|
130
|
+
/** Emits a typed event from an action after unpublished draft changes are committed. */
|
|
131
|
+
emit<TEvent extends string & keyof TEvents>(name: TEvent, ...[detail]: EventParameters<TEvents[TEvent]>): void;
|
|
132
|
+
}
|
|
133
|
+
/** Helpers for observing, accessing, capturing, and replacing committed sigma state. */
|
|
134
|
+
declare const sigma: Readonly<{
|
|
135
|
+
/** Subscribes to committed state publishes or to one signal-backed top-level state key. */subscribe: {
|
|
136
|
+
<TState extends object>(instance: Sigma<TState>, listener: (nextState: Immutable<TState>, baseState: Immutable<TState>, patches: Patch[], inversePatches: Patch[]) => void, options: {
|
|
137
|
+
patches: true;
|
|
138
|
+
}): Cleanup;
|
|
139
|
+
<TState extends object>(instance: Sigma<TState>, listener: (nextState: Immutable<TState>, baseState: Immutable<TState>, patches: Patch[] | undefined, inversePatches: Patch[] | undefined) => void, options: {
|
|
140
|
+
patches: boolean;
|
|
141
|
+
}): Cleanup;
|
|
142
|
+
<TState extends object>(instance: Sigma<TState>, listener: (nextState: Immutable<TState>, baseState: Immutable<TState>) => void): Cleanup;
|
|
143
|
+
<TState extends object, TKey extends Extract<keyof TState, string>>(instance: Sigma<TState>, key: TKey, listener: (value: Immutable<TState[TKey]>) => void): Cleanup;
|
|
144
|
+
}; /** Returns the readonly signal backing one top-level state key. */
|
|
145
|
+
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. */
|
|
146
|
+
captureState<TState extends object>(instance: Sigma<TState>): Immutable<TState>; /** Publishes a plain-object snapshot as the current committed state. */
|
|
147
|
+
replaceState<TState extends object>(target: Sigma<TState>, nextState: TState): void;
|
|
148
|
+
}>;
|
|
149
|
+
/** Marks a class method as a committed-state reactive read with arguments instead of an action. */
|
|
150
|
+
declare function query<TThis extends object, TArgs extends any[], TReturn>(method: (this: TThis, ...args: TArgs) => TReturn): (this: TThis, ...args: TArgs) => TReturn;
|
|
151
|
+
declare const protectedSymbol: unique symbol;
|
|
152
|
+
type ProtectedKey = typeof listenersSymbol | typeof snapshotSymbol | "act" | "commit" | "emit" | "onSetup";
|
|
153
|
+
type BrandProtected<T> = T & {
|
|
154
|
+
[protectedSymbol]: true;
|
|
155
|
+
};
|
|
156
|
+
/** Readonly public view returned by `castProtected(...)` and `useSigma(...)`. */
|
|
157
|
+
type Protected<T extends Sigma<any>> = BrandProtected<T extends {
|
|
158
|
+
[typeSymbol]: {
|
|
159
|
+
state: infer TState extends object;
|
|
160
|
+
};
|
|
161
|
+
} ? { [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>;
|
|
162
|
+
//#endregion
|
|
163
|
+
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 };
|