atom.io 0.18.0 → 0.18.2

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 (103) hide show
  1. package/dist/chunk-3J2EGSBE.js +31 -0
  2. package/dist/chunk-3J2EGSBE.js.map +1 -0
  3. package/dist/chunk-A4ZCNKWQ.js +18 -0
  4. package/dist/chunk-A4ZCNKWQ.js.map +1 -0
  5. package/dist/{chunk-OEVFAUPE.js → chunk-IZHOMSXA.js} +53 -11
  6. package/dist/chunk-IZHOMSXA.js.map +1 -0
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts +5 -1
  9. package/dist/index.js.map +1 -1
  10. package/internal/dist/index.cjs +110 -134
  11. package/internal/dist/index.cjs.map +1 -1
  12. package/internal/dist/index.d.ts +24 -24
  13. package/internal/dist/index.js +110 -134
  14. package/internal/dist/index.js.map +1 -1
  15. package/internal/src/atom/delete-atom.ts +7 -6
  16. package/internal/src/caching.ts +6 -6
  17. package/internal/src/families/create-regular-atom-family.ts +4 -4
  18. package/internal/src/get-state/get-from-store.ts +2 -5
  19. package/internal/src/ingest-updates/ingest-atom-update.ts +6 -2
  20. package/internal/src/mutable/create-mutable-atom-family.ts +4 -4
  21. package/internal/src/selector/register-selector.ts +3 -14
  22. package/internal/src/set-state/copy-mutable-if-needed.ts +5 -0
  23. package/internal/src/set-state/emit-update.ts +25 -11
  24. package/internal/src/set-state/set-atom.ts +4 -3
  25. package/internal/src/set-state/set-into-store.ts +2 -5
  26. package/internal/src/store/withdraw-new-family-member.ts +38 -28
  27. package/internal/src/store/withdraw.ts +20 -23
  28. package/internal/src/subscribe/subscribe-to-state.ts +2 -3
  29. package/internal/src/subscribe/subscribe-to-timeline.ts +0 -5
  30. package/internal/src/subscribe/subscribe-to-transaction.ts +0 -5
  31. package/internal/src/timeline/add-atom-to-timeline.ts +6 -15
  32. package/internal/src/timeline/create-timeline.ts +0 -27
  33. package/internal/src/transaction/apply-transaction.ts +0 -1
  34. package/internal/src/transaction/set-epoch-number.ts +0 -1
  35. package/json/src/index.ts +3 -3
  36. package/package.json +242 -241
  37. package/react/dist/index.cjs +2 -3
  38. package/react/dist/index.cjs.map +1 -1
  39. package/react/dist/index.js +2 -3
  40. package/react/dist/index.js.map +1 -1
  41. package/react/src/use-tl.ts +2 -2
  42. package/react-devtools/dist/index.cjs +1 -1
  43. package/react-devtools/dist/index.cjs.map +1 -1
  44. package/react-devtools/dist/index.js +1 -15
  45. package/react-devtools/dist/index.js.map +1 -1
  46. package/react-devtools/src/StateEditor.tsx +6 -6
  47. package/react-devtools/src/StateIndex.tsx +2 -2
  48. package/react-devtools/src/Updates.tsx +1 -1
  49. package/react-devtools/src/index.ts +3 -3
  50. package/realtime/dist/index.cjs +50 -2
  51. package/realtime/dist/index.cjs.map +1 -1
  52. package/realtime/dist/index.d.ts +110 -3
  53. package/realtime/dist/index.js +47 -4
  54. package/realtime/dist/index.js.map +1 -1
  55. package/realtime/src/index.ts +1 -0
  56. package/realtime/src/realtime-continuity.ts +14 -4
  57. package/realtime/src/shared-room-store.ts +48 -0
  58. package/realtime-client/dist/index.cjs +113 -200
  59. package/realtime-client/dist/index.cjs.map +1 -1
  60. package/realtime-client/dist/index.d.ts +2 -5
  61. package/realtime-client/dist/index.js +22 -190
  62. package/realtime-client/dist/index.js.map +1 -1
  63. package/realtime-client/src/index.ts +0 -2
  64. package/realtime-client/src/pull-mutable-atom-family-member.ts +5 -5
  65. package/realtime-client/src/realtime-client-stores/client-main-store.ts +10 -0
  66. package/realtime-client/src/sync-continuity.ts +56 -9
  67. package/realtime-react/dist/index.cjs +51 -26
  68. package/realtime-react/dist/index.cjs.map +1 -1
  69. package/realtime-react/dist/index.d.ts +2 -6
  70. package/realtime-react/dist/index.js +2 -17
  71. package/realtime-react/dist/index.js.map +1 -1
  72. package/realtime-react/src/index.ts +0 -2
  73. package/realtime-server/dist/index.cjs +399 -327
  74. package/realtime-server/dist/index.cjs.map +1 -1
  75. package/realtime-server/dist/index.d.ts +55 -60
  76. package/realtime-server/dist/index.js +394 -319
  77. package/realtime-server/dist/index.js.map +1 -1
  78. package/realtime-server/src/index.ts +2 -4
  79. package/realtime-server/src/ipc-sockets/child-socket.ts +135 -0
  80. package/realtime-server/src/ipc-sockets/custom-socket.ts +90 -0
  81. package/realtime-server/src/ipc-sockets/index.ts +3 -0
  82. package/realtime-server/src/ipc-sockets/parent-socket.ts +186 -0
  83. package/realtime-server/src/realtime-continuity-synchronizer.ts +225 -96
  84. package/realtime-server/src/realtime-server-stores/index.ts +2 -1
  85. package/realtime-server/src/realtime-server-stores/realtime-continuity-store.ts +50 -31
  86. package/realtime-server/src/realtime-server-stores/server-room-external-actions.ts +64 -0
  87. package/realtime-server/src/realtime-server-stores/server-room-external-store.ts +42 -0
  88. package/realtime-server/src/realtime-server-stores/server-sync-store.ts +49 -26
  89. package/realtime-testing/dist/index.cjs +51 -11
  90. package/realtime-testing/dist/index.cjs.map +1 -1
  91. package/realtime-testing/dist/index.d.ts +1 -0
  92. package/realtime-testing/dist/index.js +11 -11
  93. package/realtime-testing/dist/index.js.map +1 -1
  94. package/realtime-testing/src/setup-realtime-test.tsx +11 -11
  95. package/src/logger.ts +5 -1
  96. package/dist/chunk-OEVFAUPE.js.map +0 -1
  97. package/realtime-client/src/sync-server-action.ts +0 -168
  98. package/realtime-client/src/sync-state.ts +0 -19
  99. package/realtime-react/src/use-sync-server-action.ts +0 -17
  100. package/realtime-react/src/use-sync.ts +0 -17
  101. package/realtime-server/src/ipc-socket.ts +0 -230
  102. package/realtime-server/src/realtime-action-synchronizer.ts +0 -164
  103. package/realtime-server/src/realtime-server-stores/server-room-store.ts +0 -97
@@ -0,0 +1,64 @@
1
+ import * as AtomIO from "atom.io"
2
+ import type { Loadable } from "atom.io/data"
3
+ import type { UserInRoomMeta } from "atom.io/realtime"
4
+ import { roomIndex, usersInRooms } from "atom.io/realtime"
5
+
6
+ import type { ChildSocket } from "../ipc-sockets"
7
+ import type { RoomArguments } from "./server-room-external-store"
8
+ import { roomArgumentsAtoms, roomSelectors } from "./server-room-external-store"
9
+
10
+ export const createRoomTX = AtomIO.transaction<
11
+ (
12
+ roomId: string,
13
+ script: string,
14
+ options?: string[],
15
+ ) => Loadable<ChildSocket<any, any>>
16
+ >({
17
+ key: `createRoom`,
18
+ do: ({ get, set, find }, roomId, script, options) => {
19
+ const args: RoomArguments = options ? [script, options] : [script]
20
+ const roomArgumentsState = find(roomArgumentsAtoms, roomId)
21
+ set(roomArgumentsState, args)
22
+ set(roomIndex, (s) => s.add(roomId))
23
+ const roomState = find(roomSelectors, roomId)
24
+ const room = get(roomState)
25
+ return room
26
+ },
27
+ })
28
+ export type CreateRoomIO = AtomIO.TransactionIO<typeof createRoomTX>
29
+
30
+ export const joinRoomTX = AtomIO.transaction<
31
+ (roomId: string, userId: string, enteredAtEpoch: number) => UserInRoomMeta
32
+ >({
33
+ key: `joinRoom`,
34
+ do: (transactors, roomId, userId, enteredAtEpoch) => {
35
+ const meta = { enteredAtEpoch }
36
+ usersInRooms.transact(transactors, ({ relations }) => {
37
+ relations.set(roomId, userId, meta)
38
+ })
39
+ return meta
40
+ },
41
+ })
42
+ export type JoinRoomIO = AtomIO.TransactionIO<typeof joinRoomTX>
43
+
44
+ export const leaveRoomTX = AtomIO.transaction<
45
+ (roomId: string, userId: string) => void
46
+ >({
47
+ key: `leaveRoom`,
48
+ do: (transactors, roomId, userId) => {
49
+ usersInRooms.transact(transactors, ({ relations }) => {
50
+ relations.delete({ room: roomId, user: userId })
51
+ })
52
+ },
53
+ })
54
+ export type LeaveRoomIO = AtomIO.TransactionIO<typeof leaveRoomTX>
55
+
56
+ export const destroyRoomTX = AtomIO.transaction<(roomId: string) => void>({
57
+ key: `destroyRoom`,
58
+ do: (transactors, roomId) => {
59
+ usersInRooms.transact(transactors, ({ relations }) => {
60
+ relations.delete({ room: roomId })
61
+ })
62
+ transactors.set(roomIndex, (s) => (s.delete(roomId), s))
63
+ },
64
+ })
@@ -0,0 +1,42 @@
1
+ import type { ChildProcessWithoutNullStreams } from "child_process"
2
+ import { spawn } from "child_process"
3
+
4
+ import { atomFamily, selectorFamily } from "atom.io"
5
+ import type { Loadable } from "atom.io/data"
6
+ import { ChildSocket } from "../ipc-sockets"
7
+
8
+ export type RoomArguments =
9
+ | [script: string, options: string[]]
10
+ | [script: string]
11
+
12
+ export const roomArgumentsAtoms = atomFamily<RoomArguments, string>({
13
+ key: `roomArguments`,
14
+ default: [`echo`, [`Hello World!`]],
15
+ })
16
+
17
+ export const roomSelectors = selectorFamily<
18
+ Loadable<ChildSocket<any, any>>,
19
+ string
20
+ >({
21
+ key: `room`,
22
+ get:
23
+ (roomId) =>
24
+ async ({ get, find }) => {
25
+ const argumentsState = find(roomArgumentsAtoms, roomId)
26
+ const args = get(argumentsState)
27
+ const [script, options] = args
28
+ const child = await new Promise<ChildProcessWithoutNullStreams>(
29
+ (resolve) => {
30
+ const room = spawn(script, options, { env: process.env })
31
+ const resolver = (data: Buffer) => {
32
+ if (data.toString() === `✨`) {
33
+ room.stdout.off(`data`, resolver)
34
+ resolve(room)
35
+ }
36
+ }
37
+ room.stdout.on(`data`, resolver)
38
+ },
39
+ )
40
+ return new ChildSocket(child, roomId)
41
+ },
42
+ })
@@ -1,40 +1,63 @@
1
1
  import type { TransactionUpdate, TransactionUpdateContent } from "atom.io"
