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.
- package/package.json +22 -22
- package/react-devtools/dist/index.js +1 -1
- package/react-devtools/src/AtomIODevtools.tsx +1 -1
- package/react-devtools/src/elastic-input/NumberInput.tsx +0 -1
- package/react-devtools/src/elastic-input/TextInput.tsx +0 -1
- package/realtime/dist/index.d.ts +2 -1
- package/realtime/src/realtime-continuity.ts +3 -2
- package/realtime-server/dist/index.d.ts +9 -8
- package/realtime-server/dist/index.js +282 -237
- package/realtime-server/src/continuity/prepare-to-send-initial-payload.ts +54 -0
- package/realtime-server/src/continuity/prepare-to-serve-transaction-request.ts +53 -0
- package/realtime-server/src/continuity/prepare-to-sync-realtime-continuity.ts +145 -0
- package/realtime-server/src/continuity/prepare-to-track-client-acknowledgement.ts +38 -0
- package/realtime-server/src/continuity/subscribe-to-continuity-actions.ts +106 -0
- package/realtime-server/src/continuity/subscribe-to-continuity-perpectives.ts +60 -0
- package/realtime-server/src/index.ts +1 -1
- package/realtime-server/src/ipc-sockets/child-socket.ts +1 -1
- package/realtime-server/src/ipc-sockets/parent-socket.ts +1 -1
- package/realtime-server/src/realtime-server-stores/server-room-external-store.ts +1 -1
- package/realtime-server/src/realtime-server-stores/server-sync-store.ts +11 -5
- package/realtime-server/src/realtime-continuity-synchronizer.ts +0 -343
|
@@ -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
|
-
}
|