@livestore/livestore 0.0.54-dev.26 → 0.0.54-dev.28

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 (70) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/QueryCache.d.ts.map +1 -1
  3. package/dist/__tests__/react/fixture.d.ts +5 -15
  4. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  5. package/dist/__tests__/react/fixture.js +2 -2
  6. package/dist/__tests__/react/fixture.js.map +1 -1
  7. package/dist/effect/LiveStore.d.ts +15 -8
  8. package/dist/effect/LiveStore.d.ts.map +1 -1
  9. package/dist/effect/LiveStore.js +15 -16
  10. package/dist/effect/LiveStore.js.map +1 -1
  11. package/dist/effect/index.d.ts +1 -1
  12. package/dist/effect/index.d.ts.map +1 -1
  13. package/dist/effect/index.js +1 -1
  14. package/dist/effect/index.js.map +1 -1
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/react/LiveStoreContext.d.ts +5 -2
  20. package/dist/react/LiveStoreContext.d.ts.map +1 -1
  21. package/dist/react/LiveStoreContext.js.map +1 -1
  22. package/dist/react/LiveStoreProvider.d.ts +3 -2
  23. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  24. package/dist/react/LiveStoreProvider.js +63 -39
  25. package/dist/react/LiveStoreProvider.js.map +1 -1
  26. package/dist/react/LiveStoreProvider.test.js +28 -9
  27. package/dist/react/LiveStoreProvider.test.js.map +1 -1
  28. package/dist/react/components/LiveList.d.ts.map +1 -1
  29. package/dist/react/useAtom.d.ts +1 -1
  30. package/dist/react/useAtom.d.ts.map +1 -1
  31. package/dist/react/useLocalId.d.ts.map +1 -1
  32. package/dist/react/useQuery.d.ts.map +1 -1
  33. package/dist/react/useQuery.js +2 -2
  34. package/dist/react/useQuery.js.map +1 -1
  35. package/dist/react/useRow.d.ts.map +1 -1
  36. package/dist/react/useRow.js +2 -2
  37. package/dist/react/useRow.js.map +1 -1
  38. package/dist/react/useRow.test.js +1 -1
  39. package/dist/react/useRow.test.js.map +1 -1
  40. package/dist/react/useTemporaryQuery.d.ts.map +1 -1
  41. package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
  42. package/dist/reactive.d.ts +1 -1
  43. package/dist/reactive.d.ts.map +1 -1
  44. package/dist/reactive.js +3 -4
  45. package/dist/reactive.js.map +1 -1
  46. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  47. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  48. package/dist/reactiveQueries/js.d.ts.map +1 -1
  49. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  50. package/dist/reactiveQueries/sql.test.js +6 -6
  51. package/dist/reactiveQueries/sql.test.js.map +1 -1
  52. package/dist/store.d.ts +12 -5
  53. package/dist/store.d.ts.map +1 -1
  54. package/dist/store.js +59 -122
  55. package/dist/store.js.map +1 -1
  56. package/dist/utils/otel.d.ts.map +1 -1
  57. package/package.json +8 -10
  58. package/src/__tests__/react/fixture.tsx +2 -2
  59. package/src/effect/LiveStore.ts +50 -43
  60. package/src/effect/index.ts +2 -1
  61. package/src/index.ts +7 -3
  62. package/src/react/LiveStoreContext.ts +3 -2
  63. package/src/react/LiveStoreProvider.test.tsx +47 -10
  64. package/src/react/LiveStoreProvider.tsx +95 -38
  65. package/src/react/useQuery.ts +2 -2
  66. package/src/react/useRow.test.tsx +1 -1
  67. package/src/react/useRow.ts +2 -5
  68. package/src/reactive.ts +3 -4
  69. package/src/reactiveQueries/sql.test.ts +6 -6
  70. package/src/store.ts +243 -286
package/src/store.ts CHANGED
@@ -6,14 +6,31 @@ import type {
6
6
  ResetMode,
7
7
  StoreAdapter,
8
8
  StoreAdapterFactory,
9
+ } from '@livestore/common'
10
+ import {
11
+ Devtools,
12
+ getExecArgsFromMutation,
13
+ liveStoreVersion,
14
+ prepareBindValues,
9
15
  UnexpectedError,
10
16
  } from '@livestore/common'
11
- import { Devtools, getExecArgsFromMutation, liveStoreVersion, prepareBindValues } from '@livestore/common'
12
17
  import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
