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