@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/LiveStoreProvider.d.ts +4 -1
- package/dist/LiveStoreProvider.d.ts.map +1 -1
- package/dist/LiveStoreProvider.js +17 -17
- 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 +22 -17
- 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 = (
|
|
@@ -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
|
-
|
|
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
|