@livestore/livestore 0.2.0 → 0.3.0-dev.11

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 (163) 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 +212 -0
  5. package/dist/SqliteDbWrapper.js.map +1 -0
  6. package/dist/SynchronousDatabaseWrapper.d.ts +20 -6
  7. package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
  8. package/dist/SynchronousDatabaseWrapper.js +38 -6
  9. package/dist/SynchronousDatabaseWrapper.js.map +1 -1
  10. package/dist/__tests__/fixture.d.ts +252 -0
  11. package/dist/__tests__/fixture.d.ts.map +1 -0
  12. package/dist/__tests__/fixture.js +18 -0
  13. package/dist/__tests__/fixture.js.map +1 -0
  14. package/dist/effect/LiveStore.d.ts +16 -12
  15. package/dist/effect/LiveStore.d.ts.map +1 -1
  16. package/dist/effect/LiveStore.js +14 -14
  17. package/dist/effect/LiveStore.js.map +1 -1
  18. package/dist/index.d.ts +6 -7
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +4 -4
  21. package/dist/index.js.map +1 -1
  22. package/dist/live-queries/base-class.d.ts +64 -21
  23. package/dist/live-queries/base-class.d.ts.map +1 -1
  24. package/dist/live-queries/base-class.js +56 -13
  25. package/dist/live-queries/base-class.js.map +1 -1
  26. package/dist/live-queries/computed.d.ts +7 -7
  27. package/dist/live-queries/computed.d.ts.map +1 -1
  28. package/dist/live-queries/computed.js +35 -11
  29. package/dist/live-queries/computed.js.map +1 -1
  30. package/dist/live-queries/{sql.d.ts → db-query.d.ts} +19 -14
  31. package/dist/live-queries/db-query.d.ts.map +1 -0
  32. package/dist/live-queries/db-query.js +244 -0
  33. package/dist/live-queries/db-query.js.map +1 -0
  34. package/dist/live-queries/db-query.test.d.ts +2 -0
  35. package/dist/live-queries/db-query.test.d.ts.map +1 -0
  36. package/dist/live-queries/db-query.test.js +123 -0
  37. package/dist/live-queries/db-query.test.js.map +1 -0
  38. package/dist/live-queries/db.d.ts +12 -15
  39. package/dist/live-queries/db.d.ts.map +1 -1
  40. package/dist/live-queries/db.js +72 -48
  41. package/dist/live-queries/db.js.map +1 -1
  42. package/dist/live-queries/db.test.js +18 -15
  43. package/dist/live-queries/db.test.js.map +1 -1
  44. package/dist/live-queries/graphql.d.ts +8 -8
  45. package/dist/live-queries/graphql.d.ts.map +1 -1
  46. package/dist/live-queries/graphql.js +35 -9
  47. package/dist/live-queries/graphql.js.map +1 -1
  48. package/dist/live-queries/make-ref.d.ts +20 -0
  49. package/dist/live-queries/make-ref.d.ts.map +1 -0
  50. package/dist/live-queries/make-ref.js +33 -0
  51. package/dist/live-queries/make-ref.js.map +1 -0
  52. package/dist/reactive.d.ts +15 -13
  53. package/dist/reactive.d.ts.map +1 -1
  54. package/dist/reactive.js +15 -9
  55. package/dist/reactive.js.map +1 -1
  56. package/dist/row-query-utils.d.ts +4 -4
  57. package/dist/row-query-utils.d.ts.map +1 -1
  58. package/dist/row-query-utils.js +14 -10
  59. package/dist/row-query-utils.js.map +1 -1
  60. package/dist/store/create-store.d.ts +13 -12
  61. package/dist/store/create-store.d.ts.map +1 -1
  62. package/dist/store/create-store.js +27 -33
  63. package/dist/store/create-store.js.map +1 -1
  64. package/dist/store/devtools.d.ts +3 -3
  65. package/dist/store/devtools.d.ts.map +1 -1
  66. package/dist/store/devtools.js +56 -34
  67. package/dist/store/devtools.js.map +1 -1
  68. package/dist/store/store-types.d.ts +18 -18
  69. package/dist/store/store-types.d.ts.map +1 -1
  70. package/dist/store/store-types.js.map +1 -1
  71. package/dist/store/store.d.ts +57 -38
  72. package/dist/store/store.d.ts.map +1 -1
  73. package/dist/store/store.js +225 -188
  74. package/dist/store/store.js.map +1 -1
  75. package/dist/store/store.test.d.ts +2 -0
  76. package/dist/store/store.test.d.ts.map +1 -0
  77. package/dist/store/store.test.js +27 -0
  78. package/dist/store/store.test.js.map +1 -0
  79. package/dist/utils/dev.d.ts.map +1 -1
  80. package/dist/utils/dev.js +3 -2
  81. package/dist/utils/dev.js.map +1 -1
  82. package/dist/utils/expo.d.ts +2 -0
  83. package/dist/utils/expo.d.ts.map +1 -0
  84. package/dist/utils/expo.js +8 -0
  85. package/dist/utils/expo.js.map +1 -0
  86. package/dist/utils/function-string.d.ts +7 -0
  87. package/dist/utils/function-string.d.ts.map +1 -0
  88. package/dist/utils/function-string.js +9 -0
  89. package/dist/utils/function-string.js.map +1 -0
  90. package/dist/utils/stack-info.d.ts.map +1 -1
  91. package/dist/utils/stack-info.js +6 -1
  92. package/dist/utils/stack-info.js.map +1 -1
  93. package/dist/utils/stack-info.test.js +54 -1
  94. package/dist/utils/stack-info.test.js.map +1 -1
  95. package/dist/utils/tests/fixture.d.ts +2 -6
  96. package/dist/utils/tests/fixture.d.ts.map +1 -1
  97. package/dist/utils/tests/fixture.js +7 -13
  98. package/dist/utils/tests/fixture.js.map +1 -1
  99. package/dist/utils/tests/mod.d.ts +1 -0
  100. package/dist/utils/tests/mod.d.ts.map +1 -1
  101. package/dist/utils/tests/mod.js +1 -0
  102. package/dist/utils/tests/mod.js.map +1 -1
  103. package/dist/utils/tests/otel.d.ts +60 -1
  104. package/dist/utils/tests/otel.d.ts.map +1 -1
  105. package/dist/utils/tests/otel.js +65 -4
  106. package/dist/utils/tests/otel.js.map +1 -1
  107. package/package.json +12 -12
  108. package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +59 -13
  109. package/src/ambient.d.ts +1 -1
  110. package/src/effect/LiveStore.ts +32 -33
  111. package/src/index.ts +14 -7
  112. package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +220 -69
  113. package/src/live-queries/base-class.ts +160 -40
  114. package/src/live-queries/computed.ts +45 -19
  115. package/src/live-queries/{db.test.ts → db-query.test.ts} +23 -12
  116. package/src/live-queries/{db.ts → db-query.ts} +124 -61
  117. package/src/live-queries/graphql.ts +47 -21
  118. package/src/live-queries/make-ref.ts +47 -0
  119. package/src/reactive.ts +52 -27
  120. package/src/row-query-utils.ts +29 -18
  121. package/src/store/create-store.ts +106 -113
  122. package/src/store/devtools.ts +65 -39
  123. package/src/store/store-types.ts +20 -18
  124. package/src/store/store.ts +361 -290
  125. package/src/utils/dev.ts +4 -2
  126. package/src/utils/function-string.ts +12 -0
  127. package/src/utils/stack-info.test.ts +58 -1
  128. package/src/utils/stack-info.ts +6 -1
  129. package/src/utils/tests/fixture.ts +6 -16
  130. package/src/utils/tests/mod.ts +1 -0
  131. package/src/utils/tests/otel.ts +71 -5
  132. package/dist/live-queries/sql.d.ts.map +0 -1
  133. package/dist/live-queries/sql.js +0 -175
  134. package/dist/live-queries/sql.js.map +0 -1
  135. package/dist/live-queries/sql.test.d.ts +0 -2
  136. package/dist/live-queries/sql.test.d.ts.map +0 -1
  137. package/dist/live-queries/sql.test.js +0 -285
  138. package/dist/live-queries/sql.test.js.map +0 -1
  139. package/dist/reactiveQueries/base-class.d.ts +0 -64
  140. package/dist/reactiveQueries/base-class.d.ts.map +0 -1
  141. package/dist/reactiveQueries/base-class.js +0 -31
  142. package/dist/reactiveQueries/base-class.js.map +0 -1
  143. package/dist/reactiveQueries/computed.d.ts +0 -26
  144. package/dist/reactiveQueries/computed.d.ts.map +0 -1
  145. package/dist/reactiveQueries/computed.js +0 -38
  146. package/dist/reactiveQueries/computed.js.map +0 -1
  147. package/dist/reactiveQueries/graphql.d.ts +0 -49
  148. package/dist/reactiveQueries/graphql.d.ts.map +0 -1
  149. package/dist/reactiveQueries/graphql.js +0 -122
  150. package/dist/reactiveQueries/graphql.js.map +0 -1
  151. package/dist/reactiveQueries/sql.d.ts +0 -62
  152. package/dist/reactiveQueries/sql.d.ts.map +0 -1
  153. package/dist/reactiveQueries/sql.js +0 -175
  154. package/dist/reactiveQueries/sql.js.map +0 -1
  155. package/dist/reactiveQueries/sql.test.d.ts +0 -2
  156. package/dist/reactiveQueries/sql.test.d.ts.map +0 -1
  157. package/dist/reactiveQueries/sql.test.js +0 -285
  158. package/dist/reactiveQueries/sql.test.js.map +0 -1
  159. package/dist/row-query.d.ts +0 -16
  160. package/dist/row-query.d.ts.map +0 -1
  161. package/dist/row-query.js +0 -30
  162. package/dist/row-query.js.map +0 -1
  163. 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 { defCounterRef, 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,37 @@ 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
+ id: ++defCounterRef.current,
41
+ make: withRCMap(hash, (ctx, _otelContext) => {
42
+ return new LiveStoreGraphQLQuery({
43
+ document,
44
+ genVariableValues,
45
+ label,
46
+ map,
47
+ reactivityGraph: ctx.reactivityGraph.deref()!,
48
+ })
49
+ }),
50
+ label,
51
+ hash,
52
+ queryInfo: { _tag: 'None' },
53
+ }
54
+ }
36
55
 
