atom.io 0.16.1 → 0.16.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.
Files changed (99) hide show
  1. package/data/dist/index.cjs +31 -14
  2. package/data/dist/index.cjs.map +1 -1
  3. package/data/dist/index.js +31 -14
  4. package/data/dist/index.js.map +1 -1
  5. package/data/src/join.ts +31 -14
  6. package/dist/chunk-H4Q5FTPZ.js +11 -0
  7. package/dist/chunk-H4Q5FTPZ.js.map +1 -0
  8. package/dist/index.cjs +7 -14
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.ts +8 -7
  11. package/dist/index.js +8 -14
  12. package/dist/index.js.map +1 -1
  13. package/internal/dist/index.cjs +240 -193
  14. package/internal/dist/index.cjs.map +1 -1
  15. package/internal/dist/index.d.ts +30 -9
  16. package/internal/dist/index.js +233 -194
  17. package/internal/dist/index.js.map +1 -1
  18. package/internal/src/families/find-in-store.ts +74 -0
  19. package/internal/src/families/index.ts +1 -0
  20. package/internal/src/ingest-updates/ingest-transaction-update.ts +1 -0
  21. package/internal/src/mutable/tracker.ts +37 -32
  22. package/internal/src/mutable/transceiver.ts +1 -1
  23. package/internal/src/not-found-error.ts +14 -3
  24. package/internal/src/operation.ts +2 -1
  25. package/internal/src/selector/create-writable-selector.ts +2 -1
  26. package/internal/src/selector/register-selector.ts +5 -4
  27. package/internal/src/set-state/set-atom.ts +23 -6
  28. package/internal/src/set-state/stow-update.ts +2 -4
  29. package/internal/src/store/store.ts +13 -4
  30. package/internal/src/timeline/add-atom-to-timeline.ts +5 -5
  31. package/internal/src/transaction/abort-transaction.ts +2 -1
  32. package/internal/src/transaction/apply-transaction.ts +5 -3
  33. package/internal/src/transaction/build-transaction.ts +17 -10
  34. package/internal/src/transaction/create-transaction.ts +2 -3
  35. package/internal/src/transaction/index.ts +3 -2
  36. package/internal/src/transaction/is-root-store.ts +23 -0
  37. package/package.json +10 -10
  38. package/react/dist/index.cjs +27 -21
  39. package/react/dist/index.cjs.map +1 -1
  40. package/react/dist/index.d.ts +8 -2
  41. package/react/dist/index.js +27 -21
  42. package/react/dist/index.js.map +1 -1
  43. package/react/src/index.ts +4 -1
  44. package/react/src/use-i.ts +36 -0
  45. package/react/src/use-json.ts +38 -0
  46. package/react/src/use-o.ts +34 -0
  47. package/react/src/use-tl.ts +45 -0
  48. package/realtime-client/dist/index.cjs +163 -62
  49. package/realtime-client/dist/index.cjs.map +1 -1
  50. package/realtime-client/dist/index.d.ts +10 -6
  51. package/realtime-client/dist/index.js +153 -60
  52. package/realtime-client/dist/index.js.map +1 -1
  53. package/realtime-client/src/index.ts +2 -1
  54. package/realtime-client/src/pull-state.ts +4 -3
  55. package/realtime-client/src/{realtime-client-store.ts → realtime-client-stores/client-main-store.ts} +0 -8
  56. package/realtime-client/src/realtime-client-stores/client-sync-store.ts +15 -0
  57. package/realtime-client/src/realtime-client-stores/index.ts +2 -0
  58. package/realtime-client/src/sync-server-action.ts +131 -39
  59. package/realtime-client/src/sync-state.ts +19 -0
  60. package/realtime-react/dist/index.cjs +43 -26
  61. package/realtime-react/dist/index.cjs.map +1 -1
  62. package/realtime-react/dist/index.d.ts +3 -1
  63. package/realtime-react/dist/index.js +41 -25
  64. package/realtime-react/dist/index.js.map +1 -1
  65. package/realtime-react/src/index.ts +1 -0
  66. package/realtime-react/src/on-mount.ts +3 -21
  67. package/realtime-react/src/use-realtime-service.ts +1 -1
  68. package/realtime-react/src/use-server-action.ts +1 -1
  69. package/realtime-react/src/use-single-effect.ts +29 -0
  70. package/realtime-react/src/use-sync-server-action.ts +5 -8
  71. package/realtime-react/src/use-sync.ts +17 -0
  72. package/realtime-server/dist/index.cjs +242 -48
  73. package/realtime-server/dist/index.cjs.map +1 -1
  74. package/realtime-server/dist/index.d.ts +147 -9
  75. package/realtime-server/dist/index.js +232 -51
  76. package/realtime-server/dist/index.js.map +1 -1
  77. package/realtime-server/src/index.ts +2 -0
  78. package/realtime-server/src/realtime-action-receiver.ts +4 -3
  79. package/realtime-server/src/realtime-action-synchronizer.ts +100 -13
  80. package/realtime-server/src/realtime-family-provider.ts +10 -6
  81. package/realtime-server/src/realtime-mutable-family-provider.ts +15 -18
  82. package/realtime-server/src/realtime-mutable-provider.ts +1 -0
  83. package/realtime-server/src/realtime-server-stores/index.ts +2 -0
  84. package/realtime-server/src/realtime-server-stores/server-sync-store.ts +115 -0
  85. package/realtime-server/src/realtime-server-stores/server-user-store.ts +45 -0
  86. package/realtime-server/src/realtime-state-provider.ts +16 -8
  87. package/realtime-server/src/realtime-state-receiver.ts +1 -0
  88. package/realtime-server/src/realtime-state-synchronizer.ts +23 -0
  89. package/realtime-testing/dist/index.cjs +65 -26
  90. package/realtime-testing/dist/index.cjs.map +1 -1
  91. package/realtime-testing/dist/index.d.ts +11 -7
  92. package/realtime-testing/dist/index.js +64 -26
  93. package/realtime-testing/dist/index.js.map +1 -1
  94. package/realtime-testing/src/setup-realtime-test.tsx +83 -43
  95. package/src/find-state.ts +8 -16
  96. package/src/logger.ts +16 -11
  97. package/src/transaction.ts +4 -4
  98. package/react/src/store-hooks.ts +0 -87
  99. package/realtime-server/src/realtime-server-store.ts +0 -39
@@ -1,28 +1,18 @@
1
+ import { isRootStore } from '../../dist/chunk-H4Q5FTPZ.js';
1
2
  import '../../dist/chunk-PZLG2HP3.js';
2
- import * as AtomIO from 'atom.io';
3
+ import * as AtomIO7 from 'atom.io';
3
4
  import { parseJson } from 'atom.io/json';
4
5
  import * as Internal3 from 'atom.io/internal';
5
6
  import { getJsonToken, getUpdateToken } from 'atom.io/internal';
6
7
 
