@livestore/react 0.4.0-dev.17 → 0.4.0-dev.19
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 +2 -2
- package/dist/LiveStoreProvider.d.ts.map +1 -1
- package/dist/LiveStoreProvider.js +2 -2
- package/dist/LiveStoreProvider.js.map +1 -1
- package/dist/LiveStoreProvider.test.js +1 -1
- package/dist/LiveStoreProvider.test.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +6 -6
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/experimental/multi-store/StoreRegistry.d.ts +11 -12
- package/dist/experimental/multi-store/StoreRegistry.d.ts.map +1 -1
- package/dist/experimental/multi-store/StoreRegistry.js +79 -43
- package/dist/experimental/multi-store/StoreRegistry.js.map +1 -1
- package/dist/experimental/multi-store/StoreRegistry.test.js +140 -49
- package/dist/experimental/multi-store/StoreRegistry.test.js.map +1 -1
- package/dist/experimental/multi-store/StoreRegistryContext.js +1 -1
- package/dist/experimental/multi-store/StoreRegistryContext.js.map +1 -1
- package/dist/experimental/multi-store/types.d.ts +6 -6
- package/dist/experimental/multi-store/types.d.ts.map +1 -1
- package/dist/useClientDocument.test.js +4 -1
- package/dist/useClientDocument.test.js.map +1 -1
- package/package.json +8 -8
- package/src/LiveStoreProvider.test.tsx +1 -1
- package/src/LiveStoreProvider.tsx +4 -4
- package/src/__snapshots__/useQuery.test.tsx.snap +12 -12
- package/src/__tests__/fixture.tsx +2 -2
- package/src/experimental/multi-store/StoreRegistry.test.ts +169 -49
- package/src/experimental/multi-store/StoreRegistry.ts +91 -47
- package/src/experimental/multi-store/StoreRegistryContext.tsx +1 -1
- package/src/experimental/multi-store/types.ts +6 -6
- package/src/useClientDocument.test.tsx +70 -70
|
@@ -4,19 +4,20 @@ import type { CachedStoreOptions, StoreId } from './types.ts'
|
|
|
4
4
|
|
|
5
5
|
type StoreEntryState<TSchema extends LiveStoreSchema> =
|
|
6
6
|
| { status: 'idle' }
|
|
7
|
-
| { status: 'loading'; promise: Promise<Store<TSchema
|
|
7
|
+
| { status: 'loading'; promise: Promise<Store<TSchema>>; abortController: AbortController }
|
|
8
8
|
| { status: 'success'; store: Store<TSchema> }
|
|
9
9
|
| { status: 'error'; error: unknown }
|
|
10
|
+
| { status: 'shutting_down'; shutdownPromise: Promise<void> }
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
* Default
|
|
13
|
+
* Default time to keep unused stores in cache.
|
|
13
14
|
*
|
|
14
15
|
* - Browser: 60 seconds (60,000ms)
|
|
15
|
-
* - SSR: Infinity (disables
|
|
16
|
+
* - SSR: Infinity (disables disposal to avoid disposing stores before server render completes)
|
|
16
17
|
*
|
|
17
18
|
* @internal Exported primarily for testing purposes.
|
|
18
19
|
*/
|
|
19
|
-
export const
|
|
20
|
+
export const DEFAULT_UNUSED_CACHE_TIME = typeof window === 'undefined' ? Number.POSITIVE_INFINITY : 60_000
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* @typeParam TSchema - The schema for this entry's store.
|
|
@@ -28,8 +29,8 @@ class StoreEntry<TSchema extends LiveStoreSchema = LiveStoreSchema> {
|
|
|
28
29
|
|
|
29
30
|
#state: StoreEntryState<TSchema> = { status: 'idle' }
|
|
30
31
|
|
|
31
|
-
#
|
|
32
|
-
#
|
|
32
|
+
#unusedCacheTime?: number
|
|
33
|
+
#disposalTimeout?: ReturnType<typeof setTimeout> | null
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Set of subscriber callbacks to notify on state changes.
|
|
@@ -41,38 +42,46 @@ class StoreEntry<TSchema extends LiveStoreSchema = LiveStoreSchema> {
|
|
|
41
42
|
this.#cache = cache
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
#
|
|
45
|
-
this.#
|
|
45
|
+
#scheduleDisposal = (): void => {
|
|
46
|
+
this.#cancelDisposal()
|
|
46
47
|
|
|
47
|
-
const
|
|
48
|
+
const effectiveTime = this.#unusedCacheTime === undefined ? DEFAULT_UNUSED_CACHE_TIME : this.#unusedCacheTime
|
|
48
49
|
|
|
49
|
-
if (
|
|
50
|
+
if (effectiveTime === Number.POSITIVE_INFINITY) return // Infinity disables disposal
|
|
50
51
|
|
|
51
|
-
this.#
|
|
52
|
-
this.#
|
|
52
|
+
this.#disposalTimeout = setTimeout(() => {
|
|
53
|
+
this.#disposalTimeout = null
|
|
53
54
|
|
|
54
55
|
// Re-check to avoid racing with a new subscription
|
|
55
56
|
if (this.#subscribers.size > 0) return
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
// Abort any in-progress loading to release resources early
|
|
59
|
+
this.#abortLoading()
|
|
60
|
+
|
|
61
|
+
// Transition to shutting_down state BEFORE starting async shutdown.
|
|
62
|
+
// This prevents new subscribers from receiving a store that's about to be disposed.
|
|
63
|
+
const shutdownPromise = this.#shutdown().finally(() => {
|
|
64
|
+
// Reset to idle so fresh loads can proceed, then remove from cache if still inactive
|
|
65
|
+
this.#setIdle()
|
|
59
66
|
if (this.#subscribers.size === 0) this.#cache.delete(this.#storeId)
|
|
60
67
|
})
|
|
61
|
-
|
|
68
|
+
|
|
69
|
+
this.#setShuttingDown(shutdownPromise)
|
|
70
|
+
}, effectiveTime)
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
#
|
|
65
|
-
if (!this.#
|
|
66
|
-
clearTimeout(this.#
|
|
67
|
-
this.#
|
|
73
|
+
#cancelDisposal = (): void => {
|
|
74
|
+
if (!this.#disposalTimeout) return
|
|
75
|
+
clearTimeout(this.#disposalTimeout)
|
|
76
|
+
this.#disposalTimeout = null
|
|
68
77
|
}
|
|
69
78
|
|
|
70
79
|
/**
|
|
71
80
|
* Transitions to the loading state.
|
|
72
81
|
*/
|
|
73
|
-
#
|
|
82
|
+
#setLoading(promise: Promise<Store<TSchema>>, abortController: AbortController): void {
|
|
74
83
|
if (this.#state.status === 'success' || this.#state.status === 'loading') return
|
|
75
|
-
this.#state = { status: 'loading', promise }
|
|
84
|
+
this.#state = { status: 'loading', promise, abortController }
|
|
76
85
|
this.#notify()
|
|
77
86
|
}
|
|
78
87
|
|
|
@@ -92,6 +101,22 @@ class StoreEntry<TSchema extends LiveStoreSchema = LiveStoreSchema> {
|
|
|
92
101
|
this.#notify()
|
|
93
102
|
}
|
|
94
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Transitions to the shutting_down state.
|
|
106
|
+
*/
|
|
107
|
+
#setShuttingDown = (shutdownPromise: Promise<void>): void => {
|
|
108
|
+
this.#state = { status: 'shutting_down', shutdownPromise }
|
|
109
|
+
this.#notify()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Transitions to the idle state.
|
|
114
|
+
*/
|
|
115
|
+
#setIdle = (): void => {
|
|
116
|
+
this.#state = { status: 'idle' }
|
|
117
|
+
// No notify needed - getOrLoad will handle the fresh load
|
|
118
|
+
}
|
|
119
|
+
|
|
95
120
|
/**
|
|
96
121
|
* Notifies all subscribers of state changes.
|
|
97
122
|
*
|
|
@@ -115,35 +140,44 @@ class StoreEntry<TSchema extends LiveStoreSchema = LiveStoreSchema> {
|
|
|
115
140
|
* @returns Unsubscribe function
|
|
116
141
|
*/
|
|
117
142
|
subscribe = (listener: () => void): Unsubscribe => {
|
|
118
|
-
this.#
|
|
143
|
+
this.#cancelDisposal()
|
|
119
144
|
this.#subscribers.add(listener)
|
|
120
145
|
return () => {
|
|
121
146
|
this.#subscribers.delete(listener)
|
|
122
|
-
// If no more subscribers remain, schedule
|
|
123
|
-
if (this.#subscribers.size === 0) this.#
|
|
147
|
+
// If no more subscribers remain, schedule disposal
|
|
148
|
+
if (this.#subscribers.size === 0) this.#scheduleDisposal()
|
|
124
149
|
}
|
|
125
150
|
}
|
|
126
151
|
|
|
127
152
|
/**
|
|
128
|
-
*
|
|
153
|
+
* Gets the loaded store or initiates loading if not already in progress.
|
|
129
154
|
*
|
|
130
155
|
* @param options - Store creation options
|
|
131
|
-
* @returns Promise that resolves to the loaded store
|
|
156
|
+
* @returns The loaded store if available, or a Promise that resolves to the loaded store
|
|
132
157
|
*
|
|
133
158
|
* @remarks
|
|
134
159
|
* This method handles the complete lifecycle of loading a store:
|
|
135
|
-
* -
|
|
160
|
+
* - Returns the store directly if already loaded (synchronous)
|
|
161
|
+
* - Returns a Promise if loading is in progress or needs to be initiated
|
|
136
162
|
* - Transitions through loading → success/error states
|
|
137
|
-
* -
|
|
163
|
+
* - Schedules disposal when loading completes without active subscribers
|
|
138
164
|
*/
|
|
139
165
|
getOrLoad = (options: CachedStoreOptions<TSchema>): Store<TSchema> | Promise<Store<TSchema>> => {
|
|
140
|
-
if (options.
|
|
166
|
+
if (options.unusedCacheTime !== undefined)
|
|
167
|
+
this.#unusedCacheTime = Math.max(this.#unusedCacheTime ?? 0, options.unusedCacheTime)
|
|
141
168
|
|
|
142
169
|
if (this.#state.status === 'success') return this.#state.store
|
|
143
170
|
if (this.#state.status === 'loading') return this.#state.promise
|
|
144
171
|
if (this.#state.status === 'error') throw this.#state.error
|
|
145
172
|
|
|
146
|
-
|
|
173
|
+
// Wait for shutdown to complete, then recursively call to load a fresh store
|
|
174
|
+
if (this.#state.status === 'shutting_down') {
|
|
175
|
+
return this.#state.shutdownPromise.then(() => this.getOrLoad(options))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const abortController = new AbortController()
|
|
179
|
+
|
|
180
|
+
const promise = createStorePromise({ ...options, signal: abortController.signal })
|
|
147
181
|
.then((store) => {
|
|
148
182
|
this.#setStore(store)
|
|
149
183
|
return store
|
|
@@ -153,19 +187,30 @@ class StoreEntry<TSchema extends LiveStoreSchema = LiveStoreSchema> {
|
|
|
153
187
|
throw error
|
|
154
188
|
})
|
|
155
189
|
.finally(() => {
|
|
156
|
-
// The store entry may have become
|
|
157
|
-
if (this.#subscribers.size === 0) this.#
|
|
190
|
+
// The store entry may have become unused (no subscribers) while loading the store
|
|
191
|
+
if (this.#subscribers.size === 0) this.#scheduleDisposal()
|
|
158
192
|
})
|
|
159
193
|
|
|
160
|
-
this.#
|
|
194
|
+
this.#setLoading(promise, abortController)
|
|
161
195
|
|
|
162
196
|
return promise
|
|
163
197
|
}
|
|
164
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Aborts an in-progress store load.
|
|
201
|
+
*
|
|
202
|
+
* This signals the createStorePromise to cancel, releasing resources like
|
|
203
|
+
* worker threads, SQLite connections, and network requests.
|
|
204
|
+
*/
|
|
205
|
+
#abortLoading = (): void => {
|
|
206
|
+
if (this.#state.status !== 'loading') return
|
|
207
|
+
this.#state.abortController.abort()
|
|
208
|
+
}
|
|
209
|
+
|
|
165
210
|
#shutdown = async (): Promise<void> => {
|
|
166
211
|
if (this.#state.status !== 'success') return
|
|
167
212
|
await this.#state.store.shutdownPromise().catch((reason) => {
|
|
168
|
-
console.warn(`Store ${this.#storeId} failed to shutdown cleanly during
|
|
213
|
+
console.warn(`Store ${this.#storeId} failed to shutdown cleanly during disposal:`, reason)
|
|
169
214
|
})
|
|
170
215
|
}
|
|
171
216
|
}
|
|
@@ -174,7 +219,7 @@ class StoreEntry<TSchema extends LiveStoreSchema = LiveStoreSchema> {
|
|
|
174
219
|
* In-memory map of {@link StoreEntry} instances keyed by {@link StoreId}.
|
|
175
220
|
*
|
|
176
221
|
* @privateRemarks
|
|
177
|
-
* The cache is intentionally small; eviction and
|
|
222
|
+
* The cache is intentionally small; eviction and disposal timers are coordinated by the client.
|
|
178
223
|
*
|
|
179
224
|
* @internal
|
|
180
225
|
*/
|
|
@@ -213,22 +258,22 @@ type DefaultStoreOptions = Partial<
|
|
|
213
258
|
>
|
|
214
259
|
> & {
|
|
215
260
|
/**
|
|
216
|
-
* The time in milliseconds that
|
|
217
|
-
* When a store becomes
|
|
261
|
+
* The time in milliseconds that unused stores remain in memory.
|
|
262
|
+
* When a store becomes unused (no subscribers), it will be disposed
|
|
218
263
|
* after this duration.
|
|
219
264
|
*
|
|
220
|
-
* Stores transition to the
|
|
265
|
+
* Stores transition to the unused state as soon as they have no
|
|
221
266
|
* subscriptions registered, so when all components which use that
|
|
222
267
|
* store have unmounted.
|
|
223
268
|
*
|
|
224
269
|
* @remarks
|
|
225
|
-
* - If set to `
|
|
270
|
+
* - If set to `Infinity`, will disable disposal
|
|
226
271
|
* - The maximum allowed time is about {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout#maximum_delay_value | 24 days}
|
|
227
272
|
*
|
|
228
273
|
* @defaultValue `60_000` (60 seconds) or `Infinity` during SSR to avoid
|
|
229
274
|
* disposing stores before server render completes.
|
|
230
275
|
*/
|
|
231
|
-
|
|
276
|
+
unusedCacheTime?: number
|
|
232
277
|
}
|
|
233
278
|
|
|
234
279
|
type StoreRegistryConfig = {
|
|
@@ -236,7 +281,7 @@ type StoreRegistryConfig = {
|
|
|
236
281
|
}
|
|
237
282
|
|
|
238
283
|
/**
|
|
239
|
-
* Store Registry coordinating
|
|
284
|
+
* Store Registry coordinating store loading, caching, and subscription
|
|
240
285
|
*
|
|
241
286
|
* @public
|
|
242
287
|
*/
|
|
@@ -259,14 +304,13 @@ export class StoreRegistry {
|
|
|
259
304
|
* Get or load a store, returning it directly if loaded or a promise if loading.
|
|
260
305
|
*
|
|
261
306
|
* @typeParam TSchema - The schema of the store to load
|
|
262
|
-
* @returns The loaded store if available, or a Promise that resolves to the store
|
|
307
|
+
* @returns The loaded store if available, or a Promise that resolves to the loaded store
|
|
263
308
|
* @throws unknown loading error
|
|
264
309
|
*
|
|
265
310
|
* @remarks
|
|
266
|
-
* -
|
|
267
|
-
* -
|
|
268
|
-
* -
|
|
269
|
-
* - This prevents re-suspension on subsequent renders when the store is already loaded
|
|
311
|
+
* - Returns the store instance directly (synchronous) when already loaded
|
|
312
|
+
* - Returns a stable Promise reference when loading is in progress or needs to be initiated
|
|
313
|
+
* - Applies default options from registry config, with call-site options taking precedence
|
|
270
314
|
*/
|
|
271
315
|
getOrLoad = <TSchema extends LiveStoreSchema>(
|
|
272
316
|
options: CachedStoreOptions<TSchema>,
|
|
@@ -285,7 +329,7 @@ export class StoreRegistry {
|
|
|
285
329
|
*
|
|
286
330
|
* @remarks
|
|
287
331
|
* - We don't return the store or throw as this is a fire-and-forget operation.
|
|
288
|
-
* - If the entry remains unused after preload resolves/rejects, it is scheduled for
|
|
332
|
+
* - If the entry remains unused after preload resolves/rejects, it is scheduled for disposal.
|
|
289
333
|
*/
|
|
290
334
|
preload = async <TSchema extends LiveStoreSchema>(options: CachedStoreOptions<TSchema>): Promise<void> => {
|
|
291
335
|
try {
|
|
@@ -17,7 +17,7 @@ export const useStoreRegistry = (override?: StoreRegistry) => {
|
|
|
17
17
|
|
|
18
18
|
const storeRegistry = React.use(StoreRegistryContext)
|
|
19
19
|
|
|
20
|
-
if (!storeRegistry) throw new Error('useStoreRegistry() must be used within <
|
|
20
|
+
if (!storeRegistry) throw new Error('useStoreRegistry() must be used within <StoreRegistryProvider>')
|
|
21
21
|
|
|
22
22
|
return storeRegistry
|
|
23
23
|
}
|
|
@@ -36,20 +36,20 @@ export type CachedStoreOptions<
|
|
|
36
36
|
otelOptions?: Partial<OtelOptions>
|
|
37
37
|
/**
|
|
38
38
|
* The time in milliseconds that this store should remain
|
|
39
|
-
* in memory after becoming
|
|
40
|
-
*
|
|
39
|
+
* in memory after becoming unused. When this store becomes
|
|
40
|
+
* unused (no subscribers), it will be disposed after this duration.
|
|
41
41
|
*
|
|
42
|
-
* Stores transition to the
|
|
42
|
+
* Stores transition to the unused state as soon as they have no
|
|
43
43
|
* subscriptions registered, so when all components which use that
|
|
44
44
|
* store have unmounted.
|
|
45
45
|
*
|
|
46
46
|
* @remarks
|
|
47
|
-
* - When different `
|
|
48
|
-
* - If set to `Infinity`, will disable
|
|
47
|
+
* - When different `unusedCacheTime` values are used for the same store, the longest one will be used.
|
|
48
|
+
* - If set to `Infinity`, will disable automatic disposal
|
|
49
49
|
* - The maximum allowed time is about {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout#maximum_delay_value | 24 days}
|
|
50
50
|
*
|
|
51
51
|
* @defaultValue `60_000` (60 seconds) or `Infinity` during SSR to avoid
|
|
52
52
|
* disposing stores before server render completes.
|
|
53
53
|
*/
|
|
54
|
-
|
|
54
|
+
unusedCacheTime?: number
|
|
55
55
|
}
|
|
@@ -254,81 +254,81 @@ Vitest.describe('useClientDocument', () => {
|
|
|
254
254
|
)
|
|
255
255
|
|
|
256
256
|
Vitest.describe('otel', () => {
|
|
257
|
-
it.each([
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
257
|
+
it.each([
|
|
258
|
+
{ strictMode: true },
|
|
259
|
+
{ strictMode: false },
|
|
260
|
+
])('should update the data based on component key strictMode=%s', async ({ strictMode }) => {
|
|
261
|
+
const exporter = new InMemorySpanExporter()
|
|
262
|
+
|
|
263
|
+
const provider = new BasicTracerProvider({
|
|
264
|
+
spanProcessors: [new SimpleSpanProcessor(exporter)],
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const otelTracer = provider.getTracer(`testing-${strictMode ? 'strict' : 'non-strict'}`)
|
|
268
|
+
|
|
269
|
+
const span = otelTracer.startSpan('test-root')
|
|
270
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
271
|
+
|
|
272
|
+
await Effect.gen(function* () {
|
|
273
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({
|
|
274
|
+
otelContext,
|
|
275
|
+
otelTracer,
|
|
276
|
+
strictMode,
|
|
264
277
|
})
|
|
265
278
|
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
279
|
+
const { result, rerender, unmount } = ReactTesting.renderHook(
|
|
280
|
+
(userId: string) => {
|
|
281
|
+
renderCount.inc()
|
|
270
282
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
})
|
|
283
|
+
const [state, setState, id] = store.useClientDocument(tables.userInfo, userId)
|
|
284
|
+
return { state, setState, id }
|
|
285
|
+
},
|
|
286
|
+
{ wrapper, initialProps: 'u1' },
|
|
287
|
+
)
|
|
277
288
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
289
|
+
expect(result.current.id).toBe('u1')
|
|
290
|
+
expect(result.current.state.username).toBe('')
|
|
291
|
+
expect(renderCount.val).toBe(1)
|
|
292
|
+
|
|
293
|
+
// For u2 we'll make sure that the row already exists,
|
|
294
|
+
// so the lazy `insert` will be skipped
|
|
295
|
+
ReactTesting.act(() => store.commit(events.UserInfoSet({ username: 'username_u2' }, 'u2')))
|
|
296
|
+
|
|
297
|
+
rerender('u2')
|
|
298
|
+
|
|
299
|
+
expect(result.current.id).toBe('u2')
|
|
300
|
+
expect(result.current.state.username).toBe('username_u2')
|
|
301
|
+
expect(renderCount.val).toBe(2)
|
|
302
|
+
|
|
303
|
+
unmount()
|
|
304
|
+
span.end()
|
|
305
|
+
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
|
|
306
|
+
|
|
307
|
+
await provider.forceFlush()
|
|
308
|
+
|
|
309
|
+
const mapAttributes = (attributes: otel.Attributes) => {
|
|
310
|
+
return ReadonlyRecord.map(attributes, (val, key) => {
|
|
311
|
+
if (key === 'code.stacktrace') {
|
|
312
|
+
return '<STACKTRACE>'
|
|
313
|
+
} else if (key === 'firstStackInfo') {
|
|
314
|
+
const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
|
|
315
|
+
// stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
|
|
316
|
+
stackInfo.frames.forEach((_) => {
|
|
317
|
+
if (_.name.includes('renderHook.wrapper')) {
|
|
318
|
+
_.name = 'renderHook.wrapper'
|
|
319
|
+
}
|
|
320
|
+
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
|
321
|
+
})
|
|
322
|
+
return JSON.stringify(stackInfo)
|
|
323
|
+
}
|
|
324
|
+
return val
|
|
325
|
+
})
|
|
326
|
+
}
|
|
281
327
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
},
|
|
285
|
-
{ wrapper, initialProps: 'u1' },
|
|
286
|
-
)
|
|
328
|
+
expect(getSimplifiedRootSpan(exporter, 'createStore', mapAttributes)).toMatchSnapshot()
|
|
329
|
+
expect(getAllSimplifiedRootSpans(exporter, 'LiveStore:commit', mapAttributes)).toMatchSnapshot()
|
|
287
330
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
expect(renderCount.val).toBe(1)
|
|
291
|
-
|
|
292
|
-
// For u2 we'll make sure that the row already exists,
|
|
293
|
-
// so the lazy `insert` will be skipped
|
|
294
|
-
ReactTesting.act(() => store.commit(events.UserInfoSet({ username: 'username_u2' }, 'u2')))
|
|
295
|
-
|
|
296
|
-
rerender('u2')
|
|
297
|
-
|
|
298
|
-
expect(result.current.id).toBe('u2')
|
|
299
|
-
expect(result.current.state.username).toBe('username_u2')
|
|
300
|
-
expect(renderCount.val).toBe(2)
|
|
301
|
-
|
|
302
|
-
unmount()
|
|
303
|
-
span.end()
|
|
304
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
|
|
305
|
-
|
|
306
|
-
await provider.forceFlush()
|
|
307
|
-
|
|
308
|
-
const mapAttributes = (attributes: otel.Attributes) => {
|
|
309
|
-
return ReadonlyRecord.map(attributes, (val, key) => {
|
|
310
|
-
if (key === 'code.stacktrace') {
|
|
311
|
-
return '<STACKTRACE>'
|
|
312
|
-
} else if (key === 'firstStackInfo') {
|
|
313
|
-
const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
|
|
314
|
-
// stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
|
|
315
|
-
stackInfo.frames.forEach((_) => {
|
|
316
|
-
if (_.name.includes('renderHook.wrapper')) {
|
|
317
|
-
_.name = 'renderHook.wrapper'
|
|
318
|
-
}
|
|
319
|
-
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
|
320
|
-
})
|
|
321
|
-
return JSON.stringify(stackInfo)
|
|
322
|
-
}
|
|
323
|
-
return val
|
|
324
|
-
})
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
expect(getSimplifiedRootSpan(exporter, 'createStore', mapAttributes)).toMatchSnapshot()
|
|
328
|
-
expect(getAllSimplifiedRootSpans(exporter, 'LiveStore:commit', mapAttributes)).toMatchSnapshot()
|
|
329
|
-
|
|
330
|
-
await provider.shutdown()
|
|
331
|
-
},
|
|
332
|
-
)
|
|
331
|
+
await provider.shutdown()
|
|
332
|
+
})
|
|
333
333
|
})
|
|
334
334
|
})
|