@livestore/livestore 0.3.0-dev.10 → 0.3.0-dev.12

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 (117) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/SqliteDbWrapper.d.ts +54 -0
  3. package/dist/SqliteDbWrapper.d.ts.map +1 -0
  4. package/dist/SqliteDbWrapper.js +211 -0
  5. package/dist/SqliteDbWrapper.js.map +1 -0
  6. package/dist/SynchronousDatabaseWrapper.d.ts +14 -5
  7. package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
  8. package/dist/SynchronousDatabaseWrapper.js +24 -4
  9. package/dist/SynchronousDatabaseWrapper.js.map +1 -1
  10. package/dist/effect/LiveStore.d.ts +12 -8
  11. package/dist/effect/LiveStore.d.ts.map +1 -1
  12. package/dist/effect/LiveStore.js +9 -2
  13. package/dist/effect/LiveStore.js.map +1 -1
  14. package/dist/index.d.ts +6 -7
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +4 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/live-queries/base-class.d.ts +57 -21
  19. package/dist/live-queries/base-class.d.ts.map +1 -1
  20. package/dist/live-queries/base-class.js +54 -13
  21. package/dist/live-queries/base-class.js.map +1 -1
  22. package/dist/live-queries/computed.d.ts +7 -7
  23. package/dist/live-queries/computed.d.ts.map +1 -1
  24. package/dist/live-queries/computed.js +34 -11
  25. package/dist/live-queries/computed.js.map +1 -1
  26. package/dist/live-queries/db-query.d.ts +67 -0
  27. package/dist/live-queries/db-query.d.ts.map +1 -0
  28. package/dist/live-queries/db-query.js +243 -0
  29. package/dist/live-queries/db-query.js.map +1 -0
  30. package/dist/live-queries/db-query.test.d.ts +2 -0
  31. package/dist/live-queries/db-query.test.d.ts.map +1 -0
  32. package/dist/live-queries/db-query.test.js +113 -0
  33. package/dist/live-queries/db-query.test.js.map +1 -0
  34. package/dist/live-queries/db.d.ts +12 -15
  35. package/dist/live-queries/db.d.ts.map +1 -1
  36. package/dist/live-queries/db.js +44 -25
  37. package/dist/live-queries/db.js.map +1 -1
  38. package/dist/live-queries/db.test.js +16 -14
  39. package/dist/live-queries/db.test.js.map +1 -1
  40. package/dist/live-queries/graphql.d.ts +8 -8
  41. package/dist/live-queries/graphql.d.ts.map +1 -1
  42. package/dist/live-queries/graphql.js +34 -9
  43. package/dist/live-queries/graphql.js.map +1 -1
  44. package/dist/live-queries/make-ref.d.ts +20 -0
  45. package/dist/live-queries/make-ref.d.ts.map +1 -0
  46. package/dist/live-queries/make-ref.js +33 -0
  47. package/dist/live-queries/make-ref.js.map +1 -0
  48. package/dist/reactive.d.ts +19 -13
  49. package/dist/reactive.d.ts.map +1 -1
  50. package/dist/reactive.js +22 -18
  51. package/dist/reactive.js.map +1 -1
  52. package/dist/reactive.test.js +1 -1
  53. package/dist/reactive.test.js.map +1 -1
  54. package/dist/row-query-utils.d.ts +6 -6
  55. package/dist/row-query-utils.d.ts.map +1 -1
  56. package/dist/row-query-utils.js +15 -11
  57. package/dist/row-query-utils.js.map +1 -1
  58. package/dist/store/create-store.d.ts +7 -5
  59. package/dist/store/create-store.d.ts.map +1 -1
  60. package/dist/store/create-store.js +21 -7
  61. package/dist/store/create-store.js.map +1 -1
  62. package/dist/store/devtools.d.ts +5 -4
  63. package/dist/store/devtools.d.ts.map +1 -1
  64. package/dist/store/devtools.js +45 -23
  65. package/dist/store/devtools.js.map +1 -1
  66. package/dist/store/store-types.d.ts +9 -4
  67. package/dist/store/store-types.d.ts.map +1 -1
  68. package/dist/store/store-types.js.map +1 -1
  69. package/dist/store/store.d.ts +36 -18
  70. package/dist/store/store.d.ts.map +1 -1
  71. package/dist/store/store.js +127 -75
  72. package/dist/store/store.js.map +1 -1
  73. package/dist/utils/expo.d.ts +2 -0
  74. package/dist/utils/expo.d.ts.map +1 -0
  75. package/dist/utils/expo.js +8 -0
  76. package/dist/utils/expo.js.map +1 -0
  77. package/dist/utils/function-string.d.ts +7 -0
  78. package/dist/utils/function-string.d.ts.map +1 -0
  79. package/dist/utils/function-string.js +9 -0
  80. package/dist/utils/function-string.js.map +1 -0
  81. package/dist/utils/stack-info.d.ts.map +1 -1
  82. package/dist/utils/stack-info.js +6 -1
  83. package/dist/utils/stack-info.js.map +1 -1
  84. package/dist/utils/stack-info.test.js +54 -1
  85. package/dist/utils/stack-info.test.js.map +1 -1
  86. package/dist/utils/tests/fixture.d.ts +2 -6
  87. package/dist/utils/tests/fixture.d.ts.map +1 -1
  88. package/dist/utils/tests/fixture.js +3 -5
  89. package/dist/utils/tests/fixture.js.map +1 -1
  90. package/dist/utils/tests/mod.d.ts +1 -0
  91. package/dist/utils/tests/mod.d.ts.map +1 -1
  92. package/dist/utils/tests/mod.js +1 -0
  93. package/dist/utils/tests/mod.js.map +1 -1
  94. package/package.json +5 -5
  95. package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +41 -12
  96. package/src/effect/LiveStore.ts +22 -14
  97. package/src/index.ts +14 -7
  98. package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +196 -45
  99. package/src/live-queries/base-class.ts +151 -40
  100. package/src/live-queries/computed.ts +44 -19
  101. package/src/live-queries/{db.test.ts → db-query.test.ts} +44 -32
  102. package/src/live-queries/{db.ts → db-query.ts} +96 -39
  103. package/src/live-queries/graphql.ts +46 -21
  104. package/src/live-queries/make-ref.ts +47 -0
  105. package/src/reactive.test.ts +1 -1
  106. package/src/reactive.ts +60 -37
  107. package/src/row-query-utils.ts +32 -21
  108. package/src/store/create-store.ts +55 -27
  109. package/src/store/devtools.ts +74 -29
  110. package/src/store/store-types.ts +6 -4
  111. package/src/store/store.ts +231 -121
  112. package/src/utils/function-string.ts +12 -0
  113. package/src/utils/stack-info.test.ts +58 -1
  114. package/src/utils/stack-info.ts +6 -1
  115. package/src/utils/tests/fixture.ts +2 -7
  116. package/src/utils/tests/mod.ts +1 -0
  117. package/src/global-state.ts +0 -20
