@livestore/livestore 0.3.0-dev.10 → 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 (114) 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 +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 +64 -21
  19. package/dist/live-queries/base-class.d.ts.map +1 -1
  20. package/dist/live-queries/base-class.js +56 -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 +35 -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 +244 -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 +123 -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 +35 -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 +15 -13
  49. package/dist/reactive.d.ts.map +1 -1
  50. package/dist/reactive.js +15 -9
  51. package/dist/reactive.js.map +1 -1
  52. package/dist/row-query-utils.d.ts +4 -4
  53. package/dist/row-query-utils.d.ts.map +1 -1
  54. package/dist/row-query-utils.js +14 -10
  55. package/dist/row-query-utils.js.map +1 -1
  56. package/dist/store/create-store.d.ts +3 -4
  57. package/dist/store/create-store.d.ts.map +1 -1
  58. package/dist/store/create-store.js +7 -7
  59. package/dist/store/create-store.js.map +1 -1
  60. package/dist/store/devtools.d.ts +2 -2
  61. package/dist/store/devtools.d.ts.map +1 -1
  62. package/dist/store/devtools.js +15 -15
  63. package/dist/store/devtools.js.map +1 -1
  64. package/dist/store/store-types.d.ts +9 -4
  65. package/dist/store/store-types.d.ts.map +1 -1
  66. package/dist/store/store-types.js.map +1 -1
  67. package/dist/store/store.d.ts +34 -16
  68. package/dist/store/store.d.ts.map +1 -1
  69. package/dist/store/store.js +125 -75
  70. package/dist/store/store.js.map +1 -1
  71. package/dist/utils/expo.d.ts +2 -0
  72. package/dist/utils/expo.d.ts.map +1 -0
  73. package/dist/utils/expo.js +8 -0
  74. package/dist/utils/expo.js.map +1 -0
  75. package/dist/utils/function-string.d.ts +7 -0
  76. package/dist/utils/function-string.d.ts.map +1 -0
  77. package/dist/utils/function-string.js +9 -0
  78. package/dist/utils/function-string.js.map +1 -0
  79. package/dist/utils/stack-info.d.ts.map +1 -1
  80. package/dist/utils/stack-info.js +6 -1
  81. package/dist/utils/stack-info.js.map +1 -1
  82. package/dist/utils/stack-info.test.js +54 -1
  83. package/dist/utils/stack-info.test.js.map +1 -1
  84. package/dist/utils/tests/fixture.d.ts +2 -6
  85. package/dist/utils/tests/fixture.d.ts.map +1 -1
  86. package/dist/utils/tests/fixture.js +3 -5
  87. package/dist/utils/tests/fixture.js.map +1 -1
  88. package/dist/utils/tests/mod.d.ts +1 -0
  89. package/dist/utils/tests/mod.d.ts.map +1 -1
  90. package/dist/utils/tests/mod.js +1 -0
  91. package/dist/utils/tests/mod.js.map +1 -1
  92. package/package.json +5 -5
  93. package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +41 -11
  94. package/src/effect/LiveStore.ts +22 -14
  95. package/src/index.ts +14 -7
  96. package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +196 -42
  97. package/src/live-queries/base-class.ts +160 -40
  98. package/src/live-queries/computed.ts +45 -19
  99. package/src/live-queries/{db.test.ts → db-query.test.ts} +21 -11
  100. package/src/live-queries/{db.ts → db-query.ts} +97 -39
  101. package/src/live-queries/graphql.ts +47 -21
  102. package/src/live-queries/make-ref.ts +47 -0
  103. package/src/reactive.ts +52 -27
  104. package/src/row-query-utils.ts +29 -18
  105. package/src/store/create-store.ts +20 -23
  106. package/src/store/devtools.ts +17 -17
  107. package/src/store/store-types.ts +6 -4
  108. package/src/store/store.ts +227 -120
  109. package/src/utils/function-string.ts +12 -0
  110. package/src/utils/stack-info.test.ts +58 -1
  111. package/src/utils/stack-info.ts +6 -1
  112. package/src/utils/tests/fixture.ts +2 -7
  113. package/src/utils/tests/mod.ts +1 -0
  114. package/src/global-state.ts +0 -20