37
56
  export class LiveStoreGraphQLQuery<
38
57
  TResult extends Record<string, any>,
@@ -46,13 +65,13 @@ export class LiveStoreGraphQLQuery<
46
65
  document: DocumentNode<TResult, TVariableValues>
47
66
 
48
67
  /** A reactive thunk representing the query results */
49
- results$: Thunk<TResultMapped, QueryContext, RefreshReason>
68
+ results$: Thunk<TResultMapped, ReactivityGraphContext, RefreshReason>
50
69
 
51
- variableValues$: Thunk<TVariableValues, QueryContext, RefreshReason> | undefined
70
+ variableValues$: Thunk<TVariableValues, ReactivityGraphContext, RefreshReason> | undefined
52
71
 
53
72
  label: string
54
73
 
55
- protected reactivityGraph: ReactivityGraph
74
+ reactivityGraph: ReactivityGraph
56
75
 
57
76
  queryInfo: QueryInfo.None = { _tag: 'None' }
58
77
 
@@ -68,7 +87,7 @@ export class LiveStoreGraphQLQuery<
68
87
  document: DocumentNode<TResult, TVariableValues>
69
88
  genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues)
70
89
  label?: string
71
- reactivityGraph?: ReactivityGraph
90
+ reactivityGraph: ReactivityGraph
72
91
  map?: MapResult<TResultMapped, TResult>
