atom.io 0.16.2 → 0.16.3

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 (90) hide show
  1. package/dist/chunk-H4Q5FTPZ.js +11 -0
  2. package/dist/chunk-H4Q5FTPZ.js.map +1 -0
  3. package/dist/index.cjs +1 -11
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +6 -6
  6. package/dist/index.js +2 -11
  7. package/dist/index.js.map +1 -1
  8. package/internal/dist/index.cjs +225 -183
  9. package/internal/dist/index.cjs.map +1 -1
  10. package/internal/dist/index.d.ts +30 -9
  11. package/internal/dist/index.js +218 -184
  12. package/internal/dist/index.js.map +1 -1
  13. package/internal/src/families/find-in-store.ts +74 -0
  14. package/internal/src/families/index.ts +1 -0
  15. package/internal/src/ingest-updates/ingest-transaction-update.ts +1 -0
  16. package/internal/src/mutable/tracker.ts +28 -25
  17. package/internal/src/mutable/transceiver.ts +1 -1
  18. package/internal/src/not-found-error.ts +14 -3
  19. package/internal/src/operation.ts +2 -1
  20. package/internal/src/selector/create-writable-selector.ts +2 -1
  21. package/internal/src/selector/register-selector.ts +5 -4
  22. package/internal/src/set-state/set-atom.ts +16 -2
  23. package/internal/src/set-state/stow-update.ts +2 -4
  24. package/internal/src/store/store.ts +13 -4
  25. package/internal/src/timeline/add-atom-to-timeline.ts +5 -5
  26. package/internal/src/transaction/abort-transaction.ts +2 -1
  27. package/internal/src/transaction/apply-transaction.ts +5 -3
  28. package/internal/src/transaction/build-transaction.ts +16 -9
  29. package/internal/src/transaction/create-transaction.ts +2 -3
  30. package/internal/src/transaction/index.ts +3 -2
  31. package/internal/src/transaction/is-root-store.ts +23 -0
  32. package/package.json +10 -10
  33. package/react/dist/index.cjs +27 -21
  34. package/react/dist/index.cjs.map +1 -1
  35. package/react/dist/index.d.ts +8 -2
  36. package/react/dist/index.js +27 -21
  37. package/react/dist/index.js.map +1 -1
  38. package/react/src/index.ts +4 -1
  39. package/react/src/use-i.ts +36 -0
  40. package/react/src/use-json.ts +38 -0
  41. package/react/src/use-o.ts +34 -0
  42. package/react/src/use-tl.ts +45 -0
  43. package/realtime-client/dist/index.cjs +163 -63
  44. package/realtime-client/dist/index.cjs.map +1 -1
  45. package/realtime-client/dist/index.d.ts +10 -6
  46. package/realtime-client/dist/index.js +153 -61
  47. package/realtime-client/dist/index.js.map +1 -1
  48. package/realtime-client/src/index.ts +2 -1
  49. package/realtime-client/src/pull-state.ts +4 -3
  50. package/realtime-client/src/{realtime-client-store.ts → realtime-client-stores/client-main-store.ts} +0 -8
  51. package/realtime-client/src/realtime-client-stores/client-sync-store.ts +15 -0
  52. package/realtime-client/src/realtime-client-stores/index.ts +2 -0
  53. package/realtime-client/src/sync-server-action.ts +131 -40
  54. package/realtime-client/src/sync-state.ts +19 -0
  55. package/realtime-react/dist/index.cjs +43 -26
  56. package/realtime-react/dist/index.cjs.map +1 -1
  57. package/realtime-react/dist/index.d.ts +3 -1
  58. package/realtime-react/dist/index.js +41 -25
  59. package/realtime-react/dist/index.js.map +1 -1
  60. package/realtime-react/src/index.ts +1 -0
  61. package/realtime-react/src/on-mount.ts +3 -21
  62. package/realtime-react/src/use-realtime-service.ts +1 -1
  63. package/realtime-react/src/use-single-effect.ts +29 -0
  64. package/realtime-react/src/use-sync-server-action.ts +4 -7
  65. package/realtime-react/src/use-sync.ts +17 -0
  66. package/realtime-server/dist/index.cjs +223 -40
  67. package/realtime-server/dist/index.cjs.map +1 -1
  68. package/realtime-server/dist/index.d.ts +140 -9
  69. package/realtime-server/dist/index.js +213 -43
  70. package/realtime-server/dist/index.js.map +1 -1
  71. package/realtime-server/src/index.ts +2 -0
  72. package/realtime-server/src/realtime-action-synchronizer.ts +87 -12
  73. package/realtime-server/src/realtime-family-provider.ts +2 -2
  74. package/realtime-server/src/realtime-mutable-family-provider.ts +2 -1
  75. package/realtime-server/src/realtime-server-stores/index.ts +2 -0
  76. package/realtime-server/src/realtime-server-stores/server-sync-store.ts +115 -0
  77. package/realtime-server/src/realtime-server-stores/server-user-store.ts +45 -0
  78. package/realtime-server/src/realtime-state-provider.ts +15 -8
  79. package/realtime-server/src/realtime-state-synchronizer.ts +23 -0
  80. package/realtime-testing/dist/index.cjs +65 -26
  81. package/realtime-testing/dist/index.cjs.map +1 -1
  82. package/realtime-testing/dist/index.d.ts +11 -7
  83. package/realtime-testing/dist/index.js +64 -26
  84. package/realtime-testing/dist/index.js.map +1 -1
  85. package/realtime-testing/src/setup-realtime-test.tsx +83 -43
  86. package/src/find-state.ts +8 -16
  87. package/src/logger.ts +1 -0
  88. package/src/transaction.ts +3 -3
  89. package/react/src/store-hooks.ts +0 -87
  90. package/realtime-server/src/realtime-server-store.ts +0 -39