2
2
  import { atomFamily, selectorFamily } from "atom.io"
3
3
 
4
- export const completeUpdateAtoms = atomFamily<
5
- TransactionUpdate<any> | null,
6
- string
7
- >({
8
- key: `completeUpdate`,
9
- default: null,
10
- })
4
+ // export const completeUpdateAtoms = atomFamily<
5
+ // TransactionUpdate<any> | null,
6
+ // string
7
+ // >({
8
+ // key: `completeUpdate`,
9
+ // default: null,
10
+ // })
11
11
 
12
- export const transactionRedactorAtoms = atomFamily<
12
+ export function redactTransactionUpdateContent(
13
+ visibleStateKeys: string[],
14
+ updates: TransactionUpdateContent[],
15
+ ): TransactionUpdateContent[] {
16
+ return updates
17
+ .map((update): TransactionUpdateContent => {
18
+ if (`newValue` in update) {
19
+ return update
20
+ }
21
+ const redacted = redactTransactionUpdateContent(
22
+ visibleStateKeys,
23
+ update.updates,
24
+ )
25
+ return { ...update, updates: redacted }
26
+ })
27
+ .filter((update) => {
28
+ if (`newValue` in update) {
29
+ return visibleStateKeys.includes(update.key)
30
+ }
31
+ return true
32
+ })
33
+ }
34
+
35
+ export const actionOcclusionAtoms = atomFamily<
13
36
  {
14
- filter: (updates: TransactionUpdateContent[]) => TransactionUpdateContent[]
37
+ occlude: (updates: TransactionUpdateContent[]) => TransactionUpdateContent[]
15
38
  },
16
39
  string
17
40
  >({
18
41
  key: `transactionRedactor`,
19
- default: { filter: (updates) => updates },
42
+ default: { occlude: (updates) => updates },
20
43
  })
21
- export const redactedUpdateSelectors = selectorFamily<
22
- TransactionUpdate<any> | null,
23
- [transactionKey: string, updateId: string]
24
- >({
25
- key: `redactedUpdate`,
26
- get:
27
- ([transactionKey, updateId]) =>
28
- ({ get, find }) => {
29
- const update = get(find(completeUpdateAtoms, updateId))
30
- const { filter } = get(find(transactionRedactorAtoms, transactionKey))
44
+ // export const redactedUpdateSelectors = selectorFamily<
45
+ // TransactionUpdate<any> | null,
46
+ // [transactionKey: string, updateId: string]
47
+ // >({
48
+ // key: `redactedUpdate`,
49
+ // get:
50
+ // ([transactionKey, updateId]) =>
51
+ // ({ get, find }) => {
52
+ // const update = get(find(completeUpdateAtoms, updateId))
53
+ // const { filter } = get(find(transactionRedactorAtoms, transactionKey))
31
54
 
32
- if (update && filter) {
33
- return { ...update, updates: filter(update.updates) }
34
- }
35
- return null
36
- },
37
- })
55
+ // if (update && filter) {
56
+ // return { ...update, updates: filter(update.updates) }
57
+ // }
58
+ // return null
59
+ // },
60
+ // })
38
61
 
39
62
  export const userUnacknowledgedQueues = atomFamily<
40
63
  Pick<TransactionUpdate<any>, `epoch` | `id` | `key` | `output` | `updates`>[],
@@ -5,6 +5,7 @@ var react = require('@testing-library/react');
5
5
  var AtomIO = require('atom.io');
6
6
  var internal = require('atom.io/internal');
7
7
  var AR = require('atom.io/react');
8
+ var RT = require('atom.io/realtime');
8
9
  var RTR = require('atom.io/realtime-react');
9
10
  var RTS = require('atom.io/realtime-server');
10
11
  var Happy = require('happy-dom');
