@livestore/livestore 0.0.0-snapshot-29dc6acb4ddfcb70ac29c4ae18419710d194e555 → 0.0.0-snapshot-669b49b56c8abe87f4e11263af7cbf506deab38e

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.
Files changed (81) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/global-state.d.ts +1 -1
  3. package/dist/global-state.d.ts.map +1 -1
  4. package/dist/global-state.js +1 -1
  5. package/dist/global-state.js.map +1 -1
  6. package/dist/index.d.ts +6 -6
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +5 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/{live-queries → reactiveQueries}/base-class.d.ts +4 -8
  11. package/dist/reactiveQueries/base-class.d.ts.map +1 -0
  12. package/dist/{live-queries → reactiveQueries}/base-class.js +0 -2
  13. package/dist/reactiveQueries/base-class.js.map +1 -0
  14. package/dist/{live-queries → reactiveQueries}/computed.d.ts +13 -4
  15. package/dist/reactiveQueries/computed.d.ts.map +1 -0
  16. package/dist/{live-queries → reactiveQueries}/computed.js +23 -4
  17. package/dist/reactiveQueries/computed.js.map +1 -0
  18. package/dist/{live-queries → reactiveQueries}/graphql.d.ts +8 -4
  19. package/dist/reactiveQueries/graphql.d.ts.map +1 -0
  20. package/dist/{live-queries → reactiveQueries}/graphql.js +16 -2
  21. package/dist/reactiveQueries/graphql.js.map +1 -0
  22. package/dist/reactiveQueries/sql.d.ts +49 -0
  23. package/dist/reactiveQueries/sql.d.ts.map +1 -0
  24. package/dist/reactiveQueries/sql.js +130 -0
  25. package/dist/reactiveQueries/sql.js.map +1 -0
  26. package/dist/reactiveQueries/sql.test.d.ts +2 -0
  27. package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
  28. package/dist/reactiveQueries/sql.test.js +284 -0
  29. package/dist/reactiveQueries/sql.test.js.map +1 -0
  30. package/dist/row-query.d.ts +33 -0
  31. package/dist/row-query.d.ts.map +1 -0
  32. package/dist/row-query.js +80 -0
  33. package/dist/row-query.js.map +1 -0
  34. package/dist/store/create-store.d.ts +1 -1
  35. package/dist/store/create-store.d.ts.map +1 -1
  36. package/dist/store/devtools.d.ts +1 -1
  37. package/dist/store/devtools.d.ts.map +1 -1
  38. package/dist/store/devtools.js.map +1 -1
  39. package/dist/store/store-types.d.ts +2 -2
  40. package/dist/store/store-types.d.ts.map +1 -1
  41. package/dist/store/store.d.ts +3 -8
  42. package/dist/store/store.d.ts.map +1 -1
  43. package/dist/store/store.js +4 -32
  44. package/dist/store/store.js.map +1 -1
  45. package/dist/utils/tests/fixture.d.ts +132 -168
  46. package/dist/utils/tests/fixture.d.ts.map +1 -1
  47. package/package.json +5 -5
  48. package/src/global-state.ts +1 -1
  49. package/src/index.ts +5 -8
  50. package/src/{live-queries → reactiveQueries}/base-class.ts +5 -10
  51. package/src/{live-queries → reactiveQueries}/computed.ts +29 -5
  52. package/src/{live-queries → reactiveQueries}/graphql.ts +21 -6
  53. package/src/reactiveQueries/sql.test.ts +308 -0
  54. package/src/reactiveQueries/sql.ts +226 -0
  55. package/src/row-query.ts +196 -0
  56. package/src/store/create-store.ts +1 -1
  57. package/src/store/devtools.ts +1 -1
  58. package/src/store/store-types.ts +2 -2
  59. package/src/store/store.ts +7 -44
  60. package/dist/live-queries/base-class.d.ts.map +0 -1
  61. package/dist/live-queries/base-class.js.map +0 -1
  62. package/dist/live-queries/computed.d.ts.map +0 -1
  63. package/dist/live-queries/computed.js.map +0 -1
  64. package/dist/live-queries/db.d.ts +0 -66
  65. package/dist/live-queries/db.d.ts.map +0 -1
  66. package/dist/live-queries/db.js +0 -199
  67. package/dist/live-queries/db.js.map +0 -1
  68. package/dist/live-queries/db.test.d.ts +0 -2
  69. package/dist/live-queries/db.test.d.ts.map +0 -1
  70. package/dist/live-queries/db.test.js +0 -117
  71. package/dist/live-queries/db.test.js.map +0 -1
  72. package/dist/live-queries/graphql.d.ts.map +0 -1
  73. package/dist/live-queries/graphql.js.map +0 -1
  74. package/dist/row-query-utils.d.ts +0 -17
  75. package/dist/row-query-utils.d.ts.map +0 -1
  76. package/dist/row-query-utils.js +0 -30
  77. package/dist/row-query-utils.js.map +0 -1
  78. package/src/live-queries/__snapshots__/db.test.ts.snap +0 -301
  79. package/src/live-queries/db.test.ts +0 -153
  80. package/src/live-queries/db.ts +0 -350
  81. package/src/row-query-utils.ts +0 -65
