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.
Files changed (108) hide show
  1. package/dist/chunk-H4Q5FTPZ.js +11 -0
  2. package/dist/chunk-H4Q5FTPZ.js.map +1 -0
  3. package/dist/index.cjs +35 -60
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +8 -8
  6. package/dist/index.js +12 -36
  7. package/dist/index.js.map +1 -1
  8. package/internal/dist/index.cjs +268 -195
  9. package/internal/dist/index.cjs.map +1 -1
  10. package/internal/dist/index.d.ts +36 -11
  11. package/internal/dist/index.js +258 -195
  12. package/internal/dist/index.js.map +1 -1
  13. package/internal/src/atom/create-regular-atom.ts +2 -3
  14. package/internal/src/families/find-in-store.ts +74 -0
  15. package/internal/src/families/index.ts +1 -0
  16. package/internal/src/get-state/get-from-store.ts +14 -0
  17. package/internal/src/get-state/index.ts +2 -0
  18. package/internal/src/{read-or-compute-value.ts → get-state/read-or-compute-value.ts} +3 -3
  19. package/internal/src/index.ts +1 -1
  20. package/internal/src/ingest-updates/ingest-atom-update.ts +2 -2
  21. package/internal/src/ingest-updates/ingest-transaction-update.ts +1 -0
  22. package/internal/src/mutable/create-mutable-atom.ts +3 -4
  23. package/internal/src/mutable/tracker.ts +43 -35
  24. package/internal/src/mutable/transceiver.ts +1 -1
  25. package/internal/src/not-found-error.ts +14 -3
  26. package/internal/src/operation.ts +2 -1
  27. package/internal/src/selector/create-writable-selector.ts +2 -1
  28. package/internal/src/selector/register-selector.ts +6 -5
  29. package/internal/src/set-state/index.ts +1 -0
  30. package/internal/src/set-state/set-atom.ts +17 -3
  31. package/internal/src/set-state/set-into-store.ts +24 -0
  32. package/internal/src/set-state/stow-update.ts +2 -4
  33. package/internal/src/store/store.ts +13 -4
  34. package/internal/src/subscribe/subscribe-to-root-atoms.ts +1 -1
  35. package/internal/src/timeline/add-atom-to-timeline.ts +5 -5
  36. package/internal/src/transaction/abort-transaction.ts +2 -1
  37. package/internal/src/transaction/apply-transaction.ts +5 -3
  38. package/internal/src/transaction/build-transaction.ts +20 -11
  39. package/internal/src/transaction/create-transaction.ts +2 -3
  40. package/internal/src/transaction/index.ts +3 -2
  41. package/internal/src/transaction/is-root-store.ts +23 -0
  42. package/package.json +10 -10
  43. package/react/dist/index.cjs +27 -21
  44. package/react/dist/index.cjs.map +1 -1
  45. package/react/dist/index.d.ts +8 -2
  46. package/react/dist/index.js +28 -22
  47. package/react/dist/index.js.map +1 -1
  48. package/react/src/index.ts +4 -1
  49. package/react/src/use-i.ts +35 -0
  50. package/react/src/use-json.ts +38 -0
  51. package/react/src/use-o.ts +33 -0
  52. package/react/src/use-tl.ts +45 -0
  53. package/realtime-client/dist/index.cjs +167 -64
  54. package/realtime-client/dist/index.cjs.map +1 -1
  55. package/realtime-client/dist/index.d.ts +10 -6
  56. package/realtime-client/dist/index.js +158 -63
  57. package/realtime-client/dist/index.js.map +1 -1
  58. package/realtime-client/src/index.ts +2 -1
  59. package/realtime-client/src/pull-family-member.ts +3 -3
  60. package/realtime-client/src/pull-mutable-family-member.ts +4 -4
  61. package/realtime-client/src/pull-mutable.ts +4 -4
  62. package/realtime-client/src/pull-state.ts +7 -6
  63. package/realtime-client/src/{realtime-client-store.ts → realtime-client-stores/client-main-store.ts} +0 -8
  64. package/realtime-client/src/realtime-client-stores/client-sync-store.ts +15 -0
  65. package/realtime-client/src/realtime-client-stores/index.ts +2 -0
  66. package/realtime-client/src/sync-server-action.ts +134 -40
  67. package/realtime-client/src/sync-state.ts +19 -0
  68. package/realtime-react/dist/index.cjs +43 -26
  69. package/realtime-react/dist/index.cjs.map +1 -1
  70. package/realtime-react/dist/index.d.ts +3 -1
  71. package/realtime-react/dist/index.js +41 -25
  72. package/realtime-react/dist/index.js.map +1 -1
  73. package/realtime-react/src/index.ts +1 -0
  74. package/realtime-react/src/on-mount.ts +3 -21
  75. package/realtime-react/src/use-realtime-service.ts +1 -1
  76. package/realtime-react/src/use-single-effect.ts +29 -0
  77. package/realtime-react/src/use-sync-server-action.ts +4 -7
  78. package/realtime-react/src/use-sync.ts +17 -0
  79. package/realtime-server/dist/index.cjs +239 -56
  80. package/realtime-server/dist/index.cjs.map +1 -1
  81. package/realtime-server/dist/index.d.ts +140 -9
  82. package/realtime-server/dist/index.js +228 -58
  83. package/realtime-server/dist/index.js.map +1 -1
  84. package/realtime-server/src/index.ts +2 -0
  85. package/realtime-server/src/realtime-action-synchronizer.ts +95 -14
  86. package/realtime-server/src/realtime-family-provider.ts +11 -6
  87. package/realtime-server/src/realtime-mutable-family-provider.ts +8 -6
  88. package/realtime-server/src/realtime-mutable-provider.ts +3 -2
  89. package/realtime-server/src/realtime-server-stores/index.ts +2 -0
  90. package/realtime-server/src/realtime-server-stores/server-sync-store.ts +115 -0
  91. package/realtime-server/src/realtime-server-stores/server-user-store.ts +45 -0
  92. package/realtime-server/src/realtime-state-provider.ts +18 -11
  93. package/realtime-server/src/realtime-state-receiver.ts +2 -2
  94. package/realtime-server/src/realtime-state-synchronizer.ts +23 -0
  95. package/realtime-testing/dist/index.cjs +65 -26
  96. package/realtime-testing/dist/index.cjs.map +1 -1
  97. package/realtime-testing/dist/index.d.ts +11 -7
  98. package/realtime-testing/dist/index.js +64 -26
  99. package/realtime-testing/dist/index.js.map +1 -1
  100. package/realtime-testing/src/setup-realtime-test.tsx +83 -43
  101. package/src/find-state.ts +8 -16
  102. package/src/get-state.ts +2 -11
  103. package/src/logger.ts +1 -0
  104. package/src/set-state.ts +1 -13
  105. package/src/silo.ts +7 -3
  106. package/src/transaction.ts +3 -3
  107. package/react/src/store-hooks.ts +0 -87
  108. package/realtime-server/src/realtime-server-store.ts +0 -39
