@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.
Files changed (170) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/QueryCache.d.ts.map +1 -1
  3. package/dist/SqliteDbWrapper.d.ts +60 -0
  4. package/dist/SqliteDbWrapper.d.ts.map +1 -0
  5. package/dist/{SynchronousDatabaseWrapper.js → SqliteDbWrapper.js} +69 -34
  6. package/dist/SqliteDbWrapper.js.map +1 -0
  7. package/dist/effect/LiveStore.d.ts +6 -34
  8. package/dist/effect/LiveStore.d.ts.map +1 -1
  9. package/dist/effect/LiveStore.js +10 -12
  10. package/dist/effect/LiveStore.js.map +1 -1
  11. package/dist/effect/mod.d.ts +3 -0
  12. package/dist/effect/mod.d.ts.map +1 -0
  13. package/dist/effect/mod.js +3 -0
  14. package/dist/effect/mod.js.map +1 -0
  15. package/dist/internal/mod.d.ts +3 -0
  16. package/dist/internal/mod.d.ts.map +1 -0
  17. package/dist/internal/mod.js +3 -0
  18. package/dist/internal/mod.js.map +1 -0
  19. package/dist/live-queries/base-class.d.ts +69 -29
  20. package/dist/live-queries/base-class.d.ts.map +1 -1
  21. package/dist/live-queries/base-class.js +60 -14
  22. package/dist/live-queries/base-class.js.map +1 -1
  23. package/dist/live-queries/client-document-get-query.d.ts +12 -0
  24. package/dist/live-queries/client-document-get-query.d.ts.map +1 -0
  25. package/dist/live-queries/client-document-get-query.js +18 -0
  26. package/dist/live-queries/client-document-get-query.js.map +1 -0
  27. package/dist/live-queries/computed.d.ts +13 -15
  28. package/dist/live-queries/computed.d.ts.map +1 -1
  29. package/dist/live-queries/computed.js +37 -15
  30. package/dist/live-queries/computed.js.map +1 -1
  31. package/dist/live-queries/db-query.d.ts +93 -0
  32. package/dist/live-queries/db-query.d.ts.map +1 -0
  33. package/dist/live-queries/{db.js → db-query.js} +113 -40
  34. package/dist/live-queries/db-query.js.map +1 -0
  35. package/dist/live-queries/db-query.test.d.ts +2 -0
  36. package/dist/live-queries/db-query.test.d.ts.map +1 -0
  37. package/dist/live-queries/db-query.test.js +133 -0
  38. package/dist/live-queries/db-query.test.js.map +1 -0
  39. package/dist/live-queries/mod.d.ts +5 -0
  40. package/dist/live-queries/mod.d.ts.map +1 -0
  41. package/dist/live-queries/mod.js +5 -0
  42. package/dist/live-queries/mod.js.map +1 -0
  43. package/dist/live-queries/signal.d.ts +25 -0
  44. package/dist/live-queries/signal.d.ts.map +1 -0
  45. package/dist/live-queries/signal.js +50 -0
  46. package/dist/live-queries/signal.js.map +1 -0
  47. package/dist/live-queries/signal.test.d.ts +2 -0
  48. package/dist/live-queries/signal.test.d.ts.map +1 -0
  49. package/dist/live-queries/signal.test.js +25 -0
  50. package/dist/live-queries/signal.test.js.map +1 -0
  51. package/dist/mod.d.ts +14 -0
  52. package/dist/mod.d.ts.map +1 -0
  53. package/dist/mod.js +13 -0
  54. package/dist/mod.js.map +1 -0
  55. package/dist/reactive.d.ts +23 -17
  56. package/dist/reactive.d.ts.map +1 -1
  57. package/dist/reactive.js +23 -19
  58. package/dist/reactive.js.map +1 -1
  59. package/dist/reactive.test.js +1 -1
  60. package/dist/reactive.test.js.map +1 -1
  61. package/dist/store/create-store.d.ts +70 -12
  62. package/dist/store/create-store.d.ts.map +1 -1
  63. package/dist/store/create-store.js +68 -19
  64. package/dist/store/create-store.js.map +1 -1
  65. package/dist/store/devtools.d.ts +5 -4
  66. package/dist/store/devtools.d.ts.map +1 -1
  67. package/dist/store/devtools.js +92 -40
  68. package/dist/store/devtools.js.map +1 -1
  69. package/dist/store/store-types.d.ts +54 -42
  70. package/dist/store/store-types.d.ts.map +1 -1
  71. package/dist/store/store-types.js +2 -5
  72. package/dist/store/store-types.js.map +1 -1
  73. package/dist/store/store.d.ts +141 -35
  74. package/dist/store/store.d.ts.map +1 -1
  75. package/dist/store/store.js +322 -154
  76. package/dist/store/store.js.map +1 -1
  77. package/dist/utils/data-structures.d.ts.map +1 -1
  78. package/dist/utils/dev.d.ts.map +1 -1
  79. package/dist/utils/dev.js +6 -1
  80. package/dist/utils/dev.js.map +1 -1
  81. package/dist/utils/function-string.d.ts +7 -0
  82. package/dist/utils/function-string.d.ts.map +1 -0
  83. package/dist/utils/function-string.js +9 -0
  84. package/dist/utils/function-string.js.map +1 -0
  85. package/dist/utils/stack-info.d.ts.map +1 -1
  86. package/dist/utils/stack-info.js +6 -1
  87. package/dist/utils/stack-info.js.map +1 -1
  88. package/dist/utils/stack-info.test.js +54 -1
  89. package/dist/utils/stack-info.test.js.map +1 -1
  90. package/dist/utils/tests/fixture.d.ts +59 -216
  91. package/dist/utils/tests/fixture.d.ts.map +1 -1
  92. package/dist/utils/tests/fixture.js +23 -18
  93. package/dist/utils/tests/fixture.js.map +1 -1
  94. package/dist/utils/tests/mod.d.ts +1 -0
  95. package/dist/utils/tests/mod.d.ts.map +1 -1
  96. package/dist/utils/tests/mod.js +1 -0
  97. package/dist/utils/tests/mod.js.map +1 -1
  98. package/dist/utils/tests/otel.d.ts.map +1 -1
  99. package/dist/utils/tests/otel.js +8 -3
  100. package/dist/utils/tests/otel.js.map +1 -1
  101. package/package.json +29 -26
  102. package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +92 -42
  103. package/src/effect/LiveStore.ts +27 -64
  104. package/src/effect/{index.ts → mod.ts} +2 -3
  105. package/src/internal/mod.ts +2 -0
  106. package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +241 -45
  107. package/src/live-queries/base-class.ts +170 -53
  108. package/src/live-queries/client-document-get-query.ts +52 -0
  109. package/src/live-queries/computed.ts +51 -33
  110. package/src/live-queries/db-query.test.ts +192 -0
  111. package/src/live-queries/{db.ts → db-query.ts} +171 -82
  112. package/src/live-queries/mod.ts +4 -0
  113. package/src/live-queries/signal.test.ts +40 -0
  114. package/src/live-queries/signal.ts +81 -0
  115. package/src/mod.ts +51 -0
  116. package/src/reactive.test.ts +1 -1
  117. package/src/reactive.ts +66 -43
  118. package/src/store/create-store.ts +188 -62
  119. package/src/store/devtools.ts +124 -46
  120. package/src/store/store-types.ts +54 -43
  121. package/src/store/store.ts +457 -237
  122. package/src/utils/dev.ts +6 -1
  123. package/src/utils/function-string.ts +12 -0
  124. package/src/utils/stack-info.test.ts +58 -1
  125. package/src/utils/stack-info.ts +6 -1
  126. package/src/utils/tests/fixture.ts +22 -31
  127. package/src/utils/tests/mod.ts +1 -0
  128. package/src/utils/tests/otel.ts +10 -3
  129. package/dist/SynchronousDatabaseWrapper.d.ts +0 -41
  130. package/dist/SynchronousDatabaseWrapper.d.ts.map +0 -1
  131. package/dist/SynchronousDatabaseWrapper.js.map +0 -1
  132. package/dist/effect/index.d.ts +0 -2
  133. package/dist/effect/index.d.ts.map +0 -1
  134. package/dist/effect/index.js +0 -2
  135. package/dist/effect/index.js.map +0 -1
  136. package/dist/global-state.d.ts +0 -14
  137. package/dist/global-state.d.ts.map +0 -1
  138. package/dist/global-state.js +0 -16
  139. package/dist/global-state.js.map +0 -1
  140. package/dist/index.d.ts +0 -20
  141. package/dist/index.d.ts.map +0 -1
  142. package/dist/index.js +0 -16
  143. package/dist/index.js.map +0 -1
  144. package/dist/live-queries/db.d.ts +0 -66
  145. package/dist/live-queries/db.d.ts.map +0 -1
  146. package/dist/live-queries/db.js.map +0 -1
  147. package/dist/live-queries/db.test.d.ts +0 -2
  148. package/dist/live-queries/db.test.d.ts.map +0 -1
  149. package/dist/live-queries/db.test.js +0 -118
  150. package/dist/live-queries/db.test.js.map +0 -1
  151. package/dist/live-queries/graphql.d.ts +0 -49
  152. package/dist/live-queries/graphql.d.ts.map +0 -1
  153. package/dist/live-queries/graphql.js +0 -122
  154. package/dist/live-queries/graphql.js.map +0 -1
  155. package/dist/row-query-utils.d.ts +0 -17
  156. package/dist/row-query-utils.d.ts.map +0 -1
  157. package/dist/row-query-utils.js +0 -30
  158. package/dist/row-query-utils.js.map +0 -1
  159. package/dist/utils/otel.d.ts +0 -4
  160. package/dist/utils/otel.d.ts.map +0 -1
  161. package/dist/utils/otel.js +0 -6
  162. package/dist/utils/otel.js.map +0 -1
  163. package/src/global-state.ts +0 -20
  164. package/src/index.ts +0 -66
  165. package/src/live-queries/db.test.ts +0 -154
  166. package/src/live-queries/graphql.ts +0 -219
  167. package/src/row-query-utils.ts +0 -65
  168. package/src/utils/otel.ts +0 -9
  169. package/tsconfig.json +0 -18
  170. package/vitest.config.js +0 -9
