atom.io 0.2.0 → 0.3.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.
- package/README.md +8 -15
- package/dist/index.d.ts +507 -4
- package/dist/index.js +924 -368
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +913 -364
- package/dist/index.mjs.map +1 -1
- package/package.json +24 -6
- package/{dist/react → react/dist}/index.d.ts +2 -5
- package/react/dist/index.js +68 -0
- package/react/dist/index.js.map +1 -0
- package/react/dist/index.mjs +44 -0
- package/react/dist/index.mjs.map +1 -0
- package/react/package.json +12 -3
- package/src/atom.ts +18 -53
- package/src/index.ts +29 -37
- package/src/internal/atom-internal.ts +50 -0
- package/src/internal/families-internal.ts +142 -0
- package/src/internal/get.ts +41 -43
- package/src/internal/index.ts +5 -17
- package/src/internal/is-default.ts +20 -4
- package/src/internal/operation.ts +111 -16
- package/src/internal/selector-internal.ts +116 -15
- package/src/internal/set.ts +31 -17
- package/src/internal/store.ts +57 -45
- package/src/internal/subscribe-internal.ts +55 -11
- package/src/internal/timeline-internal.ts +293 -0
- package/src/internal/transaction-internal.ts +157 -16
- package/src/logger.ts +46 -0
- package/src/react/index.ts +5 -6
- package/src/selector.ts +34 -104
- package/src/subscribe.ts +55 -0
- package/src/timeline.ts +29 -0
- package/src/transaction.ts +29 -36
- package/dist/index-9d9f5a05.d.ts +0 -293
- package/dist/react/index.js +0 -909
- package/dist/react/index.js.map +0 -1
- package/dist/react/index.mjs +0 -880
- package/dist/react/index.mjs.map +0 -1
|
@@ -1,14 +1,58 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getState__INTERNAL,
|
|
3
|
+
withdraw,
|
|
4
|
+
recallState,
|
|
5
|
+
traceAllSelectorAtoms,
|
|
6
|
+
} from "."
|
|
7
|
+
import type { Atom, ReadonlySelector, Selector, Store } from "."
|
|
8
|
+
import type { StateUpdate } from ".."
|
|
8
9
|
|
|
9
|
-
export const
|
|
10
|
+
export const prepareUpdate = <T>(
|
|
10
11
|
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
11
|
-
store: Store
|
|
12
|
+
store: Store
|
|
13
|
+
): StateUpdate<T> => {
|
|
14
|
+
const oldValue = recallState(state, store)
|
|
15
|
+
const newValue = getState__INTERNAL(state, store)
|
|
16
|
+
return { newValue, oldValue }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const stowUpdate = <T>(
|
|
20
|
+
state: Atom<T>,
|
|
21
|
+
update: StateUpdate<T>,
|
|
22
|
+
store: Store
|
|
23
|
+
): void => {
|
|
24
|
+
const { key } = state
|
|
25
|
+
const { logger } = store.config
|
|
26
|
+
if (store.transactionStatus.phase !== `building`) {
|
|
27
|
+
store.config.logger?.warn(
|
|
28
|
+
`stowUpdate called outside of a transaction. This is probably a bug.`
|
|
29
|
+
)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
store.transactionStatus.atomUpdates.push({ key, ...update })
|
|
33
|
+
logger?.info(`📝 ${key} stowed (`, update.oldValue, `->`, update.newValue, `)`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const emitUpdate = <T>(
|
|
37
|
+
state: Atom<T> | ReadonlySelector<T> | Selector<T>,
|
|
38
|
+
update: StateUpdate<T>,
|
|
39
|
+
store: Store
|
|
40
|
+
): void => {
|
|
41
|
+
const { key } = state
|
|
42
|
+
const { logger } = store.config
|
|
43
|
+
logger?.info(
|
|
44
|
+
`📢 ${state.type} "${key}" went (`,
|
|
45
|
+
update.oldValue,
|
|
46
|
+
`->`,
|
|
47
|
+
update.newValue,
|
|
48
|
+
`)`
|
|
49
|
+
)
|
|
50
|
+
state.subject.next(update)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const subscribeToRootAtoms = <T>(
|
|
54
|
+
state: ReadonlySelector<T> | Selector<T>,
|
|
55
|
+
store: Store
|
|
12
56
|
): { unsubscribe: () => void }[] | null => {
|
|
13
57
|
const dependencySubscriptions =
|
|
14
58
|
`default` in state
|
|
@@ -17,11 +61,11 @@ export const subscribeToRootAtoms = <T>(
|
|
|
17
61
|
const atom = withdraw(atomToken, store)
|
|
18
62
|
return atom.subject.subscribe((atomChange) => {
|
|
19
63
|
store.config.logger?.info(
|
|
20
|
-
`📢
|
|
64
|
+
`📢 selector "${state.key}" saw root "${atomToken.key}" go (`,
|
|
21
65
|
atomChange.oldValue,
|
|
22
66
|
`->`,
|
|
23
67
|
atomChange.newValue,
|
|
24
|
-
`)
|
|
68
|
+
`)`
|
|
25
69
|
)
|
|
26
70
|
const oldValue = recallState(state, store)
|
|
27
71
|
const newValue = getState__INTERNAL(state, store)
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
|
|
3
|
+
import HAMT from "hamt_plus"
|
|
4
|
+
|
|
5
|
+
import type { KeyedStateUpdate, TransactionUpdate, Store } from "."
|
|
6
|
+
import { target, IMPLICIT, withdraw } from "."
|
|
7
|
+
import { setState } from ".."
|
|
8
|
+
import type { AtomToken, TimelineOptions, TimelineToken, ƒn } from ".."
|
|
9
|
+
|
|
10
|
+
export type Timeline = {
|
|
11
|
+
key: string
|
|
12
|
+
type: `timeline`
|
|
13
|
+
next: () => void
|
|
14
|
+
prev: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type TimelineAtomUpdate = KeyedStateUpdate<unknown> & {
|
|
18
|
+
type: `atom_update`
|
|
19
|
+
}
|
|
20
|
+
export type TimelineSelectorUpdate = {
|
|
21
|
+
key: string
|
|
22
|
+
type: `selector_update`
|
|
23
|
+
atomUpdates: TimelineAtomUpdate[]
|
|
24
|
+
}
|
|
25
|
+
export type TimelineTransactionUpdate = TransactionUpdate<ƒn> & {
|
|
26
|
+
type: `transaction_update`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TimelineData = {
|
|
30
|
+
at: number
|
|
31
|
+
timeTraveling: boolean
|
|
32
|
+
history: (
|
|
33
|
+
| TimelineAtomUpdate
|
|
34
|
+
| TimelineSelectorUpdate
|
|
35
|
+
| TimelineTransactionUpdate
|
|
36
|
+
)[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function timeline__INTERNAL(
|
|
40
|
+
options: TimelineOptions,
|
|
41
|
+
store: Store = IMPLICIT.STORE
|
|
42
|
+
): TimelineToken {
|
|
43
|
+
let incompleteSelectorTime: number | null = null
|
|
44
|
+
// let selectorAtomUpdates: TimelineAtomUpdate[] = []
|
|
45
|
+
let incompleteTransactionKey: string | null = null
|
|
46
|
+
const timelineData: TimelineData = {
|
|
47
|
+
at: 0,
|
|
48
|
+
timeTraveling: false,
|
|
49
|
+
history: [],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const subscribeToAtom = (token: AtomToken<any>) => {
|
|
53
|
+
const state = withdraw(token, store)
|
|
54
|
+
state.subject.subscribe((update) => {
|
|
55
|
+
const storeCurrentSelectorKey =
|
|
56
|
+
store.operation.open && store.operation.token.type === `selector`
|
|
57
|
+
? store.operation.token.key
|
|
58
|
+
: null
|
|
59
|
+
const storeCurrentSelectorTime =
|
|
60
|
+
store.operation.open && store.operation.token.type === `selector`
|
|
61
|
+
? store.operation.time
|
|
62
|
+
: null
|
|
63
|
+
|
|
64
|
+
const storeCurrentTransactionKey =
|
|
65
|
+
store.transactionStatus.phase === `applying`
|
|
66
|
+
? store.transactionStatus.key
|
|
67
|
+
: null
|
|
68
|
+
store.config.logger?.info(
|
|
69
|
+
`⏳ timeline "${options.key}" saw atom "${token.key}" go (`,
|
|
70
|
+
update.oldValue,
|
|
71
|
+
`->`,
|
|
72
|
+
update.newValue,
|
|
73
|
+
storeCurrentTransactionKey
|
|
74
|
+
? `) in "${storeCurrentTransactionKey}"`
|
|
75
|
+
: `) independently`
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
storeCurrentTransactionKey &&
|
|
80
|
+
store.transactionStatus.phase === `applying`
|
|
81
|
+
) {
|
|
82
|
+
const currentTransaction = withdraw(
|
|
83
|
+
{ key: storeCurrentTransactionKey, type: `transaction` },
|
|
84
|
+
store
|
|
85
|
+
)
|
|
86
|
+
if (incompleteTransactionKey !== storeCurrentTransactionKey) {
|
|
87
|
+
if (incompleteTransactionKey) {
|
|
88
|
+
store.config.logger?.error(
|
|
89
|
+
`Timeline "${options.key}" was unable to resolve transaction "${incompleteTransactionKey}. This is probably a bug.`
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
incompleteTransactionKey = storeCurrentTransactionKey
|
|
93
|
+
const subscription = currentTransaction.subject.subscribe((update) => {
|
|
94
|
+
if (timelineData.timeTraveling === false) {
|
|
95
|
+
if (timelineData.at !== timelineData.history.length) {
|
|
96
|
+
timelineData.history.splice(timelineData.at)
|
|
97
|
+
}
|
|
98
|
+
timelineData.history.push({
|
|
99
|
+
type: `transaction_update`,
|
|
100
|
+
...update,
|
|
101
|
+
atomUpdates: update.atomUpdates.filter((atomUpdate) =>
|
|
102
|
+
options.atoms.some((atom) => atom.key === atomUpdate.key)
|
|
103
|
+
),
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
timelineData.at = timelineData.history.length
|
|
107
|
+
subscription.unsubscribe()
|
|
108
|
+
incompleteTransactionKey = null
|
|
109
|
+
store.config.logger?.info(
|
|
110
|
+
`⌛ timeline "${options.key}" got a transaction_update "${update.key}"`
|
|
111
|
+
)
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
} else if (storeCurrentSelectorKey) {
|
|
115
|
+
if (timelineData.timeTraveling === false) {
|
|
116
|
+
if (storeCurrentSelectorTime !== incompleteSelectorTime) {
|
|
117
|
+
const newSelectorUpdate: TimelineSelectorUpdate = {
|
|
118
|
+
type: `selector_update`,
|
|
119
|
+
key: storeCurrentSelectorKey,
|
|
120
|
+
atomUpdates: [],
|
|
121
|
+
}
|
|
122
|
+
newSelectorUpdate.atomUpdates.push({
|
|
123
|
+
key: token.key,
|
|
124
|
+
type: `atom_update`,
|
|
125
|
+
...update,
|
|
126
|
+
})
|
|
127
|
+
if (timelineData.at !== timelineData.history.length) {
|
|
128
|
+
timelineData.history.splice(timelineData.at)
|
|
129
|
+
}
|
|
130
|
+
timelineData.history.push(newSelectorUpdate)
|
|
131
|
+
|
|
132
|
+
store.config.logger?.info(
|
|
133
|
+
`⌛ timeline "${options.key}" got a selector_update "${storeCurrentSelectorKey}" with`,
|
|
134
|
+
newSelectorUpdate.atomUpdates.map((atomUpdate) => atomUpdate.key)
|
|
135
|
+
)
|
|
136
|
+
timelineData.at = timelineData.history.length
|
|
137
|
+
incompleteSelectorTime = storeCurrentSelectorTime
|
|
138
|
+
} else {
|
|
139
|
+
const latestUpdate = timelineData.history.at(-1)
|
|
140
|
+
if (latestUpdate?.type === `selector_update`) {
|
|
141
|
+
latestUpdate.atomUpdates.push({
|
|
142
|
+
key: token.key,
|
|
143
|
+
type: `atom_update`,
|
|
144
|
+
...update,
|
|
145
|
+
})
|
|
146
|
+
store.config.logger?.info(
|
|
147
|
+
` ⌛ timeline "${options.key}" set selector_update "${storeCurrentSelectorKey}" to`,
|
|
148
|
+
latestUpdate?.atomUpdates.map((atomUpdate) => atomUpdate.key)
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
if (timelineData.timeTraveling === false) {
|
|
155
|
+
incompleteSelectorTime = null
|
|
156
|
+
if (timelineData.at !== timelineData.history.length) {
|
|
157
|
+
timelineData.history.splice(timelineData.at)
|
|
158
|
+
}
|
|
159
|
+
timelineData.history.push({
|
|
160
|
+
type: `atom_update`,
|
|
161
|
+
key: token.key,
|
|
162
|
+
oldValue: update.oldValue,
|
|
163
|
+
newValue: update.newValue,
|
|
164
|
+
})
|
|
165
|
+
store.config.logger?.info(
|
|
166
|
+
`⌛ timeline "${options.key}" got a state_update to "${token.key}"`
|
|
167
|
+
)
|
|
168
|
+
timelineData.at = timelineData.history.length
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
const core = target(store)
|
|
174
|
+
for (const tokenOrFamily of options.atoms) {
|
|
175
|
+
const timelineKey = core.timelineAtoms.getRelatedId(tokenOrFamily.key)
|
|
176
|
+
if (timelineKey) {
|
|
177
|
+
store.config.logger?.error(
|
|
178
|
+
`❌ Failed to add atom "${tokenOrFamily.key}" to timeline "${options.key}" because it belongs to timeline "${timelineKey}"`
|
|
179
|
+
)
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
if (tokenOrFamily.type === `atom_family`) {
|
|
183
|
+
const family = tokenOrFamily
|
|
184
|
+
family.subject.subscribe((token) => subscribeToAtom(token))
|
|
185
|
+
} else {
|
|
186
|
+
const token = tokenOrFamily
|
|
187
|
+
if (`family` in token && token.family) {
|
|
188
|
+
const familyTimelineKey = core.timelineAtoms.getRelatedId(
|
|
189
|
+
token.family.key
|
|
190
|
+
)
|
|
191
|
+
if (familyTimelineKey) {
|
|
192
|
+
store.config.logger?.error(
|
|
193
|
+
`❌ Failed to add atom "${token.key}" to timeline "${options.key}" because its family "${token.family.key}" belongs to timeline "${familyTimelineKey}"`
|
|
194
|
+
)
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
subscribeToAtom(token)
|
|
199
|
+
}
|
|
200
|
+
core.timelineAtoms = core.timelineAtoms.set(tokenOrFamily.key, options.key)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
store.timelineStore = HAMT.set(options.key, timelineData, store.timelineStore)
|
|
204
|
+
return {
|
|
205
|
+
key: options.key,
|
|
206
|
+
type: `timeline`,
|
|
207
|
+
}
|
|
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
|
+
)
|
|
293
|
+
}
|
|
@@ -1,34 +1,175 @@
|
|
|
1
|
-
import
|
|
1
|
+
import HAMT from "hamt_plus"
|
|
2
|
+
import * as Rx from "rxjs"
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import type { Store, StoreCore } from "."
|
|
5
|
+
import { deposit, withdraw, IMPLICIT } from "."
|
|
6
|
+
import { getState, setState } from ".."
|
|
7
|
+
import type {
|
|
8
|
+
AtomToken,
|
|
9
|
+
StateUpdate,
|
|
10
|
+
Transaction,
|
|
11
|
+
TransactionOptions,
|
|
12
|
+
TransactionToken,
|
|
13
|
+
ƒn,
|
|
14
|
+
} from ".."
|
|
15
|
+
|
|
16
|
+
export const TRANSACTION_PHASES = [`idle`, `building`, `applying`] as const
|
|
17
|
+
export type TransactionPhase = (typeof TRANSACTION_PHASES)[number]
|
|
18
|
+
|
|
19
|
+
export type KeyedStateUpdate<T> = StateUpdate<T> & {
|
|
20
|
+
key: string
|
|
21
|
+
}
|
|
22
|
+
export type TransactionUpdate<ƒ extends ƒn> = {
|
|
23
|
+
key: string
|
|
24
|
+
atomUpdates: KeyedStateUpdate<unknown>[]
|
|
25
|
+
params: Parameters<ƒ>
|
|
26
|
+
output: ReturnType<ƒ>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TransactionUpdateInProgress<ƒ extends ƒn> = TransactionUpdate<ƒ> & {
|
|
30
|
+
phase: `applying` | `building`
|
|
31
|
+
core: StoreCore
|
|
32
|
+
}
|
|
33
|
+
export type TransactionIdle = {
|
|
34
|
+
phase: `idle`
|
|
6
35
|
}
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
36
|
+
export type TransactionStatus<ƒ extends ƒn> =
|
|
37
|
+
| TransactionIdle
|
|
38
|
+
| TransactionUpdateInProgress<ƒ>
|
|
39
|
+
|
|
40
|
+
export const buildTransaction = (
|
|
41
|
+
key: string,
|
|
42
|
+
params: any[],
|
|
43
|
+
store: Store
|
|
44
|
+
): void => {
|
|
45
|
+
store.transactionStatus = {
|
|
46
|
+
key,
|
|
47
|
+
phase: `building`,
|
|
48
|
+
core: {
|
|
11
49
|
atoms: store.atoms,
|
|
50
|
+
atomsThatAreDefault: store.atomsThatAreDefault,
|
|
51
|
+
operation: { open: false },
|
|
12
52
|
readonlySelectors: store.readonlySelectors,
|
|
53
|
+
timelines: store.timelines,
|
|
54
|
+
timelineAtoms: store.timelineAtoms,
|
|
55
|
+
transactions: store.transactions,
|
|
56
|
+
selectorAtoms: store.selectorAtoms,
|
|
13
57
|
selectorGraph: store.selectorGraph,
|
|
14
58
|
selectors: store.selectors,
|
|
15
59
|
valueMap: store.valueMap,
|
|
16
60
|
},
|
|
61
|
+
atomUpdates: [],
|
|
62
|
+
params,
|
|
63
|
+
output: undefined,
|
|
64
|
+
}
|
|
65
|
+
store.config.logger?.info(`🛫`, `transaction "${key}" started`)
|
|
66
|
+
}
|
|
67
|
+
export const applyTransaction = <ƒ extends ƒn>(
|
|
68
|
+
output: ReturnType<ƒ>,
|
|
69
|
+
store: Store
|
|
70
|
+
): void => {
|
|
71
|
+
if (store.transactionStatus.phase !== `building`) {
|
|
72
|
+
store.config.logger?.warn(
|
|
73
|
+
`abortTransaction called outside of a transaction. This is probably a bug.`
|
|
74
|
+
)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
store.config.logger?.info(
|
|
78
|
+
` ▶️ apply transaction "${store.transactionStatus.key}" (init)`
|
|
79
|
+
)
|
|
80
|
+
store.transactionStatus.phase = `applying`
|
|
81
|
+
store.transactionStatus.output = output
|
|
82
|
+
const { atomUpdates } = store.transactionStatus
|
|
83
|
+
for (const { key, oldValue, newValue } of atomUpdates) {
|
|
84
|
+
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
85
|
+
const state = withdraw(token, store)
|
|
86
|
+
setState(state, newValue, store)
|
|
87
|
+
}
|
|
88
|
+
const myTransaction = withdraw<ƒ>(
|
|
89
|
+
{ key: store.transactionStatus.key, type: `transaction` },
|
|
90
|
+
store
|
|
91
|
+
)
|
|
92
|
+
myTransaction.subject.next({
|
|
93
|
+
key: store.transactionStatus.key,
|
|
94
|
+
atomUpdates,
|
|
95
|
+
output,
|
|
96
|
+
params: store.transactionStatus.params as Parameters<ƒ>,
|
|
97
|
+
})
|
|
98
|
+
store.transactionStatus = { phase: `idle` }
|
|
99
|
+
store.config.logger?.info(`🛬`, `transaction done`)
|
|
100
|
+
}
|
|
101
|
+
export const undoTransactionUpdate = <ƒ extends ƒn>(
|
|
102
|
+
update: TransactionUpdate<ƒ>,
|
|
103
|
+
store: Store
|
|
104
|
+
): void => {
|
|
105
|
+
store.config.logger?.info(` ⏮ undo transaction "${update.key}" (undo)`)
|
|
106
|
+
for (const { key, oldValue, newValue } of update.atomUpdates) {
|
|
107
|
+
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
108
|
+
const state = withdraw(token, store)
|
|
109
|
+
setState(state, oldValue, store)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export const redoTransactionUpdate = <ƒ extends ƒn>(
|
|
113
|
+
update: TransactionUpdate<ƒ>,
|
|
114
|
+
store: Store
|
|
115
|
+
): void => {
|
|
116
|
+
store.config.logger?.info(` ⏭ redo transaction "${update.key}" (redo)`)
|
|
117
|
+
for (const { key, oldValue, newValue } of update.atomUpdates) {
|
|
118
|
+
const token: AtomToken<unknown> = { key, type: `atom` }
|
|
119
|
+
const state = withdraw(token, store)
|
|
120
|
+
setState(state, newValue, store)
|
|
17
121
|
}
|
|
18
|
-
store.config.logger?.info(`🛫`, `transaction start`)
|
|
19
122
|
}
|
|
123
|
+
|
|
20
124
|
export const abortTransaction = (store: Store): void => {
|
|
21
|
-
if (
|
|
125
|
+
if (store.transactionStatus.phase === `idle`) {
|
|
22
126
|
store.config.logger?.warn(
|
|
23
127
|
`abortTransaction called outside of a transaction. This is probably a bug.`
|
|
24
128
|
)
|
|
25
129
|
return
|
|
26
130
|
}
|
|
27
|
-
store.
|
|
28
|
-
store.readonlySelectors = store.transaction.prev.readonlySelectors
|
|
29
|
-
store.selectorGraph = store.transaction.prev.selectorGraph
|
|
30
|
-
store.selectors = store.transaction.prev.selectors
|
|
31
|
-
store.valueMap = store.transaction.prev.valueMap
|
|
32
|
-
store.transaction = { open: false }
|
|
131
|
+
store.transactionStatus = { phase: `idle` }
|
|
33
132
|
store.config.logger?.info(`🪂`, `transaction fail`)
|
|
34
133
|
}
|
|
134
|
+
|
|
135
|
+
export function transaction__INTERNAL<ƒ extends ƒn>(
|
|
136
|
+
options: TransactionOptions<ƒ>,
|
|
137
|
+
store: Store = IMPLICIT.STORE
|
|
138
|
+
): TransactionToken<ƒ> {
|
|
139
|
+
const newTransaction: Transaction<ƒ> = {
|
|
140
|
+
key: options.key,
|
|
141
|
+
type: `transaction`,
|
|
142
|
+
run: (...params: Parameters<ƒ>) => {
|
|
143
|
+
buildTransaction(options.key, params, store)
|
|
144
|
+
try {
|
|
145
|
+
const output = options.do(
|
|
146
|
+
{
|
|
147
|
+
get: (token) => getState(token, store),
|
|
148
|
+
set: (token, value) => setState(token, value, store),
|
|
149
|
+
},
|
|
150
|
+
...params
|
|
151
|
+
)
|
|
152
|
+
applyTransaction(output, store)
|
|
153
|
+
return output
|
|
154
|
+
} catch (thrown) {
|
|
155
|
+
abortTransaction(store)
|
|
156
|
+
store.config.logger?.error(`Transaction ${options.key} failed`, thrown)
|
|
157
|
+
throw thrown
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
subject: new Rx.Subject(),
|
|
161
|
+
}
|
|
162
|
+
const core = target(store)
|
|
163
|
+
core.transactions = HAMT.set(
|
|
164
|
+
newTransaction.key,
|
|
165
|
+
newTransaction,
|
|
166
|
+
core.transactions
|
|
167
|
+
)
|
|
168
|
+
const token = deposit(newTransaction)
|
|
169
|
+
return token
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const target = (store: Store = IMPLICIT.STORE): StoreCore =>
|
|
173
|
+
store.transactionStatus.phase === `building`
|
|
174
|
+
? store.transactionStatus.core
|
|
175
|
+
: store
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { doNothing } from "~/packages/anvl/src/function"
|
|
2
|
+
|
|
3
|
+
import type { Store } from "./internal/store"
|
|
4
|
+
import { IMPLICIT } from "./internal/store"
|
|
5
|
+
|
|
6
|
+
export type Logger = Pick<Console, `error` | `info` | `warn`>
|
|
7
|
+
export const LOG_LEVELS: ReadonlyArray<keyof Logger> = [
|
|
8
|
+
`info`,
|
|
9
|
+
`warn`,
|
|
10
|
+
`error`,
|
|
11
|
+
] as const
|
|
12
|
+
|
|
13
|
+
export const setLogLevel = (
|
|
14
|
+
preferredLevel: `error` | `info` | `warn` | null,
|
|
15
|
+
store: Store = IMPLICIT.STORE
|
|
16
|
+
): void => {
|
|
17
|
+
const { logger__INTERNAL } = store.config
|
|
18
|
+
if (preferredLevel === null) {
|
|
19
|
+
store.config.logger = null
|
|
20
|
+
} else {
|
|
21
|
+
store.config.logger = { ...console }
|
|
22
|
+
LOG_LEVELS.forEach((logLevel) => {
|
|
23
|
+
if (LOG_LEVELS.indexOf(logLevel) < LOG_LEVELS.indexOf(preferredLevel)) {
|
|
24
|
+
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
|
25
|
+
store.config.logger![logLevel] = doNothing
|
|
26
|
+
} else {
|
|
27
|
+
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
|
28
|
+
store.config.logger![logLevel] = logger__INTERNAL[logLevel]
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const useLogger = (
|
|
35
|
+
logger: Logger,
|
|
36
|
+
store: Store = IMPLICIT.STORE
|
|
37
|
+
): void => {
|
|
38
|
+
const currentLogLevel =
|
|
39
|
+
store.config.logger === null
|
|
40
|
+
? null
|
|
41
|
+
: LOG_LEVELS.find(
|
|
42
|
+
(logLevel) => store.config.logger?.[logLevel] !== doNothing
|
|
43
|
+
) ?? null
|
|
44
|
+
store.config.logger__INTERNAL = { ...logger }
|
|
45
|
+
setLogLevel(currentLogLevel, store)
|
|
46
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -2,11 +2,10 @@ import type Preact from "preact/hooks"
|
|
|
2
2
|
|
|
3
3
|
import type React from "react"
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { subscribe, setState, __INTERNAL__ } from "atom.io"
|
|
6
|
+
import type { ReadonlyValueToken, StateToken } from "atom.io"
|
|
6
7
|
|
|
7
|
-
import type {
|
|
8
|
-
import { subscribe, setState, __INTERNAL__ } from ".."
|
|
9
|
-
import { withdraw } from "../internal"
|
|
8
|
+
import type { Modifier } from "~/packages/anvl/src/function"
|
|
10
9
|
|
|
11
10
|
export type AtomStoreReactConfig = {
|
|
12
11
|
useState: typeof Preact.useState | typeof React.useState
|
|
@@ -26,7 +25,7 @@ export const composeStoreHooks = ({
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
function useO<T>(token: ReadonlyValueToken<T> | StateToken<T>): T {
|
|
29
|
-
const state = withdraw(token, store)
|
|
28
|
+
const state = __INTERNAL__.withdraw(token, store)
|
|
30
29
|
const initialValue = __INTERNAL__.getState__INTERNAL(state, store)
|
|
31
30
|
const [current, dispatch] = useState(initialValue)
|
|
32
31
|
useEffect(() => {
|
|
@@ -40,7 +39,7 @@ export const composeStoreHooks = ({
|
|
|
40
39
|
store
|
|
41
40
|
)
|
|
42
41
|
return unsubscribe
|
|
43
|
-
}, [
|
|
42
|
+
}, [])
|
|
44
43
|
|
|
45
44
|
return current
|
|
46
45
|
}
|