@@ -1,12 +1,21 @@
1
1
  import * as AtomIO from "atom.io"
2
- import { IMPLICIT, subscribeToTransaction } from "atom.io/internal"
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
- AtomIO.setState(
24
- AtomIO.findState(transactionRedactorAtoms, tx.key),
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
- const unsubscribe = subscribeToTransaction(
69
+ unsubscribeFromTransaction = subscribeToTransaction(
47
70
  tx,
48
71
  (update) => {
49
- unsubscribe()
50
- const updateState = AtomIO.findState(completeUpdateAtoms, update.id)
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
- ? AtomIO.getState(
54
- AtomIO.findState(redactedUpdateSelectors, [tx.key, update.id]),
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}`, unsubscribe)
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 { IMPLICIT, subscribeToState } from "atom.io/internal"
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 = AtomIO.getState(index, store)
47
+ const keys = getFromStore(index, store)
43
48
  for (const key of keys) {
44
- const token = AtomIO.findInStore(family, key, store)
49
+ const token = findInStore(family, key, store)
45
50
  socket.emit(
46
51
  `serve:${family.key}`,
47
52
  parseJson(token.family?.subKey || `null`),
48
- AtomIO.getState(token, store),
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}`, AtomIO.getState(token, store))
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 = AtomIO.getState(index, store)
53
+ const keys = getFromStore(index, store)
52
54
  for (const key of keys) {
53
- const token = family(key)
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
- AtomIO.getState(jsonToken, store),
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
- AtomIO.getState(jsonToken, store),
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}`, AtomIO.getState(jsonToken, store))
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}`, AtomIO.getState(jsonToken, store))
35
+ socket.emit(`init:${token.key}`, getFromStore(jsonToken, store))
35
36
  unsubscribeFromStateUpdates = subscribeToState(
36
37
  trackerToken,
37
38
  ({ newValue }) => {
@@ -0,0 +1,2 @@
1
+ export * from "./server-user-store"
2
+ export * from "./server-sync-store"
@@ -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) | null = null
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}`, AtomIO.getState(token, store))
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) => setState(token, newValue, store)
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
- const silo = new AtomIO__namespace.Silo(`SERVER`, Internal__namespace.IMPLICIT.STORE);
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 socket = socket_ioClient.io(`http://localhost:${port}/`);
86
- const silo = new AtomIO__namespace.Silo(name, Internal__namespace.IMPLICIT.STORE);
87
- const { document } = new Happy__namespace.Window();
88
- document.body.innerHTML = `<div id="app"></div>`;
89
- const renderResult = react.render(
90
- /* @__PURE__ */ jsxRuntime.jsx(AR__namespace.StoreProvider, { store: silo.store, children: /* @__PURE__ */ jsxRuntime.jsx(RTR__namespace.RealtimeProvider, { socket, children: /* @__PURE__ */ jsxRuntime.jsx(options.client, {}) }) }),
91
- {
92
- container: document.querySelector(`#app`)
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
- const prettyPrint = () => console.log(react.prettyDOM(renderResult.container));
96
- const disconnect = () => socket.disconnect();
97
- const reconnect = () => socket.connect();
98
- const dispose = () => {
99
- socket.disconnect();
100
- Internal__namespace.clearStore(silo.store);
101
- };
102
- return {
103
- name,
104
- silo,
105
- renderResult,
106
- prettyPrint,
107
- disconnect,
108
- reconnect,
109
- dispose
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);