@livestore/livestore 0.0.0-snapshot-5e5bc344e2b3e234a08cf85e4dc890053d91b170 → 0.0.0-snapshot-3d6080d88e57e27247dc7f7d5252e86944494dfd

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.
@@ -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",
@@ -189,4 +189,141 @@ Vitest.describe('otel', () => {
189
189
  ),
190
190
  ),
191
191
  )
192
+
193
+ Vitest.scopedLive('QueryBuilder subscription - basic functionality', () =>
194
+ Effect.gen(function* () {
195
+ const { store, exporter, span, provider } = yield* makeQuery
196
+
197
+ const callbackResults: any[] = []
198
+ const defaultTodo = { id: '', text: '', completed: false }
199
+
200
+ const queryBuilder = tables.todos.where({ completed: false }).first({ fallback: () => defaultTodo })
201
+
202
+ const unsubscribe = store.subscribe(queryBuilder, {
203
+ onUpdate: (result) => {
204
+ callbackResults.push(result)
205
+ },
206
+ })
207
+
208
+ expect(callbackResults).toHaveLength(1)
209
+ expect(callbackResults[0]).toMatchObject(defaultTodo)
210
+
211
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
212
+
213
+ expect(callbackResults).toHaveLength(2)
214
+ expect(callbackResults[1]).toMatchObject({
215
+ id: 't1',
216
+ text: 'buy milk',
217
+ completed: false,
218
+ })
219
+
220
+ unsubscribe()
221
+ span.end()
222
+
223
+ return { exporter, provider }
224
+ }).pipe(
225
+ Effect.scoped,
226
+ Effect.tap(({ exporter, provider }) =>
227
+ Effect.promise(async () => {
228
+ await provider.forceFlush()
229
+ expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
230
+ await provider.shutdown()
231
+ }),
232
+ ),
233
+ ),
234
+ )
235
+
236
+ Vitest.scopedLive('QueryBuilder subscription - unsubscribe functionality', () =>
237
+ Effect.gen(function* () {
238
+ const { store, exporter, span, provider } = yield* makeQuery
239
+
240
+ const callbackResults1: any[] = []
241
+ const callbackResults2: any[] = []
242
+ const defaultTodo = { id: '', text: '', completed: false }
243
+
244
+ const queryBuilder = tables.todos.where({ completed: false }).first({ fallback: () => defaultTodo })
245
+
246
+ const unsubscribe1 = store.subscribe(queryBuilder, {
247
+ onUpdate: (result) => {
248
+ callbackResults1.push(result)
249
+ },
250
+ })
251
+
252
+ const unsubscribe2 = store.subscribe(queryBuilder, {
253
+ onUpdate: (result) => {
254
+ callbackResults2.push(result)
255
+ },
256
+ })
257
+
258
+ expect(callbackResults1).toHaveLength(1)
259
+ expect(callbackResults2).toHaveLength(1)
260
+
261
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t3', 'read book', 0)` }))
262
+
263
+ expect(callbackResults1).toHaveLength(2)
264
+ expect(callbackResults2).toHaveLength(2)
265
+
266
+ unsubscribe1()
267
+
268
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t4', 'cook dinner', 0)` }))
269
+
270
+ expect(callbackResults1).toHaveLength(2)
271
+ expect(callbackResults2).toHaveLength(3)
272
+
273
+ unsubscribe2()
274
+ span.end()
275
+
276
+ return { exporter, provider }
277
+ }).pipe(
278
+ Effect.scoped,
279
+ Effect.tap(({ exporter, provider }) =>
280
+ Effect.promise(async () => {
281
+ await provider.forceFlush()
282
+ expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
283
+ await provider.shutdown()
284
+ }),
285
+ ),
286
+ ),
287
+ )
288
+
289
+ Vitest.scopedLive('QueryBuilder subscription - direct table subscription', () =>
290
+ Effect.gen(function* () {
291
+ const { store, exporter, span, provider } = yield* makeQuery
292
+
293
+ const callbackResults: any[] = []
294
+
295
+ const unsubscribe = store.subscribe(tables.todos, {
296
+ onUpdate: (result) => {
297
+ callbackResults.push(result)
298
+ },
299
+ })
300
+
301
+ expect(callbackResults).toHaveLength(1)
302
+ expect(callbackResults[0]).toEqual([])
303
+
304
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t5', 'clean house', 1)` }))
305
+
306
+ expect(callbackResults).toHaveLength(2)
307
+ expect(callbackResults[1]).toHaveLength(1)
308
+ expect(callbackResults[1][0]).toMatchObject({
309
+ id: 't5',
310
+ text: 'clean house',
311
+ completed: true,
312
+ })
313
+
314
+ unsubscribe()
315
+ span.end()
316
+
317
+ return { exporter, provider }
318
+ }).pipe(
319
+ Effect.scoped,
320
+ Effect.tap(({ exporter, provider }) =>
321
+ Effect.promise(async () => {
322
+ await provider.forceFlush()
323
+ expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
324
+ await provider.shutdown()
325
+ }),
326
+ ),
327
+ ),
328
+ )
192
329
  })
@@ -37,6 +37,7 @@ import type {
37
37
  } from '../live-queries/base-class.js'
38
38
  import { makeReactivityGraph } from '../live-queries/base-class.js'
39
39
  import { makeExecBeforeFirstRun } from '../live-queries/client-document-get-query.js'
40
+ import { queryDb } from '../live-queries/db-query.js'
40
41
  import type { Ref } from '../reactive.js'
41
42
  import { SqliteDbWrapper } from '../SqliteDbWrapper.js'
42
43
  import { ReferenceCountedSet } from '../utils/data-structures.js'
@@ -290,7 +291,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
290
291
  * ```
291
292
  */
292
293
  subscribe = <TResult>(
293
- query: LiveQueryDef<TResult, 'def' | 'signal-def'> | LiveQuery<TResult>,
294
+ query: LiveQueryDef<TResult, 'def' | 'signal-def'> | LiveQuery<TResult> | QueryBuilder<TResult, any, any>,
294
295
  options: {
295
296
  /** Called when the query result has changed */
296
297
  onUpdate: (value: TResult) => void
@@ -310,14 +311,15 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
310
311
  ): Unsubscribe =>
311
312
  this.otel.tracer.startActiveSpan(
312
313
  `LiveStore.subscribe`,
313
- { attributes: { label: options?.label, queryLabel: query.label } },
314
+ { attributes: { label: options?.label, queryLabel: isQueryBuilder(query) ? query.toString() : query.label } },
314
315
  options?.otelContext ?? this.otel.queriesSpanContext,
315
316
  (span) => {
316
317
  // console.debug('store sub', query$.id, query$.label)
317
318
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
318
319
 
319
- const queryRcRef =
320
- query._tag === 'def' || query._tag === 'signal-def'
320
+ const queryRcRef = isQueryBuilder(query)
321
+ ? queryDb(query).make(this.reactivityGraph.context!)
322
+ : query._tag === 'def' || query._tag === 'signal-def'
321
323
  ? query.make(this.reactivityGraph.context!)
322
324
  : {
323
325
  value: query as LiveQuery<TResult>,