atom.io 0.6.9 → 0.8.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.
Files changed (170) hide show
  1. package/README.md +21 -2
  2. package/dist/index.d.mts +34 -421
  3. package/dist/index.d.ts +34 -421
  4. package/dist/index.js +248 -23
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +209 -4
  7. package/dist/index.mjs.map +1 -1
  8. package/internal/dist/index.d.mts +364 -0
  9. package/internal/dist/index.d.ts +364 -0
  10. package/internal/dist/index.js +1906 -0
  11. package/internal/dist/index.js.map +1 -0
  12. package/internal/dist/index.mjs +1830 -0
  13. package/internal/dist/index.mjs.map +1 -0
  14. package/internal/package.json +15 -0
  15. package/internal/src/atom/create-atom.ts +75 -0
  16. package/internal/src/atom/delete-atom.ts +10 -0
  17. package/internal/src/atom/index.ts +3 -0
  18. package/internal/src/atom/is-default.ts +37 -0
  19. package/internal/src/caching.ts +38 -0
  20. package/internal/src/families/create-atom-family.ts +59 -0
  21. package/internal/src/families/create-readonly-selector-family.ts +45 -0
  22. package/internal/src/families/create-selector-family.ts +67 -0
  23. package/internal/src/families/index.ts +3 -0
  24. package/internal/src/future.ts +39 -0
  25. package/internal/src/get-state-internal.ts +23 -0
  26. package/internal/src/index.ts +14 -0
  27. package/internal/src/mutable/create-mutable-atom-family.ts +25 -0
  28. package/internal/src/mutable/create-mutable-atom.ts +49 -0
  29. package/internal/src/mutable/get-json-token.ts +22 -0
  30. package/internal/src/mutable/get-update-token.ts +20 -0
  31. package/internal/src/mutable/index.ts +17 -0
  32. package/internal/src/mutable/is-atom-token-mutable.ts +7 -0
  33. package/internal/src/mutable/tracker-family.ts +61 -0
  34. package/internal/src/mutable/tracker.ts +164 -0
  35. package/internal/src/mutable/transceiver.ts +110 -0
  36. package/internal/src/operation.ts +68 -0
  37. package/internal/src/selector/create-read-write-selector.ts +65 -0
  38. package/internal/src/selector/create-readonly-selector.ts +49 -0
  39. package/internal/src/selector/create-selector.ts +65 -0
  40. package/internal/src/selector/index.ts +5 -0
  41. package/internal/src/selector/lookup-selector-sources.ts +20 -0
  42. package/internal/src/selector/register-selector.ts +61 -0
  43. package/internal/src/selector/trace-selector-atoms.ts +45 -0
  44. package/internal/src/selector/update-selector-atoms.ts +34 -0
  45. package/internal/src/set-state/become.ts +10 -0
  46. package/internal/src/set-state/copy-mutable-if-needed.ts +23 -0
  47. package/internal/src/set-state/copy-mutable-in-transaction.ts +59 -0
  48. package/internal/src/set-state/copy-mutable-into-new-store.ts +34 -0
  49. package/internal/src/set-state/emit-update.ts +23 -0
  50. package/internal/src/set-state/evict-downstream.ts +39 -0
  51. package/internal/src/set-state/index.ts +2 -0
  52. package/internal/src/set-state/set-atom-state.ts +38 -0
  53. package/internal/src/set-state/set-selector-state.ts +19 -0
  54. package/internal/src/set-state/set-state-internal.ts +18 -0
  55. package/internal/src/set-state/stow-update.ts +42 -0
  56. package/internal/src/store/deposit.ts +43 -0
  57. package/internal/src/store/index.ts +5 -0
  58. package/internal/src/store/lookup.ts +26 -0
  59. package/internal/src/store/store.ts +154 -0
  60. package/internal/src/store/withdraw-new-family-member.ts +53 -0
  61. package/internal/src/store/withdraw.ts +113 -0
  62. package/internal/src/subject.ts +21 -0
  63. package/internal/src/subscribe/index.ts +1 -0
  64. package/internal/src/subscribe/recall-state.ts +19 -0
  65. package/internal/src/subscribe/subscribe-to-root-atoms.ts +47 -0
  66. package/internal/src/timeline/add-atom-to-timeline.ts +189 -0
  67. package/internal/src/timeline/index.ts +3 -0
  68. package/internal/src/timeline/time-travel-internal.ts +91 -0
  69. package/internal/src/timeline/timeline-internal.ts +115 -0
  70. package/internal/src/transaction/abort-transaction.ts +12 -0
  71. package/internal/src/transaction/apply-transaction.ts +64 -0
  72. package/internal/src/transaction/build-transaction.ts +39 -0
  73. package/internal/src/transaction/index.ts +26 -0
  74. package/internal/src/transaction/redo-transaction.ts +22 -0
  75. package/internal/src/transaction/transaction-internal.ts +64 -0
  76. package/internal/src/transaction/undo-transaction.ts +22 -0
  77. package/introspection/dist/index.d.mts +3 -197
  78. package/introspection/dist/index.d.ts +3 -197
  79. package/introspection/dist/index.js +329 -4
  80. package/introspection/dist/index.js.map +1 -1
  81. package/introspection/dist/index.mjs +310 -4
  82. package/introspection/dist/index.mjs.map +1 -1
  83. package/introspection/src/attach-atom-index.ts +84 -0
  84. package/introspection/src/attach-introspection-states.ts +38 -0
  85. package/introspection/src/attach-selector-index.ts +90 -0
  86. package/introspection/src/attach-timeline-family.ts +59 -0
  87. package/introspection/src/attach-timeline-index.ts +38 -0
  88. package/introspection/src/attach-transaction-index.ts +40 -0
  89. package/introspection/src/attach-transaction-logs.ts +43 -0
  90. package/introspection/src/index.ts +20 -0
  91. package/json/dist/index.d.mts +10 -2
  92. package/json/dist/index.d.ts +10 -2
  93. package/json/dist/index.js +83 -26
  94. package/json/dist/index.js.map +1 -1
  95. package/json/dist/index.mjs +74 -3
  96. package/json/dist/index.mjs.map +1 -1
  97. package/json/src/index.ts +5 -0
  98. package/json/src/select-json-family.ts +35 -0
  99. package/json/src/select-json.ts +22 -0
  100. package/package.json +103 -63
  101. package/react/dist/index.d.mts +9 -17
  102. package/react/dist/index.d.ts +9 -17
  103. package/react/dist/index.js +44 -27
  104. package/react/dist/index.js.map +1 -1
  105. package/react/dist/index.mjs +24 -4
  106. package/react/dist/index.mjs.map +1 -1
  107. package/react/src/index.ts +2 -0
  108. package/react/src/store-context.tsx +12 -0
  109. package/react/src/store-hooks.ts +36 -0
  110. package/react-devtools/dist/index.css +50 -1
  111. package/react-devtools/dist/index.css.map +1 -1
  112. package/react-devtools/dist/index.d.mts +104 -71
  113. package/react-devtools/dist/index.d.ts +104 -71
  114. package/react-devtools/dist/index.js +2821 -45
  115. package/react-devtools/dist/index.js.map +1 -1
  116. package/react-devtools/dist/index.mjs +2790 -11
  117. package/react-devtools/dist/index.mjs.map +1 -1
  118. package/react-devtools/src/AtomIODevtools.tsx +109 -0
  119. package/react-devtools/src/Button.tsx +23 -0
  120. package/react-devtools/src/StateEditor.tsx +75 -0
  121. package/react-devtools/src/StateIndex.tsx +159 -0
  122. package/react-devtools/src/TimelineIndex.tsx +88 -0
  123. package/react-devtools/src/TransactionIndex.tsx +70 -0
  124. package/react-devtools/src/Updates.tsx +150 -0
  125. package/react-devtools/src/devtools.scss +310 -0
  126. package/react-devtools/src/index.ts +72 -0
  127. package/realtime-react/dist/index.d.mts +8 -22
  128. package/realtime-react/dist/index.d.ts +8 -22
  129. package/realtime-react/dist/index.js +87 -32
  130. package/realtime-react/dist/index.js.map +1 -1
  131. package/realtime-react/dist/index.mjs +62 -6
  132. package/realtime-react/dist/index.mjs.map +1 -1
  133. package/realtime-react/src/index.ts +7 -0
  134. package/realtime-react/src/realtime-context.tsx +29 -0
  135. package/realtime-react/src/use-pull-family-member.ts +15 -0
  136. package/realtime-react/src/use-pull-mutable-family-member.ts +20 -0
  137. package/realtime-react/src/use-pull-mutable.ts +17 -0
  138. package/realtime-react/src/use-pull.ts +15 -0
  139. package/realtime-react/src/use-push.ts +19 -0
  140. package/realtime-react/src/use-server-action.ts +18 -0
  141. package/realtime-testing/dist/index.d.mts +49 -0
  142. package/realtime-testing/dist/index.d.ts +49 -0
  143. package/realtime-testing/dist/index.js +147 -0
  144. package/realtime-testing/dist/index.js.map +1 -0
  145. package/realtime-testing/dist/index.mjs +116 -0
  146. package/realtime-testing/dist/index.mjs.map +1 -0
  147. package/realtime-testing/src/index.ts +1 -0
  148. package/realtime-testing/src/setup-realtime-test.tsx +161 -0
  149. package/src/atom.ts +64 -9
  150. package/src/index.ts +29 -31
  151. package/src/logger.ts +3 -3
  152. package/src/selector.ts +3 -3
  153. package/src/silo.ts +29 -20
  154. package/src/subscribe.ts +3 -3
  155. package/src/timeline.ts +2 -2
  156. package/transceivers/set-rtx/dist/index.d.mts +39 -0
  157. package/transceivers/set-rtx/dist/index.d.ts +39 -0
  158. package/transceivers/set-rtx/dist/index.js +213 -0
  159. package/transceivers/set-rtx/dist/index.js.map +1 -0
  160. package/transceivers/set-rtx/dist/index.mjs +211 -0
  161. package/transceivers/set-rtx/dist/index.mjs.map +1 -0
  162. package/{realtime → transceivers/set-rtx}/package.json +1 -1
  163. package/transceivers/set-rtx/src/index.ts +1 -0
  164. package/transceivers/set-rtx/src/set-rtx.ts +242 -0
  165. package/realtime/dist/index.d.mts +0 -23
  166. package/realtime/dist/index.d.ts +0 -23
  167. package/realtime/dist/index.js +0 -32
  168. package/realtime/dist/index.js.map +0 -1
  169. package/realtime/dist/index.mjs +0 -7
  170. package/realtime/dist/index.mjs.map +0 -1
