@livestore/livestore 0.0.54-dev.26 → 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
@@ -1,7 +1,6 @@
1
- import type { BootDb, StoreAdapterFactory } from '@livestore/common'
1
+ import type { BootDb, StoreAdapterFactory, UnexpectedError } from '@livestore/common'
2
2
  import type { LiveStoreSchema } from '@livestore/common/schema'
3
- import type { Scope } from '@livestore/utils/effect'
4
- import { Context, Deferred, Duration, Effect, Layer, OtelTracer, pipe, Runtime } from '@livestore/utils/effect'
3
+ import { Context, Deferred, Duration, Effect, Layer, OtelTracer, pipe, Runtime, Scope } from '@livestore/utils/effect'
5
4
  import * as otel from '@opentelemetry/api'
6
5
  import type { GraphQLSchema } from 'graphql'
7
6
 
@@ -11,7 +10,17 @@ import type { BaseGraphQLContext, GraphQLOptions, OtelOptions, Store } from '../
11
10
  import { createStore } from '../store.js'
12
11
 
13
12
  // TODO get rid of `LiveStoreContext` wrapper and only expose the `Store` directly
14
- export type LiveStoreContext = {
13
+ export type LiveStoreContext =
14
+ | LiveStoreContextRunning
15
+ | {
16
+ stage: 'error'
17
+ error: UnexpectedError | unknown
18
+ }
19
+ | {
20
+ stage: 'shutdown'
21
+ }
22
+
23
+ export type LiveStoreContextRunning = {
15
24
  stage: 'running'
16
25
  store: Store
17
26
  }
@@ -26,13 +35,16 @@ export type LiveStoreCreateStoreOptions<GraphQLContext extends BaseGraphQLContex
26
35
  adapter: StoreAdapterFactory
27
36
  batchUpdates?: (run: () => void) => void
28
37
  disableDevtools?: boolean
38
+ signal?: AbortSignal
29
39
  }
30
40
 
31
- export const LiveStoreContext = Context.GenericTag<LiveStoreContext>('@livestore/livestore/LiveStoreContext')
41
+ export const LiveStoreContextRunning = Context.GenericTag<LiveStoreContextRunning>(
42
+ '@livestore/livestore/effect/LiveStoreContextRunning',
43
+ )
32
44
 
33
- export type DeferredStoreContext = Deferred.Deferred<LiveStoreContext>
45
+ export type DeferredStoreContext = Deferred.Deferred<LiveStoreContextRunning>
34
46
  export const DeferredStoreContext = Context.GenericTag<DeferredStoreContext>(
35
- '@livestore/livestore/DeferredStoreContext',
47
+ '@livestore/livestore/effect/DeferredStoreContext',
36
48
  )
37
49
 
38
50
  // export const DeferredStoreContext = Effect.cached(Effect.flatMap(StoreContext, (_) => Effect.succeed(_)))
