atom.io 0.17.0 → 0.18.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.
Files changed (153) hide show
  1. package/data/dist/index.cjs +62 -40
  2. package/data/dist/index.cjs.map +1 -1
  3. package/data/dist/index.d.ts +8 -2
  4. package/data/dist/index.js +64 -42
  5. package/data/dist/index.js.map +1 -1
  6. package/data/src/dict.ts +8 -4
  7. package/data/src/join.ts +74 -33
  8. package/data/src/struct-family.ts +18 -17
  9. package/dist/chunk-IZHOMSXA.js +331 -0
  10. package/dist/chunk-IZHOMSXA.js.map +1 -0
  11. package/dist/chunk-JDUNWJFB.js +18 -0
  12. package/dist/chunk-JDUNWJFB.js.map +1 -0
  13. package/dist/index.cjs +4 -10
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.ts +66 -51
  16. package/dist/index.js +5 -11
  17. package/dist/index.js.map +1 -1
  18. package/internal/dist/index.cjs +187 -58
  19. package/internal/dist/index.cjs.map +1 -1
  20. package/internal/dist/index.d.ts +95 -71
  21. package/internal/dist/index.js +179 -53
  22. package/internal/dist/index.js.map +1 -1
  23. package/internal/src/arbitrary.ts +3 -0
  24. package/internal/src/atom/delete-atom.ts +7 -6
  25. package/internal/src/caching.ts +6 -4
  26. package/internal/src/families/find-in-store.ts +16 -0
  27. package/internal/src/get-environment-data.ts +4 -7
  28. package/internal/src/index.ts +6 -5
  29. package/internal/src/ingest-updates/ingest-atom-update.ts +6 -2
  30. package/internal/src/ingest-updates/ingest-transaction-update.ts +0 -1
  31. package/internal/src/selector/create-standalone-selector.ts +0 -2
  32. package/internal/src/set-state/copy-mutable-if-needed.ts +5 -0
  33. package/internal/src/set-state/emit-update.ts +25 -11
  34. package/internal/src/set-state/set-atom.ts +15 -18
  35. package/internal/src/store/store.ts +14 -2
  36. package/internal/src/store/withdraw.ts +72 -2
  37. package/internal/src/subscribe/subscribe-to-timeline.ts +2 -2
  38. package/internal/src/subscribe/subscribe-to-transaction.ts +2 -2
  39. package/internal/src/timeline/create-timeline.ts +12 -1
  40. package/internal/src/transaction/act-upon-store.ts +19 -0
  41. package/internal/src/transaction/apply-transaction.ts +6 -1
  42. package/internal/src/transaction/assign-transaction-to-continuity.ts +18 -0
  43. package/internal/src/transaction/build-transaction.ts +7 -6
  44. package/internal/src/transaction/create-transaction.ts +1 -1
  45. package/internal/src/transaction/get-epoch-number.ts +40 -0
  46. package/internal/src/transaction/index.ts +10 -1
  47. package/internal/src/transaction/set-epoch-number.ts +30 -0
  48. package/introspection/dist/index.cjs.map +1 -1
  49. package/introspection/dist/index.d.ts +3 -3
  50. package/introspection/dist/index.js.map +1 -1
  51. package/introspection/src/attach-introspection-states.ts +6 -2
  52. package/introspection/src/attach-timeline-family.ts +5 -2
  53. package/introspection/src/attach-transaction-logs.ts +2 -2
  54. package/json/dist/index.d.ts +3 -1
  55. package/json/src/index.ts +4 -0
  56. package/package.json +241 -230
  57. package/react/dist/index.cjs.map +1 -1
  58. package/react/dist/index.d.ts +1 -1
  59. package/react/dist/index.js.map +1 -1
  60. package/react/src/use-json.ts +1 -1
  61. package/react-devtools/dist/index.cjs +131 -134
  62. package/react-devtools/dist/index.cjs.map +1 -1
  63. package/react-devtools/dist/index.css +2 -2
  64. package/react-devtools/dist/index.css.map +1 -1
  65. package/react-devtools/dist/index.d.ts +3 -3
  66. package/react-devtools/dist/index.js +91 -108
  67. package/react-devtools/dist/index.js.map +1 -1
  68. package/react-devtools/src/StateEditor.tsx +4 -4
  69. package/react-devtools/src/StateIndex.tsx +1 -4
  70. package/react-devtools/src/TimelineIndex.tsx +3 -3
  71. package/react-devtools/src/TransactionIndex.tsx +9 -8
  72. package/react-devtools/src/index.ts +2 -2
  73. package/realtime/dist/index.cjs +120 -0
  74. package/realtime/dist/index.cjs.map +1 -0
  75. package/realtime/dist/index.d.ts +146 -0
  76. package/realtime/dist/index.js +111 -0
  77. package/realtime/dist/index.js.map +1 -0
  78. package/realtime/package.json +16 -0
  79. package/realtime/src/index.ts +2 -0
  80. package/realtime/src/realtime-continuity.ts +162 -0
  81. package/realtime/src/shared-room-store.ts +48 -0
  82. package/realtime-client/dist/index.cjs +424 -170
  83. package/realtime-client/dist/index.cjs.map +1 -1
  84. package/realtime-client/dist/index.d.ts +15 -11
  85. package/realtime-client/dist/index.js +96 -177
  86. package/realtime-client/dist/index.js.map +1 -1
  87. package/realtime-client/src/index.ts +8 -7
  88. package/realtime-client/src/{pull-family-member.ts → pull-atom-family-member.ts} +2 -2
  89. package/realtime-client/src/{pull-state.ts → pull-atom.ts} +2 -2
  90. package/realtime-client/src/{pull-mutable-family-member.ts → pull-mutable-atom-family-member.ts} +6 -6
  91. package/realtime-client/src/{pull-mutable.ts → pull-mutable-atom.ts} +1 -1
  92. package/realtime-client/src/pull-selector-family-member.ts +42 -0
  93. package/realtime-client/src/pull-selector.ts +38 -0
  94. package/realtime-client/src/realtime-client-stores/client-main-store.ts +12 -2
  95. package/realtime-client/src/realtime-client-stores/client-sync-store.ts +7 -7
  96. package/realtime-client/src/sync-continuity.ts +368 -0
  97. package/realtime-react/dist/index.cjs +367 -27
  98. package/realtime-react/dist/index.cjs.map +1 -1
  99. package/realtime-react/dist/index.d.ts +24 -8
  100. package/realtime-react/dist/index.js +38 -22
  101. package/realtime-react/dist/index.js.map +1 -1
  102. package/realtime-react/src/index.ts +6 -5
  103. package/realtime-react/src/use-pull-atom-family-member.ts +21 -0
  104. package/realtime-react/src/{use-sync.ts → use-pull-atom.ts} +4 -4
  105. package/realtime-react/src/{use-pull-mutable.ts → use-pull-mutable-atom.ts} +4 -3
  106. package/realtime-react/src/use-pull-mutable-family-member.ts +9 -4
  107. package/realtime-react/src/use-pull-selector-family-member.ts +21 -0
  108. package/realtime-react/src/{use-pull.ts → use-pull-selector.ts} +7 -5
  109. package/realtime-react/src/use-push.ts +3 -2
  110. package/realtime-react/src/use-server-action.ts +3 -2
  111. package/realtime-react/src/use-sync-continuity.ts +12 -0
  112. package/realtime-server/dist/index.cjs +769 -371
  113. package/realtime-server/dist/index.cjs.map +1 -1
  114. package/realtime-server/dist/index.d.ts +130 -60
  115. package/realtime-server/dist/index.js +753 -361
  116. package/realtime-server/dist/index.js.map +1 -1
  117. package/realtime-server/src/index.ts +17 -3
  118. package/realtime-server/src/ipc-sockets/child-socket.ts +135 -0
  119. package/realtime-server/src/ipc-sockets/custom-socket.ts +90 -0
  120. package/realtime-server/src/ipc-sockets/index.ts +3 -0
  121. package/realtime-server/src/ipc-sockets/parent-socket.ts +185 -0
  122. package/realtime-server/src/realtime-action-receiver.ts +8 -5
  123. package/realtime-server/src/realtime-continuity-synchronizer.ts +376 -0
  124. package/realtime-server/src/realtime-family-provider.ts +30 -71
  125. package/realtime-server/src/realtime-mutable-family-provider.ts +24 -86
  126. package/realtime-server/src/realtime-server-stores/index.ts +4 -1
  127. package/realtime-server/src/realtime-server-stores/realtime-continuity-store.ts +109 -0
  128. package/realtime-server/src/realtime-server-stores/server-room-external-actions.ts +64 -0
  129. package/realtime-server/src/realtime-server-stores/server-room-external-store.ts +42 -0
  130. package/realtime-server/src/realtime-server-stores/server-sync-store.ts +51 -98
  131. package/realtime-server/src/realtime-server-stores/server-user-store.ts +14 -29
  132. package/realtime-server/src/realtime-state-receiver.ts +0 -1
  133. package/realtime-testing/dist/index.cjs +34 -32
  134. package/realtime-testing/dist/index.cjs.map +1 -1
  135. package/realtime-testing/dist/index.d.ts +1 -0
  136. package/realtime-testing/dist/index.js +33 -31
  137. package/realtime-testing/dist/index.js.map +1 -1
  138. package/realtime-testing/src/setup-realtime-test.tsx +44 -32
  139. package/src/atom.ts +49 -31
  140. package/src/logger.ts +14 -5
  141. package/src/selector.ts +44 -25
  142. package/src/subscribe.ts +2 -1
  143. package/src/timeline.ts +4 -4
  144. package/src/transaction.ts +13 -17
  145. package/src/validators.ts +15 -9
  146. package/dist/chunk-H4Q5FTPZ.js +0 -11
  147. package/dist/chunk-H4Q5FTPZ.js.map +0 -1
  148. package/internal/src/set-state/copy-mutable-in-transaction.ts +0 -19
  149. package/realtime-client/src/sync-server-action.ts +0 -170
  150. package/realtime-client/src/sync-state.ts +0 -19
  151. package/realtime-react/src/use-pull-family-member.ts +0 -16
  152. package/realtime-react/src/use-sync-server-action.ts +0 -16
  153. package/realtime-server/src/realtime-action-synchronizer.ts +0 -152
