atom.io 0.40.5 → 0.40.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 (163) hide show
  1. package/README.md +1 -1
  2. package/dist/data/index.d.ts +1 -1
  3. package/dist/employ-socket-D6wgByWh.js +12 -0
  4. package/dist/employ-socket-D6wgByWh.js.map +1 -0
  5. package/dist/has-role-hv4-hJMw.js +1149 -0
  6. package/dist/has-role-hv4-hJMw.js.map +1 -0
  7. package/dist/internal/index.d.ts +248 -248
  8. package/dist/internal/index.d.ts.map +1 -1
  9. package/dist/internal/index.js +570 -1712
  10. package/dist/internal/index.js.map +1 -1
  11. package/dist/introspection/index.d.ts +1 -1
  12. package/dist/is-fn-DY1wZ-md.js +10 -0
  13. package/dist/is-fn-DY1wZ-md.js.map +1 -0
  14. package/dist/main/index.d.ts +33 -33
  15. package/dist/main/index.d.ts.map +1 -1
  16. package/dist/main/index.js +2 -2
  17. package/dist/main/index.js.map +1 -1
  18. package/dist/mutex-store-CSvxY9i3.js +11 -0
  19. package/dist/mutex-store-CSvxY9i3.js.map +1 -0
  20. package/dist/react/index.d.ts +5 -21
  21. package/dist/react/index.d.ts.map +1 -1
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/react-devtools/index.js +7 -7
  24. package/dist/react-devtools/index.js.map +1 -1
  25. package/dist/realtime/index.d.ts +7 -15
  26. package/dist/realtime/index.d.ts.map +1 -1
  27. package/dist/realtime/index.js +3 -33
  28. package/dist/realtime/index.js.map +1 -1
  29. package/dist/realtime-client/index.d.ts +5 -5
  30. package/dist/realtime-client/index.d.ts.map +1 -1
  31. package/dist/realtime-client/index.js +92 -69
  32. package/dist/realtime-client/index.js.map +1 -1
  33. package/dist/realtime-react/index.d.ts +17 -10
  34. package/dist/realtime-react/index.d.ts.map +1 -1
  35. package/dist/realtime-react/index.js +41 -41
  36. package/dist/realtime-react/index.js.map +1 -1
  37. package/dist/realtime-server/index.d.ts +60 -53
  38. package/dist/realtime-server/index.d.ts.map +1 -1
  39. package/dist/realtime-server/index.js +592 -485
  40. package/dist/realtime-server/index.js.map +1 -1
  41. package/dist/realtime-testing/index.d.ts +1 -2
  42. package/dist/realtime-testing/index.d.ts.map +1 -1
  43. package/dist/realtime-testing/index.js +25 -18
  44. package/dist/realtime-testing/index.js.map +1 -1
  45. package/dist/shared-room-store-COGGKqes.js +32 -0
  46. package/dist/shared-room-store-COGGKqes.js.map +1 -0
  47. package/dist/shared-room-store-D2o4ZLjC.d.ts +15 -0
  48. package/dist/shared-room-store-D2o4ZLjC.d.ts.map +1 -0
  49. package/dist/web/index.d.ts +3 -3
  50. package/dist/web/index.d.ts.map +1 -1
  51. package/dist/web/index.js +4 -3
  52. package/dist/web/index.js.map +1 -1
  53. package/package.json +12 -12
  54. package/src/internal/atom/create-regular-atom.ts +5 -4
  55. package/src/internal/atom/dispose-atom.ts +7 -2
  56. package/src/internal/atom/has-role.ts +3 -3
  57. package/src/internal/caching.ts +4 -2
  58. package/src/internal/families/create-readonly-held-selector-family.ts +2 -1
  59. package/src/internal/families/create-readonly-pure-selector-family.ts +5 -2
  60. package/src/internal/families/create-regular-atom-family.ts +2 -1
  61. package/src/internal/families/create-writable-held-selector-family.ts +2 -1
  62. package/src/internal/families/create-writable-pure-selector-family.ts +5 -2
  63. package/src/internal/families/dispose-from-store.ts +4 -4
  64. package/src/internal/families/find-in-store.ts +10 -10
  65. package/src/internal/families/get-family-of-token.ts +2 -2
  66. package/src/internal/families/index.ts +1 -0
  67. package/src/internal/families/mint-in-store.ts +54 -19
  68. package/src/internal/families/seek-in-store.ts +1 -1
  69. package/src/internal/get-state/get-fallback.ts +2 -2
  70. package/src/internal/get-state/get-from-store.ts +5 -5
  71. package/src/internal/get-state/read-or-compute-value.ts +1 -1
  72. package/src/internal/get-state/reduce-reference.ts +8 -6
  73. package/src/internal/index.ts +2 -220
  74. package/src/internal/molecule.ts +1 -2
  75. package/src/internal/mutable/create-mutable-atom-family.ts +3 -2
  76. package/src/internal/mutable/create-mutable-atom.ts +4 -2
  77. package/src/internal/mutable/get-json-family.ts +1 -1
  78. package/src/internal/mutable/get-update-family.ts +1 -1
  79. package/src/internal/mutable/tracker-family.ts +2 -1
  80. package/src/internal/mutable/tracker.ts +5 -8
  81. package/src/internal/safe-compute.ts +1 -1
  82. package/src/internal/selector/create-readonly-held-selector.ts +2 -1
  83. package/src/internal/selector/create-readonly-pure-selector.ts +2 -1
  84. package/src/internal/selector/create-writable-held-selector.ts +2 -1
  85. package/src/internal/selector/create-writable-pure-selector.ts +2 -1
  86. package/src/internal/selector/dispose-selector.ts +3 -2
  87. package/src/internal/selector/register-selector.ts +8 -5
  88. package/src/internal/selector/trace-selector-atoms.ts +2 -1
  89. package/src/internal/set-state/dispatch-state-update.ts +3 -2
  90. package/src/internal/set-state/evict-downstream.ts +1 -1
  91. package/src/internal/set-state/operate-on-store.ts +16 -22
  92. package/src/internal/set-state/reset-atom-or-selector.ts +5 -3
  93. package/src/internal/set-state/reset-in-store.ts +5 -5
  94. package/src/internal/set-state/set-atom-or-selector.ts +2 -2
  95. package/src/internal/set-state/set-atom.ts +4 -2
  96. package/src/internal/set-state/set-into-store.ts +21 -39
  97. package/src/internal/set-state/set-selector.ts +3 -2
  98. package/src/internal/state-types.ts +228 -0
  99. package/src/internal/store/deposit.ts +4 -4
  100. package/src/internal/store/index.ts +0 -1
  101. package/src/internal/store/store.ts +9 -9
  102. package/src/internal/store/withdraw.ts +4 -4
  103. package/src/internal/subscribe/recall-state.ts +1 -1
  104. package/src/internal/subscribe/subscribe-to-root-atoms.ts +1 -12
  105. package/src/internal/subscribe/subscribe-to-transaction.ts +3 -2
  106. package/src/internal/transaction/build-transaction.ts +3 -2
  107. package/src/internal/transaction/index.ts +1 -23
  108. package/src/internal/transaction/is-root-store.ts +4 -1
  109. package/src/internal/transaction/transaction-meta-progress.ts +22 -0
  110. package/src/main/atom.ts +1 -2
  111. package/src/main/find-state.ts +5 -5
  112. package/src/main/get-state.ts +4 -4
  113. package/src/main/realm.ts +2 -2
  114. package/src/main/set-state.ts +10 -10
  115. package/src/react/parse-state-overloads.ts +3 -3
  116. package/src/react/use-i.ts +6 -4
  117. package/src/react/use-loadable.ts +4 -30
  118. package/src/react/use-o.ts +6 -4
  119. package/src/react-devtools/store.ts +6 -6
  120. package/src/realtime/index.ts +1 -0
  121. package/src/realtime/mutex-store.ts +11 -0
  122. package/src/realtime/realtime-continuity.ts +1 -5
  123. package/src/realtime-client/pull-atom-family-member.ts +14 -17
  124. package/src/realtime-client/pull-atom.ts +1 -1
  125. package/src/realtime-client/pull-mutable-atom-family-member.ts +16 -12
  126. package/src/realtime-client/pull-selector-family-member.ts +8 -35
  127. package/src/realtime-client/pull-selector-roots.ts +90 -0
  128. package/src/realtime-client/pull-selector.ts +2 -27
  129. package/src/realtime-client/push-state.ts +33 -5
  130. package/src/realtime-client/realtime-client-stores/client-main-store.ts +2 -5
  131. package/src/realtime-react/index.ts +2 -1
  132. package/src/realtime-react/realtime-context.tsx +9 -5
  133. package/src/realtime-react/use-pull-atom-family-member.ts +2 -3
  134. package/src/realtime-react/use-pull-mutable-family-member.ts +2 -3
  135. package/src/realtime-react/use-pull-selector-family-member.ts +5 -6
  136. package/src/realtime-react/use-push.ts +7 -3
  137. package/src/realtime-react/use-realtime-service.ts +11 -11
  138. package/src/realtime-react/use-single-effect.ts +11 -14
  139. package/src/realtime-server/{realtime-server-stores/server-sync-store.ts → continuity/continuity-store.ts} +1 -1
  140. package/src/realtime-server/continuity/prepare-to-sync-realtime-continuity.ts +1 -1
  141. package/src/realtime-server/continuity/prepare-to-track-client-acknowledgement.ts +3 -5
  142. package/src/realtime-server/continuity/subscribe-to-continuity-actions.ts +1 -1
  143. package/src/realtime-server/employ-socket.ts +14 -0
  144. package/src/realtime-server/index.ts +2 -20
  145. package/src/realtime-server/ipc-sockets/child-socket.ts +125 -66
  146. package/src/realtime-server/ipc-sockets/custom-socket.ts +16 -14
  147. package/src/realtime-server/ipc-sockets/parent-socket.ts +81 -58
  148. package/src/realtime-server/realtime-family-provider.ts +78 -29
  149. package/src/realtime-server/realtime-mutable-family-provider.ts +80 -31
  150. package/src/realtime-server/realtime-mutable-provider.ts +30 -22
  151. package/src/realtime-server/realtime-server-stores/index.ts +0 -2
  152. package/src/realtime-server/realtime-server-stores/server-room-external-store.ts +77 -36
  153. package/src/realtime-server/realtime-server-stores/server-user-store.ts +12 -1
  154. package/src/realtime-server/realtime-state-provider.ts +30 -29
  155. package/src/realtime-server/realtime-state-receiver.ts +62 -16
  156. package/src/realtime-server/server-config.ts +9 -0
  157. package/src/realtime-server/socket-interface.ts +14 -0
  158. package/src/realtime-testing/setup-realtime-test.tsx +56 -23
  159. package/src/web/index.ts +1 -1
  160. package/src/web/{persist-sync.ts → storage-sync.ts} +5 -2
  161. package/src/internal/store/mint-or-counterfeit.ts +0 -108
  162. package/src/realtime-react/on-mount.ts +0 -5
  163. package/src/realtime-server/realtime-server-stores/server-room-external-actions.ts +0 -79
