@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.
Files changed (67) hide show
  1. package/README.md +27 -24
  2. package/package.json +9 -10
  3. package/s/signals/index.ts +2 -1
  4. package/s/signals/parts/derive.ts +29 -0
  5. package/s/signals/parts/effect.ts +3 -3
  6. package/s/signals/parts/lazy.ts +27 -0
  7. package/s/signals/parts/signal.ts +21 -43
  8. package/s/signals/parts/types.ts +11 -0
  9. package/s/signals/parts/units.ts +150 -0
  10. package/s/signals/signals.test.ts +103 -8
  11. package/s/tests.test.ts +1 -1
  12. package/s/tree/index.ts +1 -1
  13. package/s/tree/parts/branch.ts +17 -40
  14. package/s/tree/parts/chronobranch.ts +13 -13
  15. package/s/tree/parts/trunk.ts +27 -29
  16. package/s/tree/parts/types.ts +18 -6
  17. package/s/tree/parts/utils/setup.ts +4 -4
  18. package/s/tree/tree.test.ts +77 -68
  19. package/x/signals/index.d.ts +2 -1
  20. package/x/signals/index.js +2 -1
  21. package/x/signals/index.js.map +1 -1
  22. package/x/signals/parts/derive.d.ts +12 -0
  23. package/x/signals/parts/derive.js +12 -0
  24. package/x/signals/parts/derive.js.map +1 -0
  25. package/x/signals/parts/effect.d.ts +2 -2
  26. package/x/signals/parts/effect.js +2 -2
  27. package/x/signals/parts/effect.js.map +1 -1
  28. package/x/signals/parts/lazy.d.ts +10 -0
  29. package/x/signals/parts/lazy.js +12 -0
  30. package/x/signals/parts/lazy.js.map +1 -0
  31. package/x/signals/parts/signal.d.ts +11 -8
  32. package/x/signals/parts/signal.js +8 -37
  33. package/x/signals/parts/signal.js.map +1 -1
  34. package/x/signals/parts/types.d.ts +7 -0
  35. package/x/signals/parts/types.js +2 -0
  36. package/x/signals/parts/types.js.map +1 -0
  37. package/x/signals/parts/units.d.ts +43 -0
  38. package/x/signals/parts/units.js +131 -0
  39. package/x/signals/parts/units.js.map +1 -0
  40. package/x/signals/signals.test.d.ts +8 -3
  41. package/x/signals/signals.test.js +87 -8
  42. package/x/signals/signals.test.js.map +1 -1
  43. package/x/tests.test.js +1 -1
  44. package/x/tests.test.js.map +1 -1
  45. package/x/tree/index.d.ts +1 -1
  46. package/x/tree/index.js +1 -1
  47. package/x/tree/index.js.map +1 -1
  48. package/x/tree/parts/branch.d.ts +4 -7
  49. package/x/tree/parts/branch.js +11 -30
  50. package/x/tree/parts/branch.js.map +1 -1
  51. package/x/tree/parts/chronobranch.d.ts +4 -4
  52. package/x/tree/parts/chronobranch.js +12 -12
  53. package/x/tree/parts/chronobranch.js.map +1 -1
  54. package/x/tree/parts/trunk.d.ts +5 -5
  55. package/x/tree/parts/trunk.js +18 -29
  56. package/x/tree/parts/trunk.js.map +1 -1
  57. package/x/tree/parts/types.d.ts +12 -6
  58. package/x/tree/parts/utils/setup.d.ts +2 -2
  59. package/x/tree/parts/utils/setup.js +1 -1
  60. package/x/tree/parts/utils/setup.js.map +1 -1
  61. package/x/tree/tree.test.d.ts +7 -7
  62. package/x/tree/tree.test.js +76 -68
  63. package/x/tree/tree.test.js.map +1 -1
  64. package/s/signals/parts/computed.ts +0 -53
  65. package/x/signals/parts/computed.d.ts +0 -14
  66. package/x/signals/parts/computed.js +0 -41
  67. package/x/signals/parts/computed.js.map +0 -1
@@ -1,19 +1,11 @@
1
1
 
2
- import {debounce, deep, sub} from "@e280/stz"
3
-
4
- import {Chronobranch} from "./chronobranch.js"
5
- import {tracker} from "../../tracker/tracker.js"
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
- dispose: () => void
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
- const state = this.selector(this.parent.state)
25
- this.#immutable = deep.freeze(this.options.clone(state))
26
-
27
- this.dispose = this.parent.watch(async parentState => {
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
- #updateState(state: S) {
40
- this.#immutable = deep.freeze(this.options.clone(state))
22
+ get state() {
23
+ return this.#immutable.get()
41
24
  }
42
25
 
43
- get state(): S {
44
- tracker.see(this)
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 => mutator(this.selector(parentState)))
50
- return this.#immutable
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, Branchstate} from "./types.js"
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
- #substrata: Branch<Chronicle<S>, ParentState>
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.#substrata = parent.branch(selector)
14
+ this.#branch = parent.branch(selector)
15
15
  }
