@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
package/src/store.ts
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
|
|
2
1
|
import { assertNever, makeNoopSpan, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
|
|
2
|
+
import { identity } from '@livestore/utils/effect'
|
|
3
3
|
import * as otel from '@opentelemetry/api'
|
|
4
4
|
import type { GraphQLSchema } from 'graphql'
|
|
5
|
-
import * as
|
|
6
|
-
import { uniqueId } from 'lodash-es'
|
|
7
|
-
import * as ReactDOM from 'react-dom'
|
|
5
|
+
import type * as Sqlite from 'sqlite-esm'
|
|
8
6
|
import { v4 as uuid } from 'uuid'
|
|
9
7
|
|
|
10
|
-
import type { Backend, BackendOptions } from './backends/index.js'
|
|
11
|
-
import { createBackend } from './backends/index.js'
|
|
12
8
|
import type { ComponentKey } from './componentKey.js'
|
|
13
9
|
import { tableNameForComponentKey } from './componentKey.js'
|
|
14
10
|
import type { LiveStoreEvent } from './events.js'
|
|
15
11
|
import { InMemoryDatabase } from './inMemoryDatabase.js'
|
|
12
|
+
import { migrateDb } from './migrations.js'
|
|
16
13
|
import { getDurationMsFromSpan } from './otel.js'
|
|
17
|
-
import type {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import type {
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
14
|
+
import type { ReactiveGraph, Ref } from './reactive.js'
|
|
15
|
+
import type { ILiveStoreQuery } from './reactiveQueries/base-class.js'
|
|
16
|
+
import { type DbContext, dbGraph } from './reactiveQueries/graph.js'
|
|
17
|
+
import type { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js'
|
|
18
|
+
import type { LiveStoreJSQuery } from './reactiveQueries/js.js'
|
|
19
|
+
import type { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
|
|
20
|
+
import type { ActionDefinition, GetActionArgs, Schema, SQLWriteStatement } from './schema.js'
|
|
21
|
+
import { componentStateTables } from './schema.js'
|
|
22
|
+
import type { Storage, StorageInit } from './storage/index.js'
|
|
23
|
+
import type { ParamsObject } from './util.js'
|
|
24
|
+
import { isPromise, prepareBindValues, sql } from './util.js'
|
|
26
25
|
|
|
27
26
|
export type LiveStoreQuery<TResult extends Record<string, any> = any> =
|
|
28
27
|
| LiveStoreSQLQuery<TResult>
|
|
@@ -35,8 +34,6 @@ export type BaseGraphQLContext = {
|
|
|
35
34
|
otelContext?: otel.Context
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
export const RESET_DB_LOCAL_STORAGE_KEY = 'livestore-reset'
|
|
39
|
-
|
|
40
37
|
export type QueryResult<TQuery> = TQuery extends LiveStoreSQLQuery<infer R>
|
|
41
38
|
? ReadonlyArray<Readonly<R>>
|
|
42
39
|
: TQuery extends LiveStoreJSQuery<infer S>
|
|
@@ -45,7 +42,7 @@ export type QueryResult<TQuery> = TQuery extends LiveStoreSQLQuery<infer R>
|
|
|
45
42
|
? Readonly<Result>
|
|
46
43
|
: never
|
|
47
44
|
|
|
48
|
-
const globalComponentKey: ComponentKey = { _tag: 'singleton', componentName: '__global', id: 'singleton' }
|
|
45
|
+
export const globalComponentKey: ComponentKey = { _tag: 'singleton', componentName: '__global', id: 'singleton' }
|
|
49
46
|
|
|
50
47
|
export type GraphQLOptions<TContext> = {
|
|
51
48
|
schema: GraphQLSchema
|
|
@@ -54,8 +51,10 @@ export type GraphQLOptions<TContext> = {
|
|
|
54
51
|
|
|
55
52
|
export type StoreOptions<TGraphQLContext extends BaseGraphQLContext> = {
|
|
56
53
|
db: InMemoryDatabase
|
|
54
|
+
/** A `Proxy`d version of `db` except that it also mirrors `execute` calls to the storage */
|
|
55
|
+
dbProxy: InMemoryDatabase
|
|
57
56
|
schema: Schema
|
|
58
|
-
|
|
57
|
+
storage?: Storage
|
|
59
58
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
60
59
|
otelTracer: otel.Tracer
|
|
61
60
|
otelRootSpanContext: otel.Context
|
|
@@ -98,9 +97,11 @@ export type StoreOtel = {
|
|
|
98
97
|
queriesSpanContext: otel.Context
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
102
|
-
graph: ReactiveGraph<RefreshReason, QueryDebugInfo>
|
|
100
|
+
export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLContext> {
|
|
101
|
+
graph: ReactiveGraph<RefreshReason, QueryDebugInfo, DbContext>
|
|
103
102
|
inMemoryDB: InMemoryDatabase
|
|
103
|
+
// TODO refactor
|
|
104
|
+
_proxyDb: InMemoryDatabase
|
|
104
105
|
schema: Schema
|
|
105
106
|
graphQLSchema?: GraphQLSchema
|
|
106
107
|
graphQLContext?: TGraphQLContext
|
|
@@ -111,29 +112,31 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
111
112
|
*/
|
|
112
113
|
tableRefs: { [key: string]: Ref<null> }
|
|
113
114
|
activeQueries: Set<LiveStoreQuery>
|
|
114
|
-
|
|
115
|
+
storage?: Storage
|
|
115
116
|
temporaryQueries: Set<LiveStoreQuery> | undefined
|
|
116
117
|
|
|
117
118
|
private constructor({
|
|
118
119
|
db,
|
|
120
|
+
dbProxy,
|
|
119
121
|
schema,
|
|
120
|
-
|
|
122
|
+
storage,
|
|
121
123
|
graphQLOptions,
|
|
122
124
|
otelTracer,
|
|
123
125
|
otelRootSpanContext,
|
|
124
126
|
}: StoreOptions<TGraphQLContext>) {
|
|
125
127
|
this.inMemoryDB = db
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
this._proxyDb = dbProxy
|
|
129
|
+
// this.graph = new ReactiveGraph({
|
|
130
|
+
// // TODO move this into React module
|
|
131
|
+
// // Do all our updates inside a single React setState batch to avoid multiple UI re-renders
|
|
132
|
+
// effectsWrapper: (run) => ReactDOM.unstable_batchedUpdates(() => run()),
|
|
133
|
+
// otelTracer,
|
|
134
|
+
// })
|
|
132
135
|
this.schema = schema
|
|
133
136
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
134
137
|
this.tableRefs = {}
|
|
135
138
|
this.activeQueries = new Set()
|
|
136
|
-
this.
|
|
139
|
+
this.storage = storage
|
|
137
140
|
|
|
138
141
|
const applyEventsSpan = otelTracer.startSpan('LiveStore:applyEvents', {}, otelRootSpanContext)
|
|
139
142
|
const otelApplyEventsSpanContext = otel.trace.setSpan(otel.context.active(), applyEventsSpan)
|
|
@@ -141,6 +144,9 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
141
144
|
const queriesSpan = otelTracer.startSpan('LiveStore:queries', {}, otelRootSpanContext)
|
|
142
145
|
const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan)
|
|
143
146
|
|
|
147
|
+
this.graph = dbGraph
|
|
148
|
+
this.graph.context = { store: this, otelTracer, rootOtelContext: otelQueriesSpanContext }
|
|
149
|
+
|
|
144
150
|
this.otel = {
|
|
145
151
|
tracer: otelTracer,
|
|
146
152
|
applyEventsSpanContext: otelApplyEventsSpanContext,
|
|
@@ -185,372 +191,340 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
185
191
|
*
|
|
186
192
|
* NOTE The query is actually running (even if no one has subscribed to it yet) and will be kept up to date.
|
|
187
193
|
*/
|
|
188
|
-
querySQL = <TResult>(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
): LiveStoreSQLQuery<TResult> =>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
194
|
+
// querySQL = <TResult>(
|
|
195
|
+
// genQueryString: string | ((get: GetAtomResult) => string),
|
|
196
|
+
// {
|
|
197
|
+
// queriedTables,
|
|
198
|
+
// bindValues,
|
|
199
|
+
// componentKey,
|
|
200
|
+
// label,
|
|
201
|
+
// otelContext = otel.context.active(),
|
|
202
|
+
// }: {
|
|
203
|
+
// /**
|
|
204
|
+
// * List of tables that are queried in this query;
|
|
205
|
+
// * used to determine reactive dependencies.
|
|
206
|
+
// *
|
|
207
|
+
// * NOTE In the future we want to auto-generate this via parsing the query
|
|
208
|
+
// */
|
|
209
|
+
// queriedTables: string[]
|
|
210
|
+
// bindValues?: Bindable | undefined
|
|
211
|
+
// componentKey?: ComponentKey | undefined
|
|
212
|
+
// label?: string | undefined
|
|
213
|
+
// otelContext?: otel.Context
|
|
214
|
+
// },
|
|
215
|
+
// ): LiveStoreSQLQuery<TResult> =>
|
|
216
|
+
// this.otel.tracer.startActiveSpan(
|
|
217
|
+
// 'querySQL', // NOTE span name will be overridden further down
|
|
218
|
+
// { attributes: { label } },
|
|
219
|
+
// otelContext,
|
|
220
|
+
// (span) => {
|
|
221
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
222
|
+
|
|
223
|
+
// const queryString$ = this.graph.makeThunk(
|
|
224
|
+
// (get, addDebugInfo) => {
|
|
225
|
+
// if (typeof genQueryString === 'function') {
|
|
226
|
+
// const queryString = genQueryString(makeGetAtomResult(get))
|
|
227
|
+
// addDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString })
|
|
228
|
+
// return queryString
|
|
229
|
+
// } else {
|
|
230
|
+
// return genQueryString
|
|
231
|
+
// }
|
|
232
|
+
// },
|
|
233
|
+
// { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
|
|
234
|
+
// otelContext,
|
|
235
|
+
// )
|
|
236
|
+
|
|
237
|
+
// label = label ?? queryString$.result
|
|
238
|
+
// span.updateName(`querySQL:${label}`)
|
|
239
|
+
|
|
240
|
+
// const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
241
|
+
|
|
242
|
+
// const results$ = this.graph.makeThunk<ReadonlyArray<TResult>>(
|
|
243
|
+
// (get, addDebugInfo) =>
|
|
244
|
+
// this.otel.tracer.startActiveSpan(
|
|
245
|
+
// 'sql:', // NOTE span name will be overridden further down
|
|
246
|
+
// {},
|
|
247
|
+
// otelContext,
|
|
248
|
+
// (span) => {
|
|
249
|
+
// try {
|
|
250
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
251
|
+
|
|
252
|
+
// // Establish a reactive dependency on the tables used in the query
|
|
253
|
+
// for (const tableName of queriedTables) {
|
|
254
|
+
// const tableRef =
|
|
255
|
+
// this.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
|
|
256
|
+
// get(tableRef)
|
|
257
|
+
// }
|
|
258
|
+
// const sqlString = get(queryString$)
|
|
259
|
+
|
|
260
|
+
// span.setAttribute('sql.query', sqlString)
|
|
261
|
+
// span.updateName(`sql:${sqlString.slice(0, 50)}`)
|
|
262
|
+
|
|
263
|
+
// const results = this.inMemoryDB.select<TResult>(sqlString, {
|
|
264
|
+
// queriedTables,
|
|
265
|
+
// bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
|
|
266
|
+
// otelContext,
|
|
267
|
+
// })
|
|
268
|
+
|
|
269
|
+
// span.setAttribute('sql.rowsCount', results.length)
|
|
270
|
+
// addDebugInfo({ _tag: 'sql', label: label ?? '', query: sqlString })
|
|
271
|
+
|
|
272
|
+
// return results
|
|
273
|
+
// } finally {
|
|
274
|
+
// span.end()
|
|
275
|
+
// }
|
|
276
|
+
// },
|
|
277
|
+
// ),
|
|
278
|
+
// { label: queryLabel },
|
|
279
|
+
// otelContext,
|
|
280
|
+
// )
|
|
281
|
+
|
|
282
|
+
// const query = new LiveStoreSQLQuery<TResult>({
|
|
283
|
+
// label,
|
|
284
|
+
// queryString$,
|
|
285
|
+
// results$,
|
|
286
|
+
// componentKey: componentKey ?? globalComponentKey,
|
|
287
|
+
// store: this,
|
|
288
|
+
// otelContext,
|
|
289
|
+
// })
|
|
290
|
+
|
|
291
|
+
// this.activeQueries.add(query)
|
|
292
|
+
|
|
293
|
+
// // TODO get rid of temporary query workaround
|
|
294
|
+
// if (this.temporaryQueries !== undefined) {
|
|
295
|
+
// this.temporaryQueries.add(query)
|
|
296
|
+
// }
|
|
297
|
+
|
|
298
|
+
// // NOTE we are not ending the span here but in the query `destroy` method
|
|
299
|
+
// return query
|
|
300
|
+
// },
|
|
301
|
+
// )
|
|
302
|
+
|
|
303
|
+
// queryJS = <TResult>(
|
|
304
|
+
// genResults: (get: GetAtomResult) => TResult,
|
|
305
|
+
// {
|
|
306
|
+
// componentKey = globalComponentKey,
|
|
307
|
+
// label = `js${uniqueId()}`,
|
|
308
|
+
// otelContext = otel.context.active(),
|
|
309
|
+
// }: { componentKey?: ComponentKey; label?: string; otelContext?: otel.Context },
|
|
310
|
+
// ): LiveStoreJSQuery<TResult> =>
|
|
311
|
+
// this.otel.tracer.startActiveSpan(`queryJS:${label}`, { attributes: { label } }, otelContext, (span) => {
|
|
312
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
313
|
+
// const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
314
|
+
// const results$ = this.graph.makeThunk(
|
|
315
|
+
// (get, addDebugInfo) => {
|
|
316
|
+
// addDebugInfo({ _tag: 'js', label, query: genResults.toString() })
|
|
317
|
+
// return genResults(makeGetAtomResult(get))
|
|
318
|
+
// },
|
|
319
|
+
// { label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } },
|
|
320
|
+
// otelContext,
|
|
321
|
+
// )
|
|
322
|
+
|
|
323
|
+
// // const query = new LiveStoreJSQuery<TResult>({
|
|
324
|
+
// // label,
|
|
325
|
+
// // results$,
|
|
326
|
+
// // componentKey,
|
|
327
|
+
// // store: this,
|
|
328
|
+
// // otelContext,
|
|
329
|
+
// // })
|
|
330
|
+
|
|
331
|
+
// this.activeQueries.add(query)
|
|
332
|
+
|
|
333
|
+
// // TODO get rid of temporary query workaround
|
|
334
|
+
// if (this.temporaryQueries !== undefined) {
|
|
335
|
+
// this.temporaryQueries.add(query)
|
|
336
|
+
// }
|
|
337
|
+
|
|
338
|
+
// // NOTE we are not ending the span here but in the query `destroy` method
|
|
339
|
+
// return query
|
|
340
|
+
// })
|
|
341
|
+
|
|
342
|
+
// queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
|
|
343
|
+
// document: DocumentNode<TResult, TVariableValues>,
|
|
344
|
+
// genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
|
|
345
|
+
// {
|
|
346
|
+
// componentKey,
|
|
347
|
+
// label,
|
|
348
|
+
// otelContext = otel.context.active(),
|
|
349
|
+
// }: {
|
|
350
|
+
// componentKey: ComponentKey
|
|
351
|
+
// label?: string
|
|
352
|
+
// otelContext?: otel.Context
|
|
353
|
+
// },
|
|
354
|
+
// ): LiveStoreGraphQLQuery<TResult, TVariableValues, TGraphQLContext> =>
|
|
355
|
+
// this.otel.tracer.startActiveSpan(
|
|
356
|
+
// `queryGraphQL:`, // NOTE span name will be overridden further down
|
|
357
|
+
// {},
|
|
358
|
+
// otelContext,
|
|
359
|
+
// (span) => {
|
|
360
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
361
|
+
|
|
362
|
+
// if (this.graphQLContext === undefined) {
|
|
363
|
+
// return shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
|
|
364
|
+
// }
|
|
365
|
+
|
|
366
|
+
// const labelWithDefault = label ?? graphql.getOperationAST(document)?.name?.value ?? 'graphql'
|
|
367
|
+
|
|
368
|
+
// span.updateName(`queryGraphQL:${labelWithDefault}`)
|
|
369
|
+
|
|
370
|
+
// const variableValues$ = this.graph.makeThunk(
|
|
371
|
+
// (get) => {
|
|
372
|
+
// if (typeof genVariableValues === 'function') {
|
|
373
|
+
// return genVariableValues(makeGetAtomResult(get))
|
|
374
|
+
// } else {
|
|
375
|
+
// return genVariableValues
|
|
376
|
+
// }
|
|
377
|
+
// },
|
|
378
|
+
// { label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } },
|
|
379
|
+
// // otelContext,
|
|
380
|
+
// )
|
|
381
|
+
|
|
382
|
+
// const resultsLabel = `${labelWithDefault}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
383
|
+
// const results$ = this.graph.makeThunk<TResult>(
|
|
384
|
+
// (get, addDebugInfo) => {
|
|
385
|
+
// const variableValues = get(variableValues$)
|
|
386
|
+
// const { result, queriedTables } = this.queryGraphQLOnce(document, variableValues, otelContext)
|
|
387
|
+
|
|
388
|
+
// // Add dependencies on any tables that were used
|
|
389
|
+
// for (const tableName of queriedTables) {
|
|
390
|
+
// const tableRef = this.tableRefs[tableName]
|
|
391
|
+
// assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
392
|
+
// get(tableRef!)
|
|
393
|
+
// }
|
|
394
|
+
|
|
395
|
+
// addDebugInfo({ _tag: 'graphql', label: resultsLabel, query: graphql.print(document) })
|
|
396
|
+
|
|
397
|
+
// return result
|
|
398
|
+
// },
|
|
399
|
+
// { label: resultsLabel, meta: { liveStoreThunkType: 'graphqlResults' } },
|
|
400
|
+
// // otelContext,
|
|
401
|
+
// )
|
|
402
|
+
|
|
403
|
+
// const query = new LiveStoreGraphQLQuery({
|
|
404
|
+
// document,
|
|
405
|
+
// context: this.graphQLContext,
|
|
406
|
+
// results$,
|
|
407
|
+
// componentKey,
|
|
408
|
+
// label: labelWithDefault,
|
|
409
|
+
// store: this,
|
|
410
|
+
// otelContext,
|
|
411
|
+
// })
|
|
412
|
+
|
|
413
|
+
// this.activeQueries.add(query)
|
|
414
|
+
|
|
415
|
+
// // TODO get rid of temporary query workaround
|
|
416
|
+
// if (this.temporaryQueries !== undefined) {
|
|
417
|
+
// this.temporaryQueries.add(query)
|
|
418
|
+
// }
|
|
419
|
+
|
|
420
|
+
// // NOTE we are not ending the span here but in the query `destroy` method
|
|
421
|
+
// return query
|
|
422
|
+
// },
|
|
423
|
+
// )
|
|
424
|
+
|
|
425
|
+
// queryGraphQLOnce = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
|
|
426
|
+
// document: DocumentNode<TResult, TVariableValues>,
|
|
427
|
+
// variableValues: TVariableValues,
|
|
428
|
+
// otelContext: otel.Context = this.otel.queriesSpanContext,
|
|
429
|
+
// ): { result: TResult; queriedTables: string[] } => {
|
|
430
|
+
// const schema =
|
|
431
|
+
// this.graphQLSchema ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL schema")
|
|
432
|
+
// const context =
|
|
433
|
+
// this.graphQLContext ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
|
|
434
|
+
// const tracer = this.otel.tracer
|
|
435
|
+
|
|
436
|
+
// const operationName = graphql.getOperationAST(document)?.name?.value
|
|
437
|
+
|
|
438
|
+
// return tracer.startActiveSpan(`executeGraphQLQuery: ${operationName}`, {}, otelContext, (span) => {
|
|
439
|
+
// try {
|
|
440
|
+
// span.setAttribute('graphql.variables', JSON.stringify(variableValues))
|
|
441
|
+
// span.setAttribute('graphql.query', graphql.print(document))
|
|
442
|
+
|
|
443
|
+
// context.queriedTables.clear()
|
|
444
|
+
|
|
445
|
+
// context.otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
446
|
+
|
|
447
|
+
// const res = graphql.executeSync({
|
|
448
|
+
// document,
|
|
449
|
+
// contextValue: context,
|
|
450
|
+
// schema: schema,
|
|
451
|
+
// variableValues,
|
|
452
|
+
// })
|
|
453
|
+
|
|
454
|
+
// // TODO track number of nested SQL queries via Otel + debug info
|
|
455
|
+
|
|
456
|
+
// if (res.errors) {
|
|
457
|
+
// span.setStatus({ code: otel.SpanStatusCode.ERROR, message: 'GraphQL error' })
|
|
458
|
+
// span.setAttribute('graphql.error', res.errors.join('\n'))
|
|
459
|
+
// span.setAttribute('graphql.error-detail', JSON.stringify(res.errors))
|
|
460
|
+
// console.error(`graphql error (${operationName})`, res.errors)
|
|
461
|
+
// }
|
|
462
|
+
|
|
463
|
+
// return { result: res.data as unknown as TResult, queriedTables: Array.from(context.queriedTables.values()) }
|
|
464
|
+
// } finally {
|
|
465
|
+
// span.end()
|
|
466
|
+
// }
|
|
467
|
+
// })
|
|
468
|
+
// }
|
|
449
469
|
|
|
450
470
|
/**
|
|
451
471
|
* Subscribe to the results of a query
|
|
452
472
|
* Returns a function to cancel the subscription.
|
|
453
473
|
*/
|
|
454
|
-
subscribe = <
|
|
455
|
-
query:
|
|
456
|
-
onNewValue: (value:
|
|
474
|
+
subscribe = <TResult>(
|
|
475
|
+
query: ILiveStoreQuery<TResult>,
|
|
476
|
+
onNewValue: (value: TResult) => void,
|
|
457
477
|
onSubsubscribe?: () => void,
|
|
458
|
-
options?: { label?: string } | undefined,
|
|
478
|
+
options?: { label?: string; otelContext?: otel.Context } | undefined,
|
|
459
479
|
): (() => void) =>
|
|
460
480
|
this.otel.tracer.startActiveSpan(
|
|
461
481
|
`LiveStore.subscribe`,
|
|
462
482
|
{ attributes: { label: options?.label } },
|
|
463
|
-
|
|
483
|
+
options?.otelContext ?? this.otel.queriesSpanContext,
|
|
464
484
|
(span) => {
|
|
465
485
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
466
486
|
|
|
467
|
-
const effect = this.graph.makeEffect(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
{ label: `subscribe:${options?.label}` },
|
|
473
|
-
otelContext,
|
|
474
|
-
)
|
|
487
|
+
const effect = this.graph.makeEffect((get) => onNewValue(get(query.results$)), {
|
|
488
|
+
label: `subscribe:${options?.label}`,
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
effect.doEffect(otelContext)
|
|
475
492
|
|
|
476
|
-
const subscriptionKey = uuid()
|
|
493
|
+
// const subscriptionKey = uuid()
|
|
477
494
|
|
|
478
495
|
const unsubscribe = () => {
|
|
479
496
|
try {
|
|
480
497
|
this.graph.destroy(effect)
|
|
481
|
-
|
|
498
|
+
this.activeQueries.delete(query as LiveStoreQuery)
|
|
499
|
+
// query.activeSubscriptions.delete(subscriptionKey)
|
|
482
500
|
onSubsubscribe?.()
|
|
483
501
|
} finally {
|
|
484
502
|
span.end()
|
|
485
503
|
}
|
|
486
504
|
}
|
|
487
505
|
|
|
488
|
-
|
|
506
|
+
this.activeQueries.add(query as LiveStoreQuery)
|
|
507
|
+
|
|
508
|
+
// query.activeSubscriptions.set(subscriptionKey, unsubscribe)
|
|
489
509
|
|
|
490
510
|
return unsubscribe
|
|
491
511
|
},
|
|
492
512
|
)
|
|
493
513
|
|
|
494
|
-
/**
|
|
495
|
-
* Any queries created in the callback will be destroyed when the callback is complete.
|
|
496
|
-
* Useful for temporarily creating reactive queries, which is an idempotent operation
|
|
497
|
-
* that can be safely called inside a React useMemo hook.
|
|
498
|
-
*/
|
|
499
|
-
inTempQueryContext = <TResult>(callback: () => TResult): TResult => {
|
|
500
|
-
this.temporaryQueries = new Set()
|
|
501
|
-
// TODO: consider errors / try/finally here?
|
|
502
|
-
const result = callback()
|
|
503
|
-
for (const query of this.temporaryQueries) {
|
|
504
|
-
this.destroyQuery(query)
|
|
505
|
-
}
|
|
506
|
-
this.temporaryQueries = undefined
|
|
507
|
-
return result
|
|
508
|
-
}
|
|
509
|
-
|
|
510
514
|
/**
|
|
511
515
|
* Destroys the entire store, including all queries and subscriptions.
|
|
512
516
|
*
|
|
513
517
|
* Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
|
|
514
518
|
*/
|
|
515
519
|
destroy = () => {
|
|
516
|
-
for (const query of this.activeQueries) {
|
|
517
|
-
this.destroyQuery(query)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
520
|
Object.values(this.tableRefs).forEach((tableRef) => this.graph.destroy(tableRef))
|
|
521
521
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const queriesSpan = otel.trace.getSpan(this.otel.queriesSpanContext)!
|
|
526
|
-
queriesSpan.end()
|
|
522
|
+
otel.trace.getSpan(this.otel.applyEventsSpanContext)!.end()
|
|
523
|
+
otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
|
|
527
524
|
|
|
528
525
|
// TODO destroy active subscriptions
|
|
529
526
|
}
|
|
530
527
|
|
|
531
|
-
private destroyQuery = (query: LiveStoreQuery) => {
|
|
532
|
-
if (query._tag === 'sql') {
|
|
533
|
-
// results are downstream of query string, so will automatically be destroyed together
|
|
534
|
-
this.graph.destroy(query.queryString$)
|
|
535
|
-
} else {
|
|
536
|
-
this.graph.destroy(query.results$)
|
|
537
|
-
}
|
|
538
|
-
this.activeQueries.delete(query)
|
|
539
|
-
query.destroy()
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Clean up queries and downstream subscriptions associated with a component.
|
|
544
|
-
* This is critical to avoid memory leaks.
|
|
545
|
-
*/
|
|
546
|
-
unmountComponent = (componentKey: ComponentKey) => {
|
|
547
|
-
for (const query of this.activeQueries) {
|
|
548
|
-
if (query.componentKey === componentKey) {
|
|
549
|
-
this.destroyQuery(query)
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
528
|
/* Apply a single write event to the store, and refresh all queries in response */
|
|
555
529
|
applyEvent = <TEventType extends string & keyof LiveStoreActionDefinitionsTypes>(
|
|
556
530
|
eventType: TEventType,
|
|
@@ -579,20 +553,25 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
579
553
|
tablesToUpdate.push([tableRef!, null])
|
|
580
554
|
}
|
|
581
555
|
|
|
556
|
+
const debugRefreshReason = {
|
|
557
|
+
_tag: 'applyEvent' as const,
|
|
558
|
+
event: { type: eventType, args },
|
|
559
|
+
writeTables: [...writeTables],
|
|
560
|
+
}
|
|
561
|
+
|
|
582
562
|
// Update all table refs together in a batch, to only trigger one reactive update
|
|
583
|
-
this.graph.setRefs(
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
)
|
|
563
|
+
this.graph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext })
|
|
564
|
+
|
|
565
|
+
if (skipRefresh === false) {
|
|
566
|
+
// TODO update the graph
|
|
567
|
+
// this.graph.refresh(
|
|
568
|
+
// {
|
|
569
|
+
// otelHint: 'applyEvents',
|
|
570
|
+
// debugRefreshReason,
|
|
571
|
+
// },
|
|
572
|
+
// otelContext,
|
|
573
|
+
// )
|
|
574
|
+
}
|
|
596
575
|
} catch (e: any) {
|
|
597
576
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
|
598
577
|
|
|
@@ -642,7 +621,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
642
621
|
try {
|
|
643
622
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
644
623
|
|
|
645
|
-
// TODO: what to do about
|
|
624
|
+
// TODO: what to do about storage transaction here?
|
|
646
625
|
this.inMemoryDB.txn(() => {
|
|
647
626
|
for (const event of events) {
|
|
648
627
|
try {
|
|
@@ -675,20 +654,18 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
675
654
|
tablesToUpdate.push([tableRef!, null])
|
|
676
655
|
}
|
|
677
656
|
|
|
657
|
+
const debugRefreshReason = {
|
|
658
|
+
_tag: 'applyEvents' as const,
|
|
659
|
+
events: [...events].map((e) => ({ type: e.eventType, args: e.args })),
|
|
660
|
+
writeTables: [...writeTables],
|
|
661
|
+
}
|
|
678
662
|
// Update all table refs together in a batch, to only trigger one reactive update
|
|
679
|
-
this.graph.setRefs(
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
_tag: 'applyEvents',
|
|
686
|
-
events: [...events].map((e) => ({ type: e.eventType, args: e.args })),
|
|
687
|
-
writeTables: [...writeTables],
|
|
688
|
-
},
|
|
689
|
-
},
|
|
690
|
-
otelContext,
|
|
691
|
-
)
|
|
663
|
+
this.graph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext })
|
|
664
|
+
|
|
665
|
+
if (skipRefresh === false) {
|
|
666
|
+
// TODO update the graph
|
|
667
|
+
// this.graph.refresh({ debugRefreshReason, otelHint: 'applyEvents' }, otelContext)
|
|
668
|
+
}
|
|
692
669
|
} catch (e: any) {
|
|
693
670
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
|
694
671
|
} finally {
|
|
@@ -711,8 +688,9 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
711
688
|
{ attributes: { 'livestore.manualRefreshLabel': label } },
|
|
712
689
|
this.otel.applyEventsSpanContext,
|
|
713
690
|
(span) => {
|
|
714
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
715
|
-
|
|
691
|
+
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
692
|
+
// TODO update the graph
|
|
693
|
+
// this.graph.refresh({ otelHint: 'manualRefresh', debugRefreshReason: { _tag: 'manualRefresh' } }, otelContext)
|
|
716
694
|
span.end()
|
|
717
695
|
},
|
|
718
696
|
)
|
|
@@ -757,6 +735,15 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
757
735
|
}
|
|
758
736
|
},
|
|
759
737
|
},
|
|
738
|
+
|
|
739
|
+
RawSql: {
|
|
740
|
+
statement: ({ sql, writeTables }: { sql: string; writeTables: string[] }) => ({
|
|
741
|
+
sql,
|
|
742
|
+
writeTables,
|
|
743
|
+
argsAlreadyBound: false,
|
|
744
|
+
}),
|
|
745
|
+
prepareBindValues: ({ bindValues }) => bindValues,
|
|
746
|
+
},
|
|
760
747
|
}
|
|
761
748
|
|
|
762
749
|
const actionDefinition = actionDefinitions[eventType] ?? shouldNeverHappen(`Unknown event type: ${eventType}`)
|
|
@@ -765,20 +752,30 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
765
752
|
const eventWithId: LiveStoreEvent = { id: uuid(), type: eventType, args }
|
|
766
753
|
|
|
767
754
|
// Synchronously apply the event to the in-memory database
|
|
768
|
-
const { durationMs } = this.inMemoryDB.applyEvent(eventWithId, actionDefinition, otelContext)
|
|
755
|
+
// const { durationMs } = this.inMemoryDB.applyEvent(eventWithId, actionDefinition, otelContext)
|
|
756
|
+
const { statement, bindValues } = eventToSql(eventWithId, actionDefinition)
|
|
757
|
+
const { durationMs } = this.inMemoryDB.execute(
|
|
758
|
+
statement.sql,
|
|
759
|
+
prepareBindValues(bindValues, statement.sql),
|
|
760
|
+
statement.writeTables,
|
|
761
|
+
{
|
|
762
|
+
otelContext,
|
|
763
|
+
},
|
|
764
|
+
)
|
|
769
765
|
|
|
770
|
-
// Asynchronously apply the event to a persistent
|
|
771
|
-
if (this.
|
|
772
|
-
this.
|
|
766
|
+
// Asynchronously apply the event to a persistent storage (we're not awaiting this promise here)
|
|
767
|
+
if (this.storage !== undefined) {
|
|
768
|
+
// this.storage.applyEvent(eventWithId, actionDefinition, span)
|
|
769
|
+
this.storage.execute(statement.sql, prepareBindValues(bindValues, statement.sql), span)
|
|
773
770
|
}
|
|
774
771
|
|
|
775
772
|
// Uncomment to print a list of queries currently registered on the store
|
|
776
773
|
// console.log(JSON.parse(JSON.stringify([...this.queries].map((q) => `${labelForKey(q.componentKey)}/${q.label}`))))
|
|
777
774
|
|
|
778
|
-
const statement =
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
775
|
+
// const statement =
|
|
776
|
+
// typeof actionDefinition.statement === 'function'
|
|
777
|
+
// ? actionDefinition.statement(args)
|
|
778
|
+
// : actionDefinition.statement
|
|
782
779
|
|
|
783
780
|
span.end()
|
|
784
781
|
|
|
@@ -792,12 +789,12 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
792
789
|
* This should only be used for framework-internal purposes;
|
|
793
790
|
* all app writes should go through applyEvent.
|
|
794
791
|
*/
|
|
795
|
-
execute =
|
|
796
|
-
this.inMemoryDB.execute(query, params, writeTables)
|
|
792
|
+
execute = (query: string, params: ParamsObject = {}, writeTables?: string[]) => {
|
|
793
|
+
this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables)
|
|
797
794
|
|
|
798
|
-
if (this.
|
|
795
|
+
if (this.storage !== undefined) {
|
|
799
796
|
const parentSpan = otel.trace.getSpan(otel.context.active())
|
|
800
|
-
this.
|
|
797
|
+
this.storage.execute(query, prepareBindValues(params, query), parentSpan)
|
|
801
798
|
}
|
|
802
799
|
}
|
|
803
800
|
}
|
|
@@ -805,83 +802,95 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
805
802
|
/** Create a new LiveStore Store */
|
|
806
803
|
export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
|
|
807
804
|
schema,
|
|
808
|
-
|
|
805
|
+
loadStorage,
|
|
809
806
|
graphQLOptions,
|
|
810
807
|
otelTracer = makeNoopTracer(),
|
|
811
808
|
otelRootSpanContext = otel.context.active(),
|
|
812
809
|
boot,
|
|
810
|
+
sqlite3,
|
|
813
811
|
}: {
|
|
814
812
|
schema: Schema
|
|
815
|
-
|
|
813
|
+
loadStorage: () => StorageInit | Promise<StorageInit>
|
|
816
814
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
817
815
|
otelTracer?: otel.Tracer
|
|
818
816
|
otelRootSpanContext?: otel.Context
|
|
819
|
-
boot?: (
|
|
817
|
+
boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
|
|
818
|
+
sqlite3: Sqlite.Sqlite3Static
|
|
820
819
|
}): Promise<Store<TGraphQLContext>> => {
|
|
821
820
|
return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
|
|
822
821
|
try {
|
|
823
|
-
|
|
824
|
-
const backend = await createBackend(backendOptions, {
|
|
825
|
-
otelTracer: otelTracer ?? makeNoopTracer(),
|
|
826
|
-
parentSpan: otel.trace.getSpan(otelRootSpanContext ?? otel.context.active()) ?? makeNoopSpan(),
|
|
827
|
-
})
|
|
828
|
-
// if we're resetting the database, run boot here.
|
|
822
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
829
823
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
824
|
+
const storage = await otelTracer.startActiveSpan('storage:load', {}, otelContext, async (span) => {
|
|
825
|
+
try {
|
|
826
|
+
const init = await loadStorage()
|
|
827
|
+
const parentSpan = otel.trace.getSpan(otel.context.active()) ?? makeNoopSpan()
|
|
828
|
+
return init({ otelTracer, parentSpan })
|
|
829
|
+
} finally {
|
|
830
|
+
span.end()
|
|
831
|
+
}
|
|
832
|
+
})
|
|
833
833
|
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
834
|
+
const persistedData = await otelTracer.startActiveSpan(
|
|
835
|
+
'storage:getPersistedData',
|
|
836
|
+
{},
|
|
837
|
+
otelContext,
|
|
838
|
+
async (span) => {
|
|
839
|
+
try {
|
|
840
|
+
return await storage.getPersistedData(span)
|
|
841
|
+
} finally {
|
|
842
|
+
span.end()
|
|
843
|
+
}
|
|
844
|
+
},
|
|
838
845
|
)
|
|
839
|
-
const existingTables = existingTablesRaw.results.map((t: { name: string }) => t.name)
|
|
840
|
-
const missingTables = Object.keys(schema.tables).filter((tableName) => !existingTables.includes(tableName))
|
|
841
|
-
if (existingTables.length === 0) {
|
|
842
|
-
console.log('No existing tables found, loading from schema')
|
|
843
|
-
shouldResetDB = true
|
|
844
|
-
} else if (
|
|
845
|
-
missingTables.length > 0 &&
|
|
846
|
-
window.confirm(
|
|
847
|
-
`Existing DB is missing ${missingTables.length} tables: ${missingTables.join(
|
|
848
|
-
', ',
|
|
849
|
-
)}\n\nReset DB? This will reset all of the following tables to empty: ${Object.keys(schema).join(', ')}`,
|
|
850
|
-
)
|
|
851
|
-
) {
|
|
852
|
-
shouldResetDB = true
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
if (localStorage.getItem(RESET_DB_LOCAL_STORAGE_KEY) !== null) {
|
|
856
|
-
shouldResetDB = true
|
|
857
|
-
}
|
|
858
846
|
|
|
859
|
-
|
|
860
|
-
await loadSchema(backend, schema)
|
|
861
|
-
localStorage.removeItem(RESET_DB_LOCAL_STORAGE_KEY)
|
|
862
|
-
}
|
|
847
|
+
const db = InMemoryDatabase.load({ data: persistedData, otelTracer, otelRootSpanContext, sqlite3 })
|
|
863
848
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
849
|
+
// Proxy to `db` that also mirrors `execute` calls to `storage`
|
|
850
|
+
const dbProxy = new Proxy(db, {
|
|
851
|
+
get: (db, prop, receiver) => {
|
|
852
|
+
if (prop === 'execute') {
|
|
853
|
+
const execute: InMemoryDatabase['execute'] = (query, bindValues, writeTables, options) => {
|
|
854
|
+
storage.execute(query, bindValues, span)
|
|
855
|
+
return db.execute(query, bindValues, writeTables, options)
|
|
856
|
+
}
|
|
857
|
+
return execute
|
|
858
|
+
} else if (prop === 'select') {
|
|
859
|
+
// NOTE we're even proxying `select` calls here as some apps (e.g. Overtone) currently rely on this
|
|
860
|
+
// TODO remove this once we've migrated all apps to use `execute` instead of `select`
|
|
861
|
+
const select: InMemoryDatabase['select'] = (query, options = {}) => {
|
|
862
|
+
storage.execute(query, options.bindValues as any)
|
|
863
|
+
return db.select(query, options)
|
|
864
|
+
}
|
|
865
|
+
return select
|
|
866
|
+
} else {
|
|
867
|
+
return Reflect.get(db, prop, receiver)
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
})
|
|
867
871
|
|
|
868
|
-
|
|
869
|
-
await otelTracer.startActiveSpan('backend-getPersistedData', {}, otelContext, async (span) => {
|
|
872
|
+
otelTracer.startActiveSpan('migrateDb', {}, otelContext, async (span) => {
|
|
870
873
|
try {
|
|
871
|
-
|
|
874
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
875
|
+
migrateDb({ db: dbProxy, schema, otelContext })
|
|
872
876
|
} finally {
|
|
873
877
|
span.end()
|
|
874
878
|
}
|
|
875
879
|
})
|
|
876
880
|
|
|
877
|
-
|
|
878
|
-
|
|
881
|
+
if (boot !== undefined) {
|
|
882
|
+
const booting = boot(dbProxy, span)
|
|
883
|
+
// NOTE only awaiting if it's actually a promise to avoid unnecessary async/await
|
|
884
|
+
if (isPromise(booting)) {
|
|
885
|
+
await booting
|
|
886
|
+
}
|
|
887
|
+
}
|
|
879
888
|
|
|
880
889
|
// TODO: we can't apply the schema at this point, we've already loaded persisted data!
|
|
881
890
|
// Think about what to do about this case.
|
|
882
891
|
// await applySchema(db, schema)
|
|
883
892
|
return Store.createStore<TGraphQLContext>(
|
|
884
|
-
{ db, schema,
|
|
893
|
+
{ db, dbProxy, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext },
|
|
885
894
|
span,
|
|
886
895
|
)
|
|
887
896
|
} finally {
|
|
@@ -890,17 +899,17 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
|
|
|
890
899
|
})
|
|
891
900
|
}
|
|
892
901
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
902
|
+
const eventToSql = (
|
|
903
|
+
event: LiveStoreEvent,
|
|
904
|
+
eventDefinition: ActionDefinition,
|
|
905
|
+
): { statement: SQLWriteStatement; bindValues: ParamsObject } => {
|
|
906
|
+
const statement =
|
|
907
|
+
typeof eventDefinition.statement === 'function' ? eventDefinition.statement(event.args) : eventDefinition.statement
|
|
908
|
+
|
|
909
|
+
const prepareBindValues = eventDefinition.prepareBindValues ?? identity
|
|
910
|
+
|
|
911
|
+
const bindValues =
|
|
912
|
+
typeof eventDefinition.statement === 'function' && statement.argsAlreadyBound ? {} : prepareBindValues(event.args)
|
|
913
|
+
|
|
914
|
+
return { statement, bindValues }
|
|
906
915
|
}
|