atom.io 0.3.1 → 0.4.1

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 (54) hide show
  1. package/README.md +11 -3
  2. package/dist/index.d.ts +84 -30
  3. package/dist/index.js +427 -230
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +427 -230
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +15 -5
  8. package/react/dist/index.d.ts +12 -17
  9. package/react/dist/index.js +25 -34
  10. package/react/dist/index.js.map +1 -1
  11. package/react/dist/index.mjs +21 -34
  12. package/react/dist/index.mjs.map +1 -1
  13. package/react-devtools/dist/index.css +26 -0
  14. package/react-devtools/dist/index.css.map +1 -0
  15. package/react-devtools/dist/index.d.ts +15 -0
  16. package/react-devtools/dist/index.js +1582 -0
  17. package/react-devtools/dist/index.js.map +1 -0
  18. package/react-devtools/dist/index.mjs +1554 -0
  19. package/react-devtools/dist/index.mjs.map +1 -0
  20. package/react-devtools/package.json +15 -0
  21. package/src/index.ts +3 -3
  22. package/src/internal/atom-internal.ts +10 -5
  23. package/src/internal/families-internal.ts +4 -4
  24. package/src/internal/get.ts +8 -8
  25. package/src/internal/index.ts +2 -0
  26. package/src/internal/meta/attach-meta.ts +17 -0
  27. package/src/internal/meta/index.ts +4 -0
  28. package/src/internal/meta/meta-state.ts +135 -0
  29. package/src/internal/meta/meta-timelines.ts +1 -0
  30. package/src/internal/meta/meta-transactions.ts +1 -0
  31. package/src/internal/operation.ts +0 -1
  32. package/src/internal/selector-internal.ts +34 -13
  33. package/src/internal/store.ts +35 -5
  34. package/src/internal/time-travel-internal.ts +89 -0
  35. package/src/internal/timeline-internal.ts +23 -103
  36. package/src/internal/transaction-internal.ts +14 -5
  37. package/src/react/index.ts +28 -46
  38. package/src/react-devtools/AtomIODevtools.tsx +107 -0
  39. package/src/react-devtools/StateEditor.tsx +73 -0
  40. package/src/react-devtools/TokenList.tsx +57 -0
  41. package/src/react-devtools/devtools.scss +130 -0
  42. package/src/react-devtools/index.ts +1 -0
  43. package/src/react-explorer/AtomIOExplorer.tsx +208 -0
  44. package/src/react-explorer/explorer-effects.ts +20 -0
  45. package/src/react-explorer/explorer-states.ts +224 -0
  46. package/src/react-explorer/index.ts +23 -0
  47. package/src/react-explorer/space-states.ts +73 -0
  48. package/src/react-explorer/view-states.ts +43 -0
  49. package/src/selector.ts +6 -6
  50. package/src/subscribe.ts +2 -2
  51. package/src/timeline.ts +1 -5
  52. package/src/transaction.ts +4 -2
  53. package/src/web-effects/index.ts +1 -0
  54. package/src/web-effects/storage.ts +30 -0
