atom.io 0.44.5 → 0.44.7

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 (50) hide show
  1. package/dist/internal/index.d.ts +5 -6
  2. package/dist/internal/index.d.ts.map +1 -1
  3. package/dist/internal/index.js.map +1 -1
  4. package/dist/introspection/index.d.ts.map +1 -1
  5. package/dist/realtime/index.d.ts +4 -14
  6. package/dist/realtime/index.d.ts.map +1 -1
  7. package/dist/realtime/index.js +10 -8
  8. package/dist/realtime/index.js.map +1 -1
  9. package/dist/realtime-client/index.d.ts +6 -7
  10. package/dist/realtime-client/index.d.ts.map +1 -1
  11. package/dist/realtime-client/index.js +18 -22
  12. package/dist/realtime-client/index.js.map +1 -1
  13. package/dist/realtime-react/index.d.ts +3 -1
  14. package/dist/realtime-react/index.d.ts.map +1 -1
  15. package/dist/realtime-react/index.js +10 -6
  16. package/dist/realtime-react/index.js.map +1 -1
  17. package/dist/realtime-server/index.d.ts +40 -32
  18. package/dist/realtime-server/index.d.ts.map +1 -1
  19. package/dist/realtime-server/index.js +89 -59
  20. package/dist/realtime-server/index.js.map +1 -1
  21. package/dist/realtime-testing/index.d.ts +4 -4
  22. package/dist/realtime-testing/index.d.ts.map +1 -1
  23. package/dist/realtime-testing/index.js +5 -5
  24. package/dist/realtime-testing/index.js.map +1 -1
  25. package/dist/shared-room-store-zzjyXJv7.d.ts +28 -0
  26. package/dist/shared-room-store-zzjyXJv7.d.ts.map +1 -0
  27. package/package.json +17 -17
  28. package/src/internal/get-state/get-from-store.ts +3 -5
  29. package/src/internal/join/get-internal-relations-from-store.ts +5 -9
  30. package/src/internal/join/join-internal.ts +5 -2
  31. package/src/realtime/index.ts +1 -0
  32. package/src/realtime/realtime-continuity.ts +2 -1
  33. package/src/realtime/realtime-key-types.ts +11 -0
  34. package/src/realtime/shared-room-store.ts +20 -13
  35. package/src/realtime-client/continuity/register-and-attempt-confirmed-update.ts +5 -5
  36. package/src/realtime-client/realtime-client-stores/client-main-store.ts +8 -12
  37. package/src/realtime-client/realtime-client-stores/client-sync-store.ts +4 -4
  38. package/src/realtime-client/sync-continuity.ts +6 -6
  39. package/src/realtime-react/realtime-context.tsx +14 -5
  40. package/src/realtime-server/continuity/continuity-store.ts +1 -2
  41. package/src/realtime-server/continuity/provide-continuity.ts +2 -2
  42. package/src/realtime-server/continuity/provide-outcomes.ts +1 -2
  43. package/src/realtime-server/continuity/provide-perspectives.ts +1 -3
  44. package/src/realtime-server/continuity/provide-startup-payloads.ts +1 -3
  45. package/src/realtime-server/continuity/track-acknowledgements.ts +1 -2
  46. package/src/realtime-server/ipc-sockets/parent-socket.ts +1 -1
  47. package/src/realtime-server/realtime-mutable-family-provider.ts +12 -9
  48. package/src/realtime-server/realtime-server-stores/server-room-external-store.ts +127 -27
  49. package/src/realtime-server/realtime-server-stores/server-user-store.ts +11 -18
  50. package/src/realtime-testing/setup-realtime-test.tsx +7 -6
@@ -5,16 +5,12 @@ import type { RootStore } from "../transaction"
5
5
  import { getJoin } from "./get-join"
6
6
 
7
7
  export function getInternalRelationsFromStore<