73
92
  }) {
74
93
  super()
@@ -78,7 +97,7 @@ export class LiveStoreGraphQLQuery<
78
97
  this.label = labelWithDefault
79
98
  this.document = document
80
99
 
81
- this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
100
+ this.reactivityGraph = reactivityGraph
82
101
 
83
102
  this.mapResult =
84
103
  map === undefined
@@ -102,8 +121,10 @@ export class LiveStoreGraphQLQuery<
102
121
 
103
122
  if (typeof genVariableValues === 'function') {
104
123
  variableValues$OrvariableValues = this.reactivityGraph.makeThunk(
105
- (get, _setDebugInfo, { rootOtelContext }, otelContext) => {
106
- return genVariableValues(makeGetAtomResult(get, otelContext ?? rootOtelContext))
124
+ (get, _setDebugInfo, ctx, otelContext) => {
125
+ return genVariableValues(
126
+ makeGetAtomResult(get, ctx, otelContext ?? ctx.rootOtelContext, this.dependencyQueriesRef),
127
+ )
107
128
  },
108
129
  { label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphql.variables' } },
109
130
  )
@@ -114,9 +135,10 @@ export class LiveStoreGraphQLQuery<
114
135
 
115
136
  const resultsLabel = `${labelWithDefault}:results`
116
137
  this.results$ = this.reactivityGraph.makeThunk<TResultMapped>(
117
- (get, setDebugInfo, { store, otelTracer, rootOtelContext }, otelContext) => {
138
+ (get, setDebugInfo, ctx, otelContext, debugRefreshReason) => {
139
+ const { store, otelTracer, rootOtelContext } = ctx
118
140
  const variableValues = isThunk(variableValues$OrvariableValues)
119
- ? (get(variableValues$OrvariableValues) as TVariableValues)
141
+ ? (get(variableValues$OrvariableValues, otelContext, debugRefreshReason) as TVariableValues)
120
142
  : (variableValues$OrvariableValues as TVariableValues)
121
143
  const { result, queriedTables, durationMs } = this.queryOnce({
122
144
  document,
@@ -124,7 +146,7 @@ export class LiveStoreGraphQLQuery<
124
146
  otelContext: otelContext ?? rootOtelContext,
125
147
  otelTracer,
126
148
  store: store as Store<TContext>,
127
- get: makeGetAtomResult(get, otelContext ?? rootOtelContext),
149
+ get: makeGetAtomResult(get, ctx, otelContext ?? rootOtelContext, this.dependencyQueriesRef),
128
150
  })
129
151
 
130
152
  // Add dependencies on any tables that were used
@@ -215,5 +237,9 @@ export class LiveStoreGraphQLQuery<
215
237
  }
216
238
 
217
239
  this.reactivityGraph.destroyNode(this.results$)
240
+
241
+ for (const query of this.dependencyQueriesRef) {
242
+ query.deref()
243
+ }
218
244
  }
219
245
  }
@@ -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
+ }
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
  >
@@ -187,6 +191,13 @@ const uniqueRefreshInfoId = () => `refresh-info-${++refreshInfoIdCounter}`
187
191
  let globalGraphIdCounter = 0
188
192
  const uniqueGraphId = () => `graph-${++globalGraphIdCounter}`
189
193
 
194
+ /** Used for testing */
195
+ export const __resetIds = () => {
196
+ nodeIdCounter = 0
197
+ refreshInfoIdCounter = 0
198
+ globalGraphIdCounter = 0
199
+ }
200
+
190
201
  export class ReactiveGraph<
191
202
  TDebugRefreshReason extends DebugRefreshReason,
192
203
  TDebugThunkInfo extends DebugThunkInfo,
@@ -195,7 +206,7 @@ export class ReactiveGraph<
195
206
  id = uniqueGraphId()
196
207
 
197
208
  readonly atoms: Set<Atom<any, TContext, TDebugRefreshReason>> = new Set()
198
- readonly effects: Set<Effect> = new Set()
209
+ readonly effects: Set<Effect<TDebugRefreshReason>> = new Set()
199
210
 
200
211
  context: TContext | undefined
201
212
 
@@ -205,7 +216,7 @@ export class ReactiveGraph<
205
216
  | { refreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[]; startMs: DOMHighResTimeStamp }
206
217
  | undefined
207
218
 
208
- private deferredEffects: Map<Effect, Set<TDebugRefreshReason>> = new Map()
219
+ private deferredEffects: Map<Effect<TDebugRefreshReason>, Set<TDebugRefreshReason>> = new Map()
209
220
 
210
221
  private refreshCallbacks: Set<() => void> = new Set()
211
222
 
@@ -239,6 +250,7 @@ export class ReactiveGraph<
239
250
  setDebugInfo: (debugInfo: TDebugThunkInfo) => void,
240
251
  ctx: TContext,
241
252
  otelContext: otel.Context | undefined,
253
+ debugRefreshReason: TDebugRefreshReason | undefined,
242
254
  ) => T,
243
255
  options?:
244
256
  | {
@@ -266,7 +278,7 @@ export class ReactiveGraph<
266
278
 
267
279
  const getAtom = (atom: Atom<T, TContext, TDebugRefreshReason>, otelContext: otel.Context) => {
268
280
  this.addEdge(thunk, atom)
269
- return compute(atom, otelContext)
281
+ return compute(atom, otelContext, debugRefreshReason)
270
282
  }
271
283
 
272
284
  let debugInfo: TDebugThunkInfo | undefined = undefined
@@ -279,6 +291,7 @@ export class ReactiveGraph<
279
291
  setDebugInfo,
280
292
  this.context ?? throwContextNotSetError(this),
281
293
  otelContext,
294
+ debugRefreshReason,
282
295
  )
283
296
 
284
297
  const resultChanged = thunk.equal(thunk.previousResult as T, result) === false
@@ -365,14 +378,18 @@ export class ReactiveGraph<
365
378
  }
366
379
 
367
380
  makeEffect(
368
- doEffect: (get: GetAtom, otelContext?: otel.Context) => void,
381
+ doEffect: (
382
+ get: GetAtom,
383
+ otelContext: otel.Context | undefined,
384
+ debugRefreshReason: DebugRefreshReason | undefined,
385
+ ) => void,
369
386
  options?: { label?: string } | undefined,
370
- ): Effect {
371
- const effect: Effect = {
387
+ ): Effect<TDebugRefreshReason> {
388
+ const effect: Effect<TDebugRefreshReason> = {
372
389
  _tag: 'effect',
373
390
  id: uniqueNodeId(),
374
391
  isDestroyed: false,
375
- doEffect: (otelContext) => {
392
+ doEffect: (otelContext, debugRefreshReason) => {
376
393
  effect.invocations++
377
394
 
378
395
  // NOTE we're not tracking any debug refresh info for effects as they're tracked by the thunks they depend on
@@ -380,12 +397,16 @@ export class ReactiveGraph<
380
397
  // Reset previous subcomputations as we're about to re-add them as part of the `doEffect` call below
381
398
  effect.sub = new Set()
382
399
 
383
- const getAtom = (atom: Atom<any, TContext, TDebugRefreshReason>, otelContext: otel.Context) => {
400
+ const getAtom = (
401
+ atom: Atom<any, TContext, TDebugRefreshReason>,
402
+ otelContext: otel.Context,
403
+ debugRefreshReason: DebugRefreshReason | undefined,
404
+ ) => {
384
405
  this.addEdge(effect, atom)
385
- return compute(atom, otelContext)
406
+ return compute(atom, otelContext, debugRefreshReason)
386
407
  }
387
408
 
388
- doEffect(getAtom as GetAtom, otelContext)
409
+ doEffect(getAtom as GetAtom, otelContext, debugRefreshReason)
389
410
  },
390
411
  sub: new Set(),
391
412
  label: options?.label,
@@ -421,7 +442,7 @@ export class ReactiveGraph<
421
442
  }
422
443
  | undefined,
423
444
  ) {
424
- const effectsToRefresh = new Set<Effect>()
445
+ const effectsToRefresh = new Set<Effect<TDebugRefreshReason>>()
425
446
  for (const [ref, val] of refs) {
426
447
  ref.previousResult = val
427
448
  ref.refreshes++
@@ -448,7 +469,7 @@ export class ReactiveGraph<
448
469
  }
449
470
 
450
471
  private runEffects = (
451
- effectsToRefresh: Set<Effect>,
472
+ effectsToRefresh: Set<Effect<TDebugRefreshReason>>,
452
473
  options: {
453
474
  debugRefreshReason: TDebugRefreshReason
454
475
  otelContext?: otel.Context
@@ -459,7 +480,7 @@ export class ReactiveGraph<
459
480
  this.currentDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
460
481
 
461
482
  for (const effect of effectsToRefresh) {
462
- effect.doEffect(options?.otelContext)
483
+ effect.doEffect(options?.otelContext, options.debugRefreshReason)
463
484
  }
464
485
 
465
486
  const refreshedAtoms = this.currentDebugRefresh.refreshedAtoms
@@ -504,7 +525,7 @@ export class ReactiveGraph<
504
525
  }
505
526
 
506
527
  addEdge(
507
- superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect,
528
+ superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>,
508
529
  subComp: Atom<any, TContext, TDebugRefreshReason>,
509
530
  ) {
510
531
  superComp.sub.add(subComp)
@@ -516,11 +537,11 @@ export class ReactiveGraph<
516
537
  }
517
538
 
518
539
  removeEdge(
519
- superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect,
540
+ superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>,
520
541
  subComp: Atom<any, TContext, TDebugRefreshReason>,
521
542
  ) {
522
543
  superComp.sub.delete(subComp)
523
- const effectsToRefresh = new Set<Effect>()
544
+ const effectsToRefresh = new Set<Effect<TDebugRefreshReason>>()
524
545
  markSuperCompDirtyRec(subComp, effectsToRefresh)
525
546
 
526
547
  for (const effect of effectsToRefresh) {
@@ -563,7 +584,11 @@ export class ReactiveGraph<
563
584
  }
564
585
  }
565
586
 
566
- const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T => {
587
+ const compute = <T>(
588
+ atom: Atom<T, unknown, any>,
589
+ otelContext: otel.Context,
590
+ debugRefreshReason: DebugRefreshReason | undefined,
591
+ ): T => {
567
592
  // const __getResult = atom._tag === 'thunk' ? atom.__getResult.toString() : ''
568
593
  if (atom.isDestroyed) {
569
594
  shouldNeverHappen(`LiveStore Error: Attempted to compute destroyed ${atom._tag} (${atom.id}): ${atom.label ?? ''}`)
@@ -571,7 +596,7 @@ const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T =
571
596
 
572
597
  if (atom.isDirty) {
573
598
  // console.log('atom is dirty', atom.id, atom.label ?? '', atom._tag, __getResult)
574
- const result = atom.computeResult(otelContext)
599
+ const result = atom.computeResult(otelContext, debugRefreshReason)
575
600
  atom.isDirty = false
576
601
  atom.previousResult = result
577
602
  return result
@@ -581,7 +606,7 @@ const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T =
581
606
  }
582
607
  }
583
608
 
584
- const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh: Set<Effect>) => {
609
+ const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh: Set<Effect<any>>) => {
585
610
  for (const superComp of atom.super) {
586
611
  if (superComp._tag === 'thunk') {
587
612
  superComp.isDirty = true
@@ -644,7 +669,7 @@ const serializeAtom = (atom: Atom<any, unknown, any>, includeResult: boolean): S
644
669
  }
645
670
 
646
671
  // NOTE This function is performance-optimized (i.e. not using `pick` and `Array.from`)
647
- const serializeEffect = (effect: Effect): SerializedEffect => {
672
+ const serializeEffect = (effect: Effect<any>): SerializedEffect => {
648
673
  const sub: string[] = []
649
674
  for (const a of effect.sub) {
650
675
  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
10
  export const rowQueryLabel = (table: DbSchema.TableDefBase, id: string | SessionIdSymbol | 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
 
@@ -41,15 +46,17 @@ export const makeExecBeforeFirstRun =
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
53
  const idStr = 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
+ [idStr] 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.mutateWithoutRefresh(table.insert({ id, ...insertValues }), { otelContext, coordinatorMode: 'default' })
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}:${idStr}` },
73
+ table.insert({ id, ...insertValues }),
74
+ )
64
75
  }
65
76
  }