@@ -1,26 +1,27 @@
1
- import type { Bindable, QueryBuilder, QueryInfo } from '@livestore/common'
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 { getDurationMsFromSpan } from '../utils/otel.js'
20
- import type { GetAtomResult, LiveQuery, QueryContext, ReactivityGraph } from './base-class.js'
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, TQueryInfo extends QueryInfo> = {
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
- queryInfo?: TQueryInfo
34
- execBeforeFirstRun?: (ctx: QueryContext) => void
34
+ execBeforeFirstRun?: (ctx: ReactivityGraphContext) => void
35
35
  }
36
36
 
37
- export type QueryInput<TDecoded, TEncoded, TQueryInfo extends QueryInfo> =
38
- | QueryInputRaw<TDecoded, TEncoded, TQueryInfo>
39
- | QueryBuilder<TDecoded, any, any, TQueryInfo>
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 `query` is only supposed to read data. Don't use it to insert/update/delete data but use mutations instead.
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, TQueryInfo extends QueryInfo = QueryInfo.None>(
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
- reactivityGraph?: ReactivityGraph
56
- otelContext?: otel.Context
83
+ deps?: DepKey
57
84
  },
58
- ): LiveQuery<TResult, TQueryInfo>
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, TQueryInfo extends QueryInfo = QueryInfo.None>(
89
+ <TResultSchema, TResult = TResultSchema>(
63
90
  queryInput:
64
- | ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
65
- | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any, TQueryInfo>),
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
- reactivityGraph?: ReactivityGraph
73
- queryInfo?: TQueryInfo
74
- otelContext?: otel.Context
99
+ deps?: DepKey
75
100
  },
