@e280/strata 0.2.1 → 0.2.3

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 (93) hide show
  1. package/README.md +132 -131
  2. package/package.json +7 -5
  3. package/s/index.ts +1 -0
  4. package/s/prism/chrono/chronicle.ts +11 -0
  5. package/s/prism/chrono/chrono.ts +91 -0
  6. package/s/prism/chrono/types.ts +12 -0
  7. package/s/prism/index.ts +11 -0
  8. package/s/prism/lens.ts +54 -0
  9. package/s/prism/prism.test.ts +330 -0
  10. package/s/prism/prism.ts +41 -0
  11. package/s/prism/types.ts +27 -0
  12. package/s/prism/utils/cache-cell.ts +22 -0
  13. package/s/prism/utils/immute.ts +8 -0
  14. package/s/prism/utils/optic-symbol.ts +3 -0
  15. package/s/prism/vault/local-store.ts +31 -0
  16. package/s/prism/vault/types.ts +19 -0
  17. package/s/prism/vault/vault.ts +19 -0
  18. package/s/signals/core/effect.ts +2 -2
  19. package/s/tests.test.ts +4 -1
  20. package/s/tree/parts/branch.ts +28 -14
  21. package/s/tree/parts/chronobranch.ts +4 -2
  22. package/s/tree/parts/trunk.ts +18 -22
  23. package/s/tree/parts/types.ts +36 -31
  24. package/s/tree/parts/utils/immute.ts +43 -0
  25. package/s/tree/parts/utils/process-options.ts +2 -2
  26. package/s/tree/tree.test.ts +69 -19
  27. package/x/index.d.ts +1 -0
  28. package/x/index.js +1 -0
  29. package/x/index.js.map +1 -1
  30. package/x/prism/chrono/chronicle.d.ts +2 -0
  31. package/x/prism/chrono/chronicle.js +8 -0
  32. package/x/prism/chrono/chronicle.js.map +1 -0
  33. package/x/prism/chrono/chrono.d.ts +26 -0
  34. package/x/prism/chrono/chrono.js +79 -0
  35. package/x/prism/chrono/chrono.js.map +1 -0
  36. package/x/prism/chrono/types.d.ts +5 -0
  37. package/x/prism/chrono/types.js +2 -0
  38. package/x/prism/chrono/types.js.map +1 -0
  39. package/x/prism/index.d.ts +9 -0
  40. package/x/prism/index.js +10 -0
  41. package/x/prism/index.js.map +1 -0
  42. package/x/prism/lens.d.ts +13 -0
  43. package/x/prism/lens.js +45 -0
  44. package/x/prism/lens.js.map +1 -0
  45. package/x/prism/prism.d.ts +10 -0
  46. package/x/prism/prism.js +34 -0
  47. package/x/prism/prism.js.map +1 -0
  48. package/x/prism/prism.test.d.ts +35 -0
  49. package/x/prism/prism.test.js +286 -0
  50. package/x/prism/prism.test.js.map +1 -0
  51. package/x/prism/types.d.ts +17 -0
  52. package/x/prism/types.js +2 -0
  53. package/x/prism/types.js.map +1 -0
  54. package/x/prism/utils/cache-cell.d.ts +7 -0
  55. package/x/prism/utils/cache-cell.js +20 -0
  56. package/x/prism/utils/cache-cell.js.map +1 -0
  57. package/x/prism/utils/immute.d.ts +2 -0
  58. package/x/prism/utils/immute.js +5 -0
  59. package/x/prism/utils/immute.js.map +1 -0
  60. package/x/prism/utils/optic-symbol.d.ts +1 -0
  61. package/x/prism/utils/optic-symbol.js +2 -0
  62. package/x/prism/utils/optic-symbol.js.map +1 -0
  63. package/x/prism/vault/local-store.d.ts +9 -0
  64. package/x/prism/vault/local-store.js +27 -0
  65. package/x/prism/vault/local-store.js.map +1 -0
  66. package/x/prism/vault/types.d.ts +14 -0
  67. package/x/prism/vault/types.js +2 -0
  68. package/x/prism/vault/types.js.map +1 -0
  69. package/x/prism/vault/vault.d.ts +7 -0
  70. package/x/prism/vault/vault.js +17 -0
  71. package/x/prism/vault/vault.js.map +1 -0
  72. package/x/signals/core/effect.js +2 -2
  73. package/x/signals/core/effect.js.map +1 -1
  74. package/x/tests.test.js +3 -1
  75. package/x/tests.test.js.map +1 -1
  76. package/x/tree/parts/branch.d.ts +4 -2
  77. package/x/tree/parts/branch.js +20 -9
  78. package/x/tree/parts/branch.js.map +1 -1
  79. package/x/tree/parts/chronobranch.d.ts +5 -3
  80. package/x/tree/parts/chronobranch.js +1 -0
  81. package/x/tree/parts/chronobranch.js.map +1 -1
  82. package/x/tree/parts/trunk.d.ts +8 -6
  83. package/x/tree/parts/trunk.js +14 -14
  84. package/x/tree/parts/trunk.js.map +1 -1
  85. package/x/tree/parts/types.d.ts +5 -20
  86. package/x/tree/parts/utils/immute.d.ts +11 -0
  87. package/x/tree/parts/utils/immute.js +33 -0
  88. package/x/tree/parts/utils/immute.js.map +1 -0
  89. package/x/tree/parts/utils/process-options.d.ts +2 -2
  90. package/x/tree/parts/utils/process-options.js.map +1 -1
  91. package/x/tree/tree.test.d.ts +6 -3
  92. package/x/tree/tree.test.js +64 -18
  93. package/x/tree/tree.test.js.map +1 -1