@@ -33,6 +34,7 @@ function _interopNamespace(e) {
33
34
  var http__namespace = /*#__PURE__*/_interopNamespace(http);
34
35
  var AtomIO__namespace = /*#__PURE__*/_interopNamespace(AtomIO);
35
36
  var AR__namespace = /*#__PURE__*/_interopNamespace(AR);
37
+ var RT__namespace = /*#__PURE__*/_interopNamespace(RT);
36
38
  var RTR__namespace = /*#__PURE__*/_interopNamespace(RTR);
37
39
  var RTS__namespace = /*#__PURE__*/_interopNamespace(RTS);
38
40
  var Happy__namespace = /*#__PURE__*/_interopNamespace(Happy);
@@ -60,13 +62,54 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
60
62
 
61
63
  // ../anvl/src/object/entries.ts
62
64
  var recordToEntries = (obj) => Object.entries(obj);
65
+
66
+ // __unstable__/web-effects/src/storage.ts
67
+ var persistAtom = (storage) => ({ stringify, parse }) => (key) => ({ setSelf, onSet }) => {
68
+ const savedValue = storage.getItem(key);
69
+ if (savedValue != null)
70
+ setSelf(parse(savedValue));
71
+ onSet(({ newValue }) => {
72
+ if (newValue == null) {
73
+ storage.removeItem(key);
74
+ return;
75
+ }
76
+ storage.setItem(key, stringify(newValue));
77
+ });
78
+ };
79
+ var lazyLocalStorageEffect = persistAtom(window.localStorage)(JSON);
80
+
81
+ // realtime-client/src/realtime-client-stores/client-main-store.ts
82
+ var myIdState__INTERNAL = AtomIO__namespace.atom({
83
+ key: `mySocketId__INTERNAL`,
84
+ default: void 0
85
+ });
86
+ AtomIO__namespace.selector({
87
+ key: `mySocketId`,
88
+ get: ({ get }) => get(myIdState__INTERNAL)
89
+ });
90
+ var usernameEffects = typeof window === `undefined` ? [] : [lazyLocalStorageEffect(`myUsername`)];
91
+ var myUsernameState = AtomIO__namespace.atom({
92
+ key: `myUsername`,
93
+ default: null,
94
+ effects: usernameEffects
95
+ });
96
+ AtomIO__namespace.atom({
97
+ key: `updateQueue`,
98
+ default: []
99
+ });
100
+ AtomIO__namespace.atom(
101
+ {
102
+ key: `serverConfirmedUpdateQueue`,
103
+ default: []
104
+ }
105
+ );
63
106
  var testNumber = 0;
64
107
  var setupRealtimeTestServer = (options) => {
65
108
  ++testNumber;
66
109
  const silo = new AtomIO__namespace.Silo(`SERVER-${testNumber}`, internal.IMPLICIT.STORE);
67
110
  const httpServer = http__namespace.createServer((_, res) => res.end(`Hello World!`));
68
- const address = httpServer.listen().address();
69
- const port = typeof address === `string` ? 80 : address === null ? null : address.port;
111
+ const address = httpServer.listen(options.port).address();
112
+ const port = typeof address === `string` ? null : address === null ? null : address.port;
70
113
  if (port === null)
71
114
  throw new Error(`Could not determine port for test server`);
72
115
  const server = new SocketIO__namespace.Server(httpServer).use((socket, next) => {
@@ -89,12 +132,12 @@ var setupRealtimeTestServer = (options) => {
89
132
  });
90
133
  const dispose = () => {
91
134
  server.close();
92
- const roomKeys = internal.getFromStore(RTS__namespace.roomIndex, silo.store);
135
+ const roomKeys = internal.getFromStore(RT__namespace.roomIndex, silo.store);
93
136
  for (const roomKey of roomKeys) {
94
137
  const roomState = internal.findInStore(RTS__namespace.roomSelectors, roomKey, silo.store);
95
138
  const room = internal.getFromStore(roomState, silo.store);
96
139
  if (room && !(room instanceof Promise)) {
97
- room.kill();
140
+ room.process.kill();
98
141
  }
99
142
  }
100
143
  silo.store.valueMap.clear();
@@ -119,6 +162,7 @@ var setupRealtimeTestClient = (options, name, port) => {
119
162
  silo.store.valueMap.set(key, [...value]);
120
163
  }
121
164
  }
165
+ silo.setState(myUsernameState, `${name}-${testNumber}`);
122
166
  const { document } = new Happy__namespace.Window();
123
167
  document.body.innerHTML = `<div id="app"></div>`;
124
168
  const renderResult = react.render(
@@ -146,17 +190,13 @@ var setupRealtimeTestClient = (options, name, port) => {
146
190
  };
147
191
  var singleClient = (options) => {
148
192
  const server = setupRealtimeTestServer(options);
149
- const client = setupRealtimeTestClient(
150
- options,
151
- `CLIENT-${testNumber}`,
152
- server.port
153
- );
193
+ const client = setupRealtimeTestClient(options, `CLIENT`, server.port);
154
194
  return {
155
195
  client,
156
196
  server,
157
197
  teardown: () => {
158
- client.dispose();
159
198
  server.dispose();
199
+ client.dispose();
160
200
  }
161
201
  };
162
202
  };
@@ -177,10 +217,10 @@ var multiClient = (options) => {
177
217
  clients,
178
218
  server,
179
219
  teardown: () => {
220
+ server.dispose();
180
221
  for (const [, client] of recordToEntries(clients)) {
181
222
  client.dispose();
182
223
  }
183
- server.dispose();
184
224
  }
185
225
  };
186
226
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/setup-realtime-test.tsx","../../../anvl/src/object/entries.ts"],"names":["usersOfSockets","clients"],"mappings":"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","sourcesContent":["import * as http from \"http\"\n\nimport { prettyDOM, render } from \"@testing-library/react\"\nimport type { RenderResult } from \"@testing-library/react\"\nimport * as AtomIO from \"atom.io\"\nimport {\n\tIMPLICIT,\n\tclearStore,\n\tfindInStore,\n\tgetFromStore,\n\tsetIntoStore,\n} from \"atom.io/internal\"\nimport * as AR from \"atom.io/react\"\nimport * as RTR from \"atom.io/realtime-react\"\nimport * as RTS from \"atom.io/realtime-server\"\nimport * as Happy from \"happy-dom\"\nimport * as React from \"react\"\nimport * as SocketIO from \"socket.io\"\nimport type { Socket as ClientSocket } from \"socket.io-client\"\nimport { io } from \"socket.io-client\"\n\nimport { recordToEntries } from \"~/packages/anvl/src/object\"\n\nlet testNumber = 0\n\nexport type TestSetupOptions = {\n\tserver: (tools: { socket: SocketIO.Socket; silo: AtomIO.Silo }) => void\n}\nexport type TestSetupOptions__SingleClient = TestSetupOptions & {\n\tclient: React.FC\n}\nexport type TestSetupOptions__MultiClient<ClientNames extends string> =\n\tTestSetupOptions & {\n\t\tclients: {\n\t\t\t[K in ClientNames]: React.FC\n\t\t}\n\t}\n\nexport type RealtimeTestTools = {\n\tname: string\n\tsilo: AtomIO.Silo\n}\nexport type RealtimeTestClient = RealtimeTestTools & {\n\trenderResult: RenderResult\n\tprettyPrint: () => void\n\tsocket: ClientSocket\n}\nexport type RealtimeTestClientBuilder = {\n\tdispose: () => void\n\tinit: () => RealtimeTestClient\n}\n\nexport type RealtimeTestServer = RealtimeTestTools & {\n\tdispose: () => void\n\tport: number\n}\n\nexport type RealtimeTestAPI = {\n\tserver: RealtimeTestServer\n\tteardown: () => void\n}\nexport type RealtimeTestAPI__SingleClient = RealtimeTestAPI & {\n\tclient: RealtimeTestClientBuilder\n}\nexport type RealtimeTestAPI__MultiClient<ClientNames extends string> =\n\tRealtimeTestAPI & {\n\t\tclients: Record<ClientNames, RealtimeTestClientBuilder>\n\t}\n\nexport const setupRealtimeTestServer = (\n\toptions: TestSetupOptions,\n): RealtimeTestServer => {\n\t++testNumber\n\tconst silo = new AtomIO.Silo(`SERVER-${testNumber}`, IMPLICIT.STORE)\n\n\tconst httpServer = http.createServer((_, res) => res.end(`Hello World!`))\n\tconst address = httpServer.listen().address()\n\tconst port =\n\t\ttypeof address === `string` ? 80 : address === null ? null : address.port\n\tif (port === null) throw new Error(`Could not determine port for test server`)\n\n\tconst server = new SocketIO.Server(httpServer).use((socket, next) => {\n\t\tconst { token, username } = socket.handshake.auth\n\t\tif (token === `test` && socket.id) {\n\t\t\tconst socketState = findInStore(RTS.socketAtoms, socket.id, silo.store)\n\t\t\tsetIntoStore(socketState, socket, silo.store)\n\t\t\tconst usersOfSockets = RTS.usersOfSockets.in(silo.store)\n\t\t\tusersOfSockets.relations.set(socket.id, username)\n\t\t\tsetIntoStore(RTS.userIndex, (index) => index.add(username), silo.store)\n\t\t\tsetIntoStore(RTS.socketIndex, (index) => index.add(socket.id), silo.store)\n\t\t\tconsole.log(`${username} connected on ${socket.id}`)\n\t\t\tnext()\n\t\t} else {\n\t\t\tnext(new Error(`Authentication error`))\n\t\t}\n\t})\n\n\tserver.on(`connection`, (socket: SocketIO.Socket) => {\n\t\toptions.server({ socket, silo })\n\t})\n\n\tconst dispose = () => {\n\t\tserver.close()\n\t\tconst roomKeys = getFromStore(RTS.roomIndex, silo.store)\n\t\tfor (const roomKey of roomKeys) {\n\t\t\tconst roomState = findInStore(RTS.roomSelectors, roomKey, silo.store)\n\t\t\tconst room = getFromStore(roomState, silo.store)\n\t\t\tif (room && !(room instanceof Promise)) {\n\t\t\t\troom.kill()\n\t\t\t}\n\t\t}\n\t\tsilo.store.valueMap.clear()\n\t}\n\n\treturn {\n\t\tname: `SERVER`,\n\t\tsilo,\n\t\tdispose,\n\t\tport,\n\t}\n}\nexport const setupRealtimeTestClient = (\n\toptions: TestSetupOptions__SingleClient,\n\tname: string,\n\tport: number,\n): RealtimeTestClientBuilder => {\n\tconst testClient = { dispose: () => {} }\n\tconst init = () => {\n\t\tconst socket: ClientSocket = io(`http://localhost:${port}/`, {\n\t\t\tauth: { token: `test`, username: `${name}-${testNumber}` },\n\t\t})\n\t\tconst silo = new AtomIO.Silo(name, IMPLICIT.STORE)\n\t\tfor (const [key, value] of silo.store.valueMap.entries()) {\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\tsilo.store.valueMap.set(key, [...value])\n\t\t\t}\n\t\t}\n\n\t\tconst { document } = new Happy.Window()\n\t\tdocument.body.innerHTML = `<div id=\"app\"></div>`\n\t\tconst renderResult = render(\n\t\t\t<AR.StoreProvider store={silo.store}>\n\t\t\t\t<RTR.RealtimeProvider socket={socket}>\n\t\t\t\t\t<options.client />\n\t\t\t\t</RTR.RealtimeProvider>\n\t\t\t</AR.StoreProvider>,\n\t\t\t{\n\t\t\t\tcontainer: document.querySelector(`#app`) as unknown as HTMLElement,\n\t\t\t},\n\t\t)\n\n\t\tconst prettyPrint = () => console.log(prettyDOM(renderResult.container))\n\n\t\tconst dispose = () => {\n\t\t\trenderResult.unmount()\n\t\t\tsocket.disconnect()\n\t\t\tclearStore(silo.store)\n\t\t}\n\t\ttestClient.dispose = dispose\n\n\t\treturn {\n\t\t\tname,\n\t\t\tsilo,\n\t\t\tsocket,\n\t\t\trenderResult,\n\t\t\tprettyPrint,\n\t\t}\n\t}\n\treturn Object.assign(testClient, { init })\n}\n\nexport const singleClient = (\n\toptions: TestSetupOptions__SingleClient,\n): RealtimeTestAPI__SingleClient => {\n\tconst server = setupRealtimeTestServer(options)\n\tconst client = setupRealtimeTestClient(\n\t\toptions,\n\t\t`CLIENT-${testNumber}`,\n\t\tserver.port,\n\t)\n\n\treturn {\n\t\tclient,\n\t\tserver,\n\t\tteardown: () => {\n\t\t\tclient.dispose()\n\t\t\tserver.dispose()\n\t\t},\n\t}\n}\n\nexport const multiClient = <ClientNames extends string>(\n\toptions: TestSetupOptions__MultiClient<ClientNames>,\n): RealtimeTestAPI__MultiClient<ClientNames> => {\n\tconst server = setupRealtimeTestServer(options)\n\tconst clients = recordToEntries(options.clients).reduce(\n\t\t(clients, [name, client]) => {\n\t\t\tclients[name] = setupRealtimeTestClient(\n\t\t\t\t{ ...options, client },\n\t\t\t\tname,\n\t\t\t\tserver.port,\n\t\t\t)\n\t\t\treturn clients\n\t\t},\n\t\t{} as Record<ClientNames, RealtimeTestClientBuilder>,\n\t)\n\n\treturn {\n\t\tclients,\n\t\tserver,\n\t\tteardown: () => {\n\t\t\tfor (const [, client] of recordToEntries(clients)) {\n\t\t\t\tclient.dispose()\n\t\t\t}\n\t\t\tserver.dispose()\n\t\t},\n\t}\n}\n","export type Entries<K extends keyof any, V> = [key: K, value: V][]\n\nexport const recordToEntries = <K extends keyof any, V>(\n\tobj: Record<K, V>,\n): Entries<K, V> => Object.entries(obj) as Entries<K, V>\n\nexport const entriesToRecord = <K extends keyof any, V>(\n\tentries: Entries<K, V>,\n): Record<K, V> => Object.fromEntries(entries) as Record<K, V>\n"]}
1
+ {"version":3,"sources":["../src/setup-realtime-test.tsx","../../../anvl/src/object/entries.ts","../../realtime-client/src/realtime-client-stores/client-main-store.ts","../../__unstable__/web-effects/src/storage.ts","../../realtime-client/src/realtime-client-stores/client-sync-store.ts"],"names":["AtomIO","usersOfSockets","clients"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,YAAY,UAAU;AAEtB,SAAS,WAAW,cAAc;AAElC,YAAYA,aAAY;AACxB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,YAAY,SAAS;AACrB,YAAY,SAAS;AACrB,YAAY,WAAW;AAEvB,YAAY,cAAc;AAE1B,SAAS,UAAU;;;AClBZ,IAAM,kBAAkB,CAC9B,QACmB,OAAO,QAAQ,GAAG;;;ACJtC,YAAY,YAAY;;;ACQjB,IAAM,cACZ,CAAI,YACJ,CAAC,EAAE,WAAW,MAAM,MACpB,CAAC,QACD,CAAC,EAAE,SAAS,MAAM,MAAM;AACvB,QAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,MAAI,cAAc;AAAM,YAAQ,MAAM,UAAU,CAAC;AAEjD,QAAM,CAAC,EAAE,SAAS,MAAM;AACvB,QAAI,YAAY,MAAM;AACrB,cAAQ,WAAW,GAAG;AACtB;AAAA,IACD;AACA,YAAQ,QAAQ,KAAK,UAAU,QAAQ,CAAC;AAAA,EACzC,CAAC;AACF;AAEM,IAAM,yBAEQ,YAAY,OAAO,YAAY,EAAE,IAAI;;;ADvBnD,IAAM,sBAA6B,YAAyB;AAAA,EAClE,KAAK;AAAA,EACL,SAAS;AACV,CAAC;AACM,IAAM,YAAmB,gBAA6B;AAAA,EAC5D,KAAK;AAAA,EACL,KAAK,CAAC,EAAE,IAAI,MAAM,IAAI,mBAAmB;AAC1C,CAAC;AAED,IAAM,kBACL,OAAO,WAAW,cAAc,CAAC,IAAI,CAAC,uBAAuB,YAAY,CAAC;AACpE,IAAM,kBAAyB,YAAoB;AAAA,EACzD,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AACV,CAAC;;;AEnBD,YAAYA,aAAY;AAEjB,IAAM,wBAA+B,aAE1C;AAAA,EACD,KAAK;AAAA,EACL,SAAS,CAAC;AACX,CAAC;AAEM,IAAM,uBAA8B;AAAA,EAC1C;AAAA,IACC,KAAK;AAAA,IACL,SAAS,CAAC;AAAA,EACX;AACD;;;AJqIK;AA1HL,IAAI,aAAa;AA+CV,IAAM,0BAA0B,CACtC,YACwB;AACxB,IAAE;AACF,QAAM,OAAO,IAAW,aAAK,UAAU,UAAU,IAAI,SAAS,KAAK;AAEnE,QAAM,aAAkB,kBAAa,CAAC,GAAG,QAAQ,IAAI,IAAI,cAAc,CAAC;AACxE,QAAM,UAAU,WAAW,OAAO,QAAQ,IAAI,EAAE,QAAQ;AACxD,QAAM,OACL,OAAO,YAAY,WAAW,OAAO,YAAY,OAAO,OAAO,QAAQ;AACxE,MAAI,SAAS;AAAM,UAAM,IAAI,MAAM,0CAA0C;AAE7E,QAAM,SAAS,IAAa,gBAAO,UAAU,EAAE,IAAI,CAAC,QAAQ,SAAS;AACpE,UAAM,EAAE,OAAO,SAAS,IAAI,OAAO,UAAU;AAC7C,QAAI,UAAU,UAAU,OAAO,IAAI;AAClC,YAAM,cAAc,YAAgB,iBAAa,OAAO,IAAI,KAAK,KAAK;AACtE,mBAAa,aAAa,QAAQ,KAAK,KAAK;AAC5C,YAAMC,kBAAqB,mBAAe,GAAG,KAAK,KAAK;AACvD,MAAAA,gBAAe,UAAU,IAAI,OAAO,IAAI,QAAQ;AAChD,mBAAiB,eAAW,CAAC,UAAU,MAAM,IAAI,QAAQ,GAAG,KAAK,KAAK;AACtE,mBAAiB,iBAAa,CAAC,UAAU,MAAM,IAAI,OAAO,EAAE,GAAG,KAAK,KAAK;AACzE,cAAQ,IAAI,GAAG,QAAQ,iBAAiB,OAAO,EAAE,EAAE;AACnD,WAAK;AAAA,IACN,OAAO;AACN,WAAK,IAAI,MAAM,sBAAsB,CAAC;AAAA,IACvC;AAAA,EACD,CAAC;AAED,SAAO,GAAG,cAAc,CAAC,WAA4B;AACpD,YAAQ,OAAO,EAAE,QAAQ,KAAK,CAAC;AAAA,EAChC,CAAC;AAED,QAAM,UAAU,MAAM;AACrB,WAAO,MAAM;AACb,UAAM,WAAW,aAAgB,cAAW,KAAK,KAAK;AACtD,eAAW,WAAW,UAAU;AAC/B,YAAM,YAAY,YAAgB,mBAAe,SAAS,KAAK,KAAK;AACpE,YAAM,OAAO,aAAa,WAAW,KAAK,KAAK;AAC/C,UAAI,QAAQ,EAAE,gBAAgB,UAAU;AACvC,aAAK,QAAQ,KAAK;AAAA,MACnB;AAAA,IACD;AACA,SAAK,MAAM,SAAS,MAAM;AAAA,EAC3B;AAEA,SAAO;AAAA,IACN,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AACO,IAAM,0BAA0B,CACtC,SACA,MACA,SAC+B;AAC/B,QAAM,aAAa,EAAE,SAAS,MAAM;AAAA,EAAC,EAAE;AACvC,QAAM,OAAO,MAAM;AAClB,UAAM,SAAuB,GAAG,oBAAoB,IAAI,KAAK;AAAA,MAC5D,MAAM,EAAE,OAAO,QAAQ,UAAU,GAAG,IAAI,IAAI,UAAU,GAAG;AAAA,IAC1D,CAAC;AACD,UAAM,OAAO,IAAW,aAAK,MAAM,SAAS,KAAK;AACjD,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,QAAQ,GAAG;AACzD,UAAI,MAAM,QAAQ,KAAK,GAAG;AACzB,aAAK,MAAM,SAAS,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC;AAAA,MACxC;AAAA,IACD;AACA,SAAK,SAAS,iBAAiB,GAAG,IAAI,IAAI,UAAU,EAAE;AAEtD,UAAM,EAAE,SAAS,IAAI,IAAU,aAAO;AACtC,aAAS,KAAK,YAAY;AAC1B,UAAM,eAAe;AAAA,MACpB,oBAAI,kBAAH,EAAiB,OAAO,KAAK,OAC7B,8BAAK,sBAAJ,EAAqB,QACrB,8BAAC,QAAQ,QAAR,EAAe,GACjB,GACD;AAAA,MACA;AAAA,QACC,WAAW,SAAS,cAAc,MAAM;AAAA,MACzC;AAAA,IACD;AAEA,UAAM,cAAc,MAAM,QAAQ,IAAI,UAAU,aAAa,SAAS,CAAC;AAEvE,UAAM,UAAU,MAAM;AACrB,mBAAa,QAAQ;AACrB,aAAO,WAAW;AAClB,iBAAW,KAAK,KAAK;AAAA,IACtB;AACA,eAAW,UAAU;AAErB,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AACA,SAAO,OAAO,OAAO,YAAY,EAAE,KAAK,CAAC;AAC1C;AAEO,IAAM,eAAe,CAC3B,YACmC;AACnC,QAAM,SAAS,wBAAwB,OAAO;AAC9C,QAAM,SAAS,wBAAwB,SAAS,UAAU,OAAO,IAAI;AAErE,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,UAAU,MAAM;AACf,aAAO,QAAQ;AACf,aAAO,QAAQ;AAAA,IAChB;AAAA,EACD;AACD;AAEO,IAAM,cAAc,CAC1B,YAC+C;AAC/C,QAAM,SAAS,wBAAwB,OAAO;AAC9C,QAAM,UAAU,gBAAgB,QAAQ,OAAO,EAAE;AAAA,IAChD,CAACC,UAAS,CAAC,MAAM,MAAM,MAAM;AAC5B,MAAAA,SAAQ,IAAI,IAAI;AAAA,QACf,iCAAK,UAAL,EAAc,OAAO;AAAA,QACrB;AAAA,QACA,OAAO;AAAA,MACR;AACA,aAAOA;AAAA,IACR;AAAA,IACA,CAAC;AAAA,EACF;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,UAAU,MAAM;AACf,aAAO,QAAQ;AACf,iBAAW,CAAC,EAAE,MAAM,KAAK,gBAAgB,OAAO,GAAG;AAClD,eAAO,QAAQ;AAAA,MAChB;AAAA,IACD;AAAA,EACD;AACD","sourcesContent":["import * as http from \"http\"\n\nimport { prettyDOM, render } from \"@testing-library/react\"\nimport type { RenderResult } from \"@testing-library/react\"\nimport * as AtomIO from \"atom.io\"\nimport {\n\tIMPLICIT,\n\tclearStore,\n\tfindInStore,\n\tgetFromStore,\n\tsetIntoStore,\n} from \"atom.io/internal\"\nimport * as AR from \"atom.io/react\"\nimport * as RT from \"atom.io/realtime\"\nimport * as RTR from \"atom.io/realtime-react\"\nimport * as RTS from \"atom.io/realtime-server\"\nimport * as Happy from \"happy-dom\"\nimport * as React from \"react\"\nimport * as SocketIO from \"socket.io\"\nimport type { Socket as ClientSocket } from \"socket.io-client\"\nimport { io } from \"socket.io-client\"\n\nimport { recordToEntries } from \"~/packages/anvl/src/object\"\nimport { myUsernameState } from \"../../realtime-client/src/realtime-client-stores\"\n\nlet testNumber = 0\n\nexport type TestSetupOptions = {\n\tport: number\n\tserver: (tools: { socket: SocketIO.Socket; silo: AtomIO.Silo }) => void\n}\nexport type TestSetupOptions__SingleClient = TestSetupOptions & {\n\tclient: React.FC\n}\nexport type TestSetupOptions__MultiClient<ClientNames extends string> =\n\tTestSetupOptions & {\n\t\tclients: {\n\t\t\t[K in ClientNames]: React.FC\n\t\t}\n\t}\n\nexport type RealtimeTestTools = {\n\tname: string\n\tsilo: AtomIO.Silo\n}\nexport type RealtimeTestClient = RealtimeTestTools & {\n\trenderResult: RenderResult\n\tprettyPrint: () => void\n\tsocket: ClientSocket\n}\nexport type RealtimeTestClientBuilder = {\n\tdispose: () => void\n\tinit: () => RealtimeTestClient\n}\n\nexport type RealtimeTestServer = RealtimeTestTools & {\n\tdispose: () => void\n\tport: number\n}\n\nexport type RealtimeTestAPI = {\n\tserver: RealtimeTestServer\n\tteardown: () => void\n}\nexport type RealtimeTestAPI__SingleClient = RealtimeTestAPI & {\n\tclient: RealtimeTestClientBuilder\n}\nexport type RealtimeTestAPI__MultiClient<ClientNames extends string> =\n\tRealtimeTestAPI & {\n\t\tclients: Record<ClientNames, RealtimeTestClientBuilder>\n\t}\n\nexport const setupRealtimeTestServer = (\n\toptions: TestSetupOptions,\n): RealtimeTestServer => {\n\t++testNumber\n\tconst silo = new AtomIO.Silo(`SERVER-${testNumber}`, IMPLICIT.STORE)\n\n\tconst httpServer = http.createServer((_, res) => res.end(`Hello World!`))\n\tconst address = httpServer.listen(options.port).address()\n\tconst port =\n\t\ttypeof address === `string` ? null : address === null ? null : address.port\n\tif (port === null) throw new Error(`Could not determine port for test server`)\n\n\tconst server = new SocketIO.Server(httpServer).use((socket, next) => {\n\t\tconst { token, username } = socket.handshake.auth\n\t\tif (token === `test` && socket.id) {\n\t\t\tconst socketState = findInStore(RTS.socketAtoms, socket.id, silo.store)\n\t\t\tsetIntoStore(socketState, socket, silo.store)\n\t\t\tconst usersOfSockets = RTS.usersOfSockets.in(silo.store)\n\t\t\tusersOfSockets.relations.set(socket.id, username)\n\t\t\tsetIntoStore(RTS.userIndex, (index) => index.add(username), silo.store)\n\t\t\tsetIntoStore(RTS.socketIndex, (index) => index.add(socket.id), silo.store)\n\t\t\tconsole.log(`${username} connected on ${socket.id}`)\n\t\t\tnext()\n\t\t} else {\n\t\t\tnext(new Error(`Authentication error`))\n\t\t}\n\t})\n\n\tserver.on(`connection`, (socket: SocketIO.Socket) => {\n\t\toptions.server({ socket, silo })\n\t})\n\n\tconst dispose = () => {\n\t\tserver.close()\n\t\tconst roomKeys = getFromStore(RT.roomIndex, silo.store)\n\t\tfor (const roomKey of roomKeys) {\n\t\t\tconst roomState = findInStore(RTS.roomSelectors, roomKey, silo.store)\n\t\t\tconst room = getFromStore(roomState, silo.store)\n\t\t\tif (room && !(room instanceof Promise)) {\n\t\t\t\troom.process.kill()\n\t\t\t}\n\t\t}\n\t\tsilo.store.valueMap.clear()\n\t}\n\n\treturn {\n\t\tname: `SERVER`,\n\t\tsilo,\n\t\tdispose,\n\t\tport,\n\t}\n}\nexport const setupRealtimeTestClient = (\n\toptions: TestSetupOptions__SingleClient,\n\tname: string,\n\tport: number,\n): RealtimeTestClientBuilder => {\n\tconst testClient = { dispose: () => {} }\n\tconst init = () => {\n\t\tconst socket: ClientSocket = io(`http://localhost:${port}/`, {\n\t\t\tauth: { token: `test`, username: `${name}-${testNumber}` },\n\t\t})\n\t\tconst silo = new AtomIO.Silo(name, IMPLICIT.STORE)\n\t\tfor (const [key, value] of silo.store.valueMap.entries()) {\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\tsilo.store.valueMap.set(key, [...value])\n\t\t\t}\n\t\t}\n\t\tsilo.setState(myUsernameState, `${name}-${testNumber}`)\n\n\t\tconst { document } = new Happy.Window()\n\t\tdocument.body.innerHTML = `<div id=\"app\"></div>`\n\t\tconst renderResult = render(\n\t\t\t<AR.StoreProvider store={silo.store}>\n\t\t\t\t<RTR.RealtimeProvider socket={socket}>\n\t\t\t\t\t<options.client />\n\t\t\t\t</RTR.RealtimeProvider>\n\t\t\t</AR.StoreProvider>,\n\t\t\t{\n\t\t\t\tcontainer: document.querySelector(`#app`) as unknown as HTMLElement,\n\t\t\t},\n\t\t)\n\n\t\tconst prettyPrint = () => console.log(prettyDOM(renderResult.container))\n\n\t\tconst dispose = () => {\n\t\t\trenderResult.unmount()\n\t\t\tsocket.disconnect()\n\t\t\tclearStore(silo.store)\n\t\t}\n\t\ttestClient.dispose = dispose\n\n\t\treturn {\n\t\t\tname,\n\t\t\tsilo,\n\t\t\tsocket,\n\t\t\trenderResult,\n\t\t\tprettyPrint,\n\t\t}\n\t}\n\treturn Object.assign(testClient, { init })\n}\n\nexport const singleClient = (\n\toptions: TestSetupOptions__SingleClient,\n): RealtimeTestAPI__SingleClient => {\n\tconst server = setupRealtimeTestServer(options)\n\tconst client = setupRealtimeTestClient(options, `CLIENT`, server.port)\n\n\treturn {\n\t\tclient,\n\t\tserver,\n\t\tteardown: () => {\n\t\t\tserver.dispose()\n\t\t\tclient.dispose()\n\t\t},\n\t}\n}\n\nexport const multiClient = <ClientNames extends string>(\n\toptions: TestSetupOptions__MultiClient<ClientNames>,\n): RealtimeTestAPI__MultiClient<ClientNames> => {\n\tconst server = setupRealtimeTestServer(options)\n\tconst clients = recordToEntries(options.clients).reduce(\n\t\t(clients, [name, client]) => {\n\t\t\tclients[name] = setupRealtimeTestClient(\n\t\t\t\t{ ...options, client },\n\t\t\t\tname,\n\t\t\t\tserver.port,\n\t\t\t)\n\t\t\treturn clients\n\t\t},\n\t\t{} as Record<ClientNames, RealtimeTestClientBuilder>,\n\t)\n\n\treturn {\n\t\tclients,\n\t\tserver,\n\t\tteardown: () => {\n\t\t\tserver.dispose()\n\t\t\tfor (const [, client] of recordToEntries(clients)) {\n\t\t\t\tclient.dispose()\n\t\t\t}\n\t\t},\n\t}\n}\n","export type Entries<K extends keyof any, V> = [key: K, value: V][]\n\nexport const recordToEntries = <K extends keyof any, V>(\n\tobj: Record<K, V>,\n): Entries<K, V> => Object.entries(obj) as Entries<K, V>\n\nexport const entriesToRecord = <K extends keyof any, V>(\n\tentries: Entries<K, V>,\n): Record<K, V> => Object.fromEntries(entries) as Record<K, V>\n","import * as AtomIO from \"atom.io\"\n\nimport { lazyLocalStorageEffect } from \"~/packages/atom.io/__unstable__/web-effects/src/storage\"\n\nexport const myIdState__INTERNAL = AtomIO.atom<string | undefined>({\n\tkey: `mySocketId__INTERNAL`,\n\tdefault: undefined,\n})\nexport const myIdState = AtomIO.selector<string | undefined>({\n\tkey: `mySocketId`,\n\tget: ({ get }) => get(myIdState__INTERNAL),\n})\n\nconst usernameEffects =\n\ttypeof window === `undefined` ? [] : [lazyLocalStorageEffect(`myUsername`)]\nexport const myUsernameState = AtomIO.atom<string | null>({\n\tkey: `myUsername`,\n\tdefault: null,\n\teffects: usernameEffects,\n})\n","import type { AtomEffect } from \"atom.io\"\nimport type { Json } from \"atom.io/json\"\n\nexport type StringInterface<T> = {\n\tstringify: (t: T) => string\n\tparse: (s: string) => T\n}\n\nexport const persistAtom =\n\t<T>(storage: Storage) =>\n\t({ stringify, parse }: StringInterface<T>) =>\n\t(key: string): AtomEffect<T> =>\n\t({ setSelf, onSet }) => {\n\t\tconst savedValue = storage.getItem(key)\n\t\tif (savedValue != null) setSelf(parse(savedValue))\n\n\t\tonSet(({ newValue }) => {\n\t\t\tif (newValue == null) {\n\t\t\t\tstorage.removeItem(key)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tstorage.setItem(key, stringify(newValue))\n\t\t})\n\t}\n\nexport const lazyLocalStorageEffect: <J extends Json.Serializable>(\n\tkey: string,\n) => AtomEffect<J> = persistAtom(window.localStorage)(JSON)\n","import * as AtomIO from \"atom.io\"\n\nexport const optimisticUpdateQueue = AtomIO.atom<\n\tAtomIO.TransactionUpdate<any>[]\n>({\n\tkey: `updateQueue`,\n\tdefault: [],\n})\n\nexport const confirmedUpdateQueue = AtomIO.atom<AtomIO.TransactionUpdate<any>[]>(\n\t{\n\t\tkey: `serverConfirmedUpdateQueue`,\n\t\tdefault: [],\n\t},\n)\n"]}
@@ -5,6 +5,7 @@ import * as SocketIO from 'socket.io';
5
5
  import { Socket } from 'socket.io-client';
6
6
 
7
7
  type TestSetupOptions = {
8
+ port: number;
8
9
  server: (tools: {
9
10
  socket: SocketIO.Socket;
10
11
  silo: AtomIO.Silo;
@@ -1,10 +1,13 @@
1
+ import { myUsernameState } from '../../dist/chunk-3J2EGSBE.js';
1
2
  import { recordToEntries } from '../../dist/chunk-NYCVSXQB.js';
3
+ import '../../dist/chunk-A4ZCNKWQ.js';
2
4
  import { __spreadProps, __spreadValues } from '../../dist/chunk-PZLG2HP3.js';
3
5
  import * as http from 'http';
4
6
  import { render, prettyDOM } from '@testing-library/react';
5
7
  import * as AtomIO from 'atom.io';
6
8
  import { IMPLICIT, findInStore, setIntoStore, getFromStore, clearStore } from 'atom.io/internal';
7
9
  import * as AR from 'atom.io/react';
10
+ import * as RT from 'atom.io/realtime';
8
11
  import * as RTR from 'atom.io/realtime-react';
9
12
  import * as RTS from 'atom.io/realtime-server';
10
13
  import * as Happy from 'happy-dom';
@@ -17,8 +20,8 @@ var setupRealtimeTestServer = (options) => {
17
20
  ++testNumber;
18
21
  const silo = new AtomIO.Silo(`SERVER-${testNumber}`, IMPLICIT.STORE);
19
22
  const httpServer = http.createServer((_, res) => res.end(`Hello World!`));
20
- const address = httpServer.listen().address();
21
- const port = typeof address === `string` ? 80 : address === null ? null : address.port;
23
+ const address = httpServer.listen(options.port).address();
24
+ const port = typeof address === `string` ? null : address === null ? null : address.port;
22
25
  if (port === null)
23
26
  throw new Error(`Could not determine port for test server`);
24
27
  const server = new SocketIO.Server(httpServer).use((socket, next) => {
@@ -41,12 +44,12 @@ var setupRealtimeTestServer = (options) => {
41
44
  });
42
45
  const dispose = () => {
43
46
  server.close();
44
- const roomKeys = getFromStore(RTS.roomIndex, silo.store);
47
+ const roomKeys = getFromStore(RT.roomIndex, silo.store);
45
48
  for (const roomKey of roomKeys) {
46
49
  const roomState = findInStore(RTS.roomSelectors, roomKey, silo.store);
47
50
  const room = getFromStore(roomState, silo.store);
48
51
  if (room && !(room instanceof Promise)) {
49
- room.kill();
52
+ room.process.kill();
50
53
  }
51
54
  }
52
55
  silo.store.valueMap.clear();
@@ -71,6 +74,7 @@ var setupRealtimeTestClient = (options, name, port) => {
71
74
  silo.store.valueMap.set(key, [...value]);
72
75
  }
73
76
  }
77
+ silo.setState(myUsernameState, `${name}-${testNumber}`);
74
78
  const { document } = new Happy.Window();
75
79
  document.body.innerHTML = `<div id="app"></div>`;
76
80
  const renderResult = render(
@@ -98,17 +102,13 @@ var setupRealtimeTestClient = (options, name, port) => {
98
102
  };
99
103
  var singleClient = (options) => {
100
104
  const server = setupRealtimeTestServer(options);
101
- const client = setupRealtimeTestClient(
102
- options,
103
- `CLIENT-${testNumber}`,
104
- server.port
105
- );
105
+ const client = setupRealtimeTestClient(options, `CLIENT`, server.port);
106
106
  return {
107
107
  client,
108
108
  server,
109
109
  teardown: () => {
110
- client.dispose();
111
110
  server.dispose();
111
+ client.dispose();
112
112
  }
113
113
  };
114
114
  };
@@ -129,10 +129,10 @@ var multiClient = (options) => {
129
129
  clients,
130
130
  server,
131
131
  teardown: () => {
132
+ server.dispose();
132
133
  for (const [, client] of recordToEntries(clients)) {
133
134
  client.dispose();
134
135
  }
135
- server.dispose();
136
136
  }
137
137
  };
138
138
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/setup-realtime-test.tsx"],"names":["usersOfSockets","clients"],"mappings":"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","sourcesContent":["import * as http from \"http\"\n\nimport { prettyDOM, render } from \"@testing-library/react\"\nimport type { RenderResult } from \"@testing-library/react\"\nimport * as AtomIO from \"atom.io\"\nimport {\n\tIMPLICIT,\n\tclearStore,\n\tfindInStore,\n\tgetFromStore,\n\tsetIntoStore,\n} from \"atom.io/internal\"\nimport * as AR from \"atom.io/react\"\nimport * as RTR from \"atom.io/realtime-react\"\nimport * as RTS from \"atom.io/realtime-server\"\nimport * as Happy from \"happy-dom\"\nimport * as React from \"react\"\nimport * as SocketIO from \"socket.io\"\nimport type { Socket as ClientSocket } from \"socket.io-client\"\nimport { io } from \"socket.io-client\"\n\nimport { recordToEntries } from \"~/packages/anvl/src/object\"\n\nlet testNumber = 0\n\nexport type TestSetupOptions = {\n\tserver: (tools: { socket: SocketIO.Socket; silo: AtomIO.Silo }) => void\n}\nexport type TestSetupOptions__SingleClient = TestSetupOptions & {\n\tclient: React.FC\n}\nexport type TestSetupOptions__MultiClient<ClientNames extends string> =\n\tTestSetupOptions & {\n\t\tclients: {\n\t\t\t[K in ClientNames]: React.FC\n\t\t}\n\t}\n\nexport type RealtimeTestTools = {\n\tname: string\n\tsilo: AtomIO.Silo\n}\nexport type RealtimeTestClient = RealtimeTestTools & {\n\trenderResult: RenderResult\n\tprettyPrint: () => void\n\tsocket: ClientSocket\n}\nexport type RealtimeTestClientBuilder = {\n\tdispose: () => void\n\tinit: () => RealtimeTestClient\n}\n\nexport type RealtimeTestServer = RealtimeTestTools & {\n\tdispose: () => void\n\tport: number\n}\n\nexport type RealtimeTestAPI = {\n\tserver: RealtimeTestServer\n\tteardown: () => void\n}\nexport type RealtimeTestAPI__SingleClient = RealtimeTestAPI & {\n\tclient: RealtimeTestClientBuilder\n}\nexport type RealtimeTestAPI__MultiClient<ClientNames extends string> =\n\tRealtimeTestAPI & {\n\t\tclients: Record<ClientNames, RealtimeTestClientBuilder>\n\t}\n\nexport const setupRealtimeTestServer = (\n\toptions: TestSetupOptions,\n): RealtimeTestServer => {\n\t++testNumber\n\tconst silo = new AtomIO.Silo(`SERVER-${testNumber}`, IMPLICIT.STORE)\n\n\tconst httpServer = http.createServer((_, res) => res.end(`Hello World!`))\n\tconst address = httpServer.listen().address()\n\tconst port =\n\t\ttypeof address === `string` ? 80 : address === null ? null : address.port\n\tif (port === null) throw new Error(`Could not determine port for test server`)\n\n\tconst server = new SocketIO.Server(httpServer).use((socket, next) => {\n\t\tconst { token, username } = socket.handshake.auth\n\t\tif (token === `test` && socket.id) {\n\t\t\tconst socketState = findInStore(RTS.socketAtoms, socket.id, silo.store)\n\t\t\tsetIntoStore(socketState, socket, silo.store)\n\t\t\tconst usersOfSockets = RTS.usersOfSockets.in(silo.store)\n\t\t\tusersOfSockets.relations.set(socket.id, username)\n\t\t\tsetIntoStore(RTS.userIndex, (index) => index.add(username), silo.store)\n\t\t\tsetIntoStore(RTS.socketIndex, (index) => index.add(socket.id), silo.store)\n\t\t\tconsole.log(`${username} connected on ${socket.id}`)\n\t\t\tnext()\n\t\t} else {\n\t\t\tnext(new Error(`Authentication error`))\n\t\t}\n\t})\n\n\tserver.on(`connection`, (socket: SocketIO.Socket) => {\n\t\toptions.server({ socket, silo })\n\t})\n\n\tconst dispose = () => {\n\t\tserver.close()\n\t\tconst roomKeys = getFromStore(RTS.roomIndex, silo.store)\n\t\tfor (const roomKey of roomKeys) {\n\t\t\tconst roomState = findInStore(RTS.roomSelectors, roomKey, silo.store)\n\t\t\tconst room = getFromStore(roomState, silo.store)\n\t\t\tif (room && !(room instanceof Promise)) {\n\t\t\t\troom.kill()\n\t\t\t}\n\t\t}\n\t\tsilo.store.valueMap.clear()\n\t}\n\n\treturn {\n\t\tname: `SERVER`,\n\t\tsilo,\n\t\tdispose,\n\t\tport,\n\t}\n}\nexport const setupRealtimeTestClient = (\n\toptions: TestSetupOptions__SingleClient,\n\tname: string,\n\tport: number,\n): RealtimeTestClientBuilder => {\n\tconst testClient = { dispose: () => {} }\n\tconst init = () => {\n\t\tconst socket: ClientSocket = io(`http://localhost:${port}/`, {\n\t\t\tauth: { token: `test`, username: `${name}-${testNumber}` },\n\t\t})\n\t\tconst silo = new AtomIO.Silo(name, IMPLICIT.STORE)\n\t\tfor (const [key, value] of silo.store.valueMap.entries()) {\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\tsilo.store.valueMap.set(key, [...value])\n\t\t\t}\n\t\t}\n\n\t\tconst { document } = new Happy.Window()\n\t\tdocument.body.innerHTML = `<div id=\"app\"></div>`\n\t\tconst renderResult = render(\n\t\t\t<AR.StoreProvider store={silo.store}>\n\t\t\t\t<RTR.RealtimeProvider socket={socket}>\n\t\t\t\t\t<options.client />\n\t\t\t\t</RTR.RealtimeProvider>\n\t\t\t</AR.StoreProvider>,\n\t\t\t{\n\t\t\t\tcontainer: document.querySelector(`#app`) as unknown as HTMLElement,\n\t\t\t},\n\t\t)\n\n\t\tconst prettyPrint = () => console.log(prettyDOM(renderResult.container))\n\n\t\tconst dispose = () => {\n\t\t\trenderResult.unmount()\n\t\t\tsocket.disconnect()\n\t\t\tclearStore(silo.store)\n\t\t}\n\t\ttestClient.dispose = dispose\n\n\t\treturn {\n\t\t\tname,\n\t\t\tsilo,\n\t\t\tsocket,\n\t\t\trenderResult,\n\t\t\tprettyPrint,\n\t\t}\n\t}\n\treturn Object.assign(testClient, { init })\n}\n\nexport const singleClient = (\n\toptions: TestSetupOptions__SingleClient,\n): RealtimeTestAPI__SingleClient => {\n\tconst server = setupRealtimeTestServer(options)\n\tconst client = setupRealtimeTestClient(\n\t\toptions,\n\t\t`CLIENT-${testNumber}`,\n\t\tserver.port,\n\t)\n\n\treturn {\n\t\tclient,\n\t\tserver,\n\t\tteardown: () => {\n\t\t\tclient.dispose()\n\t\t\tserver.dispose()\n\t\t},\n\t}\n}\n\nexport const multiClient = <ClientNames extends string>(\n\toptions: TestSetupOptions__MultiClient<ClientNames>,\n): RealtimeTestAPI__MultiClient<ClientNames> => {\n\tconst server = setupRealtimeTestServer(options)\n\tconst clients = recordToEntries(options.clients).reduce(\n\t\t(clients, [name, client]) => {\n\t\t\tclients[name] = setupRealtimeTestClient(\n\t\t\t\t{ ...options, client },\n\t\t\t\tname,\n\t\t\t\tserver.port,\n\t\t\t)\n\t\t\treturn clients\n\t\t},\n\t\t{} as Record<ClientNames, RealtimeTestClientBuilder>,\n\t)\n\n\treturn {\n\t\tclients,\n\t\tserver,\n\t\tteardown: () => {\n\t\t\tfor (const [, client] of recordToEntries(clients)) {\n\t\t\t\tclient.dispose()\n\t\t\t}\n\t\t\tserver.dispose()\n\t\t},\n\t}\n}\n"]}
1
+ {"version":3,"sources":["../src/setup-realtime-test.tsx"],"names":["usersOfSockets","clients"],"mappings":"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","sourcesContent":["import * as http from \"http\"\n\nimport { prettyDOM, render } from \"@testing-library/react\"\nimport type { RenderResult } from \"@testing-library/react\"\nimport * as AtomIO from \"atom.io\"\nimport {\n\tIMPLICIT,\n\tclearStore,\n\tfindInStore,\n\tgetFromStore,\n\tsetIntoStore,\n} from \"atom.io/internal\"\nimport * as AR from \"atom.io/react\"\nimport * as RT from \"atom.io/realtime\"\nimport * as RTR from \"atom.io/realtime-react\"\nimport * as RTS from \"atom.io/realtime-server\"\nimport * as Happy from \"happy-dom\"\nimport * as React from \"react\"\nimport * as SocketIO from \"socket.io\"\nimport type { Socket as ClientSocket } from \"socket.io-client\"\nimport { io } from \"socket.io-client\"\n\nimport { recordToEntries } from \"~/packages/anvl/src/object\"\nimport { myUsernameState } from \"../../realtime-client/src/realtime-client-stores\"\n\nlet testNumber = 0\n\nexport type TestSetupOptions = {\n\tport: number\n\tserver: (tools: { socket: SocketIO.Socket; silo: AtomIO.Silo }) => void\n}\nexport type TestSetupOptions__SingleClient = TestSetupOptions & {\n\tclient: React.FC\n}\nexport type TestSetupOptions__MultiClient<ClientNames extends string> =\n\tTestSetupOptions & {\n\t\tclients: {\n\t\t\t[K in ClientNames]: React.FC\n\t\t}\n\t}\n\nexport type RealtimeTestTools = {\n\tname: string\n\tsilo: AtomIO.Silo\n}\nexport type RealtimeTestClient = RealtimeTestTools & {\n\trenderResult: RenderResult\n\tprettyPrint: () => void\n\tsocket: ClientSocket\n}\nexport type RealtimeTestClientBuilder = {\n\tdispose: () => void\n\tinit: () => RealtimeTestClient\n}\n\nexport type RealtimeTestServer = RealtimeTestTools & {\n\tdispose: () => void\n\tport: number\n}\n\nexport type RealtimeTestAPI = {\n\tserver: RealtimeTestServer\n\tteardown: () => void\n}\nexport type RealtimeTestAPI__SingleClient = RealtimeTestAPI & {\n\tclient: RealtimeTestClientBuilder\n}\nexport type RealtimeTestAPI__MultiClient<ClientNames extends string> =\n\tRealtimeTestAPI & {\n\t\tclients: Record<ClientNames, RealtimeTestClientBuilder>\n\t}\n\nexport const setupRealtimeTestServer = (\n\toptions: TestSetupOptions,\n): RealtimeTestServer => {\n\t++testNumber\n\tconst silo = new AtomIO.Silo(`SERVER-${testNumber}`, IMPLICIT.STORE)\n\n\tconst httpServer = http.createServer((_, res) => res.end(`Hello World!`))\n\tconst address = httpServer.listen(options.port).address()\n\tconst port =\n\t\ttypeof address === `string` ? null : address === null ? null : address.port\n\tif (port === null) throw new Error(`Could not determine port for test server`)\n\n\tconst server = new SocketIO.Server(httpServer).use((socket, next) => {\n\t\tconst { token, username } = socket.handshake.auth\n\t\tif (token === `test` && socket.id) {\n\t\t\tconst socketState = findInStore(RTS.socketAtoms, socket.id, silo.store)\n\t\t\tsetIntoStore(socketState, socket, silo.store)\n\t\t\tconst usersOfSockets = RTS.usersOfSockets.in(silo.store)\n\t\t\tusersOfSockets.relations.set(socket.id, username)\n\t\t\tsetIntoStore(RTS.userIndex, (index) => index.add(username), silo.store)\n\t\t\tsetIntoStore(RTS.socketIndex, (index) => index.add(socket.id), silo.store)\n\t\t\tconsole.log(`${username} connected on ${socket.id}`)\n\t\t\tnext()\n\t\t} else {\n\t\t\tnext(new Error(`Authentication error`))\n\t\t}\n\t})\n\n\tserver.on(`connection`, (socket: SocketIO.Socket) => {\n\t\toptions.server({ socket, silo })\n\t})\n\n\tconst dispose = () => {\n\t\tserver.close()\n\t\tconst roomKeys = getFromStore(RT.roomIndex, silo.store)\n\t\tfor (const roomKey of roomKeys) {\n\t\t\tconst roomState = findInStore(RTS.roomSelectors, roomKey, silo.store)\n\t\t\tconst room = getFromStore(roomState, silo.store)\n\t\t\tif (room && !(room instanceof Promise)) {\n\t\t\t\troom.process.kill()\n\t\t\t}\n\t\t}\n\t\tsilo.store.valueMap.clear()\n\t}\n\n\treturn {\n\t\tname: `SERVER`,\n\t\tsilo,\n\t\tdispose,\n\t\tport,\n\t}\n}\nexport const setupRealtimeTestClient = (\n\toptions: TestSetupOptions__SingleClient,\n\tname: string,\n\tport: number,\n): RealtimeTestClientBuilder => {\n\tconst testClient = { dispose: () => {} }\n\tconst init = () => {\n\t\tconst socket: ClientSocket = io(`http://localhost:${port}/`, {\n\t\t\tauth: { token: `test`, username: `${name}-${testNumber}` },\n\t\t})\n\t\tconst silo = new AtomIO.Silo(name, IMPLICIT.STORE)\n\t\tfor (const [key, value] of silo.store.valueMap.entries()) {\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\tsilo.store.valueMap.set(key, [...value])\n\t\t\t}\n\t\t}\n\t\tsilo.setState(myUsernameState, `${name}-${testNumber}`)\n\n\t\tconst { document } = new Happy.Window()\n\t\tdocument.body.innerHTML = `<div id=\"app\"></div>`\n\t\tconst renderResult = render(\n\t\t\t<AR.StoreProvider store={silo.store}>\n\t\t\t\t<RTR.RealtimeProvider socket={socket}>\n\t\t\t\t\t<options.client />\n\t\t\t\t</RTR.RealtimeProvider>\n\t\t\t</AR.StoreProvider>,\n\t\t\t{\n\t\t\t\tcontainer: document.querySelector(`#app`) as unknown as HTMLElement,\n\t\t\t},\n\t\t)\n\n\t\tconst prettyPrint = () => console.log(prettyDOM(renderResult.container))\n\n\t\tconst dispose = () => {\n\t\t\trenderResult.unmount()\n\t\t\tsocket.disconnect()\n\t\t\tclearStore(silo.store)\n\t\t}\n\t\ttestClient.dispose = dispose\n\n\t\treturn {\n\t\t\tname,\n\t\t\tsilo,\n\t\t\tsocket,\n\t\t\trenderResult,\n\t\t\tprettyPrint,\n\t\t}\n\t}\n\treturn Object.assign(testClient, { init })\n}\n\nexport const singleClient = (\n\toptions: TestSetupOptions__SingleClient,\n): RealtimeTestAPI__SingleClient => {\n\tconst server = setupRealtimeTestServer(options)\n\tconst client = setupRealtimeTestClient(options, `CLIENT`, server.port)\n\n\treturn {\n\t\tclient,\n\t\tserver,\n\t\tteardown: () => {\n\t\t\tserver.dispose()\n\t\t\tclient.dispose()\n\t\t},\n\t}\n}\n\nexport const multiClient = <ClientNames extends string>(\n\toptions: TestSetupOptions__MultiClient<ClientNames>,\n): RealtimeTestAPI__MultiClient<ClientNames> => {\n\tconst server = setupRealtimeTestServer(options)\n\tconst clients = recordToEntries(options.clients).reduce(\n\t\t(clients, [name, client]) => {\n\t\t\tclients[name] = setupRealtimeTestClient(\n\t\t\t\t{ ...options, client },\n\t\t\t\tname,\n\t\t\t\tserver.port,\n\t\t\t)\n\t\t\treturn clients\n\t\t},\n\t\t{} as Record<ClientNames, RealtimeTestClientBuilder>,\n\t)\n\n\treturn {\n\t\tclients,\n\t\tserver,\n\t\tteardown: () => {\n\t\t\tserver.dispose()\n\t\t\tfor (const [, client] of recordToEntries(clients)) {\n\t\t\t\tclient.dispose()\n\t\t\t}\n\t\t},\n\t}\n}\n"]}
@@ -11,6 +11,7 @@ import {
11
11
  setIntoStore,
12
12
  } from "atom.io/internal"
13
13
  import * as AR from "atom.io/react"
14
+ import * as RT from "atom.io/realtime"
14
15
  import * as RTR from "atom.io/realtime-react"
15
16
  import * as RTS from "atom.io/realtime-server"
16
17
  import * as Happy from "happy-dom"
@@ -20,10 +21,12 @@ import type { Socket as ClientSocket } from "socket.io-client"
20
21
  import { io } from "socket.io-client"
21
22
 
22
23
  import { recordToEntries } from "~/packages/anvl/src/object"
24
+ import { myUsernameState } from "../../realtime-client/src/realtime-client-stores"
23
25
 
24
26
  let testNumber = 0
25
27
 
26
28
  export type TestSetupOptions = {
29
+ port: number
27
30
  server: (tools: { socket: SocketIO.Socket; silo: AtomIO.Silo }) => void
28
31
  }
29
32
  export type TestSetupOptions__SingleClient = TestSetupOptions & {
@@ -74,9 +77,9 @@ export const setupRealtimeTestServer = (
74
77
  const silo = new AtomIO.Silo(`SERVER-${testNumber}`, IMPLICIT.STORE)
75
78
 
76
79
  const httpServer = http.createServer((_, res) => res.end(`Hello World!`))
77
- const address = httpServer.listen().address()
80
+ const address = httpServer.listen(options.port).address()
78
81
  const port =
79
- typeof address === `string` ? 80 : address === null ? null : address.port
82
+ typeof address === `string` ? null : address === null ? null : address.port
80
83
  if (port === null) throw new Error(`Could not determine port for test server`)
81
84
 
82
85
  const server = new SocketIO.Server(httpServer).use((socket, next) => {
@@ -101,12 +104,12 @@ export const setupRealtimeTestServer = (
101
104
 
102
105
  const dispose = () => {
103
106
  server.close()
104
- const roomKeys = getFromStore(RTS.roomIndex, silo.store)
107
+ const roomKeys = getFromStore(RT.roomIndex, silo.store)
105
108
  for (const roomKey of roomKeys) {
106
109
  const roomState = findInStore(RTS.roomSelectors, roomKey, silo.store)
107
110
  const room = getFromStore(roomState, silo.store)
108
111
  if (room && !(room instanceof Promise)) {
109
- room.kill()
112
+ room.process.kill()
110
113
  }
111
114
  }
112
115
  silo.store.valueMap.clear()
@@ -135,6 +138,7 @@ export const setupRealtimeTestClient = (
135
138
  silo.store.valueMap.set(key, [...value])
136
139
  }
137
140
  }
141
+ silo.setState(myUsernameState, `${name}-${testNumber}`)
138
142
 
139
143
  const { document } = new Happy.Window()
140
144
  document.body.innerHTML = `<div id="app"></div>`
@@ -173,18 +177,14 @@ export const singleClient = (
173
177
  options: TestSetupOptions__SingleClient,
174
178
  ): RealtimeTestAPI__SingleClient => {
175
179
  const server = setupRealtimeTestServer(options)
176
- const client = setupRealtimeTestClient(
177
- options,
178
- `CLIENT-${testNumber}`,
179
- server.port,
180
- )
180
+ const client = setupRealtimeTestClient(options, `CLIENT`, server.port)
181
181
 
182
182
  return {
183
183
  client,
184
184
  server,
185
185
  teardown: () => {
186
- client.dispose()
187
186
  server.dispose()
187
+ client.dispose()
188
188
  },
189
189
  }
190
190
  }
@@ -209,10 +209,10 @@ export const multiClient = <ClientNames extends string>(
209
209
  clients,
210
210
  server,
211
211
  teardown: () => {
212
+ server.dispose()
212
213
  for (const [, client] of recordToEntries(clients)) {
213
214
  client.dispose()
214
215
  }
215
- server.dispose()
216
216
  },
217
217
  }
218
218
  }
package/src/logger.ts CHANGED
@@ -41,7 +41,11 @@ const LoggerIconDictionary = {
41
41
  "🪂": `Abort transaction`,
42
42
  "🤞": `Realtime optimistic update enqueued`,
43
43
  "👈": `Realtime confirmed update enqueued`,
44
- "⚖️": `Realtime update beginning reconciliation`,
44
+ "🧑‍⚖️": `Realtime update beginning reconciliation`,
45
+ "🛎️": `Realtime transaction received`,
46
+ "🔭": `Determining realtime perspective`,
47
+ "🖌": `Redacting realtime update`,
48
+ "👁": `Determining perspective`,
45
49
  } as const
46
50
  export type LoggerIcon = keyof typeof LoggerIconDictionary
47
51
  export type TokenDenomination =
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../realtime-client/src/sync-continuity.ts"],"names":["confirmedUpdate"],"mappings":"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","sourcesContent":["import type * as AtomIO from \"atom.io\"\nimport type { Store } from \"atom.io/internal\"\nimport {\n\tactUponStore,\n\tassignTransactionToContinuity,\n\tgetEpochNumberOfContinuity,\n\tgetFromStore,\n\tingestTransactionUpdate,\n\tisRootStore,\n\tsetEpochNumberOfContinuity,\n\tsetIntoStore,\n\tsubscribeToTransaction,\n} from \"atom.io/internal\"\nimport type { Json } from \"atom.io/json\"\nimport type { ContinuityToken } from \"atom.io/realtime\"\nimport {\n\tconfirmedUpdateQueue,\n\toptimisticUpdateQueue,\n} from \"atom.io/realtime-client\"\nimport type { Socket } from \"socket.io-client\"\n\nexport function syncContinuity<ƒ extends AtomIO.ƒn>(\n\tcontinuity: ContinuityToken,\n\tsocket: Socket,\n\tstore: Store,\n): () => void {\n\tconst continuityKey = continuity.key\n\tconst optimisticUpdates = getFromStore(optimisticUpdateQueue, store)\n\tconst confirmedUpdates = getFromStore(confirmedUpdateQueue, store)\n\n\tconst initializeContinuity = (epoch: number, payload: Json.Array) => {\n\t\tlet i = 0\n\t\tlet k: any = ``\n\t\tlet v: any = null\n\t\tfor (const x of payload) {\n\t\t\tif (i % 2 === 0) {\n\t\t\t\tk = x\n\t\t\t} else {\n\t\t\t\tv = x\n\t\t\t\tsetIntoStore(k, v, store)\n\t\t\t}\n\t\t\ti++\n\t\t}\n\t\tsetEpochNumberOfContinuity(continuityKey, epoch, store)\n\t}\n\tsocket.off(`continuity-init:${continuityKey}`)\n\tsocket.on(`continuity-init:${continuityKey}`, initializeContinuity)\n\n\tconst registerAndAttemptConfirmedUpdate = (\n\t\tconfirmedUpdate: AtomIO.TransactionUpdate<ƒ>,\n\t) => {\n\t\tfunction reconcileEpoch(\n\t\t\toptimisticUpdate: AtomIO.TransactionUpdate<any>,\n\t\t\tconfirmedUpdate: AtomIO.TransactionUpdate<any>,\n\t\t): void {\n\t\t\tstore.logger.info(`⚖️`, `continuity`, continuityKey, `reconciling updates`)\n\t\t\tsetIntoStore(\n\t\t\t\toptimisticUpdateQueue,\n\t\t\t\t(queue) => {\n\t\t\t\t\tqueue.shift()\n\t\t\t\t\treturn queue\n\t\t\t\t},\n\t\t\t\tstore,\n\t\t\t)\n\t\t\tif (optimisticUpdate.id === confirmedUpdate.id) {\n\t\t\t\tconst clientResult = JSON.stringify(optimisticUpdate.updates)\n\t\t\t\tconst serverResult = JSON.stringify(confirmedUpdate.updates)\n\t\t\t\tif (clientResult === serverResult) {\n\t\t\t\t\tstore.logger.info(\n\t\t\t\t\t\t`✅`,\n\t\t\t\t\t\t`continuity`,\n\t\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t\t`results for ${optimisticUpdate.id} match between client and server`,\n\t\t\t\t\t)\n\t\t\t\t\tsocket.emit(`ack:${continuityKey}`, confirmedUpdate.epoch)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// id mismatch\n\t\t\t\tstore.logger.info(\n\t\t\t\t\t`❌`,\n\t\t\t\t\t`continuity`,\n\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t`thought update #${confirmedUpdate.epoch} was ${optimisticUpdate.key}:${optimisticUpdate.id}, but it was actually ${confirmedUpdate.key}:${confirmedUpdate.id}`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst reversedOptimisticUpdates = optimisticUpdates.toReversed()\n\t\t\tfor (const subsequentOptimistic of reversedOptimisticUpdates) {\n\t\t\t\tingestTransactionUpdate(`oldValue`, subsequentOptimistic, store)\n\t\t\t}\n\t\t\tstore.logger.info(\n\t\t\t\t`⏪`,\n\t\t\t\t`continuity`,\n\t\t\t\tcontinuityKey,\n\t\t\t\t`undid optimistic updates:`,\n\t\t\t\treversedOptimisticUpdates,\n\t\t\t)\n\t\t\tingestTransactionUpdate(`oldValue`, optimisticUpdate, store)\n\t\t\tstore.logger.info(\n\t\t\t\t`⏪`,\n\t\t\t\t`continuity`,\n\t\t\t\tcontinuityKey,\n\t\t\t\t`undid zeroth optimistic update`,\n\t\t\t\toptimisticUpdate,\n\t\t\t)\n\t\t\tingestTransactionUpdate(`newValue`, confirmedUpdate, store)\n\t\t\tstore.logger.info(\n\t\t\t\t`⏩`,\n\t\t\t\t`continuity`,\n\t\t\t\tcontinuityKey,\n\t\t\t\t`applied confirmed update`,\n\t\t\t\tconfirmedUpdate,\n\t\t\t)\n\t\t\tsocket.emit(`ack:${continuityKey}`, confirmedUpdate.epoch)\n\n\t\t\tfor (const subsequentOptimistic of optimisticUpdates) {\n\t\t\t\tconst token = {\n\t\t\t\t\ttype: `transaction`,\n\t\t\t\t\tkey: subsequentOptimistic.key,\n\t\t\t\t} as const\n\t\t\t\tconst { id, params } = subsequentOptimistic\n\t\t\t\tactUponStore(token, id, store)(...params)\n\t\t\t}\n\t\t\tstore.logger.info(\n\t\t\t\t`⏩`,\n\t\t\t\t`continuity`,\n\t\t\t\tcontinuityKey,\n\t\t\t\t`reapplied subsequent optimistic updates:`,\n\t\t\t\toptimisticUpdates,\n\t\t\t)\n\t\t}\n\n\t\tstore.logger.info(\n\t\t\t`⚖️`,\n\t\t\t`continuity`,\n\t\t\tcontinuityKey,\n\t\t\t`integrating confirmed update`,\n\t\t\t{ confirmedUpdate, confirmedUpdates, optimisticUpdates },\n\t\t)\n\t\tconst zerothOptimisticUpdate = optimisticUpdates[0]\n\t\tif (zerothOptimisticUpdate) {\n\t\t\tstore.logger.info(\n\t\t\t\t`⚖️`,\n\t\t\t\t`continuity`,\n\t\t\t\tcontinuityKey,\n\t\t\t\t`has optimistic updates to reconcile`,\n\t\t\t)\n\t\t\tif (confirmedUpdate.epoch === zerothOptimisticUpdate.epoch) {\n\t\t\t\tstore.logger.info(\n\t\t\t\t\t`⚖️`,\n\t\t\t\t\t`continuity`,\n\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t`epoch of confirmed update #${confirmedUpdate.epoch} matches zeroth optimistic update`,\n\t\t\t\t)\n\t\t\t\treconcileEpoch(zerothOptimisticUpdate, confirmedUpdate)\n\t\t\t\tfor (const nextConfirmed of confirmedUpdates) {\n\t\t\t\t\tconst nextOptimistic = optimisticUpdates[0]\n\t\t\t\t\tif (nextConfirmed.epoch === nextOptimistic?.epoch) {\n\t\t\t\t\t\treconcileEpoch(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\t\t\t\tstore.logger.info(\n\t\t\t\t\t`⚖️`,\n\t\t\t\t\t`continuity`,\n\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t`epoch of confirmed update #${confirmedUpdate.epoch} does not match zeroth optimistic update #${zerothOptimisticUpdate.epoch}`,\n\t\t\t\t)\n\t\t\t\tconst confirmedUpdateIsAlreadyEnqueued = confirmedUpdates.some(\n\t\t\t\t\t(update) => update.epoch === confirmedUpdate.epoch,\n\t\t\t\t)\n\t\t\t\tif (!confirmedUpdateIsAlreadyEnqueued) {\n\t\t\t\t\tstore.logger.info(\n\t\t\t\t\t\t`👈`,\n\t\t\t\t\t\t`continuity`,\n\t\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t\t`pushing confirmed update to queue`,\n\t\t\t\t\t\tconfirmedUpdate,\n\t\t\t\t\t)\n\t\t\t\t\tsetIntoStore(\n\t\t\t\t\t\tconfirmedUpdateQueue,\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\tstore.logger.info(\n\t\t\t\t`⚖️`,\n\t\t\t\t`continuity`,\n\t\t\t\tcontinuityKey,\n\t\t\t\t`has no optimistic updates to deal with`,\n\t\t\t)\n\t\t\tconst continuityEpoch = getEpochNumberOfContinuity(continuityKey, store)\n\t\t\tconst isRoot = isRootStore(store)\n\n\t\t\tif (isRoot && continuityEpoch === confirmedUpdate.epoch - 1) {\n\t\t\t\tstore.logger.info(\n\t\t\t\t\t`✅`,\n\t\t\t\t\t`continuity`,\n\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t`integrating update #${confirmedUpdate.epoch} (${confirmedUpdate.key} ${confirmedUpdate.id})`,\n\t\t\t\t)\n\t\t\t\tingestTransactionUpdate(`newValue`, confirmedUpdate, store)\n\t\t\t\tsocket.emit(`ack:${continuityKey}`, confirmedUpdate.epoch)\n\t\t\t\tsetEpochNumberOfContinuity(continuityKey, confirmedUpdate.epoch, store)\n\t\t\t} else if (isRoot && continuityEpoch !== undefined) {\n\t\t\t\tstore.logger.info(\n\t\t\t\t\t`⚖️`,\n\t\t\t\t\t`continuity`,\n\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t`received update #${\n\t\t\t\t\t\tconfirmedUpdate.epoch\n\t\t\t\t\t} but still waiting for update #${continuityEpoch + 1}`,\n\t\t\t\t\t{\n\t\t\t\t\t\tclientEpoch: continuityEpoch,\n\t\t\t\t\t\tserverEpoch: confirmedUpdate.epoch,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tconst confirmedUpdateIsAlreadyEnqueued = confirmedUpdates.some(\n\t\t\t\t\t(update) => update.epoch === confirmedUpdate.epoch,\n\t\t\t\t)\n\t\t\t\tif (confirmedUpdateIsAlreadyEnqueued) {\n\t\t\t\t\tstore.logger.info(\n\t\t\t\t\t\t`👍`,\n\t\t\t\t\t\t`continuity`,\n\t\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t\t`confirmed update #${confirmedUpdate.epoch} is already enqueued`,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tstore.logger.info(\n\t\t\t\t\t\t`👈`,\n\t\t\t\t\t\t`continuity`,\n\t\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t\t`pushing confirmed update #${confirmedUpdate.epoch} to queue`,\n\t\t\t\t\t)\n\t\t\t\t\tsetIntoStore(\n\t\t\t\t\t\tconfirmedUpdateQueue,\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}\n\t}\n\tsocket.off(`tx-new:${continuityKey}`)\n\tsocket.on(`tx-new:${continuityKey}`, registerAndAttemptConfirmedUpdate)\n\n\tconst unsubscribeFunctions = continuity.actions.map((transaction) => {\n\t\tassignTransactionToContinuity(continuityKey, transaction.key, store)\n\t\tconst unsubscribeFromTransactionUpdates = subscribeToTransaction(\n\t\t\ttransaction,\n\t\t\t(clientUpdate) => {\n\t\t\t\tstore.logger.info(\n\t\t\t\t\t`🤞`,\n\t\t\t\t\t`continuity`,\n\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t`enqueuing optimistic update`,\n\t\t\t\t)\n\t\t\t\tconst optimisticUpdateIndex = optimisticUpdates.findIndex(\n\t\t\t\t\t(update) => update.id === clientUpdate.id,\n\t\t\t\t)\n\t\t\t\tif (optimisticUpdateIndex === -1) {\n\t\t\t\t\tstore.logger.info(\n\t\t\t\t\t\t`🤞`,\n\t\t\t\t\t\t`continuity`,\n\t\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t\t`enqueuing new optimistic update`,\n\t\t\t\t\t)\n\t\t\t\t\tsetIntoStore(\n\t\t\t\t\t\toptimisticUpdateQueue,\n\t\t\t\t\t\t(queue) => {\n\t\t\t\t\t\t\tqueue.push(clientUpdate)\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} else {\n\t\t\t\t\tstore.logger.info(\n\t\t\t\t\t\t`🤞`,\n\t\t\t\t\t\t`continuity`,\n\t\t\t\t\t\tcontinuityKey,\n\t\t\t\t\t\t`replacing existing optimistic update at index ${optimisticUpdateIndex}`,\n\t\t\t\t\t)\n\t\t\t\t\tsetIntoStore(\n\t\t\t\t\t\toptimisticUpdateQueue,\n\t\t\t\t\t\t(queue) => {\n\t\t\t\t\t\t\tqueue[optimisticUpdateIndex] = clientUpdate\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\tsocket.emit(`tx-run:${continuityKey}`, clientUpdate)\n\t\t\t},\n\t\t\t`tx-run:${continuityKey}`,\n\t\t\tstore,\n\t\t)\n\t\treturn unsubscribeFromTransactionUpdates\n\t})\n\n\tsocket.emit(`get:${continuityKey}`)\n\treturn () => {\n\t\tsocket.off(`continuity-init:${continuityKey}`)\n\t\tsocket.off(`tx-new:${continuityKey}`)\n\t\tfor (const unsubscribe of unsubscribeFunctions) unsubscribe()\n\t\tsocket.emit(`unsub:${continuityKey}`)\n\t}\n}\n"]}