@livestore/react 0.3.0-dev.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/LiveStoreContext.d.ts +10 -4
  3. package/dist/LiveStoreContext.d.ts.map +1 -1
  4. package/dist/LiveStoreContext.js +1 -11
  5. package/dist/LiveStoreContext.js.map +1 -1
  6. package/dist/LiveStoreProvider.d.ts +29 -12
  7. package/dist/LiveStoreProvider.d.ts.map +1 -1
  8. package/dist/LiveStoreProvider.js +84 -55
  9. package/dist/LiveStoreProvider.js.map +1 -1
  10. package/dist/LiveStoreProvider.test.js +80 -29
  11. package/dist/LiveStoreProvider.test.js.map +1 -1
  12. package/dist/__tests__/fixture.d.ts +122 -556
  13. package/dist/__tests__/fixture.d.ts.map +1 -1
  14. package/dist/__tests__/fixture.js +71 -30
  15. package/dist/__tests__/fixture.js.map +1 -1
  16. package/dist/experimental/components/LiveList.d.ts +2 -2
  17. package/dist/experimental/components/LiveList.d.ts.map +1 -1
  18. package/dist/experimental/components/LiveList.js +10 -6
  19. package/dist/experimental/components/LiveList.js.map +1 -1
  20. package/dist/mod.d.ts +4 -5
  21. package/dist/mod.d.ts.map +1 -1
  22. package/dist/mod.js +4 -5
  23. package/dist/mod.js.map +1 -1
  24. package/dist/useClientDocument.d.ts +61 -0
  25. package/dist/useClientDocument.d.ts.map +1 -0
  26. package/dist/useClientDocument.js +79 -0
  27. package/dist/useClientDocument.js.map +1 -0
  28. package/dist/useClientDocument.test.d.ts +2 -0
  29. package/dist/useClientDocument.test.d.ts.map +1 -0
  30. package/dist/useClientDocument.test.js +175 -0
  31. package/dist/useClientDocument.test.js.map +1 -0
  32. package/dist/useQuery.d.ts +25 -3
  33. package/dist/useQuery.d.ts.map +1 -1
  34. package/dist/useQuery.js +67 -47
  35. package/dist/useQuery.js.map +1 -1
  36. package/dist/useQuery.test.d.ts +1 -1
  37. package/dist/useQuery.test.d.ts.map +1 -1
  38. package/dist/useQuery.test.js +86 -24
  39. package/dist/useQuery.test.js.map +1 -1
  40. package/dist/useRcResource.d.ts +76 -0
  41. package/dist/useRcResource.d.ts.map +1 -0
  42. package/dist/useRcResource.js +152 -0
  43. package/dist/useRcResource.js.map +1 -0
  44. package/dist/useRcResource.test.d.ts +2 -0
  45. package/dist/useRcResource.test.d.ts.map +1 -0
  46. package/dist/useRcResource.test.js +122 -0
  47. package/dist/useRcResource.test.js.map +1 -0
  48. package/dist/useStore.d.ts +9 -0
  49. package/dist/useStore.d.ts.map +1 -0
  50. package/dist/useStore.js +28 -0
  51. package/dist/useStore.js.map +1 -0
  52. package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
  53. package/package.json +19 -13
  54. package/src/LiveStoreContext.ts +11 -16
  55. package/src/LiveStoreProvider.test.tsx +176 -37
  56. package/src/LiveStoreProvider.tsx +156 -81
  57. package/src/__snapshots__/useClientDocument.test.tsx.snap +613 -0
  58. package/src/__snapshots__/useQuery.test.tsx.snap +2011 -0
  59. package/src/__tests__/fixture.tsx +74 -47
  60. package/src/experimental/components/LiveList.tsx +10 -7
  61. package/src/mod.ts +5 -6
  62. package/src/useClientDocument.test.tsx +306 -0
  63. package/src/useClientDocument.ts +157 -0
  64. package/src/useQuery.test.tsx +182 -71
  65. package/src/useQuery.ts +95 -58
  66. package/src/useRcResource.test.tsx +167 -0
  67. package/src/useRcResource.ts +182 -0
  68. package/src/useStore.ts +36 -0
  69. package/dist/useAtom.d.ts +0 -5
  70. package/dist/useAtom.d.ts.map +0 -1
  71. package/dist/useAtom.js +0 -38
  72. package/dist/useAtom.js.map +0 -1
  73. package/dist/useRow.d.ts +0 -50
  74. package/dist/useRow.d.ts.map +0 -1
  75. package/dist/useRow.js +0 -93
  76. package/dist/useRow.js.map +0 -1
  77. package/dist/useRow.test.d.ts +0 -2
  78. package/dist/useRow.test.d.ts.map +0 -1
  79. package/dist/useRow.test.js +0 -202
  80. package/dist/useRow.test.js.map +0 -1
  81. package/dist/useScopedQuery.d.ts +0 -33
  82. package/dist/useScopedQuery.d.ts.map +0 -1
  83. package/dist/useScopedQuery.js +0 -87
  84. package/dist/useScopedQuery.js.map +0 -1
  85. package/dist/useScopedQuery.test.d.ts +0 -2
  86. package/dist/useScopedQuery.test.d.ts.map +0 -1
  87. package/dist/useScopedQuery.test.js +0 -60
  88. package/dist/useScopedQuery.test.js.map +0 -1
  89. package/src/__snapshots__/useRow.test.tsx.snap +0 -360
  90. package/src/useAtom.ts +0 -52
  91. package/src/useRow.test.tsx +0 -344
  92. package/src/useRow.ts +0 -188
  93. package/src/useScopedQuery.test.tsx +0 -96
  94. package/src/useScopedQuery.ts +0 -143
  95. package/tsconfig.json +0 -20
  96. package/vitest.config.js +0 -17
