@livestore/livestore 0.3.1-dev.0 → 0.3.2-dev.0

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 (55) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/QueryCache.d.ts.map +1 -1
  3. package/dist/QueryCache.js +8 -3
  4. package/dist/QueryCache.js.map +1 -1
  5. package/dist/SqliteDbWrapper.d.ts +17 -4
  6. package/dist/SqliteDbWrapper.d.ts.map +1 -1
  7. package/dist/SqliteDbWrapper.js +14 -6
  8. package/dist/SqliteDbWrapper.js.map +1 -1
  9. package/dist/SqliteDbWrapper.test.d.ts +2 -0
  10. package/dist/SqliteDbWrapper.test.d.ts.map +1 -0
  11. package/dist/SqliteDbWrapper.test.js +25 -0
  12. package/dist/SqliteDbWrapper.test.js.map +1 -0
  13. package/dist/effect/LiveStore.js +1 -1
  14. package/dist/effect/LiveStore.js.map +1 -1
  15. package/dist/live-queries/client-document-get-query.js +1 -1
  16. package/dist/live-queries/client-document-get-query.js.map +1 -1
  17. package/dist/live-queries/db-query.js +1 -1
  18. package/dist/live-queries/db-query.js.map +1 -1
  19. package/dist/live-queries/db-query.test.js +91 -1
  20. package/dist/live-queries/db-query.test.js.map +1 -1
  21. package/dist/reactive.js +1 -1
  22. package/dist/reactive.js.map +1 -1
  23. package/dist/store/create-store.js +1 -1
  24. package/dist/store/create-store.js.map +1 -1
  25. package/dist/store/devtools.js +9 -5
  26. package/dist/store/devtools.js.map +1 -1
  27. package/dist/store/store.d.ts +5 -4
  28. package/dist/store/store.d.ts.map +1 -1
  29. package/dist/store/store.js +42 -17
  30. package/dist/store/store.js.map +1 -1
  31. package/dist/utils/stack-info.d.ts.map +1 -1
  32. package/dist/utils/stack-info.js +5 -1
  33. package/dist/utils/stack-info.js.map +1 -1
  34. package/dist/utils/stack-info.test.js +6 -2
  35. package/dist/utils/stack-info.test.js.map +1 -1
  36. package/dist/utils/tests/fixture.d.ts +44 -54
  37. package/dist/utils/tests/fixture.d.ts.map +1 -1
  38. package/dist/utils/tests/otel.js +1 -1
  39. package/dist/utils/tests/otel.js.map +1 -1
  40. package/package.json +7 -7
  41. package/src/QueryCache.ts +9 -4
  42. package/src/SqliteDbWrapper.test.ts +38 -0
  43. package/src/SqliteDbWrapper.ts +24 -15
  44. package/src/effect/LiveStore.ts +1 -1
  45. package/src/live-queries/__snapshots__/db-query.test.ts.snap +389 -0
  46. package/src/live-queries/client-document-get-query.ts +1 -1
  47. package/src/live-queries/db-query.test.ts +144 -1
  48. package/src/live-queries/db-query.ts +1 -1
  49. package/src/reactive.ts +1 -1
  50. package/src/store/create-store.ts +1 -1
  51. package/src/store/devtools.ts +5 -5
  52. package/src/store/store.ts +55 -23
  53. package/src/utils/stack-info.test.ts +6 -2
  54. package/src/utils/stack-info.ts +5 -1
  55. package/src/utils/tests/otel.ts +1 -1
