atom.io 0.3.1 → 0.4.0
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 +11 -3
- package/dist/index.d.ts +83 -30
- package/dist/index.js +427 -230
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +427 -230
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -5
- package/react/dist/index.d.ts +12 -17
- package/react/dist/index.js +25 -34
- package/react/dist/index.js.map +1 -1
- package/react/dist/index.mjs +21 -34
- package/react/dist/index.mjs.map +1 -1
- package/react-devtools/dist/index.css +26 -0
- package/react-devtools/dist/index.css.map +1 -0
- package/react-devtools/dist/index.d.ts +15 -0
- package/react-devtools/dist/index.js +1579 -0
- package/react-devtools/dist/index.js.map +1 -0
- package/react-devtools/dist/index.mjs +1551 -0
- package/react-devtools/dist/index.mjs.map +1 -0
- package/react-devtools/package.json +15 -0
- package/src/index.ts +3 -3
- package/src/internal/atom-internal.ts +10 -5
- package/src/internal/families-internal.ts +4 -4
- package/src/internal/get.ts +8 -8
- package/src/internal/index.ts +2 -0
- package/src/internal/meta/attach-meta.ts +17 -0
- package/src/internal/meta/index.ts +4 -0
- package/src/internal/meta/meta-state.ts +135 -0
- package/src/internal/meta/meta-timelines.ts +1 -0
- package/src/internal/meta/meta-transactions.ts +1 -0
- package/src/internal/operation.ts +0 -1
- package/src/internal/selector-internal.ts +34 -13
- package/src/internal/store.ts +35 -5
- package/src/internal/time-travel-internal.ts +89 -0
- package/src/internal/timeline-internal.ts +23 -103
- package/src/internal/transaction-internal.ts +14 -5
- package/src/react/index.ts +28 -46
- package/src/react-devtools/AtomIODevtools.tsx +107 -0
- package/src/react-devtools/StateEditor.tsx +73 -0
- package/src/react-devtools/TokenList.tsx +49 -0
- package/src/react-devtools/devtools.scss +130 -0
- package/src/react-devtools/index.ts +1 -0
- package/src/react-explorer/AtomIOExplorer.tsx +208 -0
- package/src/react-explorer/explorer-effects.ts +20 -0
- package/src/react-explorer/explorer-states.ts +224 -0
- package/src/react-explorer/index.ts +23 -0
- package/src/react-explorer/space-states.ts +73 -0
- package/src/react-explorer/view-states.ts +43 -0
- package/src/selector.ts +6 -6
- package/src/subscribe.ts +2 -2
- package/src/timeline.ts +1 -5
- package/src/transaction.ts +2 -2
- package/src/web-effects/index.ts +1 -0
- 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
|
-
|
|
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
|
-
|
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
146
|
-
|
|
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
|
-
):
|
|
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
|
-
):
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/internal/store.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
:
|
|
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 (
|
|
87
|
-
if (
|
|
86
|
+
if (timelineData.transactionKey !== storeCurrentTransactionKey) {
|
|
87
|
+
if (timelineData.transactionKey) {
|
|
88
88
|
store.config.logger?.error(
|
|
89
|
-
`Timeline "${options.key}" was unable to resolve transaction "${
|
|
89
|
+
`Timeline "${options.key}" was unable to resolve transaction "${timelineData.transactionKey}. This is probably a bug.`
|
|
90
90
|
)
|
|
91
91
|
}
|
|
92
|
-
|
|
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
|
-
|
|
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 !==
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|