@@ -1,26 +1,35 @@
1
- import type { Adapter, BootStatus, IntentionalShutdownCause } from '@livestore/common'
1
+ import type { Adapter, BootStatus, IntentionalShutdownCause, MigrationsReport } from '@livestore/common'
2
2
  import { provideOtel, UnexpectedError } from '@livestore/common'
3
3
  import type { LiveStoreSchema } from '@livestore/common/schema'
4
4
  import type {
5
- BaseGraphQLContext,
6
5
  CreateStoreOptions,
7
- GraphQLOptions,
8
6
  LiveStoreContext as StoreContext_,
9
7
  OtelOptions,
10
8
  ShutdownDeferred,
11
9
  Store,
12
10
  } from '@livestore/livestore'
13
- import { createStore, StoreAbort, StoreInterrupted } from '@livestore/livestore'
14
- import { errorToString, LS_DEV } from '@livestore/utils'
11
+ import { createStore, makeShutdownDeferred, StoreInterrupted } from '@livestore/livestore'
12
+ import { errorToString, IS_REACT_NATIVE, LS_DEV } from '@livestore/utils'
15
13
  import type { OtelTracer } from '@livestore/utils/effect'
16
- import { Deferred, Effect, Exit, identity, Logger, LogLevel, Schema, Scope, TaskTracing } from '@livestore/utils/effect'
14
+ import {
15
+ Cause,
16
+ Deferred,
17
+ Effect,
18
+ Exit,
19
+ identity,
20
+ Logger,
21
+ LogLevel,
22
+ Schema,
23
+ Scope,
24
+ TaskTracing,
25
+ } from '@livestore/utils/effect'
17
26
  import type * as otel from '@opentelemetry/api'
18
27
  import type { ReactElement, ReactNode } from 'react'
19
28
  import React from 'react'
20
29
 
21
30
  import { LiveStoreContext } from './LiveStoreContext.js'
22
31
 
