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