@@ -1,17 +1,20 @@
1
+ import type { Readable, Writable } from "node:stream"
2
+
1
3
  import { Subject } from "atom.io/internal"
2
4
  import type { Json } from "atom.io/json"
3
5
  import { parseJson, stringifyJson } from "atom.io/json"
4
6
  import { SetRTX } from "atom.io/transceivers/set-rtx"
5
7
 
6
- import type { EventBuffer, Events } from "./custom-socket"
8
+ import type { StderrLog } from "./child-socket"
9
+ import type { EventBuffer, EventPayload, Events } from "./custom-socket"
7
10
  import { CustomSocket } from "./custom-socket"
8
11
 
9
12
  export class SubjectSocket<
10
13
  I extends Events,
11
14
  O extends Events,
12
15
  > extends CustomSocket<I, O> {
13
- public in: Subject<[string, ...Json.Serializable[]]>
14
- public out: Subject<[string, ...Json.Serializable[]]>
16
+ public in: Subject<EventPayload<I>>
17
+ public out: Subject<EventPayload<O>>
15
18
  public id = `no_id_retrieved`
16
19
  public disposalFunctions: (() => void)[] = []
17
20
 
@@ -24,7 +27,7 @@ export class SubjectSocket<
24
27
  this.in = new Subject()
25
28
  this.out = new Subject()
26
29
  this.in.subscribe(`socket`, (event) => {
27
- this.handleEvent(...(event as [string, ...I[keyof I]]))
30
+ this.handleEvent(...event)
28
31
  })
29
32
  }