23
- interface LiveStoreProviderProps<GraphQLContext extends BaseGraphQLContext> {
32
+ export interface LiveStoreProviderProps {
24
33
  schema: LiveStoreSchema
25
34
  /**
26
35
  * The `storeId` can be used to isolate multiple stores from each other.
@@ -34,17 +43,16 @@ interface LiveStoreProviderProps<GraphQLContext extends BaseGraphQLContext> {
34
43
  */
35
44
  storeId?: string
36
45
  boot?: (
37
- store: Store<GraphQLContext, LiveStoreSchema>,
38
- parentSpan: otel.Span,
46
+ store: Store<LiveStoreSchema>,
47
+ ctx: { migrationsReport: MigrationsReport; parentSpan: otel.Span },
39
48
  ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer>
40
- graphQLOptions?: GraphQLOptions<GraphQLContext>
41
49
  otelOptions?: Partial<OtelOptions>
42
- renderLoading: (status: BootStatus) => ReactElement
50
+ renderLoading?: (status: BootStatus) => ReactElement
43
51
  renderError?: (error: UnexpectedError | unknown) => ReactElement
44
- renderShutdown?: (cause: IntentionalShutdownCause | StoreAbort) => ReactElement
52
+ renderShutdown?: (cause: IntentionalShutdownCause | StoreInterrupted) => ReactElement
45
53
  adapter: Adapter
46
54
  /**
47
- * In order for LiveStore to apply multiple mutations in a single render,
55
+ * In order for LiveStore to apply multiple events in a single render,
48
56
  * you need to pass the `batchUpdates` function from either `react-dom` or `react-native`.
49
57
  *
50
58
  * ```ts
@@ -58,31 +66,51 @@ interface LiveStoreProviderProps<GraphQLContext extends BaseGraphQLContext> {
58
66
  batchUpdates: (run: () => void) => void
59
67
  disableDevtools?: boolean
60
68
  signal?: AbortSignal
69
+ /**
70
+ * Currently only used in the web adapter:
71
+ * If true, registers a beforeunload event listener to confirm unsaved changes.
72
+ *
73
+ * @default true
74
+ */
75
+ confirmUnsavedChanges?: boolean
76
+ /**
77
+ * Payload that will be passed to the sync backend when connecting
78
+ *
79
+ * @default undefined
80
+ */
81
+ syncPayload?: Schema.JsonValue
82
+ debug?: {
83
+ instanceId?: string
84
+ }
61
85
  }
62
86
 
63
- const defaultRenderError = (error: UnexpectedError | unknown) => (
64
- <>{Schema.is(UnexpectedError)(error) ? error.toString() : errorToString(error)}</>
65
- )
66
- const defaultRenderShutdown = (cause: IntentionalShutdownCause | StoreAbort) => {
87
+ const defaultRenderError = (error: UnexpectedError | unknown) =>
88
+ IS_REACT_NATIVE ? <></> : <>{Schema.is(UnexpectedError)(error) ? error.toString() : errorToString(error)}</>
89
+
90
+ const defaultRenderShutdown = (cause: IntentionalShutdownCause | StoreInterrupted) => {
67
91
  const reason =
68
- cause._tag === 'LiveStore.StoreAbort'
69
- ? 'abort signal'
92
+ cause._tag === 'LiveStore.StoreInterrupted'
93
+ ? `interrupted due to: ${cause.reason}`
70
94
  : cause.reason === 'devtools-import'
71
95
  ? 'devtools import'
72
96
  : cause.reason === 'devtools-reset'
73
97
  ? 'devtools reset'
74
- : cause.reason === 'manual'
75
- ? 'manual shutdown'
76
- : 'unknown reason'
98
+ : cause.reason === 'adapter-reset'
99
+ ? 'adapter reset'
100
+ : cause.reason === 'manual'
101
+ ? 'manual shutdown'
102
+ : 'unknown reason'
77
103
 
78
- return <>LiveStore Shutdown due to {reason}</>
104
+ return IS_REACT_NATIVE ? <></> : <>LiveStore Shutdown due to {reason}</>
79
105
  }
80
106
 
81
- export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
82
- renderLoading,
107
+ const defaultRenderLoading = (status: BootStatus) =>
108
+ IS_REACT_NATIVE ? <></> : <>LiveStore is loading ({status.stage})...</>
109
+
110
+ export const LiveStoreProvider = ({
111
+ renderLoading = defaultRenderLoading,
83
112
  renderError = defaultRenderError,
84
113
  renderShutdown = defaultRenderShutdown,
85
- graphQLOptions,
86
114
  otelOptions,
87
115
  children,
88
116
  schema,
@@ -92,17 +120,22 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
92
120
  batchUpdates,
93
121
  disableDevtools,
94
122
  signal,
95
- }: LiveStoreProviderProps<GraphQLContext> & { children?: ReactNode }): React.ReactElement => {
123
+ confirmUnsavedChanges = true,
124
+ syncPayload,
125
+ debug,
126
+ }: LiveStoreProviderProps & { children?: ReactNode }): React.ReactElement => {
96
127
  const storeCtx = useCreateStore({
97
128
  storeId,
98
129
  schema,
99
- graphQLOptions,
100
130
  otelOptions,
101
131
  boot,
102
132
  adapter,
103
133
  batchUpdates,
104
134
  disableDevtools,
105
135
  signal,
136
+ confirmUnsavedChanges,
137
+ syncPayload,
138
+ debug,
106
139
  })
107
140
 
108
141
  if (storeCtx.stage === 'error') {
@@ -121,35 +154,26 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
121
154
  if (Object.keys(globalThis.__debugLiveStore).length === 0) {
122
155
  globalThis.__debugLiveStore['_'] = storeCtx.store
123
156
  }
124
- globalThis.__debugLiveStore[storeId] = storeCtx.store
157
+ globalThis.__debugLiveStore[debug?.instanceId ?? storeId] = storeCtx.store
125
158
 
126
- return <LiveStoreContext.Provider value={storeCtx}>{children}</LiveStoreContext.Provider>
159
+ return <LiveStoreContext.Provider value={storeCtx as TODO}>{children}</LiveStoreContext.Provider>
127
160
  }
128
161
 
129
- type SchemaKey = string
130
- const semaphoreMap = new Map<SchemaKey, Effect.Semaphore>()
131
-
132
- const withSemaphore = (storeId: SchemaKey) => {
133
- let semaphore = semaphoreMap.get(storeId)
134
- if (!semaphore) {
135
- semaphore = Effect.makeSemaphore(1).pipe(Effect.runSync)
136
- semaphoreMap.set(storeId, semaphore)
137
- }
138
- return semaphore.withPermits(1)
139
- }
140
-
141
- const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
162
+ const useCreateStore = ({
142
163
  schema,
143
164
  storeId,
144
- graphQLOptions,
145
165
  otelOptions,
146
166
  boot,
147
167
  adapter,
148
168
  batchUpdates,
149
169
  disableDevtools,
150
- reactivityGraph,
151
170
  signal,
152
- }: CreateStoreOptions<GraphQLContext, LiveStoreSchema> & {
171
+ context,
172
+ params,
173
+ confirmUnsavedChanges,
174
+ syncPayload,
175
+ debug,
176
+ }: CreateStoreOptions<LiveStoreSchema> & {
153
177
  signal?: AbortSignal
154
178
  otelOptions?: Partial<OtelOptions>
155
179
  }) => {
@@ -158,32 +182,39 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
158
182
  value: StoreContext_ | BootStatus
159
183
  componentScope: Scope.CloseableScope | undefined
160
184
  shutdownDeferred: ShutdownDeferred | undefined
185
+ /** Used to wait for the previous shutdown deferred to fully complete before creating a new one */
186
+ previousShutdownDeferred: ShutdownDeferred | undefined
161
187
  counter: number
162
188
  }>({
163
189
  value: { stage: 'loading' },
164
190
  componentScope: undefined,
165
191
  shutdownDeferred: undefined,
192
+ previousShutdownDeferred: undefined,
166
193
  counter: 0,
167
194
  })
195
+ const debugInstanceId = debug?.instanceId
168
196
 
169
197
  // console.debug(`useCreateStore (${ctxValueRef.current.counter})`, ctxValueRef.current.value.stage)
170
198
 
171
199
  const inputPropsCacheRef = React.useRef({
172
200
  schema,
173
- graphQLOptions,
174
201
  otelOptions,
175
202
  boot,
176
203
  adapter,
177
204
  batchUpdates,
178
205
  disableDevtools,
179
- reactivityGraph,
180
206
  signal,
207
+ context,
208
+ params,
209
+ confirmUnsavedChanges,
210
+ syncPayload,
211
+ debugInstanceId,
181
212
  })
182
213
 
183
214
  const interrupt = (
184
215
  componentScope: Scope.CloseableScope,
185
216
  shutdownDeferred: ShutdownDeferred,
186
- error: StoreAbort | StoreInterrupted,
217
+ error: StoreInterrupted,
187
218
  ) =>
188
219
  Effect.gen(function* () {
189
220
  // console.log('[@livestore/livestore/react] interupting', error)
@@ -194,30 +225,58 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
194
225
  Effect.runFork,
195
226
  )
196
227
 
228
+ const inputPropChanges = {
229
+ schema: inputPropsCacheRef.current.schema !== schema,
230
+ otelOptions: inputPropsCacheRef.current.otelOptions !== otelOptions,
231
+ boot: inputPropsCacheRef.current.boot !== boot,
232
+ adapter: inputPropsCacheRef.current.adapter !== adapter,
233
+ batchUpdates: inputPropsCacheRef.current.batchUpdates !== batchUpdates,
234
+ disableDevtools: inputPropsCacheRef.current.disableDevtools !== disableDevtools,
235
+ signal: inputPropsCacheRef.current.signal !== signal,
236
+ context: inputPropsCacheRef.current.context !== context,
237
+ params: inputPropsCacheRef.current.params !== params,
238
+ confirmUnsavedChanges: inputPropsCacheRef.current.confirmUnsavedChanges !== confirmUnsavedChanges,
239
+ syncPayload: inputPropsCacheRef.current.syncPayload !== syncPayload,
240
+ debugInstanceId: inputPropsCacheRef.current.debugInstanceId !== debugInstanceId,
241
+ }
242
+
197
243
  if (
198
- inputPropsCacheRef.current.schema !== schema ||
199
- inputPropsCacheRef.current.graphQLOptions !== graphQLOptions ||
200
- inputPropsCacheRef.current.otelOptions !== otelOptions ||
201
- inputPropsCacheRef.current.boot !== boot ||
202
- inputPropsCacheRef.current.adapter !== adapter ||
203
- inputPropsCacheRef.current.batchUpdates !== batchUpdates ||
204
- inputPropsCacheRef.current.disableDevtools !== disableDevtools ||
205
- inputPropsCacheRef.current.reactivityGraph !== reactivityGraph ||
206
- inputPropsCacheRef.current.signal !== signal
244
+ inputPropChanges.schema ||
245
+ inputPropChanges.otelOptions ||
246
+ inputPropChanges.boot ||
247
+ inputPropChanges.adapter ||
248
+ inputPropChanges.batchUpdates ||
249
+ inputPropChanges.disableDevtools ||
250
+ inputPropChanges.signal ||
251
+ inputPropChanges.context ||
252
+ inputPropChanges.params ||
253
+ inputPropChanges.confirmUnsavedChanges ||
254
+ inputPropChanges.syncPayload
207
255
  ) {
208
256
  inputPropsCacheRef.current = {
209
257
  schema,
210
- graphQLOptions,
211
258
  otelOptions,
212
259
  boot,
213
260
  adapter,
214
261
  batchUpdates,
215
262
  disableDevtools,
216
- reactivityGraph,
217
263
  signal,
264
+ context,
265
+ params,
266
+ confirmUnsavedChanges,
267
+ syncPayload,
268
+ debugInstanceId,
218
269
  }
219
270
  if (ctxValueRef.current.componentScope !== undefined && ctxValueRef.current.shutdownDeferred !== undefined) {
220
- interrupt(ctxValueRef.current.componentScope, ctxValueRef.current.shutdownDeferred, new StoreInterrupted())
271
+ const changedInputProps = Object.keys(inputPropChanges).filter(
272
+ (key) => inputPropChanges[key as keyof typeof inputPropChanges],
273
+ )
274
+
275
+ interrupt(
276
+ ctxValueRef.current.componentScope,
277
+ ctxValueRef.current.shutdownDeferred,
278
+ new StoreInterrupted({ reason: `re-rendering due to changed input props: ${changedInputProps.join(', ')}` }),
279
+ )
221
280
  ctxValueRef.current.componentScope = undefined
222
281
  ctxValueRef.current.shutdownDeferred = undefined
223
282
  }
@@ -225,6 +284,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
225
284
  value: { stage: 'loading' },
226
285
  componentScope: undefined,
227
286
  shutdownDeferred: undefined,
287
+ previousShutdownDeferred: ctxValueRef.current.shutdownDeferred,
228
288
  counter: ctxValueRef.current.counter + 1,
229
289
  }
230
290
  }
@@ -244,18 +304,24 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
244
304
  ctxValueRef.current.shutdownDeferred !== undefined &&
245
305
  ctxValueRef.current.counter === counter
246
306
  ) {
247
- interrupt(ctxValueRef.current.componentScope, ctxValueRef.current.shutdownDeferred, new StoreAbort())
307
+ interrupt(
308
+ ctxValueRef.current.componentScope,
309
+ ctxValueRef.current.shutdownDeferred,
310
+ new StoreInterrupted({ reason: 'Aborted via provided AbortController' }),
311
+ )
248
312
  ctxValueRef.current.componentScope = undefined
249
313
  ctxValueRef.current.shutdownDeferred = undefined
250
314
  }
251
315
  })
