@e280/strata 0.2.1 → 0.2.3
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 +132 -131
- package/package.json +7 -5
- package/s/index.ts +1 -0
- package/s/prism/chrono/chronicle.ts +11 -0
- package/s/prism/chrono/chrono.ts +91 -0
- package/s/prism/chrono/types.ts +12 -0
- package/s/prism/index.ts +11 -0
- package/s/prism/lens.ts +54 -0
- package/s/prism/prism.test.ts +330 -0
- package/s/prism/prism.ts +41 -0
- package/s/prism/types.ts +27 -0
- package/s/prism/utils/cache-cell.ts +22 -0
- package/s/prism/utils/immute.ts +8 -0
- package/s/prism/utils/optic-symbol.ts +3 -0
- package/s/prism/vault/local-store.ts +31 -0
- package/s/prism/vault/types.ts +19 -0
- package/s/prism/vault/vault.ts +19 -0
- package/s/signals/core/effect.ts +2 -2
- package/s/tests.test.ts +4 -1
- package/s/tree/parts/branch.ts +28 -14
- package/s/tree/parts/chronobranch.ts +4 -2
- package/s/tree/parts/trunk.ts +18 -22
- package/s/tree/parts/types.ts +36 -31
- package/s/tree/parts/utils/immute.ts +43 -0
- package/s/tree/parts/utils/process-options.ts +2 -2
- package/s/tree/tree.test.ts +69 -19
- package/x/index.d.ts +1 -0
- package/x/index.js +1 -0
- package/x/index.js.map +1 -1
- package/x/prism/chrono/chronicle.d.ts +2 -0
- package/x/prism/chrono/chronicle.js +8 -0
- package/x/prism/chrono/chronicle.js.map +1 -0
- package/x/prism/chrono/chrono.d.ts +26 -0
- package/x/prism/chrono/chrono.js +79 -0
- package/x/prism/chrono/chrono.js.map +1 -0
- package/x/prism/chrono/types.d.ts +5 -0
- package/x/prism/chrono/types.js +2 -0
- package/x/prism/chrono/types.js.map +1 -0
- package/x/prism/index.d.ts +9 -0
- package/x/prism/index.js +10 -0
- package/x/prism/index.js.map +1 -0
- package/x/prism/lens.d.ts +13 -0
- package/x/prism/lens.js +45 -0
- package/x/prism/lens.js.map +1 -0
- package/x/prism/prism.d.ts +10 -0
- package/x/prism/prism.js +34 -0
- package/x/prism/prism.js.map +1 -0
- package/x/prism/prism.test.d.ts +35 -0
- package/x/prism/prism.test.js +286 -0
- package/x/prism/prism.test.js.map +1 -0
- package/x/prism/types.d.ts +17 -0
- package/x/prism/types.js +2 -0
- package/x/prism/types.js.map +1 -0
- package/x/prism/utils/cache-cell.d.ts +7 -0
- package/x/prism/utils/cache-cell.js +20 -0
- package/x/prism/utils/cache-cell.js.map +1 -0
- package/x/prism/utils/immute.d.ts +2 -0
- package/x/prism/utils/immute.js +5 -0
- package/x/prism/utils/immute.js.map +1 -0
- package/x/prism/utils/optic-symbol.d.ts +1 -0
- package/x/prism/utils/optic-symbol.js +2 -0
- package/x/prism/utils/optic-symbol.js.map +1 -0
- package/x/prism/vault/local-store.d.ts +9 -0
- package/x/prism/vault/local-store.js +27 -0
- package/x/prism/vault/local-store.js.map +1 -0
- package/x/prism/vault/types.d.ts +14 -0
- package/x/prism/vault/types.js +2 -0
- package/x/prism/vault/types.js.map +1 -0
- package/x/prism/vault/vault.d.ts +7 -0
- package/x/prism/vault/vault.js +17 -0
- package/x/prism/vault/vault.js.map +1 -0
- package/x/signals/core/effect.js +2 -2
- package/x/signals/core/effect.js.map +1 -1
- package/x/tests.test.js +3 -1
- package/x/tests.test.js.map +1 -1
- package/x/tree/parts/branch.d.ts +4 -2
- package/x/tree/parts/branch.js +20 -9
- package/x/tree/parts/branch.js.map +1 -1
- package/x/tree/parts/chronobranch.d.ts +5 -3
- package/x/tree/parts/chronobranch.js +1 -0
- package/x/tree/parts/chronobranch.js.map +1 -1
- package/x/tree/parts/trunk.d.ts +8 -6
- package/x/tree/parts/trunk.js +14 -14
- package/x/tree/parts/trunk.js.map +1 -1
- package/x/tree/parts/types.d.ts +5 -20
- package/x/tree/parts/utils/immute.d.ts +11 -0
- package/x/tree/parts/utils/immute.js +33 -0
- package/x/tree/parts/utils/immute.js.map +1 -0
- package/x/tree/parts/utils/process-options.d.ts +2 -2
- package/x/tree/parts/utils/process-options.js.map +1 -1
- package/x/tree/tree.test.d.ts +6 -3
- package/x/tree/tree.test.js +64 -18
- package/x/tree/tree.test.js.map +1 -1
package/README.md
CHANGED
|
@@ -9,20 +9,22 @@
|
|
|
9
9
|
|
|
10
10
|
### get in loser, we're managing state
|
|
11
11
|
📦 `npm install @e280/strata`
|
|
12
|
+
✨ it's all about automagically rerendering ui when data changes
|
|
13
|
+
🦝 powers auto-reactivity in our view library [@e280/sly](https://github.com/e280/sly)
|
|
12
14
|
🧙♂️ probably my tenth state management library, lol
|
|
13
|
-
|
|
14
|
-
🦝 powers reactivity in our view library [@e280/sly](https://github.com/e280/sly)
|
|
15
|
-
🧑💻 a project by https://e280.org/
|
|
15
|
+
🧑💻 a project by https://e280.org/
|
|
16
16
|
|
|
17
|
-
🚦 **signals** — ephemeral view-level state
|
|
18
|
-
|
|
19
|
-
🪄 **tracker** — reactivity integration hub
|
|
17
|
+
🚦 [**signals**](#signals) — ephemeral view-level state
|
|
18
|
+
🔮 [**prism**](#prism) — app-level state tree
|
|
19
|
+
🪄 [**tracker**](#tracker) — reactivity integration hub
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
<br/><br/>
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
<a id="signals"></a>
|
|
26
|
+
|
|
27
|
+
## 🍋 strata signals
|
|
26
28
|
> *ephemeral view-level state*
|
|
27
29
|
|
|
28
30
|
```ts
|
|
@@ -150,144 +152,143 @@ import {signal, effect} from "@e280/strata"
|
|
|
150
152
|
|
|
151
153
|
<br/><br/>
|
|
152
154
|
|
|
153
|
-
|
|
154
|
-
> *persistent app-level state*
|
|
155
|
+
<a id="prism"></a>
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
```
|
|
157
|
+
## 🍋 strata prism
|
|
158
|
+
> *persistent app-level state*
|
|
159
159
|
|
|
160
160
|
- single-source-of-truth state tree
|
|
161
|
-
- immutable except for `mutate(fn)` calls
|
|
162
|
-
- localStorage persistence, cross-tab sync, undo/redo history
|
|
163
161
|
- no spooky-dookie proxy magic — just god's honest javascript
|
|
162
|
+
- immutable except for `mutate(fn)` calls
|
|
163
|
+
- use many lenses, efficient reactivity
|
|
164
|
+
- chrono provides undo/redo history
|
|
165
|
+
- persistence, localstorage, cross-tab sync
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
```
|
|
203
|
-
- you can branch a branch
|
|
167
|
+
### 🔮 prism and lenses
|
|
168
|
+
- **import prism**
|
|
169
|
+
```ts
|
|
170
|
+
import {Prism} from "@e280/strata"
|
|
171
|
+
```
|
|
172
|
+
- **prism is a state tree**
|
|
173
|
+
```ts
|
|
174
|
+
const prism = new Prism({
|
|
175
|
+
snacks: {
|
|
176
|
+
peanuts: 8,
|
|
177
|
+
bag: ["popcorn", "butter"],
|
|
178
|
+
person: {
|
|
179
|
+
name: "chase",
|
|
180
|
+
incredi: true,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
```
|
|
185
|
+
- **create lenses, which are views into state subtrees**
|
|
186
|
+
```ts
|
|
187
|
+
const snacks = prism.lens(state => state.snacks)
|
|
188
|
+
const person = snacks.lens(state => state.person)
|
|
189
|
+
```
|
|
190
|
+
- you can lens another lens
|
|
191
|
+
- **lenses provide immutable access to state**
|
|
192
|
+
```ts
|
|
193
|
+
snacks.state.peanuts // 8
|
|
194
|
+
person.state.name // "chase"
|
|
195
|
+
```
|
|
196
|
+
- **only formal mutations can change state**
|
|
197
|
+
```ts
|
|
198
|
+
snacks.state.peanuts++
|
|
199
|
+
// ⛔ error: casual mutations forbidden
|
|
200
|
+
```
|
|
201
|
+
```ts
|
|
202
|
+
snacks.mutate(state => state.peanuts++)
|
|
203
|
+
// ✅ only proper mutations can make state changes
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
```ts
|
|
212
|
-
snacks.on(s => console.log(s.peanuts))
|
|
213
|
-
```
|
|
214
|
-
- on returns a fn to stop listening
|
|
215
|
-
```ts
|
|
216
|
-
const stop = trunk.on(s => console.log(s.count))
|
|
217
|
-
stop() // stop listening
|
|
218
|
-
```
|
|
205
|
+
snacks.state.peanuts // 9
|
|
206
|
+
```
|
|
207
|
+
- **array mutations are unironically based, actually**
|
|
208
|
+
```ts
|
|
209
|
+
await snacks.mutate(state => state.bag.push("salt"))
|
|
210
|
+
```
|
|
219
211
|
|
|
220
|
-
###
|
|
221
|
-
|
|
212
|
+
### 🔮 chrono for time travel
|
|
213
|
+
- **import stuff**
|
|
214
|
+
```ts
|
|
215
|
+
import {Chrono, chronicle} from "@e280/strata"
|
|
216
|
+
```
|
|
217
|
+
- **create a chronicle in your state**
|
|
218
|
+
```ts
|
|
219
|
+
const prism = new Prism({
|
|
220
|
+
|
|
221
|
+
// chronicle stores history
|
|
222
|
+
// 👇
|
|
223
|
+
snacks: chronicle({
|
|
224
|
+
peanuts: 8,
|
|
225
|
+
bag: ["popcorn", "butter"],
|
|
226
|
+
person: {
|
|
227
|
+
name: "chase",
|
|
228
|
+
incredi: true,
|
|
229
|
+
},
|
|
230
|
+
}),
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
- *big-brain moment:* the whole chronicle *itself* is stored in the state.. serializable.. think persistence — user can close their project, reopen, and their undo/redo history is still chillin' — *brat girl summer*
|
|
234
|
+
- **create a chrono-wrapped lens to interact with your chronicle**
|
|
235
|
+
```ts
|
|
236
|
+
const snacks = new Chrono(64, prism.lens(state => state.snacks))
|
|
237
|
+
// 👆
|
|
238
|
+
// how many past snapshots to store
|
|
239
|
+
```
|
|
240
|
+
- **mutations will advance history,** and undo/redo works
|
|
241
|
+
```ts
|
|
242
|
+
snacks.mutate(s => s.peanuts = 101)
|
|
222
243
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
- simple setup
|
|
226
|
-
```ts
|
|
227
|
-
const {trunk} = await Trunk.setup({
|
|
228
|
-
version: 1, // 👈 bump whenever you change state schema!
|
|
229
|
-
initialState: {count: 0},
|
|
230
|
-
})
|
|
231
|
-
```
|
|
232
|
-
- uses localStorage by default
|
|
233
|
-
- it's compatible with [`@e280/kv`](https://github.com/e280/kv)
|
|
234
|
-
```ts
|
|
235
|
-
import {Kv, StorageDriver} from "@e280/kv"
|
|
244
|
+
snacks.undo()
|
|
245
|
+
// back to 8 peanuts
|
|
236
246
|
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
snacks.redo()
|
|
248
|
+
// forward to 101 peanuts
|
|
249
|
+
```
|
|
250
|
+
- **check how many undoable or redoable steps are available**
|
|
251
|
+
```ts
|
|
252
|
+
snacks.undoable // 1
|
|
253
|
+
snacks.redoable // 0
|
|
254
|
+
```
|
|
255
|
+
- **you can make sub-lenses of a chrono,** all their mutations advance history too
|
|
256
|
+
- **plz pinky-swear right now,** that you won't create a chrono under a lens under another chrono 💀
|
|
239
257
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
258
|
+
### 🔮 persistence to localStorage
|
|
259
|
+
- **import prism**
|
|
260
|
+
```ts
|
|
261
|
+
import {Vault, LocalStore} from "@e280/strata"
|
|
262
|
+
```
|
|
263
|
+
- **create a local storage store**
|
|
264
|
+
```ts
|
|
265
|
+
const store = new LocalStore("myAppState")
|
|
266
|
+
```
|
|
267
|
+
- **make a vault for your prism**
|
|
268
|
+
```ts
|
|
269
|
+
const vault = new Vault({
|
|
270
|
+
prism,
|
|
244
271
|
store,
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
bag: ["popcorn", "butter"],
|
|
258
|
-
}),
|
|
259
|
-
})
|
|
260
|
-
```
|
|
261
|
-
- *big-brain moment:* the whole chronicle *itself* is stored in the state.. serializable.. think persistence — user can close their project, reopen, and their undo/redo history is still chillin' — *brat girl summer*
|
|
262
|
-
- second, make a `Chronobranch` which is like a branch, but is concerned with history
|
|
263
|
-
```ts
|
|
264
|
-
const snacks = trunk.chronobranch(64, s => s.snacks)
|
|
265
|
-
// \
|
|
266
|
-
// how many past snapshots to store
|
|
267
|
-
```
|
|
268
|
-
- mutations will advance history (undoable/redoable)
|
|
269
|
-
```ts
|
|
270
|
-
await snacks.mutate(s => s.peanuts = 101)
|
|
271
|
-
|
|
272
|
-
await snacks.undo()
|
|
273
|
-
// back to 8 peanuts
|
|
274
|
-
|
|
275
|
-
await snacks.redo()
|
|
276
|
-
// forward to 101 peanuts
|
|
277
|
-
```
|
|
278
|
-
- you can check how many undoable or redoable steps are available
|
|
279
|
-
```ts
|
|
280
|
-
snacks.undoable // 2
|
|
281
|
-
snacks.redoable // 1
|
|
282
|
-
```
|
|
283
|
-
- chronobranch can have its own branches — all their mutations advance history
|
|
284
|
-
- plz pinky-swear right now, that you won't create a chronobranch under a branch under another chronobranch 💀
|
|
272
|
+
version: 1, // 👈 bump this when you break your state schema!
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
- `store` type is compatible with [`@e280/kv`](https://github.com/e280/kv)
|
|
276
|
+
- **cross-tab sync (load on storage events)**
|
|
277
|
+
```ts
|
|
278
|
+
store.onStorageEvent(vault.load)
|
|
279
|
+
```
|
|
280
|
+
- **initial load**
|
|
281
|
+
```ts
|
|
282
|
+
await vault.load()
|
|
283
|
+
```
|
|
285
284
|
|
|
286
285
|
|
|
287
286
|
|
|
288
287
|
<br/><br/>
|
|
289
288
|
|
|
290
|
-
|
|
289
|
+
<a id="tracker"></a>
|
|
290
|
+
|
|
291
|
+
## 🍋 strata tracker
|
|
291
292
|
> *reactivity integration hub*
|
|
292
293
|
|
|
293
294
|
```ts
|
|
@@ -318,8 +319,8 @@ note, the *items* that the tracker tracks can be any object, or symbol.. the tra
|
|
|
318
319
|
```
|
|
319
320
|
- it's a good idea to debounce your rerender fn
|
|
320
321
|
```ts
|
|
321
|
-
import {
|
|
322
|
-
const myDebouncedRerenderFn =
|
|
322
|
+
import {microbounce} from "@e280/stz"
|
|
323
|
+
const myDebouncedRerenderFn = microbounce(myRerenderFn)
|
|
323
324
|
```
|
|
324
325
|
- `tracker.subscribe` to respond to changes
|
|
325
326
|
```ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e280/strata",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "state management",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"exports": {
|
|
14
14
|
".": "./x/index.js",
|
|
15
|
+
"./prism": "./x/prism/index.js",
|
|
15
16
|
"./signals": "./x/signals/index.js",
|
|
16
17
|
"./tracker": "./x/tracker/index.js",
|
|
17
18
|
"./tree": "./x/tree/index.js"
|
|
@@ -22,18 +23,19 @@
|
|
|
22
23
|
"test-watch": "node --watch x/tests.test.js",
|
|
23
24
|
"test-inspect": "node inspect x/tests.test.js",
|
|
24
25
|
"count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +",
|
|
25
|
-
"
|
|
26
|
+
"start": "octo 'npm run _tscw -s' 'npm run test-watch -s'",
|
|
26
27
|
"_clean": "rm -rf x && mkdir x",
|
|
27
28
|
"_links": "ln -s \"$(realpath node_modules)\" x/node_modules",
|
|
28
29
|
"_tsc": "tsc",
|
|
29
30
|
"_tscw": "tsc -w"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@e280/stz": "^0.2.
|
|
33
|
+
"@e280/stz": "^0.2.14"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@e280/science": "^0.1.
|
|
36
|
-
"@
|
|
36
|
+
"@e280/science": "^0.1.4",
|
|
37
|
+
"@e280/scute": "^0.1.1",
|
|
38
|
+
"@types/node": "^24.10.0",
|
|
37
39
|
"npm-run-all": "^4.1.5",
|
|
38
40
|
"typescript": "^5.9.3"
|
|
39
41
|
},
|
package/s/index.ts
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
import {deep} from "@e280/stz"
|
|
3
|
+
import {Lens} from "../lens.js"
|
|
4
|
+
import {Chronicle} from "./types.js"
|
|
5
|
+
import {LensLike} from "../types.js"
|
|
6
|
+
import {_optic} from "../utils/optic-symbol.js"
|
|
7
|
+
|
|
8
|
+
export class Chrono<State> implements LensLike<State> {
|
|
9
|
+
constructor(
|
|
10
|
+
public limit: number,
|
|
11
|
+
private basis: Lens<Chronicle<State>>,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
get chronicle() {
|
|
15
|
+
return this.basis.state
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get state() {
|
|
19
|
+
return this.basis.state.present
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get undoable() {
|
|
23
|
+
return this.chronicle.past.length
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get redoable() {
|
|
27
|
+
return this.chronicle.future.length
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#mut<R>(chronicle: Chronicle<State>, fn: (state: State) => R) {
|
|
31
|
+
const limit = Math.max(0, this.limit)
|
|
32
|
+
const snapshot = deep.clone(this.chronicle.present) as State
|
|
33
|
+
const result = fn(chronicle.present)
|
|
34
|
+
chronicle.past.push(snapshot)
|
|
35
|
+
chronicle.past = chronicle.past.slice(-limit)
|
|
36
|
+
chronicle.future = []
|
|
37
|
+
return result
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** progress forwards, depositing history into the past */
|
|
41
|
+
async mutate<R>(fn: (state: State) => R): Promise<R> {
|
|
42
|
+
return this.basis.mutate(chronicle => this.#mut(chronicle, fn))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** step backwards into the past, by n steps */
|
|
46
|
+
async undo(n = 1) {
|
|
47
|
+
await this.basis.mutate(chronicle => {
|
|
48
|
+
const snapshots = chronicle.past.slice(-n)
|
|
49
|
+
if (snapshots.length >= n) {
|
|
50
|
+
const oldPresent = chronicle.present
|
|
51
|
+
chronicle.present = snapshots.shift()!
|
|
52
|
+
chronicle.past = chronicle.past.slice(0, -n)
|
|
53
|
+
chronicle.future.unshift(oldPresent, ...snapshots)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** step forwards into the future, by n steps */
|
|
59
|
+
async redo(n = 1) {
|
|
60
|
+
await this.basis.mutate(chronicle => {
|
|
61
|
+
const snapshots = chronicle.future.slice(0, n)
|
|
62
|
+
if (snapshots.length >= n) {
|
|
63
|
+
const oldPresent = chronicle.present
|
|
64
|
+
chronicle.present = snapshots.shift()!
|
|
65
|
+
chronicle.past.push(oldPresent, ...snapshots)
|
|
66
|
+
chronicle.future = chronicle.future.slice(n)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** wipe past and future snapshots */
|
|
72
|
+
async wipe() {
|
|
73
|
+
await this.basis.mutate(chronicle => {
|
|
74
|
+
chronicle.past = []
|
|
75
|
+
chronicle.future = []
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
lens<State2>(selector: (state: State) => State2) {
|
|
80
|
+
const lens = new Lens<State2>({
|
|
81
|
+
registerLens: this.basis[_optic].registerLens,
|
|
82
|
+
getState: () => selector(this.basis[_optic].getState().present),
|
|
83
|
+
mutate: fn => this.basis[_optic].mutate(chronicle => {
|
|
84
|
+
return this.#mut(chronicle, state => fn(selector(state)))
|
|
85
|
+
}),
|
|
86
|
+
})
|
|
87
|
+
this.basis[_optic].registerLens(lens)
|
|
88
|
+
return lens
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
package/s/prism/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
export * from "./chrono/chronicle.js"
|
|
3
|
+
export * from "./chrono/chrono.js"
|
|
4
|
+
export * from "./chrono/types.js"
|
|
5
|
+
export * from "./vault/local-store.js"
|
|
6
|
+
export * from "./vault/vault.js"
|
|
7
|
+
export * from "./vault/types.js"
|
|
8
|
+
export * from "./lens.js"
|
|
9
|
+
export * from "./prism.js"
|
|
10
|
+
export * from "./types.js"
|
|
11
|
+
|
package/s/prism/lens.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
import {deep, microbounce, sub} from "@e280/stz"
|
|
3
|
+
import {immute} from "./utils/immute.js"
|
|
4
|
+
import {tracker} from "../tracker/tracker.js"
|
|
5
|
+
import {_optic} from "./utils/optic-symbol.js"
|
|
6
|
+
import {CacheCell} from "./utils/cache-cell.js"
|
|
7
|
+
import {Immutable, LensLike, Optic} from "./types.js"
|
|
8
|
+
|
|
9
|
+
/** reactive view into a state prism, with formalized mutations */
|
|
10
|
+
export class Lens<State> implements LensLike<State> {
|
|
11
|
+
on = sub<[state: Immutable<State>]>()
|
|
12
|
+
|
|
13
|
+
;[_optic]: Optic<State>
|
|
14
|
+
#previous: State
|
|
15
|
+
#immutable: CacheCell<Immutable<State>>
|
|
16
|
+
#onPublishDebounced = microbounce(() => this.on.publish(this.state))
|
|
17
|
+
|
|
18
|
+
constructor(optic: Optic<State>) {
|
|
19
|
+
this[_optic] = optic
|
|
20
|
+
this.#previous = deep.clone(optic.getState())
|
|
21
|
+
this.#immutable = new CacheCell(() => immute(optic.getState()))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async update() {
|
|
25
|
+
const state = this[_optic].getState()
|
|
26
|
+
const isChanged = !deep.equal(state, this.#previous)
|
|
27
|
+
if (isChanged) {
|
|
28
|
+
this.#immutable.invalidate()
|
|
29
|
+
this.#previous = deep.clone(state)
|
|
30
|
+
this.#onPublishDebounced()
|
|
31
|
+
await tracker.notifyWrite(this)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get state() {
|
|
36
|
+
tracker.notifyRead(this)
|
|
37
|
+
return this.#immutable.get()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async mutate<R>(fn: (state: State) => R) {
|
|
41
|
+
return this[_optic].mutate(fn)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
lens<State2>(selector: (state: State) => State2) {
|
|
45
|
+
const lens = new Lens<State2>({
|
|
46
|
+
getState: () => selector(this[_optic].getState()),
|
|
47
|
+
mutate: fn => this[_optic].mutate(state => fn(selector(state))),
|
|
48
|
+
registerLens: this[_optic].registerLens,
|
|
49
|
+
})
|
|
50
|
+
this[_optic].registerLens(lens)
|
|
51
|
+
return lens
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|