30
33
 
@@ -35,18 +38,27 @@ export class SubjectSocket<
35
38
  }
36
39
  }
37
40
 
41
+ export type ParentProcess = {
42
+ pid?: number | undefined
43
+ stdin: Readable
44
+ stdout: Writable
45
+ stderr: Writable
46
+ exit: (code?: number) => void
47
+ }
48
+
38
49
  export class ParentSocket<
39
50
  I extends Events & {
40
- [id in string as `relay:${id}`]: [string, ...Json.Serializable[]]
51
+ [id in string as `relay:${id}`]: [string, ...Json.Array[]]
41
52
  },
42
53
  O extends Events & {
43
- [id in string as `user:${id}`]: [string, ...Json.Serializable[]]
54
+ [id in string as `user:${id}`]: [string, ...Json.Array[]]
44
55
  } & {
45
56
  /* eslint-disable quotes */
46
57
  "user-joins": [string]
47
58
  "user-leaves": [string]
48
59
  /* eslint-enable quotes */
49
60
  },
61
+ P extends ParentProcess = ParentProcess,
50
62
  > extends CustomSocket<I, O> {
51
63
  protected incompleteData = ``
52
64
  protected unprocessedEvents: string[] = []
@@ -54,12 +66,12 @@ export class ParentSocket<
54
66
  protected relayServices: ((
55
67
  socket: SubjectSocket<any, any>,
56
68
  ) => (() => void) | void)[]
57
- protected process: NodeJS.Process
69
+ public proc: P
58
70
 
59
71
  public id = `#####`
60
72
 
61
- protected log(...args: any[]): void {
62
- this.process.stderr.write(
73
+ protected log(...args: StderrLog): void {
74
+ this.proc.stderr.write(
63
75
  stringifyJson(
64
76
  args.map((arg) =>
65
77
  arg instanceof SetRTX
@@ -70,82 +82,93 @@ export class ParentSocket<
70
82
  )
71
83
  }
72
84
  public logger = {
73
- info: (...args: any[]): void => {
85
+ info: (...args: Json.Array): void => {
74
86
  this.log(`i`, ...args)
75
87
  },
76
- warn: (...args: any[]): void => {
88
+ warn: (...args: Json.Array): void => {
77
89
  this.log(`w`, ...args)
78
90
  },
79
- error: (...args: any[]): void => {
91
+ error: (...args: Json.Array): void => {
80
92
  this.log(`e`, ...args)
81
93
  },
82
94
  }
83
95
 
84
- public constructor() {
96
+ public constructor(proc: P) {
85
97
  super((event, ...args) => {
86
98
  const stringifiedEvent = JSON.stringify([event, ...args])
87
- this.process.stdout.write(stringifiedEvent + `\x03`)
99
+ this.proc.stdout.write(stringifiedEvent + `\x03`)
88
100
  return this
89
101
  })
90
- this.process = process
91
- this.process.stdin.resume()
102
+ this.proc = proc
103
+ this.proc.stdin.resume()
92
104
  this.relays = new Map()
93
105
  this.relayServices = []
94
- // this.logger.info(`🔗`, `uplink`, process.pid)
95
106
 
96
- this.process.stdin.on(
107
+ this.proc.stdin.on(
97
108
  `data`,
98
- <Event extends keyof I>(buffer: EventBuffer<string, I[Event]>) => {
109
+ <K extends string & keyof I>(buffer: EventBuffer<I, K>) => {
99
110
  const chunk = buffer.toString()
100
- this.unprocessedEvents.push(...chunk.split(`\x03`))
101
- const newInput = this.unprocessedEvents.shift()
102
- this.incompleteData += newInput ?? ``
103
-
104
- try {
105
- const parsedData = parseJson(this.incompleteData)
106
- this.logger.info(`🎰`, `received`, parsedData)
107
- this.handleEvent(...(parsedData as [string, ...I[keyof I]]))
108
- while (this.unprocessedEvents.length > 0) {
109
- const event = this.unprocessedEvents.shift()
110
- if (event) {
111
- if (this.unprocessedEvents.length === 0) {
112
- this.incompleteData = event
111
+ const pieces = chunk.split(`\x03`)
112
+ const initialMaybeWellFormed = pieces[0]
113
+ pieces[0] = this.incompleteData + initialMaybeWellFormed
114
+ let idx = 0
115
+ for (const piece of pieces) {
116
+ if (piece === ``) {
117
+ continue
118
+ }
119
+ try {
120
+ const jsonPiece = parseJson(piece)
121
+ this.logger.info(`🎰`, `received`, jsonPiece)
122
+ this.handleEvent(...(jsonPiece as EventPayload<I>))
123
+ this.incompleteData = ``
124
+ } catch (thrown0) {
125
+ if (thrown0 instanceof Error) {
126
+ this.logger.error(
127
+ [
128
+ `received malformed data from parent process:`,
129
+ ``,
130
+ piece,
131
+ ``,
132
+ thrown0.message,
133
+ ].join(`\n❌\t`),
134
+ )
135
+ }
136
+ try {
137
+ if (idx === 0) {
138
+ this.incompleteData = piece
139
+ const maybeActualJsonPiece = parseJson(initialMaybeWellFormed)
140
+ this.logger.info(`🎰`, `received`, maybeActualJsonPiece)
141
+ this.handleEvent(...(maybeActualJsonPiece as EventPayload<I>))
142
+ this.incompleteData = ``
143
+ } else {
144
+ this.incompleteData += piece
145
+ }
146
+ } catch (thrown1) {
147
+ if (thrown1 instanceof Error) {
148
+ this.logger.error(
149
+ [
150
+ `received malformed data from parent process:`,
151
+ ``,
152
+ initialMaybeWellFormed,
153
+ ``,
154
+ thrown1.message,
155
+ ].join(`\n❌\t`),
156
+ )
113
157
  }
114
- const parsedEvent = parseJson(event)
115
- this.handleEvent(...(parsedEvent as [string, ...I[keyof I]]))
116
158
  }
117
159
  }
118
- this.incompleteData = ``
119
- } catch (thrown) {
120
- if (thrown instanceof Error) {
121
- this.logger.error(`❗`, thrown.message, thrown.cause, thrown.stack)
122
- }
160
+ ++idx
123
161
  }
124
162
  },
125
163
  )
126
164
 
127
165
  this.on(`exit`, () => {
128
166
  this.logger.info(`🔥`, this.id, `received "exit"`)
129
- process.exit(0)
130
- })
131
- process.on(`exit`, (code) => {
132
- this.logger.info(`🔥`, this.id, `exited with code ${code}`)
133
- })
134
- process.on(`end`, () => {
135
- this.logger.info(`🔥`, this.id, `ended`)
136
- process.exit(0)
137
- })
138
- process.on(`SIGTERM`, () => {
139
- this.logger.error(`🔥`, this.id, `terminated`)
140
- process.exit(0)
141
- })
142
- process.on(`SIGINT`, () => {
143
- this.logger.error(`🔥`, this.id, `interrupted`)
144
- process.exit(0)
167
+ this.proc.exit(0)
145
168
  })
146
169
 
147
- if (process.pid) {
148
- this.id = process.pid?.toString()
170
+ if (this.proc.pid) {
171
+ this.id = this.proc.pid?.toString()
149
172
  }
150
173
 
151
174
  this.on(`user-joins`, (username) => {
@@ -180,7 +203,7 @@ export class ParentSocket<
180
203
  }
181
204
  })
182
205
 
183
- process.stdout.write(`ALIVE`)
206
+ this.proc.stdout.write(`ALIVE`)
184
207
  }
185
208
 
186
209
  public relay(
@@ -5,10 +5,11 @@ import {
5
5
  IMPLICIT,
6
6
  subscribeToState,
7
7
  } from "atom.io/internal"
8
- import type { Canonical, Json } from "atom.io/json"
8
+ import type { Canonical, Json, stringified } from "atom.io/json"
9
9
  import { stringifyJson } from "atom.io/json"
10
10
 
11
11
  import type { ServerConfig } from "."
12
+ import { employSocket } from "./employ-socket"
12
13
 
13
14
  export type FamilyProvider = ReturnType<typeof realtimeAtomFamilyProvider>
14
15
  export function realtimeAtomFamilyProvider({
@@ -22,49 +23,97 @@ export function realtimeAtomFamilyProvider({
22
23
  family: AtomIO.RegularAtomFamilyToken<J, K>,
23
24
  index: AtomIO.ReadableToken<Iterable<K>>,
24
25
  ): () => void {
25
- const unsubCallbacksByKey = new Map<string, () => void>()
26
+ const coreSubscriptions = new Set<() => void>()
27
+ const clearCoreSubscriptions = () => {
28
+ for (const unsub of coreSubscriptions) unsub()
29
+ coreSubscriptions.clear()
30
+ }
31
+ const familyMemberSubscriptionsWanted = new Set<stringified<K>>()
32
+ const familyMemberSubscriptions = new Map<string, () => void>()
33
+ const clearFamilySubscriptions = () => {
34
+ for (const unsub of familyMemberSubscriptions.values()) unsub()
35
+ familyMemberSubscriptions.clear()
36
+ }
26
37
 
27
38
  const fillUnsubRequest = (key: string) => {
28
- socket.off(`unsub:${key}`, fillUnsubRequest)
29
- const unsub = unsubCallbacksByKey.get(key)
39
+ const unsubUnsub = familyMemberSubscriptions.get(`${key}:unsub`)
40
+ if (unsubUnsub) {
41
+ unsubUnsub()
42
+ familyMemberSubscriptions.delete(`${key}:unsub`)
43
+ }
44
+ const unsub = familyMemberSubscriptions.get(key)
30
45
  if (unsub) {
31
46
  unsub()
32
- unsubCallbacksByKey.delete(key)
47
+ familyMemberSubscriptions.delete(key)
33
48
  }
34
49
  }
35
50
 
36
- const fillSubRequest = (subKey: K) => {
37
- const exposedSubKeys = getFromStore(store, index)
51
+ const exposeFamilyMembers = (subKey: K) => {
52
+ const token = findInStore(store, family, subKey)
53
+ getFromStore(store, token)
54
+ socket.emit(`serve:${token.key}`, getFromStore(store, token))
55
+ familyMemberSubscriptions.set(
56
+ token.key,
57
+ subscribeToState(
58
+ store,
59
+ token,
60
+ `expose-family:${family.key}:${socket.id}`,
61
+ ({ newValue }) => {
62
+ socket.emit(`serve:${token.key}`, newValue)
63
+ },
64
+ ),
65
+ )
66
+ familyMemberSubscriptions.set(
67
+ `${token.key}:unsub`,
68
+ employSocket(socket, `unsub:${token.key}`, () => {
69
+ fillUnsubRequest(token.key)
70
+ }),
71
+ )
72
+ }
73
+
74
+ const isAvailable = (exposedSubKeys: Iterable<K>, subKey: K): boolean => {
38
75
  for (const exposedSubKey of exposedSubKeys) {
39
76
  if (stringifyJson(exposedSubKey) === stringifyJson(subKey)) {
40
- const token = findInStore(store, family, subKey)
41
- socket.emit(`serve:${token.key}`, getFromStore(store, token))
42
- const unsubscribe = subscribeToState(
43
- store,
44
- token,
45
- `expose-family:${family.key}:${socket.id}`,
46
- ({ newValue }) => {
47
- socket.emit(`serve:${token.key}`, newValue)
48
- },
49
- )
50
- unsubCallbacksByKey.set(token.key, unsubscribe)
51
- socket.on(`unsub:${token.key}`, () => {
52
- fillUnsubRequest(token.key)
53
- })
54
- break
77
+ return true
55
78
  }
56
79
  }
80
+ return false
57
81
  }
58
82
 
59
- socket.on(`sub:${family.key}`, fillSubRequest)
83
+ const start = () => {
84
+ coreSubscriptions.add(
85
+ employSocket(socket, `sub:${family.key}`, (subKey: K) => {
86
+ const exposedSubKeys = getFromStore(store, index)
87
+ const shouldExpose = isAvailable(exposedSubKeys, subKey)
88
+ if (shouldExpose) {
89
+ exposeFamilyMembers(subKey)
90
+ } else {
91
+ familyMemberSubscriptionsWanted.add(stringifyJson(subKey))
92
+ socket.emit(`unavailable:${family.key}`, subKey)
93
+ }
94
+ }),
95
+ )
96
+ coreSubscriptions.add(
97
+ subscribeToState(
98
+ store,
99
+ index,
100
+ `expose-family:${family.key}:${socket.id}`,
101
+ ({ newValue: newExposedSubKeys }) => {
102
+ for (const subKey of newExposedSubKeys) {
103
+ if (familyMemberSubscriptionsWanted.has(stringifyJson(subKey))) {
104
+ exposeFamilyMembers(subKey)
105
+ }
106
+ }
107
+ },
108
+ ),
109
+ )
110
+ }
60
111
 
61
- return () => {
62
- socket.off(`sub:${family.key}`, fillSubRequest)
112
+ start()
63
113
 
64
- for (const [, unsub] of unsubCallbacksByKey) {
65
- unsub()
66
- }
67
- unsubCallbacksByKey.clear()
114
+ return () => {
115
+ clearCoreSubscriptions()
116
+ clearFamilySubscriptions()
68
117
  }
69
118
  }
70
119
  }
@@ -8,10 +8,11 @@ import {
8
8
  IMPLICIT,
9
9
  subscribeToState,
10
10
  } from "atom.io/internal"
11
- import type { Canonical } from "atom.io/json"
11
+ import type { Canonical, stringified } from "atom.io/json"
12
12
  import { stringifyJson } from "atom.io/json"
13
13
 
14
14
  import type { ServerConfig } from "."
15
+ import { employSocket } from "./employ-socket"
15
16
 
16
17
  export type MutableFamilyProvider = ReturnType<
17
18
  typeof realtimeMutableFamilyProvider
@@ -27,51 +28,99 @@ export function realtimeMutableFamilyProvider({
27
28
  family: AtomIO.MutableAtomFamilyToken<T, K>,
28
29
  index: AtomIO.ReadableToken<Iterable<K>>,
29
30
  ): () => void {
30
- const unsubCallbacksByKey = new Map<string, () => void>()
31
+ const coreSubscriptions = new Set<() => void>()
32
+ const clearCoreSubscriptions = () => {
33
+ for (const unsub of coreSubscriptions) unsub()
34
+ coreSubscriptions.clear()
35
+ }
36
+ const familyMemberSubscriptionsWanted = new Set<stringified<K>>()
37
+ const familyMemberSubscriptions = new Map<string, () => void>()
38
+ const clearFamilySubscriptions = () => {
39
+ for (const unsub of familyMemberSubscriptions.values()) unsub()
40
+ familyMemberSubscriptions.clear()
41
+ }
31
42
 
32
43
  const fillUnsubRequest = (key: string) => {
33
- socket.off(`unsub:${key}`, fillUnsubRequest)
34
- const unsub = unsubCallbacksByKey.get(key)
44
+ const unsubUnsub = familyMemberSubscriptions.get(`${key}:unsub`)
45
+ if (unsubUnsub) {
46
+ unsubUnsub()
47
+ familyMemberSubscriptions.delete(`${key}:unsub`)
48
+ }
49
+ const unsub = familyMemberSubscriptions.get(key)
35
50
  if (unsub) {
36
51
  unsub()
37
- unsubCallbacksByKey.delete(key)
52
+ familyMemberSubscriptions.delete(key)
38
53
  }
39
54
  }
40
55
 
41
- const fillSubRequest = (subKey: K) => {
42
- const exposedSubKeys = getFromStore(store, index)
56
+ const exposeFamilyMembers = (subKey: K) => {
57
+ const token = findInStore(store, family, subKey)
58
+ getFromStore(store, token)
59
+ const jsonToken = getJsonToken(store, token)
60
+ const updateToken = getUpdateToken(token)
61
+ socket.emit(`init:${token.key}`, getFromStore(store, jsonToken))
62
+ familyMemberSubscriptions.set(
63
+ token.key,
64
+ subscribeToState(
65
+ store,
66
+ updateToken,
67
+ `expose-family:${family.key}:${socket.id}`,
68
+ ({ newValue }) => {
69
+ socket.emit(`next:${token.key}`, newValue)
70
+ },
71
+ ),
72
+ )
73
+ familyMemberSubscriptions.set(
74
+ `${token.key}:unsub`,
75
+ employSocket(socket, `unsub:${token.key}`, () => {
76
+ fillUnsubRequest(token.key)
77
+ }),
78
+ )
79
+ }
80
+
81
+ const isAvailable = (exposedSubKeys: Iterable<K>, subKey: K): boolean => {
43
82
  for (const exposedSubKey of exposedSubKeys) {
44
83
  if (stringifyJson(exposedSubKey) === stringifyJson(subKey)) {
45
- const token = findInStore(store, family, subKey)
46
- getFromStore(store, token)
47
- const jsonToken = getJsonToken(store, token)
48
- const updateToken = getUpdateToken(token)
49
- socket.emit(`init:${token.key}`, getFromStore(store, jsonToken))
50
- const unsubscribe = subscribeToState(
51
- store,
52
- updateToken,
53
- `expose-family:${family.key}:${socket.id}`,
54
- ({ newValue }) => {
55
- socket.emit(`next:${token.key}`, newValue)
56
- },
57
- )
58
- unsubCallbacksByKey.set(token.key, unsubscribe)
59
- socket.on(`unsub:${token.key}`, () => {
60
- fillUnsubRequest(token.key)
61
- })
62
- break
84
+ return true
63
85
  }
64
86
  }
87
+ return false
65
88
  }
66
89
 
67
- socket.on(`sub:${family.key}`, fillSubRequest)
90
+ const start = () => {
91
+ coreSubscriptions.add(
92
+ employSocket(socket, `sub:${family.key}`, (subKey: K) => {
93
+ const exposedSubKeys = getFromStore(store, index)
94
+ const shouldExpose = isAvailable(exposedSubKeys, subKey)
95
+ if (shouldExpose) {
96
+ exposeFamilyMembers(subKey)
97
+ } else {
98
+ familyMemberSubscriptionsWanted.add(stringifyJson(subKey))
99
+ socket.emit(`unavailable:${family.key}`, subKey)
100
+ }
101
+ }),
102
+ )
103
+ coreSubscriptions.add(
104
+ subscribeToState(
105
+ store,
106
+ index,
107
+ `expose-family:${family.key}:${socket.id}`,
108
+ ({ newValue: newExposedSubKeys }) => {
109
+ for (const subKey of newExposedSubKeys) {
110
+ if (familyMemberSubscriptionsWanted.has(stringifyJson(subKey))) {
111
+ exposeFamilyMembers(subKey)
112
+ }
113
+ }
114
+ },
115
+ ),
116
+ )
117
+ }
118
+
119
+ start()
68
120
 
69
121
  return () => {
70
- socket.off(`sub:${family.key}`, fillSubRequest)
71
- for (const [, unsub] of unsubCallbacksByKey) {
72
- unsub()
73
- }
74
- unsubCallbacksByKey.clear()
122
+ clearCoreSubscriptions()
123
+ clearFamilySubscriptions()
75
124
  }
76
125
  }
77
126
  }
@@ -10,6 +10,7 @@ import {
10
10
  import type { Json } from "atom.io/json"
11
11
 
12
12
  import type { ServerConfig } from "."
13
+ import { employSocket } from "./employ-socket"
13
14
 
14
15
  export type MutableProvider = ReturnType<typeof realtimeMutableProvider>
15
16
  export function realtimeMutableProvider({
@@ -19,35 +20,42 @@ export function realtimeMutableProvider({
19
20
  return function mutableProvider<
20
21
  Core extends Transceiver<any, Json.Serializable, Json.Serializable>,
21
22
  >(token: AtomIO.MutableAtomToken<Core>): () => void {
22
- let unsubscribeFromStateUpdates: (() => void) | null = null
23
+ const subscriptions = new Set<() => void>()
24
+ const clearSubscriptions = () => {
25
+ for (const unsub of subscriptions) unsub()
26
+ subscriptions.clear()
27
+ }
23
28
 
24
29
  const jsonToken = getJsonToken(store, token)
25
30
  const trackerToken = getUpdateToken(token)
26
31
 
27
- const fillUnsubRequest = () => {
28
- socket.off(`unsub:${token.key}`, fillUnsubRequest)
29
- unsubscribeFromStateUpdates?.()
30
- unsubscribeFromStateUpdates = null
31
- }
32
-
33
- const fillSubRequest = () => {
34
- socket.emit(`init:${token.key}`, getFromStore(store, jsonToken))
35
- unsubscribeFromStateUpdates = subscribeToState(
36
- store,
37
- trackerToken,
38
- `expose-single:${socket.id}`,
39
- ({ newValue }) => {
40
- socket.emit(`next:${token.key}`, newValue)
41
- },
32
+ const start = () => {
33
+ subscriptions.add(
34
+ employSocket(socket, `sub:${token.key}`, () => {
35
+ clearSubscriptions()
36
+ socket.emit(`init:${token.key}`, getFromStore(store, jsonToken))
37
+ subscriptions.add(
38
+ subscribeToState(
39
+ store,
40
+ trackerToken,
41
+ `expose-single:${socket.id}`,
42
+ ({ newValue }) => {
43
+ socket.emit(`next:${token.key}`, newValue)
44
+ },
45
+ ),
46
+ )
47
+ subscriptions.add(
48
+ employSocket(socket, `unsub:${token.key}`, () => {
49
+ clearSubscriptions()
50
+ start()
51
+ }),
52
+ )
53
+ }),
42
54
  )
43
- socket.on(`unsub:${token.key}`, fillUnsubRequest)
44
55
  }
45
56
 
46
- socket.on(`sub:${token.key}`, fillSubRequest)
57
+ start()
47
58
 
48
- return () => {
49
- socket.off(`sub:${token.key}`, fillSubRequest)
50
- unsubscribeFromStateUpdates?.()
51
- }
59
+ return clearSubscriptions
52
60
  }
53
61
  }
@@ -1,4 +1,2 @@
1
- export * from "./server-room-external-actions"
2
1
  export * from "./server-room-external-store"
3
- export * from "./server-sync-store"
4
2
  export * from "./server-user-store"