@livestore/livestore 0.4.0 → 0.5.0-dev.0
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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +7 -5
- package/dist/SqliteDbWrapper.js.map +1 -1
- package/dist/SqliteDbWrapper.test.js +20 -3
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +15 -15
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +17 -17
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/LiveStore.test.js +5 -6
- package/dist/effect/LiveStore.test.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +19 -2
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +28 -2
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +4 -4
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.d.ts +2 -2
- package/dist/live-queries/db-query.d.ts.map +1 -1
- package/dist/live-queries/db-query.js +26 -17
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +9 -9
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/live-queries/signal.d.ts.map +1 -1
- package/dist/live-queries/signal.js +2 -2
- package/dist/live-queries/signal.js.map +1 -1
- package/dist/live-queries/signal.test.js +2 -2
- package/dist/live-queries/signal.test.js.map +1 -1
- package/dist/reactive.d.ts +1 -1
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +20 -1
- package/dist/reactive.js.map +1 -1
- package/dist/store/StoreRegistry.d.ts +12 -12
- package/dist/store/StoreRegistry.d.ts.map +1 -1
- package/dist/store/StoreRegistry.js +24 -24
- package/dist/store/StoreRegistry.js.map +1 -1
- package/dist/store/StoreRegistry.test.js +62 -59
- package/dist/store/StoreRegistry.test.js.map +1 -1
- package/dist/store/create-store.d.ts +12 -15
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +43 -24
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +1 -2
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +3 -2
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-eventstream.test.js +5 -5
- package/dist/store/store-eventstream.test.js.map +1 -1
- package/dist/store/store-types.d.ts +10 -28
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js +2 -38
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store-types.test.js +1 -1
- package/dist/store/store.d.ts +2 -2
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +114 -56
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.js +1 -1
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +2 -1
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +26 -34
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/package.json +14 -24
- package/src/SqliteDbWrapper.test.ts +25 -3
- package/src/SqliteDbWrapper.ts +12 -9
- package/src/effect/LiveStore.test.ts +16 -16
- package/src/effect/LiveStore.ts +79 -50
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +273 -49
- package/src/live-queries/base-class.ts +38 -6
- package/src/live-queries/computed.ts +5 -5
- package/src/live-queries/db-query.test.ts +9 -9
- package/src/live-queries/db-query.ts +42 -30
- package/src/live-queries/signal.test.ts +2 -2
- package/src/live-queries/signal.ts +3 -3
- package/src/reactive.ts +45 -35
- package/src/store/StoreRegistry.test.ts +62 -59
- package/src/store/StoreRegistry.ts +49 -38
- package/src/store/create-store.ts +85 -51
- package/src/store/devtools.ts +283 -283
- package/src/store/store-eventstream.test.ts +16 -7
- package/src/store/store-types.test.ts +1 -1
- package/src/store/store-types.ts +10 -56
- package/src/store/store.ts +148 -71
- package/src/utils/dev.ts +1 -1
- package/src/utils/stack-info.ts +4 -1
package/src/store/devtools.ts
CHANGED
|
@@ -8,8 +8,7 @@ import {
|
|
|
8
8
|
UnknownError,
|
|
9
9
|
} from '@livestore/common'
|
|
10
10
|
import { throttle } from '@livestore/utils'
|
|
11
|
-
import type
|
|
12
|
-
import { Effect, Stream } from '@livestore/utils/effect'
|
|
11
|
+
import { type WebChannel, Effect, Stream } from '@livestore/utils/effect'
|
|
13
12
|
import { nanoid } from '@livestore/utils/nanoid'
|
|
14
13
|
|
|
15
14
|
import { NOT_REFRESHED_YET } from '../reactive.ts'
|
|
@@ -40,257 +39,137 @@ export const connectDevtoolsToStore = Effect.fn('LSD.devtools.connectStoreToDevt
|
|
|
40
39
|
>
|
|
41
40
|
store: Store
|
|
42
41
|
}) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
42
|
+
const reactivityGraphSubcriptions: SubMap = new Map()
|
|
43
|
+
const liveQueriesSubscriptions: SubMap = new Map()
|
|
44
|
+
const debugInfoHistorySubscriptions: SubMap = new Map()
|
|
45
|
+
const syncHeadClientSessionSubscriptions: SubMap = new Map()
|
|
46
|
+
const services = yield* Effect.context()
|
|
47
|
+
|
|
48
|
+
const { clientId, sessionId } = store[StoreInternalsSymbol].clientSession
|
|
49
|
+
|
|
50
|
+
yield* Effect.addFinalizer(() =>
|
|
51
|
+
Effect.sync(() => {
|
|
52
|
+
for (const unsub of reactivityGraphSubcriptions.values()) unsub()
|
|
53
|
+
for (const unsub of liveQueriesSubscriptions.values()) unsub()
|
|
54
|
+
for (const unsub of debugInfoHistorySubscriptions.values()) unsub()
|
|
55
|
+
for (const unsub of syncHeadClientSessionSubscriptions.values()) unsub()
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const handledRequestIds = new Set<RequestId>()
|
|
60
|
+
|
|
61
|
+
const sendToDevtools = (message: Devtools.ClientSession.MessageFromApp) =>
|
|
62
|
+
storeDevtoolsChannel.send(message).pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
63
|
+
|
|
64
|
+
const onMessage = (decodedMessage: Devtools.ClientSession.MessageToApp) => {
|
|
65
|
+
// console.debug('@livestore/livestore:store:devtools:onMessage', decodedMessage)
|
|
66
|
+
|
|
67
|
+
if (decodedMessage.clientId !== clientId || decodedMessage.sessionId !== sessionId) {
|
|
68
|
+
// console.log(`Unknown message`, event)
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
72
|
+
if (decodedMessage._tag === 'LSD.ClientSession.Disconnect') {
|
|
73
|
+
// Gracefully tear down all DevTools subscriptions and close the channel.
|
|
74
|
+
// This prevents background fibers from lingering after DevTools closes
|
|
75
|
+
// (e.g. when a window is closed without sending explicit unsubs).
|
|
76
|
+
for (const unsub of reactivityGraphSubcriptions.values()) unsub()
|
|
77
|
+
reactivityGraphSubcriptions.clear()
|
|
78
|
+
for (const unsub of liveQueriesSubscriptions.values()) unsub()
|
|
79
|
+
liveQueriesSubscriptions.clear()
|
|
80
|
+
for (const unsub of debugInfoHistorySubscriptions.values()) unsub()
|
|
81
|
+
debugInfoHistorySubscriptions.clear()
|
|
82
|
+
for (const unsub of syncHeadClientSessionSubscriptions.values()) unsub()
|
|
83
|
+
syncHeadClientSessionSubscriptions.clear()
|
|
84
|
+
|
|
85
|
+
// Signal the WebChannel to shut down; this causes the `listen` stream
|
|
86
|
+
// to complete and allows the surrounding scoped fiber to exit.
|
|
87
|
+
storeDevtoolsChannel.shutdown.pipe(Effect.runFork)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
const requestId = decodedMessage.requestId
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
// TODO we should try to move the duplicate message handling on the webmesh layer
|
|
94
|
+
// So far I could only observe this problem with webmesh proxy channels (e.g. for Expo)
|
|
95
|
+
// Proof: https://share.cleanshot.com/V9G87B0B
|
|
96
|
+
// Also see `leader-worker-devtools.ts` for same problem
|
|
97
|
+
if (handledRequestIds.has(requestId) === true) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const requestIdleCallback = globalThis.requestIdleCallback ?? ((cb: () => void) => cb())
|
|
104
|
-
|
|
105
|
-
switch (decodedMessage._tag) {
|
|
106
|
-
case 'LSD.ClientSession.ReactivityGraphSubscribe': {
|
|
107
|
-
const includeResults = decodedMessage.includeResults
|
|
108
|
-
const { subscriptionId } = decodedMessage
|
|
109
|
-
|
|
110
|
-
const send = () =>
|
|
111
|
-
// In order to not add more work to the current tick, we use requestIdleCallback
|
|
112
|
-
// to send the reactivity graph updates to the devtools
|
|
113
|
-
requestIdleCallback(
|
|
114
|
-
() =>
|
|
115
|
-
sendToDevtools(
|
|
116
|
-
Devtools.ClientSession.ReactivityGraphRes.make({
|
|
117
|
-
reactivityGraph: store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults }),
|
|
118
|
-
requestId: nanoid(10),
|
|
119
|
-
clientId,
|
|
120
|
-
sessionId,
|
|
121
|
-
liveStoreVersion,
|
|
122
|
-
subscriptionId,
|
|
123
|
-
}),
|
|
124
|
-
),
|
|
125
|
-
{ timeout: 500 },
|
|
126
|
-
)
|
|
101
|
+
handledRequestIds.add(requestId)
|
|
127
102
|
|
|
128
|
-
|
|
103
|
+
const requestIdleCallback = globalThis.requestIdleCallback ?? ((cb: () => void) => cb())
|
|
129
104
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
105
|
+
switch (decodedMessage._tag) {
|
|
106
|
+
case 'LSD.ClientSession.ReactivityGraphSubscribe': {
|
|
107
|
+
const includeResults = decodedMessage.includeResults
|
|
108
|
+
const { subscriptionId } = decodedMessage
|
|
134
109
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
break
|
|
141
|
-
}
|
|
142
|
-
case 'LSD.ClientSession.DebugInfoReq': {
|
|
143
|
-
sendToDevtools(
|
|
144
|
-
Devtools.ClientSession.DebugInfoRes.make({
|
|
145
|
-
debugInfo: store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo,
|
|
146
|
-
requestId,
|
|
147
|
-
clientId,
|
|
148
|
-
sessionId,
|
|
149
|
-
liveStoreVersion,
|
|
150
|
-
}),
|
|
151
|
-
)
|
|
152
|
-
break
|
|
153
|
-
}
|
|
154
|
-
case 'LSD.ClientSession.DebugInfoHistorySubscribe': {
|
|
155
|
-
const { subscriptionId } = decodedMessage
|
|
156
|
-
const buffer: DebugInfo[] = []
|
|
157
|
-
let hasStopped = false
|
|
158
|
-
let tickHandle: number | undefined
|
|
159
|
-
|
|
160
|
-
const tick = () => {
|
|
161
|
-
buffer.push(store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo)
|
|
162
|
-
|
|
163
|
-
// NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
|
|
164
|
-
// will get the empty debug info
|
|
165
|
-
// TODO We need to come up with a more graceful way to do store. Probably via a single global
|
|
166
|
-
// `requestAnimationFrame` loop that is passed in somehow.
|
|
167
|
-
store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
|
|
168
|
-
|
|
169
|
-
if (buffer.length > 10) {
|
|
110
|
+
const send = () =>
|
|
111
|
+
// In order to not add more work to the current tick, we use requestIdleCallback
|
|
112
|
+
// to send the reactivity graph updates to the devtools
|
|
113
|
+
requestIdleCallback(
|
|
114
|
+
() =>
|
|
170
115
|
sendToDevtools(
|
|
171
|
-
Devtools.ClientSession.
|
|
172
|
-
|
|
116
|
+
Devtools.ClientSession.ReactivityGraphRes.make({
|
|
117
|
+
reactivityGraph: store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults }),
|
|
173
118
|
requestId: nanoid(10),
|
|
174
119
|
clientId,
|
|
175
120
|
sessionId,
|
|
176
121
|
liveStoreVersion,
|
|
177
122
|
subscriptionId,
|
|
178
123
|
}),
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (hasStopped === false) {
|
|
184
|
-
tickHandle = requestNextTick(tick)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
tickHandle = requestNextTick(tick)
|
|
189
|
-
|
|
190
|
-
const unsub = () => {
|
|
191
|
-
hasStopped = true
|
|
192
|
-
if (tickHandle !== undefined) {
|
|
193
|
-
cancelTick(tickHandle)
|
|
194
|
-
tickHandle = undefined
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
debugInfoHistorySubscriptions.set(subscriptionId, unsub)
|
|
199
|
-
|
|
200
|
-
break
|
|
201
|
-
}
|
|
202
|
-
case 'LSD.ClientSession.DebugInfoHistoryUnsubscribe': {
|
|
203
|
-
const { subscriptionId } = decodedMessage
|
|
204
|
-
// NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
|
|
205
|
-
// Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
206
|
-
debugInfoHistorySubscriptions.get(subscriptionId)?.()
|
|
207
|
-
debugInfoHistorySubscriptions.delete(subscriptionId)
|
|
208
|
-
break
|
|
209
|
-
}
|
|
210
|
-
case 'LSD.ClientSession.DebugInfoResetReq': {
|
|
211
|
-
store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo.slowQueries.clear()
|
|
212
|
-
sendToDevtools(
|
|
213
|
-
Devtools.ClientSession.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
|
|
214
|
-
)
|
|
215
|
-
break
|
|
216
|
-
}
|
|
217
|
-
case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
|
|
218
|
-
const { queryStr, bindValues, queriedTables } = decodedMessage
|
|
219
|
-
store[StoreInternalsSymbol].sqliteDbWrapper.cachedSelect(queryStr, bindValues, {
|
|
220
|
-
queriedTables,
|
|
221
|
-
skipCache: true,
|
|
222
|
-
})
|
|
223
|
-
sendToDevtools(
|
|
224
|
-
Devtools.ClientSession.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
|
|
124
|
+
),
|
|
125
|
+
{ timeout: 500 },
|
|
225
126
|
)
|
|
226
|
-
break
|
|
227
|
-
}
|
|
228
|
-
case 'LSD.ClientSession.ReactivityGraphUnsubscribe': {
|
|
229
|
-
const { subscriptionId } = decodedMessage
|
|
230
|
-
// NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
|
|
231
|
-
// Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
232
|
-
reactivityGraphSubcriptions.get(subscriptionId)?.()
|
|
233
|
-
reactivityGraphSubcriptions.delete(subscriptionId)
|
|
234
|
-
break
|
|
235
|
-
}
|
|
236
|
-
case 'LSD.ClientSession.LiveQueriesSubscribe': {
|
|
237
|
-
const { subscriptionId } = decodedMessage
|
|
238
|
-
const send = () =>
|
|
239
|
-
requestIdleCallback(
|
|
240
|
-
() =>
|
|
241
|
-
sendToDevtools(
|
|
242
|
-
Devtools.ClientSession.LiveQueriesRes.make({
|
|
243
|
-
liveQueries: [...store[StoreInternalsSymbol].activeQueries].map((q) => ({
|
|
244
|
-
/** TODO: include schema metadata for schema-aware rendering in devtools (e.g., schema AST/hash/identifier or table+columns for QueryBuilder-derived queries). */
|
|
245
|
-
_tag: q._tag,
|
|
246
|
-
id: q.id,
|
|
247
|
-
label: q.label,
|
|
248
|
-
hash: q.def.hash,
|
|
249
|
-
runs: q.runs,
|
|
250
|
-
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
251
|
-
lastestResult:
|
|
252
|
-
q.results$.previousResult === NOT_REFRESHED_YET
|
|
253
|
-
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
254
|
-
: q.results$.previousResult,
|
|
255
|
-
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
256
|
-
})),
|
|
257
|
-
requestId: nanoid(10),
|
|
258
|
-
liveStoreVersion,
|
|
259
|
-
clientId,
|
|
260
|
-
sessionId,
|
|
261
|
-
subscriptionId,
|
|
262
|
-
}),
|
|
263
|
-
),
|
|
264
|
-
{ timeout: 500 },
|
|
265
|
-
)
|
|
266
127
|
|
|
267
|
-
|
|
128
|
+
send()
|
|
268
129
|
|
|
269
|
-
|
|
270
|
-
|
|
130
|
+
// In some cases, there can be A LOT of reactivity graph updates in a short period of time
|
|
131
|
+
// so we throttle the updates to avoid sending too much data
|
|
132
|
+
// This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
133
|
+
const throttledSend = throttle(send, 20)
|
|
271
134
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
135
|
+
reactivityGraphSubcriptions.set(
|
|
136
|
+
subscriptionId,
|
|
137
|
+
store[StoreInternalsSymbol].reactivityGraph.subscribeToRefresh(throttledSend),
|
|
138
|
+
)
|
|
276
139
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
140
|
+
break
|
|
141
|
+
}
|
|
142
|
+
case 'LSD.ClientSession.DebugInfoReq': {
|
|
143
|
+
sendToDevtools(
|
|
144
|
+
Devtools.ClientSession.DebugInfoRes.make({
|
|
145
|
+
debugInfo: store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo,
|
|
146
|
+
requestId,
|
|
147
|
+
clientId,
|
|
148
|
+
sessionId,
|
|
149
|
+
liveStoreVersion,
|
|
150
|
+
}),
|
|
151
|
+
)
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
case 'LSD.ClientSession.DebugInfoHistorySubscribe': {
|
|
155
|
+
const { subscriptionId } = decodedMessage
|
|
156
|
+
const buffer: DebugInfo[] = []
|
|
157
|
+
let hasStopped = false
|
|
158
|
+
let tickHandle: number | undefined
|
|
159
|
+
|
|
160
|
+
const tick = () => {
|
|
161
|
+
buffer.push(store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo)
|
|
162
|
+
|
|
163
|
+
// NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
|
|
164
|
+
// will get the empty debug info
|
|
165
|
+
// TODO We need to come up with a more graceful way to do store. Probably via a single global
|
|
166
|
+
// `requestAnimationFrame` loop that is passed in somehow.
|
|
167
|
+
store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
|
|
168
|
+
|
|
169
|
+
if (buffer.length > 10) {
|
|
290
170
|
sendToDevtools(
|
|
291
|
-
Devtools.ClientSession.
|
|
292
|
-
|
|
293
|
-
upstream: syncState.upstreamHead,
|
|
171
|
+
Devtools.ClientSession.DebugInfoHistoryRes.make({
|
|
172
|
+
debugInfoHistory: buffer,
|
|
294
173
|
requestId: nanoid(10),
|
|
295
174
|
clientId,
|
|
296
175
|
sessionId,
|
|
@@ -298,68 +177,189 @@ export const connectDevtoolsToStore = Effect.fn('LSD.devtools.connectStoreToDevt
|
|
|
298
177
|
subscriptionId,
|
|
299
178
|
}),
|
|
300
179
|
)
|
|
180
|
+
buffer.length = 0
|
|
181
|
+
}
|
|
301
182
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
subscriptionId,
|
|
306
|
-
store[StoreInternalsSymbol].syncProcessor.syncState.changes.pipe(
|
|
307
|
-
Stream.tap((syncState) => send(syncState)),
|
|
308
|
-
Stream.runDrain,
|
|
309
|
-
Effect.interruptible,
|
|
310
|
-
Effect.tapCauseLogPretty,
|
|
311
|
-
Effect.runCallback,
|
|
312
|
-
),
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
break
|
|
316
|
-
}
|
|
317
|
-
case 'LSD.ClientSession.SyncHeadUnsubscribe': {
|
|
318
|
-
const { subscriptionId } = decodedMessage
|
|
319
|
-
// NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
|
|
320
|
-
// Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
321
|
-
syncHeadClientSessionSubscriptions.get(subscriptionId)?.()
|
|
322
|
-
syncHeadClientSessionSubscriptions.delete(subscriptionId)
|
|
323
|
-
break
|
|
183
|
+
if (hasStopped === false) {
|
|
184
|
+
tickHandle = requestNextTick(tick)
|
|
185
|
+
}
|
|
324
186
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
appVersion: liveStoreVersion,
|
|
334
|
-
receivedVersion: decodedMessage.liveStoreVersion,
|
|
335
|
-
appDevtoolsProtocolVersion: devtoolsProtocolVersion,
|
|
336
|
-
receivedDevtoolsProtocolVersion: resolveDevtoolsProtocolVersion(decodedMessage.devtoolsProtocolVersion),
|
|
337
|
-
}),
|
|
338
|
-
)
|
|
339
|
-
break
|
|
187
|
+
|
|
188
|
+
tickHandle = requestNextTick(tick)
|
|
189
|
+
|
|
190
|
+
const unsub = () => {
|
|
191
|
+
hasStopped = true
|
|
192
|
+
if (tickHandle !== undefined) {
|
|
193
|
+
cancelTick(tickHandle)
|
|
194
|
+
tickHandle = undefined
|
|
340
195
|
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
debugInfoHistorySubscriptions.set(subscriptionId, unsub)
|
|
199
|
+
|
|
200
|
+
break
|
|
201
|
+
}
|
|
202
|
+
case 'LSD.ClientSession.DebugInfoHistoryUnsubscribe': {
|
|
203
|
+
const { subscriptionId } = decodedMessage
|
|
204
|
+
// NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
|
|
205
|
+
// Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
206
|
+
debugInfoHistorySubscriptions.get(subscriptionId)?.()
|
|
207
|
+
debugInfoHistorySubscriptions.delete(subscriptionId)
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
case 'LSD.ClientSession.DebugInfoResetReq': {
|
|
211
|
+
store[StoreInternalsSymbol].sqliteDbWrapper.debugInfo.slowQueries.clear()
|
|
212
|
+
sendToDevtools(
|
|
213
|
+
Devtools.ClientSession.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
|
|
214
|
+
)
|
|
215
|
+
break
|
|
216
|
+
}
|
|
217
|
+
case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
|
|
218
|
+
const { queryStr, bindValues, queriedTables } = decodedMessage
|
|
219
|
+
store[StoreInternalsSymbol].sqliteDbWrapper.cachedSelect(queryStr, bindValues, {
|
|
220
|
+
queriedTables,
|
|
221
|
+
skipCache: true,
|
|
222
|
+
})
|
|
223
|
+
sendToDevtools(
|
|
224
|
+
Devtools.ClientSession.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
|
|
225
|
+
)
|
|
226
|
+
break
|
|
227
|
+
}
|
|
228
|
+
case 'LSD.ClientSession.ReactivityGraphUnsubscribe': {
|
|
229
|
+
const { subscriptionId } = decodedMessage
|
|
230
|
+
// NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
|
|
231
|
+
// Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
232
|
+
reactivityGraphSubcriptions.get(subscriptionId)?.()
|
|
233
|
+
reactivityGraphSubcriptions.delete(subscriptionId)
|
|
234
|
+
break
|
|
235
|
+
}
|
|
236
|
+
case 'LSD.ClientSession.LiveQueriesSubscribe': {
|
|
237
|
+
const { subscriptionId } = decodedMessage
|
|
238
|
+
const send = () =>
|
|
239
|
+
requestIdleCallback(
|
|
240
|
+
() =>
|
|
241
|
+
sendToDevtools(
|
|
242
|
+
Devtools.ClientSession.LiveQueriesRes.make({
|
|
243
|
+
liveQueries: [...store[StoreInternalsSymbol].activeQueries].map((q) => ({
|
|
244
|
+
/** TODO: include schema metadata for schema-aware rendering in devtools (e.g., schema AST/hash/identifier or table+columns for QueryBuilder-derived queries). */
|
|
245
|
+
_tag: q._tag,
|
|
246
|
+
id: q.id,
|
|
247
|
+
label: q.label,
|
|
248
|
+
hash: q.def.hash,
|
|
249
|
+
runs: q.runs,
|
|
250
|
+
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
251
|
+
lastestResult:
|
|
252
|
+
q.results$.previousResult === NOT_REFRESHED_YET
|
|
253
|
+
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
254
|
+
: q.results$.previousResult,
|
|
255
|
+
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
256
|
+
})),
|
|
257
|
+
requestId: nanoid(10),
|
|
258
|
+
liveStoreVersion,
|
|
259
|
+
clientId,
|
|
260
|
+
sessionId,
|
|
261
|
+
subscriptionId,
|
|
262
|
+
}),
|
|
263
|
+
),
|
|
264
|
+
{ timeout: 500 },
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
send()
|
|
268
|
+
|
|
269
|
+
// Same as in the reactivity graph subscription case above, we need to throttle the updates
|
|
270
|
+
const throttledSend = throttle(send, 20)
|
|
271
|
+
|
|
272
|
+
liveQueriesSubscriptions.set(
|
|
273
|
+
subscriptionId,
|
|
274
|
+
store[StoreInternalsSymbol].reactivityGraph.subscribeToRefresh(throttledSend),
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
break
|
|
278
|
+
}
|
|
279
|
+
case 'LSD.ClientSession.LiveQueriesUnsubscribe': {
|
|
280
|
+
const { subscriptionId } = decodedMessage
|
|
281
|
+
// NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
|
|
282
|
+
// Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
283
|
+
liveQueriesSubscriptions.get(subscriptionId)?.()
|
|
284
|
+
liveQueriesSubscriptions.delete(subscriptionId)
|
|
285
|
+
break
|
|
286
|
+
}
|
|
287
|
+
case 'LSD.ClientSession.SyncHeadSubscribe': {
|
|
288
|
+
const { subscriptionId } = decodedMessage
|
|
289
|
+
const send = (syncState: SyncState.SyncState) =>
|
|
290
|
+
sendToDevtools(
|
|
291
|
+
Devtools.ClientSession.SyncHeadRes.make({
|
|
292
|
+
local: syncState.localHead,
|
|
293
|
+
upstream: syncState.upstreamHead,
|
|
294
|
+
requestId: nanoid(10),
|
|
295
|
+
clientId,
|
|
296
|
+
sessionId,
|
|
297
|
+
liveStoreVersion,
|
|
298
|
+
subscriptionId,
|
|
299
|
+
}),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
send(store[StoreInternalsSymbol].syncProcessor.syncState.pipe(Effect.runSync))
|
|
303
|
+
|
|
304
|
+
syncHeadClientSessionSubscriptions.set(
|
|
305
|
+
subscriptionId,
|
|
306
|
+
store[StoreInternalsSymbol].syncProcessor.syncState.changes.pipe(
|
|
307
|
+
Stream.tap((syncState) => Effect.sync(() => send(syncState))),
|
|
308
|
+
Stream.runDrain,
|
|
309
|
+
Effect.interruptible,
|
|
310
|
+
Effect.tapCauseLogPretty,
|
|
311
|
+
Effect.runCallbackWith(services),
|
|
312
|
+
),
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
break
|
|
316
|
+
}
|
|
317
|
+
case 'LSD.ClientSession.SyncHeadUnsubscribe': {
|
|
318
|
+
const { subscriptionId } = decodedMessage
|
|
319
|
+
// NOTE given Webmesh channels have persistent retry behaviour, it can happen that a previous
|
|
320
|
+
// Webmesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
321
|
+
syncHeadClientSessionSubscriptions.get(subscriptionId)?.()
|
|
322
|
+
syncHeadClientSessionSubscriptions.delete(subscriptionId)
|
|
323
|
+
break
|
|
324
|
+
}
|
|
325
|
+
case 'LSD.ClientSession.Ping': {
|
|
326
|
+
if (isDevtoolsProtocolVersionSupported(decodedMessage.devtoolsProtocolVersion) === false) {
|
|
341
327
|
sendToDevtools(
|
|
342
|
-
Devtools.ClientSession.
|
|
328
|
+
Devtools.ClientSession.VersionMismatch.make({
|
|
343
329
|
requestId,
|
|
344
330
|
clientId,
|
|
345
331
|
sessionId,
|
|
346
332
|
liveStoreVersion,
|
|
347
|
-
|
|
333
|
+
appVersion: liveStoreVersion,
|
|
334
|
+
receivedVersion: decodedMessage.liveStoreVersion,
|
|
335
|
+
appDevtoolsProtocolVersion: devtoolsProtocolVersion,
|
|
336
|
+
receivedDevtoolsProtocolVersion: resolveDevtoolsProtocolVersion(decodedMessage.devtoolsProtocolVersion),
|
|
348
337
|
}),
|
|
349
338
|
)
|
|
350
339
|
break
|
|
351
340
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
341
|
+
sendToDevtools(
|
|
342
|
+
Devtools.ClientSession.Pong.make({
|
|
343
|
+
requestId,
|
|
344
|
+
clientId,
|
|
345
|
+
sessionId,
|
|
346
|
+
liveStoreVersion,
|
|
347
|
+
devtoolsProtocolVersion,
|
|
348
|
+
}),
|
|
349
|
+
)
|
|
350
|
+
break
|
|
351
|
+
}
|
|
352
|
+
default: {
|
|
353
|
+
console.warn(`[LSD.ClientSession] Unknown message`, decodedMessage)
|
|
355
354
|
}
|
|
356
355
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
yield* storeDevtoolsChannel.listen.pipe(
|
|
359
|
+
// Stream.tapLogWithLabel('@livestore/livestore:store:devtools:onMessage'),
|
|
360
|
+
Stream.mapEffect(Effect.fromResult),
|
|
361
|
+
Stream.tapSync((message) => onMessage(message)),
|
|
362
|
+
Stream.runDrain,
|
|
363
|
+
Effect.withSpan('LSD.devtools.onMessage'),
|
|
364
|
+
)
|
|
365
365
|
}, UnknownError.mapToUnknownError)
|