atom.io 0.40.6 → 0.40.8

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 (186) hide show
  1. package/README.md +1 -1
  2. package/dist/data/index.d.ts +1 -1
  3. package/dist/data/index.js +1 -2
  4. package/dist/data/index.js.map +1 -1
  5. package/dist/employ-socket-D6wgByWh.js +12 -0
  6. package/dist/employ-socket-D6wgByWh.js.map +1 -0
  7. package/dist/eslint-plugin/index.js.map +1 -1
  8. package/dist/has-role-CMlaUlaf.js +1133 -0
  9. package/dist/has-role-CMlaUlaf.js.map +1 -0
  10. package/dist/internal/index.d.ts +248 -248
  11. package/dist/internal/index.d.ts.map +1 -1
  12. package/dist/internal/index.js +590 -1803
  13. package/dist/internal/index.js.map +1 -1
  14. package/dist/introspection/index.d.ts +1 -1
  15. package/dist/introspection/index.d.ts.map +1 -1
  16. package/dist/introspection/index.js +13 -32
  17. package/dist/introspection/index.js.map +1 -1
  18. package/dist/is-fn-DY1wZ-md.js +10 -0
  19. package/dist/is-fn-DY1wZ-md.js.map +1 -0
  20. package/dist/json/index.d.ts.map +1 -1
  21. package/dist/json/index.js.map +1 -1
  22. package/dist/main/index.d.ts +33 -33
  23. package/dist/main/index.d.ts.map +1 -1
  24. package/dist/main/index.js +3 -4
  25. package/dist/main/index.js.map +1 -1
  26. package/dist/mutex-store-CSvxY9i3.js +11 -0
  27. package/dist/mutex-store-CSvxY9i3.js.map +1 -0
  28. package/dist/react/index.d.ts +5 -5
  29. package/dist/react/index.d.ts.map +1 -1
  30. package/dist/react/index.js.map +1 -1
  31. package/dist/react-devtools/index.d.ts.map +1 -1
  32. package/dist/react-devtools/index.js +9 -11
  33. package/dist/react-devtools/index.js.map +1 -1
  34. package/dist/realtime/index.d.ts +7 -15
  35. package/dist/realtime/index.d.ts.map +1 -1
  36. package/dist/realtime/index.js +4 -35
  37. package/dist/realtime/index.js.map +1 -1
  38. package/dist/realtime-client/index.d.ts +6 -9
  39. package/dist/realtime-client/index.d.ts.map +1 -1
  40. package/dist/realtime-client/index.js +96 -88
  41. package/dist/realtime-client/index.js.map +1 -1
  42. package/dist/realtime-react/index.d.ts +17 -13
  43. package/dist/realtime-react/index.d.ts.map +1 -1
  44. package/dist/realtime-react/index.js +39 -50
  45. package/dist/realtime-react/index.js.map +1 -1
  46. package/dist/realtime-server/index.d.ts +83 -84
  47. package/dist/realtime-server/index.d.ts.map +1 -1
  48. package/dist/realtime-server/index.js +604 -543
  49. package/dist/realtime-server/index.js.map +1 -1
  50. package/dist/realtime-testing/index.d.ts +5 -4
  51. package/dist/realtime-testing/index.d.ts.map +1 -1
  52. package/dist/realtime-testing/index.js +35 -22
  53. package/dist/realtime-testing/index.js.map +1 -1
  54. package/dist/shared-room-store-BfW3nWif.js +31 -0
  55. package/dist/shared-room-store-BfW3nWif.js.map +1 -0
  56. package/dist/shared-room-store-D2o4ZLjC.d.ts +15 -0
  57. package/dist/shared-room-store-D2o4ZLjC.d.ts.map +1 -0
  58. package/dist/transceivers/set-rtx/index.d.ts.map +1 -1
  59. package/dist/transceivers/set-rtx/index.js +4 -8
  60. package/dist/transceivers/set-rtx/index.js.map +1 -1
  61. package/dist/web/index.d.ts +3 -3
  62. package/dist/web/index.d.ts.map +1 -1
  63. package/dist/web/index.js +4 -3
  64. package/dist/web/index.js.map +1 -1
  65. package/package.json +13 -13
  66. package/src/internal/atom/create-regular-atom.ts +5 -4
  67. package/src/internal/atom/dispose-atom.ts +7 -2
  68. package/src/internal/atom/has-role.ts +3 -3
  69. package/src/internal/caching.ts +4 -2
  70. package/src/internal/families/create-readonly-held-selector-family.ts +2 -1
  71. package/src/internal/families/create-readonly-pure-selector-family.ts +5 -2
  72. package/src/internal/families/create-regular-atom-family.ts +2 -1
  73. package/src/internal/families/create-writable-held-selector-family.ts +2 -1
  74. package/src/internal/families/create-writable-pure-selector-family.ts +5 -2
  75. package/src/internal/families/dispose-from-store.ts +4 -4
  76. package/src/internal/families/find-in-store.ts +10 -10
  77. package/src/internal/families/get-family-of-token.ts +2 -2
  78. package/src/internal/families/index.ts +1 -0
  79. package/src/internal/families/mint-in-store.ts +54 -19
  80. package/src/internal/families/seek-in-store.ts +1 -1
  81. package/src/internal/get-state/get-fallback.ts +2 -2
  82. package/src/internal/get-state/get-from-store.ts +5 -5
  83. package/src/internal/get-state/read-or-compute-value.ts +1 -1
  84. package/src/internal/get-state/reduce-reference.ts +8 -6
  85. package/src/internal/index.ts +2 -220
  86. package/src/internal/molecule.ts +1 -2
  87. package/src/internal/mutable/create-mutable-atom-family.ts +3 -2
  88. package/src/internal/mutable/create-mutable-atom.ts +4 -2
  89. package/src/internal/mutable/get-json-family.ts +1 -1
  90. package/src/internal/mutable/get-update-family.ts +1 -1
  91. package/src/internal/mutable/tracker-family.ts +2 -1
  92. package/src/internal/mutable/tracker.ts +71 -59
  93. package/src/internal/safe-compute.ts +1 -1
  94. package/src/internal/selector/create-readonly-held-selector.ts +2 -1
  95. package/src/internal/selector/create-readonly-pure-selector.ts +2 -1
  96. package/src/internal/selector/create-writable-held-selector.ts +2 -1
  97. package/src/internal/selector/create-writable-pure-selector.ts +2 -1
  98. package/src/internal/selector/dispose-selector.ts +3 -2
  99. package/src/internal/selector/register-selector.ts +8 -5
  100. package/src/internal/selector/trace-selector-atoms.ts +2 -1
  101. package/src/internal/set-state/dispatch-state-update.ts +3 -2
  102. package/src/internal/set-state/evict-downstream.ts +1 -1
  103. package/src/internal/set-state/operate-on-store.ts +16 -22
  104. package/src/internal/set-state/reset-atom-or-selector.ts +5 -3
  105. package/src/internal/set-state/reset-in-store.ts +5 -5
  106. package/src/internal/set-state/set-atom-or-selector.ts +2 -2
  107. package/src/internal/set-state/set-atom.ts +4 -2
  108. package/src/internal/set-state/set-into-store.ts +21 -39
  109. package/src/internal/set-state/set-selector.ts +3 -2
  110. package/src/internal/state-types.ts +228 -0
  111. package/src/internal/store/deposit.ts +4 -4
  112. package/src/internal/store/index.ts +0 -1
  113. package/src/internal/store/store.ts +9 -9
  114. package/src/internal/store/withdraw.ts +4 -4
  115. package/src/internal/subscribe/recall-state.ts +1 -1
  116. package/src/internal/subscribe/subscribe-to-root-atoms.ts +1 -12
  117. package/src/internal/subscribe/subscribe-to-state.ts +9 -0
  118. package/src/internal/subscribe/subscribe-to-transaction.ts +3 -2
  119. package/src/internal/transaction/build-transaction.ts +3 -2
  120. package/src/internal/transaction/index.ts +1 -23
  121. package/src/internal/transaction/is-root-store.ts +4 -1
  122. package/src/internal/transaction/transaction-meta-progress.ts +22 -0
  123. package/src/main/atom.ts +1 -2
  124. package/src/main/find-state.ts +5 -5
  125. package/src/main/get-state.ts +4 -4
  126. package/src/main/realm.ts +2 -2
  127. package/src/main/set-state.ts +10 -10
  128. package/src/react/parse-state-overloads.ts +3 -3
  129. package/src/react/use-i.ts +6 -4
  130. package/src/react/use-loadable.ts +4 -10
  131. package/src/react/use-o.ts +6 -4
  132. package/src/react-devtools/store.ts +6 -6
  133. package/src/realtime/index.ts +1 -0
  134. package/src/realtime/mutex-store.ts +11 -0
  135. package/src/realtime/realtime-continuity.ts +1 -5
  136. package/src/realtime-client/index.ts +0 -1
  137. package/src/realtime-client/pull-atom-family-member.ts +14 -17
  138. package/src/realtime-client/pull-atom.ts +1 -1
  139. package/src/realtime-client/pull-mutable-atom-family-member.ts +16 -12
  140. package/src/realtime-client/pull-selector-family-member.ts +8 -35
  141. package/src/realtime-client/pull-selector-roots.ts +90 -0
  142. package/src/realtime-client/pull-selector.ts +2 -27
  143. package/src/realtime-client/push-state.ts +33 -5
  144. package/src/realtime-client/realtime-client-stores/client-main-store.ts +2 -5
  145. package/src/realtime-react/index.ts +2 -2
  146. package/src/realtime-react/realtime-context.tsx +9 -5
  147. package/src/realtime-react/use-pull-atom-family-member.ts +2 -3
  148. package/src/realtime-react/use-pull-mutable-family-member.ts +2 -3
  149. package/src/realtime-react/use-pull-selector-family-member.ts +5 -6
  150. package/src/realtime-react/use-push.ts +7 -3
  151. package/src/realtime-react/use-realtime-service.ts +11 -11
  152. package/src/realtime-react/use-single-effect.ts +11 -14
  153. package/src/realtime-server/{realtime-server-stores/server-sync-store.ts → continuity/continuity-store.ts} +2 -27
  154. package/src/realtime-server/continuity/provide-continuity.ts +50 -0
  155. package/src/realtime-server/continuity/{subscribe-to-continuity-actions.ts → provide-outcomes.ts} +15 -13
  156. package/src/realtime-server/continuity/{subscribe-to-continuity-perpectives.ts → provide-perspectives.ts} +10 -8
  157. package/src/realtime-server/continuity/{prepare-to-send-initial-payload.ts → provide-startup-payloads.ts} +6 -4
  158. package/src/realtime-server/continuity/receive-action-requests.ts +68 -0
  159. package/src/realtime-server/continuity/track-acknowledgements.ts +46 -0
  160. package/src/realtime-server/employ-socket.ts +14 -0
  161. package/src/realtime-server/index.ts +3 -22
  162. package/src/realtime-server/ipc-sockets/child-socket.ts +125 -66
  163. package/src/realtime-server/ipc-sockets/custom-socket.ts +16 -14
  164. package/src/realtime-server/ipc-sockets/parent-socket.ts +98 -69
  165. package/src/realtime-server/realtime-family-provider.ts +78 -29
  166. package/src/realtime-server/realtime-mutable-family-provider.ts +80 -31
  167. package/src/realtime-server/realtime-mutable-provider.ts +30 -22
  168. package/src/realtime-server/realtime-server-stores/index.ts +0 -2
  169. package/src/realtime-server/realtime-server-stores/server-room-external-store.ts +77 -36
  170. package/src/realtime-server/realtime-server-stores/server-user-store.ts +12 -1
  171. package/src/realtime-server/realtime-state-provider.ts +30 -29
  172. package/src/realtime-server/realtime-state-receiver.ts +62 -16
  173. package/src/realtime-server/server-config.ts +8 -0
  174. package/src/realtime-server/socket-interface.ts +14 -0
  175. package/src/realtime-testing/setup-realtime-test.tsx +70 -31
  176. package/src/web/index.ts +1 -1
  177. package/src/web/{persist-sync.ts → storage-sync.ts} +5 -2
  178. package/src/internal/store/mint-or-counterfeit.ts +0 -108
  179. package/src/realtime-client/server-action.ts +0 -23
  180. package/src/realtime-react/on-mount.ts +0 -5
  181. package/src/realtime-react/use-server-action.ts +0 -19
  182. package/src/realtime-server/continuity/prepare-to-serve-transaction-request.ts +0 -59
  183. package/src/realtime-server/continuity/prepare-to-sync-realtime-continuity.ts +0 -145
  184. package/src/realtime-server/continuity/prepare-to-track-client-acknowledgement.ts +0 -41
  185. package/src/realtime-server/realtime-action-receiver.ts +0 -40
  186. package/src/realtime-server/realtime-server-stores/server-room-external-actions.ts +0 -79
