@livestore/livestore 0.4.0-dev.9 → 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/README.md +0 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/QueryCache.js +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/SqliteDbWrapper.d.ts +5 -5
- package/dist/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +15 -13
- package/dist/SqliteDbWrapper.js.map +1 -1
- package/dist/SqliteDbWrapper.test.js +24 -6
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +134 -6
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +190 -11
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/LiveStore.test.d.ts +2 -0
- package/dist/effect/LiveStore.test.d.ts.map +1 -0
- package/dist/effect/LiveStore.test.js +41 -0
- package/dist/effect/LiveStore.test.js.map +1 -0
- package/dist/effect/mod.d.ts +1 -1
- package/dist/effect/mod.d.ts.map +1 -1
- package/dist/effect/mod.js +3 -1
- package/dist/effect/mod.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +129 -9
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +30 -4
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/client-document-get-query.d.ts +1 -1
- package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
- package/dist/live-queries/client-document-get-query.js +4 -3
- package/dist/live-queries/client-document-get-query.js.map +1 -1
- package/dist/live-queries/computed.d.ts +56 -0
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +62 -6
- 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 +41 -30
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +112 -29
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/live-queries/signal.d.ts +49 -0
- package/dist/live-queries/signal.d.ts.map +1 -1
- package/dist/live-queries/signal.js +51 -2
- package/dist/live-queries/signal.js.map +1 -1
- package/dist/live-queries/signal.test.js +4 -4
- package/dist/live-queries/signal.test.js.map +1 -1
- package/dist/mod.d.ts +3 -3
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +3 -2
- package/dist/mod.js.map +1 -1
- package/dist/reactive.d.ts +10 -10
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +29 -27
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +2 -2
- package/dist/reactive.test.js.map +1 -1
- package/dist/store/StoreRegistry.d.ts +215 -0
- package/dist/store/StoreRegistry.d.ts.map +1 -0
- package/dist/store/StoreRegistry.js +267 -0
- package/dist/store/StoreRegistry.js.map +1 -0
- package/dist/store/StoreRegistry.test.d.ts +2 -0
- package/dist/store/StoreRegistry.test.d.ts.map +1 -0
- package/dist/store/StoreRegistry.test.js +384 -0
- package/dist/store/StoreRegistry.test.js.map +1 -0
- package/dist/store/create-store.d.ts +99 -22
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +85 -37
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +6 -18
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +61 -19
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-eventstream.test.d.ts +2 -0
- package/dist/store/store-eventstream.test.d.ts.map +1 -0
- package/dist/store/store-eventstream.test.js +65 -0
- package/dist/store/store-eventstream.test.js.map +1 -0
- package/dist/store/store-types.d.ts +271 -31
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js +41 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store-types.test.d.ts +2 -0
- package/dist/store/store-types.test.d.ts.map +1 -0
- package/dist/store/store-types.test.js +39 -0
- package/dist/store/store-types.test.js.map +1 -0
- package/dist/store/store.d.ts +255 -68
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +533 -186
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.d.ts.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 +3 -2
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +46 -39
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +7 -0
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +5 -5
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +50 -18
- package/src/QueryCache.ts +1 -1
- package/src/SqliteDbWrapper.test.ts +30 -6
- package/src/SqliteDbWrapper.ts +21 -17
- package/src/ambient.d.ts +0 -7
- package/src/effect/LiveStore.test.ts +61 -0
- package/src/effect/LiveStore.ts +426 -22
- package/src/effect/mod.ts +13 -1
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +814 -178
- package/src/live-queries/base-class.ts +162 -32
- package/src/live-queries/client-document-get-query.ts +6 -4
- package/src/live-queries/computed.ts +65 -8
- package/src/live-queries/db-query.test.ts +168 -30
- package/src/live-queries/db-query.ts +58 -43
- package/src/live-queries/signal.test.ts +5 -4
- package/src/live-queries/signal.ts +52 -3
- package/src/mod.ts +19 -2
- package/src/reactive.test.ts +3 -2
- package/src/reactive.ts +58 -49
- package/src/store/StoreRegistry.test.ts +543 -0
- package/src/store/StoreRegistry.ts +429 -0
- package/src/store/create-store.ts +227 -74
- package/src/store/devtools.ts +305 -261
- package/src/store/store-eventstream.test.ts +123 -0
- package/src/store/store-types.test.ts +52 -0
- package/src/store/store-types.ts +318 -44
- package/src/store/store.ts +695 -283
- package/src/utils/dev.ts +3 -4
- package/src/utils/stack-info.ts +5 -2
- package/src/utils/tests/fixture.ts +9 -1
- package/src/utils/tests/otel.ts +8 -7
package/src/store/devtools.ts
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { DebugInfo, SyncState } from '@livestore/common'
|
|
2
|
+
import {
|
|
3
|
+
Devtools,
|
|
4
|
+
devtoolsProtocolVersion,
|
|
5
|
+
isDevtoolsProtocolVersionSupported,
|
|
6
|
+
liveStoreVersion,
|
|
7
|
+
resolveDevtoolsProtocolVersion,
|
|
8
|
+
UnknownError,
|
|
9
|
+
} from '@livestore/common'
|
|
3
10
|
import { throttle } from '@livestore/utils'
|
|
4
|
-
import type
|
|
5
|
-
import { Effect, Stream } from '@livestore/utils/effect'
|
|
11
|
+
import { type WebChannel, Effect, Stream } from '@livestore/utils/effect'
|
|
6
12
|
import { nanoid } from '@livestore/utils/nanoid'
|
|
7
13
|
|
|
8
|
-
import type { LiveQuery, ReactivityGraph } from '../live-queries/base-class.ts'
|
|
9
14
|
import { NOT_REFRESHED_YET } from '../reactive.ts'
|
|
10
|
-
import type { SqliteDbWrapper } from '../SqliteDbWrapper.ts'
|
|
11
15
|
import { emptyDebugInfo as makeEmptyDebugInfo } from '../SqliteDbWrapper.ts'
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
type IStore = {
|
|
15
|
-
clientSession: ClientSession
|
|
16
|
-
reactivityGraph: ReactivityGraph
|
|
17
|
-
sqliteDbWrapper: SqliteDbWrapper
|
|
18
|
-
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
19
|
-
syncProcessor: ClientSessionSyncProcessor
|
|
20
|
-
}
|
|
16
|
+
import { StoreInternalsSymbol } from './store-types.ts'
|
|
17
|
+
import type { Store } from './store.ts'
|
|
21
18
|
|
|
22
19
|
type Unsub = () => void
|
|
23
20
|
type RequestId = string
|
|
@@ -32,7 +29,7 @@ const requestNextTick: (cb: () => void) => number =
|
|
|
32
29
|
const cancelTick: (id: number) => void =
|
|
33
30
|
globalThis.cancelAnimationFrame === undefined ? (id: number) => clearTimeout(id) : globalThis.cancelAnimationFrame
|
|
34
31
|
|
|
35
|
-
export const connectDevtoolsToStore = ({
|
|
32
|
+
export const connectDevtoolsToStore = Effect.fn('LSD.devtools.connectStoreToDevtools')(function* ({
|
|
36
33
|
storeDevtoolsChannel,
|
|
37
34
|
store,
|
|
38
35
|
}: {
|
|
@@ -40,236 +37,139 @@ export const connectDevtoolsToStore = ({
|
|
|
40
37
|
Devtools.ClientSession.MessageToApp,
|
|
41
38
|
Devtools.ClientSession.MessageFromApp
|
|
42
39
|
>
|
|
43
|
-
store:
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (decodedMessage._tag === 'LSD.ClientSession.Disconnect') {
|
|
76
|
-
// console.error('TODO handle disconnect properly in store')
|
|
77
|
-
return
|
|
78
|
-
}
|
|
40
|
+
store: Store
|
|
41
|
+
}) {
|
|
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
|
+
}
|
|
79
71
|
|
|
80
|
-
|
|
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
|
+
}
|
|
81
90
|
|
|
82
|
-
|
|
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
|
-
}
|
|
91
|
+
const requestId = decodedMessage.requestId
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const { subscriptionId } = decodedMessage
|
|
98
|
-
|
|
99
|
-
const send = () =>
|
|
100
|
-
// In order to not add more work to the current tick, we use requestIdleCallback
|
|
101
|
-
// to send the reactivity graph updates to the devtools
|
|
102
|
-
requestIdleCallback(
|
|
103
|
-
() =>
|
|
104
|
-
sendToDevtools(
|
|
105
|
-
Devtools.ClientSession.ReactivityGraphRes.make({
|
|
106
|
-
reactivityGraph: store.reactivityGraph.getSnapshot({ includeResults }),
|
|
107
|
-
requestId: nanoid(10),
|
|
108
|
-
clientId,
|
|
109
|
-
sessionId,
|
|
110
|
-
liveStoreVersion,
|
|
111
|
-
subscriptionId,
|
|
112
|
-
}),
|
|
113
|
-
),
|
|
114
|
-
{ timeout: 500 },
|
|
115
|
-
)
|
|
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
|
+
}
|
|
116
100
|
|
|
117
|
-
|
|
101
|
+
handledRequestIds.add(requestId)
|
|
118
102
|
|
|
119
|
-
|
|
120
|
-
// so we throttle the updates to avoid sending too much data
|
|
121
|
-
// This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
122
|
-
const throttledSend = throttle(send, 20)
|
|
103
|
+
const requestIdleCallback = globalThis.requestIdleCallback ?? ((cb: () => void) => cb())
|
|
123
104
|
|
|
124
|
-
|
|
105
|
+
switch (decodedMessage._tag) {
|
|
106
|
+
case 'LSD.ClientSession.ReactivityGraphSubscribe': {
|
|
107
|
+
const includeResults = decodedMessage.includeResults
|
|
108
|
+
const { subscriptionId } = decodedMessage
|
|
125
109
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
debugInfo: store.sqliteDbWrapper.debugInfo,
|
|
132
|
-
requestId,
|
|
133
|
-
clientId,
|
|
134
|
-
sessionId,
|
|
135
|
-
liveStoreVersion,
|
|
136
|
-
}),
|
|
137
|
-
)
|
|
138
|
-
break
|
|
139
|
-
}
|
|
140
|
-
case 'LSD.ClientSession.DebugInfoHistorySubscribe': {
|
|
141
|
-
const { subscriptionId } = decodedMessage
|
|
142
|
-
const buffer: DebugInfo[] = []
|
|
143
|
-
let hasStopped = false
|
|
144
|
-
let tickHandle: number | undefined
|
|
145
|
-
|
|
146
|
-
const tick = () => {
|
|
147
|
-
buffer.push(store.sqliteDbWrapper.debugInfo)
|
|
148
|
-
|
|
149
|
-
// NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
|
|
150
|
-
// will get the empty debug info
|
|
151
|
-
// TODO We need to come up with a more graceful way to do store. Probably via a single global
|
|
152
|
-
// `requestAnimationFrame` loop that is passed in somehow.
|
|
153
|
-
store.sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
|
|
154
|
-
|
|
155
|
-
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
|
+
() =>
|
|
156
115
|
sendToDevtools(
|
|
157
|
-
Devtools.ClientSession.
|
|
158
|
-
|
|
116
|
+
Devtools.ClientSession.ReactivityGraphRes.make({
|
|
117
|
+
reactivityGraph: store[StoreInternalsSymbol].reactivityGraph.getSnapshot({ includeResults }),
|
|
159
118
|
requestId: nanoid(10),
|
|
160
119
|
clientId,
|
|
161
120
|
sessionId,
|
|
162
121
|
liveStoreVersion,
|
|
163
122
|
subscriptionId,
|
|
164
123
|
}),
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (hasStopped === false) {
|
|
170
|
-
tickHandle = requestNextTick(tick)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
tickHandle = requestNextTick(tick)
|
|
175
|
-
|
|
176
|
-
const unsub = () => {
|
|
177
|
-
hasStopped = true
|
|
178
|
-
if (tickHandle !== undefined) {
|
|
179
|
-
cancelTick(tickHandle)
|
|
180
|
-
tickHandle = undefined
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
debugInfoHistorySubscriptions.set(subscriptionId, unsub)
|
|
185
|
-
|
|
186
|
-
break
|
|
187
|
-
}
|
|
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)
|
|
194
|
-
break
|
|
195
|
-
}
|
|
196
|
-
case 'LSD.ClientSession.DebugInfoResetReq': {
|
|
197
|
-
store.sqliteDbWrapper.debugInfo.slowQueries.clear()
|
|
198
|
-
sendToDevtools(
|
|
199
|
-
Devtools.ClientSession.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
|
|
200
|
-
)
|
|
201
|
-
break
|
|
202
|
-
}
|
|
203
|
-
case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
|
|
204
|
-
const { queryStr, bindValues, queriedTables } = decodedMessage
|
|
205
|
-
store.sqliteDbWrapper.cachedSelect(queryStr, bindValues, { queriedTables, skipCache: true })
|
|
206
|
-
sendToDevtools(
|
|
207
|
-
Devtools.ClientSession.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }),
|
|
124
|
+
),
|
|
125
|
+
{ timeout: 500 },
|
|
208
126
|
)
|
|
209
|
-
break
|
|
210
|
-
}
|
|
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)
|
|
217
|
-
break
|
|
218
|
-
}
|
|
219
|
-
case 'LSD.ClientSession.LiveQueriesSubscribe': {
|
|
220
|
-
const { subscriptionId } = decodedMessage
|
|
221
|
-
const send = () =>
|
|
222
|
-
requestIdleCallback(
|
|
223
|
-
() =>
|
|
224
|
-
sendToDevtools(
|
|
225
|
-
Devtools.ClientSession.LiveQueriesRes.make({
|
|
226
|
-
liveQueries: [...store.activeQueries].map((q) => ({
|
|
227
|
-
_tag: q._tag,
|
|
228
|
-
id: q.id,
|
|
229
|
-
label: q.label,
|
|
230
|
-
hash: q.def.hash,
|
|
231
|
-
runs: q.runs,
|
|
232
|
-
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
233
|
-
lastestResult:
|
|
234
|
-
q.results$.previousResult === NOT_REFRESHED_YET
|
|
235
|
-
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
236
|
-
: q.results$.previousResult,
|
|
237
|
-
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
238
|
-
})),
|
|
239
|
-
requestId: nanoid(10),
|
|
240
|
-
liveStoreVersion,
|
|
241
|
-
clientId,
|
|
242
|
-
sessionId,
|
|
243
|
-
subscriptionId,
|
|
244
|
-
}),
|
|
245
|
-
),
|
|
246
|
-
{ timeout: 500 },
|
|
247
|
-
)
|
|
248
127
|
|
|
249
|
-
|
|
128
|
+
send()
|
|
250
129
|
|
|
251
|
-
|
|
252
|
-
|
|
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)
|
|
253
134
|
|
|
254
|
-
|
|
135
|
+
reactivityGraphSubcriptions.set(
|
|
136
|
+
subscriptionId,
|
|
137
|
+
store[StoreInternalsSymbol].reactivityGraph.subscribeToRefresh(throttledSend),
|
|
138
|
+
)
|
|
255
139
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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) {
|
|
269
170
|
sendToDevtools(
|
|
270
|
-
Devtools.ClientSession.
|
|
271
|
-
|
|
272
|
-
upstream: syncState.upstreamHead,
|
|
171
|
+
Devtools.ClientSession.DebugInfoHistoryRes.make({
|
|
172
|
+
debugInfoHistory: buffer,
|
|
273
173
|
requestId: nanoid(10),
|
|
274
174
|
clientId,
|
|
275
175
|
sessionId,
|
|
@@ -277,45 +177,189 @@ export const connectDevtoolsToStore = ({
|
|
|
277
177
|
subscriptionId,
|
|
278
178
|
}),
|
|
279
179
|
)
|
|
180
|
+
buffer.length = 0
|
|
181
|
+
}
|
|
280
182
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
183
|
+
if (hasStopped === false) {
|
|
184
|
+
tickHandle = requestNextTick(tick)
|
|
185
|
+
}
|
|
295
186
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
187
|
+
|
|
188
|
+
tickHandle = requestNextTick(tick)
|
|
189
|
+
|
|
190
|
+
const unsub = () => {
|
|
191
|
+
hasStopped = true
|
|
192
|
+
if (tickHandle !== undefined) {
|
|
193
|
+
cancelTick(tickHandle)
|
|
194
|
+
tickHandle = undefined
|
|
195
|
+
}
|
|
303
196
|
}
|
|
304
|
-
|
|
305
|
-
|
|
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) {
|
|
327
|
+
sendToDevtools(
|
|
328
|
+
Devtools.ClientSession.VersionMismatch.make({
|
|
329
|
+
requestId,
|
|
330
|
+
clientId,
|
|
331
|
+
sessionId,
|
|
332
|
+
liveStoreVersion,
|
|
333
|
+
appVersion: liveStoreVersion,
|
|
334
|
+
receivedVersion: decodedMessage.liveStoreVersion,
|
|
335
|
+
appDevtoolsProtocolVersion: devtoolsProtocolVersion,
|
|
336
|
+
receivedDevtoolsProtocolVersion: resolveDevtoolsProtocolVersion(decodedMessage.devtoolsProtocolVersion),
|
|
337
|
+
}),
|
|
338
|
+
)
|
|
306
339
|
break
|
|
307
340
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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)
|
|
311
354
|
}
|
|
312
355
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
)
|
|
321
|
-
|
|
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
|
+
}, UnknownError.mapToUnknownError)
|