@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.
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 +65 -27
  20. package/dist/live-queries/base-class.d.ts.map +1 -1
  21. package/dist/live-queries/base-class.js +54 -13
  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 +12 -14
  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} +111 -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 +20 -0
  44. package/dist/live-queries/signal.d.ts.map +1 -0
  45. package/dist/live-queries/signal.js +33 -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 +319 -153
  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 +152 -50
  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} +168 -81
  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 +47 -0
  115. package/src/mod.ts +42 -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 +454 -236
  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,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
- 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 =
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<QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>, QueryContext, RefreshReason> | undefined
171
+ queryInput$: Thunk<QueryInputRaw<any, any>, ReactivityGraphContext, RefreshReason> | undefined
97
172
 
98
173
  /** A reactive thunk representing the query results */
99
- results$: Thunk<TResult, QueryContext, RefreshReason>
174
+ results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
100
175
 
101
176
  label: string
102
177
 
103
- queryInfo: TQueryInfo
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>, TQueryInfo>
120
- | ((get: GetAtomResult, ctx: QueryContext) => QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
121
- reactivityGraph?: 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
- queryInfo?: TQueryInfo
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
- let queryInfo = inputQueryInfo ?? ({ _tag: 'None' } as TQueryInfo)
130
- this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
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: { current: ((ctx: QueryContext, otelContext: otel.Context) => void) | undefined } = {
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, QueryInfo>
220
+ type TQueryInputRaw = QueryInputRaw<any, any>
144
221
 
145
- let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, QueryContext, RefreshReason>
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
- insertValues: ast.insertValues,
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(makeGetAtomResult(get, otelContext ?? ctx.rootOtelContext), ctx)
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.syncDbWrapper.getTablesUsed(sqlString)
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.syncDbWrapper.select<any>(sqlString, {
295
- queriedTables: queriedTablesRef.current,
296
- bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
297
- otelContext,
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
- console.error(
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,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,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'
@@ -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
  })