@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,308 @@
1
+ import { Effect, Schema } from '@livestore/utils/effect'
2
+ import * as otel from '@opentelemetry/api'
3
+ import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
4
+ import { describe, expect, it } from 'vitest'
5
+
6
+ import { computed, querySQL, rawSqlMutation, sql } from '../index.js'
7
+ import { makeTodoMvc, tables } from '../utils/tests/fixture.js'
8
+ import { getSimplifiedRootSpan } from '../utils/tests/otel.js'
9
+
10
+ /*
11
+ TODO write tests for:
12
+
13
+ - sql queries without and with `map` (incl. callback and schemas)
14
+ - optional and explicit `queriedTables` argument
15
+ */
16
+
17
+ describe('otel', () => {
18
+ let cachedProvider: BasicTracerProvider | undefined
19
+
20
+ const makeQuery = Effect.gen(function* () {
21
+ const exporter = new InMemorySpanExporter()
22
+
23
+ const provider = cachedProvider ?? new BasicTracerProvider()
24
+ cachedProvider = provider
25
+ provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
26
+ provider.register()
27
+
28
+ const otelTracer = otel.trace.getTracer('test')
29
+
30
+ const span = otelTracer.startSpan('test')
31
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
32
+
33
+ const { store } = yield* makeTodoMvc({ otelTracer, otelContext })
34
+
35
+ return {
36
+ store,
37
+ otelTracer,
38
+ exporter,
39
+ span,
40
+ provider,
41
+ }
42
+ })
43
+
44
+ it('otel', async () => {
45
+ const { exporter } = await Effect.gen(function* () {
46
+ const { store, exporter, span } = yield* makeQuery
47
+
48
+ const query = querySQL(`select * from todos`, {
49
+ schema: Schema.Array(tables.todos.schema),
50
+ queriedTables: new Set(['todos']),
51
+ })
52
+ expect(query.run()).toMatchInlineSnapshot('[]')
53
+
54
+ store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
55
+
56
+ expect(query.run()).toMatchInlineSnapshot(`
57
+ [
58
+ {
59
+ "completed": false,
60
+ "id": "t1",
61
+ "text": "buy milk",
62
+ },
63
+ ]
64
+ `)
65
+
66
+ query.destroy()
67
+ span.end()
68
+
69
+ return { exporter }
70
+ }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
71
+
72
+ expect(getSimplifiedRootSpan(exporter)).toMatchInlineSnapshot(`
73
+ {
74
+ "_name": "test",
75
+ "children": [
76
+ {
77
+ "_name": "livestore.in-memory-db:execute",
78
+ "attributes": {
79
+ "sql.query": "
80
+ PRAGMA page_size=32768;
81
+ PRAGMA cache_size=10000;
82
+ PRAGMA journal_mode='MEMORY'; -- we don't flush to disk before committing a write
83
+ PRAGMA synchronous='OFF';
84
+ PRAGMA temp_store='MEMORY';
85
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
86
+ ",
87
+ },
88
+ },
89
+ {
90
+ "_name": "LiveStore:mutations",
91
+ "children": [
92
+ {
93
+ "_name": "LiveStore:mutate",
94
+ "attributes": {
95
+ "livestore.mutateLabel": "mutate",
96
+ },
97
+ "children": [
98
+ {
99
+ "_name": "LiveStore:processWrites",
100
+ "attributes": {
101
+ "livestore.mutateLabel": "mutate",
102
+ },
103
+ "children": [
104
+ {
105
+ "_name": "LiveStore:mutateWithoutRefresh",
106
+ "attributes": {
107
+ "livestore.args": "{
108
+ "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)"
109
+ }",
110
+ "livestore.mutation": "livestore.RawSql",
111
+ },
112
+ "children": [
113
+ {
114
+ "_name": "livestore.in-memory-db:execute",
115
+ "attributes": {
116
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
117
+ },
118
+ },
119
+ ],
120
+ },
121
+ ],
122
+ },
123
+ ],
124
+ },
125
+ ],
126
+ },
127
+ {
128
+ "_name": "LiveStore:queries",
129
+ "children": [
130
+ {
131
+ "_name": "sql:select * from todos",
132
+ "attributes": {
133
+ "sql.query": "select * from todos",
134
+ "sql.rowsCount": 0,
135
+ },
136
+ "children": [
137
+ {
138
+ "_name": "sql-in-memory-select",
139
+ "attributes": {
140
+ "sql.cached": false,
141
+ "sql.query": "select * from todos",
142
+ "sql.rowsCount": 0,
143
+ },
144
+ },
145
+ ],
146
+ },
147
+ {
148
+ "_name": "sql:select * from todos",
149
+ "attributes": {
150
+ "sql.query": "select * from todos",
151
+ "sql.rowsCount": 1,
152
+ },
153
+ "children": [
154
+ {
155
+ "_name": "sql-in-memory-select",
156
+ "attributes": {
157
+ "sql.cached": false,
158
+ "sql.query": "select * from todos",
159
+ "sql.rowsCount": 1,
160
+ },
161
+ },
162
+ ],
163
+ },
164
+ ],
165
+ },
166
+ ],
167
+ }
168
+ `)
169
+ })
170
+
171
+ it('with thunks', async () => {
172
+ const { exporter } = await Effect.gen(function* () {
173
+ const { store, exporter, span } = yield* makeQuery
174
+
175
+ const defaultTodo = { id: '', text: '', completed: false }
176
+
177
+ const filter = computed(() => `where completed = 0`, { label: 'where-filter' })
178
+ const query = querySQL((get) => `select * from todos ${get(filter)}`, {
179
+ label: 'all todos',
180
+ schema: Schema.Array(tables.todos.schema).pipe(Schema.headOrElse(() => defaultTodo)),
181
+ })
182
+
183
+ expect(query.run()).toMatchInlineSnapshot(`
184
+ {
185
+ "completed": false,
186
+ "id": "",
187
+ "text": "",
188
+ }
189
+ `)
190
+
191
+ store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
192
+
193
+ expect(query.run()).toMatchInlineSnapshot(`
194
+ {
195
+ "completed": false,
196
+ "id": "t1",
197
+ "text": "buy milk",
198
+ }
199
+ `)
200
+
201
+ query.destroy()
202
+ span.end()
203
+
204
+ return { exporter }
205
+ }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
206
+
207
+ expect(getSimplifiedRootSpan(exporter)).toMatchInlineSnapshot(`
208
+ {
209
+ "_name": "test",
210
+ "children": [
211
+ {
212
+ "_name": "livestore.in-memory-db:execute",
213
+ "attributes": {
214
+ "sql.query": "
215
+ PRAGMA page_size=32768;
216
+ PRAGMA cache_size=10000;
217
+ PRAGMA journal_mode='MEMORY'; -- we don't flush to disk before committing a write
218
+ PRAGMA synchronous='OFF';
219
+ PRAGMA temp_store='MEMORY';
220
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
221
+ ",
222
+ },
223
+ },
224
+ {
225
+ "_name": "LiveStore:mutations",
226
+ "children": [
227
+ {
228
+ "_name": "LiveStore:mutate",
229
+ "attributes": {
230
+ "livestore.mutateLabel": "mutate",
231
+ },
232
+ "children": [
233
+ {
234
+ "_name": "LiveStore:processWrites",
235
+ "attributes": {
236
+ "livestore.mutateLabel": "mutate",
237
+ },
238
+ "children": [
239
+ {
240
+ "_name": "LiveStore:mutateWithoutRefresh",
241
+ "attributes": {
242
+ "livestore.args": "{
243
+ "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)"
244
+ }",
245
+ "livestore.mutation": "livestore.RawSql",
246
+ },
247
+ "children": [
248
+ {
249
+ "_name": "livestore.in-memory-db:execute",
250
+ "attributes": {
251
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
252
+ },
253
+ },
254
+ ],
255
+ },
256
+ ],
257
+ },
258
+ ],
259
+ },
260
+ ],
261
+ },
262
+ {
263
+ "_name": "LiveStore:queries",
264
+ "children": [
265
+ {
266
+ "_name": "sql:select * from todos where completed = 0",
267
+ "attributes": {
268
+ "sql.query": "select * from todos where completed = 0",
269
+ "sql.rowsCount": 0,
270
+ },
271
+ "children": [
272
+ {
273
+ "_name": "js:where-filter",
274
+ },
275
+ {
276
+ "_name": "sql-in-memory-select",
277
+ "attributes": {
278
+ "sql.cached": false,
279
+ "sql.query": "select * from todos where completed = 0",
280
+ "sql.rowsCount": 0,
281
+ },
282
+ },
283
+ ],
284
+ },
285
+ {
286
+ "_name": "sql:select * from todos where completed = 0",
287
+ "attributes": {
288
+ "sql.query": "select * from todos where completed = 0",
289
+ "sql.rowsCount": 1,
290
+ },
291
+ "children": [
292
+ {
293
+ "_name": "sql-in-memory-select",
294
+ "attributes": {
295
+ "sql.cached": false,
296
+ "sql.query": "select * from todos where completed = 0",
297
+ "sql.rowsCount": 1,
298
+ },
299
+ },
300
+ ],
301
+ },
302
+ ],
303
+ },
304
+ ],
305
+ }
306
+ `)
307
+ })
308
+ })
@@ -0,0 +1,226 @@
1
+ import { type Bindable, prepareBindValues, type QueryInfo, type QueryInfoNone } from '@livestore/common'
2
+ import { shouldNeverHappen } from '@livestore/utils'
3
+ import { Schema, TreeFormatter } from '@livestore/utils/effect'
4
+ import * as otel from '@opentelemetry/api'
5
+
6
+ import { globalReactivityGraph } from '../global-state.js'
7
+ import type { Thunk } from '../reactive.js'
8
+ import { NOT_REFRESHED_YET } from '../reactive.js'
9
+ import type { RefreshReason } from '../store.js'
10
+ import { getDurationMsFromSpan } from '../utils/otel.js'
11
+ import type { GetAtomResult, LiveQuery, QueryContext, ReactivityGraph } from './base-class.js'
12
+ import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
13
+
14
+ /**
15
+ * NOTE `querySQL` is only supposed to read data. Don't use it to insert/update/delete data but use mutations instead.
16
+ */
17
+ export const querySQL = <TResultSchema, TResult = TResultSchema>(
18
+ query: string | ((get: GetAtomResult) => string),
19
+ options: {
20
+ schema: Schema.Schema<TResultSchema, ReadonlyArray<any>>
21
+ map?: (rows: TResultSchema) => TResult
22
+ /**
23
+ * Can be provided explicitly to slightly speed up initial query performance
24
+ *
25
+ * NOTE In the future we want to do this automatically at build time
26
+ */
27
+ queriedTables?: Set<string>
28
+ bindValues?: Bindable
29
+ label?: string
30
+ reactivityGraph?: ReactivityGraph
31
+ },
32
+ ): LiveQuery<TResult, QueryInfoNone> =>
33
+ new LiveStoreSQLQuery<TResultSchema, TResult, QueryInfoNone>({
34
+ label: options?.label,
35
+ genQueryString: query,
36
+ queriedTables: options?.queriedTables,
37
+ bindValues: options?.bindValues,
38
+ reactivityGraph: options?.reactivityGraph,
39
+ map: options?.map,
40
+ schema: options.schema,
41
+ queryInfo: { _tag: 'None' },
42
+ })
43
+
44
+ /* An object encapsulating a reactive SQL query */
45
+ export class LiveStoreSQLQuery<
46
+ TResultSchema,
47
+ TResult = TResultSchema,
48
+ TQueryInfo extends QueryInfo = QueryInfoNone,
49
+ > extends LiveStoreQueryBase<TResult, TQueryInfo> {
50
+ _tag: 'sql' = 'sql'
51
+
52
+ /** A reactive thunk representing the query text */
53
+ queryString$: Thunk<string, QueryContext, RefreshReason> | undefined
54
+
55
+ /** A reactive thunk representing the query results */
56
+ results$: Thunk<TResult, QueryContext, RefreshReason>
57
+
58
+ label: string
59
+
60
+ protected reactivityGraph
61
+
62
+ /** Currently only used by `rowQuery` for lazy table migrations and eager default row insertion */
63
+ private execBeforeFirstRun
64
+
65
+ private mapResult: (rows: TResultSchema) => TResult
66
+ private schema: Schema.Schema<TResultSchema, ReadonlyArray<any>>
67
+
68
+ queryInfo: TQueryInfo
69
+
70
+ constructor({
71
+ genQueryString,
72
+ queriedTables,
73
+ bindValues,
74
+ label = genQueryString.toString(),
75
+ reactivityGraph,
76
+ schema,
77
+ map,
78
+ execBeforeFirstRun,
79
+ queryInfo,
80
+ }: {
81
+ label?: string
82
+ genQueryString: string | ((get: GetAtomResult, ctx: QueryContext) => string)
83
+ queriedTables?: Set<string>
84
+ bindValues?: Bindable
85
+ reactivityGraph?: ReactivityGraph
86
+ schema: Schema.Schema<TResultSchema, ReadonlyArray<any>>
87
+ map?: (rows: TResultSchema) => TResult
88
+ execBeforeFirstRun?: (ctx: QueryContext) => void
89
+ queryInfo?: TQueryInfo
90
+ }) {
91
+ super()
92
+
93
+ this.label = `sql(${label})`
94
+ this.reactivityGraph = reactivityGraph ?? globalReactivityGraph
95
+ this.execBeforeFirstRun = execBeforeFirstRun
96
+ this.queryInfo = queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
97
+
98
+ this.schema = schema
99
+ this.mapResult = map === undefined ? (rows: any) => rows as TResult : map
100
+
101
+ let queryString$OrQueryString: string | Thunk<string, QueryContext, RefreshReason>
102
+ if (typeof genQueryString === 'function') {
103
+ queryString$OrQueryString = this.reactivityGraph.makeThunk(
104
+ (get, setDebugInfo, ctx, otelContext) => {
105
+ const startMs = performance.now()
106
+ const queryString = genQueryString(makeGetAtomResult(get, otelContext ?? ctx.rootOtelContext), ctx)
107
+ const durationMs = performance.now() - startMs
108
+ setDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString, durationMs })
109
+ return queryString
110
+ },
111
+ {
112
+ label: `${label}:queryString`,
113
+ meta: { liveStoreThunkType: 'sqlQueryString' },
114
+ equal: (a, b) => a === b,
115
+ },
116
+ )
117
+
118
+ this.queryString$ = queryString$OrQueryString
119
+ } else {
120
+ queryString$OrQueryString = genQueryString
121
+ }
122
+
123
+ const queryLabel = `${label}:results`
124
+
125
+ const queriedTablesRef = { current: queriedTables }
126
+
127
+ const schemaEqual = Schema.equivalence(schema)
128
+ // TODO also support derived equality for `map` (probably will depend on having an easy way to transform a schema without an `encode` step)
129
+ // This would mean dropping the `map` option
130
+ const equal =
131
+ map === undefined
132
+ ? (a: TResult, b: TResult) =>
133
+ a === NOT_REFRESHED_YET || b === NOT_REFRESHED_YET ? false : schemaEqual(a as any, b as any)
134
+ : undefined
135
+
136
+ const results$ = this.reactivityGraph.makeThunk<TResult>(
137
+ (get, setDebugInfo, { store, otelTracer, rootOtelContext }, otelContext) =>
138
+ otelTracer.startActiveSpan(
139
+ 'sql:...', // NOTE span name will be overridden further down
140
+ {},
141
+ otelContext ?? rootOtelContext,
142
+ (span) => {
143
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
144
+
145
+ if (this.execBeforeFirstRun !== undefined) {
146
+ this.execBeforeFirstRun({ store, otelTracer, rootOtelContext, effectsWrapper: (run) => run() })
147
+ this.execBeforeFirstRun = undefined
148
+ }
149
+
150
+ const sqlString =
151
+ typeof queryString$OrQueryString === 'string'
152
+ ? queryString$OrQueryString
153
+ : get(queryString$OrQueryString, otelContext)
154
+
155
+ if (queriedTablesRef.current === undefined) {
156
+ queriedTablesRef.current = store.syncDbWrapper.getTablesUsed(sqlString)
157
+ }
158
+
159
+ // Establish a reactive dependency on the tables used in the query
160
+ for (const tableName of queriedTablesRef.current) {
161
+ const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
162
+ get(tableRef, otelContext)
163
+ }
164
+
165
+ span.setAttribute('sql.query', sqlString)
166
+ span.updateName(`sql:${sqlString.slice(0, 50)}`)
167
+
168
+ const rawResults = store.syncDbWrapper.select<any>(sqlString, {
169
+ queriedTables,
170
+ bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
171
+ otelContext,
172
+ })
173
+
174
+ span.setAttribute('sql.rowsCount', rawResults.length)
175
+
176
+ const parsedResult = Schema.decodeEither(this.schema)(rawResults)
177
+
178
+ if (parsedResult._tag === 'Left') {
179
+ const parseErrorStr = TreeFormatter.formatErrorSync(parsedResult.left)
180
+ const expectedSchemaStr = String(this.schema.ast)
181
+ const bindValuesStr = bindValues === undefined ? '' : `\nBind values: ${JSON.stringify(bindValues)}`
182
+
183
+ console.error(
184
+ `\
185
+ Error parsing SQL query result.
186
+
187
+ Query: ${sqlString}\
188
+ ${bindValuesStr}
189
+
190
+ Expected schema: ${expectedSchemaStr}
191
+
192
+ Error: ${parseErrorStr}
193
+
194
+ Result:`,
195
+ rawResults,
196
+ )
197
+ return shouldNeverHappen(`Error parsing SQL query result: ${parsedResult.left}`)
198
+ }
199
+
200
+ const result = this.mapResult(parsedResult.right)
201
+
202
+ span.end()
203
+
204
+ const durationMs = getDurationMsFromSpan(span)
205
+
206
+ this.executionTimes.push(durationMs)
207
+
208
+ setDebugInfo({ _tag: 'sql', label, query: sqlString, durationMs })
209
+
210
+ return result
211
+ },
212
+ ),
213
+ { label: queryLabel, equal },
214
+ )
215
+
216
+ this.results$ = results$
217
+ }
218
+
219
+ destroy = () => {
220
+ if (this.queryString$ !== undefined) {
221
+ this.reactivityGraph.destroyNode(this.queryString$)
222
+ }
223
+
224
+ this.reactivityGraph.destroyNode(this.results$)
225
+ }
226
+ }