atom.io 0.29.5 → 0.30.1

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 (44) hide show
  1. package/data/dist/index.d.ts +84 -51
  2. package/data/dist/index.js +35 -31
  3. package/data/src/join.ts +240 -134
  4. package/dist/chunk-ADMEAXYU.js +167 -0
  5. package/dist/{chunk-TCINPEYE.js → chunk-SMKF3ZNG.js} +221 -137
  6. package/dist/index.d.ts +75 -10
  7. package/dist/index.js +3 -17
  8. package/internal/dist/index.d.ts +76 -41
  9. package/internal/dist/index.js +2 -1
  10. package/internal/src/atom/dispose-atom.ts +4 -8
  11. package/internal/src/index.ts +1 -1
  12. package/internal/src/ingest-updates/ingest-creation-disposal.ts +71 -27
  13. package/internal/src/junction.ts +152 -84
  14. package/internal/src/molecule/create-molecule-family.ts +2 -2
  15. package/internal/src/molecule/dispose-molecule.ts +4 -2
  16. package/internal/src/molecule/make-molecule-in-store.ts +11 -9
  17. package/internal/src/molecule/molecule-internal.ts +12 -8
  18. package/internal/src/mutable/create-mutable-atom-family.ts +2 -2
  19. package/internal/src/mutable/get-json-family.ts +2 -2
  20. package/internal/src/store/store.ts +10 -2
  21. package/internal/src/timeline/create-timeline.ts +99 -71
  22. package/internal/src/transaction/index.ts +1 -1
  23. package/internal/src/utility-types.ts +9 -2
  24. package/json/dist/index.d.ts +3 -3
  25. package/json/dist/index.js +2 -1
  26. package/json/src/entries.ts +3 -3
  27. package/package.json +15 -15
  28. package/react-devtools/dist/index.js +9 -5
  29. package/react-devtools/src/TimelineIndex.tsx +4 -1
  30. package/react-devtools/src/Updates.tsx +18 -3
  31. package/realtime/dist/index.d.ts +1 -1
  32. package/realtime/dist/index.js +3 -1
  33. package/realtime/src/shared-room-store.ts +2 -0
  34. package/realtime-server/dist/index.d.ts +13 -4
  35. package/realtime-server/dist/index.js +4 -2
  36. package/realtime-server/src/realtime-continuity-synchronizer.ts +2 -2
  37. package/realtime-server/src/realtime-server-stores/server-user-store.ts +17 -1
  38. package/realtime-testing/dist/index.d.ts +3 -0
  39. package/realtime-testing/dist/index.js +11 -4
  40. package/realtime-testing/src/setup-realtime-test.tsx +12 -4
  41. package/src/allocate.ts +277 -0
  42. package/src/index.ts +1 -0
  43. package/src/molecule.ts +9 -5
  44. package/src/transaction.ts +22 -4
@@ -25,6 +25,7 @@ import { newest } from "../lineage"
25
25
  import { getUpdateToken } from "../mutable"
26
26
  import { type Store, withdraw } from "../store"
27
27
  import { Subject } from "../subject"
28
+ import { isChildStore } from "../transaction"
28
29
  import type { Flat, Func } from "../utility-types"
29
30
 
30
31
  export type TimelineAtomUpdate<ManagedAtom extends TimelineManageable> = Flat<
@@ -371,74 +372,81 @@ function addMoleculeFamilyToTimeline(
371
372
  `got a molecule creation or disposal`,
372
373
  creationOrDisposal,
373
374
  )
