atom.io 0.6.9 → 0.7.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 (169) 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 +342 -0
  9. package/internal/dist/index.d.ts +342 -0
  10. package/internal/dist/index.js +1873 -0
  11. package/internal/dist/index.js.map +1 -0
  12. package/internal/dist/index.mjs +1798 -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 +21 -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/get-state-internal.ts +23 -0
  25. package/internal/src/index.ts +13 -0
  26. package/internal/src/mutable/create-mutable-atom-family.ts +25 -0
  27. package/internal/src/mutable/create-mutable-atom.ts +49 -0
  28. package/internal/src/mutable/get-json-token.ts +22 -0
  29. package/internal/src/mutable/get-update-token.ts +20 -0
  30. package/internal/src/mutable/index.ts +17 -0
  31. package/internal/src/mutable/is-atom-token-mutable.ts +7 -0
  32. package/internal/src/mutable/tracker-family.ts +61 -0
  33. package/internal/src/mutable/tracker.ts +164 -0
  34. package/internal/src/mutable/transceiver.ts +110 -0
  35. package/internal/src/operation.ts +68 -0
  36. package/internal/src/selector/create-read-write-selector.ts +65 -0
  37. package/internal/src/selector/create-readonly-selector.ts +49 -0
  38. package/internal/src/selector/create-selector.ts +65 -0
  39. package/internal/src/selector/index.ts +5 -0
  40. package/internal/src/selector/lookup-selector-sources.ts +20 -0
  41. package/internal/src/selector/register-selector.ts +61 -0
  42. package/internal/src/selector/trace-selector-atoms.ts +45 -0
  43. package/internal/src/selector/update-selector-atoms.ts +34 -0
  44. package/internal/src/set-state/become.ts +10 -0
  45. package/internal/src/set-state/copy-mutable-if-needed.ts +23 -0
  46. package/internal/src/set-state/copy-mutable-in-transaction.ts +59 -0
  47. package/internal/src/set-state/copy-mutable-into-new-store.ts +34 -0
  48. package/internal/src/set-state/emit-update.ts +23 -0
  49. package/internal/src/set-state/evict-downstream.ts +39 -0
  50. package/internal/src/set-state/index.ts +2 -0
  51. package/internal/src/set-state/set-atom-state.ts +38 -0
  52. package/internal/src/set-state/set-selector-state.ts +19 -0
  53. package/internal/src/set-state/set-state-internal.ts +18 -0
  54. package/internal/src/set-state/stow-update.ts +42 -0
  55. package/internal/src/store/deposit.ts +43 -0
  56. package/internal/src/store/index.ts +5 -0
  57. package/internal/src/store/lookup.ts +26 -0
  58. package/internal/src/store/store.ts +154 -0
  59. package/internal/src/store/withdraw-new-family-member.ts +53 -0
  60. package/internal/src/store/withdraw.ts +113 -0
  61. package/internal/src/subject.ts +21 -0
  62. package/internal/src/subscribe/index.ts +1 -0
  63. package/internal/src/subscribe/recall-state.ts +19 -0
  64. package/internal/src/subscribe/subscribe-to-root-atoms.ts +47 -0
  65. package/internal/src/timeline/add-atom-to-timeline.ts +189 -0
  66. package/internal/src/timeline/index.ts +3 -0
  67. package/internal/src/timeline/time-travel-internal.ts +91 -0
  68. package/internal/src/timeline/timeline-internal.ts +115 -0
  69. package/internal/src/transaction/abort-transaction.ts +12 -0
  70. package/internal/src/transaction/apply-transaction.ts +64 -0
  71. package/internal/src/transaction/build-transaction.ts +39 -0
  72. package/internal/src/transaction/index.ts +26 -0
  73. package/internal/src/transaction/redo-transaction.ts +22 -0
  74. package/internal/src/transaction/transaction-internal.ts +64 -0
  75. package/internal/src/transaction/undo-transaction.ts +22 -0
  76. package/introspection/dist/index.d.mts +3 -197
  77. package/introspection/dist/index.d.ts +3 -197
  78. package/introspection/dist/index.js +329 -4
  79. package/introspection/dist/index.js.map +1 -1
  80. package/introspection/dist/index.mjs +310 -4
  81. package/introspection/dist/index.mjs.map +1 -1
  82. package/introspection/src/attach-atom-index.ts +84 -0
  83. package/introspection/src/attach-introspection-states.ts +38 -0
  84. package/introspection/src/attach-selector-index.ts +90 -0
  85. package/introspection/src/attach-timeline-family.ts +59 -0
  86. package/introspection/src/attach-timeline-index.ts +38 -0
  87. package/introspection/src/attach-transaction-index.ts +40 -0
  88. package/introspection/src/attach-transaction-logs.ts +43 -0
  89. package/introspection/src/index.ts +20 -0
  90. package/json/dist/index.d.mts +10 -2
  91. package/json/dist/index.d.ts +10 -2
  92. package/json/dist/index.js +83 -26
  93. package/json/dist/index.js.map +1 -1
  94. package/json/dist/index.mjs +74 -3
  95. package/json/dist/index.mjs.map +1 -1
  96. package/json/src/index.ts +5 -0
  97. package/json/src/select-json-family.ts +35 -0
  98. package/json/src/select-json.ts +22 -0
  99. package/package.json +103 -63
  100. package/react/dist/index.d.mts +9 -17
  101. package/react/dist/index.d.ts +9 -17
  102. package/react/dist/index.js +44 -27
  103. package/react/dist/index.js.map +1 -1
  104. package/react/dist/index.mjs +24 -4
  105. package/react/dist/index.mjs.map +1 -1
  106. package/react/src/index.ts +2 -0
  107. package/react/src/store-context.tsx +12 -0
  108. package/react/src/store-hooks.ts +36 -0
  109. package/react-devtools/dist/index.css +50 -1
  110. package/react-devtools/dist/index.css.map +1 -1
  111. package/react-devtools/dist/index.d.mts +104 -71
  112. package/react-devtools/dist/index.d.ts +104 -71
  113. package/react-devtools/dist/index.js +2806 -44
  114. package/react-devtools/dist/index.js.map +1 -1
  115. package/react-devtools/dist/index.mjs +2775 -10
  116. package/react-devtools/dist/index.mjs.map +1 -1
  117. package/react-devtools/src/AtomIODevtools.tsx +109 -0
  118. package/react-devtools/src/Button.tsx +23 -0
  119. package/react-devtools/src/StateEditor.tsx +75 -0
  120. package/react-devtools/src/StateIndex.tsx +159 -0
  121. package/react-devtools/src/TimelineIndex.tsx +88 -0
  122. package/react-devtools/src/TransactionIndex.tsx +70 -0
  123. package/react-devtools/src/Updates.tsx +150 -0
  124. package/react-devtools/src/devtools.scss +310 -0
  125. package/react-devtools/src/index.ts +72 -0
  126. package/realtime-react/dist/index.d.mts +8 -22
  127. package/realtime-react/dist/index.d.ts +8 -22
  128. package/realtime-react/dist/index.js +87 -32
  129. package/realtime-react/dist/index.js.map +1 -1
  130. package/realtime-react/dist/index.mjs +62 -6
  131. package/realtime-react/dist/index.mjs.map +1 -1
  132. package/realtime-react/src/index.ts +7 -0
  133. package/realtime-react/src/realtime-context.tsx +29 -0
  134. package/realtime-react/src/use-pull-family-member.ts +15 -0
  135. package/realtime-react/src/use-pull-mutable-family-member.ts +20 -0
  136. package/realtime-react/src/use-pull-mutable.ts +17 -0
  137. package/realtime-react/src/use-pull.ts +15 -0
  138. package/realtime-react/src/use-push.ts +19 -0
  139. package/realtime-react/src/use-server-action.ts +18 -0
  140. package/realtime-testing/dist/index.d.mts +49 -0
  141. package/realtime-testing/dist/index.d.ts +49 -0
  142. package/realtime-testing/dist/index.js +147 -0
  143. package/realtime-testing/dist/index.js.map +1 -0
  144. package/realtime-testing/dist/index.mjs +116 -0
  145. package/realtime-testing/dist/index.mjs.map +1 -0
  146. package/realtime-testing/src/index.ts +1 -0
  147. package/realtime-testing/src/setup-realtime-test.tsx +161 -0
  148. package/src/atom.ts +64 -9
  149. package/src/index.ts +36 -32
  150. package/src/logger.ts +3 -3
  151. package/src/selector.ts +3 -3
  152. package/src/silo.ts +29 -20
  153. package/src/subscribe.ts +3 -3
  154. package/src/timeline.ts +2 -2
  155. package/transceivers/set-rtx/dist/index.d.mts +39 -0
  156. package/transceivers/set-rtx/dist/index.d.ts +39 -0
  157. package/transceivers/set-rtx/dist/index.js +213 -0
  158. package/transceivers/set-rtx/dist/index.js.map +1 -0
  159. package/transceivers/set-rtx/dist/index.mjs +211 -0
  160. package/transceivers/set-rtx/dist/index.mjs.map +1 -0
  161. package/{realtime → transceivers/set-rtx}/package.json +1 -1
  162. package/transceivers/set-rtx/src/index.ts +1 -0
  163. package/transceivers/set-rtx/src/set-rtx.ts +242 -0
  164. package/realtime/dist/index.d.mts +0 -23
  165. package/realtime/dist/index.d.ts +0 -23
  166. package/realtime/dist/index.js +0 -32
  167. package/realtime/dist/index.js.map +0 -1
  168. package/realtime/dist/index.mjs +0 -7
  169. package/realtime/dist/index.mjs.map +0 -1
