@livestore/livestore 0.0.54-dev.25 → 0.0.54-dev.27

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 (44) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  3. package/dist/__tests__/react/fixture.js +2 -2
  4. package/dist/__tests__/react/fixture.js.map +1 -1
  5. package/dist/effect/LiveStore.d.ts +14 -8
  6. package/dist/effect/LiveStore.d.ts.map +1 -1
  7. package/dist/effect/LiveStore.js +15 -16
  8. package/dist/effect/LiveStore.js.map +1 -1
  9. package/dist/effect/index.d.ts +1 -1
  10. package/dist/effect/index.d.ts.map +1 -1
  11. package/dist/effect/index.js +1 -1
  12. package/dist/effect/index.js.map +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/react/LiveStoreContext.d.ts +5 -2
  18. package/dist/react/LiveStoreContext.d.ts.map +1 -1
  19. package/dist/react/LiveStoreContext.js.map +1 -1
  20. package/dist/react/LiveStoreProvider.d.ts +3 -2
  21. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  22. package/dist/react/LiveStoreProvider.js +63 -39
  23. package/dist/react/LiveStoreProvider.js.map +1 -1
  24. package/dist/react/LiveStoreProvider.test.js +28 -9
  25. package/dist/react/LiveStoreProvider.test.js.map +1 -1
  26. package/dist/react/useRow.test.js +1 -1
  27. package/dist/react/useRow.test.js.map +1 -1
  28. package/dist/reactiveQueries/sql.test.js +6 -6
  29. package/dist/reactiveQueries/sql.test.js.map +1 -1
  30. package/dist/store.d.ts +11 -4
  31. package/dist/store.d.ts.map +1 -1
  32. package/dist/store.js +52 -120
  33. package/dist/store.js.map +1 -1
  34. package/package.json +5 -5
  35. package/src/__tests__/react/fixture.tsx +2 -2
  36. package/src/effect/LiveStore.ts +48 -41
  37. package/src/effect/index.ts +2 -1
  38. package/src/index.ts +6 -2
  39. package/src/react/LiveStoreContext.ts +3 -2
  40. package/src/react/LiveStoreProvider.test.tsx +47 -10
  41. package/src/react/LiveStoreProvider.tsx +95 -38
  42. package/src/react/useRow.test.tsx +1 -1
  43. package/src/reactiveQueries/sql.test.ts +6 -6
  44. package/src/store.ts +234 -284
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: {
@@ -535,268 +564,166 @@ export class Store<
535
564
  })
536
565
 
537
566
  // 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