@@ -0,0 +1,38 @@
1
+ import { Effect } from '@livestore/utils/effect'
2
+ import { Vitest } from '@livestore/utils-dev/node-vitest'
3
+ import { expect } from 'vitest'
4
+
5
+ import { makeTodoMvc } from './utils/tests/fixture.js'
6
+
7
+ Vitest.describe('SqliteDbWrapper', () => {
8
+ Vitest.describe('getTablesUsed', () => {
9
+ const getTablesUsed = (query: string) =>
10
+ Effect.gen(function* () {
11
+ const store = yield* makeTodoMvc({})
12
+ return store.sqliteDbWrapper.getTablesUsed(query)
13
+ })
14
+
15
+ Vitest.scopedLive('should return the correct tables used', (_test) =>
16
+ Effect.gen(function* () {
17
+ const tablesUsed = yield* getTablesUsed('select * from todos')
18
+ expect(tablesUsed).toEqual(new Set(['todos']))
19
+ }),
20
+ )
21
+
22
+ Vitest.scopedLive('should handle DELETE FROM statement without WHERE clause', (_test) =>
23
+ Effect.gen(function* () {
24
+ const tablesUsed = yield* getTablesUsed('DELETE FROM todos')
25
+ expect(tablesUsed).toEqual(new Set(['todos']))
26
+ }),
27
+ )
28
+
29
+ Vitest.scopedLive('should handle INSERT with ON CONFLICT clause', (_test) =>
30
+ Effect.gen(function* () {
31
+ const tablesUsed = yield* getTablesUsed(
32
+ 'INSERT INTO todos (id, text, completed) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET text = ?',
33
+ )
34
+ expect(tablesUsed).toEqual(new Set(['todos']))
35
+ }),
36
+ )
37
+ })
38
+ })
@@ -1,21 +1,20 @@
1
1
  /* eslint-disable prefer-arrow/prefer-arrow-functions */
2
2
 
3
- import type {
4
- DebugInfo,
5
- MutableDebugInfo,
6
- PreparedBindValues,
7
- PreparedStatement,
8
- SqliteDb,
9
- SqliteDbChangeset,
10
- SqliteDbSession,
11
- } from '@livestore/common'
12
3
  import {
13
4
  BoundArray,
14
5
  BoundMap,
6
+ type DebugInfo,
15
7
  getDurationMsFromSpan,
16
8
  getStartTimeHighResFromSpan,
17
- sql,
9
+ type MutableDebugInfo,
10
+ type PreparedBindValues,
11
+ type PreparedStatement,
12
+ type SqliteDb,
13
+ type SqliteDbChangeset,
14
+ SqliteDbHelper,
15
+ type SqliteDbSession,
18
16
  SqliteError,
17
+ sql,
19
18
  } from '@livestore/common'
20
19
  import { isDevEnv, LS_DEV } from '@livestore/utils'
21
20
  import type * as otel from '@opentelemetry/api'
@@ -67,7 +66,12 @@ export class SqliteDbWrapper implements SqliteDb {
67
66
 
68
67
  configureSQLite(this)
69
68
  }
70
- metadata: any
69
+ get debug() {
70
+ return this.db.debug
71
+ }
72
+ get metadata() {
73
+ return this.db.metadata
74
+ }
71
75
  prepare(queryStr: string): PreparedStatement {
72
76
  return this.db.prepare(queryStr)
73
77
  }
@@ -75,10 +79,10 @@ export class SqliteDbWrapper implements SqliteDb {
75
79
  return this.db.import(data)
76
80
  }
77
81
  close(): void {
78
- return this.db.close()
82
+ this.db.close()
79
83
  }
80
84
  destroy(): void {
81
- return this.db.destroy()
85
+ this.db.destroy()
82
86
  }