@@ -0,0 +1,189 @@
1
+ import type { AtomToken, TimelineUpdate } from "atom.io"
2
+
3
+ import type { Store } from "../store"
4
+ import { IMPLICIT, withdraw } from "../store"
5
+ import { target } from "../transaction"
6
+ import type {
7
+ Timeline,
8
+ TimelineAtomUpdate,
9
+ TimelineTransactionUpdate,
10
+ } from "./timeline-internal"
11
+
12
+ export const addAtomToTimeline = (
13
+ atomToken: AtomToken<any>,
14
+ tl: Timeline,
15
+ store: Store = IMPLICIT.STORE,
16
+ ): void => {
17
+ const atom = withdraw(atomToken, store)
18
+ if (atom === null) {
19
+ throw new Error(
20
+ `Cannot subscribe to atom "${atomToken.key}" because it has not been initialized in store "${store.config.name}"`,
21
+ )
22
+ }
23
+ atom.subject.subscribe(`timeline`, (update) => {
24
+ const currentSelectorKey =
25
+ store.operation.open && store.operation.token.type === `selector`
26
+ ? store.operation.token.key
27
+ : null
28
+ const currentSelectorTime =
29
+ store.operation.open && store.operation.token.type === `selector`
30
+ ? store.operation.time
31
+ : null
32
+ const currentTransactionKey =
33
+ store.transactionStatus.phase === `applying`
34
+ ? store.transactionStatus.key
35
+ : null
36
+ const currentTransactionTime =
37
+ store.transactionStatus.phase === `applying`
38
+ ? store.transactionStatus.time
39
+ : null
40
+
41
+ store.config.logger?.info(
42
+ `⏳ timeline "${tl.key}" saw atom "${atomToken.key}" go (`,
43
+ update.oldValue,
44
+ `->`,
45
+ update.newValue,
46
+ currentTransactionKey
47
+ ? `) in transaction "${currentTransactionKey}"`
48
+ : currentSelectorKey
49
+ ? `) in selector "${currentSelectorKey}"`
50
+ : `)`,
51
+ )
52
+
53
+ if (tl.timeTraveling === null) {
54
+ if (tl.selectorTime && tl.selectorTime !== currentSelectorTime) {
55
+ const mostRecentUpdate: TimelineUpdate | undefined = tl.history.at(-1)
56
+ if (mostRecentUpdate === undefined) {
57
+ throw new Error(
58
+ `Timeline "${tl.key}" has a selectorTime, but no history. This is most likely a bug in AtomIO.`,
59
+ )
60
+ }
61
+ }
62
+ if (
63
+ currentTransactionKey &&
64
+ store.transactionStatus.phase === `applying`
65
+ ) {
66
+ const currentTransaction = withdraw(
67
+ { key: currentTransactionKey, type: `transaction` },
68
+ store,
69
+ )
70
+ if (currentTransaction === null) {
71
+ throw new Error(
72
+ `Transaction "${currentTransactionKey}" not found in store "${store.config.name}". This is surprising, because we are in the application phase of "${currentTransactionKey}".`,
73
+ )
74
+ }
75
+ if (tl.transactionKey !== currentTransactionKey) {
76
+ if (tl.transactionKey) {
77
+ store.config.logger?.error(
78
+ `Timeline "${tl.key}" was unable to resolve transaction "${tl.transactionKey}. This is probably a bug.`,
79
+ )
80
+ }
81
+ tl.transactionKey = currentTransactionKey
82
+ const unsubscribe = currentTransaction.subject.subscribe(
83
+ `timeline:${tl.key}`,
84
+ (update) => {
85
+ unsubscribe()
86
+ if (tl.timeTraveling === null && currentTransactionTime) {
87
+ if (tl.at !== tl.history.length) {
88
+ tl.history.splice(tl.at)
89
+ }
90
+
91
+ const atomUpdates = update.atomUpdates.filter((atomUpdate) => {
92
+ const core = target(store)
93
+ const atomOrFamilyKeys = core.timelineAtoms.getRelatedKeys(
94
+ tl.key,
95
+ )
96
+
97
+ return atomOrFamilyKeys
98
+ ? [...atomOrFamilyKeys].some(
99
+ (key) =>
100
+ key === atomUpdate.key ||
101
+ key === atomUpdate.family?.key,
102
+ )
103
+ : false
104
+ })
105
+
106
+ const timelineTransactionUpdate: TimelineTransactionUpdate = {
107
+ type: `transaction_update`,
108
+ timestamp: currentTransactionTime,
109
+ ...update,
110
+ atomUpdates,
111
+ }
112
+ tl.history.push(timelineTransactionUpdate)
113
+ tl.at = tl.history.length
114
+ tl.subject.next(timelineTransactionUpdate)
115
+ }
116
+ tl.transactionKey = null
117
+ store.config.logger?.info(
118
+ `⌛ timeline "${tl.key}" got a transaction_update "${update.key}"`,
119
+ )
120
+ },
121
+ )
122
+ }
123
+ } else if (currentSelectorKey && currentSelectorTime) {
124
+ let latestUpdate: TimelineUpdate | undefined = tl.history.at(-1)
125
+
126
+ if (currentSelectorTime !== tl.selectorTime) {
127
+ latestUpdate = {
128
+ type: `selector_update`,
129
+ timestamp: currentSelectorTime,
130
+ key: currentSelectorKey,
131
+ atomUpdates: [],
132
+ }
133
+ latestUpdate.atomUpdates.push({
134
+ key: atom.key,
135
+ type: `atom_update`,
136
+ ...update,
137
+ })
138
+ if (tl.at !== tl.history.length) {
139
+ tl.history.splice(tl.at)
140
+ }
141
+ tl.history.push(latestUpdate)
142
+
143
+ store.config.logger?.info(
144
+ `⌛ timeline "${tl.key}" got a selector_update "${currentSelectorKey}" with`,
145
+ latestUpdate.atomUpdates.map((atomUpdate) => atomUpdate.key),
146
+ )
147
+
148
+ tl.at = tl.history.length
149
+ tl.selectorTime = currentSelectorTime
150
+ } else {
151
+ if (latestUpdate?.type === `selector_update`) {
152
+ latestUpdate.atomUpdates.push({
153
+ key: atom.key,
154
+ type: `atom_update`,
155
+ ...update,
156
+ })
157
+ store.config.logger?.info(
158
+ ` ⌛ timeline "${tl.key}" set selector_update "${currentSelectorKey}" to`,
159
+ latestUpdate?.atomUpdates.map((atomUpdate) => atomUpdate.key),
160
+ )
161
+ }
162
+ }
163
+ if (latestUpdate) tl.subject.next(latestUpdate)
164
+ } else {
165
+ const timestamp = Date.now()
166
+ tl.selectorTime = null
167
+ if (tl.at !== tl.history.length) {
168
+ tl.history.splice(tl.at)
169
+ }
170
+ const atomUpdate: TimelineAtomUpdate = {
171
+ type: `atom_update`,
172
+ timestamp,
173
+ key: atom.key,
174
+ oldValue: update.oldValue,
175
+ newValue: update.newValue,
176
+ }
177
+ if (atom.family) {
178
+ atomUpdate.family = atom.family
179
+ }
180
+ tl.history.push(atomUpdate)
181
+ tl.subject.next(atomUpdate)
182
+ store.config.logger?.info(
183
+ `⌛ timeline "${tl.key}" got an atom_update to "${atom.key}"`,
184
+ )
185
+ tl.at = tl.history.length
186
+ }
187
+ }
188
+ })
189
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./add-atom-to-timeline"
2
+ export * from "./time-travel-internal"
3
+ export * from "./timeline-internal"
@@ -0,0 +1,91 @@
1
+ import type { TimelineToken } from "atom.io"
2
+ import { setState } from "atom.io"
3
+
4
+ import type { Store } from "../store"
5
+ import { IMPLICIT } from "../store"
6
+
7
+ export const redo__INTERNAL = (
8
+ token: TimelineToken,
9
+ store: Store = IMPLICIT.STORE,
10
+ ): void => {
11
+ store.config.logger?.info(`⏩ redo "${token.key}"`)
12
+ const timelineData = store.timelines.get(token.key)
13
+ if (!timelineData) {
14
+ store.config.logger?.error(
15
+ `Failed to redo on timeline "${token.key}". This timeline has not been initialized.`,
16
+ )
17
+ return
18
+ }
19
+ if (timelineData.at === timelineData.history.length) {
20
+ store.config.logger?.warn(
21
+ `Failed to redo at the end of timeline "${token.key}". There is nothing to redo.`,
22
+ )
23
+ return
24
+ }
25
+ timelineData.timeTraveling = `into_future`
26
+ const update = timelineData.history[timelineData.at]
27
+ switch (update.type) {
28
+ case `atom_update`: {
29
+ const { key, newValue } = update
30
+ setState({ key, type: `atom` }, newValue, store)
31
+ break
32
+ }
33
+ case `selector_update`:
34
+ case `transaction_update`: {
35
+ for (const atomUpdate of update.atomUpdates) {
36
+ const { key, newValue } = atomUpdate
37
+ setState({ key, type: `atom` }, newValue, store)
38
+ }
39
+ break
40
+ }
41
+ }
42
+ ++timelineData.at
43
+ timelineData.subject.next(`redo`)
44
+ timelineData.timeTraveling = null
45
+ store.config.logger?.info(
46
+ `⏹️ "${token.key}" is now at ${timelineData.at} / ${timelineData.history.length}`,
47
+ )
48
+ }
49
+
50
+ export const undo__INTERNAL = (
51
+ token: TimelineToken,
52
+ store: Store = IMPLICIT.STORE,
53
+ ): void => {
54
+ store.config.logger?.info(`⏪ undo "${token.key}"`)
55
+ const timelineData = store.timelines.get(token.key)
56
+ if (!timelineData) {
57
+ store.config.logger?.error(
58
+ `Failed to undo on timeline "${token.key}". This timeline has not been initialized.`,
59
+ )
60
+ return
61
+ }
62
+ if (timelineData.at === 0) {
63
+ store.config.logger?.warn(
64
+ `Failed to undo at the beginning of timeline "${token.key}". There is nothing to undo.`,
65
+ )
66
+ return
67
+ }
68
+ timelineData.timeTraveling = `into_past`
69
+ --timelineData.at
70
+ const update = timelineData.history[timelineData.at]
71
+ switch (update.type) {
72
+ case `atom_update`: {
73
+ const { key, oldValue } = update
74
+ setState({ key, type: `atom` }, oldValue, store)
75
+ break
76
+ }
77
+ case `selector_update`:
78
+ case `transaction_update`: {
79
+ for (const atomUpdate of [...update.atomUpdates].reverse()) {
80
+ const { key, oldValue } = atomUpdate
81
+ setState({ key, type: `atom` }, oldValue, store)
82
+ }
83
+ break
84
+ }
85
+ }
86
+ timelineData.subject.next(`undo`)
87
+ timelineData.timeTraveling = null
88
+ store.config.logger?.info(
89
+ `⏹️ "${token.key}" is now at ${timelineData.at} / ${timelineData.history.length}`,
90
+ )
91
+ }
@@ -0,0 +1,115 @@
1
+ import type {
2
+ FamilyMetadata,
3
+ StateUpdate,
4
+ TimelineOptions,
5
+ TimelineToken,
6
+ TimelineUpdate,
7
+ TransactionUpdate,
8
+ ƒn,
9
+ } from "atom.io"
10
+
11
+ import type { Store } from "../store"
12
+ import { IMPLICIT } from "../store"
13
+ import { Subject } from "../subject"
14
+ import { target } from "../transaction"
15
+ import { addAtomToTimeline } from "./add-atom-to-timeline"
16
+
17
+ export type TimelineAtomUpdate = StateUpdate<unknown> & {
18
+ key: string
19
+ type: `atom_update`
20
+ timestamp: number
21
+ family?: FamilyMetadata
22
+ }
23
+ export type TimelineSelectorUpdate = {
24
+ key: string
25
+ type: `selector_update`
26
+ timestamp: number
27
+ atomUpdates: Omit<TimelineAtomUpdate, `timestamp`>[]
28
+ }
29
+ export type TimelineTransactionUpdate = TransactionUpdate<ƒn> & {
30
+ key: string
31
+ type: `transaction_update`
32
+ timestamp: number
33
+ }
34
+
35
+ export type Timeline = {
36
+ type: `timeline`
37
+ key: string
38
+ at: number
39
+ timeTraveling: `into_future` | `into_past` | null
40
+ history: TimelineUpdate[]
41
+ selectorTime: number | null
42
+ transactionKey: string | null
43
+ install: (store: Store) => void
44
+ subject: Subject<
45
+ | TimelineAtomUpdate
46
+ | TimelineSelectorUpdate
47
+ | TimelineTransactionUpdate
48
+ | `redo`
49
+ | `undo`
50
+ >
51
+ }
52
+
53
+ export function timeline__INTERNAL(
54
+ options: TimelineOptions,
55
+ store: Store = IMPLICIT.STORE,
56
+ data: Timeline | null = null,
57
+ ): TimelineToken {
58
+ const tl: Timeline = {
59
+ type: `timeline`,
60
+ key: options.key,
61
+ at: 0,
62
+ timeTraveling: null,
63
+ selectorTime: null,
64
+ transactionKey: null,
65
+ ...data,
66
+ history: data?.history.map((update) => ({ ...update })) ?? [],
67
+ install: (store) => timeline__INTERNAL(options, store, tl),
68
+ subject: new Subject(),
69
+ }
70
+
71
+ const core = target(store)
72
+ for (const tokenOrFamily of options.atoms) {
73
+ const timelineKey = core.timelineAtoms.getRelatedKey(tokenOrFamily.key)
74
+ if (timelineKey) {
75
+ store.config.logger?.error(
76
+ `❌ Failed to add atom "${tokenOrFamily.key}" to timeline "${options.key}" because it belongs to timeline "${timelineKey}"`,
77
+ )
78
+ continue
79
+ }
80
+ if (tokenOrFamily.type === `atom_family`) {
81
+ const family = tokenOrFamily
82
+ family.subject.subscribe(`timeline:${options.key}`, (token) => {
83
+ if (!core.atoms.has(token.key)) {
84
+ addAtomToTimeline(token, tl, store)
85
+ }
86
+ })
87
+ } else {
88
+ const token = tokenOrFamily
89
+ if (`family` in token && token.family) {
90
+ const familyTimelineKey = core.timelineAtoms.getRelatedKey(
91
+ token.family.key,
92
+ )
93
+ if (familyTimelineKey) {
94
+ store.config.logger?.error(
95
+ `❌ Failed to add atom "${token.key}" to timeline "${options.key}" because its family "${token.family.key}" belongs to timeline "${familyTimelineKey}"`,
96
+ )
97
+ continue
98
+ }
99
+ }
100
+ addAtomToTimeline(token, tl, store)
101
+ }
102
+ core.timelineAtoms = core.timelineAtoms.set({
103
+ atomKey: tokenOrFamily.key,
104
+ timelineKey: options.key,
105
+ })
106
+ }
107
+
108
+ store.timelines.set(options.key, tl)
109
+ const token: TimelineToken = {
110
+ key: options.key,
111
+ type: `timeline`,
112
+ }
113
+ store.subject.timelineCreation.next(token)
114
+ return token
115
+ }
@@ -0,0 +1,12 @@
1
+ import type { Store } from "../store"
2
+
3
+ export const abortTransaction = (store: Store): void => {
4
+ if (store.transactionStatus.phase === `idle`) {
5
+ store.config.logger?.warn(
6
+ `abortTransaction called outside of a transaction. This is probably a bug.`,
7
+ )
8
+ return
9
+ }
10
+ store.transactionStatus = { phase: `idle` }
11
+ store.config.logger?.info(`🪂`, `transaction fail`)
12
+ }
@@ -0,0 +1,64 @@
1
+ import type { AtomToken, ƒn } from "atom.io"
2
+ import { setState, transaction } from "atom.io"
3
+
4
+ import { withdraw } from "../store"
5
+ import type { Store } from "../store"
6
+
7
+ export const applyTransaction = <ƒ extends ƒn>(
8
+ output: ReturnType<ƒ>,
9
+ store: Store,
10
+ ): void => {
11
+ if (store.transactionStatus.phase !== `building`) {
12
+ store.config.logger?.warn(
13
+ `abortTransaction called outside of a transaction. This is probably a bug.`,
14
+ )
15
+ return
16
+ }
17
+ store.transactionStatus.phase = `applying`
18
+ store.transactionStatus.output = output
19
+ const { atomUpdates } = store.transactionStatus
20
+ store.config.logger?.info(
21
+ `🛃 applying transaction "${store.transactionStatus.key}" with ${atomUpdates.length} updates.`,
22
+ )
23
+ store.config.logger?.info(`🛃 the updates are:`, atomUpdates)
24
+ for (const { key, newValue } of atomUpdates) {
25
+ const token: AtomToken<unknown> = { key, type: `atom` }
26
+ if (!store.valueMap.has(token.key)) {
27
+ if (token.family) {
28
+ const family = store.families.get(token.family.key)
29
+ if (family) {
30
+ family(token.family.subKey)
31
+ }
32
+ } else {
33
+ const newAtom = store.transactionStatus.core.atoms.get(token.key)
34
+ if (!newAtom) {
35
+ throw new Error(
36
+ `Absurd Error: Atom "${token.key}" not found while copying updates from transaction "${store.transactionStatus.key}" to store "${store.config.name}"`,
37
+ )
38
+ }
39
+ store.atoms.set(newAtom.key, newAtom)
40
+ store.valueMap.set(newAtom.key, newAtom.default)
41
+ store.config.logger?.info(`🔧`, `add atom "${newAtom.key}"`)
42
+ }
43
+ }
44
+ // if (store.transactionStatus.key === `dealCards`) debugger
45
+ setState(token, newValue, store)
46
+ }
47
+ const myTransaction = withdraw<ƒ>(
48
+ { key: store.transactionStatus.key, type: `transaction` },
49
+ store,
50
+ )
51
+ if (myTransaction === null) {
52
+ throw new Error(
53
+ `Transaction "${store.transactionStatus.key}" not found. Absurd. How is this running?`,
54
+ )
55
+ }
56
+ myTransaction.subject.next({
57
+ key: store.transactionStatus.key,
58
+ atomUpdates,
59
+ output,
60
+ params: store.transactionStatus.params as Parameters<ƒ>,
61
+ })
62
+ store.transactionStatus = { phase: `idle` }
63
+ store.config.logger?.info(`🛬`, `transaction "${myTransaction.key}" applied`)
64
+ }
@@ -0,0 +1,39 @@
1
+ import { Junction } from "~/packages/rel8/junction/src"
2
+
3
+ import type { Store } from "../store"
4
+
5
+ export const buildTransaction = (
6
+ key: string,
7
+ params: any[],
8
+ store: Store,
9
+ ): void => {
10
+ store.transactionStatus = {
11
+ key,
12
+ phase: `building`,
13
+ time: Date.now(),
14
+ core: {
15
+ atoms: new Map(store.atoms),
16
+ atomsThatAreDefault: new Set(store.atomsThatAreDefault),
17
+ families: new Map(store.families),
18
+ operation: { open: false },
19
+ readonlySelectors: new Map(store.readonlySelectors),
20
+ timelines: new Map(store.timelines),
21
+ timelineAtoms: new Junction(store.timelineAtoms.toJSON()),
22
+ trackers: new Map(),
23
+ transactions: new Map(store.transactions),
24
+ selectorAtoms: new Junction(store.selectorAtoms.toJSON()),
25
+ selectorGraph: new Junction(store.selectorGraph.toJSON(), {
26
+ makeContentKey: (...keys) => keys.sort().join(`:`),
27
+ }),
28
+ selectors: new Map(store.selectors),
29
+ valueMap: new Map(store.valueMap),
30
+ },
31
+ atomUpdates: [],
32
+ params,
33
+ output: undefined,
34
+ }
35
+ store.config.logger?.info(
36
+ `🛫`,
37
+ `transaction "${key}" building in store "${store.config.name}"`,
38
+ )
39
+ }
@@ -0,0 +1,26 @@
1
+ import type { TransactionUpdate, ƒn } from "atom.io"
2
+
3
+ import type { StoreCore } from ".."
4
+
5
+ export * from "./transaction-internal"
6
+
7
+ export * from "./abort-transaction"
8
+ export * from "./apply-transaction"
9
+ export * from "./build-transaction"
10
+ export * from "./redo-transaction"
11
+ export * from "./undo-transaction"
12
+
13
+ export const TRANSACTION_PHASES = [`idle`, `building`, `applying`] as const
14
+ export type TransactionPhase = typeof TRANSACTION_PHASES[number]
15
+
16
+ export type TransactionUpdateInProgress<ƒ extends ƒn> = TransactionUpdate<ƒ> & {
17
+ phase: `applying` | `building`
18
+ time: number
19
+ core: StoreCore
20
+ }
21
+ export type TransactionIdle = {
22
+ phase: `idle`
23
+ }
24
+ export type TransactionStatus<ƒ extends ƒn> =
25
+ | TransactionIdle
26
+ | TransactionUpdateInProgress<ƒ>
@@ -0,0 +1,22 @@
1
+ import type { AtomToken, TransactionUpdate, ƒn } from "atom.io"
2
+ import { setState } from "atom.io"
3
+
4
+ import { withdraw } from "../store"
5
+ import type { Store } from "../store"
6
+
7
+ export const redoTransactionUpdate = <ƒ extends ƒn>(
8
+ update: TransactionUpdate<ƒ>,
9
+ store: Store,
10
+ ): void => {
11
+ store.config.logger?.info(` ⏭ redo transaction "${update.key}" (redo)`)
12
+ for (const { key, newValue } of update.atomUpdates) {
13
+ const token: AtomToken<unknown> = { key, type: `atom` }
14
+ const state = withdraw(token, store)
15
+ if (state === null) {
16
+ throw new Error(
17
+ `State "${token.key}" not found in this store. This is surprising, because we are navigating the history of the store.`,
18
+ )
19
+ }
20
+ setState(state, newValue, store)
21
+ }
22
+ }
@@ -0,0 +1,64 @@
1
+ import type {
2
+ TransactionOptions,
3
+ TransactionToken,
4
+ TransactionUpdate,
5
+ ƒn,
6
+ } from "atom.io"
7
+ import { getState, setState } from "atom.io"
8
+
9
+ import { IMPLICIT, type Store, type StoreCore, deposit } from "../store"
10
+ import { Subject } from "../subject"
11
+ import { abortTransaction } from "./abort-transaction"
12
+ import { applyTransaction } from "./apply-transaction"
13
+ import { buildTransaction } from "./build-transaction"
14
+
15
+ export type Transaction<ƒ extends ƒn> = {
16
+ key: string
17
+ type: `transaction`
18
+ install: (store: Store) => void
19
+ subject: Subject<TransactionUpdate<ƒ>>
20
+ run: (...parameters: Parameters<ƒ>) => ReturnType<ƒ>
21
+ }
22
+
23
+ export function transaction__INTERNAL<ƒ extends ƒn>(
24
+ options: TransactionOptions<ƒ>,
25
+ store: Store = IMPLICIT.STORE,
26
+ ): TransactionToken<ƒ> {
27
+ const newTransaction: Transaction<ƒ> = {
28
+ key: options.key,
29
+ type: `transaction`,
30
+ run: (...params: Parameters<ƒ>) => {
31
+ buildTransaction(options.key, params, store)
32
+ try {
33
+ const output = options.do(
34
+ {
35
+ get: (token) => getState(token, store),
36
+ set: (token, value) => setState(token, value, store),
37
+ },
38
+ ...params,
39
+ )
40
+ applyTransaction(output, store)
41
+ return output
42
+ } catch (thrown) {
43
+ abortTransaction(store)
44
+ store.config.logger?.error(
45
+ `Transaction "${options.key}" failed in store "${store.config.name}":`,
46
+ thrown,
47
+ )
48
+ throw thrown
49
+ }
50
+ },
51
+ install: (store) => transaction__INTERNAL(options, store),
52
+ subject: new Subject(),
53
+ }
54
+ const core = target(store)
55
+ core.transactions.set(newTransaction.key, newTransaction)
56
+ const token = deposit(newTransaction)
57
+ store.subject.transactionCreation.next(token)
58
+ return token
59
+ }
60
+
61
+ export const target = (store: Store = IMPLICIT.STORE): StoreCore =>
62
+ store.transactionStatus.phase === `building`
63
+ ? store.transactionStatus.core
64
+ : store
@@ -0,0 +1,22 @@
1
+ import type { AtomToken, TransactionUpdate, ƒn } from "atom.io"
2
+ import { setState } from "atom.io"
3
+
4
+ import { withdraw } from "../store"
5
+ import type { Store } from "../store"
6
+
7
+ export const undoTransactionUpdate = <ƒ extends ƒn>(
8
+ update: TransactionUpdate<ƒ>,
9
+ store: Store,
10
+ ): void => {
11
+ store.config.logger?.info(` ⏮ undo transaction "${update.key}" (undo)`)
12
+ for (const { key, oldValue } of update.atomUpdates) {
13
+ const token: AtomToken<unknown> = { key, type: `atom` }
14
+ const state = withdraw(token, store)
15
+ if (state === null) {
16
+ throw new Error(
17
+ `State "${token.key}" not found in this store. This is surprising, because we are navigating the history of the store.`,
18
+ )
19
+ }
20
+ setState(state, oldValue, store)
21
+ }
22
+ }