@livestore/livestore 0.0.0-snapshot-909cdd1ac2fd591945c2be2b0f53e14d87f3c9d4

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 (131) hide show
  1. package/README.md +1 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/QueryCache.d.ts +20 -0
  4. package/dist/QueryCache.d.ts.map +1 -0
  5. package/dist/QueryCache.js +61 -0
  6. package/dist/QueryCache.js.map +1 -0
  7. package/dist/SynchronousDatabaseWrapper.d.ts +36 -0
  8. package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -0
  9. package/dist/SynchronousDatabaseWrapper.js +176 -0
  10. package/dist/SynchronousDatabaseWrapper.js.map +1 -0
  11. package/dist/effect/LiveStore.d.ts +38 -0
  12. package/dist/effect/LiveStore.d.ts.map +1 -0
  13. package/dist/effect/LiveStore.js +38 -0
  14. package/dist/effect/LiveStore.js.map +1 -0
  15. package/dist/effect/index.d.ts +2 -0
  16. package/dist/effect/index.d.ts.map +1 -0
  17. package/dist/effect/index.js +2 -0
  18. package/dist/effect/index.js.map +1 -0
  19. package/dist/global-state.d.ts +14 -0
  20. package/dist/global-state.d.ts.map +1 -0
  21. package/dist/global-state.js +16 -0
  22. package/dist/global-state.js.map +1 -0
  23. package/dist/index.d.ts +19 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +15 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/reactive.d.ts +163 -0
  28. package/dist/reactive.d.ts.map +1 -0
  29. package/dist/reactive.js +382 -0
  30. package/dist/reactive.js.map +1 -0
  31. package/dist/reactive.test.d.ts +2 -0
  32. package/dist/reactive.test.d.ts.map +1 -0
  33. package/dist/reactive.test.js +345 -0
  34. package/dist/reactive.test.js.map +1 -0
  35. package/dist/reactiveQueries/base-class.d.ts +59 -0
  36. package/dist/reactiveQueries/base-class.d.ts.map +1 -0
  37. package/dist/reactiveQueries/base-class.js +29 -0
  38. package/dist/reactiveQueries/base-class.js.map +1 -0
  39. package/dist/reactiveQueries/graphql.d.ts +52 -0
  40. package/dist/reactiveQueries/graphql.d.ts.map +1 -0
  41. package/dist/reactiveQueries/graphql.js +136 -0
  42. package/dist/reactiveQueries/graphql.js.map +1 -0
  43. package/dist/reactiveQueries/js.d.ts +35 -0
  44. package/dist/reactiveQueries/js.d.ts.map +1 -0
  45. package/dist/reactiveQueries/js.js +57 -0
  46. package/dist/reactiveQueries/js.js.map +1 -0
  47. package/dist/reactiveQueries/sql.d.ts +49 -0
  48. package/dist/reactiveQueries/sql.d.ts.map +1 -0
  49. package/dist/reactiveQueries/sql.js +130 -0
  50. package/dist/reactiveQueries/sql.js.map +1 -0
  51. package/dist/reactiveQueries/sql.test.d.ts +2 -0
  52. package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
  53. package/dist/reactiveQueries/sql.test.js +284 -0
  54. package/dist/reactiveQueries/sql.test.js.map +1 -0
  55. package/dist/row-query.d.ts +33 -0
  56. package/dist/row-query.d.ts.map +1 -0
  57. package/dist/row-query.js +84 -0
  58. package/dist/row-query.js.map +1 -0
  59. package/dist/store-context.d.ts +26 -0
  60. package/dist/store-context.d.ts.map +1 -0
  61. package/dist/store-context.js +6 -0
  62. package/dist/store-context.js.map +1 -0
  63. package/dist/store-devtools.d.ts +19 -0
  64. package/dist/store-devtools.d.ts.map +1 -0
  65. package/dist/store-devtools.js +141 -0
  66. package/dist/store-devtools.js.map +1 -0
  67. package/dist/store.d.ts +175 -0
  68. package/dist/store.d.ts.map +1 -0
  69. package/dist/store.js +507 -0
  70. package/dist/store.js.map +1 -0
  71. package/dist/utils/data-structures.d.ts +10 -0
  72. package/dist/utils/data-structures.d.ts.map +1 -0
  73. package/dist/utils/data-structures.js +32 -0
  74. package/dist/utils/data-structures.js.map +1 -0
  75. package/dist/utils/dev.d.ts +3 -0
  76. package/dist/utils/dev.d.ts.map +1 -0
  77. package/dist/utils/dev.js +17 -0
  78. package/dist/utils/dev.js.map +1 -0
  79. package/dist/utils/otel.d.ts +4 -0
  80. package/dist/utils/otel.d.ts.map +1 -0
  81. package/dist/utils/otel.js +6 -0
  82. package/dist/utils/otel.js.map +1 -0
  83. package/dist/utils/stack-info.d.ts +10 -0
  84. package/dist/utils/stack-info.d.ts.map +1 -0
  85. package/dist/utils/stack-info.js +41 -0
  86. package/dist/utils/stack-info.js.map +1 -0
  87. package/dist/utils/stack-info.test.d.ts +2 -0
  88. package/dist/utils/stack-info.test.d.ts.map +1 -0
  89. package/dist/utils/stack-info.test.js +75 -0
  90. package/dist/utils/stack-info.test.js.map +1 -0
  91. package/dist/utils/tests/fixture.d.ts +259 -0
  92. package/dist/utils/tests/fixture.d.ts.map +1 -0
  93. package/dist/utils/tests/fixture.js +33 -0
  94. package/dist/utils/tests/fixture.js.map +1 -0
  95. package/dist/utils/tests/mod.d.ts +3 -0
  96. package/dist/utils/tests/mod.d.ts.map +1 -0
  97. package/dist/utils/tests/mod.js +3 -0
  98. package/dist/utils/tests/mod.js.map +1 -0
  99. package/dist/utils/tests/otel.d.ts +10 -0
  100. package/dist/utils/tests/otel.d.ts.map +1 -0
  101. package/dist/utils/tests/otel.js +42 -0
  102. package/dist/utils/tests/otel.js.map +1 -0
  103. package/package.json +60 -0
  104. package/src/QueryCache.ts +81 -0
  105. package/src/SynchronousDatabaseWrapper.ts +256 -0
  106. package/src/ambient.d.ts +10 -0
  107. package/src/effect/LiveStore.ts +112 -0
  108. package/src/effect/index.ts +8 -0
  109. package/src/global-state.ts +20 -0
  110. package/src/index.ts +64 -0
  111. package/src/reactive.test.ts +426 -0
  112. package/src/reactive.ts +661 -0
  113. package/src/reactiveQueries/base-class.ts +115 -0
  114. package/src/reactiveQueries/graphql.ts +233 -0
  115. package/src/reactiveQueries/js.ts +108 -0
  116. package/src/reactiveQueries/sql.test.ts +308 -0
  117. package/src/reactiveQueries/sql.ts +226 -0
  118. package/src/row-query.ts +200 -0
  119. package/src/store-context.ts +23 -0
  120. package/src/store-devtools.ts +217 -0
  121. package/src/store.ts +920 -0
  122. package/src/utils/data-structures.ts +36 -0
  123. package/src/utils/dev.ts +24 -0
  124. package/src/utils/otel.ts +9 -0
  125. package/src/utils/stack-info.test.ts +79 -0
  126. package/src/utils/stack-info.ts +54 -0
  127. package/src/utils/tests/fixture.ts +77 -0
  128. package/src/utils/tests/mod.ts +2 -0
  129. package/src/utils/tests/otel.ts +61 -0
  130. package/tsconfig.json +18 -0
  131. package/vitest.config.js +9 -0