@@ -50,13 +62,13 @@ export type LiveStoreContextProps<GraphQLContext extends BaseGraphQLContext> = {
50
62
 
51
63
  export const LiveStoreContextLayer = <GraphQLContext extends BaseGraphQLContext>(
52
64
  props: LiveStoreContextProps<GraphQLContext>,
53
- ): Layer.Layer<LiveStoreContext, never, otel.Tracer> =>
54
- Layer.scoped(LiveStoreContext, makeLiveStoreContext(props)).pipe(
65
+ ): Layer.Layer<LiveStoreContextRunning, never, otel.Tracer> =>
66
+ Layer.scoped(LiveStoreContextRunning, makeLiveStoreContext(props)).pipe(
55
67
  Layer.withSpan('LiveStore'),
56
68
  Layer.provide(LiveStoreContextDeferred),
57
69
  )
58
70
 
59
- export const LiveStoreContextDeferred = Layer.effect(DeferredStoreContext, Deferred.make<LiveStoreContext>())
71
+ export const LiveStoreContextDeferred = Layer.effect(DeferredStoreContext, Deferred.make<LiveStoreContextRunning>())
60
72
 
61
73
  export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>({
62
74
  schema,
@@ -65,54 +77,49 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
65
77
  adapter,
66
78
  disableDevtools,
67
79
  }: LiveStoreContextProps<GraphQLContext>): Effect.Effect<
68
- LiveStoreContext,
80
+ LiveStoreContextRunning,
69
81
  never,
70
82
  DeferredStoreContext | Scope.Scope | otel.Tracer
71
83
  > =>
72
84
  pipe(
73
- Effect.gen(function* ($) {
74
- const runtime = yield* $(Effect.runtime<never>())
85
+ Effect.gen(function* () {
86
+ const runtime = yield* Effect.runtime<never>()
75
87
 
76
88
  const otelRootSpanContext = otel.context.active()
77
89
 
78
- const otelTracer = yield* $(OtelTracer.Tracer)
90
+ const storeScope = yield* Scope.make()
91
+
92
+ yield* Effect.addFinalizer((ex) => Scope.close(storeScope, ex))
79
93
 
80
- const graphQLOptions = yield* $(
81
- graphQLOptions_
82
- ? Effect.all({ schema: graphQLOptions_.schema, makeContext: Effect.succeed(graphQLOptions_.makeContext) })
83
- : Effect.succeed(undefined),
84
- )
94
+ const otelTracer = yield* OtelTracer.Tracer
95
+
96
+ const graphQLOptions = yield* graphQLOptions_
97
+ ? Effect.all({ schema: graphQLOptions_.schema, makeContext: Effect.succeed(graphQLOptions_.makeContext) })
98
+ : Effect.succeed(undefined)
85
99
 
86
100
  const boot = boot_
87
101
  ? (db: BootDb) => boot_(db).pipe(Effect.withSpan('boot'), Effect.tapCauseLogPretty, Runtime.runPromise(runtime))
88
102
  : undefined
89
103
 
90
- const store = yield* $(
91
- Effect.tryPromise(() =>
92
- createStore({
93
- schema,
94
- graphQLOptions,
95
- otelOptions: {
96
- tracer: otelTracer,
97
- rootSpanContext: otelRootSpanContext,
98
- },
99
- boot,
100
- adapter,
101
- disableDevtools,
102
- }),
103
- ),
104
- Effect.acquireRelease((store) => Effect.sync(() => store.destroy())),
105
- )
104
+ const store = yield* createStore({
105
+ schema,
106
+ graphQLOptions,
107
+ otelOptions: {
108
+ tracer: otelTracer,
109
+ rootSpanContext: otelRootSpanContext,
110
+ },
111
+ boot,
112
+ adapter,
113
+ disableDevtools,
114
+ storeScope,
115
+ })
106
116
 
107
117
  window.__debugLiveStore = store
108
118
 
109
- return { stage: 'running', store } satisfies LiveStoreContext
119
+ return { stage: 'running', store } satisfies LiveStoreContextRunning
110
120
  }),
111
121
  Effect.tap((storeCtx) => Effect.flatMap(DeferredStoreContext, (def) => Deferred.succeed(def, storeCtx))),
112
- Effect.timeoutFail({
113
- // NOTE migrating from the mutation log can take a long time (so might need to increase this even further)
114
- onTimeout: () => new Error('Timed out while creating LiveStore store after 60sec'),
115
- duration: Duration.seconds(60),
116
- }),
122
+ Effect.timeout(Duration.seconds(60)),
123
+ Effect.withSpan('@livestore/livestore/effect:makeLiveStoreContext'),
117
124
  Effect.orDie,
118
125
  )
@@ -1,6 +1,7 @@
1
1
  export {
2
2
  LiveStoreContextLayer,
3
- LiveStoreContext,
3
+ LiveStoreContextRunning as LiveStoreContext,
4
+ LiveStoreContextRunning,
4
5
  LiveStoreContextDeferred,
5
6
  DeferredStoreContext,
6
7
  type LiveStoreContextProps,
package/src/index.ts CHANGED
@@ -1,7 +1,11 @@
1
- export { Store, createStore } from './store.js'
1
+ export { Store, createStorePromise, createStore } from './store.js'
2
2
  export type { BaseGraphQLContext, QueryDebugInfo, RefreshReason } from './store.js'
3
3
 
4
- export type { QueryDefinition, LiveStoreCreateStoreOptions, LiveStoreContext } from './effect/LiveStore.js'
4
+ export type {
5
+ QueryDefinition,
6
+ LiveStoreCreateStoreOptions,
7
+ LiveStoreContextRunning as LiveStoreContext,
8
+ } from './effect/LiveStore.js'
5
9
 
6
10
  export { MainDatabaseWrapper, emptyDebugInfo } from './MainDatabaseWrapper.js'
7
11
 
@@ -1,10 +1,11 @@
1
1
  import React, { useContext } from 'react'
2
2
 
3
- import type { LiveStoreContext as LiveStoreContext_ } from '../effect/LiveStore.js'
3
+ import type { LiveStoreContextRunning as LiveStoreContext_ } from '../effect/LiveStore.js'
4
+ import type { Store } from '../store.js'
4
5
 
5
6
  export const LiveStoreContext = React.createContext<LiveStoreContext_ | undefined>(undefined)
6
7
 
7
- export const useStore = (): LiveStoreContext_ => {
8
+ export const useStore = (): { store: Store } => {
8
9
  const storeContext = useContext(LiveStoreContext)
9
10
 
10
11
  if (storeContext === undefined) {
@@ -7,27 +7,25 @@ import { describe, expect, it } from 'vitest'
7
7
 
8
8
  import { parseTodos, schema } from '../__tests__/react/fixture.js'
9
9
  import { querySQL } from '../reactiveQueries/sql.js'
10
- import type { Store } from '../store.js'
11
10
  import * as LiveStoreReact from './index.js'
12
11
  import { LiveStoreProvider } from './LiveStoreProvider.js'
13
12
 
14
13
  describe('LiveStoreProvider', () => {
15
14
  it('simple', async () => {
16
- let renderCount = 0
15
+ let appRenderCount = 0
17
16
 
18
17
  const allTodos$ = querySQL(`select * from todos`, { map: parseTodos })
19
- let latestStoreCtx: { store: Store } | undefined = undefined
20
18
 
21
19
  const App = () => {
22
- renderCount++
23
-
24
- latestStoreCtx = LiveStoreReact.useStore()
20
+ appRenderCount++
25
21
 
26
22
  const todos = LiveStoreReact.useQuery(allTodos$)
27
23
 
28
24
  return <div>{JSON.stringify(todos)}</div>
29
25
  }
30
26
 
27
+ const abortController = new AbortController()
28
+
31
29
  const Root = ({ forceUpdate }: { forceUpdate: number }) => {
32
30
  const bootCb = React.useCallback(
33
31
  (db: BootDb) =>
@@ -42,6 +40,7 @@ describe('LiveStoreProvider', () => {
42
40
  renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
43
41
  adapter={adapterMemo}
44
42
  boot={bootCb}
43
+ signal={abortController.signal}
45
44
  >
46
45
  <App />
47
46
  </LiveStoreProvider>
@@ -50,19 +49,57 @@ describe('LiveStoreProvider', () => {
50
49
 
51
50
  const { rerender } = render(<Root forceUpdate={1} />)
52
51
 
53
- expect(renderCount).toBe(0)
52
+ expect(appRenderCount).toBe(0)
54
53
 
55
54
  await waitForElementToBeRemoved(() => screen.getByText((_) => _.startsWith('Loading LiveStore')))
56
55
 
57
- expect(renderCount).toBe(1)
56
+ expect(appRenderCount).toBe(1)
58
57
 
59
58
  rerender(<Root forceUpdate={2} />)
60
59
 
61
60
  await waitFor(() => screen.getByText('Loading LiveStore: loading'))
62
61
  await waitForElementToBeRemoved(() => screen.getByText((_) => _.startsWith('Loading LiveStore')))
63
62
 
64
- expect(renderCount).toBe(2)
63
+ expect(appRenderCount).toBe(2)
64
+
65
+ abortController.abort()
66
+
67
+ await waitFor(() => screen.getByText('LiveStore Shutdown'))
68
+ })
69
+
70
+ it('error during boot', async () => {
71
+ let appRenderCount = 0
72
+
73
+ const App = () => {
74
+ appRenderCount++
75
+
76
+ return <div>hello world</div>
77
+ }
78
+
79
+ const Root = ({ forceUpdate }: { forceUpdate: number }) => {
80
+ const bootCb = React.useCallback(
81
+ (db: BootDb) =>
82
+ db.execute(sql`INSERT INTO todos_mising_table (id, text, completed) VALUES ('t1', 'buy milk', 0);`),
83
+ [],
84
+ )
85
+ // eslint-disable-next-line react-hooks/exhaustive-deps
86
+ const adapterMemo = React.useMemo(() => makeInMemoryAdapter(), [forceUpdate])
87
+ return (
88
+ <LiveStoreProvider
89
+ schema={schema}
90
+ renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
91
+ adapter={adapterMemo}
92
+ boot={bootCb}
93
+ >
94
+ <App />
95
+ </LiveStoreProvider>
96
+ )
97
+ }
98
+
99
+ render(<Root forceUpdate={1} />)
100
+
101
+ expect(appRenderCount).toBe(0)
65
102
 
66
- await latestStoreCtx!.store.destroy()
103
+ await waitFor(() => screen.getByText((_) => _.startsWith('LiveStore.UnexpectedError')))
67
104
  })
68
105
  })
@@ -1,13 +1,14 @@
1
- import type { BootDb, BootStatus, StoreAdapterFactory } from '@livestore/common'
1
+ import { type BootDb, type BootStatus, type StoreAdapterFactory, UnexpectedError } from '@livestore/common'
2
2
  import type { LiveStoreSchema } from '@livestore/common/schema'
3
- import { shouldNeverHappen } from '@livestore/utils'
3
+ import { errorToString } from '@livestore/utils'
4
+ import { Effect, Exit, Logger, LogLevel, Schema, Scope } from '@livestore/utils/effect'
4
5
  import type * as otel from '@opentelemetry/api'
5
6
  import type { ReactElement, ReactNode } from 'react'
6
7
  import React from 'react'
7
8
 
8
9
  // TODO refactor so the `react` module doesn't depend on `effect` module
9
10
  import type { LiveStoreContext as StoreContext_, LiveStoreCreateStoreOptions } from '../effect/LiveStore.js'
10
- import type { BaseGraphQLContext, GraphQLOptions, OtelOptions, Store } from '../store.js'
11
+ import type { BaseGraphQLContext, GraphQLOptions, OtelOptions } from '../store.js'
11
12
  import { createStore } from '../store.js'
12
13
  import { LiveStoreContext } from './LiveStoreContext.js'
13
14
 
@@ -20,6 +21,7 @@ interface LiveStoreProviderProps<GraphQLContext> {
20
21
  adapter: StoreAdapterFactory
21
22
  batchUpdates?: (run: () => void) => void
22
23
  disableDevtools?: boolean
24
+ signal?: AbortSignal
23
25
  }
24
26
 
25
27
  export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
@@ -32,6 +34,7 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
32
34
  adapter,
33
35
  batchUpdates,
34
36
  disableDevtools,
37
+ signal,
35
38
  }: LiveStoreProviderProps<GraphQLContext> & { children?: ReactNode }): JSX.Element => {
36
39
  const storeCtx = useCreateStore({
37
40
  schema,
@@ -41,8 +44,21 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
41
44
  adapter,
42
45
  batchUpdates,
43
46
  disableDevtools,
47
+ signal,
44
48
  })
45
49
 
50
+ if (storeCtx.stage === 'error') {
51
+ return (
52
+ <div>
53
+ {Schema.is(UnexpectedError)(storeCtx.error) ? storeCtx.error.toString() : errorToString(storeCtx.error)}
54
+ </div>
55
+ )
56
+ }
57
+
58
+ if (storeCtx.stage === 'shutdown') {
59
+ return <div>LiveStore Shutdown</div>
60
+ }
61
+
46
62
  if (storeCtx.stage !== 'running') {
47
63
  return <div>{renderLoading(storeCtx)}</div>
48
64
  }
@@ -60,9 +76,21 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
60
76
  adapter,
61
77
  batchUpdates,
62
78
  disableDevtools,
79
+ signal,
63
80
  }: LiveStoreCreateStoreOptions<GraphQLContext>) => {
64
81
  const [_, rerender] = React.useState(0)
65
- const ctxValueRef = React.useRef<StoreContext_ | BootStatus>({ stage: 'loading' })
82
+ const ctxValueRef = React.useRef<{
83
+ value: StoreContext_ | BootStatus
84
+ scope: Scope.CloseableScope | undefined
85
+ counter: number
86
+ }>({
87
+ value: { stage: 'loading' },
88
+ scope: undefined,
89
+ counter: 0,
90
+ })
91
+
92
+ // console.debug(`useCreateStore (${ctxValueRef.current.counter})`, ctxValueRef.current.value.stage)
93
+
66
94
  const inputPropsCacheRef = React.useRef({
67
95
  schema,
68
96
  graphQLOptions,
@@ -70,8 +98,9 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
70
98
  boot,
71
99
  adapter,
72
100
  batchUpdates,
101
+ disableDevtools,
102
+ signal,
73
103
  })
74
- const oldStoreAlreadyDestroyedRef = React.useRef(false)
75
104
 
76
105
  if (
77
106
  inputPropsCacheRef.current.schema !== schema ||
@@ -79,7 +108,9 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
79
108
  inputPropsCacheRef.current.otelOptions !== otelOptions ||
80
109
  inputPropsCacheRef.current.boot !== boot ||
81
110
  inputPropsCacheRef.current.adapter !== adapter ||
82
- inputPropsCacheRef.current.batchUpdates !== batchUpdates
111
+ inputPropsCacheRef.current.batchUpdates !== batchUpdates ||
112
+ inputPropsCacheRef.current.disableDevtools !== disableDevtools ||
113
+ inputPropsCacheRef.current.signal !== signal
83
114
  ) {
84
115
  inputPropsCacheRef.current = {
85
116
  schema,
@@ -88,47 +119,73 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
88
119
  boot,
89
120
  adapter,
90
121
  batchUpdates,
122
+ disableDevtools,
123
+ signal,
91
124
  }
92
- if (ctxValueRef.current.stage === 'running') {
93
- ctxValueRef.current.store.destroy()
94
- oldStoreAlreadyDestroyedRef.current = true
95
- ctxValueRef.current = { stage: 'loading' }
125
+ if (ctxValueRef.current.scope !== undefined) {
126
+ Scope.close(ctxValueRef.current.scope, Exit.void).pipe(Effect.tapCauseLogPretty, Effect.runFork)
96
127
  }
128
+ ctxValueRef.current = { value: { stage: 'loading' }, scope: undefined, counter: ctxValueRef.current.counter + 1 }
97
129
  }
98
130
 
99
131
  React.useEffect(() => {
100
- let store: Store | undefined
101
-
102
- void (async () => {
103
- try {
104
- store = await createStore({
105
- schema,
106
- graphQLOptions,
107
- otelOptions,
108
- boot,
109
- adapter,
110
- batchUpdates,
111
- disableDevtools,
112
- onBootStatus: (status) => {
113
- if (ctxValueRef.current.stage === 'running') return
114
- ctxValueRef.current = status
115
- rerender((c) => c + 1)
116
- },
117
- })
118
- ctxValueRef.current = { stage: 'running', store }
119
- oldStoreAlreadyDestroyedRef.current = false
120
- rerender((c) => c + 1)
121
- } catch (e) {
122
- shouldNeverHappen(`Error creating LiveStore store: ${e}`)
132
+ const storeScope = Scope.make().pipe(Effect.runSync)
133
+
134
+ const counter = ctxValueRef.current.counter
135
+
136
+ const setContextValue = (value: StoreContext_ | BootStatus) => {
137
+ if (ctxValueRef.current.counter !== counter) return
138
+ ctxValueRef.current.value = value
139
+ rerender((c) => c + 1)
140
+ }
141
+
142
+ Scope.addFinalizer(
143
+ storeScope,
144
+ Effect.sync(() => setContextValue({ stage: 'shutdown' })),
145
+ ).pipe(Effect.runSync)
146
+
147
+ ctxValueRef.current.scope = storeScope
148
+
149
+ signal?.addEventListener('abort', () => {
150
+ if (ctxValueRef.current.scope !== undefined && ctxValueRef.current.counter === counter) {
151
+ Scope.close(ctxValueRef.current.scope, Exit.void).pipe(Effect.tapCauseLogPretty, Effect.runFork)
152
+ ctxValueRef.current.scope = undefined
123
153
  }
124
- })()
154
+ })
155
+
156
+ createStore({
157
+ storeScope,
158
+ schema,
159
+ graphQLOptions,
160
+ otelOptions,
161
+ boot,
162
+ adapter,
163
+ batchUpdates,
164
+ disableDevtools,
165
+ onBootStatus: (status) => {
166
+ if (ctxValueRef.current.value.stage === 'running' || ctxValueRef.current.value.stage === 'error') return
167
+ setContextValue(status)
168
+ },
169
+ }).pipe(
170
+ Effect.tapSync((store) => setContextValue({ stage: 'running', store })),
171
+ Effect.tapError((error) => Effect.sync(() => setContextValue({ stage: 'error', error }))),
172
+ Effect.tapDefect((defect) => Effect.sync(() => setContextValue({ stage: 'error', error: defect }))),
173
+ Scope.extend(storeScope),
174
+ Effect.forkIn(storeScope),
175
+ Effect.tapCauseLogPretty,
176
+ Effect.annotateLogs({ thread: 'window' }),
177
+ Effect.provide(Logger.pretty),
178
+ Logger.withMinimumLogLevel(LogLevel.Debug),
179
+ Effect.runFork,
180
+ )
125
181
 
126
182
  return () => {
127
- if (oldStoreAlreadyDestroyedRef.current === false) {
128
- store?.destroy()
183
+ if (ctxValueRef.current.scope !== undefined) {
184
+ Scope.close(ctxValueRef.current.scope, Exit.void).pipe(Effect.tapCauseLogPretty, Effect.runFork)
185
+ ctxValueRef.current.scope = undefined
129
186
  }
130
187
  }
131
- }, [schema, graphQLOptions, otelOptions, boot, adapter, batchUpdates, disableDevtools])
188
+ }, [schema, graphQLOptions, otelOptions, boot, adapter, batchUpdates, disableDevtools, signal])
132
189
 
133
- return ctxValueRef.current
190
+ return ctxValueRef.current.value
134
191
  }
@@ -297,7 +297,7 @@ describe.concurrent('useRow', () => {
297
297
  expect(renderCount.val).toBe(2)
298
298
 
299
299
  unmount()
300
- store.destroy()
300
+ await store.destroy()
301
301
  span.end()
302
302
 
303
303
  const mapAttributes = (attributes: otel.Attributes) => {
@@ -24,17 +24,17 @@ describe('otel', () => {
24
24
  provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
25
25
  provider.register()
26
26
 
27
- const tracer = otel.trace.getTracer('test')
27
+ const otelTracer = otel.trace.getTracer('test')
28
28
 
29
- const span = tracer.startSpan('test')
29
+ const span = otelTracer.startSpan('test')
30
30
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
31
31
 
32
- const { store } = await makeTodoMvc({ otelTracer: tracer, otelContext })
32
+ const { store } = await makeTodoMvc({ otelTracer, otelContext })
33
33
 
34
34
  return {
35
35
  [Symbol.dispose]: () => store.destroy(),
36
36
  store,
37
- tracer,
37
+ otelTracer,
38
38
  exporter,
39
39
  span,
40
40
  provider,
@@ -60,7 +60,7 @@ describe('otel', () => {
60
60
  ]
61
61
  `)
62
62
 
63
- store.destroy()
63
+ await store.destroy()
64
64
  query.destroy()
65
65
  span.end()
66
66
 
@@ -193,7 +193,7 @@ describe('otel', () => {
193
193
  }
194
194
  `)
195
195
 
196
- store.destroy()
196
+ await store.destroy()
197
197
  query.destroy()
198
198
  span.end()
199
199