@livestore/livestore 0.0.0-snapshot-d9d66b354a9f4cfae987725d38971992ff14e4ad → 0.0.0-snapshot-1d99fea7d2ce2c7a5d9ed0a3752f8a7bda6bc3db
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 +54 -0
- package/dist/SqliteDbWrapper.d.ts.map +1 -0
- package/dist/{SynchronousDatabaseWrapper.js → SqliteDbWrapper.js} +44 -9
- package/dist/SqliteDbWrapper.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +16 -12
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +14 -14
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +57 -21
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +54 -13
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/computed.d.ts +7 -7
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +34 -11
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/{db.d.ts → db-query.d.ts} +15 -14
- package/dist/live-queries/db-query.d.ts.map +1 -0
- package/dist/live-queries/{db.js → db-query.js} +87 -43
- package/dist/live-queries/db-query.js.map +1 -0
- package/dist/live-queries/db-query.test.d.ts +2 -0
- package/dist/live-queries/db-query.test.d.ts.map +1 -0
- package/dist/live-queries/db-query.test.js +113 -0
- package/dist/live-queries/db-query.test.js.map +1 -0
- package/dist/live-queries/graphql.d.ts +8 -8
- package/dist/live-queries/graphql.d.ts.map +1 -1
- package/dist/live-queries/graphql.js +34 -9
- package/dist/live-queries/graphql.js.map +1 -1
- package/dist/live-queries/make-ref.d.ts +20 -0
- package/dist/live-queries/make-ref.d.ts.map +1 -0
- package/dist/live-queries/make-ref.js +33 -0
- package/dist/live-queries/make-ref.js.map +1 -0
- package/dist/reactive.d.ts +19 -13
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +22 -18
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +1 -1
- package/dist/reactive.test.js.map +1 -1
- package/dist/row-query-utils.d.ts +4 -4
- package/dist/row-query-utils.d.ts.map +1 -1
- package/dist/row-query-utils.js +14 -10
- package/dist/row-query-utils.js.map +1 -1
- package/dist/store/create-store.d.ts +13 -12
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +27 -33
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +3 -3
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +56 -34
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +18 -18
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store.d.ts +57 -38
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +226 -188
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js +3 -2
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/function-string.d.ts +7 -0
- package/dist/utils/function-string.d.ts.map +1 -0
- package/dist/utils/function-string.js +9 -0
- package/dist/utils/function-string.js.map +1 -0
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +6 -1
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/stack-info.test.js +54 -1
- package/dist/utils/stack-info.test.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +2 -6
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +7 -13
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/mod.d.ts +1 -0
- package/dist/utils/tests/mod.d.ts.map +1 -1
- package/dist/utils/tests/mod.js +1 -0
- package/dist/utils/tests/mod.js.map +1 -1
- package/dist/utils/tests/otel.d.ts +60 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +65 -4
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +12 -12
- package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +59 -14
- package/src/ambient.d.ts +1 -1
- package/src/effect/LiveStore.ts +32 -33
- package/src/index.ts +14 -7
- package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +220 -72
- package/src/live-queries/base-class.ts +151 -40
- package/src/live-queries/computed.ts +44 -19
- package/src/live-queries/{db.test.ts → db-query.test.ts} +46 -33
- package/src/live-queries/{db.ts → db-query.ts} +123 -61
- package/src/live-queries/graphql.ts +46 -21
- package/src/live-queries/make-ref.ts +47 -0
- package/src/reactive.test.ts +1 -1
- package/src/reactive.ts +60 -37
- package/src/row-query-utils.ts +29 -18
- package/src/store/create-store.ts +106 -113
- package/src/store/devtools.ts +65 -39
- package/src/store/store-types.ts +20 -18
- package/src/store/store.ts +362 -289
- package/src/utils/dev.ts +4 -2
- package/src/utils/function-string.ts +12 -0
- package/src/utils/stack-info.test.ts +58 -1
- package/src/utils/stack-info.ts +6 -1
- package/src/utils/tests/fixture.ts +6 -16
- package/src/utils/tests/mod.ts +1 -0
- package/src/utils/tests/otel.ts +71 -5
- package/dist/SynchronousDatabaseWrapper.d.ts +0 -36
- package/dist/SynchronousDatabaseWrapper.d.ts.map +0 -1
- package/dist/SynchronousDatabaseWrapper.js.map +0 -1
- package/dist/global-state.d.ts +0 -14
- package/dist/global-state.d.ts.map +0 -1
- package/dist/global-state.js +0 -16
- package/dist/global-state.js.map +0 -1
- package/dist/live-queries/db.d.ts.map +0 -1
- package/dist/live-queries/db.js.map +0 -1
- package/dist/live-queries/db.test.d.ts +0 -2
- package/dist/live-queries/db.test.d.ts.map +0 -1
- package/dist/live-queries/db.test.js +0 -117
- package/dist/live-queries/db.test.js.map +0 -1
- package/src/global-state.ts +0 -20
|
@@ -1,27 +1,47 @@
|
|
|
1
1
|
import type { QueryInfo } from '@livestore/common'
|
|
2
2
|
import * as otel from '@opentelemetry/api'
|
|
3
3
|
|
|
4
|
-
import { globalReactivityGraph } from '../global-state.js'
|
|
5
4
|
import type { Thunk } from '../reactive.js'
|
|
6
5
|
import type { RefreshReason } from '../store/store-types.js'
|
|
6
|
+
import { isValidFunctionString } from '../utils/function-string.js'
|
|
7
7
|
import { getDurationMsFromSpan } from '../utils/otel.js'
|
|
8
|
-
import type {
|
|
9
|
-
import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
|
|
8
|
+
import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
|
|
9
|
+
import { depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.js'
|
|
10
10
|
|
|
11
11
|
export const computed = <TResult, TQueryInfo extends QueryInfo = QueryInfo.None>(
|
|
12
12
|
fn: (get: GetAtomResult) => TResult,
|
|
13
13
|
options?: {
|
|
14
|
-
label
|
|
15
|
-
reactivityGraph?: ReactivityGraph
|
|
14
|
+
label?: string
|
|
16
15
|
queryInfo?: TQueryInfo
|
|
16
|
+
deps?: DepKey
|
|
17
17
|
},
|
|
18
|
-
):
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
): LiveQueryDef<TResult, TQueryInfo> => {
|
|
19
|
+
const hash = options?.deps ? depsToString(options.deps) : fn.toString()
|
|
20
|
+
if (isValidFunctionString(hash)._tag === 'invalid') {
|
|
21
|
+
throw new Error(`On Expo/React Native, computed queries must provide a \`deps\` option`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const queryInfo = options?.queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
_tag: 'def',
|
|
28
|
+
make: withRCMap(hash, (ctx, _otelContext) => {
|
|
29
|
+
// TODO onDestroy
|
|
30
|
+
return new LiveStoreComputedQuery<TResult, TQueryInfo>({
|
|
31
|
+
fn,
|
|
32
|
+
label: options?.label ?? fn.toString(),
|
|
33
|
+
queryInfo: options?.queryInfo,
|
|
34
|
+
reactivityGraph: ctx.reactivityGraph.deref()!,
|
|
35
|
+
})
|
|
36
|
+
}),
|
|
21
37
|
label: options?.label ?? fn.toString(),
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
// NOTE We're using the `makeQuery` function body string to make sure the key is unique across the app
|
|
39
|
+
// TODO we should figure out whether this could cause some problems and/or if there's a better way to do this
|
|
40
|
+
// NOTE `fn.toString()` doesn't work in Expo as it always produces `[native code]`
|
|
41
|
+
hash,
|
|
42
|
+
queryInfo,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
25
45
|
|
|
26
46
|
export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = QueryInfo.None> extends LiveStoreQueryBase<
|
|
27
47
|
TResult,
|
|
@@ -30,11 +50,11 @@ export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = Quer
|
|
|
30
50
|
_tag: 'computed' = 'computed'
|
|
31
51
|
|
|
32
52
|
/** A reactive thunk representing the query results */
|
|
33
|
-
results$: Thunk<TResult,
|
|
53
|
+
results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
|
|
34
54
|
|
|
35
55
|
label: string
|
|
36
56
|
|
|
37
|
-
|
|
57
|
+
reactivityGraph: ReactivityGraph
|
|
38
58
|
|
|
39
59
|
queryInfo: TQueryInfo
|
|
40
60
|
|
|
@@ -46,23 +66,22 @@ export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = Quer
|
|
|
46
66
|
}: {
|
|
47
67
|
label: string
|
|
48
68
|
fn: (get: GetAtomResult) => TResult
|
|
49
|
-
reactivityGraph
|
|
69
|
+
reactivityGraph: ReactivityGraph
|
|
50
70
|
queryInfo?: TQueryInfo
|
|
51
71
|
}) {
|
|
52
72
|
super()
|
|
53
73
|
|
|
54
74
|
this.label = label
|
|
55
|
-
|
|
56
|
-
this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
|
|
75
|
+
this.reactivityGraph = reactivityGraph
|
|
57
76
|
this.queryInfo = queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
|
|
58
77
|
|
|
59
78
|
const queryLabel = `${label}:results`
|
|
60
79
|
|
|
61
80
|
this.results$ = this.reactivityGraph.makeThunk(
|
|
62
|
-
(get, setDebugInfo,
|
|
63
|
-
otelTracer.startActiveSpan(`js:${label}`, {}, otelContext ?? rootOtelContext, (span) => {
|
|
81
|
+
(get, setDebugInfo, ctx, otelContext) =>
|
|
82
|
+
ctx.otelTracer.startActiveSpan(`js:${label}`, {}, otelContext ?? ctx.rootOtelContext, (span) => {
|
|
64
83
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
65
|
-
const res = fn(makeGetAtomResult(get, otelContext))
|
|
84
|
+
const res = fn(makeGetAtomResult(get, ctx, otelContext, this.dependencyQueriesRef))
|
|
66
85
|
|
|
67
86
|
span.end()
|
|
68
87
|
|
|
@@ -79,6 +98,12 @@ export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = Quer
|
|
|
79
98
|
}
|
|
80
99
|
|
|
81
100
|
destroy = () => {
|
|
101
|
+
this.isDestroyed = true
|
|
102
|
+
|
|
82
103
|
this.reactivityGraph.destroyNode(this.results$)
|
|
104
|
+
|
|
105
|
+
for (const query of this.dependencyQueriesRef) {
|
|
106
|
+
query.deref()
|
|
107
|
+
}
|
|
83
108
|
}
|
|
84
109
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Effect, Schema } from '@livestore/utils/effect'
|
|
2
|
+
import { Vitest } from '@livestore/utils/node-vitest'
|
|
2
3
|
import * as otel from '@opentelemetry/api'
|
|
3
4
|
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
4
|
-
import {
|
|
5
|
+
import { expect } from 'vitest'
|
|
5
6
|
|
|
6
7
|
import { computed, queryDb, rawSqlMutation, sql } from '../index.js'
|
|
8
|
+
import * as RG from '../reactive.js'
|
|
7
9
|
import { makeTodoMvc, tables } from '../utils/tests/fixture.js'
|
|
8
10
|
import { getSimplifiedRootSpan } from '../utils/tests/otel.js'
|
|
9
11
|
|
|
@@ -14,12 +16,15 @@ TODO write tests for:
|
|
|
14
16
|
- optional and explicit `queriedTables` argument
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
|
-
describe('otel', () => {
|
|
19
|
+
Vitest.describe('otel', () => {
|
|
18
20
|
let cachedProvider: BasicTracerProvider | undefined
|
|
19
21
|
|
|
20
22
|
const makeQuery = Effect.gen(function* () {
|
|
21
23
|
const exporter = new InMemorySpanExporter()
|
|
22
24
|
|
|
25
|
+
RG.__resetIds()
|
|
26
|
+
|
|
27
|
+
// const provider = cachedProvider ?? new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] })
|
|
23
28
|
const provider = cachedProvider ?? new BasicTracerProvider()
|
|
24
29
|
cachedProvider = provider
|
|
25
30
|
provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
|
|
@@ -27,10 +32,10 @@ describe('otel', () => {
|
|
|
27
32
|
|
|
28
33
|
const otelTracer = otel.trace.getTracer('test')
|
|
29
34
|
|
|
30
|
-
const span = otelTracer.startSpan('test')
|
|
35
|
+
const span = otelTracer.startSpan('test-root')
|
|
31
36
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
32
37
|
|
|
33
|
-
const
|
|
38
|
+
const store = yield* makeTodoMvc({ otelTracer, otelContext })
|
|
34
39
|
|
|
35
40
|
return {
|
|
36
41
|
store,
|
|
@@ -41,8 +46,8 @@ describe('otel', () => {
|
|
|
41
46
|
}
|
|
42
47
|
})
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
Vitest.scopedLive('otel', () =>
|
|
50
|
+
Effect.gen(function* () {
|
|
46
51
|
const { store, exporter, span } = yield* makeQuery
|
|
47
52
|
|
|
48
53
|
const query$ = queryDb({
|
|
@@ -50,11 +55,11 @@ describe('otel', () => {
|
|
|
50
55
|
schema: Schema.Array(tables.todos.schema),
|
|
51
56
|
queriedTables: new Set(['todos']),
|
|
52
57
|
})
|
|
53
|
-
expect(query
|
|
58
|
+
expect(store.query(query$)).toMatchInlineSnapshot('[]')
|
|
54
59
|
|
|
55
60
|
store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
56
61
|
|
|
57
|
-
expect(query
|
|
62
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
58
63
|
[
|
|
59
64
|
{
|
|
60
65
|
"completed": false,
|
|
@@ -64,17 +69,17 @@ describe('otel', () => {
|
|
|
64
69
|
]
|
|
65
70
|
`)
|
|
66
71
|
|
|
67
|
-
query$.destroy()
|
|
68
72
|
span.end()
|
|
69
73
|
|
|
70
74
|
return { exporter }
|
|
71
|
-
}).pipe(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
}).pipe(
|
|
76
|
+
Effect.scoped,
|
|
77
|
+
Effect.tap(({ exporter }) => expect(getSimplifiedRootSpan(exporter)).toMatchSnapshot()),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
Vitest.scopedLive('with thunks', () =>
|
|
82
|
+
Effect.gen(function* () {
|
|
78
83
|
const { store, exporter, span } = yield* makeQuery
|
|
79
84
|
|
|
80
85
|
const defaultTodo = { id: '', text: '', completed: false }
|
|
@@ -88,7 +93,9 @@ describe('otel', () => {
|
|
|
88
93
|
{ label: 'all todos' },
|
|
89
94
|
)
|
|
90
95
|
|
|
91
|
-
expect(
|
|
96
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
97
|
+
|
|
98
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
92
99
|
{
|
|
93
100
|
"completed": false,
|
|
94
101
|
"id": "",
|
|
@@ -96,9 +103,13 @@ describe('otel', () => {
|
|
|
96
103
|
}
|
|
97
104
|
`)
|
|
98
105
|
|
|
106
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
107
|
+
|
|
99
108
|
store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
100
109
|
|
|
101
|
-
expect(
|
|
110
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
111
|
+
|
|
112
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
102
113
|
{
|
|
103
114
|
"completed": false,
|
|
104
115
|
"id": "t1",
|
|
@@ -106,17 +117,19 @@ describe('otel', () => {
|
|
|
106
117
|
}
|
|
107
118
|
`)
|
|
108
119
|
|
|
109
|
-
|
|
120
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
121
|
+
|
|
110
122
|
span.end()
|
|
111
123
|
|
|
112
124
|
return { exporter }
|
|
113
|
-
}).pipe(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
}).pipe(
|
|
126
|
+
Effect.scoped,
|
|
127
|
+
Effect.tap(({ exporter }) => expect(getSimplifiedRootSpan(exporter)).toMatchSnapshot()),
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
Vitest.scopedLive('with thunks with query builder and without labels', () =>
|
|
132
|
+
Effect.gen(function* () {
|
|
120
133
|
const { store, exporter, span } = yield* makeQuery
|
|
121
134
|
|
|
122
135
|
const defaultTodo = { id: '', text: '', completed: false }
|
|
@@ -124,7 +137,7 @@ describe('otel', () => {
|
|
|
124
137
|
const filter = computed(() => ({ completed: false }))
|
|
125
138
|
const query$ = queryDb((get) => tables.todos.query.where(get(filter)).first({ fallback: () => defaultTodo }))
|
|
126
139
|
|
|
127
|
-
expect(query
|
|
140
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
128
141
|
{
|
|
129
142
|
"completed": false,
|
|
130
143
|
"id": "",
|
|
@@ -134,7 +147,7 @@ describe('otel', () => {
|
|
|
134
147
|
|
|
135
148
|
store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
136
149
|
|
|
137
|
-
expect(query
|
|
150
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
138
151
|
{
|
|
139
152
|
"completed": false,
|
|
140
153
|
"id": "t1",
|
|
@@ -142,12 +155,12 @@ describe('otel', () => {
|
|
|
142
155
|
}
|
|
143
156
|
`)
|
|
144
157
|
|
|
145
|
-
query$.destroy()
|
|
146
158
|
span.end()
|
|
147
159
|
|
|
148
160
|
return { exporter }
|
|
149
|
-
}).pipe(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
}).pipe(
|
|
162
|
+
Effect.scoped,
|
|
163
|
+
Effect.tap(({ exporter }) => expect(getSimplifiedRootSpan(exporter)).toMatchSnapshot()),
|
|
164
|
+
),
|
|
165
|
+
)
|
|
153
166
|
})
|
|
@@ -5,19 +5,20 @@ import {
|
|
|
5
5
|
prepareBindValues,
|
|
6
6
|
QueryBuilderAstSymbol,
|
|
7
7
|
replaceSessionIdSymbol,
|
|
8
|
+
UnexpectedError,
|
|
8
9
|
} from '@livestore/common'
|
|
9
10
|
import { deepEqual, shouldNeverHappen } from '@livestore/utils'
|
|
10
11
|
import { Predicate, Schema, TreeFormatter } from '@livestore/utils/effect'
|
|
11
12
|
import * as otel from '@opentelemetry/api'
|
|
12
13
|
|
|
13
|
-
import { globalReactivityGraph } from '../global-state.js'
|
|
14
14
|
import type { Thunk } from '../reactive.js'
|
|
15
15
|
import { isThunk, NOT_REFRESHED_YET } from '../reactive.js'
|
|
16
16
|
import { makeExecBeforeFirstRun, rowQueryLabel } from '../row-query-utils.js'
|
|
17
17
|
import type { RefreshReason } from '../store/store-types.js'
|
|
18
|
+
import { isValidFunctionString } from '../utils/function-string.js'
|
|
18
19
|
import { getDurationMsFromSpan } from '../utils/otel.js'
|
|
19
|
-
import type {
|
|
20
|
-
import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
|
|
20
|
+
import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
|
|
21
|
+
import { depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.js'
|
|
21
22
|
|
|
22
23
|
export type QueryInputRaw<TDecoded, TEncoded, TQueryInfo extends QueryInfo> = {
|
|
23
24
|
query: string
|
|
@@ -30,9 +31,12 @@ export type QueryInputRaw<TDecoded, TEncoded, TQueryInfo extends QueryInfo> = {
|
|
|
30
31
|
*/
|
|
31
32
|
queriedTables?: Set<string>
|
|
32
33
|
queryInfo?: TQueryInfo
|
|
33
|
-
execBeforeFirstRun?: (ctx:
|
|
34
|
+
execBeforeFirstRun?: (ctx: ReactivityGraphContext) => void
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
export const isQueryInputRaw = (value: unknown): value is QueryInputRaw<any, any, any> =>
|
|
38
|
+
Predicate.hasProperty(value, 'query') && Predicate.hasProperty(value, 'schema')
|
|
39
|
+
|
|
36
40
|
export type QueryInput<TDecoded, TEncoded, TQueryInfo extends QueryInfo> =
|
|
37
41
|
| QueryInputRaw<TDecoded, TEncoded, TQueryInfo>
|
|
38
42
|
| QueryBuilder<TDecoded, any, any, TQueryInfo>
|
|
@@ -51,10 +55,10 @@ export const queryDb: {
|
|
|
51
55
|
* Used for debugging / devtools
|
|
52
56
|
*/
|
|
53
57
|
label?: string
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
deps?: DepKey
|
|
59
|
+
queryInfo?: TQueryInfo
|
|
56
60
|
},
|
|
57
|
-
):
|
|
61
|
+
): LiveQueryDef<TResult, TQueryInfo>
|
|
58
62
|
// NOTE in this "thunk case", we can't directly derive label/queryInfo from the queryInput,
|
|
59
63
|
// so the caller needs to provide them explicitly otherwise queryInfo will be set to `None`,
|
|
60
64
|
// and label will be set during the query execution
|
|
@@ -68,20 +72,46 @@ export const queryDb: {
|
|
|
68
72
|
* Used for debugging / devtools
|
|
69
73
|
*/
|
|
70
74
|
label?: string
|
|
71
|
-
|
|
75
|
+
deps?: DepKey
|
|
72
76
|
queryInfo?: TQueryInfo
|
|
73
|
-
otelContext?: otel.Context
|
|
74
77
|
},
|
|
75
|
-
):
|
|
76
|
-
} = (queryInput, options) =>
|
|
77
|
-
|
|
78
|
-
queryInput
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
): LiveQueryDef<TResult, TQueryInfo>
|
|
79
|
+
} = (queryInput, options) => {
|
|
80
|
+
const queryString = isQueryBuilder(queryInput)
|
|
81
|
+
? queryInput.toString()
|
|
82
|
+
: isQueryInputRaw(queryInput)
|
|
83
|
+
? queryInput.query
|
|
84
|
+
: typeof queryInput === 'function'
|
|
85
|
+
? queryInput.toString()
|
|
86
|
+
: shouldNeverHappen(`Invalid query input: ${queryInput}`)
|
|
87
|
+
|
|
88
|
+
const hash = options?.deps ? queryString + '-' + depsToString(options.deps) : queryString
|
|
89
|
+
if (isValidFunctionString(hash)._tag === 'invalid') {
|
|
90
|
+
throw new Error(`On Expo/React Native, db queries must provide a \`deps\` option`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const label = options?.label ?? queryString
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
_tag: 'def',
|
|
97
|
+
make: withRCMap(hash, (ctx, otelContext) => {
|
|
98
|
+
// TODO onDestroy
|
|
99
|
+
return new LiveStoreDbQuery({
|
|
100
|
+
reactivityGraph: ctx.reactivityGraph.deref()!,
|
|
101
|
+
queryInput,
|
|
102
|
+
label,
|
|
103
|
+
map: options?.map,
|
|
104
|
+
// We're not falling back to `None` here as the queryInfo will be set dynamically
|
|
105
|
+
queryInfo: options?.queryInfo,
|
|
106
|
+
otelContext,
|
|
107
|
+
})
|
|
108
|
+
}),
|
|
109
|
+
label,
|
|
110
|
+
hash,
|
|
111
|
+
queryInfo:
|
|
112
|
+
options?.queryInfo ?? (isQueryBuilder(queryInput) ? queryInfoFromQueryBuilder(queryInput) : { _tag: 'None' }),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
85
115
|
|
|
86
116
|
/* An object encapsulating a reactive SQL query */
|
|
87
117
|
export class LiveStoreDbQuery<
|
|
@@ -92,16 +122,16 @@ export class LiveStoreDbQuery<
|
|
|
92
122
|
_tag: 'db' = 'db'
|
|
93
123
|
|
|
94
124
|
/** A reactive thunk representing the query text */
|
|
95
|
-
queryInput$: Thunk<
|
|
125
|
+
queryInput$: Thunk<QueryInputRaw<any, any, QueryInfo>, ReactivityGraphContext, RefreshReason> | undefined
|
|
96
126
|
|
|
97
127
|
/** A reactive thunk representing the query results */
|
|
98
|
-
results$: Thunk<TResult,
|
|
128
|
+
results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
|
|
99
129
|
|
|
100
130
|
label: string
|
|
101
131
|
|
|
102
132
|
queryInfo: TQueryInfo
|
|
103
133
|
|
|
104
|
-
|
|
134
|
+
readonly reactivityGraph
|
|
105
135
|
|
|
106
136
|
private mapResult: (rows: TResultSchema) => TResult
|
|
107
137
|
|
|
@@ -116,17 +146,18 @@ export class LiveStoreDbQuery<
|
|
|
116
146
|
label?: string
|
|
117
147
|
queryInput:
|
|
118
148
|
| QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>
|
|
119
|
-
| ((get: GetAtomResult, ctx:
|
|
120
|
-
reactivityGraph
|
|
149
|
+
| ((get: GetAtomResult, ctx: ReactivityGraphContext) => QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
|
|
150
|
+
reactivityGraph: ReactivityGraph
|
|
121
151
|
map?: (rows: TResultSchema) => TResult
|
|
122
152
|
queryInfo?: TQueryInfo
|
|
153
|
+
/** Only used for the initial query execution */
|
|
123
154
|
otelContext?: otel.Context
|
|
124
155
|
}) {
|
|
125
156
|
super()
|
|
126
157
|
|
|
127
158
|
let label = inputLabel ?? 'db(unknown)'
|
|
128
159
|
let queryInfo = inputQueryInfo ?? ({ _tag: 'None' } as TQueryInfo)
|
|
129
|
-
this.reactivityGraph = reactivityGraph
|
|
160
|
+
this.reactivityGraph = reactivityGraph
|
|
130
161
|
|
|
131
162
|
this.mapResult = map === undefined ? (rows: any) => rows as TResult : map
|
|
132
163
|
|
|
@@ -135,37 +166,43 @@ export class LiveStoreDbQuery<
|
|
|
135
166
|
typeof queryInput === 'function' ? undefined : isQueryBuilder(queryInput) ? undefined : queryInput.schema,
|
|
136
167
|
}
|
|
137
168
|
|
|
138
|
-
const execBeforeFirstRunRef: {
|
|
169
|
+
const execBeforeFirstRunRef: {
|
|
170
|
+
current: ((ctx: ReactivityGraphContext, otelContext: otel.Context) => void) | undefined
|
|
171
|
+
} = {
|
|
139
172
|
current: undefined,
|
|
140
173
|
}
|
|
141
174
|
|
|
142
175
|
type TQueryInputRaw = QueryInputRaw<any, any, QueryInfo>
|
|
143
176
|
|
|
144
|
-
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw,
|
|
177
|
+
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, ReactivityGraphContext, RefreshReason>
|
|
145
178
|
|
|
146
179
|
const fromQueryBuilder = (qb: QueryBuilder.Any, otelContext: otel.Context | undefined) => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
180
|
+
try {
|
|
181
|
+
const qbRes = qb.asSql()
|
|
182
|
+
const schema = getResultSchema(qb) as Schema.Schema<TResultSchema, ReadonlyArray<any>>
|
|
183
|
+
const ast = qb[QueryBuilderAstSymbol]
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
queryInputRaw: {
|
|
187
|
+
query: qbRes.query,
|
|
188
|
+
schema,
|
|
189
|
+
bindValues: qbRes.bindValues,
|
|
190
|
+
queriedTables: new Set([ast.tableDef.sqliteDef.name]),
|
|
191
|
+
queryInfo: queryInfoFromQueryBuilder(qb),
|
|
192
|
+
} satisfies TQueryInputRaw,
|
|
193
|
+
label: ast._tag === 'RowQuery' ? rowQueryLabel(ast.tableDef, ast.id) : qb.toString(),
|
|
194
|
+
execBeforeFirstRun:
|
|
195
|
+
ast._tag === 'RowQuery'
|
|
196
|
+
? makeExecBeforeFirstRun({
|
|
197
|
+
table: ast.tableDef,
|
|
198
|
+
insertValues: ast.insertValues,
|
|
199
|
+
id: ast.id,
|
|
200
|
+
otelContext,
|
|
201
|
+
})
|
|
202
|
+
: undefined,
|
|
203
|
+
}
|
|
204
|
+
} catch (cause) {
|
|
205
|
+
throw new UnexpectedError({ cause, note: `Error building query for ${qb.toString()}`, payload: { qb } })
|
|
169
206
|
}
|
|
170
207
|
}
|
|
171
208
|
|
|
@@ -173,7 +210,10 @@ export class LiveStoreDbQuery<
|
|
|
173
210
|
queryInputRaw$OrQueryInputRaw = this.reactivityGraph.makeThunk(
|
|
174
211
|
(get, setDebugInfo, ctx, otelContext) => {
|
|
175
212
|
const startMs = performance.now()
|
|
176
|
-
const queryInputResult = queryInput(
|
|
213
|
+
const queryInputResult = queryInput(
|
|
214
|
+
makeGetAtomResult(get, ctx, otelContext ?? ctx.rootOtelContext, this.dependencyQueriesRef),
|
|
215
|
+
ctx,
|
|
216
|
+
)
|
|
177
217
|
const durationMs = performance.now() - startMs
|
|
178
218
|
|
|
179
219
|
let queryInputRaw: TQueryInputRaw
|
|
@@ -205,6 +245,8 @@ export class LiveStoreDbQuery<
|
|
|
205
245
|
equal: (a, b) => a.query === b.query && deepEqual(a.bindValues, b.bindValues),
|
|
206
246
|
},
|
|
207
247
|
)
|
|
248
|
+
|
|
249
|
+
this.queryInput$ = queryInputRaw$OrQueryInputRaw
|
|
208
250
|
} else {
|
|
209
251
|
let queryInputRaw: TQueryInputRaw
|
|
210
252
|
if (isQueryBuilder(queryInput)) {
|
|
@@ -248,10 +290,16 @@ export class LiveStoreDbQuery<
|
|
|
248
290
|
: undefined
|
|
249
291
|
|
|
250
292
|
const results$ = this.reactivityGraph.makeThunk<TResult>(
|
|
251
|
-
(get, setDebugInfo, queryContext, otelContext) =>
|
|
293
|
+
(get, setDebugInfo, queryContext, otelContext, debugRefreshReason) =>
|
|
252
294
|
queryContext.otelTracer.startActiveSpan(
|
|
253
295
|
'db:...', // NOTE span name will be overridden further down
|
|
254
|
-
{
|
|
296
|
+
{
|
|
297
|
+
attributes: {
|
|
298
|
+
'livestore.debugRefreshReason': Predicate.hasProperty(debugRefreshReason, 'label')
|
|
299
|
+
? (debugRefreshReason.label as string)
|
|
300
|
+
: debugRefreshReason?._tag,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
255
303
|
otelContext ?? queryContext.rootOtelContext,
|
|
256
304
|
(span) => {
|
|
257
305
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
@@ -263,34 +311,37 @@ export class LiveStoreDbQuery<
|
|
|
263
311
|
}
|
|
264
312
|
|
|
265
313
|
const queryInputResult = isThunk(queryInputRaw$OrQueryInputRaw)
|
|
266
|
-
? (get(queryInputRaw$OrQueryInputRaw, otelContext) as TQueryInputRaw)
|
|
314
|
+
? (get(queryInputRaw$OrQueryInputRaw, otelContext, debugRefreshReason) as TQueryInputRaw)
|
|
267
315
|
: (queryInputRaw$OrQueryInputRaw as TQueryInputRaw)
|
|
268
316
|
|
|
269
317
|
const sqlString = queryInputResult.query
|
|
270
318
|
const bindValues = queryInputResult.bindValues
|
|
271
319
|
|
|
272
320
|
if (queriedTablesRef.current === undefined) {
|
|
273
|
-
queriedTablesRef.current = store.
|
|
321
|
+
queriedTablesRef.current = store.sqliteDbWrapper.getTablesUsed(sqlString)
|
|
274
322
|
}
|
|
275
323
|
|
|
276
324
|
if (bindValues !== undefined) {
|
|
277
|
-
replaceSessionIdSymbol(bindValues, store.clientSession.
|
|
325
|
+
replaceSessionIdSymbol(bindValues, store.clientSession.sessionId)
|
|
278
326
|
}
|
|
279
327
|
|
|
280
328
|
// Establish a reactive dependency on the tables used in the query
|
|
281
329
|
for (const tableName of queriedTablesRef.current) {
|
|
282
330
|
const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
|
|
283
|
-
get(tableRef, otelContext)
|
|
331
|
+
get(tableRef, otelContext, debugRefreshReason)
|
|
284
332
|
}
|
|
285
333
|
|
|
286
334
|
span.setAttribute('sql.query', sqlString)
|
|
287
335
|
span.updateName(`db:${sqlString.slice(0, 50)}`)
|
|
288
336
|
|
|
289
|
-
const rawDbResults = store.
|
|
290
|
-
|
|
291
|
-
bindValues
|
|
292
|
-
|
|
293
|
-
|
|
337
|
+
const rawDbResults = store.sqliteDbWrapper.select<any>(
|
|
338
|
+
sqlString,
|
|
339
|
+
bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
|
|
340
|
+
{
|
|
341
|
+
queriedTables: queriedTablesRef.current,
|
|
342
|
+
otelContext,
|
|
343
|
+
},
|
|
344
|
+
)
|
|
294
345
|
|
|
295
346
|
span.setAttribute('sql.rowsCount', rawDbResults.length)
|
|
296
347
|
|
|
@@ -341,10 +392,21 @@ Result:`,
|
|
|
341
392
|
}
|
|
342
393
|
|
|
343
394
|
destroy = () => {
|
|
395
|
+
this.isDestroyed = true
|
|
396
|
+
|
|
344
397
|
if (this.queryInput$ !== undefined) {
|
|
345
398
|
this.reactivityGraph.destroyNode(this.queryInput$)
|
|
346
399
|
}
|
|
347
400
|
|
|
348
401
|
this.reactivityGraph.destroyNode(this.results$)
|
|
402
|
+
|
|
403
|
+
for (const query of this.dependencyQueriesRef) {
|
|
404
|
+
query.deref()
|
|
405
|
+
}
|
|
349
406
|
}
|
|
350
407
|
}
|
|
408
|
+
|
|
409
|
+
const queryInfoFromQueryBuilder = (qb: QueryBuilder.Any): QueryInfo.Row | QueryInfo.None => {
|
|
410
|
+
const ast = qb[QueryBuilderAstSymbol]
|
|
411
|
+
return ast._tag === 'RowQuery' ? { _tag: 'Row', table: ast.tableDef, id: ast.id } : { _tag: 'None' }
|
|
412
|
+
}
|