@livestore/livestore 0.3.0-dev.10 → 0.3.0-dev.12

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 (117) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/SqliteDbWrapper.d.ts +54 -0
  3. package/dist/SqliteDbWrapper.d.ts.map +1 -0
  4. package/dist/SqliteDbWrapper.js +211 -0
  5. package/dist/SqliteDbWrapper.js.map +1 -0
  6. package/dist/SynchronousDatabaseWrapper.d.ts +14 -5
  7. package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
  8. package/dist/SynchronousDatabaseWrapper.js +24 -4
  9. package/dist/SynchronousDatabaseWrapper.js.map +1 -1
  10. package/dist/effect/LiveStore.d.ts +12 -8
  11. package/dist/effect/LiveStore.d.ts.map +1 -1
  12. package/dist/effect/LiveStore.js +9 -2
  13. package/dist/effect/LiveStore.js.map +1 -1
  14. package/dist/index.d.ts +6 -7
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +4 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/live-queries/base-class.d.ts +57 -21
  19. package/dist/live-queries/base-class.d.ts.map +1 -1
  20. package/dist/live-queries/base-class.js +54 -13
  21. package/dist/live-queries/base-class.js.map +1 -1
  22. package/dist/live-queries/computed.d.ts +7 -7
  23. package/dist/live-queries/computed.d.ts.map +1 -1
  24. package/dist/live-queries/computed.js +34 -11
  25. package/dist/live-queries/computed.js.map +1 -1
  26. package/dist/live-queries/db-query.d.ts +67 -0
  27. package/dist/live-queries/db-query.d.ts.map +1 -0
  28. package/dist/live-queries/db-query.js +243 -0
  29. package/dist/live-queries/db-query.js.map +1 -0
  30. package/dist/live-queries/db-query.test.d.ts +2 -0
  31. package/dist/live-queries/db-query.test.d.ts.map +1 -0
  32. package/dist/live-queries/db-query.test.js +113 -0
  33. package/dist/live-queries/db-query.test.js.map +1 -0
  34. package/dist/live-queries/db.d.ts +12 -15
  35. package/dist/live-queries/db.d.ts.map +1 -1
  36. package/dist/live-queries/db.js +44 -25
  37. package/dist/live-queries/db.js.map +1 -1
  38. package/dist/live-queries/db.test.js +16 -14
  39. package/dist/live-queries/db.test.js.map +1 -1
  40. package/dist/live-queries/graphql.d.ts +8 -8
  41. package/dist/live-queries/graphql.d.ts.map +1 -1
  42. package/dist/live-queries/graphql.js +34 -9
  43. package/dist/live-queries/graphql.js.map +1 -1
  44. package/dist/live-queries/make-ref.d.ts +20 -0
  45. package/dist/live-queries/make-ref.d.ts.map +1 -0
  46. package/dist/live-queries/make-ref.js +33 -0
  47. package/dist/live-queries/make-ref.js.map +1 -0
  48. package/dist/reactive.d.ts +19 -13
  49. package/dist/reactive.d.ts.map +1 -1
  50. package/dist/reactive.js +22 -18
  51. package/dist/reactive.js.map +1 -1
  52. package/dist/reactive.test.js +1 -1
  53. package/dist/reactive.test.js.map +1 -1
  54. package/dist/row-query-utils.d.ts +6 -6
  55. package/dist/row-query-utils.d.ts.map +1 -1
  56. package/dist/row-query-utils.js +15 -11
  57. package/dist/row-query-utils.js.map +1 -1
  58. package/dist/store/create-store.d.ts +7 -5
  59. package/dist/store/create-store.d.ts.map +1 -1
  60. package/dist/store/create-store.js +21 -7
  61. package/dist/store/create-store.js.map +1 -1
  62. package/dist/store/devtools.d.ts +5 -4
  63. package/dist/store/devtools.d.ts.map +1 -1
  64. package/dist/store/devtools.js +45 -23
  65. package/dist/store/devtools.js.map +1 -1
  66. package/dist/store/store-types.d.ts +9 -4
  67. package/dist/store/store-types.d.ts.map +1 -1
  68. package/dist/store/store-types.js.map +1 -1
  69. package/dist/store/store.d.ts +36 -18
  70. package/dist/store/store.d.ts.map +1 -1
  71. package/dist/store/store.js +127 -75
  72. package/dist/store/store.js.map +1 -1
  73. package/dist/utils/expo.d.ts +2 -0
  74. package/dist/utils/expo.d.ts.map +1 -0
  75. package/dist/utils/expo.js +8 -0
  76. package/dist/utils/expo.js.map +1 -0
  77. package/dist/utils/function-string.d.ts +7 -0
  78. package/dist/utils/function-string.d.ts.map +1 -0
  79. package/dist/utils/function-string.js +9 -0
  80. package/dist/utils/function-string.js.map +1 -0
  81. package/dist/utils/stack-info.d.ts.map +1 -1
  82. package/dist/utils/stack-info.js +6 -1
  83. package/dist/utils/stack-info.js.map +1 -1
  84. package/dist/utils/stack-info.test.js +54 -1
  85. package/dist/utils/stack-info.test.js.map +1 -1
  86. package/dist/utils/tests/fixture.d.ts +2 -6
  87. package/dist/utils/tests/fixture.d.ts.map +1 -1
  88. package/dist/utils/tests/fixture.js +3 -5
  89. package/dist/utils/tests/fixture.js.map +1 -1
  90. package/dist/utils/tests/mod.d.ts +1 -0
  91. package/dist/utils/tests/mod.d.ts.map +1 -1
  92. package/dist/utils/tests/mod.js +1 -0
  93. package/dist/utils/tests/mod.js.map +1 -1
  94. package/package.json +5 -5
  95. package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +41 -12
  96. package/src/effect/LiveStore.ts +22 -14
  97. package/src/index.ts +14 -7
  98. package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +196 -45
  99. package/src/live-queries/base-class.ts +151 -40
  100. package/src/live-queries/computed.ts +44 -19
  101. package/src/live-queries/{db.test.ts → db-query.test.ts} +44 -32
  102. package/src/live-queries/{db.ts → db-query.ts} +96 -39
  103. package/src/live-queries/graphql.ts +46 -21
  104. package/src/live-queries/make-ref.ts +47 -0
  105. package/src/reactive.test.ts +1 -1
  106. package/src/reactive.ts +60 -37
  107. package/src/row-query-utils.ts +32 -21
  108. package/src/store/create-store.ts +55 -27
  109. package/src/store/devtools.ts +74 -29
  110. package/src/store/store-types.ts +6 -4
  111. package/src/store/store.ts +231 -121
  112. package/src/utils/function-string.ts +12 -0
  113. package/src/utils/stack-info.test.ts +58 -1
  114. package/src/utils/stack-info.ts +6 -1
  115. package/src/utils/tests/fixture.ts +2 -7
  116. package/src/utils/tests/mod.ts +1 -0
  117. package/src/global-state.ts +0 -20