252
316
 
253
- Effect.gen(function* () {
254
- const componentScope = yield* Scope.make()
255
- const shutdownDeferred = yield* Deferred.make<
256
- void,
257
- UnexpectedError | IntentionalShutdownCause | StoreAbort | StoreInterrupted
258
- >()
317
+ const cancel = Effect.gen(function* () {
318
+ // Wait for the previous store to fully shutdown before creating a new one
319
+ if (ctxValueRef.current.previousShutdownDeferred) {
320
+ yield* Deferred.await(ctxValueRef.current.previousShutdownDeferred)
321
+ }
322
+
323
+ const componentScope = yield* Scope.make().pipe(Effect.acquireRelease(Scope.close))
324
+ const shutdownDeferred = yield* makeShutdownDeferred
259
325
 
260
326
  ctxValueRef.current.componentScope = componentScope
261
327
  ctxValueRef.current.shutdownDeferred = shutdownDeferred
@@ -264,39 +330,39 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
264
330
  const store = yield* createStore({
265
331
  schema,
266
332
  storeId,
267
- graphQLOptions,
268
333
  boot,
269
334
  adapter,
270
- reactivityGraph,
271
335
  batchUpdates,
272
336
  disableDevtools,
273
337
  shutdownDeferred,
338
+ context,
339
+ params,
340
+ confirmUnsavedChanges,
341
+ syncPayload,
274
342
  onBootStatus: (status) => {
275
343
  if (ctxValueRef.current.value.stage === 'running' || ctxValueRef.current.value.stage === 'error') return
344
+ // NOTE sometimes when status come in in rapid succession, only the last value will be rendered by React
276
345
  setContextValue(status)
277
346
  },
347
+ debug: { instanceId: debugInstanceId },
278
348
  }).pipe(Effect.tapErrorCause((cause) => Deferred.failCause(shutdownDeferred, cause)))
279
349
 
280
350
  setContextValue({ stage: 'running', store })
281
351
  }).pipe(Scope.extend(componentScope), Effect.forkIn(componentScope))
282
352
 
283
- const shutdownContext = (cause: IntentionalShutdownCause | StoreAbort) =>
353
+ const shutdownContext = (cause: IntentionalShutdownCause | StoreInterrupted) =>
284
354
  Effect.sync(() => setContextValue({ stage: 'shutdown', cause }))
285
355
 
286
356
  yield* Deferred.await(shutdownDeferred).pipe(
287
- Effect.tapErrorCause((cause) => Effect.logDebug('[@livestore/livestore/react] shutdown', cause)),
357
+ Effect.tapErrorCause((cause) => Effect.logDebug('[@livestore/livestore/react] shutdown', Cause.pretty(cause))),
288
358
  Effect.catchTag('LiveStore.IntentionalShutdownCause', (cause) => shutdownContext(cause)),
289
- Effect.catchTag('LiveStore.StoreAbort', (cause) => shutdownContext(cause)),
359
+ Effect.catchTag('LiveStore.StoreInterrupted', (cause) => shutdownContext(cause)),
290
360
  Effect.tapError((error) => Effect.sync(() => setContextValue({ stage: 'error', error }))),
291
361
  Effect.tapDefect((defect) => Effect.sync(() => setContextValue({ stage: 'error', error: defect }))),
292
362
  Effect.exit,
293
363
  )
294
364
  }).pipe(
295
365
  Effect.scoped,
296
- // NOTE we're running the code above in a semaphore to make sure a previous store is always fully
297
- // shutdown before a new one is created - especially when shutdown logic is async. You can't trust `React.useEffect`.
298
- // Thank you to Mattia Manzati for this idea.
299
- withSemaphore(storeId),
300
366
  Effect.withSpan('@livestore/react:useCreateStore'),
301
367
  LS_DEV ? TaskTracing.withAsyncTaggingTracing((name: string) => (console as any).createTask(name)) : identity,
302
368
  provideOtel({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer }),
@@ -304,27 +370,36 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
304
370
  Effect.annotateLogs({ thread: 'window' }),
305
371
  Effect.provide(Logger.prettyWithThread('window')),
306
372
  Logger.withMinimumLogLevel(LogLevel.Debug),
307
- Effect.runFork,
373
+ Effect.runCallback,
308
374
  )
309
375
 
310
376
  return () => {
377
+ cancel()
378
+
311
379
  if (ctxValueRef.current.componentScope !== undefined && ctxValueRef.current.shutdownDeferred !== undefined) {
312
- interrupt(ctxValueRef.current.componentScope, ctxValueRef.current.shutdownDeferred, new StoreInterrupted())
380
+ interrupt(
381
+ ctxValueRef.current.componentScope,
382
+ ctxValueRef.current.shutdownDeferred,
383
+ new StoreInterrupted({ reason: 'unmounting component' }),
384
+ )
313
385
  ctxValueRef.current.componentScope = undefined
314
386
  ctxValueRef.current.shutdownDeferred = undefined
315
387
  }
316
388
  }
317
389
  }, [
318
390
  schema,
319
- graphQLOptions,
320
391
  otelOptions,
321
392
  boot,
322
393
  adapter,
323
394
  batchUpdates,
324
395
  disableDevtools,
325
396
  signal,
326
- reactivityGraph,
327
397
  storeId,
398
+ context,
399
+ params,
400
+ confirmUnsavedChanges,
401
+ syncPayload,
402
+ debugInstanceId,
328
403
  ])
329
404
 
330
405
  return ctxValueRef.current.value