preact-sigma 4.0.0 → 5.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 +3 -4
- package/dist/framework-GgPzRfff.d.mts +331 -0
- package/dist/index.d.mts +3 -328
- package/dist/index.mjs +5 -583
- package/dist/persist.d.mts +101 -0
- package/dist/persist.mjs +248 -0
- package/dist/runtime-nX4Aygb8.mjs +595 -0
- package/docs/context.md +33 -14
- package/docs/persist.md +66 -0
- package/examples/async-commit.ts +2 -2
- package/examples/observe-and-restore.ts +9 -7
- package/examples/persist-search-draft.ts +67 -0
- package/examples/sigma-target.ts +1 -1
- package/examples/signal-access.ts +5 -2
- package/package.json +14 -9
package/docs/persist.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Persist
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`preact-sigma/persist` persists and restores committed top-level sigma state without moving storage, scheduling, or migration policy into `SigmaType`.
|
|
6
|
+
|
|
7
|
+
The module builds on the core committed-state helpers:
|
|
8
|
+
|
|
9
|
+
- `sigma.getState(instance)` reads the current committed snapshot.
|
|
10
|
+
- `sigma.replaceState(instance, nextState)` restores a committed snapshot.
|
|
11
|
+
- `sigma.subscribe(instance, handler)` observes future committed publishes.
|
|
12
|
+
|
|
13
|
+
Use the persist module when those primitives are the right boundary, but you do not want each application to reimplement store adapters, partial persistence, restore sequencing, or write scheduling. Exact signatures live in [`dist/persist.d.mts`](../dist/persist.d.mts).
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- State should survive reloads, navigation, or app restarts.
|
|
18
|
+
- Persistence needs to stay instance-specific instead of becoming part of the model definition.
|
|
19
|
+
- Storage may be synchronous or asynchronous.
|
|
20
|
+
- Stored payloads need versioning, migration, or partial persistence.
|
|
21
|
+
- Restore and future persistence should share one small lifecycle helper.
|
|
22
|
+
|
|
23
|
+
## When Not to Use
|
|
24
|
+
|
|
25
|
+
- A one-off snapshot or replay flow is enough. Use `sigma.getState(...)` and `sigma.replaceState(...)` directly.
|
|
26
|
+
- The data is really a remote cache, normalization layer, or conflict-resolution problem.
|
|
27
|
+
- You need unpublished drafts, computeds, queries, setup resources, or emitted events persisted.
|
|
28
|
+
- The model should start side effects before async restore completes. Sequence that explicitly outside `useSigma(...)`.
|
|
29
|
+
|
|
30
|
+
## Core Pieces
|
|
31
|
+
|
|
32
|
+
- Store: owns `read`, `write`, and `remove` for persisted records.
|
|
33
|
+
- Codec: owns payload shape, versioning, and migration logic between stored data and a full committed snapshot.
|
|
34
|
+
- Helper: owns restore sequencing, subscription lifecycle, and write scheduling for one sigma-state instance.
|
|
35
|
+
|
|
36
|
+
## Common Tasks -> Recommended APIs
|
|
37
|
+
|
|
38
|
+
- Restore once through an async store: `restoreState(instance, options)`
|
|
39
|
+
- Restore once through a sync store: `restoreStateSync(instance, options)`
|
|
40
|
+
- Persist future committed changes only: `persistState(instance, options)`
|
|
41
|
+
- Restore first, then persist future changes: `bindPersistence(instance, options)` or `bindPersistenceSync(instance, options)`
|
|
42
|
+
- Persist only selected top-level keys while restoring the full state shape: `pickStateCodec(keys)`
|
|
43
|
+
|
|
44
|
+
## Scheduling and Lifecycle
|
|
45
|
+
|
|
46
|
+
- Persistence helpers only read and write committed snapshots. Unpublished drafts never reach storage.
|
|
47
|
+
- `persistState(...)` defaults to `"microtask"` scheduling so multiple same-turn publishes can coalesce into one write.
|
|
48
|
+
- `writeInitial` defaults to `false`, which prevents a new binding from overwriting an older record before restore runs.
|
|
49
|
+
- `flush()` waits for scheduled or active writes to finish.
|
|
50
|
+
- `clear()` removes the stored record and keeps the binding usable for later writes.
|
|
51
|
+
- `stop()` unsubscribes the binding and waits for any in-flight write to settle.
|
|
52
|
+
- `bindPersistence(...)` starts future persistence only after restore resolves successfully.
|
|
53
|
+
|
|
54
|
+
## Constraints
|
|
55
|
+
|
|
56
|
+
- `sigma.replaceState(...)` still requires a plain object with the exact top-level state-key shape.
|
|
57
|
+
- Partial persistence codecs must reconstruct a full replacement snapshot before restore finishes.
|
|
58
|
+
- Nested sigma-state values are stored only if the chosen codec and payload format support them explicitly.
|
|
59
|
+
- Async restore failures reject through `restoreState(...)` or the `restored` promise from `bindPersistence(...)`.
|
|
60
|
+
- Background write failures route through `onWriteError(...)` without automatically stopping persistence.
|
|
61
|
+
|
|
62
|
+
## Example Routes
|
|
63
|
+
|
|
64
|
+
- [`examples/persist-search-draft.ts`](../examples/persist-search-draft.ts): sync restore-first persistence with `bindPersistenceSync(...)` and `pickStateCodec(...)`
|
|
65
|
+
- [`examples/observe-and-restore.ts`](../examples/observe-and-restore.ts): direct snapshot and restore without the persist subpath
|
|
66
|
+
- [`dist/persist.d.mts`](../dist/persist.d.mts): exact exported signatures for the persist module
|
package/examples/async-commit.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SigmaType } from "preact-sigma";
|
|
1
|
+
import { listen, SigmaType } from "preact-sigma";
|
|
2
2
|
|
|
3
3
|
const SaveIndicator = new SigmaType<
|
|
4
4
|
{
|
|
@@ -32,7 +32,7 @@ const SaveIndicator = new SigmaType<
|
|
|
32
32
|
|
|
33
33
|
const indicator = new SaveIndicator();
|
|
34
34
|
|
|
35
|
-
indicator
|
|
35
|
+
listen(indicator, "saved", ({ count }) => {
|
|
36
36
|
console.log(`Saved ${count} times`);
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sigma, SigmaType } from "preact-sigma";
|
|
2
2
|
|
|
3
3
|
const TodoList = new SigmaType<{
|
|
4
4
|
todos: string[];
|
|
@@ -6,9 +6,6 @@ const TodoList = new SigmaType<{
|
|
|
6
6
|
.defaultState({
|
|
7
7
|
todos: [],
|
|
8
8
|
})
|
|
9
|
-
.observe(function (change) {
|
|
10
|
-
console.log(`${change.oldState.todos.length} -> ${change.newState.todos.length}`);
|
|
11
|
-
})
|
|
12
9
|
.actions({
|
|
13
10
|
add(title: string) {
|
|
14
11
|
this.todos.push(title);
|
|
@@ -16,12 +13,17 @@ const TodoList = new SigmaType<{
|
|
|
16
13
|
});
|
|
17
14
|
|
|
18
15
|
const todoList = new TodoList();
|
|
16
|
+
const stop = sigma.subscribe(todoList, (change) => {
|
|
17
|
+
console.log(`${change.oldState.todos.length} -> ${change.newState.todos.length}`);
|
|
18
|
+
});
|
|
19
19
|
|
|
20
20
|
todoList.add("Write docs");
|
|
21
21
|
|
|
22
|
-
const saved =
|
|
22
|
+
const saved = sigma.getState(todoList);
|
|
23
23
|
|
|
24
24
|
todoList.add("Ship release");
|
|
25
|
-
replaceState(todoList, saved);
|
|
25
|
+
sigma.replaceState(todoList, saved);
|
|
26
|
+
|
|
27
|
+
console.log(sigma.getState(todoList).todos); // ["Write docs"]
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
stop();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { SigmaType } from "preact-sigma";
|
|
2
|
+
import {
|
|
3
|
+
bindPersistenceSync,
|
|
4
|
+
pickStateCodec,
|
|
5
|
+
type PersistRecord,
|
|
6
|
+
type SyncPersistStore,
|
|
7
|
+
} from "preact-sigma/persist";
|
|
8
|
+
|
|
9
|
+
const Search = new SigmaType<{
|
|
10
|
+
draft: string;
|
|
11
|
+
page: number;
|
|
12
|
+
}>("Search")
|
|
13
|
+
.defaultState({
|
|
14
|
+
draft: "",
|
|
15
|
+
page: 1,
|
|
16
|
+
})
|
|
17
|
+
.actions({
|
|
18
|
+
nextPage() {
|
|
19
|
+
this.page += 1;
|
|
20
|
+
},
|
|
21
|
+
setDraft(draft: string) {
|
|
22
|
+
this.draft = draft;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const records = new Map<string, PersistRecord<{ draft: string }>>([
|
|
27
|
+
[
|
|
28
|
+
"search",
|
|
29
|
+
{
|
|
30
|
+
savedAt: 100,
|
|
31
|
+
value: { draft: "restored" },
|
|
32
|
+
version: 1,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const store: SyncPersistStore<PersistRecord<{ draft: string }>> = {
|
|
38
|
+
read(key) {
|
|
39
|
+
return records.get(key);
|
|
40
|
+
},
|
|
41
|
+
write(key, record) {
|
|
42
|
+
records.set(key, record);
|
|
43
|
+
},
|
|
44
|
+
remove(key) {
|
|
45
|
+
records.delete(key);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const search = new Search({ page: 3 });
|
|
50
|
+
const persistence = bindPersistenceSync(search, {
|
|
51
|
+
codec: pickStateCodec(["draft"]),
|
|
52
|
+
key: "search",
|
|
53
|
+
store,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(persistence.restored); // { status: "restored", savedAt: 100, storedVersion: 1 }
|
|
57
|
+
console.log(search.draft); // "restored"
|
|
58
|
+
console.log(search.page); // 3
|
|
59
|
+
|
|
60
|
+
search.setDraft("signals");
|
|
61
|
+
search.nextPage();
|
|
62
|
+
|
|
63
|
+
await persistence.flush();
|
|
64
|
+
|
|
65
|
+
console.log(records.get("search")?.value); // { draft: "signals" }
|
|
66
|
+
|
|
67
|
+
await persistence.stop();
|
package/examples/sigma-target.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { effect, SigmaType } from "preact-sigma";
|
|
1
|
+
import { effect, sigma, SigmaType } from "preact-sigma";
|
|
2
2
|
|
|
3
3
|
const Counter = new SigmaType<{
|
|
4
4
|
count: number;
|
|
@@ -20,7 +20,10 @@ const Counter = new SigmaType<{
|
|
|
20
20
|
const counter = new Counter();
|
|
21
21
|
|
|
22
22
|
const stop = effect(() => {
|
|
23
|
-
console.log(
|
|
23
|
+
console.log(
|
|
24
|
+
sigma.getSignal(counter, "count").value,
|
|
25
|
+
sigma.getSignal(counter, "doubled").value,
|
|
26
|
+
);
|
|
24
27
|
});
|
|
25
28
|
|
|
26
29
|
counter.increment();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "preact-sigma",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alec Larson",
|
|
@@ -18,8 +18,20 @@
|
|
|
18
18
|
".": {
|
|
19
19
|
"types": "./dist/index.d.mts",
|
|
20
20
|
"import": "./dist/index.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./persist": {
|
|
23
|
+
"types": "./dist/persist.d.mts",
|
|
24
|
+
"import": "./dist/persist.mjs"
|
|
21
25
|
}
|
|
22
26
|
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsdown",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"typecheck": "tsc --noEmit --allowImportingTsExtensions",
|
|
31
|
+
"fmt": "oxfmt",
|
|
32
|
+
"lint": "oxlint",
|
|
33
|
+
"prepublishOnly": "pnpm build"
|
|
34
|
+
},
|
|
23
35
|
"devDependencies": {
|
|
24
36
|
"@preact/preset-vite": "^2.10.5",
|
|
25
37
|
"@types/node": "^25.5.0",
|
|
@@ -37,12 +49,5 @@
|
|
|
37
49
|
"@preact/signals": ">=2",
|
|
38
50
|
"immer": ">=11",
|
|
39
51
|
"preact": ">=10"
|
|
40
|
-
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"build": "tsdown",
|
|
43
|
-
"test": "vitest run",
|
|
44
|
-
"typecheck": "tsc --noEmit --allowImportingTsExtensions",
|
|
45
|
-
"fmt": "oxfmt",
|
|
46
|
-
"lint": "oxlint"
|
|
47
52
|
}
|
|
48
|
-
}
|
|
53
|
+
}
|