@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/s/tree/parts/trunk.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
2
|
import {deep} from "@e280/stz"
|
|
3
3
|
import {Branch} from "./branch.js"
|
|
4
|
-
import {
|
|
4
|
+
import {Immute} from "./utils/immute.js"
|
|
5
5
|
import {trunkSetup} from "./utils/setup.js"
|
|
6
|
-
import {Derived} from "../../signals/core/derived.js"
|
|
7
|
-
import {Signal} from "../../signals/core/signal.js"
|
|
8
6
|
import {Chronobranch} from "./chronobranch.js"
|
|
9
7
|
import {processOptions} from "./utils/process-options.js"
|
|
10
|
-
import {Branchstate,
|
|
8
|
+
import {Branchstate, Mutator, TreeOptions, Selector, Tree, Trunkstate} from "./types.js"
|
|
9
|
+
import {Chronicle} from "../../prism/index.js"
|
|
11
10
|
|
|
11
|
+
/** @deprecated tree stuff has been replaced by prism/lens stuff */
|
|
12
12
|
export class Trunk<S extends Trunkstate> implements Tree<S> {
|
|
13
13
|
static setup = trunkSetup
|
|
14
14
|
static chronicle = <S extends Branchstate>(state: S): Chronicle<S> => ({
|
|
@@ -17,47 +17,43 @@ export class Trunk<S extends Trunkstate> implements Tree<S> {
|
|
|
17
17
|
future: [],
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
options:
|
|
21
|
-
|
|
22
|
-
#immutable: Derived<Immutable<S>>
|
|
23
|
-
#mutable: Signal<S>
|
|
20
|
+
options: TreeOptions
|
|
24
21
|
#mutationLock = 0
|
|
22
|
+
#immute: Immute<S>
|
|
25
23
|
|
|
26
|
-
constructor(state: S, options: Partial<
|
|
24
|
+
constructor(state: S, options: Partial<TreeOptions> = {}) {
|
|
27
25
|
this.options = processOptions(options)
|
|
28
|
-
this.#
|
|
29
|
-
this.#immutable = signal.derived(() =>
|
|
30
|
-
deep.freeze(this.options.clone(this.#mutable.get())) as Immutable<S>
|
|
31
|
-
)
|
|
26
|
+
this.#immute = new Immute(state, this.options)
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
get state() {
|
|
35
|
-
return this.#immutable
|
|
30
|
+
return this.#immute.immutable
|
|
36
31
|
}
|
|
37
32
|
|
|
38
33
|
get on() {
|
|
39
|
-
return this.#
|
|
34
|
+
return this.#immute.on
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
async mutate(mutator: Mutator<S>) {
|
|
43
|
-
const oldState = this
|
|
38
|
+
const oldState = this.#immute.get()
|
|
44
39
|
if (this.#mutationLock > 0)
|
|
45
40
|
throw new Error("nested mutations are forbidden")
|
|
41
|
+
let promise = Promise.resolve()
|
|
46
42
|
try {
|
|
47
43
|
this.#mutationLock++
|
|
48
|
-
const
|
|
49
|
-
mutator(
|
|
50
|
-
const newState = value
|
|
44
|
+
const newState = this.options.clone(oldState)
|
|
45
|
+
mutator(newState)
|
|
51
46
|
const isChanged = !deep.equal(newState, oldState)
|
|
52
47
|
if (isChanged)
|
|
53
|
-
|
|
48
|
+
promise = this.overwrite(newState)
|
|
54
49
|
}
|
|
55
50
|
finally { this.#mutationLock-- }
|
|
56
|
-
|
|
51
|
+
await promise
|
|
52
|
+
return this.state
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
async overwrite(state: S) {
|
|
60
|
-
await this.#
|
|
56
|
+
await this.#immute.set(state)
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
package/s/tree/parts/types.ts
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
|
|
2
|
+
import { EzStore, Versioned } from "../../prism/index.js"
|
|
3
|
+
import { Immutable } from "../../prism/types.js"
|
|
2
4
|
import {Branch} from "./branch.js"
|
|
3
5
|
|
|
4
|
-
export type
|
|
6
|
+
export type TreeOptions = {
|
|
5
7
|
clone: <X>(x: X) => X
|
|
6
8
|
}
|
|
7
9
|
|
|
10
|
+
/** @deprecated renamed to `TreeOptions` */
|
|
11
|
+
export type Options = TreeOptions
|
|
12
|
+
|
|
8
13
|
export type Selector<Sub, S> = (state: S) => Sub
|
|
9
14
|
export type Mutator<S> = (state: S) => void
|
|
10
15
|
|
|
11
16
|
export type Trunkstate = {}
|
|
12
17
|
export type Branchstate = {} | null | undefined
|
|
13
18
|
|
|
14
|
-
export type Versioned<S extends Trunkstate> = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type Immutable<T> =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export type Mutable<T> =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
// export type Versioned<S extends Trunkstate> = {
|
|
20
|
+
// state: S
|
|
21
|
+
// version: number
|
|
22
|
+
// }
|
|
23
|
+
//
|
|
24
|
+
// export type Immutable<T> =
|
|
25
|
+
// T extends (...args: any[]) => any ? T :
|
|
26
|
+
// T extends readonly any[] ? ReadonlyArray<Immutable<T[number]>> :
|
|
27
|
+
// T extends object ? { readonly [K in keyof T]: Immutable<T[K]> } :
|
|
28
|
+
// T
|
|
29
|
+
//
|
|
30
|
+
// export type Mutable<T> =
|
|
31
|
+
// T extends (...args: any[]) => any ? T :
|
|
32
|
+
// T extends ReadonlyArray<infer U> ? Mutable<U>[] :
|
|
33
|
+
// T extends object ? { -readonly [K in keyof T]: Mutable<T[K]> } :
|
|
34
|
+
// T
|
|
30
35
|
|
|
31
36
|
export type Tree<S extends Branchstate> = {
|
|
32
37
|
get state(): Immutable<S>
|
|
@@ -42,21 +47,21 @@ export type SetupOptions<S extends Trunkstate> = {
|
|
|
42
47
|
persistence?: Persistence<Versioned<S>>
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
export type Chronicle<S extends Branchstate> = {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
50
|
+
// export type Chronicle<S extends Branchstate> = {
|
|
51
|
+
// // [abc] d [efg]
|
|
52
|
+
// // \ \ \
|
|
53
|
+
// // \ \ future
|
|
54
|
+
// // \ present
|
|
55
|
+
// // past
|
|
56
|
+
// past: S[]
|
|
57
|
+
// present: S
|
|
58
|
+
// future: S[]
|
|
59
|
+
// }
|
|
55
60
|
|
|
56
|
-
export type EzStore<X> = {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
61
|
+
// export type EzStore<X> = {
|
|
62
|
+
// get(): Promise<X | undefined>
|
|
63
|
+
// set(state: X | undefined): Promise<void>
|
|
64
|
+
// }
|
|
60
65
|
|
|
61
66
|
export type Persistence<X> = {
|
|
62
67
|
store: EzStore<X>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
import {deep, sub} from "@e280/stz"
|
|
3
|
+
import {TreeOptions} from "../types.js"
|
|
4
|
+
import {tracker} from "../../../tracker/tracker.js"
|
|
5
|
+
import {Immutable} from "../../../prism/types.js"
|
|
6
|
+
|
|
7
|
+
export class Immute<S> {
|
|
8
|
+
#mutable: S
|
|
9
|
+
#immutable: Immutable<S>
|
|
10
|
+
|
|
11
|
+
constructor(mutable: S, private options: TreeOptions) {
|
|
12
|
+
this.#mutable = mutable
|
|
13
|
+
this.#immutable = this.#petrify(mutable)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#petrify<X>(x: X) {
|
|
17
|
+
return deep.freeze(
|
|
18
|
+
this.options.clone(x)
|
|
19
|
+
) as Immutable<X>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
on = sub<[Immutable<S>]>()
|
|
23
|
+
|
|
24
|
+
get() {
|
|
25
|
+
tracker.notifyRead(this)
|
|
26
|
+
return this.#mutable
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async set(mutable: S) {
|
|
30
|
+
this.#mutable = mutable
|
|
31
|
+
this.#immutable = deep.freeze(this.options.clone(this.get())) as Immutable<S>
|
|
32
|
+
await Promise.all([
|
|
33
|
+
this.on.publish(this.#immutable),
|
|
34
|
+
tracker.notifyWrite(this),
|
|
35
|
+
])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get immutable() {
|
|
39
|
+
tracker.notifyRead(this)
|
|
40
|
+
return this.#immutable
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import {TreeOptions} from "../types.js"
|
|
3
3
|
|
|
4
|
-
export const processOptions = (options: Partial<
|
|
4
|
+
export const processOptions = (options: Partial<TreeOptions>): TreeOptions => ({
|
|
5
5
|
clone: options.clone ?? (<X>(x: X) => structuredClone(x)),
|
|
6
6
|
})
|
|
7
7
|
|
package/s/tree/tree.test.ts
CHANGED
|
@@ -56,13 +56,15 @@ export default Science.suite({
|
|
|
56
56
|
expect(mutationCount).is(2)
|
|
57
57
|
}),
|
|
58
58
|
|
|
59
|
-
"
|
|
59
|
+
"trunk.on is debounced": Science.test.skip(async() => {
|
|
60
60
|
const trunk = new Trunk({count: 0})
|
|
61
61
|
let mutationCount = 0
|
|
62
62
|
trunk.on.sub(() => {mutationCount++})
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
await Promise.all([
|
|
64
|
+
trunk.mutate(state => state.count++),
|
|
65
|
+
trunk.mutate(state => state.count++),
|
|
66
|
+
trunk.mutate(state => state.count++),
|
|
67
|
+
])
|
|
66
68
|
expect(mutationCount).is(1)
|
|
67
69
|
}),
|
|
68
70
|
|
|
@@ -75,19 +77,30 @@ export default Science.suite({
|
|
|
75
77
|
expect(trunk.state.items.length).is(3)
|
|
76
78
|
}),
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
// // conceptually incompatible with sequential synchronous mutations
|
|
81
|
+
// "prevent mutation loops": Science.test(async() => {
|
|
82
|
+
// const trunk = new Trunk({count: 0})
|
|
83
|
+
// let mutationCount = 0
|
|
84
|
+
// trunk.on.sub(async() => {
|
|
85
|
+
// mutationCount++
|
|
86
|
+
// if (mutationCount > 100)
|
|
87
|
+
// return
|
|
88
|
+
// await trunk.mutate(s => s.count++)
|
|
89
|
+
// })
|
|
90
|
+
// await expect(async() => {
|
|
91
|
+
// await trunk.mutate(state => state.count++)
|
|
92
|
+
// }).throwsAsync()
|
|
93
|
+
// expect(mutationCount).is(1)
|
|
94
|
+
// }),
|
|
95
|
+
|
|
96
|
+
"sync coherence": Science.test(async() => {
|
|
79
97
|
const trunk = new Trunk({count: 0})
|
|
80
|
-
|
|
81
|
-
trunk.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
})
|
|
87
|
-
await expect(async() => {
|
|
88
|
-
await trunk.mutate(state => state.count++)
|
|
89
|
-
}).throwsAsync()
|
|
90
|
-
expect(mutationCount).is(1)
|
|
98
|
+
const p1 = trunk.mutate(s => s.count++)
|
|
99
|
+
expect(trunk.state.count).is(1)
|
|
100
|
+
const p2 = trunk.mutate(s => s.count++)
|
|
101
|
+
expect(trunk.state.count).is(2)
|
|
102
|
+
await p1
|
|
103
|
+
await p2
|
|
91
104
|
}),
|
|
92
105
|
}),
|
|
93
106
|
|
|
@@ -98,9 +111,32 @@ export default Science.suite({
|
|
|
98
111
|
expect(branch.state.rofls).is(0)
|
|
99
112
|
}),
|
|
100
113
|
|
|
114
|
+
"mutation triggers effect": Science.test(async() => {
|
|
115
|
+
const trunk = new Trunk({a: {x: 0}})
|
|
116
|
+
const branch = trunk.branch(s => s.a)
|
|
117
|
+
expect(branch.state.x).is(0)
|
|
118
|
+
let triggered = 0
|
|
119
|
+
effect(() => {
|
|
120
|
+
void branch.state.x
|
|
121
|
+
triggered++
|
|
122
|
+
})
|
|
123
|
+
expect(triggered).is(1)
|
|
124
|
+
await branch.mutate(s => s.x++)
|
|
125
|
+
expect(triggered).is(2)
|
|
126
|
+
}),
|
|
127
|
+
|
|
128
|
+
"sync coherence": Science.test(async() => {
|
|
129
|
+
const trunk = new Trunk({count: 0, sub: {rofls: 0}})
|
|
130
|
+
const branch = trunk.branch(s => s.sub)
|
|
131
|
+
expect(branch.state.rofls).is(0)
|
|
132
|
+
const p = branch.mutate(s => s.rofls++)
|
|
133
|
+
expect(branch.state.rofls).is(1)
|
|
134
|
+
await p
|
|
135
|
+
}),
|
|
136
|
+
|
|
101
137
|
"nullable selector": Science.test(async () => {
|
|
102
138
|
const trunk = new Trunk({
|
|
103
|
-
a: {b: 0}
|
|
139
|
+
a: {b: 0} as (null | {b: number}),
|
|
104
140
|
})
|
|
105
141
|
const a = trunk.branch(s => s.a)
|
|
106
142
|
expect(trunk.state.a?.b).is(0)
|
|
@@ -139,17 +175,31 @@ export default Science.suite({
|
|
|
139
175
|
expect(b.state.c).is(103)
|
|
140
176
|
}),
|
|
141
177
|
|
|
142
|
-
"
|
|
178
|
+
"branch.on ignores outside mutations": Science.test(async() => {
|
|
143
179
|
const trunk = new Trunk({a: {x: 0}, b: {x: 0}})
|
|
144
180
|
const a = trunk.branch(s => s.a)
|
|
145
181
|
const b = trunk.branch(s => s.b)
|
|
146
182
|
let counted = 0
|
|
147
|
-
b.on
|
|
183
|
+
b.on(() => {counted++})
|
|
148
184
|
expect(counted).is(0)
|
|
149
185
|
await a.mutate(a => a.x = 1)
|
|
150
186
|
expect(counted).is(0)
|
|
151
187
|
}),
|
|
152
188
|
|
|
189
|
+
"effects ignore outside mutations": Science.test(async() => {
|
|
190
|
+
const trunk = new Trunk({a: {x: 0}, b: {x: 0}})
|
|
191
|
+
const a = trunk.branch(s => s.a)
|
|
192
|
+
const b = trunk.branch(s => s.b)
|
|
193
|
+
let counted = 0
|
|
194
|
+
effect(() => {
|
|
195
|
+
void b.state.x
|
|
196
|
+
counted++
|
|
197
|
+
})
|
|
198
|
+
expect(counted).is(1)
|
|
199
|
+
await a.mutate(a => a.x++)
|
|
200
|
+
expect(counted).is(1)
|
|
201
|
+
}),
|
|
202
|
+
|
|
153
203
|
"forbid submutation in mutation": Science.test(async() => {
|
|
154
204
|
const trunk = new Trunk({a: {b: 0}})
|
|
155
205
|
const a = trunk.branch(s => s.a)
|
package/x/index.d.ts
CHANGED
package/x/index.js
CHANGED
package/x/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../s/index.ts"],"names":[],"mappings":"AACA,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../s/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chronicle.js","sourceRoot":"","sources":["../../../s/prism/chrono/chronicle.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,SAAS,CAAQ,KAAY;IAC5C,OAAO;QACN,IAAI,EAAE,EAAE;QACR,OAAO,EAAE,KAAK;QACd,MAAM,EAAE,EAAE;KACV,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Lens } from "../lens.js";
|
|
2
|
+
import { Chronicle } from "./types.js";
|
|
3
|
+
import { LensLike } from "../types.js";
|
|
4
|
+
export declare class Chrono<State> implements LensLike<State> {
|
|
5
|
+
#private;
|
|
6
|
+
limit: number;
|
|
7
|
+
private basis;
|
|
8
|
+
constructor(limit: number, basis: Lens<Chronicle<State>>);
|
|
9
|
+
get chronicle(): {
|
|
10
|
+
readonly past: readonly import("../types.js").Immutable<State>[];
|
|
11
|
+
readonly present: import("../types.js").Immutable<State>;
|
|
12
|
+
readonly future: readonly import("../types.js").Immutable<State>[];
|
|
13
|
+
};
|
|
14
|
+
get state(): import("../types.js").Immutable<State>;
|
|
15
|
+
get undoable(): number;
|
|
16
|
+
get redoable(): number;
|
|
17
|
+
/** progress forwards, depositing history into the past */
|
|
18
|
+
mutate<R>(fn: (state: State) => R): Promise<R>;
|
|
19
|
+
/** step backwards into the past, by n steps */
|
|
20
|
+
undo(n?: number): Promise<void>;
|
|
21
|
+
/** step forwards into the future, by n steps */
|
|
22
|
+
redo(n?: number): Promise<void>;
|
|
23
|
+
/** wipe past and future snapshots */
|
|
24
|
+
wipe(): Promise<void>;
|
|
25
|
+
lens<State2>(selector: (state: State) => State2): Lens<State2>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { deep } from "@e280/stz";
|
|
2
|
+
import { Lens } from "../lens.js";
|
|
3
|
+
import { _optic } from "../utils/optic-symbol.js";
|
|
4
|
+
export class Chrono {
|
|
5
|
+
limit;
|
|
6
|
+
basis;
|
|
7
|
+
constructor(limit, basis) {
|
|
8
|
+
this.limit = limit;
|
|
9
|
+
this.basis = basis;
|
|
10
|
+
}
|
|
11
|
+
get chronicle() {
|
|
12
|
+
return this.basis.state;
|
|
13
|
+
}
|
|
14
|
+
get state() {
|
|
15
|
+
return this.basis.state.present;
|
|
16
|
+
}
|
|
17
|
+
get undoable() {
|
|
18
|
+
return this.chronicle.past.length;
|
|
19
|
+
}
|
|
20
|
+
get redoable() {
|
|
21
|
+
return this.chronicle.future.length;
|
|
22
|
+
}
|
|
23
|
+
#mut(chronicle, fn) {
|
|
24
|
+
const limit = Math.max(0, this.limit);
|
|
25
|
+
const snapshot = deep.clone(this.chronicle.present);
|
|
26
|
+
const result = fn(chronicle.present);
|
|
27
|
+
chronicle.past.push(snapshot);
|
|
28
|
+
chronicle.past = chronicle.past.slice(-limit);
|
|
29
|
+
chronicle.future = [];
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
/** progress forwards, depositing history into the past */
|
|
33
|
+
async mutate(fn) {
|
|
34
|
+
return this.basis.mutate(chronicle => this.#mut(chronicle, fn));
|
|
35
|
+
}
|
|
36
|
+
/** step backwards into the past, by n steps */
|
|
37
|
+
async undo(n = 1) {
|
|
38
|
+
await this.basis.mutate(chronicle => {
|
|
39
|
+
const snapshots = chronicle.past.slice(-n);
|
|
40
|
+
if (snapshots.length >= n) {
|
|
41
|
+
const oldPresent = chronicle.present;
|
|
42
|
+
chronicle.present = snapshots.shift();
|
|
43
|
+
chronicle.past = chronicle.past.slice(0, -n);
|
|
44
|
+
chronicle.future.unshift(oldPresent, ...snapshots);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** step forwards into the future, by n steps */
|
|
49
|
+
async redo(n = 1) {
|
|
50
|
+
await this.basis.mutate(chronicle => {
|
|
51
|
+
const snapshots = chronicle.future.slice(0, n);
|
|
52
|
+
if (snapshots.length >= n) {
|
|
53
|
+
const oldPresent = chronicle.present;
|
|
54
|
+
chronicle.present = snapshots.shift();
|
|
55
|
+
chronicle.past.push(oldPresent, ...snapshots);
|
|
56
|
+
chronicle.future = chronicle.future.slice(n);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** wipe past and future snapshots */
|
|
61
|
+
async wipe() {
|
|
62
|
+
await this.basis.mutate(chronicle => {
|
|
63
|
+
chronicle.past = [];
|
|
64
|
+
chronicle.future = [];
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
lens(selector) {
|
|
68
|
+
const lens = new Lens({
|
|
69
|
+
registerLens: this.basis[_optic].registerLens,
|
|
70
|
+
getState: () => selector(this.basis[_optic].getState().present),
|
|
71
|
+
mutate: fn => this.basis[_optic].mutate(chronicle => {
|
|
72
|
+
return this.#mut(chronicle, state => fn(selector(state)));
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
this.basis[_optic].registerLens(lens);
|
|
76
|
+
return lens;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=chrono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrono.js","sourceRoot":"","sources":["../../../s/prism/chrono/chrono.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../s/prism/chrono/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./chrono/chronicle.js";
|
|
2
|
+
export * from "./chrono/chrono.js";
|
|
3
|
+
export * from "./chrono/types.js";
|
|
4
|
+
export * from "./vault/local-store.js";
|
|
5
|
+
export * from "./vault/vault.js";
|
|
6
|
+
export * from "./vault/types.js";
|
|
7
|
+
export * from "./lens.js";
|
|
8
|
+
export * from "./prism.js";
|
|
9
|
+
export * from "./types.js";
|
package/x/prism/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./chrono/chronicle.js";
|
|
2
|
+
export * from "./chrono/chrono.js";
|
|
3
|
+
export * from "./chrono/types.js";
|
|
4
|
+
export * from "./vault/local-store.js";
|
|
5
|
+
export * from "./vault/vault.js";
|
|
6
|
+
export * from "./vault/types.js";
|
|
7
|
+
export * from "./lens.js";
|
|
8
|
+
export * from "./prism.js";
|
|
9
|
+
export * from "./types.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/prism/index.ts"],"names":[],"mappings":"AACA,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { _optic } from "./utils/optic-symbol.js";
|
|
2
|
+
import { Immutable, LensLike, Optic } from "./types.js";
|
|
3
|
+
/** reactive view into a state prism, with formalized mutations */
|
|
4
|
+
export declare class Lens<State> implements LensLike<State> {
|
|
5
|
+
#private;
|
|
6
|
+
on: import("@e280/stz").Sub<[state: Immutable<State>]>;
|
|
7
|
+
[_optic]: Optic<State>;
|
|
8
|
+
constructor(optic: Optic<State>);
|
|
9
|
+
update(): Promise<void>;
|
|
10
|
+
get state(): Immutable<State>;
|
|
11
|
+
mutate<R>(fn: (state: State) => R): Promise<R>;
|
|
12
|
+
lens<State2>(selector: (state: State) => State2): Lens<State2>;
|
|
13
|
+
}
|
package/x/prism/lens.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { deep, microbounce, sub } from "@e280/stz";
|
|
2
|
+
import { immute } from "./utils/immute.js";
|
|
3
|
+
import { tracker } from "../tracker/tracker.js";
|
|
4
|
+
import { _optic } from "./utils/optic-symbol.js";
|
|
5
|
+
import { CacheCell } from "./utils/cache-cell.js";
|
|
6
|
+
/** reactive view into a state prism, with formalized mutations */
|
|
7
|
+
export class Lens {
|
|
8
|
+
on = sub();
|
|
9
|
+
[_optic];
|
|
10
|
+
#previous;
|
|
11
|
+
#immutable;
|
|
12
|
+
#onPublishDebounced = microbounce(() => this.on.publish(this.state));
|
|
13
|
+
constructor(optic) {
|
|
14
|
+
this[_optic] = optic;
|
|
15
|
+
this.#previous = deep.clone(optic.getState());
|
|
16
|
+
this.#immutable = new CacheCell(() => immute(optic.getState()));
|
|
17
|
+
}
|
|
18
|
+
async update() {
|
|
19
|
+
const state = this[_optic].getState();
|
|
20
|
+
const isChanged = !deep.equal(state, this.#previous);
|
|
21
|
+
if (isChanged) {
|
|
22
|
+
this.#immutable.invalidate();
|
|
23
|
+
this.#previous = deep.clone(state);
|
|
24
|
+
this.#onPublishDebounced();
|
|
25
|
+
await tracker.notifyWrite(this);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
get state() {
|
|
29
|
+
tracker.notifyRead(this);
|
|
30
|
+
return this.#immutable.get();
|
|
31
|
+
}
|
|
32
|
+
async mutate(fn) {
|
|
33
|
+
return this[_optic].mutate(fn);
|
|
34
|
+
}
|
|
35
|
+
lens(selector) {
|
|
36
|
+
const lens = new Lens({
|
|
37
|
+
getState: () => selector(this[_optic].getState()),
|
|
38
|
+
mutate: fn => this[_optic].mutate(state => fn(selector(state))),
|
|
39
|
+
registerLens: this[_optic].registerLens,
|
|
40
|
+
});
|
|
41
|
+
this[_optic].registerLens(lens);
|
|
42
|
+
return lens;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=lens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lens.js","sourceRoot":"","sources":["../../s/prism/lens.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAC,MAAM,WAAW,CAAA;AAChD,OAAO,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAA;AACxC,OAAO,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAA;AAC7C,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAA;AAG/C,kEAAkE;AAClE,MAAM,OAAO,IAAI;IAChB,EAAE,GAAG,GAAG,EAA6B,CAEpC;IAAA,CAAC,MAAM,CAAC,CAAc;IACvB,SAAS,CAAO;IAChB,UAAU,CAA6B;IACvC,mBAAmB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IAEpE,YAAY,KAAmB;QAC9B,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,CAAA;QACpB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC7C,IAAI,CAAC,UAAU,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IAChE,CAAC;IAED,KAAK,CAAC,MAAM;QACX,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAA;QACrC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;QACpD,IAAI,SAAS,EAAE,CAAC;YACf,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAA;YAC5B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAClC,IAAI,CAAC,mBAAmB,EAAE,CAAA;YAC1B,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QAChC,CAAC;IACF,CAAC;IAED,IAAI,KAAK;QACR,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACxB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAA;IAC7B,CAAC;IAED,KAAK,CAAC,MAAM,CAAI,EAAuB;QACtC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC/B,CAAC;IAED,IAAI,CAAS,QAAkC;QAC9C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAS;YAC7B,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;YACjD,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/D,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,YAAY;SACvC,CAAC,CAAA;QACF,IAAI,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;QAC/B,OAAO,IAAI,CAAA;IACZ,CAAC;CACD"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Lens } from "./lens.js";
|
|
2
|
+
/** state mangagement source-of-truth */
|
|
3
|
+
export declare class Prism<State> {
|
|
4
|
+
#private;
|
|
5
|
+
on: import("@e280/stz").Sub<[state: State]>;
|
|
6
|
+
constructor(state: State);
|
|
7
|
+
get(): State;
|
|
8
|
+
set(state: State): Promise<void>;
|
|
9
|
+
lens<State2>(selector: (state: State) => State2): Lens<State2>;
|
|
10
|
+
}
|
package/x/prism/prism.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { microbounce, sub } from "@e280/stz";
|
|
2
|
+
import { Lens } from "./lens.js";
|
|
3
|
+
/** state mangagement source-of-truth */
|
|
4
|
+
export class Prism {
|
|
5
|
+
#state;
|
|
6
|
+
#lenses = new Set();
|
|
7
|
+
on = sub();
|
|
8
|
+
#onPublishDebounced = microbounce(() => this.on.publish(this.#state));
|
|
9
|
+
constructor(state) {
|
|
10
|
+
this.#state = state;
|
|
11
|
+
}
|
|
12
|
+
get() {
|
|
13
|
+
return this.#state;
|
|
14
|
+
}
|
|
15
|
+
async set(state) {
|
|
16
|
+
this.#state = state;
|
|
17
|
+
await Promise.all([...this.#lenses].map(lens => lens.update()));
|
|
18
|
+
await this.#onPublishDebounced();
|
|
19
|
+
}
|
|
20
|
+
lens(selector) {
|
|
21
|
+
const lens = new Lens({
|
|
22
|
+
getState: () => selector(this.#state),
|
|
23
|
+
mutate: async (fn) => {
|
|
24
|
+
const result = fn(selector(this.#state));
|
|
25
|
+
await this.set(this.#state);
|
|
26
|
+
return result;
|
|
27
|
+
},
|
|
28
|
+
registerLens: lens => this.#lenses.add(lens),
|
|
29
|
+
});
|
|
30
|
+
this.#lenses.add(lens);
|
|
31
|
+
return lens;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=prism.js.map
|