@livestore/livestore 0.0.0-snapshot-909cdd1ac2fd591945c2be2b0f53e14d87f3c9d4

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 (131) hide show
  1. package/README.md +1 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/QueryCache.d.ts +20 -0
  4. package/dist/QueryCache.d.ts.map +1 -0
  5. package/dist/QueryCache.js +61 -0
  6. package/dist/QueryCache.js.map +1 -0
  7. package/dist/SynchronousDatabaseWrapper.d.ts +36 -0
  8. package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -0
  9. package/dist/SynchronousDatabaseWrapper.js +176 -0
  10. package/dist/SynchronousDatabaseWrapper.js.map +1 -0
  11. package/dist/effect/LiveStore.d.ts +38 -0
  12. package/dist/effect/LiveStore.d.ts.map +1 -0
  13. package/dist/effect/LiveStore.js +38 -0
  14. package/dist/effect/LiveStore.js.map +1 -0
  15. package/dist/effect/index.d.ts +2 -0
  16. package/dist/effect/index.d.ts.map +1 -0
  17. package/dist/effect/index.js +2 -0
  18. package/dist/effect/index.js.map +1 -0
  19. package/dist/global-state.d.ts +14 -0
  20. package/dist/global-state.d.ts.map +1 -0
  21. package/dist/global-state.js +16 -0
  22. package/dist/global-state.js.map +1 -0
  23. package/dist/index.d.ts +19 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +15 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/reactive.d.ts +163 -0
  28. package/dist/reactive.d.ts.map +1 -0
  29. package/dist/reactive.js +382 -0
  30. package/dist/reactive.js.map +1 -0
  31. package/dist/reactive.test.d.ts +2 -0
  32. package/dist/reactive.test.d.ts.map +1 -0
  33. package/dist/reactive.test.js +345 -0
  34. package/dist/reactive.test.js.map +1 -0
  35. package/dist/reactiveQueries/base-class.d.ts +59 -0
  36. package/dist/reactiveQueries/base-class.d.ts.map +1 -0
  37. package/dist/reactiveQueries/base-class.js +29 -0
  38. package/dist/reactiveQueries/base-class.js.map +1 -0
  39. package/dist/reactiveQueries/graphql.d.ts +52 -0
  40. package/dist/reactiveQueries/graphql.d.ts.map +1 -0
  41. package/dist/reactiveQueries/graphql.js +136 -0
  42. package/dist/reactiveQueries/graphql.js.map +1 -0
  43. package/dist/reactiveQueries/js.d.ts +35 -0
  44. package/dist/reactiveQueries/js.d.ts.map +1 -0
  45. package/dist/reactiveQueries/js.js +57 -0
  46. package/dist/reactiveQueries/js.js.map +1 -0
  47. package/dist/reactiveQueries/sql.d.ts +49 -0
  48. package/dist/reactiveQueries/sql.d.ts.map +1 -0
  49. package/dist/reactiveQueries/sql.js +130 -0
  50. package/dist/reactiveQueries/sql.js.map +1 -0
  51. package/dist/reactiveQueries/sql.test.d.ts +2 -0
  52. package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
  53. package/dist/reactiveQueries/sql.test.js +284 -0
  54. package/dist/reactiveQueries/sql.test.js.map +1 -0
  55. package/dist/row-query.d.ts +33 -0
  56. package/dist/row-query.d.ts.map +1 -0
  57. package/dist/row-query.js +84 -0
  58. package/dist/row-query.js.map +1 -0
  59. package/dist/store-context.d.ts +26 -0
  60. package/dist/store-context.d.ts.map +1 -0
  61. package/dist/store-context.js +6 -0
  62. package/dist/store-context.js.map +1 -0
  63. package/dist/store-devtools.d.ts +19 -0
  64. package/dist/store-devtools.d.ts.map +1 -0
  65. package/dist/store-devtools.js +141 -0
  66. package/dist/store-devtools.js.map +1 -0
  67. package/dist/store.d.ts +175 -0
  68. package/dist/store.d.ts.map +1 -0
  69. package/dist/store.js +507 -0
  70. package/dist/store.js.map +1 -0
  71. package/dist/utils/data-structures.d.ts +10 -0
  72. package/dist/utils/data-structures.d.ts.map +1 -0
  73. package/dist/utils/data-structures.js +32 -0
  74. package/dist/utils/data-structures.js.map +1 -0
  75. package/dist/utils/dev.d.ts +3 -0
  76. package/dist/utils/dev.d.ts.map +1 -0
  77. package/dist/utils/dev.js +17 -0
  78. package/dist/utils/dev.js.map +1 -0
  79. package/dist/utils/otel.d.ts +4 -0
  80. package/dist/utils/otel.d.ts.map +1 -0
  81. package/dist/utils/otel.js +6 -0
  82. package/dist/utils/otel.js.map +1 -0
  83. package/dist/utils/stack-info.d.ts +10 -0
  84. package/dist/utils/stack-info.d.ts.map +1 -0
  85. package/dist/utils/stack-info.js +41 -0
  86. package/dist/utils/stack-info.js.map +1 -0
  87. package/dist/utils/stack-info.test.d.ts +2 -0
  88. package/dist/utils/stack-info.test.d.ts.map +1 -0
  89. package/dist/utils/stack-info.test.js +75 -0
  90. package/dist/utils/stack-info.test.js.map +1 -0
  91. package/dist/utils/tests/fixture.d.ts +259 -0
  92. package/dist/utils/tests/fixture.d.ts.map +1 -0
  93. package/dist/utils/tests/fixture.js +33 -0
  94. package/dist/utils/tests/fixture.js.map +1 -0
  95. package/dist/utils/tests/mod.d.ts +3 -0
  96. package/dist/utils/tests/mod.d.ts.map +1 -0
  97. package/dist/utils/tests/mod.js +3 -0
  98. package/dist/utils/tests/mod.js.map +1 -0
  99. package/dist/utils/tests/otel.d.ts +10 -0
  100. package/dist/utils/tests/otel.d.ts.map +1 -0
  101. package/dist/utils/tests/otel.js +42 -0
  102. package/dist/utils/tests/otel.js.map +1 -0
  103. package/package.json +60 -0
  104. package/src/QueryCache.ts +81 -0
  105. package/src/SynchronousDatabaseWrapper.ts +256 -0
  106. package/src/ambient.d.ts +10 -0
  107. package/src/effect/LiveStore.ts +112 -0
  108. package/src/effect/index.ts +8 -0
  109. package/src/global-state.ts +20 -0
  110. package/src/index.ts +64 -0
  111. package/src/reactive.test.ts +426 -0
  112. package/src/reactive.ts +661 -0
  113. package/src/reactiveQueries/base-class.ts +115 -0
  114. package/src/reactiveQueries/graphql.ts +233 -0
  115. package/src/reactiveQueries/js.ts +108 -0
  116. package/src/reactiveQueries/sql.test.ts +308 -0
  117. package/src/reactiveQueries/sql.ts +226 -0
  118. package/src/row-query.ts +200 -0
  119. package/src/store-context.ts +23 -0
  120. package/src/store-devtools.ts +217 -0
  121. package/src/store.ts +920 -0
  122. package/src/utils/data-structures.ts +36 -0
  123. package/src/utils/dev.ts +24 -0
  124. package/src/utils/otel.ts +9 -0
  125. package/src/utils/stack-info.test.ts +79 -0
  126. package/src/utils/stack-info.ts +54 -0
  127. package/src/utils/tests/fixture.ts +77 -0
  128. package/src/utils/tests/mod.ts +2 -0
  129. package/src/utils/tests/otel.ts +61 -0
  130. package/tsconfig.json +18 -0
  131. package/vitest.config.js +9 -0