13
18
  import { makeMutationEventSchemaMemo } from '@livestore/common/schema'
14
19
  import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen, throttle } from '@livestore/utils'
15
20
  import { cuid } from '@livestore/utils/cuid'
16
- import { Effect, Layer, Logger, LogLevel, OtelTracer, Queue, Schema, Stream } from '@livestore/utils/effect'
21
+ import {
22
+ Effect,
23
+ Exit,
24
+ Layer,
25
+ Logger,
26
+ LogLevel,
27
+ OtelTracer,
28
+ Queue,
29
+ Runtime,
30
+ Schema,
31
+ Scope,
32
+ Stream,
33
+ } from '@livestore/utils/effect'
17
34
  import * as otel from '@opentelemetry/api'
18
35
  import type { GraphQLSchema } from 'graphql'
19
36
 
@@ -53,6 +70,7 @@ export type StoreOptions<
53
70
  otelOptions: OtelOptions
54
71
  reactivityGraph: ReactivityGraph
55
72
  disableDevtools?: boolean
73
+ storeScope: Scope.CloseableScope
56
74
  // TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
57
75
  __processedMutationIds: Set<string>
58
76
  }
@@ -110,6 +128,7 @@ export class Store<
110
128
  > {
111
129
  id = uniqueStoreId()
112
130
  readonly devtoolsConnectionId = cuid()
131
+ private storeScope: Scope.CloseableScope
113
132
  reactivityGraph: ReactivityGraph
114
133
  mainDbWrapper: MainDatabaseWrapper
115
134
  adapter: StoreAdapter
@@ -140,11 +159,14 @@ export class Store<
140
159
  otelOptions,
141
160
  disableDevtools,
142
161
  __processedMutationIds,
162
+ storeScope,
143
163
  }: StoreOptions<TGraphQLContext, TSchema>) {
144
164
  this.mainDbWrapper = new MainDatabaseWrapper({ otel: otelOptions, db: adapter.mainDb })
145
165
  this.adapter = adapter
146
166
  this.schema = schema
147
167
 
168
+ this.storeScope = storeScope
169
+
148
170
  // TODO refactor
149
171
  this.__mutationEventSchema = makeMutationEventSchemaMemo(schema)
150
172
 
@@ -168,18 +190,6 @@ export class Store<
168
190
  rootOtelContext: otelQueriesSpanContext,
169
191
  }
170
192
 
171
- this.adapter.coordinator.syncMutations.pipe(
172
- Stream.tapSync((mutationEventDecoded) => {
173
- this.mutate({ wasSyncMessage: true }, mutationEventDecoded)
174
- }),
175
- Stream.runDrain,
176
- runEffectFork,
177
- )
178
-
179
- if (disableDevtools !== true) {
180
- this.bootDevtools()
181
- }
182
-
183
193
  this.otel = {
184
194
  tracer: otelOptions.tracer,
185
195
  mutationsSpanContext: otelMuationsSpanContext,
@@ -201,6 +211,34 @@ export class Store<
201
211
  this.graphQLSchema = graphQLOptions.schema
202
212
  this.graphQLContext = graphQLOptions.makeContext(this.mainDbWrapper, this.otel.tracer)
203
213
  }
214
+
215
+ Effect.gen(this, function* () {
216
+ yield* this.adapter.coordinator.syncMutations.pipe(
217
+ Stream.tapSync((mutationEventDecoded) => {
218
+ this.mutate({ wasSyncMessage: true }, mutationEventDecoded)
219
+ }),
220
+ Stream.runDrain,
221
+ Effect.withSpan('LiveStore:syncMutations'),
222
+ Effect.forkScoped,
223
+ )
224
+
225
+ if (disableDevtools !== true) {
226
+ yield* this.bootDevtools().pipe(Effect.forkScoped)
227
+ }
228
+
229
+ yield* Effect.addFinalizer(() =>
230
+ Effect.sync(() => {
231
+ for (const tableRef of Object.values(this.tableRefs)) {
232
+ for (const superComp of tableRef.super) {
233
+ this.reactivityGraph.removeEdge(superComp, tableRef)
234
+ }
235
+ }
236
+
237
+ otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
238
+ otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
239
+ }),
240
+ )
241
+ }).pipe(Scope.extend(storeScope), Effect.forkIn(storeScope), Effect.scoped, runEffectFork)
204
242
  }
