@e280/strata 0.0.0-2 → 0.0.0-4
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 +25 -10
- package/package.json +1 -1
- package/s/index.ts +1 -0
- package/s/parts/chronstrata.ts +4 -4
- package/s/parts/persistence.ts +31 -0
- package/s/parts/strata.ts +17 -17
- package/s/parts/substrata.ts +4 -4
- package/s/parts/types.ts +24 -2
- package/s/parts/utils/process-options.ts +1 -1
- package/s/parts/utils/setup.ts +40 -0
- package/x/index.d.ts +1 -0
- package/x/index.js +1 -0
- package/x/index.js.map +1 -1
- package/x/parts/chronstrata.d.ts +4 -4
- package/x/parts/persistence.d.ts +2 -0
- package/x/parts/persistence.js +23 -0
- package/x/parts/persistence.js.map +1 -0
- package/x/parts/strata.d.ts +5 -2
- package/x/parts/strata.js +18 -18
- package/x/parts/strata.js.map +1 -1
- package/x/parts/substrata.d.ts +4 -4
- package/x/parts/types.d.ts +20 -2
- package/x/parts/utils/process-options.js +1 -1
- package/x/parts/utils/process-options.js.map +1 -1
- package/x/parts/utils/setup.d.ts +8 -0
- package/x/parts/utils/setup.js +24 -0
- package/x/parts/utils/setup.js.map +1 -0
package/README.md
CHANGED
|
@@ -76,6 +76,31 @@
|
|
|
76
76
|
|
|
77
77
|
## only high-class discerning aristocrats permitted beyond this point
|
|
78
78
|
|
|
79
|
+
### `Strata.setup` for localStorage persistence etc
|
|
80
|
+
- simple setup
|
|
81
|
+
```ts
|
|
82
|
+
const {strata} = await Strata.setup({
|
|
83
|
+
version: 1, // 👈 bump whenever your change state schema!
|
|
84
|
+
initialState: {count: 0},
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
- it's compatible with [`@e280/kv`](https://github.com/e280/kv)
|
|
88
|
+
```ts
|
|
89
|
+
import {Kv, StorageDriver} from "@e280/kv"
|
|
90
|
+
|
|
91
|
+
const kv = new Kv(new StorageDriver())
|
|
92
|
+
const store = kv.store<any>("strata")
|
|
93
|
+
|
|
94
|
+
const {strata} = await Strata.setup({
|
|
95
|
+
version: 1,
|
|
96
|
+
initialState: {count: 0},
|
|
97
|
+
persistence: {
|
|
98
|
+
store,
|
|
99
|
+
onChange: StorageDriver.onStorageEvent,
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
79
104
|
### `Chronstrata` for undo/redo history
|
|
80
105
|
- first, put a `Chronicle` into your state tree
|
|
81
106
|
```ts
|
|
@@ -106,16 +131,6 @@
|
|
|
106
131
|
```
|
|
107
132
|
- you can check how many undoable or redoable steps are available
|
|
108
133
|
```ts
|
|
109
|
-
snacks.undoable // 0
|
|
110
|
-
|
|
111
|
-
await snacks.mutate(s => s.peanuts = 101)
|
|
112
|
-
await snacks.mutate(s => s.peanuts = 102)
|
|
113
|
-
await snacks.mutate(s => s.peanuts = 103)
|
|
114
|
-
|
|
115
|
-
snacks.undoable // 3
|
|
116
|
-
|
|
117
|
-
await snacks.undo()
|
|
118
|
-
|
|
119
134
|
snacks.undoable // 2
|
|
120
135
|
snacks.redoable // 1
|
|
121
136
|
```
|
package/package.json
CHANGED
package/s/index.ts
CHANGED
package/s/parts/chronstrata.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import {Substrata} from "./substrata.js"
|
|
3
3
|
import {Chronicle, Mutator, Options, Selector, Stratum, Substate} from "./types.js"
|
|
4
4
|
|
|
5
|
-
export class Chronstrata<
|
|
6
|
-
#substrata: Substrata<
|
|
5
|
+
export class Chronstrata<S extends Substate, ParentState extends Substate = any> implements Stratum<S> {
|
|
6
|
+
#substrata: Substrata<Chronicle<S>, ParentState>
|
|
7
7
|
|
|
8
8
|
constructor(
|
|
9
9
|
public limit: number,
|
|
10
10
|
public parent: Stratum<ParentState>,
|
|
11
|
-
public selector: Selector<
|
|
11
|
+
public selector: Selector<Chronicle<S>, ParentState>,
|
|
12
12
|
public options: Options,
|
|
13
13
|
) {
|
|
14
14
|
this.#substrata = parent.substrata(selector)
|
|
@@ -77,7 +77,7 @@ export class Chronstrata<ParentState extends Substate, S extends Substate> imple
|
|
|
77
77
|
})
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
substrata<Sub extends Substate>(selector: Selector<
|
|
80
|
+
substrata<Sub extends Substate>(selector: Selector<Sub, S>): Substrata<Sub, S> {
|
|
81
81
|
return new Substrata(this, selector, this.options)
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -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
|
+
|
package/s/parts/strata.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
|
|
2
2
|
import {debounce, deep, sub} from "@e280/stz"
|
|
3
3
|
|
|
4
|
+
import {strataSetup} from "./utils/setup.js"
|
|
4
5
|
import {Substrata} from "./substrata.js"
|
|
5
6
|
import {Chronstrata} from "./chronstrata.js"
|
|
6
7
|
import {processOptions} from "./utils/process-options.js"
|
|
7
8
|
import {Chronicle, Mutator, Options, Selector, State, Stratum, Substate} from "./types.js"
|
|
8
9
|
|
|
9
10
|
export class Strata<S extends State> implements Stratum<S> {
|
|
11
|
+
static setup = strataSetup
|
|
10
12
|
static chronicle = <S extends Substate>(state: S): Chronicle<S> => ({
|
|
11
13
|
present: state,
|
|
12
14
|
past: [],
|
|
@@ -19,11 +21,6 @@ export class Strata<S extends State> implements Stratum<S> {
|
|
|
19
21
|
#mutable: S
|
|
20
22
|
#immutable: S
|
|
21
23
|
#mutationLock = 0
|
|
22
|
-
#dispatchMutation = debounce(0, async(state: S) => {
|
|
23
|
-
this.#mutationLock++
|
|
24
|
-
try { await this.watch.pub(state) }
|
|
25
|
-
finally { this.#mutationLock-- }
|
|
26
|
-
})
|
|
27
24
|
|
|
28
25
|
constructor(state: S, options: Partial<Options> = {}) {
|
|
29
26
|
this.options = processOptions(options)
|
|
@@ -31,11 +28,6 @@ export class Strata<S extends State> implements Stratum<S> {
|
|
|
31
28
|
this.#immutable = deep.freeze(this.options.clone(state))
|
|
32
29
|
}
|
|
33
30
|
|
|
34
|
-
#updateState(state: S) {
|
|
35
|
-
this.#mutable = state
|
|
36
|
-
this.#immutable = deep.freeze(this.options.clone(state))
|
|
37
|
-
}
|
|
38
|
-
|
|
39
31
|
get state() {
|
|
40
32
|
return this.#immutable
|
|
41
33
|
}
|
|
@@ -49,23 +41,31 @@ export class Strata<S extends State> implements Stratum<S> {
|
|
|
49
41
|
finally { this.#mutationLock-- }
|
|
50
42
|
const newState = this.#mutable
|
|
51
43
|
const isChanged = !deep.equal(newState, oldState)
|
|
52
|
-
if (isChanged)
|
|
53
|
-
this.#updateState(newState)
|
|
54
|
-
const immutable = this.state
|
|
55
|
-
await this.#dispatchMutation(immutable)
|
|
56
|
-
}
|
|
44
|
+
if (isChanged) await this.overwrite(newState)
|
|
57
45
|
return this.#immutable
|
|
58
46
|
}
|
|
59
47
|
|
|
60
|
-
|
|
48
|
+
async overwrite(state: S) {
|
|
49
|
+
this.#mutable = state
|
|
50
|
+
this.#immutable = deep.freeze(this.options.clone(state))
|
|
51
|
+
await this.#dispatchMutation()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
substrata<Sub extends Substate>(selector: Selector<Sub, S>): Substrata<Sub, S> {
|
|
61
55
|
return new Substrata(this, selector, this.options)
|
|
62
56
|
}
|
|
63
57
|
|
|
64
58
|
chronstrata<Sub extends Substate>(
|
|
65
59
|
limit: number,
|
|
66
|
-
selector: Selector<
|
|
60
|
+
selector: Selector<Chronicle<Sub>, S>,
|
|
67
61
|
) {
|
|
68
62
|
return new Chronstrata(limit, this, selector, this.options)
|
|
69
63
|
}
|
|
64
|
+
|
|
65
|
+
#dispatchMutation = debounce(0, async() => {
|
|
66
|
+
this.#mutationLock++
|
|
67
|
+
try { await this.watch.pub(this.#immutable) }
|
|
68
|
+
finally { this.#mutationLock-- }
|
|
69
|
+
})
|
|
70
70
|
}
|
|
71
71
|
|
package/s/parts/substrata.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {debounce, deep, sub} from "@e280/stz"
|
|
|
3
3
|
import {Chronstrata} from "./chronstrata.js"
|
|
4
4
|
import {Chronicle, Mutator, Options, Selector, Stratum, Substate} from "./types.js"
|
|
5
5
|
|
|
6
|
-
export class Substrata<
|
|
6
|
+
export class Substrata<S extends Substate, ParentState extends Substate = any> implements Stratum<S> {
|
|
7
7
|
dispose: () => void
|
|
8
8
|
watch = sub<[state: S]>()
|
|
9
9
|
|
|
@@ -12,7 +12,7 @@ export class Substrata<ParentState extends Substate, S extends Substate> impleme
|
|
|
12
12
|
|
|
13
13
|
constructor(
|
|
14
14
|
private parent: Stratum<ParentState>,
|
|
15
|
-
private selector: Selector<
|
|
15
|
+
private selector: Selector<S, ParentState>,
|
|
16
16
|
private options: Options,
|
|
17
17
|
) {
|
|
18
18
|
|
|
@@ -44,13 +44,13 @@ export class Substrata<ParentState extends Substate, S extends Substate> impleme
|
|
|
44
44
|
return this.#immutable
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
substrata<Sub extends Substate>(selector: Selector<
|
|
47
|
+
substrata<Sub extends Substate>(selector: Selector<Sub, S>): Substrata<Sub, S> {
|
|
48
48
|
return new Substrata(this, selector, this.options)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
chronstrata<Sub extends Substate>(
|
|
52
52
|
limit: number,
|
|
53
|
-
selector: Selector<
|
|
53
|
+
selector: Selector<Chronicle<Sub>, S>,
|
|
54
54
|
) {
|
|
55
55
|
return new Chronstrata(limit, this, selector, this.options)
|
|
56
56
|
}
|
package/s/parts/types.ts
CHANGED
|
@@ -5,17 +5,29 @@ export type Options = {
|
|
|
5
5
|
clone: <X>(x: X) => X
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export type Selector<
|
|
8
|
+
export type Selector<Sub, S> = (state: S) => Sub
|
|
9
9
|
export type Mutator<S> = (state: S) => void
|
|
10
10
|
|
|
11
11
|
export type State = {}
|
|
12
12
|
export type Substate = {} | null | undefined
|
|
13
13
|
|
|
14
|
+
export type Versioned<S extends State> = {
|
|
15
|
+
state: S
|
|
16
|
+
version: number
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export type Stratum<S extends Substate> = {
|
|
15
20
|
readonly state: S
|
|
16
21
|
watch(fn: (s: S) => void): () => void
|
|
17
22
|
mutate(mutator: Mutator<S>): Promise<S>
|
|
18
|
-
substrata<Sub extends Substate>(selector: Selector<
|
|
23
|
+
substrata<Sub extends Substate>(selector: Selector<Sub, S>): Substrata<Sub, S>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type SetupOptions<S extends State> = {
|
|
27
|
+
version: number
|
|
28
|
+
initialState: S
|
|
29
|
+
saveDebounceTime?: number
|
|
30
|
+
persistence?: Persistence<Versioned<S>>
|
|
19
31
|
}
|
|
20
32
|
|
|
21
33
|
export type Chronicle<S extends Substate> = {
|
|
@@ -29,3 +41,13 @@ export type Chronicle<S extends Substate> = {
|
|
|
29
41
|
future: S[]
|
|
30
42
|
}
|
|
31
43
|
|
|
44
|
+
export type EzStore<X> = {
|
|
45
|
+
get(): Promise<X | undefined>
|
|
46
|
+
set(state: X | undefined): Promise<void>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type Persistence<X> = {
|
|
50
|
+
store: EzStore<X>
|
|
51
|
+
onChange: (fn: () => void) => (() => void)
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
import {debounce} from "@e280/stz"
|
|
3
|
+
|
|
4
|
+
import {Strata} from "../strata.js"
|
|
5
|
+
import {State, SetupOptions} from "../types.js"
|
|
6
|
+
import {localPersistence} from "../persistence.js"
|
|
7
|
+
|
|
8
|
+
export async function strataSetup<S extends State>(options: SetupOptions<S>) {
|
|
9
|
+
const {
|
|
10
|
+
version,
|
|
11
|
+
initialState,
|
|
12
|
+
saveDebounceTime = 500,
|
|
13
|
+
persistence = localPersistence("strata"),
|
|
14
|
+
} = options
|
|
15
|
+
|
|
16
|
+
const strata = new Strata<S>(initialState)
|
|
17
|
+
|
|
18
|
+
async function load() {
|
|
19
|
+
const pickle = await persistence.store.get()
|
|
20
|
+
if (pickle && pickle.version === version)
|
|
21
|
+
await strata.overwrite(pickle.state)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const save = debounce(saveDebounceTime, async() => persistence.store.set({
|
|
25
|
+
version,
|
|
26
|
+
state: strata.state,
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
// persistence: initial load from store
|
|
30
|
+
await load()
|
|
31
|
+
|
|
32
|
+
// persistence: save to store
|
|
33
|
+
strata.watch(save)
|
|
34
|
+
|
|
35
|
+
// cross-tab sync
|
|
36
|
+
const dispose = persistence.onChange(load)
|
|
37
|
+
|
|
38
|
+
return {strata, load, save, dispose}
|
|
39
|
+
}
|
|
40
|
+
|
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,wBAAwB,CAAA;AACtC,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../s/index.ts"],"names":[],"mappings":"AACA,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA"}
|
package/x/parts/chronstrata.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Substrata } from "./substrata.js";
|
|
2
2
|
import { Chronicle, Mutator, Options, Selector, Stratum, Substate } from "./types.js";
|
|
3
|
-
export declare class Chronstrata<
|
|
3
|
+
export declare class Chronstrata<S extends Substate, ParentState extends Substate = any> implements Stratum<S> {
|
|
4
4
|
#private;
|
|
5
5
|
limit: number;
|
|
6
6
|
parent: Stratum<ParentState>;
|
|
7
|
-
selector: Selector<
|
|
7
|
+
selector: Selector<Chronicle<S>, ParentState>;
|
|
8
8
|
options: Options;
|
|
9
|
-
constructor(limit: number, parent: Stratum<ParentState>, selector: Selector<
|
|
9
|
+
constructor(limit: number, parent: Stratum<ParentState>, selector: Selector<Chronicle<S>, ParentState>, options: Options);
|
|
10
10
|
get state(): S;
|
|
11
11
|
get undoable(): number;
|
|
12
12
|
get redoable(): number;
|
|
@@ -19,5 +19,5 @@ export declare class Chronstrata<ParentState extends Substate, S extends Substat
|
|
|
19
19
|
redo(n?: number): Promise<void>;
|
|
20
20
|
/** wipe past and future snapshots */
|
|
21
21
|
wipe(): Promise<void>;
|
|
22
|
-
substrata<Sub extends Substate>(selector: Selector<
|
|
22
|
+
substrata<Sub extends Substate>(selector: Selector<Sub, S>): Substrata<Sub, S>;
|
|
23
23
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const localPersistence = (key, storage = window.localStorage) => ({
|
|
2
|
+
store: {
|
|
3
|
+
async get() {
|
|
4
|
+
const json = storage.getItem(key);
|
|
5
|
+
return json
|
|
6
|
+
? JSON.parse(json)
|
|
7
|
+
: undefined;
|
|
8
|
+
},
|
|
9
|
+
async set(state) {
|
|
10
|
+
const json = JSON.stringify(state);
|
|
11
|
+
storage.setItem(key, json);
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
onChange: (fn) => {
|
|
15
|
+
const listener = (event) => {
|
|
16
|
+
if (event.storageArea === storage && event.key === key)
|
|
17
|
+
fn();
|
|
18
|
+
};
|
|
19
|
+
window.addEventListener("storage", listener);
|
|
20
|
+
return () => window.removeEventListener("storage", listener);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=persistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../s/parts/persistence.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,GAAW,EACX,UAAmB,MAAM,CAAC,YAAY,EACrB,EAAE,CAAC,CAAC;IAEtB,KAAK,EAAE;QACN,KAAK,CAAC,GAAG;YACR,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YACjC,OAAO,IAAI;gBACV,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;gBAClB,CAAC,CAAC,SAAS,CAAA;QACb,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,KAAK;YACd,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;YAClC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAC3B,CAAC;KACD;IAED,QAAQ,EAAE,CAAC,EAAc,EAAE,EAAE;QAC5B,MAAM,QAAQ,GAAG,CAAC,KAAmB,EAAE,EAAE;YACxC,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG;gBACrD,EAAE,EAAE,CAAA;QACN,CAAC,CAAA;QACD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAC5C,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;IAC7D,CAAC;CACD,CAAC,CAAA"}
|
package/x/parts/strata.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { strataSetup } from "./utils/setup.js";
|
|
1
2
|
import { Substrata } from "./substrata.js";
|
|
2
3
|
import { Chronstrata } from "./chronstrata.js";
|
|
3
4
|
import { Chronicle, Mutator, Options, Selector, State, Stratum, Substate } from "./types.js";
|
|
4
5
|
export declare class Strata<S extends State> implements Stratum<S> {
|
|
5
6
|
#private;
|
|
7
|
+
static setup: typeof strataSetup;
|
|
6
8
|
static chronicle: <S_1 extends Substate>(state: S_1) => Chronicle<S_1>;
|
|
7
9
|
options: Options;
|
|
8
10
|
watch: import("@e280/stz").Sub<[state: S]>;
|
|
9
11
|
constructor(state: S, options?: Partial<Options>);
|
|
10
12
|
get state(): S;
|
|
11
13
|
mutate(mutator: Mutator<S>): Promise<S>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
overwrite(state: S): Promise<void>;
|
|
15
|
+
substrata<Sub extends Substate>(selector: Selector<Sub, S>): Substrata<Sub, S>;
|
|
16
|
+
chronstrata<Sub extends Substate>(limit: number, selector: Selector<Chronicle<Sub>, S>): Chronstrata<Sub, S>;
|
|
14
17
|
}
|
package/x/parts/strata.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { debounce, deep, sub } from "@e280/stz";
|
|
2
|
+
import { strataSetup } from "./utils/setup.js";
|
|
2
3
|
import { Substrata } from "./substrata.js";
|
|
3
4
|
import { Chronstrata } from "./chronstrata.js";
|
|
4
5
|
import { processOptions } from "./utils/process-options.js";
|
|
5
6
|
export class Strata {
|
|
7
|
+
static setup = strataSetup;
|
|
6
8
|
static chronicle = (state) => ({
|
|
7
9
|
present: state,
|
|
8
10
|
past: [],
|
|
@@ -13,24 +15,11 @@ export class Strata {
|
|
|
13
15
|
#mutable;
|
|
14
16
|
#immutable;
|
|
15
17
|
#mutationLock = 0;
|
|
16
|
-
#dispatchMutation = debounce(0, async (state) => {
|
|
17
|
-
this.#mutationLock++;
|
|
18
|
-
try {
|
|
19
|
-
await this.watch.pub(state);
|
|
20
|
-
}
|
|
21
|
-
finally {
|
|
22
|
-
this.#mutationLock--;
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
18
|
constructor(state, options = {}) {
|
|
26
19
|
this.options = processOptions(options);
|
|
27
20
|
this.#mutable = state;
|
|
28
21
|
this.#immutable = deep.freeze(this.options.clone(state));
|
|
29
22
|
}
|
|
30
|
-
#updateState(state) {
|
|
31
|
-
this.#mutable = state;
|
|
32
|
-
this.#immutable = deep.freeze(this.options.clone(state));
|
|
33
|
-
}
|
|
34
23
|
get state() {
|
|
35
24
|
return this.#immutable;
|
|
36
25
|
}
|
|
@@ -47,18 +36,29 @@ export class Strata {
|
|
|
47
36
|
}
|
|
48
37
|
const newState = this.#mutable;
|
|
49
38
|
const isChanged = !deep.equal(newState, oldState);
|
|
50
|
-
if (isChanged)
|
|
51
|
-
this
|
|
52
|
-
const immutable = this.state;
|
|
53
|
-
await this.#dispatchMutation(immutable);
|
|
54
|
-
}
|
|
39
|
+
if (isChanged)
|
|
40
|
+
await this.overwrite(newState);
|
|
55
41
|
return this.#immutable;
|
|
56
42
|
}
|
|
43
|
+
async overwrite(state) {
|
|
44
|
+
this.#mutable = state;
|
|
45
|
+
this.#immutable = deep.freeze(this.options.clone(state));
|
|
46
|
+
await this.#dispatchMutation();
|
|
47
|
+
}
|
|
57
48
|
substrata(selector) {
|
|
58
49
|
return new Substrata(this, selector, this.options);
|
|
59
50
|
}
|
|
60
51
|
chronstrata(limit, selector) {
|
|
61
52
|
return new Chronstrata(limit, this, selector, this.options);
|
|
62
53
|
}
|
|
54
|
+
#dispatchMutation = debounce(0, async () => {
|
|
55
|
+
this.#mutationLock++;
|
|
56
|
+
try {
|
|
57
|
+
await this.watch.pub(this.#immutable);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
this.#mutationLock--;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
63
|
}
|
|
64
64
|
//# sourceMappingURL=strata.js.map
|
package/x/parts/strata.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strata.js","sourceRoot":"","sources":["../../s/parts/strata.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAC,MAAM,WAAW,CAAA;AAE7C,OAAO,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAA;AAGzD,MAAM,OAAO,MAAM;IAClB,MAAM,CAAC,SAAS,GAAG,CAAqB,KAAQ,EAAgB,EAAE,CAAC,CAAC;QACnE,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,EAAE;QACR,MAAM,EAAE,EAAE;KACV,CAAC,CAAA;IAEF,OAAO,CAAS;IAChB,KAAK,GAAG,GAAG,EAAc,CAAA;IAEzB,QAAQ,CAAG;IACX,UAAU,CAAG;IACb,aAAa,GAAG,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"strata.js","sourceRoot":"","sources":["../../s/parts/strata.ts"],"names":[],"mappings":"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"}
|
package/x/parts/substrata.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Chronstrata } from "./chronstrata.js";
|
|
2
2
|
import { Chronicle, Mutator, Options, Selector, Stratum, Substate } from "./types.js";
|
|
3
|
-
export declare class Substrata<
|
|
3
|
+
export declare class Substrata<S extends Substate, ParentState extends Substate = any> implements Stratum<S> {
|
|
4
4
|
#private;
|
|
5
5
|
private parent;
|
|
6
6
|
private selector;
|
|
7
7
|
private options;
|
|
8
8
|
dispose: () => void;
|
|
9
9
|
watch: import("@e280/stz").Sub<[state: S]>;
|
|
10
|
-
constructor(parent: Stratum<ParentState>, selector: Selector<
|
|
10
|
+
constructor(parent: Stratum<ParentState>, selector: Selector<S, ParentState>, options: Options);
|
|
11
11
|
get state(): S;
|
|
12
12
|
mutate(mutator: Mutator<S>): Promise<S>;
|
|
13
|
-
substrata<Sub extends Substate>(selector: Selector<
|
|
14
|
-
chronstrata<Sub extends Substate>(limit: number, selector: Selector<
|
|
13
|
+
substrata<Sub extends Substate>(selector: Selector<Sub, S>): Substrata<Sub, S>;
|
|
14
|
+
chronstrata<Sub extends Substate>(limit: number, selector: Selector<Chronicle<Sub>, S>): Chronstrata<Sub, S>;
|
|
15
15
|
}
|
package/x/parts/types.d.ts
CHANGED
|
@@ -2,18 +2,36 @@ import { Substrata } from "./substrata.js";
|
|
|
2
2
|
export type Options = {
|
|
3
3
|
clone: <X>(x: X) => X;
|
|
4
4
|
};
|
|
5
|
-
export type Selector<
|
|
5
|
+
export type Selector<Sub, S> = (state: S) => Sub;
|
|
6
6
|
export type Mutator<S> = (state: S) => void;
|
|
7
7
|
export type State = {};
|
|
8
8
|
export type Substate = {} | null | undefined;
|
|
9
|
+
export type Versioned<S extends State> = {
|
|
10
|
+
state: S;
|
|
11
|
+
version: number;
|
|
12
|
+
};
|
|
9
13
|
export type Stratum<S extends Substate> = {
|
|
10
14
|
readonly state: S;
|
|
11
15
|
watch(fn: (s: S) => void): () => void;
|
|
12
16
|
mutate(mutator: Mutator<S>): Promise<S>;
|
|
13
|
-
substrata<Sub extends Substate>(selector: Selector<
|
|
17
|
+
substrata<Sub extends Substate>(selector: Selector<Sub, S>): Substrata<Sub, S>;
|
|
18
|
+
};
|
|
19
|
+
export type SetupOptions<S extends State> = {
|
|
20
|
+
version: number;
|
|
21
|
+
initialState: S;
|
|
22
|
+
saveDebounceTime?: number;
|
|
23
|
+
persistence?: Persistence<Versioned<S>>;
|
|
14
24
|
};
|
|
15
25
|
export type Chronicle<S extends Substate> = {
|
|
16
26
|
past: S[];
|
|
17
27
|
present: S;
|
|
18
28
|
future: S[];
|
|
19
29
|
};
|
|
30
|
+
export type EzStore<X> = {
|
|
31
|
+
get(): Promise<X | undefined>;
|
|
32
|
+
set(state: X | undefined): Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
export type Persistence<X> = {
|
|
35
|
+
store: EzStore<X>;
|
|
36
|
+
onChange: (fn: () => void) => (() => void);
|
|
37
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-options.js","sourceRoot":"","sources":["../../../s/parts/utils/process-options.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAyB,EAAW,EAAE,CAAC,CAAC;IACtE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,eAAe;
|
|
1
|
+
{"version":3,"file":"process-options.js","sourceRoot":"","sources":["../../../s/parts/utils/process-options.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAyB,EAAW,EAAE,CAAC,CAAC;IACtE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,CAAI,CAAI,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;CACzD,CAAC,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Strata } from "../strata.js";
|
|
2
|
+
import { State, SetupOptions } from "../types.js";
|
|
3
|
+
export declare function strataSetup<S extends State>(options: SetupOptions<S>): Promise<{
|
|
4
|
+
strata: Strata<S>;
|
|
5
|
+
load: () => Promise<void>;
|
|
6
|
+
save: import("@e280/stz").DebounceReturn<() => Promise<void>>;
|
|
7
|
+
dispose: () => void;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { debounce } from "@e280/stz";
|
|
2
|
+
import { Strata } from "../strata.js";
|
|
3
|
+
import { localPersistence } from "../persistence.js";
|
|
4
|
+
export async function strataSetup(options) {
|
|
5
|
+
const { version, initialState, saveDebounceTime = 500, persistence = localPersistence("strata"), } = options;
|
|
6
|
+
const strata = new Strata(initialState);
|
|
7
|
+
async function load() {
|
|
8
|
+
const pickle = await persistence.store.get();
|
|
9
|
+
if (pickle && pickle.version === version)
|
|
10
|
+
await strata.overwrite(pickle.state);
|
|
11
|
+
}
|
|
12
|
+
const save = debounce(saveDebounceTime, async () => persistence.store.set({
|
|
13
|
+
version,
|
|
14
|
+
state: strata.state,
|
|
15
|
+
}));
|
|
16
|
+
// persistence: initial load from store
|
|
17
|
+
await load();
|
|
18
|
+
// persistence: save to store
|
|
19
|
+
strata.watch(save);
|
|
20
|
+
// cross-tab sync
|
|
21
|
+
const dispose = persistence.onChange(load);
|
|
22
|
+
return { strata, load, save, dispose };
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../s/parts/utils/setup.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,WAAW,CAAA;AAElC,OAAO,EAAC,MAAM,EAAC,MAAM,cAAc,CAAA;AAEnC,OAAO,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAA;AAElD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAkB,OAAwB;IAC1E,MAAM,EACL,OAAO,EACP,YAAY,EACZ,gBAAgB,GAAG,GAAG,EACtB,WAAW,GAAG,gBAAgB,CAAC,QAAQ,CAAC,GACxC,GAAG,OAAO,CAAA;IAEX,MAAM,MAAM,GAAG,IAAI,MAAM,CAAI,YAAY,CAAC,CAAA;IAE1C,KAAK,UAAU,IAAI;QAClB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,CAAA;QAC5C,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO;YACvC,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACtC,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,gBAAgB,EAAE,KAAK,IAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC;QACxE,OAAO;QACP,KAAK,EAAE,MAAM,CAAC,KAAK;KACnB,CAAC,CAAC,CAAA;IAEH,uCAAuC;IACvC,MAAM,IAAI,EAAE,CAAA;IAEZ,6BAA6B;IAC7B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAElB,iBAAiB;IACjB,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAE1C,OAAO,EAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAC,CAAA;AACrC,CAAC"}
|