@@ -1,17 +1,21 @@
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 { UserKey } from "../realtime-server-stores"
9
+ import type { StderrLog } from "./child-socket"
10
+ import type { EventBuffer, EventPayload, Events } from "./custom-socket"
7
11
  import { CustomSocket } from "./custom-socket"
8
12
 
9
13
  export class SubjectSocket<
10
14
  I extends Events,
11
15
  O extends Events,
12
16
  > extends CustomSocket<I, O> {
13
- public in: Subject<[string, ...Json.Serializable[]]>
14
- public out: Subject<[string, ...Json.Serializable[]]>
17
+ public in: Subject<EventPayload<I>>
18
+ public out: Subject<EventPayload<O>>
15
19
  public id = `no_id_retrieved`
16
20
  public disposalFunctions: (() => void)[] = []
17
21
 
@@ -24,7 +28,7 @@ export class SubjectSocket<
24
28
  this.in = new Subject()
25
29
  this.out = new Subject()
26
30
  this.in.subscribe(`socket`, (event) => {
27
- this.handleEvent(...(event as [string, ...I[keyof I]]))
31
+ this.handleEvent(...event)
28
32
  })
29
33
  }
30
34
 
@@ -35,31 +39,41 @@ export class SubjectSocket<
35
39
  }
36
40
  }