76
- ): LiveQuery<TResult, TQueryInfo>
77
- } = (queryInput, options) =>
78
- new LiveStoreDbQuery({
79
- queryInput,
80
- label: options?.label,
81
- reactivityGraph: options?.reactivityGraph,
82
- map: options?.map,
83
- queryInfo: Predicate.hasProperty(options, 'queryInfo') ? (options.queryInfo as QueryInfo) : undefined,
84
- otelContext: options?.otelContext,
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
- TResultSchema,
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<QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>, QueryContext, RefreshReason> | undefined
173
+ queryInput$: Thunk<QueryInputRaw<any, any>, ReactivityGraphContext, RefreshReason> | undefined
97
174
 
98
175
  /** A reactive thunk representing the query results */
99
- results$: Thunk<TResult, QueryContext, RefreshReason>
176
+ results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
100
177
 
101
178
  label: string
102
179
 
103
- queryInfo: TQueryInfo
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>, TQueryInfo>
120
- | ((get: GetAtomResult, ctx: QueryContext) => QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
121
- reactivityGraph?: 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
- queryInfo?: TQueryInfo
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
- let queryInfo = inputQueryInfo ?? ({ _tag: 'None' } as TQueryInfo)
130
- this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
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: { current: ((ctx: QueryContext, otelContext: otel.Context) => void) | undefined } = {
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, QueryInfo>
222
+ type TQueryInputRaw = QueryInputRaw<any, any>
144
223
 
145
- let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, QueryContext, RefreshReason>
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
- insertValues: ast.insertValues,
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(makeGetAtomResult(get, otelContext ?? ctx.rootOtelContext), ctx)
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.syncDbWrapper.getTablesUsed(sqlString)
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.syncDbWrapper.select<any>(sqlString, {
295
- queriedTables: queriedTablesRef.current,
296
- bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
297
- otelContext,
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
- console.error(
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,4 @@
1
+ export * from './base-class.js'
2
+ export * from './computed.js'
3
+ export * from './db-query.js'
4
+ export * from './signal.js'
@@ -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'
@@ -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-58): b]`,
248
+ `[Error: This should never happen: LiveStore Error: Attempted to compute destroyed ref (node-2): b]`,
249
249
  )
250
250
  })
251
251
  })