@@ -21,9 +21,6 @@ exports[`otel > otel 3`] = `
21
21
  ",
22
22
  },
23
23
  },
24
- {
25
- "_name": "LiveStore:createStore",
26
- },
27
24
  {
28
25
  "_name": "LiveStore:sync",
29
26
  },
@@ -33,22 +30,17 @@ exports[`otel > otel 3`] = `
33
30
  {
34
31
  "_name": "LiveStore:mutate",
35
32
  "attributes": {
36
- "livestore.mutateLabel": "mutate",
33
+ "livestore.mutationEventTags": [
34
+ "livestore.RawSql",
35
+ ],
36
+ "livestore.mutationEventsCount": 1,
37
37
  },
38
38
  "children": [
39
39
  {
40
- "_name": "LiveStore:mutate:applyMutations",
40
+ "_name": "livestore.in-memory-db:execute",
41
41
  "attributes": {
42
- "livestore.mutateLabel": "mutate",
42
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
43
43
  },
44
- "children": [
45
- {
46
- "_name": "livestore.in-memory-db:execute",
47
- "attributes": {
48
- "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
49
- },
50
- },
51
- ],
52
44
  },
53
45
  ],
54
46
  },
@@ -97,7 +89,179 @@ exports[`otel > otel 3`] = `
97
89
  }
98
90
  `;
99
91
 
