@livestore/livestore 0.3.0-dev.4 → 0.3.0-dev.40

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 (170) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/QueryCache.d.ts.map +1 -1
  3. package/dist/SqliteDbWrapper.d.ts +60 -0
  4. package/dist/SqliteDbWrapper.d.ts.map +1 -0
  5. package/dist/{SynchronousDatabaseWrapper.js → SqliteDbWrapper.js} +69 -34
  6. package/dist/SqliteDbWrapper.js.map +1 -0
  7. package/dist/effect/LiveStore.d.ts +6 -34
  8. package/dist/effect/LiveStore.d.ts.map +1 -1
  9. package/dist/effect/LiveStore.js +10 -12
  10. package/dist/effect/LiveStore.js.map +1 -1
  11. package/dist/effect/mod.d.ts +3 -0
  12. package/dist/effect/mod.d.ts.map +1 -0
  13. package/dist/effect/mod.js +3 -0
  14. package/dist/effect/mod.js.map +1 -0
  15. package/dist/internal/mod.d.ts +3 -0
  16. package/dist/internal/mod.d.ts.map +1 -0
  17. package/dist/internal/mod.js +3 -0
  18. package/dist/internal/mod.js.map +1 -0
  19. package/dist/live-queries/base-class.d.ts +65 -27
  20. package/dist/live-queries/base-class.d.ts.map +1 -1
  21. package/dist/live-queries/base-class.js +54 -13
  22. package/dist/live-queries/base-class.js.map +1 -1
  23. package/dist/live-queries/client-document-get-query.d.ts +12 -0
  24. package/dist/live-queries/client-document-get-query.d.ts.map +1 -0
  25. package/dist/live-queries/client-document-get-query.js +18 -0
  26. package/dist/live-queries/client-document-get-query.js.map +1 -0
  27. package/dist/live-queries/computed.d.ts +12 -14
  28. package/dist/live-queries/computed.d.ts.map +1 -1
  29. package/dist/live-queries/computed.js +37 -15
  30. package/dist/live-queries/computed.js.map +1 -1
  31. package/dist/live-queries/db-query.d.ts +64 -0
  32. package/dist/live-queries/db-query.d.ts.map +1 -0
  33. package/dist/live-queries/{db.js → db-query.js} +83 -41
  34. package/dist/live-queries/db-query.js.map +1 -0
  35. package/dist/live-queries/db-query.test.d.ts +2 -0
  36. package/dist/live-queries/db-query.test.d.ts.map +1 -0
  37. package/dist/live-queries/db-query.test.js +133 -0
  38. package/dist/live-queries/db-query.test.js.map +1 -0
  39. package/dist/live-queries/mod.d.ts +5 -0
  40. package/dist/live-queries/mod.d.ts.map +1 -0
  41. package/dist/live-queries/mod.js +5 -0
  42. package/dist/live-queries/mod.js.map +1 -0
  43. package/dist/live-queries/signal.d.ts +20 -0
  44. package/dist/live-queries/signal.d.ts.map +1 -0
  45. package/dist/live-queries/signal.js +33 -0
  46. package/dist/live-queries/signal.js.map +1 -0
  47. package/dist/live-queries/signal.test.d.ts +2 -0
  48. package/dist/live-queries/signal.test.d.ts.map +1 -0
  49. package/dist/live-queries/signal.test.js +17 -0
  50. package/dist/live-queries/signal.test.js.map +1 -0
  51. package/dist/mod.d.ts +14 -0
  52. package/dist/mod.d.ts.map +1 -0
  53. package/dist/mod.js +13 -0
  54. package/dist/mod.js.map +1 -0
  55. package/dist/reactive.d.ts +23 -17
  56. package/dist/reactive.d.ts.map +1 -1
  57. package/dist/reactive.js +23 -19
  58. package/dist/reactive.js.map +1 -1
  59. package/dist/reactive.test.js +1 -1
  60. package/dist/reactive.test.js.map +1 -1
  61. package/dist/store/create-store.d.ts +70 -12
  62. package/dist/store/create-store.d.ts.map +1 -1
  63. package/dist/store/create-store.js +69 -19
  64. package/dist/store/create-store.js.map +1 -1
  65. package/dist/store/devtools.d.ts +5 -4
  66. package/dist/store/devtools.d.ts.map +1 -1
  67. package/dist/store/devtools.js +103 -47
  68. package/dist/store/devtools.js.map +1 -1
  69. package/dist/store/store-types.d.ts +32 -42
  70. package/dist/store/store-types.d.ts.map +1 -1
  71. package/dist/store/store-types.js +2 -5
  72. package/dist/store/store-types.js.map +1 -1
  73. package/dist/store/store.d.ts +104 -39
  74. package/dist/store/store.d.ts.map +1 -1
  75. package/dist/store/store.js +261 -214
  76. package/dist/store/store.js.map +1 -1
  77. package/dist/utils/data-structures.d.ts.map +1 -1
  78. package/dist/utils/dev.d.ts.map +1 -1
  79. package/dist/utils/dev.js +6 -1
  80. package/dist/utils/dev.js.map +1 -1
  81. package/dist/utils/function-string.d.ts +7 -0
  82. package/dist/utils/function-string.d.ts.map +1 -0
  83. package/dist/utils/function-string.js +9 -0
  84. package/dist/utils/function-string.js.map +1 -0
  85. package/dist/utils/stack-info.d.ts.map +1 -1
  86. package/dist/utils/stack-info.js +6 -1
  87. package/dist/utils/stack-info.js.map +1 -1
  88. package/dist/utils/stack-info.test.js +54 -1
  89. package/dist/utils/stack-info.test.js.map +1 -1
  90. package/dist/utils/tests/fixture.d.ts +59 -216
  91. package/dist/utils/tests/fixture.d.ts.map +1 -1
  92. package/dist/utils/tests/fixture.js +23 -18
  93. package/dist/utils/tests/fixture.js.map +1 -1
  94. package/dist/utils/tests/mod.d.ts +1 -0
  95. package/dist/utils/tests/mod.d.ts.map +1 -1
  96. package/dist/utils/tests/mod.js +1 -0
  97. package/dist/utils/tests/mod.js.map +1 -1
  98. package/dist/utils/tests/otel.d.ts.map +1 -1
  99. package/dist/utils/tests/otel.js +8 -3
  100. package/dist/utils/tests/otel.js.map +1 -1
  101. package/package.json +29 -26
  102. package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +92 -42
  103. package/src/effect/LiveStore.ts +27 -64
  104. package/src/effect/{index.ts → mod.ts} +2 -3
  105. package/src/internal/mod.ts +2 -0
  106. package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +220 -45
  107. package/src/live-queries/base-class.ts +152 -50
  108. package/src/live-queries/client-document-get-query.ts +52 -0
  109. package/src/live-queries/computed.ts +51 -33
  110. package/src/live-queries/db-query.test.ts +192 -0
  111. package/src/live-queries/{db.ts → db-query.ts} +140 -82
  112. package/src/live-queries/mod.ts +4 -0
  113. package/src/live-queries/signal.test.ts +25 -0
  114. package/src/live-queries/signal.ts +47 -0
  115. package/src/mod.ts +42 -0
  116. package/src/reactive.test.ts +1 -1
  117. package/src/reactive.ts +66 -43
  118. package/src/store/create-store.ts +187 -59
  119. package/src/store/devtools.ts +136 -54
  120. package/src/store/store-types.ts +31 -43
  121. package/src/store/store.ts +385 -309
  122. package/src/utils/dev.ts +6 -1
  123. package/src/utils/function-string.ts +12 -0
  124. package/src/utils/stack-info.test.ts +58 -1
  125. package/src/utils/stack-info.ts +6 -1
  126. package/src/utils/tests/fixture.ts +22 -31
  127. package/src/utils/tests/mod.ts +1 -0
  128. package/src/utils/tests/otel.ts +10 -3
  129. package/dist/SynchronousDatabaseWrapper.d.ts +0 -41
  130. package/dist/SynchronousDatabaseWrapper.d.ts.map +0 -1
  131. package/dist/SynchronousDatabaseWrapper.js.map +0 -1
  132. package/dist/effect/index.d.ts +0 -2
  133. package/dist/effect/index.d.ts.map +0 -1
  134. package/dist/effect/index.js +0 -2
  135. package/dist/effect/index.js.map +0 -1
  136. package/dist/global-state.d.ts +0 -14
  137. package/dist/global-state.d.ts.map +0 -1
  138. package/dist/global-state.js +0 -16
  139. package/dist/global-state.js.map +0 -1
  140. package/dist/index.d.ts +0 -20
  141. package/dist/index.d.ts.map +0 -1
  142. package/dist/index.js +0 -16
  143. package/dist/index.js.map +0 -1
  144. package/dist/live-queries/db.d.ts +0 -66
  145. package/dist/live-queries/db.d.ts.map +0 -1
  146. package/dist/live-queries/db.js.map +0 -1
  147. package/dist/live-queries/db.test.d.ts +0 -2
  148. package/dist/live-queries/db.test.d.ts.map +0 -1
  149. package/dist/live-queries/db.test.js +0 -118
  150. package/dist/live-queries/db.test.js.map +0 -1
  151. package/dist/live-queries/graphql.d.ts +0 -49
  152. package/dist/live-queries/graphql.d.ts.map +0 -1
  153. package/dist/live-queries/graphql.js +0 -122
  154. package/dist/live-queries/graphql.js.map +0 -1
  155. package/dist/row-query-utils.d.ts +0 -17
  156. package/dist/row-query-utils.d.ts.map +0 -1
  157. package/dist/row-query-utils.js +0 -31
  158. package/dist/row-query-utils.js.map +0 -1
  159. package/dist/utils/otel.d.ts +0 -4
  160. package/dist/utils/otel.d.ts.map +0 -1
  161. package/dist/utils/otel.js +0 -6
  162. package/dist/utils/otel.js.map +0 -1
  163. package/src/global-state.ts +0 -20
  164. package/src/index.ts +0 -66
  165. package/src/live-queries/db.test.ts +0 -154
  166. package/src/live-queries/graphql.ts +0 -219
  167. package/src/row-query-utils.ts +0 -66
  168. package/src/utils/otel.ts +0 -9
  169. package/tsconfig.json +0 -18
  170. package/vitest.config.js +0 -9
