@livestore/livestore 0.3.0-dev.28 → 0.3.0-dev.30
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/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +4 -1
- package/dist/SqliteDbWrapper.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +8 -12
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/client-document-get-query.d.ts +12 -0
- package/dist/live-queries/client-document-get-query.d.ts.map +1 -0
- package/dist/live-queries/client-document-get-query.js +18 -0
- package/dist/live-queries/client-document-get-query.js.map +1 -0
- package/dist/live-queries/computed.d.ts +4 -8
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +1 -6
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.d.ts +13 -18
- package/dist/live-queries/db-query.d.ts.map +1 -1
- package/dist/live-queries/db-query.js +34 -34
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +32 -23
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/live-queries/make-ref.d.ts.map +1 -1
- package/dist/live-queries/make-ref.js +1 -0
- package/dist/live-queries/make-ref.js.map +1 -1
- package/dist/mod.d.ts +0 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +0 -1
- package/dist/mod.js.map +1 -1
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +1 -1
- package/dist/reactive.js.map +1 -1
- package/dist/store/create-store.js +2 -2
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/store-types.d.ts +5 -5
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store.d.ts +27 -26
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +74 -69
- package/dist/store/store.js.map +1 -1
- package/dist/utils/stack-info.test.js +6 -6
- package/dist/utils/tests/fixture.d.ts +54 -207
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +20 -13
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +8 -3
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +7 -7
- package/src/SqliteDbWrapper.ts +4 -1
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +9 -9
- package/src/live-queries/base-class.ts +8 -25
- package/src/live-queries/client-document-get-query.ts +52 -0
- package/src/live-queries/computed.ts +4 -18
- package/src/live-queries/db-query.test.ts +38 -24
- package/src/live-queries/db-query.ts +60 -66
- package/src/live-queries/make-ref.ts +2 -0
- package/src/mod.ts +0 -2
- package/src/reactive.ts +1 -1
- package/src/store/create-store.ts +2 -2
- package/src/store/store-types.ts +5 -5
- package/src/store/store.ts +97 -91
- package/src/utils/stack-info.test.ts +6 -6
- package/src/utils/tests/fixture.ts +21 -25
- package/src/utils/tests/otel.ts +10 -3
- package/tmp/pack.tgz +0 -0
- package/dist/row-query-utils.d.ts +0 -17
- package/dist/row-query-utils.d.ts.map +0 -1
- package/dist/row-query-utils.js +0 -34
- package/dist/row-query-utils.js.map +0 -1
- package/src/row-query-utils.ts +0 -76
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PreparedBindValues } from '@livestore/common'
|
|
2
|
+
import { SessionIdSymbol } from '@livestore/common'
|
|
3
|
+
import { State } from '@livestore/common/schema'
|
|
4
|
+
import { shouldNeverHappen } from '@livestore/utils'
|
|
5
|
+
import type * as otel from '@opentelemetry/api'
|
|
6
|
+
|
|
7
|
+
import type { ReactivityGraphContext } from './base-class.js'
|
|
8
|
+
|
|
9
|
+
export const rowQueryLabel = (
|
|
10
|
+
table: State.SQLite.ClientDocumentTableDef.Any,
|
|
11
|
+
id: string | SessionIdSymbol | undefined,
|
|
12
|
+
) => `${table.sqliteDef.name}.get:${id === undefined ? table.default.id : id === SessionIdSymbol ? 'sessionId' : id}`
|
|
13
|
+
|
|
14
|
+
export const makeExecBeforeFirstRun =
|
|
15
|
+
({
|
|
16
|
+
id,
|
|
17
|
+
explicitDefaultValues,
|
|
18
|
+
table,
|
|
19
|
+
otelContext: otelContext_,
|
|
20
|
+
}: {
|
|
21
|
+
id?: string | SessionIdSymbol
|
|
22
|
+
explicitDefaultValues?: any
|
|
23
|
+
table: State.SQLite.TableDefBase
|
|
24
|
+
otelContext: otel.Context | undefined
|
|
25
|
+
}) =>
|
|
26
|
+
({ store }: ReactivityGraphContext) => {
|
|
27
|
+
if (State.SQLite.tableIsClientDocumentTable(table) === false) {
|
|
28
|
+
return shouldNeverHappen(
|
|
29
|
+
`Cannot insert row for table "${table.sqliteDef.name}" which does not have 'deriveEvents: true' set`,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const otelContext = otelContext_ ?? store.otel.queriesSpanContext
|
|
34
|
+
|
|
35
|
+
const idVal = id === SessionIdSymbol ? store.sessionId : id!
|
|
36
|
+
const rowExists =
|
|
37
|
+
store.sqliteDbWrapper.select(
|
|
38
|
+
`SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`,
|
|
39
|
+
[idVal] as any as PreparedBindValues,
|
|
40
|
+
{ otelContext },
|
|
41
|
+
).length === 1
|
|
42
|
+
|
|
43
|
+
if (rowExists) return
|
|
44
|
+
|
|
45
|
+
// It's important that we only commit and don't refresh here, as this function might be called during a render
|
|
46
|
+
// and otherwise we might end up in a "reactive loop"
|
|
47
|
+
|
|
48
|
+
store.commit(
|
|
49
|
+
{ otelContext, skipRefresh: true, label: `${table.sqliteDef.name}.set:${idVal}` },
|
|
50
|
+
table.set(explicitDefaultValues, idVal as TODO),
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { QueryInfo } from '@livestore/common'
|
|
2
1
|
import { getDurationMsFromSpan } from '@livestore/common'
|
|
3
2
|
import * as otel from '@opentelemetry/api'
|
|
4
3
|
|
|
@@ -8,29 +7,25 @@ import { isValidFunctionString } from '../utils/function-string.js'
|
|
|
8
7
|
import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
|
|
9
8
|
import { depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.js'
|
|
10
9
|
|
|
11
|
-
export const computed = <TResult
|
|
10
|
+
export const computed = <TResult>(
|
|
12
11
|
fn: (get: GetAtomResult) => TResult,
|
|
13
12
|
options?: {
|
|
14
13
|
label?: string
|
|
15
|
-
queryInfo?: TQueryInfo
|
|
16
14
|
deps?: DepKey
|
|
17
15
|
},
|
|
18
|
-
): LiveQueryDef<TResult
|
|
16
|
+
): LiveQueryDef<TResult> => {
|
|
19
17
|
const hash = options?.deps ? depsToString(options.deps) : fn.toString()
|
|
20
18
|
if (isValidFunctionString(hash)._tag === 'invalid') {
|
|
21
19
|
throw new Error(`On Expo/React Native, computed queries must provide a \`deps\` option`)
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
const queryInfo = options?.queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
|
|
25
|
-
|
|
26
22
|
return {
|
|
27
23
|
_tag: 'def',
|
|
28
24
|
make: withRCMap(hash, (ctx, _otelContext) => {
|
|
29
25
|
// TODO onDestroy
|
|
30
|
-
return new LiveStoreComputedQuery<TResult
|
|
26
|
+
return new LiveStoreComputedQuery<TResult>({
|
|
31
27
|
fn,
|
|
32
28
|
label: options?.label ?? fn.toString(),
|
|
33
|
-
queryInfo: options?.queryInfo,
|
|
34
29
|
reactivityGraph: ctx.reactivityGraph.deref()!,
|
|
35
30
|
})
|
|
36
31
|
}),
|
|
@@ -39,14 +34,10 @@ export const computed = <TResult, TQueryInfo extends QueryInfo = QueryInfo.None>
|
|
|
39
34
|
// TODO we should figure out whether this could cause some problems and/or if there's a better way to do this
|
|
40
35
|
// NOTE `fn.toString()` doesn't work in Expo as it always produces `[native code]`
|
|
41
36
|
hash,
|
|
42
|
-
queryInfo,
|
|
43
37
|
}
|
|
44
38
|
}
|
|
45
39
|
|
|
46
|
-
export class LiveStoreComputedQuery<TResult
|
|
47
|
-
TResult,
|
|
48
|
-
TQueryInfo
|
|
49
|
-
> {
|
|
40
|
+
export class LiveStoreComputedQuery<TResult> extends LiveStoreQueryBase<TResult> {
|
|
50
41
|
_tag: 'computed' = 'computed'
|
|
51
42
|
|
|
52
43
|
/** A reactive thunk representing the query results */
|
|
@@ -56,24 +47,19 @@ export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = Quer
|
|
|
56
47
|
|
|
57
48
|
reactivityGraph: ReactivityGraph
|
|
58
49
|
|
|
59
|
-
queryInfo: TQueryInfo
|
|
60
|
-
|
|
61
50
|
constructor({
|
|
62
51
|
fn,
|
|
63
52
|
label,
|
|
64
53
|
reactivityGraph,
|
|
65
|
-
queryInfo,
|
|
66
54
|
}: {
|
|
67
55
|
label: string
|
|
68
56
|
fn: (get: GetAtomResult) => TResult
|
|
69
57
|
reactivityGraph: ReactivityGraph
|
|
70
|
-
queryInfo?: TQueryInfo
|
|
71
58
|
}) {
|
|
72
59
|
super()
|
|
73
60
|
|
|
74
61
|
this.label = label
|
|
75
62
|
this.reactivityGraph = reactivityGraph
|
|
76
|
-
this.queryInfo = queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
|
|
77
63
|
|
|
78
64
|
const queryLabel = `${label}:results`
|
|
79
65
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sql } from '@livestore/common'
|
|
2
|
-
import {
|
|
2
|
+
import { rawSqlEvent } from '@livestore/common/schema'
|
|
3
3
|
import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect'
|
|
4
4
|
import { Vitest } from '@livestore/utils/node-vitest'
|
|
5
5
|
import * as otel from '@opentelemetry/api'
|
|
@@ -20,8 +20,6 @@ TODO write tests for:
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
Vitest.describe('otel', () => {
|
|
23
|
-
let cachedProvider: BasicTracerProvider | undefined
|
|
24
|
-
|
|
25
23
|
const mapAttributes = (attributes: otel.Attributes) => {
|
|
26
24
|
return ReadonlyRecord.map(attributes, (val, key) => {
|
|
27
25
|
if (key === 'code.stacktrace') {
|
|
@@ -36,13 +34,11 @@ Vitest.describe('otel', () => {
|
|
|
36
34
|
|
|
37
35
|
RG.__resetIds()
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
|
|
43
|
-
provider.register()
|
|
37
|
+
const provider = new BasicTracerProvider({
|
|
38
|
+
spanProcessors: [new SimpleSpanProcessor(exporter)],
|
|
39
|
+
})
|
|
44
40
|
|
|
45
|
-
const otelTracer =
|
|
41
|
+
const otelTracer = provider.getTracer('test')
|
|
46
42
|
|
|
47
43
|
const span = otelTracer.startSpan('test-root')
|
|
48
44
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
@@ -60,16 +56,16 @@ Vitest.describe('otel', () => {
|
|
|
60
56
|
|
|
61
57
|
Vitest.scopedLive('otel', () =>
|
|
62
58
|
Effect.gen(function* () {
|
|
63
|
-
const { store, exporter, span } = yield* makeQuery
|
|
59
|
+
const { store, exporter, span, provider } = yield* makeQuery
|
|
64
60
|
|
|
65
61
|
const query$ = queryDb({
|
|
66
62
|
query: `select * from todos`,
|
|
67
|
-
schema: Schema.Array(tables.todos.
|
|
63
|
+
schema: Schema.Array(tables.todos.rowSchema),
|
|
68
64
|
queriedTables: new Set(['todos']),
|
|
69
65
|
})
|
|
70
66
|
expect(store.query(query$)).toMatchInlineSnapshot('[]')
|
|
71
67
|
|
|
72
|
-
store.commit(
|
|
68
|
+
store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
73
69
|
|
|
74
70
|
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
75
71
|
[
|
|
@@ -83,16 +79,22 @@ Vitest.describe('otel', () => {
|
|
|
83
79
|
|
|
84
80
|
span.end()
|
|
85
81
|
|
|
86
|
-
return { exporter }
|
|
82
|
+
return { exporter, provider }
|
|
87
83
|
}).pipe(
|
|
88
84
|
Effect.scoped,
|
|
89
|
-
Effect.tap(({ exporter }) =>
|
|
85
|
+
Effect.tap(({ exporter, provider }) =>
|
|
86
|
+
Effect.promise(async () => {
|
|
87
|
+
await provider.forceFlush()
|
|
88
|
+
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
|
|
89
|
+
await provider.shutdown()
|
|
90
|
+
}),
|
|
91
|
+
),
|
|
90
92
|
),
|
|
91
93
|
)
|
|
92
94
|
|
|
93
95
|
Vitest.scopedLive('with thunks', () =>
|
|
94
96
|
Effect.gen(function* () {
|
|
95
|
-
const { store, exporter, span } = yield* makeQuery
|
|
97
|
+
const { store, exporter, span, provider } = yield* makeQuery
|
|
96
98
|
|
|
97
99
|
const defaultTodo = { id: '', text: '', completed: false }
|
|
98
100
|
|
|
@@ -100,7 +102,7 @@ Vitest.describe('otel', () => {
|
|
|
100
102
|
const query$ = queryDb(
|
|
101
103
|
(get) => ({
|
|
102
104
|
query: `select * from todos ${get(filter)}`,
|
|
103
|
-
schema: Schema.Array(tables.todos.
|
|
105
|
+
schema: Schema.Array(tables.todos.rowSchema).pipe(Schema.headOrElse(() => defaultTodo)),
|
|
104
106
|
}),
|
|
105
107
|
{ label: 'all todos' },
|
|
106
108
|
)
|
|
@@ -117,7 +119,7 @@ Vitest.describe('otel', () => {
|
|
|
117
119
|
|
|
118
120
|
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
119
121
|
|
|
120
|
-
store.commit(
|
|
122
|
+
store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
121
123
|
|
|
122
124
|
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
123
125
|
|
|
@@ -133,21 +135,27 @@ Vitest.describe('otel', () => {
|
|
|
133
135
|
|
|
134
136
|
span.end()
|
|
135
137
|
|
|
136
|
-
return { exporter }
|
|
138
|
+
return { exporter, provider }
|
|
137
139
|
}).pipe(
|
|
138
140
|
Effect.scoped,
|
|
139
|
-
Effect.tap(({ exporter }) =>
|
|
141
|
+
Effect.tap(({ exporter, provider }) =>
|
|
142
|
+
Effect.promise(async () => {
|
|
143
|
+
await provider.forceFlush()
|
|
144
|
+
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
|
|
145
|
+
await provider.shutdown()
|
|
146
|
+
}),
|
|
147
|
+
),
|
|
140
148
|
),
|
|
141
149
|
)
|
|
142
150
|
|
|
143
151
|
Vitest.scopedLive('with thunks with query builder and without labels', () =>
|
|
144
152
|
Effect.gen(function* () {
|
|
145
|
-
const { store, exporter, span } = yield* makeQuery
|
|
153
|
+
const { store, exporter, span, provider } = yield* makeQuery
|
|
146
154
|
|
|
147
155
|
const defaultTodo = { id: '', text: '', completed: false }
|
|
148
156
|
|
|
149
157
|
const filter = computed(() => ({ completed: false }))
|
|
150
|
-
const query$ = queryDb((get) => tables.todos.
|
|
158
|
+
const query$ = queryDb((get) => tables.todos.where(get(filter)).first({ fallback: () => defaultTodo }))
|
|
151
159
|
|
|
152
160
|
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
153
161
|
{
|
|
@@ -157,7 +165,7 @@ Vitest.describe('otel', () => {
|
|
|
157
165
|
}
|
|
158
166
|
`)
|
|
159
167
|
|
|
160
|
-
store.commit(
|
|
168
|
+
store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
161
169
|
|
|
162
170
|
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
163
171
|
{
|
|
@@ -169,10 +177,16 @@ Vitest.describe('otel', () => {
|
|
|
169
177
|
|
|
170
178
|
span.end()
|
|
171
179
|
|
|
172
|
-
return { exporter }
|
|
180
|
+
return { exporter, provider }
|
|
173
181
|
}).pipe(
|
|
174
182
|
Effect.scoped,
|
|
175
|
-
Effect.tap(({ exporter }) =>
|
|
183
|
+
Effect.tap(({ exporter, provider }) =>
|
|
184
|
+
Effect.promise(async () => {
|
|
185
|
+
await provider.forceFlush()
|
|
186
|
+
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
|
|
187
|
+
await provider.shutdown()
|
|
188
|
+
}),
|
|
189
|
+
),
|
|
176
190
|
),
|
|
177
191
|
)
|
|
178
192
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Bindable, QueryBuilder
|
|
1
|
+
import type { Bindable, QueryBuilder } from '@livestore/common'
|
|
2
2
|
import {
|
|
3
3
|
getDurationMsFromSpan,
|
|
4
4
|
getResultSchema,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
prepareBindValues,
|
|
7
7
|
QueryBuilderAstSymbol,
|
|
8
8
|
replaceSessionIdSymbol,
|
|
9
|
+
SessionIdSymbol,
|
|
9
10
|
UnexpectedError,
|
|
10
11
|
} from '@livestore/common'
|
|
11
12
|
import { deepEqual, shouldNeverHappen } from '@livestore/utils'
|
|
@@ -14,13 +15,13 @@ import * as otel from '@opentelemetry/api'
|
|
|
14
15
|
|
|
15
16
|
import type { Thunk } from '../reactive.js'
|
|
16
17
|
import { isThunk, NOT_REFRESHED_YET } from '../reactive.js'
|
|
17
|
-
import { makeExecBeforeFirstRun, rowQueryLabel } from '../row-query-utils.js'
|
|
18
18
|
import type { RefreshReason } from '../store/store-types.js'
|
|
19
19
|
import { isValidFunctionString } from '../utils/function-string.js'
|
|
20
20
|
import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
|
|
21
21
|
import { depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.js'
|
|
22
|
+
import { makeExecBeforeFirstRun, rowQueryLabel } from './client-document-get-query.js'
|
|
22
23
|
|
|
23
|
-
export type QueryInputRaw<TDecoded, TEncoded
|
|
24
|
+
export type QueryInputRaw<TDecoded, TEncoded> = {
|
|
24
25
|
query: string
|
|
25
26
|
schema: Schema.Schema<TDecoded, TEncoded>
|
|
26
27
|
bindValues?: Bindable
|
|
@@ -30,25 +31,20 @@ export type QueryInputRaw<TDecoded, TEncoded, TQueryInfo extends QueryInfo> = {
|
|
|
30
31
|
* NOTE In the future we want to do this automatically at build time
|
|
31
32
|
*/
|
|
32
33
|
queriedTables?: Set<string>
|
|
33
|
-
queryInfo?: TQueryInfo
|
|
34
34
|
execBeforeFirstRun?: (ctx: ReactivityGraphContext) => void
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export const isQueryInputRaw = (value: unknown): value is QueryInputRaw<any, any
|
|
37
|
+
export const isQueryInputRaw = (value: unknown): value is QueryInputRaw<any, any> =>
|
|
38
38
|
Predicate.hasProperty(value, 'query') && Predicate.hasProperty(value, 'schema')
|
|
39
39
|
|
|
40
|
-
export type QueryInput<TDecoded, TEncoded,
|
|
41
|
-
| QueryInputRaw<TDecoded, TEncoded, TQueryInfo>
|
|
42
|
-
| QueryBuilder<TDecoded, any, any, TQueryInfo>
|
|
40
|
+
export type QueryInput<TDecoded, TEncoded> = QueryInputRaw<TDecoded, TEncoded> | QueryBuilder<TDecoded, any, any>
|
|
43
41
|
|
|
44
42
|
/**
|
|
45
|
-
* NOTE `
|
|
43
|
+
* NOTE `queryDb` is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
|
|
46
44
|
*/
|
|
47
45
|
export const queryDb: {
|
|
48
|
-
<TResultSchema, TResult = TResultSchema
|
|
49
|
-
queryInput:
|
|
50
|
-
| QueryInputRaw<TResultSchema, ReadonlyArray<any>, TQueryInfo>
|
|
51
|
-
| QueryBuilder<TResultSchema, any, any, TQueryInfo>,
|
|
46
|
+
<TResultSchema, TResult = TResultSchema>(
|
|
47
|
+
queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>,
|
|
52
48
|
options?: {
|
|
53
49
|
map?: (rows: TResultSchema) => TResult
|
|
54
50
|
/**
|
|
@@ -56,16 +52,15 @@ export const queryDb: {
|
|
|
56
52
|
*/
|
|
57
53
|
label?: string
|
|
58
54
|
deps?: DepKey
|
|
59
|
-
queryInfo?: TQueryInfo
|
|
60
55
|
},
|
|
61
|
-
): LiveQueryDef<TResult
|
|
56
|
+
): LiveQueryDef<TResult>
|
|
62
57
|
// NOTE in this "thunk case", we can't directly derive label/queryInfo from the queryInput,
|
|
63
58
|
// so the caller needs to provide them explicitly otherwise queryInfo will be set to `None`,
|
|
64
59
|
// and label will be set during the query execution
|
|
65
|
-
<TResultSchema, TResult = TResultSchema
|
|
60
|
+
<TResultSchema, TResult = TResultSchema>(
|
|
66
61
|
queryInput:
|
|
67
|
-
| ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any
|
|
68
|
-
| ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any
|
|
62
|
+
| ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>)
|
|
63
|
+
| ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>),
|
|
69
64
|
options?: {
|
|
70
65
|
map?: (rows: TResultSchema) => TResult
|
|
71
66
|
/**
|
|
@@ -73,23 +68,21 @@ export const queryDb: {
|
|
|
73
68
|
*/
|
|
74
69
|
label?: string
|
|
75
70
|
deps?: DepKey
|
|
76
|
-
queryInfo?: TQueryInfo
|
|
77
71
|
},
|
|
78
|
-
): LiveQueryDef<TResult
|
|
72
|
+
): LiveQueryDef<TResult>
|
|
79
73
|
} = (queryInput, options) => {
|
|
80
|
-
const queryString =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
: typeof queryInput === 'function'
|
|
85
|
-
? queryInput.toString()
|
|
86
|
-
: shouldNeverHappen(`Invalid query input: ${queryInput}`)
|
|
87
|
-
|
|
88
|
-
const hash = options?.deps ? queryString + '-' + depsToString(options.deps) : queryString
|
|
74
|
+
const { queryString, extraDeps } = getQueryStringAndExtraDeps(queryInput)
|
|
75
|
+
|
|
76
|
+
const hash =
|
|
77
|
+
(options?.deps ? queryString + '-' + depsToString(options.deps) : queryString) + '-' + depsToString(extraDeps)
|
|
89
78
|
if (isValidFunctionString(hash)._tag === 'invalid') {
|
|
90
79
|
throw new Error(`On Expo/React Native, db queries must provide a \`deps\` option`)
|
|
91
80
|
}
|
|
92
81
|
|
|
82
|
+
if (hash.trim() === '') {
|
|
83
|
+
return shouldNeverHappen(`Invalid query hash for query: ${queryInput}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
93
86
|
const label = options?.label ?? queryString
|
|
94
87
|
|
|
95
88
|
return {
|
|
@@ -101,36 +94,55 @@ export const queryDb: {
|
|
|
101
94
|
queryInput,
|
|
102
95
|
label,
|
|
103
96
|
map: options?.map,
|
|
104
|
-
// We're not falling back to `None` here as the queryInfo will be set dynamically
|
|
105
|
-
queryInfo: options?.queryInfo,
|
|
106
97
|
otelContext,
|
|
107
98
|
})
|
|
108
99
|
}),
|
|
109
100
|
label,
|
|
110
101
|
hash,
|
|
111
|
-
queryInfo:
|
|
112
|
-
options?.queryInfo ?? (isQueryBuilder(queryInput) ? queryInfoFromQueryBuilder(queryInput) : { _tag: 'None' }),
|
|
113
102
|
}
|
|
114
103
|
}
|
|
115
104
|
|
|
105
|
+
const bindValuesToDepKey = (bindValues: Bindable | undefined): DepKey => {
|
|
106
|
+
if (bindValues === undefined) {
|
|
107
|
+
return []
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return Object.entries(bindValues)
|
|
111
|
+
.map(([key, value]: [string, any]) => `${key}:${value === SessionIdSymbol ? 'SessionIdSymbol' : value}`)
|
|
112
|
+
.join(',')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const getQueryStringAndExtraDeps = (
|
|
116
|
+
queryInput: QueryInput<any, any> | ((get: GetAtomResult) => QueryInput<any, any>),
|
|
117
|
+
): { queryString: string; extraDeps: DepKey } => {
|
|
118
|
+
if (isQueryBuilder(queryInput)) {
|
|
119
|
+
const { query, bindValues } = queryInput.asSql()
|
|
120
|
+
return { queryString: query, extraDeps: bindValuesToDepKey(bindValues) }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (isQueryInputRaw(queryInput)) {
|
|
124
|
+
return { queryString: queryInput.query, extraDeps: bindValuesToDepKey(queryInput.bindValues) }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (typeof queryInput === 'function') {
|
|
128
|
+
return { queryString: queryInput.toString(), extraDeps: [] }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return shouldNeverHappen(`Invalid query input: ${queryInput}`)
|
|
132
|
+
}
|
|
133
|
+
|
|
116
134
|
/* An object encapsulating a reactive SQL query */
|
|
117
|
-
export class LiveStoreDbQuery<
|
|
118
|
-
TResultSchema,
|
|
119
|
-
TResult = TResultSchema,
|
|
120
|
-
TQueryInfo extends QueryInfo = QueryInfo.None,
|
|
121
|
-
> extends LiveStoreQueryBase<TResult, TQueryInfo> {
|
|
135
|
+
export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends LiveStoreQueryBase<TResult> {
|
|
122
136
|
_tag: 'db' = 'db'
|
|
123
137
|
|
|
124
138
|
/** A reactive thunk representing the query text */
|
|
125
|
-
queryInput$: Thunk<QueryInputRaw<any, any
|
|
139
|
+
queryInput$: Thunk<QueryInputRaw<any, any>, ReactivityGraphContext, RefreshReason> | undefined
|
|
126
140
|
|
|
127
141
|
/** A reactive thunk representing the query results */
|
|
128
142
|
results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
|
|
129
143
|
|
|
130
144
|
label: string
|
|
131
145
|
|
|
132
|
-
queryInfo: TQueryInfo
|
|
133
|
-
|
|
134
146
|
readonly reactivityGraph
|
|
135
147
|
|
|
136
148
|
private mapResult: (rows: TResultSchema) => TResult
|
|
@@ -140,23 +152,20 @@ export class LiveStoreDbQuery<
|
|
|
140
152
|
label: inputLabel,
|
|
141
153
|
reactivityGraph,
|
|
142
154
|
map,
|
|
143
|
-
queryInfo: inputQueryInfo,
|
|
144
155
|
otelContext,
|
|
145
156
|
}: {
|
|
146
157
|
label?: string
|
|
147
158
|
queryInput:
|
|
148
|
-
| QueryInput<TResultSchema, ReadonlyArray<any
|
|
149
|
-
| ((get: GetAtomResult, ctx: ReactivityGraphContext) => QueryInput<TResultSchema, ReadonlyArray<any
|
|
159
|
+
| QueryInput<TResultSchema, ReadonlyArray<any>>
|
|
160
|
+
| ((get: GetAtomResult, ctx: ReactivityGraphContext) => QueryInput<TResultSchema, ReadonlyArray<any>>)
|
|
150
161
|
reactivityGraph: ReactivityGraph
|
|
151
162
|
map?: (rows: TResultSchema) => TResult
|
|
152
|
-
queryInfo?: TQueryInfo
|
|
153
163
|
/** Only used for the initial query execution */
|
|
154
164
|
otelContext?: otel.Context
|
|
155
165
|
}) {
|
|
156
166
|
super()
|
|
157
167
|
|
|
158
168
|
let label = inputLabel ?? 'db(unknown)'
|
|
159
|
-
let queryInfo = inputQueryInfo ?? ({ _tag: 'None' } as TQueryInfo)
|
|
160
169
|
this.reactivityGraph = reactivityGraph
|
|
161
170
|
|
|
162
171
|
this.mapResult = map === undefined ? (rows: any) => rows as TResult : map
|
|
@@ -172,7 +181,7 @@ export class LiveStoreDbQuery<
|
|
|
172
181
|
current: undefined,
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
type TQueryInputRaw = QueryInputRaw<any, any
|
|
184
|
+
type TQueryInputRaw = QueryInputRaw<any, any>
|
|
176
185
|
|
|
177
186
|
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, ReactivityGraphContext, RefreshReason>
|
|
178
187
|
|
|
@@ -188,14 +197,13 @@ export class LiveStoreDbQuery<
|
|
|
188
197
|
schema,
|
|
189
198
|
bindValues: qbRes.bindValues,
|
|
190
199
|
queriedTables: new Set([ast.tableDef.sqliteDef.name]),
|
|
191
|
-
queryInfo: queryInfoFromQueryBuilder(qb),
|
|
192
200
|
} satisfies TQueryInputRaw,
|
|
193
201
|
label: ast._tag === 'RowQuery' ? rowQueryLabel(ast.tableDef, ast.id) : qb.toString(),
|
|
194
202
|
execBeforeFirstRun:
|
|
195
203
|
ast._tag === 'RowQuery'
|
|
196
204
|
? makeExecBeforeFirstRun({
|
|
197
205
|
table: ast.tableDef,
|
|
198
|
-
|
|
206
|
+
explicitDefaultValues: ast.explicitDefaultValues,
|
|
199
207
|
id: ast.id,
|
|
200
208
|
otelContext,
|
|
201
209
|
})
|
|
@@ -232,10 +240,6 @@ export class LiveStoreDbQuery<
|
|
|
232
240
|
|
|
233
241
|
schemaRef.current = queryInputRaw.schema
|
|
234
242
|
|
|
235
|
-
if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
|
|
236
|
-
queryInfo = queryInputRaw.queryInfo as TQueryInfo
|
|
237
|
-
}
|
|
238
|
-
|
|
239
243
|
return queryInputRaw
|
|
240
244
|
},
|
|
241
245
|
{
|
|
@@ -268,10 +272,6 @@ export class LiveStoreDbQuery<
|
|
|
268
272
|
label = `db(${rowQueryLabel(ast.tableDef, ast.id)})`
|
|
269
273
|
}
|
|
270
274
|
}
|
|
271
|
-
|
|
272
|
-
if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
|
|
273
|
-
queryInfo = queryInputRaw.queryInfo as TQueryInfo
|
|
274
|
-
}
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
const queriedTablesRef: { current: Set<string> | undefined } = { current: undefined }
|
|
@@ -352,9 +352,9 @@ export class LiveStoreDbQuery<
|
|
|
352
352
|
const expectedSchemaStr = String(schemaRef.current!.ast)
|
|
353
353
|
const bindValuesStr = bindValues === undefined ? '' : `\nBind values: ${JSON.stringify(bindValues)}`
|
|
354
354
|
|
|
355
|
-
|
|
355
|
+
return shouldNeverHappen(
|
|
356
356
|
`\
|
|
357
|
-
Error parsing SQL query result.
|
|
357
|
+
Error parsing SQL query result (${label}).
|
|
358
358
|
|
|
359
359
|
Query: ${sqlString}\
|
|
360
360
|
${bindValuesStr}
|
|
@@ -365,8 +365,8 @@ Error: ${parseErrorStr}
|
|
|
365
365
|
|
|
366
366
|
Result:`,
|
|
367
367
|
rawDbResults,
|
|
368
|
+
'\n',
|
|
368
369
|
)
|
|
369
|
-
return shouldNeverHappen(`Error parsing SQL query result: ${parsedResult.left}`)
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
const result = this.mapResult(parsedResult.right)
|
|
@@ -388,7 +388,6 @@ Result:`,
|
|
|
388
388
|
this.results$ = results$
|
|
389
389
|
|
|
390
390
|
this.label = label
|
|
391
|
-
this.queryInfo = queryInfo
|
|
392
391
|
}
|
|
393
392
|
|
|
394
393
|
destroy = () => {
|
|
@@ -405,8 +404,3 @@ Result:`,
|
|
|
405
404
|
}
|
|
406
405
|
}
|
|
407
406
|
}
|
|
408
|
-
|
|
409
|
-
const queryInfoFromQueryBuilder = (qb: QueryBuilder.Any): QueryInfo.Row | QueryInfo.None => {
|
|
410
|
-
const ast = qb[QueryBuilderAstSymbol]
|
|
411
|
-
return ast._tag === 'RowQuery' ? { _tag: 'Row', table: ast.tableDef, id: ast.id } : { _tag: 'None' }
|
|
412
|
-
}
|
|
@@ -5,6 +5,8 @@ import type { RefreshReason } from '../store/store-types.js'
|
|
|
5
5
|
import type { ILiveQueryRef, ILiveQueryRefDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
|
|
6
6
|
import { withRCMap } from './base-class.js'
|
|
7
7
|
|
|
8
|
+
// TODO rename to `signal`
|
|
9
|
+
|
|
8
10
|
export const makeRef = <T>(
|
|
9
11
|
defaultValue: T,
|
|
10
12
|
options?: {
|
package/src/mod.ts
CHANGED
|
@@ -13,8 +13,6 @@ export {
|
|
|
13
13
|
|
|
14
14
|
export { SqliteDbWrapper, emptyDebugInfo } from './SqliteDbWrapper.js'
|
|
15
15
|
|
|
16
|
-
export { deriveColQuery } from './row-query-utils.js'
|
|
17
|
-
|
|
18
16
|
export { queryDb, computed, makeRef, type LiveQuery, type LiveQueryDef } from './live-queries/mod.js'
|
|
19
17
|
|
|
20
18
|
export * from '@livestore/common/schema'
|
package/src/reactive.ts
CHANGED
|
@@ -203,7 +203,7 @@ export class ReactiveGraph<
|
|
|
203
203
|
|
|
204
204
|
context: TContext | undefined
|
|
205
205
|
|
|
206
|
-
debugRefreshInfos: BoundArray<RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo>> = new BoundArray(
|
|
206
|
+
debugRefreshInfos: BoundArray<RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo>> = new BoundArray(200)
|
|
207
207
|
|
|
208
208
|
private currentDebugRefresh:
|
|
209
209
|
| { refreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[]; startMs: DOMHighResTimeStamp }
|
|
@@ -267,7 +267,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
|
|
|
267
267
|
// But for now this is a good enough approximation with little downsides
|
|
268
268
|
__runningInDevtools: getDevtoolsEnabled(disableDevtools) === false,
|
|
269
269
|
confirmUnsavedChanges,
|
|
270
|
-
// NOTE during boot we're not yet executing
|
|
270
|
+
// NOTE during boot we're not yet executing events in a batched context
|
|
271
271
|
// but only set the provided `batchUpdates` function after boot
|
|
272
272
|
batchUpdates: (run) => run(),
|
|
273
273
|
storeId,
|
|
@@ -276,7 +276,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
|
|
|
276
276
|
},
|
|
277
277
|
})
|
|
278
278
|
|
|
279
|
-
// Starts background fibers (syncing,
|
|
279
|
+
// Starts background fibers (syncing, event processing, etc) for store
|
|
280
280
|
yield* store.boot
|
|
281
281
|
|
|
282
282
|
if (boot !== undefined) {
|
package/src/store/store-types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ClientSession, IntentionalShutdownCause, StoreInterrupted, UnexpectedError } from '@livestore/common'
|
|
2
|
-
import type {
|
|
2
|
+
import type { LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
|
|
3
3
|
import type { Effect, Runtime, Scope } from '@livestore/utils/effect'
|
|
4
4
|
import { Deferred } from '@livestore/utils/effect'
|
|
5
5
|
import type * as otel from '@opentelemetry/api'
|
|
@@ -57,8 +57,8 @@ export type RefreshReason =
|
|
|
57
57
|
| DebugRefreshReasonBase
|
|
58
58
|
| {
|
|
59
59
|
_tag: 'commit'
|
|
60
|
-
/** The
|
|
61
|
-
|
|
60
|
+
/** The events that were applied */
|
|
61
|
+
events: ReadonlyArray<LiveStoreEvent.AnyDecoded | LiveStoreEvent.PartialAnyDecoded>
|
|
62
62
|
|
|
63
63
|
/** The tables that were written to by the event */
|
|
64
64
|
writeTables: ReadonlyArray<string>
|
|
@@ -83,11 +83,11 @@ export type QueryDebugInfo = {
|
|
|
83
83
|
|
|
84
84
|
export type StoreOtel = {
|
|
85
85
|
tracer: otel.Tracer
|
|
86
|
-
|
|
86
|
+
commitsSpanContext: otel.Context
|
|
87
87
|
queriesSpanContext: otel.Context
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
export type
|
|
90
|
+
export type StoreCommitOptions = {
|
|
91
91
|
label?: string
|
|
92
92
|
skipRefresh?: boolean
|
|
93
93
|
spanLinks?: otel.Link[]
|