@livestore/livestore 0.3.0-dev.4 → 0.3.0-dev.40

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 +64 -0
  32. package/dist/live-queries/db-query.d.ts.map +1 -0
  33. package/dist/live-queries/{db.js → db-query.js} +83 -41
  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 +17 -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 +69 -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 +103 -47
  68. package/dist/store/devtools.js.map +1 -1
  69. package/dist/store/store-types.d.ts +32 -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 +104 -39
  74. package/dist/store/store.d.ts.map +1 -1
  75. package/dist/store/store.js +261 -214
  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} +220 -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} +140 -82
  112. package/src/live-queries/mod.ts +4 -0
  113. package/src/live-queries/signal.test.ts +25 -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 +187 -59
  119. package/src/store/devtools.ts +136 -54
  120. package/src/store/store-types.ts +31 -43
  121. package/src/store/store.ts +385 -309
  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 -31
  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 -66
  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,149 @@ 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.
43
44
  */
44
45
  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>,
46
+ <TResultSchema, TResult = TResultSchema>(
47
+ queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>,
49
48
  options?: {
50
49
  map?: (rows: TResultSchema) => TResult
51
50
  /**
52
51
  * Used for debugging / devtools
53
52
  */
54
53
  label?: string
55
- reactivityGraph?: ReactivityGraph
56
- otelContext?: otel.Context
54
+ deps?: DepKey
57
55
  },
58
- ): LiveQuery<TResult, TQueryInfo>
56
+ ): LiveQueryDef<TResult>
59
57
  // NOTE in this "thunk case", we can't directly derive label/queryInfo from the queryInput,
60
58
  // so the caller needs to provide them explicitly otherwise queryInfo will be set to `None`,
61
59
  // and label will be set during the query execution
62
- <TResultSchema, TResult = TResultSchema, TQueryInfo extends QueryInfo = QueryInfo.None>(
60
+ <TResultSchema, TResult = TResultSchema>(
63
61
  queryInput:
64
- | ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
65
- | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any, TQueryInfo>),
62
+ | ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>)
63
+ | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>),
66
64
  options?: {
67
65
  map?: (rows: TResultSchema) => TResult
68
66
  /**
69
67
  * Used for debugging / devtools
70
68
  */
71
69
  label?: string
72
- reactivityGraph?: ReactivityGraph
73
- queryInfo?: TQueryInfo
74
- otelContext?: otel.Context
70
+ deps?: DepKey
75
71
  },
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
- })
72
+ ): LiveQueryDef<TResult>
73
+ } = (queryInput, options) => {
74
+ const { queryString, extraDeps } = getQueryStringAndExtraDeps(queryInput)
75
+
76
+ const hash =
77
+ (options?.deps ? queryString + '-' + depsToString(options.deps) : queryString) + '-' + depsToString(extraDeps)
78
+ if (isValidFunctionString(hash)._tag === 'invalid') {
79
+ throw new Error(`On Expo/React Native, db queries must provide a \`deps\` option`)
80
+ }
81
+
82
+ if (hash.trim() === '') {
83
+ return shouldNeverHappen(`Invalid query hash for query: ${queryInput}`)
84
+ }
85
+
86
+ const label = options?.label ?? queryString
87
+
88
+ const def: LiveQueryDef.Any = {
89
+ _tag: 'def',
90
+ make: withRCMap(hash, (ctx, otelContext) => {
91
+ // TODO onDestroy
92
+ return new LiveStoreDbQuery({
93
+ reactivityGraph: ctx.reactivityGraph.deref()!,
94
+ queryInput,
95
+ label,
96
+ map: options?.map,
97
+ otelContext,
98
+ def,
99
+ })
100
+ }),
101
+ label,
102
+ hash,
103
+ }
104
+
105
+ return def
106
+ }
107
+
108
+ const bindValuesToDepKey = (bindValues: Bindable | undefined): DepKey => {
109
+ if (bindValues === undefined) {
110
+ return []
111
+ }
112
+
113
+ return Object.entries(bindValues)
114
+ .map(([key, value]: [string, any]) => `${key}:${value === SessionIdSymbol ? 'SessionIdSymbol' : value}`)
115
+ .join(',')
116
+ }
117
+
118
+ const getQueryStringAndExtraDeps = (
119
+ queryInput: QueryInput<any, any> | ((get: GetAtomResult) => QueryInput<any, any>),
120
+ ): { queryString: string; extraDeps: DepKey } => {
121
+ if (isQueryBuilder(queryInput)) {
122
+ const { query, bindValues } = queryInput.asSql()
123
+ return { queryString: query, extraDeps: bindValuesToDepKey(bindValues) }
124
+ }
125
+
126
+ if (isQueryInputRaw(queryInput)) {
127
+ return { queryString: queryInput.query, extraDeps: bindValuesToDepKey(queryInput.bindValues) }
128
+ }
129
+
130
+ if (typeof queryInput === 'function') {
131
+ return { queryString: queryInput.toString(), extraDeps: [] }
132
+ }
133
+
134
+ return shouldNeverHappen(`Invalid query input: ${queryInput}`)
135
+ }
86
136
 