@@ -2,8 +2,9 @@ import type {
2
2
  Adapter,
3
3
  BootStatus,
4
4
  ClientSession,
5
+ ClientSessionDevtoolsChannel,
5
6
  IntentionalShutdownCause,
6
- StoreDevtoolsChannel,
7
+ MigrationsReport,
7
8
  } from '@livestore/common'
8
9
  import { provideOtel, UnexpectedError } from '@livestore/common'
9
10
  import type { EventId, LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
@@ -14,6 +15,7 @@ import {
14
15
  Effect,
15
16
  Exit,
16
17
  identity,
18
+ Layer,
17
19
  Logger,
18
20
  LogLevel,
19
21
  MutableHashMap,
@@ -26,8 +28,7 @@ import {
26
28
  import { nanoid } from '@livestore/utils/nanoid'
27
29
  import * as otel from '@opentelemetry/api'
28
30
 
29
- import { globalReactivityGraph } from '../global-state.js'
30
- import type { ReactivityGraph } from '../live-queries/base-class.js'
31
+ import { LiveStoreContextRunning } from '../effect/index.js'
31
32
  import { connectDevtoolsToStore } from './devtools.js'
32
33
  import { Store } from './store.js'
33
34
  import type { BaseGraphQLContext, GraphQLOptions, OtelOptions, ShutdownDeferred } from './store-types.js'
@@ -36,12 +37,14 @@ export interface CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext,
36
37
  schema: TSchema
37
38
  adapter: Adapter
38
39
  storeId: string
39
- reactivityGraph?: ReactivityGraph
40
40
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
41
41
  boot?: (
42
42
  store: Store<TGraphQLContext, TSchema>,
43
- parentSpan: otel.Span,
44
- ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer>
43
+ ctx: {
44
+ migrationsReport: MigrationsReport
45
+ parentSpan: otel.Span
46
+ },
47
+ ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
45
48
  batchUpdates?: (run: () => void) => void
46
49
  disableDevtools?: boolean
47
50
  onBootStatus?: (status: BootStatus) => void
@@ -95,7 +98,6 @@ export const createStore = <
95
98
  storeId,
96
99
  graphQLOptions,
97
100
  boot,
98
- reactivityGraph = globalReactivityGraph,
99
101
  batchUpdates,
100
102
  disableDevtools,
101
103
  onBootStatus,
@@ -109,6 +111,8 @@ export const createStore = <
109
111
  Effect.gen(function* () {
110
112
  const lifetimeScope = yield* Scope.make()
111
113
 
114
+ yield* validateStoreId(storeId)
115
+
112
116
  yield* Effect.addFinalizer((_) => Scope.close(lifetimeScope, _))
113
117
 
114
118
  const debugInstanceId = debug?.instanceId ?? nanoid(10)
@@ -130,7 +134,7 @@ export const createStore = <
130
134
 
131
135
  const storeDeferred = yield* Deferred.make<Store>()
132
136
 
133
- const connectDevtoolsToStore_ = (storeDevtoolsChannel: StoreDevtoolsChannel) =>
137
+ const connectDevtoolsToStore_ = (storeDevtoolsChannel: ClientSessionDevtoolsChannel) =>
134
138
  Effect.gen(function* () {
135
139
  const store = yield* storeDeferred
136
140
  yield* connectDevtoolsToStore({ storeDevtoolsChannel, store })
@@ -160,32 +164,44 @@ export const createStore = <
160
164
  debugInstanceId,
161
165
  }).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
162
166
 
167
+ if (LS_DEV && clientSession.leaderThread.initialState.migrationsReport.migrations.length > 0) {
168
+ yield* Effect.logDebug(
169
+ '[@livestore/livestore:createStore] migrationsReport',
170
+ ...clientSession.leaderThread.initialState.migrationsReport.migrations.map(
171
+ (m) =>
172
+ `Schema hash mismatch for table '${m.tableName}' (DB: ${m.hashes.actual}, expected: ${m.hashes.expected}), migrating table...`,
173
+ ),
174
+ )
175
+ }
176
+
163
177
  // TODO fill up with unsynced mutation events from the client session
164
178
  const unsyncedMutationEvents = MutableHashMap.empty<EventId.EventId, MutationEvent.ForSchema<TSchema>>()
165
179
 
166
- const store = Store.createStore<TGraphQLContext, TSchema>(
167
- {
168
- clientSession,
169
- schema,
170
- graphQLOptions,
171
- otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
172
- reactivityGraph,
173
- disableDevtools,
174
- unsyncedMutationEvents,
175
- lifetimeScope,
176
- runtime,
177
- // NOTE during boot we're not yet executing mutations in a batched context
178
- // but only set the provided `batchUpdates` function after boot
179
- batchUpdates: (run) => run(),
180
- storeId,
181
- },
182
- span,
183
- )
180
+ const store = new Store<TGraphQLContext, TSchema>({
181
+ clientSession,
182
+ schema,
183
+ graphQLOptions,
184
+ otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
185
+ disableDevtools,
186
+ unsyncedMutationEvents,
187
+ lifetimeScope,
188
+ runtime,
189
+ // NOTE during boot we're not yet executing mutations in a batched context
190
+ // but only set the provided `batchUpdates` function after boot
191
+ batchUpdates: (run) => run(),
192
+ storeId,
193
+ })
194
+
195
+ // Starts background fibers (syncing, mutation processing, etc) for store
196
+ yield* store.boot
184
197
 
185
198
  if (boot !== undefined) {
186
199
  // TODO also incorporate `boot` function progress into `bootStatusQueue`
187
- yield* Effect.tryAll(() => boot(store, span)).pipe(
200
+ yield* Effect.tryAll(() =>
201
+ boot(store, { migrationsReport: clientSession.leaderThread.initialState.migrationsReport, parentSpan: span }),
202
+ ).pipe(
188
203
  UnexpectedError.mapToUnexpectedError,
204
+ Effect.provide(Layer.succeed(LiveStoreContextRunning, { stage: 'running', store: store as any as Store })),
189
205
  Effect.withSpan('createStore:boot'),
190
206
  )
191
207
  }
@@ -208,3 +224,15 @@ export const createStore = <
208
224
  Scope.extend(lifetimeScope),
209
225
  )
210
226
  })
227
+
228
+ const validateStoreId = (storeId: string) =>
229
+ Effect.gen(function* () {
230
+ const validChars = /^[a-zA-Z0-9_-]+$/
231
+
232
+ if (!validChars.test(storeId)) {
233
+ return yield* UnexpectedError.make({
234
+ cause: `Invalid storeId: ${storeId}. Only alphanumeric characters, underscores, and hyphens are allowed.`,
235
+ payload: { storeId },
236
+ })
237
+ }
238
+ })
@@ -1,4 +1,4 @@
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'
@@ -6,15 +6,16 @@ import { Effect, Stream } from '@livestore/utils/effect'
6
6
 
7
7
  import type { LiveQuery, ReactivityGraph } from '../live-queries/base-class.js'
8
8
  import { NOT_REFRESHED_YET } from '../reactive.js'
9
- import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
10
- import { emptyDebugInfo as makeEmptyDebugInfo } from '../SynchronousDatabaseWrapper.js'
9
+ import type { SqliteDbWrapper } from '../SqliteDbWrapper.js'
10
+ import { emptyDebugInfo as makeEmptyDebugInfo } from '../SqliteDbWrapper.js'
11
11
  import type { ReferenceCountedSet } from '../utils/data-structures.js'
12
12
 
13
13
  type IStore = {
14
14
  clientSession: ClientSession
15
15
  reactivityGraph: ReactivityGraph
16
- syncDbWrapper: SynchronousDatabaseWrapper
16
+ sqliteDbWrapper: SqliteDbWrapper
17
17
  activeQueries: ReferenceCountedSet<LiveQuery<any>>
18
+ syncProcessor: ClientSessionSyncProcessor
18
19
  }
19
20
 
20
21
  type Unsub = () => void
@@ -34,13 +35,17 @@ export const connectDevtoolsToStore = ({
34
35
  storeDevtoolsChannel,
35
36
  store,
36
37
  }: {
37
- storeDevtoolsChannel: WebChannel.WebChannel<Devtools.MessageToAppClientSession, Devtools.MessageFromAppClientSession>
38
+ storeDevtoolsChannel: WebChannel.WebChannel<
39
+ Devtools.ClientSession.MessageToApp,
40
+ Devtools.ClientSession.MessageFromApp
41
+ >
38
42
  store: IStore
39
43
  }) =>
40
44
  Effect.gen(function* () {
41
45
  const reactivityGraphSubcriptions: SubMap = new Map()
42
46
  const liveQueriesSubscriptions: SubMap = new Map()
43
47
  const debugInfoHistorySubscriptions: SubMap = new Map()
48
+ const syncHeadClientSessionSubscriptions: SubMap = new Map()
44
49
 
45
50
  const { clientId, sessionId } = store.clientSession
46
51
 
@@ -49,13 +54,14 @@ export const connectDevtoolsToStore = ({
49
54
  reactivityGraphSubcriptions.forEach((unsub) => unsub())
50
55
  liveQueriesSubscriptions.forEach((unsub) => unsub())
51
56
  debugInfoHistorySubscriptions.forEach((unsub) => unsub())
57
+ syncHeadClientSessionSubscriptions.forEach((unsub) => unsub())
52
58
  }),
53
59
  )
54
60
 
55
- const sendToDevtools = (message: Devtools.MessageFromAppClientSession) =>
61
+ const sendToDevtools = (message: Devtools.ClientSession.MessageFromApp) =>
56
62
  storeDevtoolsChannel.send(message).pipe(Effect.tapCauseLogPretty, Effect.runFork)
57
63
 
58
- const onMessage = (decodedMessage: typeof Devtools.MessageToAppClientSession.Type) => {
64
+ const onMessage = (decodedMessage: typeof Devtools.ClientSession.MessageToApp.Type) => {
59
65
  // console.debug('@livestore/livestore:store:devtools:onMessage', decodedMessage)
60
66
 
61
67
  if (decodedMessage.clientId !== clientId || decodedMessage.sessionId !== sessionId) {
@@ -63,7 +69,7 @@ export const connectDevtoolsToStore = ({
63
69
  return
64
70
  }
65
71
 
66
- if (decodedMessage._tag === 'LSD.Disconnect') {
72
+ if (decodedMessage._tag === 'LSD.ClientSession.Disconnect') {
67
73
  // console.error('TODO handle disconnect properly in store')
68
74
  return
69
75
  }
@@ -73,7 +79,7 @@ export const connectDevtoolsToStore = ({
73
79
  const requestIdleCallback = globalThis.requestIdleCallback ?? ((cb: () => void) => cb())
74
80
 
75
81
  switch (decodedMessage._tag) {
76
- case 'LSD.ReactivityGraphSubscribe': {
82
+ case 'LSD.ClientSession.ReactivityGraphSubscribe': {
77
83
  const includeResults = decodedMessage.includeResults
78
84
 
79
85
  const send = () =>
@@ -82,7 +88,7 @@ export const connectDevtoolsToStore = ({
82
88
  requestIdleCallback(
83
89
  () =>
84
90
  sendToDevtools(
85
- Devtools.ReactivityGraphRes.make({
91
+ Devtools.ClientSession.ReactivityGraphRes.make({
86
92
  reactivityGraph: store.reactivityGraph.getSnapshot({ includeResults }),
87
93
  requestId,
88
94
  clientId,
@@ -104,10 +110,10 @@ export const connectDevtoolsToStore = ({
104
110
 
105
111
  break
106
112
  }
107
- case 'LSD.DebugInfoReq': {
113
+ case 'LSD.ClientSession.DebugInfoReq': {
108
114
  sendToDevtools(
109
- Devtools.DebugInfoRes.make({
110
- debugInfo: store.syncDbWrapper.debugInfo,
115
+ Devtools.ClientSession.DebugInfoRes.make({
116
+ debugInfo: store.sqliteDbWrapper.debugInfo,
111
117
  requestId,
112
118
  clientId,
113
119
  sessionId,
@@ -116,23 +122,23 @@ export const connectDevtoolsToStore = ({
116
122
  )
117
123
  break
118
124
  }
119
- case 'LSD.DebugInfoHistorySubscribe': {
125
+ case 'LSD.ClientSession.DebugInfoHistorySubscribe': {
120
126
  const buffer: DebugInfo[] = []
121
127
  let hasStopped = false
122
128
  let tickHandle: number | undefined
123
129
 
124
130
  const tick = () => {
125
- buffer.push(store.syncDbWrapper.debugInfo)
131
+ buffer.push(store.sqliteDbWrapper.debugInfo)
126
132
 
127
133
  // NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
128
134
  // will get the empty debug info
129
135
  // TODO We need to come up with a more graceful way to do store. Probably via a single global
130
136
  // `requestAnimationFrame` loop that is passed in somehow.
131
- store.syncDbWrapper.debugInfo = makeEmptyDebugInfo()
137
+ store.sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
132
138
 
133
139
  if (buffer.length > 10) {
134
140
  sendToDevtools(
135
- Devtools.DebugInfoHistoryRes.make({
141
+ Devtools.ClientSession.DebugInfoHistoryRes.make({
136
142
  debugInfoHistory: buffer,
137
143
  requestId,
138
144
  clientId,
@@ -162,36 +168,40 @@ export const connectDevtoolsToStore = ({
162
168
 
163
169
  break
164
170
  }
165
- case 'LSD.DebugInfoHistoryUnsubscribe': {
171
+ case 'LSD.ClientSession.DebugInfoHistoryUnsubscribe': {
166
172
  // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
167
173
  // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
168
174
  debugInfoHistorySubscriptions.get(requestId)?.()
169
175
  debugInfoHistorySubscriptions.delete(requestId)
170
176
  break
171
177
  }
172
- case 'LSD.DebugInfoResetReq': {
173
- store.syncDbWrapper.debugInfo.slowQueries.clear()
174
- sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
178
+ case 'LSD.ClientSession.DebugInfoResetReq': {
179
+ store.sqliteDbWrapper.debugInfo.slowQueries.clear()
180
+ sendToDevtools(
181
+ Devtools.ClientSession.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
182
+ )
175
183
  break
176
184
  }
177
- case 'LSD.DebugInfoRerunQueryReq': {
185
+ case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
178
186
  const { queryStr, bindValues, queriedTables } = decodedMessage
179
- store.syncDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
180
- sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
187
+ store.sqliteDbWrapper.select(queryStr, bindValues, { queriedTables, skipCache: true })
188
+ sendToDevtools(
189
+ Devtools.ClientSession.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
190
+ )
181
191
  break
182
192
  }
183
- case 'LSD.ReactivityGraphUnsubscribe': {
193
+ case 'LSD.ClientSession.ReactivityGraphUnsubscribe': {
184
194
  // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
185
195
  // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
186
196
  reactivityGraphSubcriptions.get(requestId)?.()
187
197
  break
188
198
  }
189
- case 'LSD.LiveQueriesSubscribe': {
199
+ case 'LSD.ClientSession.LiveQueriesSubscribe': {
190
200
  const send = () =>
191
201
  requestIdleCallback(
192
202
  () =>
193
203
  sendToDevtools(
194
- Devtools.LiveQueriesRes.make({
204
+ Devtools.ClientSession.LiveQueriesRes.make({
195
205
  liveQueries: [...store.activeQueries].map((q) => ({
196
206
  _tag: q._tag,
197
207
  id: q.id,
@@ -222,14 +232,49 @@ export const connectDevtoolsToStore = ({
222
232
 
223
233
  break
224
234
  }
225
- case 'LSD.LiveQueriesUnsubscribe': {
235
+ case 'LSD.ClientSession.LiveQueriesUnsubscribe': {
226
236
  // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
227
237
  // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
228
238
  liveQueriesSubscriptions.get(requestId)?.()
229
239
  liveQueriesSubscriptions.delete(requestId)
230
240
  break
231
241
  }
232
- // No default
242
+ case 'LSD.ClientSession.SyncHeadSubscribe': {
243
+ const send = (syncState: SyncState) =>
244
+ sendToDevtools(
245
+ Devtools.ClientSession.SyncHeadRes.make({
246
+ local: syncState.localHead,
247
+ upstream: syncState.upstreamHead,
248
+ requestId,
249
+ clientId,
250
+ sessionId,
251
+ liveStoreVersion,
252
+ }),
253
+ )
254
+
255
+ send(store.syncProcessor.syncState.pipe(Effect.runSync))
256
+
257
+ syncHeadClientSessionSubscriptions.set(
258
+ requestId,
259
+ store.syncProcessor.syncState.changes.pipe(
260
+ Stream.tap((syncState) => send(syncState)),
261
+ Stream.runDrain,
262
+ Effect.interruptible,
263
+ Effect.tapCauseLogPretty,
264
+ Effect.runCallback,
265
+ ),
266
+ )
267
+
268
+ break
269
+ }
270
+ case 'LSD.ClientSession.SyncHeadUnsubscribe': {
271
+ syncHeadClientSessionSubscriptions.get(requestId)?.()
272
+ syncHeadClientSessionSubscriptions.delete(requestId)
273
+ break
274
+ }
275
+ default: {
276
+ console.warn(`[LSD.ClientSession] Unknown message`, decodedMessage)
277
+ }
233
278
  }
234
279
  }
235
280
 
@@ -5,9 +5,8 @@ import { Schema } from '@livestore/utils/effect'
5
5
  import type * as otel from '@opentelemetry/api'
6
6
  import type { GraphQLSchema } from 'graphql'
7
7
 
8
- import type { ReactivityGraph } from '../live-queries/base-class.js'
9
8
  import type { DebugRefreshReasonBase } from '../reactive.js'
10
- import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
9
+ import type { SqliteDbWrapper } from '../SqliteDbWrapper.js'
11
10
  import type { StackInfo } from '../utils/stack-info.js'
12
11
  import type { Store } from './store.js'
13
12
 
@@ -43,7 +42,7 @@ export type BaseGraphQLContext = {
43
42
 
44
43
  export type GraphQLOptions<TContext> = {
45
44
  schema: GraphQLSchema
46
- makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer, sessionId: string) => TContext
45
+ makeContext: (db: SqliteDbWrapper, tracer: otel.Tracer, sessionId: string) => TContext
47
46
  }
48
47
 
49
48
  export type OtelOptions = {
@@ -61,7 +60,6 @@ export type StoreOptions<
61
60
  // TODO remove graphql-related stuff from store and move to GraphQL query directly
62
61
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
63
62
  otelOptions: OtelOptions
64
- reactivityGraph: ReactivityGraph
65
63
  disableDevtools?: boolean
66
64
  lifetimeScope: Scope.Scope
67
65
  runtime: Runtime.Runtime<Scope.Scope>
@@ -87,6 +85,8 @@ export type RefreshReason =
87
85
  label?: string
88
86
  stackInfo?: StackInfo
89
87
  }
88
+ | { _tag: 'subscribe.initial'; label?: string }
89
+ | { _tag: 'subscribe.update'; label?: string }
90
90
  | { _tag: 'manual'; label?: string }
91
91
 
92
92
  export type QueryDebugInfo = {
@@ -108,3 +108,5 @@ export type StoreMutateOptions = {
108
108
  spanLinks?: otel.Link[]
109
109
  otelContext?: otel.Context
110
110
  }
111
+
112
+ export type Unsubscribe = () => void