374
- switch (creationOrDisposal.type) {
375
- case `molecule_creation`:
376
- {
377
- store.timelineTopics.set(
378
- {
379
- topicKey: creationOrDisposal.token.key,
380
- timelineKey: tl.key,
381
- },
382
- { topicType: `molecule` },
383
- )
384
- const txUpdateInProgress =
385
- newest(store).on.transactionApplying.state?.update
386
- if (txUpdateInProgress) {
387
- joinTransaction(tl, txUpdateInProgress, store)
388
- } else if (tl.timeTraveling === null) {
389
- const event = Object.assign(creationOrDisposal, {
390
- timestamp: Date.now(),
391
- })
392
- tl.history.push(event)
393
- tl.at = tl.history.length
394
- tl.subject.next(event)
395
- }
396
- const molecule = withdraw(creationOrDisposal.token, store)
375
+ if (creationOrDisposal.subType === `classic`) {
376
+ switch (creationOrDisposal.type) {
377
+ case `molecule_creation`:
378
+ {
379
+ store.timelineTopics.set(
380
+ {
381
+ topicKey: creationOrDisposal.token.key,
382
+ timelineKey: tl.key,
383
+ },
384
+ { topicType: `molecule` },
385
+ )
386
+ const txUpdateInProgress =
387
+ newest(store).on.transactionApplying.state?.update
388
+ if (txUpdateInProgress) {
389
+ joinTransaction(tl, txUpdateInProgress, store)
390
+ } else if (tl.timeTraveling === null) {
391
+ const event = Object.assign(creationOrDisposal, {
392
+ timestamp: Date.now(),
393
+ })
394
+ tl.history.push(event)
395
+ tl.at = tl.history.length
396
+ tl.subject.next(event)
397
+ }
398
+ const molecule = withdraw(creationOrDisposal.token, store)
397
399
 
398
- for (const token of molecule.tokens.values()) {
399
- switch (token.type) {
400
- case `atom`:
401
- case `mutable_atom`:
402
- addAtomToTimeline(token, tl, store)
403
- break
400
+ for (const token of molecule.tokens.values()) {
401
+ switch (token.type) {
402
+ case `atom`:
403
+ case `mutable_atom`:
404
+ addAtomToTimeline(token, tl, store)
405
+ break
406
+ }
404
407
  }
408
+ tl.subscriptions.set(
409
+ molecule.key,
410
+ molecule.subject.subscribe(
411
+ `timeline:${tl.key}`,
412
+ (stateCreationOrDisposal) => {
413
+ handleStateLifecycleEvent(
414
+ stateCreationOrDisposal,
415
+ tl,
416
+ store,
417
+ )
418
+ },
419
+ ),
420
+ )
405
421
  }
406
- tl.subscriptions.set(
407
- molecule.key,
408
- molecule.subject.subscribe(
409
- `timeline:${tl.key}`,
410
- (stateCreationOrDisposal) => {
411
- handleStateLifecycleEvent(stateCreationOrDisposal, tl, store)
412
- },
413
- ),
414
- )
415
- }
416
- break
417
- case `molecule_disposal`:
418
- {
419
- const txUpdateInProgress =
420
- newest(store).on.transactionApplying.state?.update
421
- if (txUpdateInProgress) {
422
- joinTransaction(tl, txUpdateInProgress, store)
423
- } else if (tl.timeTraveling === null) {
424
- const event = Object.assign(creationOrDisposal, {
425
- timestamp: Date.now(),
426
- })
427
- tl.history.push(event)
428
- tl.at = tl.history.length
429
- tl.subject.next(event)
430
- }
431
- const moleculeKey = creationOrDisposal.token.key
432
- tl.subscriptions.get(moleculeKey)?.()
433
- tl.subscriptions.delete(moleculeKey)
434
- for (const [familyKey] of creationOrDisposal.values) {
435
- const stateKey = `${familyKey}(${stringifyJson(moleculeKey)})`
436
- tl.subscriptions.get(stateKey)?.()
437
- tl.subscriptions.delete(stateKey)
438
- store.timelineTopics.delete(stateKey)
422
+ break
423
+ case `molecule_disposal`:
424
+ {
425
+ const txUpdateInProgress =
426
+ newest(store).on.transactionApplying.state?.update
427
+ if (txUpdateInProgress) {
428
+ joinTransaction(tl, txUpdateInProgress, store)
429
+ } else if (tl.timeTraveling === null) {
430
+ const event = Object.assign(creationOrDisposal, {
431
+ timestamp: Date.now(),
432
+ })
433
+ tl.history.push(event)
434
+ tl.at = tl.history.length
435
+ tl.subject.next(event)
436
+ }
437
+ const moleculeKey = stringifyJson(creationOrDisposal.token.key)
438
+
439
+ tl.subscriptions.get(moleculeKey)?.()
440
+ tl.subscriptions.delete(moleculeKey)
441
+ for (const [familyKey] of creationOrDisposal.values) {
442
+ const stateKey = `${familyKey}(${stringifyJson(moleculeKey)})`
443
+ tl.subscriptions.get(stateKey)?.()
444
+ tl.subscriptions.delete(stateKey)
445
+ store.timelineTopics.delete(stateKey)
446
+ }
439
447
  }
440
- }
441
- break
448
+ break
449
+ }
442
450
  }
443
451
  }),
444
452
  )
@@ -506,17 +514,32 @@ function filterTransactionUpdates(
506
514
  }
507
515
 
508
516
  let key: string
517
+ let familyKey: string | undefined
509
518
  switch (updateFromTx.type) {
510
519
  case `state_creation`:
511
520
  case `state_disposal`:
521
+ key = updateFromTx.token.key
522
+ familyKey = updateFromTx.token.family?.key
523
+ break
512
524
  case `molecule_creation`:
513
525
  case `molecule_disposal`:
514
- key = updateFromTx.token.key
526
+ switch (updateFromTx.subType) {
527
+ case `classic`:
528
+ key = updateFromTx.token.key
529
+ break
530
+ case `modern`:
531
+ return true // always include
532
+ }
515
533
  break
516
534
  default:
517
535
  key = updateFromTx.key
536
+ familyKey = updateFromTx.family?.key
518
537
  break
519
538
  }
539
+ timelineTopics.has(key)
540
+ if (familyKey && timelineTopics.has(familyKey)) {
541
+ return true
542
+ }
520
543
  return timelineTopics.has(key)
521
544
  })
