atom.io 0.30.2 → 0.30.4

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.
@@ -1,343 +0,0 @@
1
- import type * as AtomIO from "atom.io"
2
- import { findRelationsInStore } from "atom.io/data"
3
- import {
4
- actUponStore,
5
- findInStore,
6
- getFromStore,
7
- getJsonToken,
8
- getUpdateToken,
9
- IMPLICIT,
10
- isRootStore,
11
- setIntoStore,
12
- subscribeToState,
13
- subscribeToTransaction,
14
- } from "atom.io/internal"
15
- import type { Json, JsonIO } from "atom.io/json"
16
- import type { ContinuityToken } from "atom.io/realtime"
17
-
18
- import type { ServerConfig, Socket, SocketKey } from "."
19
- import { socketAtoms, usersOfSockets } from "."
20
- import {
21
- redactTransactionUpdateContent,
22
- userUnacknowledgedQueues,
23
- } from "./realtime-server-stores"
24
-
25
- export type RealtimeContinuitySynchronizer = ReturnType<
26
- typeof realtimeContinuitySynchronizer
27
- >
28
- export function realtimeContinuitySynchronizer({
29
- socket: initialSocket,
30
- store = IMPLICIT.STORE,
31
- }: ServerConfig) {
32
- return function synchronizer(continuity: ContinuityToken): () => void {
33
- let socket: Socket | null = initialSocket
34
-
35
- const continuityKey = continuity.key
36
- const userKeyState = findRelationsInStore(
37
- usersOfSockets,
38
- `socket::${socket.id}`,
39
- store,
40
- ).userKeyOfSocket
41
- const userKey = getFromStore(store, userKeyState)
42
- if (!userKey) {
43
- store.logger.error(
44
- `❌`,
45
- `continuity`,
46
- continuityKey,
47
- `Tried to create a synchronizer for a socket (${socket.id}) that is not connected to a user.`,
48
- )
49
- return () => {}
50
- }
51
-
52
- const socketKeyState = findRelationsInStore(
53
- usersOfSockets,
54
- userKey,
55
- store,
56
- ).socketKeyOfUser
57
- subscribeToState(
58
- socketKeyState,
59
- ({ newValue: newSocketKey }) => {
60
- store.logger.info(
61
- `👋`,
62
- `continuity`,
63
- continuityKey,
64
- `seeing ${userKey} on new socket ${newSocketKey}`,
65
- )
66
- if (newSocketKey === null) {
67
- store.logger.warn(
68
- `❌`,
69
- `continuity`,
70
- continuityKey,
71
- `User (${userKey}) is not connected to a socket, waiting for them to reappear.`,
72
- )
73
- return
74
- }
75
- const newSocketState = findInStore(store, socketAtoms, newSocketKey)
76
- const newSocket = getFromStore(store, newSocketState)
77
- socket = newSocket
78
- for (const unacknowledgedUpdate of userUnacknowledgedUpdates) {
79
- socket?.emit(
80
- `tx-new:${continuityKey}`,
81
- unacknowledgedUpdate as Json.Serializable,
82
- )
83
- }
84
- },
85
- `sync-continuity:${continuityKey}:${userKey}`,
86
- store,
87
- )
88
-
89
- const userUnacknowledgedQueue = findInStore(
90
- store,
91
- userUnacknowledgedQueues,
92
- userKey,
93
- )
94
- const userUnacknowledgedUpdates = getFromStore(
95
- store,
96
- userUnacknowledgedQueue,
97
- )
98
- const unsubscribeFunctions: (() => void)[] = []
99
-
100
- const revealPerspectives = (): (() => void) => {
101
- const unsubFns: (() => void)[] = []
102
- for (const perspective of continuity.perspectives) {
103
- const { viewAtoms } = perspective
104
- const userViewState = findInStore(store, viewAtoms, userKey)
105
- const unsubscribe = subscribeToState(
106
- userViewState,
107
- ({ oldValue, newValue }) => {
108
- const oldKeys = oldValue.map((token) => token.key)
109
- const newKeys = newValue.map((token) => token.key)
110
- const concealed = oldValue.filter(
111
- (token) => !newKeys.includes(token.key),
112
- )
113
- const revealed = newValue
114
- .filter((token) => !oldKeys.includes(token.key))
115
- .flatMap((token) => {
116
- const resourceToken =
117
- token.type === `mutable_atom`
118
- ? getJsonToken(store, token)
119
- : token
120
- const resource = getFromStore(store, resourceToken)
121
- return [resourceToken, resource]
122
- })
123
- store.logger.info(
124
- `👁`,
125
- `atom`,
126
- perspective.resourceAtoms.key,
127
- `${userKey} has a new perspective`,
128
- { oldKeys, newKeys, revealed, concealed },
129
- )
130
- if (revealed.length > 0) {
131
- socket?.emit(`reveal:${continuityKey}`, revealed)
132
- }
133
- if (concealed.length > 0) {
134
- socket?.emit(`conceal:${continuityKey}`, concealed)
135
- }
136
- },
137
- `sync-continuity:${continuityKey}:${userKey}:perspective:${perspective.resourceAtoms.key}`,
138
- store,
139
- )
140
- unsubFns.push(unsubscribe)
141
- }
142
- return () => {
143
- for (const unsubscribe of unsubFns) unsubscribe()
144
- }
145
- }
146
- const unsubscribeFromPerspectives = revealPerspectives()
147
-
148
- const sendInitialPayload = () => {
149
- const initialPayload: Json.Serializable[] = []
150
- for (const atom of continuity.globals) {
151
- const resourceToken =
152
- atom.type === `mutable_atom` ? getJsonToken(store, atom) : atom
153
- const resource = getFromStore(store, resourceToken)
154
- initialPayload.push(resourceToken, resource)
155
- }
156
- for (const perspective of continuity.perspectives) {
157
- const { viewAtoms, resourceAtoms } = perspective
158
- const userViewState = findInStore(store, viewAtoms, userKey)
159
- const userView = getFromStore(store, userViewState)
160
- store.logger.info(`👁`, `atom`, resourceAtoms.key, `${userKey} can see`, {
161
- viewAtoms,
162
- resourceAtoms,
163
- userView,
164
- })
165
- for (const visibleToken of userView) {
166
- const resourceToken =
167
- visibleToken.type === `mutable_atom`
168
- ? getJsonToken(store, visibleToken)
169
- : visibleToken
170
- const resource = getFromStore(store, resourceToken)
171
-
172
- initialPayload.push(resourceToken, resource)
173
- }
174
- }
175
-
176
- const epoch = isRootStore(store)
177
- ? (store.transactionMeta.epoch.get(continuityKey) ?? null)
178
- : null
179
-
180
- socket?.emit(`continuity-init:${continuityKey}`, epoch, initialPayload)
181
-
182
- for (const transaction of continuity.actions) {
183
- const unsubscribeFromTransaction = subscribeToTransaction(
184
- transaction,
185
- (update) => {
186
- try {
187
- const visibleKeys = continuity.globals
188
- .map((atom) => {
189
- if (atom.type === `atom`) {
190
- return atom.key
191
- }
192
- return getUpdateToken(atom).key
193
- })
194
- .concat(
195
- continuity.perspectives.flatMap((perspective) => {
196
- const { viewAtoms } = perspective
197
- const userPerspectiveTokenState = findInStore(
198
- store,
199
- viewAtoms,
200
- userKey,
201
- )
202
- const visibleTokens = getFromStore(
203
- store,
204
- userPerspectiveTokenState,
205
- )
206
- return visibleTokens.map((token) => {
207
- const key =
208
- token.type === `mutable_atom`
209
- ? `*` + token.key
210
- : token.key
211
- return key
212
- })
213
- }),
214
- )
215
- const redactedUpdates = redactTransactionUpdateContent(
216
- visibleKeys,
217
- update.updates,
218
- )
219
- const redactedUpdate = {
220
- ...update,
221
- updates: redactedUpdates,
222
- }
223
- setIntoStore(store, userUnacknowledgedQueue, (updates) => {
224
- if (redactedUpdate) {
225
- updates.push(redactedUpdate)
226
- updates.sort((a, b) => a.epoch - b.epoch)
227
- }
228
- return updates
229
- })
230
-
231
- socket?.emit(
232
- `tx-new:${continuityKey}`,
233
- redactedUpdate as Json.Serializable,
234
- )
235
- } catch (thrown) {
236
- if (thrown instanceof Error) {
237
- store.logger.error(
238
- `❌`,
239
- `continuity`,
240
- continuityKey,
241
- `failed to send update from transaction ${transaction.key} to ${userKey}`,
242
- thrown.message,
243
- )
244
- }
245
- }
246
- },
247
- `sync-continuity:${continuityKey}:${userKey}`,
248
- store,
249
- )
250
- unsubscribeFunctions.push(unsubscribeFromTransaction)
251
- }
252
- }
253
- socket.off(`get:${continuityKey}`, sendInitialPayload)
254
- socket.on(`get:${continuityKey}`, sendInitialPayload)
255
-
256
- const fillTransactionRequest = (
257
- update: Pick<AtomIO.TransactionUpdate<JsonIO>, `id` | `key` | `params`>,
258
- ) => {
259
- store.logger.info(`🛎️`, `continuity`, continuityKey, `received`, update)
260
- const transactionKey = update.key
261
- const updateId = update.id
262
- const performanceKey = `tx-run:${transactionKey}:${updateId}`
263
- const performanceKeyStart = `${performanceKey}:start`
264
- const performanceKeyEnd = `${performanceKey}:end`
265
- performance.mark(performanceKeyStart)
266
- try {
267
- actUponStore(
268
- { type: `transaction`, key: transactionKey },
269
- updateId,
270
- store,
271
- )(...update.params)
272
- } catch (thrown) {
273
- if (thrown instanceof Error) {
274
- store.logger.error(
275
- `❌`,
276
- `continuity`,
277
- continuityKey,
278
- `failed to run transaction ${transactionKey} with update ${updateId}`,
279
- thrown.message,
280
- )
281
- }
282
- }
283
- performance.mark(performanceKeyEnd)
284
- const metric = performance.measure(
285
- performanceKey,
286
- performanceKeyStart,
287
- performanceKeyEnd,
288
- )
289
- store?.logger.info(
290
- `🚀`,
291
- `transaction`,
292
- transactionKey,
293
- updateId,
294
- metric.duration,
295
- )
296
-
297
- const valuesOfCardsViewKey = `valuesOfCardsView("${userKey}")`
298
- const rootsOfCardValueView =
299
- store.selectorAtoms.getRelatedKeys(valuesOfCardsViewKey)
300
- const myCardValueView = store.valueMap.get(valuesOfCardsViewKey)
301
-
302
- store.logger.info(
303
- `👁`,
304
- `continuity`,
305
- continuityKey,
306
- `seeing ${userKey} card values`,
307
- {
308
- valuesOfCardsViewKey,
309
- rootsOfCardValueView,
310
- myCardValueView,
311
- },
312
- )
313
- }
314
- socket.off(`tx-run:${continuityKey}`, fillTransactionRequest)
315
- socket.on(`tx-run:${continuityKey}`, fillTransactionRequest)
316
-
317
- const trackClientAcknowledgement = (epoch: number) => {
318
- store.logger.info(
319
- `👍`,
320
- `continuity`,
321
- continuityKey,
322
- `${userKey} acknowledged epoch ${epoch}`,
323
- )
324
- const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch
325
- if (isUnacknowledged) {
326
- setIntoStore(store, userUnacknowledgedQueue, (updates) => {
327
- updates.shift()
328
- return updates
329
- })
330
- }
331
- }
332
- socket?.on(`ack:${continuityKey}`, trackClientAcknowledgement)
333
-
334
- return () => {
335
- // clearInterval(retryTimeout)
336
- for (const unsubscribe of unsubscribeFunctions) unsubscribe()
337
- socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
338
- unsubscribeFromPerspectives()
339
- socket?.off(`get:${continuityKey}`, sendInitialPayload)
340
- socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest)
341
- }
342
- }
343
- }