7
- var myIdState__INTERNAL = AtomIO.atom({
8
- key: `myId__INTERNAL`,
9
- default: void 0
10
- });
11
- var myIdState = AtomIO.selector({
12
- key: `myId`,
13
- get: ({ get }) => get(myIdState__INTERNAL)
14
- });
15
- var updateQueueAtoms = AtomIO.atomFamily({
16
- key: `updateQueue`,
17
- default: []
18
- });
19
8
  function pullState(token, socket, store) {
20
- socket.on(`serve:${token.key}`, (data) => {
21
- AtomIO.setState(token, data, store);
22
- });
9
+ const setServedValue = (data) => {
10
+ AtomIO7.setState(token, data, store);
11
+ };
12
+ socket.on(`serve:${token.key}`, setServedValue);
23
13
  socket.emit(`sub:${token.key}`);
24
14
  return () => {
25
- socket.off(`serve:${token.key}`);
15
+ socket.off(`serve:${token.key}`, setServedValue);
26
16
  socket.emit(`unsub:${token.key}`);
27
17
  };
28
18
  }
@@ -35,7 +25,7 @@ function pullFamilyMember(token, socket, store) {
35
25
  const { key: familyKey, subKey: serializedSubKey } = token.family;
36
26
  const subKey = parseJson(serializedSubKey);
37
27
  socket == null ? void 0 : socket.on(`serve:${token.key}`, (data) => {
38
- AtomIO.setState(token, data, store);
28
+ AtomIO7.setState(token, data, store);
39
29
  });
40
30
  socket == null ? void 0 : socket.emit(`sub:${familyKey}`, subKey);
41
31
  return () => {
@@ -47,12 +37,12 @@ function pullMutableState(token, socket, store) {
47
37
  const jsonToken = getJsonToken(token);
48
38
  const updateToken = getUpdateToken(token);
49
39
  socket.on(`init:${token.key}`, (data) => {
50
- AtomIO.setState(jsonToken, data, store);
40
+ AtomIO7.setState(jsonToken, data, store);
51
41
  });
52
42
  socket.on(
53
43
  `next:${token.key}`,
54
44
  (data) => {
55
- AtomIO.setState(updateToken, data, store);
45
+ AtomIO7.setState(updateToken, data, store);
56
46
  }
57
47
  );
58
48
  socket.emit(`sub:${token.key}`);
@@ -72,13 +62,13 @@ function pullMutableFamilyMember(token, socket, store) {
72
62
  const subKey = parseJson(serializedSubKey);
73
63
  socket == null ? void 0 : socket.on(`init:${token.key}`, (data) => {
74
64
  const jsonToken = getJsonToken(token);
75
- AtomIO.setState(jsonToken, data, store);
65
+ AtomIO7.setState(jsonToken, data, store);
76
66
  });
77
67
  socket == null ? void 0 : socket.on(
78
68
  `next:${token.key}`,
79
69
  (data) => {
80
70
  const trackerToken = getUpdateToken(token);
81
- AtomIO.setState(trackerToken, data, store);
71
+ AtomIO7.setState(trackerToken, data, store);
82
72
  }
83
73
  );
84
74
  socket == null ? void 0 : socket.emit(`sub:${familyKey}`, subKey);
@@ -115,62 +105,155 @@ function serverAction(token, socket, store) {
115
105
  unsubscribeFromLocalUpdates();
116
106
  };
117
107
  }
118
- function syncAction(token, socket, updateQueue, store) {
119
- const updateQueueState = AtomIO.findState(updateQueueAtoms, token);
108
+ var myIdState__INTERNAL = AtomIO7.atom({
109
+ key: `myId__INTERNAL`,
110
+ default: void 0
111
+ });
112
+ var myIdState = AtomIO7.selector({
113
+ key: `myId`,
114
+ get: ({ get }) => get(myIdState__INTERNAL)
115
+ });
116
+ var optimisticUpdateQueueState = AtomIO7.atom({
117
+ key: `updateQueue`,
118
+ default: []
119
+ });
120
+ var confirmedUpdateQueueState = AtomIO7.atom({
121
+ key: `serverConfirmedUpdateQueue`,
122
+ default: []
123
+ });
124
+ function syncAction(token, socket, store) {
125
+ const optimisticQueue = AtomIO7.getState(optimisticUpdateQueueState, store);
126
+ const confirmedQueue = AtomIO7.getState(confirmedUpdateQueueState, store);
120
127
  const unsubscribeFromLocalUpdates = Internal3.subscribeToTransaction(
121
128
  token,
122
129
  (clientUpdate) => {
123
- AtomIO.setState(updateQueueState, (queue) => {
124
- queue.push(clientUpdate);
125
- return queue;
126
- });
127
- socket.emit(`tx-run:${token.key}`, clientUpdate);
130
+ const optimisticUpdateQueueIndex = optimisticQueue.findIndex(
131
+ (update) => update.id === clientUpdate.id
132
+ );
133
+ if (optimisticUpdateQueueIndex === -1) {
134
+ AtomIO7.setState(
135
+ optimisticUpdateQueueState,
136
+ (queue) => {
137
+ queue.push(clientUpdate);
138
+ queue.sort((a, b) => a.epoch - b.epoch);
139
+ return queue;
140
+ },
141
+ store
142
+ );
143
+ socket.emit(`tx-run:${token.key}`, clientUpdate);
144
+ } else {
145
+ AtomIO7.setState(
146
+ optimisticUpdateQueueState,
147
+ (queue) => {
148
+ queue[optimisticUpdateQueueIndex] = clientUpdate;
149
+ return queue;
150
+ },
151
+ store
152
+ );
153
+ socket.emit(`tx-run:${token.key}`, clientUpdate);
154
+ }
128
155
  },
129
- `tx-run:${token.key}:${socket.id}`,
156
+ `tx-run:${token.key}`,
130
157
  store
131
158
  );
132
- const applyIncomingUpdate = (serverUpdate) => {
133
- const clientUpdate = updateQueue[0];
134
- if (clientUpdate) {
135
- if (clientUpdate.id !== serverUpdate.id) {
136
- store.logger.error(
137
- `\u274C`,
138
- `transaction`,
139
- serverUpdate.key,
140
- `did not match position 0 in queue of updates awaiting sync:`,
141
- updateQueue
142
- );
143
- }
144
- const clientResult = JSON.stringify(clientUpdate);
145
- const serverResult = JSON.stringify(serverUpdate);
146
- if (clientResult !== serverResult) {
147
- store.logger.error(
148
- `\u274C`,
159
+ const reconcileUpdates = (optimisticUpdate, confirmedUpdate) => {
160
+ AtomIO7.setState(
161
+ optimisticUpdateQueueState,
162
+ (queue) => {
163
+ queue.shift();
164
+ return queue;
165
+ },
166
+ store
167
+ );
168
+ if (optimisticUpdate.id === confirmedUpdate.id) {
169
+ const clientResult = JSON.stringify(optimisticUpdate.updates);
170
+ const serverResult = JSON.stringify(confirmedUpdate.updates);
171
+ if (clientResult === serverResult) {
172
+ store.logger.info(
173
+ `\u2705`,
149
174
  `transaction`,
150
175
  token.key,
151
- `results do not match between client and server:`,
152
- { clientResult, serverResult }
176
+ `results for ${optimisticUpdate.id} match between client and server`
153
177
  );
154
- Internal3.ingestTransactionUpdate(`oldValue`, clientUpdate, store);
178
+ socket.emit(`tx-ack:${token.key}`, confirmedUpdate.epoch);
179
+ return;
180
+ }
181
+ } else {
182
+ store.logger.info(
183
+ `\u274C`,
184
+ `transaction`,
185
+ token.key,
186
+ `${store.config.name} thought update #${confirmedUpdate.epoch} was ${optimisticUpdate.key}:${optimisticUpdate.id}, but it was actually ${confirmedUpdate.key}:${confirmedUpdate.id}`
187
+ );
188
+ }
189
+ for (const subsequentOptimistic of optimisticQueue.toReversed()) {
190
+ Internal3.ingestTransactionUpdate(`oldValue`, subsequentOptimistic, store);
191
+ }
192
+ Internal3.ingestTransactionUpdate(`oldValue`, optimisticUpdate, store);
193
+ Internal3.ingestTransactionUpdate(`newValue`, confirmedUpdate, store);
194
+ socket.emit(`tx-ack:${token.key}`, confirmedUpdate.epoch);
195
+ for (const subsequentOptimistic of optimisticQueue) {
196
+ const token2 = Object.assign(
197
+ { type: `transaction` },
198
+ subsequentOptimistic
199
+ );
200
+ const { id, params } = subsequentOptimistic;
201
+ AtomIO7.runTransaction(token2, id, store)(...params);
202
+ }
203
+ };
204
+ const registerAndAttemptConfirmedUpdate = (confirmedUpdate) => {
205
+ const zerothOptimisticUpdate = optimisticQueue[0];
206
+ if (zerothOptimisticUpdate) {
207
+ if (zerothOptimisticUpdate.epoch === confirmedUpdate.epoch) {
208
+ reconcileUpdates(zerothOptimisticUpdate, confirmedUpdate);
209
+ for (const nextConfirmed of confirmedQueue) {
210
+ const nextOptimistic = optimisticQueue[0];
211
+ if (nextConfirmed.epoch === nextOptimistic.epoch) {
212
+ reconcileUpdates(nextOptimistic, nextConfirmed);
213
+ } else {
214
+ break;
215
+ }
216
+ }
155
217
  } else {
218
+ const hasEnqueuedOptimisticUpdate = optimisticQueue.some(
219
+ (update) => update.epoch === confirmedUpdate.epoch
220
+ );
221
+ if (hasEnqueuedOptimisticUpdate) {
222
+ AtomIO7.setState(
223
+ confirmedUpdateQueueState,
224
+ (queue) => {
225
+ queue.push(confirmedUpdate);
226
+ queue.sort((a, b) => a.epoch - b.epoch);
227
+ return queue;
228
+ },
229
+ store
230
+ );
231
+ }
232
+ }
233
+ } else {
234
+ if (isRootStore(store) && store.transactionMeta.epoch === confirmedUpdate.epoch - 1) {
235
+ Internal3.ingestTransactionUpdate(`newValue`, confirmedUpdate, store);
236
+ socket.emit(`tx-ack:${token.key}`, confirmedUpdate.epoch);
237
+ store.transactionMeta.epoch = confirmedUpdate.epoch;
238
+ } else if (isRootStore(store)) {
156
239
  store.logger.info(
157
- `\u2705`,
240
+ `\u274C`,
158
241
  `transaction`,
159
242
  token.key,
160
- `results match between client and server`
243
+ `received out-of-order update from server`,
244
+ {
245
+ clientEpoch: store.transactionMeta.epoch,
246
+ serverEpoch: confirmedUpdate.epoch
247
+ }
161
248
  );
162
249
  }
163
- AtomIO.setState(updateQueueState, (queue) => {
164
- queue.shift();
165
- return queue;
166
- });
167
250
  }
168
- Internal3.ingestTransactionUpdate(`newValue`, serverUpdate, store);
169
251
  };
170
- socket.on(`tx-new:${token.key}`, applyIncomingUpdate);
252
+ socket.off(`tx-new:${token.key}`, registerAndAttemptConfirmedUpdate);
253
+ socket.on(`tx-new:${token.key}`, registerAndAttemptConfirmedUpdate);
171
254
  socket.emit(`tx-sub:${token.key}`);
172
255
  const unsubscribeFromIncomingUpdates = () => {
173
- socket.off(`tx-new:${token.key}`, applyIncomingUpdate);
256
+ socket.off(`tx-new:${token.key}`, registerAndAttemptConfirmedUpdate);
174
257
  socket.emit(`tx-unsub:${token.key}`);
175
258
  };
176
259
  return () => {
@@ -178,7 +261,17 @@ function syncAction(token, socket, updateQueue, store) {
178
261
  unsubscribeFromIncomingUpdates();
179
262
  };
180
263
  }
264
+ function syncState(token, socket, store) {
265
+ const setServedValue = (data) => {
266
+ AtomIO7.setState(token, data, store);
267
+ };
268
+ socket.on(`value:${token.key}`, setServedValue);
269
+ socket.emit(`get:${token.key}`);
270
+ return () => {
271
+ socket.off(`value:${token.key}`, setServedValue);
272
+ };
273
+ }
181
274
 
182
- export { myIdState, myIdState__INTERNAL, pullFamilyMember, pullMutableFamilyMember, pullMutableState, pullState, pushState, serverAction, syncAction, updateQueueAtoms };
275
+ export { confirmedUpdateQueueState, myIdState, myIdState__INTERNAL, optimisticUpdateQueueState, pullFamilyMember, pullMutableFamilyMember, pullMutableState, pullState, pushState, serverAction, syncAction, syncState };
183
276
  //# sourceMappingURL=out.js.map
184
277
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/realtime-client-store.ts","../src/pull-state.ts","../src/pull-family-member.ts","../src/pull-mutable.ts","../src/pull-mutable-family-member.ts","../src/push-state.ts","../src/server-action.ts","../src/sync-server-action.ts"],"names":["AtomIO","getJsonToken","getUpdateToken","parseJson","Internal"],"mappings":"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","sourcesContent":["import * as AtomIO from \"atom.io\"\n\nexport const myIdState__INTERNAL = AtomIO.atom<string | undefined>({\n\tkey: `myId__INTERNAL`,\n\tdefault: undefined,\n})\nexport const myIdState = AtomIO.selector<string | undefined>({\n\tkey: `myId`,\n\tget: ({ get }) => get(myIdState__INTERNAL),\n})\n\nexport const updateQueueAtoms = AtomIO.atomFamily<\n\tAtomIO.TransactionUpdate<any>[],\n\tAtomIO.TransactionToken<any>\n>({\n\tkey: `updateQueue`,\n\tdefault: [],\n})\n","import * as AtomIO from \"atom.io\"\nimport type { Store } from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pullState<J extends Json.Serializable>(\n\ttoken: AtomIO.WritableToken<J>,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tsocket.on(`serve:${token.key}`, (data) => {\n\t\tAtomIO.setState(token, data, store)\n\t})\n\tsocket.emit(`sub:${token.key}`)\n\treturn () => {\n\t\tsocket.off(`serve:${token.key}`)\n\t\tsocket.emit(`unsub:${token.key}`)\n\t}\n}\n","import * as AtomIO from \"atom.io\"\nimport type { Store } from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport { parseJson } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pullFamilyMember<J extends Json.Serializable>(\n\ttoken: AtomIO.WritableToken<J>,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tif (!(`family` in token)) {\n\t\tconsole.error(`Token is not a family member:`, token)\n\t\treturn () => {}\n\t}\n\tconst { key: familyKey, subKey: serializedSubKey } = token.family\n\tconst subKey = parseJson(serializedSubKey)\n\tsocket?.on(`serve:${token.key}`, (data: J) => {\n\t\tAtomIO.setState(token, data, store)\n\t})\n\tsocket?.emit(`sub:${familyKey}`, subKey)\n\treturn () => {\n\t\tsocket?.off(`serve:${token.key}`)\n\t\tsocket?.emit(`unsub:${token.key}`)\n\t}\n}\n","import * as AtomIO from \"atom.io\"\nimport type { Store, Transceiver } from \"atom.io/internal\"\nimport { getJsonToken, getUpdateToken } from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pullMutableState<\n\tT extends Transceiver<any>,\n\tJ extends Json.Serializable,\n>(\n\ttoken: AtomIO.MutableAtomToken<T, J>,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tconst jsonToken = getJsonToken(token)\n\tconst updateToken = getUpdateToken(token)\n\tsocket.on(`init:${token.key}`, (data: J) => {\n\t\tAtomIO.setState(jsonToken, data, store)\n\t})\n\tsocket.on(\n\t\t`next:${token.key}`,\n\t\t(data: T extends Transceiver<infer Update> ? Update : never) => {\n\t\t\tAtomIO.setState(updateToken, data, store)\n\t\t},\n\t)\n\tsocket.emit(`sub:${token.key}`)\n\treturn () => {\n\t\tsocket.off(`init:${token.key}`)\n\t\tsocket.off(`next:${token.key}`)\n\t\tsocket.emit(`unsub:${token.key}`)\n\t}\n}\n","import * as AtomIO from \"atom.io\"\nimport { getJsonToken, getUpdateToken } from \"atom.io/internal\"\nimport type { Store, Transceiver } from \"atom.io/internal\"\nimport { parseJson } from \"atom.io/json\"\nimport type { Json } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pullMutableFamilyMember<\n\tT extends Transceiver<any>,\n\tJ extends Json.Serializable,\n>(\n\ttoken: AtomIO.MutableAtomToken<T, J>,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tif (!(`family` in token)) {\n\t\tconsole.error(`Token is not a family member:`, token)\n\t\treturn () => {}\n\t}\n\tconst { key: familyKey, subKey: serializedSubKey } = token.family\n\tconst subKey = parseJson(serializedSubKey)\n\tsocket?.on(`init:${token.key}`, (data: J) => {\n\t\tconst jsonToken = getJsonToken(token)\n\t\tAtomIO.setState(jsonToken, data, store)\n\t})\n\tsocket?.on(\n\t\t`next:${token.key}`,\n\t\t(data: T extends Transceiver<infer Signal> ? Signal : never) => {\n\t\t\tconst trackerToken = getUpdateToken(token)\n\t\t\tAtomIO.setState(trackerToken, data, store)\n\t\t},\n\t)\n\tsocket?.emit(`sub:${familyKey}`, subKey)\n\treturn () => {\n\t\tsocket?.off(`serve:${token.key}`)\n\t\tsocket?.emit(`unsub:${token.key}`)\n\t}\n}\n","import type * as AtomIO from \"atom.io\"\nimport * as Internal from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pushState<J extends Json.Serializable>(\n\ttoken: AtomIO.WritableToken<J>,\n\tsocket: Socket,\n\tstore: Internal.Store,\n): () => void {\n\tsocket.emit(`claim:${token.key}`)\n\tInternal.subscribeToState(\n\t\ttoken,\n\t\t({ newValue }) => {\n\t\t\tsocket.emit(`pub:${token.key}`, newValue)\n\t\t},\n\t\t`push`,\n\t\tstore,\n\t)\n\treturn () => {\n\t\tsocket.off(`pub:${token.key}`)\n\t\tsocket.emit(`unclaim:${token.key}`)\n\t}\n}\n","import type * as AtomIO from \"atom.io\"\nimport * as Internal from \"atom.io/internal\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function serverAction<ƒ extends AtomIO.ƒn>(\n\ttoken: AtomIO.TransactionToken<ƒ>,\n\tsocket: Socket,\n\tstore: Internal.Store,\n): () => void {\n\tconst unsubscribeFromLocalUpdates = Internal.subscribeToTransaction(\n\t\ttoken,\n\t\t(clientUpdate) => {\n\t\t\tsocket.emit(`tx-run:${token.key}`, clientUpdate)\n\t\t},\n\t\t`tx-run:${token.key}:${socket.id}`,\n\t\tstore,\n\t)\n\n\treturn () => {\n\t\tunsubscribeFromLocalUpdates()\n\t}\n}\n","import * as AtomIO from \"atom.io\"\nimport * as Internal from \"atom.io/internal\"\nimport type { Socket } from \"socket.io-client\"\nimport { updateQueueAtoms } from \"./realtime-client-store\"\n\nexport function syncAction<ƒ extends AtomIO.ƒn>(\n\ttoken: AtomIO.TransactionToken<ƒ>,\n\tsocket: Socket,\n\tupdateQueue: AtomIO.TransactionUpdate<any>[],\n\tstore: Internal.Store,\n): () => void {\n\tconst updateQueueState = AtomIO.findState(updateQueueAtoms, token)\n\n\tconst unsubscribeFromLocalUpdates = Internal.subscribeToTransaction(\n\t\ttoken,\n\t\t(clientUpdate) => {\n\t\t\tAtomIO.setState(updateQueueState, (queue) => {\n\t\t\t\tqueue.push(clientUpdate)\n\t\t\t\treturn queue\n\t\t\t})\n\t\t\tsocket.emit(`tx-run:${token.key}`, clientUpdate)\n\t\t},\n\t\t`tx-run:${token.key}:${socket.id}`,\n\t\tstore,\n\t)\n\n\tconst applyIncomingUpdate = (serverUpdate: AtomIO.TransactionUpdate<ƒ>) => {\n\t\tconst clientUpdate = updateQueue[0]\n\t\tif (clientUpdate) {\n\t\t\tif (clientUpdate.id !== serverUpdate.id) {\n\t\t\t\tstore.logger.error(\n\t\t\t\t\t`❌`,\n\t\t\t\t\t`transaction`,\n\t\t\t\t\tserverUpdate.key,\n\t\t\t\t\t`did not match position 0 in queue of updates awaiting sync:`,\n\t\t\t\t\tupdateQueue,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst clientResult = JSON.stringify(clientUpdate)\n\t\t\tconst serverResult = JSON.stringify(serverUpdate)\n\t\t\tif (clientResult !== serverResult) {\n\t\t\t\tstore.logger.error(\n\t\t\t\t\t`❌`,\n\t\t\t\t\t`transaction`,\n\t\t\t\t\ttoken.key,\n\t\t\t\t\t`results do not match between client and server:`,\n\t\t\t\t\t{ clientResult, serverResult },\n\t\t\t\t)\n\t\t\t\tInternal.ingestTransactionUpdate(`oldValue`, clientUpdate, store)\n\t\t\t} else {\n\t\t\t\tstore.logger.info(\n\t\t\t\t\t`✅`,\n\t\t\t\t\t`transaction`,\n\t\t\t\t\ttoken.key,\n\t\t\t\t\t`results match between client and server`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tAtomIO.setState(updateQueueState, (queue) => {\n\t\t\t\tqueue.shift()\n\t\t\t\treturn queue\n\t\t\t})\n\t\t}\n\t\tInternal.ingestTransactionUpdate(`newValue`, serverUpdate, store)\n\t}\n\tsocket.on(`tx-new:${token.key}`, applyIncomingUpdate)\n\tsocket.emit(`tx-sub:${token.key}`)\n\tconst unsubscribeFromIncomingUpdates = () => {\n\t\tsocket.off(`tx-new:${token.key}`, applyIncomingUpdate)\n\t\tsocket.emit(`tx-unsub:${token.key}`)\n\t}\n\treturn () => {\n\t\tunsubscribeFromLocalUpdates()\n\t\tunsubscribeFromIncomingUpdates()\n\t}\n}\n"]}
1
+ {"version":3,"sources":["../src/pull-state.ts","../src/pull-family-member.ts","../src/pull-mutable.ts","../src/pull-mutable-family-member.ts","../src/push-state.ts","../src/server-action.ts","../src/realtime-client-stores/client-main-store.ts","../src/realtime-client-stores/client-sync-store.ts","../src/sync-server-action.ts","../src/sync-state.ts"],"names":["AtomIO","getJsonToken","getUpdateToken","parseJson","Internal","token"],"mappings":"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","sourcesContent":["import * as AtomIO from \"atom.io\"\nimport type { Store } from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pullState<J extends Json.Serializable>(\n\ttoken: AtomIO.WritableToken<J>,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tconst setServedValue = (data: J) => {\n\t\tAtomIO.setState(token, data, store)\n\t}\n\tsocket.on(`serve:${token.key}`, setServedValue)\n\tsocket.emit(`sub:${token.key}`)\n\treturn () => {\n\t\tsocket.off(`serve:${token.key}`, setServedValue)\n\t\tsocket.emit(`unsub:${token.key}`)\n\t}\n}\n","import * as AtomIO from \"atom.io\"\nimport type { Store } from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport { parseJson } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pullFamilyMember<J extends Json.Serializable>(\n\ttoken: AtomIO.WritableToken<J>,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tif (!(`family` in token)) {\n\t\tconsole.error(`Token is not a family member:`, token)\n\t\treturn () => {}\n\t}\n\tconst { key: familyKey, subKey: serializedSubKey } = token.family\n\tconst subKey = parseJson(serializedSubKey)\n\tsocket?.on(`serve:${token.key}`, (data: J) => {\n\t\tAtomIO.setState(token, data, store)\n\t})\n\tsocket?.emit(`sub:${familyKey}`, subKey)\n\treturn () => {\n\t\tsocket?.off(`serve:${token.key}`)\n\t\tsocket?.emit(`unsub:${token.key}`)\n\t}\n}\n","import * as AtomIO from \"atom.io\"\nimport type { Store, Transceiver } from \"atom.io/internal\"\nimport { getJsonToken, getUpdateToken } from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pullMutableState<\n\tT extends Transceiver<any>,\n\tJ extends Json.Serializable,\n>(\n\ttoken: AtomIO.MutableAtomToken<T, J>,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tconst jsonToken = getJsonToken(token)\n\tconst updateToken = getUpdateToken(token)\n\tsocket.on(`init:${token.key}`, (data: J) => {\n\t\tAtomIO.setState(jsonToken, data, store)\n\t})\n\tsocket.on(\n\t\t`next:${token.key}`,\n\t\t(data: T extends Transceiver<infer Update> ? Update : never) => {\n\t\t\tAtomIO.setState(updateToken, data, store)\n\t\t},\n\t)\n\tsocket.emit(`sub:${token.key}`)\n\treturn () => {\n\t\tsocket.off(`init:${token.key}`)\n\t\tsocket.off(`next:${token.key}`)\n\t\tsocket.emit(`unsub:${token.key}`)\n\t}\n}\n","import * as AtomIO from \"atom.io\"\nimport { getJsonToken, getUpdateToken } from \"atom.io/internal\"\nimport type { Store, Transceiver } from \"atom.io/internal\"\nimport { parseJson } from \"atom.io/json\"\nimport type { Json } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pullMutableFamilyMember<\n\tT extends Transceiver<any>,\n\tJ extends Json.Serializable,\n>(\n\ttoken: AtomIO.MutableAtomToken<T, J>,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tif (!(`family` in token)) {\n\t\tconsole.error(`Token is not a family member:`, token)\n\t\treturn () => {}\n\t}\n\tconst { key: familyKey, subKey: serializedSubKey } = token.family\n\tconst subKey = parseJson(serializedSubKey)\n\tsocket?.on(`init:${token.key}`, (data: J) => {\n\t\tconst jsonToken = getJsonToken(token)\n\t\tAtomIO.setState(jsonToken, data, store)\n\t})\n\tsocket?.on(\n\t\t`next:${token.key}`,\n\t\t(data: T extends Transceiver<infer Signal> ? Signal : never) => {\n\t\t\tconst trackerToken = getUpdateToken(token)\n\t\t\tAtomIO.setState(trackerToken, data, store)\n\t\t},\n\t)\n\tsocket?.emit(`sub:${familyKey}`, subKey)\n\treturn () => {\n\t\tsocket?.off(`serve:${token.key}`)\n\t\tsocket?.emit(`unsub:${token.key}`)\n\t}\n}\n","import type * as AtomIO from \"atom.io\"\nimport * as Internal from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function pushState<J extends Json.Serializable>(\n\ttoken: AtomIO.WritableToken<J>,\n\tsocket: Socket,\n\tstore: Internal.Store,\n): () => void {\n\tsocket.emit(`claim:${token.key}`)\n\tInternal.subscribeToState(\n\t\ttoken,\n\t\t({ newValue }) => {\n\t\t\tsocket.emit(`pub:${token.key}`, newValue)\n\t\t},\n\t\t`push`,\n\t\tstore,\n\t)\n\treturn () => {\n\t\tsocket.off(`pub:${token.key}`)\n\t\tsocket.emit(`unclaim:${token.key}`)\n\t}\n}\n","import type * as AtomIO from \"atom.io\"\nimport * as Internal from \"atom.io/internal\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function serverAction<ƒ extends AtomIO.ƒn>(\n\ttoken: AtomIO.TransactionToken<ƒ>,\n\tsocket: Socket,\n\tstore: Internal.Store,\n): () => void {\n\tconst unsubscribeFromLocalUpdates = Internal.subscribeToTransaction(\n\t\ttoken,\n\t\t(clientUpdate) => {\n\t\t\tsocket.emit(`tx-run:${token.key}`, clientUpdate)\n\t\t},\n\t\t`tx-run:${token.key}:${socket.id}`,\n\t\tstore,\n\t)\n\n\treturn () => {\n\t\tunsubscribeFromLocalUpdates()\n\t}\n}\n","import * as AtomIO from \"atom.io\"\n\nexport const myIdState__INTERNAL = AtomIO.atom<string | undefined>({\n\tkey: `myId__INTERNAL`,\n\tdefault: undefined,\n})\nexport const myIdState = AtomIO.selector<string | undefined>({\n\tkey: `myId`,\n\tget: ({ get }) => get(myIdState__INTERNAL),\n})\n","import * as AtomIO from \"atom.io\"\n\nexport const optimisticUpdateQueueState = AtomIO.atom<\n\tAtomIO.TransactionUpdate<any>[]\n>({\n\tkey: `updateQueue`,\n\tdefault: [],\n})\n\nexport const confirmedUpdateQueueState = AtomIO.atom<\n\tAtomIO.TransactionUpdate<any>[]\n>({\n\tkey: `serverConfirmedUpdateQueue`,\n\tdefault: [],\n})\n","import * as AtomIO from \"atom.io\"\nimport * as Internal from \"atom.io/internal\"\nimport type { Socket } from \"socket.io-client\"\n\nimport { isRootStore } from \"../../internal/src/transaction/is-root-store\"\nimport {\n\tconfirmedUpdateQueueState,\n\toptimisticUpdateQueueState,\n} from \"./realtime-client-stores\"\n\nexport function syncAction<ƒ extends AtomIO.ƒn>(\n\ttoken: AtomIO.TransactionToken<ƒ>,\n\tsocket: Socket,\n\tstore: Internal.Store,\n): () => void {\n\tconst optimisticQueue = AtomIO.getState(optimisticUpdateQueueState, store)\n\tconst confirmedQueue = AtomIO.getState(confirmedUpdateQueueState, store)\n\n\tconst unsubscribeFromLocalUpdates = Internal.subscribeToTransaction(\n\t\ttoken,\n\t\t(clientUpdate) => {\n\t\t\tconst optimisticUpdateQueueIndex = optimisticQueue.findIndex(\n\t\t\t\t(update) => update.id === clientUpdate.id,\n\t\t\t)\n\t\t\tif (optimisticUpdateQueueIndex === -1) {\n\t\t\t\tAtomIO.setState(\n\t\t\t\t\toptimisticUpdateQueueState,\n\t\t\t\t\t(queue) => {\n\t\t\t\t\t\tqueue.push(clientUpdate)\n\t\t\t\t\t\tqueue.sort((a, b) => a.epoch - b.epoch)\n\t\t\t\t\t\treturn queue\n\t\t\t\t\t},\n\t\t\t\t\tstore,\n\t\t\t\t)\n\t\t\t\tsocket.emit(`tx-run:${token.key}`, clientUpdate)\n\t\t\t} else {\n\t\t\t\tAtomIO.setState(\n\t\t\t\t\toptimisticUpdateQueueState,\n\t\t\t\t\t(queue) => {\n\t\t\t\t\t\tqueue[optimisticUpdateQueueIndex] = clientUpdate\n\t\t\t\t\t\treturn queue\n\t\t\t\t\t},\n\t\t\t\t\tstore,\n\t\t\t\t)\n\t\t\t\tsocket.emit(`tx-run:${token.key}`, clientUpdate)\n\t\t\t}\n\t\t},\n\t\t`tx-run:${token.key}`,\n\t\tstore,\n\t)\n\tconst reconcileUpdates = (\n\t\toptimisticUpdate: AtomIO.TransactionUpdate<ƒ>,\n\t\tconfirmedUpdate: AtomIO.TransactionUpdate<ƒ>,\n\t) => {\n\t\tAtomIO.setState(\n\t\t\toptimisticUpdateQueueState,\n\t\t\t(queue) => {\n\t\t\t\tqueue.shift()\n\t\t\t\treturn queue\n\t\t\t},\n\t\t\tstore,\n\t\t)\n\t\tif (optimisticUpdate.id === confirmedUpdate.id) {\n\t\t\tconst clientResult = JSON.stringify(optimisticUpdate.updates)\n\t\t\tconst serverResult = JSON.stringify(confirmedUpdate.updates)\n\t\t\tif (clientResult === serverResult) {\n\t\t\t\tstore.logger.info(\n\t\t\t\t\t`✅`,\n\t\t\t\t\t`transaction`,\n\t\t\t\t\ttoken.key,\n\t\t\t\t\t`results for ${optimisticUpdate.id} match between client and server`,\n\t\t\t\t)\n\t\t\t\tsocket.emit(`tx-ack:${token.key}`, confirmedUpdate.epoch)\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\t// id mismatch\n\t\t\tstore.logger.info(\n\t\t\t\t`❌`,\n\t\t\t\t`transaction`,\n\t\t\t\ttoken.key,\n\t\t\t\t`${store.config.name} thought update #${confirmedUpdate.epoch} was ${optimisticUpdate.key}:${optimisticUpdate.id}, but it was actually ${confirmedUpdate.key}:${confirmedUpdate.id}`,\n\t\t\t)\n\t\t}\n\t\tfor (const subsequentOptimistic of optimisticQueue.toReversed()) {\n\t\t\tInternal.ingestTransactionUpdate(`oldValue`, subsequentOptimistic, store)\n\t\t}\n\t\tInternal.ingestTransactionUpdate(`oldValue`, optimisticUpdate, store)\n\t\tInternal.ingestTransactionUpdate(`newValue`, confirmedUpdate, store)\n\t\tsocket.emit(`tx-ack:${token.key}`, confirmedUpdate.epoch)\n\t\tfor (const subsequentOptimistic of optimisticQueue) {\n\t\t\tconst token = Object.assign(\n\t\t\t\t{ type: `transaction` } as const,\n\t\t\t\tsubsequentOptimistic,\n\t\t\t)\n\t\t\tconst { id, params } = subsequentOptimistic\n\t\t\tAtomIO.runTransaction(token, id, store)(...params)\n\t\t}\n\t}\n\n\tconst registerAndAttemptConfirmedUpdate = (\n\t\tconfirmedUpdate: AtomIO.TransactionUpdate<ƒ>,\n\t) => {\n\t\tconst zerothOptimisticUpdate = optimisticQueue[0]\n\t\tif (zerothOptimisticUpdate) {\n\t\t\tif (zerothOptimisticUpdate.epoch === confirmedUpdate.epoch) {\n\t\t\t\treconcileUpdates(zerothOptimisticUpdate, confirmedUpdate)\n\t\t\t\tfor (const nextConfirmed of confirmedQueue) {\n\t\t\t\t\tconst nextOptimistic = optimisticQueue[0]\n\t\t\t\t\tif (nextConfirmed.epoch === nextOptimistic.epoch) {\n\t\t\t\t\t\treconcileUpdates(nextOptimistic, nextConfirmed)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// epoch mismatch\n\n\t\t\t\tconst hasEnqueuedOptimisticUpdate = optimisticQueue.some(\n\t\t\t\t\t(update) => update.epoch === confirmedUpdate.epoch,\n\t\t\t\t)\n\t\t\t\tif (hasEnqueuedOptimisticUpdate) {\n\t\t\t\t\tAtomIO.setState(\n\t\t\t\t\t\tconfirmedUpdateQueueState,\n\t\t\t\t\t\t(queue) => {\n\t\t\t\t\t\t\tqueue.push(confirmedUpdate)\n\t\t\t\t\t\t\tqueue.sort((a, b) => a.epoch - b.epoch)\n\t\t\t\t\t\t\treturn queue\n\t\t\t\t\t\t},\n\t\t\t\t\t\tstore,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif (\n\t\t\t\tisRootStore(store) &&\n\t\t\t\tstore.transactionMeta.epoch === confirmedUpdate.epoch - 1\n\t\t\t) {\n\t\t\t\tInternal.ingestTransactionUpdate(`newValue`, confirmedUpdate, store)\n\t\t\t\tsocket.emit(`tx-ack:${token.key}`, confirmedUpdate.epoch)\n\t\t\t\tstore.transactionMeta.epoch = confirmedUpdate.epoch\n\t\t\t} else if (isRootStore(store)) {\n\t\t\t\tstore.logger.info(\n\t\t\t\t\t`❌`,\n\t\t\t\t\t`transaction`,\n\t\t\t\t\ttoken.key,\n\t\t\t\t\t`received out-of-order update from server`,\n\t\t\t\t\t{\n\t\t\t\t\t\tclientEpoch: store.transactionMeta.epoch,\n\t\t\t\t\t\tserverEpoch: confirmedUpdate.epoch,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\tsocket.off(`tx-new:${token.key}`, registerAndAttemptConfirmedUpdate)\n\tsocket.on(`tx-new:${token.key}`, registerAndAttemptConfirmedUpdate)\n\tsocket.emit(`tx-sub:${token.key}`)\n\tconst unsubscribeFromIncomingUpdates = () => {\n\t\tsocket.off(`tx-new:${token.key}`, registerAndAttemptConfirmedUpdate)\n\t\tsocket.emit(`tx-unsub:${token.key}`)\n\t}\n\treturn () => {\n\t\tunsubscribeFromLocalUpdates()\n\t\tunsubscribeFromIncomingUpdates()\n\t}\n}\n","import * as AtomIO from \"atom.io\"\nimport type { Store } from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function syncState<J extends Json.Serializable>(\n\ttoken: AtomIO.WritableToken<J>,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tconst setServedValue = (data: J) => {\n\t\tAtomIO.setState(token, data, store)\n\t}\n\tsocket.on(`value:${token.key}`, setServedValue)\n\tsocket.emit(`get:${token.key}`)\n\treturn () => {\n\t\tsocket.off(`value:${token.key}`, setServedValue)\n\t}\n}\n"]}
@@ -1,8 +1,9 @@
1
- export * from "./realtime-client-store"
2
1
  export * from "./pull-state"
3
2
  export * from "./pull-family-member"
4
3
  export * from "./pull-mutable"
5
4
  export * from "./pull-mutable-family-member"
6
5
  export * from "./push-state"
7
6
  export * from "./server-action"
7
+ export * from "./realtime-client-stores"
8
8
  export * from "./sync-server-action"
9
+ export * from "./sync-state"
@@ -8,12 +8,13 @@ export function pullState<J extends Json.Serializable>(
8
8
  socket: Socket,
9
9
  store: Store,
10
10
  ): () => void {
11
- socket.on(`serve:${token.key}`, (data) => {
11
+ const setServedValue = (data: J) => {
12
12
  AtomIO.setState(token, data, store)
13
- })
13
+ }
14
+ socket.on(`serve:${token.key}`, setServedValue)
14
15
  socket.emit(`sub:${token.key}`)
15
16
  return () => {
16
- socket.off(`serve:${token.key}`)
17
+ socket.off(`serve:${token.key}`, setServedValue)
17
18
  socket.emit(`unsub:${token.key}`)
18
19
  }
19
20
  }
@@ -8,11 +8,3 @@ export const myIdState = AtomIO.selector<string | undefined>({
8
8
  key: `myId`,
9
9
  get: ({ get }) => get(myIdState__INTERNAL),
10
10
  })
11
-
12
- export const updateQueueAtoms = AtomIO.atomFamily<
13
- AtomIO.TransactionUpdate<any>[],
14
- AtomIO.TransactionToken<any>
15
- >({
16
- key: `updateQueue`,
17
- default: [],
18
- })
@@ -0,0 +1,15 @@
1
+ import * as AtomIO from "atom.io"
2
+
3
+ export const optimisticUpdateQueueState = AtomIO.atom<
4
+ AtomIO.TransactionUpdate<any>[]
5
+ >({
6
+ key: `updateQueue`,
7
+ default: [],
8
+ })
9
+
10
+ export const confirmedUpdateQueueState = AtomIO.atom<
11
+ AtomIO.TransactionUpdate<any>[]
12
+ >({
13
+ key: `serverConfirmedUpdateQueue`,
14
+ default: [],
15
+ })
@@ -0,0 +1,2 @@
1
+ export * from "./client-main-store"
2
+ export * from "./client-sync-store"
@@ -1,71 +1,163 @@
1
1
  import * as AtomIO from "atom.io"
2
2
  import * as Internal from "atom.io/internal"
3
3
  import type { Socket } from "socket.io-client"
4
- import { updateQueueAtoms } from "./realtime-client-store"
4
+
5
+ import { isRootStore } from "../../internal/src/transaction/is-root-store"
6
+ import {
7
+ confirmedUpdateQueueState,
8
+ optimisticUpdateQueueState,
9
+ } from "./realtime-client-stores"
5
10
 
6
11
  export function syncAction<ƒ extends AtomIO.ƒn>(
7
12
  token: AtomIO.TransactionToken<ƒ>,
8
13
  socket: Socket,
9
- updateQueue: AtomIO.TransactionUpdate<any>[],
10
14
  store: Internal.Store,
11
15
  ): () => void {
12
- const updateQueueState = AtomIO.findState(updateQueueAtoms, token)
16
+ const optimisticQueue = AtomIO.getState(optimisticUpdateQueueState, store)
17
+ const confirmedQueue = AtomIO.getState(confirmedUpdateQueueState, store)
13
18
 
14
19
  const unsubscribeFromLocalUpdates = Internal.subscribeToTransaction(
15
20
  token,
16
21
  (clientUpdate) => {
17
- AtomIO.setState(updateQueueState, (queue) => {
18
- queue.push(clientUpdate)
19
- return queue
20
- })
21
- socket.emit(`tx-run:${token.key}`, clientUpdate)
22
+ const optimisticUpdateQueueIndex = optimisticQueue.findIndex(
23
+ (update) => update.id === clientUpdate.id,
24
+ )
25
+ if (optimisticUpdateQueueIndex === -1) {
26
+ AtomIO.setState(
27
+ optimisticUpdateQueueState,
28
+ (queue) => {
29
+ queue.push(clientUpdate)
30
+ queue.sort((a, b) => a.epoch - b.epoch)
31
+ return queue
32
+ },
33
+ store,
34
+ )
35
+ socket.emit(`tx-run:${token.key}`, clientUpdate)
36
+ } else {
37
+ AtomIO.setState(
38
+ optimisticUpdateQueueState,
39
+ (queue) => {
40
+ queue[optimisticUpdateQueueIndex] = clientUpdate
41
+ return queue
42
+ },
43
+ store,
44
+ )
45
+ socket.emit(`tx-run:${token.key}`, clientUpdate)
46
+ }
22
47
  },
23
- `tx-run:${token.key}:${socket.id}`,
48
+ `tx-run:${token.key}`,
24
49
  store,
25
50
  )
26
-
27
- const applyIncomingUpdate = (serverUpdate: AtomIO.TransactionUpdate<ƒ>) => {
28
- const clientUpdate = updateQueue[0]
29
- if (clientUpdate) {
30
- if (clientUpdate.id !== serverUpdate.id) {
31
- store.logger.error(
32
- `❌`,
33
- `transaction`,
34
- serverUpdate.key,
35
- `did not match position 0 in queue of updates awaiting sync:`,
36
- updateQueue,
37
- )
38
- }
39
- const clientResult = JSON.stringify(clientUpdate)
40
- const serverResult = JSON.stringify(serverUpdate)
41
- if (clientResult !== serverResult) {
42
- store.logger.error(
43
- `❌`,
51
+ const reconcileUpdates = (
52
+ optimisticUpdate: AtomIO.TransactionUpdate<ƒ>,
53
+ confirmedUpdate: AtomIO.TransactionUpdate<ƒ>,
54
+ ) => {
55
+ AtomIO.setState(
56
+ optimisticUpdateQueueState,
57
+ (queue) => {
58
+ queue.shift()
59
+ return queue
60
+ },
61
+ store,
62
+ )
63
+ if (optimisticUpdate.id === confirmedUpdate.id) {
64
+ const clientResult = JSON.stringify(optimisticUpdate.updates)
65
+ const serverResult = JSON.stringify(confirmedUpdate.updates)
66
+ if (clientResult === serverResult) {
67
+ store.logger.info(
68
+ `✅`,
44
69
  `transaction`,
45
70
  token.key,
46
- `results do not match between client and server:`,
47
- { clientResult, serverResult },
71
+ `results for ${optimisticUpdate.id} match between client and server`,
48
72
  )
49
- Internal.ingestTransactionUpdate(`oldValue`, clientUpdate, store)
73
+ socket.emit(`tx-ack:${token.key}`, confirmedUpdate.epoch)
74
+ return
75
+ }
76
+ } else {
77
+ // id mismatch
78
+ store.logger.info(
79
+ `❌`,
80
+ `transaction`,
81
+ token.key,
82
+ `${store.config.name} thought update #${confirmedUpdate.epoch} was ${optimisticUpdate.key}:${optimisticUpdate.id}, but it was actually ${confirmedUpdate.key}:${confirmedUpdate.id}`,
83
+ )
84
+ }
85
+ for (const subsequentOptimistic of optimisticQueue.toReversed()) {
86
+ Internal.ingestTransactionUpdate(`oldValue`, subsequentOptimistic, store)
87
+ }
88
+ Internal.ingestTransactionUpdate(`oldValue`, optimisticUpdate, store)
89
+ Internal.ingestTransactionUpdate(`newValue`, confirmedUpdate, store)
90
+ socket.emit(`tx-ack:${token.key}`, confirmedUpdate.epoch)
91
+ for (const subsequentOptimistic of optimisticQueue) {
92
+ const token = Object.assign(
93
+ { type: `transaction` } as const,
94
+ subsequentOptimistic,
95
+ )
96
+ const { id, params } = subsequentOptimistic
97
+ AtomIO.runTransaction(token, id, store)(...params)
98
+ }
99
+ }
100
+
101
+ const registerAndAttemptConfirmedUpdate = (
102
+ confirmedUpdate: AtomIO.TransactionUpdate<ƒ>,
103
+ ) => {
104
+ const zerothOptimisticUpdate = optimisticQueue[0]
105
+ if (zerothOptimisticUpdate) {
106
+ if (zerothOptimisticUpdate.epoch === confirmedUpdate.epoch) {
107
+ reconcileUpdates(zerothOptimisticUpdate, confirmedUpdate)
108
+ for (const nextConfirmed of confirmedQueue) {
109
+ const nextOptimistic = optimisticQueue[0]
110
+ if (nextConfirmed.epoch === nextOptimistic.epoch) {
111
+ reconcileUpdates(nextOptimistic, nextConfirmed)
112
+ } else {
113
+ break
114
+ }
115
+ }
50
116
  } else {
117
+ // epoch mismatch
118
+
119
+ const hasEnqueuedOptimisticUpdate = optimisticQueue.some(
120
+ (update) => update.epoch === confirmedUpdate.epoch,
121
+ )
122
+ if (hasEnqueuedOptimisticUpdate) {
123
+ AtomIO.setState(
124
+ confirmedUpdateQueueState,
125
+ (queue) => {
126
+ queue.push(confirmedUpdate)
127
+ queue.sort((a, b) => a.epoch - b.epoch)
128
+ return queue
129
+ },
130
+ store,
131
+ )
132
+ }
133
+ }
134
+ } else {
135
+ if (
136
+ isRootStore(store) &&
137
+ store.transactionMeta.epoch === confirmedUpdate.epoch - 1
138
+ ) {
139
+ Internal.ingestTransactionUpdate(`newValue`, confirmedUpdate, store)
140
+ socket.emit(`tx-ack:${token.key}`, confirmedUpdate.epoch)
141
+ store.transactionMeta.epoch = confirmedUpdate.epoch
142
+ } else if (isRootStore(store)) {
51
143
  store.logger.info(
52
- `✅`,
144
+ `❌`,
53
145
  `transaction`,
54
146
  token.key,
55
- `results match between client and server`,
147
+ `received out-of-order update from server`,
148
+ {
149
+ clientEpoch: store.transactionMeta.epoch,
150
+ serverEpoch: confirmedUpdate.epoch,
151
+ },
56
152
  )
57
153
  }
58
- AtomIO.setState(updateQueueState, (queue) => {
59
- queue.shift()
60
- return queue
61
- })
62
154
  }
63
- Internal.ingestTransactionUpdate(`newValue`, serverUpdate, store)
64
155
  }
65
- socket.on(`tx-new:${token.key}`, applyIncomingUpdate)
156
+ socket.off(`tx-new:${token.key}`, registerAndAttemptConfirmedUpdate)
157
+ socket.on(`tx-new:${token.key}`, registerAndAttemptConfirmedUpdate)
66
158
  socket.emit(`tx-sub:${token.key}`)
67
159
  const unsubscribeFromIncomingUpdates = () => {
68
- socket.off(`tx-new:${token.key}`, applyIncomingUpdate)
160
+ socket.off(`tx-new:${token.key}`, registerAndAttemptConfirmedUpdate)
69
161
  socket.emit(`tx-unsub:${token.key}`)
70
162
  }
71
163
  return () => {
@@ -0,0 +1,19 @@
1
+ import * as AtomIO from "atom.io"
2
+ import type { Store } from "atom.io/internal"
3
+ import type { Json } from "atom.io/json"
4
+ import type { Socket } from "socket.io-client"
5
+
6
+ export function syncState<J extends Json.Serializable>(
7
+ token: AtomIO.WritableToken<J>,
8
+ socket: Socket,
9
+ store: Store,
10
+ ): () => void {
11
+ const setServedValue = (data: J) => {
12
+ AtomIO.setState(token, data, store)
13
+ }
14
+ socket.on(`value:${token.key}`, setServedValue)
15
+ socket.emit(`get:${token.key}`)
16
+ return () => {
17
+ socket.off(`value:${token.key}`, setServedValue)
18
+ }
19
+ }