205
243
 
206
244
  static createStore = <TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema = LiveStoreSchema>(
@@ -266,16 +304,7 @@ export class Store<
266
304
  * Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
267
305
  */
268
306
  destroy = async () => {
269
- for (const tableRef of Object.values(this.tableRefs)) {
270
- for (const superComp of tableRef.super) {
271
- this.reactivityGraph.removeEdge(superComp, tableRef)
272
- }
273
- }
274
-
275
- otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
276
- otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
277
-
278
- await this.adapter.coordinator.shutdown.pipe(runEffectPromise)
307
+ await Scope.close(this.storeScope, Exit.void).pipe(Effect.withSpan('Store:destroy'), runEffectPromise)
279
308
  }
280
309
 
281
310
  mutate: {
@@ -335,6 +364,8 @@ export class Store<
335
364
  // mutationsEvents.forEach((_) => console.log(_.mutation, _.id, _.args))
336
365
  // console.groupEnd()
337
366
 
367
+ let durationMs: number
368
+
338
369
  return this.otel.tracer.startActiveSpan(
339
370
  'LiveStore:mutate',
340
371
  { attributes: { 'livestore.mutateLabel': label } },
@@ -365,8 +396,8 @@ export class Store<
365
396
  writeTables.add(tableName)
366
397
  }
367
398
  } catch (e: any) {
368
- debugger
369
399
  console.error(e, mutationEvent)
400
+ throw e
370
401
  }
371
402
  }
372
403
  }
@@ -380,6 +411,7 @@ export class Store<
380
411
  } catch (e: any) {
381
412
  console.error(e)
382
413
  span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
414
+ throw e
383
415
  } finally {
384
416
  span.end()
385
417
  }
@@ -402,12 +434,16 @@ export class Store<
402
434
  // Update all table refs together in a batch, to only trigger one reactive update
403
435
  this.reactivityGraph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext, skipRefresh })
404
436
  } catch (e: any) {
437
+ console.error(e)
405
438
  span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
439
+ throw e
406
440
  } finally {
407
441
  span.end()
408
442
 
409
- return { durationMs: getDurationMsFromSpan(span) }
443
+ durationMs = getDurationMsFromSpan(span)
410
444
  }
445
+
446
+ return { durationMs }
411
447
  },
412
448
  )
413
449
  }
@@ -535,268 +571,166 @@ export class Store<
535
571
  })
536
572
 
537
573
  // TODO shutdown behaviour
