@livestore/livestore 0.4.0-dev.21 → 0.4.0-dev.22

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 (75) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/effect/LiveStore.d.ts +123 -2
  3. package/dist/effect/LiveStore.d.ts.map +1 -1
  4. package/dist/effect/LiveStore.js +195 -1
  5. package/dist/effect/LiveStore.js.map +1 -1
  6. package/dist/effect/mod.d.ts +1 -1
  7. package/dist/effect/mod.d.ts.map +1 -1
  8. package/dist/effect/mod.js +3 -1
  9. package/dist/effect/mod.js.map +1 -1
  10. package/dist/mod.d.ts +1 -0
  11. package/dist/mod.d.ts.map +1 -1
  12. package/dist/mod.js +1 -0
  13. package/dist/mod.js.map +1 -1
  14. package/dist/store/StoreRegistry.d.ts +190 -0
  15. package/dist/store/StoreRegistry.d.ts.map +1 -0
  16. package/dist/store/StoreRegistry.js +244 -0
  17. package/dist/store/StoreRegistry.js.map +1 -0
  18. package/dist/store/StoreRegistry.test.d.ts +2 -0
  19. package/dist/store/StoreRegistry.test.d.ts.map +1 -0
  20. package/dist/store/StoreRegistry.test.js +380 -0
  21. package/dist/store/StoreRegistry.test.js.map +1 -0
  22. package/dist/store/create-store.d.ts +50 -4
  23. package/dist/store/create-store.d.ts.map +1 -1
  24. package/dist/store/create-store.js +19 -0
  25. package/dist/store/create-store.js.map +1 -1
  26. package/dist/store/devtools.d.ts.map +1 -1
  27. package/dist/store/devtools.js +13 -0
  28. package/dist/store/devtools.js.map +1 -1
  29. package/dist/store/store-types.d.ts +10 -25
  30. package/dist/store/store-types.d.ts.map +1 -1
  31. package/dist/store/store-types.js.map +1 -1
  32. package/dist/store/store.d.ts +23 -6
  33. package/dist/store/store.d.ts.map +1 -1
  34. package/dist/store/store.js +20 -2
  35. package/dist/store/store.js.map +1 -1
  36. package/docs/building-with-livestore/complex-ui-state/index.md +0 -2
  37. package/docs/building-with-livestore/crud/index.md +0 -2
  38. package/docs/building-with-livestore/data-modeling/index.md +29 -0
  39. package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -6
  40. package/docs/building-with-livestore/opentelemetry/index.md +25 -6
  41. package/docs/building-with-livestore/rules-for-ai-agents/index.md +2 -2
  42. package/docs/building-with-livestore/state/sql-queries/index.md +22 -0
  43. package/docs/building-with-livestore/state/sqlite-schema/index.md +2 -2
  44. package/docs/building-with-livestore/store/index.md +344 -0
  45. package/docs/framework-integrations/react-integration/index.md +380 -361
  46. package/docs/framework-integrations/vue-integration/index.md +2 -2
  47. package/docs/getting-started/expo/index.md +189 -43
  48. package/docs/getting-started/react-web/index.md +77 -24
  49. package/docs/getting-started/vue/index.md +3 -3
  50. package/docs/index.md +1 -2
  51. package/docs/llms.txt +0 -1
  52. package/docs/misc/troubleshooting/index.md +3 -3
  53. package/docs/overview/how-livestore-works/index.md +1 -1
  54. package/docs/overview/introduction/index.md +409 -1
  55. package/docs/overview/why-livestore/index.md +108 -2
  56. package/docs/patterns/auth/index.md +185 -34
  57. package/docs/patterns/effect/index.md +11 -1
  58. package/docs/patterns/storybook/index.md +43 -26
  59. package/docs/platform-adapters/expo-adapter/index.md +36 -19
  60. package/docs/platform-adapters/web-adapter/index.md +71 -2
  61. package/docs/tutorial/1-setup-starter-project/index.md +5 -5
  62. package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +54 -35
  63. package/docs/tutorial/5-expand-business-logic/index.md +1 -1
  64. package/docs/tutorial/6-persist-ui-state/index.md +12 -12
  65. package/package.json +6 -6
  66. package/src/effect/LiveStore.ts +385 -3
  67. package/src/effect/mod.ts +13 -1
  68. package/src/mod.ts +1 -0
  69. package/src/store/StoreRegistry.test.ts +516 -0
  70. package/src/store/StoreRegistry.ts +393 -0
  71. package/src/store/create-store.ts +50 -4
  72. package/src/store/devtools.ts +15 -0
  73. package/src/store/store-types.ts +17 -5
  74. package/src/store/store.ts +25 -5
  75. package/docs/building-with-livestore/examples/index.md +0 -30