@@ -5,13 +5,12 @@ import { Schema, TreeFormatter } from '@livestore/utils/effect'
5
5
  import * as otel from '@opentelemetry/api'
6
6
  import * as graphql from 'graphql'
7
7
 
8
- import { globalReactivityGraph } from '../global-state.js'
9
8
  import { isThunk, type Thunk } from '../reactive.js'
10
9
  import type { Store } from '../store/store.js'
11
10
  import type { BaseGraphQLContext, RefreshReason } from '../store/store-types.js'
12
11
  import { getDurationMsFromSpan } from '../utils/otel.js'
13
- import type { GetAtomResult, LiveQuery, QueryContext, ReactivityGraph } from './base-class.js'
14
- import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
12
+ import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
13
+ import { depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.js'
15
14
 
16
15
  export type MapResult<To, From> = ((res: From, get: GetAtomResult) => To) | Schema.Schema<To, From>
17
16
 
@@ -22,17 +21,36 @@ export const queryGraphQL = <
22
21
  >(
23
22
  document: DocumentNode<TResult, TVariableValues>,
24
23
  genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
25
- {
26
- label,
27
- reactivityGraph,
28
- map,
29
- }: {
24
+ options: {
30
25
  label?: string
31
- reactivityGraph?: ReactivityGraph
26
+ // reactivityGraph?: ReactivityGraph
32
27
  map?: MapResult<TResultMapped, TResult>
28
+ deps?: DepKey
33
29
  } = {},
34
- ): LiveQuery<TResultMapped, QueryInfo.None> =>
35
- new LiveStoreGraphQLQuery({ document, genVariableValues, label, reactivityGraph, map })
30
+ ): LiveQueryDef<TResultMapped, QueryInfo.None> => {
31
+ const documentName = graphql.getOperationAST(document)?.name?.value
32
+ const hash = options.deps
33
+ ? depsToString(options.deps)
34
+ : (documentName ?? shouldNeverHappen('No document name found and no deps provided'))
35
+ const label = options.label ?? documentName ?? 'graphql'
36
+ const map = options.map
37
+
38
+ return {
39
+ _tag: 'def',
40
+ make: withRCMap(hash, (ctx, _otelContext) => {
41
+ return new LiveStoreGraphQLQuery({
42
+ document,
43
+ genVariableValues,
44
+ label,
45
+ map,
46
+ reactivityGraph: ctx.reactivityGraph.deref()!,
47
+ })
48
+ }),
49
+ label,
50
+ hash,
51
+ queryInfo: { _tag: 'None' },
52
+ }
53
+ }
36
54
 
37
55
  export class LiveStoreGraphQLQuery<
38
56
  TResult extends Record<string, any>,
@@ -46,13 +64,13 @@ export class LiveStoreGraphQLQuery<
46
64
  document: DocumentNode<TResult, TVariableValues>
47
65
 
48
66
  /** A reactive thunk representing the query results */
49
- results$: Thunk<TResultMapped, QueryContext, RefreshReason>
67
+ results$: Thunk<TResultMapped, ReactivityGraphContext, RefreshReason>
50
68
 
51
- variableValues$: Thunk<TVariableValues, QueryContext, RefreshReason> | undefined
69
+ variableValues$: Thunk<TVariableValues, ReactivityGraphContext, RefreshReason> | undefined
52
70
 
53
71
  label: string
54
72
 
55
- protected reactivityGraph: ReactivityGraph
73
+ reactivityGraph: ReactivityGraph
56
74
 
57
75
  queryInfo: QueryInfo.None = { _tag: 'None' }
58
76
 
@@ -68,7 +86,7 @@ export class LiveStoreGraphQLQuery<
68
86
  document: DocumentNode<TResult, TVariableValues>
69
87
  genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues)