@@ -0,0 +1,185 @@
1
+ import { IMPLICIT, Subject } from "atom.io/internal"
2
+ import { parseJson, stringifyJson } from "atom.io/json"
3
+ import type { Json } from "atom.io/json"
4
+
5
+ import { SetRTX } from "atom.io/transceivers/set-rtx"
6
+ import { CustomSocket } from "./custom-socket"
7
+ import type { EventBuffer, Events } from "./custom-socket"
8
+
9
+ export class SubjectSocket<
10
+ I extends Events,
11
+ O extends Events,
12
+ > extends CustomSocket<I, O> {
13
+ public in: Subject<[string, ...Json.Serializable[]]>
14
+ public out: Subject<[string, ...Json.Serializable[]]>
15
+ public id = `no_id_retrieved`
16
+ public disposalFunctions: (() => void)[] = []
17
+
18
+ public constructor(id: string) {
19
+ super((...args) => {
20
+ this.out.next(args as any)
21
+ return this
22
+ })
23
+ this.id = id
24
+ this.in = new Subject()
25
+ this.out = new Subject()
26
+ this.in.subscribe(`socket`, (event) => {
27
+ this.handleEvent(...(event as [string, ...I[keyof I]]))
28
+ })
29
+ }
30
+
31
+ public dispose(): void {
32
+ for (const dispose of this.disposalFunctions) {
33
+ dispose()
34
+ }
35
+ }
36
+ }
37
+
38
+ export class ParentSocket<
39
+ I extends Events & {
40
+ [id in string as `user:${id}`]: [string, ...Json.Serializable[]]
41
+ } & {
42
+ /* eslint-disable quotes */
43
+ "user-joins": [string]
44
+ "user-leaves": [string]
45
+ /* eslint-enable quotes */
46
+ },
47
+ O extends Events & {
48
+ [id in string as `relay:${id}`]: [string, ...Json.Serializable[]]
49
+ },
50
+ > extends CustomSocket<I, O> {
51
+ protected incompleteData = ``
52
+ protected unprocessedEvents: string[] = []
53
+ protected relays: Map<string, SubjectSocket<any, any>>
54
+ protected relayServices: ((
55
+ socket: SubjectSocket<any, any>,
56
+ ) => (() => void) | void)[]
57
+ protected process: NodeJS.Process
58
+
59
+ public id = `#####`
60
+
61
+ protected log(...args: any[]): void {
62
+ this.process.stderr.write(
63
+ stringifyJson(
64
+ args.map((arg) =>
65
+ arg instanceof SetRTX
66
+ ? `{ ${arg.toJSON().members.join(` | `)} }`
67
+ : arg,
68
+ ),
69
+ ) + `\x03`,
70
+ )
71
+ }
72
+ public logger = {
73
+ info: (...args: any[]): void => this.log(`i`, ...args),
74
+ warn: (...args: any[]): void => this.log(`w`, ...args),
75
+ error: (...args: any[]): void => this.log(`e`, ...args),
76
+ }
77
+
78
+ public constructor() {
79
+ super((event, ...args) => {
80
+ const stringifiedEvent = JSON.stringify([event, ...args])
81
+ this.process.stdout.write(stringifiedEvent + `\x03`)
82
+ return this
83
+ })
84
+ this.process = process
85
+ this.process.stdin.resume()
86
+ this.relays = new Map()
87
+ this.relayServices = []
88
+
89
+ this.process.stdin.on(
90
+ `data`,
91
+ <Event extends keyof I>(buffer: EventBuffer<string, I[Event]>) => {
92
+ const chunk = buffer.toString()
93
+ this.unprocessedEvents.push(...chunk.split(`\x03`))
94
+ const newInput = this.unprocessedEvents.shift()
95
+ this.incompleteData += newInput || ``
96
+
97
+ try {
98
+ const parsedEvent = parseJson(this.incompleteData)
99
+ this.logger.info(`🎰`, `received`, parsedEvent)
100
+ this.handleEvent(...(parsedEvent as [string, ...I[keyof I]]))
101
+ while (this.unprocessedEvents.length > 0) {
102
+ const event = this.unprocessedEvents.shift()
103
+ if (event) {
104
+ if (this.unprocessedEvents.length === 0) {
105
+ this.incompleteData = event
106
+ }
107
+ const parsedEvent = parseJson(event)
108
+ this.handleEvent(...(parsedEvent as [string, ...I[keyof I]]))
109
+ }
110
+ }
111
+ this.incompleteData = ``
112
+ } catch (thrown) {
113
+ if (thrown instanceof Error) {
114
+ this.logger.error(`❗`, thrown.message, thrown.cause, thrown.stack)
115
+ }
116
+ }
117
+ },
118
+ )
119
+
120
+ this.on(`exit`, () => {
121
+ process.exit(0)
122
+ })
123
+ process.on(`exit`, () => {
124
+ this.logger.info(`🔥`, this.id, `exited`)
125
+ process.exit(0)
126
+ })
127
+ process.on(`end`, () => {
128
+ this.logger.info(`🔥`, this.id, `ended`)
129
+ process.exit(0)
130
+ })
131
+ process.on(`SIGTERM`, () => {
132
+ this.logger.error(`🔥`, this.id, `terminated`)
133
+ process.exit(0)
134
+ })
135
+ process.on(`SIGINT`, () => {
136
+ this.logger.error(`🔥`, this.id, `interrupted`)
137
+ process.exit(0)
138
+ })
139
+
140
+ if (process.pid) {
141
+ this.id = process.pid?.toString()
142
+ }
143
+
144
+ this.on(`user-joins`, (username) => {
145
+ this.logger.info(`👤`, `user`, username, `joined`)
146
+ const relay = new SubjectSocket(`user:${username}`)
147
+ this.relays.set(username, relay)
148
+ this.logger.info(
149
+ `🔗`,
150
+ `attaching services:`,
151
+ `[${[...this.relayServices.keys()].join(`, `)}]`,
152
+ )
153
+ for (const attachServices of this.relayServices) {
154
+ const cleanup = attachServices(relay)
155
+ if (cleanup) {
156
+ relay.disposalFunctions.push(cleanup)
157
+ }
158
+ }
159
+ this.on(`user:${username}`, (...data) => {
160
+ relay.in.next(data)
161
+ })
162
+ relay.out.subscribe(`socket`, (data) => {
163
+ this.emit(...(data as [string, ...O[keyof O]]))
164
+ })
165
+ })
166
+
167
+ this.on(`user-leaves`, (username) => {
168
+ const relay = this.relays.get(username)
169
+ this.off(`relay:${username}`)
170
+ if (relay) {
171
+ relay.dispose()
172
+ this.relays.delete(username)
173
+ }
174
+ })
175
+
176
+ process.stdout.write(`✨`)
177
+ }
178
+
179
+ public relay(
180
+ attachServices: (socket: SubjectSocket<any, any>) => (() => void) | void,
181
+ ): void {
182
+ this.logger.info(`🔗`, `running relay method`)
183
+ this.relayServices.push(attachServices)
184
+ }
185
+ }
@@ -1,5 +1,6 @@
1
- import * as AtomIO from "atom.io"
2
- import { IMPLICIT } from "atom.io/internal"
1
+ import type * as AtomIO from "atom.io"
2
+ import { IMPLICIT, actUponStore } from "atom.io/internal"
3
+ import type { JsonIO } from "atom.io/json"
3
4
 