37
41
 
42
+ export type ParentProcess = {
43
+ pid?: number | undefined
44
+ stdin: Readable
45
+ stdout: Writable
46
+ stderr: Writable
47
+ exit: (code?: number) => void
48
+ }
49
+
38
50
  export class ParentSocket<
39
51
  I extends Events & {
40
- [id in string as `relay:${id}`]: [string, ...Json.Serializable[]]
52
+ [id in string as `relay::${id}`]: [string, ...Json.Array[]]
41
53
  },
42
54
  O extends Events & {
43
- [id in string as `user:${id}`]: [string, ...Json.Serializable[]]
55
+ [id in string as `user::${id}`]: [string, ...Json.Array[]]
44
56
  } & {
45
57
  /* eslint-disable quotes */
46
- "user-joins": [string]
47
- "user-leaves": [string]
58
+ "user-joins": [key: UserKey]
59
+ "user-leaves": [key: UserKey]
48
60
  /* eslint-enable quotes */
49
61
  },
62
+ P extends ParentProcess = ParentProcess,
50
63
  > extends CustomSocket<I, O> {
51
64
  protected incompleteData = ``
52
65
  protected unprocessedEvents: string[] = []
53
66
  protected relays: Map<string, SubjectSocket<any, any>>
54
67
  protected relayServices: ((
55
68
  socket: SubjectSocket<any, any>,
69
+ userKey: UserKey,
56
70
  ) => (() => void) | void)[]
57
- protected process: NodeJS.Process
71
+ public proc: P
58
72
 
59
73
  public id = `#####`
60
74
 
61
- protected log(...args: any[]): void {
62
- this.process.stderr.write(
75
+ protected log(...args: StderrLog): void {
76
+ this.proc.stderr.write(
63
77
  stringifyJson(
64
78
  args.map((arg) =>
65
79
  arg instanceof SetRTX
@@ -70,100 +84,112 @@ export class ParentSocket<
70
84
  )
71
85
  }
72
86
  public logger = {
73
- info: (...args: any[]): void => {
87
+ info: (...args: Json.Array): void => {
74
88
  this.log(`i`, ...args)
75
89
  },
76
- warn: (...args: any[]): void => {
90
+ warn: (...args: Json.Array): void => {
77
91
  this.log(`w`, ...args)
78
92
  },
79
- error: (...args: any[]): void => {
93
+ error: (...args: Json.Array): void => {
80
94
  this.log(`e`, ...args)
81
95
  },
82
96
  }
83
97
 
84
- public constructor() {
98
+ public constructor(proc: P) {
85
99
  super((event, ...args) => {
86
100
  const stringifiedEvent = JSON.stringify([event, ...args])
87
- this.process.stdout.write(stringifiedEvent + `\x03`)
101
+ this.proc.stdout.write(stringifiedEvent + `\x03`)
88
102
  return this
89
103
  })
90
- this.process = process
91
- this.process.stdin.resume()
104
+ this.proc = proc
105
+ this.proc.stdin.resume()
92
106
  this.relays = new Map()
93
107
  this.relayServices = []
94
- // this.logger.info(`🔗`, `uplink`, process.pid)
95
108
 
96
- this.process.stdin.on(
109
+ this.proc.stdin.on(
97
110
  `data`,
98
- <Event extends keyof I>(buffer: EventBuffer<string, I[Event]>) => {
111
+ <K extends string & keyof I>(buffer: EventBuffer<I, K>) => {
99
112
  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
113
+ const pieces = chunk.split(`\x03`)
114
+ const initialMaybeWellFormed = pieces[0]
115
+ pieces[0] = this.incompleteData + initialMaybeWellFormed
116
+ let idx = 0
117
+ for (const piece of pieces) {
118
+ if (piece === ``) {
119
+ continue
120
+ }
121
+ try {
122
+ const jsonPiece = parseJson(piece)
123
+ this.logger.info(`🎰`, `received`, jsonPiece)
124
+ this.handleEvent(...(jsonPiece as EventPayload<I>))
125
+ this.incompleteData = ``
126
+ } catch (thrown0) {
127
+ if (thrown0 instanceof Error) {
128
+ this.logger.error(
129
+ [
130
+ `received malformed data from parent process:`,
131
+ ``,
132
+ piece,
133
+ ``,
134
+ thrown0.message,
135
+ ].join(`\n❌\t`),
136
+ )
137
+ }
138
+ try {
139
+ if (idx === 0) {
140
+ this.incompleteData = piece
141
+ const maybeActualJsonPiece = parseJson(initialMaybeWellFormed)
142
+ this.logger.info(`🎰`, `received`, maybeActualJsonPiece)
143
+ this.handleEvent(...(maybeActualJsonPiece as EventPayload<I>))
144
+ this.incompleteData = ``
145
+ } else {
146
+ this.incompleteData += piece
147
+ }
148
+ } catch (thrown1) {
149
+ if (thrown1 instanceof Error) {
150
+ this.logger.error(
151
+ [
152
+ `received malformed data from parent process:`,
153
+ ``,
154
+ initialMaybeWellFormed,
155
+ ``,
156
+ thrown1.message,
157
+ ].join(`\n❌\t`),
158
+ )
113
159
  }
114
- const parsedEvent = parseJson(event)
115
- this.handleEvent(...(parsedEvent as [string, ...I[keyof I]]))
116
160
  }
117
161
  }
118
- this.incompleteData = ``
119
- } catch (thrown) {
120
- if (thrown instanceof Error) {
121
- this.logger.error(`❗`, thrown.message, thrown.cause, thrown.stack)
122
- }
162
+ ++idx
123
163
  }
124
164
  },
125
165
  )
126
166
 
127
167
  this.on(`exit`, () => {
128
168
  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)
169
+ this.proc.exit(0)
145
170
  })
146
171
 
147
- if (process.pid) {
148
- this.id = process.pid?.toString()
172
+ if (this.proc.pid) {
173
+ this.id = this.proc.pid?.toString()
149
174
  }
150
175
 
151
- this.on(`user-joins`, (username) => {
176
+ this.on(`user-joins`, (username: string) => {
152
177
  this.logger.info(`👤`, `user`, username, `joined`)
153
- const relay = new SubjectSocket(`user:${username}`)
178
+ const userKey = `user::${username}` satisfies UserKey
179
+ const relay = new SubjectSocket(userKey)
154
180
  this.relays.set(username, relay)
155
181
  this.logger.info(
156
182
  `🔗`,
157
183
  `attaching services:`,
158
184
  `[${[...this.relayServices.keys()].join(`, `)}]`,
159
185
  )
160
- for (const attachServices of this.relayServices) {
161
- const cleanup = attachServices(relay)
162
- if (cleanup) {
163
- relay.disposalFunctions.push(cleanup)
186
+ for (const attachRelay of this.relayServices) {
187
+ const cleanupRelay = attachRelay(relay, userKey)
188
+ if (cleanupRelay) {
189
+ relay.disposalFunctions.push(cleanupRelay)
164
190
  }
165
191
  }
166
- this.on(`user:${username}`, (...data) => {
192
+ this.on(userKey, (...data) => {
167
193
  relay.in.next(data)
168
194
  })
169
195
  relay.out.subscribe(`socket`, (data) => {
@@ -180,11 +206,14 @@ export class ParentSocket<
180
206
  }
181
207
  })
182
208
 
183
- process.stdout.write(`ALIVE`)
209
+ this.proc.stdout.write(`ALIVE`)
184
210
  }
185
211
 
186
- public relay(
187
- attachServices: (socket: SubjectSocket<any, any>) => (() => void) | void,
212
+ public receiveRelay(
213
+ attachServices: (
214
+ socket: SubjectSocket<any, any>,
215
+ userKey: UserKey,
216
+ ) => (() => void) | void,
188
217
  ): void {
189
218
  this.logger.info(`🔗`, `running relay method`)
190
219
  this.relayServices.push(attachServices)
@@ -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"