538
- private bootDevtools = () => {
539
- const sendToDevtoolsContentscript = (
540
- message: typeof Devtools.DevtoolsWindowMessage.MessageForContentscript.Type,
541
- ) => {
542
- window.postMessage(Schema.encodeSync(Devtools.DevtoolsWindowMessage.MessageForContentscript)(message), '*')
543
- }
574
+ private bootDevtools = () =>
575
+ Effect.gen(this, function* () {
576
+ const sendToDevtoolsContentscript = (
577
+ message: typeof Devtools.DevtoolsWindowMessage.MessageForContentscript.Type,
578
+ ) => {
579
+ window.postMessage(Schema.encodeSync(Devtools.DevtoolsWindowMessage.MessageForContentscript)(message), '*')
580
+ }
544
581
 
545
- const channelId = this.adapter.coordinator.devtools.channelId
582
+ sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.LoadIframe.make({}))
546
583
 
547
- window.addEventListener('message', (event) => {
548
- const decodedMessageRes = Schema.decodeOption(Devtools.DevtoolsWindowMessage.MessageForStore)(event.data)
549
- if (decodedMessageRes._tag === 'None') return
584
+ const channelId = this.adapter.coordinator.devtools.channelId
550
585
 
551
- const message = decodedMessageRes.value
586
+ window.addEventListener('message', (event) => {
587
+ const decodedMessageRes = Schema.decodeOption(Devtools.DevtoolsWindowMessage.MessageForStore)(event.data)
588
+ if (decodedMessageRes._tag === 'None') return
552
589
 
553
- if (message._tag === 'LSD.WindowMessage.ContentscriptListening') {
554
- sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
555
- return
556
- }
590
+ const message = decodedMessageRes.value
557
591
 
558
- if (message.channelId !== channelId) return
592
+ if (message._tag === 'LSD.WindowMessage.ContentscriptListening') {
593
+ sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
594
+ return
595
+ }
559
596
 
560
- if (message._tag === 'LSD.WindowMessage.MessagePortForStore') {
561
- this.adapter.coordinator.devtools.connect({ port: message.port, connectionId: this.devtoolsConnectionId }).pipe(
562
- Effect.tapSync(({ storeMessagePort }) => {
563
- // console.log('storeMessagePort', storeMessagePort)
564
- storeMessagePort.addEventListener('message', (event) => {
565
- const decodedMessage = Schema.decodeUnknownSync(Devtools.MessageToAppHostStore)(event.data)
566
- // console.log('storeMessagePort message', decodedMessage)
597
+ if (message.channelId !== channelId) return
567
598
 
568
- if (decodedMessage.channelId !== this.adapter.coordinator.devtools.channelId) {
569
- // console.log(`Unknown message`, event)
570
- return
571
- }
599
+ if (message._tag === 'LSD.WindowMessage.MessagePortForStore') {
600
+ type Unsub = () => void
601
+ type RequestId = string
572
602
 
573
- const requestId = decodedMessage.requestId
574
- const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
575
- storeMessagePort.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message))
576
-
577
- const requestIdleCallback = window.requestIdleCallback ?? ((cb: Function) => cb())
578
-
579
- switch (decodedMessage._tag) {
580
- case 'LSD.ReactivityGraphSubscribe': {
581
- const includeResults = decodedMessage.includeResults
582
-
583
- const send = () =>
584
- // In order to not add more work to the current tick, we use requestIdleCallback
585
- // to send the reactivity graph updates to the devtools
586
- requestIdleCallback(
587
- () =>
588
- sendToDevtools(
589
- Devtools.ReactivityGraphRes.make({
590
- reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
591
- requestId,
592
- liveStoreVersion,
593
- }),
594
- ),
595
- { timeout: 500 },
596
- )
597
-
598
- send()
599
-
600
- // In some cases, there can be A LOT of reactivity graph updates in a short period of time
601
- // so we throttle the updates to avoid sending too much data
602
- // This might need to be tweaked further and possibly be exposed to the user in some way.
603
- const throttledSend = throttle(send, 20)
604
-
605
- reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
606
-
607
- break
608
- }
609
- case 'LSD.DebugInfoReq': {
610
- sendToDevtools(
611
- Devtools.DebugInfoRes.make({
612
- debugInfo: this.mainDbWrapper.debugInfo,
613
- requestId,
614
- liveStoreVersion,
615
- }),
616
- )
617
- break
618
- }
619
- case 'LSD.DebugInfoResetReq': {
620
- this.mainDbWrapper.debugInfo.slowQueries.clear()
621
- sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
622
- break
623
- }
624
- case 'LSD.DebugInfoRerunQueryReq': {
625
- const { queryStr, bindValues, queriedTables } = decodedMessage
626
- this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
627
- sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
628
- break
629
- }
630
- case 'LSD.ReactivityGraphUnsubscribe': {
631
- reactivityGraphSubcriptions.get(requestId)!()
632
- break
633
- }
634
- case 'LSD.LiveQueriesSubscribe': {
635
- const send = () =>
636
- requestIdleCallback(
637
- () =>
638
- sendToDevtools(
639
- Devtools.LiveQueriesRes.make({
640
- liveQueries: [...this.activeQueries].map((q) => ({
641
- _tag: q._tag,
642
- id: q.id,
643
- label: q.label,
644
- runs: q.runs,
645
- executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
646
- lastestResult:
647
- q.results$.previousResult === NOT_REFRESHED_YET
648
- ? 'SYMBOL_NOT_REFRESHED_YET'
649
- : q.results$.previousResult,
650
- activeSubscriptions: Array.from(q.activeSubscriptions),
651
- })),
652
- requestId,
653
- liveStoreVersion,
654
- }),
655
- ),
656
- { timeout: 500 },
657
- )
658
-
659
- send()
660
-
661
- // Same as in the reactivity graph subscription case above, we need to throttle the updates
662
- const throttledSend = throttle(send, 20)
663
-
664
- liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
665
-
666
- break
667
- }
668
- case 'LSD.LiveQueriesUnsubscribe': {
669
- liveQueriesSubscriptions.get(requestId)!()
670
- break
671
- }
672
- // No default
673
- }
674
- })
603
+ const reactivityGraphSubcriptions = new Map<RequestId, Unsub>()
604
+ const liveQueriesSubscriptions = new Map<RequestId, Unsub>()
675
605
 
676
- storeMessagePort.start()
677
- }),
678
- runEffectFork,
679
- )
606
+ this.adapter.coordinator.devtools
607
+ .connect({ port: message.port, connectionId: this.devtoolsConnectionId })
608
+ .pipe(
609
+ Effect.tapSync(({ storeMessagePort }) => {
610
+ // console.log('storeMessagePort', storeMessagePort)
611
+ storeMessagePort.addEventListener('message', (event) => {
612
+ const decodedMessage = Schema.decodeUnknownSync(Devtools.MessageToAppHostStore)(event.data)
613
+ // console.log('storeMessagePort message', decodedMessage)
680
614
 
681
- return
682
- }
683
- })
615
+ if (decodedMessage.channelId !== this.adapter.coordinator.devtools.channelId) {
616
+ // console.log(`Unknown message`, event)
617
+ return
618
+ }
684
619
 
685
- sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
686
-
687
- // const devtoolsChannel = Devtools.makeBroadcastChannels()
688
-
689
- type Unsub = () => void
690
- type RequestId = string
691
-
692
- const reactivityGraphSubcriptions = new Map<RequestId, Unsub>()
693
- const liveQueriesSubscriptions = new Map<RequestId, Unsub>()
694
- // devtoolsChannel.toAppHost.addEventListener('message', async (event) => {
695
- // const decoded = Schema.decodeUnknownOption(Devtools.MessageToAppHostStore)(event.data)
696
- // if (decoded._tag === 'None' || decoded.value.channelId !== this.adapter.coordinator.devtools.channelId) {
697
- // // console.log(`Unknown message`, event)
698
- // return
699
- // }
700
-
701
- // const requestId = decoded.value.requestId
702
- // const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
703
- // devtoolsChannel.fromAppHost.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message))
704
-
705
- // const requestIdleCallback = window.requestIdleCallback ?? ((cb: Function) => cb())
706
-
707
- // switch (decoded.value._tag) {
708
- // case 'LSD.ReactivityGraphSubscribe': {
709
- // const includeResults = decoded.value.includeResults
710
-
711
- // const send = () =>
712
- // // In order to not add more work to the current tick, we use requestIdleCallback
713
- // // to send the reactivity graph updates to the devtools
714
- // requestIdleCallback(
715
- // () =>
716
- // sendToDevtools(
717
- // Devtools.ReactivityGraphRes.make({
718
- // reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
719
- // requestId,
720
- // liveStoreVersion,
721
- // }),
722
- // ),
723
- // { timeout: 500 },
724
- // )
725
-
726
- // send()
727
-
728
- // // In some cases, there can be A LOT of reactivity graph updates in a short period of time
729
- // // so we throttle the updates to avoid sending too much data
730
- // // This might need to be tweaked further and possibly be exposed to the user in some way.
731
- // const throttledSend = throttle(send, 20)
732
-
733
- // reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
734
-
735
- // break
736
- // }
737
- // case 'LSD.DebugInfoReq': {
738
- // sendToDevtools(
739
- // Devtools.DebugInfoRes.make({ debugInfo: this.mainDbWrapper.debugInfo, requestId, liveStoreVersion }),
740
- // )
741
- // break
742
- // }
743
- // case 'LSD.DebugInfoResetReq': {
744
- // this.mainDbWrapper.debugInfo.slowQueries.clear()
745
- // sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
746
- // break
747
- // }
748
- // case 'LSD.DebugInfoRerunQueryReq': {
749
- // const { queryStr, bindValues, queriedTables } = decoded.value
750
- // this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
751
- // sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
752
- // break
753
- // }
754
- // case 'LSD.ReactivityGraphUnsubscribe': {
755
- // reactivityGraphSubcriptions.get(requestId)!()
756
- // break
757
- // }
758
- // case 'LSD.LiveQueriesSubscribe': {
759
- // const send = () =>
760
- // requestIdleCallback(
761
- // () =>
762
- // sendToDevtools(
763
- // Devtools.LiveQueriesRes.make({
764
- // liveQueries: [...this.activeQueries].map((q) => ({
765
- // _tag: q._tag,
766
- // id: q.id,
767
- // label: q.label,
768
- // runs: q.runs,
769
- // executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
770
- // lastestResult:
771
- // q.results$.previousResult === NOT_REFRESHED_YET
772
- // ? 'SYMBOL_NOT_REFRESHED_YET'
773
- // : q.results$.previousResult,
774
- // activeSubscriptions: Array.from(q.activeSubscriptions),
775
- // })),
776
- // requestId,
777
- // liveStoreVersion,
778
- // }),
779
- // ),
780
- // { timeout: 500 },
781
- // )
782
-
783
- // send()
784
-
785
- // // Same as in the reactivity graph subscription case above, we need to throttle the updates
786
- // const throttledSend = throttle(send, 20)
787
-
788
- // liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
789
-
790
- // break
791
- // }
792
- // case 'LSD.LiveQueriesUnsubscribe': {
793
- // liveQueriesSubscriptions.get(requestId)!()
794
- // break
795
- // }
796
- // // No default
797
- // }
798
- // })
799
- }
620
+ const requestId = decodedMessage.requestId
621
+ const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
622
+ storeMessagePort.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message))
623
+
624
+ const requestIdleCallback = window.requestIdleCallback ?? ((cb: Function) => cb())
625
+
626
+ switch (decodedMessage._tag) {
627
+ case 'LSD.ReactivityGraphSubscribe': {
628
+ const includeResults = decodedMessage.includeResults
629
+
630
+ const send = () =>
631
+ // In order to not add more work to the current tick, we use requestIdleCallback
632
+ // to send the reactivity graph updates to the devtools
633
+ requestIdleCallback(
634
+ () =>
635
+ sendToDevtools(
636
+ Devtools.ReactivityGraphRes.make({
637
+ reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
638
+ requestId,
639
+ liveStoreVersion,
640
+ }),
641
+ ),
642
+ { timeout: 500 },
643
+ )
644
+
645
+ send()
646
+
647
+ // In some cases, there can be A LOT of reactivity graph updates in a short period of time
648
+ // so we throttle the updates to avoid sending too much data
649
+ // This might need to be tweaked further and possibly be exposed to the user in some way.
650
+ const throttledSend = throttle(send, 20)
651
+
652
+ reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
653
+
654
+ break
655
+ }
656
+ case 'LSD.DebugInfoReq': {
657
+ sendToDevtools(
658
+ Devtools.DebugInfoRes.make({
659
+ debugInfo: this.mainDbWrapper.debugInfo,
660
+ requestId,
661
+ liveStoreVersion,
662
+ }),
663
+ )
664
+ break
665
+ }
666
+ case 'LSD.DebugInfoResetReq': {
667
+ this.mainDbWrapper.debugInfo.slowQueries.clear()
668
+ sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
669
+ break
670
+ }
671
+ case 'LSD.DebugInfoRerunQueryReq': {
672
+ const { queryStr, bindValues, queriedTables } = decodedMessage
673
+ this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
674
+ sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
675
+ break
676
+ }
677
+ case 'LSD.ReactivityGraphUnsubscribe': {
678
+ reactivityGraphSubcriptions.get(requestId)!()
679
+ break
680
+ }
681
+ case 'LSD.LiveQueriesSubscribe': {
682
+ const send = () =>
683
+ requestIdleCallback(
684
+ () =>
685
+ sendToDevtools(
686
+ Devtools.LiveQueriesRes.make({
687
+ liveQueries: [...this.activeQueries].map((q) => ({
688
+ _tag: q._tag,
689
+ id: q.id,
690
+ label: q.label,
691
+ runs: q.runs,
692
+ executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
693
+ lastestResult:
694
+ q.results$.previousResult === NOT_REFRESHED_YET
695
+ ? 'SYMBOL_NOT_REFRESHED_YET'
696
+ : q.results$.previousResult,
697
+ activeSubscriptions: Array.from(q.activeSubscriptions),
698
+ })),
699
+ requestId,
700
+ liveStoreVersion,
701
+ }),
702
+ ),
703
+ { timeout: 500 },
704
+ )
705
+
706
+ send()
707
+
708
+ // Same as in the reactivity graph subscription case above, we need to throttle the updates
709
+ const throttledSend = throttle(send, 20)
710
+
711
+ liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
712
+
713
+ break
714
+ }
715
+ case 'LSD.LiveQueriesUnsubscribe': {
716
+ liveQueriesSubscriptions.get(requestId)!()
717
+ break
718
+ }
719
+ // No default
720
+ }
721
+ })
722
+
723
+ storeMessagePort.start()
724
+ }),
725
+ runEffectFork,
726
+ )
727
+
728
+ return
729
+ }
730
+ })
731
+
732
+ sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
733
+ })
800
734
 