8
- AName extends string,
9
- BName extends string,
8
+ A extends string,
9
+ B extends string,
10
10
  >(
11
- token: JoinToken<any, AName, any, BName, any>,
11
+ token: JoinToken<any, A, any, B, any>,
12
12
  store: RootStore,
13
- ): MutableAtomFamilyToken<UList<AName> | UList<BName>, string> {
13
+ ): MutableAtomFamilyToken<UList<A> | UList<B>, A | B> {
14
14
  const myJoin = getJoin(token, store)
15
- const family = myJoin.relatedKeysAtoms as MutableAtomFamilyToken<
16
- UList<AName> | UList<BName>,
17
- string
18
- >
19
- return family
15
+ return myJoin.relatedKeysAtoms
20
16
  }
@@ -72,7 +72,7 @@ export class Join<
72
72
  public options: JoinOptions<AName, A, BName, B, Cardinality>
73
73
  public relations: Junction<AName, A, BName, B>
74
74
  public states: JoinStateFamilies<AName, A, BName, B, Cardinality>
75
- public relatedKeysAtoms: MutableAtomFamilyToken<UList<string>, string>
75
+ public relatedKeysAtoms: MutableAtomFamilyToken<UList<A> | UList<B>, A | B>
76
76
 
77
77
  public transact(
78
78
  toolkit: WriterToolkit,
@@ -120,7 +120,10 @@ export class Join<
120
120
  },
121
121
  [`join`, `relations`],
122
122
  )
123
- this.relatedKeysAtoms = relatedKeysAtoms
123
+ this.relatedKeysAtoms = relatedKeysAtoms as MutableAtomFamilyToken<
124
+ UList<A> | UList<B>,
125
+ A | B
126
+ >
124
127
 
125
128
  const replaceRelationsSafely: Write<
126
129
  (a: string, newRelationsOfA: string[]) => void
@@ -1,5 +1,6 @@
1
1
  export * from "./employ-socket"
2
2
  export * from "./mutex-store"
3
3
  export * from "./realtime-continuity"
4
+ export * from "./realtime-key-types"
4
5
  export * from "./shared-room-store"
5
6
  export type * from "./socket-interface"
@@ -12,7 +12,8 @@ import {
12
12
  setEpochNumberOfContinuity,
13
13
  } from "atom.io/internal"
14
14
  import type { Canonical } from "atom.io/json"
15
- import type { UserKey } from "atom.io/realtime-server"
15
+
16
+ import type { UserKey } from "./realtime-key-types"
16
17
 
17
18
  /* eslint-disable no-console */
18
19
 