16
16
 
17
17
  get state() {
18
- return this.#substrata.state.present
18
+ return this.#branch.state.present
19
19
  }
20
20
 
21
21
  get undoable() {
22
- return this.#substrata.state.past.length
22
+ return this.#branch.state.past.length
23
23
  }
24
24
 
25
25
  get redoable() {
26
- return this.#substrata.state.future.length
26
+ return this.#branch.state.future.length
27
27
  }
28
28
 
29
- watch(fn: (state: S) => void) {
30
- return this.#substrata.watch(chronicle => fn(chronicle.present))
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.#substrata.state.present)
37
- await this.#substrata.mutate(chronicle => {
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.#substrata.mutate(chronicle => {
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.#substrata.mutate(chronicle => {
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.#substrata.mutate(chronicle => {
74
+ await this.#branch.mutate(chronicle => {
75
75
  chronicle.past = []
76
76
  chronicle.future = []
77
77
  })
@@ -1,14 +1,14 @@
1
1
 
2
- import {debounce, deep, sub} from "@e280/stz"
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 {Chronicle, Mutator, Options, Selector, Treestate, Tree, Branchstate} from "./types.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
10
 
11
- export class Trunk<S extends Treestate> implements Tree<S> {
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
- #mutable: S
23
- #immutable: S
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 = deep.freeze(this.options.clone(state))
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
- tracker.see(this)
34
- return this.#immutable
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
- this.#mutationLock++
42
- try { mutator(this.#mutable) }
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
- const newState = this.#mutable
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 = state
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
 
@@ -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 Treestate = {}
11
+ export type Trunkstate = {}
12
12
  export type Branchstate = {} | null | undefined
13
13
 
14
- export type Versioned<S extends Treestate> = {
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
- readonly state: S
21
- watch(fn: (s: S) => void): () => void
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 Treestate> = {
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 {Treestate, SetupOptions} from "../types.js"
6
+ import {SetupOptions, Trunkstate} from "../types.js"
7
7
 
8
- export async function trunkSetup<S extends Treestate>(options: SetupOptions<S>) {
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.watch(save)
33
+ trunk.on(save)
34
34
 
35
35
  // cross-tab sync
36
36
  const dispose = persistence.onChange(load)
@@ -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
- "strata": Science.suite({
9
+ "trunk": Science.suite({
7
10
  "get state": Science.test(async() => {
8
- const strata = new Trunk({count: 0})
9
- expect(strata.state.count).is(0)
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 strata = new Trunk({count: 0})
14
- expect(() => strata.state.count++).throws()
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 strata = new Trunk({count: 0})
19
- expect(strata.state.count).is(0)
20
- await strata.mutate(state => state.count++)
21
- expect(strata.state.count).is(1)
22
- await strata.mutate(state => state.count++)
23
- expect(strata.state.count).is(2)
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 strata = new Trunk({count: 0})
30
+ const trunk = new Trunk({count: 0})
28
31
  await expect(async() => {
29
32
  let promise!: Promise<any>
30
- await strata.mutate(() => {
31
- promise = strata.mutate(() => {})
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 strata = new Trunk({x: 1})
39
- await strata.mutate(s => { s.x = 2 })
40
- expect(() => strata.state.x = 3).throws()
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
- "watch is published": Science.test(async() => {
44
- const strata = new Trunk({count: 0})
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
- strata.watch.sub(() => {mutationCount++})
47
- await strata.mutate(state => state.count++)
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
- "watch is debounced": Science.test(async() => {
52
- const strata = new Trunk({count: 0})
59
+ "signal.on is debounced": Science.test(async() => {
60
+ const trunk = new Trunk({count: 0})
53
61
  let mutationCount = 0
54
- strata.watch.sub(() => {mutationCount++})
55
- const promise = strata.mutate(state => state.count++)
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
- "watch is fired when array item is pushed": Science.test(async() => {
62
- const strata = new Trunk({items: ["hello", "world"]})
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
- strata.watch.sub(() => {mutationCount++})
65
- await strata.mutate(state => state.items.push("lol"))
72
+ trunk.on.sub(() => {mutationCount++})
73
+ await trunk.mutate(state => state.items.push("lol"))
66
74
  expect(mutationCount).is(1)
67
- expect(strata.state.items.length).is(3)
75
+ expect(trunk.state.items.length).is(3)
68
76
  }),
69
77
 
70
78
  "prevent mutation loops": Science.test(async() => {
71
- const strata = new Trunk({count: 0})
79
+ const trunk = new Trunk({count: 0})
72
80
  let mutationCount = 0
73
- strata.watch.sub(async() => {
81
+ trunk.on.sub(async() => {
74
82
  mutationCount++
75
83
  if (mutationCount > 100)
76
84
  return
77
- await strata.mutate(s => s.count++)
85
+ await trunk.mutate(s => s.count++)
78
86
  })
79
87
  await expect(async() => {
80
- await strata.mutate(state => state.count++)
88
+ await trunk.mutate(state => state.count++)
81
89
  }).throwsAsync()
82
90
  expect(mutationCount).is(1)
83
91
  }),
84
92
  }),
85
93
 
86
- "substrata": Science.suite({
94
+ "branch": Science.suite({
87
95
  "get state": Science.test(async() => {
88
- const strata = new Trunk({count: 0, sub: {rofls: 0}})
89
- const substrata = strata.branch(s => s.sub)
90
- expect(substrata.state.rofls).is(0)
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 strata = new Trunk({
102
+ const trunk = new Trunk({
95
103
  a: {b: 0} as (null | {b: number}),
96
104
  })
97
- const a = strata.branch(s => s.a)
98
- expect(strata.state.a?.b).is(0)
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(strata.state.a?.b).is(1)
109
+ expect(trunk.state.a?.b).is(1)
102
110
  expect(a.state?.b).is(1)
103
- await strata.mutate(s => s.a = null)
104
- expect(strata.state.a?.b).is(undefined)
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 strata = new Trunk({a: {b: {c: 0}}})
110
- const a = strata.branch(s => s.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(strata.state.a.b.c).is(0)
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 strata = new Trunk({a: {b: {c: 0}}})
118
- const a = strata.branch(s => s.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(strata.state.a.b.c).is(101)
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(strata.state.a.b.c).is(102)
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 strata.mutate(s => { s.a = {b: {c: 103}} })
129
- expect(strata.state.a.b.c).is(103)
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
- "watch ignores outside mutations": Science.test(async() => {
135
- const strata = new Trunk({a: {x: 0}, b: {x: 0}})
136
- const a = strata.branch(s => s.a)
137
- const b = strata.branch(s => s.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.watch.sub(() => {counted++})
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 strata = new Trunk({a: {b: 0}})
146
- const a = strata.branch(s => s.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 strata.mutate(() => {
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 strata = new Trunk({a: {b: 0}})
158
- const a = strata.branch(s => s.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 = strata.mutate(() => {})
171
+ promise = trunk.mutate(() => {})
163
172
  })
164
173
  await promise
165
174
  }).throwsAsync()
166
175
  }),
167
176
  }),
168
177
 
169
- "chronstrata": (() => {
178
+ "chronobranch": (() => {
170
179
  const setup = () => {
171
- const strata = new Trunk({
180
+ const trunk = new Trunk({
172
181
  chron: Trunk.chronicle({count: 0}),
173
182
  })
174
- const chron = strata.chronobranch(64, s => s.chron)
175
- return {strata, chron}
183
+ const chron = trunk.chronobranch(64, s => s.chron)
184
+ return {trunk, chron}
176
185
  }
177
186
 
178
187
  return Science.suite({
@@ -1,3 +1,4 @@
1
- export * from "./parts/computed.js";
1
+ export * from "./parts/lazy.js";
2
2
  export * from "./parts/effect.js";
3
3
  export * from "./parts/signal.js";
4
+ export * from "./parts/types.js";
@@ -1,4 +1,5 @@
1
- export * from "./parts/computed.js";
1
+ export * from "./parts/lazy.js";
2
2
  export * from "./parts/effect.js";
3
3
  export * from "./parts/signal.js";
4
+ export * from "./parts/types.js";
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/signals/index.ts"],"names":[],"mappings":"AACA,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA"}
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<C = void>(collector: () => C, responder?: () => void): () => void;
2
- export declare function initEffect<C = void>(collector: () => C, responder?: () => void): {
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 initEffect(collector, responder).dispose;
4
+ return collectorEffect(collector, responder).dispose;
5
5
  }
6
- export function initEffect(collector, responder = collector) {
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,CAAW,SAAkB,EAAE,YAAwB,SAAS;IACrF,OAAO,UAAU,CAAI,SAAS,EAAE,SAAS,CAAC,CAAC,OAAO,CAAA;AACnD,CAAC;AAED,MAAM,UAAU,UAAU,CAAW,SAAkB,EAAE,YAAwB,SAAS;IACzF,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"}
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"}