92
+ exports[`otel > with thunks 1`] = `
93
+ {
94
+ "atoms": [
95
+ {
96
+ "_tag": "ref",
97
+ "id": "node-1",
98
+ "isDestroyed": false,
99
+ "isDirty": false,
100
+ "label": "tableRef:todos",
101
+ "meta": {
102
+ "liveStoreRefType": "table",
103
+ },
104
+ "previousResult": {
105
+ "_tag": "Some",
106
+ "value": "null",
107
+ },
108
+ "refreshes": 0,
109
+ "sub": [],
110
+ "super": [],
111
+ },
112
+ {
113
+ "_tag": "ref",
114
+ "id": "node-2",
115
+ "isDestroyed": false,
116
+ "isDirty": false,
117
+ "label": "tableRef:app",
118
+ "meta": {
119
+ "liveStoreRefType": "table",
120
+ },
121
+ "previousResult": {
122
+ "_tag": "Some",
123
+ "value": "null",
124
+ },
125
+ "refreshes": 0,
126
+ "sub": [],
127
+ "super": [],
128
+ },
129
+ ],
130
+ "deferredEffects": [],
131
+ "effects": [],
132
+ }
133
+ `;
134
+
100
135
  exports[`otel > with thunks 3`] = `
136
+ {
137
+ "atoms": [
138
+ {
139
+ "_tag": "ref",
140
+ "id": "node-1",
141
+ "isDestroyed": false,
142
+ "isDirty": false,
143
+ "label": "tableRef:todos",
144
+ "meta": {
145
+ "liveStoreRefType": "table",
146
+ },
147
+ "previousResult": {
148
+ "_tag": "Some",
149
+ "value": "null",
150
+ },
151
+ "refreshes": 0,
152
+ "sub": [],
153
+ "super": [],
154
+ },
155
+ {
156
+ "_tag": "ref",
157
+ "id": "node-2",
158
+ "isDestroyed": false,
159
+ "isDirty": false,
160
+ "label": "tableRef:app",
161
+ "meta": {
162
+ "liveStoreRefType": "table",
163
+ },
164
+ "previousResult": {
165
+ "_tag": "Some",
166
+ "value": "null",
167
+ },
168
+ "refreshes": 0,
169
+ "sub": [],
170
+ "super": [],
171
+ },
172
+ ],
173
+ "deferredEffects": [],
174
+ "effects": [],
175
+ }
176
+ `;
177
+
178
+ exports[`otel > with thunks 4`] = `
179
+ {
180
+ "atoms": [
181
+ {
182
+ "_tag": "ref",
183
+ "id": "node-1",
184
+ "isDestroyed": false,
185
+ "isDirty": false,
186
+ "label": "tableRef:todos",
187
+ "meta": {
188
+ "liveStoreRefType": "table",
189
+ },
190
+ "previousResult": {
191
+ "_tag": "Some",
192
+ "value": "null",
193
+ },
194
+ "refreshes": 1,
195
+ "sub": [],
196
+ "super": [],
197
+ },
198
+ {
199
+ "_tag": "ref",
200
+ "id": "node-2",
201
+ "isDestroyed": false,
202
+ "isDirty": false,
203
+ "label": "tableRef:app",
204
+ "meta": {
205
+ "liveStoreRefType": "table",
206
+ },
207
+ "previousResult": {
208
+ "_tag": "Some",
209
+ "value": "null",
210
+ },
211
+ "refreshes": 0,
212
+ "sub": [],
213
+ "super": [],
214
+ },
215
+ ],
216
+ "deferredEffects": [],
217
+ "effects": [],
218
+ }
219
+ `;
220
+
221
+ exports[`otel > with thunks 6`] = `
222
+ {
223
+ "atoms": [
224
+ {
225
+ "_tag": "ref",
226
+ "id": "node-1",
227
+ "isDestroyed": false,
228
+ "isDirty": false,
229
+ "label": "tableRef:todos",
230
+ "meta": {
231
+ "liveStoreRefType": "table",
232
+ },
233
+ "previousResult": {
234
+ "_tag": "Some",
235
+ "value": "null",
236
+ },
237
+ "refreshes": 1,
238
+ "sub": [],
239
+ "super": [],
240
+ },
241
+ {
242
+ "_tag": "ref",
243
+ "id": "node-2",
244
+ "isDestroyed": false,
245
+ "isDirty": false,
246
+ "label": "tableRef:app",
247
+ "meta": {
248
+ "liveStoreRefType": "table",
249
+ },
250
+ "previousResult": {
251
+ "_tag": "Some",
252
+ "value": "null",
253
+ },
254
+ "refreshes": 0,
255
+ "sub": [],
256
+ "super": [],
257
+ },
258
+ ],
259
+ "deferredEffects": [],
260
+ "effects": [],
261
+ }
262
+ `;
263
+
264
+ exports[`otel > with thunks 7`] = `
101
265
  {
102
266
  "_name": "createStore",
103
267
  "attributes": {
@@ -118,9 +282,6 @@ exports[`otel > with thunks 3`] = `
118
282
  ",
119
283
  },
120
284
  },
121
- {
122
- "_name": "LiveStore:createStore",
123
- },
124
285
  {
125
286
  "_name": "LiveStore:sync",
126
287
  },
@@ -130,22 +291,17 @@ exports[`otel > with thunks 3`] = `
130
291
  {
131
292
  "_name": "LiveStore:mutate",
132
293
  "attributes": {
133
- "livestore.mutateLabel": "mutate",
294
+ "livestore.mutationEventTags": [
295
+ "livestore.RawSql",
296
+ ],
297
+ "livestore.mutationEventsCount": 1,
134
298
  },
135
299
  "children": [
136
300
  {
137
- "_name": "LiveStore:mutate:applyMutations",
301
+ "_name": "livestore.in-memory-db:execute",
138
302
  "attributes": {
139
- "livestore.mutateLabel": "mutate",
303
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
140
304
  },
141
- "children": [
142
- {
143
- "_name": "livestore.in-memory-db:execute",
144
- "attributes": {
145
- "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
146
- },
147
- },
148
- ],
149
305
  },
150
306
  ],
151
307
  },
@@ -181,6 +337,9 @@ exports[`otel > with thunks 3`] = `
181
337
  "sql.rowsCount": 1,