70
88
  label?: string
71
- reactivityGraph?: ReactivityGraph
89
+ reactivityGraph: ReactivityGraph
72
90
  map?: MapResult<TResultMapped, TResult>
73
91
  }) {
74
92
  super()
@@ -78,7 +96,7 @@ export class LiveStoreGraphQLQuery<
78
96
  this.label = labelWithDefault
79
97
  this.document = document
80
98
 
81
- this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
99
+ this.reactivityGraph = reactivityGraph
82
100
 
83
101
  this.mapResult =
84
102
  map === undefined
@@ -102,8 +120,10 @@ export class LiveStoreGraphQLQuery<
102
120
 
103
121
  if (typeof genVariableValues === 'function') {
104
122
  variableValues$OrvariableValues = this.reactivityGraph.makeThunk(
105
- (get, _setDebugInfo, { rootOtelContext }, otelContext) => {
106
- return genVariableValues(makeGetAtomResult(get, otelContext ?? rootOtelContext))
123
+ (get, _setDebugInfo, ctx, otelContext) => {
124
+ return genVariableValues(
125
+ makeGetAtomResult(get, ctx, otelContext ?? ctx.rootOtelContext, this.dependencyQueriesRef),
126
+ )
107
127
  },
108
128
  { label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphql.variables' } },
109
129
  )
@@ -114,9 +134,10 @@ export class LiveStoreGraphQLQuery<
114
134
 
115
135
  const resultsLabel = `${labelWithDefault}:results`
116
136
  this.results$ = this.reactivityGraph.makeThunk<TResultMapped>(
117
- (get, setDebugInfo, { store, otelTracer, rootOtelContext }, otelContext) => {
137
+ (get, setDebugInfo, ctx, otelContext, debugRefreshReason) => {
138
+ const { store, otelTracer, rootOtelContext } = ctx
118
139
  const variableValues = isThunk(variableValues$OrvariableValues)
119
- ? (get(variableValues$OrvariableValues) as TVariableValues)
140
+ ? (get(variableValues$OrvariableValues, otelContext, debugRefreshReason) as TVariableValues)
120
141
  : (variableValues$OrvariableValues as TVariableValues)
121
142
  const { result, queriedTables, durationMs } = this.queryOnce({
122
143
  document,
@@ -124,7 +145,7 @@ export class LiveStoreGraphQLQuery<
124
145
  otelContext: otelContext ?? rootOtelContext,
125
146
  otelTracer,
126
147
  store: store as Store<TContext>,
127
- get: makeGetAtomResult(get, otelContext ?? rootOtelContext),
148
+ get: makeGetAtomResult(get, ctx, otelContext ?? rootOtelContext, this.dependencyQueriesRef),
128
149
  })
129
150
 
130
151
  // Add dependencies on any tables that were used
@@ -215,5 +236,9 @@ export class LiveStoreGraphQLQuery<
215
236
  }
216
237
 
217
238
  this.reactivityGraph.destroyNode(this.results$)
239
+
240
+ for (const query of this.dependencyQueriesRef) {
241
+ query.deref()
242
+ }
218
243
  }
219
244
  }
@@ -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 { ILiveQueryRef, ILiveQueryRefDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
6
+ import { withRCMap } from './base-class.js'
7
+
8
+ export const makeRef = <T>(
9
+ defaultValue: T,
10
+ options?: {
11
+ label?: string
12
+ },
13
+ ): ILiveQueryRefDef<T> => {
14
+ const id = nanoid()
15
+ return {
16
+ _tag: 'live-ref-def',
17
+ defaultValue,
18
+ make: withRCMap(id, (ctx) => new LiveQueryRef(defaultValue, ctx.reactivityGraph.deref()!, options)),
19
+ }
20
+ }
21
+
22
+ export class LiveQueryRef<T> implements ILiveQueryRef<T> {
23
+ _tag = 'live-ref' 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
+ }
@@ -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
  })
package/src/reactive.ts CHANGED
@@ -32,7 +32,11 @@ import type * as otel from '@opentelemetry/api'
32
32
  export const NOT_REFRESHED_YET = Symbol.for('NOT_REFRESHED_YET')
33
33
  export type NOT_REFRESHED_YET = typeof NOT_REFRESHED_YET
34
34
 
35
- export type GetAtom = <T>(atom: Atom<T, any, any>, otelContext?: otel.Context) => T
35
+ export type GetAtom = <T>(
36
+ atom: Atom<T, any, any>,
37
+ otelContext?: otel.Context | undefined,
38
+ debugRefreshReason?: TODO | undefined,
39
+ ) => T
36
40
 
37
41
  export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
38
42
  _tag: 'ref'
@@ -42,7 +46,7 @@ export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
42
46
  previousResult: T
43
47
  computeResult: () => T
44
48
  sub: Set<Atom<any, TContext, TDebugRefreshReason>> // always empty
45
- super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect>
49
+ super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
46
50
  label?: string
47
51
  /** Container for meta information (e.g. the LiveStore Store) */
48
52
  meta?: any
@@ -58,7 +62,7 @@ export type Thunk<TResult, TContext, TDebugRefreshReason extends DebugRefreshRea
58
62
  computeResult: (otelContext?: otel.Context, debugRefreshReason?: TDebugRefreshReason) => TResult
59
63
  previousResult: TResult | NOT_REFRESHED_YET
60
64
  sub: Set<Atom<any, TContext, TDebugRefreshReason>>
61
- super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect>
65
+ super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
62
66
  label?: string
63
67
  /** Container for meta information (e.g. the LiveStore Store) */
64
68
  meta?: any
@@ -72,11 +76,11 @@ export type Atom<T, TContext, TDebugRefreshReason extends DebugRefreshReason> =
72
76
  | Ref<T, TContext, TDebugRefreshReason>
73
77
  | Thunk<T, TContext, TDebugRefreshReason>
74
78
 
75
- export type Effect = {
79
+ export type Effect<TDebugRefreshReason extends DebugRefreshReason> = {
76
80
  _tag: 'effect'
77
81
  id: string
78
82
  isDestroyed: boolean
79
- doEffect: (otelContext?: otel.Context) => void
83
+ doEffect: (otelContext?: otel.Context | undefined, debugRefreshReason?: TDebugRefreshReason | undefined) => void
80
84
  sub: Set<Atom<any, TODO, TODO>>
81
85
  label?: string
82
86
  invocations: number
@@ -84,7 +88,7 @@ export type Effect = {
84
88
 
85
89
  export type Node<T, TContext, TDebugRefreshReason extends DebugRefreshReason> =
86
90
  | Atom<T, TContext, TDebugRefreshReason>
87
- | Effect
91
+ | Effect<TDebugRefreshReason>
88
92
 
89
93
  export const isThunk = <T, TContext, TDebugRefreshReason extends DebugRefreshReason>(
90
94
  obj: unknown,
@@ -166,7 +170,7 @@ export type SerializedThunk = Readonly<
166
170
 
167
171
  export type SerializedEffect = Readonly<
168
172
  PrettifyFlat<
169
- Pick<Effect, '_tag' | 'id' | 'label' | 'invocations' | 'isDestroyed'> & {
173
+ Pick<Effect<any>, '_tag' | 'id' | 'label' | 'invocations' | 'isDestroyed'> & {
170
174
  sub: ReadonlyArray<string>
171
175
  }
172
176
  >
@@ -179,14 +183,14 @@ export type ReactiveGraphSnapshot = {
179
183
  readonly deferredEffects: ReadonlyArray<string>
180
184
  }
181
185
 
182
- let nodeIdCounter = 0
183
- const uniqueNodeId = () => `node-${++nodeIdCounter}`
184
- let refreshInfoIdCounter = 0
185
- const uniqueRefreshInfoId = () => `refresh-info-${++refreshInfoIdCounter}`
186
-
187
186
  let globalGraphIdCounter = 0
188
187
  const uniqueGraphId = () => `graph-${++globalGraphIdCounter}`
189
188
 
189
+ /** Used for testing */
190
+ export const __resetIds = () => {
191
+ globalGraphIdCounter = 0
192
+ }
193
+
190
194
  export class ReactiveGraph<
191
195
  TDebugRefreshReason extends DebugRefreshReason,
192
196
  TDebugThunkInfo extends DebugThunkInfo,
@@ -195,7 +199,7 @@ export class ReactiveGraph<
195
199
  id = uniqueGraphId()
196
200
 
197
201
  readonly atoms: Set<Atom<any, TContext, TDebugRefreshReason>> = new Set()
198
- readonly effects: Set<Effect> = new Set()
202
+ readonly effects: Set<Effect<TDebugRefreshReason>> = new Set()
199
203
 
200
204
  context: TContext | undefined
201
205
 
@@ -205,17 +209,22 @@ export class ReactiveGraph<
205
209
  | { refreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[]; startMs: DOMHighResTimeStamp }
206
210
  | undefined
207
211
 
208
- private deferredEffects: Map<Effect, Set<TDebugRefreshReason>> = new Map()
212
+ private deferredEffects: Map<Effect<TDebugRefreshReason>, Set<TDebugRefreshReason>> = new Map()
209
213
 
210
214
  private refreshCallbacks: Set<() => void> = new Set()
211
215
 
216
+ private nodeIdCounter = 0
217
+ private uniqueNodeId = () => `node-${++this.nodeIdCounter}`
218
+ private refreshInfoIdCounter = 0
219
+ private uniqueRefreshInfoId = () => `refresh-info-${++this.refreshInfoIdCounter}`
220
+
212
221
  makeRef<T>(
213
222
  val: T,
214
223
  options?: { label?: string; meta?: unknown; equal?: (a: T, b: T) => boolean },
215
224
  ): Ref<T, TContext, TDebugRefreshReason> {
216
225
  const ref: Ref<T, TContext, TDebugRefreshReason> = {
217
226
  _tag: 'ref',
218
- id: uniqueNodeId(),
227
+ id: this.uniqueNodeId(),
219
228
  isDirty: false,
220
229
  isDestroyed: false,
221
230
  previousResult: val,
@@ -239,6 +248,7 @@ export class ReactiveGraph<
239
248
  setDebugInfo: (debugInfo: TDebugThunkInfo) => void,
240
249
  ctx: TContext,
241
250
  otelContext: otel.Context | undefined,
251
+ debugRefreshReason: TDebugRefreshReason | undefined,
242
252
  ) => T,
243
253
  options?:
244
254
  | {
@@ -250,7 +260,7 @@ export class ReactiveGraph<
250
260
  ): Thunk<T, TContext, TDebugRefreshReason> {
251
261
  const thunk: Thunk<T, TContext, TDebugRefreshReason> = {
252
262
  _tag: 'thunk',
253
- id: uniqueNodeId(),
263
+ id: this.uniqueNodeId(),
254
264
  previousResult: NOT_REFRESHED_YET,
255
265
  isDirty: true,
256
266
  isDestroyed: false,
@@ -266,7 +276,7 @@ export class ReactiveGraph<
266
276
 
267
277
  const getAtom = (atom: Atom<T, TContext, TDebugRefreshReason>, otelContext: otel.Context) => {
268
278
  this.addEdge(thunk, atom)
269
- return compute(atom, otelContext)
279
+ return compute(atom, otelContext, debugRefreshReason)
270
280
  }
271
281
 
272
282
  let debugInfo: TDebugThunkInfo | undefined = undefined
@@ -279,6 +289,7 @@ export class ReactiveGraph<
279
289
  setDebugInfo,
280
290
  this.context ?? throwContextNotSetError(this),
281
291
  otelContext,
292
+ debugRefreshReason,
282
293
  )
283
294
 
284
295
  const resultChanged = thunk.equal(thunk.previousResult as T, result) === false
@@ -301,7 +312,7 @@ export class ReactiveGraph<
301
312
  this.currentDebugRefresh = undefined
302
313
 
303
314
  this.debugRefreshInfos.push({
304
- id: uniqueRefreshInfoId(),
315
+ id: this.uniqueRefreshInfoId(),
305
316
  reason: debugRefreshReason ?? ({ _tag: 'makeThunk', label: options?.label } as TDebugRefreshReason),
306
317
  skippedRefresh: false,
307
318
  refreshedAtoms,
@@ -365,14 +376,18 @@ export class ReactiveGraph<
365
376
  }
366
377
 
367
378
  makeEffect(
368
- doEffect: (get: GetAtom, otelContext?: otel.Context) => void,
379
+ doEffect: (
380
+ get: GetAtom,
381
+ otelContext: otel.Context | undefined,
382
+ debugRefreshReason: DebugRefreshReason | undefined,
383
+ ) => void,
369
384
  options?: { label?: string } | undefined,
370
- ): Effect {
371
- const effect: Effect = {
385
+ ): Effect<TDebugRefreshReason> {
386
+ const effect: Effect<TDebugRefreshReason> = {
372
387
  _tag: 'effect',
373
- id: uniqueNodeId(),
388
+ id: this.uniqueNodeId(),
374
389
  isDestroyed: false,
375
- doEffect: (otelContext) => {
390
+ doEffect: (otelContext, debugRefreshReason) => {
376
391
  effect.invocations++
377
392
 
378
393
  // NOTE we're not tracking any debug refresh info for effects as they're tracked by the thunks they depend on
@@ -380,12 +395,16 @@ export class ReactiveGraph<
380
395
  // Reset previous subcomputations as we're about to re-add them as part of the `doEffect` call below
381
396
  effect.sub = new Set()
382
397
 
383
- const getAtom = (atom: Atom<any, TContext, TDebugRefreshReason>, otelContext: otel.Context) => {
398
+ const getAtom = (
399
+ atom: Atom<any, TContext, TDebugRefreshReason>,
400
+ otelContext: otel.Context,
401
+ debugRefreshReason: DebugRefreshReason | undefined,
402
+ ) => {
384
403
  this.addEdge(effect, atom)
385
- return compute(atom, otelContext)
404
+ return compute(atom, otelContext, debugRefreshReason)
386
405
  }
387
406
 
388
- doEffect(getAtom as GetAtom, otelContext)
407
+ doEffect(getAtom as GetAtom, otelContext, debugRefreshReason)
389
408
  },
390
409
  sub: new Set(),
391
410
  label: options?.label,
@@ -421,7 +440,7 @@ export class ReactiveGraph<
421
440
  }
422
441
  | undefined,
423
442
  ) {
424
- const effectsToRefresh = new Set<Effect>()
443
+ const effectsToRefresh = new Set<Effect<TDebugRefreshReason>>()
425
444
  for (const [ref, val] of refs) {
426
445
  ref.previousResult = val
427
446
  ref.refreshes++
@@ -448,7 +467,7 @@ export class ReactiveGraph<
448
467
  }
449
468
 
450
469
  private runEffects = (
451
- effectsToRefresh: Set<Effect>,
470
+ effectsToRefresh: Set<Effect<TDebugRefreshReason>>,
452
471
  options: {
453
472
  debugRefreshReason: TDebugRefreshReason
454
473
  otelContext?: otel.Context
@@ -459,7 +478,7 @@ export class ReactiveGraph<
459
478
  this.currentDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
460
479
 
461
480
  for (const effect of effectsToRefresh) {
462
- effect.doEffect(options?.otelContext)
481
+ effect.doEffect(options?.otelContext, options.debugRefreshReason)
463
482
  }
464
483
 
465
484
  const refreshedAtoms = this.currentDebugRefresh.refreshedAtoms
@@ -467,7 +486,7 @@ export class ReactiveGraph<
467
486
  this.currentDebugRefresh = undefined
468
487
 
469
488
  const refreshDebugInfo: RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo> = {
470
- id: uniqueRefreshInfoId(),
489
+ id: this.uniqueRefreshInfoId(),
471
490
  reason: options.debugRefreshReason,
472
491
  skippedRefresh: false,
473
492
  refreshedAtoms,
@@ -504,7 +523,7 @@ export class ReactiveGraph<
504
523
  }
505
524
 
506
525
  addEdge(
507
- superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect,
526
+ superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>,
508
527
  subComp: Atom<any, TContext, TDebugRefreshReason>,
509
528
  ) {
510
529
  superComp.sub.add(subComp)
@@ -516,11 +535,11 @@ export class ReactiveGraph<
516
535
  }
517
536
 
518
537
  removeEdge(
519
- superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect,
538
+ superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>,
520
539
  subComp: Atom<any, TContext, TDebugRefreshReason>,
521
540
  ) {
522
541
  superComp.sub.delete(subComp)
523
- const effectsToRefresh = new Set<Effect>()
542
+ const effectsToRefresh = new Set<Effect<TDebugRefreshReason>>()
524
543
  markSuperCompDirtyRec(subComp, effectsToRefresh)
525
544
 
526
545
  for (const effect of effectsToRefresh) {
@@ -563,7 +582,11 @@ export class ReactiveGraph<
563
582
  }
564
583
  }
565
584
 
566
- const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T => {
585
+ const compute = <T>(
586
+ atom: Atom<T, unknown, any>,
587
+ otelContext: otel.Context,
588
+ debugRefreshReason: DebugRefreshReason | undefined,
589
+ ): T => {
567
590
  // const __getResult = atom._tag === 'thunk' ? atom.__getResult.toString() : ''
568
591
  if (atom.isDestroyed) {
569
592
  shouldNeverHappen(`LiveStore Error: Attempted to compute destroyed ${atom._tag} (${atom.id}): ${atom.label ?? ''}`)
@@ -571,7 +594,7 @@ const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T =
571
594
 
572
595
  if (atom.isDirty) {
573
596
  // console.log('atom is dirty', atom.id, atom.label ?? '', atom._tag, __getResult)
574
- const result = atom.computeResult(otelContext)
597
+ const result = atom.computeResult(otelContext, debugRefreshReason)
575
598
  atom.isDirty = false
576
599
  atom.previousResult = result
577
600
  return result
@@ -581,7 +604,7 @@ const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T =
581
604
  }
582
605
  }
583
606
 
584
- const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh: Set<Effect>) => {
607
+ const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh: Set<Effect<any>>) => {
585
608
  for (const superComp of atom.super) {
586
609
  if (superComp._tag === 'thunk') {
587
610
  superComp.isDirty = true
@@ -644,7 +667,7 @@ const serializeAtom = (atom: Atom<any, unknown, any>, includeResult: boolean): S
644
667
  }
645
668
 
646
669
  // NOTE This function is performance-optimized (i.e. not using `pick` and `Array.from`)
647
- const serializeEffect = (effect: Effect): SerializedEffect => {
670
+ const serializeEffect = (effect: Effect<any>): SerializedEffect => {
648
671
  const sub: string[] = []
649
672
  for (const a of effect.sub) {
650
673
  sub.push(a.id)
@@ -4,28 +4,33 @@ import { DbSchema } from '@livestore/common/schema'
4
4
  import { shouldNeverHappen } from '@livestore/utils'
5
5
  import type * as otel from '@opentelemetry/api'
6
6
 
7
- import type { LiveQuery, LiveQueryAny, QueryContext } from './live-queries/base-class.js'
7
+ import type { GetResult, LiveQueryDef, ReactivityGraphContext } from './live-queries/base-class.js'
8
8
  import { computed } from './live-queries/computed.js'
9
9
 
10
- export const rowQueryLabel = (table: DbSchema.TableDefBase, id: string | SessionIdSymbol | undefined) =>
10
+ export const rowQueryLabel = (table: DbSchema.TableDefBase, id: string | SessionIdSymbol | number | undefined) =>
11
11
  `row:${table.sqliteDef.name}${id === undefined ? '' : id === SessionIdSymbol ? `:sessionId` : `:${id}`}`
12
12
 
13
13
  export const deriveColQuery: {
14
- <TQuery extends LiveQuery<any, QueryInfo.None>, TCol extends keyof TQuery['__result!'] & string>(
15
- query$: TQuery,
14
+ <TQueryDef extends LiveQueryDef<any, QueryInfo.None>, TCol extends keyof GetResult<TQueryDef> & string>(
15
+ queryDef: TQueryDef,
16
16
  colName: TCol,
17
- ): LiveQuery<TQuery['__result!'][TCol], QueryInfo.None>
18
- <TQuery extends LiveQuery<any, QueryInfo.Row>, TCol extends keyof TQuery['__result!'] & string>(
19
- query$: TQuery,
17
+ ): LiveQueryDef<GetResult<TQueryDef>[TCol], QueryInfo.None>
18
+ <TQueryDef extends LiveQueryDef<any, QueryInfo.Row>, TCol extends keyof GetResult<TQueryDef> & string>(
19
+ queryDef: TQueryDef,
20
20
  colName: TCol,
21
- ): LiveQuery<TQuery['__result!'][TCol], QueryInfo.Col>
22
- } = (query$: LiveQueryAny, colName: string) => {
23
- return computed((get) => get(query$)[colName], {
24
- label: `deriveColQuery:${query$.label}:${colName}`,
21
+ ): LiveQueryDef<GetResult<TQueryDef>[TCol], QueryInfo.Col>
22
+ } = (queryDef: LiveQueryDef<any, QueryInfo.Row | QueryInfo.Col>, colName: string) => {
23
+ return computed((get) => get(queryDef)[colName], {
24
+ label: `deriveColQuery:${queryDef.label}:${colName}`,
25
25
  queryInfo:
26
- query$.queryInfo._tag === 'Row'
27
- ? { _tag: 'Col', table: query$.queryInfo.table, column: colName, id: query$.queryInfo.id }
26
+ queryDef.queryInfo._tag === 'Row'
27
+ ? { _tag: 'Col', table: queryDef.queryInfo.table, column: colName, id: queryDef.queryInfo.id }
28
28
  : undefined,
29
+ deps: [
30
+ queryDef.queryInfo.table.sqliteDef.name,
31
+ queryDef.queryInfo.id === SessionIdSymbol ? 'sessionId' : queryDef.queryInfo.id,
32
+ queryDef.queryInfo._tag === 'Col' ? queryDef.queryInfo.column : undefined,
33
+ ],
29
34
  }) as any
30
35
  }
31
36
 
@@ -36,20 +41,22 @@ export const makeExecBeforeFirstRun =
36
41
  table,
37
42
  otelContext: otelContext_,
38
43
  }: {
39
- id?: string | SessionIdSymbol
44
+ id?: string | SessionIdSymbol | number
40
45
  insertValues?: any
41
46
  table: DbSchema.TableDefBase
42
47
  otelContext: otel.Context | undefined
43
48
  }) =>
44
- ({ store }: QueryContext) => {
49
+ ({ store }: ReactivityGraphContext) => {
45
50
  const otelContext = otelContext_ ?? store.otel.queriesSpanContext
46
51
 
47
52
  if (table.options.isSingleton === false) {
48
- const idStr = id === SessionIdSymbol ? store.sessionId : id!
53
+ const idVal = id === SessionIdSymbol ? store.sessionId : id!
49
54
  const rowExists =
50
- store.syncDbWrapper.select(`SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`, {
51
- bindValues: [idStr] as any as PreparedBindValues,
52
- }).length === 1
55
+ store.sqliteDbWrapper.select(
56
+ `SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`,
57
+ [idVal] as any as PreparedBindValues,
58
+ { otelContext },
59
+ ).length === 1
53
60
 
54
61
  if (rowExists) return
55
62
 
@@ -59,7 +66,11 @@ export const makeExecBeforeFirstRun =
59
66
  )
60
67
  }
61
68
 
62
- // NOTE It's important that we only mutate and don't refresh here, as this function is called during a render
63
- store.mutate({ otelContext, skipRefresh: true }, table.insert({ id, ...insertValues }))
69
+ // It's important that we only mutate and don't refresh here, as this function might be called during a render
70
+ // and otherwise we might end up in a "reactive loop"
71
+ store.mutate(
72
+ { otelContext, skipRefresh: true, label: `rowQuery:${table.sqliteDef.name}:${idVal}` },
73
+ table.insert({ id, ...insertValues }),
74
+ )
64
75
  }
65
76
  }