@@ -0,0 +1,135 @@
1
+ import type { AtomToken, ReadonlySelectorToken, SelectorToken } from "../.."
2
+ import { selector, atom } from "../.."
3
+ import type { Store } from "../store"
4
+ import { IMPLICIT } from "../store"
5
+
6
+ export type StateTokenIndex<
7
+ Token extends
8
+ | AtomToken<unknown>
9
+ | ReadonlySelectorToken<unknown>
10
+ | SelectorToken<unknown>
11
+ > = Record<
12
+ string,
13
+ | Token
14
+ | {
15
+ key: string
16
+ familyMembers: Record<string, Token>
17
+ }
18
+ >
19
+
20
+ export type AtomTokenIndex = StateTokenIndex<AtomToken<unknown>>
21
+ export type SelectorTokenIndex = StateTokenIndex<
22
+ ReadonlySelectorToken<unknown> | SelectorToken<unknown>
23
+ >
24
+
25
+ export const attachMetaAtoms = (
26
+ store: Store = IMPLICIT.STORE
27
+ ): ReadonlySelectorToken<AtomTokenIndex> => {
28
+ const atomTokenIndexState__INTERNAL = atom<AtomTokenIndex>({
29
+ key: `👁‍🗨_atom_token_index__INTERNAL`,
30
+ default: () =>
31
+ [...store.atoms].reduce<AtomTokenIndex>((acc, [key]) => {
32
+ acc[key] = { key, type: `atom` }
33
+ return acc
34
+ }, {}),
35
+ effects: [
36
+ ({ setSelf }) => {
37
+ store.subject.atomCreation.subscribe((atomToken) => {
38
+ if (store.operation.open) {
39
+ return
40
+ }
41
+ setSelf((state) => {
42
+ const { key, family } = atomToken
43
+ if (family) {
44
+ const { key: familyKey, subKey } = family
45
+ const current = state[familyKey]
46
+ if (current === undefined || `familyMembers` in current) {
47
+ const familyKeyState = current || {
48
+ key: familyKey,
49
+ familyMembers: {},
50
+ }
51
+ return {
52
+ ...state,
53
+ [familyKey]: {
54
+ ...familyKeyState,
55
+ familyMembers: {
56
+ ...familyKeyState.familyMembers,
57
+ [subKey]: atomToken,
58
+ },
59
+ },
60
+ }
61
+ }
62
+ }
63
+ return {
64
+ ...state,
65
+ [key]: atomToken,
66
+ }
67
+ })
68
+ })
69
+ },
70
+ ],
71
+ })
72
+ return selector({
73
+ key: `👁‍🗨_atom_token_index`,
74
+ get: ({ get }) => get(atomTokenIndexState__INTERNAL),
75
+ })
76
+ }
77
+
78
+ export const attachMetaSelectors = (
79
+ store: Store = IMPLICIT.STORE
80
+ ): ReadonlySelectorToken<SelectorTokenIndex> => {
81
+ const readonlySelectorTokenIndexState__INTERNAL = atom<SelectorTokenIndex>({
82
+ key: `👁‍🗨_selector_token_index__INTERNAL`,
83
+ default: () =>
84
+ Object.assign(
85
+ [...store.readonlySelectors].reduce<SelectorTokenIndex>((acc, [key]) => {
86
+ acc[key] = { key, type: `readonly_selector` }
87
+ return acc
88
+ }, {}),
89
+ [...store.selectors].reduce<SelectorTokenIndex>((acc, [key]) => {
90
+ acc[key] = { key, type: `selector` }
91
+ return acc
92
+ }, {})
93
+ ),
94
+ effects: [
95
+ ({ setSelf }) => {
96
+ store.subject.selectorCreation.subscribe((selectorToken) => {
97
+ if (store.operation.open) {
98
+ return
99
+ }
100
+ setSelf((state) => {
101
+ const { key, family } = selectorToken
102
+ if (family) {
103
+ const { key: familyKey, subKey } = family
104
+ const current = state[familyKey]
105
+ if (current === undefined || `familyMembers` in current) {
106
+ const familyKeyState = current || {
107
+ key: familyKey,
108
+ familyMembers: {},
109
+ }
110
+ return {
111
+ ...state,
112
+ [familyKey]: {
113
+ ...familyKeyState,
114
+ familyMembers: {
115
+ ...familyKeyState.familyMembers,
116
+ [subKey]: selectorToken,
117
+ },
118
+ },
119
+ }
120
+ }
121
+ }
122
+ return {
123
+ ...state,
124
+ [key]: selectorToken,
125
+ }
126
+ })
127
+ })
128
+ },
129
+ ],
130
+ })
131
+ return selector({
132
+ key: `👁‍🗨_selector_token_index`,
133
+ get: ({ get }) => get(readonlySelectorTokenIndexState__INTERNAL),
134
+ })
135
+ }
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1 @@
1
+ export {}
@@ -22,7 +22,6 @@ export type OperationProgress =
22
22
  export const openOperation = (token: StateToken<any>, store: Store): void => {
23
23
  const core = target(store)
24
24
  if (core.operation.open) {
25
- console.warn(core.operation.open)
26
25
  store.config.logger?.error(
27
26
  `❌ failed to setState to "${token.key}" during a setState for "${core.operation.token.key}"`
28
27
  )
@@ -18,7 +18,7 @@ import type {
18
18
  AtomToken,
19
19
  FamilyMetadata,
20
20
  ReadonlySelectorOptions,
21
- ReadonlyValueToken,
21
+ ReadonlySelectorToken,
22
22
  SelectorOptions,
23
23
  SelectorToken,
24
24
  StateToken,
@@ -46,7 +46,7 @@ export const lookupSelectorSources = (
46
46
  store: Store
47
47
  ): (
48
48
  | AtomToken<unknown>
49
- | ReadonlyValueToken<unknown>
49
+ | ReadonlySelectorToken<unknown>
50
50
  | SelectorToken<unknown>
51
51
  )[] =>
52
52
  target(store)
@@ -56,7 +56,7 @@ export const lookupSelectorSources = (
56
56
 
57
57
  export const traceSelectorAtoms = (
58
58
  selectorKey: string,
59
- dependency: ReadonlyValueToken<unknown> | StateToken<unknown>,
59
+ dependency: ReadonlySelectorToken<unknown> | StateToken<unknown>,
60
60
  store: Store
61
61
  ): AtomToken<unknown>[] => {
62
62
  const roots: AtomToken<unknown>[] = []
@@ -97,12 +97,15 @@ export const traceAllSelectorAtoms = (
97
97
 
98
98
  export const updateSelectorAtoms = (
99
99
  selectorKey: string,
100
- dependency: ReadonlyValueToken<unknown> | StateToken<unknown>,
100
+ dependency: ReadonlySelectorToken<unknown> | StateToken<unknown>,
101
101
  store: Store
102
102
  ): void => {
103
103
  const core = target(store)
104
104
  if (dependency.type === `atom`) {
105
- core.selectorAtoms = core.selectorAtoms.set(selectorKey, dependency.key)
105
+ core.selectorAtoms = core.selectorAtoms.set({
106
+ selectorKey,
107
+ atomKey: dependency.key,
108
+ })
106
109
  store.config.logger?.info(
107
110
  ` || adding root for "${selectorKey}": ${dependency.key}`
108
111
  )
@@ -114,7 +117,10 @@ export const updateSelectorAtoms = (
114
117
  roots.map((r) => r.key)
115
118
  )
116
119
  for (const root of roots) {
117
- core.selectorAtoms = core.selectorAtoms.set(selectorKey, root.key)
120
+ core.selectorAtoms = core.selectorAtoms.set({
121
+ selectorKey,
122
+ atomKey: root.key,
123
+ })
118
124
  }
119
125
  }
120
126
 
@@ -142,9 +148,12 @@ export const registerSelector = (
142
148
  dependencyValue,
143
149
  `)`
144
150
  )
145
- core.selectorGraph = core.selectorGraph.set(selectorKey, dependency.key, {
146
- source: dependency.key,
147
- })
151
+ core.selectorGraph = core.selectorGraph.set(
152
+ { from: dependency.key, to: selectorKey },
153
+ {
154
+ source: dependency.key,
155
+ }
156
+ )
148
157
  }
149
158
  updateSelectorAtoms(selectorKey, dependency, store)
150
159
  return dependencyValue
@@ -164,12 +173,12 @@ export function selector__INTERNAL<T>(
164
173
  options: ReadonlySelectorOptions<T>,
165
174
  family?: FamilyMetadata,
166
175
  store?: Store
167
- ): ReadonlyValueToken<T>
176
+ ): ReadonlySelectorToken<T>
168
177
  export function selector__INTERNAL<T>(
169
178
  options: ReadonlySelectorOptions<T> | SelectorOptions<T>,
170
179
  family?: FamilyMetadata,
171
180
  store: Store = IMPLICIT.STORE
172
- ): ReadonlyValueToken<T> | SelectorToken<T> {
181
+ ): ReadonlySelectorToken<T> | SelectorToken<T> {
173
182
  const core = target(store)
174
183
  if (HAMT.has(options.key, core.selectors)) {
175
184
  store.config.logger?.error(
@@ -200,7 +209,13 @@ export function selector__INTERNAL<T>(
200
209
  )
201
210
  const initialValue = getSelf()
202
211
  store.config.logger?.info(` ✨ "${options.key}" =`, initialValue)
203
- return { ...readonlySelector, type: `readonly_selector` }
212
+ const token: ReadonlySelectorToken<T> = {
213
+ key: options.key,
214
+ type: `readonly_selector`,
215
+ family,
216
+ }
217
+ store.subject.selectorCreation.next(token)
218
+ return token
204
219
  }
205
220
  const setSelf = (next: T | ((oldValue: T) => T)): void => {
206
221
  store.config.logger?.info(` <- "${options.key}" became`, next)
@@ -224,5 +239,11 @@ export function selector__INTERNAL<T>(
224
239
  core.selectors = HAMT.set(options.key, mySelector, core.selectors)
225
240
  const initialValue = getSelf()
226
241
  store.config.logger?.info(` ✨ "${options.key}" =`, initialValue)
227
- return { ...mySelector, type: `selector` }
242
+ const token: SelectorToken<T> = {
243
+ key: options.key,
244
+ type: `selector`,
245
+ family,
246
+ }
247
+ store.subject.selectorCreation.next(token)
248
+ return token
228
249
  }
@@ -1,5 +1,6 @@
1
1
  import type { Hamt } from "hamt_plus"
2
2
  import HAMT from "hamt_plus"
3
+ import * as Rx from "rxjs"
3
4
 
4
5
  import { doNothing } from "~/packages/anvl/src/function"
5
6
  import { Join } from "~/packages/anvl/src/join"
@@ -13,7 +14,16 @@ import type {
13
14
  Timeline,
14
15
  TimelineData,
15
16
  } from "."
16
- import type { Logger, Transaction, ƒn } from ".."
17
+ import type {
18
+ AtomToken,
19
+ Logger,
20
+ ReadonlySelectorToken,
21
+ SelectorToken,
22
+ TimelineToken,
23
+ Transaction,
24
+ TransactionToken,
25
+ ƒn,
26
+ } from ".."
17
27
 
18
28
  export type StoreCore = Pick<
19
29
  Store,
@@ -34,15 +44,24 @@ export interface Store {
34
44
  atoms: Hamt<Atom<any>, string>
35
45
  atomsThatAreDefault: Set<string>
36
46
  readonlySelectors: Hamt<ReadonlySelector<any>, string>
37
- selectorAtoms: Join
47
+ selectorAtoms: Join<null, `selectorKey`, `atomKey`>
38
48
  selectorGraph: Join<{ source: string }>
39
49
  selectors: Hamt<Selector<any>, string>
40
50
  timelines: Hamt<Timeline, string>
41
- timelineAtoms: Join
51
+ timelineAtoms: Join<null, `timelineKey`, `atomKey`>
42
52
  timelineStore: Hamt<TimelineData, string>
43
53
  transactions: Hamt<Transaction<any>, string>
44
54
  valueMap: Hamt<any, string>
45
55
 
56
+ subject: {
57
+ atomCreation: Rx.Subject<AtomToken<unknown>>
58
+ selectorCreation: Rx.Subject<
59
+ ReadonlySelectorToken<unknown> | SelectorToken<unknown>
60
+ >
61
+ transactionCreation: Rx.Subject<TransactionToken<unknown>>
62
+ timelineCreation: Rx.Subject<TimelineToken>
63
+ }
64
+
46
65
  operation: OperationProgress
47
66
  transactionStatus: TransactionStatus<ƒn>
48
67
  config: {
@@ -57,15 +76,26 @@ export const createStore = (name: string): Store =>
57
76
  atoms: HAMT.make<Atom<any>, string>(),
58
77
  atomsThatAreDefault: new Set(),
59
78
  readonlySelectors: HAMT.make<ReadonlySelector<any>, string>(),
60
- selectorAtoms: new Join({ relationType: `n:n` }),
79
+ selectorAtoms: new Join({ relationType: `n:n` })
80
+ .from(`selectorKey`)
81
+ .to(`atomKey`),
61
82
  selectorGraph: new Join({ relationType: `n:n` }),
62
83
  selectors: HAMT.make<Selector<any>, string>(),
63
84
  timelines: HAMT.make<Timeline, string>(),
64
- timelineAtoms: new Join({ relationType: `1:n` }),
85
+ timelineAtoms: new Join({ relationType: `1:n` })
86
+ .from(`timelineKey`)
87
+ .to(`atomKey`),
65
88
  timelineStore: HAMT.make<TimelineData, string>(),
66
89
  transactions: HAMT.make<Transaction<any>, string>(),
67
90
  valueMap: HAMT.make<any, string>(),
68
91
 
92
+ subject: {
93
+ atomCreation: new Rx.Subject(),
94
+ selectorCreation: new Rx.Subject(),
95
+ transactionCreation: new Rx.Subject(),
96
+ timelineCreation: new Rx.Subject(),
97
+ },
98
+
69
99
  operation: {
70
100
  open: false,
71
101
  },
@@ -0,0 +1,89 @@
1
+ import type { Store } from "."
2
+ import { IMPLICIT } from "."
3
+ import { setState } from ".."
4
+ import type { TimelineToken } from ".."
5
+
6
+ export const redo__INTERNAL = (
7
+ token: TimelineToken,
8
+ store: Store = IMPLICIT.STORE
9
+ ): void => {
10
+ store.config.logger?.info(`⏩ redo "${token.key}"`)
11
+ const timelineData = store.timelineStore.get(token.key)
12
+ if (!timelineData) {
13
+ store.config.logger?.error(
14
+ `Failed to redo on timeline "${token.key}". This timeline has not been initialized.`
15
+ )
16
+ return
17
+ }
18
+ if (timelineData.at === timelineData.history.length) {
19
+ store.config.logger?.warn(
20
+ `Failed to redo at the end of timeline "${token.key}". There is nothing to redo.`
21
+ )
22
+ return
23
+ }
24
+ timelineData.timeTraveling = true
25
+ const update = timelineData.history[timelineData.at]
26
+ switch (update.type) {
27
+ case `atom_update`: {
28
+ const { key, newValue } = update
29
+ setState({ key, type: `atom` }, newValue)
30
+ break
31
+ }
32
+ case `selector_update`:
33
+ case `transaction_update`: {
34
+ for (const atomUpdate of update.atomUpdates) {
35
+ const { key, newValue } = atomUpdate
36
+ setState({ key, type: `atom` }, newValue)
37
+ }
38
+ break
39
+ }
40
+ }
41
+ ++timelineData.at
42
+ timelineData.timeTraveling = false
43
+ store.config.logger?.info(
44
+ `⏹️ "${token.key}" is now at ${timelineData.at} / ${timelineData.history.length}`
45
+ )
46
+ }
47
+
48
+ export const undo__INTERNAL = (
49
+ token: TimelineToken,
50
+ store: Store = IMPLICIT.STORE
51
+ ): void => {
52
+ store.config.logger?.info(`⏪ undo "${token.key}"`)
53
+ const timelineData = store.timelineStore.get(token.key)
54
+ if (!timelineData) {
55
+ store.config.logger?.error(
56
+ `Failed to undo on timeline "${token.key}". This timeline has not been initialized.`
57
+ )
58
+ return
59
+ }
60
+ if (timelineData.at === 0) {
61
+ store.config.logger?.warn(
62
+ `Failed to undo at the beginning of timeline "${token.key}". There is nothing to undo.`
63
+ )
64
+ return
65
+ }
66
+ timelineData.timeTraveling = true
67
+
68
+ --timelineData.at
69
+ const update = timelineData.history[timelineData.at]
70
+ switch (update.type) {
71
+ case `atom_update`: {
72
+ const { key, oldValue } = update
73
+ setState({ key, type: `atom` }, oldValue)
74
+ break
75
+ }
76
+ case `selector_update`:
77
+ case `transaction_update`: {
78
+ for (const atomUpdate of update.atomUpdates) {
79
+ const { key, oldValue } = atomUpdate
80
+ setState({ key, type: `atom` }, oldValue)
81
+ }
82
+ break
83
+ }
84
+ }
85
+ timelineData.timeTraveling = false
86
+ store.config.logger?.info(
87
+ `⏹️ "${token.key}" is now at ${timelineData.at} / ${timelineData.history.length}`
88
+ )
89
+ }
@@ -1,10 +1,7 @@
1
- /* eslint-disable max-lines */
2
-
3
1
  import HAMT from "hamt_plus"
4
2
 
5
3
  import type { KeyedStateUpdate, TransactionUpdate, Store } from "."
6
4
  import { target, IMPLICIT, withdraw } from "."
7
- import { setState } from ".."
8
5
  import type { AtomToken, TimelineOptions, TimelineToken, ƒn } from ".."
9
6
 
10
7
  export type Timeline = {
@@ -34,19 +31,20 @@ export type TimelineData = {
34
31
  | TimelineSelectorUpdate
35
32
  | TimelineTransactionUpdate
36
33
  )[]
34
+ selectorTime: number | null
35
+ transactionKey: string | null
37
36
  }
38
37
 
39
38
  export function timeline__INTERNAL(
40
39
  options: TimelineOptions,
41
40
  store: Store = IMPLICIT.STORE
42
41
  ): TimelineToken {
43
- let incompleteSelectorTime: number | null = null
44
- // let selectorAtomUpdates: TimelineAtomUpdate[] = []
45
- let incompleteTransactionKey: string | null = null
46
42
  const timelineData: TimelineData = {
47
43
  at: 0,
48
44
  timeTraveling: false,
49
45
  history: [],
46
+ selectorTime: null,
47
+ transactionKey: null,
50
48
  }
51
49
 
52
50
  const subscribeToAtom = (token: AtomToken<any>) => {
@@ -71,8 +69,10 @@ export function timeline__INTERNAL(
71
69
  `->`,
72
70
  update.newValue,
73
71
  storeCurrentTransactionKey
74
- ? `) in "${storeCurrentTransactionKey}"`
75
- : `) independently`
72
+ ? `) in transaction "${storeCurrentTransactionKey}"`
73
+ : storeCurrentSelectorKey
74
+ ? `) in selector "${storeCurrentSelectorKey}"`
75
+ : `)`
76
76
  )
77
77
 
78
78
  if (
@@ -83,13 +83,13 @@ export function timeline__INTERNAL(
83
83
  { key: storeCurrentTransactionKey, type: `transaction` },
84
84
  store
85
85
  )
86
- if (incompleteTransactionKey !== storeCurrentTransactionKey) {
87
- if (incompleteTransactionKey) {
86
+ if (timelineData.transactionKey !== storeCurrentTransactionKey) {
87
+ if (timelineData.transactionKey) {
88
88
  store.config.logger?.error(
89
- `Timeline "${options.key}" was unable to resolve transaction "${incompleteTransactionKey}. This is probably a bug.`
89
+ `Timeline "${options.key}" was unable to resolve transaction "${timelineData.transactionKey}. This is probably a bug.`
90
90
  )
91
91
  }
92
- incompleteTransactionKey = storeCurrentTransactionKey
92
+ timelineData.transactionKey = storeCurrentTransactionKey
93
93
  const subscription = currentTransaction.subject.subscribe((update) => {
94
94
  if (timelineData.timeTraveling === false) {
95
95
  if (timelineData.at !== timelineData.history.length) {
@@ -105,7 +105,7 @@ export function timeline__INTERNAL(
105
105
  }
106
106
  timelineData.at = timelineData.history.length
107
107
  subscription.unsubscribe()
108
- incompleteTransactionKey = null
108
+ timelineData.transactionKey = null
109
109
  store.config.logger?.info(
110
110
  `⌛ timeline "${options.key}" got a transaction_update "${update.key}"`
111
111
  )
@@ -113,7 +113,7 @@ export function timeline__INTERNAL(
113
113
  }
114
114
  } else if (storeCurrentSelectorKey) {
115
115
  if (timelineData.timeTraveling === false) {
116
- if (storeCurrentSelectorTime !== incompleteSelectorTime) {
116
+ if (storeCurrentSelectorTime !== timelineData.selectorTime) {
117
117
  const newSelectorUpdate: TimelineSelectorUpdate = {
118
118
  type: `selector_update`,
119
119
  key: storeCurrentSelectorKey,
@@ -134,7 +134,7 @@ export function timeline__INTERNAL(
134
134
  newSelectorUpdate.atomUpdates.map((atomUpdate) => atomUpdate.key)
135
135
  )
136
136
  timelineData.at = timelineData.history.length
137
- incompleteSelectorTime = storeCurrentSelectorTime
137
+ timelineData.selectorTime = storeCurrentSelectorTime
138
138
  } else {
139
139
  const latestUpdate = timelineData.history.at(-1)
140
140
  if (latestUpdate?.type === `selector_update`) {
@@ -152,7 +152,7 @@ export function timeline__INTERNAL(
152
152
  }
153
153
  } else {
154
154
  if (timelineData.timeTraveling === false) {
155
- incompleteSelectorTime = null
155
+ timelineData.selectorTime = null
156
156
  if (timelineData.at !== timelineData.history.length) {
157
157
  timelineData.history.splice(timelineData.at)
158
158
  }
@@ -197,97 +197,17 @@ export function timeline__INTERNAL(
197
197
  }
198
198
  subscribeToAtom(token)
199
199
  }
200
- core.timelineAtoms = core.timelineAtoms.set(tokenOrFamily.key, options.key)
200
+ core.timelineAtoms = core.timelineAtoms.set({
201
+ atomKey: tokenOrFamily.key,
202
+ timelineKey: options.key,
203
+ })
201
204
  }
202
205
 
203
206
  store.timelineStore = HAMT.set(options.key, timelineData, store.timelineStore)
204
- return {
207
+ const token: TimelineToken = {
205
208
  key: options.key,
206
209
  type: `timeline`,
207
210
  }
208
- }
209
-
210
- export const redo__INTERNAL = (
211
- token: TimelineToken,
212
- store: Store = IMPLICIT.STORE
213
- ): void => {
214
- store.config.logger?.info(`⏩ redo "${token.key}"`)
215
- const timelineData = store.timelineStore.get(token.key)
216
- if (!timelineData) {
217
- store.config.logger?.error(
218
- `Failed to redo on timeline "${token.key}". This timeline has not been initialized.`
219
- )
220
- return
221
- }
222
- if (timelineData.at === timelineData.history.length) {
223
- store.config.logger?.warn(
224
- `Failed to redo at the end of timeline "${token.key}". There is nothing to redo.`
225
- )
226
- return
227
- }
228
- timelineData.timeTraveling = true
229
- const update = timelineData.history[timelineData.at]
230
- switch (update.type) {
231
- case `atom_update`: {
232
- const { key, newValue } = update
233
- setState({ key, type: `atom` }, newValue)
234
- break
235
- }
236
- case `selector_update`:
237
- case `transaction_update`: {
238
- for (const atomUpdate of update.atomUpdates) {
239
- const { key, newValue } = atomUpdate
240
- setState({ key, type: `atom` }, newValue)
241
- }
242
- break
243
- }
244
- }
245
- ++timelineData.at
246
- timelineData.timeTraveling = false
247
- store.config.logger?.info(
248
- `⏹️ "${token.key}" is now at ${timelineData.at} / ${timelineData.history.length}`
249
- )
250
- }
251
-
252
- export const undo__INTERNAL = (
253
- token: TimelineToken,
254
- store: Store = IMPLICIT.STORE
255
- ): void => {
256
- store.config.logger?.info(`⏪ undo "${token.key}"`)
257
- const timelineData = store.timelineStore.get(token.key)
258
- if (!timelineData) {
259
- store.config.logger?.error(
260
- `Failed to undo on timeline "${token.key}". This timeline has not been initialized.`
261
- )
262
- return
263
- }
264
- if (timelineData.at === 0) {
265
- store.config.logger?.warn(
266
- `Failed to undo at the beginning of timeline "${token.key}". There is nothing to undo.`
267
- )
268
- return
269
- }
270
- timelineData.timeTraveling = true
271
-
272
- --timelineData.at
273
- const update = timelineData.history[timelineData.at]
274
- switch (update.type) {
275
- case `atom_update`: {
276
- const { key, oldValue } = update
277
- setState({ key, type: `atom` }, oldValue)
278
- break
279
- }
280
- case `selector_update`:
281
- case `transaction_update`: {
282
- for (const atomUpdate of update.atomUpdates) {
283
- const { key, oldValue } = atomUpdate
284
- setState({ key, type: `atom` }, oldValue)
285
- }
286
- break
287
- }
288
- }
289
- timelineData.timeTraveling = false
290
- store.config.logger?.info(
291
- `⏹️ "${token.key}" is now at ${timelineData.at} / ${timelineData.history.length}`
292
- )
211
+ store.subject.timelineCreation.next(token)
212
+ return token
293
213
  }
@@ -2,7 +2,7 @@ import HAMT from "hamt_plus"
2
2
  import * as Rx from "rxjs"
3
3
 
4
4
  import type { Store, StoreCore } from "."
5
- import { deposit, withdraw, IMPLICIT } from "."
5
+ import { cacheValue, deposit, withdraw, IMPLICIT } from "."
6
6
  import { getState, setState } from ".."
7
7
  import type {
8
8
  AtomToken,
@@ -75,14 +75,22 @@ export const applyTransaction = <ƒ extends ƒn>(
75
75
  return
76
76
  }
77
77
  store.config.logger?.info(
78
- ` ▶️ apply transaction "${store.transactionStatus.key}" (init)`
78
+ `🛃 apply transaction "${store.transactionStatus.key}"`
79
79
  )
80
80
  store.transactionStatus.phase = `applying`
81
81
  store.transactionStatus.output = output
82
82
  const { atomUpdates } = store.transactionStatus
83
- for (const { key, oldValue, newValue } of atomUpdates) {
83
+
84
+ for (const { key, newValue } of atomUpdates) {
84
85
  const token: AtomToken<unknown> = { key, type: `atom` }
86
+ if (!HAMT.has(token.key, store.valueMap)) {
87
+ const atom = HAMT.get(token.key, store.transactionStatus.core.atoms)
88
+ store.atoms = HAMT.set(atom.key, atom, store.atoms)
89
+ store.valueMap = HAMT.set(atom.key, atom.default, store.valueMap)
90
+ store.config.logger?.info(`🔧`, `add atom "${atom.key}"`)
91
+ }
85
92
  const state = withdraw(token, store)
93
+
86
94
  setState(state, newValue, store)
87
95
  }
88
96
  const myTransaction = withdraw<ƒ>(
@@ -103,7 +111,7 @@ export const undoTransactionUpdate = <ƒ extends ƒn>(
103
111
  store: Store
104
112
  ): void => {
105
113
  store.config.logger?.info(` ⏮ undo transaction "${update.key}" (undo)`)
106
- for (const { key, oldValue, newValue } of update.atomUpdates) {
114
+ for (const { key, oldValue } of update.atomUpdates) {
107
115
  const token: AtomToken<unknown> = { key, type: `atom` }
108
116
  const state = withdraw(token, store)
109
117
  setState(state, oldValue, store)
@@ -114,7 +122,7 @@ export const redoTransactionUpdate = <ƒ extends ƒn>(
114
122
  store: Store
115
123
  ): void => {
116
124
  store.config.logger?.info(` ⏭ redo transaction "${update.key}" (redo)`)
117
- for (const { key, oldValue, newValue } of update.atomUpdates) {
125
+ for (const { key, newValue } of update.atomUpdates) {
118
126
  const token: AtomToken<unknown> = { key, type: `atom` }
119
127
  const state = withdraw(token, store)
120
128
  setState(state, newValue, store)
@@ -166,6 +174,7 @@ export function transaction__INTERNAL<ƒ extends ƒn>(
166
174
  core.transactions
167
175
  )
168
176
  const token = deposit(newTransaction)
177
+ store.subject.transactionCreation.next(token)
169
178
  return token
170
179
  }
171
180