- }
567
+ private bootDevtools = () =>
568
+ Effect.gen(this, function* () {
569
+ const sendToDevtoolsContentscript = (
570
+ message: typeof Devtools.DevtoolsWindowMessage.MessageForContentscript.Type,
571
+ ) => {
572
+ window.postMessage(Schema.encodeSync(Devtools.DevtoolsWindowMessage.MessageForContentscript)(message), '*')
573
+ }
544
574
 
545
- const channelId = this.adapter.coordinator.devtools.channelId
575
+ sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.LoadIframe.make({}))
546
576
 
547
- window.addEventListener('message', (event) => {
548
- const decodedMessageRes = Schema.decodeOption(Devtools.DevtoolsWindowMessage.MessageForStore)(event.data)
549
- if (decodedMessageRes._tag === 'None') return
577
+ const channelId = this.adapter.coordinator.devtools.channelId
550
578
 
551
- const message = decodedMessageRes.value
579
+ window.addEventListener('message', (event) => {
580
+ const decodedMessageRes = Schema.decodeOption(Devtools.DevtoolsWindowMessage.MessageForStore)(event.data)
581
+ if (decodedMessageRes._tag === 'None') return
552
582
 
553
- if (message._tag === 'LSD.WindowMessage.ContentscriptListening') {
554
- sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
555
- return
556
- }
583
+ const message = decodedMessageRes.value
557
584
 
558
- if (message.channelId !== channelId) return
585
+ if (message._tag === 'LSD.WindowMessage.ContentscriptListening') {
586
+ sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
587
+ return
588
+ }
559
589
 
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)
590
+ if (message.channelId !== channelId) return
567
591
 
568
- if (decodedMessage.channelId !== this.adapter.coordinator.devtools.channelId) {
569
- // console.log(`Unknown message`, event)
570
- return
571
- }
592
+ if (message._tag === 'LSD.WindowMessage.MessagePortForStore') {
593
+ type Unsub = () => void
594
+ type RequestId = string
572
595
 
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
- })
596
+ const reactivityGraphSubcriptions = new Map<RequestId, Unsub>()
597
+ const liveQueriesSubscriptions = new Map<RequestId, Unsub>()
675
598
 
676
- storeMessagePort.start()
677
- }),
678
- runEffectFork,
679
- )
599
+ this.adapter.coordinator.devtools
600
+ .connect({ port: message.port, connectionId: this.devtoolsConnectionId })
601
+ .pipe(
602
+ Effect.tapSync(({ storeMessagePort }) => {
603
+ // console.log('storeMessagePort', storeMessagePort)
604
+ storeMessagePort.addEventListener('message', (event) => {
605
+ const decodedMessage = Schema.decodeUnknownSync(Devtools.MessageToAppHostStore)(event.data)
606
+ // console.log('storeMessagePort message', decodedMessage)
680
607
 
681
- return
682
- }
683
- })
608
+ if (decodedMessage.channelId !== this.adapter.coordinator.devtools.channelId) {
609
+ // console.log(`Unknown message`, event)
610
+ return
611
+ }
684
612
 
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
- }
613
+ const requestId = decodedMessage.requestId
614
+ const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
615
+ storeMessagePort.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message))
616
+
617
+ const requestIdleCallback = window.requestIdleCallback ?? ((cb: Function) => cb())
618
+
619
+ switch (decodedMessage._tag) {
620
+ case 'LSD.ReactivityGraphSubscribe': {
621
+ const includeResults = decodedMessage.includeResults
622
+
623
+ const send = () =>
624
+ // In order to not add more work to the current tick, we use requestIdleCallback
625
+ // to send the reactivity graph updates to the devtools
626
+ requestIdleCallback(
627
+ () =>
628
+ sendToDevtools(
629
+ Devtools.ReactivityGraphRes.make({
630
+ reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
631
+ requestId,
632
+ liveStoreVersion,
633
+ }),
634
+ ),
635
+ { timeout: 500 },
636
+ )
637
+
638
+ send()
639
+
640
+ // In some cases, there can be A LOT of reactivity graph updates in a short period of time
641
+ // so we throttle the updates to avoid sending too much data
642
+ // This might need to be tweaked further and possibly be exposed to the user in some way.
643
+ const throttledSend = throttle(send, 20)
644
+
645
+ reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
646
+
647
+ break
648
+ }
649
+ case 'LSD.DebugInfoReq': {
650
+ sendToDevtools(
651
+ Devtools.DebugInfoRes.make({
652
+ debugInfo: this.mainDbWrapper.debugInfo,
653
+ requestId,
654
+ liveStoreVersion,
655
+ }),
656
+ )
657
+ break
658
+ }
659
+ case 'LSD.DebugInfoResetReq': {
660
+ this.mainDbWrapper.debugInfo.slowQueries.clear()
661
+ sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
662
+ break
663
+ }
664
+ case 'LSD.DebugInfoRerunQueryReq': {
665
+ const { queryStr, bindValues, queriedTables } = decodedMessage
666
+ this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
667
+ sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
668
+ break
669
+ }
670
+ case 'LSD.ReactivityGraphUnsubscribe': {
671
+ reactivityGraphSubcriptions.get(requestId)!()
672
+ break
673
+ }
674
+ case 'LSD.LiveQueriesSubscribe': {
675
+ const send = () =>
676
+ requestIdleCallback(
677
+ () =>
678
+ sendToDevtools(
679
+ Devtools.LiveQueriesRes.make({
680
+ liveQueries: [...this.activeQueries].map((q) => ({
681
+ _tag: q._tag,
682
+ id: q.id,
683
+ label: q.label,
684
+ runs: q.runs,
685
+ executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
686
+ lastestResult:
687
+ q.results$.previousResult === NOT_REFRESHED_YET
688
+ ? 'SYMBOL_NOT_REFRESHED_YET'
689
+ : q.results$.previousResult,
690
+ activeSubscriptions: Array.from(q.activeSubscriptions),
691
+ })),
692
+ requestId,
693
+ liveStoreVersion,
694
+ }),
695
+ ),
696
+ { timeout: 500 },
697
+ )
698
+
699
+ send()
700
+
701
+ // Same as in the reactivity graph subscription case above, we need to throttle the updates
702
+ const throttledSend = throttle(send, 20)
703
+
704
+ liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
705
+
706
+ break
707
+ }
708
+ case 'LSD.LiveQueriesUnsubscribe': {
709
+ liveQueriesSubscriptions.get(requestId)!()
710
+ break
711
+ }
712
+ // No default
713
+ }
714
+ })
715
+
716
+ storeMessagePort.start()
717
+ }),
718
+ runEffectFork,
719
+ )
720
+
721
+ return
722
+ }
723
+ })
724
+
725
+ sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
726
+ })
800
727
 