522
545
  .map((updateFromTx) => {
@@ -543,13 +566,18 @@ function handleStateLifecycleEvent(
543
566
  timestamp,
544
567
  }) as TimelineUpdate<any>
545
568
  if (!tl.timeTraveling) {
546
- const txUpdateInProgress = newest(store).on.transactionApplying.state?.update
547
- if (txUpdateInProgress) {
548
- joinTransaction(tl, txUpdateInProgress, store)
569
+ const target = newest(store)
570
+ if (isChildStore(target)) {
571
+ // we don't want to update the true timeline while we are in a transaction
549
572
  } else {
550
- tl.history.push(timelineEvent)
551
- tl.at = tl.history.length
552
- tl.subject.next(timelineEvent)
573
+ const txUpdateInProgress = target.on.transactionApplying.state
574
+ if (txUpdateInProgress) {
575
+ joinTransaction(tl, txUpdateInProgress.update, store)
576
+ } else {
577
+ tl.history.push(timelineEvent)
578
+ tl.at = tl.history.length
579
+ tl.subject.next(timelineEvent)
580
+ }
553
581
  }
554
582
  }
555
583
  switch (event.type) {
@@ -24,5 +24,5 @@ export type TransactionProgress<F extends Func> = {
24
24
 
25
25
  export type TransactionEpoch = {
26
26
  epoch: Map<string, number>
27
- actionContinuities: Junction<`continuity`, `action`>
27
+ actionContinuities: Junction<`continuity`, string, `action`, string>
28
28
  }
@@ -4,6 +4,13 @@ export type Flat<R extends { [K in PropertyKey]: any }> = {
4
4
  [K in keyof R]: R[K]
5
5
  }
6
6
 
7
- export type Range<N extends number, A extends any[] = []> = A[`length`] extends N
7
+ export type Count<N extends number, A extends any[] = []> = [
8
+ ...A,
9
+ any,
10
+ ][`length`] extends N
8
11
  ? A[`length`]
9
- : A[`length`] | Range<N, [...A, any]>
12
+ : A[`length`] | Count<N, [...A, any]>
13
+
14
+ export type Each<E extends any[]> = {
15
+ [P in Count<E[`length`]>]: E[P]
16
+ }
@@ -1,11 +1,11 @@
1
- import { Range, Flat, Store, Transceiver } from 'atom.io/internal';
1
+ import { Count, Flat, Store, Transceiver } from 'atom.io/internal';
2
2
  import * as AtomIO from 'atom.io';
3
3
 
4
4
  type Entries<K extends PropertyKey = keyof any, V = any> = [K, V][];
5
5
  type KeyOfEntries<E extends Entries> = E extends [infer K, any][] ? K : never;
6
6
  type ValueOfEntry<E extends Entries, K extends KeyOfEntries<E>> = {
7
- [P in Range<E[`length`]>]: E[P] extends [K, infer V] ? V : never;
8
- }[Range<E[`length`]>];
7
+ [P in Count<E[`length`]>]: E[P] extends [K, infer V] ? V : never;
8
+ }[Count<E[`length`]>];
9
9
  type FromEntries<E extends Entries> = Flat<{
10
10
  [K in KeyOfEntries<E>]: ValueOfEntry<E, K>;
11
11
  }>;
@@ -1,4 +1,5 @@
1
- import { createWritableSelectorFamily } from '../../dist/chunk-TCINPEYE.js';
1
+ import { createWritableSelectorFamily } from '../../dist/chunk-SMKF3ZNG.js';
2
+ import '../../dist/chunk-ADMEAXYU.js';
2
3
  import '../../dist/chunk-XWL6SNVU.js';
3
4
  import { createStandaloneSelector, IMPLICIT, growMoleculeInStore, initFamilyMemberInStore, withdraw, seekInStore } from 'atom.io/internal';
4
5
 
@@ -1,4 +1,4 @@
1
- import type { Flat, Range } from "atom.io/internal"
1
+ import type { Count, Flat } from "atom.io/internal"
2
2
 
3
3
  export type Entries<K extends PropertyKey = keyof any, V = any> = [K, V][]
4
4
 
@@ -7,8 +7,8 @@ export type KeyOfEntries<E extends Entries> = E extends [infer K, any][]
7
7
  : never
8
8
 
9
9
  export type ValueOfEntry<E extends Entries, K extends KeyOfEntries<E>> = {
10
- [P in Range<E[`length`]>]: E[P] extends [K, infer V] ? V : never
11
- }[Range<E[`length`]>]
10
+ [P in Count<E[`length`]>]: E[P] extends [K, infer V] ? V : never
11
+ }[Count<E[`length`]>]
12
12
 
13
13
  export type FromEntries<E extends Entries> = Flat<{
14
14
  [K in KeyOfEntries<E>]: ValueOfEntry<E, K>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atom.io",
3
- "version": "0.29.5",
3
+ "version": "0.30.1",
4
4
  "description": "Composable and testable reactive data library.",
5
5
  "homepage": "https://atom.io.fyi",
6
6
  "sideEffects": false,
@@ -57,34 +57,34 @@
57
57
  "@types/estree": "1.0.6",
58
58
  "@types/http-proxy": "1.17.15",
59
59
  "@types/npmlog": "7.0.0",
60
- "@types/react": "18.3.8",
60
+ "@types/react": "18.3.12",
61
61
  "@types/tmp": "0.2.6",
62
- "@typescript-eslint/parser": "8.6.0",
63
- "@typescript-eslint/rule-tester": "8.6.0",
64
- "@vitest/coverage-v8": "2.1.1",
65
- "@vitest/ui": "2.1.1",
62
+ "@typescript-eslint/parser": "8.10.0",
63
+ "@typescript-eslint/rule-tester": "8.10.0",
64
+ "@vitest/coverage-v8": "2.1.3",
65
+ "@vitest/ui": "2.1.3",
66
66
  "concurrently": "9.0.1",
67
- "drizzle-kit": "0.24.2",
68
- "drizzle-orm": "0.33.0",
69
- "eslint": "9.11.0",
70
- "framer-motion": "11.5.6",
67
+ "drizzle-kit": "0.26.2",
68
+ "drizzle-orm": "0.35.3",
69
+ "eslint": "9.13.0",
70
+ "framer-motion": "11.11.9",
71
71
  "happy-dom": "15.7.4",
72
72
  "http-proxy": "1.18.1",
73
73
  "npmlog": "7.0.1",
74
74
  "postgres": "3.4.4",
75
- "preact": "10.24.0",
75
+ "preact": "10.24.3",
76
76
  "react": "18.3.1",
77
77
  "react-dom": "18.3.1",
78
- "react-router-dom": "6.26.2",
78
+ "react-router-dom": "6.27.0",
79
79
  "socket.io": "4.8.0",
80
80
  "socket.io-client": "4.8.0",
81
81
  "tmp": "0.2.3",
82
82
  "tsup": "8.3.0",
83
83
  "tsx": "4.19.1",
84
- "typescript": "5.6.2",
85
- "vite": "5.4.7",
84
+ "typescript": "5.6.3",
85
+ "vite": "5.4.10",
86
86
  "vite-tsconfig-paths": "5.0.1",
87
- "vitest": "2.1.1",
87
+ "vitest": "2.1.3",
88
88
  "zod": "3.23.8"
89
89
  },
90
90
  "main": "dist/index.js",
@@ -6,7 +6,7 @@ import { undo, redo, getState } from 'atom.io';
6
6
  import { IMPLICIT, createStandaloneAtom, createAtomFamily, findInStore, become } from 'atom.io/internal';
7
7
  import { jsonRefinery, attachIntrospectionStates, primitiveRefinery, prettyJson, discoverType } from 'atom.io/introspection';
8
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
- import { isJson, fromEntries, toEntries, JSON_DEFAULTS } from 'atom.io/json';
9
+ import { isJson, fromEntries, toEntries, JSON_DEFAULTS, stringifyJson } from 'atom.io/json';
10
10
  import { persistSync } from 'atom.io/web';
11
11
 
12
12
  var OpenClose = ({ isOpen, setIsOpen, disabled, testid }) => {
@@ -1131,7 +1131,9 @@ var TransactionUpdateFC = ({ serialNumber, transactionUpdate }) => {
1131
1131
  ] }),
1132
1132
  /* @__PURE__ */ jsxs("section", { className: "transaction_impact", children: [
1133
1133
  /* @__PURE__ */ jsx("span", { className: "detail", children: "impact: " }),
1134
- transactionUpdate.updates.filter((token) => `key` in token && !token.key.startsWith(`\u{1F441}\u200D\u{1F5E8}`)).map((update, index) => {
1134
+ transactionUpdate.updates.filter(
1135
+ (token) => token.type !== `molecule_creation` && token.type !== `molecule_disposal` && token.type !== `state_creation` && token.type !== `state_disposal` && !token.key.startsWith(`\u{1F441}\u200D\u{1F5E8}`)
1136
+ ).map((update, index) => {
1135
1137
  switch (update.type) {
1136
1138
  case `atom_update`:
1137
1139
  case `selector_update`:
@@ -1165,7 +1167,7 @@ var TimelineUpdateFC = ({ timelineUpdate, serialNumber }) => {
1165
1167
  "article",
1166
1168
  {
1167
1169
  className: "node timeline_update",
1168
- "data-testid": `timeline-update-${timelineUpdate.key}-${serialNumber}`,
1170
+ "data-testid": `timeline-update-${typeof timelineUpdate.key === `string` ? timelineUpdate.key : stringifyJson(timelineUpdate.key)}-${serialNumber}`,
1169
1171
  children: [
1170
1172
  /* @__PURE__ */ jsx("header", { children: /* @__PURE__ */ jsxs("h4", { children: [
1171
1173
  timelineUpdate.timestamp,
@@ -1175,7 +1177,9 @@ var TimelineUpdateFC = ({ timelineUpdate, serialNumber }) => {
1175
1177
  timelineUpdate.key,
1176
1178
  ")"
1177
1179
  ] }) }),
1178
- /* @__PURE__ */ jsx("main", { children: timelineUpdate.type === `transaction_update` ? timelineUpdate.updates.filter((token) => `key` in token && !token.key.startsWith(`\u{1F441}\u200D\u{1F5E8}`)).map((update, index) => {
1180
+ /* @__PURE__ */ jsx("main", { children: timelineUpdate.type === `transaction_update` ? timelineUpdate.updates.filter(
1181
+ (token) => token.type !== `molecule_creation` && token.type !== `molecule_disposal` && token.type !== `state_creation` && token.type !== `state_disposal` && !token.key.startsWith(`\u{1F441}\u200D\u{1F5E8}`)
1182
+ ).map((update, index) => {
1179
1183
  switch (update.type) {
1180
1184
  case `atom_update`:
1181
1185
  case `selector_update`:
@@ -1276,7 +1280,7 @@ var TimelineLog = ({ token, isOpenState, timelineState }) => {
1276
1280
  ] })
1277
1281
  ] }),
1278
1282
  isOpen ? /* @__PURE__ */ jsx("main", { children: timeline.history.map(
1279
- (update, index) => `key` in update ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1283
+ (update, index) => update.type !== `molecule_creation` && update.type !== `molecule_disposal` && update.type !== `state_creation` && update.type !== `state_disposal` ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1280
1284
  index === timeline.at ? /* @__PURE__ */ jsx(YouAreHere, {}) : null,
1281
1285
  /* @__PURE__ */ jsx(
1282
1286
  article.TimelineUpdate,
@@ -64,7 +64,10 @@ export const TimelineLog: FC<{
64
64
  {isOpen ? (
65
65
  <main>
66
66
  {timeline.history.map((update, index) =>
67
- `key` in update ? (
67
+ update.type !== `molecule_creation` &&
68
+ update.type !== `molecule_disposal` &&
69
+ update.type !== `state_creation` &&
70
+ update.type !== `state_disposal` ? (
68
71
  <Fragment key={update.key + index + timeline.at}>
69
72
  {index === timeline.at ? <YouAreHere /> : null}
70
73
  <article.TimelineUpdate
@@ -5,6 +5,7 @@ import type {
5
5
  } from "atom.io"
6
6
  import type { Func } from "atom.io/internal"
7
7
  import { discoverType, prettyJson } from "atom.io/introspection"
8
+ import { stringifyJson } from "atom.io/json"
8
9
  import * as React from "react"
9
10
 
10
11
  /* eslint-disable no-console */
@@ -90,7 +91,14 @@ const TransactionUpdateFC: React.FC<{
90
91
  <section className="transaction_impact">
91
92
  <span className="detail">impact: </span>
92
93
  {transactionUpdate.updates
93
- .filter((token) => `key` in token && !token.key.startsWith(`👁‍🗨`))
94
+ .filter(
95
+ (token) =>
96
+ token.type !== `molecule_creation` &&
97
+ token.type !== `molecule_disposal` &&
98
+ token.type !== `state_creation` &&
99
+ token.type !== `state_disposal` &&
100
+ !token.key.startsWith(`👁‍🗨`),
101
+ )
94
102
  .map((update, index) => {
95
103
  switch (update.type) {
96
104
  case `atom_update`:
@@ -125,7 +133,7 @@ export const TimelineUpdateFC: React.FC<{
125
133
  return `key` in timelineUpdate ? (
126
134
  <article
127
135
  className="node timeline_update"
128
- data-testid={`timeline-update-${timelineUpdate.key}-${serialNumber}`}
136
+ data-testid={`timeline-update-${typeof timelineUpdate.key === `string` ? timelineUpdate.key : stringifyJson(timelineUpdate.key)}-${serialNumber}`}
129
137
  >
130
138
  <header>
131
139
  <h4>
@@ -136,7 +144,14 @@ export const TimelineUpdateFC: React.FC<{
136
144
  <main>
137
145
  {timelineUpdate.type === `transaction_update` ? (
138
146
  timelineUpdate.updates
139
- .filter((token) => `key` in token && !token.key.startsWith(`👁‍🗨`))
147
+ .filter(
148
+ (token) =>
149
+ token.type !== `molecule_creation` &&
150
+ token.type !== `molecule_disposal` &&
151
+ token.type !== `state_creation` &&
152
+ token.type !== `state_disposal` &&
153
+ !token.key.startsWith(`👁‍🗨`),
154
+ )
140
155
  .map((update, index) => {
141
156
  switch (update.type) {
142
157
  case `atom_update`:
@@ -45,7 +45,7 @@ type UserInRoomMeta = {
45
45
  enteredAtEpoch: number;
46
46
  };
47
47
  declare const DEFAULT_USER_IN_ROOM_META: UserInRoomMeta;
48
- declare const usersInRooms: atom_io_data.JoinToken<"room", "user", "1:n", UserInRoomMeta>;
48
+ declare const usersInRooms: atom_io_data.JoinToken<"room", string, "user", string, "1:n", UserInRoomMeta>;
49
49
  declare const usersInMyRoomView: atom_io.ReadonlySelectorFamilyToken<MutableAtomToken<SetRTX<string>, SetRTXJson<string>>[], string>;
50
50
 
51
51
  export { type ContinuityOptions, type ContinuityToken, DEFAULT_USER_IN_ROOM_META, InvariantMap, type PerspectiveToken, SyncGroup, type UserInRoomMeta, continuity, roomIndex, usersInMyRoomView, usersInRooms, usersInThisRoomIndex };
@@ -90,7 +90,9 @@ var usersInRooms = join(
90
90
  {
91
91
  key: `usersInRooms`,
92
92
  between: [`room`, `user`],
93
- cardinality: `1:n`
93
+ cardinality: `1:n`,
94
+ isAType: (input) => typeof input === `string`,
95
+ isBType: (input) => typeof input === `string`
94
96
  },
95
97
  DEFAULT_USER_IN_ROOM_META
96
98
  );
@@ -31,6 +31,8 @@ export const usersInRooms = join(
31
31
  key: `usersInRooms`,
32
32
  between: [`room`, `user`],
33
33
  cardinality: `1:n`,
34
+ isAType: (input): input is string => typeof input === `string`,
35
+ isBType: (input): input is string => typeof input === `string`,
34
36
  },
35
37
  DEFAULT_USER_IN_ROOM_META,
36
38
  )
@@ -2,7 +2,7 @@ import { Subject, Transceiver, Store } from 'atom.io/internal';
2
2
  import { Json, stringified, JsonIO, Canonical } from 'atom.io/json';
3
3
  import { ChildProcessWithoutNullStreams } from 'node:child_process';
4
4
  import * as AtomIO from 'atom.io';
5
- import { TransactionUpdateContent, TransactionUpdate, WritableToken } from 'atom.io';
5
+ import { TransactionUpdateContent, TransactionUpdate, Hierarchy, WritableToken } from 'atom.io';
6
6
  import { ContinuityToken, UserInRoomMeta } from 'atom.io/realtime';
7
7
  import * as atom_io_data from 'atom.io/data';
8
8
  import { Loadable } from 'atom.io/data';
@@ -112,10 +112,19 @@ declare const actionOcclusionAtoms: AtomIO.RegularAtomFamilyToken<{
112
112
  }, string>;
113
113
  declare const userUnacknowledgedQueues: AtomIO.RegularAtomFamilyToken<Pick<TransactionUpdate<any>, "key" | "epoch" | "id" | "updates" | "output">[], string>;
114
114
 
115
- declare const socketAtoms: AtomIO.RegularAtomFamilyToken<Socket | null, string>;
115
+ type SocketKey = `socket::${string}`;
116
+ type UserKey = `user::${string}`;
117
+ type RoomKey = `room::${string}`;
118
+ type SocketSystemHierarchy = Hierarchy<[
119
+ {
120
+ above: `root`;
121
+ below: [UserKey, SocketKey, RoomKey];
122
+ }
123
+ ]>;
124
+ declare const socketAtoms: AtomIO.RegularAtomFamilyToken<Socket | null, `socket::${string}`>;
116
125
  declare const socketIndex: AtomIO.MutableAtomToken<SetRTX<string>, SetRTXJson<string>>;
117
126
  declare const userIndex: AtomIO.MutableAtomToken<SetRTX<string>, SetRTXJson<string>>;
118
- declare const usersOfSockets: atom_io_data.JoinToken<"user", "socket", "1:1", null>;
127
+ declare const usersOfSockets: atom_io_data.JoinToken<"user", `user::${string}`, "socket", `socket::${string}`, "1:1", null>;
119
128
 
120
129
  type StateProvider = ReturnType<typeof realtimeStateProvider>;
121
130
  declare function realtimeStateProvider({ socket, store, }: ServerConfig): <J extends Json.Serializable>(token: AtomIO.WritableToken<J>) => () => void;
@@ -136,4 +145,4 @@ type ServerConfig = {
136
145
  store?: Store;
137
146
  };
138
147
 
139
- export { type ActionReceiver, ChildSocket, type CreateRoomIO, CustomSocket, type EventBuffer, type Events, type FamilyProvider, type JoinRoomIO, type LeaveRoomIO, type MutableFamilyProvider, type MutableProvider, ParentSocket, type RealtimeContinuitySynchronizer, type RoomArguments, type ServerConfig, type Socket, type StateProvider, type StateReceiver, type StringifiedEvent, SubjectSocket, actionOcclusionAtoms, createRoomTX, destroyRoomTX, joinRoomTX, leaveRoomTX, realtimeActionReceiver, realtimeAtomFamilyProvider, realtimeContinuitySynchronizer, realtimeMutableFamilyProvider, realtimeMutableProvider, realtimeStateProvider, realtimeStateReceiver, redactTransactionUpdateContent, roomArgumentsAtoms, roomSelectors, socketAtoms, socketIndex, userIndex, userUnacknowledgedQueues, usersOfSockets };
148
+ export { type ActionReceiver, ChildSocket, type CreateRoomIO, CustomSocket, type EventBuffer, type Events, type FamilyProvider, type JoinRoomIO, type LeaveRoomIO, type MutableFamilyProvider, type MutableProvider, ParentSocket, type RealtimeContinuitySynchronizer, type RoomArguments, type RoomKey, type ServerConfig, type Socket, type SocketKey, type SocketSystemHierarchy, type StateProvider, type StateReceiver, type StringifiedEvent, SubjectSocket, type UserKey, actionOcclusionAtoms, createRoomTX, destroyRoomTX, joinRoomTX, leaveRoomTX, realtimeActionReceiver, realtimeAtomFamilyProvider, realtimeContinuitySynchronizer, realtimeMutableFamilyProvider, realtimeMutableProvider, realtimeStateProvider, realtimeStateReceiver, redactTransactionUpdateContent, roomArgumentsAtoms, roomSelectors, socketAtoms, socketIndex, userIndex, userUnacknowledgedQueues, usersOfSockets };
@@ -468,7 +468,9 @@ var userIndex = atom({
468
468
  var usersOfSockets = join({
469
469
  key: `usersOfSockets`,
470
470
  between: [`user`, `socket`],
471
- cardinality: `1:1`
471
+ cardinality: `1:1`,
472
+ isAType: (s) => s.startsWith(`user::`),
473
+ isBType: (s) => s.startsWith(`socket::`)
472
474
  });
473
475
 
474
476
  // realtime-server/src/realtime-continuity-synchronizer.ts
@@ -481,7 +483,7 @@ function realtimeContinuitySynchronizer({
481
483
  const continuityKey = continuity.key;
482
484
  const userKeyState = findRelationsInStore(
483
485
  usersOfSockets,
484
- socket.id,
486
+ `socket::${socket.id}`,
485
487
  store
486
488
  ).userKeyOfSocket;
487
489
  const userKey = getFromStore(store, userKeyState);
@@ -15,7 +15,7 @@ import {
15
15
  import type { Json, JsonIO } from "atom.io/json"
16
16
  import type { ContinuityToken } from "atom.io/realtime"
17
17
 
18
- import type { ServerConfig, Socket } from "."
18
+ import type { ServerConfig, Socket, SocketKey } from "."
19
19
  import { socketAtoms, usersOfSockets } from "."
20
20
  import {
21
21
  redactTransactionUpdateContent,
@@ -35,7 +35,7 @@ export function realtimeContinuitySynchronizer({
35
35
  const continuityKey = continuity.key
36
36
  const userKeyState = findRelationsInStore(
37
37
  usersOfSockets,
38
- socket.id,
38
+ `socket::${socket.id}`,
39
39
  store,
40
40
  ).userKeyOfSocket
41
41
  const userKey = getFromStore(store, userKeyState)
@@ -1,3 +1,4 @@
1
+ import type { Hierarchy } from "atom.io"
1
2
  import { atom, atomFamily } from "atom.io"
2
3
  import { join } from "atom.io/data"
3
4
  import type { SetRTXJson } from "atom.io/transceivers/set-rtx"
@@ -5,7 +6,20 @@ import { SetRTX } from "atom.io/transceivers/set-rtx"
5
6
 
6
7
  import type { Socket } from ".."
7
8
 
8
- export const socketAtoms = atomFamily<Socket | null, string>({
9
+ export type SocketKey = `socket::${string}`
10
+ export type UserKey = `user::${string}`
11
+ export type RoomKey = `room::${string}`
12
+
13
+ export type SocketSystemHierarchy = Hierarchy<
14
+ [
15
+ {
16
+ above: `root`
17
+ below: [UserKey, SocketKey, RoomKey]
18
+ },
19
+ ]
20
+ >
21
+
22
+ export const socketAtoms = atomFamily<Socket | null, SocketKey>({
9
23
  key: `sockets`,
10
24
  default: null,
11
25
  })
@@ -28,4 +42,6 @@ export const usersOfSockets = join({
28
42
  key: `usersOfSockets`,
29
43
  between: [`user`, `socket`],
30
44
  cardinality: `1:1`,
45
+ isAType: (s): s is UserKey => s.startsWith(`user::`),
46
+ isBType: (s): s is SocketKey => s.startsWith(`socket::`),
31
47
  })
@@ -6,6 +6,9 @@ import { Socket } from 'socket.io-client';
6
6
 
7
7
  type TestSetupOptions = {
8
8
  port: number;
9
+ immortal?: {
10
+ server?: boolean;
11
+ };
9
12
  server: (tools: {
10
13
  socket: SocketIO.Socket;
11
14
  silo: AtomIO.Silo;
@@ -2,6 +2,7 @@ import '../../dist/chunk-XWL6SNVU.js';
2
2
  import * as http from 'node:http';
3
3
  import { render, prettyDOM } from '@testing-library/react';
4
4
  import * as AtomIO from 'atom.io';
5
+ import { realm } from 'atom.io';
5
6
  import { editRelationsInStore, findRelationsInStore } from 'atom.io/data';
6
7
  import { IMPLICIT, findInStore, setIntoStore, getFromStore, clearStore } from 'atom.io/internal';
7
8
  import { toEntries } from 'atom.io/json';
@@ -32,9 +33,13 @@ function prefixLogger(store, prefix) {
32
33
  var setupRealtimeTestServer = (options) => {
33
34
  ++testNumber;
34
35
  const silo = new AtomIO.Silo(
35
- { name: `SERVER-${testNumber}`, lifespan: `ephemeral` },
36
+ {
37
+ name: `SERVER-${testNumber}`,
38
+ lifespan: options.immortal?.server ? `immortal` : `ephemeral`
39
+ },
36
40
  IMPLICIT.STORE
37
41
  );
42
+ const socketRealm = realm(silo.store);
38
43
  const httpServer = http.createServer((_, res) => res.end(`Hello World!`));
39
44
  const address = httpServer.listen(options.port).address();
40
45
  const port = typeof address === `string` ? null : address === null ? null : address.port;
@@ -42,12 +47,14 @@ var setupRealtimeTestServer = (options) => {
42
47
  const server = new SocketIO.Server(httpServer).use((socket, next) => {
43
48
  const { token, username } = socket.handshake.auth;
44
49
  if (token === `test` && socket.id) {
45
- const socketState = findInStore(silo.store, RTS.socketAtoms, socket.id);
50
+ const userClaim = socketRealm.allocate(`root`, `user::${username}`);
51
+ const socketClaim = socketRealm.allocate(`root`, `socket::${socket.id}`);
52
+ const socketState = findInStore(silo.store, RTS.socketAtoms, socketClaim);
46
53
  setIntoStore(silo.store, socketState, socket);
47
54
  editRelationsInStore(
48
55
  RTS.usersOfSockets,
49
56
  (relations) => {
50
- relations.set(socket.id, username);
57
+ relations.set(userClaim, socketClaim);
51
58
  },
52
59
  silo.store
53
60
  );
@@ -64,7 +71,7 @@ var setupRealtimeTestServer = (options) => {
64
71
  function enableLogging() {
65
72
  const userKeyState = findRelationsInStore(
66
73
  RTS.usersOfSockets,
67
- socket.id,
74
+ `socket::${socket.id}`,
68
75
  silo.store
69
76
  ).userKeyOfSocket;
70
77
  userKey = getFromStore(silo.store, userKeyState);