@livestore/livestore 0.3.0-dev.5 → 0.3.0-dev.51
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 +69 -29
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +60 -14
- 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 +13 -15
- 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} +113 -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 +25 -0
- package/dist/live-queries/signal.d.ts.map +1 -0
- package/dist/live-queries/signal.js +50 -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 +322 -154
- 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 +170 -53
- 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} +171 -82
- package/src/live-queries/mod.ts +4 -0
- package/src/live-queries/signal.test.ts +40 -0
- package/src/live-queries/signal.ts +81 -0
- package/src/mod.ts +51 -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 +457 -237
- 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,180 @@ 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 = [queryString, options?.deps ? depsToString(options.deps) : undefined, depsToString(extraDeps)]
|
106
|
+
.filter(Boolean)
|
107
|
+
.join('-')
|
108
|
+
|
109
|
+
if (isValidFunctionString(hash)._tag === 'invalid') {
|
110
|
+
throw new Error(`On Expo/React Native, db queries must provide a \`deps\` option`)
|
111
|
+
}
|
112
|
+
|
113
|
+
if (hash.trim() === '') {
|
114
|
+
return shouldNeverHappen(`Invalid query hash for query: ${queryInput}`)
|
115
|
+
}
|
116
|
+
|
117
|
+
const label = options?.label ?? queryString
|
118
|
+
|
119
|
+
const def: LiveQueryDef<any> = {
|
120
|
+
_tag: 'def',
|
121
|
+
make: withRCMap(hash, (ctx, otelContext) => {
|
122
|
+
// TODO onDestroy
|
123
|
+
return new LiveStoreDbQuery({
|
124
|
+
reactivityGraph: ctx.reactivityGraph.deref()!,
|
125
|
+
queryInput,
|
126
|
+
label,
|
127
|
+
map: options?.map,
|
128
|
+
otelContext,
|
129
|
+
def,
|
130
|
+
})
|
131
|
+
}),
|
132
|
+
label,
|
133
|
+
hash,
|
134
|
+
}
|
135
|
+
|
136
|
+
return def
|
137
|
+
}
|
138
|
+
|
139
|
+
const bindValuesToDepKey = (bindValues: Bindable | undefined): DepKey => {
|
140
|
+
if (bindValues === undefined) {
|
141
|
+
return []
|
142
|
+
}
|
143
|
+
|
144
|
+
return Object.entries(bindValues)
|
145
|
+
.map(([key, value]: [string, any]) => `${key}:${value === SessionIdSymbol ? 'SessionIdSymbol' : value}`)
|
146
|
+
.join(',')
|
147
|
+
}
|
148
|
+
|
149
|
+
const getQueryStringAndExtraDeps = (
|
150
|
+
queryInput: QueryInput<any, any> | ((get: GetAtomResult) => QueryInput<any, any>),
|
151
|
+
): { queryString: string; extraDeps: DepKey } => {
|
152
|
+
if (isQueryBuilder(queryInput)) {
|
153
|
+
const { query, bindValues } = queryInput.asSql()
|
154
|
+
return { queryString: query, extraDeps: bindValuesToDepKey(bindValues) }
|
155
|
+
}
|
156
|
+
|
157
|
+
if (isQueryInputRaw(queryInput)) {
|
158
|
+
return { queryString: queryInput.query, extraDeps: bindValuesToDepKey(queryInput.bindValues) }
|
159
|
+
}
|
160
|
+
|
161
|
+
if (typeof queryInput === 'function') {
|
162
|
+
return { queryString: queryInput.toString(), extraDeps: [] }
|
163
|
+
}
|
164
|
+
|
165
|
+
return shouldNeverHappen(`Invalid query input: ${queryInput}`)
|
166
|
+
}
|
86
167
|
|
87
168
|
/* An object encapsulating a reactive SQL query */
|
88
|
-
export class LiveStoreDbQuery<
|
89
|
-
|
90
|
-
TResult = TResultSchema,
|
91
|
-
TQueryInfo extends QueryInfo = QueryInfo.None,
|
92
|
-
> extends LiveStoreQueryBase<TResult, TQueryInfo> {
|
93
|
-
_tag: 'db' = 'db'
|
169
|
+
export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends LiveStoreQueryBase<TResult> {
|
170
|
+
_tag = 'db' as const
|
94
171
|
|
95
172
|
/** A reactive thunk representing the query text */
|
96
|
-
queryInput$: Thunk<
|
173
|
+
queryInput$: Thunk<QueryInputRaw<any, any>, ReactivityGraphContext, RefreshReason> | undefined
|
97
174
|
|
98
175
|
/** A reactive thunk representing the query results */
|
99
|
-
results$: Thunk<TResult,
|
176
|
+
results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
|
100
177
|
|
101
178
|
label: string
|
102
179
|
|
103
|
-
|
104
|
-
|
105
|
-
protected reactivityGraph
|
180
|
+
readonly reactivityGraph
|
106
181
|
|
107
182
|
private mapResult: (rows: TResultSchema) => TResult
|
183
|
+
def: LiveQueryDef<TResult>
|
108
184
|
|
109
185
|
constructor({
|
110
186
|
queryInput,
|
111
187
|
label: inputLabel,
|
112
188
|
reactivityGraph,
|
113
189
|
map,
|
114
|
-
queryInfo: inputQueryInfo,
|
115
190
|
otelContext,
|
191
|
+
def,
|
116
192
|
}: {
|
117
193
|
label?: string
|
118
194
|
queryInput:
|
119
|
-
| QueryInput<TResultSchema, ReadonlyArray<any
|
120
|
-
| ((get: GetAtomResult, ctx:
|
121
|
-
reactivityGraph
|
195
|
+
| QueryInput<TResultSchema, ReadonlyArray<any>>
|
196
|
+
| ((get: GetAtomResult, ctx: ReactivityGraphContext) => QueryInput<TResultSchema, ReadonlyArray<any>>)
|
197
|
+
reactivityGraph: ReactivityGraph
|
122
198
|
map?: (rows: TResultSchema) => TResult
|
123
|
-
|
199
|
+
/** Only used for the initial query execution */
|
124
200
|
otelContext?: otel.Context
|
201
|
+
def: LiveQueryDef<TResult>
|
125
202
|
}) {
|
126
203
|
super()
|
127
204
|
|
128
205
|
let label = inputLabel ?? 'db(unknown)'
|
129
|
-
|
130
|
-
this.
|
206
|
+
this.reactivityGraph = reactivityGraph
|
207
|
+
this.def = def
|
131
208
|
|
132
209
|
this.mapResult = map === undefined ? (rows: any) => rows as TResult : map
|
133
210
|
|
@@ -136,13 +213,15 @@ export class LiveStoreDbQuery<
|
|
136
213
|
typeof queryInput === 'function' ? undefined : isQueryBuilder(queryInput) ? undefined : queryInput.schema,
|
137
214
|
}
|
138
215
|
|
139
|
-
const execBeforeFirstRunRef: {
|
216
|
+
const execBeforeFirstRunRef: {
|
217
|
+
current: ((ctx: ReactivityGraphContext, otelContext: otel.Context) => void) | undefined
|
218
|
+
} = {
|
140
219
|
current: undefined,
|
141
220
|
}
|
142
221
|
|
143
|
-
type TQueryInputRaw = QueryInputRaw<any, any
|
222
|
+
type TQueryInputRaw = QueryInputRaw<any, any>
|
144
223
|
|
145
|
-
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw,
|
224
|
+
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, ReactivityGraphContext, RefreshReason>
|
146
225
|
|
147
226
|
const fromQueryBuilder = (qb: QueryBuilder.Any, otelContext: otel.Context | undefined) => {
|
148
227
|
try {
|
@@ -156,14 +235,13 @@ export class LiveStoreDbQuery<
|
|
156
235
|
schema,
|
157
236
|
bindValues: qbRes.bindValues,
|
158
237
|
queriedTables: new Set([ast.tableDef.sqliteDef.name]),
|
159
|
-
queryInfo: ast._tag === 'RowQuery' ? { _tag: 'Row', table: ast.tableDef, id: ast.id } : { _tag: 'None' },
|
160
238
|
} satisfies TQueryInputRaw,
|
161
239
|
label: ast._tag === 'RowQuery' ? rowQueryLabel(ast.tableDef, ast.id) : qb.toString(),
|
162
240
|
execBeforeFirstRun:
|
163
241
|
ast._tag === 'RowQuery'
|
164
242
|
? makeExecBeforeFirstRun({
|
165
243
|
table: ast.tableDef,
|
166
|
-
|
244
|
+
explicitDefaultValues: ast.explicitDefaultValues,
|
167
245
|
id: ast.id,
|
168
246
|
otelContext,
|
169
247
|
})
|
@@ -178,7 +256,10 @@ export class LiveStoreDbQuery<
|
|
178
256
|
queryInputRaw$OrQueryInputRaw = this.reactivityGraph.makeThunk(
|
179
257
|
(get, setDebugInfo, ctx, otelContext) => {
|
180
258
|
const startMs = performance.now()
|
181
|
-
const queryInputResult = queryInput(
|
259
|
+
const queryInputResult = queryInput(
|
260
|
+
makeGetAtomResult(get, ctx, otelContext ?? ctx.rootOtelContext, this.dependencyQueriesRef),
|
261
|
+
ctx,
|
262
|
+
)
|
182
263
|
const durationMs = performance.now() - startMs
|
183
264
|
|
184
265
|
let queryInputRaw: TQueryInputRaw
|
@@ -197,10 +278,6 @@ export class LiveStoreDbQuery<
|
|
197
278
|
|
198
279
|
schemaRef.current = queryInputRaw.schema
|
199
280
|
|
200
|
-
if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
|
201
|
-
queryInfo = queryInputRaw.queryInfo as TQueryInfo
|
202
|
-
}
|
203
|
-
|
204
281
|
return queryInputRaw
|
205
282
|
},
|
206
283
|
{
|
@@ -210,6 +287,8 @@ export class LiveStoreDbQuery<
|
|
210
287
|
equal: (a, b) => a.query === b.query && deepEqual(a.bindValues, b.bindValues),
|
211
288
|
},
|
212
289
|
)
|
290
|
+
|
291
|
+
this.queryInput$ = queryInputRaw$OrQueryInputRaw
|
213
292
|
} else {
|
214
293
|
let queryInputRaw: TQueryInputRaw
|
215
294
|
if (isQueryBuilder(queryInput)) {
|
@@ -231,10 +310,6 @@ export class LiveStoreDbQuery<
|
|
231
310
|
label = `db(${rowQueryLabel(ast.tableDef, ast.id)})`
|
232
311
|
}
|
233
312
|
}
|
234
|
-
|
235
|
-
if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
|
236
|
-
queryInfo = queryInputRaw.queryInfo as TQueryInfo
|
237
|
-
}
|
238
313
|
}
|
239
314
|
|
240
315
|
const queriedTablesRef: { current: Set<string> | undefined } = { current: undefined }
|
@@ -253,10 +328,16 @@ export class LiveStoreDbQuery<
|
|
253
328
|
: undefined
|
254
329
|
|
255
330
|
const results$ = this.reactivityGraph.makeThunk<TResult>(
|
256
|
-
(get, setDebugInfo, queryContext, otelContext) =>
|
331
|
+
(get, setDebugInfo, queryContext, otelContext, debugRefreshReason) =>
|
257
332
|
queryContext.otelTracer.startActiveSpan(
|
258
333
|
'db:...', // NOTE span name will be overridden further down
|
259
|
-
{
|
334
|
+
{
|
335
|
+
attributes: {
|
336
|
+
'livestore.debugRefreshReason': Predicate.hasProperty(debugRefreshReason, 'label')
|
337
|
+
? (debugRefreshReason.label as string)
|
338
|
+
: debugRefreshReason?._tag,
|
339
|
+
},
|
340
|
+
},
|
260
341
|
otelContext ?? queryContext.rootOtelContext,
|
261
342
|
(span) => {
|
262
343
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
@@ -268,14 +349,14 @@ export class LiveStoreDbQuery<
|
|
268
349
|
}
|
269
350
|
|
270
351
|
const queryInputResult = isThunk(queryInputRaw$OrQueryInputRaw)
|
271
|
-
? (get(queryInputRaw$OrQueryInputRaw, otelContext) as TQueryInputRaw)
|
352
|
+
? (get(queryInputRaw$OrQueryInputRaw, otelContext, debugRefreshReason) as TQueryInputRaw)
|
272
353
|
: (queryInputRaw$OrQueryInputRaw as TQueryInputRaw)
|
273
354
|
|
274
355
|
const sqlString = queryInputResult.query
|
275
356
|
const bindValues = queryInputResult.bindValues
|
276
357
|
|
277
358
|
if (queriedTablesRef.current === undefined) {
|
278
|
-
queriedTablesRef.current = store.
|
359
|
+
queriedTablesRef.current = store.sqliteDbWrapper.getTablesUsed(sqlString)
|
279
360
|
}
|
280
361
|
|
281
362
|
if (bindValues !== undefined) {
|
@@ -285,17 +366,20 @@ export class LiveStoreDbQuery<
|
|
285
366
|
// Establish a reactive dependency on the tables used in the query
|
286
367
|
for (const tableName of queriedTablesRef.current) {
|
287
368
|
const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
|
288
|
-
get(tableRef, otelContext)
|
369
|
+
get(tableRef, otelContext, debugRefreshReason)
|
289
370
|
}
|
290
371
|
|
291
372
|
span.setAttribute('sql.query', sqlString)
|
292
373
|
span.updateName(`db:${sqlString.slice(0, 50)}`)
|
293
374
|
|
294
|
-
const rawDbResults = store.
|
295
|
-
|
296
|
-
bindValues
|
297
|
-
|
298
|
-
|
375
|
+
const rawDbResults = store.sqliteDbWrapper.select<any>(
|
376
|
+
sqlString,
|
377
|
+
bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
|
378
|
+
{
|
379
|
+
queriedTables: queriedTablesRef.current,
|
380
|
+
otelContext,
|
381
|
+
},
|
382
|
+
)
|
299
383
|
|
300
384
|
span.setAttribute('sql.rowsCount', rawDbResults.length)
|
301
385
|
|
@@ -306,9 +390,9 @@ export class LiveStoreDbQuery<
|
|
306
390
|
const expectedSchemaStr = String(schemaRef.current!.ast)
|
307
391
|
const bindValuesStr = bindValues === undefined ? '' : `\nBind values: ${JSON.stringify(bindValues)}`
|
308
392
|
|
309
|
-
|
393
|
+
return shouldNeverHappen(
|
310
394
|
`\
|
311
|
-
Error parsing SQL query result.
|
395
|
+
Error parsing SQL query result (${label}).
|
312
396
|
|
313
397
|
Query: ${sqlString}\
|
314
398
|
${bindValuesStr}
|
@@ -319,8 +403,8 @@ Error: ${parseErrorStr}
|
|
319
403
|
|
320
404
|
Result:`,
|
321
405
|
rawDbResults,
|
406
|
+
'\n',
|
322
407
|
)
|
323
|
-
return shouldNeverHappen(`Error parsing SQL query result: ${parsedResult.left}`)
|
324
408
|
}
|
325
409
|
|
326
410
|
const result = this.mapResult(parsedResult.right)
|
@@ -342,14 +426,19 @@ Result:`,
|
|
342
426
|
this.results$ = results$
|
343
427
|
|
344
428
|
this.label = label
|
345
|
-
this.queryInfo = queryInfo
|
346
429
|
}
|
347
430
|
|
348
431
|
destroy = () => {
|
432
|
+
this.isDestroyed = true
|
433
|
+
|
349
434
|
if (this.queryInput$ !== undefined) {
|
350
435
|
this.reactivityGraph.destroyNode(this.queryInput$)
|
351
436
|
}
|
352
437
|
|
353
438
|
this.reactivityGraph.destroyNode(this.results$)
|
439
|
+
|
440
|
+
for (const query of this.dependencyQueriesRef) {
|
441
|
+
query.deref()
|
442
|
+
}
|
354
443
|
}
|
355
444
|
}
|
@@ -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,81 @@
|
|
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 { LiveStoreQueryBase, 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
|
+
const def: SignalDef<T> = {
|
16
|
+
_tag: 'signal-def',
|
17
|
+
defaultValue,
|
18
|
+
hash: id,
|
19
|
+
label: options?.label ?? 'Signal',
|
20
|
+
make: withRCMap(
|
21
|
+
id,
|
22
|
+
(ctx) =>
|
23
|
+
new Signal({
|
24
|
+
defaultValue,
|
25
|
+
reactivityGraph: ctx.reactivityGraph.deref()!,
|
26
|
+
label: options?.label ?? 'Signal',
|
27
|
+
def,
|
28
|
+
}),
|
29
|
+
),
|
30
|
+
}
|
31
|
+
|
32
|
+
return def
|
33
|
+
}
|
34
|
+
|
35
|
+
export class Signal<T> extends LiveStoreQueryBase<T> implements ISignal<T> {
|
36
|
+
_tag = 'signal' as const
|
37
|
+
readonly ref: RG.Ref<T, ReactivityGraphContext, RefreshReason>
|
38
|
+
label: string
|
39
|
+
reactivityGraph: ReactivityGraph
|
40
|
+
results$: RG.Ref<T, ReactivityGraphContext, RefreshReason>
|
41
|
+
def: SignalDef<T>
|
42
|
+
constructor(
|
43
|
+
// private defaultValue: T,
|
44
|
+
// readonly reactivityGraph: ReactivityGraph,
|
45
|
+
// private options?: {
|
46
|
+
// label?: string
|
47
|
+
// },
|
48
|
+
{
|
49
|
+
defaultValue,
|
50
|
+
reactivityGraph,
|
51
|
+
label,
|
52
|
+
def,
|
53
|
+
}: {
|
54
|
+
defaultValue: T
|
55
|
+
reactivityGraph: ReactivityGraph
|
56
|
+
label: string
|
57
|
+
def: SignalDef<T>
|
58
|
+
},
|
59
|
+
) {
|
60
|
+
super()
|
61
|
+
|
62
|
+
this.ref = reactivityGraph.makeRef(defaultValue, { label })
|
63
|
+
this.label = label
|
64
|
+
this.reactivityGraph = reactivityGraph
|
65
|
+
this.def = def
|
66
|
+
|
67
|
+
this.results$ = this.ref
|
68
|
+
}
|
69
|
+
|
70
|
+
set = (value: T) => {
|
71
|
+
this.reactivityGraph.setRef(this.ref, value)
|
72
|
+
}
|
73
|
+
|
74
|
+
get = () => {
|
75
|
+
return this.ref.computeResult()
|
76
|
+
}
|
77
|
+
|
78
|
+
destroy = () => {
|
79
|
+
this.reactivityGraph.destroyNode(this.ref)
|
80
|
+
}
|
81
|
+
}
|
package/src/mod.ts
ADDED
@@ -0,0 +1,51 @@
|
|
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 {
|
17
|
+
queryDb,
|
18
|
+
computed,
|
19
|
+
signal,
|
20
|
+
type LiveQuery,
|
21
|
+
type LiveQueryDef,
|
22
|
+
type Signal,
|
23
|
+
type SignalDef,
|
24
|
+
type RcRef,
|
25
|
+
} from './live-queries/mod.js'
|
26
|
+
|
27
|
+
export * from '@livestore/common/schema'
|
28
|
+
export {
|
29
|
+
sql,
|
30
|
+
SessionIdSymbol,
|
31
|
+
type BootStatus,
|
32
|
+
type SqliteDb,
|
33
|
+
type DebugInfo,
|
34
|
+
type MutableDebugInfo,
|
35
|
+
prepareBindValues,
|
36
|
+
type Bindable,
|
37
|
+
type PreparedBindValues,
|
38
|
+
type QueryBuilderAst,
|
39
|
+
type QueryBuilder,
|
40
|
+
type RowQuery,
|
41
|
+
StoreInterrupted,
|
42
|
+
IntentionalShutdownCause,
|
43
|
+
provideOtel,
|
44
|
+
} from '@livestore/common'
|
45
|
+
|
46
|
+
export { deepEqual } from '@livestore/utils'
|
47
|
+
export { nanoid } from '@livestore/utils/nanoid'
|
48
|
+
|
49
|
+
export * from './utils/stack-info.js'
|
50
|
+
|
51
|
+
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
|
})
|