@livestore/livestore 0.0.12 → 0.0.14
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 +25 -28
- package/dist/.tsbuildinfo +1 -0
- package/dist/QueryCache.d.ts +20 -0
- package/dist/QueryCache.d.ts.map +1 -0
- package/dist/QueryCache.js +71 -0
- package/dist/QueryCache.js.map +1 -0
- package/dist/__tests__/react/fixture.d.ts +26 -0
- package/dist/__tests__/react/fixture.d.ts.map +1 -0
- package/dist/__tests__/react/fixture.js +60 -0
- package/dist/__tests__/react/fixture.js.map +1 -0
- 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.d.ts +2 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +1 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js +73 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -0
- 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.d.ts +2 -0
- package/dist/__tests__/reactive.test.d.ts.map +1 -0
- package/dist/__tests__/reactive.test.js +271 -0
- package/dist/__tests__/reactive.test.js.map +1 -0
- 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/bounded-collections.d.ts +34 -0
- package/dist/bounded-collections.d.ts.map +1 -0
- package/dist/bounded-collections.js +103 -0
- package/dist/bounded-collections.js.map +1 -0
- package/dist/componentKey.d.ts +20 -0
- package/dist/componentKey.d.ts.map +1 -0
- package/dist/componentKey.js +3 -0
- package/dist/componentKey.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +36 -0
- package/dist/effect/LiveStore.d.ts.map +1 -0
- package/dist/effect/LiveStore.js +41 -0
- package/dist/effect/LiveStore.js.map +1 -0
- package/dist/effect/index.d.ts +2 -0
- package/dist/effect/index.d.ts.map +1 -0
- package/dist/effect/index.js +2 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/events.d.ts +7 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +2 -0
- package/dist/events.js.map +1 -0
- package/dist/inMemoryDatabase.d.ts +56 -0
- package/dist/inMemoryDatabase.d.ts.map +1 -0
- package/dist/inMemoryDatabase.js +223 -0
- package/dist/inMemoryDatabase.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.d.ts +16 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +67 -0
- package/dist/migrations.js.map +1 -0
- package/dist/otel.d.ts +4 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +6 -0
- package/dist/otel.js.map +1 -0
- package/dist/react/LiveStoreContext.d.ts +11 -0
- package/dist/react/LiveStoreContext.d.ts.map +1 -0
- package/dist/react/LiveStoreContext.js +10 -0
- package/dist/react/LiveStoreContext.js.map +1 -0
- package/dist/react/LiveStoreProvider.d.ts +20 -0
- package/dist/react/LiveStoreProvider.d.ts.map +1 -0
- package/dist/react/LiveStoreProvider.js +52 -0
- package/dist/react/LiveStoreProvider.js.map +1 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/react/index.js.map +1 -0
- 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 +13 -0
- package/dist/react/useGraphQL.d.ts.map +1 -0
- package/dist/react/useGraphQL.js +87 -0
- package/dist/react/useGraphQL.js.map +1 -0
- package/dist/react/useLiveStoreComponent.d.ts +75 -0
- package/dist/react/useLiveStoreComponent.d.ts.map +1 -0
- package/dist/react/useLiveStoreComponent.js +361 -0
- package/dist/react/useLiveStoreComponent.js.map +1 -0
- package/dist/react/useQuery.d.ts +3 -0
- package/dist/react/useQuery.d.ts.map +1 -0
- package/dist/react/useQuery.js +42 -0
- package/dist/react/useQuery.js.map +1 -0
- 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/react/utils/useStateRefWithReactiveInput.d.ts +13 -0
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -0
- package/dist/react/utils/useStateRefWithReactiveInput.js +38 -0
- package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -0
- package/dist/reactive.d.ts +134 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +409 -0
- package/dist/reactive.js.map +1 -0
- package/dist/reactiveQueries/base-class.d.ts +32 -0
- package/dist/reactiveQueries/base-class.d.ts.map +1 -0
- package/dist/reactiveQueries/base-class.js +30 -0
- package/dist/reactiveQueries/base-class.js.map +1 -0
- 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 +42 -0
- package/dist/reactiveQueries/graphql.d.ts.map +1 -0
- package/dist/reactiveQueries/graphql.js +99 -0
- package/dist/reactiveQueries/graphql.js.map +1 -0
- package/dist/reactiveQueries/js.d.ts +23 -0
- package/dist/reactiveQueries/js.d.ts.map +1 -0
- package/dist/reactiveQueries/js.js +36 -0
- package/dist/reactiveQueries/js.js.map +1 -0
- package/dist/reactiveQueries/sql.d.ts +35 -0
- package/dist/reactiveQueries/sql.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.js +97 -0
- package/dist/reactiveQueries/sql.js.map +1 -0
- package/dist/schema.d.ts +81 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +46 -0
- package/dist/schema.js.map +1 -0
- package/dist/storage/in-memory/index.d.ts +15 -0
- package/dist/storage/in-memory/index.d.ts.map +1 -0
- package/dist/storage/in-memory/index.js +14 -0
- package/dist/storage/in-memory/index.js.map +1 -0
- package/dist/storage/index.d.ts +14 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +9 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/tauri/index.d.ts +19 -0
- package/dist/storage/tauri/index.d.ts.map +1 -0
- package/dist/storage/tauri/index.js +38 -0
- package/dist/storage/tauri/index.js.map +1 -0
- package/dist/storage/utils/idb.d.ts +10 -0
- package/dist/storage/utils/idb.d.ts.map +1 -0
- package/dist/storage/utils/idb.js +58 -0
- package/dist/storage/utils/idb.js.map +1 -0
- package/dist/storage/web-worker/index.d.ts +27 -0
- package/dist/storage/web-worker/index.d.ts.map +1 -0
- package/dist/storage/web-worker/index.js +74 -0
- package/dist/storage/web-worker/index.js.map +1 -0
- package/dist/storage/web-worker/worker.d.ts +13 -0
- package/dist/storage/web-worker/worker.d.ts.map +1 -0
- package/dist/storage/web-worker/worker.js +110 -0
- package/dist/storage/web-worker/worker.js.map +1 -0
- package/dist/store.d.ts +159 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +626 -0
- package/dist/store.js.map +1 -0
- package/dist/util.d.ts +28 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +55 -0
- package/dist/util.js.map +1 -0
- package/package.json +47 -19
- package/src/QueryCache.ts +1 -1
- package/src/__tests__/react/fixture.tsx +35 -39
- 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 +194 -142
- package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
- package/src/effect/LiveStore.ts +22 -31
- package/src/events.ts +1 -1
- package/src/inMemoryDatabase.ts +117 -142
- package/src/index.ts +18 -22
- package/src/migrations.ts +119 -0
- package/src/otel.ts +0 -11
- package/src/react/LiveStoreProvider.tsx +24 -23
- package/src/react/index.ts +12 -7
- package/src/react/useComponentState.ts +409 -0
- package/src/react/useQuery.ts +58 -0
- package/src/react/useTemporaryQuery.ts +21 -0
- package/src/react/utils/extractStackInfoFromStackTrace.ts +47 -0
- package/src/reactive.ts +386 -267
- package/src/reactiveQueries/base-class.ts +61 -39
- package/src/reactiveQueries/graph.ts +15 -0
- package/src/reactiveQueries/graphql.ts +147 -31
- package/src/reactiveQueries/js.ts +54 -21
- package/src/reactiveQueries/sql.ts +128 -37
- package/src/schema.ts +69 -145
- package/src/storage/in-memory/index.ts +21 -0
- package/src/storage/index.ts +27 -0
- package/src/{backends/tauri.ts → storage/tauri/index.ts} +14 -28
- package/src/storage/web-worker/index.ts +116 -0
- package/src/{backends/web-worker.ts → storage/web-worker/worker.ts} +17 -52
- package/src/store.ts +466 -457
- package/src/util.ts +13 -3
- package/tsconfig.json +1 -3
- package/src/backends/base.ts +0 -67
- package/src/backends/index.ts +0 -98
- package/src/backends/noop.ts +0 -32
- package/src/backends/web-in-memory.ts +0 -65
- package/src/backends/web.ts +0 -97
- package/src/react/useGlobalQuery.ts +0 -40
- package/src/react/useGraphQL.ts +0 -112
- package/src/react/useLiveStoreComponent.ts +0 -483
- /package/src/{backends → storage}/utils/idb.ts +0 -0
|
@@ -1,49 +1,71 @@
|
|
|
1
|
-
import * as otel from '@opentelemetry/api'
|
|
1
|
+
import type * as otel from '@opentelemetry/api'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
3
|
+
import type { StackInfo } from '../react/utils/extractStackInfoFromStackTrace.js'
|
|
4
|
+
import type { Atom, GetAtom, Thunk } from '../reactive.js'
|
|
5
|
+
import { type DbContext } from './graph.js'
|
|
6
|
+
import type { LiveStoreJSQuery } from './js.js'
|
|
5
7
|
|
|
6
8
|
export type UnsubscribeQuery = () => void
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
let queryIdCounter = 0
|
|
11
|
+
|
|
12
|
+
export interface ILiveStoreQuery<TResult> {
|
|
13
|
+
id: number
|
|
14
|
+
|
|
15
|
+
/** A reactive thunk representing the query results */
|
|
16
|
+
results$: Thunk<TResult, DbContext>
|
|
17
|
+
|
|
12
18
|
label: string
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
|
|
20
|
+
run: (otelContext?: otel.Context) => TResult
|
|
21
|
+
|
|
22
|
+
destroy(): void
|
|
23
|
+
|
|
24
|
+
activeSubscriptions: Set<SubscriberInfo>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type SubscriberInfo = {
|
|
28
|
+
stack: StackInfo[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export abstract class LiveStoreQueryBase<TResult> implements ILiveStoreQuery<TResult> {
|
|
32
|
+
id = queryIdCounter++
|
|
33
|
+
|
|
34
|
+
/** Human-readable label for the query for debugging */
|
|
35
|
+
abstract label: string
|
|
36
|
+
|
|
37
|
+
abstract results$: Thunk<TResult, DbContext>
|
|
38
|
+
|
|
39
|
+
activeSubscriptions: Set<SubscriberInfo> = new Set()
|
|
40
|
+
|
|
41
|
+
get runs() {
|
|
42
|
+
return this.results$.recomputations
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
destroy
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
abstract destroy: () => void
|
|
46
|
+
|
|
47
|
+
// subscribe = (
|
|
48
|
+
// onNewValue: (value: TResult) => void,
|
|
49
|
+
// onSubsubscribe?: () => void,
|
|
50
|
+
// options?: { label?: string } | undefined,
|
|
51
|
+
// ): (() => void) => this.store.subscribe(this as any, onNewValue as any, onSubsubscribe, options)
|
|
52
|
+
|
|
53
|
+
run = (otelContext?: otel.Context): TResult => this.results$.computeResult(otelContext)
|
|
41
54
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
unsubscribe()
|
|
47
|
-
}
|
|
55
|
+
runAndDestroy = (otelContext?: otel.Context): TResult => {
|
|
56
|
+
const result = this.run(otelContext)
|
|
57
|
+
this.destroy()
|
|
58
|
+
return result
|
|
48
59
|
}
|
|
49
60
|
}
|
|
61
|
+
|
|
62
|
+
export type GetAtomResult = <T>(atom: Atom<T, any> | LiveStoreJSQuery<T>) => T
|
|
63
|
+
|
|
64
|
+
export const makeGetAtomResult = (get: GetAtom, otelContext: otel.Context) => {
|
|
65
|
+
const getAtom: GetAtomResult = (atom) => {
|
|
66
|
+
if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom, otelContext)
|
|
67
|
+
return get(atom.results$, otelContext)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return getAtom
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type * as otel from '@opentelemetry/api'
|
|
2
|
+
import ReactDOM from 'react-dom'
|
|
3
|
+
|
|
4
|
+
import { ReactiveGraph } from '../reactive.js'
|
|
5
|
+
import type { QueryDebugInfo, RefreshReason, Store } from '../store.js'
|
|
6
|
+
|
|
7
|
+
export type DbContext = {
|
|
8
|
+
store: Store
|
|
9
|
+
otelTracer: otel.Tracer
|
|
10
|
+
rootOtelContext: otel.Context
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const dbGraph = new ReactiveGraph<RefreshReason, QueryDebugInfo, DbContext>({
|
|
14
|
+
effectsWrapper: (run) => ReactDOM.unstable_batchedUpdates(() => run()),
|
|
15
|
+
})
|
|
@@ -1,54 +1,170 @@
|
|
|
1
1
|
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
|
|
2
|
-
import
|
|
2
|
+
import { assertNever, shouldNeverHappen } from '@livestore/utils'
|
|
3
|
+
import * as otel from '@opentelemetry/api'
|
|
4
|
+
import * as graphql from 'graphql'
|
|
3
5
|
|
|
4
|
-
import type {
|
|
5
|
-
import type
|
|
6
|
-
import type
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
6
|
+
import type { Thunk } from '../reactive.js'
|
|
7
|
+
import { type BaseGraphQLContext, type Store } from '../store.js'
|
|
8
|
+
import { type GetAtomResult, LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
|
|
9
|
+
import { type DbContext, dbGraph } from './graph.js'
|
|
10
|
+
import { LiveStoreJSQuery } from './js.js'
|
|
11
|
+
|
|
12
|
+
export const queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
|
|
13
|
+
document: DocumentNode<TResult, TVariableValues>,
|
|
14
|
+
genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
|
|
15
|
+
{ label }: { label?: string } = {},
|
|
16
|
+
) => new LiveStoreGraphQLQuery({ document, genVariableValues, label })
|
|
9
17
|
|
|
10
18
|
export class LiveStoreGraphQLQuery<
|
|
11
19
|
TResult extends Record<string, any>,
|
|
12
|
-
|
|
20
|
+
TVariableValues extends Record<string, any>,
|
|
13
21
|
TContext extends BaseGraphQLContext,
|
|
14
|
-
> extends LiveStoreQueryBase {
|
|
22
|
+
> extends LiveStoreQueryBase<TResult> {
|
|
15
23
|
_tag: 'graphql' = 'graphql'
|
|
16
24
|
|
|
17
25
|
/** The abstract GraphQL query */
|
|
18
|
-
document: DocumentNode<TResult,
|
|
26
|
+
document: DocumentNode<TResult, TVariableValues>
|
|
19
27
|
|
|
20
28
|
/** A reactive thunk representing the query results */
|
|
21
|
-
results$: Thunk<TResult>
|
|
29
|
+
results$: Thunk<TResult, DbContext>
|
|
30
|
+
|
|
31
|
+
variableValues$: Thunk<TVariableValues, DbContext>
|
|
32
|
+
|
|
33
|
+
label: string
|
|
22
34
|
|
|
23
35
|
constructor({
|
|
24
36
|
document,
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
label,
|
|
38
|
+
genVariableValues, // context,
|
|
27
39
|
}: {
|
|
28
|
-
document: DocumentNode<TResult,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
componentKey: ComponentKey
|
|
32
|
-
label: string
|
|
33
|
-
store: Store<TContext>
|
|
34
|
-
otelContext: otel.Context
|
|
40
|
+
document: DocumentNode<TResult, TVariableValues>
|
|
41
|
+
genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues)
|
|
42
|
+
label?: string
|
|
35
43
|
}) {
|
|
36
|
-
super(
|
|
44
|
+
super()
|
|
37
45
|
|
|
46
|
+
const labelWithDefault = label ?? graphql.getOperationAST(document)?.name?.value ?? 'graphql'
|
|
47
|
+
|
|
48
|
+
this.label = labelWithDefault
|
|
38
49
|
this.document = document
|
|
39
|
-
this.results$ = results$
|
|
40
|
-
}
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
// if (context === undefined) {
|
|
52
|
+
// return shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// TODO don't even create a thunk if variables are static
|
|
56
|
+
const variableValues$ = dbGraph.makeThunk(
|
|
57
|
+
(get, _addDebugInfo, { rootOtelContext }, otelContext) => {
|
|
58
|
+
if (typeof genVariableValues === 'function') {
|
|
59
|
+
return genVariableValues(makeGetAtomResult(get, otelContext ?? rootOtelContext))
|
|
60
|
+
} else {
|
|
61
|
+
return genVariableValues
|
|
62
|
+
}
|
|
47
63
|
},
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
{ label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } },
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
this.variableValues$ = variableValues$
|
|
68
|
+
|
|
69
|
+
// const resultsLabel = `${labelWithDefault}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
70
|
+
const resultsLabel = `${labelWithDefault}:results`
|
|
71
|
+
this.results$ = dbGraph.makeThunk<TResult>(
|
|
72
|
+
(get, addDebugInfo, { store, otelTracer, rootOtelContext }, otelContext) => {
|
|
73
|
+
const variableValues = get(variableValues$)
|
|
74
|
+
const { result, queriedTables } = this.queryOnce({
|
|
75
|
+
document,
|
|
76
|
+
variableValues,
|
|
77
|
+
otelContext: otelContext ?? rootOtelContext,
|
|
78
|
+
otelTracer,
|
|
79
|
+
store: store as Store<TContext>,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Add dependencies on any tables that were used
|
|
83
|
+
for (const tableName of queriedTables) {
|
|
84
|
+
const tableRef = store.tableRefs[tableName]
|
|
85
|
+
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
86
|
+
get(tableRef!)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
addDebugInfo({ _tag: 'graphql', label: resultsLabel, query: graphql.print(document) })
|
|
90
|
+
|
|
91
|
+
return result
|
|
52
92
|
},
|
|
93
|
+
{ label: resultsLabel, meta: { liveStoreThunkType: 'graphqlResults' } },
|
|
94
|
+
// otelContext,
|
|
53
95
|
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns a new reactive query that contains the result of
|
|
100
|
+
* running an arbitrary JS computation on the results of this SQL query.
|
|
101
|
+
*/
|
|
102
|
+
pipe = <U>(fn: (result: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
|
|
103
|
+
new LiveStoreJSQuery({
|
|
104
|
+
fn: (get) => {
|
|
105
|
+
const results = get(this.results$)
|
|
106
|
+
return fn(results, get)
|
|
107
|
+
},
|
|
108
|
+
label: `${this.label}:js`,
|
|
109
|
+
onDestroy: () => this.destroy(),
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
queryOnce = ({
|
|
113
|
+
document,
|
|
114
|
+
otelContext,
|
|
115
|
+
otelTracer,
|
|
116
|
+
variableValues,
|
|
117
|
+
store,
|
|
118
|
+
}: {
|
|
119
|
+
document: graphql.DocumentNode
|
|
120
|
+
otelContext: otel.Context
|
|
121
|
+
otelTracer: otel.Tracer
|
|
122
|
+
variableValues: TVariableValues
|
|
123
|
+
store: Store<TContext>
|
|
124
|
+
}) => {
|
|
125
|
+
// const schema = this.schema
|
|
126
|
+
// const context = this.context
|
|
127
|
+
const schema =
|
|
128
|
+
store.graphQLSchema ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL schema")
|
|
129
|
+
const context =
|
|
130
|
+
store.graphQLContext ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
|
|
131
|
+
|
|
132
|
+
const operationName = graphql.getOperationAST(document)?.name?.value
|
|
133
|
+
|
|
134
|
+
return otelTracer.startActiveSpan(`executeGraphQLQuery: ${operationName}`, {}, otelContext, (span) => {
|
|
135
|
+
try {
|
|
136
|
+
span.setAttribute('graphql.variables', JSON.stringify(variableValues))
|
|
137
|
+
span.setAttribute('graphql.query', graphql.print(document))
|
|
138
|
+
|
|
139
|
+
context.queriedTables.clear()
|
|
140
|
+
|
|
141
|
+
context.otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
142
|
+
|
|
143
|
+
const res = graphql.executeSync({
|
|
144
|
+
document,
|
|
145
|
+
contextValue: context,
|
|
146
|
+
schema: schema,
|
|
147
|
+
variableValues,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// TODO track number of nested SQL queries via Otel + debug info
|
|
151
|
+
|
|
152
|
+
if (res.errors) {
|
|
153
|
+
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: 'GraphQL error' })
|
|
154
|
+
span.setAttribute('graphql.error', res.errors.join('\n'))
|
|
155
|
+
span.setAttribute('graphql.error-detail', JSON.stringify(res.errors))
|
|
156
|
+
console.error(`graphql error (${operationName})`, res.errors)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { result: res.data as unknown as TResult, queriedTables: Array.from(context.queriedTables.values()) }
|
|
160
|
+
} finally {
|
|
161
|
+
span.end()
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
destroy = () => {
|
|
167
|
+
dbGraph.destroy(this.variableValues$)
|
|
168
|
+
dbGraph.destroy(this.results$)
|
|
169
|
+
}
|
|
54
170
|
}
|
|
@@ -1,36 +1,69 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as otel from '@opentelemetry/api'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import type
|
|
5
|
-
import type {
|
|
6
|
-
import {
|
|
3
|
+
import type { Thunk } from '../reactive.js'
|
|
4
|
+
import { type GetAtomResult, LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
|
|
5
|
+
import type { DbContext } from './graph.js'
|
|
6
|
+
import { dbGraph } from './graph.js'
|
|
7
7
|
|
|
8
|
-
export
|
|
8
|
+
export const queryJS = <TResult>(fn: (get: GetAtomResult) => TResult, options: { label: string }) =>
|
|
9
|
+
new LiveStoreJSQuery<TResult>({ fn, label: options.label })
|
|
10
|
+
|
|
11
|
+
export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase<TResult> {
|
|
9
12
|
_tag: 'js' = 'js'
|
|
13
|
+
|
|
10
14
|
/** A reactive thunk representing the query results */
|
|
11
|
-
results$: Thunk<TResult>
|
|
15
|
+
results$: Thunk<TResult, DbContext>
|
|
16
|
+
|
|
17
|
+
label: string
|
|
18
|
+
|
|
19
|
+
/** Currently only used for "nested destruction" of piped queries */
|
|
20
|
+
private onDestroy: (() => void) | undefined
|
|
12
21
|
|
|
13
22
|
constructor({
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
fn,
|
|
24
|
+
label,
|
|
25
|
+
onDestroy,
|
|
16
26
|
}: {
|
|
17
|
-
results$: Thunk<TResult>
|
|
18
|
-
componentKey: ComponentKey
|
|
19
27
|
label: string
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
fn: (get: GetAtomResult) => TResult
|
|
29
|
+
/** Currently only used for "nested destruction" of piped queries */
|
|
30
|
+
onDestroy?: () => void
|
|
22
31
|
}) {
|
|
23
|
-
super(
|
|
32
|
+
super()
|
|
33
|
+
|
|
34
|
+
this.onDestroy = onDestroy
|
|
35
|
+
this.label = label
|
|
36
|
+
|
|
37
|
+
const queryLabel = `${label}:results`
|
|
24
38
|
|
|
25
|
-
this.results$ =
|
|
39
|
+
this.results$ = dbGraph.makeThunk(
|
|
40
|
+
(get, addDebugInfo, { otelTracer, rootOtelContext }, otelContext) =>
|
|
41
|
+
otelTracer.startActiveSpan(`js:${label}`, {}, otelContext ?? rootOtelContext, (span) => {
|
|
42
|
+
try {
|
|
43
|
+
addDebugInfo({ _tag: 'js', label, query: fn.toString() })
|
|
44
|
+
|
|
45
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
46
|
+
return fn(makeGetAtomResult(get, otelContext))
|
|
47
|
+
} finally {
|
|
48
|
+
span.end()
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
{ label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } },
|
|
52
|
+
)
|
|
26
53
|
}
|
|
27
54
|
|
|
28
|
-
pipe = <U>(
|
|
29
|
-
|
|
30
|
-
(get) => {
|
|
55
|
+
pipe = <U>(fn: (result: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
|
|
56
|
+
new LiveStoreJSQuery({
|
|
57
|
+
fn: (get) => {
|
|
31
58
|
const results = get(this.results$)
|
|
32
|
-
return
|
|
59
|
+
return fn(results, get)
|
|
33
60
|
},
|
|
34
|
-
|
|
35
|
-
|
|
61
|
+
label: `${this.label}:js`,
|
|
62
|
+
onDestroy: () => this.destroy(),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
destroy = () => {
|
|
66
|
+
dbGraph.destroy(this.results$)
|
|
67
|
+
this.onDestroy?.()
|
|
68
|
+
}
|
|
36
69
|
}
|
|
@@ -1,34 +1,121 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
|
+
import * as otel from '@opentelemetry/api'
|
|
2
3
|
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import
|
|
6
|
-
import { LiveStoreQueryBase } from './base-class.js'
|
|
7
|
-
import type {
|
|
4
|
+
import type { Thunk } from '../reactive.js'
|
|
5
|
+
import type { Bindable } from '../util.js'
|
|
6
|
+
import { prepareBindValues } from '../util.js'
|
|
7
|
+
import { type GetAtomResult, LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
|
|
8
|
+
import type { DbContext } from './graph.js'
|
|
9
|
+
import { dbGraph } from './graph.js'
|
|
10
|
+
import { LiveStoreJSQuery } from './js.js'
|
|
11
|
+
|
|
12
|
+
export const querySQL = <Row>(
|
|
13
|
+
query: string | ((get: GetAtomResult) => string),
|
|
14
|
+
options: {
|
|
15
|
+
queriedTables: ReadonlyArray<string>
|
|
16
|
+
bindValues?: Bindable
|
|
17
|
+
label?: string
|
|
18
|
+
},
|
|
19
|
+
) =>
|
|
20
|
+
new LiveStoreSQLQuery<Row>({
|
|
21
|
+
label: options.label,
|
|
22
|
+
genQueryString: query,
|
|
23
|
+
queriedTables: options.queriedTables,
|
|
24
|
+
bindValues: options.bindValues,
|
|
25
|
+
})
|
|
8
26
|
|
|
9
27
|
/* An object encapsulating a reactive SQL query */
|
|
10
|
-
export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
|
|
28
|
+
export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase<ReadonlyArray<Row>> {
|
|
11
29
|
_tag: 'sql' = 'sql'
|
|
30
|
+
|
|
12
31
|
/** A reactive thunk representing the query text */
|
|
13
|
-
queryString$: Thunk<string>
|
|
32
|
+
queryString$: Thunk<string, DbContext>
|
|
33
|
+
|
|
14
34
|
/** A reactive thunk representing the query results */
|
|
15
|
-
results$: Thunk<Row
|
|
35
|
+
results$: Thunk<ReadonlyArray<Row>, DbContext>
|
|
36
|
+
|
|
37
|
+
label: string
|
|
16
38
|
|
|
17
39
|
constructor({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
genQueryString,
|
|
41
|
+
queriedTables,
|
|
42
|
+
bindValues,
|
|
43
|
+
label,
|
|
21
44
|
}: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
store: Store<any>
|
|
27
|
-
otelContext: otel.Context
|
|
45
|
+
label?: string
|
|
46
|
+
genQueryString: string | ((get: GetAtomResult) => string)
|
|
47
|
+
queriedTables: ReadonlyArray<string>
|
|
48
|
+
bindValues?: Bindable
|
|
28
49
|
}) {
|
|
29
|
-
super(
|
|
50
|
+
super()
|
|
51
|
+
|
|
52
|
+
// TODO don't even create a thunk if query string is static
|
|
53
|
+
const queryString$ = dbGraph.makeThunk(
|
|
54
|
+
(get, addDebugInfo, { rootOtelContext }, otelContext) => {
|
|
55
|
+
if (typeof genQueryString === 'function') {
|
|
56
|
+
const queryString = genQueryString(makeGetAtomResult(get, otelContext ?? rootOtelContext))
|
|
57
|
+
addDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString })
|
|
58
|
+
return queryString
|
|
59
|
+
} else {
|
|
60
|
+
return genQueryString
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{ label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
|
|
64
|
+
)
|
|
30
65
|
|
|
31
66
|
this.queryString$ = queryString$
|
|
67
|
+
|
|
68
|
+
// TODO come up with different way to handle labels
|
|
69
|
+
// label = label ?? `sql(${queryString$.computeResult()})`
|
|
70
|
+
|
|
71
|
+
this.label = label ?? `sql(${genQueryString.toString()})`
|
|
72
|
+
// span.updateName(`querySQL:${label}`)
|
|
73
|
+
|
|
74
|
+
const queryLabel = `${label}:results`
|
|
75
|
+
// const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
76
|
+
|
|
77
|
+
const results$ = dbGraph.makeThunk<ReadonlyArray<Row>>(
|
|
78
|
+
(get, addDebugInfo, { store, otelTracer, rootOtelContext }, otelContext) =>
|
|
79
|
+
otelTracer.startActiveSpan(
|
|
80
|
+
'sql:', // NOTE span name will be overridden further down
|
|
81
|
+
{},
|
|
82
|
+
otelContext ?? rootOtelContext,
|
|
83
|
+
(span) => {
|
|
84
|
+
try {
|
|
85
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
86
|
+
|
|
87
|
+
// Establish a reactive dependency on the tables used in the query
|
|
88
|
+
for (const tableName of queriedTables) {
|
|
89
|
+
const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
|
|
90
|
+
get(tableRef, otelContext)
|
|
91
|
+
}
|
|
92
|
+
const sqlString = get(queryString$, otelContext)
|
|
93
|
+
|
|
94
|
+
span.setAttribute('sql.query', sqlString)
|
|
95
|
+
span.updateName(`sql:${sqlString.slice(0, 50)}`)
|
|
96
|
+
|
|
97
|
+
const results = store.inMemoryDB.select<Row>(sqlString, {
|
|
98
|
+
queriedTables,
|
|
99
|
+
bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
|
|
100
|
+
otelContext,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
span.setAttribute('sql.rowsCount', results.length)
|
|
104
|
+
addDebugInfo({ _tag: 'sql', label: label ?? '', query: sqlString })
|
|
105
|
+
|
|
106
|
+
return results
|
|
107
|
+
} finally {
|
|
108
|
+
span.end()
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
),
|
|
112
|
+
{ label: queryLabel },
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
// this.queryString$ = queryString$
|
|
116
|
+
// this.results$ = results$
|
|
117
|
+
// this.payload = payload
|
|
118
|
+
|
|
32
119
|
this.results$ = results$
|
|
33
120
|
}
|
|
34
121
|
|
|
@@ -36,30 +123,34 @@ export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
|
|
|
36
123
|
* Returns a new reactive query that contains the result of
|
|
37
124
|
* running an arbitrary JS computation on the results of this SQL query.
|
|
38
125
|
*/
|
|
39
|
-
pipe = <U>(
|
|
40
|
-
|
|
41
|
-
(get) => {
|
|
42
|
-
const results = get(this.results
|
|
43
|
-
return
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
componentKey: this.componentKey,
|
|
47
|
-
label: `${this.label}:js`,
|
|
48
|
-
otelContext: this.otelContext,
|
|
126
|
+
pipe = <U>(fn: (result: ReadonlyArray<Row>, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
|
|
127
|
+
new LiveStoreJSQuery({
|
|
128
|
+
fn: (get) => {
|
|
129
|
+
const results = get(this.results$!)
|
|
130
|
+
return fn(results, get)
|
|
49
131
|
},
|
|
50
|
-
|
|
132
|
+
label: `${this.label}:js`,
|
|
133
|
+
onDestroy: () => this.destroy(),
|
|
134
|
+
})
|
|
51
135
|
|
|
52
136
|
/** Returns a reactive query */
|
|
53
137
|
getFirstRow = (args?: { defaultValue?: Row }) =>
|
|
54
|
-
|
|
55
|
-
(get) => {
|
|
56
|
-
const results = get(this.results
|
|
138
|
+
new LiveStoreJSQuery({
|
|
139
|
+
fn: (get) => {
|
|
140
|
+
const results = get(this.results$!)
|
|
57
141
|
if (results.length === 0 && args?.defaultValue === undefined) {
|
|
58
|
-
const queryLabel = this._tag === 'sql' ? this.queryString
|
|
142
|
+
// const queryLabel = this._tag === 'sql' ? this.queryString$!.computeResult(otelContext) : this.label
|
|
143
|
+
const queryLabel = this.label
|
|
59
144
|
throw new Error(`Expected query ${queryLabel} to return at least one result`)
|
|
60
145
|
}
|
|
61
|
-
return
|
|
146
|
+
return results[0] ?? args!.defaultValue!
|
|
62
147
|
},
|
|
63
|
-
|
|
64
|
-
|
|
148
|
+
label: `${this.label}:first`,
|
|
149
|
+
onDestroy: () => this.destroy(),
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
destroy = () => {
|
|
153
|
+
dbGraph.destroy(this.queryString$)
|
|
154
|
+
dbGraph.destroy(this.results$)
|
|
155
|
+
}
|
|
65
156
|
}
|