@@ -0,0 +1,200 @@
1
+ import type { QueryInfoCol, QueryInfoNone, QueryInfoRow } from '@livestore/common'
2
+ import { SessionIdSymbol, sql } from '@livestore/common'
3
+ import { DbSchema } from '@livestore/common/schema'
4
+ import type { SqliteDsl } from '@livestore/db-schema'
5
+ import type { GetValForKey } from '@livestore/utils'
6
+ import { shouldNeverHappen } from '@livestore/utils'
7
+ import { Schema } from '@livestore/utils/effect'
8
+ import type * as otel from '@opentelemetry/api'
9
+
10
+ import type {
11
+ GetAtomResult,
12
+ LiveQuery,
13
+ LiveQueryAny,
14
+ QueryContext,
15
+ ReactivityGraph,
16
+ } from './reactiveQueries/base-class.js'
17
+ import { computed } from './reactiveQueries/js.js'
18
+ import { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
19
+ import type { Store } from './store.js'
20
+
21
+ export type RowQueryOptions<TTableDef extends DbSchema.TableDef, TResult = RowResult<TTableDef>> = {
22
+ otelContext?: otel.Context
23
+ skipInsertDefaultRow?: boolean
24
+ reactivityGraph?: ReactivityGraph
25
+ map?: (result: RowResult<TTableDef>) => TResult
26
+ label?: string
27
+ }
28
+
29
+ export type RowQueryOptionsDefaulValues<TTableDef extends DbSchema.TableDef> = {
30
+ defaultValues?: Partial<RowResult<TTableDef>>
31
+ }
32
+
33
+ export type MakeRowQuery = {
34
+ <
35
+ TTableDef extends DbSchema.TableDef<
36
+ DbSchema.DefaultSqliteTableDef,
37
+ boolean,
38
+ DbSchema.TableOptions & { isSingleton: true }
39
+ >,
40
+ TResult = RowResult<TTableDef>,
41
+ >(
42
+ table: TTableDef,
43
+ options?: RowQueryOptions<TTableDef, TResult>,
44
+ ): LiveQuery<RowResult<TTableDef>, QueryInfoRow<TTableDef>>
45
+ <
46
+ TTableDef extends DbSchema.TableDef<
47
+ DbSchema.DefaultSqliteTableDef,
48
+ boolean,
49
+ DbSchema.TableOptions & { isSingleton: false }
50
+ >,
51
+ TResult = RowResult<TTableDef>,
52
+ >(
53
+ table: TTableDef,
54
+ // TODO adjust so it works with arbitrary primary keys or unique constraints
55
+ id: string | SessionIdSymbol,
56
+ options?: RowQueryOptions<TTableDef, TResult> & RowQueryOptionsDefaulValues<TTableDef>,
57
+ ): LiveQuery<TResult, QueryInfoRow<TTableDef>>
58
+ }
59
+
60
+ // TODO also allow other where clauses and multiple rows
61
+ export const rowQuery: MakeRowQuery = <TTableDef extends DbSchema.TableDef>(
62
+ table: TTableDef,
63
+ idOrOptions?: string | SessionIdSymbol | RowQueryOptions<TTableDef, any>,
64
+ options_?: RowQueryOptions<TTableDef, any> & RowQueryOptionsDefaulValues<TTableDef>,
65
+ ) => {
66
+ const id = typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? idOrOptions : undefined
67
+ const options = typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? options_ : idOrOptions
68
+ const defaultValues: Partial<RowResult<TTableDef>> | undefined = (options as any)?.defaultValues ?? {}
69
+
70
+ // Validate query args
71
+ if (table.options.isSingleton === true && id !== undefined && id !== SessionIdSymbol) {
72
+ shouldNeverHappen(`Cannot query state table ${table.sqliteDef.name} with id "${id}" as it is a singleton`)
73
+ } else if (table.options.isSingleton !== true && id === undefined) {
74
+ shouldNeverHappen(`Cannot query state table ${table.sqliteDef.name} without id`)
75
+ }
76
+
77
+ const tableSchema = table.sqliteDef
78
+ const tableName = tableSchema.name
79
+
80
+ const makeQueryString = (id: string | undefined) =>
81
+ sql`select * from ${tableName} ${id === undefined ? '' : `where id = '${id}'`} limit 1`
82
+
83
+ const genQueryString =
84
+ id === SessionIdSymbol
85
+ ? (_: GetAtomResult, ctx: QueryContext) => makeQueryString(ctx.store.sessionId)
86
+ : makeQueryString(id)
87
+
88
+ const rowSchema = table.isSingleColumn === true ? table.schema.pipe(Schema.pluck('value' as any)) : table.schema
89
+
90
+ return new LiveStoreSQLQuery({
91
+ label:
92
+ options?.label ??
93
+ `rowQuery:query:${tableSchema.name}${id === undefined ? '' : id === SessionIdSymbol ? `:sessionId` : `:${id}`}`,
94
+ genQueryString,
95
+ queriedTables: new Set([tableName]),
96
+ reactivityGraph: options?.reactivityGraph,
97
+ // While this code-path is not needed for singleton tables, it's still needed for `useRow` with non-existing rows for a given ID
98
+ execBeforeFirstRun: makeExecBeforeFirstRun({
99
+ otelContext: options?.otelContext,
100
+ table,
101
+ defaultValues,
102
+ id,
103
+ skipInsertDefaultRow: options?.skipInsertDefaultRow,
104
+ }),
105
+ schema: rowSchema.pipe(Schema.Array, Schema.headOrElse()),
106
+ map: options?.map,
107
+ queryInfo: {
108
+ _tag: 'Row',
109
+ table,
110
+ id: id === SessionIdSymbol ? 'sessionId' : (id ?? 'singleton'),
111
+ },
112
+ })
113
+ }
114
+
115
+ export type RowResult<TTableDef extends DbSchema.TableDef> = TTableDef['isSingleColumn'] extends true
116
+ ? GetValForKey<SqliteDsl.FromColumns.RowDecoded<TTableDef['sqliteDef']['columns']>, 'value'>
117
+ : SqliteDsl.FromColumns.RowDecoded<TTableDef['sqliteDef']['columns']>
118
+
119
+ export type RowResultEncoded<TTableDef extends DbSchema.TableDef> = TTableDef['isSingleColumn'] extends true
120
+ ? GetValForKey<SqliteDsl.FromColumns.RowEncoded<TTableDef['sqliteDef']['columns']>, 'value'>
121
+ : SqliteDsl.FromColumns.RowEncoded<TTableDef['sqliteDef']['columns']>
122
+
123
+ export const deriveColQuery: {
124
+ <TQuery extends LiveQuery<any, QueryInfoNone>, TCol extends keyof TQuery['__result!'] & string>(
125
+ query$: TQuery,
126
+ colName: TCol,
127
+ ): LiveQuery<TQuery['__result!'][TCol], QueryInfoNone>
128
+ <TQuery extends LiveQuery<any, QueryInfoRow<any>>, TCol extends keyof TQuery['__result!'] & string>(
129
+ query$: TQuery,
130
+ colName: TCol,
131
+ ): LiveQuery<TQuery['__result!'][TCol], QueryInfoCol<TQuery['queryInfo']['table'], TCol>>
132
+ } = (query$: LiveQueryAny, colName: string) => {
133
+ return computed((get) => get(query$)[colName], {
134
+ label: `deriveColQuery:${query$.label}:${colName}`,
135
+ queryInfo:
136
+ query$.queryInfo._tag === 'Row'
137
+ ? { _tag: 'Col', table: query$.queryInfo.table, column: colName, id: query$.queryInfo.id }
138
+ : undefined,
139
+ }) as any
140
+ }
141
+
142
+ const makeExecBeforeFirstRun =
143
+ ({
144
+ id,
145
+ defaultValues,
146
+ skipInsertDefaultRow,
147
+ otelContext: otelContext_,
148
+ table,
149
+ }: {
150
+ id?: string | SessionIdSymbol
151
+ defaultValues?: any
152
+ skipInsertDefaultRow?: boolean
153
+ otelContext?: otel.Context
154
+ table: DbSchema.TableDef
155
+ }) =>
156
+ ({ store }: QueryContext) => {
157
+ const otelContext = otelContext_ ?? store.otel.queriesSpanContext
158
+
159
+ if (skipInsertDefaultRow !== true && table.options.isSingleton === false) {
160
+ insertRowWithDefaultValuesOrIgnore({
161
+ store,
162
+ id: id!,
163
+ table,
164
+ otelContext,
165
+ explicitDefaultValues: defaultValues,
166
+ })
167
+ }
168
+ }
169
+
170
+ const insertRowWithDefaultValuesOrIgnore = ({
171
+ store,
172
+ id,
173
+ table,
174
+ otelContext,
175
+ explicitDefaultValues,
176
+ }: {
177
+ store: Store
178
+ id: string | SessionIdSymbol
179
+ table: DbSchema.TableDef
180
+ otelContext: otel.Context
181
+ explicitDefaultValues: Partial<RowResult<DbSchema.TableDef>> | undefined
182
+ }) => {
183
+ const idStr = id === SessionIdSymbol ? store.sessionId : id
184
+ const rowExists =
185
+ store.syncDbWrapper.select(`select 1 from ${table.sqliteDef.name} where id = '${idStr}'`).length === 1
186
+
187
+ if (rowExists) return
188
+
189
+ // const mutationDef = deriveCreateMutationDef(table)
190
+ if (DbSchema.tableHasDerivedMutations(table) === false) {
191
+ return shouldNeverHappen(
192
+ `Cannot insert row for table "${table.sqliteDef.name}" which does not have 'deriveMutations: true' set`,
193
+ )
194
+ }
195
+ // NOTE It's important that we only mutate and don't refresh here, as this function is called during a render
196
+ store.mutateWithoutRefresh(table.insert({ id, ...explicitDefaultValues }), {
197
+ otelContext,
198
+ coordinatorMode: 'default',
199
+ })
200
+ }
@@ -0,0 +1,23 @@
1
+ import type { IntentionalShutdownCause, UnexpectedError } from '@livestore/common'
2
+ import { Schema } from '@livestore/utils/effect'
3
+
4
+ import type { Store } from './store.js'
5
+
6
+ export type LiveStoreContext =
7
+ | LiveStoreContextRunning
8
+ | {
9
+ stage: 'error'
10
+ error: UnexpectedError | unknown
11
+ }
12
+ | {
13
+ stage: 'shutdown'
14
+ cause: IntentionalShutdownCause | StoreAbort
15
+ }
16
+
17
+ export class StoreAbort extends Schema.TaggedError<StoreAbort>()('LiveStore.StoreAbort', {}) {}
18
+ export class StoreInterrupted extends Schema.TaggedError<StoreInterrupted>()('LiveStore.StoreInterrupted', {}) {}
19
+
20
+ export type LiveStoreContextRunning = {
21
+ stage: 'running'
22
+ store: Store
23
+ }
@@ -0,0 +1,217 @@
1
+ import type { ClientSession, DebugInfo } from '@livestore/common'
2
+ import { Devtools, liveStoreVersion, UnexpectedError } from '@livestore/common'
3
+ import { throttle } from '@livestore/utils'
4
+ import type { WebChannel } from '@livestore/utils/effect'
5
+ import { Effect, Stream } from '@livestore/utils/effect'
6
+
7
+ import { NOT_REFRESHED_YET } from './reactive.js'
8
+ import type { LiveQuery, ReactivityGraph } from './reactiveQueries/base-class.js'
9
+ import type { SynchronousDatabaseWrapper } from './SynchronousDatabaseWrapper.js'
10
+ import { emptyDebugInfo as makeEmptyDebugInfo } from './SynchronousDatabaseWrapper.js'
11
+ import type { ReferenceCountedSet } from './utils/data-structures.js'
12
+
13
+ type IStore = {
14
+ clientSession: ClientSession
15
+ reactivityGraph: ReactivityGraph
16
+ syncDbWrapper: SynchronousDatabaseWrapper
17
+ activeQueries: ReferenceCountedSet<LiveQuery<any>>
18
+ }
19
+
20
+ type Unsub = () => void
21
+ type RequestId = string
22
+ type SubMap = Map<RequestId, Unsub>
23
+
24
+ export const connectDevtoolsToStore = ({
25
+ storeDevtoolsChannel,
26
+ store,
27
+ }: {
28
+ storeDevtoolsChannel: WebChannel.WebChannel<Devtools.MessageToAppHostStore, Devtools.MessageFromAppHostStore>
29
+ store: IStore
30
+ }) =>
31
+ Effect.gen(function* () {
32
+ const appHostId = store.clientSession.coordinator.devtools.appHostId
33
+
34
+ const reactivityGraphSubcriptions: SubMap = new Map()
35
+ const liveQueriesSubscriptions: SubMap = new Map()
36
+ const debugInfoHistorySubscriptions: SubMap = new Map()
37
+
38
+ yield* Effect.addFinalizer(() =>
39
+ Effect.sync(() => {
40
+ reactivityGraphSubcriptions.forEach((unsub) => unsub())
41
+ liveQueriesSubscriptions.forEach((unsub) => unsub())
42
+ debugInfoHistorySubscriptions.forEach((unsub) => unsub())
43
+ }),
44
+ )
45
+
46
+ const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
47
+ storeDevtoolsChannel.send(message).pipe(Effect.tapCauseLogPretty, Effect.runSync)
48
+
49
+ const onMessage = (decodedMessage: typeof Devtools.MessageToAppHostStore.Type) => {
50
+ // console.log('storeMessagePort message', decodedMessage)
51
+
52
+ if (decodedMessage.appHostId !== store.clientSession.coordinator.devtools.appHostId) {
53
+ // console.log(`Unknown message`, event)
54
+ return
55
+ }
56
+
57
+ const requestId = decodedMessage.requestId
58
+
59
+ const requestIdleCallback = globalThis.requestIdleCallback ?? ((cb: () => void) => cb())
60
+
61
+ switch (decodedMessage._tag) {
62
+ case 'LSD.ReactivityGraphSubscribe': {
63
+ const includeResults = decodedMessage.includeResults
64
+
65
+ const send = () =>
66
+ // In order to not add more work to the current tick, we use requestIdleCallback
67
+ // to send the reactivity graph updates to the devtools
68
+ requestIdleCallback(
69
+ () =>
70
+ sendToDevtools(
71
+ Devtools.ReactivityGraphRes.make({
72
+ reactivityGraph: store.reactivityGraph.getSnapshot({ includeResults }),
73
+ requestId,
74
+ appHostId,
75
+ liveStoreVersion,
76
+ }),
77
+ ),
78
+ { timeout: 500 },
79
+ )
80
+
81
+ send()
82
+
83
+ // In some cases, there can be A LOT of reactivity graph updates in a short period of time
84
+ // so we throttle the updates to avoid sending too much data
85
+ // This might need to be tweaked further and possibly be exposed to the user in some way.
86
+ const throttledSend = throttle(send, 20)
87
+
88
+ reactivityGraphSubcriptions.set(requestId, store.reactivityGraph.subscribeToRefresh(throttledSend))
89
+
90
+ break
91
+ }
92
+ case 'LSD.DebugInfoReq': {
93
+ sendToDevtools(
94
+ Devtools.DebugInfoRes.make({
95
+ debugInfo: store.syncDbWrapper.debugInfo,
96
+ requestId,
97
+ appHostId,
98
+ liveStoreVersion,
99
+ }),
100
+ )
101
+ break
102
+ }
103
+ case 'LSD.DebugInfoHistorySubscribe': {
104
+ const buffer: DebugInfo[] = []
105
+ let hasStopped = false
106
+ let rafHandle: number | undefined
107
+
108
+ const tick = () => {
109
+ buffer.push(store.syncDbWrapper.debugInfo)
110
+
111
+ // NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
112
+ // will get the empty debug info
113
+ // TODO We need to come up with a more graceful way to do store. Probably via a single global
114
+ // `requestAnimationFrame` loop that is passed in somehow.
115
+ store.syncDbWrapper.debugInfo = makeEmptyDebugInfo()
116
+
117
+ if (buffer.length > 10) {
118
+ sendToDevtools(
119
+ Devtools.DebugInfoHistoryRes.make({
120
+ debugInfoHistory: buffer,
121
+ requestId,
122
+ appHostId,
123
+ liveStoreVersion,
124
+ }),
125
+ )
126
+ buffer.length = 0
127
+ }
128
+
129
+ if (hasStopped === false) {
130
+ rafHandle = requestAnimationFrame(tick)
131
+ }
132
+ }
133
+
134
+ rafHandle = requestAnimationFrame(tick)
135
+
136
+ const unsub = () => {
137
+ hasStopped = true
138
+ if (rafHandle !== undefined) {
139
+ cancelAnimationFrame(rafHandle)
140
+ }
141
+ }
142
+
143
+ debugInfoHistorySubscriptions.set(requestId, unsub)
144
+
145
+ break
146
+ }
147
+ case 'LSD.DebugInfoHistoryUnsubscribe': {
148
+ debugInfoHistorySubscriptions.get(requestId)!()
149
+ debugInfoHistorySubscriptions.delete(requestId)
150
+ break
151
+ }
152
+ case 'LSD.DebugInfoResetReq': {
153
+ store.syncDbWrapper.debugInfo.slowQueries.clear()
154
+ sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, appHostId, liveStoreVersion }))
155
+ break
156
+ }
157
+ case 'LSD.DebugInfoRerunQueryReq': {
158
+ const { queryStr, bindValues, queriedTables } = decodedMessage
159
+ store.syncDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
160
+ sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, appHostId, liveStoreVersion }))
161
+ break
162
+ }
163
+ case 'LSD.ReactivityGraphUnsubscribe': {
164
+ reactivityGraphSubcriptions.get(requestId)!()
165
+ break
166
+ }
167
+ case 'LSD.LiveQueriesSubscribe': {
168
+ const send = () =>
169
+ requestIdleCallback(
170
+ () =>
171
+ sendToDevtools(
172
+ Devtools.LiveQueriesRes.make({
173
+ liveQueries: [...store.activeQueries].map((q) => ({
174
+ _tag: q._tag,
175
+ id: q.id,
176
+ label: q.label,
177
+ runs: q.runs,
178
+ executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
179
+ lastestResult:
180
+ q.results$.previousResult === NOT_REFRESHED_YET
181
+ ? 'SYMBOL_NOT_REFRESHED_YET'
182
+ : q.results$.previousResult,
183
+ activeSubscriptions: Array.from(q.activeSubscriptions),
184
+ })),
185
+ requestId,
186
+ liveStoreVersion,
187
+ appHostId,
188
+ }),
189
+ ),
190
+ { timeout: 500 },
191
+ )
192
+
193
+ send()
194
+
195
+ // Same as in the reactivity graph subscription case above, we need to throttle the updates
196
+ const throttledSend = throttle(send, 20)
197
+
198
+ liveQueriesSubscriptions.set(requestId, store.reactivityGraph.subscribeToRefresh(throttledSend))
199
+
200
+ break
201
+ }
202
+ case 'LSD.LiveQueriesUnsubscribe': {
203
+ liveQueriesSubscriptions.get(requestId)!()
204
+ liveQueriesSubscriptions.delete(requestId)
205
+ break
206
+ }
207
+ // No default
208
+ }
209
+ }
210
+
211
+ yield* storeDevtoolsChannel.listen.pipe(
212
+ Stream.flatten(),
213
+ Stream.tapSync((message) => onMessage(message)),
214
+ Stream.runDrain,
215
+ Effect.withSpan('LSD.devtools.onMessage'),
216
+ )
217
+ }).pipe(UnexpectedError.mapToUnexpectedError, Effect.withSpan('LSD.devtools.connectStoreToDevtools'))