801
735
  __devDownloadDb = () => {
802
736
  const data = this.mainDbWrapper.export()
@@ -825,14 +759,27 @@ export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSche
825
759
  }
826
760
 
827
761
  /** Create a new LiveStore Store */
828
- export const createStore = async <
762
+ export const createStorePromise = async <
829
763
  TGraphQLContext extends BaseGraphQLContext,
830
764
  TSchema extends LiveStoreSchema = LiveStoreSchema,
831
- >(
832
- options: CreateStoreOptions<TGraphQLContext, TSchema>,
833
- ): Promise<Store<TGraphQLContext, TSchema>> => createStoreEff(options).pipe(runEffectPromise)
765
+ >({
766
+ signal,
767
+ ...options
768
+ }: CreateStoreOptions<TGraphQLContext, TSchema> & { signal?: AbortSignal }): Promise<Store<TGraphQLContext, TSchema>> =>
769
+ Effect.gen(function* () {
770
+ const scope = yield* Scope.make()
771
+ const runtime = yield* Effect.runtime()
772
+
773
+ if (signal !== undefined) {
774
+ signal.addEventListener('abort', () => {
775
+ Scope.close(scope, Exit.void).pipe(Effect.tapCauseLogPretty, Runtime.runFork(runtime))
776
+ })
777
+ }
778
+
779
+ return yield* createStore({ ...options, storeScope: scope }).pipe(Scope.extend(scope))
780
+ }).pipe(Effect.withSpan('createStore'), runEffectPromise)
834
781
 
