@e280/strata 0.0.0-1 → 0.0.0-10
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 +165 -45
- package/package.json +14 -8
- package/s/index.ts +3 -4
- package/s/signals/index.ts +6 -0
- package/s/signals/parts/derive.ts +29 -0
- package/s/signals/parts/effect.ts +23 -0
- package/s/signals/parts/lazy.ts +27 -0
- package/s/signals/parts/signal.ts +44 -0
- package/s/signals/parts/types.ts +11 -0
- package/s/signals/parts/units.ts +152 -0
- package/s/signals/signals.test.ts +285 -0
- package/s/tests.test.ts +45 -286
- package/s/tracker/index.ts +3 -0
- package/s/tracker/tracker.test.ts +40 -0
- package/s/tracker/tracker.ts +73 -0
- package/s/tree/index.ts +7 -0
- package/s/tree/parts/branch.ts +41 -0
- package/s/{parts/chronstrata.ts → tree/parts/chronobranch.ts} +19 -19
- package/s/tree/parts/persistence.ts +31 -0
- package/s/tree/parts/trunk.ts +72 -0
- package/s/tree/parts/types.ts +65 -0
- package/s/{parts → tree/parts}/utils/process-options.ts +1 -1
- package/s/tree/parts/utils/setup.ts +40 -0
- package/s/tree/tree.test.ts +316 -0
- package/x/index.d.ts +3 -4
- package/x/index.js +3 -4
- package/x/index.js.map +1 -1
- package/x/signals/index.d.ts +4 -0
- package/x/signals/index.js +5 -0
- package/x/signals/index.js.map +1 -0
- package/x/signals/parts/derive.d.ts +12 -0
- package/x/signals/parts/derive.js +12 -0
- package/x/signals/parts/derive.js.map +1 -0
- package/x/signals/parts/effect.d.ts +5 -0
- package/x/signals/parts/effect.js +17 -0
- package/x/signals/parts/effect.js.map +1 -0
- package/x/signals/parts/lazy.d.ts +10 -0
- package/x/signals/parts/lazy.js +12 -0
- package/x/signals/parts/lazy.js.map +1 -0
- package/x/signals/parts/signal.d.ts +21 -0
- package/x/signals/parts/signal.js +18 -0
- package/x/signals/parts/signal.js.map +1 -0
- package/x/signals/parts/types.d.ts +7 -0
- package/x/signals/parts/types.js.map +1 -0
- package/x/signals/parts/units.d.ts +43 -0
- package/x/signals/parts/units.js +133 -0
- package/x/signals/parts/units.js.map +1 -0
- package/x/signals/signals.test.d.ts +24 -0
- package/x/signals/signals.test.js +230 -0
- package/x/signals/signals.test.js.map +1 -0
- package/x/tests.test.js +46 -265
- package/x/tests.test.js.map +1 -1
- package/x/tracker/index.d.ts +1 -0
- package/x/tracker/index.js +2 -0
- package/x/tracker/index.js.map +1 -0
- package/x/tracker/tracker.d.ts +29 -0
- package/x/tracker/tracker.js +62 -0
- package/x/tracker/tracker.js.map +1 -0
- package/x/tracker/tracker.test.d.ts +6 -0
- package/x/tracker/tracker.test.js +32 -0
- package/x/tracker/tracker.test.js.map +1 -0
- package/x/tree/index.d.ts +5 -0
- package/x/tree/index.js +6 -0
- package/x/tree/index.js.map +1 -0
- package/x/tree/parts/branch.d.ts +12 -0
- package/x/tree/parts/branch.js +31 -0
- package/x/tree/parts/branch.js.map +1 -0
- package/x/tree/parts/chronobranch.d.ts +23 -0
- package/x/{parts/chronstrata.js → tree/parts/chronobranch.js} +17 -17
- package/x/tree/parts/chronobranch.js.map +1 -0
- package/x/tree/parts/persistence.d.ts +2 -0
- package/x/tree/parts/persistence.js +23 -0
- package/x/tree/parts/persistence.js.map +1 -0
- package/x/tree/parts/trunk.d.ts +17 -0
- package/x/tree/parts/trunk.js +56 -0
- package/x/tree/parts/trunk.js.map +1 -0
- package/x/tree/parts/types.d.ts +43 -0
- package/x/tree/parts/types.js +2 -0
- package/x/{parts → tree/parts}/types.js.map +1 -1
- package/x/tree/parts/utils/process-options.js +4 -0
- package/x/tree/parts/utils/process-options.js.map +1 -0
- package/x/tree/parts/utils/setup.d.ts +8 -0
- package/x/tree/parts/utils/setup.js +24 -0
- package/x/tree/parts/utils/setup.js.map +1 -0
- package/x/tree/tree.test.d.ts +37 -0
- package/x/tree/tree.test.js +279 -0
- package/x/tree/tree.test.js.map +1 -0
- package/s/parts/strata.ts +0 -71
- package/s/parts/substrata.ts +0 -58
- package/s/parts/types.ts +0 -31
- package/x/parts/chronstrata.d.ts +0 -23
- package/x/parts/chronstrata.js.map +0 -1
- package/x/parts/strata.d.ts +0 -14
- package/x/parts/strata.js +0 -64
- package/x/parts/strata.js.map +0 -1
- package/x/parts/substrata.d.ts +0 -15
- package/x/parts/substrata.js +0 -45
- package/x/parts/substrata.js.map +0 -1
- package/x/parts/types.d.ts +0 -19
- package/x/parts/utils/process-options.js +0 -4
- package/x/parts/utils/process-options.js.map +0 -1
- /package/x/{parts → signals/parts}/types.js +0 -0
- /package/x/{parts → tree/parts}/utils/process-options.d.ts +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
import {sub, Sub} from "@e280/stz"
|
|
3
|
+
|
|
4
|
+
export type TrackableItem = object | symbol
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* tracking system for state management
|
|
8
|
+
* - it tracks when items are seen or changed
|
|
9
|
+
*
|
|
10
|
+
* for state item integration (like you're integrating a new kind of state object)
|
|
11
|
+
* - items can call `tracker.see(this)` when they are accessed
|
|
12
|
+
* - items can call `tracker.change(this)` when they are reassigned
|
|
13
|
+
*
|
|
14
|
+
* for reactivity integration (like you're integrating a new view library that reacts to state changes)
|
|
15
|
+
* - run `tracker.seen(renderFn)`, collecting a set of seen items
|
|
16
|
+
* - loop over each seen item, attach a changed handler `tracker.changed(item, handlerFn)`
|
|
17
|
+
*/
|
|
18
|
+
export class Tracker<Item extends TrackableItem = any> {
|
|
19
|
+
#seeables: Set<Item>[] = []
|
|
20
|
+
#changeables = new WeakMap<Item, Sub>()
|
|
21
|
+
#changeStack: Set<Promise<void>>[] = []
|
|
22
|
+
#busy = new Set<Item>()
|
|
23
|
+
|
|
24
|
+
/** indicate item was accessed */
|
|
25
|
+
see(item: Item) {
|
|
26
|
+
this.#seeables.at(-1)?.add(item)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** collect which items were seen during fn */
|
|
30
|
+
seen<R>(fn: () => R) {
|
|
31
|
+
this.#seeables.push(new Set())
|
|
32
|
+
const result = fn()
|
|
33
|
+
const seen = this.#seeables.pop()!
|
|
34
|
+
return {seen, result}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** indicate item was changed */
|
|
38
|
+
async change(item: Item) {
|
|
39
|
+
if (this.#busy.has(item))
|
|
40
|
+
throw new Error("circularity forbidden")
|
|
41
|
+
const prom = this.#guaranteeChangeable(item).pub()
|
|
42
|
+
this.#changeStack.at(-1)?.add(prom)
|
|
43
|
+
return prom
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** respond to changes by calling fn */
|
|
47
|
+
changed(item: Item, fn: () => Promise<void>) {
|
|
48
|
+
return this.#guaranteeChangeable(item)(async() => {
|
|
49
|
+
const collected = new Set<Promise<void>>()
|
|
50
|
+
this.#changeStack.push(collected)
|
|
51
|
+
this.#busy.add(item)
|
|
52
|
+
collected.add(fn())
|
|
53
|
+
this.#busy.delete(item)
|
|
54
|
+
await Promise.all(collected)
|
|
55
|
+
this.#changeStack.pop()
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#guaranteeChangeable(item: Item) {
|
|
60
|
+
let on = this.#changeables.get(item)
|
|
61
|
+
if (!on) {
|
|
62
|
+
on = sub()
|
|
63
|
+
this.#changeables.set(item, on)
|
|
64
|
+
}
|
|
65
|
+
return on
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const key = Symbol.for("e280.tracker.v2")
|
|
70
|
+
|
|
71
|
+
/** standard global tracker for integrations */
|
|
72
|
+
export const tracker: Tracker = (globalThis as any)[key] ??= new Tracker()
|
|
73
|
+
|
package/s/tree/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
import {deep} from "@e280/stz"
|
|
3
|
+
import {signal} from "../../signals/parts/signal.js"
|
|
4
|
+
import {DerivedSignal} from "../../signals/parts/derive.js"
|
|
5
|
+
import {Branchstate, Immutable, Mutator, Options, Selector, Tree} from "./types.js"
|
|
6
|
+
|
|
7
|
+
export class Branch<S extends Branchstate, ParentState extends Branchstate = any> implements Tree<S> {
|
|
8
|
+
#immutable: DerivedSignal<Immutable<S>>
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
private parent: Tree<ParentState>,
|
|
12
|
+
private selector: Selector<S, ParentState>,
|
|
13
|
+
private options: Options,
|
|
14
|
+
) {
|
|
15
|
+
|
|
16
|
+
this.#immutable = signal.derive(() => {
|
|
17
|
+
const state = selector(parent.state as any)
|
|
18
|
+
return deep.freeze(options.clone(state)) as Immutable<S>
|
|
19
|
+
}, {compare: deep.equal})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get state() {
|
|
23
|
+
return this.#immutable.get()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get on() {
|
|
27
|
+
return this.#immutable.on
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async mutate(mutator: Mutator<S>) {
|
|
31
|
+
await this.parent.mutate(parentState =>
|
|
32
|
+
mutator(this.selector(parentState))
|
|
33
|
+
)
|
|
34
|
+
return this.#immutable.get()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
38
|
+
return new Branch(this, selector, this.options)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
import {Chronicle, Mutator, Options, Selector,
|
|
2
|
+
import {Branch} from "./branch.js"
|
|
3
|
+
import {Branchstate, Chronicle, Immutable, Mutator, Options, Selector, Tree} from "./types.js"
|
|
4
4
|
|
|
5
|
-
export class
|
|
6
|
-
#
|
|
5
|
+
export class Chronobranch<S extends Branchstate, ParentState extends Branchstate = any> implements Tree<S> {
|
|
6
|
+
#branch: Branch<Chronicle<S>, ParentState>
|
|
7
7
|
|
|
8
8
|
constructor(
|
|
9
9
|
public limit: number,
|
|
10
|
-
public parent:
|
|
11
|
-
public selector: Selector<
|
|
10
|
+
public parent: Tree<ParentState>,
|
|
11
|
+
public selector: Selector<Chronicle<S>, ParentState>,
|
|
12
12
|
public options: Options,
|
|
13
13
|
) {
|
|
14
|
-
this.#
|
|
14
|
+
this.#branch = parent.branch(selector)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
get state() {
|
|
18
|
-
return this.#
|
|
18
|
+
return this.#branch.state.present
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
get undoable() {
|
|
22
|
-
return this.#
|
|
22
|
+
return this.#branch.state.past.length
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
get redoable() {
|
|
26
|
-
return this.#
|
|
26
|
+
return this.#branch.state.future.length
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
return this.#
|
|
29
|
+
on(fn: (state: Immutable<S>) => void) {
|
|
30
|
+
return this.#branch.on(chronicle => fn(chronicle.present))
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/** progress forwards in history */
|
|
34
34
|
async mutate(mutator: Mutator<S>) {
|
|
35
35
|
const limit = Math.max(0, this.limit)
|
|
36
|
-
const snapshot = this.options.clone(this.#
|
|
37
|
-
await this.#
|
|
36
|
+
const snapshot = this.options.clone(this.#branch.state.present) as S
|
|
37
|
+
await this.#branch.mutate(chronicle => {
|
|
38
38
|
mutator(chronicle.present)
|
|
39
39
|
chronicle.past.push(snapshot)
|
|
40
40
|
chronicle.past = chronicle.past.slice(-limit)
|
|
@@ -45,7 +45,7 @@ export class Chronstrata<ParentState extends Substate, S extends Substate> imple
|
|
|
45
45
|
|
|
46
46
|
/** step backwards into the past, by n steps */
|
|
47
47
|
async undo(n = 1) {
|
|
48
|
-
await this.#
|
|
48
|
+
await this.#branch.mutate(chronicle => {
|
|
49
49
|
const snapshots = chronicle.past.slice(-n)
|
|
50
50
|
if (snapshots.length >= n) {
|
|
51
51
|
const oldPresent = chronicle.present
|
|
@@ -58,7 +58,7 @@ export class Chronstrata<ParentState extends Substate, S extends Substate> imple
|
|
|
58
58
|
|
|
59
59
|
/** step forwards into the future, by n steps */
|
|
60
60
|
async redo(n = 1) {
|
|
61
|
-
await this.#
|
|
61
|
+
await this.#branch.mutate(chronicle => {
|
|
62
62
|
const snapshots = chronicle.future.slice(0, n)
|
|
63
63
|
if (snapshots.length >= n) {
|
|
64
64
|
const oldPresent = chronicle.present
|
|
@@ -71,14 +71,14 @@ export class Chronstrata<ParentState extends Substate, S extends Substate> imple
|
|
|
71
71
|
|
|
72
72
|
/** wipe past and future snapshots */
|
|
73
73
|
async wipe() {
|
|
74
|
-
await this.#
|
|
74
|
+
await this.#branch.mutate(chronicle => {
|
|
75
75
|
chronicle.past = []
|
|
76
76
|
chronicle.future = []
|
|
77
77
|
})
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
return new
|
|
80
|
+
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
81
|
+
return new Branch(this, selector, this.options)
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
|
|
2
|
+
import {deep} from "@e280/stz"
|
|
3
|
+
import {Branch} from "./branch.js"
|
|
4
|
+
import {trunkSetup} from "./utils/setup.js"
|
|
5
|
+
import {Chronobranch} from "./chronobranch.js"
|
|
6
|
+
import {processOptions} from "./utils/process-options.js"
|
|
7
|
+
import {DerivedSignal} from "../../signals/parts/derive.js"
|
|
8
|
+
import {signal, Signal} from "../../signals/parts/signal.js"
|
|
9
|
+
import {Branchstate, Chronicle, Immutable, Mutator, Options, Selector, Tree, Trunkstate} from "./types.js"
|
|
10
|
+
|
|
11
|
+
export class Trunk<S extends Trunkstate> implements Tree<S> {
|
|
12
|
+
static setup = trunkSetup
|
|
13
|
+
static chronicle = <S extends Branchstate>(state: S): Chronicle<S> => ({
|
|
14
|
+
present: state,
|
|
15
|
+
past: [],
|
|
16
|
+
future: [],
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
options: Options
|
|
20
|
+
|
|
21
|
+
#immutable: DerivedSignal<Immutable<S>>
|
|
22
|
+
#mutable: Signal<S>
|
|
23
|
+
#mutationLock = 0
|
|
24
|
+
|
|
25
|
+
constructor(state: S, options: Partial<Options> = {}) {
|
|
26
|
+
this.options = processOptions(options)
|
|
27
|
+
this.#mutable = signal(state)
|
|
28
|
+
this.#immutable = signal.derive(() =>
|
|
29
|
+
deep.freeze(this.options.clone(this.#mutable.get())) as Immutable<S>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get state() {
|
|
34
|
+
return this.#immutable.get()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get on() {
|
|
38
|
+
return this.#immutable.on
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async mutate(mutator: Mutator<S>) {
|
|
42
|
+
const oldState = this.options.clone(this.#mutable.get())
|
|
43
|
+
if (this.#mutationLock > 0)
|
|
44
|
+
throw new Error("nested mutations are forbidden")
|
|
45
|
+
try {
|
|
46
|
+
this.#mutationLock++
|
|
47
|
+
mutator(this.#mutable())
|
|
48
|
+
const newState = this.#mutable.get()
|
|
49
|
+
const isChanged = !deep.equal(newState, oldState)
|
|
50
|
+
if (isChanged)
|
|
51
|
+
await this.overwrite(newState)
|
|
52
|
+
}
|
|
53
|
+
finally { this.#mutationLock-- }
|
|
54
|
+
return this.#immutable.get()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async overwrite(state: S) {
|
|
58
|
+
await this.#mutable.publish(state)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
62
|
+
return new Branch(this, selector, this.options)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
chronobranch<Sub extends Branchstate>(
|
|
66
|
+
limit: number,
|
|
67
|
+
selector: Selector<Chronicle<Sub>, S>,
|
|
68
|
+
) {
|
|
69
|
+
return new Chronobranch(limit, this, selector, this.options)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
import {Branch} from "./branch.js"
|
|
3
|
+
|
|
4
|
+
export type Options = {
|
|
5
|
+
clone: <X>(x: X) => X
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type Selector<Sub, S> = (state: S) => Sub
|
|
9
|
+
export type Mutator<S> = (state: S) => void
|
|
10
|
+
|
|
11
|
+
export type Trunkstate = {}
|
|
12
|
+
export type Branchstate = {} | null | undefined
|
|
13
|
+
|
|
14
|
+
export type Versioned<S extends Trunkstate> = {
|
|
15
|
+
state: S
|
|
16
|
+
version: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Immutable<T> =
|
|
20
|
+
T extends (...args: any[]) => any ? T :
|
|
21
|
+
T extends readonly any[] ? ReadonlyArray<Immutable<T[number]>> :
|
|
22
|
+
T extends object ? { readonly [K in keyof T]: Immutable<T[K]> } :
|
|
23
|
+
T
|
|
24
|
+
|
|
25
|
+
export type Mutable<T> =
|
|
26
|
+
T extends (...args: any[]) => any ? T :
|
|
27
|
+
T extends ReadonlyArray<infer U> ? Mutable<U>[] :
|
|
28
|
+
T extends object ? { -readonly [K in keyof T]: Mutable<T[K]> } :
|
|
29
|
+
T
|
|
30
|
+
|
|
31
|
+
export type Tree<S extends Branchstate> = {
|
|
32
|
+
get state(): Immutable<S>
|
|
33
|
+
on(fn: (state: Immutable<S>) => void): () => void
|
|
34
|
+
mutate(mutator: Mutator<S>): Promise<Immutable<S>>
|
|
35
|
+
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type SetupOptions<S extends Trunkstate> = {
|
|
39
|
+
version: number
|
|
40
|
+
initialState: S
|
|
41
|
+
saveDebounceTime?: number
|
|
42
|
+
persistence?: Persistence<Versioned<S>>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type Chronicle<S extends Branchstate> = {
|
|
46
|
+
// [abc] d [efg]
|
|
47
|
+
// \ \ \
|
|
48
|
+
// \ \ future
|
|
49
|
+
// \ present
|
|
50
|
+
// past
|
|
51
|
+
past: S[]
|
|
52
|
+
present: S
|
|
53
|
+
future: S[]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type EzStore<X> = {
|
|
57
|
+
get(): Promise<X | undefined>
|
|
58
|
+
set(state: X | undefined): Promise<void>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type Persistence<X> = {
|
|
62
|
+
store: EzStore<X>
|
|
63
|
+
onChange: (fn: () => void) => (() => void)
|
|
64
|
+
}
|
|
65
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
|