@@ -0,0 +1,43 @@
1
+ import type {
2
+ AtomToken,
3
+ ReadonlySelectorToken,
4
+ SelectorToken,
5
+ StateToken,
6
+ TransactionToken,
7
+ ƒn,
8
+ } from "atom.io"
9
+
10
+ import type { Atom } from "../atom"
11
+ import type { ReadonlySelector, Selector } from "../selector"
12
+ import type { Transaction } from "../transaction"
13
+
14
+ export function deposit<T>(state: Atom<T>): AtomToken<T>
15
+ export function deposit<T>(state: Selector<T>): SelectorToken<T>
16
+ export function deposit<T>(state: Atom<T> | Selector<T>): StateToken<T>
17
+ export function deposit<T>(state: ReadonlySelector<T>): ReadonlySelectorToken<T>
18
+ export function deposit<T>(
19
+ state: Transaction<T extends ƒn ? T : never>,
20
+ ): TransactionToken<T>
21
+ export function deposit<T>(
22
+ state: Atom<T> | ReadonlySelector<T> | Selector<T>,
23
+ ): ReadonlySelectorToken<T> | StateToken<T>
24
+ export function deposit<T>(
25
+ state:
26
+ | Atom<T>
27
+ | ReadonlySelector<T>
28
+ | Selector<T>
29
+ | Transaction<T extends ƒn ? T : never>,
30
+ ):
31
+ | AtomToken<T>
32
+ | ReadonlySelectorToken<T>
33
+ | SelectorToken<T>
34
+ | TransactionToken<T> {
35
+ const token = {
36
+ key: state.key,
37
+ type: state.type,
38
+ } as any
39
+ if (`family` in state) {
40
+ token.family = state.family
41
+ }
42
+ return token
43
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./deposit"
2
+ export * from "./lookup"
3
+ export * from "./store"
4
+ export * from "./withdraw"
5
+ export * from "./withdraw-new-family-member"
@@ -0,0 +1,26 @@
1
+ import type { AtomToken, ReadonlySelectorToken, SelectorToken } from "atom.io"
2
+
3
+ import { target } from "../transaction"
4
+ import type { Store } from "./store"
5
+
6
+ export function lookup(
7
+ key: string,
8
+ store: Store,
9
+ ): AtomToken<unknown> | ReadonlySelectorToken<unknown> | SelectorToken<unknown> {
10
+ const core = target(store)
11
+ let type: string = core.atoms.has(key)
12
+ ? `atom`
13
+ : core.selectors.has(key)
14
+ ? `selector`
15
+ : core.readonlySelectors.has(key)
16
+ ? `readonly_selector`
17
+ : ``
18
+ if (!type) {
19
+ const errorId = Math.random().toString(36)
20
+ type = `🚨 This state could not be found by lookup! Check the console for "${errorId}"`
21
+ store.config.logger?.error(
22
+ `${errorId}: Key "${key}" does not exist in the store.`,
23
+ )
24
+ }
25
+ return { key, type } as any
26
+ }
@@ -0,0 +1,154 @@
1
+ import type {
2
+ AtomFamily,
3
+ AtomToken,
4
+ Logger,
5
+ ReadonlySelectorFamily,
6
+ ReadonlySelectorToken,
7
+ SelectorFamily,
8
+ SelectorToken,
9
+ TimelineToken,
10
+ TransactionToken,
11
+ ƒn,
12
+ } from "atom.io"
13
+
14
+ import { Junction } from "~/packages/rel8/junction/src"
15
+
16
+ import type { Atom } from "../atom"
17
+ import type { MutableAtom, Tracker, Transceiver } from "../mutable"
18
+ import type { OperationProgress } from "../operation"
19
+ import type { ReadonlySelector, Selector } from "../selector"
20
+ import { Subject } from "../subject"
21
+ import type { Timeline } from "../timeline"
22
+ import type { Transaction, TransactionStatus } from "../transaction"
23
+
24
+ export type StoreCore = Pick<
25
+ Store,
26
+ | `atoms`
27
+ | `atomsThatAreDefault`
28
+ | `families`
29
+ | `operation`
30
+ | `readonlySelectors`
31
+ | `selectorAtoms`
32
+ | `selectorGraph`
33
+ | `selectors`
34
+ | `timelineAtoms`
35
+ | `timelines`
36
+ | `trackers`
37
+ | `transactions`
38
+ | `valueMap`
39
+ >
40
+
41
+ export class Store {
42
+ public valueMap = new Map<string, any>()
43
+
44
+ public atoms = new Map<string, Atom<any> | MutableAtom<any>>()
45
+ public selectors = new Map<string, Selector<any>>()
46
+ public readonlySelectors = new Map<string, ReadonlySelector<any>>()
47
+
48
+ public trackers = new Map<string, Tracker<Transceiver<any>>>()
49
+ public families = new Map<
50
+ string,
51
+ | AtomFamily<any, any>
52
+ | ReadonlySelectorFamily<any, any>
53
+ | SelectorFamily<any, any>
54
+ >()
55
+
56
+ public timelines = new Map<string, Timeline>()
57
+ public transactions = new Map<string, Transaction<ƒn>>()
58
+
59
+ public atomsThatAreDefault = new Set<string>()
60
+
61
+ public timelineAtoms = new Junction({
62
+ between: [`timelineKey`, `atomKey`],
63
+ cardinality: `1:n`,
64
+ })
65
+ public selectorAtoms = new Junction({
66
+ between: [`selectorKey`, `atomKey`],
67
+ cardinality: `n:n`,
68
+ })
69
+ public selectorGraph = new Junction<
70
+ `upstreamSelectorKey`,
71
+ `downstreamSelectorKey`,
72
+ { source: string }
73
+ >(
74
+ {
75
+ between: [`upstreamSelectorKey`, `downstreamSelectorKey`],
76
+ cardinality: `n:n`,
77
+ },
78
+ {
79
+ makeContentKey: (...keys) => keys.sort().join(`:`),
80
+ },
81
+ )
82
+
83
+ public subject = {
84
+ atomCreation: new Subject<AtomToken<unknown>>(),
85
+ selectorCreation: new Subject<
86
+ ReadonlySelectorToken<unknown> | SelectorToken<unknown>
87
+ >(),
88
+ transactionCreation: new Subject<TransactionToken<ƒn>>(),
89
+ timelineCreation: new Subject<TimelineToken>(),
90
+ operationStatus: new Subject<OperationProgress>(),
91
+ }
92
+ public operation: OperationProgress = { open: false }
93
+ public transactionStatus: TransactionStatus<ƒn> = { phase: `idle` }
94
+
95
+ public config: {
96
+ name: string
97
+ logger: Logger | null
98
+ logger__INTERNAL: Logger
99
+ } = {
100
+ name: `IMPLICIT_STORE`,
101
+ logger: { ...console, info: () => undefined },
102
+ logger__INTERNAL: console,
103
+ }
104
+
105
+ public constructor(name: string, store: Store | null = null) {
106
+ if (store !== null) {
107
+ this.valueMap = new Map(store?.valueMap)
108
+
109
+ this.operation = { ...store?.operation }
110
+ this.transactionStatus = { ...store?.transactionStatus }
111
+ this.config = {
112
+ ...store?.config,
113
+ logger__INTERNAL: console,
114
+ logger: {
115
+ ...console,
116
+ info: () => undefined,
117
+ ...store?.config?.logger,
118
+ },
119
+ name,
120
+ }
121
+ }
122
+
123
+ store?.atoms.forEach((atom) => {
124
+ atom.install(this)
125
+ })
126
+ store?.readonlySelectors.forEach((selector) => {
127
+ selector.install(this)
128
+ })
129
+ store?.selectors.forEach((selector) => {
130
+ selector.install(this)
131
+ })
132
+ store?.transactions.forEach((tx) => {
133
+ tx.install(this)
134
+ })
135
+ store?.timelines.forEach((timeline) => {
136
+ timeline.install(this)
137
+ })
138
+ }
139
+ }
140
+
141
+ export const IMPLICIT = {
142
+ STORE_INTERNAL: undefined as Store | undefined,
143
+ get STORE(): Store {
144
+ return (
145
+ this.STORE_INTERNAL ?? (this.STORE_INTERNAL = new Store(`IMPLICIT_STORE`))
146
+ )
147
+ },
148
+ }
149
+
150
+ export const clearStore = (store: Store = IMPLICIT.STORE): void => {
151
+ const { config } = store
152
+ Object.assign(store, new Store(config.name))
153
+ store.config = config
154
+ }
@@ -0,0 +1,53 @@
1
+ import type {
2
+ AtomToken,
3
+ ReadonlySelectorToken,
4
+ SelectorToken,
5
+ StateToken,
6
+ } from "atom.io"
7
+ import type { Atom, ReadonlySelector, Selector, Store } from ".."
8
+ import { withdraw } from ".."
9
+ import { target } from "../transaction"
10
+
11
+ export function withdrawNewFamilyMember<T>(
12
+ token: AtomToken<T>,
13
+ store: Store,
14
+ ): Atom<T> | null
15
+ export function withdrawNewFamilyMember<T>(
16
+ token: SelectorToken<T>,
17
+ store: Store,
18
+ ): Selector<T> | null
19
+ export function withdrawNewFamilyMember<T>(
20
+ token: ReadonlySelectorToken<T>,
21
+ store: Store,
22
+ ): ReadonlySelector<T> | null
23
+ export function withdrawNewFamilyMember<T>(
24
+ token: StateToken<T>,
25
+ store: Store,
26
+ ): Atom<T> | Selector<T> | null
27
+ export function withdrawNewFamilyMember<T>(
28
+ token: ReadonlySelectorToken<T> | StateToken<T>,
29
+ store: Store,
30
+ ): Atom<T> | ReadonlySelector<T> | Selector<T> | null
31
+ export function withdrawNewFamilyMember<T>(
32
+ token:
33
+ | AtomToken<T>
34
+ | ReadonlySelectorToken<T>
35
+ | SelectorToken<T>
36
+ | StateToken<T>,
37
+ store: Store,
38
+ ): Atom<T> | ReadonlySelector<T> | Selector<T> | null {
39
+ store.config.logger?.info(
40
+ `👪 creating new family member "${token.key}" in store "${store.config.name}"`,
41
+ )
42
+ if (token.family) {
43
+ const core = target(store)
44
+ const family = core.families.get(token.family.key)
45
+ if (family) {
46
+ const jsonSubKey = JSON.parse(token.family.subKey)
47
+ family(jsonSubKey)
48
+ const state = withdraw(token, store)
49
+ return state
50
+ }
51
+ }
52
+ return null
53
+ }
@@ -0,0 +1,113 @@
1
+ import type {
2
+ AtomToken,
3
+ ReadonlySelectorToken,
4
+ SelectorToken,
5
+ StateToken,
6
+ TimelineToken,
7
+ TransactionToken,
8
+ ƒn,
9
+ } from "atom.io"
10
+
11
+ import type { Atom } from "../atom"
12
+ import type { ReadonlySelector, Selector } from "../selector"
13
+ import { addAtomToTimeline } from "../timeline"
14
+ import type { Timeline } from "../timeline"
15
+ import type { Transaction } from "../transaction"
16
+ import { target } from "../transaction"
17
+ import type { Store } from "./store"
18
+
19
+ export function withdraw<T>(token: AtomToken<T>, store: Store): Atom<T> | null
20
+ export function withdraw<T>(
21
+ token: SelectorToken<T>,
22
+ store: Store,
23
+ ): Selector<T> | null
24
+ export function withdraw<T>(
25
+ token: StateToken<T>,
26
+ store: Store,
27
+ ): Atom<T> | Selector<T> | null
28
+ export function withdraw<T>(
29
+ token: ReadonlySelectorToken<T>,
30
+ store: Store,
31
+ ): ReadonlySelector<T> | null
32
+ export function withdraw<T>(
33
+ token: TransactionToken<T>,
34
+ store: Store,
35
+ ): Transaction<T extends ƒn ? T : never> | null
36
+ export function withdraw<T>(
37
+ token: ReadonlySelectorToken<T> | StateToken<T>,
38
+ store: Store,
39
+ ): Atom<T> | ReadonlySelector<T> | Selector<T> | null
40
+ export function withdraw<T>(token: TimelineToken, store: Store): Timeline | null
41
+ export function withdraw<T>(
42
+ token:
43
+ | ReadonlySelectorToken<T>
44
+ | StateToken<T>
45
+ | TimelineToken
46
+ | TransactionToken<T>,
47
+ store: Store,
48
+ ):
49
+ | Atom<T>
50
+ | ReadonlySelector<T>
51
+ | Selector<T>
52
+ | Timeline
53
+ | Transaction<T extends ƒn ? T : never>
54
+ | null {
55
+ let core = target(store)
56
+ let state =
57
+ core.atoms.get(token.key) ??
58
+ core.selectors.get(token.key) ??
59
+ core.readonlySelectors.get(token.key) ??
60
+ core.transactions.get(token.key) ??
61
+ core.timelines.get(token.key)
62
+ if (state) {
63
+ return state
64
+ }
65
+ if (store.transactionStatus.phase === `applying`) {
66
+ core = store.transactionStatus.core
67
+ state =
68
+ core.atoms.get(token.key) ??
69
+ core.selectors.get(token.key) ??
70
+ core.readonlySelectors.get(token.key) ??
71
+ core.transactions.get(token.key) ??
72
+ core.timelines.get(token.key)
73
+
74
+ if (state) {
75
+ store.config.logger?.info(`🛠️ add ${token.type} "${token.key}"`)
76
+ switch (state.type) {
77
+ case `atom`: {
78
+ store.atoms.set(token.key, state)
79
+ store.valueMap.set(token.key, state.default)
80
+ const stateKey = state.key
81
+ const familyKey = state.family?.key
82
+ let timelineKey = core.timelineAtoms.getRelatedKey(stateKey)
83
+ if (timelineKey === undefined && typeof familyKey === `string`) {
84
+ timelineKey = core.timelineAtoms.getRelatedKey(familyKey)
85
+ }
86
+ const timeline =
87
+ typeof timelineKey === `string`
88
+ ? store.timelines.get(timelineKey)
89
+ : undefined
90
+
91
+ if (timeline) {
92
+ addAtomToTimeline(state, timeline, store)
93
+ }
94
+ break
95
+ }
96
+ case `selector`:
97
+ core.selectors.set(token.key, state)
98
+ break
99
+ case `readonly_selector`:
100
+ core.readonlySelectors.set(token.key, state)
101
+ break
102
+ case `transaction`:
103
+ core.transactions.set(token.key, state)
104
+ break
105
+ case `timeline`:
106
+ core.timelines.set(token.key, state)
107
+ break
108
+ }
109
+ return state
110
+ }
111
+ }
112
+ return null
113
+ }
@@ -0,0 +1,21 @@
1
+ export class Subject<T> {
2
+ public Subscriber: (value: T) => void
3
+
4
+ public subscribers: Map<string, this[`Subscriber`]> = new Map()
5
+
6
+ public subscribe(key: string, subscriber: this[`Subscriber`]): () => void {
7
+ this.subscribers.set(key, subscriber)
8
+ const unsubscribe = () => this.unsubscribe(key)
9
+ return unsubscribe
10
+ }
11
+
12
+ private unsubscribe(key: string) {
13
+ this.subscribers.delete(key)
14
+ }
15
+
16
+ public next(value: T): void {
17
+ for (const subscriber of this.subscribers.values()) {
18
+ subscriber(value)
19
+ }
20
+ }
21
+ }
@@ -0,0 +1 @@
1
+ export * from "./subscribe-to-root-atoms"
@@ -0,0 +1,19 @@
1
+ import type { Atom } from "../atom"
2
+ import type { ReadonlySelector, Selector } from "../selector"
3
+ import type { Store } from "../store"
4
+ import { IMPLICIT } from "../store"
5
+ import { target } from "../transaction"
6
+
7
+ export const recallState = <T>(
8
+ state: Atom<T> | ReadonlySelector<T> | Selector<T>,
9
+ store: Store = IMPLICIT.STORE,
10
+ ): T => {
11
+ const core = target(store)
12
+ if (!core.operation.open) {
13
+ store.config.logger?.warn(
14
+ `recall called outside of an operation. This is probably a bug.`,
15
+ )
16
+ return core.valueMap.get(state.key)
17
+ }
18
+ return core.operation.prev.get(state.key)
19
+ }
@@ -0,0 +1,47 @@
1
+ import { getState__INTERNAL } from "../get-state-internal"
2
+ import type { ReadonlySelector, Selector } from "../selector"
3
+ import { traceAllSelectorAtoms } from "../selector"
4
+ import type { Store } from "../store"
5
+ import { withdraw } from "../store"
6
+ import { recallState } from "./recall-state"
7
+
8
+ export const subscribeToRootAtoms = <T>(
9
+ state: ReadonlySelector<T> | Selector<T>,
10
+ store: Store,
11
+ ): (() => void)[] | null => {
12
+ const dependencySubscriptions =
13
+ `default` in state
14
+ ? null
15
+ : traceAllSelectorAtoms(state.key, store).map((atomToken) => {
16
+ const atom = withdraw(atomToken, store)
17
+ if (atom === null) {
18
+ throw new Error(
19
+ `Atom "${atomToken.key}", a dependency of selector "${state.key}", not found in store "${store.config.name}".`,
20
+ )
21
+ }
22
+ return atom.subject.subscribe(
23
+ `${state.type}:${state.key}`,
24
+ (atomChange) => {
25
+ store.config.logger?.info(
26
+ `📢 selector "${state.key}" saw root "${atomToken.key}" go (`,
27
+ atomChange.oldValue,
28
+ `->`,
29
+ atomChange.newValue,
30
+ `)`,
31
+ )
32
+ const oldValue = recallState(state, store)
33
+ // ❗ this retrieves a stale cached value when applying a transaction on the server
34
+ const newValue = getState__INTERNAL(state, store)
35
+ store.config.logger?.info(
36
+ ` <- "${state.key}" went (`,
37
+ oldValue,
38
+ `->`,
39
+ newValue,
40
+ `)`,
41
+ )
42
+ state.subject.next({ newValue, oldValue })
43
+ },
44
+ )
45
+ })
46
+ return dependencySubscriptions
47
+ }
@@ -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"