@@ -0,0 +1,74 @@
1
+ import type { Json } from "atom.io/json"
2
+
3
+ import type {
4
+ MutableAtomFamilyToken,
5
+ MutableAtomToken,
6
+ ReadableFamilyToken,
7
+ ReadableToken,
8
+ ReadonlySelectorFamilyToken,
9
+ ReadonlySelectorToken,
10
+ RegularAtomFamilyToken,
11
+ RegularAtomToken,
12
+ WritableFamilyToken,
13
+ WritableSelectorFamilyToken,
14
+ WritableSelectorToken,
15
+ WritableToken,
16
+ } from "atom.io"
17
+ import type { Transceiver } from "../mutable"
18
+ import { NotFoundError } from "../not-found-error"
19
+ import type { Store } from "../store"
20
+
21
+ export function findInStore<
22
+ T extends Transceiver<any>,
23
+ J extends Json.Serializable,
24
+ K extends Json.Serializable,
25
+ Key extends K,
26
+ >(
27
+ token: MutableAtomFamilyToken<T, J, K>,
28
+ key: Key,
29
+ store: Store,
30
+ ): MutableAtomToken<T, J>
31
+
32
+ export function findInStore<T, K extends Json.Serializable, Key extends K>(
33
+ token: RegularAtomFamilyToken<T, K>,
34
+ key: Key,
35
+ store: Store,
36
+ ): RegularAtomToken<T>
37
+
38
+ export function findInStore<T, K extends Json.Serializable, Key extends K>(
39
+ token: WritableSelectorFamilyToken<T, K>,
40
+ key: Key,
41
+ store: Store,
42
+ ): WritableSelectorToken<T>
43
+
44
+ export function findInStore<T, K extends Json.Serializable, Key extends K>(
45
+ token: ReadonlySelectorFamilyToken<T, K>,
46
+ key: Key,
47
+ store: Store,
48
+ ): ReadonlySelectorToken<T>
49
+
50
+ export function findInStore<T, K extends Json.Serializable, Key extends K>(
51
+ token: WritableFamilyToken<T, K>,
52
+ key: Key,
53
+ store: Store,
54
+ ): WritableToken<T>
55
+
56
+ export function findInStore<T, K extends Json.Serializable, Key extends K>(
57
+ token: ReadableFamilyToken<T, K>,
58
+ key: Key,
59
+ store: Store,
60
+ ): ReadableToken<T>
61
+
62
+ export function findInStore(
63
+ token: ReadableFamilyToken<any, any>,
64
+ key: Json.Serializable,
65
+ store: Store,
66
+ ): ReadableToken<any> {
67
+ const familyKey = token.key
68
+ const family = store.families.get(familyKey)
69
+ if (family === undefined) {
70
+ throw new NotFoundError(token, store)
71
+ }
72
+ const state = family(key)
73
+ return state
74
+ }
@@ -2,3 +2,4 @@ export * from "./create-atom-family"
2
2
  export * from "./create-regular-atom-family"
