@e280/strata 0.2.8 → 0.3.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 +11 -47
- package/package.json +3 -3
- package/s/index.ts +0 -1
- package/s/prism/prism.test.ts +2 -1
- package/s/prism/prism.ts +1 -1
- package/s/signals/derived/class.ts +34 -0
- package/s/signals/derived/fn.ts +35 -0
- package/s/signals/{tests/derived.test.ts → derived/test.ts} +25 -14
- package/s/signals/effect/effect.ts +7 -0
- package/s/signals/effect/test.ts +172 -0
- package/s/signals/effect/watch.ts +32 -0
- package/s/signals/index.ts +11 -7
- package/s/signals/lazy/class.ts +74 -0
- package/s/signals/lazy/fn.ts +22 -0
- package/s/signals/{tests/lazy.test.ts → lazy/test.ts} +23 -12
- package/s/signals/signal/class.ts +77 -0
- package/s/signals/signal/fn.ts +31 -0
- package/s/signals/{tests/signal.test.ts → signal/test.ts} +56 -59
- package/s/signals/signals.test.ts +8 -8
- package/s/signals/types.ts +4 -23
- package/s/signals/utils/default-compare.ts +1 -1
- package/s/signals/utils/symbols.ts +9 -0
- package/s/tests.test.ts +1 -57
- package/x/index.d.ts +0 -1
- package/x/index.js +0 -1
- package/x/index.js.map +1 -1
- package/x/prism/prism.js.map +1 -1
- package/x/prism/prism.test.js +1 -1
- package/x/prism/prism.test.js.map +1 -1
- package/x/signals/derived/class.d.ts +14 -0
- package/x/signals/derived/class.js +23 -0
- package/x/signals/derived/class.js.map +1 -0
- package/x/signals/derived/fn.d.ts +3 -0
- package/x/signals/derived/fn.js +27 -0
- package/x/signals/derived/fn.js.map +1 -0
- package/x/signals/{tests/derived.test.d.ts → derived/test.d.ts} +2 -3
- package/x/signals/{tests/derived.test.js → derived/test.js} +25 -15
- package/x/signals/derived/test.js.map +1 -0
- package/x/signals/effect/effect.d.ts +1 -0
- package/x/signals/effect/effect.js +5 -0
- package/x/signals/effect/effect.js.map +1 -0
- package/x/signals/{tests/effect.test.d.ts → effect/test.d.ts} +7 -0
- package/x/signals/effect/test.js +132 -0
- package/x/signals/effect/test.js.map +1 -0
- package/x/signals/effect/watch.d.ts +4 -0
- package/x/signals/effect/watch.js +25 -0
- package/x/signals/effect/watch.js.map +1 -0
- package/x/signals/index.d.ts +8 -7
- package/x/signals/index.js +8 -7
- package/x/signals/index.js.map +1 -1
- package/x/signals/lazy/class.d.ts +19 -0
- package/x/signals/lazy/class.js +59 -0
- package/x/signals/lazy/class.js.map +1 -0
- package/x/signals/lazy/fn.d.ts +3 -0
- package/x/signals/lazy/fn.js +17 -0
- package/x/signals/lazy/fn.js.map +1 -0
- package/x/signals/{tests/lazy.test.d.ts → lazy/test.d.ts} +2 -3
- package/x/signals/{tests/lazy.test.js → lazy/test.js} +23 -13
- package/x/signals/lazy/test.js.map +1 -0
- package/x/signals/signal/class.d.ts +20 -0
- package/x/signals/signal/class.js +57 -0
- package/x/signals/signal/class.js.map +1 -0
- package/x/signals/signal/fn.d.ts +7 -0
- package/x/signals/signal/fn.js +23 -0
- package/x/signals/signal/fn.js.map +1 -0
- package/x/signals/{tests/signal.test.d.ts → signal/test.d.ts} +8 -7
- package/x/signals/{tests/signal.test.js → signal/test.js} +50 -46
- package/x/signals/signal/test.js.map +1 -0
- package/x/signals/signals.test.d.ts +26 -20
- package/x/signals/signals.test.js +8 -8
- package/x/signals/signals.test.js.map +1 -1
- package/x/signals/types.d.ts +4 -19
- package/x/signals/utils/default-compare.js +1 -1
- package/x/signals/utils/default-compare.js.map +1 -1
- package/x/signals/utils/symbols.d.ts +7 -0
- package/x/signals/utils/symbols.js +8 -0
- package/x/signals/utils/symbols.js.map +1 -0
- package/x/tests.test.js +1 -45
- package/x/tests.test.js.map +1 -1
- package/s/signals/core/derived.ts +0 -65
- package/s/signals/core/effect.ts +0 -31
- package/s/signals/core/lazy.ts +0 -78
- package/s/signals/core/parts/reactive.ts +0 -12
- package/s/signals/core/parts/readable.ts +0 -16
- package/s/signals/core/signal.ts +0 -101
- package/s/signals/porcelain.ts +0 -30
- package/s/signals/tests/effect.test.ts +0 -89
- package/s/tree/index.ts +0 -7
- package/s/tree/parts/branch.ts +0 -55
- package/s/tree/parts/chronobranch.ts +0 -86
- package/s/tree/parts/persistence.ts +0 -31
- package/s/tree/parts/trunk.ts +0 -70
- package/s/tree/parts/types.ts +0 -70
- package/s/tree/parts/utils/immute.ts +0 -43
- package/s/tree/parts/utils/process-options.ts +0 -7
- package/s/tree/parts/utils/setup.ts +0 -40
- package/s/tree/tree.test.ts +0 -366
- package/x/signals/core/derived.d.ts +0 -10
- package/x/signals/core/derived.js +0 -52
- package/x/signals/core/derived.js.map +0 -1
- package/x/signals/core/effect.d.ts +0 -5
- package/x/signals/core/effect.js +0 -17
- package/x/signals/core/effect.js.map +0 -1
- package/x/signals/core/lazy.d.ts +0 -11
- package/x/signals/core/lazy.js +0 -60
- package/x/signals/core/lazy.js.map +0 -1
- package/x/signals/core/parts/reactive.d.ts +0 -5
- package/x/signals/core/parts/reactive.js +0 -9
- package/x/signals/core/parts/reactive.js.map +0 -1
- package/x/signals/core/parts/readable.d.ts +0 -6
- package/x/signals/core/parts/readable.js +0 -15
- package/x/signals/core/parts/readable.js.map +0 -1
- package/x/signals/core/signal.d.ts +0 -13
- package/x/signals/core/signal.js +0 -77
- package/x/signals/core/signal.js.map +0 -1
- package/x/signals/porcelain.d.ts +0 -8
- package/x/signals/porcelain.js +0 -15
- package/x/signals/porcelain.js.map +0 -1
- package/x/signals/tests/derived.test.js.map +0 -1
- package/x/signals/tests/effect.test.js +0 -72
- package/x/signals/tests/effect.test.js.map +0 -1
- package/x/signals/tests/lazy.test.js.map +0 -1
- package/x/signals/tests/signal.test.js.map +0 -1
- package/x/tree/index.d.ts +0 -5
- package/x/tree/index.js +0 -6
- package/x/tree/index.js.map +0 -1
- package/x/tree/parts/branch.d.ts +0 -14
- package/x/tree/parts/branch.js +0 -42
- package/x/tree/parts/branch.js.map +0 -1
- package/x/tree/parts/chronobranch.d.ts +0 -25
- package/x/tree/parts/chronobranch.js +0 -75
- package/x/tree/parts/chronobranch.js.map +0 -1
- package/x/tree/parts/persistence.d.ts +0 -2
- package/x/tree/parts/persistence.js +0 -23
- package/x/tree/parts/persistence.js.map +0 -1
- package/x/tree/parts/trunk.d.ts +0 -19
- package/x/tree/parts/trunk.js +0 -57
- package/x/tree/parts/trunk.js.map +0 -1
- package/x/tree/parts/types.d.ts +0 -28
- package/x/tree/parts/types.js +0 -2
- package/x/tree/parts/types.js.map +0 -1
- package/x/tree/parts/utils/immute.d.ts +0 -11
- package/x/tree/parts/utils/immute.js +0 -33
- package/x/tree/parts/utils/immute.js.map +0 -1
- package/x/tree/parts/utils/process-options.d.ts +0 -2
- package/x/tree/parts/utils/process-options.js +0 -4
- package/x/tree/parts/utils/process-options.js.map +0 -1
- package/x/tree/parts/utils/setup.d.ts +0 -8
- package/x/tree/parts/utils/setup.js +0 -24
- package/x/tree/parts/utils/setup.js.map +0 -1
- package/x/tree/tree.test.d.ts +0 -40
- package/x/tree/tree.test.js +0 -325
- package/x/tree/tree.test.js.map +0 -1
package/s/signals/core/signal.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {Reactive} from "./parts/reactive.js"
|
|
3
|
-
import {tracker} from "../../tracker/tracker.js"
|
|
4
|
-
import {SignalFn, SignalOptions} from "../types.js"
|
|
5
|
-
import {defaultCompare} from "../utils/default-compare.js"
|
|
6
|
-
|
|
7
|
-
export class Signal<V> extends Reactive<V> {
|
|
8
|
-
#lock = false
|
|
9
|
-
#compare: (a: any, b: any) => boolean
|
|
10
|
-
|
|
11
|
-
constructor(value: V, options?: Partial<SignalOptions>) {
|
|
12
|
-
super(value)
|
|
13
|
-
this.#compare = options?.compare ?? defaultCompare
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
toString() {
|
|
17
|
-
return `($signal "${String(this.get())}")`
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async set(v: V, forcePublish = false) {
|
|
21
|
-
const previous = this.sneak
|
|
22
|
-
this.sneak = v
|
|
23
|
-
|
|
24
|
-
if (forcePublish || !this.#compare(previous, v))
|
|
25
|
-
await this.publish()
|
|
26
|
-
|
|
27
|
-
return v
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
get value() {
|
|
31
|
-
return this.get()
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
set value(v: V) {
|
|
35
|
-
void this.set(v)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async publish() {
|
|
39
|
-
// only wizards are allowed beyond this point.
|
|
40
|
-
// - the implementation is subtle
|
|
41
|
-
// - it looks wrong, but it's right
|
|
42
|
-
// - tarnished alchemists, take heed: lock engages only for sync activity of the async fns (think of the value setter!)
|
|
43
|
-
|
|
44
|
-
if (this.#lock)
|
|
45
|
-
throw new Error("forbid circularity")
|
|
46
|
-
|
|
47
|
-
const value = this.sneak
|
|
48
|
-
let promise: Promise<any> = Promise.resolve()
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
this.#lock = true
|
|
52
|
-
promise = Promise.all([
|
|
53
|
-
tracker.notifyWrite(this),
|
|
54
|
-
this.on.publish(value),
|
|
55
|
-
])
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
this.#lock = false
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
await promise
|
|
62
|
-
return value
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
get core() {
|
|
66
|
-
return this
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
fn() {
|
|
70
|
-
const that = this as Signal<V>
|
|
71
|
-
|
|
72
|
-
function f(): V
|
|
73
|
-
function f(v: V): Promise<V>
|
|
74
|
-
function f(_v?: V): V | Promise<V> {
|
|
75
|
-
return (arguments.length === 0)
|
|
76
|
-
? that.get()
|
|
77
|
-
: that.set(arguments[0])
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
f.core = that
|
|
81
|
-
f.get = that.get.bind(that)
|
|
82
|
-
f.set = that.set.bind(that)
|
|
83
|
-
f.on = that.on
|
|
84
|
-
f.dispose = that.dispose.bind(that)
|
|
85
|
-
f.publish = that.publish.bind(that)
|
|
86
|
-
f.fn = that.fn.bind(that)
|
|
87
|
-
|
|
88
|
-
Object.defineProperty(f, "value", {
|
|
89
|
-
get: () => that.value,
|
|
90
|
-
set: (v) => that.value = v,
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
Object.defineProperty(f, "sneak", {
|
|
94
|
-
get: () => that.sneak,
|
|
95
|
-
set: (v) => that.sneak = v,
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
return f as SignalFn<V>
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
package/s/signals/porcelain.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {SignalOptions} from "./types.js"
|
|
3
|
-
import {Lazy} from "./core/lazy.js"
|
|
4
|
-
import {Signal} from "./core/signal.js"
|
|
5
|
-
import {Derived} from "./core/derived.js"
|
|
6
|
-
|
|
7
|
-
export function lazy<V>(
|
|
8
|
-
formula: () => V,
|
|
9
|
-
options?: Partial<SignalOptions>,
|
|
10
|
-
) {
|
|
11
|
-
return new Lazy<V>(formula, options).fn()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function derived<V>(
|
|
15
|
-
formula: () => V,
|
|
16
|
-
options?: Partial<SignalOptions>,
|
|
17
|
-
) {
|
|
18
|
-
return new Derived<V>(formula, options).fn()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function signal<V>(
|
|
22
|
-
value: V,
|
|
23
|
-
options?: Partial<SignalOptions>,
|
|
24
|
-
) {
|
|
25
|
-
return new Signal<V>(value, options).fn()
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
signal.lazy = lazy
|
|
29
|
-
signal.derived = derived
|
|
30
|
-
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {Science, test, expect} from "@e280/science"
|
|
3
|
-
import {signal} from "../porcelain.js"
|
|
4
|
-
import {effect} from "../core/effect.js"
|
|
5
|
-
|
|
6
|
-
export default Science.suite({
|
|
7
|
-
"tracks signal changes": test(async() => {
|
|
8
|
-
const count = signal(1)
|
|
9
|
-
let doubled = 0
|
|
10
|
-
|
|
11
|
-
effect(() => doubled = count.value * 2)
|
|
12
|
-
expect(doubled).is(2)
|
|
13
|
-
|
|
14
|
-
await count.set(3)
|
|
15
|
-
expect(doubled).is(6)
|
|
16
|
-
}),
|
|
17
|
-
|
|
18
|
-
"is only called when signal actually changes": test(async() => {
|
|
19
|
-
const count = signal(1)
|
|
20
|
-
let runs = 0
|
|
21
|
-
effect(() => {
|
|
22
|
-
count.get()
|
|
23
|
-
runs++
|
|
24
|
-
})
|
|
25
|
-
expect(runs).is(1)
|
|
26
|
-
await count.set(999)
|
|
27
|
-
expect(runs).is(2)
|
|
28
|
-
await count.set(999)
|
|
29
|
-
expect(runs).is(2)
|
|
30
|
-
}),
|
|
31
|
-
|
|
32
|
-
"debounced": test(async() => {
|
|
33
|
-
const count = signal(1)
|
|
34
|
-
let runs = 0
|
|
35
|
-
effect(() => {
|
|
36
|
-
count.get()
|
|
37
|
-
runs++
|
|
38
|
-
})
|
|
39
|
-
expect(runs).is(1)
|
|
40
|
-
count.value++
|
|
41
|
-
count.value++
|
|
42
|
-
await count.set(count.get() + 1)
|
|
43
|
-
expect(runs).is(2)
|
|
44
|
-
}),
|
|
45
|
-
|
|
46
|
-
"can be disposed": test(async() => {
|
|
47
|
-
const count = signal(1)
|
|
48
|
-
let doubled = 0
|
|
49
|
-
|
|
50
|
-
const dispose = effect(() => doubled = count.value * 2)
|
|
51
|
-
expect(doubled).is(2)
|
|
52
|
-
|
|
53
|
-
await count.set(3)
|
|
54
|
-
expect(doubled).is(6)
|
|
55
|
-
|
|
56
|
-
dispose()
|
|
57
|
-
await count.set(4)
|
|
58
|
-
expect(doubled).is(6) // old value
|
|
59
|
-
}),
|
|
60
|
-
|
|
61
|
-
"signal set promise waits for effects": test(async() => {
|
|
62
|
-
const count = signal(1)
|
|
63
|
-
let doubled = 0
|
|
64
|
-
|
|
65
|
-
effect(() => doubled = count.value * 2)
|
|
66
|
-
expect(doubled).is(2)
|
|
67
|
-
|
|
68
|
-
await count.set(3)
|
|
69
|
-
expect(doubled).is(6)
|
|
70
|
-
}),
|
|
71
|
-
|
|
72
|
-
"only runs on change": test(async() => {
|
|
73
|
-
const sig = signal("a")
|
|
74
|
-
let runs = 0
|
|
75
|
-
|
|
76
|
-
effect(() => {
|
|
77
|
-
sig.value
|
|
78
|
-
runs++
|
|
79
|
-
})
|
|
80
|
-
expect(runs).is(1)
|
|
81
|
-
|
|
82
|
-
await sig.set("a")
|
|
83
|
-
expect(runs).is(1)
|
|
84
|
-
|
|
85
|
-
await sig.set("b")
|
|
86
|
-
expect(runs).is(2)
|
|
87
|
-
}),
|
|
88
|
-
})
|
|
89
|
-
|
package/s/tree/index.ts
DELETED
package/s/tree/parts/branch.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {deep} from "@e280/stz"
|
|
3
|
-
import {Immutable} from "../../prism/types.js"
|
|
4
|
-
import {SignalFn} from "../../signals/types.js"
|
|
5
|
-
import {signal} from "../../signals/porcelain.js"
|
|
6
|
-
import {Branchstate, Mutator, TreeOptions, Selector, Tree} from "./types.js"
|
|
7
|
-
|
|
8
|
-
/** @deprecated tree stuff has been replaced by prism/lens stuff */
|
|
9
|
-
export class Branch<S extends Branchstate, ParentState extends Branchstate = any> implements Tree<S> {
|
|
10
|
-
#previous: Immutable<S>
|
|
11
|
-
#$data: SignalFn<Immutable<S>>
|
|
12
|
-
|
|
13
|
-
constructor(
|
|
14
|
-
private parent: Tree<ParentState>,
|
|
15
|
-
private selector: Selector<S, ParentState>,
|
|
16
|
-
private options: TreeOptions,
|
|
17
|
-
) {
|
|
18
|
-
|
|
19
|
-
this.#$data = signal(this.#pull())
|
|
20
|
-
this.#previous = this.state
|
|
21
|
-
|
|
22
|
-
this.parent.on(() => {
|
|
23
|
-
const oldState = this.#previous
|
|
24
|
-
const newState = this.#pull()
|
|
25
|
-
if (!deep.equal(newState, oldState)) {
|
|
26
|
-
this.#previous = newState
|
|
27
|
-
this.#$data.set(newState, true)
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
#pull() {
|
|
33
|
-
return this.selector(this.parent.state as ParentState) as Immutable<S>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
get state() {
|
|
37
|
-
return this.#$data.get()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
get on() {
|
|
41
|
-
return this.#$data.on
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async mutate(mutator: Mutator<S>) {
|
|
45
|
-
await this.parent.mutate(
|
|
46
|
-
parentState => mutator(this.selector(parentState))
|
|
47
|
-
)
|
|
48
|
-
return this.state
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
52
|
-
return new Branch(this, selector, this.options)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {Chronicle, Immutable} from "../../prism/index.js"
|
|
3
|
-
import {Branch} from "./branch.js"
|
|
4
|
-
import {Branchstate, Mutator, TreeOptions, Selector, Tree} from "./types.js"
|
|
5
|
-
|
|
6
|
-
/** @deprecated tree stuff has been replaced by prism/lens stuff */
|
|
7
|
-
export class Chronobranch<S extends Branchstate, ParentState extends Branchstate = any> implements Tree<S> {
|
|
8
|
-
#branch: Branch<Chronicle<S>, ParentState>
|
|
9
|
-
|
|
10
|
-
constructor(
|
|
11
|
-
public limit: number,
|
|
12
|
-
public parent: Tree<ParentState>,
|
|
13
|
-
public selector: Selector<Chronicle<S>, ParentState>,
|
|
14
|
-
public options: TreeOptions,
|
|
15
|
-
) {
|
|
16
|
-
this.#branch = parent.branch(selector)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
get state() {
|
|
20
|
-
return this.#branch.state.present
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
get undoable() {
|
|
24
|
-
return this.#branch.state.past.length
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
get redoable() {
|
|
28
|
-
return this.#branch.state.future.length
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
on(fn: (state: Immutable<S>) => void) {
|
|
32
|
-
return this.#branch.on(chronicle => fn(chronicle.present))
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** progress forwards in history */
|
|
36
|
-
async mutate(mutator: Mutator<S>) {
|
|
37
|
-
const limit = Math.max(0, this.limit)
|
|
38
|
-
const snapshot = this.options.clone(this.#branch.state.present) as S
|
|
39
|
-
await this.#branch.mutate(chronicle => {
|
|
40
|
-
mutator(chronicle.present)
|
|
41
|
-
chronicle.past.push(snapshot)
|
|
42
|
-
chronicle.past = chronicle.past.slice(-limit)
|
|
43
|
-
chronicle.future = []
|
|
44
|
-
})
|
|
45
|
-
return this.state
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** step backwards into the past, by n steps */
|
|
49
|
-
async undo(n = 1) {
|
|
50
|
-
await this.#branch.mutate(chronicle => {
|
|
51
|
-
const snapshots = chronicle.past.slice(-n)
|
|
52
|
-
if (snapshots.length >= n) {
|
|
53
|
-
const oldPresent = chronicle.present
|
|
54
|
-
chronicle.present = snapshots.shift()!
|
|
55
|
-
chronicle.past = chronicle.past.slice(0, -n)
|
|
56
|
-
chronicle.future.unshift(oldPresent, ...snapshots)
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** step forwards into the future, by n steps */
|
|
62
|
-
async redo(n = 1) {
|
|
63
|
-
await this.#branch.mutate(chronicle => {
|
|
64
|
-
const snapshots = chronicle.future.slice(0, n)
|
|
65
|
-
if (snapshots.length >= n) {
|
|
66
|
-
const oldPresent = chronicle.present
|
|
67
|
-
chronicle.present = snapshots.shift()!
|
|
68
|
-
chronicle.past.push(oldPresent, ...snapshots)
|
|
69
|
-
chronicle.future = chronicle.future.slice(n)
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** wipe past and future snapshots */
|
|
75
|
-
async wipe() {
|
|
76
|
-
await this.#branch.mutate(chronicle => {
|
|
77
|
-
chronicle.past = []
|
|
78
|
-
chronicle.future = []
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
83
|
-
return new Branch(this, selector, this.options)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {Persistence} from "./types.js"
|
|
3
|
-
|
|
4
|
-
export const localPersistence = <X>(
|
|
5
|
-
key: string,
|
|
6
|
-
storage: Storage = window.localStorage,
|
|
7
|
-
): Persistence<X> => ({
|
|
8
|
-
|
|
9
|
-
store: {
|
|
10
|
-
async get() {
|
|
11
|
-
const json = storage.getItem(key)
|
|
12
|
-
return json
|
|
13
|
-
? JSON.parse(json)
|
|
14
|
-
: undefined
|
|
15
|
-
},
|
|
16
|
-
async set(state) {
|
|
17
|
-
const json = JSON.stringify(state)
|
|
18
|
-
storage.setItem(key, json)
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
onChange: (fn: () => void) => {
|
|
23
|
-
const listener = (event: StorageEvent) => {
|
|
24
|
-
if (event.storageArea === storage && event.key === key)
|
|
25
|
-
fn()
|
|
26
|
-
}
|
|
27
|
-
window.addEventListener("storage", listener)
|
|
28
|
-
return () => window.removeEventListener("storage", listener)
|
|
29
|
-
},
|
|
30
|
-
})
|
|
31
|
-
|
package/s/tree/parts/trunk.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {deep} from "@e280/stz"
|
|
3
|
-
import {Branch} from "./branch.js"
|
|
4
|
-
import {Immute} from "./utils/immute.js"
|
|
5
|
-
import {trunkSetup} from "./utils/setup.js"
|
|
6
|
-
import {Chronobranch} from "./chronobranch.js"
|
|
7
|
-
import {processOptions} from "./utils/process-options.js"
|
|
8
|
-
import {Branchstate, Mutator, TreeOptions, Selector, Tree, Trunkstate} from "./types.js"
|
|
9
|
-
import {Chronicle} from "../../prism/index.js"
|
|
10
|
-
|
|
11
|
-
/** @deprecated tree stuff has been replaced by prism/lens stuff */
|
|
12
|
-
export class Trunk<S extends Trunkstate> implements Tree<S> {
|
|
13
|
-
static setup = trunkSetup
|
|
14
|
-
static chronicle = <S extends Branchstate>(state: S): Chronicle<S> => ({
|
|
15
|
-
present: state,
|
|
16
|
-
past: [],
|
|
17
|
-
future: [],
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
options: TreeOptions
|
|
21
|
-
#mutationLock = 0
|
|
22
|
-
#immute: Immute<S>
|
|
23
|
-
|
|
24
|
-
constructor(state: S, options: Partial<TreeOptions> = {}) {
|
|
25
|
-
this.options = processOptions(options)
|
|
26
|
-
this.#immute = new Immute(state, this.options)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
get state() {
|
|
30
|
-
return this.#immute.immutable
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
get on() {
|
|
34
|
-
return this.#immute.on
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async mutate(mutator: Mutator<S>) {
|
|
38
|
-
const oldState = this.#immute.get()
|
|
39
|
-
if (this.#mutationLock > 0)
|
|
40
|
-
throw new Error("nested mutations are forbidden")
|
|
41
|
-
let promise = Promise.resolve()
|
|
42
|
-
try {
|
|
43
|
-
this.#mutationLock++
|
|
44
|
-
const newState = this.options.clone(oldState)
|
|
45
|
-
mutator(newState)
|
|
46
|
-
const isChanged = !deep.equal(newState, oldState)
|
|
47
|
-
if (isChanged)
|
|
48
|
-
promise = this.overwrite(newState)
|
|
49
|
-
}
|
|
50
|
-
finally { this.#mutationLock-- }
|
|
51
|
-
await promise
|
|
52
|
-
return this.state
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async overwrite(state: S) {
|
|
56
|
-
await this.#immute.set(state)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
60
|
-
return new Branch(this, selector, this.options)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
chronobranch<Sub extends Branchstate>(
|
|
64
|
-
limit: number,
|
|
65
|
-
selector: Selector<Chronicle<Sub>, S>,
|
|
66
|
-
) {
|
|
67
|
-
return new Chronobranch(limit, this, selector, this.options)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
package/s/tree/parts/types.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { EzStore, Versioned } from "../../prism/index.js"
|
|
3
|
-
import { Immutable } from "../../prism/types.js"
|
|
4
|
-
import {Branch} from "./branch.js"
|
|
5
|
-
|
|
6
|
-
export type TreeOptions = {
|
|
7
|
-
clone: <X>(x: X) => X
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** @deprecated renamed to `TreeOptions` */
|
|
11
|
-
export type Options = TreeOptions
|
|
12
|
-
|
|
13
|
-
export type Selector<Sub, S> = (state: S) => Sub
|
|
14
|
-
export type Mutator<S> = (state: S) => void
|
|
15
|
-
|
|
16
|
-
export type Trunkstate = {}
|
|
17
|
-
export type Branchstate = {} | null | undefined
|
|
18
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
export type Tree<S extends Branchstate> = {
|
|
37
|
-
get state(): Immutable<S>
|
|
38
|
-
on(fn: (state: Immutable<S>) => void): () => void
|
|
39
|
-
mutate(mutator: Mutator<S>): Promise<Immutable<S>>
|
|
40
|
-
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type SetupOptions<S extends Trunkstate> = {
|
|
44
|
-
version: number
|
|
45
|
-
initialState: S
|
|
46
|
-
saveDebounceTime?: number
|
|
47
|
-
persistence?: Persistence<Versioned<S>>
|
|
48
|
-
}
|
|
49
|
-
|
|
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
|
-
// }
|
|
60
|
-
|
|
61
|
-
// export type EzStore<X> = {
|
|
62
|
-
// get(): Promise<X | undefined>
|
|
63
|
-
// set(state: X | undefined): Promise<void>
|
|
64
|
-
// }
|
|
65
|
-
|
|
66
|
-
export type Persistence<X> = {
|
|
67
|
-
store: EzStore<X>
|
|
68
|
-
onChange: (fn: () => void) => (() => void)
|
|
69
|
-
}
|
|
70
|
-
|
|
@@ -1,43 +0,0 @@
|
|
|
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,40 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {debounce} from "@e280/stz"
|
|
3
|
-
|
|
4
|
-
import {Trunk} from "../trunk.js"
|
|
5
|
-
import {localPersistence} from "../persistence.js"
|
|
6
|
-
import {SetupOptions, Trunkstate} from "../types.js"
|
|
7
|
-
|
|
8
|
-
export async function trunkSetup<S extends Trunkstate>(options: SetupOptions<S>) {
|
|
9
|
-
const {
|
|
10
|
-
version,
|
|
11
|
-
initialState,
|
|
12
|
-
saveDebounceTime = 500,
|
|
13
|
-
persistence = localPersistence("strataTree"),
|
|
14
|
-
} = options
|
|
15
|
-
|
|
16
|
-
const trunk = new Trunk<S>(initialState)
|
|
17
|
-
|
|
18
|
-
async function load() {
|
|
19
|
-
const pickle = await persistence.store.get()
|
|
20
|
-
if (pickle && pickle.version === version)
|
|
21
|
-
await trunk.overwrite(pickle.state)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const save = debounce(saveDebounceTime, async() => persistence.store.set({
|
|
25
|
-
version,
|
|
26
|
-
state: trunk.state as any,
|
|
27
|
-
}))
|
|
28
|
-
|
|
29
|
-
// persistence: initial load from store
|
|
30
|
-
await load()
|
|
31
|
-
|
|
32
|
-
// persistence: save to store
|
|
33
|
-
trunk.on(save)
|
|
34
|
-
|
|
35
|
-
// cross-tab sync
|
|
36
|
-
const dispose = persistence.onChange(load)
|
|
37
|
-
|
|
38
|
-
return {trunk, load, save, dispose}
|
|
39
|
-
}
|
|
40
|
-
|