182
338
  },
183
339
  "children": [
340
+ {
341
+ "_name": "js:where-filter",
342
+ },
184
343
  {
185
344
  "_name": "sql-in-memory-select",
186
345
  "attributes": {
@@ -218,9 +377,6 @@ exports[`otel > with thunks with query builder and without labels 3`] = `
218
377
  ",
219
378
  },
220
379
  },
221
- {
222
- "_name": "LiveStore:createStore",
223
- },
224
380
  {
225
381
  "_name": "LiveStore:sync",
226
382
  },
@@ -230,22 +386,17 @@ exports[`otel > with thunks with query builder and without labels 3`] = `
230
386
  {
231
387
  "_name": "LiveStore:mutate",
232
388
  "attributes": {
233
- "livestore.mutateLabel": "mutate",
389
+ "livestore.mutationEventTags": [
390
+ "livestore.RawSql",
391
+ ],
392
+ "livestore.mutationEventsCount": 1,
234
393
  },
235
394
  "children": [
236
395
  {
237
- "_name": "LiveStore:mutate:applyMutations",
396
+ "_name": "livestore.in-memory-db:execute",
238
397
  "attributes": {
239
- "livestore.mutateLabel": "mutate",
398
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
240
399
  },
241
- "children": [
242
- {
243
- "_name": "livestore.in-memory-db:execute",
244
- "attributes": {
245
- "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
246
- },
247
- },
248
- ],
249
400
  },
250
401
  ],
251
402
  },
@@ -281,6 +432,9 @@ exports[`otel > with thunks with query builder and without labels 3`] = `
281
432
  "sql.rowsCount": 1,
282
433
  },
283
434
  "children": [
435
+ {
436
+ "_name": "js:() => ({ completed: false })",
437
+ },
284
438
  {
285
439
  "_name": "sql-in-memory-select",
286
440
  "attributes": {
@@ -1,59 +1,119 @@
1
1
  import type { QueryInfo } from '@livestore/common'
2
+ import { isNotNil } from '@livestore/utils'
3
+ import { GlobalValue } from '@livestore/utils/effect'
2
4
  import type * as otel from '@opentelemetry/api'
3
5
 
4
- import { type Atom, type GetAtom, ReactiveGraph, throwContextNotSetError, type Thunk } from '../reactive.js'
6
+ import * as RG from '../reactive.js'
5
7
  import type { Store } from '../store/store.js'
6
8
  import type { QueryDebugInfo, RefreshReason } from '../store/store-types.js'
7
9
  import type { StackInfo } from '../utils/stack-info.js'
8
10
 
9
- export type ReactivityGraph = ReactiveGraph<RefreshReason, QueryDebugInfo, QueryContext>
11
+ export type ReactivityGraph = RG.ReactiveGraph<RefreshReason, QueryDebugInfo, ReactivityGraphContext>
10
12
 
11
13
  export const makeReactivityGraph = (): ReactivityGraph =>
12
- new ReactiveGraph<RefreshReason, QueryDebugInfo, QueryContext>()
14
+ new RG.ReactiveGraph<RefreshReason, QueryDebugInfo, ReactivityGraphContext>()
13
15
 
14
- export type QueryContext = {
16
+ export const defCounterRef = GlobalValue.globalValue('livestore-def-counter', () => ({ current: 0 }))
17
+
18
+ type LiveQueryDefHash = string
19
+
20
+ export type LiveQueryRCMap = Map<LiveQueryDefHash, RcRef<LiveQueryAny | ILiveQueryRef<any>>>
21
+
22
+ export type ReactivityGraphContext = {
15
23
  store: Store
24
+ liveQueryRCMap: LiveQueryRCMap
25
+ /** Back-reference to the reactivity graph for convenience */
26
+ reactivityGraph: WeakRef<ReactivityGraph>
16
27
  otelTracer: otel.Tracer
17
28
  rootOtelContext: otel.Context
18
29
  effectsWrapper: (run: () => void) => void
19
30
  }
20
31
 
21
- export type UnsubscribeQuery = () => void
22
-
23
- export type GetResult<TQuery extends LiveQueryAny> =
24
- TQuery extends LiveQuery<infer TResult, infer _1> ? TResult : unknown
32
+ export type GetResult<TQuery extends LiveQueryDefAny | LiveQueryAny> =
33
+ TQuery extends LiveQuery<infer TResult, infer _1>
34
+ ? TResult
35
+ : TQuery extends LiveQueryDef<infer TResult, infer _1>
36
+ ? TResult
37
+ : unknown
25
38
 
26
39
  let queryIdCounter = 0
27
40
 
28
41
  export type LiveQueryAny = LiveQuery<any, QueryInfo>
42
+ export type LiveQueryDefAny = LiveQueryDef<any, any>
43
+
44
+ export interface ILiveQueryRefDef<T> {
45
+ _tag: 'live-ref-def'
46
+ defaultValue: T
47
+ make: (ctx: ReactivityGraphContext) => RcRef<ILiveQueryRef<T>>
48
+ }
49
+
50
+ export interface ILiveQueryRef<T> {
51
+ _tag: 'live-ref'
52
+ reactivityGraph: ReactivityGraph
53
+ ref: RG.Ref<T, ReactivityGraphContext, RefreshReason>
54
+ set: (value: T) => void
55
+ get: () => T
56
+ destroy: () => void
57
+ }
29
58
 
30
59
  export const TypeId = Symbol.for('LiveQuery')
31
60
  export type TypeId = typeof TypeId
32
61
 
62
+ export interface RcRef<T> {
63
+ rc: number
64
+ value: T
65
+ deref: () => void
66
+ }
67
+
68
+ export type DepKey = string | number | ReadonlyArray<string | number | undefined | null>
69
+
70
+ export const depsToString = (deps: DepKey): string => {
71
+ if (typeof deps === 'string' || typeof deps === 'number') {
72
+ return deps.toString()
73
+ }
74
+ return deps.filter(isNotNil).join(',')
75
+ }
76
+
77
+ export interface LiveQueryDef<TResult, TQueryInfo extends QueryInfo = QueryInfo.None> {
78
+ _tag: 'def'
79
+ // TODO do we need both id and hash?
80
+ /** A unique identifier for the query definition */
81
+ id: number
82
+ /** Creates a new LiveQuery instance bound to a specific store/reactivityGraph */
83
+ make: (ctx: ReactivityGraphContext, otelContext?: otel.Context) => RcRef<LiveQuery<TResult, TQueryInfo>>
84
+ label: string
85
+ hash: string
86
+ queryInfo: TQueryInfo
87
+ }
88
+
89
+ /**
90
+ * A LiveQuery is stateful
91
+ */
33
92
  export interface LiveQuery<TResult, TQueryInfo extends QueryInfo = QueryInfo.None> {
34
93
  id: number
35
94
  _tag: 'computed' | 'db' | 'graphql'
36
95
  [TypeId]: TypeId
37
96
 
97
+ // reactivityGraph: ReactivityGraph
98
+
38
99
  /** This should only be used on a type-level and doesn't hold any value during runtime */
39
100
  '__result!': TResult
40
101
 
41
102
  /** A reactive thunk representing the query results */
42
- results$: Thunk<TResult, QueryContext, RefreshReason>
103
+ results$: RG.Thunk<TResult, ReactivityGraphContext, RefreshReason>
43
104
 
44
105
  label: string
45
106
 
46
- run: (otelContext?: otel.Context, debugRefreshReason?: RefreshReason) => TResult
47
-
48
- runAndDestroy: (otelContext?: otel.Context, debugRefreshReason?: RefreshReason) => TResult
107
+ run: (args: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason }) => TResult
49
108
 
50
- destroy(): void
109
+ destroy: () => void
110
+ isDestroyed: boolean
51
111
 
52
- subscribe(
53
- onNewValue: (value: TResult) => void,
54
- onUnsubsubscribe?: () => void,
55
- options?: { label?: string; otelContext?: otel.Context },
56
- ): () => void
112
+ // subscribe(
113
+ // onNewValue: (value: TResult) => void,
114
+ // onUnsubsubscribe?: () => void,
115
+ // options?: { label?: string; otelContext?: otel.Context },
116
+ // ): () => void
57
117
 
58
118
  activeSubscriptions: Set<StackInfo>
59
119
 
@@ -75,11 +135,11 @@ export abstract class LiveStoreQueryBase<TResult, TQueryInfo extends QueryInfo>
75
135
  /** Human-readable label for the query for debugging */
76
136
  abstract label: string
77
137
 
78
- abstract results$: Thunk<TResult, QueryContext, RefreshReason>
138
+ abstract results$: RG.Thunk<TResult, ReactivityGraphContext, RefreshReason>
79
139
 
80
140
  activeSubscriptions: Set<StackInfo> = new Set()
81
141
 
82
- protected abstract reactivityGraph: ReactivityGraph
142
+ abstract readonly reactivityGraph: ReactivityGraph
83
143
 
84
144
  abstract queryInfo: TQueryInfo
85
145
 
@@ -89,33 +149,93 @@ export abstract class LiveStoreQueryBase<TResult, TQueryInfo extends QueryInfo>
89
149
 
90
150
  executionTimes: number[] = []
91
151
 
152
+ // TODO double check if this is needed
153
+ isDestroyed = false
92
154
  abstract destroy: () => void
93
155
 
94
- run = (otelContext?: otel.Context, debugRefreshReason?: RefreshReason): TResult =>
95
- this.results$.computeResult(otelContext, debugRefreshReason)
96
-
97
- runAndDestroy = (otelContext?: otel.Context, debugRefreshReason?: RefreshReason): TResult => {
98
- const result = this.run(otelContext, debugRefreshReason)
99
- this.destroy()
100
- return result
156
+ run = (args: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason }): TResult => {
157
+ return this.results$.computeResult(args.otelContext, args.debugRefreshReason)
101
158
  }
102
159
 
103
- subscribe = (
104
- onNewValue: (value: TResult) => void,
105
- onUnsubsubscribe?: () => void,
106
- options?: { label?: string; otelContext?: otel.Context } | undefined,
107
- ): (() => void) =>
108
- this.reactivityGraph.context?.store.subscribe(this, onNewValue, onUnsubsubscribe, options) ??
109
- throwContextNotSetError(this.reactivityGraph)
110
- }
160
+ protected dependencyQueriesRef: DependencyQueriesRef = new Set()
111
161
 
112
- export type GetAtomResult = <T>(atom: Atom<T, any, RefreshReason> | LiveQuery<T, any>) => T
162
+ // subscribe = (
163
+ // onNewValue: (value: TResult) => void,
164
+ // onUnsubsubscribe?: () => void,
165
+ // options?: { label?: string; otelContext?: otel.Context } | undefined,
166
+ // ): (() => void) =>
167
+ // this.reactivityGraph.context?.store.subscribe(this, onNewValue, onUnsubsubscribe, options) ??
168
+ // RG.throwContextNotSetError(this.reactivityGraph)
169
+ }
113
170
 
114
- export const makeGetAtomResult = (get: GetAtom, otelContext: otel.Context) => {
115
- const getAtom: GetAtomResult = (atom) => {
116
- if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom, otelContext)
117
- return get(atom.results$, otelContext)
171
+ export type GetAtomResult = <T>(
172
+ atom:
173
+ | RG.Atom<T, any, RefreshReason>
174
+ | LiveQueryDef<T, any>
175
+ | LiveQuery<T, any>
176
+ | ILiveQueryRef<T>
177
+ | ILiveQueryRefDef<T>,
178
+ otelContext?: otel.Context | undefined,
179
+ debugRefreshReason?: RefreshReason | undefined,
180
+ ) => T
181
+
182
+ export type DependencyQueriesRef = Set<RcRef<LiveQueryAny | ILiveQueryRef<any>>>
183
+
184
+ export const makeGetAtomResult = (
185
+ get: RG.GetAtom,
186
+ ctx: ReactivityGraphContext,
187
+ otelContext: otel.Context,
188
+ dependencyQueriesRef: DependencyQueriesRef,
189
+ ) => {
190
+ // NOTE we're using the `otelContext` from `makeGetAtomResult` here, not the `otelContext` from `getAtom`
191
+ const getAtom: GetAtomResult = (atom, _otelContext, debugRefreshReason) => {
192
+ // ReactivityGraph atoms case
193
+ if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom, otelContext, debugRefreshReason)
194
+
195
+ // LiveQueryDef case
196
+ if (atom._tag === 'def' || atom._tag === 'live-ref-def') {
197
+ const query = atom.make(ctx)
198
+ dependencyQueriesRef.add(query)
199
+ // TODO deref the query on destroy
200
+ return getAtom(query.value, _otelContext, debugRefreshReason)
201
+ }
202
+
203
+ // LiveQueryRef case
204
+ if (atom._tag === 'live-ref') return get(atom.ref, otelContext, debugRefreshReason)
205
+
206
+ // LiveQuery case
207
+ return get(atom.results$, otelContext, debugRefreshReason)
118
208
  }
119
209
 
120
210
  return getAtom
121
211
  }
212
+
213
+ export const withRCMap = <T extends LiveQueryAny | ILiveQueryRef<any>>(
214
+ id: string,
215
+ make: (ctx: ReactivityGraphContext, otelContext?: otel.Context) => T,
216
+ ): ((ctx: ReactivityGraphContext, otelContext?: otel.Context) => RcRef<T>) => {
217
+ return (ctx, otelContext) => {
218
+ let item = ctx.liveQueryRCMap.get(id)
219
+ if (item) {
220
+ item.rc++
221
+ return item as RcRef<T>
222
+ }
223
+
224
+ const query$ = make(ctx, otelContext)
225
+
226
+ item = {
227
+ rc: 1,
228
+ value: query$,
229
+ deref: () => {
230
+ item!.rc--
231
+ if (item!.rc === 0) {
232
+ item!.value.destroy()
233
+ }
234
+ ctx.liveQueryRCMap.delete(id)
235
+ },
236
+ }
237
+ ctx.liveQueryRCMap.set(id, item)
238
+
239
+ return item as RcRef<T>
240
+ }
241
+ }
@@ -1,27 +1,48 @@
1
1
  import type { QueryInfo } from '@livestore/common'
2
2
  import * as otel from '@opentelemetry/api'
3
3
 
4
- import { globalReactivityGraph } from '../global-state.js'
5
4
  import type { Thunk } from '../reactive.js'
6
5
  import type { RefreshReason } from '../store/store-types.js'
6
+ import { isValidFunctionString } from '../utils/function-string.js'
7
7
  import { getDurationMsFromSpan } from '../utils/otel.js'
8
- import type { GetAtomResult, LiveQuery, QueryContext, ReactivityGraph } from './base-class.js'
9
- import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
8
+ import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
9
+ import { defCounterRef, depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.js'
10
10
 
11
11
  export const computed = <TResult, TQueryInfo extends QueryInfo = QueryInfo.None>(
12
12
  fn: (get: GetAtomResult) => TResult,
13
13
  options?: {
14
- label: string
15
- reactivityGraph?: ReactivityGraph
14
+ label?: string
16
15
  queryInfo?: TQueryInfo
16
+ deps?: DepKey
17
17
  },
18
- ): LiveQuery<TResult, TQueryInfo> =>
19
- new LiveStoreComputedQuery<TResult, TQueryInfo>({
20
- fn,
18
+ ): LiveQueryDef<TResult, TQueryInfo> => {
19
+ const hash = options?.deps ? depsToString(options.deps) : fn.toString()
20
+ if (isValidFunctionString(hash)._tag === 'invalid') {
21
+ throw new Error(`On Expo/React Native, computed queries must provide a \`deps\` option`)
22
+ }
23
+
24
+ const queryInfo = options?.queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
25
+
26
+ return {
27
+ _tag: 'def',
28
+ id: ++defCounterRef.current,
29
+ make: withRCMap(hash, (ctx, _otelContext) => {
30
+ // TODO onDestroy
31
+ return new LiveStoreComputedQuery<TResult, TQueryInfo>({
32
+ fn,
33
+ label: options?.label ?? fn.toString(),
34
+ queryInfo: options?.queryInfo,
35
+ reactivityGraph: ctx.reactivityGraph.deref()!,
36
+ })
37
+ }),
21
38
  label: options?.label ?? fn.toString(),
22
- reactivityGraph: options?.reactivityGraph,
23
- queryInfo: options?.queryInfo,
24
- })
39
+ // NOTE We're using the `makeQuery` function body string to make sure the key is unique across the app
40
+ // TODO we should figure out whether this could cause some problems and/or if there's a better way to do this
41
+ // NOTE `fn.toString()` doesn't work in Expo as it always produces `[native code]`
42
+ hash,
43
+ queryInfo,
44
+ }
45
+ }
25
46
 