3
3
  export * from "./create-readonly-selector-family"
4
4
  export * from "./create-selector-family"
5
+ export * from "./find-in-store"
@@ -1,6 +1,7 @@
1
1
  import type { TransactionUpdate } from "atom.io"
2
2
 
3
3
  import type { Store } from "../store"
4
+ import { isRootStore } from "../transaction/is-root-store"
4
5
  import { ingestAtomUpdate } from "./ingest-atom-update"
5
6
 
6
7
  export function ingestTransactionUpdate(
@@ -5,6 +5,7 @@ import type { Json } from "atom.io/json"
5
5
  import type { Store } from ".."
6
6
  import { newest, subscribeToState, subscribeToTimeline } from ".."
7
7
  import { createRegularAtom } from "../atom"
8
+ import { isChildStore, isRootStore } from "../transaction/is-root-store"
8
9
  import type { Transceiver } from "./transceiver"
9
10
 
10
11
  /**
@@ -51,19 +52,14 @@ export class Tracker<Mutable extends Transceiver<any>> {
51
52
  private observeCore(
52
53
  mutableState: MutableAtomToken<Mutable, any>,
53
54
  latestUpdateState: RegularAtomToken<typeof this.Update | null>,
54
- store: Store,
55
+ target: Store,
55
56
  ): void {
56
- const subscriptionKey = `tracker:${store.config.name}:${
57
- store.transactionMeta === null ? `main` : store.transactionMeta.update.key
57
+ const subscriptionKey = `tracker:${target.config.name}:${
58
+ isChildStore(target) ? target.transactionMeta.update.key : `main`
58
59
  }:${mutableState.key}`
59
- const originalInnerValue = getState(mutableState, store)
60
- const target = newest(store)
60
+ const originalInnerValue = getState(mutableState, target)
61
61
  this.unsubscribeFromInnerValue = originalInnerValue.subscribe(
62
- `tracker:${store.config.name}:${
63
- target.transactionMeta === null
64
- ? `main`
65
- : target.transactionMeta.update.key
66
- }`,
62
+ subscriptionKey,
67
63
  (update) => {
68
64
  if (target.operation.open) {
69
65
  const unsubscribe = target.on.operationClose.subscribe(
@@ -84,7 +80,6 @@ export class Tracker<Mutable extends Transceiver<any>> {
84
80
  (update) => {
85
81
  if (update.newValue !== update.oldValue) {
86
82
  this.unsubscribeFromInnerValue()
87
- const target = newest(store)
88
83
  this.unsubscribeFromInnerValue = update.newValue.subscribe(
89
84
  subscriptionKey,
90
85
  (update) => {
@@ -105,26 +100,27 @@ export class Tracker<Mutable extends Transceiver<any>> {
105
100
  }
106
101
  },
107
102
  subscriptionKey,
108
- store,
103
+ target,
109
104
  )
110
105
  }
111
106
 
112
107
  private updateCore<Core extends Transceiver<any>>(
113
108
  mutableState: MutableAtomToken<Core, Json.Serializable>,
114
109
  latestUpdateState: RegularAtomToken<typeof this.Update | null>,
115
- store: Store,
110
+ target: Store,
116
111
  ): void {
117
- const subscriptionKey = `tracker:${store.config.name}:${
118
- store.transactionMeta === null ? `main` : store.transactionMeta.update.key
112
+ const subscriptionKey = `tracker:${target.config.name}:${
113
+ isChildStore(target) ? target.transactionMeta.update.key : `main`
119
114
  }:${mutableState.key}`
120
115
  subscribeToState(
121
116
  latestUpdateState,
122
117
  ({ newValue, oldValue }) => {
123
- const timelineId = store.timelineAtoms.getRelatedKey(
118
+ const timelineId = target.timelineAtoms.getRelatedKey(
124
119
  latestUpdateState.key,
125
120
  )
121
+
126
122
  if (timelineId) {
127
- const timelineData = store.timelines.get(timelineId)
123
+ const timelineData = target.timelines.get(timelineId)
128
124
  if (timelineData?.timeTraveling) {
129
125
  const unsubscribe = subscribeToTimeline(
130
126
  { key: timelineId, type: `timeline` },
@@ -140,38 +136,45 @@ export class Tracker<Mutable extends Transceiver<any>> {
140
136
  }
141
137
  return transceiver
142
138
  },
143
- store,
139
+ target,
144
140
  )
145
141
  },
146
142
  subscriptionKey,
147
- store,
143
+ target,
148
144
  )
149
145
  return
150
146
  }
151
147
  }
152
148
 
153
- const unsubscribe = store.on.operationClose.subscribe(
149
+ const unsubscribe = target.on.operationClose.subscribe(
154
150
  subscriptionKey,
155
151
  () => {
156
152
  unsubscribe()
157
- const mutable = getState(mutableState, store)
158
- // debugger
153
+ const mutable = getState(mutableState, target)
159
154
  const updateNumber =
160
155
  newValue === null ? -1 : mutable.getUpdateNumber(newValue)
161
156
  const eventOffset = updateNumber - mutable.cacheUpdateNumber
162
157
  if (newValue && eventOffset === 1) {
163
- // ❗ new:"0=add:\"myHand\"",old:"0=add:\"deckId\""
164
158
  setState(
165
159
  mutableState,
166
160
  (transceiver) => (transceiver.do(newValue), transceiver),
167
- store,
161
+ target,
162
+ )
163
+ } else {
164
+ target.logger.info(
165
+ `❌`,
166
+ `mutable_atom`,
167
+ mutableState.key,
168
+ `could not be updated. Expected update number ${
169
+ mutable.cacheUpdateNumber + 1
170
+ }, but got ${updateNumber}`,
168
171
  )
169
172
  }
170
173
  },
171
174
  )
172
175
  },
173
176
  subscriptionKey,
174
- store,
177
+ target,
175
178
  )
176
179
  }
177
180
 
@@ -1,7 +1,7 @@
1
1
  import type { Json } from "atom.io/json"
2
2
 
3
3
  export interface Transceiver<Signal extends Json.Serializable> {
4
- do: (update: Signal) => void
4
+ do: (update: Signal) => number | `OUT_OF_RANGE` | null
5
5
  undo: (update: Signal) => void
6
6
  subscribe: (key: string, fn: (update: Signal) => void) => () => void
7
7
  cacheUpdateNumber: number
@@ -1,10 +1,21 @@
1
- import type { ReadableToken } from "atom.io"
1
+ import type {
2
+ ReadableFamilyToken,
3
+ ReadableToken,
4
+ TimelineToken,
5
+ TransactionToken,
6
+ } from "atom.io"
2
7
 
3
8
  import type { Store } from "./store"
4
9
 
5
10
  const capitalize = (str: string) => str[0].toUpperCase() + str.slice(1)
6
11
 
7
- function prettyPrintTokenType(token: ReadableToken<any>) {
12
+ type AtomIOToken =
13
+ | ReadableFamilyToken<any, any>
14
+ | ReadableToken<any>
15
+ | TimelineToken<any>
16
+ | TransactionToken<any>
17
+
18
+ function prettyPrintTokenType(token: AtomIOToken) {
8
19
  if (token.type === `readonly_selector`) {
9
20
  return `Readonly Selector`
10
21
  }
@@ -12,7 +23,7 @@ function prettyPrintTokenType(token: ReadableToken<any>) {
12
23
  }
13
24
 
14
25
  export class NotFoundError extends Error {
15
- public constructor(token: ReadableToken<any>, store: Store) {
26
+ public constructor(token: AtomIOToken, store: Store) {
16
27
  super(
17
28
  `${prettyPrintTokenType(token)} "${token.key}" not found in store "${
18
29
  store.config.name
@@ -2,6 +2,7 @@ import type { WritableToken } from "atom.io"
2
2
 
3
3
  import { newest } from "./lineage"
4
4
  import type { Store } from "./store"
5
+ import { isChildStore } from "./transaction/is-root-store"
5
6
 
6
7
  export type OperationProgress =
7
8
  | {
@@ -40,7 +41,7 @@ export const openOperation = (
40
41
  token.type,
41
42
  token.key,
42
43
  `operation start in store "${store.config.name}"${
43
- store.transactionMeta === null
44
+ !isChildStore(store)
44
45
  ? ``
45
46
  : ` ${store.transactionMeta.phase} "${store.transactionMeta.update.key}"`
46
47
  }`,
@@ -11,6 +11,7 @@ import { markDone } from "../operation"
11
11
  import { become } from "../set-state/become"
12
12
  import type { Store } from "../store"
13
13
  import { Subject } from "../subject"
14
+ import { isRootStore } from "../transaction/is-root-store"
14
15
  import { registerSelector } from "./register-selector"
15
16
 
16
17
  export const createWritableSelector = <T>(
@@ -45,7 +46,7 @@ export const createWritableSelector = <T>(
45
46
  )
46
47
  cacheValue(options.key, newValue, subject, store)
47
48
  markDone(options.key, store)
48
- if (target.transactionMeta === null) {
49
+ if (isRootStore(target)) {
49
50
  subject.next({ newValue, oldValue })
50
51
  }
51
52
  options.set(transactors, newValue)
@@ -1,11 +1,11 @@
1
1
  import type { Transactors, findState } from "atom.io"
2
- import { findInStore } from "atom.io"
3
2
 
3
+ import { findInStore } from "../families"
4
4
  import { newest } from "../lineage"
5
5
  import { readOrComputeValue } from "../read-or-compute-value"
6
6
  import { setAtomOrSelector } from "../set-state"
7
7
  import type { Store } from "../store"
8
- import { withdraw } from "../store"
8
+ import { withdraw, withdrawNewFamilyMember } from "../store"
9
9
  import { updateSelectorAtoms } from "./update-selector-atoms"
10
10
 
11
11
  export const registerSelector = (
@@ -15,10 +15,11 @@ export const registerSelector = (
15
15
  get: (dependency) => {
16
16
  const target = newest(store)
17
17
 
18
- const dependencyState = withdraw(dependency, store)
18
+ const dependencyState =
19
+ withdraw(dependency, store) ?? withdrawNewFamilyMember(dependency, store)
19
20
  if (dependencyState === undefined) {
20
21
  throw new Error(
21
- `State "${dependency.key}" not found in this store. Did you forget to initialize with the "atom" or "selector" function?`,
22
+ `State "${dependency.key}" not found in store "${store.config.name}".`,
22
23
  )
23
24
  }
24
25
  const dependencyValue = readOrComputeValue(dependencyState, store)
@@ -5,6 +5,7 @@ import type { Transceiver } from "../mutable"
5
5
  import { markDone } from "../operation"
6
6
  import { readOrComputeValue } from "../read-or-compute-value"
7
7
  import type { Store } from "../store"
8
+ import { isRootStore } from "../transaction/is-root-store"
8
9
  import { become } from "./become"
9
10
  import { copyMutableIfWithinTransaction } from "./copy-mutable-in-transaction"
10
11
  import { emitUpdate } from "./emit-update"
@@ -27,7 +28,7 @@ export const setAtom = <T>(
27
28
  markDone(atom.key, target)
28
29
  evictDownStream(atom, target)
29
30
  const update = { oldValue, newValue }
30
- if (target.transactionMeta === null) {
31
+ if (isRootStore(target)) {
31
32
  emitUpdate(atom, update, target)
32
33
  } else if (target.parent) {
33
34
  if (target.on.transactionApplying.state === null) {
@@ -37,7 +38,20 @@ export const setAtom = <T>(
37
38
  const mutableAtom = target.atoms.get(mutableKey) as Atom<any>
38
39
  let mutable: Transceiver<any> = target.valueMap.get(mutableKey)
39
40
  mutable = copyMutableIfWithinTransaction(mutable, mutableAtom, target)
40
- mutable.do(update.newValue)
41
+ const output = mutable.do(update.newValue)
42
+ if (output !== null) {
43
+ target.logger.warn(
44
+ `❌`,
45
+ `mutable_atom`,
46
+ mutableKey,
47
+ `could not be updated.`,
48
+ typeof output === `number`
49
+ ? `Expected update number ${
50
+ mutable.cacheUpdateNumber + 1
51
+ }, but got ${output}`
52
+ : output,
53
+ )
54
+ }
41
55
  }
42
56
  }
43
57
  }
@@ -4,6 +4,7 @@ import type { Atom } from ".."
4
4
  import { newest } from "../lineage"
5
5
  import { isTransceiver } from "../mutable"
6
6
  import type { Store } from "../store"
7
+ import { isChildStore } from "../transaction/is-root-store"
7
8
 
8
9
  function shouldUpdateBeStowed(key: string, update: StateUpdate<any>): boolean {
9
10
  // do not stow updates that aren't json
@@ -24,10 +25,7 @@ export const stowUpdate = <T>(
24
25
  ): void => {
25
26
  const { key } = state
26
27
  const target = newest(store)
27
- if (
28
- target.transactionMeta === null ||
29
- target.transactionMeta.phase !== `building`
30
- ) {
28
+ if (!isChildStore(target) || target.transactionMeta.phase !== `building`) {
31
29
  store.logger.error(
32
30
  `🐞`,
33
31
  `atom`,
@@ -27,7 +27,11 @@ import { getJsonToken, getUpdateToken } from "../mutable"
27
27
  import type { OperationProgress } from "../operation"
28
28
  import { StatefulSubject, Subject } from "../subject"
29
29
  import type { Timeline } from "../timeline"
30
- import type { Transaction, TransactionMeta } from "../transaction"
30
+ import type {
31
+ Transaction,
32
+ TransactionEpoch,
33
+ TransactionProgress,
34
+ } from "../transaction"
31
35
 
32
36
  export class Store implements Lineage {
33
37
  public parent: Store | null = null
@@ -82,11 +86,15 @@ export class Store implements Lineage {
82
86
  >(),
83
87
  transactionCreation: new Subject<TransactionToken<ƒn>>(),
84
88
  timelineCreation: new Subject<TimelineToken<unknown>>(),
85
- transactionApplying: new StatefulSubject<TransactionMeta<ƒn> | null>(null),
89
+ transactionApplying: new StatefulSubject<TransactionProgress<ƒn> | null>(
90
+ null,
91
+ ),
86
92
  operationClose: new Subject<OperationProgress>(),
87
93
  }
88
94
  public operation: OperationProgress = { open: false }
89
- public transactionMeta: TransactionMeta<ƒn> | null = null
95
+ public transactionMeta: TransactionEpoch | TransactionProgress<ƒn> = {
96
+ epoch: -1,
97
+ }
90
98
 
91
99
  public config: {
92
100
  name: string
@@ -113,7 +121,8 @@ export class Store implements Lineage {
113
121
  if (store !== null) {
114
122
  this.valueMap = new Map(store?.valueMap)
115
123
  this.operation = { ...store?.operation }
116
- this.transactionMeta = null
124
+ this.transactionMeta = { ...store?.transactionMeta }
125
+
117
126
  this.config = {
118
127
  ...store?.config,
119
128
  name,
@@ -30,7 +30,6 @@ export const addAtomToTimeline = (
30
30
  store.timelineAtoms.set({ atomKey: atom.key, timelineKey: tl.key })
31
31
 
32
32
  atom.subject.subscribe(`timeline`, (update) => {
33
- // debugger
34
33
  const target = newest(store)
35
34
  const currentSelectorKey =
36
35
  store.operation.open && store.operation.token.type === `selector`
@@ -40,8 +39,9 @@ export const addAtomToTimeline = (
40
39
  store.operation.open && store.operation.token.type === `selector`
41
40
  ? store.operation.time
42
41
  : null
43
- const currentTransactionKey = target.on.transactionApplying.state?.update.key
44
- const currentTransactionTime = target.on.transactionApplying.state?.time
42
+ const { transactionApplying } = target.on
43
+ const currentTransactionKey = transactionApplying.state?.update.key
44
+ const currentTransactionInstanceId = transactionApplying.state?.update.id
45
45
 
46
46
  store.logger.info(
47
47
  `⏳`,
@@ -93,7 +93,7 @@ export const addAtomToTimeline = (
93
93
  `timeline:${tl.key}`,
94
94
  (update) => {
95
95
  unsubscribe()
96
- if (tl.timeTraveling === null && currentTransactionTime) {
96
+ if (tl.timeTraveling === null && currentTransactionInstanceId) {
97
97
  if (tl.at !== tl.history.length) {
98
98
  tl.history.splice(tl.at)
99
99
  }
@@ -132,7 +132,7 @@ export const addAtomToTimeline = (
132
132
 
133
133
  const timelineTransactionUpdate: TimelineTransactionUpdate = {
134
134
  type: `transaction_update`,
135
- timestamp: currentTransactionTime,
135
+ timestamp: Date.now(),
136
136
  ...update,
137
137
  updates,
138
138
  }
@@ -1,9 +1,10 @@
1
1
  import { newest } from "../lineage"
2
2
  import type { Store } from "../store"
3
+ import { isChildStore } from "./is-root-store"
3
4
 
4
5
  export const abortTransaction = (store: Store): void => {
5
6
  const target = newest(store)
6
- if (target.transactionMeta === null || target.parent === null) {
7
+ if (!isChildStore(target)) {
7
8
  store.logger.warn(
8
9
  `🐞`,
9
10
  `transaction`,
@@ -4,6 +4,7 @@ import { ingestTransactionUpdate } from "../ingest-updates"
4
4
  import { newest } from "../lineage"
5
5
  import { withdraw } from "../store"
6
6
  import type { Store } from "../store"
7
+ import { isChildStore, isRootStore } from "./is-root-store"
7
8
 
8
9
  export const applyTransaction = <ƒ extends ƒn>(
9
10
  output: ReturnType<ƒ>,
@@ -13,7 +14,7 @@ export const applyTransaction = <ƒ extends ƒn>(
13
14
  const { parent } = child
14
15
  if (
15
16
  parent === null ||
16
- child.transactionMeta === null ||
17
+ !isChildStore(child) ||
17
18
  child.transactionMeta?.phase !== `building`
18
19
  ) {
19
20
  store.logger.warn(
@@ -57,7 +58,8 @@ export const applyTransaction = <ƒ extends ƒn>(
57
58
  }
58
59
  }
59
60
  ingestTransactionUpdate(`newValue`, child.transactionMeta.update, parent)
60
- if (parent.transactionMeta === null) {
61
+ if (isRootStore(parent)) {
62
+ parent.transactionMeta.epoch = child.transactionMeta.update.epoch
61
63
  const myTransaction = withdraw<ƒ>(
62
64
  { key: child.transactionMeta.update.key, type: `transaction` },
63
65
  store,
@@ -69,7 +71,7 @@ export const applyTransaction = <ƒ extends ƒn>(
69
71
  child.transactionMeta.update.key,
70
72
  `Finished applying transaction.`,
71
73
  )
72
- } else {
74
+ } else if (isChildStore(parent)) {
73
75
  parent.transactionMeta.update.updates.push(child.transactionMeta.update)
74
76
  }
75
77
  parent.on.transactionApplying.next(null)
@@ -1,28 +1,31 @@
1
- import { findInStore, getState, runTransaction, setState } from "atom.io"
2
- import type { findState } from "atom.io"
1
+ import { getState, runTransaction, setState } from "atom.io"
2
+ import type { findState, ƒn } from "atom.io"
3
3
 
4
4
  import { Junction } from "~/packages/rel8/junction/src"
5
5
 
6
+ import type { TransactionProgress } from "."
7
+ import { findInStore } from "../families"
6
8
  import { getEnvironmentData } from "../get-environment-data"
7
9
  import { LazyMap } from "../lazy-map"
8
10
  import { newest } from "../lineage"
9
11
  import type { Store } from "../store"
12
+ import type { ChildStore, RootStore } from "./is-root-store"
13
+ import { isRootStore } from "./is-root-store"
10
14
 
11
15
  export const buildTransaction = (
12
16
  key: string,
13
17
  params: any[],
14
18
  store: Store,
15
19
  id?: string,
16
- ): void => {
17
- const parent = newest(store)
18
- const child: Store = {
20
+ ): ChildStore => {
21
+ const parent = newest(store) as ChildStore | RootStore
22
+ const childBase: Omit<ChildStore, `transactionMeta`> = {
19
23
  parent,
20
24
  child: null,
21
25
  on: parent.on,
22
26
  loggers: parent.loggers,
23
27
  logger: parent.logger,
24
28
  config: parent.config,
25
- transactionMeta: null,
26
29
  atoms: new LazyMap(parent.atoms),
27
30
  atomsThatAreDefault: new Set(parent.atomsThatAreDefault),
28
31
  families: new LazyMap(parent.families),
@@ -39,12 +42,12 @@ export const buildTransaction = (
39
42
  selectors: new LazyMap(parent.selectors),
40
43
  valueMap: new LazyMap(parent.valueMap),
41
44
  }
42
- child.transactionMeta = {
43
- phase: `building`,
44
- time: Date.now(),
45
+ const transactionMeta: TransactionProgress<ƒn> = {
46
+ phase: `building` as const,
45
47
  update: {
46
48
  key,
47
49
  id: id ?? Math.random().toString(36).slice(2),
50
+ epoch: isRootStore(parent) ? parent.transactionMeta.epoch + 1 : NaN,
48
51
  updates: [],
49
52
  params,
50
53
  output: undefined,
@@ -57,6 +60,9 @@ export const buildTransaction = (
57
60
  env: () => getEnvironmentData(child),
58
61
  },
59
62
  }
63
+ const child: ChildStore = Object.assign(childBase, {
64
+ transactionMeta,
65
+ })
60
66
  parent.child = child
61
67
  store.logger.info(
62
68
  `🛫`,
@@ -65,4 +71,5 @@ export const buildTransaction = (
65
71
  `Building transaction with params:`,
66
72
  params,
67
73
  )
74
+ return child
68
75
  }
@@ -29,11 +29,10 @@ export function createTransaction<ƒ extends ƒn>(
29
29
  key: options.key,
30
30
  type: `transaction`,
31
31
  run: (params: Parameters<ƒ>, id?: string) => {
32
- buildTransaction(options.key, params, store, id)
32
+ const childStore = buildTransaction(options.key, params, store, id)
33
33
  try {
34
34
  const target = newest(store)
35
- // biome-ignore lint/style/noNonNullAssertion: this happens right above
36
- const { transactors } = target.transactionMeta!
35
+ const { transactors } = childStore.transactionMeta
37
36
  const output = options.do(transactors, ...params)
38
37
  applyTransaction(output, target)
39
38
  return output
@@ -8,9 +8,10 @@ export * from "./create-transaction"
8
8
  export const TRANSACTION_PHASES = [`idle`, `building`, `applying`] as const
9
9
  export type TransactionPhase = (typeof TRANSACTION_PHASES)[number]
10
10
 
11
- export type TransactionMeta<ƒ extends ƒn> = {
11
+ export type TransactionProgress<ƒ extends ƒn> = {
12
12
  phase: `applying` | `building`
13
- time: number
14
13
  update: TransactionUpdate<ƒ>
15
14
  transactors: TransactorsWithRunAndEnv
16
15
  }
16
+
17
+ export type TransactionEpoch = { epoch: number }
@@ -0,0 +1,23 @@
1
+ import type { ƒn } from "atom.io"
2
+
3
+ import type { TransactionEpoch, TransactionProgress } from "."
4
+ import type { Store } from "../store"
5
+
6
+ export interface RootStore extends Store {
7
+ transactionMeta: TransactionEpoch
8
+ parent: null
9
+ child: ChildStore | null
10
+ }
11
+ export interface ChildStore extends Store {
12
+ transactionMeta: TransactionProgress<ƒn>
13
+ parent: ChildStore | RootStore
14
+ child: ChildStore | null
15
+ }
16
+
17
+ export function isRootStore(store: Store): store is RootStore {
18
+ return `epoch` in store.transactionMeta
19
+ }
20
+
21
+ export function isChildStore(store: Store): store is ChildStore {
22
+ return `phase` in store.transactionMeta
23
+ }