835
- export const createStoreEff = <
782
+ export const createStore = <
836
783
  TGraphQLContext extends BaseGraphQLContext,
837
784
  TSchema extends LiveStoreSchema = LiveStoreSchema,
838
785
  >({
@@ -845,7 +792,12 @@ export const createStoreEff = <
845
792
  batchUpdates,
846
793
  disableDevtools,
847
794
  onBootStatus,
848
- }: CreateStoreOptions<TGraphQLContext, TSchema>): Effect.Effect<Store<TGraphQLContext, TSchema>, UnexpectedError> => {
795
+ storeScope,
796
+ }: CreateStoreOptions<TGraphQLContext, TSchema> & { storeScope: Scope.CloseableScope }): Effect.Effect<
797
+ Store<TGraphQLContext, TSchema>,
798
+ UnexpectedError,
799
+ Scope.Scope
800
+ > => {
849
801
  const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
850
802
  const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
851
803
 
@@ -869,6 +821,7 @@ export const createStoreEff = <
869
821
  schema,
870
822
  devtoolsEnabled: disableDevtools !== true,
871
823
  bootStatusQueue,
824
+ shutdown: (cause) => Scope.close(storeScope, Exit.failCause(cause)),
872
825
  }).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
873
826
 
874
827
  if (batchUpdates !== undefined) {
@@ -946,10 +899,13 @@ export const createStoreEff = <
946
899
  },
947
900
  }
948
901
 
949
- const booting = boot(bootDbImpl, span)
902
+ const booting = yield* Effect.try({
903
+ try: () => boot(bootDbImpl, span),
904
+ catch: (cause) => new UnexpectedError({ cause }),
905
+ })
950
906
  // NOTE only awaiting if it's actually a promise to avoid unnecessary async/await
951
907
  if (isPromise(booting)) {
952
- yield* Effect.promise(() => booting)
908
+ yield* Effect.tryPromise({ try: () => booting, catch: (cause) => new UnexpectedError({ cause }) })
953
909
  }
954
910
  }
955
911
 
@@ -965,11 +921,12 @@ export const createStoreEff = <
965
921
  reactivityGraph,
966
922
  disableDevtools,
967
923
  __processedMutationIds,
924
+ storeScope,
968
925
  },
969
926
  span,
970
927
  )
971
928
  }).pipe(
972
- Effect.scoped,
929
+ // Effect.scoped,
973
930
  Effect.withSpan('createStore', {
974
931
  parent: otelOptions?.rootSpanContext
975
932
  ? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext)!)