@e280/strata 0.0.0-7 → 0.0.0-9
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 +27 -24
- package/package.json +9 -10
- package/s/signals/index.ts +2 -1
- package/s/signals/parts/derive.ts +29 -0
- package/s/signals/parts/effect.ts +3 -3
- package/s/signals/parts/lazy.ts +27 -0
- package/s/signals/parts/signal.ts +21 -43
- package/s/signals/parts/types.ts +11 -0
- package/s/signals/parts/units.ts +150 -0
- package/s/signals/signals.test.ts +103 -8
- package/s/tests.test.ts +1 -1
- package/s/tree/index.ts +1 -1
- package/s/tree/parts/branch.ts +17 -40
- package/s/tree/parts/chronobranch.ts +13 -13
- package/s/tree/parts/trunk.ts +27 -29
- package/s/tree/parts/types.ts +18 -6
- package/s/tree/parts/utils/setup.ts +4 -4
- package/s/tree/tree.test.ts +77 -68
- package/x/signals/index.d.ts +2 -1
- package/x/signals/index.js +2 -1
- package/x/signals/index.js.map +1 -1
- 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 +2 -2
- package/x/signals/parts/effect.js +2 -2
- package/x/signals/parts/effect.js.map +1 -1
- 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 +11 -8
- package/x/signals/parts/signal.js +8 -37
- package/x/signals/parts/signal.js.map +1 -1
- package/x/signals/parts/types.d.ts +7 -0
- package/x/signals/parts/types.js +2 -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 +131 -0
- package/x/signals/parts/units.js.map +1 -0
- package/x/signals/signals.test.d.ts +8 -3
- package/x/signals/signals.test.js +87 -8
- package/x/signals/signals.test.js.map +1 -1
- package/x/tests.test.js +1 -1
- package/x/tests.test.js.map +1 -1
- package/x/tree/index.d.ts +1 -1
- package/x/tree/index.js +1 -1
- package/x/tree/index.js.map +1 -1
- package/x/tree/parts/branch.d.ts +4 -7
- package/x/tree/parts/branch.js +11 -30
- package/x/tree/parts/branch.js.map +1 -1
- package/x/tree/parts/chronobranch.d.ts +4 -4
- package/x/tree/parts/chronobranch.js +12 -12
- package/x/tree/parts/chronobranch.js.map +1 -1
- package/x/tree/parts/trunk.d.ts +5 -5
- package/x/tree/parts/trunk.js +18 -29
- package/x/tree/parts/trunk.js.map +1 -1
- package/x/tree/parts/types.d.ts +12 -6
- package/x/tree/parts/utils/setup.d.ts +2 -2
- package/x/tree/parts/utils/setup.js +1 -1
- package/x/tree/parts/utils/setup.js.map +1 -1
- package/x/tree/tree.test.d.ts +7 -7
- package/x/tree/tree.test.js +76 -68
- package/x/tree/tree.test.js.map +1 -1
- package/s/signals/parts/computed.ts +0 -53
- package/x/signals/parts/computed.d.ts +0 -14
- package/x/signals/parts/computed.js +0 -41
- package/x/signals/parts/computed.js.map +0 -1
package/s/tree/parts/branch.ts
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {Chronicle, Mutator, Options, Selector, Tree, Branchstate} from "./types.js"
|
|
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"
|
|
7
6
|
|
|
8
7
|
export class Branch<S extends Branchstate, ParentState extends Branchstate = any> implements Tree<S> {
|
|
9
|
-
|
|
10
|
-
watch = sub<[state: S]>()
|
|
11
|
-
|
|
12
|
-
#immutable: S
|
|
13
|
-
#dispatchMutation = debounce(0, async(state: S) => {
|
|
14
|
-
await this.watch.pub(state)
|
|
15
|
-
await tracker.change(this)
|
|
16
|
-
})
|
|
8
|
+
#immutable: DerivedSignal<Immutable<S>>
|
|
17
9
|
|
|
18
10
|
constructor(
|
|
19
11
|
private parent: Tree<ParentState>,
|
|
@@ -21,44 +13,29 @@ export class Branch<S extends Branchstate, ParentState extends Branchstate = any
|
|
|
21
13
|
private options: Options,
|
|
22
14
|
) {
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const oldState = this.#immutable
|
|
29
|
-
const newState = this.selector(parentState)
|
|
30
|
-
const isChanged = !deep.equal(newState, oldState)
|
|
31
|
-
if (isChanged) {
|
|
32
|
-
this.#updateState(newState)
|
|
33
|
-
const immutable = this.state
|
|
34
|
-
await this.#dispatchMutation(immutable)
|
|
35
|
-
}
|
|
36
|
-
})
|
|
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})
|
|
37
20
|
}
|
|
38
21
|
|
|
39
|
-
|
|
40
|
-
this.#immutable
|
|
22
|
+
get state() {
|
|
23
|
+
return this.#immutable.get()
|
|
41
24
|
}
|
|
42
25
|
|
|
43
|
-
get
|
|
44
|
-
|
|
45
|
-
return this.#immutable
|
|
26
|
+
get on() {
|
|
27
|
+
return this.#immutable.on
|
|
46
28
|
}
|
|
47
29
|
|
|
48
30
|
async mutate(mutator: Mutator<S>) {
|
|
49
|
-
await this.parent.mutate(parentState =>
|
|
50
|
-
|
|
31
|
+
await this.parent.mutate(parentState =>
|
|
32
|
+
mutator(this.selector(parentState))
|
|
33
|
+
)
|
|
34
|
+
return this.#immutable.get()
|
|
51
35
|
}
|
|
52
36
|
|
|
53
37
|
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
54
38
|
return new Branch(this, selector, this.options)
|
|
55
39
|
}
|
|
56
|
-
|
|
57
|
-
chronobranch<Sub extends Branchstate>(
|
|
58
|
-
limit: number,
|
|
59
|
-
selector: Selector<Chronicle<Sub>, S>,
|
|
60
|
-
) {
|
|
61
|
-
return new Chronobranch(limit, this, selector, this.options)
|
|
62
|
-
}
|
|
63
40
|
}
|
|
64
41
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
import {Branch} from "./branch.js"
|
|
3
|
-
import {Chronicle, Mutator, Options, Selector, Tree
|
|
3
|
+
import {Branchstate, Chronicle, Immutable, Mutator, Options, Selector, Tree} from "./types.js"
|
|
4
4
|
|
|
5
5
|
export class Chronobranch<S extends Branchstate, ParentState extends Branchstate = any> implements Tree<S> {
|
|
6
|
-
#
|
|
6
|
+
#branch: Branch<Chronicle<S>, ParentState>
|
|
7
7
|
|
|
8
8
|
constructor(
|
|
9
9
|
public limit: number,
|
|
@@ -11,30 +11,30 @@ export class Chronobranch<S extends Branchstate, ParentState extends Branchstate
|
|
|
11
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 Chronobranch<S extends Branchstate, ParentState extends Branchstate
|
|
|
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 Chronobranch<S extends Branchstate, ParentState extends Branchstate
|
|
|
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,7 +71,7 @@ export class Chronobranch<S extends Branchstate, ParentState extends Branchstate
|
|
|
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
|
})
|
package/s/tree/parts/trunk.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {deep} from "@e280/stz"
|
|
4
3
|
import {Branch} from "./branch.js"
|
|
5
4
|
import {trunkSetup} from "./utils/setup.js"
|
|
6
5
|
import {Chronobranch} from "./chronobranch.js"
|
|
7
|
-
import {tracker} from "../../tracker/tracker.js"
|
|
8
6
|
import {processOptions} from "./utils/process-options.js"
|
|
9
|
-
import {
|
|
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
10
|
|
|
11
|
-
export class Trunk<S extends
|
|
11
|
+
export class Trunk<S extends Trunkstate> implements Tree<S> {
|
|
12
12
|
static setup = trunkSetup
|
|
13
13
|
static chronicle = <S extends Branchstate>(state: S): Chronicle<S> => ({
|
|
14
14
|
present: state,
|
|
@@ -17,40 +17,45 @@ export class Trunk<S extends Treestate> implements Tree<S> {
|
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
options: Options
|
|
20
|
-
watch = sub<[state: S]>()
|
|
21
20
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
21
|
+
#immutable: DerivedSignal<Immutable<S>>
|
|
22
|
+
#mutable: Signal<S>
|
|
24
23
|
#mutationLock = 0
|
|
25
24
|
|
|
26
25
|
constructor(state: S, options: Partial<Options> = {}) {
|
|
27
26
|
this.options = processOptions(options)
|
|
28
|
-
this.#mutable = state
|
|
29
|
-
this.#immutable =
|
|
27
|
+
this.#mutable = signal(state)
|
|
28
|
+
this.#immutable = signal.derive(() =>
|
|
29
|
+
deep.freeze(this.options.clone(this.#mutable.get())) as Immutable<S>
|
|
30
|
+
)
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
get state() {
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
return this.#immutable.get()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get on() {
|
|
38
|
+
return this.#immutable.on
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
async mutate(mutator: Mutator<S>) {
|
|
38
|
-
const oldState = this.options.clone(this.#mutable)
|
|
42
|
+
const oldState = this.options.clone(this.#mutable.get())
|
|
39
43
|
if (this.#mutationLock > 0)
|
|
40
44
|
throw new Error("nested mutations are forbidden")
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
}
|
|
43
53
|
finally { this.#mutationLock-- }
|
|
44
|
-
|
|
45
|
-
const isChanged = !deep.equal(newState, oldState)
|
|
46
|
-
if (isChanged) await this.overwrite(newState)
|
|
47
|
-
return this.#immutable
|
|
54
|
+
return this.#immutable.get()
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
async overwrite(state: S) {
|
|
51
|
-
this.#mutable
|
|
52
|
-
this.#immutable = deep.freeze(this.options.clone(state))
|
|
53
|
-
await this.#dispatchMutation()
|
|
58
|
+
await this.#mutable.publish(state)
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
@@ -63,12 +68,5 @@ export class Trunk<S extends Treestate> implements Tree<S> {
|
|
|
63
68
|
) {
|
|
64
69
|
return new Chronobranch(limit, this, selector, this.options)
|
|
65
70
|
}
|
|
66
|
-
|
|
67
|
-
#dispatchMutation = debounce(0, async() => {
|
|
68
|
-
this.#mutationLock++
|
|
69
|
-
try { await this.watch.pub(this.#immutable) }
|
|
70
|
-
finally { this.#mutationLock-- }
|
|
71
|
-
await tracker.change(this)
|
|
72
|
-
})
|
|
73
71
|
}
|
|
74
72
|
|
package/s/tree/parts/types.ts
CHANGED
|
@@ -8,22 +8,34 @@ export type Options = {
|
|
|
8
8
|
export type Selector<Sub, S> = (state: S) => Sub
|
|
9
9
|
export type Mutator<S> = (state: S) => void
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type Trunkstate = {}
|
|
12
12
|
export type Branchstate = {} | null | undefined
|
|
13
13
|
|
|
14
|
-
export type Versioned<S extends
|
|
14
|
+
export type Versioned<S extends Trunkstate> = {
|
|
15
15
|
state: S
|
|
16
16
|
version: number
|
|
17
17
|
}
|
|
18
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
|
+
|
|
19
31
|
export type Tree<S extends Branchstate> = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
mutate(mutator: Mutator<S>): Promise<S
|
|
32
|
+
get state(): Immutable<S>
|
|
33
|
+
on(fn: (state: Immutable<S>) => void): () => void
|
|
34
|
+
mutate(mutator: Mutator<S>): Promise<Immutable<S>>
|
|
23
35
|
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S>
|
|
24
36
|
}
|
|
25
37
|
|
|
26
|
-
export type SetupOptions<S extends
|
|
38
|
+
export type SetupOptions<S extends Trunkstate> = {
|
|
27
39
|
version: number
|
|
28
40
|
initialState: S
|
|
29
41
|
saveDebounceTime?: number
|
|
@@ -3,9 +3,9 @@ import {debounce} from "@e280/stz"
|
|
|
3
3
|
|
|
4
4
|
import {Trunk} from "../trunk.js"
|
|
5
5
|
import {localPersistence} from "../persistence.js"
|
|
6
|
-
import {
|
|
6
|
+
import {SetupOptions, Trunkstate} from "../types.js"
|
|
7
7
|
|
|
8
|
-
export async function trunkSetup<S extends
|
|
8
|
+
export async function trunkSetup<S extends Trunkstate>(options: SetupOptions<S>) {
|
|
9
9
|
const {
|
|
10
10
|
version,
|
|
11
11
|
initialState,
|
|
@@ -23,14 +23,14 @@ export async function trunkSetup<S extends Treestate>(options: SetupOptions<S>)
|
|
|
23
23
|
|
|
24
24
|
const save = debounce(saveDebounceTime, async() => persistence.store.set({
|
|
25
25
|
version,
|
|
26
|
-
state: trunk.state,
|
|
26
|
+
state: trunk.state as any,
|
|
27
27
|
}))
|
|
28
28
|
|
|
29
29
|
// persistence: initial load from store
|
|
30
30
|
await load()
|
|
31
31
|
|
|
32
32
|
// persistence: save to store
|
|
33
|
-
trunk.
|
|
33
|
+
trunk.on(save)
|
|
34
34
|
|
|
35
35
|
// cross-tab sync
|
|
36
36
|
const dispose = persistence.onChange(load)
|
package/s/tree/tree.test.ts
CHANGED
|
@@ -1,152 +1,161 @@
|
|
|
1
1
|
|
|
2
|
+
import {nap} from "@e280/stz"
|
|
2
3
|
import {Science, expect} from "@e280/science"
|
|
4
|
+
|
|
3
5
|
import {Trunk} from "./parts/trunk.js"
|
|
6
|
+
import {effect} from "../signals/parts/effect.js"
|
|
4
7
|
|
|
5
8
|
export default Science.suite({
|
|
6
|
-
"
|
|
9
|
+
"trunk": Science.suite({
|
|
7
10
|
"get state": Science.test(async() => {
|
|
8
|
-
const
|
|
9
|
-
expect(
|
|
11
|
+
const trunk = new Trunk({count: 0})
|
|
12
|
+
expect(trunk.state.count).is(0)
|
|
10
13
|
}),
|
|
11
14
|
|
|
12
15
|
"state is immutable": Science.test(async() => {
|
|
13
|
-
const
|
|
14
|
-
expect(() =>
|
|
16
|
+
const trunk = new Trunk({count: 0})
|
|
17
|
+
expect(() => (trunk.state as any).count++).throws()
|
|
15
18
|
}),
|
|
16
19
|
|
|
17
20
|
"run a proper mutation": Science.test(async() => {
|
|
18
|
-
const
|
|
19
|
-
expect(
|
|
20
|
-
await
|
|
21
|
-
expect(
|
|
22
|
-
await
|
|
23
|
-
expect(
|
|
21
|
+
const trunk = new Trunk({count: 0})
|
|
22
|
+
expect(trunk.state.count).is(0)
|
|
23
|
+
await trunk.mutate(state => state.count++)
|
|
24
|
+
expect(trunk.state.count).is(1)
|
|
25
|
+
await trunk.mutate(state => state.count++)
|
|
26
|
+
expect(trunk.state.count).is(2)
|
|
24
27
|
}),
|
|
25
28
|
|
|
26
29
|
"forbidden mutation nesting": Science.test(async() => {
|
|
27
|
-
const
|
|
30
|
+
const trunk = new Trunk({count: 0})
|
|
28
31
|
await expect(async() => {
|
|
29
32
|
let promise!: Promise<any>
|
|
30
|
-
await
|
|
31
|
-
promise =
|
|
33
|
+
await trunk.mutate(() => {
|
|
34
|
+
promise = trunk.mutate(() => {})
|
|
32
35
|
})
|
|
33
36
|
await promise
|
|
34
37
|
}).throwsAsync()
|
|
35
38
|
}),
|
|
36
39
|
|
|
37
40
|
"state after mutation is frozen": Science.test(async () => {
|
|
38
|
-
const
|
|
39
|
-
await
|
|
40
|
-
expect(() =>
|
|
41
|
+
const trunk = new Trunk({x: 1})
|
|
42
|
+
await trunk.mutate(s => { s.x = 2 })
|
|
43
|
+
expect(() => (trunk.state as any).x = 3).throws()
|
|
41
44
|
}),
|
|
42
45
|
|
|
43
|
-
"
|
|
44
|
-
const
|
|
46
|
+
"effect reacts to trunk mutation": Science.test(async() => {
|
|
47
|
+
const trunk = new Trunk({count: 0})
|
|
48
|
+
await nap(10)
|
|
45
49
|
let mutationCount = 0
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
effect(() => {
|
|
51
|
+
void trunk.state.count
|
|
52
|
+
mutationCount++
|
|
53
|
+
})
|
|
48
54
|
expect(mutationCount).is(1)
|
|
55
|
+
await trunk.mutate(state => state.count++)
|
|
56
|
+
expect(mutationCount).is(2)
|
|
49
57
|
}),
|
|
50
58
|
|
|
51
|
-
"
|
|
52
|
-
const
|
|
59
|
+
"signal.on is debounced": Science.test(async() => {
|
|
60
|
+
const trunk = new Trunk({count: 0})
|
|
53
61
|
let mutationCount = 0
|
|
54
|
-
|
|
55
|
-
const promise =
|
|
62
|
+
trunk.on.sub(() => {mutationCount++})
|
|
63
|
+
const promise = trunk.mutate(state => state.count++)
|
|
56
64
|
expect(mutationCount).is(0)
|
|
57
65
|
await promise
|
|
58
66
|
expect(mutationCount).is(1)
|
|
59
67
|
}),
|
|
60
68
|
|
|
61
|
-
"
|
|
62
|
-
const
|
|
69
|
+
"listeners are fired when array item is pushed": Science.test(async() => {
|
|
70
|
+
const trunk = new Trunk({items: ["hello", "world"]})
|
|
63
71
|
let mutationCount = 0
|
|
64
|
-
|
|
65
|
-
await
|
|
72
|
+
trunk.on.sub(() => {mutationCount++})
|
|
73
|
+
await trunk.mutate(state => state.items.push("lol"))
|
|
66
74
|
expect(mutationCount).is(1)
|
|
67
|
-
expect(
|
|
75
|
+
expect(trunk.state.items.length).is(3)
|
|
68
76
|
}),
|
|
69
77
|
|
|
70
78
|
"prevent mutation loops": Science.test(async() => {
|
|
71
|
-
const
|
|
79
|
+
const trunk = new Trunk({count: 0})
|
|
72
80
|
let mutationCount = 0
|
|
73
|
-
|
|
81
|
+
trunk.on.sub(async() => {
|
|
74
82
|
mutationCount++
|
|
75
83
|
if (mutationCount > 100)
|
|
76
84
|
return
|
|
77
|
-
await
|
|
85
|
+
await trunk.mutate(s => s.count++)
|
|
78
86
|
})
|
|
79
87
|
await expect(async() => {
|
|
80
|
-
await
|
|
88
|
+
await trunk.mutate(state => state.count++)
|
|
81
89
|
}).throwsAsync()
|
|
82
90
|
expect(mutationCount).is(1)
|
|
83
91
|
}),
|
|
84
92
|
}),
|
|
85
93
|
|
|
86
|
-
"
|
|
94
|
+
"branch": Science.suite({
|
|
87
95
|
"get state": Science.test(async() => {
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
expect(
|
|
96
|
+
const trunk = new Trunk({count: 0, sub: {rofls: 0}})
|
|
97
|
+
const branch = trunk.branch(s => s.sub)
|
|
98
|
+
expect(branch.state.rofls).is(0)
|
|
91
99
|
}),
|
|
92
100
|
|
|
93
101
|
"nullable selector": Science.test(async () => {
|
|
94
|
-
const
|
|
102
|
+
const trunk = new Trunk({
|
|
95
103
|
a: {b: 0} as (null | {b: number}),
|
|
96
104
|
})
|
|
97
|
-
const a =
|
|
98
|
-
expect(
|
|
105
|
+
const a = trunk.branch(s => s.a)
|
|
106
|
+
expect(trunk.state.a?.b).is(0)
|
|
99
107
|
expect(a.state?.b).is(0)
|
|
100
108
|
await a.mutate(a => { a!.b = 1 })
|
|
101
|
-
expect(
|
|
109
|
+
expect(trunk.state.a?.b).is(1)
|
|
102
110
|
expect(a.state?.b).is(1)
|
|
103
|
-
await
|
|
104
|
-
expect(
|
|
111
|
+
await trunk.mutate(s => s.a = null)
|
|
112
|
+
expect(trunk.state.a?.b).is(undefined)
|
|
105
113
|
expect(a.state?.b).is(undefined)
|
|
106
114
|
}),
|
|
107
115
|
|
|
108
116
|
"composition": Science.test(async () => {
|
|
109
|
-
const
|
|
110
|
-
const a =
|
|
117
|
+
const trunk = new Trunk({a: {b: {c: 0}}})
|
|
118
|
+
const a = trunk.branch(s => s.a)
|
|
111
119
|
const b = a.branch(s => s.b)
|
|
112
|
-
expect(
|
|
120
|
+
expect(trunk.state.a.b.c).is(0)
|
|
113
121
|
expect(b.state.c).is(0)
|
|
114
122
|
}),
|
|
115
123
|
|
|
116
124
|
"deep mutations": Science.test(async () => {
|
|
117
|
-
const
|
|
118
|
-
const a =
|
|
125
|
+
const trunk = new Trunk({a: {b: {c: 0}}})
|
|
126
|
+
const a = trunk.branch(s => s.a)
|
|
119
127
|
const b = a.branch(s => s.b)
|
|
120
128
|
await b.mutate(b => { b.c = 101 })
|
|
121
|
-
expect(
|
|
129
|
+
expect(trunk.state.a.b.c).is(101)
|
|
122
130
|
expect(a.state.b.c).is(101)
|
|
123
131
|
expect(b.state.c).is(101)
|
|
124
132
|
await a.mutate(a => { a.b = {c: 102} })
|
|
125
|
-
expect(
|
|
133
|
+
expect(trunk.state.a.b.c).is(102)
|
|
126
134
|
expect(a.state.b.c).is(102)
|
|
127
135
|
expect(b.state.c).is(102)
|
|
128
|
-
await
|
|
129
|
-
expect(
|
|
136
|
+
await trunk.mutate(s => { s.a = {b: {c: 103}} })
|
|
137
|
+
expect(trunk.state.a.b.c).is(103)
|
|
130
138
|
expect(a.state.b.c).is(103)
|
|
131
139
|
expect(b.state.c).is(103)
|
|
132
140
|
}),
|
|
133
141
|
|
|
134
|
-
"
|
|
135
|
-
const
|
|
136
|
-
const a =
|
|
137
|
-
const b =
|
|
142
|
+
"signal.on ignores outside mutations": Science.test(async() => {
|
|
143
|
+
const trunk = new Trunk({a: {x: 0}, b: {x: 0}})
|
|
144
|
+
const a = trunk.branch(s => s.a)
|
|
145
|
+
const b = trunk.branch(s => s.b)
|
|
138
146
|
let counted = 0
|
|
139
|
-
b.
|
|
147
|
+
b.on.sub(() => {counted++})
|
|
148
|
+
expect(counted).is(0)
|
|
140
149
|
await a.mutate(a => a.x = 1)
|
|
141
150
|
expect(counted).is(0)
|
|
142
151
|
}),
|
|
143
152
|
|
|
144
153
|
"forbid submutation in mutation": Science.test(async() => {
|
|
145
|
-
const
|
|
146
|
-
const a =
|
|
154
|
+
const trunk = new Trunk({a: {b: 0}})
|
|
155
|
+
const a = trunk.branch(s => s.a)
|
|
147
156
|
await expect(async() => {
|
|
148
157
|
let promise!: Promise<any>
|
|
149
|
-
await
|
|
158
|
+
await trunk.mutate(() => {
|
|
150
159
|
promise = a.mutate(() => {})
|
|
151
160
|
})
|
|
152
161
|
await promise
|
|
@@ -154,25 +163,25 @@ export default Science.suite({
|
|
|
154
163
|
}),
|
|
155
164
|
|
|
156
165
|
"forbid mutation in submutation": Science.test(async() => {
|
|
157
|
-
const
|
|
158
|
-
const a =
|
|
166
|
+
const trunk = new Trunk({a: {b: 0}})
|
|
167
|
+
const a = trunk.branch(s => s.a)
|
|
159
168
|
await expect(async() => {
|
|
160
169
|
let promise!: Promise<any>
|
|
161
170
|
await a.mutate(() => {
|
|
162
|
-
promise =
|
|
171
|
+
promise = trunk.mutate(() => {})
|
|
163
172
|
})
|
|
164
173
|
await promise
|
|
165
174
|
}).throwsAsync()
|
|
166
175
|
}),
|
|
167
176
|
}),
|
|
168
177
|
|
|
169
|
-
"
|
|
178
|
+
"chronobranch": (() => {
|
|
170
179
|
const setup = () => {
|
|
171
|
-
const
|
|
180
|
+
const trunk = new Trunk({
|
|
172
181
|
chron: Trunk.chronicle({count: 0}),
|
|
173
182
|
})
|
|
174
|
-
const chron =
|
|
175
|
-
return {
|
|
183
|
+
const chron = trunk.chronobranch(64, s => s.chron)
|
|
184
|
+
return {trunk, chron}
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
return Science.suite({
|
package/x/signals/index.d.ts
CHANGED
package/x/signals/index.js
CHANGED
package/x/signals/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/signals/index.ts"],"names":[],"mappings":"AACA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/signals/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Sub } from "@e280/stz";
|
|
2
|
+
import { SignalOptions } from "./types.js";
|
|
3
|
+
export type DerivedSignal<V> = {
|
|
4
|
+
(): V;
|
|
5
|
+
kind: "derived";
|
|
6
|
+
sneak: V;
|
|
7
|
+
on: Sub<[V]>;
|
|
8
|
+
get(): V;
|
|
9
|
+
get value(): V;
|
|
10
|
+
dispose(): void;
|
|
11
|
+
};
|
|
12
|
+
export declare function derive<V>(formula: () => V, options?: Partial<SignalOptions>): DerivedSignal<V>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DerivedCore, processSignalOptions } from "./units.js";
|
|
2
|
+
export function derive(formula, options = {}) {
|
|
3
|
+
function fn() {
|
|
4
|
+
return fn.value;
|
|
5
|
+
}
|
|
6
|
+
const o = processSignalOptions(options);
|
|
7
|
+
const core = DerivedCore.make(fn, formula, o);
|
|
8
|
+
Object.setPrototypeOf(fn, DerivedCore.prototype);
|
|
9
|
+
Object.assign(fn, core);
|
|
10
|
+
return fn;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=derive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive.js","sourceRoot":"","sources":["../../../s/signals/parts/derive.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,WAAW,EAAE,oBAAoB,EAAC,MAAM,YAAY,CAAA;AAa5D,MAAM,UAAU,MAAM,CAAI,OAAgB,EAAE,UAAkC,EAAE;IAC/E,SAAS,EAAE;QACV,OAAQ,EAAU,CAAC,KAAK,CAAA;IACzB,CAAC;IAED,MAAM,CAAC,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAI,EAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IACvD,MAAM,CAAC,cAAc,CAAC,EAAE,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;IAChD,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IAEvB,OAAO,EAAsB,CAAA;AAC9B,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare function effect
|
|
2
|
-
export declare function
|
|
1
|
+
export declare function effect(collector: () => void, responder?: () => void): () => void;
|
|
2
|
+
export declare function collectorEffect<C = void>(collector: () => C, responder?: () => void): {
|
|
3
3
|
result: C;
|
|
4
4
|
dispose: () => void;
|
|
5
5
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { debounce } from "@e280/stz";
|
|
2
2
|
import { tracker } from "../../tracker/tracker.js";
|
|
3
3
|
export function effect(collector, responder = collector) {
|
|
4
|
-
return
|
|
4
|
+
return collectorEffect(collector, responder).dispose;
|
|
5
5
|
}
|
|
6
|
-
export function
|
|
6
|
+
export function collectorEffect(collector, responder = collector) {
|
|
7
7
|
const { seen, result } = tracker.seen(collector);
|
|
8
8
|
const fn = debounce(0, responder);
|
|
9
9
|
const disposers = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../../s/signals/parts/effect.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,WAAW,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,0BAA0B,CAAA;AAEhD,MAAM,UAAU,MAAM,
|
|
1
|
+
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../../s/signals/parts/effect.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,WAAW,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,0BAA0B,CAAA;AAEhD,MAAM,UAAU,MAAM,CAAC,SAAqB,EAAE,YAAwB,SAAS;IAC9E,OAAO,eAAe,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,OAAO,CAAA;AACrD,CAAC;AAED,MAAM,UAAU,eAAe,CAAW,SAAkB,EAAE,YAAwB,SAAS;IAC9F,MAAM,EAAC,IAAI,EAAE,MAAM,EAAC,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IAC9C,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAA;IAEjC,MAAM,SAAS,GAAmB,EAAE,CAAA;IACpC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;IAEjD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QACxC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACxB,CAAC;IAED,OAAO,EAAC,MAAM,EAAE,OAAO,EAAC,CAAA;AACzB,CAAC"}
|