@livestore/livestore 0.0.0-snapshot-2b8a9de3ec1a701aca891ebc2c98eb328274ae9e → 0.0.0-snapshot-2c861249e50661661613204300b1fc0d902c2e46
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 +7 -1
- package/dist/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +8 -2
- 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/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 +31 -30
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +7 -7
- 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/live-queries/row-query-utils.d.ts +12 -0
- package/dist/live-queries/row-query-utils.d.ts.map +1 -0
- package/dist/live-queries/row-query-utils.js +18 -0
- package/dist/live-queries/row-query-utils.js.map +1 -0
- 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.d.ts +12 -2
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +18 -11
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/store-types.d.ts +11 -10
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store.d.ts +29 -30
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +84 -85
- 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/package.json +4 -4
- package/src/SqliteDbWrapper.ts +12 -3
- 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/computed.ts +4 -18
- package/src/live-queries/db-query.test.ts +7 -7
- package/src/live-queries/db-query.ts +57 -63
- package/src/live-queries/make-ref.ts +2 -0
- package/src/live-queries/row-query-utils.ts +50 -0
- package/src/mod.ts +0 -2
- package/src/reactive.ts +1 -1
- package/src/store/create-store.ts +33 -15
- package/src/store/store-types.ts +11 -11
- package/src/store/store.ts +120 -122
- package/src/utils/stack-info.test.ts +6 -6
- package/src/utils/tests/fixture.ts +21 -25
- 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
|
@@ -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'
|
|
@@ -64,12 +64,12 @@ Vitest.describe('otel', () => {
|
|
|
64
64
|
|
|
65
65
|
const query$ = queryDb({
|
|
66
66
|
query: `select * from todos`,
|
|
67
|
-
schema: Schema.Array(tables.todos.
|
|
67
|
+
schema: Schema.Array(tables.todos.rowSchema),
|
|
68
68
|
queriedTables: new Set(['todos']),
|
|
69
69
|
})
|
|
70
70
|
expect(store.query(query$)).toMatchInlineSnapshot('[]')
|
|
71
71
|
|
|
72
|
-
store.commit(
|
|
72
|
+
store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
73
73
|
|
|
74
74
|
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
75
75
|
[
|
|
@@ -100,7 +100,7 @@ Vitest.describe('otel', () => {
|
|
|
100
100
|
const query$ = queryDb(
|
|
101
101
|
(get) => ({
|
|
102
102
|
query: `select * from todos ${get(filter)}`,
|
|
103
|
-
schema: Schema.Array(tables.todos.
|
|
103
|
+
schema: Schema.Array(tables.todos.rowSchema).pipe(Schema.headOrElse(() => defaultTodo)),
|
|
104
104
|
}),
|
|
105
105
|
{ label: 'all todos' },
|
|
106
106
|
)
|
|
@@ -117,7 +117,7 @@ Vitest.describe('otel', () => {
|
|
|
117
117
|
|
|
118
118
|
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
119
119
|
|
|
120
|
-
store.commit(
|
|
120
|
+
store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
121
121
|
|
|
122
122
|
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
123
123
|
|
|
@@ -147,7 +147,7 @@ Vitest.describe('otel', () => {
|
|
|
147
147
|
const defaultTodo = { id: '', text: '', completed: false }
|
|
148
148
|
|
|
149
149
|
const filter = computed(() => ({ completed: false }))
|
|
150
|
-
const query$ = queryDb((get) => tables.todos.
|
|
150
|
+
const query$ = queryDb((get) => tables.todos.where(get(filter)).first({ fallback: () => defaultTodo }))
|
|
151
151
|
|
|
152
152
|
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
153
153
|
{
|
|
@@ -157,7 +157,7 @@ Vitest.describe('otel', () => {
|
|
|
157
157
|
}
|
|
158
158
|
`)
|
|
159
159
|
|
|
160
|
-
store.commit(
|
|
160
|
+
store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
161
161
|
|
|
162
162
|
expect(store.query(query$)).toMatchInlineSnapshot(`
|
|
163
163
|
{
|
|
@@ -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 './row-query-utils.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 }
|
|
@@ -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?: {
|
|
@@ -0,0 +1,50 @@
|
|
|
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 = (table: State.SQLite.TableDefBase, id: string | SessionIdSymbol | number | undefined) =>
|
|
10
|
+
`row:${table.sqliteDef.name}${id === undefined ? '' : id === SessionIdSymbol ? `:sessionId` : `:${id}`}`
|
|
11
|
+
|
|
12
|
+
export const makeExecBeforeFirstRun =
|
|
13
|
+
({
|
|
14
|
+
id,
|
|
15
|
+
explicitDefaultValues,
|
|
16
|
+
table,
|
|
17
|
+
otelContext: otelContext_,
|
|
18
|
+
}: {
|
|
19
|
+
id?: string | SessionIdSymbol | number
|
|
20
|
+
explicitDefaultValues?: any
|
|
21
|
+
table: State.SQLite.TableDefBase
|
|
22
|
+
otelContext: otel.Context | undefined
|
|
23
|
+
}) =>
|
|
24
|
+
({ store }: ReactivityGraphContext) => {
|
|
25
|
+
if (State.SQLite.tableIsClientDocumentTable(table) === false) {
|
|
26
|
+
return shouldNeverHappen(
|
|
27
|
+
`Cannot insert row for table "${table.sqliteDef.name}" which does not have 'deriveEvents: true' set`,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const otelContext = otelContext_ ?? store.otel.queriesSpanContext
|
|
32
|
+
|
|
33
|
+
const idVal = id === SessionIdSymbol ? store.sessionId : id!
|
|
34
|
+
const rowExists =
|
|
35
|
+
store.sqliteDbWrapper.select(
|
|
36
|
+
`SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`,
|
|
37
|
+
[idVal] as any as PreparedBindValues,
|
|
38
|
+
{ otelContext },
|
|
39
|
+
).length === 1
|
|
40
|
+
|
|
41
|
+
if (rowExists) return
|
|
42
|
+
|
|
43
|
+
// It's important that we only commit and don't refresh here, as this function might be called during a render
|
|
44
|
+
// and otherwise we might end up in a "reactive loop"
|
|
45
|
+
|
|
46
|
+
store.commit(
|
|
47
|
+
{ otelContext, skipRefresh: true, label: `rowQuery:${table.sqliteDef.name}:${idVal}` },
|
|
48
|
+
table.set(explicitDefaultValues, idVal as TODO),
|
|
49
|
+
)
|
|
50
|
+
}
|
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 }
|
|
@@ -7,8 +7,8 @@ import type {
|
|
|
7
7
|
MigrationsReport,
|
|
8
8
|
} from '@livestore/common'
|
|
9
9
|
import { provideOtel, UnexpectedError } from '@livestore/common'
|
|
10
|
-
import type {
|
|
11
|
-
import { LS_DEV } from '@livestore/utils'
|
|
10
|
+
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
11
|
+
import { isDevEnv, LS_DEV } from '@livestore/utils'
|
|
12
12
|
import type { Cause, Schema } from '@livestore/utils/effect'
|
|
13
13
|
import {
|
|
14
14
|
Context,
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
Layer,
|
|
20
20
|
Logger,
|
|
21
21
|
LogLevel,
|
|
22
|
-
MutableHashMap,
|
|
23
22
|
OtelTracer,
|
|
24
23
|
Queue,
|
|
25
24
|
Runtime,
|
|
@@ -74,7 +73,12 @@ export type LiveStoreContextProps<TSchema extends LiveStoreSchema, TContext = {}
|
|
|
74
73
|
store: Store<TSchema, TContext>,
|
|
75
74
|
) => Effect.Effect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
|
|
76
75
|
adapter: Adapter
|
|
77
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Whether to disable devtools.
|
|
78
|
+
*
|
|
79
|
+
* @default 'auto'
|
|
80
|
+
*/
|
|
81
|
+
disableDevtools?: boolean | 'auto'
|
|
78
82
|
onBootStatus?: (status: BootStatus) => void
|
|
79
83
|
batchUpdates: (run: () => void) => void
|
|
80
84
|
}
|
|
@@ -92,7 +96,12 @@ export interface CreateStoreOptions<TSchema extends LiveStoreSchema, TContext =
|
|
|
92
96
|
},
|
|
93
97
|
) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
|
|
94
98
|
batchUpdates?: (run: () => void) => void
|
|
95
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Whether to disable devtools.
|
|
101
|
+
*
|
|
102
|
+
* @default 'auto'
|
|
103
|
+
*/
|
|
104
|
+
disableDevtools?: boolean | 'auto'
|
|
96
105
|
onBootStatus?: (status: BootStatus) => void
|
|
97
106
|
shutdownDeferred?: ShutdownDeferred
|
|
98
107
|
/**
|
|
@@ -229,7 +238,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
|
|
|
229
238
|
const clientSession: ClientSession = yield* adapter({
|
|
230
239
|
schema,
|
|
231
240
|
storeId,
|
|
232
|
-
devtoolsEnabled: disableDevtools
|
|
241
|
+
devtoolsEnabled: getDevtoolsEnabled(disableDevtools),
|
|
233
242
|
bootStatusQueue,
|
|
234
243
|
shutdown,
|
|
235
244
|
connectDevtoolsToStore: connectDevtoolsToStore_,
|
|
@@ -248,20 +257,17 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
|
|
|
248
257
|
)
|
|
249
258
|
}
|
|
250
259
|
|
|
251
|
-
// TODO fill up with unsynced mutation events from the client session
|
|
252
|
-
const unsyncedMutationEvents = MutableHashMap.empty<EventId.EventId, MutationEvent.ForSchema<TSchema>>()
|
|
253
|
-
|
|
254
260
|
const store = new Store<TSchema, TContext>({
|
|
255
261
|
clientSession,
|
|
256
262
|
schema,
|
|
257
263
|
context,
|
|
258
264
|
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
265
|
+
effectContext: { lifetimeScope, runtime },
|
|
266
|
+
// TODO find a better way to detect if we're running LiveStore in the LiveStore devtools
|
|
267
|
+
// But for now this is a good enough approximation with little downsides
|
|
268
|
+
__runningInDevtools: getDevtoolsEnabled(disableDevtools) === false,
|
|
263
269
|
confirmUnsavedChanges,
|
|
264
|
-
// NOTE during boot we're not yet executing
|
|
270
|
+
// NOTE during boot we're not yet executing events in a batched context
|
|
265
271
|
// but only set the provided `batchUpdates` function after boot
|
|
266
272
|
batchUpdates: (run) => run(),
|
|
267
273
|
storeId,
|
|
@@ -270,7 +276,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
|
|
|
270
276
|
},
|
|
271
277
|
})
|
|
272
278
|
|
|
273
|
-
// Starts background fibers (syncing,
|
|
279
|
+
// Starts background fibers (syncing, event processing, etc) for store
|
|
274
280
|
yield* store.boot
|
|
275
281
|
|
|
276
282
|
if (boot !== undefined) {
|
|
@@ -314,3 +320,15 @@ const validateStoreId = (storeId: string) =>
|
|
|
314
320
|
})
|
|
315
321
|
}
|
|
316
322
|
})
|
|
323
|
+
|
|
324
|
+
const getDevtoolsEnabled = (disableDevtools: boolean | 'auto' | undefined) => {
|
|
325
|
+
if (disableDevtools === true) {
|
|
326
|
+
return false
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (isDevEnv() === true) {
|
|
330
|
+
return true
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return false
|
|
334
|
+
}
|
package/src/store/store-types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ClientSession, IntentionalShutdownCause, StoreInterrupted, UnexpectedError } from '@livestore/common'
|
|
2
|
-
import type {
|
|
3
|
-
import type { Effect,
|
|
2
|
+
import type { LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
|
|
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'
|
|
6
6
|
|
|
@@ -41,24 +41,24 @@ export type StoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema, TCon
|
|
|
41
41
|
storeId: string
|
|
42
42
|
context: TContext
|
|
43
43
|
otelOptions: OtelOptions
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
effectContext: {
|
|
45
|
+
runtime: Runtime.Runtime<Scope.Scope>
|
|
46
|
+
lifetimeScope: Scope.Scope
|
|
47
|
+
}
|
|
47
48
|
confirmUnsavedChanges: boolean
|
|
48
49
|
batchUpdates: (runUpdates: () => void) => void
|
|
49
|
-
// TODO validate whether we still need this
|
|
50
|
-
unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId.EventId, MutationEvent.ForSchema<TSchema>>
|
|
51
50
|
params: {
|
|
52
51
|
leaderPushBatchSize: number
|
|
53
52
|
}
|
|
53
|
+
__runningInDevtools: boolean
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
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[]
|