@livestore/livestore 0.0.12 → 0.0.15
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 +7 -7
- 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 +25 -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/useLiveStoreComponent.test.d.ts +2 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +1 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js +78 -0
- package/dist/__tests__/react/useLiveStoreComponent.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 +197 -0
- package/dist/__tests__/reactive.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 +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -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/useGraphQL.d.ts +13 -0
- package/dist/react/useGraphQL.d.ts.map +1 -0
- package/dist/react/useGraphQL.js +85 -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 +317 -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 +38 -0
- package/dist/react/useQuery.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 +140 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +302 -0
- package/dist/reactive.js.map +1 -0
- package/dist/reactiveQueries/base-class.d.ts +27 -0
- package/dist/reactiveQueries/base-class.d.ts.map +1 -0
- package/dist/reactiveQueries/base-class.js +23 -0
- package/dist/reactiveQueries/base-class.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +25 -0
- package/dist/reactiveQueries/graphql.d.ts.map +1 -0
- package/dist/reactiveQueries/graphql.js +18 -0
- package/dist/reactiveQueries/graphql.js.map +1 -0
- package/dist/reactiveQueries/js.d.ts +19 -0
- package/dist/reactiveQueries/js.d.ts.map +1 -0
- package/dist/reactiveQueries/js.js +13 -0
- package/dist/reactiveQueries/js.js.map +1 -0
- package/dist/reactiveQueries/sql.d.ts +31 -0
- package/dist/reactiveQueries/sql.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.js +32 -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 +199 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +603 -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 +46 -19
- package/src/__tests__/react/fixture.tsx +23 -32
- package/src/__tests__/reactive.test.ts +3 -4
- package/src/effect/LiveStore.ts +22 -31
- package/src/events.ts +1 -1
- package/src/inMemoryDatabase.ts +115 -140
- package/src/index.ts +20 -20
- package/src/migrations.ts +119 -0
- package/src/otel.ts +0 -11
- package/src/react/LiveStoreProvider.tsx +24 -23
- package/src/react/index.ts +10 -1
- package/src/react/useGraphQL.ts +28 -2
- package/src/react/useLiveStoreComponent.ts +134 -50
- package/src/react/useQuery.ts +56 -0
- package/src/reactive.ts +6 -4
- package/src/reactiveQueries/base-class.ts +9 -3
- package/src/reactiveQueries/graphql.ts +4 -4
- package/src/reactiveQueries/js.ts +2 -2
- package/src/reactiveQueries/sql.ts +6 -6
- 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 +171 -98
- 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/{backends → storage}/utils/idb.ts +0 -0
package/src/store.ts
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
|
|
2
2
|
import { assertNever, makeNoopSpan, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
|
|
3
|
+
import { identity } from '@livestore/utils/effect'
|
|
3
4
|
import * as otel from '@opentelemetry/api'
|
|
4
5
|
import type { GraphQLSchema } from 'graphql'
|
|
5
6
|
import * as graphql from 'graphql'
|
|
6
7
|
import { uniqueId } from 'lodash-es'
|
|
7
8
|
import * as ReactDOM from 'react-dom'
|
|
9
|
+
import type * as Sqlite from 'sqlite-esm'
|
|
8
10
|
import { v4 as uuid } from 'uuid'
|
|
9
11
|
|
|
10
|
-
import type { Backend, BackendOptions } from './backends/index.js'
|
|
11
|
-
import { createBackend } from './backends/index.js'
|
|
12
12
|
import type { ComponentKey } from './componentKey.js'
|
|
13
13
|
import { tableNameForComponentKey } from './componentKey.js'
|
|
14
|
+
import type { QueryDefinition } from './effect/LiveStore.js'
|
|
14
15
|
import type { LiveStoreEvent } from './events.js'
|
|
15
16
|
import { InMemoryDatabase } from './inMemoryDatabase.js'
|
|
17
|
+
import { migrateDb } from './migrations.js'
|
|
16
18
|
import { getDurationMsFromSpan } from './otel.js'
|
|
17
|
-
import type {
|
|
19
|
+
import type { Atom, Ref } from './reactive.js'
|
|
18
20
|
import { ReactiveGraph } from './reactive.js'
|
|
19
21
|
import { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js'
|
|
20
22
|
import { LiveStoreJSQuery } from './reactiveQueries/js.js'
|
|
21
23
|
import { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
|
|
22
|
-
import type { ActionDefinition, GetActionArgs, Schema } from './schema.js'
|
|
23
|
-
import { componentStateTables
|
|
24
|
+
import type { ActionDefinition, GetActionArgs, Schema, SQLWriteStatement } from './schema.js'
|
|
25
|
+
import { componentStateTables } from './schema.js'
|
|
26
|
+
import type { Storage, StorageInit } from './storage/index.js'
|
|
24
27
|
import type { Bindable, ParamsObject } from './util.js'
|
|
25
|
-
import { sql } from './util.js'
|
|
28
|
+
import { isPromise, prepareBindValues, sql } from './util.js'
|
|
29
|
+
|
|
30
|
+
export type GetAtomResult = <T>(atom: Atom<T> | LiveStoreJSQuery<T>) => T
|
|
26
31
|
|
|
27
32
|
export type LiveStoreQuery<TResult extends Record<string, any> = any> =
|
|
28
33
|
| LiveStoreSQLQuery<TResult>
|
|
@@ -35,8 +40,6 @@ export type BaseGraphQLContext = {
|
|
|
35
40
|
otelContext?: otel.Context
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
export const RESET_DB_LOCAL_STORAGE_KEY = 'livestore-reset'
|
|
39
|
-
|
|
40
43
|
export type QueryResult<TQuery> = TQuery extends LiveStoreSQLQuery<infer R>
|
|
41
44
|
? ReadonlyArray<Readonly<R>>
|
|
42
45
|
: TQuery extends LiveStoreJSQuery<infer S>
|
|
@@ -54,8 +57,10 @@ export type GraphQLOptions<TContext> = {
|
|
|
54
57
|
|
|
55
58
|
export type StoreOptions<TGraphQLContext extends BaseGraphQLContext> = {
|
|
56
59
|
db: InMemoryDatabase
|
|
60
|
+
/** A `Proxy`d version of `db` except that it also mirrors `execute` calls to the storage */
|
|
61
|
+
dbProxy: InMemoryDatabase
|
|
57
62
|
schema: Schema
|
|
58
|
-
|
|
63
|
+
storage?: Storage
|
|
59
64
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
60
65
|
otelTracer: otel.Tracer
|
|
61
66
|
otelRootSpanContext: otel.Context
|
|
@@ -98,9 +103,11 @@ export type StoreOtel = {
|
|
|
98
103
|
queriesSpanContext: otel.Context
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
106
|
+
export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLContext> {
|
|
102
107
|
graph: ReactiveGraph<RefreshReason, QueryDebugInfo>
|
|
103
108
|
inMemoryDB: InMemoryDatabase
|
|
109
|
+
// TODO refactor
|
|
110
|
+
_proxyDb: InMemoryDatabase
|
|
104
111
|
schema: Schema
|
|
105
112
|
graphQLSchema?: GraphQLSchema
|
|
106
113
|
graphQLContext?: TGraphQLContext
|
|
@@ -111,18 +118,20 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
111
118
|
*/
|
|
112
119
|
tableRefs: { [key: string]: Ref<null> }
|
|
113
120
|
activeQueries: Set<LiveStoreQuery>
|
|
114
|
-
|
|
121
|
+
storage?: Storage
|
|
115
122
|
temporaryQueries: Set<LiveStoreQuery> | undefined
|
|
116
123
|
|
|
117
124
|
private constructor({
|
|
118
125
|
db,
|
|
126
|
+
dbProxy,
|
|
119
127
|
schema,
|
|
120
|
-
|
|
128
|
+
storage,
|
|
121
129
|
graphQLOptions,
|
|
122
130
|
otelTracer,
|
|
123
131
|
otelRootSpanContext,
|
|
124
132
|
}: StoreOptions<TGraphQLContext>) {
|
|
125
133
|
this.inMemoryDB = db
|
|
134
|
+
this._proxyDb = dbProxy
|
|
126
135
|
this.graph = new ReactiveGraph({
|
|
127
136
|
// TODO move this into React module
|
|
128
137
|
// Do all our updates inside a single React setState batch to avoid multiple UI re-renders
|
|
@@ -133,7 +142,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
133
142
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
134
143
|
this.tableRefs = {}
|
|
135
144
|
this.activeQueries = new Set()
|
|
136
|
-
this.
|
|
145
|
+
this.storage = storage
|
|
137
146
|
|
|
138
147
|
const applyEventsSpan = otelTracer.startSpan('LiveStore:applyEvents', {}, otelRootSpanContext)
|
|
139
148
|
const otelApplyEventsSpanContext = otel.trace.setSpan(otel.context.active(), applyEventsSpan)
|
|
@@ -186,7 +195,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
186
195
|
* NOTE The query is actually running (even if no one has subscribed to it yet) and will be kept up to date.
|
|
187
196
|
*/
|
|
188
197
|
querySQL = <TResult>(
|
|
189
|
-
genQueryString: (get:
|
|
198
|
+
genQueryString: string | ((get: GetAtomResult) => string),
|
|
190
199
|
{
|
|
191
200
|
queriedTables,
|
|
192
201
|
bindValues,
|
|
@@ -216,9 +225,17 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
216
225
|
|
|
217
226
|
const queryString$ = this.graph.makeThunk(
|
|
218
227
|
(get, addDebugInfo) => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
if (typeof genQueryString === 'function') {
|
|
229
|
+
const getAtom: GetAtomResult = (atom) => {
|
|
230
|
+
if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom)
|
|
231
|
+
return get(atom.results$)
|
|
232
|
+
}
|
|
233
|
+
const queryString = genQueryString(getAtom)
|
|
234
|
+
addDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString })
|
|
235
|
+
return queryString
|
|
236
|
+
} else {
|
|
237
|
+
return genQueryString
|
|
238
|
+
}
|
|
222
239
|
},
|
|
223
240
|
{ label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
|
|
224
241
|
otelContext,
|
|
@@ -229,10 +246,10 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
229
246
|
|
|
230
247
|
const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
231
248
|
|
|
232
|
-
const results$ = this.graph.makeThunk<TResult
|
|
249
|
+
const results$ = this.graph.makeThunk<ReadonlyArray<TResult>>(
|
|
233
250
|
(get, addDebugInfo) =>
|
|
234
251
|
this.otel.tracer.startActiveSpan(
|
|
235
|
-
'sql', // NOTE span name will be overridden further down
|
|
252
|
+
'sql:', // NOTE span name will be overridden further down
|
|
236
253
|
{},
|
|
237
254
|
otelContext,
|
|
238
255
|
(span) => {
|
|
@@ -250,12 +267,16 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
250
267
|
span.setAttribute('sql.query', sqlString)
|
|
251
268
|
span.updateName(`sql:${sqlString.slice(0, 50)}`)
|
|
252
269
|
|
|
253
|
-
const results = this.inMemoryDB.select(sqlString, {
|
|
270
|
+
const results = this.inMemoryDB.select<TResult>(sqlString, {
|
|
271
|
+
queriedTables,
|
|
272
|
+
bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
|
|
273
|
+
otelContext,
|
|
274
|
+
})
|
|
254
275
|
|
|
255
276
|
span.setAttribute('sql.rowsCount', results.length)
|
|
256
277
|
addDebugInfo({ _tag: 'sql', label: label ?? '', query: sqlString })
|
|
257
278
|
|
|
258
|
-
return results
|
|
279
|
+
return results
|
|
259
280
|
} finally {
|
|
260
281
|
span.end()
|
|
261
282
|
}
|
|
@@ -287,7 +308,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
287
308
|
)
|
|
288
309
|
|
|
289
310
|
queryJS = <TResult>(
|
|
290
|
-
genResults: (get:
|
|
311
|
+
genResults: (get: GetAtomResult) => TResult,
|
|
291
312
|
{
|
|
292
313
|
componentKey = globalComponentKey,
|
|
293
314
|
label = `js${uniqueId()}`,
|
|
@@ -299,8 +320,12 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
299
320
|
const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
300
321
|
const results$ = this.graph.makeThunk(
|
|
301
322
|
(get, addDebugInfo) => {
|
|
323
|
+
const getAtom: GetAtomResult = (atom) => {
|
|
324
|
+
if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom)
|
|
325
|
+
return get(atom.results$)
|
|
326
|
+
}
|
|
302
327
|
addDebugInfo({ _tag: 'js', label, query: genResults.toString() })
|
|
303
|
-
return genResults(
|
|
328
|
+
return genResults(getAtom)
|
|
304
329
|
},
|
|
305
330
|
{ label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } },
|
|
306
331
|
otelContext,
|
|
@@ -327,7 +352,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
327
352
|
|
|
328
353
|
queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
|
|
329
354
|
document: DocumentNode<TResult, TVariableValues>,
|
|
330
|
-
genVariableValues: (get:
|
|
355
|
+
genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
|
|
331
356
|
{
|
|
332
357
|
componentKey,
|
|
333
358
|
label,
|
|
@@ -354,7 +379,17 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
354
379
|
span.updateName(`queryGraphQL:${labelWithDefault}`)
|
|
355
380
|
|
|
356
381
|
const variableValues$ = this.graph.makeThunk(
|
|
357
|
-
|
|
382
|
+
(get) => {
|
|
383
|
+
if (typeof genVariableValues === 'function') {
|
|
384
|
+
const getAtom: GetAtomResult = (atom) => {
|
|
385
|
+
if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom)
|
|
386
|
+
return get(atom.results$)
|
|
387
|
+
}
|
|
388
|
+
return genVariableValues(getAtom)
|
|
389
|
+
} else {
|
|
390
|
+
return genVariableValues
|
|
391
|
+
}
|
|
392
|
+
},
|
|
358
393
|
{ label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } },
|
|
359
394
|
otelContext,
|
|
360
395
|
)
|
|
@@ -642,7 +677,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
642
677
|
try {
|
|
643
678
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
644
679
|
|
|
645
|
-
// TODO: what to do about
|
|
680
|
+
// TODO: what to do about storage transaction here?
|
|
646
681
|
this.inMemoryDB.txn(() => {
|
|
647
682
|
for (const event of events) {
|
|
648
683
|
try {
|
|
@@ -718,6 +753,13 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
718
753
|
)
|
|
719
754
|
}
|
|
720
755
|
|
|
756
|
+
// TODO get rid of this as part of new query definition approach https://www.notion.so/schickling/New-query-definition-approach-1097a78ef0e9495bac25f90417374756?pvs=4
|
|
757
|
+
runOnce = <TQueryDef extends QueryDefinition>(queryDef: TQueryDef): QueryResult<ReturnType<TQueryDef>> => {
|
|
758
|
+
return this.inTempQueryContext(() => {
|
|
759
|
+
return queryDef(this).results$.result
|
|
760
|
+
})
|
|
761
|
+
}
|
|
762
|
+
|
|
721
763
|
/**
|
|
722
764
|
* Apply an event to the store.
|
|
723
765
|
* Returns the tables that were affected by the event.
|
|
@@ -757,6 +799,15 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
757
799
|
}
|
|
758
800
|
},
|
|
759
801
|
},
|
|
802
|
+
|
|
803
|
+
RawSql: {
|
|
804
|
+
statement: ({ sql, writeTables }: { sql: string; writeTables: string[] }) => ({
|
|
805
|
+
sql,
|
|
806
|
+
writeTables,
|
|
807
|
+
argsAlreadyBound: false,
|
|
808
|
+
}),
|
|
809
|
+
prepareBindValues: ({ bindValues }) => bindValues,
|
|
810
|
+
},
|
|
760
811
|
}
|
|
761
812
|
|
|
762
813
|
const actionDefinition = actionDefinitions[eventType] ?? shouldNeverHappen(`Unknown event type: ${eventType}`)
|
|
@@ -765,20 +816,30 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
765
816
|
const eventWithId: LiveStoreEvent = { id: uuid(), type: eventType, args }
|
|
766
817
|
|
|
767
818
|
// Synchronously apply the event to the in-memory database
|
|
768
|
-
const { durationMs } = this.inMemoryDB.applyEvent(eventWithId, actionDefinition, otelContext)
|
|
819
|
+
// const { durationMs } = this.inMemoryDB.applyEvent(eventWithId, actionDefinition, otelContext)
|
|
820
|
+
const { statement, bindValues } = eventToSql(eventWithId, actionDefinition)
|
|
821
|
+
const { durationMs } = this.inMemoryDB.execute(
|
|
822
|
+
statement.sql,
|
|
823
|
+
prepareBindValues(bindValues, statement.sql),
|
|
824
|
+
statement.writeTables,
|
|
825
|
+
{
|
|
826
|
+
otelContext,
|
|
827
|
+
},
|
|
828
|
+
)
|
|
769
829
|
|
|
770
|
-
// Asynchronously apply the event to a persistent
|
|
771
|
-
if (this.
|
|
772
|
-
this.
|
|
830
|
+
// Asynchronously apply the event to a persistent storage (we're not awaiting this promise here)
|
|
831
|
+
if (this.storage !== undefined) {
|
|
832
|
+
// this.storage.applyEvent(eventWithId, actionDefinition, span)
|
|
833
|
+
this.storage.execute(statement.sql, prepareBindValues(bindValues, statement.sql), span)
|
|
773
834
|
}
|
|
774
835
|
|
|
775
836
|
// Uncomment to print a list of queries currently registered on the store
|
|
776
837
|
// console.log(JSON.parse(JSON.stringify([...this.queries].map((q) => `${labelForKey(q.componentKey)}/${q.label}`))))
|
|
777
838
|
|
|
778
|
-
const statement =
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
839
|
+
// const statement =
|
|
840
|
+
// typeof actionDefinition.statement === 'function'
|
|
841
|
+
// ? actionDefinition.statement(args)
|
|
842
|
+
// : actionDefinition.statement
|
|
782
843
|
|
|
783
844
|
span.end()
|
|
784
845
|
|
|
@@ -793,11 +854,11 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
793
854
|
* all app writes should go through applyEvent.
|
|
794
855
|
*/
|
|
795
856
|
execute = async (query: string, params: ParamsObject = {}, writeTables?: string[]) => {
|
|
796
|
-
this.inMemoryDB.execute(query, params, writeTables)
|
|
857
|
+
this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables)
|
|
797
858
|
|
|
798
|
-
if (this.
|
|
859
|
+
if (this.storage !== undefined) {
|
|
799
860
|
const parentSpan = otel.trace.getSpan(otel.context.active())
|
|
800
|
-
this.
|
|
861
|
+
this.storage.execute(query, prepareBindValues(params, query), parentSpan)
|
|
801
862
|
}
|
|
802
863
|
}
|
|
803
864
|
}
|
|
@@ -805,83 +866,95 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
805
866
|
/** Create a new LiveStore Store */
|
|
806
867
|
export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
|
|
807
868
|
schema,
|
|
808
|
-
|
|
869
|
+
loadStorage,
|
|
809
870
|
graphQLOptions,
|
|
810
871
|
otelTracer = makeNoopTracer(),
|
|
811
872
|
otelRootSpanContext = otel.context.active(),
|
|
812
873
|
boot,
|
|
874
|
+
sqlite3,
|
|
813
875
|
}: {
|
|
814
876
|
schema: Schema
|
|
815
|
-
|
|
877
|
+
loadStorage: () => StorageInit | Promise<StorageInit>
|
|
816
878
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
817
879
|
otelTracer?: otel.Tracer
|
|
818
880
|
otelRootSpanContext?: otel.Context
|
|
819
|
-
boot?: (
|
|
881
|
+
boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
|
|
882
|
+
sqlite3: Sqlite.Sqlite3Static
|
|
820
883
|
}): Promise<Store<TGraphQLContext>> => {
|
|
821
884
|
return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
|
|
822
885
|
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.
|
|
886
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
829
887
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
888
|
+
const storage = await otelTracer.startActiveSpan('storage:load', {}, otelContext, async (span) => {
|
|
889
|
+
try {
|
|
890
|
+
const init = await loadStorage()
|
|
891
|
+
const parentSpan = otel.trace.getSpan(otel.context.active()) ?? makeNoopSpan()
|
|
892
|
+
return init({ otelTracer, parentSpan })
|
|
893
|
+
} finally {
|
|
894
|
+
span.end()
|
|
895
|
+
}
|
|
896
|
+
})
|
|
833
897
|
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
898
|
+
const persistedData = await otelTracer.startActiveSpan(
|
|
899
|
+
'storage:getPersistedData',
|
|
900
|
+
{},
|
|
901
|
+
otelContext,
|
|
902
|
+
async (span) => {
|
|
903
|
+
try {
|
|
904
|
+
return await storage.getPersistedData(span)
|
|
905
|
+
} finally {
|
|
906
|
+
span.end()
|
|
907
|
+
}
|
|
908
|
+
},
|
|
838
909
|
)
|
|
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
910
|
|
|
859
|
-
|
|
860
|
-
await loadSchema(backend, schema)
|
|
861
|
-
localStorage.removeItem(RESET_DB_LOCAL_STORAGE_KEY)
|
|
862
|
-
}
|
|
911
|
+
const db = InMemoryDatabase.load({ data: persistedData, otelTracer, otelRootSpanContext, sqlite3 })
|
|
863
912
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
913
|
+
// Proxy to `db` that also mirrors `execute` calls to `storage`
|
|
914
|
+
const dbProxy = new Proxy(db, {
|
|
915
|
+
get: (db, prop, receiver) => {
|
|
916
|
+
if (prop === 'execute') {
|
|
917
|
+
const execute: InMemoryDatabase['execute'] = (query, bindValues, writeTables, options) => {
|
|
918
|
+
storage.execute(query, bindValues, span)
|
|
919
|
+
return db.execute(query, bindValues, writeTables, options)
|
|
920
|
+
}
|
|
921
|
+
return execute
|
|
922
|
+
} else if (prop === 'select') {
|
|
923
|
+
// NOTE we're even proxying `select` calls here as some apps (e.g. Overtone) currently rely on this
|
|
924
|
+
// TODO remove this once we've migrated all apps to use `execute` instead of `select`
|
|
925
|
+
const select: InMemoryDatabase['select'] = (query, options = {}) => {
|
|
926
|
+
storage.execute(query, options.bindValues as any)
|
|
927
|
+
return db.select(query, options)
|
|
928
|
+
}
|
|
929
|
+
return select
|
|
930
|
+
} else {
|
|
931
|
+
return Reflect.get(db, prop, receiver)
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
})
|
|
867
935
|
|
|
868
|
-
|
|
869
|
-
await otelTracer.startActiveSpan('backend-getPersistedData', {}, otelContext, async (span) => {
|
|
936
|
+
otelTracer.startActiveSpan('migrateDb', {}, otelContext, async (span) => {
|
|
870
937
|
try {
|
|
871
|
-
|
|
938
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
939
|
+
migrateDb({ db: dbProxy, schema, otelContext })
|
|
872
940
|
} finally {
|
|
873
941
|
span.end()
|
|
874
942
|
}
|
|
875
943
|
})
|
|
876
944
|
|
|
877
|
-
|
|
878
|
-
|
|
945
|
+
if (boot !== undefined) {
|
|
946
|
+
const booting = boot(dbProxy, span)
|
|
947
|
+
// NOTE only awaiting if it's actually a promise to avoid unnecessary async/await
|
|
948
|
+
if (isPromise(booting)) {
|
|
949
|
+
await booting
|
|
950
|
+
}
|
|
951
|
+
}
|
|
879
952
|
|
|
880
953
|
// TODO: we can't apply the schema at this point, we've already loaded persisted data!
|
|
881
954
|
// Think about what to do about this case.
|
|
882
955
|
// await applySchema(db, schema)
|
|
883
956
|
return Store.createStore<TGraphQLContext>(
|
|
884
|
-
{ db, schema,
|
|
957
|
+
{ db, dbProxy, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext },
|
|
885
958
|
span,
|
|
886
959
|
)
|
|
887
960
|
} finally {
|
|
@@ -890,17 +963,17 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
|
|
|
890
963
|
})
|
|
891
964
|
}
|
|
892
965
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
966
|
+
const eventToSql = (
|
|
967
|
+
event: LiveStoreEvent,
|
|
968
|
+
eventDefinition: ActionDefinition,
|
|
969
|
+
): { statement: SQLWriteStatement; bindValues: ParamsObject } => {
|
|
970
|
+
const statement =
|
|
971
|
+
typeof eventDefinition.statement === 'function' ? eventDefinition.statement(event.args) : eventDefinition.statement
|
|
972
|
+
|
|
973
|
+
const prepareBindValues = eventDefinition.prepareBindValues ?? identity
|
|
974
|
+
|
|
975
|
+
const bindValues =
|
|
976
|
+
typeof eventDefinition.statement === 'function' && statement.argsAlreadyBound ? {} : prepareBindValues(event.args)
|
|
977
|
+
|
|
978
|
+
return { statement, bindValues }
|
|
906
979
|
}
|
package/src/util.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
/// <reference lib="es2022" />
|
|
2
|
+
|
|
3
|
+
import type { Brand } from '@livestore/utils/effect'
|
|
4
|
+
|
|
1
5
|
export type ParamsObject = Record<string, SqlValue>
|
|
2
|
-
export type SqlValue = string | number | Uint8Array
|
|
6
|
+
export type SqlValue = string | number | Uint8Array | null
|
|
3
7
|
|
|
4
8
|
export type Bindable = SqlValue[] | ParamsObject
|
|
5
9
|
|
|
10
|
+
export type PreparedBindValues = Brand.Branded<Bindable, 'PreparedBindValues'>
|
|
11
|
+
|
|
6
12
|
/**
|
|
7
13
|
* This is a tag function for tagged literals.
|
|
8
14
|
* it lets us get syntax highlighting on SQL queries in VSCode, but
|
|
@@ -23,7 +29,9 @@ export const sql = (template: TemplateStringsArray, ...args: unknown[]): string
|
|
|
23
29
|
/* because rusqlite doesn't allow unused named params
|
|
24
30
|
/* TODO: Search for unused params via proper parsing, not string search
|
|
25
31
|
**/
|
|
26
|
-
export const prepareBindValues = (values:
|
|
32
|
+
export const prepareBindValues = (values: Bindable, statement: string): PreparedBindValues => {
|
|
33
|
+
if (Array.isArray(values)) return values as PreparedBindValues
|
|
34
|
+
|
|
27
35
|
const result: ParamsObject = {}
|
|
28
36
|
for (const [key, value] of Object.entries(values)) {
|
|
29
37
|
if (statement.includes(key)) {
|
|
@@ -31,7 +39,7 @@ export const prepareBindValues = (values: ParamsObject, statement: string): Para
|
|
|
31
39
|
}
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
return result
|
|
42
|
+
return result as PreparedBindValues
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
/**
|
|
@@ -57,3 +65,5 @@ export const objectToString = (error: any): string => {
|
|
|
57
65
|
return 'Error while printing error: ' + e
|
|
58
66
|
}
|
|
59
67
|
}
|
|
68
|
+
|
|
69
|
+
export const isPromise = (value: any): value is Promise<unknown> => typeof value?.then === 'function'
|
package/tsconfig.json
CHANGED
package/src/backends/base.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
import { errorToString } from '@livestore/utils'
|
|
3
|
-
import { identity } from '@livestore/utils/effect'
|
|
4
|
-
import * as otel from '@opentelemetry/api'
|
|
5
|
-
|
|
6
|
-
import type { LiveStoreEvent } from '../events.js'
|
|
7
|
-
// import { EVENTS_TABLE_NAME } from '../events.js'
|
|
8
|
-
import type { ActionDefinition } from '../schema.js'
|
|
9
|
-
import type { ParamsObject } from '../util.js'
|
|
10
|
-
import type { Backend, SelectResponse } from './index.js'
|
|
11
|
-
|
|
12
|
-
export abstract class BaseBackend implements Backend {
|
|
13
|
-
abstract otelTracer: otel.Tracer
|
|
14
|
-
|
|
15
|
-
select = async <T = any>(query: string, bindValues?: ParamsObject): Promise<SelectResponse<T>> => {
|
|
16
|
-
throw new Error('Method not implemented.')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
execute = (query: string, bindValues?: ParamsObject, parentSpan?: otel.Span): void => {
|
|
20
|
-
throw new Error('Method not implemented.')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
getPersistedData = async (parentSpan?: otel.Span): Promise<Uint8Array> => {
|
|
24
|
-
throw new Error('Method not implemented.')
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// TODO move `applyEvent` logic to Store and only call `execute` here
|
|
28
|
-
applyEvent = (event: LiveStoreEvent, eventDefinition: ActionDefinition, parentSpan?: otel.Span): void => {
|
|
29
|
-
const ctx = parentSpan ? otel.trace.setSpan(otel.context.active(), parentSpan) : otel.context.active()
|
|
30
|
-
this.otelTracer.startActiveSpan('LiveStore:backend:applyEvent', {}, ctx, (span) => {
|
|
31
|
-
try {
|
|
32
|
-
// Careful: this SQL statement is duplicated in the backend.
|
|
33
|
-
// Remember to update it in src-tauri/src/store.rs:apply_event as well.
|
|
34
|
-
// await this.execute(sql`insert into ${EVENTS_TABLE_NAME} (id, type, args) values ($id, $type, $args)`, {
|
|
35
|
-
// id: event.id,
|
|
36
|
-
// type: event.type,
|
|
37
|
-
// args: JSON.stringify(event.args ?? {}),
|
|
38
|
-
// })
|
|
39
|
-
|
|
40
|
-
const statement =
|
|
41
|
-
typeof eventDefinition.statement === 'function'
|
|
42
|
-
? eventDefinition.statement(event.args)
|
|
43
|
-
: eventDefinition.statement
|
|
44
|
-
|
|
45
|
-
const prepareBindValues = eventDefinition.prepareBindValues ?? identity
|
|
46
|
-
|
|
47
|
-
const bindValues =
|
|
48
|
-
typeof eventDefinition.statement === 'function' && statement.argsAlreadyBound
|
|
49
|
-
? {}
|
|
50
|
-
: prepareBindValues(event.args)
|
|
51
|
-
|
|
52
|
-
span.setAttributes({
|
|
53
|
-
'livestore.statement.sql': statement.sql,
|
|
54
|
-
'livestore.statement.writeTables': statement.writeTables,
|
|
55
|
-
'livestore.statement.bindVales': JSON.stringify(bindValues),
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
this.execute(statement.sql, bindValues, span)
|
|
59
|
-
} catch (e: any) {
|
|
60
|
-
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: errorToString(e) })
|
|
61
|
-
throw e
|
|
62
|
-
} finally {
|
|
63
|
-
span.end()
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
}
|