@@ -1,350 +0,0 @@
1
- import type { Bindable, QueryBuilder, QueryInfo } from '@livestore/common'
2
- import {
3
- getResultSchema,
4
- isQueryBuilder,
5
- prepareBindValues,
6
- QueryBuilderAstSymbol,
7
- replaceSessionIdSymbol,
8
- } from '@livestore/common'
9
- import { deepEqual, shouldNeverHappen } from '@livestore/utils'
10
- import { Predicate, Schema, TreeFormatter } from '@livestore/utils/effect'
11
- import * as otel from '@opentelemetry/api'
12
-
13
- import { globalReactivityGraph } from '../global-state.js'
14
- import type { Thunk } from '../reactive.js'
15
- import { isThunk, NOT_REFRESHED_YET } from '../reactive.js'
16
- import { makeExecBeforeFirstRun, rowQueryLabel } from '../row-query-utils.js'
17
- import type { RefreshReason } from '../store/store-types.js'
18
- import { getDurationMsFromSpan } from '../utils/otel.js'
19
- import type { GetAtomResult, LiveQuery, QueryContext, ReactivityGraph } from './base-class.js'
20
- import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
21
-
22
- export type QueryInputRaw<TDecoded, TEncoded, TQueryInfo extends QueryInfo> = {
23
- query: string
24
- schema: Schema.Schema<TDecoded, TEncoded>
25
- bindValues?: Bindable
26
- /**
27
- * Can be provided explicitly to slightly speed up initial query performance
28
- *
29
- * NOTE In the future we want to do this automatically at build time
30
- */
31
- queriedTables?: Set<string>
32
- queryInfo?: TQueryInfo
33
- execBeforeFirstRun?: (ctx: QueryContext) => void
34
- }
35
-
36
- export type QueryInput<TDecoded, TEncoded, TQueryInfo extends QueryInfo> =
37
- | QueryInputRaw<TDecoded, TEncoded, TQueryInfo>
38
- | QueryBuilder<TDecoded, any, any, TQueryInfo>
39
-
40
- /**
41
- * NOTE `query` is only supposed to read data. Don't use it to insert/update/delete data but use mutations instead.
42
- */
43
- export const queryDb: {
44
- <TResultSchema, TResult = TResultSchema, TQueryInfo extends QueryInfo = QueryInfo.None>(
45
- queryInput:
46
- | QueryInputRaw<TResultSchema, ReadonlyArray<any>, TQueryInfo>
47
- | QueryBuilder<TResultSchema, any, any, TQueryInfo>,
48
- options?: {
49
- map?: (rows: TResultSchema) => TResult
50
- /**
51
- * Used for debugging / devtools
52
- */
53
- label?: string
54
- reactivityGraph?: ReactivityGraph
55
- otelContext?: otel.Context
56
- },
57
- ): LiveQuery<TResult, TQueryInfo>
58
- // NOTE in this "thunk case", we can't directly derive label/queryInfo from the queryInput,
59
- // so the caller needs to provide them explicitly otherwise queryInfo will be set to `None`,
60
- // and label will be set during the query execution
61
- <TResultSchema, TResult = TResultSchema, TQueryInfo extends QueryInfo = QueryInfo.None>(
62
- queryInput:
63
- | ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
64
- | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any, TQueryInfo>),
65
- options?: {
66
- map?: (rows: TResultSchema) => TResult
67
- /**
68
- * Used for debugging / devtools
69
- */
70
- label?: string
71
- reactivityGraph?: ReactivityGraph
72
- queryInfo?: TQueryInfo
73
- otelContext?: otel.Context
74
- },
75
- ): LiveQuery<TResult, TQueryInfo>
76
- } = (queryInput, options) =>
77
- new LiveStoreDbQuery({
78
- queryInput,
79
- label: options?.label,
80
- reactivityGraph: options?.reactivityGraph,
81
- map: options?.map,
82
- queryInfo: Predicate.hasProperty(options, 'queryInfo') ? (options.queryInfo as QueryInfo) : undefined,
83
- otelContext: options?.otelContext,
84
- })
85
-
86
- /* An object encapsulating a reactive SQL query */
87
- export class LiveStoreDbQuery<
88
- TResultSchema,
89
- TResult = TResultSchema,
90
- TQueryInfo extends QueryInfo = QueryInfo.None,
91
- > extends LiveStoreQueryBase<TResult, TQueryInfo> {
92
- _tag: 'db' = 'db'
93
-
94
- /** A reactive thunk representing the query text */
95
- queryInput$: Thunk<QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>, QueryContext, RefreshReason> | undefined
96
-
97
- /** A reactive thunk representing the query results */
98
- results$: Thunk<TResult, QueryContext, RefreshReason>
99
-
100
- label: string
101
-
102
- queryInfo: TQueryInfo
103
-
104
- protected reactivityGraph
105
-
106
- private mapResult: (rows: TResultSchema) => TResult
107
-
108
- constructor({
109
- queryInput,
110
- label: inputLabel,
111
- reactivityGraph,
112
- map,
113
- queryInfo: inputQueryInfo,
114
- otelContext,
115
- }: {
116
- label?: string
117
- queryInput:
118
- | QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>
119
- | ((get: GetAtomResult, ctx: QueryContext) => QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
120
- reactivityGraph?: ReactivityGraph
121
- map?: (rows: TResultSchema) => TResult
122
- queryInfo?: TQueryInfo
123
- otelContext?: otel.Context
124
- }) {
125
- super()
126
-
127
- let label = inputLabel ?? 'db(unknown)'
128
- let queryInfo = inputQueryInfo ?? ({ _tag: 'None' } as TQueryInfo)
129
- this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
130
-
131
- this.mapResult = map === undefined ? (rows: any) => rows as TResult : map
132
-
133
- const schemaRef: { current: Schema.Schema<any, any> | undefined } = {
134
- current:
135
- typeof queryInput === 'function' ? undefined : isQueryBuilder(queryInput) ? undefined : queryInput.schema,
136
- }
137
-
138
- const execBeforeFirstRunRef: { current: ((ctx: QueryContext, otelContext: otel.Context) => void) | undefined } = {
139
- current: undefined,
140
- }
141
-
142
- type TQueryInputRaw = QueryInputRaw<any, any, QueryInfo>
143
-
144
- let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, QueryContext, RefreshReason>
145
-
146
- const fromQueryBuilder = (qb: QueryBuilder.Any, otelContext: otel.Context | undefined) => {
147
- const qbRes = qb.asSql()
148
- const schema = getResultSchema(qb) as Schema.Schema<TResultSchema, ReadonlyArray<any>>
149
- const ast = qb[QueryBuilderAstSymbol]
150
-
151
- return {
152
- queryInputRaw: {
153
- query: qbRes.query,
154
- schema,
155
- bindValues: qbRes.bindValues,
156
- queriedTables: new Set([ast.tableDef.sqliteDef.name]),
157
- queryInfo: ast._tag === 'RowQuery' ? { _tag: 'Row', table: ast.tableDef, id: ast.id } : { _tag: 'None' },
158
- } satisfies TQueryInputRaw,
159
- label: ast._tag === 'RowQuery' ? rowQueryLabel(ast.tableDef, ast.id) : qb.toString(),
160
- execBeforeFirstRun:
161
- ast._tag === 'RowQuery'
162
- ? makeExecBeforeFirstRun({
163
- table: ast.tableDef,
164
- insertValues: ast.insertValues,
165
- id: ast.id,
166
- otelContext,
167
- })
168
- : undefined,
169
- }
170
- }
171
-
172
- if (typeof queryInput === 'function') {
173
- queryInputRaw$OrQueryInputRaw = this.reactivityGraph.makeThunk(
174
- (get, setDebugInfo, ctx, otelContext) => {
175
- const startMs = performance.now()
176
- const queryInputResult = queryInput(makeGetAtomResult(get, otelContext ?? ctx.rootOtelContext), ctx)
177
- const durationMs = performance.now() - startMs
178
-
179
- let queryInputRaw: TQueryInputRaw
180
-
181
- if (isQueryBuilder(queryInputResult)) {
182
- const res = fromQueryBuilder(queryInputResult, otelContext)
183
- queryInputRaw = res.queryInputRaw
184
- // setting label dynamically here
185
- this.label = res.label
186
- execBeforeFirstRunRef.current = res.execBeforeFirstRun
187
- } else {
188
- queryInputRaw = queryInputResult
189
- }
190
-
191
- setDebugInfo({ _tag: 'computed', label: `${this.label}:queryInput`, query: queryInputRaw.query, durationMs })
192
-
193
- schemaRef.current = queryInputRaw.schema
194
-
195
- if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
196
- queryInfo = queryInputRaw.queryInfo as TQueryInfo
197
- }
198
-
199
- return queryInputRaw
200
- },
201
- {
202
- label: `${label}:query`,
203
- meta: { liveStoreThunkType: 'db.query' },
204
- // NOTE we're not checking the schema here as we assume the query string to always change when the schema might change
205
- equal: (a, b) => a.query === b.query && deepEqual(a.bindValues, b.bindValues),
206
- },
207
- )
208
- } else {
209
- let queryInputRaw: TQueryInputRaw
210
- if (isQueryBuilder(queryInput)) {
211
- const res = fromQueryBuilder(queryInput, otelContext)
212
- queryInputRaw = res.queryInputRaw
213
- label = res.label
214
- execBeforeFirstRunRef.current = res.execBeforeFirstRun
215
- } else {
216
- queryInputRaw = queryInput
217
- }
218
-
219
- schemaRef.current = queryInputRaw.schema
220
- queryInputRaw$OrQueryInputRaw = queryInputRaw
221
-
222
- // this.label = inputLabel ? this.label : `db(${})`
223
- if (inputLabel === undefined && isQueryBuilder(queryInput)) {
224
- const ast = queryInput[QueryBuilderAstSymbol]
225
- if (ast._tag === 'RowQuery') {
226
- label = `db(${rowQueryLabel(ast.tableDef, ast.id)})`
227
- }
228
- }
229
-
230
- if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
231
- queryInfo = queryInputRaw.queryInfo as TQueryInfo
232
- }
233
- }
234
-
235
- const queriedTablesRef: { current: Set<string> | undefined } = { current: undefined }
236
-
237
- const makeResultsEqual = (resultSchema: Schema.Schema<any, any>) => (a: TResult, b: TResult) =>
238
- a === NOT_REFRESHED_YET || b === NOT_REFRESHED_YET ? false : Schema.equivalence(resultSchema)(a, b)
239
-
240
- // NOTE we try to create the equality function eagerly as it might be expensive
241
- // TODO also support derived equality for `map` (probably will depend on having an easy way to transform a schema without an `encode` step)
242
- // This would mean dropping the `map` option
243
- const resultsEqual =
244
- map === undefined
245
- ? schemaRef.current === undefined
246
- ? (a: TResult, b: TResult) => makeResultsEqual(schemaRef.current!)(a, b)
247
- : makeResultsEqual(schemaRef.current)
248
- : undefined
249
-
250
- const results$ = this.reactivityGraph.makeThunk<TResult>(
251
- (get, setDebugInfo, queryContext, otelContext) =>
252
- queryContext.otelTracer.startActiveSpan(
253
- 'db:...', // NOTE span name will be overridden further down
254
- {},
255
- otelContext ?? queryContext.rootOtelContext,
256
- (span) => {
257
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
258
- const { store } = queryContext
259
-
260
- if (execBeforeFirstRunRef.current !== undefined) {
261
- execBeforeFirstRunRef.current(queryContext, otelContext)
262
- execBeforeFirstRunRef.current = undefined
263
- }
264
-
265
- const queryInputResult = isThunk(queryInputRaw$OrQueryInputRaw)
266
- ? (get(queryInputRaw$OrQueryInputRaw, otelContext) as TQueryInputRaw)
267
- : (queryInputRaw$OrQueryInputRaw as TQueryInputRaw)
268
-
269
- const sqlString = queryInputResult.query
270
- const bindValues = queryInputResult.bindValues
271
-
272
- if (queriedTablesRef.current === undefined) {
273
- queriedTablesRef.current = store.syncDbWrapper.getTablesUsed(sqlString)
274
- }
275
-
276
- if (bindValues !== undefined) {
277
- replaceSessionIdSymbol(bindValues, store.clientSession.coordinator.sessionId)
278
- }
279
-
280
- // Establish a reactive dependency on the tables used in the query
281
- for (const tableName of queriedTablesRef.current) {
282
- const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
283
- get(tableRef, otelContext)
284
- }
285
-
286
- span.setAttribute('sql.query', sqlString)
287
- span.updateName(`db:${sqlString.slice(0, 50)}`)
288
-
289
- const rawDbResults = store.syncDbWrapper.select<any>(sqlString, {
290
- queriedTables: queriedTablesRef.current,
291
- bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
292
- otelContext,
293
- })
294
-
295
- span.setAttribute('sql.rowsCount', rawDbResults.length)
296
-
297
- const parsedResult = Schema.decodeEither(schemaRef.current!)(rawDbResults)
298
-
299
- if (parsedResult._tag === 'Left') {
300
- const parseErrorStr = TreeFormatter.formatErrorSync(parsedResult.left)
301
- const expectedSchemaStr = String(schemaRef.current!.ast)
302
- const bindValuesStr = bindValues === undefined ? '' : `\nBind values: ${JSON.stringify(bindValues)}`
303
-
304
- console.error(
305
- `\
306
- Error parsing SQL query result.
307
-
308
- Query: ${sqlString}\
309
- ${bindValuesStr}
310
-
311
- Expected schema: ${expectedSchemaStr}
312
-
313
- Error: ${parseErrorStr}
314
-
315
- Result:`,
316
- rawDbResults,
317
- )
318
- return shouldNeverHappen(`Error parsing SQL query result: ${parsedResult.left}`)
319
- }
320
-
321
- const result = this.mapResult(parsedResult.right)
322
-
323
- span.end()
324
-
325
- const durationMs = getDurationMsFromSpan(span)
326
-
327
- this.executionTimes.push(durationMs)
328
-
329
- setDebugInfo({ _tag: 'db', label: `${label}:results`, query: sqlString, durationMs })
330
-
331
- return result
332
- },
333
- ),
334
- { label: `${label}:results`, meta: { liveStoreThunkType: 'db.result' }, equal: resultsEqual },
335
- )
336
-
337
- this.results$ = results$
338
-
339
- this.label = label
340
- this.queryInfo = queryInfo
341
- }
342
-
343
- destroy = () => {
344
- if (this.queryInput$ !== undefined) {
345
- this.reactivityGraph.destroyNode(this.queryInput$)
346
- }
347
-
348
- this.reactivityGraph.destroyNode(this.results$)
349
- }
350
- }
@@ -1,65 +0,0 @@
1
- import type { PreparedBindValues, QueryInfo } from '@livestore/common'
2
- import { SessionIdSymbol } from '@livestore/common'
3
- import { DbSchema } from '@livestore/common/schema'
4
- import { shouldNeverHappen } from '@livestore/utils'
5
- import type * as otel from '@opentelemetry/api'
6
-
7
- import type { LiveQuery, LiveQueryAny, QueryContext } from './live-queries/base-class.js'
8
- import { computed } from './live-queries/computed.js'
9
-
10
- export const rowQueryLabel = (table: DbSchema.TableDefBase, id: string | SessionIdSymbol | undefined) =>
11
- `row:${table.sqliteDef.name}${id === undefined ? '' : id === SessionIdSymbol ? `:sessionId` : `:${id}`}`
12
-
13
- export const deriveColQuery: {
14
- <TQuery extends LiveQuery<any, QueryInfo.None>, TCol extends keyof TQuery['__result!'] & string>(
15
- query$: TQuery,
16
- colName: TCol,
17
- ): LiveQuery<TQuery['__result!'][TCol], QueryInfo.None>
18
- <TQuery extends LiveQuery<any, QueryInfo.Row>, TCol extends keyof TQuery['__result!'] & string>(
19
- query$: TQuery,
20
- colName: TCol,
21
- ): LiveQuery<TQuery['__result!'][TCol], QueryInfo.Col>
22
- } = (query$: LiveQueryAny, colName: string) => {
23
- return computed((get) => get(query$)[colName], {
24
- label: `deriveColQuery:${query$.label}:${colName}`,
25
- queryInfo:
26
- query$.queryInfo._tag === 'Row'
27
- ? { _tag: 'Col', table: query$.queryInfo.table, column: colName, id: query$.queryInfo.id }
28
- : undefined,
29
- }) as any
30
- }
31
-
32
- export const makeExecBeforeFirstRun =
33
- ({
34
- id,
35
- insertValues,
36
- table,
37
- otelContext: otelContext_,
38
- }: {
39
- id?: string | SessionIdSymbol
40
- insertValues?: any
41
- table: DbSchema.TableDefBase
42
- otelContext: otel.Context | undefined
43
- }) =>
44
- ({ store }: QueryContext) => {
45
- const otelContext = otelContext_ ?? store.otel.queriesSpanContext
46
-
47
- if (table.options.isSingleton === false) {
48
- const idStr = id === SessionIdSymbol ? store.sessionId : id!
49
- const rowExists =
50
- store.syncDbWrapper.select(`SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`, {
51
- bindValues: [idStr] as any as PreparedBindValues,
52
- }).length === 1
53
-
54
- if (rowExists) return
55
-
56
- if (DbSchema.tableHasDerivedMutations(table) === false) {
57
- return shouldNeverHappen(
58
- `Cannot insert row for table "${table.sqliteDef.name}" which does not have 'deriveMutations: true' set`,
59
- )
60
- }
61
-
62
- // NOTE It's important that we only mutate and don't refresh here, as this function is called during a render
63
- store.mutateWithoutRefresh(table.insert({ id, ...insertValues }), { otelContext, coordinatorMode: 'default' })
64
- }
65
- }