@@ -0,0 +1,11 @@
1
+ export type SocketKey = `socket::${string}`
2
+ export const isSocketKey = (key: string): key is SocketKey =>
3
+ key.startsWith(`socket::`)
4
+
5
+ export type UserKey = `user::${string}`
6
+ export const isUserKey = (key: string): key is UserKey =>
7
+ key.startsWith(`user::`)
8
+
9
+ export type RoomKey = `room::${string}`
10
+ export const isRoomKey = (key: string): key is RoomKey =>
11
+ key.startsWith(`room::`)
@@ -6,14 +6,21 @@ import type {
6
6
  import { getInternalRelations, join, mutableAtom, selectorFamily } from "atom.io"
7
7
  import { UList } from "atom.io/transceivers/u-list"
8
8
 
9
- export const usersInThisRoomIndex: MutableAtomToken<UList<string>> = mutableAtom<
10
- UList<string>
11
- >({
12
- key: `usersInRoomIndex`,
13
- class: UList,
14
- })
9
+ import {
10
+ isRoomKey,
11
+ isUserKey,
12
+ type RoomKey,
13
+ type UserKey,
14
+ } from "./realtime-key-types"
15
+
16
+ export type RoomSocketInterface<RoomNames extends string> = {
17
+ createRoom: (roomName: RoomNames) => void
18
+ joinRoom: (roomKey: string) => void
19
+ [leaveRoom: `leaveRoom:${string}`]: () => void
20
+ [deleteRoom: `deleteRoom:${string}`]: () => void
21
+ }
15
22
 
16
- export const roomIndex: MutableAtomToken<UList<string>> = mutableAtom<
23
+ export const roomKeysAtom: MutableAtomToken<UList<string>> = mutableAtom<
17
24
  UList<string>
18
25
  >({
19
26
  key: `roomIndex`,
@@ -26,19 +33,19 @@ export type UserInRoomMeta = {
26
33
  export const DEFAULT_USER_IN_ROOM_META: UserInRoomMeta = {
27
34
  enteredAtEpoch: 0,
28
35
  }
29
- export const usersInRooms: JoinToken<`room`, string, `user`, string, `1:n`> =
36
+ export const usersInRooms: JoinToken<`room`, RoomKey, `user`, UserKey, `1:n`> =
30
37
  join({
31
38
  key: `usersInRooms`,
32
39
  between: [`room`, `user`],
33
40
  cardinality: `1:n`,
34
- isAType: (input): input is string => typeof input === `string`,
35
- isBType: (input): input is string => typeof input === `string`,
41
+ isAType: isRoomKey,
42
+ isBType: isUserKey,
36
43
  })
37
44
 
38
45
  export const usersInMyRoomView: ReadonlyPureSelectorFamilyToken<
39
- MutableAtomToken<UList<string>>[],
40
- string
41
- > = selectorFamily<MutableAtomToken<UList<string>>[], string>({
46
+ MutableAtomToken<UList<UserKey>>[],
47
+ UserKey
48
+ > = selectorFamily<MutableAtomToken<UList<UserKey>>[], UserKey>({
42
49
  key: `usersInMyRoomView`,
43
50
  get:
44
51
  (myUsername) =>
@@ -9,8 +9,8 @@ import {
9
9
  } from "atom.io/internal"
10
10
  import type { Socket } from "atom.io/realtime"
11
11
  import {
12
- confirmedUpdateQueue,
13
- optimisticUpdateQueue,
12
+ confirmedUpdateQueueAtom,
13
+ optimisticUpdateQueueAtom,
14
14
  } from "atom.io/realtime-client"
15
15
 
16
16
  export const useRegisterAndAttemptConfirmedUpdate =
@@ -42,7 +42,7 @@ export const useRegisterAndAttemptConfirmedUpdate =
42
42
  continuityKey,
43
43
  `reconciling updates`,
44
44
  )
45
- setIntoStore(store, optimisticUpdateQueue, (queue) => {
45
+ setIntoStore(store, optimisticUpdateQueueAtom, (queue) => {
46
46
  queue.shift()
47
47
  return queue
48
48
  })
@@ -172,7 +172,7 @@ export const useRegisterAndAttemptConfirmedUpdate =
172
172
  `pushing confirmed update to queue`,
173
173
  confirmed,
174
174
  )
175
- setIntoStore(store, confirmedUpdateQueue, (queue) => {
175
+ setIntoStore(store, confirmedUpdateQueueAtom, (queue) => {
176
176
  queue.push(confirmed)
177
177
  queue.sort((a, b) => a.epoch - b.epoch)
178
178
  return queue
@@ -229,7 +229,7 @@ export const useRegisterAndAttemptConfirmedUpdate =
229
229
  continuityKey,
230
230
  `pushing confirmed update #${confirmed.epoch} to queue`,
231
231
  )
232
- setIntoStore(store, confirmedUpdateQueue, (queue) => {
232
+ setIntoStore(store, confirmedUpdateQueueAtom, (queue) => {
233
233
  queue.push(confirmed)
234
234
  queue.sort((a, b) => a.epoch - b.epoch)
235
235
  return queue
@@ -1,20 +1,16 @@
1
1
  import * as AtomIO from "atom.io"
2
+ import type { SocketKey, UserKey } from "atom.io/realtime"
2
3
  import { storageSync } from "atom.io/web"
3
4
 
4
- export const myIdState__INTERNAL: AtomIO.RegularAtomToken<string | undefined> =
5
- AtomIO.atom<string | undefined>({
6
- key: `mySocketId__INTERNAL`,
5
+ export const mySocketKeyAtom: AtomIO.RegularAtomToken<SocketKey | undefined> =
6
+ AtomIO.atom<SocketKey | undefined>({
7
+ key: `mySocketKey`,
7
8
  default: undefined,
8
9
  })
9
- export const myIdState: AtomIO.ReadonlyPureSelectorToken<string | undefined> =
10
- AtomIO.selector<string | undefined>({
11
- key: `mySocketId`,
12
- get: ({ get }) => get(myIdState__INTERNAL),
13
- })
14
10
 
15
- export const myUsernameState: AtomIO.RegularAtomToken<string | null> =
16
- AtomIO.atom<string | null>({
17
- key: `myName`,
11
+ export const myUserKeyAtom: AtomIO.RegularAtomToken<UserKey | null> =
12
+ AtomIO.atom<UserKey | null>({
13
+ key: `myUserKey`,
18
14
  default: null,
19
- effects: [storageSync(globalThis.localStorage, JSON, `myUsername`)],
15
+ effects: [storageSync(globalThis.localStorage, JSON, `myUserKey`)],
20
16
  })
@@ -1,15 +1,15 @@
1
1
  import * as AtomIO from "atom.io"
2
2
 
3
- export const optimisticUpdateQueue: AtomIO.RegularAtomToken<
3
+ export const optimisticUpdateQueueAtom: AtomIO.RegularAtomToken<
4
4
  AtomIO.TransactionOutcomeEvent<any>[]
5
5
  > = AtomIO.atom<AtomIO.TransactionOutcomeEvent<any>[]>({
6
- key: `updateQueue`,
6
+ key: `optimisticUpdateQueue`,
7
7
  default: () => [],
8
8
  })
9
9
 
10
- export const confirmedUpdateQueue: AtomIO.RegularAtomToken<
10
+ export const confirmedUpdateQueueAtom: AtomIO.RegularAtomToken<
11
11
  AtomIO.TransactionOutcomeEvent<any>[]
12
12
  > = AtomIO.atom<AtomIO.TransactionOutcomeEvent<any>[]>({
13
- key: `serverConfirmedUpdateQueue`,
13
+ key: `confirmedUpdateQueue`,
14
14
  default: () => [],
15
15
  })
@@ -10,8 +10,8 @@ import {
10
10
  import type { Json } from "atom.io/json"
11
11
  import type { ContinuityToken } from "atom.io/realtime"
12
12
  import {
13
- confirmedUpdateQueue,
14
- optimisticUpdateQueue,
13
+ confirmedUpdateQueueAtom,
14
+ optimisticUpdateQueueAtom,
15
15
  } from "atom.io/realtime-client"
16
16
  import type { Socket } from "socket.io-client"
17
17
 
@@ -25,8 +25,8 @@ export function syncContinuity(
25
25
  continuity: ContinuityToken,
26
26
  ): () => void {
27
27
  const continuityKey = continuity.key
28
- const optimisticUpdates = getFromStore(store, optimisticUpdateQueue)
29
- const confirmedUpdates = getFromStore(store, confirmedUpdateQueue)
28
+ const optimisticUpdates = getFromStore(store, optimisticUpdateQueueAtom)
29
+ const confirmedUpdates = getFromStore(store, confirmedUpdateQueueAtom)
30
30
 
31
31
  const initializeContinuity = (epoch: number, payload: Json.Array) => {
32
32
  socket.off(`continuity-init:${continuityKey}`, initializeContinuity)
@@ -83,7 +83,7 @@ export function syncContinuity(
83
83
  continuityKey,
84
84
  `enqueuing new optimistic update`,
85
85
  )
86
- setIntoStore(store, optimisticUpdateQueue, (queue) => {
86
+ setIntoStore(store, optimisticUpdateQueueAtom, (queue) => {
87
87
  queue.push(clientUpdate)
88
88
  queue.sort((a, b) => a.epoch - b.epoch)
89
89
  return queue
@@ -95,7 +95,7 @@ export function syncContinuity(
95
95
  continuityKey,
96
96
  `replacing existing optimistic update at index ${optimisticUpdateIndex}`,
97
97
  )
98
- setIntoStore(store, optimisticUpdateQueue, (queue) => {
98
+ setIntoStore(store, optimisticUpdateQueueAtom, (queue) => {
99
99
  queue[optimisticUpdateIndex] = clientUpdate
100
100
  return queue
101
101
  })
@@ -1,4 +1,5 @@
1
1
  import { useI } from "atom.io/react"
2
+ import type { RoomSocketInterface } from "atom.io/realtime/shared-room-store"
2
3
  import * as RTC from "atom.io/realtime-client"
3
4
  import * as React from "react"
4
5
  import type { Socket } from "socket.io-client"
@@ -26,19 +27,27 @@ export const RealtimeProvider: React.FC<{
26
27
  const services = React.useRef(
27
28
  new Map<string, RealtimeServiceCounter>(),
28
29
  ).current
29
- const setMyId = useI(RTC.myIdState__INTERNAL)
30
+ const setMySocketKey = useI(RTC.mySocketKeyAtom)
30
31
  React.useEffect(() => {
31
- setMyId(socket?.id)
32
+ setMySocketKey(socket?.id ? `socket::${socket.id}` : undefined)
32
33
  socket?.on(`connect`, () => {
33
- setMyId(socket.id)
34
+ setMySocketKey(socket?.id ? `socket::${socket.id}` : undefined)
34
35
  })
35
36
  socket?.on(`disconnect`, () => {
36
- setMyId(undefined)
37
+ setMySocketKey(undefined)
37
38
  })
38
- }, [socket, setMyId])
39
+ }, [socket, setMySocketKey])
39
40
  return (
40
41
  <RealtimeContext.Provider value={{ socket, services }}>
41
42
  {children}
42
43
  </RealtimeContext.Provider>
43
44
  )
44
45
  }
46
+
47
+ export function useRealtimeRooms<RoomNames extends string>(): Socket<
48
+ {},
49
+ RoomSocketInterface<RoomNames>
50
+ > {
51
+ const { socket } = React.useContext(RealtimeContext)
52
+ return socket as Socket<{}, RoomSocketInterface<RoomNames>>
53
+ }
@@ -4,8 +4,7 @@ import type {
4
4
  TransactionSubEvent,
5
5
  } from "atom.io"
6
6
  import { atomFamily } from "atom.io"
7
-
8
- import type { UserKey } from "../realtime-server-stores"
7
+ import type { UserKey } from "atom.io/realtime"
9
8
 
10
9
  export function redactTransactionUpdateContent(
11
10
  visibleStateKeys: string[],
@@ -1,8 +1,8 @@
1
1
  import { getFromStore, IMPLICIT } from "atom.io/internal"
2
2
  import type { Json } from "atom.io/json"
3
- import type { ContinuityToken } from "atom.io/realtime"
3
+ import type { ContinuityToken, UserKey } from "atom.io/realtime"
4
4
 
5
- import type { ServerConfig, UserKey } from ".."
5
+ import type { ServerConfig } from ".."
6
6
  import { unacknowledgedUpdatesAtoms } from "./continuity-store"
7
7
  import { provideOutcomes } from "./provide-outcomes"
8
8
  import { providePerspectives } from "./provide-perspectives"
@@ -7,9 +7,8 @@ import {
7
7
  subscribeToTransaction,
8
8
  } from "atom.io/internal"
9
9
  import type { Json } from "atom.io/json"
10
- import type { ContinuityToken, Socket } from "atom.io/realtime"
10
+ import type { ContinuityToken, Socket, UserKey } from "atom.io/realtime"
11
11
 
12
- import type { UserKey } from ".."
13
12
  import {
14
13
  redactTransactionUpdateContent,
15
14
  unacknowledgedUpdatesAtoms,
@@ -5,9 +5,7 @@ import {
5
5
  getJsonToken,
6
6
  subscribeToState,
7
7
  } from "atom.io/internal"
8
- import type { ContinuityToken, Socket } from "atom.io/realtime"
9
-
10
- import type { UserKey } from "../realtime-server-stores"
8
+ import type { ContinuityToken, Socket, UserKey } from "atom.io/realtime"
11
9
 
12
10
  export function providePerspectives(
13
11
  store: Store,
@@ -6,11 +6,9 @@ import {
6
6
  isRootStore,
7
7
  } from "atom.io/internal"
8
8
  import type { Json } from "atom.io/json"
9
- import type { ContinuityToken, Socket } from "atom.io/realtime"
9
+ import type { ContinuityToken, Socket, UserKey } from "atom.io/realtime"
10
10
  import { employSocket } from "atom.io/realtime"
11
11
 
12
- import type { UserKey } from ".."
13
-
14
12
  export function provideStartupPayloads(
15
13
  store: Store,
16
14
  socket: Socket,
@@ -1,9 +1,8 @@
1
1
  import type { Store } from "atom.io/internal"
2
2
  import { getFromStore, setIntoStore } from "atom.io/internal"
3
- import type { ContinuityToken, Socket } from "atom.io/realtime"
3
+ import type { ContinuityToken, Socket, UserKey } from "atom.io/realtime"
4
4
  import { employSocket } from "atom.io/realtime"
5
5
 
6
- import type { UserKey } from "../realtime-server-stores"
7
6
  import { unacknowledgedUpdatesAtoms } from "./continuity-store"
8
7
 
9
8
  export function trackAcknowledgements(
@@ -3,9 +3,9 @@ import type { Readable, Writable } from "node:stream"
3
3
  import { Subject } from "atom.io/internal"
4
4
  import type { Json } from "atom.io/json"
5
5
  import { parseJson, stringifyJson } from "atom.io/json"
6
+ import type { UserKey } from "atom.io/realtime"
6
7
  import { UList } from "atom.io/transceivers/u-list"
7
8
 
8
- import type { UserKey } from "../realtime-server-stores"
9
9
  import type { StderrLog } from "./child-socket"
10
10
  import type { EventBuffer, EventPayload, Events } from "./custom-socket"
11
11
  import { CustomSocket } from "./custom-socket"
@@ -14,6 +14,18 @@ import { employSocket } from "atom.io/realtime"
14
14
 
15
15
  import type { ServerConfig } from "."
16
16
 
17
+ const isAvailable = <K extends Canonical>(
18
+ exposedSubKeys: Iterable<K>,
19
+ subKey: K,
20
+ ): boolean => {
21
+ for (const exposedSubKey of exposedSubKeys) {
22
+ if (stringifyJson(exposedSubKey) === stringifyJson(subKey)) {
23
+ return true
24
+ }
25
+ }
26
+ return false
27
+ }
28
+
17
29
  export type MutableFamilyProvider = ReturnType<
18
30
  typeof realtimeMutableFamilyProvider
19
31
  >
@@ -78,15 +90,6 @@ export function realtimeMutableFamilyProvider({
78
90
  )
79
91
  }
80
92
 
81
- const isAvailable = (exposedSubKeys: Iterable<K>, subKey: K): boolean => {
82
- for (const exposedSubKey of exposedSubKeys) {
83
- if (stringifyJson(exposedSubKey) === stringifyJson(subKey)) {
84
- return true
85
- }
86
- }
87
- return false
88
- }
89
-
90
93
  const start = () => {
91
94
  coreSubscriptions.add(
92
95
  employSocket(socket, `sub:${family.key}`, (subKey: K) => {
@@ -2,23 +2,50 @@ import type { ChildProcessWithoutNullStreams } from "node:child_process"
2
2
  import { spawn } from "node:child_process"
3
3
 
4
4
  import type { Store } from "atom.io/internal"
5
- import { editRelationsInStore, setIntoStore } from "atom.io/internal"
5
+ import {
6
+ editRelationsInStore,
7
+ findInStore,
8
+ findRelationsInStore,
9
+ getFromStore,
10
+ getInternalRelationsFromStore,
11
+ IMPLICIT,
12
+ setIntoStore,
13
+ } from "atom.io/internal"
6
14
  import type { Json } from "atom.io/json"
7
- import type { Socket } from "atom.io/realtime"
8
- import { roomIndex, usersInRooms } from "atom.io/realtime"
15
+ import type { RoomKey, Socket, SocketKey, UserKey } from "atom.io/realtime"
16
+ import { roomKeysAtom, usersInRooms } from "atom.io/realtime"
9
17
 
10
18
  import { ChildSocket } from "../ipc-sockets"
19
+ import { realtimeMutableFamilyProvider } from "../realtime-mutable-family-provider"
20
+ import { realtimeMutableProvider } from "../realtime-mutable-provider"
21
+ import type { ServerConfig } from "../server-config"
22
+ import {
23
+ selfListSelectors,
24
+ socketKeysAtom,
25
+ userKeysAtom,
26
+ usersOfSockets,
27
+ } from "./server-user-store"
11
28
 
12
- export const ROOMS: Map<
29
+ export type RoomMap = Map<
13
30
  string,
14
31
  ChildSocket<any, any, ChildProcessWithoutNullStreams>
15
- > = new Map()
32
+ >
33
+
34
+ declare global {
35
+ var ATOM_IO_REALTIME_SERVER_ROOMS: RoomMap
36
+ }
37
+
38
+ export const ROOMS: RoomMap =
39
+ globalThis.ATOM_IO_REALTIME_SERVER_ROOMS ??
40
+ (globalThis.ATOM_IO_REALTIME_SERVER_ROOMS = new Map())
41
+
42
+ export const roomMeta: { count: number } = { count: 0 }
16
43
 
17
44
  export async function spawnRoom(
18
- roomId: string,
45
+ store: Store,
46
+ roomKey: RoomKey,
19
47
  command: string,
20
48
  args: string[],
21
- store: Store,
22
49
  ): Promise<ChildSocket<any, any>> {
23
50
  const child = await new Promise<ChildProcessWithoutNullStreams>((resolve) => {
24
51
  const room = spawn(command, args, { env: process.env })
@@ -30,22 +57,22 @@ export async function spawnRoom(
30
57
  }
31
58
  room.stdout.on(`data`, resolver)
32
59
  })
33
- const roomSocket = new ChildSocket(child, roomId)
34
- ROOMS.set(roomId, roomSocket)
35
- setIntoStore(store, roomIndex, (index) => (index.add(roomId), index))
60
+ const roomSocket = new ChildSocket(child, roomKey)
61
+ ROOMS.set(roomKey, roomSocket)
62
+ setIntoStore(store, roomKeysAtom, (index) => (index.add(roomKey), index))
36
63
 
37
64
  roomSocket.on(`close`, () => {
38
- destroyRoom(roomId, store)
65
+ destroyRoom(store, roomKey)
39
66
  })
40
67
 
41
68
  return roomSocket
42
69
  }
43
70
 
44
71
  export function joinRoom(
45
- roomId: string,
46
- userId: string,
47
- socket: Socket,
48
72
  store: Store,
73
+ roomKey: RoomKey,
74
+ userKey: UserKey,
75
+ socket: Socket,
49
76
  ): {
50
77
  leave: () => void
51
78
  roomSocket: ChildSocket<any, any, ChildProcessWithoutNullStreams>
@@ -63,22 +90,22 @@ export function joinRoom(
63
90
  editRelationsInStore(
64
91
  usersInRooms,
65
92
  (relations) => {
66
- relations.set({ room: roomId, user: userId })
93
+ relations.set({ room: roomKey, user: userKey })
67
94
  },
68
95
  store,
69
96
  )
70
- const roomSocket = ROOMS.get(roomId)
97
+ const roomSocket = ROOMS.get(roomKey)
71
98
  if (!roomSocket) {
72
- store.logger.error(`❌`, `unknown`, roomId, `no room found with this id`)
99
+ store.logger.error(`❌`, `unknown`, roomKey, `no room found with this id`)
73
100
  return null
74
101
  }
75
102
  roomSocket.onAny((...payload) => {
76
103
  socket.emit(...payload)
77
104
  })
78
- roomSocket.emit(`user-joins`, userId)
105
+ roomSocket.emit(`user-joins`, userKey)
79
106
 
80
107
  toRoom = (payload) => {
81
- roomSocket.emit(`user::${userId}`, ...payload)
108
+ roomSocket.emit(`user::${userKey}`, ...payload)
82
109
  }
83
110
  while (roomQueue.length > 0) {
84
111
  const payload = roomQueue.shift()
@@ -88,34 +115,107 @@ export function joinRoom(
88
115
  const leave = () => {
89
116
  socket.offAny(forward)
90
117
  toRoom([`user-leaves`])
91
- leaveRoom(roomId, userId, store)
118
+ leaveRoom(store, roomKey, userKey)
92
119
  }
93
120
 
94
121
  return { leave, roomSocket }
95
122
  }
96
123
 
97
- export function leaveRoom(roomId: string, userId: string, store: Store): void {
124
+ export function leaveRoom(
125
+ store: Store,
126
+ roomKey: RoomKey,
127
+ userKey: UserKey,
128
+ ): void {
98
129
  editRelationsInStore(
99
130
  usersInRooms,
100
131
  (relations) => {
101
- relations.delete({ room: roomId, user: userId })
132
+ relations.delete({ room: roomKey, user: userKey })
102
133
  },
103
134
  store,
104
135
  )
105
136
  }
106
137
 
107
- export function destroyRoom(roomId: string, store: Store): void {
138
+ export function destroyRoom(store: Store, roomKey: RoomKey): void {
139
+ setIntoStore(store, roomKeysAtom, (s) => (s.delete(roomKey), s))
108
140
  editRelationsInStore(
109
141
  usersInRooms,
110
142
  (relations) => {
111
- relations.delete({ room: roomId })
143
+ relations.delete({ room: roomKey })
112
144
  },
113
145
  store,
114
146
  )
115
- setIntoStore(store, roomIndex, (s) => (s.delete(roomId), s))
116
- const room = ROOMS.get(roomId)
147
+ const room = ROOMS.get(roomKey)
117
148
  if (room) {
118
149
  room.emit(`exit`)
119
- ROOMS.delete(roomId)
150
+ ROOMS.delete(roomKey)
120
151
  }
121
152
  }
153
+
154
+ export function provideRooms<RoomNames extends string>(
155
+ { store = IMPLICIT.STORE, socket }: ServerConfig,
156
+ resolveRoomScript: (path: string) => [string, string[]],
157
+ ): void {
158
+ const socketKey = `socket::${socket.id}` satisfies SocketKey
159
+ const userKey = getFromStore(
160
+ store,
161
+ findRelationsInStore(usersOfSockets, socketKey, store).userKeyOfSocket,
162
+ )!
163
+
164
+ const exposeMutable = realtimeMutableProvider({ socket, store })
165
+ const exposeMutableFamily = realtimeMutableFamilyProvider({
166
+ socket,
167
+ store,
168
+ })
169
+
170
+ exposeMutable(roomKeysAtom)
171
+
172
+ const usersInRoomsAtoms = getInternalRelationsFromStore(usersInRooms, store)
173
+ const usersWhoseRoomsCanBeSeenSelector = findInStore(
174
+ store,
175
+ selfListSelectors,
176
+ userKey,
177
+ )
178
+ exposeMutableFamily(usersInRoomsAtoms, usersWhoseRoomsCanBeSeenSelector)
179
+ const usersOfSocketsAtoms = getInternalRelationsFromStore(
180
+ usersOfSockets,
181
+ store,
182
+ )
183
+ exposeMutableFamily(usersOfSocketsAtoms, socketKeysAtom)
184
+
185
+ socket.on(`createRoom`, async (roomName: RoomNames) => {
186
+ // logger.info(`[${shortId}]:${username}`, `creating room "${roomId}"`)
187
+ const roomId = `room::${roomMeta.count++}` satisfies RoomKey
188
+ await spawnRoom(store, roomId, ...resolveRoomScript(roomName))
189
+ socket.on(`deleteRoom:${roomId}`, () => {
190
+ // logger.info(`[${shortId}]:${username}`, `deleting room "${roomId}"`)
191
+ destroyRoom(store, roomId)
192
+ })
193
+ })
194
+
195
+ socket.on(`joinRoom`, (roomKey: RoomKey) => {
196
+ // logger.info(`[${shortId}]:${username}`, `joining room "${roomId}"`)
197
+ const { leave } = joinRoom(store, roomKey, userKey, socket)!
198
+ socket.on(`leaveRoom:${roomKey}`, leave)
199
+ })
200
+
201
+ socket.on(`disconnect`, () => {
202
+ editRelationsInStore(
203
+ usersOfSockets,
204
+ (relations) => relations.delete(socketKey),
205
+ store,
206
+ )
207
+ if (userKey) {
208
+ setIntoStore(
209
+ store,
210
+ userKeysAtom,
211
+ (index) => (index.delete(userKey), index),
212
+ )
213
+ }
214
+ setIntoStore(
215
+ store,
216
+ socketKeysAtom,
217
+ (index) => (index.delete(socketKey), index),
218
+ )
219
+ // logger.info(`${socket.id} disconnected`)
220
+ })
221
+ }