atom.io 0.16.2 → 0.17.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/dist/chunk-H4Q5FTPZ.js +11 -0
- package/dist/chunk-H4Q5FTPZ.js.map +1 -0
- package/dist/index.cjs +35 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +12 -36
- package/dist/index.js.map +1 -1
- package/internal/dist/index.cjs +268 -195
- package/internal/dist/index.cjs.map +1 -1
- package/internal/dist/index.d.ts +36 -11
- package/internal/dist/index.js +258 -195
- package/internal/dist/index.js.map +1 -1
- package/internal/src/atom/create-regular-atom.ts +2 -3
- package/internal/src/families/find-in-store.ts +74 -0
- package/internal/src/families/index.ts +1 -0
- 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 +1 -1
- package/internal/src/ingest-updates/ingest-atom-update.ts +2 -2
- package/internal/src/ingest-updates/ingest-transaction-update.ts +1 -0
- package/internal/src/mutable/create-mutable-atom.ts +3 -4
- package/internal/src/mutable/tracker.ts +43 -35
- package/internal/src/mutable/transceiver.ts +1 -1
- package/internal/src/not-found-error.ts +14 -3
- package/internal/src/operation.ts +2 -1
- package/internal/src/selector/create-writable-selector.ts +2 -1
- package/internal/src/selector/register-selector.ts +6 -5
- package/internal/src/set-state/index.ts +1 -0
- package/internal/src/set-state/set-atom.ts +17 -3
- package/internal/src/set-state/set-into-store.ts +24 -0
- package/internal/src/set-state/stow-update.ts +2 -4
- package/internal/src/store/store.ts +13 -4
- package/internal/src/subscribe/subscribe-to-root-atoms.ts +1 -1
- package/internal/src/timeline/add-atom-to-timeline.ts +5 -5
- package/internal/src/transaction/abort-transaction.ts +2 -1
- package/internal/src/transaction/apply-transaction.ts +5 -3
- package/internal/src/transaction/build-transaction.ts +20 -11
- package/internal/src/transaction/create-transaction.ts +2 -3
- package/internal/src/transaction/index.ts +3 -2
- package/internal/src/transaction/is-root-store.ts +23 -0
- package/package.json +10 -10
- package/react/dist/index.cjs +27 -21
- package/react/dist/index.cjs.map +1 -1
- package/react/dist/index.d.ts +8 -2
- package/react/dist/index.js +28 -22
- package/react/dist/index.js.map +1 -1
- package/react/src/index.ts +4 -1
- package/react/src/use-i.ts +35 -0
- package/react/src/use-json.ts +38 -0
- package/react/src/use-o.ts +33 -0
- package/react/src/use-tl.ts +45 -0
- package/realtime-client/dist/index.cjs +167 -64
- package/realtime-client/dist/index.cjs.map +1 -1
- package/realtime-client/dist/index.d.ts +10 -6
- package/realtime-client/dist/index.js +158 -63
- package/realtime-client/dist/index.js.map +1 -1
- package/realtime-client/src/index.ts +2 -1
- package/realtime-client/src/pull-family-member.ts +3 -3
- package/realtime-client/src/pull-mutable-family-member.ts +4 -4
- package/realtime-client/src/pull-mutable.ts +4 -4
- package/realtime-client/src/pull-state.ts +7 -6
- package/realtime-client/src/{realtime-client-store.ts → realtime-client-stores/client-main-store.ts} +0 -8
- package/realtime-client/src/realtime-client-stores/client-sync-store.ts +15 -0
- package/realtime-client/src/realtime-client-stores/index.ts +2 -0
- package/realtime-client/src/sync-server-action.ts +134 -40
- package/realtime-client/src/sync-state.ts +19 -0
- package/realtime-react/dist/index.cjs +43 -26
- package/realtime-react/dist/index.cjs.map +1 -1
- package/realtime-react/dist/index.d.ts +3 -1
- package/realtime-react/dist/index.js +41 -25
- package/realtime-react/dist/index.js.map +1 -1
- package/realtime-react/src/index.ts +1 -0
- package/realtime-react/src/on-mount.ts +3 -21
- package/realtime-react/src/use-realtime-service.ts +1 -1
- package/realtime-react/src/use-single-effect.ts +29 -0
- package/realtime-react/src/use-sync-server-action.ts +4 -7
- package/realtime-react/src/use-sync.ts +17 -0
- package/realtime-server/dist/index.cjs +239 -56
- package/realtime-server/dist/index.cjs.map +1 -1
- package/realtime-server/dist/index.d.ts +140 -9
- package/realtime-server/dist/index.js +228 -58
- package/realtime-server/dist/index.js.map +1 -1
- package/realtime-server/src/index.ts +2 -0
- package/realtime-server/src/realtime-action-synchronizer.ts +95 -14
- package/realtime-server/src/realtime-family-provider.ts +11 -6
- package/realtime-server/src/realtime-mutable-family-provider.ts +8 -6
- package/realtime-server/src/realtime-mutable-provider.ts +3 -2
- package/realtime-server/src/realtime-server-stores/index.ts +2 -0
- package/realtime-server/src/realtime-server-stores/server-sync-store.ts +115 -0
- package/realtime-server/src/realtime-server-stores/server-user-store.ts +45 -0
- package/realtime-server/src/realtime-state-provider.ts +18 -11
- package/realtime-server/src/realtime-state-receiver.ts +2 -2
- package/realtime-server/src/realtime-state-synchronizer.ts +23 -0
- package/realtime-testing/dist/index.cjs +65 -26
- package/realtime-testing/dist/index.cjs.map +1 -1
- package/realtime-testing/dist/index.d.ts +11 -7
- package/realtime-testing/dist/index.js +64 -26
- package/realtime-testing/dist/index.js.map +1 -1
- package/realtime-testing/src/setup-realtime-test.tsx +83 -43
- package/src/find-state.ts +8 -16
- package/src/get-state.ts +2 -11
- package/src/logger.ts +1 -0
- package/src/set-state.ts +1 -13
- package/src/silo.ts +7 -3
- package/src/transaction.ts +3 -3
- package/react/src/store-hooks.ts +0 -87
- package/realtime-server/src/realtime-server-store.ts +0 -39
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import * as AtomIO from "atom.io"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
IMPLICIT,
|
|
4
|
+
findInStore,
|
|
5
|
+
getFromStore,
|
|
6
|
+
setIntoStore,
|
|
7
|
+
subscribeToTransaction,
|
|
8
|
+
} from "atom.io/internal"
|
|
3
9
|
|
|
4
10
|
import type { ServerConfig } from "."
|
|
11
|
+
import { usersOfSockets } from "./realtime-server-stores"
|
|
5
12
|
import {
|
|
6
13
|
completeUpdateAtoms,
|
|
7
14
|
redactedUpdateSelectors,
|
|
15
|
+
socketEpochSelectors,
|
|
16
|
+
socketUnacknowledgedUpdatesSelectors,
|
|
8
17
|
transactionRedactorAtoms,
|
|
9
|
-
} from "./realtime-server-store"
|
|
18
|
+
} from "./realtime-server-stores/server-sync-store"
|
|
10
19
|
|
|
11
20
|
export type ActionSynchronizer = ReturnType<typeof realtimeActionSynchronizer>
|
|
12
21
|
export function realtimeActionSynchronizer({
|
|
@@ -19,12 +28,24 @@ export function realtimeActionSynchronizer({
|
|
|
19
28
|
update: AtomIO.TransactionUpdateContent[],
|
|
20
29
|
) => AtomIO.TransactionUpdateContent[],
|
|
21
30
|
): () => void {
|
|
31
|
+
const userKeyState = findInStore(
|
|
32
|
+
usersOfSockets.states.userKeyOfSocket,
|
|
33
|
+
socket.id,
|
|
34
|
+
store,
|
|
35
|
+
)
|
|
36
|
+
const userKey = getFromStore(userKeyState, store)
|
|
37
|
+
const socketUnacknowledgedUpdatesState = findInStore(
|
|
38
|
+
socketUnacknowledgedUpdatesSelectors,
|
|
39
|
+
socket.id,
|
|
40
|
+
store,
|
|
41
|
+
)
|
|
42
|
+
const socketUnacknowledgedUpdates = getFromStore(
|
|
43
|
+
socketUnacknowledgedUpdatesState,
|
|
44
|
+
store,
|
|
45
|
+
)
|
|
22
46
|
if (filter) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{ filter },
|
|
26
|
-
store,
|
|
27
|
-
)
|
|
47
|
+
const redactorState = findInStore(transactionRedactorAtoms, tx.key, store)
|
|
48
|
+
setIntoStore(redactorState, { filter }, store)
|
|
28
49
|
}
|
|
29
50
|
const fillTransactionRequest = (update: AtomIO.TransactionUpdate<ƒ>) => {
|
|
30
51
|
const performanceKey = `tx-run:${tx.key}:${update.id}`
|
|
@@ -40,32 +61,92 @@ export function realtimeActionSynchronizer({
|
|
|
40
61
|
)
|
|
41
62
|
store?.logger.info(`🚀`, `transaction`, tx.key, update.id, metric.duration)
|
|
42
63
|
}
|
|
64
|
+
socket.off(`tx-run:${tx.key}`, fillTransactionRequest)
|
|
43
65
|
socket.on(`tx-run:${tx.key}`, fillTransactionRequest)
|
|
44
66
|
|
|
67
|
+
let unsubscribeFromTransaction: (() => void) | undefined
|
|
45
68
|
const fillTransactionSubscriptionRequest = () => {
|
|
46
|
-
|
|
69
|
+
unsubscribeFromTransaction = subscribeToTransaction(
|
|
47
70
|
tx,
|
|
48
71
|
(update) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
AtomIO.setState(updateState, update, store)
|
|
72
|
+
const updateState = findInStore(completeUpdateAtoms, update.id, store)
|
|
73
|
+
setIntoStore(updateState, update, store)
|
|
52
74
|
const toEmit = filter
|
|
53
|
-
?
|
|
54
|
-
|
|
75
|
+
? getFromStore(
|
|
76
|
+
findInStore(redactedUpdateSelectors, [tx.key, update.id], store),
|
|
55
77
|
store,
|
|
56
78
|
)
|
|
57
79
|
: update
|
|
80
|
+
|
|
81
|
+
// the problem is that only while a socket is connected can
|
|
82
|
+
// updates be set in the queue for that socket's client.
|
|
83
|
+
//
|
|
84
|
+
// we need a client session that can persist between disconnects
|
|
85
|
+
setIntoStore(
|
|
86
|
+
socketUnacknowledgedUpdatesState,
|
|
87
|
+
(updates) => {
|
|
88
|
+
if (toEmit) {
|
|
89
|
+
updates.push(toEmit)
|
|
90
|
+
updates.sort((a, b) => a.epoch - b.epoch)
|
|
91
|
+
}
|
|
92
|
+
return updates
|
|
93
|
+
},
|
|
94
|
+
store,
|
|
95
|
+
)
|
|
96
|
+
|
|
58
97
|
socket.emit(`tx-new:${tx.key}`, toEmit)
|
|
59
98
|
},
|
|
60
99
|
`tx-sub:${tx.key}:${socket.id}`,
|
|
61
100
|
store,
|
|
62
101
|
)
|
|
63
|
-
socket.on(`tx-unsub:${tx.key}`,
|
|
102
|
+
socket.on(`tx-unsub:${tx.key}`, unsubscribeFromTransaction)
|
|
64
103
|
}
|
|
65
104
|
socket.on(`tx-sub:${tx.key}`, fillTransactionSubscriptionRequest)
|
|
66
105
|
|
|
106
|
+
let i = 1
|
|
107
|
+
let next = 1
|
|
108
|
+
const retry = setInterval(() => {
|
|
109
|
+
const toEmit = socketUnacknowledgedUpdates[0]
|
|
110
|
+
console.log(userKey, socketUnacknowledgedUpdates)
|
|
111
|
+
if (toEmit && i === next) {
|
|
112
|
+
socket.emit(`tx-new:${tx.key}`, toEmit)
|
|
113
|
+
next *= 2
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
i++
|
|
117
|
+
}, 250)
|
|
118
|
+
|
|
119
|
+
const trackClientAcknowledgement = (epoch: number) => {
|
|
120
|
+
i = 1
|
|
121
|
+
next = 1
|
|
122
|
+
const socketEpochState = findInStore(
|
|
123
|
+
socketEpochSelectors,
|
|
124
|
+
socket.id,
|
|
125
|
+
store,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
setIntoStore(socketEpochState, epoch, store)
|
|
129
|
+
if (socketUnacknowledgedUpdates[0]?.epoch === epoch) {
|
|
130
|
+
setIntoStore(
|
|
131
|
+
socketUnacknowledgedUpdatesState,
|
|
132
|
+
(updates) => {
|
|
133
|
+
updates.shift()
|
|
134
|
+
return updates
|
|
135
|
+
},
|
|
136
|
+
store,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
socket.on(`tx-ack:${tx.key}`, trackClientAcknowledgement)
|
|
141
|
+
|
|
67
142
|
return () => {
|
|
143
|
+
if (unsubscribeFromTransaction) {
|
|
144
|
+
unsubscribeFromTransaction()
|
|
145
|
+
unsubscribeFromTransaction = undefined
|
|
146
|
+
}
|
|
147
|
+
clearInterval(retry)
|
|
68
148
|
socket.off(`tx-run:${tx.key}`, fillTransactionRequest)
|
|
149
|
+
socket.off(`tx-sub:${tx.key}`, fillTransactionSubscriptionRequest)
|
|
69
150
|
}
|
|
70
151
|
}
|
|
71
152
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import * as AtomIO from "atom.io"
|
|
2
|
-
import {
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import {
|
|
3
|
+
IMPLICIT,
|
|
4
|
+
findInStore,
|
|
5
|
+
getFromStore,
|
|
6
|
+
subscribeToState,
|
|
7
|
+
} from "atom.io/internal"
|
|
3
8
|
import type { Json } from "atom.io/json"
|
|
4
9
|
import { parseJson } from "atom.io/json"
|
|
5
10
|
|
|
@@ -39,13 +44,13 @@ export function realtimeFamilyProvider({
|
|
|
39
44
|
|
|
40
45
|
const fillSubRequest = (subKey?: K) => {
|
|
41
46
|
if (subKey === undefined) {
|
|
42
|
-
const keys =
|
|
47
|
+
const keys = getFromStore(index, store)
|
|
43
48
|
for (const key of keys) {
|
|
44
|
-
const token =
|
|
49
|
+
const token = findInStore(family, key, store)
|
|
45
50
|
socket.emit(
|
|
46
51
|
`serve:${family.key}`,
|
|
47
52
|
parseJson(token.family?.subKey || `null`),
|
|
48
|
-
|
|
53
|
+
getFromStore(token, store),
|
|
49
54
|
)
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -72,7 +77,7 @@ export function realtimeFamilyProvider({
|
|
|
72
77
|
socket.on(`unsub:${family.key}`, fillFamilyUnsubRequest)
|
|
73
78
|
} else {
|
|
74
79
|
const token = family(subKey)
|
|
75
|
-
socket.emit(`serve:${token.key}`,
|
|
80
|
+
socket.emit(`serve:${token.key}`, getFromStore(token, store))
|
|
76
81
|
const unsubscribe = subscribeToState(
|
|
77
82
|
token,
|
|
78
83
|
({ newValue }) => {
|
|
@@ -1,7 +1,9 @@
|
|
|
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
|
+
findInStore,
|
|
6
|
+
getFromStore,
|
|
5
7
|
getJsonToken,
|
|
6
8
|
getUpdateToken,
|
|
7
9
|
subscribeToState,
|
|
@@ -48,15 +50,15 @@ export function realtimeMutableFamilyProvider({
|
|
|
48
50
|
|
|
49
51
|
const fillSubRequest = (subKey?: K) => {
|
|
50
52
|
if (subKey === undefined) {
|
|
51
|
-
const keys =
|
|
53
|
+
const keys = getFromStore(index, store)
|
|
52
54
|
for (const key of keys) {
|
|
53
|
-
const token = family
|
|
55
|
+
const token = findInStore(family, key, store)
|
|
54
56
|
const jsonToken = getJsonToken(token)
|
|
55
57
|
const trackerToken = getUpdateToken(token)
|
|
56
58
|
socket.emit(
|
|
57
59
|
`init:${family.key}`,
|
|
58
60
|
parseJson(jsonToken.family?.subKey || `null`),
|
|
59
|
-
|
|
61
|
+
getFromStore(jsonToken, store),
|
|
60
62
|
)
|
|
61
63
|
const unsubFromUpdates = subscribeToState(
|
|
62
64
|
trackerToken,
|
|
@@ -80,7 +82,7 @@ export function realtimeMutableFamilyProvider({
|
|
|
80
82
|
socket.emit(
|
|
81
83
|
`init:${family.key}`,
|
|
82
84
|
parseJson(jsonToken.family?.subKey || `null`),
|
|
83
|
-
|
|
85
|
+
getFromStore(jsonToken, store),
|
|
84
86
|
)
|
|
85
87
|
const unsubFromUpdates = subscribeToState(
|
|
86
88
|
trackerToken,
|
|
@@ -104,7 +106,7 @@ export function realtimeMutableFamilyProvider({
|
|
|
104
106
|
const token = family(subKey)
|
|
105
107
|
const jsonToken = getJsonToken(token)
|
|
106
108
|
const updateToken = getUpdateToken(token)
|
|
107
|
-
socket.emit(`init:${token.key}`,
|
|
109
|
+
socket.emit(`init:${token.key}`, getFromStore(jsonToken, store))
|
|
108
110
|
const unsubscribe = subscribeToState(
|
|
109
111
|
updateToken,
|
|
110
112
|
({ newValue }) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import * as AtomIO from "atom.io"
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
2
|
import {
|
|
3
3
|
IMPLICIT,
|
|
4
|
+
getFromStore,
|
|
4
5
|
getJsonToken,
|
|
5
6
|
getUpdateToken,
|
|
6
7
|
subscribeToState,
|
|
@@ -31,7 +32,7 @@ export function realtimeMutableProvider({
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const fillSubRequest = () => {
|
|
34
|
-
socket.emit(`init:${token.key}`,
|
|
35
|
+
socket.emit(`init:${token.key}`, getFromStore(jsonToken, store))
|
|
35
36
|
unsubscribeFromStateUpdates = subscribeToState(
|
|
36
37
|
trackerToken,
|
|
37
38
|
({ newValue }) => {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { TransactionUpdate, TransactionUpdateContent } from "atom.io"
|
|
2
|
+
import { atomFamily, selectorFamily } from "atom.io"
|
|
3
|
+
import { SyncGroup } from "~/packages/atom.io/__unstable__/realtime-server-next/create-realtime-sync-group"
|
|
4
|
+
import { usersOfSockets } from "./server-user-store"
|
|
5
|
+
|
|
6
|
+
export const completeUpdateAtoms = atomFamily<
|
|
7
|
+
TransactionUpdate<any> | null,
|
|
8
|
+
string
|
|
9
|
+
>({
|
|
10
|
+
key: `completeUpdate`,
|
|
11
|
+
default: null,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export const transactionRedactorAtoms = atomFamily<
|
|
15
|
+
{
|
|
16
|
+
filter: (updates: TransactionUpdateContent[]) => TransactionUpdateContent[]
|
|
17
|
+
},
|
|
18
|
+
string
|
|
19
|
+
>({
|
|
20
|
+
key: `transactionRedactor`,
|
|
21
|
+
default: { filter: (updates) => updates },
|
|
22
|
+
})
|
|
23
|
+
export const redactedUpdateSelectors = selectorFamily<
|
|
24
|
+
TransactionUpdate<any> | null,
|
|
25
|
+
[transactionKey: string, updateId: string]
|
|
26
|
+
>({
|
|
27
|
+
key: `redactedUpdate`,
|
|
28
|
+
get:
|
|
29
|
+
([transactionKey, updateId]) =>
|
|
30
|
+
({ get, find }) => {
|
|
31
|
+
const update = get(find(completeUpdateAtoms, updateId))
|
|
32
|
+
const { filter } = get(find(transactionRedactorAtoms, transactionKey))
|
|
33
|
+
|
|
34
|
+
if (update && filter) {
|
|
35
|
+
return { ...update, updates: filter(update.updates) }
|
|
36
|
+
}
|
|
37
|
+
return null
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export const userUnacknowledgedUpdatesAtoms = atomFamily<
|
|
42
|
+
TransactionUpdate<any>[],
|
|
43
|
+
string
|
|
44
|
+
>({
|
|
45
|
+
key: `unacknowledgedUpdates`,
|
|
46
|
+
default: () => [],
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export const socketUnacknowledgedUpdatesSelectors = selectorFamily<
|
|
50
|
+
TransactionUpdate<any>[],
|
|
51
|
+
string
|
|
52
|
+
>({
|
|
53
|
+
key: `socketUnacknowledgedUpdates`,
|
|
54
|
+
get:
|
|
55
|
+
(socketId) =>
|
|
56
|
+
({ get, find }) => {
|
|
57
|
+
const userKeyState = find(usersOfSockets.states.userKeyOfSocket, socketId)
|
|
58
|
+
const userKey = get(userKeyState)
|
|
59
|
+
if (!userKey) {
|
|
60
|
+
return []
|
|
61
|
+
}
|
|
62
|
+
const unacknowledgedUpdatesState = find(
|
|
63
|
+
userUnacknowledgedUpdatesAtoms,
|
|
64
|
+
userKey,
|
|
65
|
+
)
|
|
66
|
+
const unacknowledgedUpdates = get(unacknowledgedUpdatesState)
|
|
67
|
+
return unacknowledgedUpdates
|
|
68
|
+
},
|
|
69
|
+
set:
|
|
70
|
+
(socketId) =>
|
|
71
|
+
({ set, get, find }, newUpdates) => {
|
|
72
|
+
const userKeyState = find(usersOfSockets.states.userKeyOfSocket, socketId)
|
|
73
|
+
const userKey = get(userKeyState)
|
|
74
|
+
if (!userKey) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
const unacknowledgedUpdatesState = find(
|
|
78
|
+
userUnacknowledgedUpdatesAtoms,
|
|
79
|
+
userKey,
|
|
80
|
+
)
|
|
81
|
+
set(unacknowledgedUpdatesState, newUpdates)
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
export const userEpochAtoms = atomFamily<number | null, string>({
|
|
86
|
+
key: `clientEpoch`,
|
|
87
|
+
default: null,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
export const socketEpochSelectors = selectorFamily<number | null, string>({
|
|
91
|
+
key: `socketEpoch`,
|
|
92
|
+
get:
|
|
93
|
+
(socketId) =>
|
|
94
|
+
({ get, find }) => {
|
|
95
|
+
const userKeyState = find(usersOfSockets.states.userKeyOfSocket, socketId)
|
|
96
|
+
const userKey = get(userKeyState)
|
|
97
|
+
if (!userKey) {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
const userEpochState = find(userEpochAtoms, userKey)
|
|
101
|
+
const userEpoch = get(userEpochState)
|
|
102
|
+
return userEpoch
|
|
103
|
+
},
|
|
104
|
+
set:
|
|
105
|
+
(socketId) =>
|
|
106
|
+
({ set, get, find }, newEpoch) => {
|
|
107
|
+
const userKeyState = find(usersOfSockets.states.userKeyOfSocket, socketId)
|
|
108
|
+
const userKey = get(userKeyState)
|
|
109
|
+
if (!userKey) {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
const userEpochState = find(userEpochAtoms, userKey)
|
|
113
|
+
set(userEpochState, newEpoch)
|
|
114
|
+
},
|
|
115
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { atom, atomFamily } from "atom.io"
|
|
2
|
+
import { join } from "atom.io/data"
|
|
3
|
+
import { SetRTX } from "atom.io/transceivers/set-rtx"
|
|
4
|
+
|
|
5
|
+
export const userIndex = atom({
|
|
6
|
+
key: `usersIndex`,
|
|
7
|
+
mutable: true,
|
|
8
|
+
default: () => new SetRTX<string>(),
|
|
9
|
+
toJson: (set) => set.toJSON(),
|
|
10
|
+
fromJson: (json) => SetRTX.fromJSON(json),
|
|
11
|
+
})
|
|
12
|
+
export const usersOfSockets = join({
|
|
13
|
+
key: `usersOfSockets`,
|
|
14
|
+
between: [`user`, `socket`],
|
|
15
|
+
cardinality: `1:1`,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const roomIndex = atom({
|
|
19
|
+
key: `conclaveIndex`,
|
|
20
|
+
default: () => new SetRTX<string>(),
|
|
21
|
+
mutable: true,
|
|
22
|
+
toJson: (set) => set.toJSON(),
|
|
23
|
+
fromJson: (json) => SetRTX.fromJSON(json),
|
|
24
|
+
})
|
|
25
|
+
export type UserInRoomMeta = {
|
|
26
|
+
enteredAtEpoch: number
|
|
27
|
+
}
|
|
28
|
+
export const DEFAULT_USER_IN_ROOM_META: UserInRoomMeta = {
|
|
29
|
+
enteredAtEpoch: 0,
|
|
30
|
+
}
|
|
31
|
+
export const usersInRooms = join(
|
|
32
|
+
{
|
|
33
|
+
key: `usersInRooms`,
|
|
34
|
+
between: [`room`, `user`],
|
|
35
|
+
cardinality: `1:n`,
|
|
36
|
+
},
|
|
37
|
+
DEFAULT_USER_IN_ROOM_META,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// export const roomStoreAtoms = atomFamily<
|
|
41
|
+
// { [key: string]: any },
|
|
42
|
+
// { conclave: string }
|
|
43
|
+
// >({
|
|
44
|
+
|
|
45
|
+
// })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as AtomIO from "atom.io"
|
|
2
|
-
import { IMPLICIT, subscribeToState } from "atom.io/internal"
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import { IMPLICIT, getFromStore, subscribeToState } from "atom.io/internal"
|
|
3
3
|
import type { Json } from "atom.io/json"
|
|
4
4
|
|
|
5
5
|
import type { ServerConfig } from "."
|
|
@@ -12,16 +12,11 @@ export function realtimeStateProvider({
|
|
|
12
12
|
return function stateProvider<J extends Json.Serializable>(
|
|
13
13
|
token: AtomIO.WritableToken<J>,
|
|
14
14
|
): () => void {
|
|
15
|
-
let unsubscribeFromStateUpdates: (() => void) |
|
|
16
|
-
|
|
17
|
-
const fillUnsubRequest = () => {
|
|
18
|
-
socket.off(`unsub:${token.key}`, fillUnsubRequest)
|
|
19
|
-
unsubscribeFromStateUpdates?.()
|
|
20
|
-
unsubscribeFromStateUpdates = null
|
|
21
|
-
}
|
|
15
|
+
let unsubscribeFromStateUpdates: (() => void) | undefined
|
|
22
16
|
|
|
23
17
|
const fillSubRequest = () => {
|
|
24
|
-
socket.emit(`serve:${token.key}`,
|
|
18
|
+
socket.emit(`serve:${token.key}`, getFromStore(token, store))
|
|
19
|
+
|
|
25
20
|
unsubscribeFromStateUpdates = subscribeToState(
|
|
26
21
|
token,
|
|
27
22
|
({ newValue }) => {
|
|
@@ -30,6 +25,15 @@ export function realtimeStateProvider({
|
|
|
30
25
|
`expose-single:${socket.id}`,
|
|
31
26
|
store,
|
|
32
27
|
)
|
|
28
|
+
|
|
29
|
+
const fillUnsubRequest = () => {
|
|
30
|
+
socket.off(`unsub:${token.key}`, fillUnsubRequest)
|
|
31
|
+
if (unsubscribeFromStateUpdates) {
|
|
32
|
+
unsubscribeFromStateUpdates()
|
|
33
|
+
unsubscribeFromStateUpdates = undefined
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
socket.on(`unsub:${token.key}`, fillUnsubRequest)
|
|
34
38
|
}
|
|
35
39
|
|
|
@@ -37,7 +41,10 @@ export function realtimeStateProvider({
|
|
|
37
41
|
|
|
38
42
|
return () => {
|
|
39
43
|
socket.off(`sub:${token.key}`, fillSubRequest)
|
|
40
|
-
unsubscribeFromStateUpdates
|
|
44
|
+
if (unsubscribeFromStateUpdates) {
|
|
45
|
+
unsubscribeFromStateUpdates()
|
|
46
|
+
unsubscribeFromStateUpdates = undefined
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { setState } from "atom.io"
|
|
2
2
|
import type { WritableToken } from "atom.io"
|
|
3
|
-
import { IMPLICIT } from "atom.io/internal"
|
|
3
|
+
import { IMPLICIT, setIntoStore } from "atom.io/internal"
|
|
4
4
|
import type { Json } from "atom.io/json"
|
|
5
5
|
|
|
6
6
|
import type { ServerConfig } from "."
|
|
@@ -13,7 +13,7 @@ export function realtimeStateReceiver({
|
|
|
13
13
|
return function stateReceiver<J extends Json.Serializable>(
|
|
14
14
|
token: WritableToken<J>,
|
|
15
15
|
): () => void {
|
|
16
|
-
const publish = (newValue: J) =>
|
|
16
|
+
const publish = (newValue: J) => setIntoStore(token, newValue, store)
|
|
17
17
|
|
|
18
18
|
const fillPubUnclaim = () => {
|
|
19
19
|
socket.off(`pub:${token.key}`, publish)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import { IMPLICIT, getFromStore } from "atom.io/internal"
|
|
3
|
+
import type { Json } from "atom.io/json"
|
|
4
|
+
|
|
5
|
+
import type { ServerConfig } from "."
|
|
6
|
+
|
|
7
|
+
export function realtimeStateSynchronizer({
|
|
8
|
+
socket,
|
|
9
|
+
store = IMPLICIT.STORE,
|
|
10
|
+
}: ServerConfig) {
|
|
11
|
+
return function stateSynchronizer<J extends Json.Serializable>(
|
|
12
|
+
token: AtomIO.WritableToken<J>,
|
|
13
|
+
): () => void {
|
|
14
|
+
const fillGetRequest = () => {
|
|
15
|
+
socket.emit(`value:${token.key}`, getFromStore(token, store))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
socket.on(`get:${token.key}`, fillGetRequest)
|
|
19
|
+
return () => {
|
|
20
|
+
socket.off(`get:${token.key}`, fillGetRequest)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -6,6 +6,7 @@ var AtomIO = require('atom.io');
|
|
|
6
6
|
var Internal = require('atom.io/internal');
|
|
7
7
|
var AR = require('atom.io/react');
|
|
8
8
|
var RTR = require('atom.io/realtime-react');
|
|
9
|
+
var RTS = require('atom.io/realtime-server');
|
|
9
10
|
var Happy = require('happy-dom');
|
|
10
11
|
var SocketIO = require('socket.io');
|
|
11
12
|
var socket_ioClient = require('socket.io-client');
|
|
@@ -34,6 +35,7 @@ var AtomIO__namespace = /*#__PURE__*/_interopNamespace(AtomIO);
|
|
|
34
35
|
var Internal__namespace = /*#__PURE__*/_interopNamespace(Internal);
|
|
35
36
|
var AR__namespace = /*#__PURE__*/_interopNamespace(AR);
|
|
36
37
|
var RTR__namespace = /*#__PURE__*/_interopNamespace(RTR);
|
|
38
|
+
var RTS__namespace = /*#__PURE__*/_interopNamespace(RTS);
|
|
37
39
|
var Happy__namespace = /*#__PURE__*/_interopNamespace(Happy);
|
|
38
40
|
var SocketIO__namespace = /*#__PURE__*/_interopNamespace(SocketIO);
|
|
39
41
|
|
|
@@ -60,13 +62,41 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
|
60
62
|
// ../anvl/src/object/entries.ts
|
|
61
63
|
var recordToEntries = (obj) => Object.entries(obj);
|
|
62
64
|
var setupRealtimeTestServer = (options) => {
|
|
65
|
+
const silo = new AtomIO__namespace.Silo(`SERVER`, Internal__namespace.IMPLICIT.STORE);
|
|
63
66
|
const httpServer = http__namespace.createServer((_, res) => res.end(`Hello World!`));
|
|
64
67
|
const address = httpServer.listen().address();
|
|
65
68
|
const port = typeof address === `string` ? 80 : address === null ? null : address.port;
|
|
66
69
|
if (port === null)
|
|
67
70
|
throw new Error(`Could not determine port for test server`);
|
|
68
|
-
const server = new SocketIO__namespace.Server(httpServer)
|
|
69
|
-
|
|
71
|
+
const server = new SocketIO__namespace.Server(httpServer).use((socket, next) => {
|
|
72
|
+
const { token, username } = socket.handshake.auth;
|
|
73
|
+
if (token === `test` && socket.id) {
|
|
74
|
+
const socketRelatedKeysState = Internal__namespace.findInStore(
|
|
75
|
+
RTS__namespace.usersOfSockets.core.findRelatedKeysState,
|
|
76
|
+
socket.id,
|
|
77
|
+
silo.store
|
|
78
|
+
);
|
|
79
|
+
const clientRelatedKeysState = Internal__namespace.findInStore(
|
|
80
|
+
RTS__namespace.usersOfSockets.core.findRelatedKeysState,
|
|
81
|
+
username,
|
|
82
|
+
silo.store
|
|
83
|
+
);
|
|
84
|
+
Internal__namespace.setIntoStore(
|
|
85
|
+
socketRelatedKeysState,
|
|
86
|
+
(keys) => (keys.clear(), keys.add(username)),
|
|
87
|
+
silo.store
|
|
88
|
+
);
|
|
89
|
+
Internal__namespace.setIntoStore(
|
|
90
|
+
clientRelatedKeysState,
|
|
91
|
+
(keys) => (keys.clear(), keys.add(socket.id)),
|
|
92
|
+
silo.store
|
|
93
|
+
);
|
|
94
|
+
console.log(`${username} connected on ${socket.id}`);
|
|
95
|
+
next();
|
|
96
|
+
} else {
|
|
97
|
+
next(new Error(`Authentication error`));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
70
100
|
server.on(`connection`, (socket) => {
|
|
71
101
|
options.server({ socket, silo });
|
|
72
102
|
});
|
|
@@ -82,32 +112,41 @@ var setupRealtimeTestServer = (options) => {
|
|
|
82
112
|
};
|
|
83
113
|
};
|
|
84
114
|
var setupRealtimeTestClient = (options, name, port) => {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
115
|
+
const testClient = { dispose: () => {
|
|
116
|
+
} };
|
|
117
|
+
const init = () => {
|
|
118
|
+
const socket = socket_ioClient.io(`http://localhost:${port}/`, {
|
|
119
|
+
auth: { token: `test`, username: name }
|
|
120
|
+
});
|
|
121
|
+
const silo = new AtomIO__namespace.Silo(name, Internal__namespace.IMPLICIT.STORE);
|
|
122
|
+
for (const [key, value] of silo.store.valueMap.entries()) {
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
silo.store.valueMap.set(key, [...value]);
|
|
125
|
+
}
|
|
93
126
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
127
|
+
const { document } = new Happy__namespace.Window();
|
|
128
|
+
document.body.innerHTML = `<div id="app"></div>`;
|
|
129
|
+
const renderResult = react.render(
|
|
130
|
+
/* @__PURE__ */ jsxRuntime.jsx(AR__namespace.StoreProvider, { store: silo.store, children: /* @__PURE__ */ jsxRuntime.jsx(RTR__namespace.RealtimeProvider, { socket, children: /* @__PURE__ */ jsxRuntime.jsx(options.client, {}) }) }),
|
|
131
|
+
{
|
|
132
|
+
container: document.querySelector(`#app`)
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
const prettyPrint = () => console.log(react.prettyDOM(renderResult.container));
|
|
136
|
+
const dispose = () => {
|
|
137
|
+
socket.disconnect();
|
|
138
|
+
Internal__namespace.clearStore(silo.store);
|
|
139
|
+
};
|
|
140
|
+
testClient.dispose = dispose;
|
|
141
|
+
return {
|
|
142
|
+
name,
|
|
143
|
+
silo,
|
|
144
|
+
socket,
|
|
145
|
+
renderResult,
|
|
146
|
+
prettyPrint
|
|
147
|
+
};
|
|
110
148
|
};
|
|
149
|
+
return Object.assign(testClient, { init });
|
|
111
150
|
};
|
|
112
151
|
var singleClient = (options) => {
|
|
113
152
|
const server = setupRealtimeTestServer(options);
|