@@ -1,14 +1,14 @@
1
1
 
2
2
  import {deep} from "@e280/stz"
3
3
  import {Branch} from "./branch.js"
4
- import {signal} from "../../signals/porcelain.js"
4
+ import {Immute} from "./utils/immute.js"
5
5
  import {trunkSetup} from "./utils/setup.js"
6
- import {Derived} from "../../signals/core/derived.js"
7
- import {Signal} from "../../signals/core/signal.js"
8
6
  import {Chronobranch} from "./chronobranch.js"
9
7
  import {processOptions} from "./utils/process-options.js"
10
- import {Branchstate, Chronicle, Immutable, Mutator, Options, Selector, Tree, Trunkstate} from "./types.js"
8
+ import {Branchstate, Mutator, TreeOptions, Selector, Tree, Trunkstate} from "./types.js"
9
+ import {Chronicle} from "../../prism/index.js"
11
10
 
11
+ /** @deprecated tree stuff has been replaced by prism/lens stuff */
12
12
  export class Trunk<S extends Trunkstate> implements Tree<S> {
13
13
  static setup = trunkSetup
14
14
  static chronicle = <S extends Branchstate>(state: S): Chronicle<S> => ({
@@ -17,47 +17,43 @@ export class Trunk<S extends Trunkstate> implements Tree<S> {
17
17
  future: [],
18
18
  })
19
19
 
20
- options: Options
21
-
22
- #immutable: Derived<Immutable<S>>
23
- #mutable: Signal<S>
20
+ options: TreeOptions
24
21
  #mutationLock = 0
22
+ #immute: Immute<S>
25
23
 
26
- constructor(state: S, options: Partial<Options> = {}) {
24
+ constructor(state: S, options: Partial<TreeOptions> = {}) {
27
25
  this.options = processOptions(options)
28
- this.#mutable = signal(state)
29
- this.#immutable = signal.derived(() =>
30
- deep.freeze(this.options.clone(this.#mutable.get())) as Immutable<S>
31
- )
26
+ this.#immute = new Immute(state, this.options)
32
27
  }
33
28
 
34
29
  get state() {
35
- return this.#immutable.get()
30
+ return this.#immute.immutable
36
31
  }
37
32
 
38
33
  get on() {
39
- return this.#immutable.on
34
+ return this.#immute.on
40
35
  }
41
36
 
42
37
  async mutate(mutator: Mutator<S>) {
43
- const oldState = this.options.clone(this.#mutable.get())
38
+ const oldState = this.#immute.get()
44
39
  if (this.#mutationLock > 0)
45
40
  throw new Error("nested mutations are forbidden")
41
+ let promise = Promise.resolve()
46
42
  try {
47
43
  this.#mutationLock++
48
- const value = this.#mutable.get()
49
- mutator(value)
50
- const newState = value
44
+ const newState = this.options.clone(oldState)
45
+ mutator(newState)
51
46
  const isChanged = !deep.equal(newState, oldState)
52
47
  if (isChanged)
53
- await this.overwrite(newState)
48
+ promise = this.overwrite(newState)
54
49
  }
55
50
  finally { this.#mutationLock-- }
56
- return this.#immutable.get()
51
+ await promise
52
+ return this.state
57
53
  }
58
54
 
59
55
  async overwrite(state: S) {
60
- await this.#mutable.set(state, true)
56
+ await this.#immute.set(state)
61
57
  }
62
58
 
63
59
  branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
@@ -1,32 +1,37 @@
1
1
 
2
+ import { EzStore, Versioned } from "../../prism/index.js"
3
+ import { Immutable } from "../../prism/types.js"
2
4
  import {Branch} from "./branch.js"
3
5
 
4
- export type Options = {
6
+ export type TreeOptions = {
5
7
  clone: <X>(x: X) => X
6
8
  }
7
9
 
10
+ /** @deprecated renamed to `TreeOptions` */
11
+ export type Options = TreeOptions
12
+
8
13
  export type Selector<Sub, S> = (state: S) => Sub
9
14
  export type Mutator<S> = (state: S) => void
10
15
 
11
16
  export type Trunkstate = {}
12
17
  export type Branchstate = {} | null | undefined
13
18
 
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
19
+ // export type Versioned<S extends Trunkstate> = {
20
+ // state: S
21
+ // version: number
22
+ // }
23
+ //
24
+ // export type Immutable<T> =
25
+ // T extends (...args: any[]) => any ? T :
26
+ // T extends readonly any[] ? ReadonlyArray<Immutable<T[number]>> :
27
+ // T extends object ? { readonly [K in keyof T]: Immutable<T[K]> } :
28
+ // T
29
+ //
30
+ // export type Mutable<T> =
31
+ // T extends (...args: any[]) => any ? T :
32
+ // T extends ReadonlyArray<infer U> ? Mutable<U>[] :
33
+ // T extends object ? { -readonly [K in keyof T]: Mutable<T[K]> } :
34
+ // T
30
35
 
31
36
  export type Tree<S extends Branchstate> = {
32
37
  get state(): Immutable<S>
@@ -42,21 +47,21 @@ export type SetupOptions<S extends Trunkstate> = {
42
47
  persistence?: Persistence<Versioned<S>>
43
48
  }
44
49
 
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
- }
50
+ // export type Chronicle<S extends Branchstate> = {
51
+ // // [abc] d [efg]
52
+ // // \ \ \
53
+ // // \ \ future
54
+ // // \ present
55
+ // // past
56
+ // past: S[]
57
+ // present: S
58
+ // future: S[]
59
+ // }
55
60
 
56
- export type EzStore<X> = {
57
- get(): Promise<X | undefined>
58
- set(state: X | undefined): Promise<void>
59
- }
61
+ // export type EzStore<X> = {
62
+ // get(): Promise<X | undefined>
63
+ // set(state: X | undefined): Promise<void>
64
+ // }
60
65
 
61
66
  export type Persistence<X> = {
62
67
  store: EzStore<X>
@@ -0,0 +1,43 @@
1
+
2
+ import {deep, sub} from "@e280/stz"
3
+ import {TreeOptions} from "../types.js"
4
+ import {tracker} from "../../../tracker/tracker.js"
5
+ import {Immutable} from "../../../prism/types.js"
6
+
7
+ export class Immute<S> {
8
+ #mutable: S
9
+ #immutable: Immutable<S>
10
+
11
+ constructor(mutable: S, private options: TreeOptions) {
12
+ this.#mutable = mutable
13
+ this.#immutable = this.#petrify(mutable)
14
+ }
15
+
16
+ #petrify<X>(x: X) {
17
+ return deep.freeze(
18
+ this.options.clone(x)
19
+ ) as Immutable<X>
20
+ }
21
+
22
+ on = sub<[Immutable<S>]>()
23
+
24
+ get() {
25
+ tracker.notifyRead(this)
26
+ return this.#mutable
27
+ }
28
+
29
+ async set(mutable: S) {
30
+ this.#mutable = mutable
31
+ this.#immutable = deep.freeze(this.options.clone(this.get())) as Immutable<S>
32
+ await Promise.all([
33
+ this.on.publish(this.#immutable),
34
+ tracker.notifyWrite(this),
35
+ ])
36
+ }
37
+
38
+ get immutable() {
39
+ tracker.notifyRead(this)
40
+ return this.#immutable
41
+ }
42
+ }
43
+
@@ -1,7 +1,7 @@
1
1
 
2
- import {Options} from "../types.js"
2
+ import {TreeOptions} from "../types.js"
3
3
 
4
- export const processOptions = (options: Partial<Options>): Options => ({
4
+ export const processOptions = (options: Partial<TreeOptions>): TreeOptions => ({
5
5
  clone: options.clone ?? (<X>(x: X) => structuredClone(x)),
6
6
  })
7
7
 
@@ -56,13 +56,15 @@ export default Science.suite({
56
56
  expect(mutationCount).is(2)
57
57
  }),
58
58
 
59
- "signal.on is debounced": Science.test(async() => {
59
+ "trunk.on is debounced": Science.test.skip(async() => {
60
60
  const trunk = new Trunk({count: 0})
61
61
  let mutationCount = 0
62
62
  trunk.on.sub(() => {mutationCount++})
63
- const promise = trunk.mutate(state => state.count++)
64
- expect(mutationCount).is(0)
65
- await promise
63
+ await Promise.all([
64
+ trunk.mutate(state => state.count++),
65
+ trunk.mutate(state => state.count++),
66
+ trunk.mutate(state => state.count++),
67
+ ])
66
68
  expect(mutationCount).is(1)
67
69
  }),
68
70
 
@@ -75,19 +77,30 @@ export default Science.suite({
75
77
  expect(trunk.state.items.length).is(3)
76
78
  }),
77
79
 
78
- "prevent mutation loops": Science.test(async() => {
80
+ // // conceptually incompatible with sequential synchronous mutations
81
+ // "prevent mutation loops": Science.test(async() => {
82
+ // const trunk = new Trunk({count: 0})
83
+ // let mutationCount = 0
84
+ // trunk.on.sub(async() => {
85
+ // mutationCount++
86
+ // if (mutationCount > 100)
87
+ // return
88
+ // await trunk.mutate(s => s.count++)
89
+ // })
90
+ // await expect(async() => {
91
+ // await trunk.mutate(state => state.count++)
92
+ // }).throwsAsync()
93
+ // expect(mutationCount).is(1)
94
+ // }),
95
+
96
+ "sync coherence": Science.test(async() => {
79
97
  const trunk = new Trunk({count: 0})
80
- let mutationCount = 0
81
- trunk.on.sub(async() => {
82
- mutationCount++
83
- if (mutationCount > 100)
84
- return
85
- await trunk.mutate(s => s.count++)
86
- })
87
- await expect(async() => {
88
- await trunk.mutate(state => state.count++)
89
- }).throwsAsync()
90
- expect(mutationCount).is(1)
98
+ const p1 = trunk.mutate(s => s.count++)
99
+ expect(trunk.state.count).is(1)
100
+ const p2 = trunk.mutate(s => s.count++)
101
+ expect(trunk.state.count).is(2)
102
+ await p1
103
+ await p2
91
104
  }),
92
105
  }),
93
106
 
@@ -98,9 +111,32 @@ export default Science.suite({
98
111
  expect(branch.state.rofls).is(0)
99
112
  }),
100
113
 
114
+ "mutation triggers effect": Science.test(async() => {
115
+ const trunk = new Trunk({a: {x: 0}})
116
+ const branch = trunk.branch(s => s.a)
117
+ expect(branch.state.x).is(0)
118
+ let triggered = 0
119
+ effect(() => {
120
+ void branch.state.x
121
+ triggered++
122
+ })
123
+ expect(triggered).is(1)
124
+ await branch.mutate(s => s.x++)
125
+ expect(triggered).is(2)
126
+ }),
127
+
128
+ "sync coherence": Science.test(async() => {
129
+ const trunk = new Trunk({count: 0, sub: {rofls: 0}})
130
+ const branch = trunk.branch(s => s.sub)
131
+ expect(branch.state.rofls).is(0)
132
+ const p = branch.mutate(s => s.rofls++)
133
+ expect(branch.state.rofls).is(1)
134
+ await p
135
+ }),
136
+
101
137
  "nullable selector": Science.test(async () => {
102
138
  const trunk = new Trunk({
103
- a: {b: 0} as (null | {b: number}),
139
+ a: {b: 0} as (null | {b: number}),
104
140
  })
105
141
  const a = trunk.branch(s => s.a)
106
142
  expect(trunk.state.a?.b).is(0)
@@ -139,17 +175,31 @@ export default Science.suite({
139
175
  expect(b.state.c).is(103)
140
176
  }),
141
177
 
142
- "signal.on ignores outside mutations": Science.test(async() => {
178
+ "branch.on ignores outside mutations": Science.test(async() => {
143
179
  const trunk = new Trunk({a: {x: 0}, b: {x: 0}})
144
180
  const a = trunk.branch(s => s.a)
145
181
  const b = trunk.branch(s => s.b)
146
182
  let counted = 0
147
- b.on.sub(() => {counted++})
183
+ b.on(() => {counted++})
148
184
  expect(counted).is(0)
149
185
  await a.mutate(a => a.x = 1)
150
186
  expect(counted).is(0)
151
187
  }),
152
188
 
189
+ "effects ignore outside mutations": Science.test(async() => {
190
+ const trunk = new Trunk({a: {x: 0}, b: {x: 0}})
191
+ const a = trunk.branch(s => s.a)
192
+ const b = trunk.branch(s => s.b)
193
+ let counted = 0
194
+ effect(() => {
195
+ void b.state.x
196
+ counted++
197
+ })
198
+ expect(counted).is(1)
199
+ await a.mutate(a => a.x++)
200
+ expect(counted).is(1)
201
+ }),
202
+
153
203
  "forbid submutation in mutation": Science.test(async() => {
154
204
  const trunk = new Trunk({a: {b: 0}})
155
205
  const a = trunk.branch(s => s.a)
package/x/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./prism/index.js";
1
2
  export * from "./signals/index.js";
2
3
  export * from "./tracker/index.js";
3
4
  export * from "./tree/index.js";
package/x/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./prism/index.js";
1
2
  export * from "./signals/index.js";
2
3
  export * from "./tracker/index.js";
3
4
  export * from "./tree/index.js";
package/x/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../s/index.ts"],"names":[],"mappings":"AACA,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../s/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA"}
@@ -0,0 +1,2 @@
1
+ import { Chronicle } from "./types.js";
2
+ export declare function chronicle<State>(state: State): Chronicle<State>;
@@ -0,0 +1,8 @@
1
+ export function chronicle(state) {
2
+ return {
3
+ past: [],
4
+ present: state,
5
+ future: [],
6
+ };
7
+ }
8
+ //# sourceMappingURL=chronicle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chronicle.js","sourceRoot":"","sources":["../../../s/prism/chrono/chronicle.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,SAAS,CAAQ,KAAY;IAC5C,OAAO;QACN,IAAI,EAAE,EAAE;QACR,OAAO,EAAE,KAAK;QACd,MAAM,EAAE,EAAE;KACV,CAAA;AACF,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { Lens } from "../lens.js";
2
+ import { Chronicle } from "./types.js";
3
+ import { LensLike } from "../types.js";
4
+ export declare class Chrono<State> implements LensLike<State> {
5
+ #private;
6
+ limit: number;
7
+ private basis;
8
+ constructor(limit: number, basis: Lens<Chronicle<State>>);
9
+ get chronicle(): {
10
+ readonly past: readonly import("../types.js").Immutable<State>[];
11
+ readonly present: import("../types.js").Immutable<State>;
12
+ readonly future: readonly import("../types.js").Immutable<State>[];
13
+ };
14
+ get state(): import("../types.js").Immutable<State>;
15
+ get undoable(): number;
16
+ get redoable(): number;
17
+ /** progress forwards, depositing history into the past */
18
+ mutate<R>(fn: (state: State) => R): Promise<R>;
19
+ /** step backwards into the past, by n steps */
20
+ undo(n?: number): Promise<void>;
21
+ /** step forwards into the future, by n steps */
22
+ redo(n?: number): Promise<void>;
23
+ /** wipe past and future snapshots */
24
+ wipe(): Promise<void>;
25
+ lens<State2>(selector: (state: State) => State2): Lens<State2>;
26
+ }
@@ -0,0 +1,79 @@
1
+ import { deep } from "@e280/stz";
2
+ import { Lens } from "../lens.js";
3
+ import { _optic } from "../utils/optic-symbol.js";
4
+ export class Chrono {
5
+ limit;
6
+ basis;
7
+ constructor(limit, basis) {
8
+ this.limit = limit;
9
+ this.basis = basis;
10
+ }
11
+ get chronicle() {
12
+ return this.basis.state;
13
+ }
14
+ get state() {
15
+ return this.basis.state.present;
16
+ }
17
+ get undoable() {
18
+ return this.chronicle.past.length;
19
+ }
20
+ get redoable() {
21
+ return this.chronicle.future.length;
22
+ }
23
+ #mut(chronicle, fn) {
24
+ const limit = Math.max(0, this.limit);
25
+ const snapshot = deep.clone(this.chronicle.present);
26
+ const result = fn(chronicle.present);
27
+ chronicle.past.push(snapshot);
28
+ chronicle.past = chronicle.past.slice(-limit);
29
+ chronicle.future = [];
30
+ return result;
31
+ }
32
+ /** progress forwards, depositing history into the past */
33
+ async mutate(fn) {
34
+ return this.basis.mutate(chronicle => this.#mut(chronicle, fn));
35
+ }
36
+ /** step backwards into the past, by n steps */
37
+ async undo(n = 1) {
38
+ await this.basis.mutate(chronicle => {
39
+ const snapshots = chronicle.past.slice(-n);
40
+ if (snapshots.length >= n) {
41
+ const oldPresent = chronicle.present;
42
+ chronicle.present = snapshots.shift();
43
+ chronicle.past = chronicle.past.slice(0, -n);
44
+ chronicle.future.unshift(oldPresent, ...snapshots);
45
+ }
46
+ });
47
+ }
48
+ /** step forwards into the future, by n steps */
49
+ async redo(n = 1) {
50
+ await this.basis.mutate(chronicle => {
51
+ const snapshots = chronicle.future.slice(0, n);
52
+ if (snapshots.length >= n) {
53
+ const oldPresent = chronicle.present;
54
+ chronicle.present = snapshots.shift();
55
+ chronicle.past.push(oldPresent, ...snapshots);
56
+ chronicle.future = chronicle.future.slice(n);
57
+ }
58
+ });
59
+ }
60
+ /** wipe past and future snapshots */
61
+ async wipe() {
62
+ await this.basis.mutate(chronicle => {
63
+ chronicle.past = [];
64
+ chronicle.future = [];
65
+ });
66
+ }
67
+ lens(selector) {
68
+ const lens = new Lens({
69
+ registerLens: this.basis[_optic].registerLens,
70
+ getState: () => selector(this.basis[_optic].getState().present),
71
+ mutate: fn => this.basis[_optic].mutate(chronicle => {
72
+ return this.#mut(chronicle, state => fn(selector(state)));
73
+ }),
74
+ });
75
+ this.basis[_optic].registerLens(lens);
76
+ return lens;
77
+ }
78
+ }
79
+ //# sourceMappingURL=chrono.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrono.js","sourceRoot":"","sources":["../../../s/prism/chrono/chrono.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type Chronicle<State> = {
2
+ past: State[];
3
+ present: State;
4
+ future: State[];
5
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../s/prism/chrono/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ export * from "./chrono/chronicle.js";
2
+ export * from "./chrono/chrono.js";
3
+ export * from "./chrono/types.js";
4
+ export * from "./vault/local-store.js";
5
+ export * from "./vault/vault.js";
6
+ export * from "./vault/types.js";
7
+ export * from "./lens.js";
8
+ export * from "./prism.js";
9
+ export * from "./types.js";
@@ -0,0 +1,10 @@
1
+ export * from "./chrono/chronicle.js";
2
+ export * from "./chrono/chrono.js";
3
+ export * from "./chrono/types.js";
4
+ export * from "./vault/local-store.js";
5
+ export * from "./vault/vault.js";
6
+ export * from "./vault/types.js";
7
+ export * from "./lens.js";
8
+ export * from "./prism.js";
9
+ export * from "./types.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/prism/index.ts"],"names":[],"mappings":"AACA,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA;AACtC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { _optic } from "./utils/optic-symbol.js";
2
+ import { Immutable, LensLike, Optic } from "./types.js";
3
+ /** reactive view into a state prism, with formalized mutations */
4
+ export declare class Lens<State> implements LensLike<State> {
5
+ #private;
6
+ on: import("@e280/stz").Sub<[state: Immutable<State>]>;
7
+ [_optic]: Optic<State>;
8
+ constructor(optic: Optic<State>);
9
+ update(): Promise<void>;
10
+ get state(): Immutable<State>;
11
+ mutate<R>(fn: (state: State) => R): Promise<R>;
12
+ lens<State2>(selector: (state: State) => State2): Lens<State2>;
13
+ }
@@ -0,0 +1,45 @@
1
+ import { deep, microbounce, sub } from "@e280/stz";
2
+ import { immute } from "./utils/immute.js";
3
+ import { tracker } from "../tracker/tracker.js";
4
+ import { _optic } from "./utils/optic-symbol.js";
5
+ import { CacheCell } from "./utils/cache-cell.js";
6
+ /** reactive view into a state prism, with formalized mutations */
7
+ export class Lens {
8
+ on = sub();
9
+ [_optic];
10
+ #previous;
11
+ #immutable;
12
+ #onPublishDebounced = microbounce(() => this.on.publish(this.state));
13
+ constructor(optic) {
14
+ this[_optic] = optic;
15
+ this.#previous = deep.clone(optic.getState());
16
+ this.#immutable = new CacheCell(() => immute(optic.getState()));
17
+ }
18
+ async update() {
19
+ const state = this[_optic].getState();
20
+ const isChanged = !deep.equal(state, this.#previous);
21
+ if (isChanged) {
22
+ this.#immutable.invalidate();
23
+ this.#previous = deep.clone(state);
24
+ this.#onPublishDebounced();
25
+ await tracker.notifyWrite(this);
26
+ }
27
+ }
28
+ get state() {
29
+ tracker.notifyRead(this);
30
+ return this.#immutable.get();
31
+ }
32
+ async mutate(fn) {
33
+ return this[_optic].mutate(fn);
34
+ }
35
+ lens(selector) {
36
+ const lens = new Lens({
37
+ getState: () => selector(this[_optic].getState()),
38
+ mutate: fn => this[_optic].mutate(state => fn(selector(state))),
39
+ registerLens: this[_optic].registerLens,
40
+ });
41
+ this[_optic].registerLens(lens);
42
+ return lens;
43
+ }
44
+ }
45
+ //# sourceMappingURL=lens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lens.js","sourceRoot":"","sources":["../../s/prism/lens.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAC,MAAM,WAAW,CAAA;AAChD,OAAO,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAA;AACxC,OAAO,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAA;AAC7C,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAA;AAG/C,kEAAkE;AAClE,MAAM,OAAO,IAAI;IAChB,EAAE,GAAG,GAAG,EAA6B,CAEpC;IAAA,CAAC,MAAM,CAAC,CAAc;IACvB,SAAS,CAAO;IAChB,UAAU,CAA6B;IACvC,mBAAmB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IAEpE,YAAY,KAAmB;QAC9B,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,CAAA;QACpB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC7C,IAAI,CAAC,UAAU,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IAChE,CAAC;IAED,KAAK,CAAC,MAAM;QACX,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAA;QACrC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;QACpD,IAAI,SAAS,EAAE,CAAC;YACf,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAA;YAC5B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAClC,IAAI,CAAC,mBAAmB,EAAE,CAAA;YAC1B,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QAChC,CAAC;IACF,CAAC;IAED,IAAI,KAAK;QACR,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACxB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAA;IAC7B,CAAC;IAED,KAAK,CAAC,MAAM,CAAI,EAAuB;QACtC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC/B,CAAC;IAED,IAAI,CAAS,QAAkC;QAC9C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAS;YAC7B,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;YACjD,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/D,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,YAAY;SACvC,CAAC,CAAA;QACF,IAAI,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;QAC/B,OAAO,IAAI,CAAA;IACZ,CAAC;CACD"}
@@ -0,0 +1,10 @@
1
+ import { Lens } from "./lens.js";
2
+ /** state mangagement source-of-truth */
3
+ export declare class Prism<State> {
4
+ #private;
5
+ on: import("@e280/stz").Sub<[state: State]>;
6
+ constructor(state: State);
7
+ get(): State;
8
+ set(state: State): Promise<void>;
9
+ lens<State2>(selector: (state: State) => State2): Lens<State2>;
10
+ }
@@ -0,0 +1,34 @@
1
+ import { microbounce, sub } from "@e280/stz";
2
+ import { Lens } from "./lens.js";
3
+ /** state mangagement source-of-truth */
4
+ export class Prism {
5
+ #state;
6
+ #lenses = new Set();
7
+ on = sub();
8
+ #onPublishDebounced = microbounce(() => this.on.publish(this.#state));
9
+ constructor(state) {
10
+ this.#state = state;
11
+ }
12
+ get() {
13
+ return this.#state;
14
+ }
15
+ async set(state) {
16
+ this.#state = state;
17
+ await Promise.all([...this.#lenses].map(lens => lens.update()));
18
+ await this.#onPublishDebounced();
19
+ }
20
+ lens(selector) {
21
+ const lens = new Lens({
22
+ getState: () => selector(this.#state),
23
+ mutate: async (fn) => {
24
+ const result = fn(selector(this.#state));
25
+ await this.set(this.#state);
26
+ return result;
27
+ },
28
+ registerLens: lens => this.#lenses.add(lens),
29
+ });
30
+ this.#lenses.add(lens);
31
+ return lens;
32
+ }
33
+ }
34
+ //# sourceMappingURL=prism.js.map