@livestore/react 0.3.0-dev.46 → 0.3.0-dev.48
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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/LiveStoreProvider.d.ts +4 -1
- package/dist/LiveStoreProvider.d.ts.map +1 -1
- package/dist/LiveStoreProvider.js +44 -30
- package/dist/LiveStoreProvider.js.map +1 -1
- package/dist/LiveStoreProvider.test.js +49 -12
- package/dist/LiveStoreProvider.test.js.map +1 -1
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +2 -1
- package/dist/useQuery.js.map +1 -1
- package/package.json +6 -6
- package/src/LiveStoreProvider.test.tsx +91 -12
- package/src/LiveStoreProvider.tsx +53 -30
- package/src/__snapshots__/useClientDocument.test.tsx.snap +44 -0
- package/src/useQuery.ts +2 -1
|
@@ -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 = (
|
|
@@ -226,18 +225,33 @@ const useCreateStore = ({
|
|
|
226
225
|
Effect.runFork,
|
|
227
226
|
)
|
|
228
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
|
+
|
|
229
243
|
if (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
241
255
|
) {
|
|
242
256
|
inputPropsCacheRef.current = {
|
|
243
257
|
schema,
|
|
@@ -251,12 +265,17 @@ const useCreateStore = ({
|
|
|
251
265
|
params,
|
|
252
266
|
confirmUnsavedChanges,
|
|
253
267
|
syncPayload,
|
|
268
|
+
debugInstanceId,
|
|
254
269
|
}
|
|
255
270
|
if (ctxValueRef.current.componentScope !== undefined && ctxValueRef.current.shutdownDeferred !== undefined) {
|
|
271
|
+
const changedInputProps = Object.keys(inputPropChanges).filter(
|
|
272
|
+
(key) => inputPropChanges[key as keyof typeof inputPropChanges],
|
|
273
|
+
)
|
|
274
|
+
|
|
256
275
|
interrupt(
|
|
257
276
|
ctxValueRef.current.componentScope,
|
|
258
277
|
ctxValueRef.current.shutdownDeferred,
|
|
259
|
-
new StoreInterrupted({ reason:
|
|
278
|
+
new StoreInterrupted({ reason: `re-rendering due to changed input props: ${changedInputProps.join(', ')}` }),
|
|
260
279
|
)
|
|
261
280
|
ctxValueRef.current.componentScope = undefined
|
|
262
281
|
ctxValueRef.current.shutdownDeferred = undefined
|
|
@@ -265,6 +284,7 @@ const useCreateStore = ({
|
|
|
265
284
|
value: { stage: 'loading' },
|
|
266
285
|
componentScope: undefined,
|
|
267
286
|
shutdownDeferred: undefined,
|
|
287
|
+
previousShutdownDeferred: ctxValueRef.current.shutdownDeferred,
|
|
268
288
|
counter: ctxValueRef.current.counter + 1,
|
|
269
289
|
}
|
|
270
290
|
}
|
|
@@ -295,7 +315,12 @@ const useCreateStore = ({
|
|
|
295
315
|
})
|
|
296
316
|
|
|
297
317
|
const cancel = Effect.gen(function* () {
|
|
298
|
-
|
|
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))
|
|
299
324
|
const shutdownDeferred = yield* makeShutdownDeferred
|
|
300
325
|
|
|
301
326
|
ctxValueRef.current.componentScope = componentScope
|
|
@@ -319,6 +344,7 @@ const useCreateStore = ({
|
|
|
319
344
|
// NOTE sometimes when status come in in rapid succession, only the last value will be rendered by React
|
|
320
345
|
setContextValue(status)
|
|
321
346
|
},
|
|
347
|
+
debug: { instanceId: debugInstanceId },
|
|
322
348
|
}).pipe(Effect.tapErrorCause((cause) => Deferred.failCause(shutdownDeferred, cause)))
|
|
323
349
|
|
|
324
350
|
setContextValue({ stage: 'running', store })
|
|
@@ -337,10 +363,6 @@ const useCreateStore = ({
|
|
|
337
363
|
)
|
|
338
364
|
}).pipe(
|
|
339
365
|
Effect.scoped,
|
|
340
|
-
// NOTE we're running the code above in a semaphore to make sure a previous store is always fully
|
|
341
|
-
// shutdown before a new one is created - especially when shutdown logic is async. You can't trust `React.useEffect`.
|
|
342
|
-
// Thank you to Mattia Manzati for this idea.
|
|
343
|
-
withSemaphore(storeId),
|
|
344
366
|
Effect.withSpan('@livestore/react:useCreateStore'),
|
|
345
367
|
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name: string) => (console as any).createTask(name)) : identity,
|
|
346
368
|
provideOtel({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer }),
|
|
@@ -377,6 +399,7 @@ const useCreateStore = ({
|
|
|
377
399
|
params,
|
|
378
400
|
confirmUnsavedChanges,
|
|
379
401
|
syncPayload,
|
|
402
|
+
debugInstanceId,
|
|
380
403
|
])
|
|
381
404
|
|
|
382
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
|
-
|
|
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
|