atom.io 0.27.5 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/dist/index.d.ts +31 -29
- package/data/dist/index.js +16 -17
- package/data/src/join.ts +17 -19
- package/dist/{chunk-6ABWLAGY.js → chunk-BX3MTH2Z.js} +320 -249
- package/dist/chunk-D52JNVER.js +721 -0
- package/dist/chunk-EUVKUTW3.js +89 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/internal/dist/index.d.ts +72 -36
- package/internal/dist/index.js +1 -1
- package/internal/src/atom/dispose-atom.ts +2 -9
- package/internal/src/families/dispose-from-store.ts +35 -18
- package/internal/src/families/find-in-store.ts +17 -7
- package/internal/src/get-state/get-from-store.ts +41 -32
- package/internal/src/ingest-updates/ingest-creation-disposal.ts +10 -1
- package/internal/src/molecule/dispose-molecule.ts +6 -17
- package/internal/src/pretty-print.ts +1 -16
- package/internal/src/selector/dispose-selector.ts +2 -9
- package/internal/src/set-state/set-into-store.ts +17 -19
- package/internal/src/store/circular-buffer.ts +34 -0
- package/internal/src/store/counterfeit.ts +109 -0
- package/internal/src/store/deposit.ts +14 -0
- package/internal/src/store/index.ts +1 -0
- package/internal/src/store/store.ts +3 -0
- package/internal/src/store/withdraw.ts +15 -10
- package/internal/src/transaction/build-transaction.ts +1 -0
- package/introspection/dist/index.d.ts +84 -4
- package/introspection/dist/index.js +1 -392
- package/introspection/src/attach-introspection-states.ts +7 -4
- package/introspection/src/attach-type-selectors.ts +26 -0
- package/introspection/src/differ.ts +167 -0
- package/introspection/src/index.ts +2 -0
- package/introspection/src/refinery.ts +100 -0
- package/json/dist/index.d.ts +31 -30
- package/json/dist/index.js +2 -77
- package/json/src/entries.ts +6 -0
- package/json/src/index.ts +47 -6
- package/package.json +17 -8
- package/react-devtools/dist/index.d.ts +1 -91
- package/react-devtools/dist/index.js +285 -414
- package/react-devtools/src/AtomIODevtools.tsx +2 -2
- package/react-devtools/src/StateEditor.tsx +20 -12
- package/react-devtools/src/StateIndex.tsx +8 -26
- package/react-devtools/src/TimelineIndex.tsx +3 -3
- package/react-devtools/src/TransactionIndex.tsx +6 -6
- package/react-devtools/src/Updates.tsx +1 -4
- package/react-devtools/src/index.ts +0 -71
- package/react-devtools/src/store.ts +51 -0
- package/realtime/dist/index.d.ts +7 -7
- package/realtime/dist/index.js +18 -22
- package/realtime/src/realtime-continuity.ts +27 -35
- package/realtime-client/dist/index.js +24 -10
- package/realtime-client/src/realtime-client-stores/client-main-store.ts +6 -6
- package/realtime-client/src/sync-continuity.ts +28 -6
- package/realtime-server/dist/index.js +41 -5
- package/realtime-server/src/realtime-continuity-synchronizer.ts +42 -78
- package/realtime-testing/dist/index.d.ts +2 -0
- package/realtime-testing/dist/index.js +50 -8
- package/realtime-testing/src/setup-realtime-test.tsx +59 -9
- package/src/silo.ts +7 -3
- package/web/dist/index.d.ts +9 -0
- package/{dist/chunk-H6EDLPKH.js → web/dist/index.js} +5 -4
- package/web/package.json +13 -0
- package/web/src/index.ts +1 -0
- package/web/src/persist-sync.ts +25 -0
- package/dist/chunk-AK23DRMD.js +0 -21
- package/dist/chunk-IW6WYRS7.js +0 -140
|
@@ -7,13 +7,15 @@ import {
|
|
|
7
7
|
getEpochNumberOfContinuity,
|
|
8
8
|
getFromStore,
|
|
9
9
|
getJsonToken,
|
|
10
|
+
growMoleculeInStore,
|
|
10
11
|
ingestTransactionUpdate,
|
|
12
|
+
initFamilyMemberInStore,
|
|
11
13
|
isRootStore,
|
|
12
14
|
setEpochNumberOfContinuity,
|
|
13
15
|
setIntoStore,
|
|
14
16
|
subscribeToTransaction,
|
|
15
17
|
} from "atom.io/internal"
|
|
16
|
-
import type
|
|
18
|
+
import { type Json, parseJson } from "atom.io/json"
|
|
17
19
|
import type { ContinuityToken } from "atom.io/realtime"
|
|
18
20
|
import {
|
|
19
21
|
confirmedUpdateQueue,
|
|
@@ -33,8 +35,8 @@ export function syncContinuity<F extends Func>(
|
|
|
33
35
|
const initializeContinuity = (epoch: number, payload: Json.Array) => {
|
|
34
36
|
socket.off(`continuity-init:${continuityKey}`, initializeContinuity)
|
|
35
37
|
let i = 0
|
|
36
|
-
let k: any
|
|
37
|
-
let v: any
|
|
38
|
+
let k: any
|
|
39
|
+
let v: any
|
|
38
40
|
for (const x of payload) {
|
|
39
41
|
if (i % 2 === 0) {
|
|
40
42
|
k = x
|
|
@@ -316,14 +318,14 @@ export function syncContinuity<F extends Func>(
|
|
|
316
318
|
|
|
317
319
|
socket.on(`reveal:${continuityKey}`, (revealed: Json.Array) => {
|
|
318
320
|
let i = 0
|
|
319
|
-
let k: any
|
|
320
|
-
let v: any
|
|
321
|
+
let k: any
|
|
322
|
+
let v: any
|
|
321
323
|
for (const x of revealed) {
|
|
322
324
|
if (i % 2 === 0) {
|
|
323
325
|
k = x
|
|
324
326
|
} else {
|
|
325
327
|
v = x
|
|
326
|
-
|
|
328
|
+
upsertState(k, v, store)
|
|
327
329
|
}
|
|
328
330
|
i++
|
|
329
331
|
}
|
|
@@ -345,3 +347,23 @@ export function syncContinuity<F extends Func>(
|
|
|
345
347
|
// socket.emit(`unsub:${continuityKey}`)
|
|
346
348
|
}
|
|
347
349
|
}
|
|
350
|
+
|
|
351
|
+
function upsertState<T>(
|
|
352
|
+
store: Store,
|
|
353
|
+
token: AtomIO.WritableToken<T>,
|
|
354
|
+
value: T,
|
|
355
|
+
): void {
|
|
356
|
+
if (token.family) {
|
|
357
|
+
const family = store.families.get(token.family.key)
|
|
358
|
+
if (family) {
|
|
359
|
+
const molecule = store.molecules.get(token.family.subKey)
|
|
360
|
+
if (molecule) {
|
|
361
|
+
growMoleculeInStore(molecule, family, store)
|
|
362
|
+
} else if (store.config.lifespan === `immortal`) {
|
|
363
|
+
throw new Error(`No molecule found for key "${token.family.subKey}"`)
|
|
364
|
+
}
|
|
365
|
+
initFamilyMemberInStore(store, family, parseJson(token.family.subKey))
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
setIntoStore(store, token, value)
|
|
369
|
+
}
|
|
@@ -510,17 +510,23 @@ function realtimeContinuitySynchronizer({
|
|
|
510
510
|
`seeing ${userKey} on new socket ${newSocketKey}`
|
|
511
511
|
);
|
|
512
512
|
if (newSocketKey === null) {
|
|
513
|
-
store.logger.
|
|
513
|
+
store.logger.warn(
|
|
514
514
|
`\u274C`,
|
|
515
515
|
`continuity`,
|
|
516
516
|
continuityKey,
|
|
517
|
-
`
|
|
517
|
+
`User (${userKey}) is not connected to a socket, waiting for them to reappear.`
|
|
518
518
|
);
|
|
519
519
|
return;
|
|
520
520
|
}
|
|
521
521
|
const newSocketState = findInStore(store, socketAtoms, newSocketKey);
|
|
522
522
|
const newSocket = getFromStore(store, newSocketState);
|
|
523
523
|
socket = newSocket;
|
|
524
|
+
for (const unacknowledgedUpdate of userUnacknowledgedUpdates) {
|
|
525
|
+
socket?.emit(
|
|
526
|
+
`tx-new:${continuityKey}`,
|
|
527
|
+
unacknowledgedUpdate
|
|
528
|
+
);
|
|
529
|
+
}
|
|
524
530
|
},
|
|
525
531
|
`sync-continuity:${continuityKey}:${userKey}`,
|
|
526
532
|
store
|
|
@@ -530,7 +536,7 @@ function realtimeContinuitySynchronizer({
|
|
|
530
536
|
userUnacknowledgedQueues,
|
|
531
537
|
userKey
|
|
532
538
|
);
|
|
533
|
-
getFromStore(
|
|
539
|
+
const userUnacknowledgedUpdates = getFromStore(
|
|
534
540
|
store,
|
|
535
541
|
userUnacknowledgedQueue
|
|
536
542
|
);
|
|
@@ -581,7 +587,8 @@ function realtimeContinuitySynchronizer({
|
|
|
581
587
|
const initialPayload = [];
|
|
582
588
|
for (const atom2 of continuity.globals) {
|
|
583
589
|
const resourceToken = atom2.type === `mutable_atom` ? getJsonToken(store, atom2) : atom2;
|
|
584
|
-
|
|
590
|
+
const resource = getFromStore(store, resourceToken);
|
|
591
|
+
initialPayload.push(resourceToken, resource);
|
|
585
592
|
}
|
|
586
593
|
for (const perspective of continuity.perspectives) {
|
|
587
594
|
const { viewAtoms, resourceAtoms } = perspective;
|
|
@@ -605,7 +612,12 @@ function realtimeContinuitySynchronizer({
|
|
|
605
612
|
transaction2,
|
|
606
613
|
(update) => {
|
|
607
614
|
try {
|
|
608
|
-
const visibleKeys = continuity.globals.map((atom2) =>
|
|
615
|
+
const visibleKeys = continuity.globals.map((atom2) => {
|
|
616
|
+
if (atom2.type === `atom`) {
|
|
617
|
+
return atom2.key;
|
|
618
|
+
}
|
|
619
|
+
return getUpdateToken(atom2).key;
|
|
620
|
+
}).concat(
|
|
609
621
|
continuity.perspectives.flatMap((perspective) => {
|
|
610
622
|
const { viewAtoms } = perspective;
|
|
611
623
|
const userPerspectiveTokenState = findInStore(
|
|
@@ -631,6 +643,13 @@ function realtimeContinuitySynchronizer({
|
|
|
631
643
|
...update,
|
|
632
644
|
updates: redactedUpdates
|
|
633
645
|
};
|
|
646
|
+
setIntoStore(store, userUnacknowledgedQueue, (updates) => {
|
|
647
|
+
if (redactedUpdate) {
|
|
648
|
+
updates.push(redactedUpdate);
|
|
649
|
+
updates.sort((a, b) => a.epoch - b.epoch);
|
|
650
|
+
}
|
|
651
|
+
return updates;
|
|
652
|
+
});
|
|
634
653
|
socket?.emit(
|
|
635
654
|
`tx-new:${continuityKey}`,
|
|
636
655
|
redactedUpdate
|
|
@@ -710,8 +729,25 @@ function realtimeContinuitySynchronizer({
|
|
|
710
729
|
};
|
|
711
730
|
socket.off(`tx-run:${continuityKey}`, fillTransactionRequest);
|
|
712
731
|
socket.on(`tx-run:${continuityKey}`, fillTransactionRequest);
|
|
732
|
+
const trackClientAcknowledgement = (epoch) => {
|
|
733
|
+
store.logger.info(
|
|
734
|
+
`\u{1F44D}`,
|
|
735
|
+
`continuity`,
|
|
736
|
+
continuityKey,
|
|
737
|
+
`${userKey} acknowledged epoch ${epoch}`
|
|
738
|
+
);
|
|
739
|
+
const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch;
|
|
740
|
+
if (isUnacknowledged) {
|
|
741
|
+
setIntoStore(store, userUnacknowledgedQueue, (updates) => {
|
|
742
|
+
updates.shift();
|
|
743
|
+
return updates;
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
socket?.on(`ack:${continuityKey}`, trackClientAcknowledgement);
|
|
713
748
|
return () => {
|
|
714
749
|
for (const unsubscribe of unsubscribeFunctions) unsubscribe();
|
|
750
|
+
socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement);
|
|
715
751
|
unsubscribeFromPerspectives();
|
|
716
752
|
socket?.off(`get:${continuityKey}`, sendInitialPayload);
|
|
717
753
|
socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest);
|
|
@@ -5,8 +5,10 @@ import {
|
|
|
5
5
|
findInStore,
|
|
6
6
|
getFromStore,
|
|
7
7
|
getJsonToken,
|
|
8
|
+
getUpdateToken,
|
|
8
9
|
IMPLICIT,
|
|
9
10
|
isRootStore,
|
|
11
|
+
setIntoStore,
|
|
10
12
|
subscribeToState,
|
|
11
13
|
subscribeToTransaction,
|
|
12
14
|
} from "atom.io/internal"
|
|
@@ -62,17 +64,23 @@ export function realtimeContinuitySynchronizer({
|
|
|
62
64
|
`seeing ${userKey} on new socket ${newSocketKey}`,
|
|
63
65
|
)
|
|
64
66
|
if (newSocketKey === null) {
|
|
65
|
-
store.logger.
|
|
67
|
+
store.logger.warn(
|
|
66
68
|
`❌`,
|
|
67
69
|
`continuity`,
|
|
68
70
|
continuityKey,
|
|
69
|
-
`
|
|
71
|
+
`User (${userKey}) is not connected to a socket, waiting for them to reappear.`,
|
|
70
72
|
)
|
|
71
73
|
return
|
|
72
74
|
}
|
|
73
75
|
const newSocketState = findInStore(store, socketAtoms, newSocketKey)
|
|
74
76
|
const newSocket = getFromStore(store, newSocketState)
|
|
75
77
|
socket = newSocket
|
|
78
|
+
for (const unacknowledgedUpdate of userUnacknowledgedUpdates) {
|
|
79
|
+
socket?.emit(
|
|
80
|
+
`tx-new:${continuityKey}`,
|
|
81
|
+
unacknowledgedUpdate as Json.Serializable,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
76
84
|
},
|
|
77
85
|
`sync-continuity:${continuityKey}:${userKey}`,
|
|
78
86
|
store,
|
|
@@ -142,7 +150,8 @@ export function realtimeContinuitySynchronizer({
|
|
|
142
150
|
for (const atom of continuity.globals) {
|
|
143
151
|
const resourceToken =
|
|
144
152
|
atom.type === `mutable_atom` ? getJsonToken(store, atom) : atom
|
|
145
|
-
|
|
153
|
+
const resource = getFromStore(store, resourceToken)
|
|
154
|
+
initialPayload.push(resourceToken, resource)
|
|
146
155
|
}
|
|
147
156
|
for (const perspective of continuity.perspectives) {
|
|
148
157
|
const { viewAtoms, resourceAtoms } = perspective
|
|
@@ -176,7 +185,12 @@ export function realtimeContinuitySynchronizer({
|
|
|
176
185
|
(update) => {
|
|
177
186
|
try {
|
|
178
187
|
const visibleKeys = continuity.globals
|
|
179
|
-
.map((atom) =>
|
|
188
|
+
.map((atom) => {
|
|
189
|
+
if (atom.type === `atom`) {
|
|
190
|
+
return atom.key
|
|
191
|
+
}
|
|
192
|
+
return getUpdateToken(atom).key
|
|
193
|
+
})
|
|
180
194
|
.concat(
|
|
181
195
|
continuity.perspectives.flatMap((perspective) => {
|
|
182
196
|
const { viewAtoms } = perspective
|
|
@@ -206,17 +220,13 @@ export function realtimeContinuitySynchronizer({
|
|
|
206
220
|
...update,
|
|
207
221
|
updates: redactedUpdates,
|
|
208
222
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// return updates
|
|
217
|
-
// },
|
|
218
|
-
// store,
|
|
219
|
-
// )
|
|
223
|
+
setIntoStore(store, userUnacknowledgedQueue, (updates) => {
|
|
224
|
+
if (redactedUpdate) {
|
|
225
|
+
updates.push(redactedUpdate)
|
|
226
|
+
updates.sort((a, b) => a.epoch - b.epoch)
|
|
227
|
+
}
|
|
228
|
+
return updates
|
|
229
|
+
})
|
|
220
230
|
|
|
221
231
|
socket?.emit(
|
|
222
232
|
`tx-new:${continuityKey}`,
|
|
@@ -304,73 +314,27 @@ export function realtimeContinuitySynchronizer({
|
|
|
304
314
|
socket.off(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
305
315
|
socket.on(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
306
316
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
// return updates
|
|
324
|
-
// },
|
|
325
|
-
// store,
|
|
326
|
-
// )
|
|
327
|
-
// }
|
|
328
|
-
// }
|
|
329
|
-
// subscribeToState(
|
|
330
|
-
// userUnacknowledgedQueue,
|
|
331
|
-
// ({ newValue }) => {
|
|
332
|
-
// if (newValue.length === 0) {
|
|
333
|
-
// clearInterval(retryTimeout)
|
|
334
|
-
// socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
335
|
-
// retryTimeout = undefined
|
|
336
|
-
// }
|
|
337
|
-
// if (newValue.length > 0) {
|
|
338
|
-
// if (retryTimeout) {
|
|
339
|
-
// return
|
|
340
|
-
// }
|
|
341
|
-
|
|
342
|
-
// socket?.on(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
343
|
-
|
|
344
|
-
// retryTimeout = setInterval(() => {
|
|
345
|
-
// i++
|
|
346
|
-
// if (i === n) {
|
|
347
|
-
// n += i
|
|
348
|
-
// const toEmit = newValue[0]
|
|
349
|
-
// if (!toEmit) return
|
|
350
|
-
// store.logger.info(
|
|
351
|
-
// `🔄`,
|
|
352
|
-
// `continuity`,
|
|
353
|
-
// continuityKey,
|
|
354
|
-
// `${store.config.name} retrying ${userKey}`,
|
|
355
|
-
// socket?.id,
|
|
356
|
-
// newValue,
|
|
357
|
-
// )
|
|
358
|
-
// socket?.emit(
|
|
359
|
-
// `tx-new:${continuityKey}`,
|
|
360
|
-
// toEmit as Json.Serializable,
|
|
361
|
-
// )
|
|
362
|
-
// }
|
|
363
|
-
// }, 250)
|
|
364
|
-
// }
|
|
365
|
-
// },
|
|
366
|
-
// `sync-continuity:${continuityKey}:${userKey}`,
|
|
367
|
-
// store,
|
|
368
|
-
// )
|
|
317
|
+
const trackClientAcknowledgement = (epoch: number) => {
|
|
318
|
+
store.logger.info(
|
|
319
|
+
`👍`,
|
|
320
|
+
`continuity`,
|
|
321
|
+
continuityKey,
|
|
322
|
+
`${userKey} acknowledged epoch ${epoch}`,
|
|
323
|
+
)
|
|
324
|
+
const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch
|
|
325
|
+
if (isUnacknowledged) {
|
|
326
|
+
setIntoStore(store, userUnacknowledgedQueue, (updates) => {
|
|
327
|
+
updates.shift()
|
|
328
|
+
return updates
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
socket?.on(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
369
333
|
|
|
370
334
|
return () => {
|
|
371
335
|
// clearInterval(retryTimeout)
|
|
372
336
|
for (const unsubscribe of unsubscribeFunctions) unsubscribe()
|
|
373
|
-
|
|
337
|
+
socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
374
338
|
unsubscribeFromPerspectives()
|
|
375
339
|
socket?.off(`get:${continuityKey}`, sendInitialPayload)
|
|
376
340
|
socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
@@ -9,6 +9,7 @@ type TestSetupOptions = {
|
|
|
9
9
|
server: (tools: {
|
|
10
10
|
socket: SocketIO.Socket;
|
|
11
11
|
silo: AtomIO.Silo;
|
|
12
|
+
enableLogging: () => void;
|
|
12
13
|
}) => void;
|
|
13
14
|
};
|
|
14
15
|
type TestSetupOptions__SingleClient = TestSetupOptions & {
|
|
@@ -26,6 +27,7 @@ type RealtimeTestTools = {
|
|
|
26
27
|
type RealtimeTestClient = RealtimeTestTools & {
|
|
27
28
|
renderResult: RenderResult;
|
|
28
29
|
prettyPrint: () => void;
|
|
30
|
+
enableLogging: () => void;
|
|
29
31
|
socket: Socket;
|
|
30
32
|
};
|
|
31
33
|
type RealtimeTestClientBuilder = {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { recordToEntries } from '../../dist/chunk-IW6WYRS7.js';
|
|
2
1
|
import '../../dist/chunk-XWL6SNVU.js';
|
|
3
2
|
import * as http from 'http';
|
|
4
3
|
import { render, prettyDOM } from '@testing-library/react';
|
|
5
4
|
import * as AtomIO from 'atom.io';
|
|
6
|
-
import { editRelationsInStore } from 'atom.io/data';
|
|
5
|
+
import { editRelationsInStore, findRelationsInStore } from 'atom.io/data';
|
|
7
6
|
import { IMPLICIT, findInStore, setIntoStore, getFromStore, clearStore } from 'atom.io/internal';
|
|
7
|
+
import { toEntries } from 'atom.io/json';
|
|
8
8
|
import * as AR from 'atom.io/react';
|
|
9
9
|
import * as RT from 'atom.io/realtime';
|
|
10
|
-
import
|
|
10
|
+
import * as RTC from 'atom.io/realtime-client';
|
|
11
11
|
import * as RTR from 'atom.io/realtime-react';
|
|
12
12
|
import * as RTS from 'atom.io/realtime-server';
|
|
13
13
|
import * as Happy from 'happy-dom';
|
|
@@ -16,6 +16,19 @@ import { io } from 'socket.io-client';
|
|
|
16
16
|
import { jsx } from 'react/jsx-runtime';
|
|
17
17
|
|
|
18
18
|
var testNumber = 0;
|
|
19
|
+
function prefixLogger(store, prefix) {
|
|
20
|
+
store.loggers[0] = new AtomIO.AtomIOLogger(`info`, void 0, {
|
|
21
|
+
info: (...args) => {
|
|
22
|
+
console.info(prefix, ...args);
|
|
23
|
+
},
|
|
24
|
+
warn: (...args) => {
|
|
25
|
+
console.warn(prefix, ...args);
|
|
26
|
+
},
|
|
27
|
+
error: (...args) => {
|
|
28
|
+
console.error(prefix, ...args);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
19
32
|
var setupRealtimeTestServer = (options) => {
|
|
20
33
|
++testNumber;
|
|
21
34
|
const silo = new AtomIO.Silo(
|
|
@@ -47,7 +60,26 @@ var setupRealtimeTestServer = (options) => {
|
|
|
47
60
|
}
|
|
48
61
|
});
|
|
49
62
|
server.on(`connection`, (socket) => {
|
|
50
|
-
|
|
63
|
+
let userKey = null;
|
|
64
|
+
function enableLogging() {
|
|
65
|
+
const userKeyState = findRelationsInStore(
|
|
66
|
+
RTS.usersOfSockets,
|
|
67
|
+
socket.id,
|
|
68
|
+
silo.store
|
|
69
|
+
).userKeyOfSocket;
|
|
70
|
+
userKey = getFromStore(silo.store, userKeyState);
|
|
71
|
+
prefixLogger(silo.store, `server`);
|
|
72
|
+
socket.onAny((event, ...args) => {
|
|
73
|
+
console.log(`\u{1F6F0} `, userKey, event, ...args);
|
|
74
|
+
});
|
|
75
|
+
socket.onAnyOutgoing((event, ...args) => {
|
|
76
|
+
console.log(`\u{1F6F0} >>`, userKey, event, ...args);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
options.server({ socket, enableLogging, silo });
|
|
80
|
+
socket.on(`disconnect`, () => {
|
|
81
|
+
console.log(`${userKey} disconnected`);
|
|
82
|
+
});
|
|
51
83
|
});
|
|
52
84
|
const dispose = () => {
|
|
53
85
|
server.close();
|
|
@@ -81,7 +113,7 @@ var setupRealtimeTestClient = (options, name, port) => {
|
|
|
81
113
|
silo.store.valueMap.set(key, [...value]);
|
|
82
114
|
}
|
|
83
115
|
}
|
|
84
|
-
silo.setState(myUsernameState, `${name}-${testNumber}`);
|
|
116
|
+
silo.setState(RTC.myUsernameState, `${name}-${testNumber}`);
|
|
85
117
|
const { document } = new Happy.Window();
|
|
86
118
|
document.body.innerHTML = `<div id="app"></div>`;
|
|
87
119
|
const renderResult = render(
|
|
@@ -93,6 +125,15 @@ var setupRealtimeTestClient = (options, name, port) => {
|
|
|
93
125
|
const prettyPrint = () => {
|
|
94
126
|
console.log(prettyDOM(renderResult.container));
|
|
95
127
|
};
|
|
128
|
+
const enableLogging = () => {
|
|
129
|
+
prefixLogger(silo.store, name);
|
|
130
|
+
socket.onAny((event, ...args) => {
|
|
131
|
+
console.log(`\u{1F4E1} `, name, event, ...args);
|
|
132
|
+
});
|
|
133
|
+
socket.onAnyOutgoing((event, ...args) => {
|
|
134
|
+
console.log(`\u{1F4E1} >>`, name, event, ...args);
|
|
135
|
+
});
|
|
136
|
+
};
|
|
96
137
|
const dispose = () => {
|
|
97
138
|
renderResult.unmount();
|
|
98
139
|
socket.disconnect();
|
|
@@ -104,7 +145,8 @@ var setupRealtimeTestClient = (options, name, port) => {
|
|
|
104
145
|
silo,
|
|
105
146
|
socket,
|
|
106
147
|
renderResult,
|
|
107
|
-
prettyPrint
|
|
148
|
+
prettyPrint,
|
|
149
|
+
enableLogging
|
|
108
150
|
};
|
|
109
151
|
};
|
|
110
152
|
return Object.assign(testClient, { init });
|
|
@@ -123,7 +165,7 @@ var singleClient = (options) => {
|
|
|
123
165
|
};
|
|
124
166
|
var multiClient = (options) => {
|
|
125
167
|
const server = setupRealtimeTestServer(options);
|
|
126
|
-
const clients =
|
|
168
|
+
const clients = toEntries(options.clients).reduce(
|
|
127
169
|
(clientRecord, [name, client]) => {
|
|
128
170
|
clientRecord[name] = setupRealtimeTestClient(
|
|
129
171
|
{ ...options, client },
|
|
@@ -139,7 +181,7 @@ var multiClient = (options) => {
|
|
|
139
181
|
server,
|
|
140
182
|
teardown: () => {
|
|
141
183
|
server.dispose();
|
|
142
|
-
for (const [, client] of
|
|
184
|
+
for (const [, client] of toEntries(clients)) {
|
|
143
185
|
client.dispose();
|
|
144
186
|
}
|
|
145
187
|
}
|
|
@@ -3,7 +3,8 @@ import * as http from "node:http"
|
|
|
3
3
|
import type { RenderResult } from "@testing-library/react"
|
|
4
4
|
import { prettyDOM, render } from "@testing-library/react"
|
|
5
5
|
import * as AtomIO from "atom.io"
|
|
6
|
-
import { editRelationsInStore } from "atom.io/data"
|
|
6
|
+
import { editRelationsInStore, findRelationsInStore } from "atom.io/data"
|
|
7
|
+
import type { Store } from "atom.io/internal"
|
|
7
8
|
import {
|
|
8
9
|
clearStore,
|
|
9
10
|
findInStore,
|
|
@@ -11,9 +12,10 @@ import {
|
|
|
11
12
|
IMPLICIT,
|
|
12
13
|
setIntoStore,
|
|
13
14
|
} from "atom.io/internal"
|
|
15
|
+
import { toEntries } from "atom.io/json"
|
|
14
16
|
import * as AR from "atom.io/react"
|
|
15
17
|
import * as RT from "atom.io/realtime"
|
|
16
|
-
import
|
|
18
|
+
import * as RTC from "atom.io/realtime-client"
|
|
17
19
|
import * as RTR from "atom.io/realtime-react"
|
|
18
20
|
import * as RTS from "atom.io/realtime-server"
|
|
19
21
|
import * as Happy from "happy-dom"
|
|
@@ -22,13 +24,29 @@ import * as SocketIO from "socket.io"
|
|
|
22
24
|
import type { Socket as ClientSocket } from "socket.io-client"
|
|
23
25
|
import { io } from "socket.io-client"
|
|
24
26
|
|
|
25
|
-
import { recordToEntries } from "~/packages/anvl/src/object"
|
|
26
|
-
|
|
27
27
|
let testNumber = 0
|
|
28
28
|
|
|
29
|
+
function prefixLogger(store: Store, prefix: string) {
|
|
30
|
+
store.loggers[0] = new AtomIO.AtomIOLogger(`info`, undefined, {
|
|
31
|
+
info: (...args) => {
|
|
32
|
+
console.info(prefix, ...args)
|
|
33
|
+
},
|
|
34
|
+
warn: (...args) => {
|
|
35
|
+
console.warn(prefix, ...args)
|
|
36
|
+
},
|
|
37
|
+
error: (...args) => {
|
|
38
|
+
console.error(prefix, ...args)
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
29
43
|
export type TestSetupOptions = {
|
|
30
44
|
port: number
|
|
31
|
-
server: (tools: {
|
|
45
|
+
server: (tools: {
|
|
46
|
+
socket: SocketIO.Socket
|
|
47
|
+
silo: AtomIO.Silo
|
|
48
|
+
enableLogging: () => void
|
|
49
|
+
}) => void
|
|
32
50
|
}
|
|
33
51
|
export type TestSetupOptions__SingleClient = TestSetupOptions & {
|
|
34
52
|
client: React.FC
|
|
@@ -47,6 +65,7 @@ export type RealtimeTestTools = {
|
|
|
47
65
|
export type RealtimeTestClient = RealtimeTestTools & {
|
|
48
66
|
renderResult: RenderResult
|
|
49
67
|
prettyPrint: () => void
|
|
68
|
+
enableLogging: () => void
|
|
50
69
|
socket: ClientSocket
|
|
51
70
|
}
|
|
52
71
|
export type RealtimeTestClientBuilder = {
|
|
@@ -57,6 +76,7 @@ export type RealtimeTestClientBuilder = {
|
|
|
57
76
|
export type RealtimeTestServer = RealtimeTestTools & {
|
|
58
77
|
dispose: () => void
|
|
59
78
|
port: number
|
|
79
|
+
// enableLogging: () => void
|
|
60
80
|
}
|
|
61
81
|
|
|
62
82
|
export type RealtimeTestAPI = {
|
|
@@ -108,7 +128,26 @@ export const setupRealtimeTestServer = (
|
|
|
108
128
|
})
|
|
109
129
|
|
|
110
130
|
server.on(`connection`, (socket: SocketIO.Socket) => {
|
|
111
|
-
|
|
131
|
+
let userKey: string | null = null
|
|
132
|
+
function enableLogging() {
|
|
133
|
+
const userKeyState = findRelationsInStore(
|
|
134
|
+
RTS.usersOfSockets,
|
|
135
|
+
socket.id,
|
|
136
|
+
silo.store,
|
|
137
|
+
).userKeyOfSocket
|
|
138
|
+
userKey = getFromStore(silo.store, userKeyState)
|
|
139
|
+
prefixLogger(silo.store, `server`)
|
|
140
|
+
socket.onAny((event, ...args) => {
|
|
141
|
+
console.log(`🛰 `, userKey, event, ...args)
|
|
142
|
+
})
|
|
143
|
+
socket.onAnyOutgoing((event, ...args) => {
|
|
144
|
+
console.log(`🛰 >>`, userKey, event, ...args)
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
options.server({ socket, enableLogging, silo })
|
|
148
|
+
socket.on(`disconnect`, () => {
|
|
149
|
+
console.log(`${userKey} disconnected`)
|
|
150
|
+
})
|
|
112
151
|
})
|
|
113
152
|
|
|
114
153
|
const dispose = () => {
|
|
@@ -147,7 +186,7 @@ export const setupRealtimeTestClient = (
|
|
|
147
186
|
silo.store.valueMap.set(key, [...value])
|
|
148
187
|
}
|
|
149
188
|
}
|
|
150
|
-
silo.setState(myUsernameState, `${name}-${testNumber}`)
|
|
189
|
+
silo.setState(RTC.myUsernameState, `${name}-${testNumber}`)
|
|
151
190
|
|
|
152
191
|
const { document } = new Happy.Window()
|
|
153
192
|
document.body.innerHTML = `<div id="app"></div>`
|
|
@@ -166,6 +205,16 @@ export const setupRealtimeTestClient = (
|
|
|
166
205
|
console.log(prettyDOM(renderResult.container))
|
|
167
206
|
}
|
|
168
207
|
|
|
208
|
+
const enableLogging = () => {
|
|
209
|
+
prefixLogger(silo.store, name)
|
|
210
|
+
socket.onAny((event, ...args) => {
|
|
211
|
+
console.log(`📡 `, name, event, ...args)
|
|
212
|
+
})
|
|
213
|
+
socket.onAnyOutgoing((event, ...args) => {
|
|
214
|
+
console.log(`📡 >>`, name, event, ...args)
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
169
218
|
const dispose = () => {
|
|
170
219
|
renderResult.unmount()
|
|
171
220
|
socket.disconnect()
|
|
@@ -179,6 +228,7 @@ export const setupRealtimeTestClient = (
|
|
|
179
228
|
socket,
|
|
180
229
|
renderResult,
|
|
181
230
|
prettyPrint,
|
|
231
|
+
enableLogging,
|
|
182
232
|
}
|
|
183
233
|
}
|
|
184
234
|
return Object.assign(testClient, { init })
|
|
@@ -204,7 +254,7 @@ export const multiClient = <ClientNames extends string>(
|
|
|
204
254
|
options: TestSetupOptions__MultiClient<ClientNames>,
|
|
205
255
|
): RealtimeTestAPI__MultiClient<ClientNames> => {
|
|
206
256
|
const server = setupRealtimeTestServer(options)
|
|
207
|
-
const clients =
|
|
257
|
+
const clients = toEntries(options.clients).reduce(
|
|
208
258
|
(clientRecord, [name, client]) => {
|
|
209
259
|
clientRecord[name] = setupRealtimeTestClient(
|
|
210
260
|
{ ...options, client },
|
|
@@ -221,7 +271,7 @@ export const multiClient = <ClientNames extends string>(
|
|
|
221
271
|
server,
|
|
222
272
|
teardown: () => {
|
|
223
273
|
server.dispose()
|
|
224
|
-
for (const [, client] of
|
|
274
|
+
for (const [, client] of toEntries(clients)) {
|
|
225
275
|
client.dispose()
|
|
226
276
|
}
|
|
227
277
|
},
|
package/src/silo.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { findState } from "atom.io/ephemeral"
|
|
2
2
|
import {
|
|
3
|
+
actUponStore,
|
|
4
|
+
arbitrary,
|
|
3
5
|
createAtomFamily,
|
|
4
6
|
createMoleculeFamily,
|
|
5
7
|
createSelectorFamily,
|
|
@@ -30,7 +32,7 @@ import type {
|
|
|
30
32
|
} from "."
|
|
31
33
|
import type { atom, atomFamily } from "./atom"
|
|
32
34
|
import type { selector, selectorFamily } from "./selector"
|
|
33
|
-
import type { transaction } from "./transaction"
|
|
35
|
+
import type { runTransaction, transaction } from "./transaction"
|
|
34
36
|
|
|
35
37
|
export class Silo {
|
|
36
38
|
public store: Store
|
|
@@ -49,6 +51,7 @@ export class Silo {
|
|
|
49
51
|
public redo: typeof redo
|
|
50
52
|
public moleculeFamily: typeof moleculeFamily
|
|
51
53
|
public makeMolecule: typeof makeMolecule
|
|
54
|
+
public runTransaction: typeof runTransaction
|
|
52
55
|
public constructor(config: Store[`config`], fromStore: Store | null = null) {
|
|
53
56
|
const s = new Store(config, fromStore)
|
|
54
57
|
this.store = s
|
|
@@ -83,8 +86,9 @@ export class Silo {
|
|
|
83
86
|
this.moleculeFamily = ((options: Parameters<typeof moleculeFamily>[0]) => {
|
|
84
87
|
return createMoleculeFamily(s, options)
|
|
85
88
|
}) as typeof moleculeFamily
|
|
86
|
-
this.makeMolecule = (
|
|
89
|
+
this.makeMolecule = (...params: Parameters<typeof makeMolecule>) => {
|
|
87
90
|
return makeMoleculeInStore(s, ...params)
|
|
88
|
-
}
|
|
91
|
+
}
|
|
92
|
+
this.runTransaction = (token, id = arbitrary()) => actUponStore(token, id, s)
|
|
89
93
|
}
|
|
90
94
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AtomEffect } from 'atom.io';
|
|
2
|
+
|
|
3
|
+
type StringInterface<T> = {
|
|
4
|
+
stringify: (t: T) => string;
|
|
5
|
+
parse: (s: string) => T;
|
|
6
|
+
};
|
|
7
|
+
declare const persistSync: <T>(storage: Storage, { stringify, parse }: StringInterface<T>, key: string) => AtomEffect<T>;
|
|
8
|
+
|
|
9
|
+
export { type StringInterface, persistSync };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import '../../dist/chunk-XWL6SNVU.js';
|
|
2
|
+
|
|
3
|
+
// web/src/persist-sync.ts
|
|
4
|
+
var persistSync = (storage, { stringify, parse }, key) => ({ setSelf, onSet }) => {
|
|
3
5
|
const savedValue = storage.getItem(key);
|
|
4
6
|
if (savedValue != null) setSelf(parse(savedValue));
|
|
5
7
|
onSet(({ newValue }) => {
|
|
@@ -10,6 +12,5 @@ var persistAtom = (storage) => ({ stringify, parse }) => (key) => ({ setSelf, on
|
|
|
10
12
|
storage.setItem(key, stringify(newValue));
|
|
11
13
|
});
|
|
12
14
|
};
|
|
13
|
-
var lazyLocalStorageEffect = persistAtom(window.localStorage)(JSON);
|
|
14
15
|
|
|
15
|
-
export {
|
|
16
|
+
export { persistSync };
|