@@ -0,0 +1,115 @@
1
+ import type { QueryInfo, QueryInfoNone } from '@livestore/common'
2
+ import type * as otel from '@opentelemetry/api'
3
+
4
+ import { type Atom, type GetAtom, ReactiveGraph, throwContextNotSetError, type Thunk } from '../reactive.js'
5
+ import type { QueryDebugInfo, RefreshReason, Store } from '../store.js'
6
+ import type { StackInfo } from '../utils/stack-info.js'
7
+
8
+ export type ReactivityGraph = ReactiveGraph<RefreshReason, QueryDebugInfo, QueryContext>
9
+
10
+ export const makeReactivityGraph = (): ReactivityGraph =>
11
+ new ReactiveGraph<RefreshReason, QueryDebugInfo, QueryContext>()
12
+
13
+ export type QueryContext = {
14
+ store: Store
15
+ otelTracer: otel.Tracer
16
+ rootOtelContext: otel.Context
17
+ effectsWrapper: (run: () => void) => void
18
+ }
19
+
20
+ export type UnsubscribeQuery = () => void
21
+
22
+ export type GetResult<TQuery extends LiveQueryAny> =
23
+ TQuery extends LiveQuery<infer TResult, infer _1> ? TResult : unknown
24
+
25
+ let queryIdCounter = 0
26
+
27
+ export type LiveQueryAny = LiveQuery<any, QueryInfo>
28
+
29
+ export interface LiveQuery<TResult, TQueryInfo extends QueryInfo = QueryInfoNone> {
30
+ id: number
31
+ _tag: 'js' | 'sql' | 'graphql'
32
+
33
+ /** This should only be used on a type-level and doesn't hold any value during runtime */
34
+ '__result!': TResult
35
+
36
+ /** A reactive thunk representing the query results */
37
+ results$: Thunk<TResult, QueryContext, RefreshReason>
38
+
39
+ label: string
40
+
41
+ run: (otelContext?: otel.Context, debugRefreshReason?: RefreshReason) => TResult
42
+
43
+ runAndDestroy: (otelContext?: otel.Context, debugRefreshReason?: RefreshReason) => TResult
44
+
45
+ destroy(): void
46
+
47
+ subscribe(
48
+ onNewValue: (value: TResult) => void,
49
+ onUnsubsubscribe?: () => void,
50
+ options?: { label?: string; otelContext?: otel.Context },
51
+ ): () => void
52
+
53
+ activeSubscriptions: Set<StackInfo>
54
+
55
+ queryInfo: TQueryInfo
56
+
57
+ runs: number
58
+
59
+ executionTimes: number[]
60
+ }
61
+
62
+ export abstract class LiveStoreQueryBase<TResult, TQueryInfo extends QueryInfo>
63
+ implements LiveQuery<TResult, TQueryInfo>
64
+ {
65
+ '__result!'!: TResult
66
+ id = queryIdCounter++
67
+ abstract _tag: 'js' | 'sql' | 'graphql'
68
+
69
+ /** Human-readable label for the query for debugging */
70
+ abstract label: string
71
+
72
+ abstract results$: Thunk<TResult, QueryContext, RefreshReason>
73
+
74
+ activeSubscriptions: Set<StackInfo> = new Set()
75
+
76
+ protected abstract reactivityGraph: ReactivityGraph
77
+
78
+ abstract queryInfo: TQueryInfo
79
+
80
+ get runs() {
81
+ return this.results$.recomputations
82
+ }
83
+
84
+ executionTimes: number[] = []
85
+
86
+ abstract destroy: () => void
87
+
88
+ run = (otelContext?: otel.Context, debugRefreshReason?: RefreshReason): TResult =>
89
+ this.results$.computeResult(otelContext, debugRefreshReason)
90
+
91
+ runAndDestroy = (otelContext?: otel.Context, debugRefreshReason?: RefreshReason): TResult => {
92
+ const result = this.run(otelContext, debugRefreshReason)
93
+ this.destroy()
94
+ return result
95
+ }
96
+
97
+ subscribe = (
98
+ onNewValue: (value: TResult) => void,
99
+ onUnsubsubscribe?: () => void,
100
+ options?: { label?: string; otelContext?: otel.Context } | undefined,
101
+ ): (() => void) =>
102
+ this.reactivityGraph.context?.store.subscribe(this, onNewValue, onUnsubsubscribe, options) ??
103
+ throwContextNotSetError(this.reactivityGraph)
104
+ }
105
+
106
+ export type GetAtomResult = <T>(atom: Atom<T, any, RefreshReason> | LiveQuery<T, any>) => T
107
+
108
+ export const makeGetAtomResult = (get: GetAtom, otelContext: otel.Context) => {
109
+ const getAtom: GetAtomResult = (atom) => {
110
+ if (atom._tag === 'thunk' || atom._tag === 'ref') return get(atom, otelContext)
111
+ return get(atom.results$, otelContext)
112
+ }
113
+
114
+ return getAtom
115
+ }
@@ -0,0 +1,233 @@
1
+ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
2
+ import type { QueryInfoNone } from '@livestore/common'
3
+ import { shouldNeverHappen } from '@livestore/utils'
4
+ import { Schema, TreeFormatter } from '@livestore/utils/effect'
5
+ import * as otel from '@opentelemetry/api'
6
+ import * as graphql from 'graphql'
7
+
8
+ import { globalReactivityGraph } from '../global-state.js'
9
+ import { isThunk, type Thunk } from '../reactive.js'
10
+ import type { BaseGraphQLContext, RefreshReason, Store } from '../store.js'
11
+ import { getDurationMsFromSpan } from '../utils/otel.js'
12
+ import type { GetAtomResult, LiveQuery, QueryContext, ReactivityGraph } from './base-class.js'
13
+ import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
14
+
15
+ export type MapResult<To, From> = ((res: From, get: GetAtomResult) => To) | Schema.Schema<To, From>
16
+
17
+ export const queryGraphQL = <
18
+ TResult extends Record<string, any>,
19
+ TVariableValues extends Record<string, any>,
20
+ TResultMapped extends Record<string, any> = TResult,
21
+ >(
22
+ document: DocumentNode<TResult, TVariableValues>,
23
+ genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
24
+ {
25
+ label,
26
+ reactivityGraph,
27
+ map,
28
+ }: {
29
+ label?: string
30
+ reactivityGraph?: ReactivityGraph
31
+ map?: MapResult<TResultMapped, TResult>
32
+ } = {},
33
+ ): LiveQuery<TResultMapped, QueryInfoNone> =>
34
+ new LiveStoreGraphQLQuery({ document, genVariableValues, label, reactivityGraph, map })
35
+
36
+ export class LiveStoreGraphQLQuery<
37
+ TResult extends Record<string, any>,
38
+ TVariableValues extends Record<string, any>,
39
+ TContext extends BaseGraphQLContext,
40
+ TResultMapped extends Record<string, any> = TResult,
41
+ > extends LiveStoreQueryBase<TResultMapped, QueryInfoNone> {
42
+ _tag: 'graphql' = 'graphql'
43
+
44
+ /** The abstract GraphQL query */
45
+ document: DocumentNode<TResult, TVariableValues>
46
+
47
+ /** A reactive thunk representing the query results */
48
+ results$: Thunk<TResultMapped, QueryContext, RefreshReason>
49
+
50
+ variableValues$: Thunk<TVariableValues, QueryContext, RefreshReason> | undefined
51
+
52
+ label: string
53
+
54
+ protected reactivityGraph: ReactivityGraph
55
+
56
+ queryInfo: QueryInfoNone = { _tag: 'None' }
57
+
58
+ private mapResult
59
+
60
+ constructor({
61
+ document,
62
+ label,
63
+ genVariableValues,
64
+ reactivityGraph,
65
+ map,
66
+ }: {
67
+ document: DocumentNode<TResult, TVariableValues>
68
+ genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues)
69
+ label?: string
70
+ reactivityGraph?: ReactivityGraph
71
+ map?: MapResult<TResultMapped, TResult>
72
+ }) {
73
+ super()
74
+
75
+ const labelWithDefault = label ?? graphql.getOperationAST(document)?.name?.value ?? 'graphql'
76
+
77
+ this.label = labelWithDefault
78
+ this.document = document
79
+
80
+ this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
81
+
82
+ this.mapResult =
83
+ map === undefined
84
+ ? (res: TResult) => res as any as TResultMapped
85
+ : Schema.isSchema(map)
86
+ ? (res: TResult) => {
87
+ const parseResult = Schema.decodeEither(map as Schema.Schema<TResultMapped, TResult>)(res)
88
+ if (parseResult._tag === 'Left') {
89
+ console.error(`Error parsing GraphQL query result: ${TreeFormatter.formatErrorSync(parseResult.left)}`)
90
+ return shouldNeverHappen(`Error parsing SQL query result: ${parseResult.left}`)
91
+ } else {
92
+ return parseResult.right as TResultMapped
93
+ }
94
+ }
95
+ : typeof map === 'function'
96
+ ? map
97
+ : shouldNeverHappen(`Invalid map function ${map}`)
98
+
99
+ // TODO don't even create a thunk if variables are static
100
+ let variableValues$OrvariableValues
101
+
102
+ if (typeof genVariableValues === 'function') {
103
+ variableValues$OrvariableValues = this.reactivityGraph.makeThunk(
104
+ (get, _setDebugInfo, { rootOtelContext }, otelContext) => {
105
+ return genVariableValues(makeGetAtomResult(get, otelContext ?? rootOtelContext))
106
+ },
107
+ { label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } },
108
+ )
109
+ this.variableValues$ = variableValues$OrvariableValues
110
+ } else {
111
+ variableValues$OrvariableValues = genVariableValues
112
+ }
113
+
114
+ const resultsLabel = `${labelWithDefault}:results`
115
+ this.results$ = this.reactivityGraph.makeThunk<TResultMapped>(
116
+ (get, setDebugInfo, { store, otelTracer, rootOtelContext }, otelContext) => {
117
+ const variableValues = isThunk(variableValues$OrvariableValues)
118
+ ? (get(variableValues$OrvariableValues) as TVariableValues)
119
+ : (variableValues$OrvariableValues as TVariableValues)
120
+ const { result, queriedTables, durationMs } = this.queryOnce({
121
+ document,
122
+ variableValues,
123
+ otelContext: otelContext ?? rootOtelContext,
124
+ otelTracer,
125
+ store: store as Store<TContext>,
126
+ get: makeGetAtomResult(get, otelContext ?? rootOtelContext),
127
+ })
128
+
129
+ // Add dependencies on any tables that were used
130
+ for (const tableName of queriedTables) {
131
+ const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
132
+ get(tableRef)
133
+ }
134
+
135
+ setDebugInfo({ _tag: 'graphql', label: resultsLabel, query: graphql.print(document), durationMs })
136
+
137
+ return result
138
+ },
139
+ { label: resultsLabel, meta: { liveStoreThunkType: 'graphqlResults' } },
140
+ // otelContext,
141
+ )
142
+ }
143
+
144
+ /**
145
+ * Returns a new reactive query that contains the result of
146
+ * running an arbitrary JS computation on the results of this SQL query.
147
+ */
148
+ // pipe = <U>(fn: (result: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
149
+ // new LiveStoreJSQuery({
150
+ // fn: (get) => {
151
+ // const results = get(this.results$)
152
+ // return fn(results, get)
153
+ // },
154
+ // label: `${this.label}:js`,
155
+ // onDestroy: () => this.destroy(),
156
+ // reactivityGraph: this.reactivityGraph,
157
+ // })
158
+
159
+ queryOnce = ({
160
+ document,
161
+ otelContext,
162
+ otelTracer,
163
+ variableValues,
164
+ store,
165
+ get,
166
+ }: {
167
+ document: graphql.DocumentNode
168
+ otelContext: otel.Context
169
+ otelTracer: otel.Tracer
170
+ variableValues: TVariableValues
171
+ store: Store<TContext>
172
+ get: GetAtomResult
173
+ }) => {
174
+ const schema =
175
+ store.graphQLSchema ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL schema")
176
+ const context =
177
+ store.graphQLContext ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context")
178
+
179
+ const operationName = graphql.getOperationAST(document)?.name?.value
180
+
181
+ return otelTracer.startActiveSpan(`executeGraphQLQuery: ${operationName}`, {}, otelContext, (span) => {
182
+ span.setAttribute('graphql.variables', JSON.stringify(variableValues))
183
+ span.setAttribute('graphql.query', graphql.print(document))
184
+
185
+ context.queriedTables.clear()
186
+
187
+ context.otelContext = otel.trace.setSpan(otel.context.active(), span)
188
+
189
+ const res = graphql.executeSync({
190
+ document,
191
+ contextValue: context,
192
+ schema: schema,
193
+ variableValues,
194
+ })
195
+
196
+ // TODO track number of nested SQL queries via Otel + debug info
197
+
198
+ if (res.errors) {
199
+ span.setStatus({ code: otel.SpanStatusCode.ERROR, message: 'GraphQL error' })
200
+ span.setAttribute('graphql.error', res.errors.join('\n'))
201
+ span.setAttribute('graphql.error-detail', JSON.stringify(res.errors))
202
+ console.error(`graphql error (${operationName}) - ${res.errors.length} errors`)
203
+ for (const error of res.errors) {
204
+ console.error(error)
205
+ }
206
+ debugger
207
+ shouldNeverHappen(`GraphQL error: ${res.errors.join('\n')}`)
208
+ }
209
+
210
+ span.end()
211
+
212
+ const result = this.mapResult(res.data as unknown as TResult, get)
213
+
214
+ const durationMs = getDurationMsFromSpan(span)
215
+
216
+ this.executionTimes.push(durationMs)
217
+
218
+ return {
219
+ result,
220
+ queriedTables: Array.from(context.queriedTables.values()),
221
+ durationMs,
222
+ }
223
+ })
224
+ }
225
+
226
+ destroy = () => {
227
+ if (this.variableValues$ !== undefined) {
228
+ this.reactivityGraph.destroyNode(this.variableValues$)
229
+ }
230
+
231
+ this.reactivityGraph.destroyNode(this.results$)
232
+ }
233
+ }
@@ -0,0 +1,108 @@
1
+ import type { QueryInfo, QueryInfoNone } from '@livestore/common'
2
+ import * as otel from '@opentelemetry/api'
3
+
4
+ import { globalReactivityGraph } from '../global-state.js'
5
+ import type { Thunk } from '../reactive.js'
6
+ import type { RefreshReason } from '../store.js'
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'
10
+
11
+ export const computed = <TResult, TQueryInfo extends QueryInfo = QueryInfoNone>(
12
+ fn: (get: GetAtomResult) => TResult,
13
+ options?: {
14
+ label: string
15
+ reactivityGraph?: ReactivityGraph
16
+ queryInfo?: TQueryInfo
17
+ },
18
+ ): LiveQuery<TResult, TQueryInfo> =>
19
+ new LiveStoreJSQuery<TResult, TQueryInfo>({
20
+ fn,
21
+ label: options?.label ?? fn.toString(),
22
+ reactivityGraph: options?.reactivityGraph,
23
+ queryInfo: options?.queryInfo,
24
+ })
25
+
26
+ export class LiveStoreJSQuery<TResult, TQueryInfo extends QueryInfo = QueryInfoNone> extends LiveStoreQueryBase<
27
+ TResult,
28
+ TQueryInfo
29
+ > {
30
+ _tag: 'js' = 'js'
31
+
32
+ /** A reactive thunk representing the query results */
33
+ results$: Thunk<TResult, QueryContext, RefreshReason>
34
+
35
+ label: string
36
+
37
+ protected reactivityGraph: ReactivityGraph
38
+
39
+ queryInfo: TQueryInfo
40
+
41
+ /**
42
+ * Currently only used for "nested destruction" of piped queries
43
+ *
44
+ * i.e. when doing something like `const q = querySQL(...).pipe(...)`
45
+ * we need to also destory the SQL query when the JS query `q` is destroyed
46
+ */
47
+ private onDestroy: (() => void) | undefined
48
+
49
+ constructor({
50
+ fn,
51
+ label,
52
+ onDestroy,
53
+ reactivityGraph,
54
+ queryInfo,
55
+ }: {
56
+ label: string
57
+ fn: (get: GetAtomResult) => TResult
58
+ /** Currently only used for "nested destruction" of piped queries */
59
+ onDestroy?: () => void
60
+ reactivityGraph?: ReactivityGraph
61
+ queryInfo?: TQueryInfo
62
+ }) {
63
+ super()
64
+
65
+ this.onDestroy = onDestroy
66
+ this.label = label
67
+
68
+ this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
69
+ this.queryInfo = queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
70
+
71
+ const queryLabel = `${label}:results`
72
+
73
+ this.results$ = this.reactivityGraph.makeThunk(
74
+ (get, setDebugInfo, { otelTracer, rootOtelContext }, otelContext) =>
75
+ otelTracer.startActiveSpan(`js:${label}`, {}, otelContext ?? rootOtelContext, (span) => {
76
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
77
+ const res = fn(makeGetAtomResult(get, otelContext))
78
+
79
+ span.end()
80
+
81
+ const durationMs = getDurationMsFromSpan(span)
82
+
83
+ this.executionTimes.push(durationMs)
84
+
85
+ setDebugInfo({ _tag: 'js', label, query: fn.toString(), durationMs })
86
+
87
+ return res
88
+ }),
89
+ { label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } },
90
+ )
91
+ }
92
+
93
+ // pipe = <U>(fn: (result: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
94
+ // new LiveStoreJSQuery({
95
+ // fn: (get) => {
96
+ // const results = get(this.results$)
97
+ // return fn(results, get)
98
+ // },
99
+ // label: `${this.label}:js`,
100
+ // onDestroy: () => this.destroy(),
101
+ // reactivityGraph: this.reactivityGraph,
102
+ // })
103
+
104
+ destroy = () => {
105
+ this.reactivityGraph.destroyNode(this.results$)
106
+ this.onDestroy?.()
107
+ }
108
+ }