@@ -0,0 +1,516 @@
1
+ import { makeInMemoryAdapter } from '@livestore/adapter-web'
2
+ import { UnknownError } from '@livestore/common'
3
+ import { sleep } from '@livestore/utils'
4
+ import { Effect } from '@livestore/utils/effect'
5
+ import { describe, expect, it } from 'vitest'
6
+ import { schema } from '../utils/tests/fixture.ts'
7
+ import { type RegistryStoreOptions, StoreRegistry, storeOptions } from './StoreRegistry.ts'
8
+ import { StoreInternalsSymbol } from './store-types.ts'
9
+
10
+ describe('StoreRegistry', () => {
11
+ it('returns a promise when the store is loading', async () => {
12
+ const storeRegistry = new StoreRegistry()
13
+ const result = storeRegistry.getOrLoadPromise(testStoreOptions())
14
+
15
+ expect(result).toBeInstanceOf(Promise)
16
+
17
+ // Clean up
18
+ const store = await result
19
+ await store.shutdownPromise()
20
+ })
21
+
22
+ it('returns cached store synchronously after first load resolves', async () => {
23
+ const storeRegistry = new StoreRegistry()
24
+
25
+ const initial = storeRegistry.getOrLoadPromise(testStoreOptions())
26
+ expect(initial).toBeInstanceOf(Promise)
27
+
28
+ const store = await initial
29
+
30
+ const cached = storeRegistry.getOrLoadPromise(testStoreOptions())
31
+ expect(cached).toBe(store)
32
+ expect(cached).not.toBeInstanceOf(Promise)
33
+
34
+ // Clean up
35
+ await store.shutdownPromise()
36
+ })
37
+
38
+ it('reuses the same promise for concurrent getOrLoadPromise calls while loading', async () => {
39
+ const storeRegistry = new StoreRegistry()
40
+ const options = testStoreOptions()
41
+
42
+ const first = storeRegistry.getOrLoadPromise(options)
43
+ const second = storeRegistry.getOrLoadPromise(options)
44
+
45
+ // Both should be the same promise
46
+ expect(first).toBe(second)
47
+ expect(first).toBeInstanceOf(Promise)
48
+
49
+ const store = await first
50
+
51
+ // Both promises should resolve to the same store
52
+ expect(await second).toBe(store)
53
+
54
+ // Clean up
55
+ await store.shutdownPromise()
56
+ })
57
+
58
+ it('throws synchronously and rethrows on subsequent calls for sync failures', () => {
59
+ const storeRegistry = new StoreRegistry()
60
+
61
+ const badOptions = testStoreOptions({
62
+ // @ts-expect-error - intentionally passing invalid adapter to trigger error
63
+ adapter: null,
64
+ })
65
+
66
+ // First call throws synchronously
67
+ expect(() => storeRegistry.getOrLoadPromise(badOptions)).toThrow()
68
+
69
+ // Subsequent call should also throw synchronously (cached error)
70
+ expect(() => storeRegistry.getOrLoadPromise(badOptions)).toThrow()
71
+ })
72
+
73
+ it('caches and rethrows rejection on subsequent calls for async failures', async () => {
74
+ const storeRegistry = new StoreRegistry()
75
+
76
+ // Create an adapter that fails asynchronously (after yielding to the event loop)
77
+ const failingAdapter = () =>
78
+ Effect.gen(function* () {
79
+ yield* Effect.sleep(0) // Force async execution
80
+ return yield* UnknownError.make({ cause: new Error('Async failure') })
81
+ })
82
+ const badOptions = testStoreOptions({
83
+ adapter: failingAdapter,
84
+ })
85
+
86
+ // First call returns a promise that rejects
87
+ await expect(storeRegistry.getOrLoadPromise(badOptions)).rejects.toThrow()
88
+
89
+ // Subsequent call should throw the cached error synchronously (RcMap caches failures)
90
+ expect(() => storeRegistry.getOrLoadPromise(badOptions)).toThrow()
91
+ })
92
+
93
+ it('throws the same error instance on multiple calls after failure', async () => {
94
+ const storeRegistry = new StoreRegistry()
95
+
96
+ // Create an adapter that fails asynchronously
97
+ const failingAdapter = () =>
98
+ Effect.gen(function* () {
99
+ yield* Effect.sleep(0) // Force async execution
100
+ return yield* UnknownError.make({ cause: new Error('Async failure') })
101
+ })
102
+
103
+ const badOptions = testStoreOptions({
104
+ adapter: failingAdapter,
105
+ })
106
+
107
+ // Wait for the first failure
108
+ await expect(storeRegistry.getOrLoadPromise(badOptions)).rejects.toThrow()
109
+
110
+ // Capture the errors from subsequent calls
111
+ let error1: unknown
112
+ let error2: unknown
113
+
114
+ try {
115
+ storeRegistry.getOrLoadPromise(badOptions)
116
+ } catch (err) {
117
+ error1 = err
118
+ }
119
+
120
+ try {
121
+ storeRegistry.getOrLoadPromise(badOptions)
122
+ } catch (err) {
123
+ error2 = err
124
+ }
125
+
126
+ // Both should be the exact same error instance (cached)
127
+ expect(error1).toBeDefined()
128
+ expect(error1).toBe(error2)
129
+ })
130
+
131
+ it('disposes store after unusedCacheTime expires', async () => {
132
+ const unusedCacheTime = 25
133
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime } })
134
+ const options = testStoreOptions()
135
+
136
+ const store = await storeRegistry.getOrLoadPromise(options)
137
+
138
+ // Store should be cached
139
+ expect(storeRegistry.getOrLoadPromise(options)).toBe(store)
140
+
141
+ // Wait for disposal
142
+ await sleep(unusedCacheTime + 50)
143
+
144
+ // After disposal, store should be removed
145
+ // The store is removed from cache, so next getOrLoadStore creates a new one
146
+ const nextStore = await storeRegistry.getOrLoadPromise(options)
147
+
148
+ // Should be a different store instance
149
+ expect(nextStore).not.toBe(store)
150
+ expect(nextStore[StoreInternalsSymbol].clientSession.debugInstanceId).toBeDefined()
151
+
152
+ // Clean up the second store (first one was disposed)
153
+ await nextStore.shutdownPromise()
154
+ })
155
+
156
+ it('does not dispose when unusedCacheTime is Infinity', async () => {
157
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime: Number.POSITIVE_INFINITY } })
158
+ const options = testStoreOptions()
159
+
160
+ const store = await storeRegistry.getOrLoadPromise(options)
161
+
162
+ // Store should be cached
163
+ expect(storeRegistry.getOrLoadPromise(options)).toBe(store)
164
+
165
+ // Wait a reasonable duration to verify no disposal
166
+ await sleep(100)
167
+
168
+ // Store should still be cached (not disposed)
169
+ expect(storeRegistry.getOrLoadPromise(options)).toBe(store)
170
+
171
+ // Clean up manually
172
+ await store.shutdownPromise()
173
+ })
174
+
175
+ it('schedules disposal if store becomes unused during loading', async () => {
176
+ const unusedCacheTime = 50
177
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime } })
178
+ const options = testStoreOptions()
179
+
180
+ // Start loading without any retain
181
+ const storePromise = storeRegistry.getOrLoadPromise(options)
182
+
183
+ // Wait for store to load (no retain registered)
184
+ const store = await storePromise
185
+
186
+ // Since there were no retain when loading completed, disposal should be scheduled
187
+ await sleep(unusedCacheTime + 50)
188
+
189
+ // Store should be disposed
190
+ const nextStore = await storeRegistry.getOrLoadPromise(options)
191
+ expect(nextStore).not.toBe(store)
192
+
193
+ await nextStore.shutdownPromise()
194
+ })
195
+
196
+ // This test is skipped because Effect doesn't yet support different `idleTimeToLive` values for each resource in `RcMap`
197
+ // See https://github.com/livestorejs/livestore/issues/917
198
+ it.skip('allows call-site options to override default options', async () => {
199
+ const storeRegistry = new StoreRegistry({
200
+ defaultOptions: {
201
+ unusedCacheTime: 1000, // Default is long
202
+ },
203
+ })
204
+
205
+ const options = testStoreOptions({
206
+ unusedCacheTime: 10, // Override with shorter time
207
+ })
208
+
209
+ const store = await storeRegistry.getOrLoadPromise(options)
210
+
211
+ // Wait for the override time (10ms)
212
+ await sleep(10)
213
+
214
+ // Should be disposed according to the override time, not default
215
+ const nextStore = await storeRegistry.getOrLoadPromise(options)
216
+ expect(nextStore).not.toBe(store)
217
+
218
+ await nextStore.shutdownPromise()
219
+ })
220
+
221
+ // This test is skipped because we don't yet support dynamic `unusedCacheTime` updates for cached stores.
222
+ // See https://github.com/livestorejs/livestore/issues/918
223
+ it.skip('keeps the longest unusedCacheTime seen for a store when options vary across calls', async () => {
224
+ const storeRegistry = new StoreRegistry()
225
+
226
+ const options = testStoreOptions({ unusedCacheTime: 10 })
227
+ const release = storeRegistry.retain(options)
228
+
229
+ const store = await storeRegistry.getOrLoadPromise(options)
230
+
231
+ // Call with longer unusedCacheTime
232
+ await storeRegistry.getOrLoadPromise(testStoreOptions({ unusedCacheTime: 100 }))
233
+
234
+ release()
235
+
236
+ // After 99ms, store should still be alive (100ms unusedCacheTime used)
237
+ await sleep(99)
238
+
239
+ // Store should still be cached
240
+ expect(storeRegistry.getOrLoadPromise(options)).toBe(store)
241
+
242
+ // After the full 100ms, store should be disposed
243
+ await sleep(1)
244
+
245
+ // Next getOrLoadStore should create a new store
246
+ const nextStore = await storeRegistry.getOrLoadPromise(options)
247
+ expect(nextStore).not.toBe(store)
248
+
249
+ // Clean up the second store (first one was disposed)
250
+ await nextStore.shutdownPromise()
251
+ })
252
+
253
+ it('preload does not throw', async () => {
254
+ const storeRegistry = new StoreRegistry()
255
+
256
+ // Create invalid options that would cause an error
257
+ const badOptions = testStoreOptions({
258
+ // @ts-expect-error - intentionally passing invalid adapter to trigger error
259
+ adapter: null,
260
+ })
261
+
262
+ // preload should not throw
263
+ await expect(storeRegistry.preload(badOptions)).resolves.toBeUndefined()
264
+
265
+ // But subsequent getOrLoadStore should throw the cached error
266
+ expect(() => storeRegistry.getOrLoadPromise(badOptions)).toThrow()
267
+ })
268
+
269
+ it('handles rapid retain/release cycles without errors', async () => {
270
+ const unusedCacheTime = 50
271
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime } })
272
+ const options = testStoreOptions()
273
+
274
+ const store = await storeRegistry.getOrLoadPromise(options)
275
+
276
+ // Rapidly retain and release multiple times
277
+ for (let i = 0; i < 10; i++) {
278
+ const release = storeRegistry.retain(options)
279
+ release()
280
+ }
281
+
282
+ // Wait for disposal to trigger
283
+ await sleep(unusedCacheTime + 50)
284
+
285
+ // Store should be disposed after the last release
286
+ const nextStore = await storeRegistry.getOrLoadPromise(options)
287
+ expect(nextStore).not.toBe(store)
288
+
289
+ await nextStore.shutdownPromise()
290
+ })
291
+
292
+ it('cancels disposal when new retain', async () => {
293
+ const unusedCacheTime = 50
294
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime } })
295
+ const options = testStoreOptions()
296
+
297
+ const store = await storeRegistry.getOrLoadPromise(options)
298
+
299
+ // Wait almost to disposal threshold
300
+ await sleep(unusedCacheTime - 5)
301
+
302
+ // Add a new retain before disposal triggers
303
+ const release = storeRegistry.retain(options)
304
+
305
+ // Complete the original unusedCacheTime
306
+ await sleep(5)
307
+
308
+ // Store should not have been disposed because we added a retain
309
+ expect(storeRegistry.getOrLoadPromise(options)).toBe(store)
310
+
311
+ // Clean up
312
+ release()
313
+ await sleep(unusedCacheTime + 50)
314
+
315
+ // Now it should be disposed
316
+ const nextStore = await storeRegistry.getOrLoadPromise(options)
317
+ expect(nextStore).not.toBe(store)
318
+
319
+ await nextStore.shutdownPromise()
320
+ })
321
+
322
+ it('aborts loading when disposal fires while store is still loading', async () => {
323
+ const unusedCacheTime = 10
324
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime } })
325
+ const options = testStoreOptions()
326
+
327
+ // Retain briefly to trigger getOrLoadStore and then release
328
+ const release = storeRegistry.retain(options)
329
+
330
+ // Start loading
331
+ const loadPromise = storeRegistry.getOrLoadPromise(options)
332
+
333
+ // Attach a catch handler to prevent unhandled rejection when the load is aborted
334
+ const abortedPromise = (loadPromise as Promise<unknown>).catch(() => {
335
+ // Expected: load was aborted by disposal
336
+ })
337
+
338
+ // Release immediately, which schedules disposal
339
+ release()
340
+
341
+ // Wait for disposal to trigger
342
+ await sleep(unusedCacheTime + 50)
343
+
344
+ // Wait for the abort to complete
345
+ await abortedPromise
346
+
347
+ // After abort, a new getOrLoadStore should start a fresh load
348
+ const freshLoadPromise = storeRegistry.getOrLoadPromise(options)
349
+
350
+ // This should be a new promise (not the aborted one)
351
+ expect(freshLoadPromise).toBeInstanceOf(Promise)
352
+ expect(freshLoadPromise).not.toBe(loadPromise)
353
+
354
+ // Wait for fresh load to complete
355
+ const store = await freshLoadPromise
356
+ expect(store).toBeDefined()
357
+
358
+ await store.shutdownPromise()
359
+ })
360
+
361
+ it('retain keeps store alive past unusedCacheTime', async () => {
362
+ const unusedCacheTime = 50
363
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime } })
364
+ const options = testStoreOptions()
365
+
366
+ // Load the store
367
+ const store = await storeRegistry.getOrLoadPromise(options)
368
+
369
+ // Retain the store before disposal could fire
370
+ const release = storeRegistry.retain(options)
371
+
372
+ // Wait past the unusedCacheTime
373
+ await sleep(unusedCacheTime + 50)
374
+
375
+ // Store should still be cached because retain keeps it alive
376
+ const cachedStore = storeRegistry.getOrLoadPromise(options)
377
+ expect(cachedStore).toBe(store)
378
+
379
+ release()
380
+ await store.shutdownPromise()
381
+ })
382
+
383
+ it('manages multiple stores with different IDs independently', async () => {
384
+ const unusedCacheTime = 50
385
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime } })
386
+
387
+ const options1 = testStoreOptions({ storeId: 'store-1' })
388
+ const options2 = testStoreOptions({ storeId: 'store-2' })
389
+
390
+ const store1 = await storeRegistry.getOrLoadPromise(options1)
391
+ const store2 = await storeRegistry.getOrLoadPromise(options2)
392
+
393
+ // Should be different store instances
394
+ expect(store1).not.toBe(store2)
395
+
396
+ // Both should be cached independently
397
+ expect(storeRegistry.getOrLoadPromise(options1)).toBe(store1)
398
+ expect(storeRegistry.getOrLoadPromise(options2)).toBe(store2)
399
+
400
+ // Wait for both stores to be disposed
401
+ await sleep(unusedCacheTime + 50)
402
+
403
+ // Both stores should be disposed, so next getOrLoadStore creates new ones
404
+ const newStore1 = await storeRegistry.getOrLoadPromise(options1)
405
+ expect(newStore1).not.toBe(store1)
406
+
407
+ const newStore2 = await storeRegistry.getOrLoadPromise(options2)
408
+ expect(newStore2).not.toBe(store2)
409
+
410
+ // Clean up
411
+ await newStore1.shutdownPromise()
412
+ await newStore2.shutdownPromise()
413
+ })
414
+
415
+ it('applies default options from constructor', async () => {
416
+ const storeRegistry = new StoreRegistry({
417
+ defaultOptions: {
418
+ unusedCacheTime: 100,
419
+ },
420
+ })
421
+
422
+ const options = testStoreOptions()
423
+
424
+ const store = await storeRegistry.getOrLoadPromise(options)
425
+
426
+ // Verify the store loads successfully
427
+ expect(store).toBeDefined()
428
+ expect(store[StoreInternalsSymbol].clientSession.debugInstanceId).toBeDefined()
429
+
430
+ // Verify configured default unusedCacheTime is applied by checking disposal doesn't happen before it
431
+ await sleep(50)
432
+
433
+ // Store should still be cached after 50ms (default is 100ms)
434
+ expect(storeRegistry.getOrLoadPromise(options)).toBe(store)
435
+
436
+ await store.shutdownPromise()
437
+ })
438
+
439
+ it('prevents getOrLoadStore from returning a dying store', async () => {
440
+ const unusedCacheTime = 25
441
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime } })
442
+ const options = testStoreOptions()
443
+
444
+ // Load the store and wait for it to be ready
445
+ const originalStore = await storeRegistry.getOrLoadPromise(options)
446
+
447
+ // Verify store is cached
448
+ expect(storeRegistry.getOrLoadPromise(options)).toBe(originalStore)
449
+
450
+ // Wait for disposal to trigger
451
+ await sleep(unusedCacheTime + 50)
452
+
453
+ // After disposal, the cache should be cleared
454
+ // Calling getOrLoadStore should start a fresh load (return Promise)
455
+ const storeOrPromise = storeRegistry.getOrLoadPromise(options)
456
+
457
+ if (!(storeOrPromise instanceof Promise)) {
458
+ expect.fail('getOrLoadStore returned dying store synchronously instead of starting fresh load')
459
+ }
460
+
461
+ const freshStore = await storeOrPromise
462
+ // A fresh load was triggered because cache was cleared
463
+ expect(freshStore).not.toBe(originalStore)
464
+ await freshStore.shutdownPromise()
465
+ })
466
+
467
+ it('warms the cache so subsequent getOrLoadStore is synchronous after preload', async () => {
468
+ const storeRegistry = new StoreRegistry()
469
+ const options = testStoreOptions()
470
+
471
+ // Preload the store
472
+ await storeRegistry.preload(options)
473
+
474
+ // Subsequent getOrLoadStore should return synchronously (not a Promise)
475
+ const store = storeRegistry.getOrLoadPromise(options)
476
+ expect(store).not.toBeInstanceOf(Promise)
477
+
478
+ // TypeScript doesn't narrow the type, so we need to assert
479
+ if (store instanceof Promise) {
480
+ throw new Error('Expected store, got Promise')
481
+ }
482
+
483
+ // Clean up
484
+ await store.shutdownPromise()
485
+ })
486
+
487
+ it('schedules disposal after preload if no retainers are added', async () => {
488
+ const unusedCacheTime = 50
489
+ const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime } })
490
+ const options = testStoreOptions()
491
+
492
+ // Preload without retaining
493
+ await storeRegistry.preload(options)
494
+
495
+ // Get the store
496
+ const store = storeRegistry.getOrLoadPromise(options)
497
+ expect(store).not.toBeInstanceOf(Promise)
498
+
499
+ // Wait for disposal to trigger
500
+ await sleep(unusedCacheTime + 50)
501
+
502
+ // Store should be disposed since no retainers were added
503
+ const nextStore = await storeRegistry.getOrLoadPromise(options)
504
+ expect(nextStore).not.toBe(store)
505
+
506
+ await nextStore.shutdownPromise()
507
+ })
508
+ })
509
+
510
+ const testStoreOptions = (overrides: Partial<RegistryStoreOptions<typeof schema>> = {}) =>
511
+ storeOptions({
512
+ storeId: 'test-store',
513
+ schema,
514
+ adapter: makeInMemoryAdapter(),
515
+ ...overrides,
516
+ })