4
5
  import type { ServerConfig } from "."
5
6
 
@@ -8,15 +9,17 @@ export function realtimeActionReceiver({
8
9
  socket,
9
10
  store = IMPLICIT.STORE,
10
11
  }: ServerConfig) {
11
- return function actionReceiver<ƒ extends AtomIO.ƒn>(
12
+ return function actionReceiver<ƒ extends JsonIO>(
12
13
  tx: AtomIO.TransactionToken<ƒ>,
13
14
  ): () => void {
14
- const fillTransactionRequest = (update: AtomIO.TransactionUpdate<ƒ>) => {
15
+ const fillTransactionRequest = (
16
+ update: Pick<AtomIO.TransactionUpdate<ƒ>, `id` | `params`>,
17
+ ) => {
15
18
  const performanceKey = `tx-run:${tx.key}:${update.id}`
16
19
  const performanceKeyStart = `${performanceKey}:start`
17
20
  const performanceKeyEnd = `${performanceKey}:end`
18
21
  performance.mark(performanceKeyStart)
19
- AtomIO.runTransaction<ƒ>(tx, update.id, store)(...update.params)
22
+ actUponStore<ƒ>(tx, update.id, store)(...update.params)
20
23
  performance.mark(performanceKeyEnd)
21
24
  const metric = performance.measure(
22
25
  performanceKey,
@@ -0,0 +1,376 @@
1
+ import type * as AtomIO from "atom.io"
2
+ import {
3
+ IMPLICIT,
4
+ actUponStore,
5
+ findInStore,
6
+ getFromStore,
7
+ getJsonToken,
8
+ isRootStore,
9
+ subscribeToState,
10
+ subscribeToTransaction,
11
+ } from "atom.io/internal"
12
+ import type { Json, JsonIO } from "atom.io/json"
13
+ import type { ContinuityToken } from "atom.io/realtime"
14
+
15
+ import type { ServerConfig, Socket } from "."
16
+ import { socketAtoms, usersOfSockets } from "."
17
+
18
+ import {
19
+ redactTransactionUpdateContent,
20
+ userUnacknowledgedQueues,
21
+ } from "./realtime-server-stores"
22
+
23
+ export type RealtimeContinuitySynchronizer = ReturnType<
24
+ typeof realtimeContinuitySynchronizer
25
+ >
26
+ export function realtimeContinuitySynchronizer({
27
+ socket: initialSocket,
28
+ store = IMPLICIT.STORE,
29
+ }: ServerConfig) {
30
+ return function synchronizer(continuity: ContinuityToken): () => void {
31
+ let socket: Socket | null = initialSocket
32
+
33
+ const continuityKey = continuity.key
34
+ const userKeyState = findInStore(
35
+ usersOfSockets.states.userKeyOfSocket,
36
+ socket.id,
37
+ store,
38
+ )
39
+ const userKey = getFromStore(userKeyState, store)
40
+ if (!userKey) {
41
+ store.logger.error(
42
+ `❌`,
43
+ `continuity`,
44
+ continuityKey,
45
+ `Tried to create a synchronizer for a socket (${socket.id}) that is not connected to a user.`,
46
+ )
47
+ return () => {}
48
+ }
49
+ const socketKeyState = findInStore(
50
+ usersOfSockets.states.socketKeyOfUser,
51
+ userKey,
52
+ store,
53
+ )
54
+ subscribeToState(
55
+ socketKeyState,
56
+ ({ newValue: newSocketKey }) => {
57
+ store.logger.info(
58
+ `👋`,
59
+ `continuity`,
60
+ continuityKey,
61
+ `seeing ${userKey} on new socket ${newSocketKey}`,
62
+ )
63
+ if (newSocketKey === null) {
64
+ store.logger.error(
65
+ `❌`,
66
+ `continuity`,
67
+ continuityKey,
68
+ `Tried to create a synchronizer for a user (${userKey}) that is not connected to a socket.`,
69
+ )
70
+ return
71
+ }
72
+ const newSocketState = findInStore(socketAtoms, newSocketKey, store)
73
+ const newSocket = getFromStore(newSocketState, store)
74
+ socket = newSocket
75
+ },
76
+ `sync-continuity:${continuityKey}:${userKey}`,
77
+ store,
78
+ )
79
+
80
+ const userUnacknowledgedQueue = findInStore(
81
+ userUnacknowledgedQueues,
82
+ userKey,
83
+ store,
84
+ )
85
+ const userUnacknowledgedUpdates = getFromStore(
86
+ userUnacknowledgedQueue,
87
+ store,
88
+ )
89
+ const unsubscribeFunctions: (() => void)[] = []
90
+
91
+ const revealPerspectives = (): (() => void) => {
92
+ const unsubscribeFunctions: (() => void)[] = []
93
+ for (const perspective of continuity.perspectives) {
94
+ const { viewAtoms } = perspective
95
+ const userViewState = findInStore(viewAtoms, userKey, store)
96
+ const unsubscribe = subscribeToState(
97
+ userViewState,
98
+ ({ oldValue, newValue }) => {
99
+ const oldKeys = oldValue.map((token) => token.key)
100
+ const newKeys = newValue.map((token) => token.key)
101
+ const concealed = oldValue.filter(
102
+ (token) => !newKeys.includes(token.key),
103
+ )
104
+ const revealed = newValue
105
+ .filter((token) => !oldKeys.includes(token.key))
106
+ .flatMap((token) => {
107
+ const resourceToken =
108
+ token.type === `mutable_atom` ? getJsonToken(token) : token
109
+ const resource = getFromStore(resourceToken, store)
110
+ return [resourceToken, resource]
111
+ })
112
+ store.logger.info(
113
+ `👁`,
114
+ `atom`,
115
+ perspective.resourceAtoms.key,
116
+ `${userKey} has a new perspective`,
117
+ { oldKeys, newKeys, revealed, concealed },
118
+ )
119
+ if (revealed.length > 0) {
120
+ socket?.emit(`reveal:${continuityKey}`, revealed)
121
+ }
122
+ if (concealed.length > 0) {
123
+ socket?.emit(`conceal:${continuityKey}`, concealed)
124
+ }
125
+ },
126
+ `sync-continuity:${continuityKey}:${userKey}:perspective:${perspective.resourceAtoms.key}`,
127
+ store,
128
+ )
129
+ unsubscribeFunctions.push(unsubscribe)
130
+ }
131
+ return () => {
132
+ for (const unsubscribe of unsubscribeFunctions) unsubscribe()
133
+ }
134
+ }
135
+ const unsubscribeFromPerspectives = revealPerspectives()
136
+
137
+ const sendInitialPayload = () => {
138
+ const initialPayload: Json.Serializable[] = []
139
+ for (const atom of continuity.globals) {
140
+ const resourceToken =
141
+ atom.type === `mutable_atom` ? getJsonToken(atom) : atom
142
+ initialPayload.push(resourceToken, getFromStore(atom, store))
143
+ }
144
+ for (const perspective of continuity.perspectives) {
145
+ const { viewAtoms, resourceAtoms } = perspective
146
+ const userViewState = findInStore(viewAtoms, userKey, store)
147
+ const userView = getFromStore(userViewState, store)
148
+ store.logger.info(`👁`, `atom`, resourceAtoms.key, `${userKey} can see`, {
149
+ viewAtoms,
150
+ resourceAtoms,
151
+ userView,
152
+ })
153
+ for (const visibleToken of userView) {
154
+ const resourceToken =
155
+ visibleToken.type === `mutable_atom`
156
+ ? getJsonToken(visibleToken)
157
+ : visibleToken
158
+ const resource = getFromStore(resourceToken, store)
159
+
160
+ initialPayload.push(resourceToken, resource)
161
+ }
162
+ }
163
+
164
+ const epoch = isRootStore(store)
165
+ ? store.transactionMeta.epoch.get(continuityKey) ?? null
166
+ : null
167
+
168
+ socket?.emit(`continuity-init:${continuityKey}`, epoch, initialPayload)
169
+
170
+ for (const transaction of continuity.actions) {
171
+ const unsubscribeFromTransaction = subscribeToTransaction(
172
+ transaction,
173
+ (update) => {
174
+ try {
175
+ const visibleKeys = continuity.globals
176
+ .map((atom) => atom.key)
177
+ .concat(
178
+ continuity.perspectives.flatMap((perspective) => {
179
+ const { viewAtoms } = perspective
180
+ const userPerspectiveTokenState = findInStore(
181
+ viewAtoms,
182
+ userKey,
183
+ store,
184
+ )
185
+ const visibleTokens = getFromStore(
186
+ userPerspectiveTokenState,
187
+ store,
188
+ )
189
+ return visibleTokens.map((token) => {
190
+ const key =
191
+ token.type === `mutable_atom`
192
+ ? `*` + token.key
193
+ : token.key
194
+ return key
195
+ })
196
+ }),
197
+ )
198
+ const redactedUpdates = redactTransactionUpdateContent(
199
+ visibleKeys,
200
+ update.updates,
201
+ )
202
+ const redactedUpdate = {
203
+ ...update,
204
+ updates: redactedUpdates,
205
+ }
206
+ // setIntoStore(
207
+ // userUnacknowledgedQueue,
208
+ // (updates) => {
209
+ // if (redactedUpdate) {
210
+ // updates.push(redactedUpdate)
211
+ // updates.sort((a, b) => a.epoch - b.epoch)
212
+ // }
213
+ // return updates
214
+ // },
215
+ // store,
216
+ // )
217
+
218
+ socket?.emit(
219
+ `tx-new:${continuityKey}`,
220
+ redactedUpdate as Json.Serializable,
221
+ )
222
+ } catch (thrown) {
223
+ if (thrown instanceof Error) {
224
+ store.logger.error(
225
+ `❌`,
226
+ `continuity`,
227
+ continuityKey,
228
+ `failed to send update from transaction ${transaction.key} to ${userKey}`,
229
+ thrown.message,
230
+ )
231
+ }
232
+ }
233
+ },
234
+ `sync-continuity:${continuityKey}:${userKey}`,
235
+ store,
236
+ )
237
+ unsubscribeFunctions.push(unsubscribeFromTransaction)
238
+ }
239
+ }
240
+ socket.off(`get:${continuityKey}`, sendInitialPayload)
241
+ socket.on(`get:${continuityKey}`, sendInitialPayload)
242
+
243
+ const fillTransactionRequest = (
244
+ update: Pick<AtomIO.TransactionUpdate<JsonIO>, `id` | `key` | `params`>,
245
+ ) => {
246
+ store.logger.info(`🛎️`, `continuity`, continuityKey, `received`, update)
247
+ const transactionKey = update.key
248
+ const updateId = update.id
249
+ const performanceKey = `tx-run:${transactionKey}:${updateId}`
250
+ const performanceKeyStart = `${performanceKey}:start`
251
+ const performanceKeyEnd = `${performanceKey}:end`
252
+ performance.mark(performanceKeyStart)
253
+ try {
254
+ actUponStore(
255
+ { type: `transaction`, key: transactionKey },
256
+ updateId,
257
+ store,
258
+ )(...update.params)
259
+ } catch (thrown) {
260
+ if (thrown instanceof Error) {
261
+ store.logger.error(
262
+ `❌`,
263
+ `continuity`,
264
+ continuityKey,
265
+ `failed to run transaction ${transactionKey} with update ${updateId}`,
266
+ thrown.message,
267
+ )
268
+ }
269
+ }
270
+ performance.mark(performanceKeyEnd)
271
+ const metric = performance.measure(
272
+ performanceKey,
273
+ performanceKeyStart,
274
+ performanceKeyEnd,
275
+ )
276
+ store?.logger.info(
277
+ `🚀`,
278
+ `transaction`,
279
+ transactionKey,
280
+ updateId,
281
+ metric.duration,
282
+ )
283
+
284
+ const valuesOfCardsViewKey = `valuesOfCardsView("${userKey}")`
285
+ const rootsOfCardValueView =
286
+ store.selectorAtoms.getRelatedKeys(valuesOfCardsViewKey)
287
+ const myCardValueView = store.valueMap.get(valuesOfCardsViewKey)
288
+
289
+ store.logger.info(
290
+ `👁`,
291
+ `continuity`,
292
+ continuityKey,
293
+ `seeing ${userKey} card values`,
294
+ {
295
+ valuesOfCardsViewKey,
296
+ rootsOfCardValueView,
297
+ myCardValueView,
298
+ },
299
+ )
300
+ }
301
+ socket.off(`tx-run:${continuityKey}`, fillTransactionRequest)
302
+ socket.on(`tx-run:${continuityKey}`, fillTransactionRequest)
303
+
304
+ // let i = 0
305
+ // let n = 1
306
+ // let retryTimeout: NodeJS.Timeout | undefined
307
+ // const trackClientAcknowledgement = (epoch: number) => {
308
+ // store.logger.info(
309
+ // `👍`,
310
+ // `continuity`,
311
+ // continuityKey,
312
+ // `${userKey} acknowledged epoch ${epoch}`,
313
+ // )
314
+ // const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch
315
+ // if (isUnacknowledged) {
316
+ // setIntoStore(
317
+ // userUnacknowledgedQueue,
318
+ // (updates) => {
319
+ // updates.shift()
320
+ // return updates
321
+ // },
322
+ // store,
323
+ // )
324
+ // }
325
+ // }
326
+ // subscribeToState(
327
+ // userUnacknowledgedQueue,
328
+ // ({ newValue }) => {
329
+ // if (newValue.length === 0) {
330
+ // clearInterval(retryTimeout)
331
+ // socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
332
+ // retryTimeout = undefined
333
+ // }
334
+ // if (newValue.length > 0) {
335
+ // if (retryTimeout) {
336
+ // return
337
+ // }
338
+
339
+ // socket?.on(`ack:${continuityKey}`, trackClientAcknowledgement)
340
+
341
+ // retryTimeout = setInterval(() => {
342
+ // i++
343
+ // if (i === n) {
344
+ // n += i
345
+ // const toEmit = newValue[0]
346
+ // if (!toEmit) return
347
+ // store.logger.info(
348
+ // `🔄`,
349
+ // `continuity`,
350
+ // continuityKey,
351
+ // `${store.config.name} retrying ${userKey}`,
352
+ // socket?.id,
353
+ // newValue,
354
+ // )
355
+ // socket?.emit(
356
+ // `tx-new:${continuityKey}`,
357
+ // toEmit as Json.Serializable,
358
+ // )
359
+ // }
360
+ // }, 250)
361
+ // }
362
+ // },
363
+ // `sync-continuity:${continuityKey}:${userKey}`,
364
+ // store,
365
+ // )
366
+
367
+ return () => {
368
+ // clearInterval(retryTimeout)
369
+ for (const unsubscribe of unsubscribeFunctions) unsubscribe()
370
+ // socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
371
+ unsubscribeFromPerspectives()
372
+ socket?.off(`get:${continuityKey}`, sendInitialPayload)
373
+ socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest)
374
+ }
375
+ }
376
+ }