atom.io 0.30.2 → 0.30.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.
@@ -0,0 +1,145 @@
1
+ import { findRelationsInStore } from "atom.io/data"
2
+ import {
3
+ findInStore,
4
+ getFromStore,
5
+ IMPLICIT,
6
+ subscribeToState,
7
+ } from "atom.io/internal"
8
+ import type { Json } from "atom.io/json"
9
+ import type { ContinuityToken } from "atom.io/realtime"
10
+
11
+ import type { ServerConfig, Socket } from ".."
12
+ import { socketAtoms, usersOfSockets } from ".."
13
+ import { userUnacknowledgedQueues } from "../realtime-server-stores"
14
+ import { prepareToSendInitialPayload } from "./prepare-to-send-initial-payload"
15
+ import { prepareToServeTransactionRequest } from "./prepare-to-serve-transaction-request"
16
+ import { prepareToTrackClientAcknowledgement } from "./prepare-to-track-client-acknowledgement"
17
+ import { subscribeToContinuityActions } from "./subscribe-to-continuity-actions"
18
+ import { subscribeToContinuityPerspectives } from "./subscribe-to-continuity-perpectives"
19
+
20
+ export type ExposeRealtimeContinuity = (
21
+ continuity: ContinuityToken,
22
+ ) => () => void
23
+ export function prepareToExposeRealtimeContinuity({
24
+ socket: initialSocket,
25
+ store = IMPLICIT.STORE,
26
+ }: ServerConfig): ExposeRealtimeContinuity {
27
+ return function syncRealtimeContinuity(continuity) {
28
+ let socket: Socket | null = initialSocket
29
+
30
+ const continuityKey = continuity.key
31
+ const userKeyState = findRelationsInStore(
32
+ usersOfSockets,
33
+ `socket::${socket.id}`,
34
+ store,
35
+ ).userKeyOfSocket
36
+ const userKey = getFromStore(store, userKeyState)
37
+ if (!userKey) {
38
+ store.logger.error(
39
+ `❌`,
40
+ `continuity`,
41
+ continuityKey,
42
+ `Tried to create a synchronizer for a socket (${socket.id}) that is not connected to a user.`,
43
+ )
44
+ return () => {}
45
+ }
46
+
47
+ const socketKeyState = findRelationsInStore(
48
+ usersOfSockets,
49
+ userKey,
50
+ store,
51
+ ).socketKeyOfUser
52
+ const unsubscribeFromSocketTracking = subscribeToState(
53
+ socketKeyState,
54
+ ({ newValue: newSocketKey }) => {
55
+ store.logger.info(
56
+ `👋`,
57
+ `continuity`,
58
+ continuityKey,
59
+ `seeing ${userKey} on new socket ${newSocketKey}`,
60
+ )
61
+ if (newSocketKey === null) {
62
+ store.logger.warn(
63
+ `❌`,
64
+ `continuity`,
65
+ continuityKey,
66
+ `User (${userKey}) is not connected to a socket, waiting for them to reappear.`,
67
+ )
68
+ return
69
+ }
70
+ const newSocketState = findInStore(store, socketAtoms, newSocketKey)
71
+ const newSocket = getFromStore(store, newSocketState)
72
+ socket = newSocket
73
+ for (const unacknowledgedUpdate of userUnacknowledgedUpdates) {
74
+ socket?.emit(
75
+ `tx-new:${continuityKey}`,
76
+ unacknowledgedUpdate as Json.Serializable,
77
+ )
78
+ }
79
+ },
80
+ `sync-continuity:${continuityKey}:${userKey}`,
81
+ store,
82
+ )
83
+
84
+ const userUnacknowledgedUpdates = getFromStore(
85
+ store,
86
+ userUnacknowledgedQueues,
87
+ userKey,
88
+ )
89
+
90
+ const unsubscribeFunctions: (() => void)[] = []
91
+
92
+ const unsubscribeFromPerspectives = subscribeToContinuityPerspectives(
93
+ store,
94
+ continuity,
95
+ userKey,
96
+ socket,
97
+ )
98
+ const unsubscribeFromTransactions = subscribeToContinuityActions(
99
+ store,
100
+ continuity,
101
+ userKey,
102
+ socket,
103
+ )
104
+ unsubscribeFunctions.push(
105
+ ...unsubscribeFromPerspectives,
106
+ ...unsubscribeFromTransactions,
107
+ )
108
+
109
+ const sendInitialPayload = prepareToSendInitialPayload(
110
+ store,
111
+ continuity,
112
+ userKey,
113
+ initialSocket,
114
+ )
115
+
116
+ socket.off(`get:${continuityKey}`, sendInitialPayload)
117
+ socket.on(`get:${continuityKey}`, sendInitialPayload)
118
+
119
+ const fillTransactionRequest = prepareToServeTransactionRequest(
120
+ store,
121
+ continuity,
122
+ userKey,
123
+ )
124
+
125
+ socket.off(`tx-run:${continuityKey}`, fillTransactionRequest)
126
+ socket.on(`tx-run:${continuityKey}`, fillTransactionRequest)
127
+
128
+ const trackClientAcknowledgement = prepareToTrackClientAcknowledgement(
129
+ store,
130
+ continuity,
131
+ userKey,
132
+ userUnacknowledgedUpdates,
133
+ )
134
+
135
+ socket?.on(`ack:${continuityKey}`, trackClientAcknowledgement)
136
+
137
+ return () => {
138
+ // clearInterval(retryTimeout)
139
+ for (const unsubscribe of unsubscribeFunctions) unsubscribe()
140
+ socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
141
+ socket?.off(`get:${continuityKey}`, sendInitialPayload)
142
+ socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest)
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,38 @@
1
+ import type { Store } from "atom.io/internal"
2
+ import { setIntoStore } from "atom.io/internal"
3
+ import type { ContinuityToken } from "atom.io/realtime"
4
+
5
+ import type { ContinuitySyncTransactionUpdate } from "../realtime-server-stores"
6
+ import { userUnacknowledgedQueues } from "../realtime-server-stores"
7
+
8
+ export function prepareToTrackClientAcknowledgement(
9
+ store: Store,
10
+ continuity: ContinuityToken,
11
+ userKey: string,
12
+ userUnacknowledgedUpdates: ContinuitySyncTransactionUpdate[],
13
+ ): (epoch: number) => void {
14
+ const continuityKey = continuity.key
15
+ return function trackClientAcknowledgement(epoch) {
16
+ store.logger.info(
17
+ `👍`,
18
+ `continuity`,
19
+ continuityKey,
20
+ `${userKey} acknowledged epoch ${epoch}`,
21
+ )
22
+ const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch
23
+ if (isUnacknowledged) {
24
+ setIntoStore(store, userUnacknowledgedQueues, userKey, (updates) => {
25
+ updates.shift()
26
+ store.logger.info(
27
+ `👍`,
28
+ `continuity`,
29
+ continuityKey,
30
+ `${userKey} unacknowledged update queue now has`,
31
+ updates.length,
32
+ `items`,
33
+ )
34
+ return updates
35
+ })
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,106 @@
1
+ import type { Store } from "atom.io/internal"
2
+ import {
3
+ findInStore,
4
+ getFromStore,
5
+ getJsonToken,
6
+ getUpdateToken,
7
+ isRootStore,
8
+ setIntoStore,
9
+ subscribeToTransaction,
10
+ } from "atom.io/internal"
11
+ import type { Json } from "atom.io/json"
12
+ import type { ContinuityToken } from "atom.io/realtime"
13
+
14
+ import type { Socket, UserKey } from ".."
15
+ import {
16
+ redactTransactionUpdateContent,
17
+ userUnacknowledgedQueues,
18
+ } from "../realtime-server-stores"
19
+
20
+ export function subscribeToContinuityActions(
21
+ store: Store,
22
+ continuity: ContinuityToken,
23
+ userKey: UserKey,
24
+ socket: Socket | null,
25
+ ): (() => void)[] {
26
+ const continuityKey = continuity.key
27
+ const unsubscribeFunctions: (() => void)[] = []
28
+
29
+ for (const transaction of continuity.actions) {
30
+ const unsubscribeFromTransaction = subscribeToTransaction(
31
+ transaction,
32
+ (update) => {
33
+ try {
34
+ const visibleKeys = continuity.globals
35
+ .map((atom) => {
36
+ if (atom.type === `atom`) {
37
+ return atom.key
38
+ }
39
+ return getUpdateToken(atom).key
40
+ })
41
+ .concat(
42
+ continuity.perspectives.flatMap((perspective) => {
43
+ const { viewAtoms } = perspective
44
+ const userPerspectiveTokenState = findInStore(
45
+ store,
46
+ viewAtoms,
47
+ userKey,
48
+ )
49
+ const visibleTokens = getFromStore(
50
+ store,
51
+ userPerspectiveTokenState,
52
+ )
53
+ return visibleTokens.map((token) => {
54
+ const key =
55
+ token.type === `mutable_atom` ? `*` + token.key : token.key
56
+ return key
57
+ })
58
+ }),
59
+ )
60
+ const redactedUpdates = redactTransactionUpdateContent(
61
+ visibleKeys,
62
+ update.updates,
63
+ )
64
+ const redactedUpdate = {
65
+ ...update,
66
+ updates: redactedUpdates,
67
+ }
68
+ setIntoStore(store, userUnacknowledgedQueues, userKey, (updates) => {
69
+ if (redactedUpdate) {
70
+ updates.push(redactedUpdate)
71
+ updates.sort((a, b) => a.epoch - b.epoch)
72
+ store.logger.info(
73
+ `👍`,
74
+ `continuity`,
75
+ continuityKey,
76
+ `${userKey} unacknowledged update queue now has`,
77
+ updates.length,
78
+ `items`,
79
+ )
80
+ }
81
+ return updates
82
+ })
83
+
84
+ socket?.emit(
85
+ `tx-new:${continuityKey}`,
86
+ redactedUpdate as Json.Serializable,
87
+ )
88
+ } catch (thrown) {
89
+ if (thrown instanceof Error) {
90
+ store.logger.error(
91
+ `❌`,
92
+ `continuity`,
93
+ continuityKey,
94
+ `${userKey} failed to send update from transaction ${transaction.key} to ${userKey}`,
95
+ thrown.message,
96
+ )
97
+ }
98
+ }
99
+ },
100
+ `sync-continuity:${continuityKey}:${userKey}`,
101
+ store,
102
+ )
103
+ unsubscribeFunctions.push(unsubscribeFromTransaction)
104
+ }
105
+ return unsubscribeFunctions
106
+ }
@@ -0,0 +1,60 @@
1
+ import type { Store } from "atom.io/internal"
2
+ import {
3
+ findInStore,
4
+ getFromStore,
5
+ getJsonToken,
6
+ subscribeToState,
7
+ } from "atom.io/internal"
8
+ import type { ContinuityToken } from "atom.io/realtime"
9
+
10
+ import type { Socket } from ".."
11
+ import type { UserKey } from "../realtime-server-stores"
12
+
13
+ export function subscribeToContinuityPerspectives(
14
+ store: Store,
15
+ continuity: ContinuityToken,
16
+ userKey: UserKey,
17
+ socket: Socket | null,
18
+ ): (() => void)[] {
19
+ const continuityKey = continuity.key
20
+ const unsubFns: (() => void)[] = []
21
+ for (const perspective of continuity.perspectives) {
22
+ const { viewAtoms } = perspective
23
+ const userViewState = findInStore(store, viewAtoms, userKey)
24
+ const unsubscribeFromUserView = subscribeToState(
25
+ userViewState,
26
+ ({ oldValue, newValue }) => {
27
+ const oldKeys = oldValue.map((token) => token.key)
28
+ const newKeys = newValue.map((token) => token.key)
29
+ const concealed = oldValue.filter(
30
+ (token) => !newKeys.includes(token.key),
31
+ )
32
+ const revealed = newValue
33
+ .filter((token) => !oldKeys.includes(token.key))
34
+ .flatMap((token) => {
35
+ const resourceToken =
36
+ token.type === `mutable_atom` ? getJsonToken(store, token) : token
37
+ const resource = getFromStore(store, resourceToken)
38
+ return [resourceToken, resource]
39
+ })
40
+ store.logger.info(
41
+ `👁`,
42
+ `atom`,
43
+ perspective.resourceAtoms.key,
44
+ `${userKey} has a new perspective`,
45
+ { oldKeys, newKeys, revealed, concealed },
46
+ )
47
+ if (revealed.length > 0) {
48
+ socket?.emit(`reveal:${continuityKey}`, revealed)
49
+ }
50
+ if (concealed.length > 0) {
51
+ socket?.emit(`conceal:${continuityKey}`, concealed)
52
+ }
53
+ },
54
+ `sync-continuity:${continuityKey}:${userKey}:perspective:${perspective.resourceAtoms.key}`,
55
+ store,
56
+ )
57
+ unsubFns.push(unsubscribeFromUserView)
58
+ }
59
+ return unsubFns
60
+ }
@@ -1,9 +1,9 @@
1
1
  import type { Store } from "atom.io/internal"
2
2
  import type { Json } from "atom.io/json"
3
3
 
4
+ export * from "./continuity/prepare-to-sync-realtime-continuity"
4
5
  export * from "./ipc-sockets"
5
6
  export * from "./realtime-action-receiver"
6
- export * from "./realtime-continuity-synchronizer"
7
7
  export * from "./realtime-family-provider"
8
8
  export * from "./realtime-mutable-family-provider"
9
9
  export * from "./realtime-mutable-provider"
@@ -1,6 +1,8 @@
1
1
  import type { TransactionUpdate, TransactionUpdateContent } from "atom.io"
2
2
  import { atomFamily } from "atom.io"
3
3
 
4
+ import type { UserKey } from "./server-user-store"
5
+
4
6
  // export const completeUpdateAtoms = atomFamily<
5
7
  // TransactionUpdate<any> | null,
6
8
  // string
@@ -42,13 +44,13 @@ export function redactTransactionUpdateContent(
42
44
  })
43
45
  }
44
46
 
45
- export const actionOcclusionAtoms = atomFamily<
47
+ export const redactorAtoms = atomFamily<
46
48
  {
47
49
  occlude: (updates: TransactionUpdateContent[]) => TransactionUpdateContent[]
48
50
  },
49
- string
51
+ UserKey
50
52
  >({
51
- key: `transactionRedactor`,
53
+ key: `redactor`,
52
54
  default: { occlude: (updates) => updates },
53
55
  })
54
56
  // export const redactedUpdateSelectors = selectorFamily<
@@ -69,9 +71,13 @@ export const actionOcclusionAtoms = atomFamily<
69
71
  // },
70
72
  // })
71
73
 
74
+ export type ContinuitySyncTransactionUpdate = Pick<
75
+ TransactionUpdate<any>,
76
+ `epoch` | `id` | `key` | `output` | `updates`
77
+ >
72
78
  export const userUnacknowledgedQueues = atomFamily<
73
- Pick<TransactionUpdate<any>, `epoch` | `id` | `key` | `output` | `updates`>[],
74
- string
79
+ ContinuitySyncTransactionUpdate[],
80
+ UserKey
75
81
  >({
76
82
  key: `unacknowledgedUpdates`,
77
83
  default: () => [],