@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.
- package/README.md +1 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/QueryCache.d.ts +20 -0
- package/dist/QueryCache.d.ts.map +1 -0
- package/dist/QueryCache.js +61 -0
- package/dist/QueryCache.js.map +1 -0
- package/dist/SynchronousDatabaseWrapper.d.ts +36 -0
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -0
- package/dist/SynchronousDatabaseWrapper.js +176 -0
- package/dist/SynchronousDatabaseWrapper.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +38 -0
- package/dist/effect/LiveStore.d.ts.map +1 -0
- package/dist/effect/LiveStore.js +38 -0
- package/dist/effect/LiveStore.js.map +1 -0
- package/dist/effect/index.d.ts +2 -0
- package/dist/effect/index.d.ts.map +1 -0
- package/dist/effect/index.js +2 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/global-state.d.ts +14 -0
- package/dist/global-state.d.ts.map +1 -0
- package/dist/global-state.js +16 -0
- package/dist/global-state.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/reactive.d.ts +163 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +382 -0
- package/dist/reactive.js.map +1 -0
- package/dist/reactive.test.d.ts +2 -0
- package/dist/reactive.test.d.ts.map +1 -0
- package/dist/reactive.test.js +345 -0
- package/dist/reactive.test.js.map +1 -0
- package/dist/reactiveQueries/base-class.d.ts +59 -0
- package/dist/reactiveQueries/base-class.d.ts.map +1 -0
- package/dist/reactiveQueries/base-class.js +29 -0
- package/dist/reactiveQueries/base-class.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +52 -0
- package/dist/reactiveQueries/graphql.d.ts.map +1 -0
- package/dist/reactiveQueries/graphql.js +136 -0
- package/dist/reactiveQueries/graphql.js.map +1 -0
- package/dist/reactiveQueries/js.d.ts +35 -0
- package/dist/reactiveQueries/js.d.ts.map +1 -0
- package/dist/reactiveQueries/js.js +57 -0
- package/dist/reactiveQueries/js.js.map +1 -0
- package/dist/reactiveQueries/sql.d.ts +49 -0
- package/dist/reactiveQueries/sql.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.js +130 -0
- package/dist/reactiveQueries/sql.js.map +1 -0
- package/dist/reactiveQueries/sql.test.d.ts +2 -0
- package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.test.js +284 -0
- package/dist/reactiveQueries/sql.test.js.map +1 -0
- package/dist/row-query.d.ts +33 -0
- package/dist/row-query.d.ts.map +1 -0
- package/dist/row-query.js +84 -0
- package/dist/row-query.js.map +1 -0
- package/dist/store-context.d.ts +26 -0
- package/dist/store-context.d.ts.map +1 -0
- package/dist/store-context.js +6 -0
- package/dist/store-context.js.map +1 -0
- package/dist/store-devtools.d.ts +19 -0
- package/dist/store-devtools.d.ts.map +1 -0
- package/dist/store-devtools.js +141 -0
- package/dist/store-devtools.js.map +1 -0
- package/dist/store.d.ts +175 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +507 -0
- package/dist/store.js.map +1 -0
- package/dist/utils/data-structures.d.ts +10 -0
- package/dist/utils/data-structures.d.ts.map +1 -0
- package/dist/utils/data-structures.js +32 -0
- package/dist/utils/data-structures.js.map +1 -0
- package/dist/utils/dev.d.ts +3 -0
- package/dist/utils/dev.d.ts.map +1 -0
- package/dist/utils/dev.js +17 -0
- package/dist/utils/dev.js.map +1 -0
- package/dist/utils/otel.d.ts +4 -0
- package/dist/utils/otel.d.ts.map +1 -0
- package/dist/utils/otel.js +6 -0
- package/dist/utils/otel.js.map +1 -0
- package/dist/utils/stack-info.d.ts +10 -0
- package/dist/utils/stack-info.d.ts.map +1 -0
- package/dist/utils/stack-info.js +41 -0
- package/dist/utils/stack-info.js.map +1 -0
- package/dist/utils/stack-info.test.d.ts +2 -0
- package/dist/utils/stack-info.test.d.ts.map +1 -0
- package/dist/utils/stack-info.test.js +75 -0
- package/dist/utils/stack-info.test.js.map +1 -0
- package/dist/utils/tests/fixture.d.ts +259 -0
- package/dist/utils/tests/fixture.d.ts.map +1 -0
- package/dist/utils/tests/fixture.js +33 -0
- package/dist/utils/tests/fixture.js.map +1 -0
- package/dist/utils/tests/mod.d.ts +3 -0
- package/dist/utils/tests/mod.d.ts.map +1 -0
- package/dist/utils/tests/mod.js +3 -0
- package/dist/utils/tests/mod.js.map +1 -0
- package/dist/utils/tests/otel.d.ts +10 -0
- package/dist/utils/tests/otel.d.ts.map +1 -0
- package/dist/utils/tests/otel.js +42 -0
- package/dist/utils/tests/otel.js.map +1 -0
- package/package.json +60 -0
- package/src/QueryCache.ts +81 -0
- package/src/SynchronousDatabaseWrapper.ts +256 -0
- package/src/ambient.d.ts +10 -0
- package/src/effect/LiveStore.ts +112 -0
- package/src/effect/index.ts +8 -0
- package/src/global-state.ts +20 -0
- package/src/index.ts +64 -0
- package/src/reactive.test.ts +426 -0
- package/src/reactive.ts +661 -0
- package/src/reactiveQueries/base-class.ts +115 -0
- package/src/reactiveQueries/graphql.ts +233 -0
- package/src/reactiveQueries/js.ts +108 -0
- package/src/reactiveQueries/sql.test.ts +308 -0
- package/src/reactiveQueries/sql.ts +226 -0
- package/src/row-query.ts +200 -0
- package/src/store-context.ts +23 -0
- package/src/store-devtools.ts +217 -0
- package/src/store.ts +920 -0
- package/src/utils/data-structures.ts +36 -0
- package/src/utils/dev.ts +24 -0
- package/src/utils/otel.ts +9 -0
- package/src/utils/stack-info.test.ts +79 -0
- package/src/utils/stack-info.ts +54 -0
- package/src/utils/tests/fixture.ts +77 -0
- package/src/utils/tests/mod.ts +2 -0
- package/src/utils/tests/otel.ts +61 -0
- package/tsconfig.json +18 -0
- package/vitest.config.js +9 -0
package/src/row-query.ts
ADDED
|
@@ -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'))
|