atom.io 0.16.3 → 0.18.0
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/data/dist/index.cjs +62 -40
- package/data/dist/index.cjs.map +1 -1
- package/data/dist/index.d.ts +8 -2
- package/data/dist/index.js +64 -42
- package/data/dist/index.js.map +1 -1
- package/data/src/dict.ts +8 -4
- package/data/src/join.ts +74 -33
- package/data/src/struct-family.ts +18 -17
- package/dist/chunk-OEVFAUPE.js +289 -0
- package/dist/chunk-OEVFAUPE.js.map +1 -0
- package/dist/index.cjs +36 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +64 -53
- package/dist/index.js +15 -36
- package/dist/index.js.map +1 -1
- package/internal/dist/index.cjs +211 -81
- package/internal/dist/index.cjs.map +1 -1
- package/internal/dist/index.d.ts +100 -72
- package/internal/dist/index.js +200 -75
- package/internal/dist/index.js.map +1 -1
- package/internal/src/arbitrary.ts +3 -0
- package/internal/src/atom/create-regular-atom.ts +2 -3
- package/internal/src/caching.ts +8 -6
- package/internal/src/families/find-in-store.ts +16 -0
- package/internal/src/get-environment-data.ts +4 -7
- package/internal/src/get-state/get-from-store.ts +14 -0
- package/internal/src/get-state/index.ts +2 -0
- package/internal/src/{read-or-compute-value.ts → get-state/read-or-compute-value.ts} +3 -3
- package/internal/src/index.ts +7 -6
- package/internal/src/ingest-updates/ingest-atom-update.ts +2 -2
- package/internal/src/ingest-updates/ingest-transaction-update.ts +0 -1
- package/internal/src/mutable/create-mutable-atom.ts +3 -4
- package/internal/src/mutable/tracker.ts +18 -13
- package/internal/src/selector/create-standalone-selector.ts +0 -2
- package/internal/src/selector/register-selector.ts +1 -1
- package/internal/src/set-state/index.ts +1 -0
- package/internal/src/set-state/set-atom.ts +15 -19
- package/internal/src/set-state/set-into-store.ts +24 -0
- package/internal/src/store/store.ts +14 -2
- package/internal/src/store/withdraw.ts +72 -2
- package/internal/src/subscribe/subscribe-to-root-atoms.ts +1 -1
- package/internal/src/subscribe/subscribe-to-timeline.ts +2 -2
- package/internal/src/subscribe/subscribe-to-transaction.ts +2 -2
- package/internal/src/timeline/create-timeline.ts +12 -1
- package/internal/src/transaction/act-upon-store.ts +19 -0
- package/internal/src/transaction/apply-transaction.ts +7 -1
- package/internal/src/transaction/assign-transaction-to-continuity.ts +18 -0
- package/internal/src/transaction/build-transaction.ts +11 -8
- package/internal/src/transaction/create-transaction.ts +1 -1
- package/internal/src/transaction/get-epoch-number.ts +40 -0
- package/internal/src/transaction/index.ts +10 -1
- package/internal/src/transaction/set-epoch-number.ts +31 -0
- package/introspection/dist/index.cjs.map +1 -1
- package/introspection/dist/index.d.ts +3 -3
- package/introspection/dist/index.js.map +1 -1
- package/introspection/src/attach-introspection-states.ts +6 -2
- package/introspection/src/attach-timeline-family.ts +5 -2
- package/introspection/src/attach-transaction-logs.ts +2 -2
- package/json/dist/index.d.ts +3 -1
- package/json/src/index.ts +6 -2
- package/package.json +24 -13
- package/react/dist/index.cjs +3 -3
- package/react/dist/index.cjs.map +1 -1
- package/react/dist/index.d.ts +1 -1
- package/react/dist/index.js +5 -5
- package/react/dist/index.js.map +1 -1
- package/react/src/use-i.ts +2 -3
- package/react/src/use-json.ts +1 -1
- package/react/src/use-o.ts +3 -4
- package/react-devtools/dist/index.cjs +131 -134
- package/react-devtools/dist/index.cjs.map +1 -1
- package/react-devtools/dist/index.css +2 -2
- package/react-devtools/dist/index.css.map +1 -1
- package/react-devtools/dist/index.d.ts +3 -3
- package/react-devtools/dist/index.js +103 -106
- package/react-devtools/dist/index.js.map +1 -1
- package/react-devtools/src/StateEditor.tsx +6 -6
- package/react-devtools/src/StateIndex.tsx +2 -5
- package/react-devtools/src/TimelineIndex.tsx +3 -3
- package/react-devtools/src/TransactionIndex.tsx +9 -8
- package/react-devtools/src/Updates.tsx +1 -1
- package/react-devtools/src/index.ts +4 -4
- package/realtime/dist/index.cjs +72 -0
- package/realtime/dist/index.cjs.map +1 -0
- package/realtime/dist/index.d.ts +39 -0
- package/realtime/dist/index.js +68 -0
- package/realtime/dist/index.js.map +1 -0
- package/realtime/package.json +16 -0
- package/realtime/src/index.ts +1 -0
- package/realtime/src/realtime-continuity.ts +152 -0
- package/realtime-client/dist/index.cjs +403 -59
- package/realtime-client/dist/index.cjs.map +1 -1
- package/realtime-client/dist/index.d.ts +16 -9
- package/realtime-client/dist/index.js +114 -48
- package/realtime-client/dist/index.js.map +1 -1
- package/realtime-client/src/index.ts +8 -5
- package/realtime-client/src/{pull-family-member.ts → pull-atom-family-member.ts} +5 -5
- package/realtime-client/src/{pull-state.ts → pull-atom.ts} +5 -5
- package/realtime-client/src/{pull-mutable-family-member.ts → pull-mutable-atom-family-member.ts} +5 -5
- package/realtime-client/src/{pull-mutable.ts → pull-mutable-atom.ts} +5 -5
- package/realtime-client/src/pull-selector-family-member.ts +42 -0
- package/realtime-client/src/pull-selector.ts +38 -0
- package/realtime-client/src/realtime-client-stores/client-main-store.ts +2 -2
- package/realtime-client/src/realtime-client-stores/client-sync-store.ts +7 -7
- package/realtime-client/src/sync-continuity.ts +321 -0
- package/realtime-client/src/sync-server-action.ts +22 -21
- package/realtime-client/src/sync-state.ts +3 -3
- package/realtime-react/dist/index.cjs +330 -15
- package/realtime-react/dist/index.cjs.map +1 -1
- package/realtime-react/dist/index.d.ts +26 -6
- package/realtime-react/dist/index.js +43 -12
- package/realtime-react/dist/index.js.map +1 -1
- package/realtime-react/src/index.ts +6 -3
- package/realtime-react/src/use-pull-atom-family-member.ts +21 -0
- package/realtime-react/src/{use-pull.ts → use-pull-atom.ts} +6 -5
- package/realtime-react/src/{use-pull-mutable.ts → use-pull-mutable-atom.ts} +4 -3
- package/realtime-react/src/use-pull-mutable-family-member.ts +9 -4
- package/realtime-react/src/use-pull-selector-family-member.ts +21 -0
- package/realtime-react/src/{use-pull-family-member.ts → use-pull-selector.ts} +7 -5
- package/realtime-react/src/use-push.ts +3 -2
- package/realtime-react/src/use-server-action.ts +3 -2
- package/realtime-react/src/use-sync-continuity.ts +12 -0
- package/realtime-react/src/use-sync-server-action.ts +3 -2
- package/realtime-server/dist/index.cjs +582 -256
- package/realtime-server/dist/index.cjs.map +1 -1
- package/realtime-server/dist/index.d.ts +124 -49
- package/realtime-server/dist/index.js +566 -249
- package/realtime-server/dist/index.js.map +1 -1
- package/realtime-server/src/index.ts +18 -2
- package/realtime-server/src/ipc-socket.ts +230 -0
- package/realtime-server/src/realtime-action-receiver.ts +8 -5
- package/realtime-server/src/realtime-action-synchronizer.ts +53 -35
- package/realtime-server/src/realtime-continuity-synchronizer.ts +247 -0
- package/realtime-server/src/realtime-family-provider.ts +37 -73
- package/realtime-server/src/realtime-mutable-family-provider.ts +26 -87
- package/realtime-server/src/realtime-mutable-provider.ts +3 -2
- package/realtime-server/src/realtime-server-stores/index.ts +3 -1
- package/realtime-server/src/realtime-server-stores/realtime-continuity-store.ts +90 -0
- package/realtime-server/src/realtime-server-stores/server-room-store.ts +97 -0
- package/realtime-server/src/realtime-server-stores/server-sync-store.ts +2 -72
- package/realtime-server/src/realtime-server-stores/server-user-store.ts +14 -29
- package/realtime-server/src/realtime-state-provider.ts +3 -3
- package/realtime-server/src/realtime-state-receiver.ts +2 -3
- package/realtime-server/src/realtime-state-synchronizer.ts +3 -3
- package/realtime-testing/dist/index.cjs +28 -28
- package/realtime-testing/dist/index.cjs.map +1 -1
- package/realtime-testing/dist/index.js +28 -27
- package/realtime-testing/dist/index.js.map +1 -1
- package/realtime-testing/src/setup-realtime-test.tsx +38 -28
- package/src/atom.ts +49 -31
- package/src/get-state.ts +2 -11
- package/src/logger.ts +10 -5
- package/src/selector.ts +44 -25
- package/src/set-state.ts +1 -13
- package/src/silo.ts +7 -3
- package/src/subscribe.ts +2 -1
- package/src/timeline.ts +4 -4
- package/src/transaction.ts +13 -17
- package/src/validators.ts +15 -9
- package/dist/chunk-H4Q5FTPZ.js +0 -11
- package/dist/chunk-H4Q5FTPZ.js.map +0 -1
- package/internal/src/set-state/copy-mutable-in-transaction.ts +0 -19
|
@@ -1,52 +1,74 @@
|
|
|
1
|
-
import * as AtomIO from "atom.io"
|
|
2
|
-
import {
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import {
|
|
3
|
+
IMPLICIT,
|
|
4
|
+
actUponStore,
|
|
5
|
+
assignTransactionToContinuity,
|
|
6
|
+
findInStore,
|
|
7
|
+
getFromStore,
|
|
8
|
+
setIntoStore,
|
|
9
|
+
subscribeToTransaction,
|
|
10
|
+
} from "atom.io/internal"
|
|
11
|
+
import type { Json, JsonIO } from "atom.io/json"
|
|
3
12
|
|
|
4
13
|
import type { ServerConfig } from "."
|
|
5
|
-
import { usersOfSockets } from "./realtime-server-stores"
|
|
6
14
|
import {
|
|
7
15
|
completeUpdateAtoms,
|
|
8
16
|
redactedUpdateSelectors,
|
|
9
|
-
socketEpochSelectors,
|
|
10
|
-
socketUnacknowledgedUpdatesSelectors,
|
|
11
17
|
transactionRedactorAtoms,
|
|
12
|
-
|
|
18
|
+
userUnacknowledgedQueues,
|
|
19
|
+
usersOfSockets,
|
|
20
|
+
} from "./realtime-server-stores"
|
|
13
21
|
|
|
14
22
|
export type ActionSynchronizer = ReturnType<typeof realtimeActionSynchronizer>
|
|
15
23
|
export function realtimeActionSynchronizer({
|
|
16
24
|
socket,
|
|
17
25
|
store = IMPLICIT.STORE,
|
|
18
26
|
}: ServerConfig) {
|
|
19
|
-
return function actionSynchronizer<ƒ extends
|
|
27
|
+
return function actionSynchronizer<ƒ extends JsonIO>(
|
|
20
28
|
tx: AtomIO.TransactionToken<ƒ>,
|
|
21
29
|
filter?: (
|
|
22
30
|
update: AtomIO.TransactionUpdateContent[],
|
|
23
31
|
) => AtomIO.TransactionUpdateContent[],
|
|
24
32
|
): () => void {
|
|
33
|
+
assignTransactionToContinuity(`default`, tx.key, store)
|
|
34
|
+
|
|
25
35
|
const userKeyState = findInStore(
|
|
26
36
|
usersOfSockets.states.userKeyOfSocket,
|
|
27
37
|
socket.id,
|
|
28
38
|
store,
|
|
29
39
|
)
|
|
30
|
-
const userKey =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
const userKey = getFromStore(userKeyState, store)
|
|
41
|
+
if (!userKey) {
|
|
42
|
+
store.logger.error(
|
|
43
|
+
`❌`,
|
|
44
|
+
`transaction`,
|
|
45
|
+
tx.key,
|
|
46
|
+
`Tried to create a synchronizer for a socket (${socket.id}) that is not connected to a user.`,
|
|
47
|
+
)
|
|
48
|
+
return () => {}
|
|
49
|
+
}
|
|
50
|
+
const userUnacknowledgedQueue = findInStore(
|
|
51
|
+
userUnacknowledgedQueues,
|
|
52
|
+
userKey,
|
|
34
53
|
store,
|
|
35
54
|
)
|
|
36
|
-
const
|
|
37
|
-
|
|
55
|
+
const userUnacknowledgedUpdates = getFromStore(
|
|
56
|
+
userUnacknowledgedQueue,
|
|
38
57
|
store,
|
|
39
58
|
)
|
|
40
59
|
if (filter) {
|
|
41
60
|
const redactorState = findInStore(transactionRedactorAtoms, tx.key, store)
|
|
42
|
-
|
|
61
|
+
setIntoStore(redactorState, { filter }, store)
|
|
43
62
|
}
|
|
44
|
-
|
|
63
|
+
|
|
64
|
+
const fillTransactionRequest = (
|
|
65
|
+
update: Pick<AtomIO.TransactionUpdate<ƒ>, `id` | `params`>,
|
|
66
|
+
) => {
|
|
45
67
|
const performanceKey = `tx-run:${tx.key}:${update.id}`
|
|
46
68
|
const performanceKeyStart = `${performanceKey}:start`
|
|
47
69
|
const performanceKeyEnd = `${performanceKey}:end`
|
|
48
70
|
performance.mark(performanceKeyStart)
|
|
49
|
-
|
|
71
|
+
actUponStore<ƒ>(tx, update.id, store)(...update.params)
|
|
50
72
|
performance.mark(performanceKeyEnd)
|
|
51
73
|
const metric = performance.measure(
|
|
52
74
|
performanceKey,
|
|
@@ -64,9 +86,12 @@ export function realtimeActionSynchronizer({
|
|
|
64
86
|
tx,
|
|
65
87
|
(update) => {
|
|
66
88
|
const updateState = findInStore(completeUpdateAtoms, update.id, store)
|
|
67
|
-
|
|
68
|
-
const toEmit
|
|
69
|
-
|
|
89
|
+
setIntoStore(updateState, update, store)
|
|
90
|
+
const toEmit: Pick<
|
|
91
|
+
AtomIO.TransactionUpdate<ƒ>,
|
|
92
|
+
`epoch` | `id` | `key` | `output` | `updates`
|
|
93
|
+
> | null = filter
|
|
94
|
+
? getFromStore(
|
|
70
95
|
findInStore(redactedUpdateSelectors, [tx.key, update.id], store),
|
|
71
96
|
store,
|
|
72
97
|
)
|
|
@@ -76,8 +101,8 @@ export function realtimeActionSynchronizer({
|
|
|
76
101
|
// updates be set in the queue for that socket's client.
|
|
77
102
|
//
|
|
78
103
|
// we need a client session that can persist between disconnects
|
|
79
|
-
|
|
80
|
-
|
|
104
|
+
setIntoStore(
|
|
105
|
+
userUnacknowledgedQueue,
|
|
81
106
|
(updates) => {
|
|
82
107
|
if (toEmit) {
|
|
83
108
|
updates.push(toEmit)
|
|
@@ -88,7 +113,7 @@ export function realtimeActionSynchronizer({
|
|
|
88
113
|
store,
|
|
89
114
|
)
|
|
90
115
|
|
|
91
|
-
socket.emit(`tx-new:${tx.key}`, toEmit)
|
|
116
|
+
socket.emit(`tx-new:${tx.key}`, toEmit as Json.Serializable)
|
|
92
117
|
},
|
|
93
118
|
`tx-sub:${tx.key}:${socket.id}`,
|
|
94
119
|
store,
|
|
@@ -100,10 +125,9 @@ export function realtimeActionSynchronizer({
|
|
|
100
125
|
let i = 1
|
|
101
126
|
let next = 1
|
|
102
127
|
const retry = setInterval(() => {
|
|
103
|
-
const toEmit =
|
|
104
|
-
console.log(userKey, socketUnacknowledgedUpdates)
|
|
128
|
+
const toEmit = userUnacknowledgedUpdates[0]
|
|
105
129
|
if (toEmit && i === next) {
|
|
106
|
-
socket.emit(`tx-new:${tx.key}`, toEmit)
|
|
130
|
+
socket.emit(`tx-new:${tx.key}`, toEmit as Json.Serializable)
|
|
107
131
|
next *= 2
|
|
108
132
|
}
|
|
109
133
|
|
|
@@ -113,16 +137,10 @@ export function realtimeActionSynchronizer({
|
|
|
113
137
|
const trackClientAcknowledgement = (epoch: number) => {
|
|
114
138
|
i = 1
|
|
115
139
|
next = 1
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
AtomIO.setState(socketEpochState, epoch, store)
|
|
123
|
-
if (socketUnacknowledgedUpdates[0]?.epoch === epoch) {
|
|
124
|
-
AtomIO.setState(
|
|
125
|
-
socketUnacknowledgedUpdatesState,
|
|
140
|
+
8
|
|
141
|
+
if (userUnacknowledgedUpdates[0]?.epoch === epoch) {
|
|
142
|
+
setIntoStore(
|
|
143
|
+
userUnacknowledgedQueue,
|
|
126
144
|
(updates) => {
|
|
127
145
|
updates.shift()
|
|
128
146
|
return updates
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import {
|
|
3
|
+
IMPLICIT,
|
|
4
|
+
actUponStore,
|
|
5
|
+
findInStore,
|
|
6
|
+
getFromStore,
|
|
7
|
+
isRootStore,
|
|
8
|
+
setIntoStore,
|
|
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
|
+
import { redactedPerspectiveUpdateSelectors } from "./realtime-server-stores"
|
|
18
|
+
import {
|
|
19
|
+
completeUpdateAtoms,
|
|
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 sendInitialPayload = () => {
|
|
92
|
+
const initialPayload: Json.Serializable[] = []
|
|
93
|
+
for (const atom of continuity.globals) {
|
|
94
|
+
initialPayload.push(atom, getFromStore(atom, store))
|
|
95
|
+
}
|
|
96
|
+
for (const { perspectiveAtoms } of continuity.perspectives) {
|
|
97
|
+
const perspectiveTokensState = findInStore(
|
|
98
|
+
perspectiveAtoms,
|
|
99
|
+
userKey,
|
|
100
|
+
store,
|
|
101
|
+
)
|
|
102
|
+
const perspectiveTokens = getFromStore(perspectiveTokensState, store)
|
|
103
|
+
for (const perspectiveToken of perspectiveTokens) {
|
|
104
|
+
const resource = getFromStore(perspectiveToken, store)
|
|
105
|
+
initialPayload.push(perspectiveToken, resource)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const epoch = isRootStore(store)
|
|
110
|
+
? store.transactionMeta.epoch.get(continuityKey) ?? null
|
|
111
|
+
: null
|
|
112
|
+
socket?.emit(`continuity-init:${continuityKey}`, epoch, initialPayload)
|
|
113
|
+
|
|
114
|
+
for (const transaction of continuity.actions) {
|
|
115
|
+
const unsubscribeFromTransaction = subscribeToTransaction(
|
|
116
|
+
transaction,
|
|
117
|
+
(update) => {
|
|
118
|
+
// store.logger.info(`userId`, userKey)
|
|
119
|
+
const updateState = findInStore(
|
|
120
|
+
completeUpdateAtoms,
|
|
121
|
+
update.id,
|
|
122
|
+
store,
|
|
123
|
+
)
|
|
124
|
+
setIntoStore(updateState, update, store)
|
|
125
|
+
const redactedUpdateKey = {
|
|
126
|
+
userId: userKey,
|
|
127
|
+
syncGroupKey: continuityKey,
|
|
128
|
+
updateId: update.id,
|
|
129
|
+
}
|
|
130
|
+
const redactedUpdateState = findInStore(
|
|
131
|
+
redactedPerspectiveUpdateSelectors,
|
|
132
|
+
redactedUpdateKey,
|
|
133
|
+
store,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const redactedUpdate = getFromStore(redactedUpdateState, store)
|
|
137
|
+
|
|
138
|
+
setIntoStore(
|
|
139
|
+
userUnacknowledgedQueue,
|
|
140
|
+
(updates) => {
|
|
141
|
+
if (redactedUpdate) {
|
|
142
|
+
updates.push(redactedUpdate)
|
|
143
|
+
updates.sort((a, b) => a.epoch - b.epoch)
|
|
144
|
+
}
|
|
145
|
+
return updates
|
|
146
|
+
},
|
|
147
|
+
store,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
socket?.emit(
|
|
151
|
+
`tx-new:${continuityKey}`,
|
|
152
|
+
redactedUpdate as Json.Serializable,
|
|
153
|
+
)
|
|
154
|
+
},
|
|
155
|
+
`sync-continuity:${continuityKey}:${userKey}`,
|
|
156
|
+
store,
|
|
157
|
+
)
|
|
158
|
+
unsubscribeFunctions.push(unsubscribeFromTransaction)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
socket.off(`get:${continuityKey}`, sendInitialPayload)
|
|
162
|
+
socket.on(`get:${continuityKey}`, sendInitialPayload)
|
|
163
|
+
|
|
164
|
+
const fillTransactionRequest = (
|
|
165
|
+
update: Pick<AtomIO.TransactionUpdate<JsonIO>, `id` | `key` | `params`>,
|
|
166
|
+
) => {
|
|
167
|
+
const transactionKey = update.key
|
|
168
|
+
const updateId = update.id
|
|
169
|
+
const performanceKey = `tx-run:${transactionKey}:${updateId}`
|
|
170
|
+
const performanceKeyStart = `${performanceKey}:start`
|
|
171
|
+
const performanceKeyEnd = `${performanceKey}:end`
|
|
172
|
+
performance.mark(performanceKeyStart)
|
|
173
|
+
actUponStore(
|
|
174
|
+
{ type: `transaction`, key: transactionKey },
|
|
175
|
+
updateId,
|
|
176
|
+
store,
|
|
177
|
+
)(...update.params)
|
|
178
|
+
performance.mark(performanceKeyEnd)
|
|
179
|
+
const metric = performance.measure(
|
|
180
|
+
performanceKey,
|
|
181
|
+
performanceKeyStart,
|
|
182
|
+
performanceKeyEnd,
|
|
183
|
+
)
|
|
184
|
+
store?.logger.info(
|
|
185
|
+
`🚀`,
|
|
186
|
+
`transaction`,
|
|
187
|
+
transactionKey,
|
|
188
|
+
updateId,
|
|
189
|
+
metric.duration,
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
socket.off(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
193
|
+
socket.on(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
194
|
+
|
|
195
|
+
let i = 1
|
|
196
|
+
let next = 1
|
|
197
|
+
const retry = setInterval(() => {
|
|
198
|
+
const toEmit = userUnacknowledgedUpdates[0]
|
|
199
|
+
store.logger.info(
|
|
200
|
+
`🔄`,
|
|
201
|
+
`continuity`,
|
|
202
|
+
continuityKey,
|
|
203
|
+
`${store.config.name} retrying ${userKey} (${i}/${next})`,
|
|
204
|
+
socket?.id,
|
|
205
|
+
userUnacknowledgedUpdates,
|
|
206
|
+
)
|
|
207
|
+
if (toEmit && i === next) {
|
|
208
|
+
socket?.emit(`tx-new:${continuityKey}`, toEmit as Json.Serializable)
|
|
209
|
+
next *= 2
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
i++
|
|
213
|
+
}, 250)
|
|
214
|
+
const trackClientAcknowledgement = (epoch: number) => {
|
|
215
|
+
store.logger.info(
|
|
216
|
+
`👍`,
|
|
217
|
+
`continuity`,
|
|
218
|
+
continuityKey,
|
|
219
|
+
`${userKey} acknowledged epoch ${epoch}`,
|
|
220
|
+
)
|
|
221
|
+
i = 1
|
|
222
|
+
next = 1
|
|
223
|
+
const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch
|
|
224
|
+
|
|
225
|
+
if (isUnacknowledged) {
|
|
226
|
+
setIntoStore(
|
|
227
|
+
userUnacknowledgedQueue,
|
|
228
|
+
(updates) => {
|
|
229
|
+
updates.shift()
|
|
230
|
+
return updates
|
|
231
|
+
},
|
|
232
|
+
store,
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
socket.off(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
237
|
+
socket.on(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
238
|
+
|
|
239
|
+
return () => {
|
|
240
|
+
clearInterval(retry)
|
|
241
|
+
for (const unsubscribe of unsubscribeFunctions) unsubscribe()
|
|
242
|
+
socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
243
|
+
socket?.off(`get:${continuityKey}`, sendInitialPayload)
|
|
244
|
+
socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import * as AtomIO from "atom.io"
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import {
|
|
3
|
+
IMPLICIT,
|
|
4
|
+
findInStore,
|
|
5
|
+
getFromStore,
|
|
6
|
+
subscribeToState,
|
|
7
|
+
} from "atom.io/internal"
|
|
8
|
+
import { type Json, stringifyJson } from "atom.io/json"
|
|
5
9
|
|
|
6
10
|
import type { ServerConfig } from "."
|
|
7
11
|
|
|
8
|
-
export type FamilyProvider = ReturnType<typeof
|
|
9
|
-
export function
|
|
12
|
+
export type FamilyProvider = ReturnType<typeof realtimeAtomFamilyProvider>
|
|
13
|
+
export function realtimeAtomFamilyProvider({
|
|
10
14
|
socket,
|
|
11
15
|
store = IMPLICIT.STORE,
|
|
12
16
|
}: ServerConfig) {
|
|
@@ -14,77 +18,40 @@ export function realtimeFamilyProvider({
|
|
|
14
18
|
J extends Json.Serializable,
|
|
15
19
|
K extends Json.Serializable,
|
|
16
20
|
>(
|
|
17
|
-
family: AtomIO.
|
|
21
|
+
family: AtomIO.RegularAtomFamilyToken<J, K>,
|
|
18
22
|
index: AtomIO.ReadableToken<Iterable<K>>,
|
|
19
23
|
): () => void {
|
|
20
|
-
const
|
|
21
|
-
const unsubFamilyCallbacksByKey = new Map<string, () => void>()
|
|
24
|
+
const unsubCallbacksByKey = new Map<string, () => void>()
|
|
22
25
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
unsubFamilyCallbacksByKey.clear()
|
|
28
|
-
socket.off(`unsub:${family.key}`, fillFamilyUnsubRequest)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const fillSingleUnsubRequest = (key: string) => {
|
|
32
|
-
socket.off(`unsub:${key}`, fillSingleUnsubRequest)
|
|
33
|
-
const unsub = unsubSingleCallbacksByKey.get(key)
|
|
26
|
+
const fillUnsubRequest = (key: string) => {
|
|
27
|
+
socket.off(`unsub:${key}`, fillUnsubRequest)
|
|
28
|
+
const unsub = unsubCallbacksByKey.get(key)
|
|
34
29
|
if (unsub) {
|
|
35
30
|
unsub()
|
|
36
|
-
|
|
31
|
+
unsubCallbacksByKey.delete(key)
|
|
37
32
|
}
|
|
38
33
|
}
|
|
39
34
|
|
|
40
|
-
const fillSubRequest = (subKey
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const token = findInStore(family,
|
|
45
|
-
socket.emit(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
const fillSubRequest = (subKey: K) => {
|
|
36
|
+
const exposedSubKeys = getFromStore(index, store)
|
|
37
|
+
for (const exposedSubKey of exposedSubKeys) {
|
|
38
|
+
if (stringifyJson(exposedSubKey) === stringifyJson(subKey)) {
|
|
39
|
+
const token = findInStore(family, subKey, store)
|
|
40
|
+
socket.emit(`serve:${token.key}`, getFromStore(token, store))
|
|
41
|
+
const unsubscribe = subscribeToState(
|
|
42
|
+
token,
|
|
43
|
+
({ newValue }) => {
|
|
44
|
+
socket.emit(`serve:${token.key}`, newValue)
|
|
45
|
+
},
|
|
46
|
+
`expose-family:${family.key}:${socket.id}`,
|
|
47
|
+
store,
|
|
49
48
|
)
|
|
49
|
+
unsubCallbacksByKey.set(token.key, unsubscribe)
|
|
50
|
+
socket.on(`unsub:${token.key}`, () => {
|
|
51
|
+
fillUnsubRequest(token.key)
|
|
52
|
+
})
|
|
53
|
+
break
|
|
50
54
|
}
|
|
51
|
-
|
|
52
|
-
const unsubscribeFromTokenCreation = family.subject.subscribe(
|
|
53
|
-
`expose-family:${socket.id}`,
|
|
54
|
-
(token: AtomIO.WritableToken<J>) => {
|
|
55
|
-
const unsub = subscribeToState(
|
|
56
|
-
token,
|
|
57
|
-
({ newValue }) => {
|
|
58
|
-
socket.emit(
|
|
59
|
-
`serve:${family.key}`,
|
|
60
|
-
parseJson(token.family?.subKey || `null`),
|
|
61
|
-
newValue,
|
|
62
|
-
)
|
|
63
|
-
},
|
|
64
|
-
`expose-family:${family.key}:${socket.id}`,
|
|
65
|
-
store,
|
|
66
|
-
)
|
|
67
|
-
unsubFamilyCallbacksByKey.set(token.key, unsub)
|
|
68
|
-
},
|
|
69
|
-
)
|
|
70
|
-
unsubFamilyCallbacksByKey.set(family.key, unsubscribeFromTokenCreation)
|
|
71
|
-
|
|
72
|
-
socket.on(`unsub:${family.key}`, fillFamilyUnsubRequest)
|
|
73
|
-
} else {
|
|
74
|
-
const token = family(subKey)
|
|
75
|
-
socket.emit(`serve:${token.key}`, AtomIO.getState(token, store))
|
|
76
|
-
const unsubscribe = subscribeToState(
|
|
77
|
-
token,
|
|
78
|
-
({ newValue }) => {
|
|
79
|
-
socket.emit(`serve:${token.key}`, newValue)
|
|
80
|
-
},
|
|
81
|
-
`expose-family:${family.key}:${socket.id}`,
|
|
82
|
-
store,
|
|
83
|
-
)
|
|
84
|
-
unsubSingleCallbacksByKey.set(token.key, unsubscribe)
|
|
85
|
-
socket.on(`unsub:${token.key}`, () => {
|
|
86
|
-
fillSingleUnsubRequest(token.key)
|
|
87
|
-
})
|
|
88
55
|
}
|
|
89
56
|
}
|
|
90
57
|
|
|
@@ -92,14 +59,11 @@ export function realtimeFamilyProvider({
|
|
|
92
59
|
|
|
93
60
|
return () => {
|
|
94
61
|
socket.off(`sub:${family.key}`, fillSubRequest)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
for (const [, unsub] of unsubSingleCallbacksByKey) {
|
|
62
|
+
|
|
63
|
+
for (const [, unsub] of unsubCallbacksByKey) {
|
|
99
64
|
unsub()
|
|
100
65
|
}
|
|
101
|
-
|
|
102
|
-
unsubSingleCallbacksByKey.clear()
|
|
66
|
+
unsubCallbacksByKey.clear()
|
|
103
67
|
}
|
|
104
68
|
}
|
|
105
69
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import * as AtomIO from "atom.io"
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
2
|
import type { Transceiver } from "atom.io/internal"
|
|
3
3
|
import {
|
|
4
4
|
IMPLICIT,
|
|
5
5
|
findInStore,
|
|
6
|
+
getFromStore,
|
|
6
7
|
getJsonToken,
|
|
7
8
|
getUpdateToken,
|
|
8
9
|
subscribeToState,
|
|
9
10
|
} from "atom.io/internal"
|
|
10
11
|
import type { Json } from "atom.io/json"
|
|
11
|
-
import {
|
|
12
|
+
import { stringifyJson } from "atom.io/json"
|
|
12
13
|
|
|
13
14
|
import type { ServerConfig } from "."
|
|
14
15
|
|
|
@@ -24,100 +25,42 @@ export function realtimeMutableFamilyProvider({
|
|
|
24
25
|
J extends Json.Serializable,
|
|
25
26
|
K extends Json.Serializable,
|
|
26
27
|
>(
|
|
27
|
-
family: AtomIO.
|
|
28
|
+
family: AtomIO.MutableAtomFamilyToken<T, J, K>,
|
|
28
29
|
index: AtomIO.ReadableToken<Iterable<K>>,
|
|
29
30
|
): () => void {
|
|
30
|
-
const
|
|
31
|
-
const unsubFamilyCallbacksByKey = new Map<string, () => void>()
|
|
31
|
+
const unsubCallbacksByKey = new Map<string, () => void>()
|
|
32
32
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
unsubFamilyCallbacksByKey.clear()
|
|
38
|
-
socket.off(`unsub:${family.key}`, fillFamilyUnsubRequest)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const fillSingleUnsubRequest = (key: string) => {
|
|
42
|
-
socket.off(`unsub:${key}`, fillSingleUnsubRequest)
|
|
43
|
-
const unsub = unsubSingleCallbacksByKey.get(key)
|
|
33
|
+
const fillUnsubRequest = (key: string) => {
|
|
34
|
+
socket.off(`unsub:${key}`, fillUnsubRequest)
|
|
35
|
+
const unsub = unsubCallbacksByKey.get(key)
|
|
44
36
|
if (unsub) {
|
|
45
37
|
unsub()
|
|
46
|
-
|
|
38
|
+
unsubCallbacksByKey.delete(key)
|
|
47
39
|
}
|
|
48
40
|
}
|
|
49
41
|
|
|
50
|
-
const fillSubRequest = (subKey
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const token = findInStore(family,
|
|
42
|
+
const fillSubRequest = (subKey: K) => {
|
|
43
|
+
const exposedSubKeys = getFromStore(index, store)
|
|
44
|
+
for (const exposedSubKey of exposedSubKeys) {
|
|
45
|
+
if (stringifyJson(exposedSubKey) === stringifyJson(subKey)) {
|
|
46
|
+
const token = findInStore(family, subKey, store)
|
|
55
47
|
const jsonToken = getJsonToken(token)
|
|
56
|
-
const
|
|
57
|
-
socket.emit(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
AtomIO.getState(jsonToken, store),
|
|
61
|
-
)
|
|
62
|
-
const unsubFromUpdates = subscribeToState(
|
|
63
|
-
trackerToken,
|
|
48
|
+
const updateToken = getUpdateToken(token)
|
|
49
|
+
socket.emit(`init:${token.key}`, getFromStore(jsonToken, store))
|
|
50
|
+
const unsubscribe = subscribeToState(
|
|
51
|
+
updateToken,
|
|
64
52
|
({ newValue }) => {
|
|
65
|
-
socket.emit(
|
|
66
|
-
`next:${token.key}`,
|
|
67
|
-
parseJson(jsonToken.family?.subKey || `null`),
|
|
68
|
-
newValue,
|
|
69
|
-
)
|
|
53
|
+
socket.emit(`next:${token.key}`, newValue)
|
|
70
54
|
},
|
|
71
55
|
`expose-family:${family.key}:${socket.id}`,
|
|
72
56
|
store,
|
|
73
57
|
)
|
|
74
|
-
|
|
58
|
+
unsubCallbacksByKey.set(token.key, unsubscribe)
|
|
59
|
+
socket.on(`unsub:${token.key}`, () => {
|
|
60
|
+
fillUnsubRequest(token.key)
|
|
61
|
+
})
|
|
62
|
+
break
|
|
75
63
|
}
|
|
76
|
-
const unsubscribeFromTokenCreation = family.subject.subscribe(
|
|
77
|
-
`expose-family:${socket.id}`,
|
|
78
|
-
(token) => {
|
|
79
|
-
const jsonToken = getJsonToken(token)
|
|
80
|
-
const trackerToken = getUpdateToken(token)
|
|
81
|
-
socket.emit(
|
|
82
|
-
`init:${family.key}`,
|
|
83
|
-
parseJson(jsonToken.family?.subKey || `null`),
|
|
84
|
-
AtomIO.getState(jsonToken, store),
|
|
85
|
-
)
|
|
86
|
-
const unsubFromUpdates = subscribeToState(
|
|
87
|
-
trackerToken,
|
|
88
|
-
({ newValue }) => {
|
|
89
|
-
socket.emit(
|
|
90
|
-
`next:${token.key}`,
|
|
91
|
-
parseJson(jsonToken.family?.subKey || `null`),
|
|
92
|
-
newValue,
|
|
93
|
-
)
|
|
94
|
-
},
|
|
95
|
-
`expose-family:${family.key}:${socket.id}`,
|
|
96
|
-
store,
|
|
97
|
-
)
|
|
98
|
-
unsubFamilyCallbacksByKey.set(token.key, unsubFromUpdates)
|
|
99
|
-
},
|
|
100
|
-
)
|
|
101
|
-
unsubFamilyCallbacksByKey.set(family.key, unsubscribeFromTokenCreation)
|
|
102
|
-
|
|
103
|
-
socket.on(`unsub:${family.key}`, fillFamilyUnsubRequest)
|
|
104
|
-
} else {
|
|
105
|
-
const token = family(subKey)
|
|
106
|
-
const jsonToken = getJsonToken(token)
|
|
107
|
-
const updateToken = getUpdateToken(token)
|
|
108
|
-
socket.emit(`init:${token.key}`, AtomIO.getState(jsonToken, store))
|
|
109
|
-
const unsubscribe = subscribeToState(
|
|
110
|
-
updateToken,
|
|
111
|
-
({ newValue }) => {
|
|
112
|
-
socket.emit(`next:${token.key}`, newValue)
|
|
113
|
-
},
|
|
114
|
-
`expose-family:${family.key}:${socket.id}`,
|
|
115
|
-
store,
|
|
116
|
-
)
|
|
117
|
-
unsubSingleCallbacksByKey.set(token.key, unsubscribe)
|
|
118
|
-
socket.on(`unsub:${token.key}`, () => {
|
|
119
|
-
fillSingleUnsubRequest(token.key)
|
|
120
|
-
})
|
|
121
64
|
}
|
|
122
65
|
}
|
|
123
66
|
|
|
@@ -125,14 +68,10 @@ export function realtimeMutableFamilyProvider({
|
|
|
125
68
|
|
|
126
69
|
return () => {
|
|
127
70
|
socket.off(`sub:${family.key}`, fillSubRequest)
|
|
128
|
-
for (const [, unsub] of
|
|
129
|
-
unsub()
|
|
130
|
-
}
|
|
131
|
-
for (const [, unsub] of unsubSingleCallbacksByKey) {
|
|
71
|
+
for (const [, unsub] of unsubCallbacksByKey) {
|
|
132
72
|
unsub()
|
|
133
73
|
}
|
|
134
|
-
|
|
135
|
-
unsubSingleCallbacksByKey.clear()
|
|
74
|
+
unsubCallbacksByKey.clear()
|
|
136
75
|
}
|
|
137
76
|
}
|
|
138
77
|
}
|