@@ -1,20 +1,22 @@
1
- import type { ClientSession, DebugInfo } from '@livestore/common'
1
+ import type { ClientSession, ClientSessionSyncProcessor, DebugInfo, SyncState } from '@livestore/common'
2
2
  import { Devtools, liveStoreVersion, UnexpectedError } from '@livestore/common'
3
3
  import { throttle } from '@livestore/utils'
4
4
  import type { WebChannel } from '@livestore/utils/effect'
5
5
  import { Effect, Stream } from '@livestore/utils/effect'
6
+ import { nanoid } from '@livestore/utils/nanoid'
6
7
 
7
8
  import type { LiveQuery, ReactivityGraph } from '../live-queries/base-class.js'
8
9
  import { NOT_REFRESHED_YET } from '../reactive.js'
9
- import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
10
- import { emptyDebugInfo as makeEmptyDebugInfo } from '../SynchronousDatabaseWrapper.js'
10
+ import type { SqliteDbWrapper } from '../SqliteDbWrapper.js'
11
+ import { emptyDebugInfo as makeEmptyDebugInfo } from '../SqliteDbWrapper.js'
11
12
  import type { ReferenceCountedSet } from '../utils/data-structures.js'
12
13
 
13
14
  type IStore = {
14
15
  clientSession: ClientSession
15
16
  reactivityGraph: ReactivityGraph
16
- syncDbWrapper: SynchronousDatabaseWrapper
17
+ sqliteDbWrapper: SqliteDbWrapper
17
18
  activeQueries: ReferenceCountedSet<LiveQuery<any>>
19
+ syncProcessor: ClientSessionSyncProcessor
18
20
  }
