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.
- package/dist/chunk-3J2EGSBE.js +31 -0
- package/dist/chunk-3J2EGSBE.js.map +1 -0
- package/dist/chunk-A4ZCNKWQ.js +18 -0
- package/dist/chunk-A4ZCNKWQ.js.map +1 -0
- package/dist/{chunk-OEVFAUPE.js → chunk-IZHOMSXA.js} +53 -11
- package/dist/chunk-IZHOMSXA.js.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js.map +1 -1
- package/internal/dist/index.cjs +110 -134
- package/internal/dist/index.cjs.map +1 -1
- package/internal/dist/index.d.ts +24 -24
- package/internal/dist/index.js +110 -134
- package/internal/dist/index.js.map +1 -1
- package/internal/src/atom/delete-atom.ts +7 -6
- package/internal/src/caching.ts +6 -6
- package/internal/src/families/create-regular-atom-family.ts +4 -4
- package/internal/src/get-state/get-from-store.ts +2 -5
- package/internal/src/ingest-updates/ingest-atom-update.ts +6 -2
- package/internal/src/mutable/create-mutable-atom-family.ts +4 -4
- package/internal/src/selector/register-selector.ts +3 -14
- package/internal/src/set-state/copy-mutable-if-needed.ts +5 -0
- package/internal/src/set-state/emit-update.ts +25 -11
- package/internal/src/set-state/set-atom.ts +4 -3
- package/internal/src/set-state/set-into-store.ts +2 -5
- package/internal/src/store/withdraw-new-family-member.ts +38 -28
- package/internal/src/store/withdraw.ts +20 -23
- package/internal/src/subscribe/subscribe-to-state.ts +2 -3
- package/internal/src/subscribe/subscribe-to-timeline.ts +0 -5
- package/internal/src/subscribe/subscribe-to-transaction.ts +0 -5
- package/internal/src/timeline/add-atom-to-timeline.ts +6 -15
- package/internal/src/timeline/create-timeline.ts +0 -27
- package/internal/src/transaction/apply-transaction.ts +0 -1
- package/internal/src/transaction/set-epoch-number.ts +0 -1
- package/json/src/index.ts +3 -3
- package/package.json +242 -241
- package/react/dist/index.cjs +2 -3
- package/react/dist/index.cjs.map +1 -1
- package/react/dist/index.js +2 -3
- package/react/dist/index.js.map +1 -1
- package/react/src/use-tl.ts +2 -2
- package/react-devtools/dist/index.cjs +1 -1
- package/react-devtools/dist/index.cjs.map +1 -1
- package/react-devtools/dist/index.js +1 -15
- package/react-devtools/dist/index.js.map +1 -1
- package/react-devtools/src/StateEditor.tsx +6 -6
- package/react-devtools/src/StateIndex.tsx +2 -2
- package/react-devtools/src/Updates.tsx +1 -1
- package/react-devtools/src/index.ts +3 -3
- package/realtime/dist/index.cjs +50 -2
- package/realtime/dist/index.cjs.map +1 -1
- package/realtime/dist/index.d.ts +110 -3
- package/realtime/dist/index.js +47 -4
- package/realtime/dist/index.js.map +1 -1
- package/realtime/src/index.ts +1 -0
- package/realtime/src/realtime-continuity.ts +14 -4
- package/realtime/src/shared-room-store.ts +48 -0
- package/realtime-client/dist/index.cjs +113 -200
- package/realtime-client/dist/index.cjs.map +1 -1
- package/realtime-client/dist/index.d.ts +2 -5
- package/realtime-client/dist/index.js +22 -190
- package/realtime-client/dist/index.js.map +1 -1
- package/realtime-client/src/index.ts +0 -2
- package/realtime-client/src/pull-mutable-atom-family-member.ts +5 -5
- package/realtime-client/src/realtime-client-stores/client-main-store.ts +10 -0
- package/realtime-client/src/sync-continuity.ts +56 -9
- package/realtime-react/dist/index.cjs +51 -26
- package/realtime-react/dist/index.cjs.map +1 -1
- package/realtime-react/dist/index.d.ts +2 -6
- package/realtime-react/dist/index.js +2 -17
- package/realtime-react/dist/index.js.map +1 -1
- package/realtime-react/src/index.ts +0 -2
- package/realtime-server/dist/index.cjs +399 -327
- package/realtime-server/dist/index.cjs.map +1 -1
- package/realtime-server/dist/index.d.ts +55 -60
- package/realtime-server/dist/index.js +394 -319
- package/realtime-server/dist/index.js.map +1 -1
- package/realtime-server/src/index.ts +2 -4
- package/realtime-server/src/ipc-sockets/child-socket.ts +135 -0
- package/realtime-server/src/ipc-sockets/custom-socket.ts +90 -0
- package/realtime-server/src/ipc-sockets/index.ts +3 -0
- package/realtime-server/src/ipc-sockets/parent-socket.ts +186 -0
- package/realtime-server/src/realtime-continuity-synchronizer.ts +225 -96
- package/realtime-server/src/realtime-server-stores/index.ts +2 -1
- package/realtime-server/src/realtime-server-stores/realtime-continuity-store.ts +50 -31
- package/realtime-server/src/realtime-server-stores/server-room-external-actions.ts +64 -0
- package/realtime-server/src/realtime-server-stores/server-room-external-store.ts +42 -0
- package/realtime-server/src/realtime-server-stores/server-sync-store.ts +49 -26
- package/realtime-testing/dist/index.cjs +51 -11
- package/realtime-testing/dist/index.cjs.map +1 -1
- package/realtime-testing/dist/index.d.ts +1 -0
- package/realtime-testing/dist/index.js +11 -11
- package/realtime-testing/dist/index.js.map +1 -1
- package/realtime-testing/src/setup-realtime-test.tsx +11 -11
- package/src/logger.ts +5 -1
- package/dist/chunk-OEVFAUPE.js.map +0 -1
- package/realtime-client/src/sync-server-action.ts +0 -168
- package/realtime-client/src/sync-state.ts +0 -19
- package/realtime-react/src/use-sync-server-action.ts +0 -17
- package/realtime-react/src/use-sync.ts +0 -17
- package/realtime-server/src/ipc-socket.ts +0 -230
- package/realtime-server/src/realtime-action-synchronizer.ts +0 -164
- 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
|
-
|
|
6
|
-
|
|
7
|
-
>({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
})
|
|
4
|
+
// export const completeUpdateAtoms = atomFamily<
|
|
5
|
+
// TransactionUpdate<any> | null,
|
|
6
|
+
// string
|
|
7
|
+
// >({
|
|
8
|
+
// key: `completeUpdate`,
|
|
9
|
+
// default: null,
|
|
10
|
+
// })
|
|
11
11
|
|
|
12
|
-
export
|
|
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
|
-
|
|
37
|
+
occlude: (updates: TransactionUpdateContent[]) => TransactionUpdateContent[]
|
|
15
38
|
},
|
|
16
39
|
string
|
|
17
40
|
>({
|
|
18
41
|
key: `transactionRedactor`,
|
|
19
|
-
default: {
|
|
42
|
+
default: { occlude: (updates) => updates },
|
|
20
43
|
})
|
|
21
|
-
export const redactedUpdateSelectors = selectorFamily<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
>({
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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` ?
|
|
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(
|
|
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"]}
|
|
@@ -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` ?
|
|
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(
|
|
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":"
|
|
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` ?
|
|
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(
|
|
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
|
-
"
|
|
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"]}
|