@livestore/react 0.3.0-dev.47 → 0.3.0-dev.49

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.
@@ -79,6 +79,9 @@ export interface LiveStoreProviderProps {
79
79
  * @default undefined
80
80
  */
81
81
  syncPayload?: Schema.JsonValue
82
+ debug?: {
83
+ instanceId?: string
84
+ }
82
85
  }
83
86
 
84
87
  const defaultRenderError = (error: UnexpectedError | unknown) =>
@@ -119,6 +122,7 @@ export const LiveStoreProvider = ({
119
122
  signal,
120
123
  confirmUnsavedChanges = true,
121
124
  syncPayload,
125
+ debug,
122
126
  }: LiveStoreProviderProps & { children?: ReactNode }): React.ReactElement => {
123
127
  const storeCtx = useCreateStore({
124
128
  storeId,
@@ -131,6 +135,7 @@ export const LiveStoreProvider = ({
131
135
  signal,
132
136
  confirmUnsavedChanges,
133
137
  syncPayload,
138
+ debug,
134
139
  })
135
140
 
136
141
  if (storeCtx.stage === 'error') {
@@ -149,23 +154,11 @@ export const LiveStoreProvider = ({
149
154
  if (Object.keys(globalThis.__debugLiveStore).length === 0) {
150
155
  globalThis.__debugLiveStore['_'] = storeCtx.store
151
156
  }
152
- globalThis.__debugLiveStore[storeId] = storeCtx.store
157
+ globalThis.__debugLiveStore[debug?.instanceId ?? storeId] = storeCtx.store
153
158
 
154
159
  return <LiveStoreContext.Provider value={storeCtx as TODO}>{children}</LiveStoreContext.Provider>
155
160
  }
156
161
 
157
- type SchemaKey = string
158
- const semaphoreMap = new Map<SchemaKey, Effect.Semaphore>()
159
-
160
- const withSemaphore = (storeId: SchemaKey) => {
161
- let semaphore = semaphoreMap.get(storeId)
162
- if (!semaphore) {
163
- semaphore = Effect.makeSemaphore(1).pipe(Effect.runSync)
164
- semaphoreMap.set(storeId, semaphore)
165
- }
166
- return semaphore.withPermits(1)
167
- }
168
-
169
162
  const useCreateStore = ({
170
163
  schema,
171
164
  storeId,
@@ -179,6 +172,7 @@ const useCreateStore = ({
179
172
  params,
180
173
  confirmUnsavedChanges,
181
174
  syncPayload,
175
+ debug,
182
176
  }: CreateStoreOptions<LiveStoreSchema> & {
183
177
  signal?: AbortSignal
184
178
  otelOptions?: Partial<OtelOptions>
@@ -188,13 +182,17 @@ const useCreateStore = ({
188
182
  value: StoreContext_ | BootStatus
189
183
  componentScope: Scope.CloseableScope | undefined
190
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
191
187
  counter: number
192
188
  }>({
193
189
  value: { stage: 'loading' },
194
190
  componentScope: undefined,
195
191
  shutdownDeferred: undefined,
192
+ previousShutdownDeferred: undefined,
196
193
  counter: 0,
197
194
  })
195
+ const debugInstanceId = debug?.instanceId
198
196
 
199
197
  // console.debug(`useCreateStore (${ctxValueRef.current.counter})`, ctxValueRef.current.value.stage)
200
198
 
@@ -210,6 +208,7 @@ const useCreateStore = ({
210
208
  params,
211
209
  confirmUnsavedChanges,
212
210
  syncPayload,
211
+ debugInstanceId,
213
212
  })
214
213
 
215
214
  const interrupt = (
@@ -238,6 +237,7 @@ const useCreateStore = ({
238
237
  params: inputPropsCacheRef.current.params !== params,
239
238
  confirmUnsavedChanges: inputPropsCacheRef.current.confirmUnsavedChanges !== confirmUnsavedChanges,
240
239
  syncPayload: inputPropsCacheRef.current.syncPayload !== syncPayload,
240
+ debugInstanceId: inputPropsCacheRef.current.debugInstanceId !== debugInstanceId,
241
241
  }
242
242
 
243
243
  if (
@@ -265,6 +265,7 @@ const useCreateStore = ({
265
265
  params,
266
266
  confirmUnsavedChanges,
267
267
  syncPayload,
268
+ debugInstanceId,
268
269
  }
269
270
  if (ctxValueRef.current.componentScope !== undefined && ctxValueRef.current.shutdownDeferred !== undefined) {
270
271
  const changedInputProps = Object.keys(inputPropChanges).filter(
@@ -283,6 +284,7 @@ const useCreateStore = ({
283
284
  value: { stage: 'loading' },
284
285
  componentScope: undefined,
285
286
  shutdownDeferred: undefined,
287
+ previousShutdownDeferred: ctxValueRef.current.shutdownDeferred,
286
288
  counter: ctxValueRef.current.counter + 1,
287
289
  }
288
290
  }
@@ -313,6 +315,11 @@ const useCreateStore = ({
313
315
  })
314
316
 
315
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
+
316
323
  const componentScope = yield* Scope.make().pipe(Effect.acquireRelease(Scope.close))
317
324
  const shutdownDeferred = yield* makeShutdownDeferred
318
325
 
@@ -337,6 +344,7 @@ const useCreateStore = ({
337
344
  // NOTE sometimes when status come in in rapid succession, only the last value will be rendered by React
338
345
  setContextValue(status)
339
346
  },
347
+ debug: { instanceId: debugInstanceId },
340
348
  }).pipe(Effect.tapErrorCause((cause) => Deferred.failCause(shutdownDeferred, cause)))
341
349
 
342
350
  setContextValue({ stage: 'running', store })
@@ -355,10 +363,6 @@ const useCreateStore = ({
355
363
  )
356
364
  }).pipe(
357
365
  Effect.scoped,
358
- // NOTE we're running the code above in a semaphore to make sure a previous store is always fully
359
- // shutdown before a new one is created - especially when shutdown logic is async. You can't trust `React.useEffect`.
360
- // Thank you to Mattia Manzati for this idea.
361
- withSemaphore(storeId),
362
366
  Effect.withSpan('@livestore/react:useCreateStore'),
363
367
  LS_DEV ? TaskTracing.withAsyncTaggingTracing((name: string) => (console as any).createTask(name)) : identity,
364
368
  provideOtel({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer }),
@@ -395,6 +399,7 @@ const useCreateStore = ({
395
399
  params,
396
400
  confirmUnsavedChanges,
397
401
  syncPayload,
402
+ debugInstanceId,
398
403
  ])
399
404
 
400
405
  return ctxValueRef.current.value
@@ -20,6 +20,28 @@ exports[`useClientDocument > otel > should update the data based on component ke
20
20
  ",
21
21
  },
22
22
  },
23
+ {
24
+ "_name": "@livestore/common:LeaderSyncProcessor:push",
25
+ "attributes": {
26
+ "batch": "undefined",
27
+ "batchSize": 1,
28
+ },
29
+ },
30
+ {
31
+ "_name": "@livestore/common:LeaderSyncProcessor:push",
32
+ "attributes": {
33
+ "batch": "undefined",
34
+ "batchSize": 1,
35
+ },
36
+ },
37
+ {
38
+ "_name": "client-session-sync-processor:pull",
39
+ "attributes": {
40
+ "code.stacktrace": "at /Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts:281:14",
41
+ "span.label": "⚠︎ Interrupted",
42
+ "status.interrupted": true,
43
+ },
44
+ },
23
45
  {
24
46
  "_name": "LiveStore:sync",
25
47
  },
@@ -224,6 +246,28 @@ exports[`useClientDocument > otel > should update the data based on component ke
224
246
  ",
225
247
  },
226
248
  },
249
+ {
250
+ "_name": "@livestore/common:LeaderSyncProcessor:push",
251
+ "attributes": {
252
+ "batch": "undefined",
253
+ "batchSize": 1,
254
+ },
255
+ },
256
+ {
257
+ "_name": "@livestore/common:LeaderSyncProcessor:push",
258
+ "attributes": {
259
+ "batch": "undefined",
260
+ "batchSize": 1,
261
+ },
262
+ },
263
+ {
264
+ "_name": "client-session-sync-processor:pull",
265
+ "attributes": {
266
+ "code.stacktrace": "at /Users/schickling/Code/overtone/submodules/livestore/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts:281:14",
267
+ "span.label": "⚠︎ Interrupted",
268
+ "status.interrupted": true,
269
+ },
270
+ },
227
271
  {
228
272
  "_name": "LiveStore:sync",
229
273
  },
package/src/useQuery.ts CHANGED
@@ -47,7 +47,8 @@ export const useQueryRef = <TQuery extends LiveQueryDef.Any>(
47
47
  React.useContext(LiveStoreContext)?.store ??
48
48
  shouldNeverHappen(`No store provided to useQuery`)
49
49
 
50
- const rcRefKey = `${store.storeId}_${queryDef.hash}`
50
+ // It's important to use all "aspects" of a store instance here, otherwise we get unexpected cache mappings
51
+ const rcRefKey = `${store.storeId}_${store.clientId}_${store.sessionId}_${queryDef.hash}`
51
52
 
52
53
  const stackInfo = React.useMemo(() => {
53
54
  Error.stackTraceLimit = 10