87
137
  /* 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> {
138
+ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends LiveStoreQueryBase<TResult> {
93
139
  _tag: 'db' = 'db'
94
140
 
95
141
  /** A reactive thunk representing the query text */
96
- queryInput$: Thunk<QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>, QueryContext, RefreshReason> | undefined
142
+ queryInput$: Thunk<QueryInputRaw<any, any>, ReactivityGraphContext, RefreshReason> | undefined
97
143
 
98
144
  /** A reactive thunk representing the query results */
99
- results$: Thunk<TResult, QueryContext, RefreshReason>
145
+ results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
100
146
 
101
147
  label: string
102
148
 
103
- queryInfo: TQueryInfo
104
-
105
- protected reactivityGraph
149
+ readonly reactivityGraph
106
150
 
107
151
  private mapResult: (rows: TResultSchema) => TResult
152
+ def: LiveQueryDef<TResult>
108
153
 
109
154
  constructor({
110
155
  queryInput,
111
156
  label: inputLabel,
112
157
  reactivityGraph,
113
158
  map,
114
- queryInfo: inputQueryInfo,
115
159
  otelContext,
160
+ def,
116
161
  }: {
117
162
  label?: string
118
163
  queryInput:
119
- | QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>
120
- | ((get: GetAtomResult, ctx: QueryContext) => QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
121
- reactivityGraph?: ReactivityGraph
164
+ | QueryInput<TResultSchema, ReadonlyArray<any>>
165
+ | ((get: GetAtomResult, ctx: ReactivityGraphContext) => QueryInput<TResultSchema, ReadonlyArray<any>>)
166
+ reactivityGraph: ReactivityGraph
122
167
  map?: (rows: TResultSchema) => TResult
123
- queryInfo?: TQueryInfo
168
+ /** Only used for the initial query execution */
124
169
  otelContext?: otel.Context
170
+ def: LiveQueryDef<TResult>
125
171
  }) {
126
172
  super()
127
173
 
128
174
  let label = inputLabel ?? 'db(unknown)'
129
- let queryInfo = inputQueryInfo ?? ({ _tag: 'None' } as TQueryInfo)
130
- this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
175
+ this.reactivityGraph = reactivityGraph
176
+ this.def = def
131
177
 
132
178
  this.mapResult = map === undefined ? (rows: any) => rows as TResult : map
133
179
 
@@ -136,13 +182,15 @@ export class LiveStoreDbQuery<
136
182
  typeof queryInput === 'function' ? undefined : isQueryBuilder(queryInput) ? undefined : queryInput.schema,
137
183
  }
138
184
 
139
- const execBeforeFirstRunRef: { current: ((ctx: QueryContext, otelContext: otel.Context) => void) | undefined } = {
185
+ const execBeforeFirstRunRef: {
186
+ current: ((ctx: ReactivityGraphContext, otelContext: otel.Context) => void) | undefined
187
+ } = {
140
188
  current: undefined,
141
189
  }
142
190
 
143
- type TQueryInputRaw = QueryInputRaw<any, any, QueryInfo>
191
+ type TQueryInputRaw = QueryInputRaw<any, any>
144
192
 
145
- let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, QueryContext, RefreshReason>
193
+ let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, ReactivityGraphContext, RefreshReason>
146
194
 
147
195
  const fromQueryBuilder = (qb: QueryBuilder.Any, otelContext: otel.Context | undefined) => {
148
196
  try {
@@ -156,14 +204,13 @@ export class LiveStoreDbQuery<
156
204
  schema,
157
205
  bindValues: qbRes.bindValues,
158
206
  queriedTables: new Set([ast.tableDef.sqliteDef.name]),
159
- queryInfo: ast._tag === 'RowQuery' ? { _tag: 'Row', table: ast.tableDef, id: ast.id } : { _tag: 'None' },
160
207
  } satisfies TQueryInputRaw,
161
208
  label: ast._tag === 'RowQuery' ? rowQueryLabel(ast.tableDef, ast.id) : qb.toString(),
162
209
  execBeforeFirstRun:
163
210
  ast._tag === 'RowQuery'
164
211
  ? makeExecBeforeFirstRun({
165
212
  table: ast.tableDef,
166
- insertValues: ast.insertValues,
213
+ explicitDefaultValues: ast.explicitDefaultValues,
167
214
  id: ast.id,
168
215
  otelContext,
169
216
  })
@@ -178,7 +225,10 @@ export class LiveStoreDbQuery<
178
225
  queryInputRaw$OrQueryInputRaw = this.reactivityGraph.makeThunk(
179
226
  (get, setDebugInfo, ctx, otelContext) => {
180
227
  const startMs = performance.now()
181
- const queryInputResult = queryInput(makeGetAtomResult(get, otelContext ?? ctx.rootOtelContext), ctx)
228
+ const queryInputResult = queryInput(
229
+ makeGetAtomResult(get, ctx, otelContext ?? ctx.rootOtelContext, this.dependencyQueriesRef),
230
+ ctx,
231
+ )
182
232
  const durationMs = performance.now() - startMs
183
233
 
184
234
  let queryInputRaw: TQueryInputRaw
@@ -197,10 +247,6 @@ export class LiveStoreDbQuery<
197
247
 
198
248
  schemaRef.current = queryInputRaw.schema
199
249
 
200
- if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
201
- queryInfo = queryInputRaw.queryInfo as TQueryInfo
202
- }
203
-
204
250
  return queryInputRaw
205
251
  },
206
252
  {
@@ -210,6 +256,8 @@ export class LiveStoreDbQuery<
210
256
  equal: (a, b) => a.query === b.query && deepEqual(a.bindValues, b.bindValues),
211
257
  },
212
258
  )
259
+
260
+ this.queryInput$ = queryInputRaw$OrQueryInputRaw
213
261
  } else {
214
262
  let queryInputRaw: TQueryInputRaw
215
263
  if (isQueryBuilder(queryInput)) {
@@ -231,10 +279,6 @@ export class LiveStoreDbQuery<
231
279
  label = `db(${rowQueryLabel(ast.tableDef, ast.id)})`
232
280
  }
233
281
  }
234
-
235
- if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
236
- queryInfo = queryInputRaw.queryInfo as TQueryInfo
237
- }
238
282
  }
