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.
Files changed (67) hide show
  1. package/data/dist/index.d.ts +31 -29
  2. package/data/dist/index.js +16 -17
  3. package/data/src/join.ts +17 -19
  4. package/dist/{chunk-6ABWLAGY.js → chunk-BX3MTH2Z.js} +320 -249
  5. package/dist/chunk-D52JNVER.js +721 -0
  6. package/dist/chunk-EUVKUTW3.js +89 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +3 -1
  9. package/internal/dist/index.d.ts +72 -36
  10. package/internal/dist/index.js +1 -1
  11. package/internal/src/atom/dispose-atom.ts +2 -9
  12. package/internal/src/families/dispose-from-store.ts +35 -18
  13. package/internal/src/families/find-in-store.ts +17 -7
  14. package/internal/src/get-state/get-from-store.ts +41 -32
  15. package/internal/src/ingest-updates/ingest-creation-disposal.ts +10 -1
  16. package/internal/src/molecule/dispose-molecule.ts +6 -17
  17. package/internal/src/pretty-print.ts +1 -16
  18. package/internal/src/selector/dispose-selector.ts +2 -9
  19. package/internal/src/set-state/set-into-store.ts +17 -19
  20. package/internal/src/store/circular-buffer.ts +34 -0
  21. package/internal/src/store/counterfeit.ts +109 -0
  22. package/internal/src/store/deposit.ts +14 -0
  23. package/internal/src/store/index.ts +1 -0
  24. package/internal/src/store/store.ts +3 -0
  25. package/internal/src/store/withdraw.ts +15 -10
  26. package/internal/src/transaction/build-transaction.ts +1 -0
  27. package/introspection/dist/index.d.ts +84 -4
  28. package/introspection/dist/index.js +1 -392
  29. package/introspection/src/attach-introspection-states.ts +7 -4
  30. package/introspection/src/attach-type-selectors.ts +26 -0
  31. package/introspection/src/differ.ts +167 -0
  32. package/introspection/src/index.ts +2 -0
  33. package/introspection/src/refinery.ts +100 -0
  34. package/json/dist/index.d.ts +31 -30
  35. package/json/dist/index.js +2 -77
  36. package/json/src/entries.ts +6 -0
  37. package/json/src/index.ts +47 -6
  38. package/package.json +17 -8
  39. package/react-devtools/dist/index.d.ts +1 -91
  40. package/react-devtools/dist/index.js +285 -414
  41. package/react-devtools/src/AtomIODevtools.tsx +2 -2
  42. package/react-devtools/src/StateEditor.tsx +20 -12
  43. package/react-devtools/src/StateIndex.tsx +8 -26
  44. package/react-devtools/src/TimelineIndex.tsx +3 -3
  45. package/react-devtools/src/TransactionIndex.tsx +6 -6
  46. package/react-devtools/src/Updates.tsx +1 -4
  47. package/react-devtools/src/index.ts +0 -71
  48. package/react-devtools/src/store.ts +51 -0
  49. package/realtime/dist/index.d.ts +7 -7
  50. package/realtime/dist/index.js +18 -22
  51. package/realtime/src/realtime-continuity.ts +27 -35
  52. package/realtime-client/dist/index.js +24 -10
  53. package/realtime-client/src/realtime-client-stores/client-main-store.ts +6 -6
  54. package/realtime-client/src/sync-continuity.ts +28 -6
  55. package/realtime-server/dist/index.js +41 -5
  56. package/realtime-server/src/realtime-continuity-synchronizer.ts +42 -78
  57. package/realtime-testing/dist/index.d.ts +2 -0
  58. package/realtime-testing/dist/index.js +50 -8
  59. package/realtime-testing/src/setup-realtime-test.tsx +59 -9
  60. package/src/silo.ts +7 -3
  61. package/web/dist/index.d.ts +9 -0
  62. package/{dist/chunk-H6EDLPKH.js → web/dist/index.js} +5 -4
  63. package/web/package.json +13 -0
  64. package/web/src/index.ts +1 -0
  65. package/web/src/persist-sync.ts +25 -0
  66. package/dist/chunk-AK23DRMD.js +0 -21
  67. 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 { Json } from "atom.io/json"
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 = null
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 = null
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
- setIntoStore(k, v, store)
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.error(
513
+ store.logger.warn(
514
514
  `\u274C`,
515
515
  `continuity`,
516
516
  continuityKey,
517
- `Tried to create a synchronizer for a user (${userKey}) that is not connected to a socket.`
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
- initialPayload.push(resourceToken, getFromStore(store, atom2));
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) => atom2.key).concat(
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.error(
67
+ store.logger.warn(
66
68
  `❌`,
67
69
  `continuity`,
68
70
  continuityKey,
69
- `Tried to create a synchronizer for a user (${userKey}) that is not connected to a socket.`,
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
- initialPayload.push(resourceToken, getFromStore(store, atom))
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) => atom.key)
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
- // setIntoStore(
210
- // userUnacknowledgedQueue,
211
- // (updates) => {
212
- // if (redactedUpdate) {
213
- // updates.push(redactedUpdate)
214
- // updates.sort((a, b) => a.epoch - b.epoch)
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
- // let i = 0
308
- // let n = 1
309
- // let retryTimeout: NodeJS.Timeout | undefined
310
- // const trackClientAcknowledgement = (epoch: number) => {
311
- // store.logger.info(
312
- // `👍`,
313
- // `continuity`,
314
- // continuityKey,
315
- // `${userKey} acknowledged epoch ${epoch}`,
316
- // )
317
- // const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch
318
- // if (isUnacknowledged) {
319
- // setIntoStore(
320
- // userUnacknowledgedQueue,
321
- // (updates) => {
322
- // updates.shift()
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
- // socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
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 { myUsernameState } from 'atom.io/realtime-client';
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
- options.server({ socket, silo });
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 = recordToEntries(options.clients).reduce(
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 recordToEntries(clients)) {
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 { myUsernameState } from "atom.io/realtime-client"
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: { socket: SocketIO.Socket; silo: AtomIO.Silo }) => void
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
- options.server({ socket, silo })
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 = recordToEntries(options.clients).reduce(
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 recordToEntries(clients)) {
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 = ((...params: Parameters<typeof makeMolecule>) => {
89
+ this.makeMolecule = (...params: Parameters<typeof makeMolecule>) => {
87
90
  return makeMoleculeInStore(s, ...params)
88
- }) as typeof makeMolecule
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
- // __unstable__/web-effects/src/storage.ts
2
- var persistAtom = (storage) => ({ stringify, parse }) => (key) => ({ setSelf, onSet }) => {
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 { lazyLocalStorageEffect };
16
+ export { persistSync };