801
728
  __devDownloadDb = () => {
802
729
  const data = this.mainDbWrapper.export()
@@ -825,14 +752,27 @@ export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSche
825
752
  }
826
753
 
827
754
  /** Create a new LiveStore Store */
828
- export const createStore = async <
755
+ export const createStorePromise = async <
829
756
  TGraphQLContext extends BaseGraphQLContext,
830
757
  TSchema extends LiveStoreSchema = LiveStoreSchema,
831
- >(
832
- options: CreateStoreOptions<TGraphQLContext, TSchema>,
833
- ): Promise<Store<TGraphQLContext, TSchema>> => createStoreEff(options).pipe(runEffectPromise)
758
+ >({
759
+ signal,
760
+ ...options
761
+ }: CreateStoreOptions<TGraphQLContext, TSchema> & { signal?: AbortSignal }): Promise<Store<TGraphQLContext, TSchema>> =>
762
+ Effect.gen(function* () {
763
+ const scope = yield* Scope.make()
764
+ const runtime = yield* Effect.runtime()
765
+
766
+ if (signal !== undefined) {
767
+ signal.addEventListener('abort', () => {
768
+ Scope.close(scope, Exit.void).pipe(Effect.tapCauseLogPretty, Runtime.runFork(runtime))
769
+ })
770
+ }
771
+
772
+ return yield* createStore({ ...options, storeScope: scope }).pipe(Scope.extend(scope))
773
+ }).pipe(Effect.withSpan('createStore'), runEffectPromise)
834
774
 
835
- export const createStoreEff = <
775
+ export const createStore = <
836
776
  TGraphQLContext extends BaseGraphQLContext,
837
777
  TSchema extends LiveStoreSchema = LiveStoreSchema,
838
778
  >({
@@ -845,7 +785,12 @@ export const createStoreEff = <
845
785
  batchUpdates,
846
786
  disableDevtools,
847
787
  onBootStatus,
848
- }: CreateStoreOptions<TGraphQLContext, TSchema>): Effect.Effect<Store<TGraphQLContext, TSchema>, UnexpectedError> => {
788
+ storeScope,
789
+ }: CreateStoreOptions<TGraphQLContext, TSchema> & { storeScope: Scope.CloseableScope }): Effect.Effect<
790
+ Store<TGraphQLContext, TSchema>,
791
+ UnexpectedError,
792
+ Scope.Scope
793
+ > => {
849
794
  const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
850
795
  const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
851
796
 
@@ -869,6 +814,7 @@ export const createStoreEff = <
869
814
  schema,
870
815
  devtoolsEnabled: disableDevtools !== true,
871
816
  bootStatusQueue,
817
+ shutdown: (cause) => Scope.close(storeScope, Exit.failCause(cause)),
872
818
  }).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
873
819
 
874
820
  if (batchUpdates !== undefined) {
@@ -946,10 +892,13 @@ export const createStoreEff = <
946
892
  },
947
893
  }
948
894
 
949
- const booting = boot(bootDbImpl, span)
895
+ const booting = yield* Effect.try({
896
+ try: () => boot(bootDbImpl, span),
897
+ catch: (cause) => new UnexpectedError({ cause }),
898
+ })
950
899
  // NOTE only awaiting if it's actually a promise to avoid unnecessary async/await
951
900
  if (isPromise(booting)) {
952
- yield* Effect.promise(() => booting)
901
+ yield* Effect.tryPromise({ try: () => booting, catch: (cause) => new UnexpectedError({ cause }) })
953
902
  }
954
903
  }
955
904
 
@@ -965,11 +914,12 @@ export const createStoreEff = <
965
914
  reactivityGraph,
966
915
  disableDevtools,
967
916
  __processedMutationIds,
917
+ storeScope,
968
918
  },
969
919
  span,
970
920
  )
971
921
  }).pipe(
972
- Effect.scoped,
922
+ // Effect.scoped,
973
923
  Effect.withSpan('createStore', {
974
924
  parent: otelOptions?.rootSpanContext
975
925
  ? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext)!)