239
283
 
240
284
  const queriedTablesRef: { current: Set<string> | undefined } = { current: undefined }
@@ -253,10 +297,16 @@ export class LiveStoreDbQuery<
253
297
  : undefined
254
298
 
255
299
  const results$ = this.reactivityGraph.makeThunk<TResult>(
256
- (get, setDebugInfo, queryContext, otelContext) =>
300
+ (get, setDebugInfo, queryContext, otelContext, debugRefreshReason) =>
257
301
  queryContext.otelTracer.startActiveSpan(
258
302
  'db:...', // NOTE span name will be overridden further down
259
- {},
303
+ {
304
+ attributes: {
305
+ 'livestore.debugRefreshReason': Predicate.hasProperty(debugRefreshReason, 'label')
306
+ ? (debugRefreshReason.label as string)
307
+ : debugRefreshReason?._tag,
308
+ },
309
+ },
260
310
  otelContext ?? queryContext.rootOtelContext,
261
311
  (span) => {
262
312
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
@@ -268,34 +318,37 @@ export class LiveStoreDbQuery<
268
318
  }
269
319
 
270
320
  const queryInputResult = isThunk(queryInputRaw$OrQueryInputRaw)
271
- ? (get(queryInputRaw$OrQueryInputRaw, otelContext) as TQueryInputRaw)
321
+ ? (get(queryInputRaw$OrQueryInputRaw, otelContext, debugRefreshReason) as TQueryInputRaw)
272
322
  : (queryInputRaw$OrQueryInputRaw as TQueryInputRaw)
273
323
 
274
324
  const sqlString = queryInputResult.query
275
325
  const bindValues = queryInputResult.bindValues
276
326
 
277
327
  if (queriedTablesRef.current === undefined) {
278
- queriedTablesRef.current = store.syncDbWrapper.getTablesUsed(sqlString)
328
+ queriedTablesRef.current = store.sqliteDbWrapper.getTablesUsed(sqlString)
279
329
  }
280
330
 
281
331
  if (bindValues !== undefined) {
282
- replaceSessionIdSymbol(bindValues, store.clientSession.coordinator.sessionId)
332
+ replaceSessionIdSymbol(bindValues, store.clientSession.sessionId)
283
333
  }
284
334
 
285
335
  // Establish a reactive dependency on the tables used in the query
286
336
  for (const tableName of queriedTablesRef.current) {
287
337
  const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
288
- get(tableRef, otelContext)
338
+ get(tableRef, otelContext, debugRefreshReason)
289
339
  }
290
340
 
291
341
  span.setAttribute('sql.query', sqlString)
292
342
  span.updateName(`db:${sqlString.slice(0, 50)}`)
293
343
 
294
- const rawDbResults = store.syncDbWrapper.select<any>(sqlString, {
295
- queriedTables: queriedTablesRef.current,
296
- bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
297
- otelContext,
298
- })
344
+ const rawDbResults = store.sqliteDbWrapper.select<any>(
345
+ sqlString,
346
+ bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
347
+ {
348
+ queriedTables: queriedTablesRef.current,
349
+ otelContext,
350
+ },
351
+ )
299
352
 
300
353
  span.setAttribute('sql.rowsCount', rawDbResults.length)
301
354
 
@@ -306,9 +359,9 @@ export class LiveStoreDbQuery<
306
359
  const expectedSchemaStr = String(schemaRef.current!.ast)
307
360
  const bindValuesStr = bindValues === undefined ? '' : `\nBind values: ${JSON.stringify(bindValues)}`
308
361
 
309
- console.error(
362
+ return shouldNeverHappen(
310
363
  `\
311
- Error parsing SQL query result.
364
+ Error parsing SQL query result (${label}).
312
365
 
313
366
  Query: ${sqlString}\
314
367
  ${bindValuesStr}
@@ -319,8 +372,8 @@ Error: ${parseErrorStr}
319
372
 
320
373
  Result:`,
321
374
  rawDbResults,
375
+ '\n',
322
376
  )
323
- return shouldNeverHappen(`Error parsing SQL query result: ${parsedResult.left}`)
324
377
  }
325
378
 
326
379
  const result = this.mapResult(parsedResult.right)
@@ -342,14 +395,19 @@ Result:`,
342
395
  this.results$ = results$
343
396
 
344
397
  this.label = label
345
- this.queryInfo = queryInfo
346
398
  }
347
399
 
348
400
  destroy = () => {
401
+ this.isDestroyed = true
402
+
349
403
  if (this.queryInput$ !== undefined) {
350
404
  this.reactivityGraph.destroyNode(this.queryInput$)
351
405
  }
352
406
 
353
407
  this.reactivityGraph.destroyNode(this.results$)
408
+
409
+ for (const query of this.dependencyQueriesRef) {
410
+ query.deref()
411
+ }
354
412
  }
355
413
  }
@@ -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,25 @@
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
+ })
@@ -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
  })