@livestore/react 0.4.0-dev.8 → 0.4.0
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/README.md +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/StoreRegistryContext.d.ts +56 -0
- package/dist/StoreRegistryContext.d.ts.map +1 -0
- package/dist/StoreRegistryContext.js +61 -0
- package/dist/StoreRegistryContext.js.map +1 -0
- package/dist/__tests__/fixture.d.ts +12 -283
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +12 -86
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/experimental/components/LiveList.d.ts +4 -2
- package/dist/experimental/components/LiveList.d.ts.map +1 -1
- package/dist/experimental/components/LiveList.js +10 -8
- package/dist/experimental/components/LiveList.js.map +1 -1
- package/dist/mod.d.ts +8 -5
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +6 -4
- package/dist/mod.js.map +1 -1
- package/dist/useClientDocument.d.ts +12 -4
- package/dist/useClientDocument.d.ts.map +1 -1
- package/dist/useClientDocument.js +4 -18
- package/dist/useClientDocument.js.map +1 -1
- package/dist/useClientDocument.test.js +21 -9
- package/dist/useClientDocument.test.js.map +1 -1
- package/dist/useQuery.d.ts +29 -8
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +43 -70
- package/dist/useQuery.js.map +1 -1
- package/dist/useQuery.test.js +57 -13
- package/dist/useQuery.test.js.map +1 -1
- package/dist/useRcResource.d.ts +1 -1
- package/dist/useRcResource.d.ts.map +1 -1
- package/dist/useRcResource.js +41 -34
- package/dist/useRcResource.js.map +1 -1
- package/dist/useRcResource.test.js +72 -50
- package/dist/useRcResource.test.js.map +1 -1
- package/dist/useStore.d.ts +74 -7
- package/dist/useStore.d.ts.map +1 -1
- package/dist/useStore.js +82 -16
- package/dist/useStore.js.map +1 -1
- package/dist/useStore.test.d.ts +2 -0
- package/dist/useStore.test.d.ts.map +1 -0
- package/dist/useStore.test.js +241 -0
- package/dist/useStore.test.js.map +1 -0
- package/dist/useSyncStatus.d.ts +22 -0
- package/dist/useSyncStatus.d.ts.map +1 -0
- package/dist/useSyncStatus.js +28 -0
- package/dist/useSyncStatus.js.map +1 -0
- package/package.json +68 -25
- package/src/StoreRegistryContext.tsx +70 -0
- package/src/__snapshots__/useClientDocument.test.tsx.snap +112 -78
- package/src/__snapshots__/useQuery.test.tsx.snap +12 -12
- package/src/__tests__/fixture.tsx +29 -133
- package/src/experimental/components/LiveList.tsx +23 -10
- package/src/mod.ts +8 -12
- package/src/useClientDocument.test.tsx +90 -79
- package/src/useClientDocument.ts +19 -38
- package/src/useQuery.test.tsx +99 -13
- package/src/useQuery.ts +74 -89
- package/src/useRcResource.test.tsx +115 -59
- package/src/useRcResource.ts +51 -37
- package/src/useStore.test.tsx +347 -0
- package/src/useStore.ts +115 -22
- package/src/useSyncStatus.ts +34 -0
- package/dist/LiveStoreContext.d.ts +0 -13
- package/dist/LiveStoreContext.d.ts.map +0 -1
- package/dist/LiveStoreContext.js +0 -3
- package/dist/LiveStoreContext.js.map +0 -1
- package/dist/LiveStoreProvider.d.ts +0 -65
- package/dist/LiveStoreProvider.d.ts.map +0 -1
- package/dist/LiveStoreProvider.js +0 -221
- package/dist/LiveStoreProvider.js.map +0 -1
- package/dist/LiveStoreProvider.test.d.ts +0 -2
- package/dist/LiveStoreProvider.test.d.ts.map +0 -1
- package/dist/LiveStoreProvider.test.js +0 -117
- package/dist/LiveStoreProvider.test.js.map +0 -1
- package/dist/utils/stack-info.d.ts +0 -4
- package/dist/utils/stack-info.d.ts.map +0 -1
- package/dist/utils/stack-info.js +0 -10
- package/dist/utils/stack-info.js.map +0 -1
- package/src/LiveStoreContext.ts +0 -14
- package/src/LiveStoreProvider.test.tsx +0 -248
- package/src/LiveStoreProvider.tsx +0 -413
- package/src/ambient.d.ts +0 -1
- package/src/utils/stack-info.ts +0 -13
package/dist/useStore.d.ts
CHANGED
|
@@ -1,9 +1,76 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type { LiveStoreSchema } from '@livestore/common/schema';
|
|
2
|
+
import type { RegistryStoreOptions, Store, SyncStatus } from '@livestore/livestore';
|
|
3
|
+
import type { Schema } from '@livestore/utils/effect';
|
|
4
|
+
import { useClientDocument } from './useClientDocument.ts';
|
|
5
|
+
import { useQuery } from './useQuery.ts';
|
|
6
|
+
/**
|
|
7
|
+
* Returns a store instance augmented with hooks (`store.useQuery()` and `store.useClientDocument()`) for reactive queries.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* function Issue() {
|
|
12
|
+
* // Suspends until loaded or returns immediately if already loaded
|
|
13
|
+
* const issueStore = useStore(issueStoreOptions('abc123'))
|
|
14
|
+
* const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
|
|
15
|
+
*
|
|
16
|
+
* const toggleStatus = () =>
|
|
17
|
+
* issueStore.commit(
|
|
18
|
+
* issueEvents.issueStatusChanged({
|
|
19
|
+
* id: issue.id,
|
|
20
|
+
* status: issue.status === 'done' ? 'todo' : 'done',
|
|
21
|
+
* }),
|
|
22
|
+
* )
|
|
23
|
+
*
|
|
24
|
+
* const preloadParentIssue = (issueId: string) =>
|
|
25
|
+
* storeRegistry.preload({
|
|
26
|
+
* ...issueStoreOptions(issueId),
|
|
27
|
+
* unusedCacheTime: 10_000,
|
|
28
|
+
* })
|
|
29
|
+
*
|
|
30
|
+
* return (
|
|
31
|
+
* <>
|
|
32
|
+
* <h2>{issue.title}</h2>
|
|
33
|
+
* <button onClick={() => toggleStatus()}>Toggle Status</button>
|
|
34
|
+
* <button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
|
|
35
|
+
* </>
|
|
36
|
+
* )
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* - Suspends until the store is loaded.
|
|
42
|
+
* - Store is cached by its `storeId` in the `StoreRegistry`. Multiple calls with the same `storeId` return the same store instance.
|
|
43
|
+
* - Store is cached as long as it's being used, and after `unusedCacheTime` expires (default `60_000` ms in browser, `Infinity` in non-browser)
|
|
44
|
+
* - Default store options can be configured in `StoreRegistry` constructor.
|
|
45
|
+
* - Store options are only applied when the store is loaded. Subsequent calls with different options will not affect the store if it's already loaded and cached in the registry.
|
|
46
|
+
*
|
|
47
|
+
* @typeParam TSchema - The schema type for the store
|
|
48
|
+
* @returns The loaded store instance augmented with React hooks
|
|
49
|
+
* @throws unknown - store loading error or if called outside `<StoreRegistryProvider>`
|
|
50
|
+
*/
|
|
51
|
+
export declare const useStore: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Store<TSchema, TContext> & ReactApi;
|
|
52
|
+
/**
|
|
53
|
+
* React-specific methods added to the Store when used via React hooks.
|
|
54
|
+
*
|
|
55
|
+
* These methods are attached by `withReactApi()` and `useStore()`, allowing you
|
|
56
|
+
* to call `store.useQuery()` and `store.useClientDocument()` directly on the
|
|
57
|
+
* Store instance.
|
|
58
|
+
*/
|
|
59
|
+
export type ReactApi = {
|
|
60
|
+
/** Hook version of query subscription—re-renders component when query result changes */
|
|
61
|
+
useQuery: typeof useQuery;
|
|
62
|
+
/** Hook for reading and writing client-document tables with React state semantics */
|
|
63
|
+
useClientDocument: typeof useClientDocument;
|
|
64
|
+
/** Hook for subscribing to sync status changes */
|
|
65
|
+
useSyncStatus: () => SyncStatus;
|
|
8
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Augments a Store instance with React-specific methods (`useQuery`, `useClientDocument`).
|
|
69
|
+
*
|
|
70
|
+
* This is called automatically by `useStore()`. You typically don't need to call it
|
|
71
|
+
* directly unless you're building custom integrations.
|
|
72
|
+
*
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
export declare const withReactApi: <TSchema extends LiveStoreSchema, TContext = {}>(store: Store<TSchema, TContext>) => Store<TSchema, TContext> & ReactApi;
|
|
9
76
|
//# sourceMappingURL=useStore.d.ts.map
|
package/dist/useStore.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStore.d.ts","sourceRoot":"","sources":["../src/useStore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useStore.d.ts","sourceRoot":"","sources":["../src/useStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACnF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAGrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,eAAO,MAAM,QAAQ,GACnB,OAAO,SAAS,eAAe,EAC/B,QAAQ,GAAG,EAAE,EACb,kBAAkB,SAAS,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,MAAM,CAAC,SAAS,EAEvE,SAAS,oBAAoB,CAAC,OAAO,EAAE,QAAQ,EAAE,kBAAkB,CAAC,KACnE,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,QA2B7B,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,wFAAwF;IACxF,QAAQ,EAAE,OAAO,QAAQ,CAAA;IACzB,qFAAqF;IACrF,iBAAiB,EAAE,OAAO,iBAAiB,CAAA;IAC3C,kDAAkD;IAClD,aAAa,EAAE,MAAM,UAAU,CAAA;CAChC,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,SAAS,eAAe,EAAE,QAAQ,GAAG,EAAE,EACzE,OAAO,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,KAC9B,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,QAW7B,CAAA"}
|
package/dist/useStore.js
CHANGED
|
@@ -1,26 +1,92 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useStoreRegistry } from "./StoreRegistryContext.js";
|
|
3
3
|
import { useClientDocument } from "./useClientDocument.js";
|
|
4
4
|
import { useQuery } from "./useQuery.js";
|
|
5
|
+
import { useSyncStatus } from "./useSyncStatus.js";
|
|
6
|
+
/**
|
|
7
|
+
* Returns a store instance augmented with hooks (`store.useQuery()` and `store.useClientDocument()`) for reactive queries.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* function Issue() {
|
|
12
|
+
* // Suspends until loaded or returns immediately if already loaded
|
|
13
|
+
* const issueStore = useStore(issueStoreOptions('abc123'))
|
|
14
|
+
* const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
|
|
15
|
+
*
|
|
16
|
+
* const toggleStatus = () =>
|
|
17
|
+
* issueStore.commit(
|
|
18
|
+
* issueEvents.issueStatusChanged({
|
|
19
|
+
* id: issue.id,
|
|
20
|
+
* status: issue.status === 'done' ? 'todo' : 'done',
|
|
21
|
+
* }),
|
|
22
|
+
* )
|
|
23
|
+
*
|
|
24
|
+
* const preloadParentIssue = (issueId: string) =>
|
|
25
|
+
* storeRegistry.preload({
|
|
26
|
+
* ...issueStoreOptions(issueId),
|
|
27
|
+
* unusedCacheTime: 10_000,
|
|
28
|
+
* })
|
|
29
|
+
*
|
|
30
|
+
* return (
|
|
31
|
+
* <>
|
|
32
|
+
* <h2>{issue.title}</h2>
|
|
33
|
+
* <button onClick={() => toggleStatus()}>Toggle Status</button>
|
|
34
|
+
* <button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
|
|
35
|
+
* </>
|
|
36
|
+
* )
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* - Suspends until the store is loaded.
|
|
42
|
+
* - Store is cached by its `storeId` in the `StoreRegistry`. Multiple calls with the same `storeId` return the same store instance.
|
|
43
|
+
* - Store is cached as long as it's being used, and after `unusedCacheTime` expires (default `60_000` ms in browser, `Infinity` in non-browser)
|
|
44
|
+
* - Default store options can be configured in `StoreRegistry` constructor.
|
|
45
|
+
* - Store options are only applied when the store is loaded. Subsequent calls with different options will not affect the store if it's already loaded and cached in the registry.
|
|
46
|
+
*
|
|
47
|
+
* @typeParam TSchema - The schema type for the store
|
|
48
|
+
* @returns The loaded store instance augmented with React hooks
|
|
49
|
+
* @throws unknown - store loading error or if called outside `<StoreRegistryProvider>`
|
|
50
|
+
*/
|
|
51
|
+
export const useStore = (options) => {
|
|
52
|
+
const storeRegistry = useStoreRegistry();
|
|
53
|
+
// Called on every render (intentionally not memoized). For already-loaded stores this returns
|
|
54
|
+
// the Store synchronously, so React.use() is skipped entirely. Caching the initial Promise via
|
|
55
|
+
// useMemo would cause React.use() to be called with a resolved Promise on subsequent renders,
|
|
56
|
+
// which blocks React transitions from committing.
|
|
57
|
+
const storeOrPromise = storeRegistry.getOrLoadPromise(options);
|
|
58
|
+
const store = storeOrPromise instanceof Promise ? React.use(storeOrPromise) : storeOrPromise;
|
|
59
|
+
// NOTE: retain() must be declared AFTER the React.use() call above. When React.use() suspends
|
|
60
|
+
// the component, any hooks declared before it get committed while hooks after the suspension
|
|
61
|
+
// point (including those in the caller) don't. On re-render when the store resolves synchronously,
|
|
62
|
+
// those late hooks appear for the first time, causing a hook-order violation in strict mode.
|
|
63
|
+
// By placing useEffect after React.use(), no effect hooks are committed during suspension,
|
|
64
|
+
// so React treats all effects as fresh mounts on the first successful render.
|
|
65
|
+
//
|
|
66
|
+
// retain() is called in useEffect (after render), while getOrLoadPromise() is called during
|
|
67
|
+
// render. This creates a timing gap where with very short unusedCacheTime values (e.g., 0),
|
|
68
|
+
// the store could theoretically be disposed before the effect fires. In practice, this is not
|
|
69
|
+
// an issue with the default 60s cache time, but it becomes an issue when `unusedCacheTime` is
|
|
70
|
+
// configured to values less than ~100ms.
|
|
71
|
+
// See https://github.com/livestorejs/livestore/issues/916
|
|
72
|
+
React.useEffect(() => storeRegistry.retain(options), [storeRegistry, options]);
|
|
73
|
+
return withReactApi(store);
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Augments a Store instance with React-specific methods (`useQuery`, `useClientDocument`).
|
|
77
|
+
*
|
|
78
|
+
* This is called automatically by `useStore()`. You typically don't need to call it
|
|
79
|
+
* directly unless you're building custom integrations.
|
|
80
|
+
*
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
5
83
|
export const withReactApi = (store) => {
|
|
6
84
|
// @ts-expect-error TODO properly implement this
|
|
7
|
-
store.useQuery = (
|
|
85
|
+
store.useQuery = (queryable) => useQuery(queryable, { store });
|
|
8
86
|
// @ts-expect-error TODO properly implement this
|
|
9
87
|
store.useClientDocument = (table, idOrOptions, options) => useClientDocument(table, idOrOptions, options, { store });
|
|
88
|
+
// @ts-expect-error TODO properly implement this
|
|
89
|
+
store.useSyncStatus = () => useSyncStatus({ store });
|
|
10
90
|
return store;
|
|
11
91
|
};
|
|
12
|
-
export const useStore = (options) => {
|
|
13
|
-
if (options?.store !== undefined) {
|
|
14
|
-
return { store: withReactApi(options.store) };
|
|
15
|
-
}
|
|
16
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: store is stable
|
|
17
|
-
const storeContext = React.useContext(LiveStoreContext);
|
|
18
|
-
if (storeContext === undefined) {
|
|
19
|
-
throw new Error(`useStore can only be used inside StoreContext.Provider`);
|
|
20
|
-
}
|
|
21
|
-
if (storeContext.stage !== 'running') {
|
|
22
|
-
throw new Error(`useStore can only be used after the store is running`);
|
|
23
|
-
}
|
|
24
|
-
return { store: withReactApi(storeContext.store) };
|
|
25
|
-
};
|
|
26
92
|
//# sourceMappingURL=useStore.js.map
|
package/dist/useStore.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStore.js","sourceRoot":"","sources":["../src/useStore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useStore.js","sourceRoot":"","sources":["../src/useStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA4B,CAAA;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAKtB,OAAoE,EAC/B,EAAE;IACvC,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAA;IAExC,8FAA8F;IAC9F,+FAA+F;IAC/F,8FAA8F;IAC9F,kDAAkD;IAClD,MAAM,cAAc,GAAG,aAAa,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAE9D,MAAM,KAAK,GAAG,cAAc,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,cAAc,CAAA;IAE5F,8FAA8F;IAC9F,6FAA6F;IAC7F,mGAAmG;IACnG,6FAA6F;IAC7F,2FAA2F;IAC3F,8EAA8E;IAC9E,EAAE;IACF,4FAA4F;IAC5F,4FAA4F;IAC5F,8FAA8F;IAC9F,8FAA8F;IAC9F,yCAAyC;IACzC,0DAA0D;IAC1D,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAA;IAE9E,OAAO,YAAY,CAAC,KAAK,CAAC,CAAA;AAC5B,CAAC,CAAA;AAkBD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,KAA+B,EACM,EAAE;IACvC,gDAAgD;IAChD,KAAK,CAAC,QAAQ,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IAE9D,gDAAgD;IAChD,KAAK,CAAC,iBAAiB,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IAEpH,gDAAgD;IAChD,KAAK,CAAC,aAAa,GAAG,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAEpD,OAAO,KAA4C,CAAA;AACrD,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStore.test.d.ts","sourceRoot":"","sources":["../src/useStore.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { makeInMemoryAdapter } from '@livestore/adapter-web';
|
|
3
|
+
import { StoreInternalsSymbol, StoreRegistry, storeOptions, } from '@livestore/livestore';
|
|
4
|
+
import { shouldNeverHappen } from '@livestore/utils';
|
|
5
|
+
import { act, fireEvent, render, renderHook, waitFor } from '@testing-library/react';
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import { schema } from "./__tests__/fixture.js";
|
|
9
|
+
import { StoreRegistryProvider } from "./StoreRegistryContext.js";
|
|
10
|
+
import { useStore } from "./useStore.js";
|
|
11
|
+
describe('experimental useStore', () => {
|
|
12
|
+
it('should return the same promise instance for concurrent getOrLoadStore calls', async () => {
|
|
13
|
+
const storeRegistry = new StoreRegistry();
|
|
14
|
+
const options = testStoreOptions();
|
|
15
|
+
// Make two concurrent calls during loading
|
|
16
|
+
const firstStore = storeRegistry.getOrLoadPromise(options);
|
|
17
|
+
const secondStore = storeRegistry.getOrLoadPromise(options);
|
|
18
|
+
// Both should be promises (store is loading)
|
|
19
|
+
expect(firstStore).toBeInstanceOf(Promise);
|
|
20
|
+
expect(secondStore).toBeInstanceOf(Promise);
|
|
21
|
+
// EXPECTED BEHAVIOR: Same promise instance for React.use() compatibility
|
|
22
|
+
// ACTUAL BEHAVIOR: Different promise instances (Effect.runPromise creates new wrapper)
|
|
23
|
+
expect(firstStore).toBe(secondStore);
|
|
24
|
+
// Cleanup
|
|
25
|
+
await firstStore;
|
|
26
|
+
await cleanupAfterUnmount(() => { });
|
|
27
|
+
});
|
|
28
|
+
it('works with Suspense boundary', async () => {
|
|
29
|
+
const storeRegistry = new StoreRegistry();
|
|
30
|
+
const options = testStoreOptions();
|
|
31
|
+
let view;
|
|
32
|
+
await act(async () => {
|
|
33
|
+
view = render(_jsx(StoreRegistryProvider, { storeRegistry: storeRegistry, children: _jsx(React.Suspense, { fallback: makeSuspenseFallback(), children: _jsx(StoreConsumer, { options: options }) }) }));
|
|
34
|
+
});
|
|
35
|
+
const renderedView = view ?? shouldNeverHappen('render failed');
|
|
36
|
+
// After loading completes, should show the actual content
|
|
37
|
+
await waitForSuspenseResolved(renderedView);
|
|
38
|
+
expect(renderedView.getByTestId('ready')).toBeDefined();
|
|
39
|
+
await cleanupAfterUnmount(() => renderedView.unmount());
|
|
40
|
+
});
|
|
41
|
+
it('does not re-suspend on subsequent renders when store is already loaded', async () => {
|
|
42
|
+
const storeRegistry = new StoreRegistry();
|
|
43
|
+
const options = testStoreOptions();
|
|
44
|
+
const Wrapper = ({ opts }) => (_jsx(StoreRegistryProvider, { storeRegistry: storeRegistry, children: _jsx(React.Suspense, { fallback: makeSuspenseFallback(), children: _jsx(StoreConsumer, { options: opts }) }) }));
|
|
45
|
+
let view;
|
|
46
|
+
await act(async () => {
|
|
47
|
+
view = render(_jsx(Wrapper, { opts: options }));
|
|
48
|
+
});
|
|
49
|
+
const renderedView = view ?? shouldNeverHappen('render failed');
|
|
50
|
+
// Wait for initial load
|
|
51
|
+
await waitForSuspenseResolved(renderedView);
|
|
52
|
+
expect(renderedView.getByTestId('ready')).toBeDefined();
|
|
53
|
+
// Rerender with new options object (but same storeId)
|
|
54
|
+
const rerenderOptions = cloneStoreOptions(options);
|
|
55
|
+
await act(async () => {
|
|
56
|
+
renderedView.rerender(_jsx(Wrapper, { opts: rerenderOptions }));
|
|
57
|
+
});
|
|
58
|
+
// Should not show fallback
|
|
59
|
+
expect(renderedView.queryByTestId('fallback')).toBeNull();
|
|
60
|
+
expect(renderedView.getByTestId('ready')).toBeDefined();
|
|
61
|
+
await cleanupAfterUnmount(() => renderedView.unmount());
|
|
62
|
+
});
|
|
63
|
+
it('throws when store loading fails', async () => {
|
|
64
|
+
const storeRegistry = new StoreRegistry();
|
|
65
|
+
const badOptions = testStoreOptions({
|
|
66
|
+
// @ts-expect-error - intentionally passing invalid adapter to trigger error
|
|
67
|
+
adapter: null,
|
|
68
|
+
});
|
|
69
|
+
// Pre-load the store to cache the error (error happens synchronously)
|
|
70
|
+
expect(() => storeRegistry.getOrLoadPromise(badOptions)).toThrow();
|
|
71
|
+
// Now when useStore tries to get it, it should throw synchronously
|
|
72
|
+
expect(() => renderHook(() => useStore(badOptions), {
|
|
73
|
+
wrapper: makeProvider(storeRegistry),
|
|
74
|
+
})).toThrow();
|
|
75
|
+
});
|
|
76
|
+
it.each([
|
|
77
|
+
{ label: 'non-strict mode', strictMode: false },
|
|
78
|
+
{ label: 'strict mode', strictMode: true },
|
|
79
|
+
])('works in $label', async ({ strictMode }) => {
|
|
80
|
+
const storeRegistry = new StoreRegistry();
|
|
81
|
+
const options = testStoreOptions();
|
|
82
|
+
let hook;
|
|
83
|
+
await act(async () => {
|
|
84
|
+
hook = renderHook(() => useStore(options), {
|
|
85
|
+
wrapper: makeProvider(storeRegistry, { suspense: true }),
|
|
86
|
+
reactStrictMode: strictMode,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
const { result, unmount } = hook ?? shouldNeverHappen('renderHook failed');
|
|
90
|
+
// Wait for store to be ready
|
|
91
|
+
await waitForStoreReady(result);
|
|
92
|
+
expect(result.current[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
93
|
+
await cleanupAfterUnmount(unmount);
|
|
94
|
+
});
|
|
95
|
+
it('handles switching between different storeId values', async () => {
|
|
96
|
+
const storeRegistry = new StoreRegistry();
|
|
97
|
+
const optionsA = testStoreOptions({ storeId: 'store-a' });
|
|
98
|
+
const optionsB = testStoreOptions({ storeId: 'store-b' });
|
|
99
|
+
let hook;
|
|
100
|
+
await act(async () => {
|
|
101
|
+
hook = renderHook((opts) => useStore(opts), {
|
|
102
|
+
initialProps: optionsA,
|
|
103
|
+
wrapper: makeProvider(storeRegistry, { suspense: true }),
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
const { result, rerender, unmount } = hook ?? shouldNeverHappen('renderHook failed');
|
|
107
|
+
// Wait for first store to load
|
|
108
|
+
await waitForStoreReady(result);
|
|
109
|
+
const storeA = result.current;
|
|
110
|
+
expect(storeA[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
111
|
+
// Switch to different storeId
|
|
112
|
+
await act(async () => {
|
|
113
|
+
rerender(optionsB);
|
|
114
|
+
});
|
|
115
|
+
// Wait for second store to load and verify it's different from the first
|
|
116
|
+
await waitFor(() => {
|
|
117
|
+
expect(result.current).not.toBe(storeA);
|
|
118
|
+
expect(result.current?.[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
119
|
+
});
|
|
120
|
+
const storeB = result.current;
|
|
121
|
+
expect(storeB[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
122
|
+
expect(storeB).not.toBe(storeA);
|
|
123
|
+
await cleanupAfterUnmount(unmount);
|
|
124
|
+
});
|
|
125
|
+
it('does not block useActionState transitions from committing', async () => {
|
|
126
|
+
const storeRegistry = new StoreRegistry();
|
|
127
|
+
const options = testStoreOptions();
|
|
128
|
+
const getOrLoadSpy = vi.spyOn(storeRegistry, 'getOrLoadPromise');
|
|
129
|
+
let view;
|
|
130
|
+
await act(async () => {
|
|
131
|
+
view = render(_jsx(StoreRegistryProvider, { storeRegistry: storeRegistry, children: _jsx(React.Suspense, { fallback: makeSuspenseFallback(), children: _jsx(StoreWithActionState, { options: options }) }) }));
|
|
132
|
+
});
|
|
133
|
+
const renderedView = view ?? shouldNeverHappen('render failed');
|
|
134
|
+
// Wait for store to load
|
|
135
|
+
await waitForSuspenseResolved(renderedView);
|
|
136
|
+
expect(renderedView.getByTestId('state').textContent).toBe('none');
|
|
137
|
+
expect(renderedView.getByTestId('pending').textContent).toBe('false');
|
|
138
|
+
// After store is loaded, clear spy to only track calls during the transition render
|
|
139
|
+
getOrLoadSpy.mockClear();
|
|
140
|
+
// Trigger a useActionState transition
|
|
141
|
+
await act(async () => {
|
|
142
|
+
fireEvent.click(renderedView.getByTestId('submit'));
|
|
143
|
+
});
|
|
144
|
+
// getOrLoadPromise must be called on each render (not cached via useMemo).
|
|
145
|
+
// When the initial Promise is cached, React.use() is called with a resolved Promise
|
|
146
|
+
// on every subsequent render, which blocks React transitions (e.g. useActionState)
|
|
147
|
+
// from ever committing in browser environments.
|
|
148
|
+
expect(getOrLoadSpy).toHaveBeenCalled();
|
|
149
|
+
// The transition should commit: state updates and isPending returns to false
|
|
150
|
+
await waitFor(() => {
|
|
151
|
+
expect(renderedView.getByTestId('state').textContent).toBe('updated');
|
|
152
|
+
expect(renderedView.getByTestId('pending').textContent).toBe('false');
|
|
153
|
+
});
|
|
154
|
+
getOrLoadSpy.mockRestore();
|
|
155
|
+
await cleanupAfterUnmount(() => renderedView.unmount());
|
|
156
|
+
});
|
|
157
|
+
// useStore doesn't handle unusedCacheTime=0 correctly because retain is called in useEffect (after render)
|
|
158
|
+
// See https://github.com/livestorejs/livestore/issues/916
|
|
159
|
+
it.skip('should load store with unusedCacheTime set to 0', async () => {
|
|
160
|
+
const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime: 0 } });
|
|
161
|
+
const options = testStoreOptions({ unusedCacheTime: 0 });
|
|
162
|
+
const StoreConsumerWithVerification = ({ opts }) => {
|
|
163
|
+
const store = useStore(opts);
|
|
164
|
+
// Verify store is usable - access internals to confirm it's not disposed
|
|
165
|
+
const clientSession = store[StoreInternalsSymbol].clientSession;
|
|
166
|
+
return _jsx("div", { "data-testid": "ready", "data-has-session": String(clientSession !== undefined) });
|
|
167
|
+
};
|
|
168
|
+
let view;
|
|
169
|
+
await act(async () => {
|
|
170
|
+
view = render(_jsx(StoreRegistryProvider, { storeRegistry: storeRegistry, children: _jsx(React.Suspense, { fallback: makeSuspenseFallback(), children: _jsx(StoreConsumerWithVerification, { opts: options }) }) }));
|
|
171
|
+
});
|
|
172
|
+
const renderedView = view ?? shouldNeverHappen('render failed');
|
|
173
|
+
await waitForSuspenseResolved(renderedView);
|
|
174
|
+
// Store should be usable while component is mounted
|
|
175
|
+
const readyElement = renderedView.getByTestId('ready');
|
|
176
|
+
expect(readyElement.getAttribute('data-has-session')).toBe('true');
|
|
177
|
+
// Allow some time to pass to ensure store isn't prematurely disposed
|
|
178
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
179
|
+
// Store should still be usable after waiting
|
|
180
|
+
expect(readyElement.getAttribute('data-has-session')).toBe('true');
|
|
181
|
+
await cleanupAfterUnmount(() => renderedView.unmount());
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
const StoreConsumer = ({ options }) => {
|
|
185
|
+
useStore(options);
|
|
186
|
+
return _jsx("div", { "data-testid": "ready" });
|
|
187
|
+
};
|
|
188
|
+
/** Component that combines useStore with useActionState to test transition compatibility. */
|
|
189
|
+
const StoreWithActionState = ({ options }) => {
|
|
190
|
+
useStore(options);
|
|
191
|
+
const [state, dispatch, isPending] = React.useActionState((_prev, value) => value, undefined);
|
|
192
|
+
return (_jsxs("div", { children: [_jsx("button", { "data-testid": "submit",
|
|
193
|
+
// eslint-disable-next-line react-perf/jsx-no-new-function-as-prop -- test component
|
|
194
|
+
onClick: () => React.startTransition(() => dispatch('updated')), children: "Submit" }), _jsx("div", { "data-testid": "state", children: state ?? 'none' }), _jsx("div", { "data-testid": "pending", children: String(isPending) })] }));
|
|
195
|
+
};
|
|
196
|
+
const makeSuspenseFallback = () => React.createElement('div', { 'data-testid': 'fallback' });
|
|
197
|
+
const cloneStoreOptions = (options) => {
|
|
198
|
+
return { ...options };
|
|
199
|
+
};
|
|
200
|
+
const makeProvider = (storeRegistry, { suspense = false } = {}) => ({ children }) => {
|
|
201
|
+
let content = _jsx(StoreRegistryProvider, { storeRegistry: storeRegistry, children: children });
|
|
202
|
+
if (suspense !== undefined) {
|
|
203
|
+
content = _jsx(React.Suspense, { fallback: null, children: content });
|
|
204
|
+
}
|
|
205
|
+
return content;
|
|
206
|
+
};
|
|
207
|
+
let testStoreCounter = 0;
|
|
208
|
+
const testStoreOptions = (overrides = {}) => storeOptions({
|
|
209
|
+
storeId: overrides.storeId ?? `test-store-${testStoreCounter++}`,
|
|
210
|
+
schema,
|
|
211
|
+
adapter: makeInMemoryAdapter(),
|
|
212
|
+
...overrides,
|
|
213
|
+
});
|
|
214
|
+
/**
|
|
215
|
+
* Cleans up after component unmount and waits for pending operations to settle.
|
|
216
|
+
*
|
|
217
|
+
* When components using stores unmount, the StoreRegistry schedules garbage collection
|
|
218
|
+
* timers for inactive stores. This helper waits for those timers to complete naturally.
|
|
219
|
+
*/
|
|
220
|
+
const cleanupAfterUnmount = async (cleanup) => {
|
|
221
|
+
cleanup();
|
|
222
|
+
// Allow any pending microtasks/timers to settle
|
|
223
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Waits for React Suspense fallback to resolve and the actual content to render.
|
|
227
|
+
*/
|
|
228
|
+
const waitForSuspenseResolved = async (view) => {
|
|
229
|
+
await waitFor(() => expect(view.queryByTestId('fallback')).toBeNull());
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Waits for a store to be fully loaded and ready to use.
|
|
233
|
+
* The store is considered ready when it has a defined clientSession.
|
|
234
|
+
*/
|
|
235
|
+
const waitForStoreReady = async (result) => {
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(result.current).not.toBeNull();
|
|
238
|
+
expect(result.current[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
//# sourceMappingURL=useStore.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStore.test.js","sourceRoot":"","sources":["../src/useStore.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Store, SyncStatus } from '@livestore/livestore';
|
|
2
|
+
/**
|
|
3
|
+
* React hook that subscribes to sync status changes.
|
|
4
|
+
*
|
|
5
|
+
* Returns the current synchronization status between the client session and
|
|
6
|
+
* the leader thread. The component re-renders whenever the sync status changes.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* function SyncIndicator() {
|
|
11
|
+
* const status = store.useSyncStatus()
|
|
12
|
+
* return <span>{status.isSynced ? '✓ Synced' : `Syncing (${status.pendingCount} pending)...`}</span>
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @param options - Options containing the store instance
|
|
17
|
+
* @returns The current sync status
|
|
18
|
+
*/
|
|
19
|
+
export declare const useSyncStatus: (options: {
|
|
20
|
+
store: Store<any>;
|
|
21
|
+
}) => SyncStatus;
|
|
22
|
+
//# sourceMappingURL=useSyncStatus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSyncStatus.d.ts","sourceRoot":"","sources":["../src/useSyncStatus.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAE7D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS;IAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;CAAE,KAAG,UAY9D,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* React hook that subscribes to sync status changes.
|
|
4
|
+
*
|
|
5
|
+
* Returns the current synchronization status between the client session and
|
|
6
|
+
* the leader thread. The component re-renders whenever the sync status changes.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* function SyncIndicator() {
|
|
11
|
+
* const status = store.useSyncStatus()
|
|
12
|
+
* return <span>{status.isSynced ? '✓ Synced' : `Syncing (${status.pendingCount} pending)...`}</span>
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @param options - Options containing the store instance
|
|
17
|
+
* @returns The current sync status
|
|
18
|
+
*/
|
|
19
|
+
export const useSyncStatus = (options) => {
|
|
20
|
+
const { store } = options;
|
|
21
|
+
const [status, setStatus] = React.useState(() => store.syncStatus());
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
return store.subscribeSyncStatus(setStatus);
|
|
24
|
+
}, [store]);
|
|
25
|
+
React.useDebugValue(`LiveStore:useSyncStatus:${status.isSynced === true ? 'synced' : 'pending'}`);
|
|
26
|
+
return status;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=useSyncStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSyncStatus.js","sourceRoot":"","sources":["../src/useSyncStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAA8B,EAAc,EAAE;IAC1E,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAA;IAEzB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAa,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAA;IAEhF,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,OAAO,KAAK,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAA;IAC7C,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEX,KAAK,CAAC,aAAa,CAAC,2BAA2B,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAA;IAEjG,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
|