@livestore/livestore 0.0.19 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -21
- package/dist/.tsbuildinfo +1 -1
- package/dist/QueryCache.d.ts +1 -1
- package/dist/QueryCache.d.ts.map +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/__tests__/react/fixture.d.ts +5 -4
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +5 -5
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/__tests__/react/useComponentState.test.d.ts +2 -0
- package/dist/__tests__/react/useComponentState.test.d.ts.map +1 -0
- package/dist/__tests__/react/useComponentState.test.js +68 -0
- package/dist/__tests__/react/useComponentState.test.js.map +1 -0
- package/dist/__tests__/react/useLQuery.test.d.ts +2 -0
- package/dist/__tests__/react/useLQuery.test.d.ts.map +1 -0
- package/dist/__tests__/react/useLQuery.test.js +38 -0
- package/dist/__tests__/react/useLQuery.test.js.map +1 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js +4 -9
- package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -1
- package/dist/__tests__/react/useQuery.test.d.ts +2 -0
- package/dist/__tests__/react/useQuery.test.d.ts.map +1 -0
- package/dist/__tests__/react/useQuery.test.js +33 -0
- package/dist/__tests__/react/useQuery.test.js.map +1 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +2 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +1 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +38 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +1 -0
- package/dist/__tests__/reactive.test.js +167 -93
- package/dist/__tests__/reactive.test.js.map +1 -1
- package/dist/__tests__/reactiveQueries/sql.test.d.ts +2 -0
- package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +1 -0
- package/dist/__tests__/reactiveQueries/sql.test.js +337 -0
- package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -0
- package/dist/inMemoryDatabase.d.ts +2 -2
- package/dist/inMemoryDatabase.d.ts.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react/useComponentState.d.ts +50 -0
- package/dist/react/useComponentState.d.ts.map +1 -0
- package/dist/react/useComponentState.js +248 -0
- package/dist/react/useComponentState.js.map +1 -0
- package/dist/react/useGlobalQuery.d.ts +3 -0
- package/dist/react/useGlobalQuery.d.ts.map +1 -0
- package/dist/react/useGlobalQuery.js +26 -0
- package/dist/react/useGlobalQuery.js.map +1 -0
- package/dist/react/useGraphQL.d.ts +3 -3
- package/dist/react/useGraphQL.d.ts.map +1 -1
- package/dist/react/useGraphQL.js +10 -8
- package/dist/react/useGraphQL.js.map +1 -1
- package/dist/react/useLiveStoreComponent.d.ts +6 -6
- package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
- package/dist/react/useLiveStoreComponent.js +143 -99
- package/dist/react/useLiveStoreComponent.js.map +1 -1
- package/dist/react/useQuery.d.ts +2 -2
- package/dist/react/useQuery.d.ts.map +1 -1
- package/dist/react/useQuery.js +26 -22
- package/dist/react/useQuery.js.map +1 -1
- package/dist/react/useTemporaryQuery.d.ts +8 -0
- package/dist/react/useTemporaryQuery.d.ts.map +1 -0
- package/dist/react/useTemporaryQuery.js +17 -0
- package/dist/react/useTemporaryQuery.js.map +1 -0
- package/dist/react/utils/extractNamesFromStackTrace.d.ts +3 -0
- package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +1 -0
- package/dist/react/utils/extractNamesFromStackTrace.js +40 -0
- package/dist/react/utils/extractNamesFromStackTrace.js.map +1 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +7 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +1 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.js +40 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.js.map +1 -0
- package/dist/reactive.d.ts +42 -48
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +293 -186
- package/dist/reactive.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +28 -23
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +25 -18
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graph.d.ts +10 -0
- package/dist/reactiveQueries/graph.d.ts.map +1 -0
- package/dist/reactiveQueries/graph.js +6 -0
- package/dist/reactiveQueries/graph.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +34 -17
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +91 -10
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +16 -12
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js +31 -8
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +22 -18
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +82 -16
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/store.d.ts +12 -52
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +283 -264
- package/dist/store.js.map +1 -1
- package/package.json +4 -3
- package/src/QueryCache.ts +1 -1
- package/src/__tests__/react/fixture.tsx +12 -7
- package/src/__tests__/react/{useLiveStoreComponent.test.tsx → useComponentState.test.tsx} +9 -20
- package/src/__tests__/react/useQuery.test.tsx +48 -0
- package/src/__tests__/react/utils/extractStackInfoFromStackTrace.test.ts +40 -0
- package/src/__tests__/reactive.test.ts +193 -140
- package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
- package/src/inMemoryDatabase.ts +2 -2
- package/src/index.ts +7 -11
- package/src/react/index.ts +3 -7
- package/src/react/{useLiveStoreComponent.ts → useComponentState.ts} +89 -247
- package/src/react/useQuery.ts +29 -27
- package/src/react/useTemporaryQuery.ts +21 -0
- package/src/react/utils/extractStackInfoFromStackTrace.ts +47 -0
- package/src/reactive.ts +385 -268
- package/src/reactiveQueries/base-class.ts +60 -44
- package/src/reactiveQueries/graph.ts +15 -0
- package/src/reactiveQueries/graphql.ts +145 -29
- package/src/reactiveQueries/js.ts +53 -20
- package/src/reactiveQueries/sql.ts +129 -36
- package/src/store.ts +338 -408
- package/src/react/useGraphQL.ts +0 -138
package/dist/store.js
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { assertNever, makeNoopSpan, makeNoopTracer, shouldNeverHappen } from '@livestore/utils';
|
|
2
2
|
import { identity } from '@livestore/utils/effect';
|
|
3
3
|
import * as otel from '@opentelemetry/api';
|
|
4
|
-
import * as graphql from 'graphql';
|
|
5
|
-
import { uniqueId } from 'lodash-es';
|
|
6
|
-
import * as ReactDOM from 'react-dom';
|
|
7
4
|
import { v4 as uuid } from 'uuid';
|
|
8
5
|
import { tableNameForComponentKey } from './componentKey.js';
|
|
9
6
|
import { InMemoryDatabase } from './inMemoryDatabase.js';
|
|
10
7
|
import { migrateDb } from './migrations.js';
|
|
11
8
|
import { getDurationMsFromSpan } from './otel.js';
|
|
12
|
-
import {
|
|
13
|
-
import { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js';
|
|
14
|
-
import { LiveStoreJSQuery } from './reactiveQueries/js.js';
|
|
15
|
-
import { LiveStoreSQLQuery } from './reactiveQueries/sql.js';
|
|
9
|
+
import { dbGraph } from './reactiveQueries/graph.js';
|
|
16
10
|
import { componentStateTables } from './schema.js';
|
|
17
11
|
import { isPromise, prepareBindValues, sql } from './util.js';
|
|
18
|
-
const globalComponentKey = { _tag: 'singleton', componentName: '__global', id: 'singleton' };
|
|
12
|
+
export const globalComponentKey = { _tag: 'singleton', componentName: '__global', id: 'singleton' };
|
|
19
13
|
export class Store {
|
|
20
14
|
constructor({ db, dbProxy, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext, }) {
|
|
21
15
|
/**
|
|
@@ -23,254 +17,274 @@ export class Store {
|
|
|
23
17
|
*
|
|
24
18
|
* NOTE The query is actually running (even if no one has subscribed to it yet) and will be kept up to date.
|
|
25
19
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
20
|
+
// querySQL = <TResult>(
|
|
21
|
+
// genQueryString: string | ((get: GetAtomResult) => string),
|
|
22
|
+
// {
|
|
23
|
+
// queriedTables,
|
|
24
|
+
// bindValues,
|
|
25
|
+
// componentKey,
|
|
26
|
+
// label,
|
|
27
|
+
// otelContext = otel.context.active(),
|
|
28
|
+
// }: {
|
|
29
|
+
// /**
|
|
30
|
+
// * List of tables that are queried in this query;
|
|
31
|
+
// * used to determine reactive dependencies.
|
|
32
|
+
// *
|
|
33
|
+
// * NOTE In the future we want to auto-generate this via parsing the query
|
|
34
|
+
// */
|
|
35
|
+
// queriedTables: string[]
|
|
36
|
+
// bindValues?: Bindable | undefined
|
|
37
|
+
// componentKey?: ComponentKey | undefined
|
|
38
|
+
// label?: string | undefined
|
|
39
|
+
// otelContext?: otel.Context
|
|
40
|
+
// },
|
|
41
|
+
// ): LiveStoreSQLQuery<TResult> =>
|
|
42
|
+
// this.otel.tracer.startActiveSpan(
|
|
43
|
+
// 'querySQL', // NOTE span name will be overridden further down
|
|
44
|
+
// { attributes: { label } },
|
|
45
|
+
// otelContext,
|
|
46
|
+
// (span) => {
|
|
47
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
48
|
+
// const queryString$ = this.graph.makeThunk(
|
|
49
|
+
// (get, addDebugInfo) => {
|
|
50
|
+
// if (typeof genQueryString === 'function') {
|
|
51
|
+
// const queryString = genQueryString(makeGetAtomResult(get))
|
|
52
|
+
// addDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString })
|
|
53
|
+
// return queryString
|
|
54
|
+
// } else {
|
|
55
|
+
// return genQueryString
|
|
56
|
+
// }
|
|
57
|
+
// },
|
|
58
|
+
// { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
|
|
59
|
+
// otelContext,
|
|
60
|
+
// )
|
|
61
|
+
// label = label ?? queryString$.result
|
|
62
|
+
// span.updateName(`querySQL:${label}`)
|
|
63
|
+
// const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
64
|
+
// const results$ = this.graph.makeThunk<ReadonlyArray<TResult>>(
|
|
65
|
+
// (get, addDebugInfo) =>
|
|
66
|
+
// this.otel.tracer.startActiveSpan(
|
|
67
|
+
// 'sql:', // NOTE span name will be overridden further down
|
|
68
|
+
// {},
|
|
69
|
+
// otelContext,
|
|
70
|
+
// (span) => {
|
|
71
|
+
// try {
|
|
72
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
73
|
+
// // Establish a reactive dependency on the tables used in the query
|
|
74
|
+
// for (const tableName of queriedTables) {
|
|
75
|
+
// const tableRef =
|
|
76
|
+
// this.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
|
|
77
|
+
// get(tableRef)
|
|
78
|
+
// }
|
|
79
|
+
// const sqlString = get(queryString$)
|
|
80
|
+
// span.setAttribute('sql.query', sqlString)
|
|
81
|
+
// span.updateName(`sql:${sqlString.slice(0, 50)}`)
|
|
82
|
+
// const results = this.inMemoryDB.select<TResult>(sqlString, {
|
|
83
|
+
// queriedTables,
|
|
84
|
+
// bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
|
|
85
|
+
// otelContext,
|
|
86
|
+
// })
|
|
87
|
+
// span.setAttribute('sql.rowsCount', results.length)
|
|
88
|
+
// addDebugInfo({ _tag: 'sql', label: label ?? '', query: sqlString })
|
|
89
|
+
// return results
|
|
90
|
+
// } finally {
|
|
91
|
+
// span.end()
|
|
92
|
+
// }
|
|
93
|
+
// },
|
|
94
|
+
// ),
|
|
95
|
+
// { label: queryLabel },
|
|
96
|
+
// otelContext,
|
|
97
|
+
// )
|
|
98
|
+
// const query = new LiveStoreSQLQuery<TResult>({
|
|
99
|
+
// label,
|
|
100
|
+
// queryString$,
|
|
101
|
+
// results$,
|
|
102
|
+
// componentKey: componentKey ?? globalComponentKey,
|
|
103
|
+
// store: this,
|
|
104
|
+
// otelContext,
|
|
105
|
+
// })
|
|
106
|
+
// this.activeQueries.add(query)
|
|
107
|
+
// // TODO get rid of temporary query workaround
|
|
108
|
+
// if (this.temporaryQueries !== undefined) {
|
|
109
|
+
// this.temporaryQueries.add(query)
|
|
110
|
+
// }
|
|
111
|
+
// // NOTE we are not ending the span here but in the query `destroy` method
|
|
112
|
+
// return query
|
|
113
|
+
// },
|
|
114
|
+
// )
|
|
115
|
+
// queryJS = <TResult>(
|
|
116
|
+
// genResults: (get: GetAtomResult) => TResult,
|
|
117
|
+
// {
|
|
118
|
+
// componentKey = globalComponentKey,
|
|
119
|
+
// label = `js${uniqueId()}`,
|
|
120
|
+
// otelContext = otel.context.active(),
|
|
121
|
+
// }: { componentKey?: ComponentKey; label?: string; otelContext?: otel.Context },
|
|
122
|
+
// ): LiveStoreJSQuery<TResult> =>
|
|
123
|
+
// this.otel.tracer.startActiveSpan(`queryJS:${label}`, { attributes: { label } }, otelContext, (span) => {
|
|
124
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
125
|
+
// const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
126
|
+
// const results$ = this.graph.makeThunk(
|
|
127
|
+
// (get, addDebugInfo) => {
|
|
128
|
+
// addDebugInfo({ _tag: 'js', label, query: genResults.toString() })
|
|
129
|
+
// return genResults(makeGetAtomResult(get))
|
|
130
|
+
// },
|
|
131
|
+
// { label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } },
|
|
132
|
+
// otelContext,
|
|
133
|
+
// )
|
|
134
|
+
// // const query = new LiveStoreJSQuery<TResult>({
|
|
135
|
+
// // label,
|
|
136
|
+
// // results$,
|
|
137
|
+
// // componentKey,
|
|
138
|
+
// // store: this,
|
|
139
|
+
// // otelContext,
|
|
140
|
+
// // })
|
|
141
|
+
// this.activeQueries.add(query)
|
|
142
|
+
// // TODO get rid of temporary query workaround
|
|
143
|
+
// if (this.temporaryQueries !== undefined) {
|
|
144
|
+
// this.temporaryQueries.add(query)
|
|
145
|
+
// }
|
|
146
|
+
// // NOTE we are not ending the span here but in the query `destroy` method
|
|
147
|
+
// return query
|
|
148
|
+
// })
|
|
149
|
+
// queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
|
|
150
|
+
// document: DocumentNode<TResult, TVariableValues>,
|
|
151
|
+
// genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
|
|
152
|
+
// {
|
|
153
|
+
// componentKey,
|
|
154
|
+
// label,
|
|
155
|
+
// otelContext = otel.context.active(),
|
|
156
|
+
// }: {
|
|
157
|
+
// componentKey: ComponentKey
|
|
158
|
+
// label?: string
|
|
159
|
+
// otelContext?: otel.Context
|
|
160
|
+
// },
|
|
161
|
+
// ): LiveStoreGraphQLQuery<TResult, TVariableValues, TGraphQLContext> =>
|
|
162
|
+
// this.otel.tracer.startActiveSpan(
|
|
163
|
+
// `queryGraphQL:`, // NOTE span name will be overridden further down
|
|
164
|
+
// {},
|
|
165
|
+
// otelContext,
|
|
166
|
+
// (span) => {
|
|
167
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
168
|
+
// if (this.graphQLContext === undefined) {
|
|
169
|
+
// return shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
|
|
170
|
+
// }
|
|
171
|
+
// const labelWithDefault = label ?? graphql.getOperationAST(document)?.name?.value ?? 'graphql'
|
|
172
|
+
// span.updateName(`queryGraphQL:${labelWithDefault}`)
|
|
173
|
+
// const variableValues$ = this.graph.makeThunk(
|
|
174
|
+
// (get) => {
|
|
175
|
+
// if (typeof genVariableValues === 'function') {
|
|
176
|
+
// return genVariableValues(makeGetAtomResult(get))
|
|
177
|
+
// } else {
|
|
178
|
+
// return genVariableValues
|
|
179
|
+
// }
|
|
180
|
+
// },
|
|
181
|
+
// { label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } },
|
|
182
|
+
// // otelContext,
|
|
183
|
+
// )
|
|
184
|
+
// const resultsLabel = `${labelWithDefault}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
185
|
+
// const results$ = this.graph.makeThunk<TResult>(
|
|
186
|
+
// (get, addDebugInfo) => {
|
|
187
|
+
// const variableValues = get(variableValues$)
|
|
188
|
+
// const { result, queriedTables } = this.queryGraphQLOnce(document, variableValues, otelContext)
|
|
189
|
+
// // Add dependencies on any tables that were used
|
|
190
|
+
// for (const tableName of queriedTables) {
|
|
191
|
+
// const tableRef = this.tableRefs[tableName]
|
|
192
|
+
// assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
193
|
+
// get(tableRef!)
|
|
194
|
+
// }
|
|
195
|
+
// addDebugInfo({ _tag: 'graphql', label: resultsLabel, query: graphql.print(document) })
|
|
196
|
+
// return result
|
|
197
|
+
// },
|
|
198
|
+
// { label: resultsLabel, meta: { liveStoreThunkType: 'graphqlResults' } },
|
|
199
|
+
// // otelContext,
|
|
200
|
+
// )
|
|
201
|
+
// const query = new LiveStoreGraphQLQuery({
|
|
202
|
+
// document,
|
|
203
|
+
// context: this.graphQLContext,
|
|
204
|
+
// results$,
|
|
205
|
+
// componentKey,
|
|
206
|
+
// label: labelWithDefault,
|
|
207
|
+
// store: this,
|
|
208
|
+
// otelContext,
|
|
209
|
+
// })
|
|
210
|
+
// this.activeQueries.add(query)
|
|
211
|
+
// // TODO get rid of temporary query workaround
|
|
212
|
+
// if (this.temporaryQueries !== undefined) {
|
|
213
|
+
// this.temporaryQueries.add(query)
|
|
214
|
+
// }
|
|
215
|
+
// // NOTE we are not ending the span here but in the query `destroy` method
|
|
216
|
+
// return query
|
|
217
|
+
// },
|
|
218
|
+
// )
|
|
219
|
+
// queryGraphQLOnce = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
|
|
220
|
+
// document: DocumentNode<TResult, TVariableValues>,
|
|
221
|
+
// variableValues: TVariableValues,
|
|
222
|
+
// otelContext: otel.Context = this.otel.queriesSpanContext,
|
|
223
|
+
// ): { result: TResult; queriedTables: string[] } => {
|
|
224
|
+
// const schema =
|
|
225
|
+
// this.graphQLSchema ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL schema")
|
|
226
|
+
// const context =
|
|
227
|
+
// this.graphQLContext ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
|
|
228
|
+
// const tracer = this.otel.tracer
|
|
229
|
+
// const operationName = graphql.getOperationAST(document)?.name?.value
|
|
230
|
+
// return tracer.startActiveSpan(`executeGraphQLQuery: ${operationName}`, {}, otelContext, (span) => {
|
|
231
|
+
// try {
|
|
232
|
+
// span.setAttribute('graphql.variables', JSON.stringify(variableValues))
|
|
233
|
+
// span.setAttribute('graphql.query', graphql.print(document))
|
|
234
|
+
// context.queriedTables.clear()
|
|
235
|
+
// context.otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
236
|
+
// const res = graphql.executeSync({
|
|
237
|
+
// document,
|
|
238
|
+
// contextValue: context,
|
|
239
|
+
// schema: schema,
|
|
240
|
+
// variableValues,
|
|
241
|
+
// })
|
|
242
|
+
// // TODO track number of nested SQL queries via Otel + debug info
|
|
243
|
+
// if (res.errors) {
|
|
244
|
+
// span.setStatus({ code: otel.SpanStatusCode.ERROR, message: 'GraphQL error' })
|
|
245
|
+
// span.setAttribute('graphql.error', res.errors.join('\n'))
|
|
246
|
+
// span.setAttribute('graphql.error-detail', JSON.stringify(res.errors))
|
|
247
|
+
// console.error(`graphql error (${operationName})`, res.errors)
|
|
248
|
+
// }
|
|
249
|
+
// return { result: res.data as unknown as TResult, queriedTables: Array.from(context.queriedTables.values()) }
|
|
250
|
+
// } finally {
|
|
251
|
+
// span.end()
|
|
252
|
+
// }
|
|
253
|
+
// })
|
|
254
|
+
// }
|
|
197
255
|
/**
|
|
198
256
|
* Subscribe to the results of a query
|
|
199
257
|
* Returns a function to cancel the subscription.
|
|
200
258
|
*/
|
|
201
|
-
this.subscribe = (query, onNewValue, onSubsubscribe, options) => this.otel.tracer.startActiveSpan(`LiveStore.subscribe`, { attributes: { label: options?.label } },
|
|
259
|
+
this.subscribe = (query, onNewValue, onSubsubscribe, options) => this.otel.tracer.startActiveSpan(`LiveStore.subscribe`, { attributes: { label: options?.label } }, options?.otelContext ?? this.otel.queriesSpanContext, (span) => {
|
|
202
260
|
const otelContext = otel.trace.setSpan(otel.context.active(), span);
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}, { label: `subscribe:${options?.label}` }, otelContext);
|
|
207
|
-
const subscriptionKey = uuid();
|
|
261
|
+
const label = `subscribe:${options?.label}`;
|
|
262
|
+
const effect = this.graph.makeEffect((get) => onNewValue(get(query.results$)), { label });
|
|
263
|
+
effect.doEffect(otelContext);
|
|
208
264
|
const unsubscribe = () => {
|
|
209
265
|
try {
|
|
210
266
|
this.graph.destroy(effect);
|
|
211
|
-
|
|
267
|
+
this.activeQueries.delete(query);
|
|
212
268
|
onSubsubscribe?.();
|
|
213
269
|
}
|
|
214
270
|
finally {
|
|
215
271
|
span.end();
|
|
216
272
|
}
|
|
217
273
|
};
|
|
218
|
-
|
|
274
|
+
this.activeQueries.add(query);
|
|
219
275
|
return unsubscribe;
|
|
220
276
|
});
|
|
221
|
-
/**
|
|
222
|
-
* Any queries created in the callback will be destroyed when the callback is complete.
|
|
223
|
-
* Useful for temporarily creating reactive queries, which is an idempotent operation
|
|
224
|
-
* that can be safely called inside a React useMemo hook.
|
|
225
|
-
*/
|
|
226
|
-
this.inTempQueryContext = (callback) => {
|
|
227
|
-
this.temporaryQueries = new Set();
|
|
228
|
-
// TODO: consider errors / try/finally here?
|
|
229
|
-
const result = callback();
|
|
230
|
-
for (const query of this.temporaryQueries) {
|
|
231
|
-
this.destroyQuery(query);
|
|
232
|
-
}
|
|
233
|
-
this.temporaryQueries = undefined;
|
|
234
|
-
return result;
|
|
235
|
-
};
|
|
236
277
|
/**
|
|
237
278
|
* Destroys the entire store, including all queries and subscriptions.
|
|
238
279
|
*
|
|
239
280
|
* Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
|
|
240
281
|
*/
|
|
241
282
|
this.destroy = () => {
|
|
242
|
-
for (const query of this.activeQueries) {
|
|
243
|
-
this.destroyQuery(query);
|
|
244
|
-
}
|
|
245
283
|
Object.values(this.tableRefs).forEach((tableRef) => this.graph.destroy(tableRef));
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const queriesSpan = otel.trace.getSpan(this.otel.queriesSpanContext);
|
|
249
|
-
queriesSpan.end();
|
|
284
|
+
otel.trace.getSpan(this.otel.applyEventsSpanContext).end();
|
|
285
|
+
otel.trace.getSpan(this.otel.queriesSpanContext).end();
|
|
250
286
|
// TODO destroy active subscriptions
|
|
251
287
|
};
|
|
252
|
-
this.destroyQuery = (query) => {
|
|
253
|
-
if (query._tag === 'sql') {
|
|
254
|
-
// results are downstream of query string, so will automatically be destroyed together
|
|
255
|
-
this.graph.destroy(query.queryString$);
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
this.graph.destroy(query.results$);
|
|
259
|
-
}
|
|
260
|
-
this.activeQueries.delete(query);
|
|
261
|
-
query.destroy();
|
|
262
|
-
};
|
|
263
|
-
/**
|
|
264
|
-
* Clean up queries and downstream subscriptions associated with a component.
|
|
265
|
-
* This is critical to avoid memory leaks.
|
|
266
|
-
*/
|
|
267
|
-
this.unmountComponent = (componentKey) => {
|
|
268
|
-
for (const query of this.activeQueries) {
|
|
269
|
-
if (query.componentKey === componentKey) {
|
|
270
|
-
this.destroyQuery(query);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
288
|
/* Apply a single write event to the store, and refresh all queries in response */
|
|
275
289
|
this.applyEvent = (eventType, args = {}, options) => {
|
|
276
290
|
const skipRefresh = options?.skipRefresh ?? false;
|
|
@@ -287,16 +301,23 @@ export class Store {
|
|
|
287
301
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`);
|
|
288
302
|
tablesToUpdate.push([tableRef, null]);
|
|
289
303
|
}
|
|
304
|
+
const debugRefreshReason = {
|
|
305
|
+
_tag: 'applyEvent',
|
|
306
|
+
event: { type: eventType, args },
|
|
307
|
+
writeTables: [...writeTables],
|
|
308
|
+
};
|
|
290
309
|
// Update all table refs together in a batch, to only trigger one reactive update
|
|
291
|
-
this.graph.setRefs(tablesToUpdate, {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
},
|
|
299
|
-
|
|
310
|
+
this.graph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext });
|
|
311
|
+
if (skipRefresh === false) {
|
|
312
|
+
// TODO update the graph
|
|
313
|
+
// this.graph.refresh(
|
|
314
|
+
// {
|
|
315
|
+
// otelHint: 'applyEvents',
|
|
316
|
+
// debugRefreshReason,
|
|
317
|
+
// },
|
|
318
|
+
// otelContext,
|
|
319
|
+
// )
|
|
320
|
+
}
|
|
300
321
|
}
|
|
301
322
|
catch (e) {
|
|
302
323
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() });
|
|
@@ -358,16 +379,17 @@ export class Store {
|
|
|
358
379
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`);
|
|
359
380
|
tablesToUpdate.push([tableRef, null]);
|
|
360
381
|
}
|
|
382
|
+
const debugRefreshReason = {
|
|
383
|
+
_tag: 'applyEvents',
|
|
384
|
+
events: [...events].map((e) => ({ type: e.eventType, args: e.args })),
|
|
385
|
+
writeTables: [...writeTables],
|
|
386
|
+
};
|
|
361
387
|
// Update all table refs together in a batch, to only trigger one reactive update
|
|
362
|
-
this.graph.setRefs(tablesToUpdate, {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
debugRefreshReason:
|
|
366
|
-
|
|
367
|
-
events: [...events].map((e) => ({ type: e.eventType, args: e.args })),
|
|
368
|
-
writeTables: [...writeTables],
|
|
369
|
-
},
|
|
370
|
-
}, otelContext);
|
|
388
|
+
this.graph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext });
|
|
389
|
+
if (skipRefresh === false) {
|
|
390
|
+
// TODO update the graph
|
|
391
|
+
// this.graph.refresh({ debugRefreshReason, otelHint: 'applyEvents' }, otelContext)
|
|
392
|
+
}
|
|
371
393
|
}
|
|
372
394
|
catch (e) {
|
|
373
395
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() });
|
|
@@ -385,17 +407,12 @@ export class Store {
|
|
|
385
407
|
this.manualRefresh = (options) => {
|
|
386
408
|
const { label } = options ?? {};
|
|
387
409
|
this.otel.tracer.startActiveSpan('LiveStore:manualRefresh', { attributes: { 'livestore.manualRefreshLabel': label } }, this.otel.applyEventsSpanContext, (span) => {
|
|
388
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
389
|
-
|
|
410
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
411
|
+
// TODO update the graph
|
|
412
|
+
// this.graph.refresh({ otelHint: 'manualRefresh', debugRefreshReason: { _tag: 'manualRefresh' } }, otelContext)
|
|
390
413
|
span.end();
|
|
391
414
|
});
|
|
392
415
|
};
|
|
393
|
-
// TODO get rid of this as part of new query definition approach https://www.notion.so/schickling/New-query-definition-approach-1097a78ef0e9495bac25f90417374756?pvs=4
|
|
394
|
-
this.runOnce = (queryDef) => {
|
|
395
|
-
return this.inTempQueryContext(() => {
|
|
396
|
-
return queryDef(this).results$.result;
|
|
397
|
-
});
|
|
398
|
-
};
|
|
399
416
|
/**
|
|
400
417
|
* Apply an event to the store.
|
|
401
418
|
* Returns the tables that were affected by the event.
|
|
@@ -462,7 +479,7 @@ export class Store {
|
|
|
462
479
|
* This should only be used for framework-internal purposes;
|
|
463
480
|
* all app writes should go through applyEvent.
|
|
464
481
|
*/
|
|
465
|
-
this.execute =
|
|
482
|
+
this.execute = (query, params = {}, writeTables) => {
|
|
466
483
|
this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables);
|
|
467
484
|
if (this.storage !== undefined) {
|
|
468
485
|
const parentSpan = otel.trace.getSpan(otel.context.active());
|
|
@@ -471,12 +488,12 @@ export class Store {
|
|
|
471
488
|
};
|
|
472
489
|
this.inMemoryDB = db;
|
|
473
490
|
this._proxyDb = dbProxy;
|
|
474
|
-
this.graph = new ReactiveGraph({
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
})
|
|
491
|
+
// this.graph = new ReactiveGraph({
|
|
492
|
+
// // TODO move this into React module
|
|
493
|
+
// // Do all our updates inside a single React setState batch to avoid multiple UI re-renders
|
|
494
|
+
// effectsWrapper: (run) => ReactDOM.unstable_batchedUpdates(() => run()),
|
|
495
|
+
// otelTracer,
|
|
496
|
+
// })
|
|
480
497
|
this.schema = schema;
|
|
481
498
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
482
499
|
this.tableRefs = {};
|
|
@@ -486,6 +503,8 @@ export class Store {
|
|
|
486
503
|
const otelApplyEventsSpanContext = otel.trace.setSpan(otel.context.active(), applyEventsSpan);
|
|
487
504
|
const queriesSpan = otelTracer.startSpan('LiveStore:queries', {}, otelRootSpanContext);
|
|
488
505
|
const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan);
|
|
506
|
+
this.graph = dbGraph;
|
|
507
|
+
this.graph.context = { store: this, otelTracer, rootOtelContext: otelQueriesSpanContext };
|
|
489
508
|
this.otel = {
|
|
490
509
|
tracer: otelTracer,
|
|
491
510
|
applyEventsSpanContext: otelApplyEventsSpanContext,
|