@livestore/livestore 0.0.13 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +8 -9
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/__tests__/reactive.test.js +3 -4
- package/dist/__tests__/reactive.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +3 -9
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +11 -7
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/inMemoryDatabase.d.ts +15 -19
- package/dist/inMemoryDatabase.d.ts.map +1 -1
- package/dist/inMemoryDatabase.js +2 -9
- package/dist/inMemoryDatabase.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/migrations.d.ts +7 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +18 -13
- package/dist/migrations.js.map +1 -1
- package/dist/react/LiveStoreProvider.d.ts +1 -3
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +13 -10
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/useGraphQL.d.ts +3 -1
- package/dist/react/useGraphQL.d.ts.map +1 -1
- package/dist/react/useGraphQL.js +19 -1
- package/dist/react/useGraphQL.js.map +1 -1
- package/dist/react/useLiveStoreComponent.d.ts +12 -12
- package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
- package/dist/react/useLiveStoreComponent.js +23 -7
- package/dist/react/useLiveStoreComponent.js.map +1 -1
- 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/reactive.d.ts.map +1 -1
- package/dist/reactive.js +3 -3
- package/dist/reactive.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +6 -3
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +1 -0
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graphql.d.ts +4 -4
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +2 -2
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +5 -5
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +2 -2
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/schema.d.ts +0 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -6
- package/dist/schema.js.map +1 -1
- package/dist/storage/in-memory/index.d.ts +2 -2
- package/dist/storage/in-memory/index.d.ts.map +1 -1
- package/dist/storage/in-memory/index.js.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/tauri/index.d.ts +2 -2
- package/dist/storage/tauri/index.d.ts.map +1 -1
- package/dist/storage/tauri/index.js.map +1 -1
- package/dist/storage/web-worker/index.d.ts +4 -4
- package/dist/storage/web-worker/index.d.ts.map +1 -1
- package/dist/storage/web-worker/index.js +3 -5
- package/dist/storage/web-worker/index.js.map +1 -1
- package/dist/storage/web-worker/worker.js +2 -2
- package/dist/storage/web-worker/worker.js.map +1 -1
- package/dist/store.d.ts +14 -7
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +80 -46
- package/dist/store.js.map +1 -1
- package/dist/util.d.ts +3 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +2 -0
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/react/fixture.tsx +9 -9
- package/src/__tests__/reactive.test.ts +3 -4
- package/src/effect/LiveStore.ts +14 -18
- package/src/inMemoryDatabase.ts +20 -28
- package/src/index.ts +10 -4
- package/src/migrations.ts +39 -21
- package/src/react/LiveStoreProvider.tsx +13 -16
- package/src/react/index.ts +1 -1
- package/src/react/useGraphQL.ts +28 -2
- package/src/react/useLiveStoreComponent.ts +50 -24
- 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 +2 -5
- package/src/storage/in-memory/index.ts +2 -2
- package/src/storage/index.ts +2 -2
- package/src/storage/tauri/index.ts +2 -2
- package/src/storage/web-worker/index.ts +6 -8
- package/src/storage/web-worker/worker.ts +2 -2
- package/src/store.ts +99 -59
- package/src/util.ts +8 -2
- package/dist/backends/base.d.ts +0 -13
- package/dist/backends/base.d.ts.map +0 -1
- package/dist/backends/base.js +0 -53
- package/dist/backends/base.js.map +0 -1
- package/dist/backends/in-memory/index.d.ts +0 -22
- package/dist/backends/in-memory/index.d.ts.map +0 -1
- package/dist/backends/in-memory/index.js +0 -45
- package/dist/backends/in-memory/index.js.map +0 -1
- package/dist/backends/index.d.ts +0 -41
- package/dist/backends/index.d.ts.map +0 -1
- package/dist/backends/index.js +0 -16
- package/dist/backends/index.js.map +0 -1
- package/dist/backends/tauri/index.d.ts +0 -21
- package/dist/backends/tauri/index.d.ts.map +0 -1
- package/dist/backends/tauri/index.js +0 -48
- package/dist/backends/tauri/index.js.map +0 -1
- package/dist/backends/utils/idb.d.ts +0 -10
- package/dist/backends/utils/idb.d.ts.map +0 -1
- package/dist/backends/utils/idb.js +0 -58
- package/dist/backends/utils/idb.js.map +0 -1
- package/dist/backends/web-worker/index.d.ts +0 -26
- package/dist/backends/web-worker/index.d.ts.map +0 -1
- package/dist/backends/web-worker/index.js +0 -63
- package/dist/backends/web-worker/index.js.map +0 -1
- package/dist/backends/web-worker/worker.d.ts +0 -17
- package/dist/backends/web-worker/worker.d.ts.map +0 -1
- package/dist/backends/web-worker/worker.js +0 -139
- package/dist/backends/web-worker/worker.js.map +0 -1
- package/dist/react/useGlobalQuery.d.ts +0 -3
- package/dist/react/useGlobalQuery.d.ts.map +0 -1
- package/dist/react/useGlobalQuery.js +0 -23
- package/dist/react/useGlobalQuery.js.map +0 -1
- package/dist/storage/base.d.ts +0 -10
- package/dist/storage/base.d.ts.map +0 -1
- package/dist/storage/base.js +0 -14
- package/dist/storage/base.js.map +0 -1
- package/src/react/useGlobalQuery.ts +0 -37
|
@@ -5,13 +5,13 @@ import type { Store } from '../store.js'
|
|
|
5
5
|
|
|
6
6
|
export type UnsubscribeQuery = () => void
|
|
7
7
|
|
|
8
|
-
export abstract class LiveStoreQueryBase {
|
|
8
|
+
export abstract class LiveStoreQueryBase<TResult> {
|
|
9
9
|
/** The key for the associated component */
|
|
10
10
|
componentKey: ComponentKey
|
|
11
11
|
/** Human-readable label for the query for debugging */
|
|
12
12
|
label: string
|
|
13
13
|
/** A pointer back to the store containing this query */
|
|
14
|
-
store: Store
|
|
14
|
+
store: Store
|
|
15
15
|
/** Otel Span is started in LiveStore store but ended in this query */
|
|
16
16
|
otelContext: otel.Context
|
|
17
17
|
|
|
@@ -26,7 +26,7 @@ export abstract class LiveStoreQueryBase {
|
|
|
26
26
|
}: {
|
|
27
27
|
componentKey: ComponentKey
|
|
28
28
|
label: string
|
|
29
|
-
store: Store
|
|
29
|
+
store: Store
|
|
30
30
|
otelContext: otel.Context
|
|
31
31
|
}) {
|
|
32
32
|
this.componentKey = componentKey
|
|
@@ -46,4 +46,10 @@ export abstract class LiveStoreQueryBase {
|
|
|
46
46
|
unsubscribe()
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
subscribe = (
|
|
51
|
+
onNewValue: (value: TResult) => void,
|
|
52
|
+
onSubsubscribe?: () => void,
|
|
53
|
+
options?: { label?: string } | undefined,
|
|
54
|
+
): (() => void) => this.store.subscribe(this as any, onNewValue as any, onSubsubscribe, options)
|
|
49
55
|
}
|
|
@@ -2,8 +2,8 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-
|
|
|
2
2
|
import type * as otel from '@opentelemetry/api'
|
|
3
3
|
|
|
4
4
|
import type { ComponentKey } from '../componentKey.js'
|
|
5
|
-
import type {
|
|
6
|
-
import type { BaseGraphQLContext, Store } from '../store.js'
|
|
5
|
+
import type { Thunk } from '../reactive.js'
|
|
6
|
+
import type { BaseGraphQLContext, GetAtomResult, Store } from '../store.js'
|
|
7
7
|
import { LiveStoreQueryBase } from './base-class.js'
|
|
8
8
|
import type { LiveStoreJSQuery } from './js.js'
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ export class LiveStoreGraphQLQuery<
|
|
|
11
11
|
TResult extends Record<string, any>,
|
|
12
12
|
VariableValues extends Record<string, any>,
|
|
13
13
|
TContext extends BaseGraphQLContext,
|
|
14
|
-
> extends LiveStoreQueryBase {
|
|
14
|
+
> extends LiveStoreQueryBase<TResult> {
|
|
15
15
|
_tag: 'graphql' = 'graphql'
|
|
16
16
|
|
|
17
17
|
/** The abstract GraphQL query */
|
|
@@ -39,7 +39,7 @@ export class LiveStoreGraphQLQuery<
|
|
|
39
39
|
this.results$ = results$
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
pipe = <U>(f: (x: TResult, get:
|
|
42
|
+
pipe = <U>(f: (x: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
|
|
43
43
|
this.store.queryJS(
|
|
44
44
|
(get) => {
|
|
45
45
|
const results = get(this.results$)
|
|
@@ -5,7 +5,7 @@ import type { GetAtom, Thunk } from '../reactive.js'
|
|
|
5
5
|
import type { Store } from '../store.js'
|
|
6
6
|
import { LiveStoreQueryBase } from './base-class.js'
|
|
7
7
|
|
|
8
|
-
export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase {
|
|
8
|
+
export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase<TResult> {
|
|
9
9
|
_tag: 'js' = 'js'
|
|
10
10
|
/** A reactive thunk representing the query results */
|
|
11
11
|
results$: Thunk<TResult>
|
|
@@ -17,7 +17,7 @@ export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase {
|
|
|
17
17
|
results$: Thunk<TResult>
|
|
18
18
|
componentKey: ComponentKey
|
|
19
19
|
label: string
|
|
20
|
-
store: Store
|
|
20
|
+
store: Store
|
|
21
21
|
otelContext: otel.Context
|
|
22
22
|
}) {
|
|
23
23
|
super(baseProps)
|
|
@@ -7,12 +7,12 @@ import { LiveStoreQueryBase } from './base-class.js'
|
|
|
7
7
|
import type { LiveStoreJSQuery } from './js.js'
|
|
8
8
|
|
|
9
9
|
/* An object encapsulating a reactive SQL query */
|
|
10
|
-
export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
|
|
10
|
+
export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase<Row> {
|
|
11
11
|
_tag: 'sql' = 'sql'
|
|
12
12
|
/** A reactive thunk representing the query text */
|
|
13
13
|
queryString$: Thunk<string>
|
|
14
14
|
/** A reactive thunk representing the query results */
|
|
15
|
-
results$: Thunk<Row
|
|
15
|
+
results$: Thunk<ReadonlyArray<Row>>
|
|
16
16
|
|
|
17
17
|
constructor({
|
|
18
18
|
queryString$,
|
|
@@ -20,10 +20,10 @@ export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
|
|
|
20
20
|
...baseProps
|
|
21
21
|
}: {
|
|
22
22
|
queryString$: Thunk<string>
|
|
23
|
-
results$: Thunk<Row
|
|
23
|
+
results$: Thunk<ReadonlyArray<Row>>
|
|
24
24
|
componentKey: ComponentKey
|
|
25
25
|
label: string
|
|
26
|
-
store: Store
|
|
26
|
+
store: Store
|
|
27
27
|
otelContext: otel.Context
|
|
28
28
|
}) {
|
|
29
29
|
super(baseProps)
|
|
@@ -36,11 +36,11 @@ export class LiveStoreSQLQuery<Row> extends LiveStoreQueryBase {
|
|
|
36
36
|
* Returns a new reactive query that contains the result of
|
|
37
37
|
* running an arbitrary JS computation on the results of this SQL query.
|
|
38
38
|
*/
|
|
39
|
-
pipe = <U>(
|
|
39
|
+
pipe = <U>(fn: (result: ReadonlyArray<Row>, get: GetAtom) => U): LiveStoreJSQuery<U> =>
|
|
40
40
|
this.store.queryJS(
|
|
41
41
|
(get) => {
|
|
42
42
|
const results = get(this.results$)
|
|
43
|
-
return
|
|
43
|
+
return fn(results, get)
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
componentKey: this.componentKey,
|
package/src/schema.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type ComponentStateSchema = SqliteDsl.TableDefinition<any, any> & {
|
|
|
38
38
|
|
|
39
39
|
// TODO get rid of "side effect" in this function (via explicit register fn)
|
|
40
40
|
export const defineComponentStateSchema = <TName extends string, TColumns extends SqliteDsl.Columns>(
|
|
41
|
+
// TODO get rid of the `name` param here and use the `componentKey` name instead
|
|
41
42
|
name: TName,
|
|
42
43
|
columns: TColumns,
|
|
43
44
|
): SqliteDsl.TableDefinition<
|
|
@@ -92,7 +93,6 @@ export type ActionDefinitions<TArgsMap extends Record<string, any>> = {
|
|
|
92
93
|
[key in keyof TArgsMap]: ActionDefinition<TArgsMap[key]>
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
export const EVENT_CURSOR_TABLE = '__livestore_event_cursor'
|
|
96
96
|
export const SCHEMA_META_TABLE = '__livestore_schema'
|
|
97
97
|
|
|
98
98
|
const schemaMetaTable = SqliteDsl.table(SCHEMA_META_TABLE, {
|
|
@@ -110,10 +110,6 @@ export const systemTables = {
|
|
|
110
110
|
// type: SqliteDsl.text({ nullable: false }),
|
|
111
111
|
// args: SqliteDsl.text({ nullable: false }),
|
|
112
112
|
// }).ast,
|
|
113
|
-
[EVENT_CURSOR_TABLE]: SqliteDsl.table(EVENT_CURSOR_TABLE, {
|
|
114
|
-
id: SqliteDsl.text({ primaryKey: true }),
|
|
115
|
-
cursor: SqliteDsl.text({ nullable: false }),
|
|
116
|
-
}).ast,
|
|
117
113
|
[SCHEMA_META_TABLE]: schemaMetaTable.ast,
|
|
118
114
|
} satisfies TableDefinitions
|
|
119
115
|
|
|
@@ -138,6 +134,7 @@ type RecordValues<T> = T extends Record<string, infer V> ? V : never
|
|
|
138
134
|
|
|
139
135
|
export type GetActionArgs<A> = A extends ActionDefinition<infer TArgs> ? TArgs : never
|
|
140
136
|
|
|
137
|
+
// TODO get rid of this
|
|
141
138
|
declare global {
|
|
142
139
|
// NOTE Can be extended
|
|
143
140
|
interface LiveStoreActionDefinitionsTypes {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as otel from '@opentelemetry/api'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { PreparedBindValues } from '../../util.js'
|
|
4
4
|
import type { Storage, StorageOtelProps } from '../index.js'
|
|
5
5
|
|
|
6
6
|
export type StorageOptionsWebInMemory = {
|
|
@@ -15,7 +15,7 @@ export class InMemoryStorage implements Storage {
|
|
|
15
15
|
return ({ otelTracer }: StorageOtelProps) => new InMemoryStorage(otelTracer)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
execute = (_query: string, _bindValues?:
|
|
18
|
+
execute = (_query: string, _bindValues?: PreparedBindValues): void => {}
|
|
19
19
|
|
|
20
20
|
getPersistedData = async (): Promise<Uint8Array> => new Uint8Array()
|
|
21
21
|
}
|
package/src/storage/index.ts
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
import type * as otel from '@opentelemetry/api'
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type { PreparedBindValues } from '../util.js'
|
|
12
12
|
|
|
13
13
|
export type StorageInit = (otelProps: StorageOtelProps) => Promise<Storage> | Storage
|
|
14
14
|
|
|
15
15
|
export interface Storage {
|
|
16
|
-
execute(query: string, bindValues?:
|
|
16
|
+
execute(query: string, bindValues?: PreparedBindValues, parentSpan?: otel.Span): void
|
|
17
17
|
|
|
18
18
|
/** Return a snapshot of persisted data from the storage */
|
|
19
19
|
getPersistedData(parentSpan?: otel.Span): Promise<Uint8Array>
|
|
@@ -2,7 +2,7 @@ import { getTraceParentHeader } from '@livestore/utils'
|
|
|
2
2
|
import type * as otel from '@opentelemetry/api'
|
|
3
3
|
import { invoke } from '@tauri-apps/api'
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { PreparedBindValues } from '../../util.js'
|
|
6
6
|
import { prepareBindValues } from '../../util.js'
|
|
7
7
|
import type { Storage, StorageOtelProps } from '../index.js'
|
|
8
8
|
|
|
@@ -28,7 +28,7 @@ export class TauriStorage implements Storage {
|
|
|
28
28
|
return new TauriStorage(dbFilePath, dbDirPath, otelTracer, parentSpan)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
execute = (query: string, bindValues?:
|
|
31
|
+
execute = (query: string, bindValues?: PreparedBindValues, parentSpan?: otel.Span): void => {
|
|
32
32
|
// console.log({ query, bindValues, prepared: prepareBindValues(bindValues ?? {}, query) })
|
|
33
33
|
void invoke('execute', {
|
|
34
34
|
dbName: this.dbFilePath,
|
|
@@ -2,8 +2,7 @@ import { casesHandled } from '@livestore/utils'
|
|
|
2
2
|
import type * as otel from '@opentelemetry/api'
|
|
3
3
|
import * as Comlink from 'comlink'
|
|
4
4
|
|
|
5
|
-
import type {
|
|
6
|
-
import { prepareBindValues } from '../../util.js'
|
|
5
|
+
import type { PreparedBindValues } from '../../util.js'
|
|
7
6
|
import type { Storage, StorageOtelProps } from '../index.js'
|
|
8
7
|
import { IDB } from '../utils/idb.js'
|
|
9
8
|
import type { WrappedWorker } from './worker.js'
|
|
@@ -13,7 +12,7 @@ export type StorageType = 'opfs' | 'indexeddb'
|
|
|
13
12
|
export type StorageOptionsWeb = {
|
|
14
13
|
/** Specifies where to persist data for this storage */
|
|
15
14
|
type: StorageType
|
|
16
|
-
|
|
15
|
+
fileName: string
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
export class WebWorkerStorage implements Storage {
|
|
@@ -21,7 +20,7 @@ export class WebWorkerStorage implements Storage {
|
|
|
21
20
|
options: StorageOptionsWeb
|
|
22
21
|
otelTracer: otel.Tracer
|
|
23
22
|
|
|
24
|
-
executionBacklog: { query: string; bindValues?:
|
|
23
|
+
executionBacklog: { query: string; bindValues?: PreparedBindValues }[] = []
|
|
25
24
|
executionPromise: Promise<void> | undefined
|
|
26
25
|
|
|
27
26
|
private constructor({
|
|
@@ -61,8 +60,7 @@ export class WebWorkerStorage implements Storage {
|
|
|
61
60
|
})
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
execute = (query: string,
|
|
65
|
-
const bindValues = prepareBindValues(bindValues_ ?? {}, query)
|
|
63
|
+
execute = (query: string, bindValues?: PreparedBindValues) => {
|
|
66
64
|
this.executionBacklog.push({ query, bindValues })
|
|
67
65
|
|
|
68
66
|
// Instead of sending the queries to the worker immediately, we wait a bit and batch them up (which reduces the number of messages sent to the worker)
|
|
@@ -91,7 +89,7 @@ const getPersistedData = async (options: StorageOptionsWeb): Promise<Uint8Array>
|
|
|
91
89
|
case 'opfs': {
|
|
92
90
|
try {
|
|
93
91
|
const rootHandle = await navigator.storage.getDirectory()
|
|
94
|
-
const fileHandle = await rootHandle.getFileHandle(options.
|
|
92
|
+
const fileHandle = await rootHandle.getFileHandle(options.fileName + '.db')
|
|
95
93
|
const file = await fileHandle.getFile()
|
|
96
94
|
const buffer = await file.arrayBuffer()
|
|
97
95
|
const data = new Uint8Array(buffer)
|
|
@@ -107,7 +105,7 @@ const getPersistedData = async (options: StorageOptionsWeb): Promise<Uint8Array>
|
|
|
107
105
|
}
|
|
108
106
|
|
|
109
107
|
case 'indexeddb': {
|
|
110
|
-
const idb = new IDB(options.
|
|
108
|
+
const idb = new IDB(options.fileName)
|
|
111
109
|
|
|
112
110
|
return (await idb.get('db')) ?? new Uint8Array()
|
|
113
111
|
}
|
|
@@ -46,7 +46,7 @@ const initialize = async (options: StorageOptionsWeb) => {
|
|
|
46
46
|
switch (options.type) {
|
|
47
47
|
case 'opfs': {
|
|
48
48
|
try {
|
|
49
|
-
db = new sqlite3.oo1.OpfsDb(fullyQualifiedFilename(options.
|
|
49
|
+
db = new sqlite3.oo1.OpfsDb(fullyQualifiedFilename(options.fileName)) // , 'c'
|
|
50
50
|
} catch (e) {
|
|
51
51
|
debugger
|
|
52
52
|
}
|
|
@@ -55,7 +55,7 @@ const initialize = async (options: StorageOptionsWeb) => {
|
|
|
55
55
|
case 'indexeddb': {
|
|
56
56
|
try {
|
|
57
57
|
db = new sqlite3.oo1.DB({ filename: ':memory:', flags: 'c' })
|
|
58
|
-
idb = new IDB(options.
|
|
58
|
+
idb = new IDB(options.fileName)
|
|
59
59
|
|
|
60
60
|
const bytes = await idb.get('db')
|
|
61
61
|
|
package/src/store.ts
CHANGED
|
@@ -6,16 +6,17 @@ import type { GraphQLSchema } from 'graphql'
|
|
|
6
6
|
import * as graphql from 'graphql'
|
|
7
7
|
import { uniqueId } from 'lodash-es'
|
|
8
8
|
import * as ReactDOM from 'react-dom'
|
|
9
|
-
import
|
|
9
|
+
import type * as Sqlite from 'sqlite-esm'
|
|
10
10
|
import { v4 as uuid } from 'uuid'
|
|
11
11
|
|
|
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'
|
|
16
17
|
import { migrateDb } from './migrations.js'
|
|
17
18
|
import { getDurationMsFromSpan } from './otel.js'
|
|
18
|
-
import type {
|
|
19
|
+
import type { Atom, Ref } from './reactive.js'
|
|
19
20
|
import { ReactiveGraph } from './reactive.js'
|
|
20
21
|
import { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js'
|
|
21
22
|
import { LiveStoreJSQuery } from './reactiveQueries/js.js'
|
|
@@ -24,7 +25,9 @@ import type { ActionDefinition, GetActionArgs, Schema, SQLWriteStatement } from
|
|
|
24
25
|
import { componentStateTables } from './schema.js'
|
|
25
26
|
import type { Storage, StorageInit } from './storage/index.js'
|
|
26
27
|
import type { Bindable, ParamsObject } from './util.js'
|
|
27
|
-
import { isPromise, sql } from './util.js'
|
|
28
|
+
import { isPromise, prepareBindValues, sql } from './util.js'
|
|
29
|
+
|
|
30
|
+
export type GetAtomResult = <T>(atom: Atom<T> | LiveStoreJSQuery<T>) => T
|
|
28
31
|
|
|
29
32
|
export type LiveStoreQuery<TResult extends Record<string, any> = any> =
|
|
30
33
|
| LiveStoreSQLQuery<TResult>
|
|
@@ -37,8 +40,6 @@ export type BaseGraphQLContext = {
|
|
|
37
40
|
otelContext?: otel.Context
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
export const RESET_DB_LOCAL_STORAGE_KEY = 'livestore-reset'
|
|
41
|
-
|
|
42
43
|
export type QueryResult<TQuery> = TQuery extends LiveStoreSQLQuery<infer R>
|
|
43
44
|
? ReadonlyArray<Readonly<R>>
|
|
44
45
|
: TQuery extends LiveStoreJSQuery<infer S>
|
|
@@ -56,6 +57,8 @@ export type GraphQLOptions<TContext> = {
|
|
|
56
57
|
|
|
57
58
|
export type StoreOptions<TGraphQLContext extends BaseGraphQLContext> = {
|
|
58
59
|
db: InMemoryDatabase
|
|
60
|
+
/** A `Proxy`d version of `db` except that it also mirrors `execute` calls to the storage */
|
|
61
|
+
dbProxy: InMemoryDatabase
|
|
59
62
|
schema: Schema
|
|
60
63
|
storage?: Storage
|
|
61
64
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
@@ -100,9 +103,11 @@ export type StoreOtel = {
|
|
|
100
103
|
queriesSpanContext: otel.Context
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
106
|
+
export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLContext> {
|
|
104
107
|
graph: ReactiveGraph<RefreshReason, QueryDebugInfo>
|
|
105
108
|
inMemoryDB: InMemoryDatabase
|
|
109
|
+
// TODO refactor
|
|
110
|
+
_proxyDb: InMemoryDatabase
|
|
106
111
|
schema: Schema
|
|
107
112
|
graphQLSchema?: GraphQLSchema
|
|
108
113
|
graphQLContext?: TGraphQLContext
|
|
@@ -118,6 +123,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
118
123
|
|
|
119
124
|
private constructor({
|
|
120
125
|
db,
|
|
126
|
+
dbProxy,
|
|
121
127
|
schema,
|
|
122
128
|
storage,
|
|
123
129
|
graphQLOptions,
|
|
@@ -125,6 +131,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
125
131
|
otelRootSpanContext,
|
|
126
132
|
}: StoreOptions<TGraphQLContext>) {
|
|
127
133
|
this.inMemoryDB = db
|
|
134
|
+
this._proxyDb = dbProxy
|
|
128
135
|
this.graph = new ReactiveGraph({
|
|
129
136
|
// TODO move this into React module
|
|
130
137
|
// Do all our updates inside a single React setState batch to avoid multiple UI re-renders
|
|
@@ -188,7 +195,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
188
195
|
* NOTE The query is actually running (even if no one has subscribed to it yet) and will be kept up to date.
|
|
189
196
|
*/
|
|
190
197
|
querySQL = <TResult>(
|
|
191
|
-
genQueryString: (get:
|
|
198
|
+
genQueryString: string | ((get: GetAtomResult) => string),
|
|
192
199
|
{
|
|
193
200
|
queriedTables,
|
|
194
201
|
bindValues,
|
|
@@ -218,9 +225,17 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
218
225
|
|
|
219
226
|
const queryString$ = this.graph.makeThunk(
|
|
220
227
|
(get, addDebugInfo) => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
+
}
|
|
224
239
|
},
|
|
225
240
|
{ label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
|
|
226
241
|
otelContext,
|
|
@@ -231,10 +246,10 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
231
246
|
|
|
232
247
|
const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
233
248
|
|
|
234
|
-
const results$ = this.graph.makeThunk<TResult
|
|
249
|
+
const results$ = this.graph.makeThunk<ReadonlyArray<TResult>>(
|
|
235
250
|
(get, addDebugInfo) =>
|
|
236
251
|
this.otel.tracer.startActiveSpan(
|
|
237
|
-
'sql', // NOTE span name will be overridden further down
|
|
252
|
+
'sql:', // NOTE span name will be overridden further down
|
|
238
253
|
{},
|
|
239
254
|
otelContext,
|
|
240
255
|
(span) => {
|
|
@@ -252,12 +267,16 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
252
267
|
span.setAttribute('sql.query', sqlString)
|
|
253
268
|
span.updateName(`sql:${sqlString.slice(0, 50)}`)
|
|
254
269
|
|
|
255
|
-
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
|
+
})
|
|
256
275
|
|
|
257
276
|
span.setAttribute('sql.rowsCount', results.length)
|
|
258
277
|
addDebugInfo({ _tag: 'sql', label: label ?? '', query: sqlString })
|
|
259
278
|
|
|
260
|
-
return results
|
|
279
|
+
return results
|
|
261
280
|
} finally {
|
|
262
281
|
span.end()
|
|
263
282
|
}
|
|
@@ -289,7 +308,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
289
308
|
)
|
|
290
309
|
|
|
291
310
|
queryJS = <TResult>(
|
|
292
|
-
genResults: (get:
|
|
311
|
+
genResults: (get: GetAtomResult) => TResult,
|
|
293
312
|
{
|
|
294
313
|
componentKey = globalComponentKey,
|
|
295
314
|
label = `js${uniqueId()}`,
|
|
@@ -301,8 +320,12 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
301
320
|
const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '')
|
|
302
321
|
const results$ = this.graph.makeThunk(
|
|
303
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
|
+
}
|
|
304
327
|
addDebugInfo({ _tag: 'js', label, query: genResults.toString() })
|
|
305
|
-
return genResults(
|
|
328
|
+
return genResults(getAtom)
|
|
306
329
|
},
|
|
307
330
|
{ label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } },
|
|
308
331
|
otelContext,
|
|
@@ -329,7 +352,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
329
352
|
|
|
330
353
|
queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
|
|
331
354
|
document: DocumentNode<TResult, TVariableValues>,
|
|
332
|
-
genVariableValues: (get:
|
|
355
|
+
genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
|
|
333
356
|
{
|
|
334
357
|
componentKey,
|
|
335
358
|
label,
|
|
@@ -356,7 +379,17 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
356
379
|
span.updateName(`queryGraphQL:${labelWithDefault}`)
|
|
357
380
|
|
|
358
381
|
const variableValues$ = this.graph.makeThunk(
|
|
359
|
-
|
|
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
|
+
},
|
|
360
393
|
{ label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } },
|
|
361
394
|
otelContext,
|
|
362
395
|
)
|
|
@@ -720,6 +753,13 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
720
753
|
)
|
|
721
754
|
}
|
|
722
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
|
+
|
|
723
763
|
/**
|
|
724
764
|
* Apply an event to the store.
|
|
725
765
|
* Returns the tables that were affected by the event.
|
|
@@ -778,14 +818,19 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
778
818
|
// Synchronously apply the event to the in-memory database
|
|
779
819
|
// const { durationMs } = this.inMemoryDB.applyEvent(eventWithId, actionDefinition, otelContext)
|
|
780
820
|
const { statement, bindValues } = eventToSql(eventWithId, actionDefinition)
|
|
781
|
-
const { durationMs } = this.inMemoryDB.execute(
|
|
782
|
-
|
|
783
|
-
|
|
821
|
+
const { durationMs } = this.inMemoryDB.execute(
|
|
822
|
+
statement.sql,
|
|
823
|
+
prepareBindValues(bindValues, statement.sql),
|
|
824
|
+
statement.writeTables,
|
|
825
|
+
{
|
|
826
|
+
otelContext,
|
|
827
|
+
},
|
|
828
|
+
)
|
|
784
829
|
|
|
785
830
|
// Asynchronously apply the event to a persistent storage (we're not awaiting this promise here)
|
|
786
831
|
if (this.storage !== undefined) {
|
|
787
832
|
// this.storage.applyEvent(eventWithId, actionDefinition, span)
|
|
788
|
-
this.storage.execute(statement.sql, bindValues, span)
|
|
833
|
+
this.storage.execute(statement.sql, prepareBindValues(bindValues, statement.sql), span)
|
|
789
834
|
}
|
|
790
835
|
|
|
791
836
|
// Uncomment to print a list of queries currently registered on the store
|
|
@@ -809,11 +854,11 @@ export class Store<TGraphQLContext extends BaseGraphQLContext> {
|
|
|
809
854
|
* all app writes should go through applyEvent.
|
|
810
855
|
*/
|
|
811
856
|
execute = async (query: string, params: ParamsObject = {}, writeTables?: string[]) => {
|
|
812
|
-
this.inMemoryDB.execute(query, params, writeTables)
|
|
857
|
+
this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables)
|
|
813
858
|
|
|
814
859
|
if (this.storage !== undefined) {
|
|
815
860
|
const parentSpan = otel.trace.getSpan(otel.context.active())
|
|
816
|
-
this.storage.execute(query, params, parentSpan)
|
|
861
|
+
this.storage.execute(query, prepareBindValues(params, query), parentSpan)
|
|
817
862
|
}
|
|
818
863
|
}
|
|
819
864
|
}
|
|
@@ -826,6 +871,7 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
|
|
|
826
871
|
otelTracer = makeNoopTracer(),
|
|
827
872
|
otelRootSpanContext = otel.context.active(),
|
|
828
873
|
boot,
|
|
874
|
+
sqlite3,
|
|
829
875
|
}: {
|
|
830
876
|
schema: Schema
|
|
831
877
|
loadStorage: () => StorageInit | Promise<StorageInit>
|
|
@@ -833,50 +879,36 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
|
|
|
833
879
|
otelTracer?: otel.Tracer
|
|
834
880
|
otelRootSpanContext?: otel.Context
|
|
835
881
|
boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
|
|
882
|
+
sqlite3: Sqlite.Sqlite3Static
|
|
836
883
|
}): Promise<Store<TGraphQLContext>> => {
|
|
837
884
|
return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
|
|
838
885
|
try {
|
|
839
886
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
840
887
|
|
|
841
|
-
const
|
|
842
|
-
|
|
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
|
+
})
|
|
897
|
+
|
|
898
|
+
const persistedData = await otelTracer.startActiveSpan(
|
|
899
|
+
'storage:getPersistedData',
|
|
900
|
+
{},
|
|
901
|
+
otelContext,
|
|
902
|
+
async (span) => {
|
|
843
903
|
try {
|
|
844
|
-
|
|
845
|
-
const parentSpan = otel.trace.getSpan(otel.context.active()) ?? makeNoopSpan()
|
|
846
|
-
return init({ otelTracer, parentSpan })
|
|
904
|
+
return await storage.getPersistedData(span)
|
|
847
905
|
} finally {
|
|
848
906
|
span.end()
|
|
849
907
|
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
const persistedData = await otelTracer.startActiveSpan(
|
|
853
|
-
'storage:getPersistedData',
|
|
854
|
-
{},
|
|
855
|
-
otelContext,
|
|
856
|
-
async (span) => {
|
|
857
|
-
try {
|
|
858
|
-
return await storage.getPersistedData(span)
|
|
859
|
-
} finally {
|
|
860
|
-
span.end()
|
|
861
|
-
}
|
|
862
|
-
},
|
|
863
|
-
)
|
|
864
|
-
|
|
865
|
-
return { storage, persistedData }
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
const loadSqlite3 = () =>
|
|
869
|
-
initSqlite3Wasm({
|
|
870
|
-
// Required to load the wasm binary asynchronously. Of course, you can host it wherever you want
|
|
871
|
-
// You can omit locateFile completely when running in node
|
|
872
|
-
// locateFile: () => `/sql-wasm.wasm`,
|
|
873
|
-
print: (message) => console.log(`[livestore sqlite] ${message}`),
|
|
874
|
-
printErr: (message) => console.error(`[livestore sqlite] ${message}`),
|
|
875
|
-
})
|
|
876
|
-
|
|
877
|
-
const [{ storage, persistedData }, sqlite3] = await Promise.all([loadStorageAndPersistedData(), loadSqlite3()])
|
|
908
|
+
},
|
|
909
|
+
)
|
|
878
910
|
|
|
879
|
-
const db = InMemoryDatabase.load(persistedData, otelTracer, otelRootSpanContext, sqlite3)
|
|
911
|
+
const db = InMemoryDatabase.load({ data: persistedData, otelTracer, otelRootSpanContext, sqlite3 })
|
|
880
912
|
|
|
881
913
|
// Proxy to `db` that also mirrors `execute` calls to `storage`
|
|
882
914
|
const dbProxy = new Proxy(db, {
|
|
@@ -887,6 +919,14 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
|
|
|
887
919
|
return db.execute(query, bindValues, writeTables, options)
|
|
888
920
|
}
|
|
889
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
|
|
890
930
|
} else {
|
|
891
931
|
return Reflect.get(db, prop, receiver)
|
|
892
932
|
}
|
|
@@ -914,7 +954,7 @@ export const createStore = async <TGraphQLContext extends BaseGraphQLContext>({
|
|
|
914
954
|
// Think about what to do about this case.
|
|
915
955
|
// await applySchema(db, schema)
|
|
916
956
|
return Store.createStore<TGraphQLContext>(
|
|
917
|
-
{ db, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext },
|
|
957
|
+
{ db, dbProxy, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext },
|
|
918
958
|
span,
|
|
919
959
|
)
|
|
920
960
|
} finally {
|
package/src/util.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/// <reference lib="es2022" />
|
|
2
2
|
|
|
3
|
+
import type { Brand } from '@livestore/utils/effect'
|
|
4
|
+
|
|
3
5
|
export type ParamsObject = Record<string, SqlValue>
|
|
4
6
|
export type SqlValue = string | number | Uint8Array | null
|
|
5
7
|
|
|
6
8
|
export type Bindable = SqlValue[] | ParamsObject
|
|
7
9
|
|
|
10
|
+
export type PreparedBindValues = Brand.Branded<Bindable, 'PreparedBindValues'>
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
13
|
* This is a tag function for tagged literals.
|
|
10
14
|
* it lets us get syntax highlighting on SQL queries in VSCode, but
|
|
@@ -25,7 +29,9 @@ export const sql = (template: TemplateStringsArray, ...args: unknown[]): string
|
|
|
25
29
|
/* because rusqlite doesn't allow unused named params
|
|
26
30
|
/* TODO: Search for unused params via proper parsing, not string search
|
|
27
31
|
**/
|
|
28
|
-
export const prepareBindValues = (values:
|
|
32
|
+
export const prepareBindValues = (values: Bindable, statement: string): PreparedBindValues => {
|
|
33
|
+
if (Array.isArray(values)) return values as PreparedBindValues
|
|
34
|
+
|
|
29
35
|
const result: ParamsObject = {}
|
|
30
36
|
for (const [key, value] of Object.entries(values)) {
|
|
31
37
|
if (statement.includes(key)) {
|
|
@@ -33,7 +39,7 @@ export const prepareBindValues = (values: ParamsObject, statement: string): Para
|
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
return result
|
|
42
|
+
return result as PreparedBindValues
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/**
|