@livestore/livestore 0.3.0-dev.5 → 0.3.0-dev.50
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/QueryCache.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.d.ts +60 -0
- package/dist/SqliteDbWrapper.d.ts.map +1 -0
- package/dist/{SynchronousDatabaseWrapper.js → SqliteDbWrapper.js} +69 -34
- package/dist/SqliteDbWrapper.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +6 -34
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +10 -12
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/mod.d.ts +3 -0
- package/dist/effect/mod.d.ts.map +1 -0
- package/dist/effect/mod.js +3 -0
- package/dist/effect/mod.js.map +1 -0
- package/dist/internal/mod.d.ts +3 -0
- package/dist/internal/mod.d.ts.map +1 -0
- package/dist/internal/mod.js +3 -0
- package/dist/internal/mod.js.map +1 -0
- package/dist/live-queries/base-class.d.ts +65 -27
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +54 -13
- 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 +12 -14
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +37 -15
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.d.ts +93 -0
- package/dist/live-queries/db-query.d.ts.map +1 -0
- package/dist/live-queries/{db.js → db-query.js} +111 -40
- package/dist/live-queries/db-query.js.map +1 -0
- package/dist/live-queries/db-query.test.d.ts +2 -0
- package/dist/live-queries/db-query.test.d.ts.map +1 -0
- package/dist/live-queries/db-query.test.js +133 -0
- package/dist/live-queries/db-query.test.js.map +1 -0
- package/dist/live-queries/mod.d.ts +5 -0
- package/dist/live-queries/mod.d.ts.map +1 -0
- package/dist/live-queries/mod.js +5 -0
- package/dist/live-queries/mod.js.map +1 -0
- package/dist/live-queries/signal.d.ts +20 -0
- package/dist/live-queries/signal.d.ts.map +1 -0
- package/dist/live-queries/signal.js +33 -0
- package/dist/live-queries/signal.js.map +1 -0
- package/dist/live-queries/signal.test.d.ts +2 -0
- package/dist/live-queries/signal.test.d.ts.map +1 -0
- package/dist/live-queries/signal.test.js +25 -0
- package/dist/live-queries/signal.test.js.map +1 -0
- package/dist/mod.d.ts +14 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +13 -0
- package/dist/mod.js.map +1 -0
- package/dist/reactive.d.ts +23 -17
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +23 -19
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +1 -1
- package/dist/reactive.test.js.map +1 -1
- package/dist/store/create-store.d.ts +70 -12
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +68 -19
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +5 -4
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +92 -40
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +54 -42
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js +2 -5
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store.d.ts +141 -35
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +319 -153
- package/dist/store/store.js.map +1 -1
- package/dist/utils/data-structures.d.ts.map +1 -1
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js +6 -1
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/function-string.d.ts +7 -0
- package/dist/utils/function-string.d.ts.map +1 -0
- package/dist/utils/function-string.js +9 -0
- package/dist/utils/function-string.js.map +1 -0
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +6 -1
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/stack-info.test.js +54 -1
- package/dist/utils/stack-info.test.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +59 -216
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +23 -18
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/mod.d.ts +1 -0
- package/dist/utils/tests/mod.d.ts.map +1 -1
- package/dist/utils/tests/mod.js +1 -0
- package/dist/utils/tests/mod.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 +29 -26
- package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +92 -42
- package/src/effect/LiveStore.ts +27 -64
- package/src/effect/{index.ts → mod.ts} +2 -3
- package/src/internal/mod.ts +2 -0
- package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +241 -45
- package/src/live-queries/base-class.ts +152 -50
- package/src/live-queries/client-document-get-query.ts +52 -0
- package/src/live-queries/computed.ts +51 -33
- package/src/live-queries/db-query.test.ts +192 -0
- package/src/live-queries/{db.ts → db-query.ts} +168 -81
- package/src/live-queries/mod.ts +4 -0
- package/src/live-queries/signal.test.ts +40 -0
- package/src/live-queries/signal.ts +47 -0
- package/src/mod.ts +42 -0
- package/src/reactive.test.ts +1 -1
- package/src/reactive.ts +66 -43
- package/src/store/create-store.ts +188 -62
- package/src/store/devtools.ts +124 -46
- package/src/store/store-types.ts +54 -43
- package/src/store/store.ts +454 -236
- package/src/utils/dev.ts +6 -1
- package/src/utils/function-string.ts +12 -0
- package/src/utils/stack-info.test.ts +58 -1
- package/src/utils/stack-info.ts +6 -1
- package/src/utils/tests/fixture.ts +22 -31
- package/src/utils/tests/mod.ts +1 -0
- package/src/utils/tests/otel.ts +10 -3
- package/dist/SynchronousDatabaseWrapper.d.ts +0 -41
- package/dist/SynchronousDatabaseWrapper.d.ts.map +0 -1
- package/dist/SynchronousDatabaseWrapper.js.map +0 -1
- package/dist/effect/index.d.ts +0 -2
- package/dist/effect/index.d.ts.map +0 -1
- package/dist/effect/index.js +0 -2
- package/dist/effect/index.js.map +0 -1
- package/dist/global-state.d.ts +0 -14
- package/dist/global-state.d.ts.map +0 -1
- package/dist/global-state.js +0 -16
- package/dist/global-state.js.map +0 -1
- package/dist/index.d.ts +0 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -16
- package/dist/index.js.map +0 -1
- package/dist/live-queries/db.d.ts +0 -66
- package/dist/live-queries/db.d.ts.map +0 -1
- package/dist/live-queries/db.js.map +0 -1
- package/dist/live-queries/db.test.d.ts +0 -2
- package/dist/live-queries/db.test.d.ts.map +0 -1
- package/dist/live-queries/db.test.js +0 -118
- package/dist/live-queries/db.test.js.map +0 -1
- package/dist/live-queries/graphql.d.ts +0 -49
- package/dist/live-queries/graphql.d.ts.map +0 -1
- package/dist/live-queries/graphql.js +0 -122
- package/dist/live-queries/graphql.js.map +0 -1
- 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 -30
- package/dist/row-query-utils.js.map +0 -1
- package/dist/utils/otel.d.ts +0 -4
- package/dist/utils/otel.d.ts.map +0 -1
- package/dist/utils/otel.js +0 -6
- package/dist/utils/otel.js.map +0 -1
- package/src/global-state.ts +0 -20
- package/src/index.ts +0 -66
- package/src/live-queries/db.test.ts +0 -154
- package/src/live-queries/graphql.ts +0 -219
- package/src/row-query-utils.ts +0 -65
- package/src/utils/otel.ts +0 -9
- package/tsconfig.json +0 -18
- package/vitest.config.js +0 -9
@@ -1,26 +1,27 @@
|
|
1
|
-
import type { Bindable, QueryBuilder
|
1
|
+
import type { Bindable, QueryBuilder } from '@livestore/common'
|
2
2
|
import {
|
3
|
+
getDurationMsFromSpan,
|
3
4
|
getResultSchema,
|
4
5
|
isQueryBuilder,
|
5
6
|
prepareBindValues,
|
6
7
|
QueryBuilderAstSymbol,
|
7
8
|
replaceSessionIdSymbol,
|
9
|
+
SessionIdSymbol,
|
8
10
|
UnexpectedError,
|
9
11
|
} from '@livestore/common'
|
10
12
|
import { deepEqual, shouldNeverHappen } from '@livestore/utils'
|
11
13
|
import { Predicate, Schema, TreeFormatter } from '@livestore/utils/effect'
|
12
14
|
import * as otel from '@opentelemetry/api'
|
13
15
|
|
14
|
-
import { globalReactivityGraph } from '../global-state.js'
|
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
|
-
import {
|
20
|
-
import type {
|
21
|
-
import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
|
19
|
+
import { isValidFunctionString } from '../utils/function-string.js'
|
20
|
+
import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
|
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,104 +31,178 @@ 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
|
-
|
34
|
-
execBeforeFirstRun?: (ctx: QueryContext) => void
|
34
|
+
execBeforeFirstRun?: (ctx: ReactivityGraphContext) => void
|
35
35
|
}
|
36
36
|
|
37
|
-
export
|
38
|
-
|
39
|
-
|
37
|
+
export const isQueryInputRaw = (value: unknown): value is QueryInputRaw<any, any> =>
|
38
|
+
Predicate.hasProperty(value, 'query') && Predicate.hasProperty(value, 'schema')
|
39
|
+
|
40
|
+
export type QueryInput<TDecoded, TEncoded> = QueryInputRaw<TDecoded, TEncoded> | QueryBuilder<TDecoded, any, any>
|
40
41
|
|
41
42
|
/**
|
42
|
-
* NOTE `
|
43
|
+
* NOTE `queryDb` is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
|
44
|
+
*
|
45
|
+
* When using contextual data when constructing the query, please make sure to include it in the `deps` option.
|
46
|
+
*
|
47
|
+
* @example
|
48
|
+
* ```ts
|
49
|
+
* const todos$ = queryDb(tables.todos.where({ complete: true }))
|
50
|
+
* ```
|
51
|
+
*
|
52
|
+
* @example
|
53
|
+
* ```ts
|
54
|
+
* // Group-by raw SQL query
|
55
|
+
* const colorCounts$ = queryDb({
|
56
|
+
* query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
|
57
|
+
* schema: Schema.Array(Schema.Struct({
|
58
|
+
* color: Schema.String,
|
59
|
+
* count: Schema.Number,
|
60
|
+
* })),
|
61
|
+
* bindValues: [1],
|
62
|
+
* })
|
63
|
+
* ```
|
64
|
+
*
|
65
|
+
* @example
|
66
|
+
* ```ts
|
67
|
+
* // Using contextual data when constructing the query
|
68
|
+
* const makeFilteredQuery = (filter: string) =>
|
69
|
+
* queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
|
70
|
+
*
|
71
|
+
* const filteredTodos$ = makeFilteredQuery('buy coffee')
|
72
|
+
* ```
|
43
73
|
*/
|
44
74
|
export const queryDb: {
|
45
|
-
<TResultSchema, TResult = TResultSchema
|
46
|
-
queryInput:
|
47
|
-
| QueryInputRaw<TResultSchema, ReadonlyArray<any>, TQueryInfo>
|
48
|
-
| QueryBuilder<TResultSchema, any, any, TQueryInfo>,
|
75
|
+
<TResultSchema, TResult = TResultSchema>(
|
76
|
+
queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>,
|
49
77
|
options?: {
|
50
78
|
map?: (rows: TResultSchema) => TResult
|
51
79
|
/**
|
52
80
|
* Used for debugging / devtools
|
53
81
|
*/
|
54
82
|
label?: string
|
55
|
-
|
56
|
-
otelContext?: otel.Context
|
83
|
+
deps?: DepKey
|
57
84
|
},
|
58
|
-
):
|
85
|
+
): LiveQueryDef<TResult>
|
59
86
|
// NOTE in this "thunk case", we can't directly derive label/queryInfo from the queryInput,
|
60
87
|
// so the caller needs to provide them explicitly otherwise queryInfo will be set to `None`,
|
61
88
|
// and label will be set during the query execution
|
62
|
-
<TResultSchema, TResult = TResultSchema
|
89
|
+
<TResultSchema, TResult = TResultSchema>(
|
63
90
|
queryInput:
|
64
|
-
| ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any
|
65
|
-
| ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any
|
91
|
+
| ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>)
|
92
|
+
| ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>),
|
66
93
|
options?: {
|
67
94
|
map?: (rows: TResultSchema) => TResult
|
68
95
|
/**
|
69
96
|
* Used for debugging / devtools
|
70
97
|
*/
|
71
98
|
label?: string
|
72
|
-
|
73
|
-
queryInfo?: TQueryInfo
|
74
|
-
otelContext?: otel.Context
|
99
|
+
deps?: DepKey
|
75
100
|
},
|
76
|
-
):
|
77
|
-
} = (queryInput, options) =>
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
101
|
+
): LiveQueryDef<TResult>
|
102
|
+
} = (queryInput, options) => {
|
103
|
+
const { queryString, extraDeps } = getQueryStringAndExtraDeps(queryInput)
|
104
|
+
|
105
|
+
const hash =
|
106
|
+
(options?.deps ? queryString + '-' + depsToString(options.deps) : queryString) + '-' + depsToString(extraDeps)
|
107
|
+
if (isValidFunctionString(hash)._tag === 'invalid') {
|
108
|
+
throw new Error(`On Expo/React Native, db queries must provide a \`deps\` option`)
|
109
|
+
}
|
110
|
+
|
111
|
+
if (hash.trim() === '') {
|
112
|
+
return shouldNeverHappen(`Invalid query hash for query: ${queryInput}`)
|
113
|
+
}
|
114
|
+
|
115
|
+
const label = options?.label ?? queryString
|
116
|
+
|
117
|
+
const def: LiveQueryDef.Any = {
|
118
|
+
_tag: 'def',
|
119
|
+
make: withRCMap(hash, (ctx, otelContext) => {
|
120
|
+
// TODO onDestroy
|
121
|
+
return new LiveStoreDbQuery({
|
122
|
+
reactivityGraph: ctx.reactivityGraph.deref()!,
|
123
|
+
queryInput,
|
124
|
+
label,
|
125
|
+
map: options?.map,
|
126
|
+
otelContext,
|
127
|
+
def,
|
128
|
+
})
|
129
|
+
}),
|
130
|
+
label,
|
131
|
+
hash,
|
132
|
+
}
|
133
|
+
|
134
|
+
return def
|
135
|
+
}
|
136
|
+
|
137
|
+
const bindValuesToDepKey = (bindValues: Bindable | undefined): DepKey => {
|
138
|
+
if (bindValues === undefined) {
|
139
|
+
return []
|
140
|
+
}
|
141
|
+
|
142
|
+
return Object.entries(bindValues)
|
143
|
+
.map(([key, value]: [string, any]) => `${key}:${value === SessionIdSymbol ? 'SessionIdSymbol' : value}`)
|
144
|
+
.join(',')
|
145
|
+
}
|
146
|
+
|
147
|
+
const getQueryStringAndExtraDeps = (
|
148
|
+
queryInput: QueryInput<any, any> | ((get: GetAtomResult) => QueryInput<any, any>),
|
149
|
+
): { queryString: string; extraDeps: DepKey } => {
|
150
|
+
if (isQueryBuilder(queryInput)) {
|
151
|
+
const { query, bindValues } = queryInput.asSql()
|
152
|
+
return { queryString: query, extraDeps: bindValuesToDepKey(bindValues) }
|
153
|
+
}
|
154
|
+
|
155
|
+
if (isQueryInputRaw(queryInput)) {
|
156
|
+
return { queryString: queryInput.query, extraDeps: bindValuesToDepKey(queryInput.bindValues) }
|
157
|
+
}
|
158
|
+
|
159
|
+
if (typeof queryInput === 'function') {
|
160
|
+
return { queryString: queryInput.toString(), extraDeps: [] }
|
161
|
+
}
|
162
|
+
|
163
|
+
return shouldNeverHappen(`Invalid query input: ${queryInput}`)
|
164
|
+
}
|
86
165
|
|
87
166
|
/* An object encapsulating a reactive SQL query */
|
88
|
-
export class LiveStoreDbQuery<
|
89
|
-
TResultSchema,
|
90
|
-
TResult = TResultSchema,
|
91
|
-
TQueryInfo extends QueryInfo = QueryInfo.None,
|
92
|
-
> extends LiveStoreQueryBase<TResult, TQueryInfo> {
|
167
|
+
export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends LiveStoreQueryBase<TResult> {
|
93
168
|
_tag: 'db' = 'db'
|
94
169
|
|
95
170
|
/** A reactive thunk representing the query text */
|
96
|
-
queryInput$: Thunk<
|
171
|
+
queryInput$: Thunk<QueryInputRaw<any, any>, ReactivityGraphContext, RefreshReason> | undefined
|
97
172
|
|
98
173
|
/** A reactive thunk representing the query results */
|
99
|
-
results$: Thunk<TResult,
|
174
|
+
results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
|
100
175
|
|
101
176
|
label: string
|
102
177
|
|
103
|
-
|
104
|
-
|
105
|
-
protected reactivityGraph
|
178
|
+
readonly reactivityGraph
|
106
179
|
|
107
180
|
private mapResult: (rows: TResultSchema) => TResult
|
181
|
+
def: LiveQueryDef<TResult>
|
108
182
|
|
109
183
|
constructor({
|
110
184
|
queryInput,
|
111
185
|
label: inputLabel,
|
112
186
|
reactivityGraph,
|
113
187
|
map,
|
114
|
-
queryInfo: inputQueryInfo,
|
115
188
|
otelContext,
|
189
|
+
def,
|
116
190
|
}: {
|
117
191
|
label?: string
|
118
192
|
queryInput:
|
119
|
-
| QueryInput<TResultSchema, ReadonlyArray<any
|
120
|
-
| ((get: GetAtomResult, ctx:
|
121
|
-
reactivityGraph
|
193
|
+
| QueryInput<TResultSchema, ReadonlyArray<any>>
|
194
|
+
| ((get: GetAtomResult, ctx: ReactivityGraphContext) => QueryInput<TResultSchema, ReadonlyArray<any>>)
|
195
|
+
reactivityGraph: ReactivityGraph
|
122
196
|
map?: (rows: TResultSchema) => TResult
|
123
|
-
|
197
|
+
/** Only used for the initial query execution */
|
124
198
|
otelContext?: otel.Context
|
199
|
+
def: LiveQueryDef<TResult>
|
125
200
|
}) {
|
126
201
|
super()
|
127
202
|
|
128
203
|
let label = inputLabel ?? 'db(unknown)'
|
129
|
-
|
130
|
-
this.
|
204
|
+
this.reactivityGraph = reactivityGraph
|
205
|
+
this.def = def
|
131
206
|
|
132
207
|
this.mapResult = map === undefined ? (rows: any) => rows as TResult : map
|
133
208
|
|
@@ -136,13 +211,15 @@ export class LiveStoreDbQuery<
|
|
136
211
|
typeof queryInput === 'function' ? undefined : isQueryBuilder(queryInput) ? undefined : queryInput.schema,
|
137
212
|
}
|
138
213
|
|
139
|
-
const execBeforeFirstRunRef: {
|
214
|
+
const execBeforeFirstRunRef: {
|
215
|
+
current: ((ctx: ReactivityGraphContext, otelContext: otel.Context) => void) | undefined
|
216
|
+
} = {
|
140
217
|
current: undefined,
|
141
218
|
}
|
142
219
|
|
143
|
-
type TQueryInputRaw = QueryInputRaw<any, any
|
220
|
+
type TQueryInputRaw = QueryInputRaw<any, any>
|
144
221
|
|
145
|
-
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw,
|
222
|
+
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, ReactivityGraphContext, RefreshReason>
|
146
223
|
|
147
224
|
const fromQueryBuilder = (qb: QueryBuilder.Any, otelContext: otel.Context | undefined) => {
|
148
225
|
try {
|
@@ -156,14 +233,13 @@ export class LiveStoreDbQuery<
|
|
156
233
|
schema,
|
157
234
|
bindValues: qbRes.bindValues,
|
158
235
|
queriedTables: new Set([ast.tableDef.sqliteDef.name]),
|
159
|
-
queryInfo: ast._tag === 'RowQuery' ? { _tag: 'Row', table: ast.tableDef, id: ast.id } : { _tag: 'None' },
|
160
236
|
} satisfies TQueryInputRaw,
|
161
237
|
label: ast._tag === 'RowQuery' ? rowQueryLabel(ast.tableDef, ast.id) : qb.toString(),
|
162
238
|
execBeforeFirstRun:
|
163
239
|
ast._tag === 'RowQuery'
|
164
240
|
? makeExecBeforeFirstRun({
|
165
241
|
table: ast.tableDef,
|
166
|
-
|
242
|
+
explicitDefaultValues: ast.explicitDefaultValues,
|
167
243
|
id: ast.id,
|
168
244
|
otelContext,
|
169
245
|
})
|
@@ -178,7 +254,10 @@ export class LiveStoreDbQuery<
|
|
178
254
|
queryInputRaw$OrQueryInputRaw = this.reactivityGraph.makeThunk(
|
179
255
|
(get, setDebugInfo, ctx, otelContext) => {
|
180
256
|
const startMs = performance.now()
|
181
|
-
const queryInputResult = queryInput(
|
257
|
+
const queryInputResult = queryInput(
|
258
|
+
makeGetAtomResult(get, ctx, otelContext ?? ctx.rootOtelContext, this.dependencyQueriesRef),
|
259
|
+
ctx,
|
260
|
+
)
|
182
261
|
const durationMs = performance.now() - startMs
|
183
262
|
|
184
263
|
let queryInputRaw: TQueryInputRaw
|
@@ -197,10 +276,6 @@ export class LiveStoreDbQuery<
|
|
197
276
|
|
198
277
|
schemaRef.current = queryInputRaw.schema
|
199
278
|
|
200
|
-
if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
|
201
|
-
queryInfo = queryInputRaw.queryInfo as TQueryInfo
|
202
|
-
}
|
203
|
-
|
204
279
|
return queryInputRaw
|
205
280
|
},
|
206
281
|
{
|
@@ -210,6 +285,8 @@ export class LiveStoreDbQuery<
|
|
210
285
|
equal: (a, b) => a.query === b.query && deepEqual(a.bindValues, b.bindValues),
|
211
286
|
},
|
212
287
|
)
|
288
|
+
|
289
|
+
this.queryInput$ = queryInputRaw$OrQueryInputRaw
|
213
290
|
} else {
|
214
291
|
let queryInputRaw: TQueryInputRaw
|
215
292
|
if (isQueryBuilder(queryInput)) {
|
@@ -231,10 +308,6 @@ export class LiveStoreDbQuery<
|
|
231
308
|
label = `db(${rowQueryLabel(ast.tableDef, ast.id)})`
|
232
309
|
}
|
233
310
|
}
|
234
|
-
|
235
|
-
if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
|
236
|
-
queryInfo = queryInputRaw.queryInfo as TQueryInfo
|
237
|
-
}
|
238
311
|
}
|
239
312
|
|
240
313
|
const queriedTablesRef: { current: Set<string> | undefined } = { current: undefined }
|
@@ -253,10 +326,16 @@ export class LiveStoreDbQuery<
|
|
253
326
|
: undefined
|
254
327
|
|
255
328
|
const results$ = this.reactivityGraph.makeThunk<TResult>(
|
256
|
-
(get, setDebugInfo, queryContext, otelContext) =>
|
329
|
+
(get, setDebugInfo, queryContext, otelContext, debugRefreshReason) =>
|
257
330
|
queryContext.otelTracer.startActiveSpan(
|
258
331
|
'db:...', // NOTE span name will be overridden further down
|
259
|
-
{
|
332
|
+
{
|
333
|
+
attributes: {
|
334
|
+
'livestore.debugRefreshReason': Predicate.hasProperty(debugRefreshReason, 'label')
|
335
|
+
? (debugRefreshReason.label as string)
|
336
|
+
: debugRefreshReason?._tag,
|
337
|
+
},
|
338
|
+
},
|
260
339
|
otelContext ?? queryContext.rootOtelContext,
|
261
340
|
(span) => {
|
262
341
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
@@ -268,14 +347,14 @@ export class LiveStoreDbQuery<
|
|
268
347
|
}
|
269
348
|
|
270
349
|
const queryInputResult = isThunk(queryInputRaw$OrQueryInputRaw)
|
271
|
-
? (get(queryInputRaw$OrQueryInputRaw, otelContext) as TQueryInputRaw)
|
350
|
+
? (get(queryInputRaw$OrQueryInputRaw, otelContext, debugRefreshReason) as TQueryInputRaw)
|
272
351
|
: (queryInputRaw$OrQueryInputRaw as TQueryInputRaw)
|
273
352
|
|
274
353
|
const sqlString = queryInputResult.query
|
275
354
|
const bindValues = queryInputResult.bindValues
|
276
355
|
|
277
356
|
if (queriedTablesRef.current === undefined) {
|
278
|
-
queriedTablesRef.current = store.
|
357
|
+
queriedTablesRef.current = store.sqliteDbWrapper.getTablesUsed(sqlString)
|
279
358
|
}
|
280
359
|
|
281
360
|
if (bindValues !== undefined) {
|
@@ -285,17 +364,20 @@ export class LiveStoreDbQuery<
|
|
285
364
|
// Establish a reactive dependency on the tables used in the query
|
286
365
|
for (const tableName of queriedTablesRef.current) {
|
287
366
|
const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
|
288
|
-
get(tableRef, otelContext)
|
367
|
+
get(tableRef, otelContext, debugRefreshReason)
|
289
368
|
}
|
290
369
|
|
291
370
|
span.setAttribute('sql.query', sqlString)
|
292
371
|
span.updateName(`db:${sqlString.slice(0, 50)}`)
|
293
372
|
|
294
|
-
const rawDbResults = store.
|
295
|
-
|
296
|
-
bindValues
|
297
|
-
|
298
|
-
|
373
|
+
const rawDbResults = store.sqliteDbWrapper.select<any>(
|
374
|
+
sqlString,
|
375
|
+
bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
|
376
|
+
{
|
377
|
+
queriedTables: queriedTablesRef.current,
|
378
|
+
otelContext,
|
379
|
+
},
|
380
|
+
)
|
299
381
|
|
300
382
|
span.setAttribute('sql.rowsCount', rawDbResults.length)
|
301
383
|
|
@@ -306,9 +388,9 @@ export class LiveStoreDbQuery<
|
|
306
388
|
const expectedSchemaStr = String(schemaRef.current!.ast)
|
307
389
|
const bindValuesStr = bindValues === undefined ? '' : `\nBind values: ${JSON.stringify(bindValues)}`
|
308
390
|
|
309
|
-
|
391
|
+
return shouldNeverHappen(
|
310
392
|
`\
|
311
|
-
Error parsing SQL query result.
|
393
|
+
Error parsing SQL query result (${label}).
|
312
394
|
|
313
395
|
Query: ${sqlString}\
|
314
396
|
${bindValuesStr}
|
@@ -319,8 +401,8 @@ Error: ${parseErrorStr}
|
|
319
401
|
|
320
402
|
Result:`,
|
321
403
|
rawDbResults,
|
404
|
+
'\n',
|
322
405
|
)
|
323
|
-
return shouldNeverHappen(`Error parsing SQL query result: ${parsedResult.left}`)
|
324
406
|
}
|
325
407
|
|
326
408
|
const result = this.mapResult(parsedResult.right)
|
@@ -342,14 +424,19 @@ Result:`,
|
|
342
424
|
this.results$ = results$
|
343
425
|
|
344
426
|
this.label = label
|
345
|
-
this.queryInfo = queryInfo
|
346
427
|
}
|
347
428
|
|
348
429
|
destroy = () => {
|
430
|
+
this.isDestroyed = true
|
431
|
+
|
349
432
|
if (this.queryInput$ !== undefined) {
|
350
433
|
this.reactivityGraph.destroyNode(this.queryInput$)
|
351
434
|
}
|
352
435
|
|
353
436
|
this.reactivityGraph.destroyNode(this.results$)
|
437
|
+
|
438
|
+
for (const query of this.dependencyQueriesRef) {
|
439
|
+
query.deref()
|
440
|
+
}
|
354
441
|
}
|
355
442
|
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { Effect } from '@livestore/utils/effect'
|
2
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
3
|
+
import { expect } from 'vitest'
|
4
|
+
|
5
|
+
import { makeTodoMvc } from '../utils/tests/fixture.js'
|
6
|
+
import { computed } from './computed.js'
|
7
|
+
import { signal } from './signal.js'
|
8
|
+
|
9
|
+
Vitest.describe('signal', () => {
|
10
|
+
Vitest.scopedLive('should be able to create a signal', () =>
|
11
|
+
Effect.gen(function* () {
|
12
|
+
const num$ = signal(0, { label: 'num$' })
|
13
|
+
|
14
|
+
const duplicated$ = computed((get) => get(num$) * 2, { label: 'duplicated$' })
|
15
|
+
|
16
|
+
const store = yield* makeTodoMvc({})
|
17
|
+
|
18
|
+
expect(store.query(duplicated$)).toBe(0)
|
19
|
+
|
20
|
+
store.setSignal(num$, 1)
|
21
|
+
|
22
|
+
expect(store.query(duplicated$)).toBe(2)
|
23
|
+
}),
|
24
|
+
)
|
25
|
+
|
26
|
+
Vitest.scopedLive('counter example', () =>
|
27
|
+
Effect.gen(function* () {
|
28
|
+
const count$ = signal(0, { label: 'count$' })
|
29
|
+
|
30
|
+
const store = yield* makeTodoMvc({})
|
31
|
+
|
32
|
+
const increment = () => store.setSignal(count$, (prev) => prev + 1)
|
33
|
+
|
34
|
+
increment()
|
35
|
+
increment()
|
36
|
+
|
37
|
+
expect(store.query(count$)).toBe(2)
|
38
|
+
}),
|
39
|
+
)
|
40
|
+
})
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import { nanoid } from '@livestore/utils/nanoid'
|
2
|
+
|
3
|
+
import type * as RG from '../reactive.js'
|
4
|
+
import type { RefreshReason } from '../store/store-types.js'
|
5
|
+
import type { ISignal, ReactivityGraph, ReactivityGraphContext, SignalDef } from './base-class.js'
|
6
|
+
import { withRCMap } from './base-class.js'
|
7
|
+
|
8
|
+
export const signal = <T>(
|
9
|
+
defaultValue: T,
|
10
|
+
options?: {
|
11
|
+
label?: string
|
12
|
+
},
|
13
|
+
): SignalDef<T> => {
|
14
|
+
const id = nanoid()
|
15
|
+
return {
|
16
|
+
_tag: 'signal-def',
|
17
|
+
defaultValue,
|
18
|
+
make: withRCMap(id, (ctx) => new Signal(defaultValue, ctx.reactivityGraph.deref()!, options)),
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
export class Signal<T> implements ISignal<T> {
|
23
|
+
_tag = 'signal' as const
|
24
|
+
readonly ref: RG.Ref<T, ReactivityGraphContext, RefreshReason>
|
25
|
+
|
26
|
+
constructor(
|
27
|
+
private defaultValue: T,
|
28
|
+
readonly reactivityGraph: ReactivityGraph,
|
29
|
+
private options?: {
|
30
|
+
label?: string
|
31
|
+
},
|
32
|
+
) {
|
33
|
+
this.ref = reactivityGraph.makeRef(defaultValue, { label: options?.label })
|
34
|
+
}
|
35
|
+
|
36
|
+
set = (value: T) => {
|
37
|
+
this.reactivityGraph.setRef(this.ref, value)
|
38
|
+
}
|
39
|
+
|
40
|
+
get = () => {
|
41
|
+
return this.ref.computeResult()
|
42
|
+
}
|
43
|
+
|
44
|
+
destroy = () => {
|
45
|
+
this.reactivityGraph.destroyNode(this.ref)
|
46
|
+
}
|
47
|
+
}
|
package/src/mod.ts
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
export { Store } from './store/store.js'
|
2
|
+
export { createStore, createStorePromise, type CreateStoreOptions } from './store/create-store.js'
|
3
|
+
export type { QueryDebugInfo, RefreshReason, OtelOptions } from './store/store-types.js'
|
4
|
+
// We're re-exporting `Schema` from `effect` for convenience
|
5
|
+
export { Schema } from '@livestore/utils/effect'
|
6
|
+
|
7
|
+
export {
|
8
|
+
type LiveStoreContext,
|
9
|
+
type LiveStoreContextRunning,
|
10
|
+
type ShutdownDeferred,
|
11
|
+
makeShutdownDeferred,
|
12
|
+
} from './store/store-types.js'
|
13
|
+
|
14
|
+
export { SqliteDbWrapper, emptyDebugInfo } from './SqliteDbWrapper.js'
|
15
|
+
|
16
|
+
export { queryDb, computed, signal, type LiveQuery, type LiveQueryDef } from './live-queries/mod.js'
|
17
|
+
|
18
|
+
export * from '@livestore/common/schema'
|
19
|
+
export {
|
20
|
+
sql,
|
21
|
+
SessionIdSymbol,
|
22
|
+
type BootStatus,
|
23
|
+
type SqliteDb,
|
24
|
+
type DebugInfo,
|
25
|
+
type MutableDebugInfo,
|
26
|
+
prepareBindValues,
|
27
|
+
type Bindable,
|
28
|
+
type PreparedBindValues,
|
29
|
+
type QueryBuilderAst,
|
30
|
+
type QueryBuilder,
|
31
|
+
type RowQuery,
|
32
|
+
StoreInterrupted,
|
33
|
+
IntentionalShutdownCause,
|
34
|
+
provideOtel,
|
35
|
+
} from '@livestore/common'
|
36
|
+
|
37
|
+
export { deepEqual } from '@livestore/utils'
|
38
|
+
export { nanoid } from '@livestore/utils/nanoid'
|
39
|
+
|
40
|
+
export * from './utils/stack-info.js'
|
41
|
+
|
42
|
+
export type { ClientSession, Adapter, PreparedStatement } from '@livestore/common'
|
package/src/reactive.test.ts
CHANGED
@@ -245,7 +245,7 @@ describe('a trivial graph', () => {
|
|
245
245
|
expect(e.isDirty).toBe(true)
|
246
246
|
|
247
247
|
expect(() => c.computeResult()).toThrowErrorMatchingInlineSnapshot(
|
248
|
-
`[Error: This should never happen: LiveStore Error: Attempted to compute destroyed ref (node-
|
248
|
+
`[Error: This should never happen: LiveStore Error: Attempted to compute destroyed ref (node-2): b]`,
|
249
249
|
)
|
250
250
|
})
|
251
251
|
})
|