@livestore/livestore 0.3.0-dev.10 → 0.3.0-dev.12
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/SqliteDbWrapper.js +211 -0
- package/dist/SqliteDbWrapper.js.map +1 -0
- package/dist/SynchronousDatabaseWrapper.d.ts +14 -5
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
- package/dist/SynchronousDatabaseWrapper.js +24 -4
- package/dist/SynchronousDatabaseWrapper.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +12 -8
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +9 -2
- 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-query.d.ts +67 -0
- package/dist/live-queries/db-query.d.ts.map +1 -0
- package/dist/live-queries/db-query.js +243 -0
- 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/db.d.ts +12 -15
- package/dist/live-queries/db.d.ts.map +1 -1
- package/dist/live-queries/db.js +44 -25
- package/dist/live-queries/db.js.map +1 -1
- package/dist/live-queries/db.test.js +16 -14
- package/dist/live-queries/db.test.js.map +1 -1
- 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 +6 -6
- package/dist/row-query-utils.d.ts.map +1 -1
- package/dist/row-query-utils.js +15 -11
- package/dist/row-query-utils.js.map +1 -1
- package/dist/store/create-store.d.ts +7 -5
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +21 -7
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +5 -4
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +45 -23
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +9 -4
- 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 +36 -18
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +127 -75
- package/dist/store/store.js.map +1 -1
- package/dist/utils/expo.d.ts +2 -0
- package/dist/utils/expo.d.ts.map +1 -0
- package/dist/utils/expo.js +8 -0
- package/dist/utils/expo.js.map +1 -0
- 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 +3 -5
- 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/package.json +5 -5
- package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +41 -12
- package/src/effect/LiveStore.ts +22 -14
- package/src/index.ts +14 -7
- package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +196 -45
- 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} +44 -32
- package/src/live-queries/{db.ts → db-query.ts} +96 -39
- 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 +32 -21
- package/src/store/create-store.ts +55 -27
- package/src/store/devtools.ts +74 -29
- package/src/store/store-types.ts +6 -4
- package/src/store/store.ts +231 -121
- 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 +2 -7
- package/src/utils/tests/mod.ts +1 -0
- 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,14 @@ 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
|
+
|
|
23
27
|
// const provider = cachedProvider ?? new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] })
|
|
24
28
|
const provider = cachedProvider ?? new BasicTracerProvider()
|
|
25
29
|
cachedProvider = provider
|
|
@@ -31,7 +35,7 @@ describe('otel', () => {
|
|
|
31
35
|
const span = otelTracer.startSpan('test-root')
|
|
32
36
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
33
37
|
|
|
34
|
-
const
|
|
38
|
+
const store = yield* makeTodoMvc({ otelTracer, otelContext })
|
|
35
39
|
|
|
36
40
|
return {
|
|
37
41
|
store,
|
|
@@ -42,8 +46,8 @@ describe('otel', () => {
|
|
|
42
46
|
}
|
|
43
47
|
})
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
Vitest.scopedLive('otel', () =>
|
|
50
|
+
Effect.gen(function* () {
|
|
47
51
|
const { store, exporter, span } = yield* makeQuery
|
|
48
52
|
|
|
49
53
|
const query$ = queryDb({
|
|
@@ -51,11 +55,11 @@ describe('otel', () => {
|
|
|
51
55
|
schema: Schema.Array(tables.todos.schema),
|
|
52
56
|
queriedTables: new Set(['todos']),
|
|
53
57
|
})
|
|
54
|
-
expect(query
|
|
58
|
+
expect(store.query(query$)).toMatchInlineSnapshot('[]')
|
|
55
59
|
|
|
56
60
|
store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
57
61
|
|
|
58
|
-
expect(query
|
|
62
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
59
63
|
[
|
|
60
64
|
{
|
|
61
65
|
"completed": false,
|
|
@@ -65,17 +69,17 @@ describe('otel', () => {
|
|
|
65
69
|
]
|
|
66
70
|
`)
|
|
67
71
|
|
|
68
|
-
query$.destroy()
|
|
69
72
|
span.end()
|
|
70
73
|
|
|
71
74
|
return { exporter }
|
|
72
|
-
}).pipe(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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* () {
|
|
79
83
|
const { store, exporter, span } = yield* makeQuery
|
|
80
84
|
|
|
81
85
|
const defaultTodo = { id: '', text: '', completed: false }
|
|
@@ -89,7 +93,9 @@ describe('otel', () => {
|
|
|
89
93
|
{ label: 'all todos' },
|
|
90
94
|
)
|
|
91
95
|
|
|
92
|
-
expect(
|
|
96
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
97
|
+
|
|
98
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
93
99
|
{
|
|
94
100
|
"completed": false,
|
|
95
101
|
"id": "",
|
|
@@ -97,9 +103,13 @@ describe('otel', () => {
|
|
|
97
103
|
}
|
|
98
104
|
`)
|
|
99
105
|
|
|
106
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
107
|
+
|
|
100
108
|
store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
101
109
|
|
|
102
|
-
expect(
|
|
110
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
111
|
+
|
|
112
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
103
113
|
{
|
|
104
114
|
"completed": false,
|
|
105
115
|
"id": "t1",
|
|
@@ -107,17 +117,19 @@ describe('otel', () => {
|
|
|
107
117
|
}
|
|
108
118
|
`)
|
|
109
119
|
|
|
110
|
-
|
|
120
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
121
|
+
|
|
111
122
|
span.end()
|
|
112
123
|
|
|
113
124
|
return { exporter }
|
|
114
|
-
}).pipe(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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* () {
|
|
121
133
|
const { store, exporter, span } = yield* makeQuery
|
|
122
134
|
|
|
123
135
|
const defaultTodo = { id: '', text: '', completed: false }
|
|
@@ -125,7 +137,7 @@ describe('otel', () => {
|
|
|
125
137
|
const filter = computed(() => ({ completed: false }))
|
|
126
138
|
const query$ = queryDb((get) => tables.todos.query.where(get(filter)).first({ fallback: () => defaultTodo }))
|
|
127
139
|
|
|
128
|
-
expect(query
|
|
140
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
129
141
|
{
|
|
130
142
|
"completed": false,
|
|
131
143
|
"id": "",
|
|
@@ -135,7 +147,7 @@ describe('otel', () => {
|
|
|
135
147
|
|
|
136
148
|
store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
137
149
|
|
|
138
|
-
expect(query
|
|
150
|
+
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
139
151
|
{
|
|
140
152
|
"completed": false,
|
|
141
153
|
"id": "t1",
|
|
@@ -143,12 +155,12 @@ describe('otel', () => {
|
|
|
143
155
|
}
|
|
144
156
|
`)
|
|
145
157
|
|
|
146
|
-
query$.destroy()
|
|
147
158
|
span.end()
|
|
148
159
|
|
|
149
160
|
return { exporter }
|
|
150
|
-
}).pipe(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
161
|
+
}).pipe(
|
|
162
|
+
Effect.scoped,
|
|
163
|
+
Effect.tap(({ exporter }) => expect(getSimplifiedRootSpan(exporter)).toMatchSnapshot()),
|
|
164
|
+
),
|
|
165
|
+
)
|
|
154
166
|
})
|
|
@@ -11,14 +11,14 @@ import { deepEqual, shouldNeverHappen } from '@livestore/utils'
|
|
|
11
11
|
import { Predicate, Schema, TreeFormatter } from '@livestore/utils/effect'
|
|
12
12
|
import * as otel from '@opentelemetry/api'
|
|
13
13
|
|
|
14
|
-
import { globalReactivityGraph } from '../global-state.js'
|
|
15
14
|
import type { Thunk } from '../reactive.js'
|
|
16
15
|
import { isThunk, NOT_REFRESHED_YET } from '../reactive.js'
|
|
17
16
|
import { makeExecBeforeFirstRun, rowQueryLabel } from '../row-query-utils.js'
|
|
18
17
|
import type { RefreshReason } from '../store/store-types.js'
|
|
18
|
+
import { isValidFunctionString } from '../utils/function-string.js'
|
|
19
19
|
import { getDurationMsFromSpan } from '../utils/otel.js'
|
|
20
|
-
import type {
|
|
21
|
-
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'
|
|
22
22
|
|
|
23
23
|
export type QueryInputRaw<TDecoded, TEncoded, TQueryInfo extends QueryInfo> = {
|
|
24
24
|
query: string
|
|
@@ -31,9 +31,12 @@ export type QueryInputRaw<TDecoded, TEncoded, TQueryInfo extends QueryInfo> = {
|
|
|
31
31
|
*/
|
|
32
32
|
queriedTables?: Set<string>
|
|
33
33
|
queryInfo?: TQueryInfo
|
|
34
|
-
execBeforeFirstRun?: (ctx:
|
|
34
|
+
execBeforeFirstRun?: (ctx: ReactivityGraphContext) => void
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export const isQueryInputRaw = (value: unknown): value is QueryInputRaw<any, any, any> =>
|
|
38
|
+
Predicate.hasProperty(value, 'query') && Predicate.hasProperty(value, 'schema')
|
|
39
|
+
|
|
37
40
|
export type QueryInput<TDecoded, TEncoded, TQueryInfo extends QueryInfo> =
|
|
38
41
|
| QueryInputRaw<TDecoded, TEncoded, TQueryInfo>
|
|
39
42
|
| QueryBuilder<TDecoded, any, any, TQueryInfo>
|
|
@@ -52,10 +55,10 @@ export const queryDb: {
|
|
|
52
55
|
* Used for debugging / devtools
|
|
53
56
|
*/
|
|
54
57
|
label?: string
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
deps?: DepKey
|
|
59
|
+
queryInfo?: TQueryInfo
|
|
57
60
|
},
|
|
58
|
-
):
|
|
61
|
+
): LiveQueryDef<TResult, TQueryInfo>
|
|
59
62
|
// NOTE in this "thunk case", we can't directly derive label/queryInfo from the queryInput,
|
|
60
63
|
// so the caller needs to provide them explicitly otherwise queryInfo will be set to `None`,
|
|
61
64
|
// and label will be set during the query execution
|
|
@@ -69,20 +72,46 @@ export const queryDb: {
|
|
|
69
72
|
* Used for debugging / devtools
|
|
70
73
|
*/
|
|
71
74
|
label?: string
|
|
72
|
-
|
|
75
|
+
deps?: DepKey
|
|
73
76
|
queryInfo?: TQueryInfo
|
|
74
|
-
otelContext?: otel.Context
|
|
75
77
|
},
|
|
76
|
-
):
|
|
77
|
-
} = (queryInput, options) =>
|
|
78
|
-
|
|
79
|
-
queryInput
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
}
|
|
86
115
|
|
|
87
116
|
/* An object encapsulating a reactive SQL query */
|
|
88
117
|
export class LiveStoreDbQuery<
|
|
@@ -93,16 +122,16 @@ export class LiveStoreDbQuery<
|
|
|
93
122
|
_tag: 'db' = 'db'
|
|
94
123
|
|
|
95
124
|
/** A reactive thunk representing the query text */
|
|
96
|
-
queryInput$: Thunk<
|
|
125
|
+
queryInput$: Thunk<QueryInputRaw<any, any, QueryInfo>, ReactivityGraphContext, RefreshReason> | undefined
|
|
97
126
|
|
|
98
127
|
/** A reactive thunk representing the query results */
|
|
99
|
-
results$: Thunk<TResult,
|
|
128
|
+
results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
|
|
100
129
|
|
|
101
130
|
label: string
|
|
102
131
|
|
|
103
132
|
queryInfo: TQueryInfo
|
|
104
133
|
|
|
105
|
-
|
|
134
|
+
readonly reactivityGraph
|
|
106
135
|
|
|
107
136
|
private mapResult: (rows: TResultSchema) => TResult
|
|
108
137
|
|
|
@@ -117,17 +146,18 @@ export class LiveStoreDbQuery<
|
|
|
117
146
|
label?: string
|
|
118
147
|
queryInput:
|
|
119
148
|
| QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>
|
|
120
|
-
| ((get: GetAtomResult, ctx:
|
|
121
|
-
reactivityGraph
|
|
149
|
+
| ((get: GetAtomResult, ctx: ReactivityGraphContext) => QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
|
|
150
|
+
reactivityGraph: ReactivityGraph
|
|
122
151
|
map?: (rows: TResultSchema) => TResult
|
|
123
152
|
queryInfo?: TQueryInfo
|
|
153
|
+
/** Only used for the initial query execution */
|
|
124
154
|
otelContext?: otel.Context
|
|
125
155
|
}) {
|
|
126
156
|
super()
|
|
127
157
|
|
|
128
158
|
let label = inputLabel ?? 'db(unknown)'
|
|
129
159
|
let queryInfo = inputQueryInfo ?? ({ _tag: 'None' } as TQueryInfo)
|
|
130
|
-
this.reactivityGraph = reactivityGraph
|
|
160
|
+
this.reactivityGraph = reactivityGraph
|
|
131
161
|
|
|
132
162
|
this.mapResult = map === undefined ? (rows: any) => rows as TResult : map
|
|
133
163
|
|
|
@@ -136,13 +166,15 @@ export class LiveStoreDbQuery<
|
|
|
136
166
|
typeof queryInput === 'function' ? undefined : isQueryBuilder(queryInput) ? undefined : queryInput.schema,
|
|
137
167
|
}
|
|
138
168
|
|
|
139
|
-
const execBeforeFirstRunRef: {
|
|
169
|
+
const execBeforeFirstRunRef: {
|
|
170
|
+
current: ((ctx: ReactivityGraphContext, otelContext: otel.Context) => void) | undefined
|
|
171
|
+
} = {
|
|
140
172
|
current: undefined,
|
|
141
173
|
}
|
|
142
174
|
|
|
143
175
|
type TQueryInputRaw = QueryInputRaw<any, any, QueryInfo>
|
|
144
176
|
|
|
145
|
-
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw,
|
|
177
|
+
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, ReactivityGraphContext, RefreshReason>
|
|
146
178
|
|
|
147
179
|
const fromQueryBuilder = (qb: QueryBuilder.Any, otelContext: otel.Context | undefined) => {
|
|
148
180
|
try {
|
|
@@ -156,7 +188,7 @@ export class LiveStoreDbQuery<
|
|
|
156
188
|
schema,
|
|
157
189
|
bindValues: qbRes.bindValues,
|
|
158
190
|
queriedTables: new Set([ast.tableDef.sqliteDef.name]),
|
|
159
|
-
queryInfo:
|
|
191
|
+
queryInfo: queryInfoFromQueryBuilder(qb),
|
|
160
192
|
} satisfies TQueryInputRaw,
|
|
161
193
|
label: ast._tag === 'RowQuery' ? rowQueryLabel(ast.tableDef, ast.id) : qb.toString(),
|
|
162
194
|
execBeforeFirstRun:
|
|
@@ -178,7 +210,10 @@ export class LiveStoreDbQuery<
|
|
|
178
210
|
queryInputRaw$OrQueryInputRaw = this.reactivityGraph.makeThunk(
|
|
179
211
|
(get, setDebugInfo, ctx, otelContext) => {
|
|
180
212
|
const startMs = performance.now()
|
|
181
|
-
const queryInputResult = queryInput(
|
|
213
|
+
const queryInputResult = queryInput(
|
|
214
|
+
makeGetAtomResult(get, ctx, otelContext ?? ctx.rootOtelContext, this.dependencyQueriesRef),
|
|
215
|
+
ctx,
|
|
216
|
+
)
|
|
182
217
|
const durationMs = performance.now() - startMs
|
|
183
218
|
|
|
184
219
|
let queryInputRaw: TQueryInputRaw
|
|
@@ -210,6 +245,8 @@ export class LiveStoreDbQuery<
|
|
|
210
245
|
equal: (a, b) => a.query === b.query && deepEqual(a.bindValues, b.bindValues),
|
|
211
246
|
},
|
|
212
247
|
)
|
|
248
|
+
|
|
249
|
+
this.queryInput$ = queryInputRaw$OrQueryInputRaw
|
|
213
250
|
} else {
|
|
214
251
|
let queryInputRaw: TQueryInputRaw
|
|
215
252
|
if (isQueryBuilder(queryInput)) {
|
|
@@ -253,10 +290,16 @@ export class LiveStoreDbQuery<
|
|
|
253
290
|
: undefined
|
|
254
291
|
|
|
255
292
|
const results$ = this.reactivityGraph.makeThunk<TResult>(
|
|
256
|
-
(get, setDebugInfo, queryContext, otelContext) =>
|
|
293
|
+
(get, setDebugInfo, queryContext, otelContext, debugRefreshReason) =>
|
|
257
294
|
queryContext.otelTracer.startActiveSpan(
|
|
258
295
|
'db:...', // NOTE span name will be overridden further down
|
|
259
|
-
{
|
|
296
|
+
{
|
|
297
|
+
attributes: {
|
|
298
|
+
'livestore.debugRefreshReason': Predicate.hasProperty(debugRefreshReason, 'label')
|
|
299
|
+
? (debugRefreshReason.label as string)
|
|
300
|
+
: debugRefreshReason?._tag,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
260
303
|
otelContext ?? queryContext.rootOtelContext,
|
|
261
304
|
(span) => {
|
|
262
305
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
@@ -268,14 +311,14 @@ export class LiveStoreDbQuery<
|
|
|
268
311
|
}
|
|
269
312
|
|
|
270
313
|
const queryInputResult = isThunk(queryInputRaw$OrQueryInputRaw)
|
|
271
|
-
? (get(queryInputRaw$OrQueryInputRaw, otelContext) as TQueryInputRaw)
|
|
314
|
+
? (get(queryInputRaw$OrQueryInputRaw, otelContext, debugRefreshReason) as TQueryInputRaw)
|
|
272
315
|
: (queryInputRaw$OrQueryInputRaw as TQueryInputRaw)
|
|
273
316
|
|
|
274
317
|
const sqlString = queryInputResult.query
|
|
275
318
|
const bindValues = queryInputResult.bindValues
|
|
276
319
|
|
|
277
320
|
if (queriedTablesRef.current === undefined) {
|
|
278
|
-
queriedTablesRef.current = store.
|
|
321
|
+
queriedTablesRef.current = store.sqliteDbWrapper.getTablesUsed(sqlString)
|
|
279
322
|
}
|
|
280
323
|
|
|
281
324
|
if (bindValues !== undefined) {
|
|
@@ -285,17 +328,20 @@ export class LiveStoreDbQuery<
|
|
|
285
328
|
// Establish a reactive dependency on the tables used in the query
|
|
286
329
|
for (const tableName of queriedTablesRef.current) {
|
|
287
330
|
const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
|
|
288
|
-
get(tableRef, otelContext)
|
|
331
|
+
get(tableRef, otelContext, debugRefreshReason)
|
|
289
332
|
}
|
|
290
333
|
|
|
291
334
|
span.setAttribute('sql.query', sqlString)
|
|
292
335
|
span.updateName(`db:${sqlString.slice(0, 50)}`)
|
|
293
336
|
|
|
294
|
-
const rawDbResults = store.
|
|
295
|
-
|
|
296
|
-
bindValues
|
|
297
|
-
|
|
298
|
-
|
|
337
|
+
const rawDbResults = store.sqliteDbWrapper.select<any>(
|
|
338
|
+
sqlString,
|
|
339
|
+
bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
|
|
340
|
+
{
|
|
341
|
+
queriedTables: queriedTablesRef.current,
|
|
342
|
+
otelContext,
|
|
343
|
+
},
|
|
344
|
+
)
|
|
299
345
|
|
|
300
346
|
span.setAttribute('sql.rowsCount', rawDbResults.length)
|
|
301
347
|
|
|
@@ -346,10 +392,21 @@ Result:`,
|
|
|
346
392
|
}
|
|
347
393
|
|
|
348
394
|
destroy = () => {
|
|
395
|
+
this.isDestroyed = true
|
|
396
|
+
|
|
349
397
|
if (this.queryInput$ !== undefined) {
|
|
350
398
|
this.reactivityGraph.destroyNode(this.queryInput$)
|
|
351
399
|
}
|
|
352
400
|
|
|
353
401
|
this.reactivityGraph.destroyNode(this.results$)
|
|
402
|
+
|
|
403
|
+
for (const query of this.dependencyQueriesRef) {
|
|
404
|
+
query.deref()
|
|
405
|
+
}
|
|
354
406
|
}
|
|
355
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
|
+
}
|