19
21
 
20
22
  type Unsub = () => void
@@ -34,47 +36,65 @@ export const connectDevtoolsToStore = ({
34
36
  storeDevtoolsChannel,
35
37
  store,
36
38
  }: {
37
- storeDevtoolsChannel: WebChannel.WebChannel<Devtools.MessageToAppClientSession, Devtools.MessageFromAppClientSession>
39
+ storeDevtoolsChannel: WebChannel.WebChannel<
40
+ Devtools.ClientSession.MessageToApp,
41
+ Devtools.ClientSession.MessageFromApp
42
+ >
38
43
  store: IStore
39
44
  }) =>
40
45
  Effect.gen(function* () {
41
- const appHostId = store.clientSession.coordinator.devtools.appHostId
42
-
43
46
  const reactivityGraphSubcriptions: SubMap = new Map()
44
47
  const liveQueriesSubscriptions: SubMap = new Map()
45
48
  const debugInfoHistorySubscriptions: SubMap = new Map()
49
+ const syncHeadClientSessionSubscriptions: SubMap = new Map()
50
+
51
+ const { clientId, sessionId } = store.clientSession
46
52
 
47
53
  yield* Effect.addFinalizer(() =>
48
54
  Effect.sync(() => {
49
55
  reactivityGraphSubcriptions.forEach((unsub) => unsub())
50
56
  liveQueriesSubscriptions.forEach((unsub) => unsub())
51
57
  debugInfoHistorySubscriptions.forEach((unsub) => unsub())
58
+ syncHeadClientSessionSubscriptions.forEach((unsub) => unsub())
52
59
  }),
53
60
  )
54
61
 
55
- const sendToDevtools = (message: Devtools.MessageFromAppClientSession) =>
62
+ const handledRequestIds = new Set<RequestId>()
63
+
64
+ const sendToDevtools = (message: Devtools.ClientSession.MessageFromApp) =>
56
65
  storeDevtoolsChannel.send(message).pipe(Effect.tapCauseLogPretty, Effect.runFork)
57
66
 
58
- const onMessage = (decodedMessage: typeof Devtools.MessageToAppClientSession.Type) => {
67
+ const onMessage = (decodedMessage: typeof Devtools.ClientSession.MessageToApp.Type) => {
59
68
  // console.debug('@livestore/livestore:store:devtools:onMessage', decodedMessage)
60
69
 
61
- if (decodedMessage.appHostId !== store.clientSession.coordinator.devtools.appHostId) {
70
+ if (decodedMessage.clientId !== clientId || decodedMessage.sessionId !== sessionId) {
62
71
  // console.log(`Unknown message`, event)
63
72
  return
64
73
  }
65
74
 
66
- if (decodedMessage._tag === 'LSD.Disconnect') {
67
- console.error('TODO handle disconnect properly in store')
75
+ if (decodedMessage._tag === 'LSD.ClientSession.Disconnect') {
76
+ // console.error('TODO handle disconnect properly in store')
68
77
  return
69
78
  }
70
79
 
71
80
  const requestId = decodedMessage.requestId
72
81
 
82
+ // TODO we should try to move the duplicate message handling on the webmesh layer
83
+ // So far I could only observe this problem with webmesh proxy channels (e.g. for Expo)
84
+ // Proof: https://share.cleanshot.com/V9G87B0B
85
+ // Also see `leader-worker-devtools.ts` for same problem
86
+ if (handledRequestIds.has(requestId)) {
87
+ return
88
+ }
89
+
90
+ handledRequestIds.add(requestId)
91
+
73
92
  const requestIdleCallback = globalThis.requestIdleCallback ?? ((cb: () => void) => cb())
74
93
 
75
94
  switch (decodedMessage._tag) {
76
- case 'LSD.ReactivityGraphSubscribe': {
95
+ case 'LSD.ClientSession.ReactivityGraphSubscribe': {
77
96
  const includeResults = decodedMessage.includeResults
97
+ const { subscriptionId } = decodedMessage
78
98
 
79
99
  const send = () =>
80
100
  // In order to not add more work to the current tick, we use requestIdleCallback
@@ -82,11 +102,13 @@ export const connectDevtoolsToStore = ({
82
102
  requestIdleCallback(
83
103
  () =>
84
104
  sendToDevtools(
85
- Devtools.ReactivityGraphRes.make({
105
+ Devtools.ClientSession.ReactivityGraphRes.make({
86
106
  reactivityGraph: store.reactivityGraph.getSnapshot({ includeResults }),
87
- requestId,
88
- appHostId,
107
+ requestId: nanoid(10),
108
+ clientId,
109
+ sessionId,
89
110
  liveStoreVersion,
111
+ subscriptionId,
90
112
  }),
91
113
  ),
92
114
  { timeout: 500 },
@@ -99,42 +121,46 @@ export const connectDevtoolsToStore = ({
99
121
  // This might need to be tweaked further and possibly be exposed to the user in some way.
100
122
  const throttledSend = throttle(send, 20)
101
123
 
102
- reactivityGraphSubcriptions.set(requestId, store.reactivityGraph.subscribeToRefresh(throttledSend))
124
+ reactivityGraphSubcriptions.set(subscriptionId, store.reactivityGraph.subscribeToRefresh(throttledSend))
103
125
 
104
126
  break
105
127
  }
106
- case 'LSD.DebugInfoReq': {
128
+ case 'LSD.ClientSession.DebugInfoReq': {
107
129
  sendToDevtools(
108
- Devtools.DebugInfoRes.make({
109
- debugInfo: store.syncDbWrapper.debugInfo,
130
+ Devtools.ClientSession.DebugInfoRes.make({
131
+ debugInfo: store.sqliteDbWrapper.debugInfo,
110
132
  requestId,
111
- appHostId,
133
+ clientId,
134
+ sessionId,
112
135
  liveStoreVersion,
113
136
  }),
114
137
  )
115
138
  break
116
139
  }
117
- case 'LSD.DebugInfoHistorySubscribe': {
140
+ case 'LSD.ClientSession.DebugInfoHistorySubscribe': {
141
+ const { subscriptionId } = decodedMessage
118
142
  const buffer: DebugInfo[] = []
119
143
  let hasStopped = false
120
144
  let tickHandle: number | undefined
121
145
 
122
146
  const tick = () => {
123
- buffer.push(store.syncDbWrapper.debugInfo)
147
+ buffer.push(store.sqliteDbWrapper.debugInfo)
124
148
 
125
149
  // NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
126
150
  // will get the empty debug info
127
151
  // TODO We need to come up with a more graceful way to do store. Probably via a single global
128
152
  // `requestAnimationFrame` loop that is passed in somehow.
129
- store.syncDbWrapper.debugInfo = makeEmptyDebugInfo()
153
+ store.sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
130
154
 
131
155
  if (buffer.length > 10) {
132
156
  sendToDevtools(
133
- Devtools.DebugInfoHistoryRes.make({
157
+ Devtools.ClientSession.DebugInfoHistoryRes.make({
134
158
  debugInfoHistory: buffer,
135
- requestId,
136
- appHostId,
159
+ requestId: nanoid(10),
160
+ clientId,
161
+ sessionId,
137
162
  liveStoreVersion,
163
+ subscriptionId,
138
164
  }),
139
165
  )
140
166
  buffer.length = 0
@@ -155,44 +181,53 @@ export const connectDevtoolsToStore = ({
155
181
  }
156
182
  }
157
183
 
158
- debugInfoHistorySubscriptions.set(requestId, unsub)
184
+ debugInfoHistorySubscriptions.set(subscriptionId, unsub)
159
185
 
160
186
  break
161
187
  }
162
- case 'LSD.DebugInfoHistoryUnsubscribe': {
163
- // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
164
- // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
165
- debugInfoHistorySubscriptions.get(requestId)?.()
166
- debugInfoHistorySubscriptions.delete(requestId)
188
+ case 'LSD.ClientSession.DebugInfoHistoryUnsubscribe': {
189
+ const { subscriptionId } = decodedMessage
190
+ // NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
191
+ // Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
192
+ debugInfoHistorySubscriptions.get(subscriptionId)?.()
193
+ debugInfoHistorySubscriptions.delete(subscriptionId)
167
194
  break
168
195
  }
169
- case 'LSD.DebugInfoResetReq': {
170
- store.syncDbWrapper.debugInfo.slowQueries.clear()
171
- sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, appHostId, liveStoreVersion }))
196
+ case 'LSD.ClientSession.DebugInfoResetReq': {
197
+ store.sqliteDbWrapper.debugInfo.slowQueries.clear()
198
+ sendToDevtools(
199
+ Devtools.ClientSession.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
200
+ )
172
201
  break
173
202
  }
174
- case 'LSD.DebugInfoRerunQueryReq': {
203
+ case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
175
204
  const { queryStr, bindValues, queriedTables } = decodedMessage
176
- store.syncDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
177
- sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, appHostId, liveStoreVersion }))
205
+ store.sqliteDbWrapper.select(queryStr, bindValues, { queriedTables, skipCache: true })
206
+ sendToDevtools(
207
+ Devtools.ClientSession.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
208
+ )
178
209
  break
179
210
  }
180
- case 'LSD.ReactivityGraphUnsubscribe': {
181
- // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
182
- // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
183
- reactivityGraphSubcriptions.get(requestId)?.()
211
+ case 'LSD.ClientSession.ReactivityGraphUnsubscribe': {
212
+ const { subscriptionId } = decodedMessage
213
+ // NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
214
+ // Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
215
+ reactivityGraphSubcriptions.get(subscriptionId)?.()
216
+ reactivityGraphSubcriptions.delete(subscriptionId)
184
217
  break
185
218
  }
186
- case 'LSD.LiveQueriesSubscribe': {
219
+ case 'LSD.ClientSession.LiveQueriesSubscribe': {
220
+ const { subscriptionId } = decodedMessage
187
221
  const send = () =>
188
222
  requestIdleCallback(
189
223
  () =>
190
224
  sendToDevtools(
191
- Devtools.LiveQueriesRes.make({
225
+ Devtools.ClientSession.LiveQueriesRes.make({
192
226
  liveQueries: [...store.activeQueries].map((q) => ({
193
227
  _tag: q._tag,
194
228
  id: q.id,
195
229
  label: q.label,
230
+ hash: q.def.hash,
196
231
  runs: q.runs,
197
232
  executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
198
233
  lastestResult:
@@ -201,9 +236,11 @@ export const connectDevtoolsToStore = ({
201
236
  : q.results$.previousResult,
202
237
  activeSubscriptions: Array.from(q.activeSubscriptions),
203
238
  })),
204
- requestId,
239
+ requestId: nanoid(10),
205
240
  liveStoreVersion,
206
- appHostId,
241
+ clientId,
242
+ sessionId,
243
+ subscriptionId,
207
244
  }),
208
245
  ),
209
246
  { timeout: 500 },
@@ -214,18 +251,63 @@ export const connectDevtoolsToStore = ({
214
251
  // Same as in the reactivity graph subscription case above, we need to throttle the updates
215
252
  const throttledSend = throttle(send, 20)
216
253
 
217
- liveQueriesSubscriptions.set(requestId, store.reactivityGraph.subscribeToRefresh(throttledSend))
254
+ liveQueriesSubscriptions.set(subscriptionId, store.reactivityGraph.subscribeToRefresh(throttledSend))
218
255
 
219
256
  break
220
257
  }
221
- case 'LSD.LiveQueriesUnsubscribe': {
222
- // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
223
- // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
224
- liveQueriesSubscriptions.get(requestId)?.()
225
- liveQueriesSubscriptions.delete(requestId)
258
+ case 'LSD.ClientSession.LiveQueriesUnsubscribe': {
259
+ const { subscriptionId } = decodedMessage
260
+ // NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
261
+ // Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
262
+ liveQueriesSubscriptions.get(subscriptionId)?.()
263
+ liveQueriesSubscriptions.delete(subscriptionId)
226
264
  break
227
265
  }
228
- // No default
266
+ case 'LSD.ClientSession.SyncHeadSubscribe': {
267
+ const { subscriptionId } = decodedMessage
268
+ const send = (syncState: SyncState.SyncState) =>
269
+ sendToDevtools(
270
+ Devtools.ClientSession.SyncHeadRes.make({
271
+ local: syncState.localHead,
272
+ upstream: syncState.upstreamHead,
273
+ requestId: nanoid(10),
274
+ clientId,
275
+ sessionId,
276
+ liveStoreVersion,
277
+ subscriptionId,
278
+ }),
279
+ )
280
+
281
+ send(store.syncProcessor.syncState.pipe(Effect.runSync))
282
+
283
+ syncHeadClientSessionSubscriptions.set(
284
+ subscriptionId,
285
+ store.syncProcessor.syncState.changes.pipe(
286
+ Stream.tap((syncState) => send(syncState)),
287
+ Stream.runDrain,
288
+ Effect.interruptible,
289
+ Effect.tapCauseLogPretty,
290
+ Effect.runCallback,
291
+ ),
292
+ )
293
+
294
+ break
295
+ }
296
+ case 'LSD.ClientSession.SyncHeadUnsubscribe': {
297
+ const { subscriptionId } = decodedMessage
298
+ // NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
299
+ // Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
300
+ syncHeadClientSessionSubscriptions.get(subscriptionId)?.()
301
+ syncHeadClientSessionSubscriptions.delete(subscriptionId)
302
+ break
303
+ }
304
+ case 'LSD.ClientSession.Ping': {
305
+ sendToDevtools(Devtools.ClientSession.Pong.make({ requestId, clientId, sessionId, liveStoreVersion }))
306
+ break
307
+ }
308
+ default: {
309
+ console.warn(`[LSD.ClientSession] Unknown message`, decodedMessage)
310
+ }
229
311
  }
230
312
  }
231
313
 
@@ -1,13 +1,10 @@
1
- import type { ClientSession, IntentionalShutdownCause, UnexpectedError } from '@livestore/common'
2
- import type { EventId, LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
3
- import type { Deferred, MutableHashMap, Runtime, Scope } from '@livestore/utils/effect'
4
- import { Schema } from '@livestore/utils/effect'
1
+ import type { ClientSession, IntentionalShutdownCause, StoreInterrupted, UnexpectedError } from '@livestore/common'
2
+ import type { LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
3
+ import type { Effect, Runtime, Scope } from '@livestore/utils/effect'
4
+ import { Deferred } from '@livestore/utils/effect'
5
5
  import type * as otel from '@opentelemetry/api'
6
- import type { GraphQLSchema } from 'graphql'
7
6
 
8
- import type { ReactivityGraph } from '../live-queries/base-class.js'
9
7
  import type { DebugRefreshReasonBase } from '../reactive.js'
10
- import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
11
8
  import type { StackInfo } from '../utils/stack-info.js'
12
9
  import type { Store } from './store.js'
13
10
 
@@ -19,63 +16,49 @@ export type LiveStoreContext =
19
16
  }
20
17
  | {
21
18
  stage: 'shutdown'
22
- cause: IntentionalShutdownCause | StoreAbort
19
+ cause: IntentionalShutdownCause | StoreInterrupted
23
20
  }
24
21
 
25
- export class StoreAbort extends Schema.TaggedError<StoreAbort>()('LiveStore.StoreAbort', {}) {}
26
- export class StoreInterrupted extends Schema.TaggedError<StoreInterrupted>()('LiveStore.StoreInterrupted', {}) {}
27
-
28
- export type ShutdownDeferred = Deferred.Deferred<
22
+ export type ShutdownDeferred = Deferred.Deferred<void, UnexpectedError | IntentionalShutdownCause | StoreInterrupted>
23
+ export const makeShutdownDeferred: Effect.Effect<ShutdownDeferred> = Deferred.make<
29
24
  void,
30
- UnexpectedError | IntentionalShutdownCause | StoreInterrupted | StoreAbort
31
- >
25
+ UnexpectedError | IntentionalShutdownCause | StoreInterrupted
26
+ >()
32
27
 
33
28
  export type LiveStoreContextRunning = {
34
29
  stage: 'running'
35
30
  store: Store
36
31
  }
37
32
 
38
- export type BaseGraphQLContext = {
39
- queriedTables: Set<string>
40
- /** Needed by Pothos Otel plugin for resolver tracing to work */
41
- otelContext?: otel.Context
42
- }
43
-
44
- export type GraphQLOptions<TContext> = {
45
- schema: GraphQLSchema
46
- makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer, sessionId: string) => TContext
47
- }
48
-
49
33
  export type OtelOptions = {
50
34
  tracer: otel.Tracer
51
35
  rootSpanContext: otel.Context
52
36
  }
53
37
 
54
- export type StoreOptions<
55
- TGraphQLContext extends BaseGraphQLContext,
56
- TSchema extends LiveStoreSchema = LiveStoreSchema,
57
- > = {
38
+ export type StoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext = {}> = {
58
39
  clientSession: ClientSession
59
40
  schema: TSchema
60
41
  storeId: string
61
- // TODO remove graphql-related stuff from store and move to GraphQL query directly
62
- graphQLOptions?: GraphQLOptions<TGraphQLContext>
42
+ context: TContext
63
43
  otelOptions: OtelOptions
64
- reactivityGraph: ReactivityGraph
65
- disableDevtools?: boolean
66
- lifetimeScope: Scope.Scope
67
- runtime: Runtime.Runtime<Scope.Scope>
44
+ effectContext: {
45
+ runtime: Runtime.Runtime<Scope.Scope>
46
+ lifetimeScope: Scope.Scope
47
+ }
48
+ confirmUnsavedChanges: boolean
68
49
  batchUpdates: (runUpdates: () => void) => void
69
- // TODO validate whether we still need this
70
- unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId.EventId, MutationEvent.ForSchema<TSchema>>
50
+ params: {
51
+ leaderPushBatchSize: number
52
+ }
53
+ __runningInDevtools: boolean
71
54
  }
72
55
 
73
56
  export type RefreshReason =
74
57
  | DebugRefreshReasonBase
75
58
  | {
76
- _tag: 'mutate'
77
- /** The mutations that were applied */
78
- mutations: ReadonlyArray<MutationEvent.Any | MutationEvent.PartialAny>
59
+ _tag: 'commit'
60
+ /** The events that were applied */
61
+ events: ReadonlyArray<LiveStoreEvent.AnyDecoded | LiveStoreEvent.PartialAnyDecoded>
79
62
 
80
63
  /** The tables that were written to by the event */
81
64
  writeTables: ReadonlyArray<string>
@@ -87,10 +70,12 @@ export type RefreshReason =
87
70
  label?: string
88
71
  stackInfo?: StackInfo
89
72
  }
73
+ | { _tag: 'subscribe.initial'; label?: string }
74
+ | { _tag: 'subscribe.update'; label?: string }
90
75
  | { _tag: 'manual'; label?: string }
91
76
 
92
77
  export type QueryDebugInfo = {
93
- _tag: 'graphql' | 'db' | 'computed' | 'unknown'
78
+ _tag: string
94
79
  label: string
95
80
  query: string
96
81
  durationMs: number
@@ -98,13 +83,16 @@ export type QueryDebugInfo = {
98
83
 
99
84
  export type StoreOtel = {
100
85
  tracer: otel.Tracer
101
- mutationsSpanContext: otel.Context
86
+ rootSpanContext: otel.Context
87
+ commitsSpanContext: otel.Context
102
88
  queriesSpanContext: otel.Context
103
89
  }
104
90
 
105
- export type StoreMutateOptions = {
91
+ export type StoreCommitOptions = {
106
92
  label?: string
107
93
  skipRefresh?: boolean
108
94
  spanLinks?: otel.Link[]
109
95
  otelContext?: otel.Context
110
96
  }
97
+
98
+ export type Unsubscribe = () => void