atom.io 0.30.2 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +22 -22
- package/react-devtools/dist/index.js +1 -1
- package/react-devtools/src/AtomIODevtools.tsx +1 -1
- package/react-devtools/src/elastic-input/NumberInput.tsx +0 -1
- package/react-devtools/src/elastic-input/TextInput.tsx +0 -1
- package/realtime/dist/index.d.ts +2 -1
- package/realtime/src/realtime-continuity.ts +3 -2
- package/realtime-server/dist/index.d.ts +9 -8
- package/realtime-server/dist/index.js +279 -234
- package/realtime-server/src/continuity/prepare-to-send-initial-payload.ts +54 -0
- package/realtime-server/src/continuity/prepare-to-serve-transaction-request.ts +53 -0
- package/realtime-server/src/continuity/prepare-to-sync-realtime-continuity.ts +145 -0
- package/realtime-server/src/continuity/prepare-to-track-client-acknowledgement.ts +38 -0
- package/realtime-server/src/continuity/subscribe-to-continuity-actions.ts +106 -0
- package/realtime-server/src/continuity/subscribe-to-continuity-perpectives.ts +60 -0
- package/realtime-server/src/index.ts +1 -1
- package/realtime-server/src/realtime-server-stores/server-sync-store.ts +11 -5
- package/realtime-server/src/realtime-continuity-synchronizer.ts +0 -343
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import '../../dist/chunk-XWL6SNVU.js';
|
|
2
|
-
import { parseJson, stringifyJson } from 'atom.io/json';
|
|
3
|
-
import { Subject, IMPLICIT, getFromStore, subscribeToState, findInStore, getJsonToken, getUpdateToken, actUponStore, isRootStore, subscribeToTransaction, setIntoStore } from 'atom.io/internal';
|
|
4
|
-
import { SetRTX } from 'atom.io/transceivers/set-rtx';
|
|
5
2
|
import { editRelationsInStore, join, findRelationsInStore } from 'atom.io/data';
|
|
3
|
+
import { Subject, getFromStore, subscribeToState, findInStore, IMPLICIT, getJsonToken, getUpdateToken, isRootStore, actUponStore, setIntoStore, subscribeToTransaction } from 'atom.io/internal';
|
|
6
4
|
import * as AtomIO from 'atom.io';
|
|
7
5
|
import { atomFamily, selectorFamily, atom } from 'atom.io';
|
|
8
6
|
import { roomIndex, usersInRooms } from 'atom.io/realtime';
|
|
9
7
|
import { spawn } from 'node:child_process';
|
|
8
|
+
import { parseJson, stringifyJson } from 'atom.io/json';
|
|
9
|
+
import { SetRTX } from 'atom.io/transceivers/set-rtx';
|
|
10
10
|
|
|
11
11
|
// realtime-server/src/ipc-sockets/custom-socket.ts
|
|
12
12
|
var CustomSocket = class {
|
|
@@ -309,31 +309,8 @@ var ParentSocket = class extends CustomSocket {
|
|
|
309
309
|
this.relayServices.push(attachServices);
|
|
310
310
|
}
|
|
311
311
|
};
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
store = IMPLICIT.STORE
|
|
315
|
-
}) {
|
|
316
|
-
return function actionReceiver(tx) {
|
|
317
|
-
const fillTransactionRequest = (update) => {
|
|
318
|
-
const performanceKey = `tx-run:${tx.key}:${update.id}`;
|
|
319
|
-
const performanceKeyStart = `${performanceKey}:start`;
|
|
320
|
-
const performanceKeyEnd = `${performanceKey}:end`;
|
|
321
|
-
performance.mark(performanceKeyStart);
|
|
322
|
-
actUponStore(tx, update.id, store)(...update.params);
|
|
323
|
-
performance.mark(performanceKeyEnd);
|
|
324
|
-
const metric = performance.measure(
|
|
325
|
-
performanceKey,
|
|
326
|
-
performanceKeyStart,
|
|
327
|
-
performanceKeyEnd
|
|
328
|
-
);
|
|
329
|
-
store?.logger.info(`\u{1F680}`, `transaction`, tx.key, update.id, metric.duration);
|
|
330
|
-
};
|
|
331
|
-
socket.on(`tx-run:${tx.key}`, fillTransactionRequest);
|
|
332
|
-
return () => {
|
|
333
|
-
socket.off(`tx-run:${tx.key}`, fillTransactionRequest);
|
|
334
|
-
};
|
|
335
|
-
};
|
|
336
|
-
}
|
|
312
|
+
|
|
313
|
+
// realtime-server/src/realtime-server-stores/server-room-external-store.ts
|
|
337
314
|
var roomArgumentsAtoms = atomFamily({
|
|
338
315
|
key: `roomArguments`,
|
|
339
316
|
default: [`echo`, [`Hello World!`]]
|
|
@@ -439,8 +416,8 @@ function redactTransactionUpdateContent(visibleStateKeys, updates) {
|
|
|
439
416
|
}
|
|
440
417
|
});
|
|
441
418
|
}
|
|
442
|
-
var
|
|
443
|
-
key: `
|
|
419
|
+
var redactorAtoms = atomFamily({
|
|
420
|
+
key: `redactor`,
|
|
444
421
|
default: { occlude: (updates) => updates }
|
|
445
422
|
});
|
|
446
423
|
var userUnacknowledgedQueues = atomFamily({
|
|
@@ -472,13 +449,227 @@ var usersOfSockets = join({
|
|
|
472
449
|
isAType: (s) => s.startsWith(`user::`),
|
|
473
450
|
isBType: (s) => s.startsWith(`socket::`)
|
|
474
451
|
});
|
|
452
|
+
function prepareToSendInitialPayload(store, continuity, userKey, socket) {
|
|
453
|
+
const continuityKey = continuity.key;
|
|
454
|
+
return function sendInitialPayload() {
|
|
455
|
+
const initialPayload = [];
|
|
456
|
+
for (const atom2 of continuity.globals) {
|
|
457
|
+
const resourceToken = atom2.type === `mutable_atom` ? getJsonToken(store, atom2) : atom2;
|
|
458
|
+
const resource = getFromStore(store, resourceToken);
|
|
459
|
+
initialPayload.push(resourceToken, resource);
|
|
460
|
+
}
|
|
461
|
+
for (const perspective of continuity.perspectives) {
|
|
462
|
+
const { viewAtoms, resourceAtoms } = perspective;
|
|
463
|
+
const userViewState = findInStore(store, viewAtoms, userKey);
|
|
464
|
+
const userView = getFromStore(store, userViewState);
|
|
465
|
+
store.logger.info(`\u{1F441}`, `atom`, resourceAtoms.key, `${userKey} can see`, {
|
|
466
|
+
viewAtoms,
|
|
467
|
+
resourceAtoms,
|
|
468
|
+
userView
|
|
469
|
+
});
|
|
470
|
+
for (const visibleToken of userView) {
|
|
471
|
+
const resourceToken = visibleToken.type === `mutable_atom` ? getJsonToken(store, visibleToken) : visibleToken;
|
|
472
|
+
const resource = getFromStore(store, resourceToken);
|
|
473
|
+
initialPayload.push(resourceToken, resource);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const epoch = isRootStore(store) ? store.transactionMeta.epoch.get(continuityKey) ?? null : null;
|
|
477
|
+
socket?.emit(`continuity-init:${continuityKey}`, epoch, initialPayload);
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function prepareToServeTransactionRequest(store, continuity, userKey) {
|
|
481
|
+
const continuityKey = continuity.key;
|
|
482
|
+
return function serveTransactionRequest(update) {
|
|
483
|
+
store.logger.info(`\u{1F6CE}\uFE0F`, `continuity`, continuityKey, `received`, update);
|
|
484
|
+
const transactionKey = update.key;
|
|
485
|
+
const updateId = update.id;
|
|
486
|
+
const performanceKey = `tx-run:${transactionKey}:${updateId}`;
|
|
487
|
+
const performanceKeyStart = `${performanceKey}:start`;
|
|
488
|
+
const performanceKeyEnd = `${performanceKey}:end`;
|
|
489
|
+
performance.mark(performanceKeyStart);
|
|
490
|
+
try {
|
|
491
|
+
actUponStore(
|
|
492
|
+
{ type: `transaction`, key: transactionKey },
|
|
493
|
+
updateId,
|
|
494
|
+
store
|
|
495
|
+
)(...update.params);
|
|
496
|
+
} catch (thrown) {
|
|
497
|
+
if (thrown instanceof Error) {
|
|
498
|
+
store.logger.error(
|
|
499
|
+
`\u274C`,
|
|
500
|
+
`continuity`,
|
|
501
|
+
continuityKey,
|
|
502
|
+
`failed to run transaction ${transactionKey} from ${userKey} with update ${updateId}`,
|
|
503
|
+
thrown.message
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
performance.mark(performanceKeyEnd);
|
|
508
|
+
const metric = performance.measure(
|
|
509
|
+
performanceKey,
|
|
510
|
+
performanceKeyStart,
|
|
511
|
+
performanceKeyEnd
|
|
512
|
+
);
|
|
513
|
+
store?.logger.info(
|
|
514
|
+
`\u{1F680}`,
|
|
515
|
+
`transaction`,
|
|
516
|
+
transactionKey,
|
|
517
|
+
updateId,
|
|
518
|
+
userKey,
|
|
519
|
+
metric.duration
|
|
520
|
+
);
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function prepareToTrackClientAcknowledgement(store, continuity, userKey, userUnacknowledgedUpdates) {
|
|
524
|
+
const continuityKey = continuity.key;
|
|
525
|
+
return function trackClientAcknowledgement(epoch) {
|
|
526
|
+
store.logger.info(
|
|
527
|
+
`\u{1F44D}`,
|
|
528
|
+
`continuity`,
|
|
529
|
+
continuityKey,
|
|
530
|
+
`${userKey} acknowledged epoch ${epoch}`
|
|
531
|
+
);
|
|
532
|
+
const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch;
|
|
533
|
+
if (isUnacknowledged) {
|
|
534
|
+
setIntoStore(store, userUnacknowledgedQueues, userKey, (updates) => {
|
|
535
|
+
updates.shift();
|
|
536
|
+
store.logger.info(
|
|
537
|
+
`\u{1F44D}`,
|
|
538
|
+
`continuity`,
|
|
539
|
+
continuityKey,
|
|
540
|
+
`${userKey} unacknowledged update queue now has`,
|
|
541
|
+
updates.length,
|
|
542
|
+
`items`
|
|
543
|
+
);
|
|
544
|
+
return updates;
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function subscribeToContinuityActions(store, continuity, userKey, socket) {
|
|
550
|
+
const continuityKey = continuity.key;
|
|
551
|
+
const unsubscribeFunctions = [];
|
|
552
|
+
for (const transaction2 of continuity.actions) {
|
|
553
|
+
const unsubscribeFromTransaction = subscribeToTransaction(
|
|
554
|
+
transaction2,
|
|
555
|
+
(update) => {
|
|
556
|
+
try {
|
|
557
|
+
const visibleKeys = continuity.globals.map((atom2) => {
|
|
558
|
+
if (atom2.type === `atom`) {
|
|
559
|
+
return atom2.key;
|
|
560
|
+
}
|
|
561
|
+
return getUpdateToken(atom2).key;
|
|
562
|
+
}).concat(
|
|
563
|
+
continuity.perspectives.flatMap((perspective) => {
|
|
564
|
+
const { viewAtoms } = perspective;
|
|
565
|
+
const userPerspectiveTokenState = findInStore(
|
|
566
|
+
store,
|
|
567
|
+
viewAtoms,
|
|
568
|
+
userKey
|
|
569
|
+
);
|
|
570
|
+
const visibleTokens = getFromStore(
|
|
571
|
+
store,
|
|
572
|
+
userPerspectiveTokenState
|
|
573
|
+
);
|
|
574
|
+
return visibleTokens.map((token) => {
|
|
575
|
+
const key = token.type === `mutable_atom` ? `*` + token.key : token.key;
|
|
576
|
+
return key;
|
|
577
|
+
});
|
|
578
|
+
})
|
|
579
|
+
);
|
|
580
|
+
const redactedUpdates = redactTransactionUpdateContent(
|
|
581
|
+
visibleKeys,
|
|
582
|
+
update.updates
|
|
583
|
+
);
|
|
584
|
+
const redactedUpdate = {
|
|
585
|
+
...update,
|
|
586
|
+
updates: redactedUpdates
|
|
587
|
+
};
|
|
588
|
+
setIntoStore(store, userUnacknowledgedQueues, userKey, (updates) => {
|
|
589
|
+
if (redactedUpdate) {
|
|
590
|
+
updates.push(redactedUpdate);
|
|
591
|
+
updates.sort((a, b) => a.epoch - b.epoch);
|
|
592
|
+
store.logger.info(
|
|
593
|
+
`\u{1F44D}`,
|
|
594
|
+
`continuity`,
|
|
595
|
+
continuityKey,
|
|
596
|
+
`${userKey} unacknowledged update queue now has`,
|
|
597
|
+
updates.length,
|
|
598
|
+
`items`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return updates;
|
|
602
|
+
});
|
|
603
|
+
socket?.emit(
|
|
604
|
+
`tx-new:${continuityKey}`,
|
|
605
|
+
redactedUpdate
|
|
606
|
+
);
|
|
607
|
+
} catch (thrown) {
|
|
608
|
+
if (thrown instanceof Error) {
|
|
609
|
+
store.logger.error(
|
|
610
|
+
`\u274C`,
|
|
611
|
+
`continuity`,
|
|
612
|
+
continuityKey,
|
|
613
|
+
`${userKey} failed to send update from transaction ${transaction2.key} to ${userKey}`,
|
|
614
|
+
thrown.message
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
`sync-continuity:${continuityKey}:${userKey}`,
|
|
620
|
+
store
|
|
621
|
+
);
|
|
622
|
+
unsubscribeFunctions.push(unsubscribeFromTransaction);
|
|
623
|
+
}
|
|
624
|
+
return unsubscribeFunctions;
|
|
625
|
+
}
|
|
626
|
+
function subscribeToContinuityPerspectives(store, continuity, userKey, socket) {
|
|
627
|
+
const continuityKey = continuity.key;
|
|
628
|
+
const unsubFns = [];
|
|
629
|
+
for (const perspective of continuity.perspectives) {
|
|
630
|
+
const { viewAtoms } = perspective;
|
|
631
|
+
const userViewState = findInStore(store, viewAtoms, userKey);
|
|
632
|
+
const unsubscribeFromUserView = subscribeToState(
|
|
633
|
+
userViewState,
|
|
634
|
+
({ oldValue, newValue }) => {
|
|
635
|
+
const oldKeys = oldValue.map((token) => token.key);
|
|
636
|
+
const newKeys = newValue.map((token) => token.key);
|
|
637
|
+
const concealed = oldValue.filter(
|
|
638
|
+
(token) => !newKeys.includes(token.key)
|
|
639
|
+
);
|
|
640
|
+
const revealed = newValue.filter((token) => !oldKeys.includes(token.key)).flatMap((token) => {
|
|
641
|
+
const resourceToken = token.type === `mutable_atom` ? getJsonToken(store, token) : token;
|
|
642
|
+
const resource = getFromStore(store, resourceToken);
|
|
643
|
+
return [resourceToken, resource];
|
|
644
|
+
});
|
|
645
|
+
store.logger.info(
|
|
646
|
+
`\u{1F441}`,
|
|
647
|
+
`atom`,
|
|
648
|
+
perspective.resourceAtoms.key,
|
|
649
|
+
`${userKey} has a new perspective`,
|
|
650
|
+
{ oldKeys, newKeys, revealed, concealed }
|
|
651
|
+
);
|
|
652
|
+
if (revealed.length > 0) {
|
|
653
|
+
socket?.emit(`reveal:${continuityKey}`, revealed);
|
|
654
|
+
}
|
|
655
|
+
if (concealed.length > 0) {
|
|
656
|
+
socket?.emit(`conceal:${continuityKey}`, concealed);
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
`sync-continuity:${continuityKey}:${userKey}:perspective:${perspective.resourceAtoms.key}`,
|
|
660
|
+
store
|
|
661
|
+
);
|
|
662
|
+
unsubFns.push(unsubscribeFromUserView);
|
|
663
|
+
}
|
|
664
|
+
return unsubFns;
|
|
665
|
+
}
|
|
475
666
|
|
|
476
|
-
// realtime-server/src/realtime-continuity
|
|
477
|
-
function
|
|
667
|
+
// realtime-server/src/continuity/prepare-to-sync-realtime-continuity.ts
|
|
668
|
+
function prepareToExposeRealtimeContinuity({
|
|
478
669
|
socket: initialSocket,
|
|
479
670
|
store = IMPLICIT.STORE
|
|
480
671
|
}) {
|
|
481
|
-
return function
|
|
672
|
+
return function syncRealtimeContinuity(continuity) {
|
|
482
673
|
let socket = initialSocket;
|
|
483
674
|
const continuityKey = continuity.key;
|
|
484
675
|
const userKeyState = findRelationsInStore(
|
|
@@ -533,226 +724,80 @@ function realtimeContinuitySynchronizer({
|
|
|
533
724
|
`sync-continuity:${continuityKey}:${userKey}`,
|
|
534
725
|
store
|
|
535
726
|
);
|
|
536
|
-
const
|
|
727
|
+
const userUnacknowledgedUpdates = getFromStore(
|
|
537
728
|
store,
|
|
538
729
|
userUnacknowledgedQueues,
|
|
539
730
|
userKey
|
|
540
731
|
);
|
|
541
|
-
const
|
|
732
|
+
const unsubscribeFunctions = [];
|
|
733
|
+
const unsubscribeFromPerspectives = subscribeToContinuityPerspectives(
|
|
542
734
|
store,
|
|
543
|
-
|
|
735
|
+
continuity,
|
|
736
|
+
userKey,
|
|
737
|
+
socket
|
|
738
|
+
);
|
|
739
|
+
const unsubscribeFromTransactions = subscribeToContinuityActions(
|
|
740
|
+
store,
|
|
741
|
+
continuity,
|
|
742
|
+
userKey,
|
|
743
|
+
socket
|
|
744
|
+
);
|
|
745
|
+
unsubscribeFunctions.push(
|
|
746
|
+
...unsubscribeFromPerspectives,
|
|
747
|
+
...unsubscribeFromTransactions
|
|
748
|
+
);
|
|
749
|
+
const sendInitialPayload = prepareToSendInitialPayload(
|
|
750
|
+
store,
|
|
751
|
+
continuity,
|
|
752
|
+
userKey,
|
|
753
|
+
initialSocket
|
|
544
754
|
);
|
|
545
|
-
const unsubscribeFunctions = [];
|
|
546
|
-
const revealPerspectives = () => {
|
|
547
|
-
const unsubFns = [];
|
|
548
|
-
for (const perspective of continuity.perspectives) {
|
|
549
|
-
const { viewAtoms } = perspective;
|
|
550
|
-
const userViewState = findInStore(store, viewAtoms, userKey);
|
|
551
|
-
const unsubscribe = subscribeToState(
|
|
552
|
-
userViewState,
|
|
553
|
-
({ oldValue, newValue }) => {
|
|
554
|
-
const oldKeys = oldValue.map((token) => token.key);
|
|
555
|
-
const newKeys = newValue.map((token) => token.key);
|
|
556
|
-
const concealed = oldValue.filter(
|
|
557
|
-
(token) => !newKeys.includes(token.key)
|
|
558
|
-
);
|
|
559
|
-
const revealed = newValue.filter((token) => !oldKeys.includes(token.key)).flatMap((token) => {
|
|
560
|
-
const resourceToken = token.type === `mutable_atom` ? getJsonToken(store, token) : token;
|
|
561
|
-
const resource = getFromStore(store, resourceToken);
|
|
562
|
-
return [resourceToken, resource];
|
|
563
|
-
});
|
|
564
|
-
store.logger.info(
|
|
565
|
-
`\u{1F441}`,
|
|
566
|
-
`atom`,
|
|
567
|
-
perspective.resourceAtoms.key,
|
|
568
|
-
`${userKey} has a new perspective`,
|
|
569
|
-
{ oldKeys, newKeys, revealed, concealed }
|
|
570
|
-
);
|
|
571
|
-
if (revealed.length > 0) {
|
|
572
|
-
socket?.emit(`reveal:${continuityKey}`, revealed);
|
|
573
|
-
}
|
|
574
|
-
if (concealed.length > 0) {
|
|
575
|
-
socket?.emit(`conceal:${continuityKey}`, concealed);
|
|
576
|
-
}
|
|
577
|
-
},
|
|
578
|
-
`sync-continuity:${continuityKey}:${userKey}:perspective:${perspective.resourceAtoms.key}`,
|
|
579
|
-
store
|
|
580
|
-
);
|
|
581
|
-
unsubFns.push(unsubscribe);
|
|
582
|
-
}
|
|
583
|
-
return () => {
|
|
584
|
-
for (const unsubscribe of unsubFns) unsubscribe();
|
|
585
|
-
};
|
|
586
|
-
};
|
|
587
|
-
const unsubscribeFromPerspectives = revealPerspectives();
|
|
588
|
-
const sendInitialPayload = () => {
|
|
589
|
-
const initialPayload = [];
|
|
590
|
-
for (const atom2 of continuity.globals) {
|
|
591
|
-
const resourceToken = atom2.type === `mutable_atom` ? getJsonToken(store, atom2) : atom2;
|
|
592
|
-
const resource = getFromStore(store, resourceToken);
|
|
593
|
-
initialPayload.push(resourceToken, resource);
|
|
594
|
-
}
|
|
595
|
-
for (const perspective of continuity.perspectives) {
|
|
596
|
-
const { viewAtoms, resourceAtoms } = perspective;
|
|
597
|
-
const userViewState = findInStore(store, viewAtoms, userKey);
|
|
598
|
-
const userView = getFromStore(store, userViewState);
|
|
599
|
-
store.logger.info(`\u{1F441}`, `atom`, resourceAtoms.key, `${userKey} can see`, {
|
|
600
|
-
viewAtoms,
|
|
601
|
-
resourceAtoms,
|
|
602
|
-
userView
|
|
603
|
-
});
|
|
604
|
-
for (const visibleToken of userView) {
|
|
605
|
-
const resourceToken = visibleToken.type === `mutable_atom` ? getJsonToken(store, visibleToken) : visibleToken;
|
|
606
|
-
const resource = getFromStore(store, resourceToken);
|
|
607
|
-
initialPayload.push(resourceToken, resource);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
const epoch = isRootStore(store) ? store.transactionMeta.epoch.get(continuityKey) ?? null : null;
|
|
611
|
-
socket?.emit(`continuity-init:${continuityKey}`, epoch, initialPayload);
|
|
612
|
-
for (const transaction2 of continuity.actions) {
|
|
613
|
-
const unsubscribeFromTransaction = subscribeToTransaction(
|
|
614
|
-
transaction2,
|
|
615
|
-
(update) => {
|
|
616
|
-
try {
|
|
617
|
-
const visibleKeys = continuity.globals.map((atom2) => {
|
|
618
|
-
if (atom2.type === `atom`) {
|
|
619
|
-
return atom2.key;
|
|
620
|
-
}
|
|
621
|
-
return getUpdateToken(atom2).key;
|
|
622
|
-
}).concat(
|
|
623
|
-
continuity.perspectives.flatMap((perspective) => {
|
|
624
|
-
const { viewAtoms } = perspective;
|
|
625
|
-
const userPerspectiveTokenState = findInStore(
|
|
626
|
-
store,
|
|
627
|
-
viewAtoms,
|
|
628
|
-
userKey
|
|
629
|
-
);
|
|
630
|
-
const visibleTokens = getFromStore(
|
|
631
|
-
store,
|
|
632
|
-
userPerspectiveTokenState
|
|
633
|
-
);
|
|
634
|
-
return visibleTokens.map((token) => {
|
|
635
|
-
const key = token.type === `mutable_atom` ? `*` + token.key : token.key;
|
|
636
|
-
return key;
|
|
637
|
-
});
|
|
638
|
-
})
|
|
639
|
-
);
|
|
640
|
-
const redactedUpdates = redactTransactionUpdateContent(
|
|
641
|
-
visibleKeys,
|
|
642
|
-
update.updates
|
|
643
|
-
);
|
|
644
|
-
const redactedUpdate = {
|
|
645
|
-
...update,
|
|
646
|
-
updates: redactedUpdates
|
|
647
|
-
};
|
|
648
|
-
setIntoStore(store, userUnacknowledgedQueue, (updates) => {
|
|
649
|
-
if (redactedUpdate) {
|
|
650
|
-
updates.push(redactedUpdate);
|
|
651
|
-
updates.sort((a, b) => a.epoch - b.epoch);
|
|
652
|
-
}
|
|
653
|
-
return updates;
|
|
654
|
-
});
|
|
655
|
-
socket?.emit(
|
|
656
|
-
`tx-new:${continuityKey}`,
|
|
657
|
-
redactedUpdate
|
|
658
|
-
);
|
|
659
|
-
} catch (thrown) {
|
|
660
|
-
if (thrown instanceof Error) {
|
|
661
|
-
store.logger.error(
|
|
662
|
-
`\u274C`,
|
|
663
|
-
`continuity`,
|
|
664
|
-
continuityKey,
|
|
665
|
-
`failed to send update from transaction ${transaction2.key} to ${userKey}`,
|
|
666
|
-
thrown.message
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
`sync-continuity:${continuityKey}:${userKey}`,
|
|
672
|
-
store
|
|
673
|
-
);
|
|
674
|
-
unsubscribeFunctions.push(unsubscribeFromTransaction);
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
755
|
socket.off(`get:${continuityKey}`, sendInitialPayload);
|
|
678
756
|
socket.on(`get:${continuityKey}`, sendInitialPayload);
|
|
757
|
+
const fillTransactionRequest = prepareToServeTransactionRequest(
|
|
758
|
+
store,
|
|
759
|
+
continuity,
|
|
760
|
+
userKey
|
|
761
|
+
);
|
|
762
|
+
socket.off(`tx-run:${continuityKey}`, fillTransactionRequest);
|
|
763
|
+
socket.on(`tx-run:${continuityKey}`, fillTransactionRequest);
|
|
764
|
+
const trackClientAcknowledgement = prepareToTrackClientAcknowledgement(
|
|
765
|
+
store,
|
|
766
|
+
continuity,
|
|
767
|
+
userKey,
|
|
768
|
+
userUnacknowledgedUpdates
|
|
769
|
+
);
|
|
770
|
+
socket?.on(`ack:${continuityKey}`, trackClientAcknowledgement);
|
|
771
|
+
return () => {
|
|
772
|
+
for (const unsubscribe of unsubscribeFunctions) unsubscribe();
|
|
773
|
+
socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement);
|
|
774
|
+
socket?.off(`get:${continuityKey}`, sendInitialPayload);
|
|
775
|
+
socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest);
|
|
776
|
+
};
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function realtimeActionReceiver({
|
|
780
|
+
socket,
|
|
781
|
+
store = IMPLICIT.STORE
|
|
782
|
+
}) {
|
|
783
|
+
return function actionReceiver(tx) {
|
|
679
784
|
const fillTransactionRequest = (update) => {
|
|
680
|
-
|
|
681
|
-
const transactionKey = update.key;
|
|
682
|
-
const updateId = update.id;
|
|
683
|
-
const performanceKey = `tx-run:${transactionKey}:${updateId}`;
|
|
785
|
+
const performanceKey = `tx-run:${tx.key}:${update.id}`;
|
|
684
786
|
const performanceKeyStart = `${performanceKey}:start`;
|
|
685
787
|
const performanceKeyEnd = `${performanceKey}:end`;
|
|
686
788
|
performance.mark(performanceKeyStart);
|
|
687
|
-
|
|
688
|
-
actUponStore(
|
|
689
|
-
{ type: `transaction`, key: transactionKey },
|
|
690
|
-
updateId,
|
|
691
|
-
store
|
|
692
|
-
)(...update.params);
|
|
693
|
-
} catch (thrown) {
|
|
694
|
-
if (thrown instanceof Error) {
|
|
695
|
-
store.logger.error(
|
|
696
|
-
`\u274C`,
|
|
697
|
-
`continuity`,
|
|
698
|
-
continuityKey,
|
|
699
|
-
`failed to run transaction ${transactionKey} with update ${updateId}`,
|
|
700
|
-
thrown.message
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
789
|
+
actUponStore(tx, update.id, store)(...update.params);
|
|
704
790
|
performance.mark(performanceKeyEnd);
|
|
705
791
|
const metric = performance.measure(
|
|
706
792
|
performanceKey,
|
|
707
793
|
performanceKeyStart,
|
|
708
794
|
performanceKeyEnd
|
|
709
795
|
);
|
|
710
|
-
store?.logger.info(
|
|
711
|
-
`\u{1F680}`,
|
|
712
|
-
`transaction`,
|
|
713
|
-
transactionKey,
|
|
714
|
-
updateId,
|
|
715
|
-
metric.duration
|
|
716
|
-
);
|
|
717
|
-
const valuesOfCardsViewKey = `valuesOfCardsView("${userKey}")`;
|
|
718
|
-
const rootsOfCardValueView = store.selectorAtoms.getRelatedKeys(valuesOfCardsViewKey);
|
|
719
|
-
const myCardValueView = store.valueMap.get(valuesOfCardsViewKey);
|
|
720
|
-
store.logger.info(
|
|
721
|
-
`\u{1F441}`,
|
|
722
|
-
`continuity`,
|
|
723
|
-
continuityKey,
|
|
724
|
-
`seeing ${userKey} card values`,
|
|
725
|
-
{
|
|
726
|
-
valuesOfCardsViewKey,
|
|
727
|
-
rootsOfCardValueView,
|
|
728
|
-
myCardValueView
|
|
729
|
-
}
|
|
730
|
-
);
|
|
731
|
-
};
|
|
732
|
-
socket.off(`tx-run:${continuityKey}`, fillTransactionRequest);
|
|
733
|
-
socket.on(`tx-run:${continuityKey}`, fillTransactionRequest);
|
|
734
|
-
const trackClientAcknowledgement = (epoch) => {
|
|
735
|
-
store.logger.info(
|
|
736
|
-
`\u{1F44D}`,
|
|
737
|
-
`continuity`,
|
|
738
|
-
continuityKey,
|
|
739
|
-
`${userKey} acknowledged epoch ${epoch}`
|
|
740
|
-
);
|
|
741
|
-
const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch;
|
|
742
|
-
if (isUnacknowledged) {
|
|
743
|
-
setIntoStore(store, userUnacknowledgedQueue, (updates) => {
|
|
744
|
-
updates.shift();
|
|
745
|
-
return updates;
|
|
746
|
-
});
|
|
747
|
-
}
|
|
796
|
+
store?.logger.info(`\u{1F680}`, `transaction`, tx.key, update.id, metric.duration);
|
|
748
797
|
};
|
|
749
|
-
socket
|
|
798
|
+
socket.on(`tx-run:${tx.key}`, fillTransactionRequest);
|
|
750
799
|
return () => {
|
|
751
|
-
|
|
752
|
-
socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement);
|
|
753
|
-
unsubscribeFromPerspectives();
|
|
754
|
-
socket?.off(`get:${continuityKey}`, sendInitialPayload);
|
|
755
|
-
socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest);
|
|
800
|
+
socket.off(`tx-run:${tx.key}`, fillTransactionRequest);
|
|
756
801
|
};
|
|
757
802
|
};
|
|
758
803
|
}
|
|
@@ -942,4 +987,4 @@ function realtimeStateReceiver({
|
|
|
942
987
|
};
|
|
943
988
|
}
|
|
944
989
|
|
|
945
|
-
export { ChildSocket, CustomSocket, ParentSocket, SubjectSocket,
|
|
990
|
+
export { ChildSocket, CustomSocket, ParentSocket, SubjectSocket, createRoomTX, destroyRoomTX, joinRoomTX, leaveRoomTX, prepareToExposeRealtimeContinuity, realtimeActionReceiver, realtimeAtomFamilyProvider, realtimeMutableFamilyProvider, realtimeMutableProvider, realtimeStateProvider, realtimeStateReceiver, redactTransactionUpdateContent, redactorAtoms, roomArgumentsAtoms, roomSelectors, socketAtoms, socketIndex, userIndex, userUnacknowledgedQueues, usersOfSockets };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Store } from "atom.io/internal"
|
|
2
|
+
import {
|
|
3
|
+
findInStore,
|
|
4
|
+
getFromStore,
|
|
5
|
+
getJsonToken,
|
|
6
|
+
isRootStore,
|
|
7
|
+
} from "atom.io/internal"
|
|
8
|
+
import type { Json } from "atom.io/json"
|
|
9
|
+
import type { ContinuityToken } from "atom.io/realtime"
|
|
10
|
+
|
|
11
|
+
import type { Socket, UserKey } from ".."
|
|
12
|
+
|
|
13
|
+
export function prepareToSendInitialPayload(
|
|
14
|
+
store: Store,
|
|
15
|
+
continuity: ContinuityToken,
|
|
16
|
+
userKey: UserKey,
|
|
17
|
+
socket: Socket | null,
|
|
18
|
+
): () => void {
|
|
19
|
+
const continuityKey = continuity.key
|
|
20
|
+
return function sendInitialPayload(): void {
|
|
21
|
+
const initialPayload: Json.Serializable[] = []
|
|
22
|
+
for (const atom of continuity.globals) {
|
|
23
|
+
const resourceToken =
|
|
24
|
+
atom.type === `mutable_atom` ? getJsonToken(store, atom) : atom
|
|
25
|
+
const resource = getFromStore(store, resourceToken)
|
|
26
|
+
initialPayload.push(resourceToken, resource)
|
|
27
|
+
}
|
|
28
|
+
for (const perspective of continuity.perspectives) {
|
|
29
|
+
const { viewAtoms, resourceAtoms } = perspective
|
|
30
|
+
const userViewState = findInStore(store, viewAtoms, userKey)
|
|
31
|
+
const userView = getFromStore(store, userViewState)
|
|
32
|
+
store.logger.info(`👁`, `atom`, resourceAtoms.key, `${userKey} can see`, {
|
|
33
|
+
viewAtoms,
|
|
34
|
+
resourceAtoms,
|
|
35
|
+
userView,
|
|
36
|
+
})
|
|
37
|
+
for (const visibleToken of userView) {
|
|
38
|
+
const resourceToken =
|
|
39
|
+
visibleToken.type === `mutable_atom`
|
|
40
|
+
? getJsonToken(store, visibleToken)
|
|
41
|
+
: visibleToken
|
|
42
|
+
const resource = getFromStore(store, resourceToken)
|
|
43
|
+
|
|
44
|
+
initialPayload.push(resourceToken, resource)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const epoch = isRootStore(store)
|
|
49
|
+
? (store.transactionMeta.epoch.get(continuityKey) ?? null)
|
|
50
|
+
: null
|
|
51
|
+
|
|
52
|
+
socket?.emit(`continuity-init:${continuityKey}`, epoch, initialPayload)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { TransactionUpdate } from "atom.io"
|
|
2
|
+
import type { Store } from "atom.io/internal"
|
|
3
|
+
import { actUponStore } from "atom.io/internal"
|
|
4
|
+
import type { JsonIO } from "atom.io/json"
|
|
5
|
+
import type { ContinuityToken } from "atom.io/realtime"
|
|
6
|
+
|
|
7
|
+
export function prepareToServeTransactionRequest(
|
|
8
|
+
store: Store,
|
|
9
|
+
continuity: ContinuityToken,
|
|
10
|
+
userKey: string,
|
|
11
|
+
): (update: Pick<TransactionUpdate<JsonIO>, `id` | `key` | `params`>) => void {
|
|
12
|
+
const continuityKey = continuity.key
|
|
13
|
+
return function serveTransactionRequest(update) {
|
|
14
|
+
store.logger.info(`🛎️`, `continuity`, continuityKey, `received`, update)
|
|
15
|
+
const transactionKey = update.key
|
|
16
|
+
const updateId = update.id
|
|
17
|
+
const performanceKey = `tx-run:${transactionKey}:${updateId}`
|
|
18
|
+
const performanceKeyStart = `${performanceKey}:start`
|
|
19
|
+
const performanceKeyEnd = `${performanceKey}:end`
|
|
20
|
+
performance.mark(performanceKeyStart)
|
|
21
|
+
try {
|
|
22
|
+
actUponStore(
|
|
23
|
+
{ type: `transaction`, key: transactionKey },
|
|
24
|
+
updateId,
|
|
25
|
+
store,
|
|
26
|
+
)(...update.params)
|
|
27
|
+
} catch (thrown) {
|
|
28
|
+
if (thrown instanceof Error) {
|
|
29
|
+
store.logger.error(
|
|
30
|
+
`❌`,
|
|
31
|
+
`continuity`,
|
|
32
|
+
continuityKey,
|
|
33
|
+
`failed to run transaction ${transactionKey} from ${userKey} with update ${updateId}`,
|
|
34
|
+
thrown.message,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
performance.mark(performanceKeyEnd)
|
|
39
|
+
const metric = performance.measure(
|
|
40
|
+
performanceKey,
|
|
41
|
+
performanceKeyStart,
|
|
42
|
+
performanceKeyEnd,
|
|
43
|
+
)
|
|
44
|
+
store?.logger.info(
|
|
45
|
+
`🚀`,
|
|
46
|
+
`transaction`,
|
|
47
|
+
transactionKey,
|
|
48
|
+
updateId,
|
|
49
|
+
userKey,
|
|
50
|
+
metric.duration,
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}
|