26
47
  export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = QueryInfo.None> extends LiveStoreQueryBase<
27
48
  TResult,
@@ -30,11 +51,11 @@ export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = Quer
30
51
  _tag: 'computed' = 'computed'
31
52
 
32
53
  /** A reactive thunk representing the query results */
33
- results$: Thunk<TResult, QueryContext, RefreshReason>
54
+ results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
34
55
 
35
56
  label: string
36
57
 
37
- protected reactivityGraph: ReactivityGraph
58
+ reactivityGraph: ReactivityGraph
38
59
 
39
60
  queryInfo: TQueryInfo
40
61
 
@@ -46,23 +67,22 @@ export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = Quer
46
67
  }: {
47
68
  label: string
48
69
  fn: (get: GetAtomResult) => TResult
49
- reactivityGraph?: ReactivityGraph
70
+ reactivityGraph: ReactivityGraph
50
71
  queryInfo?: TQueryInfo
51
72
  }) {
52
73
  super()
53
74
 
54
75
  this.label = label
55
-
56
- this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
76
+ this.reactivityGraph = reactivityGraph
57
77
  this.queryInfo = queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
58
78
 
59
79
  const queryLabel = `${label}:results`
60
80
 
61
81
  this.results$ = this.reactivityGraph.makeThunk(
62
- (get, setDebugInfo, { otelTracer, rootOtelContext }, otelContext) =>
63
- otelTracer.startActiveSpan(`js:${label}`, {}, otelContext ?? rootOtelContext, (span) => {
82
+ (get, setDebugInfo, ctx, otelContext) =>
83
+ ctx.otelTracer.startActiveSpan(`js:${label}`, {}, otelContext ?? ctx.rootOtelContext, (span) => {
64
84
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
65
- const res = fn(makeGetAtomResult(get, otelContext))
85
+ const res = fn(makeGetAtomResult(get, ctx, otelContext, this.dependencyQueriesRef))
66
86
 
67
87
  span.end()
68
88
 
@@ -79,6 +99,12 @@ export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = Quer
79
99
  }
80
100
 
81
101
  destroy = () => {
102
+ this.isDestroyed = true
103
+
82
104
  this.reactivityGraph.destroyNode(this.results$)
105
+
106
+ for (const query of this.dependencyQueriesRef) {
107
+ query.deref()
108
+ }
83
109
  }
84
110
  }