83
87
  session(): SqliteDbSession {
84
88
  return this.db.session()
@@ -157,7 +161,7 @@ export class SqliteDbWrapper implements SqliteDb {
157
161
  return tablesUsed
158
162
  }
159
163
 
160
- execute(
164
+ cachedExecute(
161
165
  queryStr: string,
162
166
  bindValues?: PreparedBindValues | undefined,
163
167
  options?: {
@@ -213,6 +217,7 @@ export class SqliteDbWrapper implements SqliteDb {
213
217
  span.recordException(cause)
214
218
  span.end()
215
219
  if (LS_DEV) {
220
+ // biome-ignore lint/suspicious/noDebugger: debug
216
221
  debugger
217
222
  }
218
223
  throw new SqliteError({ cause, query: { bindValues: bindValues ?? {}, sql: queryStr } })
@@ -221,7 +226,11 @@ export class SqliteDbWrapper implements SqliteDb {
221
226
  )
222
227
  }
223
228
 
224
- select<T = any>(
229
+ execute = SqliteDbHelper.makeExecute((queryStr, bindValues) => this.cachedExecute(queryStr, bindValues))
230
+
231
+ select = SqliteDbHelper.makeSelect((queryStr, bindValues) => this.cachedSelect(queryStr, bindValues))
232
+
233
+ cachedSelect<T = any>(
225
234
  queryStr: string,
226
235
  bindValues?: PreparedBindValues | undefined,
227
236
  options?: {
@@ -35,7 +35,7 @@ export const makeLiveStoreContext = <TSchema extends LiveStoreSchema, TContext =
35
35
 
36
36
  globalThis.__debugLiveStore ??= {}
37
37
  if (Object.keys(globalThis.__debugLiveStore).length === 0) {
38
- globalThis.__debugLiveStore['_'] = store
38
+ globalThis.__debugLiveStore._ = store
39
39
  }
40
40
  globalThis.__debugLiveStore[storeId] = store
41
41
 
@@ -1,5 +1,394 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
+ exports[`otel > QueryBuilder subscription - basic functionality 1`] = `
4
+ {
5
+ "_name": "createStore",
6
+ "attributes": {
7
+ "debugInstanceId": "test",
8
+ "storeId": "default",
9
+ },
10
+ "children": [
11
+ {
12
+ "_name": "livestore.in-memory-db:execute",
13
+ "attributes": {
14
+ "sql.query": "
15
+ PRAGMA page_size=32768;
16
+ PRAGMA cache_size=10000;
17
+ PRAGMA synchronous='OFF';
18
+ PRAGMA temp_store='MEMORY';
19
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
20
+ ",
21
+ },
22
+ },
23
+ {
24
+ "_name": "@livestore/common:LeaderSyncProcessor:push",
25
+ "attributes": {
26
+ "batch": "undefined",
27
+ "batchSize": 1,
28
+ },
29
+ },
30
+ {
31
+ "_name": "client-session-sync-processor:pull",
32
+ "attributes": {
33
+ "code.stacktrace": "<STACKTRACE>",
34
+ "span.label": "⚠︎ Interrupted",
35
+ "status.interrupted": true,
36
+ },
37
+ },
38
+ {
39
+ "_name": "LiveStore:sync",
40
+ },
41
+ {
42
+ "_name": "LiveStore:commits",
43
+ "children": [
44
+ {
45
+ "_name": "LiveStore:commit",
46
+ "attributes": {
47
+ "livestore.eventTags": [
48
+ "livestore.RawSql",
49
+ ],
50
+ "livestore.eventsCount": 1,
51
+ },
52
+ "children": [
53
+ {
54
+ "_name": "livestore.in-memory-db:execute",
55
+ "attributes": {
56
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
57
+ },
58
+ },
59
+ ],
60
+ },
61
+ ],
62
+ },
63
+ {
64
+ "_name": "LiveStore:queries",
65
+ "children": [
66
+ {
67
+ "_name": "LiveStore.subscribe",
68
+ "attributes": {
69
+ "queryLabel": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
70
+ },
71
+ "children": [
72
+ {
73
+ "_name": "db:SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
74
+ "attributes": {
75
+ "livestore.debugRefreshReason": "subscribe-initial-run:undefined",
76
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
77
+ "sql.rowsCount": 0,
78
+ },
79
+ "children": [
80
+ {
81
+ "_name": "sql-in-memory-select",
82
+ "attributes": {
83
+ "sql.cached": false,
84
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
85
+ "sql.rowsCount": 0,
86
+ },
87
+ },
88
+ ],
89
+ },
90
+ {
91
+ "_name": "db:SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
92
+ "attributes": {
93
+ "livestore.debugRefreshReason": "commit",
94
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
95
+ "sql.rowsCount": 1,
96
+ },
97
+ "children": [
98
+ {
99
+ "_name": "sql-in-memory-select",
100
+ "attributes": {
101
+ "sql.cached": false,
102
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
103
+ "sql.rowsCount": 1,
104
+ },
105
+ },
106
+ ],
107
+ },
108
+ ],
109
+ },
110
+ ],
111
+ },
112
+ ],
113
+ }
114
+ `;
115
+
116
+ exports[`otel > QueryBuilder subscription - direct table subscription 1`] = `
117
+ {
118
+ "_name": "createStore",
119
+ "attributes": {
120
+ "debugInstanceId": "test",
121
+ "storeId": "default",
122
+ },
123
+ "children": [
124
+ {
125
+ "_name": "livestore.in-memory-db:execute",
126
+ "attributes": {
127
+ "sql.query": "
128
+ PRAGMA page_size=32768;
129
+ PRAGMA cache_size=10000;
130
+ PRAGMA synchronous='OFF';
131
+ PRAGMA temp_store='MEMORY';
132
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
133
+ ",
134
+ },
135
+ },
136
+ {
137
+ "_name": "@livestore/common:LeaderSyncProcessor:push",
138
+ "attributes": {
139
+ "batch": "undefined",
140
+ "batchSize": 1,
141
+ },
142
+ },
143
+ {
144
+ "_name": "client-session-sync-processor:pull",
145
+ "attributes": {
146
+ "code.stacktrace": "<STACKTRACE>",
147
+ "span.label": "⚠︎ Interrupted",
148
+ "status.interrupted": true,
149
+ },
150
+ },
151
+ {
152
+ "_name": "LiveStore:sync",
153
+ },
154
+ {
155
+ "_name": "LiveStore:commits",
156
+ "children": [
157
+ {
158
+ "_name": "LiveStore:commit",
159
+ "attributes": {
160
+ "livestore.eventTags": [
161
+ "livestore.RawSql",
162
+ ],
163
+ "livestore.eventsCount": 1,
164
+ },
165
+ "children": [
166
+ {
167
+ "_name": "livestore.in-memory-db:execute",
168
+ "attributes": {
169
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t5', 'clean house', 1)",
170
+ },
171
+ },
172
+ ],
173
+ },
174
+ ],
175
+ },
176
+ {
177
+ "_name": "LiveStore:queries",
178
+ "children": [
179
+ {
180
+ "_name": "LiveStore.subscribe",
181
+ "attributes": {
182
+ "queryLabel": "SELECT * FROM 'todos'",
183
+ },
184
+ "children": [
185
+ {
186
+ "_name": "db:SELECT * FROM 'todos'",
187
+ "attributes": {
188
+ "livestore.debugRefreshReason": "subscribe-initial-run:undefined",
189
+ "sql.query": "SELECT * FROM 'todos'",
190
+ "sql.rowsCount": 0,
191
+ },
192
+ "children": [
193
+ {
194
+ "_name": "sql-in-memory-select",
195
+ "attributes": {
196
+ "sql.cached": false,
197
+ "sql.query": "SELECT * FROM 'todos'",
198
+ "sql.rowsCount": 0,
199
+ },
200
+ },
201
+ ],
202
+ },
203
+ {
204
+ "_name": "db:SELECT * FROM 'todos'",
205
+ "attributes": {
206
+ "livestore.debugRefreshReason": "commit",
207
+ "sql.query": "SELECT * FROM 'todos'",
208
+ "sql.rowsCount": 1,
209
+ },
210
+ "children": [
211
+ {
212
+ "_name": "sql-in-memory-select",
213
+ "attributes": {
214
+ "sql.cached": false,
215
+ "sql.query": "SELECT * FROM 'todos'",
216
+ "sql.rowsCount": 1,
217
+ },
218
+ },
219
+ ],
220
+ },
221
+ ],
222
+ },
223
+ ],
224
+ },
225
+ ],
226
+ }
227
+ `;
228
+
229
+ exports[`otel > QueryBuilder subscription - unsubscribe functionality 1`] = `
230
+ {
231
+ "_name": "createStore",
232
+ "attributes": {
233
+ "debugInstanceId": "test",
234
+ "storeId": "default",
235
+ },
236
+ "children": [
237
+ {
238
+ "_name": "livestore.in-memory-db:execute",
239
+ "attributes": {
240
+ "sql.query": "
241
+ PRAGMA page_size=32768;
242
+ PRAGMA cache_size=10000;
243
+ PRAGMA synchronous='OFF';
244
+ PRAGMA temp_store='MEMORY';
245
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
246
+ ",
247
+ },
248
+ },
249
+ {
250
+ "_name": "@livestore/common:LeaderSyncProcessor:push",
251
+ "attributes": {
252
+ "batch": "undefined",
253
+ "batchSize": 1,
254
+ },
255
+ },
256
+ {
257
+ "_name": "@livestore/common:LeaderSyncProcessor:push",
258
+ "attributes": {
259
+ "batch": "undefined",
260
+ "batchSize": 1,
261
+ },
262
+ },
263
+ {
264
+ "_name": "client-session-sync-processor:pull",
265
+ "attributes": {
266
+ "code.stacktrace": "<STACKTRACE>",
267
+ "span.label": "⚠︎ Interrupted",
268
+ "status.interrupted": true,
269
+ },
270
+ },
271
+ {
272
+ "_name": "LiveStore:sync",
273
+ },
274
+ {
275
+ "_name": "LiveStore:commits",
276
+ "children": [
277
+ {
278
+ "_name": "LiveStore:commit",
279
+ "attributes": {
280
+ "livestore.eventTags": [
281
+ "livestore.RawSql",
282
+ ],
283
+ "livestore.eventsCount": 1,
284
+ },
285
+ "children": [
286
+ {
287
+ "_name": "livestore.in-memory-db:execute",
288
+ "attributes": {
289
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t3', 'read book', 0)",
290
+ },
291
+ },
292
+ ],
293
+ },
294
+ {
295
+ "_name": "LiveStore:commit",
296
+ "attributes": {
297
+ "livestore.eventTags": [
298
+ "livestore.RawSql",
299
+ ],
300
+ "livestore.eventsCount": 1,
301
+ },
302
+ "children": [
303
+ {
304
+ "_name": "livestore.in-memory-db:execute",
305
+ "attributes": {
306
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t4', 'cook dinner', 0)",
307
+ },
308
+ },
309
+ ],
310
+ },
311
+ ],
312
+ },
313
+ {
314
+ "_name": "LiveStore:queries",
315
+ "children": [
316
+ {
317
+ "_name": "LiveStore.subscribe",
318
+ "attributes": {
319
+ "queryLabel": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
320
+ },
321
+ "children": [
322
+ {
323
+ "_name": "db:SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
324
+ "attributes": {
325
+ "livestore.debugRefreshReason": "subscribe-initial-run:undefined",
326
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
327
+ "sql.rowsCount": 0,
328
+ },
329
+ "children": [
330
+ {
331
+ "_name": "sql-in-memory-select",
332
+ "attributes": {
333
+ "sql.cached": false,
334
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
335
+ "sql.rowsCount": 0,
336
+ },
337
+ },
338
+ ],
339
+ },
340
+ {
341
+ "_name": "db:SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
342
+ "attributes": {
343
+ "livestore.debugRefreshReason": "commit",
344
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
345
+ "sql.rowsCount": 1,
346
+ },
347
+ "children": [
348
+ {
349
+ "_name": "sql-in-memory-select",
350
+ "attributes": {
351
+ "sql.cached": false,
352
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
353
+ "sql.rowsCount": 1,
354
+ },
355
+ },
356
+ ],
357
+ },
358
+ ],
359
+ },
360
+ {
361
+ "_name": "LiveStore.subscribe",
362
+ "attributes": {
363
+ "queryLabel": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
364
+ },
365
+ "children": [
366
+ {
367
+ "_name": "db:SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
368
+ "attributes": {
369
+ "livestore.debugRefreshReason": "commit",
370
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
371
+ "sql.rowsCount": 1,
372
+ },
373
+ "children": [
374
+ {
375
+ "_name": "sql-in-memory-select",
376
+ "attributes": {
377
+ "sql.cached": false,
378
+ "sql.query": "SELECT * FROM 'todos' WHERE completed = ? LIMIT ?",
379
+ "sql.rowsCount": 1,
380
+ },
381
+ },
382
+ ],
383
+ },
384
+ ],
385
+ },
386
+ ],
387
+ },
388
+ ],
389
+ }
390
+ `;
391
+
3
392
  exports[`otel > otel 3`] = `
4
393
  {
5
394
  "_name": "createStore",
@@ -34,7 +34,7 @@ export const makeExecBeforeFirstRun =
34
34
 
35
35
  const idVal = id === SessionIdSymbol ? store.sessionId : id!
36
36
  const rowExists =
37
- store.sqliteDbWrapper.select(
37
+ store.sqliteDbWrapper.cachedSelect(
38
38
  `SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`,
39
39
  [idVal] as any as PreparedBindValues,
40
40
  { otelContext },
@@ -155,7 +155,9 @@ Vitest.describe('otel', () => {
155
155
  const defaultTodo = { id: '', text: '', completed: false }
156
156
 
157
157
  const filter = computed(() => ({ completed: false }))
158
- const query$ = queryDb((get) => tables.todos.where(get(filter)).first({ fallback: () => defaultTodo }))
158
+ const query$ = queryDb((get) =>
159
+ tables.todos.where(get(filter)).first({ behaviour: 'fallback', fallback: () => defaultTodo }),
160
+ )
159
161
 
160
162
  expect(store.query(query$)).toMatchInlineSnapshot(`
161
163
  {
@@ -189,4 +191,145 @@ Vitest.describe('otel', () => {
189
191
  ),
190
192
  ),
191
193
  )
194
+
195
+ Vitest.scopedLive('QueryBuilder subscription - basic functionality', () =>
196
+ Effect.gen(function* () {
197
+ const { store, exporter, span, provider } = yield* makeQuery
198
+
199
+ const callbackResults: any[] = []
200
+ const defaultTodo = { id: '', text: '', completed: false }
201
+
202
+ const queryBuilder = tables.todos
203
+ .where({ completed: false })
204
+ .first({ behaviour: 'fallback', fallback: () => defaultTodo })
205
+
206
+ const unsubscribe = store.subscribe(queryBuilder, {
207
+ onUpdate: (result) => {
208
+ callbackResults.push(result)
209
+ },
210
+ })
211
+
212
+ expect(callbackResults).toHaveLength(1)
213
+ expect(callbackResults[0]).toMatchObject(defaultTodo)
214
+
215
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
216
+
217
+ expect(callbackResults).toHaveLength(2)
218
+ expect(callbackResults[1]).toMatchObject({
219
+ id: 't1',
220
+ text: 'buy milk',
221
+ completed: false,
222
+ })
223
+
224
+ unsubscribe()
225
+ span.end()
226
+
227
+ return { exporter, provider }
228
+ }).pipe(
229
+ Effect.scoped,
230
+ Effect.tap(({ exporter, provider }) =>
231
+ Effect.promise(async () => {
232
+ await provider.forceFlush()
233
+ expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
234
+ await provider.shutdown()
235
+ }),
236
+ ),
237
+ ),
238
+ )
239
+
240
+ Vitest.scopedLive('QueryBuilder subscription - unsubscribe functionality', () =>
241
+ Effect.gen(function* () {
242
+ const { store, exporter, span, provider } = yield* makeQuery
243
+
244
+ const callbackResults1: any[] = []
245
+ const callbackResults2: any[] = []
246
+ const defaultTodo = { id: '', text: '', completed: false }
247
+
248
+ const queryBuilder = tables.todos
249
+ .where({ completed: false })
250
+ .first({ behaviour: 'fallback', fallback: () => defaultTodo })
251
+
252
+ const unsubscribe1 = store.subscribe(queryBuilder, {
253
+ onUpdate: (result) => {
254
+ callbackResults1.push(result)
255
+ },
256
+ })
257
+
258
+ const unsubscribe2 = store.subscribe(queryBuilder, {
259
+ onUpdate: (result) => {
260
+ callbackResults2.push(result)
261
+ },
262
+ })
263
+
264
+ expect(callbackResults1).toHaveLength(1)
265
+ expect(callbackResults2).toHaveLength(1)
266
+
267
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t3', 'read book', 0)` }))
268
+
269
+ expect(callbackResults1).toHaveLength(2)
270
+ expect(callbackResults2).toHaveLength(2)
271
+
272
+ unsubscribe1()
273
+
274
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t4', 'cook dinner', 0)` }))
275
+
276
+ expect(callbackResults1).toHaveLength(2)
277
+ expect(callbackResults2).toHaveLength(3)
278
+
279
+ unsubscribe2()
280
+ span.end()
281
+
282
+ return { exporter, provider }
283
+ }).pipe(
284
+ Effect.scoped,
285
+ Effect.tap(({ exporter, provider }) =>
286
+ Effect.promise(async () => {
287
+ await provider.forceFlush()
288
+ expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
289
+ await provider.shutdown()
290
+ }),
291
+ ),
292
+ ),
293
+ )
294
+
295
+ Vitest.scopedLive('QueryBuilder subscription - direct table subscription', () =>
296
+ Effect.gen(function* () {
297
+ const { store, exporter, span, provider } = yield* makeQuery
298
+
299
+ const callbackResults: any[] = []
300
+
301
+ const unsubscribe = store.subscribe(tables.todos, {
302
+ onUpdate: (result) => {
303
+ callbackResults.push(result)
304
+ },
305
+ })
306
+
307
+ expect(callbackResults).toHaveLength(1)
308
+ expect(callbackResults[0]).toEqual([])
309
+
310
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t5', 'clean house', 1)` }))
311
+
312
+ expect(callbackResults).toHaveLength(2)
313
+ expect(callbackResults[1]).toHaveLength(1)
314
+ expect(callbackResults[1][0]).toMatchObject({
315
+ id: 't5',
316
+ text: 'clean house',
317
+ completed: true,
318
+ })
319
+
320
+ unsubscribe()
321
+ span.end()
322
+
323
+ return { exporter, provider }
324
+ }).pipe(
325
+ Effect.scoped,
326
+ Effect.tap(({ exporter, provider }) =>
327
+ Effect.promise(async () => {
328
+ await provider.forceFlush()
329
+ expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
330
+ await provider.shutdown()
331
+ }),
332
+ ),
333
+ ),
334
+ )
192
335
  })
@@ -372,7 +372,7 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
372
372
  span.setAttribute('sql.query', sqlString)
373
373
  span.updateName(`db:${sqlString.slice(0, 50)}`)
374
374
 
375
- const rawDbResults = store.sqliteDbWrapper.select<any>(
375
+ const rawDbResults = store.sqliteDbWrapper.cachedSelect<any>(
376
376
  sqlString,
377
377
  bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
378
378
  {