@livestore/react 0.4.0-dev.20 → 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.
- 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.map +1 -1
- package/dist/__tests__/fixture.js +1 -6
- 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 +6 -5
- package/dist/experimental/components/LiveList.js.map +1 -1
- package/dist/experimental/mod.d.ts +0 -1
- package/dist/experimental/mod.d.ts.map +1 -1
- package/dist/experimental/mod.js +0 -1
- package/dist/experimental/mod.js.map +1 -1
- package/dist/mod.d.ts +4 -3
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +3 -2
- package/dist/mod.js.map +1 -1
- package/dist/useClientDocument.d.ts +33 -0
- package/dist/useClientDocument.d.ts.map +1 -1
- package/dist/useClientDocument.js +1 -4
- package/dist/useClientDocument.js.map +1 -1
- package/dist/useQuery.d.ts +1 -1
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +2 -5
- package/dist/useQuery.js.map +1 -1
- package/dist/useStore.d.ts +62 -7
- package/dist/useStore.d.ts.map +1 -1
- package/dist/useStore.js +73 -15
- package/dist/useStore.js.map +1 -1
- package/dist/useStore.test.d.ts.map +1 -0
- package/dist/useStore.test.js +196 -0
- package/dist/useStore.test.js.map +1 -0
- package/package.json +7 -7
- package/src/StoreRegistryContext.tsx +69 -0
- package/src/__tests__/fixture.tsx +1 -13
- package/src/experimental/components/LiveList.tsx +13 -4
- package/src/experimental/mod.ts +0 -1
- package/src/mod.ts +4 -3
- package/src/useClientDocument.ts +36 -5
- package/src/useQuery.ts +2 -6
- package/src/useStore.test.tsx +271 -0
- package/src/useStore.ts +102 -23
- 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 -66
- package/dist/LiveStoreProvider.d.ts.map +0 -1
- package/dist/LiveStoreProvider.js +0 -232
- 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/experimental/multi-store/StoreRegistry.d.ts +0 -61
- package/dist/experimental/multi-store/StoreRegistry.d.ts.map +0 -1
- package/dist/experimental/multi-store/StoreRegistry.js +0 -275
- package/dist/experimental/multi-store/StoreRegistry.js.map +0 -1
- package/dist/experimental/multi-store/StoreRegistry.test.d.ts +0 -2
- package/dist/experimental/multi-store/StoreRegistry.test.d.ts.map +0 -1
- package/dist/experimental/multi-store/StoreRegistry.test.js +0 -464
- package/dist/experimental/multi-store/StoreRegistry.test.js.map +0 -1
- package/dist/experimental/multi-store/StoreRegistryContext.d.ts +0 -10
- package/dist/experimental/multi-store/StoreRegistryContext.d.ts.map +0 -1
- package/dist/experimental/multi-store/StoreRegistryContext.js +0 -15
- package/dist/experimental/multi-store/StoreRegistryContext.js.map +0 -1
- package/dist/experimental/multi-store/mod.d.ts +0 -6
- package/dist/experimental/multi-store/mod.d.ts.map +0 -1
- package/dist/experimental/multi-store/mod.js +0 -6
- package/dist/experimental/multi-store/mod.js.map +0 -1
- package/dist/experimental/multi-store/storeOptions.d.ts +0 -4
- package/dist/experimental/multi-store/storeOptions.d.ts.map +0 -1
- package/dist/experimental/multi-store/storeOptions.js +0 -4
- package/dist/experimental/multi-store/storeOptions.js.map +0 -1
- package/dist/experimental/multi-store/types.d.ts +0 -44
- package/dist/experimental/multi-store/types.d.ts.map +0 -1
- package/dist/experimental/multi-store/types.js +0 -2
- package/dist/experimental/multi-store/types.js.map +0 -1
- package/dist/experimental/multi-store/useStore.d.ts +0 -11
- package/dist/experimental/multi-store/useStore.d.ts.map +0 -1
- package/dist/experimental/multi-store/useStore.js +0 -21
- package/dist/experimental/multi-store/useStore.js.map +0 -1
- package/dist/experimental/multi-store/useStore.test.d.ts.map +0 -1
- package/dist/experimental/multi-store/useStore.test.js +0 -144
- package/dist/experimental/multi-store/useStore.test.js.map +0 -1
- package/src/LiveStoreContext.ts +0 -14
- package/src/LiveStoreProvider.test.tsx +0 -248
- package/src/LiveStoreProvider.tsx +0 -421
- package/src/experimental/multi-store/StoreRegistry.test.ts +0 -631
- package/src/experimental/multi-store/StoreRegistry.ts +0 -347
- package/src/experimental/multi-store/StoreRegistryContext.tsx +0 -23
- package/src/experimental/multi-store/mod.ts +0 -5
- package/src/experimental/multi-store/storeOptions.ts +0 -8
- package/src/experimental/multi-store/types.ts +0 -55
- package/src/experimental/multi-store/useStore.test.tsx +0 -197
- package/src/experimental/multi-store/useStore.ts +0 -34
- /package/dist/{experimental/multi-store/useStore.test.d.ts → useStore.test.d.ts} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useStore.d.ts","sourceRoot":"","sources":["../../../src/experimental/multi-store/useStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAGzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,SAAS,eAAe,EACtD,SAAS,kBAAkB,CAAC,OAAO,CAAC,KACnC,KAAK,CAAC,OAAO,CAAC,GAAG,QAkBnB,CAAA"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { withReactApi } from "../../useStore.js";
|
|
3
|
-
import { useStoreRegistry } from "./StoreRegistryContext.js";
|
|
4
|
-
/**
|
|
5
|
-
* Suspense + Error Boundary friendly hook.
|
|
6
|
-
* - Returns data or throws (Promise|Error).
|
|
7
|
-
* - No loading or error states are returned.
|
|
8
|
-
*/
|
|
9
|
-
export const useStore = (options) => {
|
|
10
|
-
const storeRegistry = useStoreRegistry();
|
|
11
|
-
const subscribe = React.useCallback((onChange) => storeRegistry.subscribe(options.storeId, onChange), [storeRegistry, options.storeId]);
|
|
12
|
-
const getSnapshot = React.useCallback(() => {
|
|
13
|
-
const storeOrPromise = storeRegistry.getOrLoad(options);
|
|
14
|
-
if (storeOrPromise instanceof Promise)
|
|
15
|
-
throw storeOrPromise;
|
|
16
|
-
return storeOrPromise;
|
|
17
|
-
}, [storeRegistry, options]);
|
|
18
|
-
const loadedStore = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
19
|
-
return withReactApi(loadedStore);
|
|
20
|
-
};
|
|
21
|
-
//# sourceMappingURL=useStore.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useStore.js","sourceRoot":"","sources":["../../../src/experimental/multi-store/useStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA4B,CAAA;AAG7D;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,OAAoC,EACT,EAAE;IAC7B,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAA;IAExC,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CACjC,CAAC,QAAoB,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,EAC5E,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,CACjC,CAAA;IACD,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACzC,MAAM,cAAc,GAAG,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;QAEvD,IAAI,cAAc,YAAY,OAAO;YAAE,MAAM,cAAc,CAAA;QAE3D,OAAO,cAAc,CAAA;IACvB,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAA;IAE5B,MAAM,WAAW,GAAG,KAAK,CAAC,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,WAAW,CAAC,CAAA;IAEnF,OAAO,YAAY,CAAC,WAAW,CAAC,CAAA;AAClC,CAAC,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useStore.test.d.ts","sourceRoot":"","sources":["../../../src/experimental/multi-store/useStore.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { makeInMemoryAdapter } from '@livestore/adapter-web';
|
|
3
|
-
import { StoreInternalsSymbol } from '@livestore/livestore';
|
|
4
|
-
import { render, renderHook, waitFor } from '@testing-library/react';
|
|
5
|
-
import * as React from 'react';
|
|
6
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
-
import { schema } from "../../__tests__/fixture.js";
|
|
8
|
-
import { StoreRegistry } from "./StoreRegistry.js";
|
|
9
|
-
import { StoreRegistryProvider } from "./StoreRegistryContext.js";
|
|
10
|
-
import { storeOptions } from "./storeOptions.js";
|
|
11
|
-
import { useStore } from "./useStore.js";
|
|
12
|
-
describe('experimental useStore', () => {
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
vi.clearAllTimers();
|
|
15
|
-
vi.useRealTimers();
|
|
16
|
-
});
|
|
17
|
-
it('suspends when the store is loading', async () => {
|
|
18
|
-
const registry = new StoreRegistry();
|
|
19
|
-
const options = testStoreOptions();
|
|
20
|
-
const view = render(_jsx(StoreRegistryProvider, { storeRegistry: registry, children: _jsx(React.Suspense, { fallback: _jsx("div", { "data-testid": "fallback" }), children: _jsx(StoreConsumer, { options: options }) }) }));
|
|
21
|
-
// Should show fallback while loading
|
|
22
|
-
expect(view.getByTestId('fallback')).toBeDefined();
|
|
23
|
-
// Wait for store to load and component to render
|
|
24
|
-
await waitForSuspenseResolved(view);
|
|
25
|
-
expect(view.getByTestId('ready')).toBeDefined();
|
|
26
|
-
cleanupWithPendingTimers(() => view.unmount());
|
|
27
|
-
});
|
|
28
|
-
it('does not re-suspend on subsequent renders when store is already loaded', async () => {
|
|
29
|
-
const registry = new StoreRegistry();
|
|
30
|
-
const options = testStoreOptions();
|
|
31
|
-
const Wrapper = ({ opts }) => (_jsx(StoreRegistryProvider, { storeRegistry: registry, children: _jsx(React.Suspense, { fallback: _jsx("div", { "data-testid": "fallback" }), children: _jsx(StoreConsumer, { options: opts }) }) }));
|
|
32
|
-
const view = render(_jsx(Wrapper, { opts: options }));
|
|
33
|
-
// Wait for initial load
|
|
34
|
-
await waitForSuspenseResolved(view);
|
|
35
|
-
expect(view.getByTestId('ready')).toBeDefined();
|
|
36
|
-
// Rerender with new options object (but same storeId)
|
|
37
|
-
view.rerender(_jsx(Wrapper, { opts: { ...options } }));
|
|
38
|
-
// Should not show fallback
|
|
39
|
-
expect(view.queryByTestId('fallback')).toBeNull();
|
|
40
|
-
expect(view.getByTestId('ready')).toBeDefined();
|
|
41
|
-
cleanupWithPendingTimers(() => view.unmount());
|
|
42
|
-
});
|
|
43
|
-
it('throws when store loading fails', async () => {
|
|
44
|
-
const registry = new StoreRegistry();
|
|
45
|
-
const badOptions = testStoreOptions({
|
|
46
|
-
// @ts-expect-error - intentionally passing invalid adapter to trigger error
|
|
47
|
-
adapter: null,
|
|
48
|
-
});
|
|
49
|
-
// Pre-load the store to cache the error
|
|
50
|
-
await expect(registry.getOrLoad(badOptions)).rejects.toThrow();
|
|
51
|
-
// Now when useStore tries to get it, it should throw synchronously
|
|
52
|
-
expect(() => renderHook(() => useStore(badOptions), {
|
|
53
|
-
wrapper: makeProvider(registry),
|
|
54
|
-
})).toThrow();
|
|
55
|
-
});
|
|
56
|
-
it.each([
|
|
57
|
-
{ label: 'non-strict mode', strictMode: false },
|
|
58
|
-
{ label: 'strict mode', strictMode: true },
|
|
59
|
-
])('works in $label', async ({ strictMode }) => {
|
|
60
|
-
const registry = new StoreRegistry();
|
|
61
|
-
const options = testStoreOptions();
|
|
62
|
-
const { result, unmount } = renderHook(() => useStore(options), {
|
|
63
|
-
wrapper: makeProvider(registry, { suspense: true }),
|
|
64
|
-
reactStrictMode: strictMode,
|
|
65
|
-
});
|
|
66
|
-
// Wait for store to be ready
|
|
67
|
-
await waitForStoreReady(result);
|
|
68
|
-
expect(result.current[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
69
|
-
cleanupWithPendingTimers(unmount);
|
|
70
|
-
});
|
|
71
|
-
it('handles switching between different storeId values', async () => {
|
|
72
|
-
const registry = new StoreRegistry();
|
|
73
|
-
const optionsA = testStoreOptions({ storeId: 'store-a' });
|
|
74
|
-
const optionsB = testStoreOptions({ storeId: 'store-b' });
|
|
75
|
-
const { result, rerender, unmount } = renderHook((opts) => useStore(opts), {
|
|
76
|
-
initialProps: optionsA,
|
|
77
|
-
wrapper: makeProvider(registry, { suspense: true }),
|
|
78
|
-
});
|
|
79
|
-
// Wait for first store to load
|
|
80
|
-
await waitForStoreReady(result);
|
|
81
|
-
const storeA = result.current;
|
|
82
|
-
expect(storeA[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
83
|
-
// Switch to different storeId
|
|
84
|
-
rerender(optionsB);
|
|
85
|
-
// Wait for second store to load and verify it's different from the first
|
|
86
|
-
await waitFor(() => {
|
|
87
|
-
expect(result.current).not.toBe(storeA);
|
|
88
|
-
expect(result.current?.[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
89
|
-
});
|
|
90
|
-
const storeB = result.current;
|
|
91
|
-
expect(storeB[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
92
|
-
expect(storeB).not.toBe(storeA);
|
|
93
|
-
cleanupWithPendingTimers(unmount);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
const StoreConsumer = ({ options }) => {
|
|
97
|
-
useStore(options);
|
|
98
|
-
return _jsx("div", { "data-testid": "ready" });
|
|
99
|
-
};
|
|
100
|
-
const makeProvider = (registry, { suspense = false } = {}) => ({ children }) => {
|
|
101
|
-
let content = _jsx(StoreRegistryProvider, { storeRegistry: registry, children: children });
|
|
102
|
-
if (suspense) {
|
|
103
|
-
content = _jsx(React.Suspense, { fallback: null, children: content });
|
|
104
|
-
}
|
|
105
|
-
return content;
|
|
106
|
-
};
|
|
107
|
-
const testStoreOptions = (overrides = {}) => storeOptions({
|
|
108
|
-
storeId: 'test-store',
|
|
109
|
-
schema,
|
|
110
|
-
adapter: makeInMemoryAdapter(),
|
|
111
|
-
...overrides,
|
|
112
|
-
});
|
|
113
|
-
/**
|
|
114
|
-
* Cleans up after component unmount by synchronously executing any pending GC timers.
|
|
115
|
-
*
|
|
116
|
-
* When components using stores unmount, the StoreRegistry schedules garbage collection
|
|
117
|
-
* timers for inactive stores. Without this cleanup, those timers may fire during
|
|
118
|
-
* subsequent tests, causing cross-test pollution and flaky failures.
|
|
119
|
-
*
|
|
120
|
-
* This helper switches to fake timers, executes only the already-pending timers
|
|
121
|
-
* (allowing stores to shut down cleanly), then restores real timers for the next test.
|
|
122
|
-
*/
|
|
123
|
-
const cleanupWithPendingTimers = (cleanup) => {
|
|
124
|
-
vi.useFakeTimers();
|
|
125
|
-
cleanup();
|
|
126
|
-
vi.runOnlyPendingTimers();
|
|
127
|
-
};
|
|
128
|
-
/**
|
|
129
|
-
* Waits for React Suspense fallback to resolve and the actual content to render.
|
|
130
|
-
*/
|
|
131
|
-
const waitForSuspenseResolved = async (view) => {
|
|
132
|
-
await waitFor(() => expect(view.queryByTestId('fallback')).toBeNull());
|
|
133
|
-
};
|
|
134
|
-
/**
|
|
135
|
-
* Waits for a store to be fully loaded and ready to use.
|
|
136
|
-
* The store is considered ready when it has a defined clientSession.
|
|
137
|
-
*/
|
|
138
|
-
const waitForStoreReady = async (result) => {
|
|
139
|
-
await waitFor(() => {
|
|
140
|
-
expect(result.current).not.toBeNull();
|
|
141
|
-
expect(result.current[StoreInternalsSymbol].clientSession).toBeDefined();
|
|
142
|
-
});
|
|
143
|
-
};
|
|
144
|
-
//# sourceMappingURL=useStore.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useStore.test.js","sourceRoot":"","sources":["../../../src/experimental/multi-store/useStore.test.tsx"],"names":[],"mappings":"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"}
|
package/src/LiveStoreContext.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { LiveStoreContextRunning } from '@livestore/livestore'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
|
|
4
|
-
import type { useClientDocument } from './useClientDocument.ts'
|
|
5
|
-
import type { useQuery } from './useQuery.ts'
|
|
6
|
-
|
|
7
|
-
export type ReactApi = {
|
|
8
|
-
useQuery: typeof useQuery
|
|
9
|
-
useClientDocument: typeof useClientDocument
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const LiveStoreContext = React.createContext<
|
|
13
|
-
{ stage: 'running'; store: LiveStoreContextRunning['store'] & ReactApi } | undefined
|
|
14
|
-
>(undefined)
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/** biome-ignore-all lint/a11y: test files need a11y disabled */
|
|
2
|
-
import { makeInMemoryAdapter } from '@livestore/adapter-web'
|
|
3
|
-
import { queryDb, type Store, StoreInternalsSymbol } from '@livestore/livestore'
|
|
4
|
-
import { Schema } from '@livestore/utils/effect'
|
|
5
|
-
import * as ReactTesting from '@testing-library/react'
|
|
6
|
-
import React from 'react'
|
|
7
|
-
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
|
|
8
|
-
import { describe, expect, it } from 'vitest'
|
|
9
|
-
|
|
10
|
-
import { events, schema, tables } from './__tests__/fixture.tsx'
|
|
11
|
-
import { LiveStoreProvider } from './LiveStoreProvider.tsx'
|
|
12
|
-
import * as LiveStoreReact from './mod.ts'
|
|
13
|
-
|
|
14
|
-
describe.each([true, false])('LiveStoreProvider (strictMode: %s)', (strictMode) => {
|
|
15
|
-
const WithStrictMode = strictMode ? React.StrictMode : React.Fragment
|
|
16
|
-
|
|
17
|
-
it('simple', async () => {
|
|
18
|
-
let appRenderCount = 0
|
|
19
|
-
|
|
20
|
-
const allTodos$ = queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.rowSchema) })
|
|
21
|
-
|
|
22
|
-
const App = () => {
|
|
23
|
-
appRenderCount++
|
|
24
|
-
const { store } = LiveStoreReact.useStore()
|
|
25
|
-
|
|
26
|
-
const todos = store.useQuery(allTodos$)
|
|
27
|
-
|
|
28
|
-
return <div>{JSON.stringify(todos)}</div>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const abortController = new AbortController()
|
|
32
|
-
|
|
33
|
-
const Root = ({ forceUpdate }: { forceUpdate: number }) => {
|
|
34
|
-
const bootCb = React.useCallback(
|
|
35
|
-
(store: Store) => store.commit(events.todoCreated({ id: 't1', text: 'buy milk', completed: false })),
|
|
36
|
-
[],
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: forceUpdate is used to force a re-render
|
|
40
|
-
const adapterMemo = React.useMemo(() => makeInMemoryAdapter(), [forceUpdate])
|
|
41
|
-
return (
|
|
42
|
-
<WithStrictMode>
|
|
43
|
-
<LiveStoreProvider
|
|
44
|
-
schema={schema}
|
|
45
|
-
renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
|
|
46
|
-
adapter={adapterMemo}
|
|
47
|
-
boot={bootCb}
|
|
48
|
-
signal={abortController.signal}
|
|
49
|
-
batchUpdates={batchUpdates}
|
|
50
|
-
>
|
|
51
|
-
<App />
|
|
52
|
-
</LiveStoreProvider>
|
|
53
|
-
</WithStrictMode>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const { rerender } = ReactTesting.render(<Root forceUpdate={1} />)
|
|
58
|
-
|
|
59
|
-
expect(appRenderCount).toBe(0)
|
|
60
|
-
|
|
61
|
-
await ReactTesting.waitForElementToBeRemoved(() =>
|
|
62
|
-
ReactTesting.screen.getByText((_) => _.startsWith('Loading LiveStore')),
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
expect(appRenderCount).toBe(strictMode ? 2 : 1)
|
|
66
|
-
|
|
67
|
-
rerender(<Root forceUpdate={2} />)
|
|
68
|
-
|
|
69
|
-
await ReactTesting.waitFor(() => ReactTesting.screen.getByText('Loading LiveStore: loading'))
|
|
70
|
-
await ReactTesting.waitFor(() => ReactTesting.screen.getByText((_) => _.includes('buy milk')))
|
|
71
|
-
|
|
72
|
-
expect(appRenderCount).toBe(strictMode ? 4 : 2)
|
|
73
|
-
|
|
74
|
-
abortController.abort()
|
|
75
|
-
|
|
76
|
-
await ReactTesting.waitFor(() =>
|
|
77
|
-
ReactTesting.screen.getByText('LiveStore Shutdown due to interrupted', { exact: false }),
|
|
78
|
-
)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
// TODO test aborting during boot
|
|
82
|
-
|
|
83
|
-
it('error during boot', async () => {
|
|
84
|
-
let appRenderCount = 0
|
|
85
|
-
|
|
86
|
-
const App = () => {
|
|
87
|
-
appRenderCount++
|
|
88
|
-
|
|
89
|
-
return <div>hello world</div>
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const Root = ({ forceUpdate }: { forceUpdate: number }) => {
|
|
93
|
-
const bootCb = React.useCallback((_store: Store) => {
|
|
94
|
-
// This should trigger an error because we're trying to insert invalid data
|
|
95
|
-
throw new Error('Simulated boot error')
|
|
96
|
-
}, [])
|
|
97
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: forceUpdate is used to force a re-render
|
|
98
|
-
const adapterMemo = React.useMemo(() => makeInMemoryAdapter(), [forceUpdate])
|
|
99
|
-
return (
|
|
100
|
-
<WithStrictMode>
|
|
101
|
-
<LiveStoreProvider
|
|
102
|
-
schema={schema}
|
|
103
|
-
renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
|
|
104
|
-
adapter={adapterMemo}
|
|
105
|
-
boot={bootCb}
|
|
106
|
-
batchUpdates={batchUpdates}
|
|
107
|
-
>
|
|
108
|
-
<App />
|
|
109
|
-
</LiveStoreProvider>
|
|
110
|
-
</WithStrictMode>
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
ReactTesting.render(<Root forceUpdate={1} />)
|
|
115
|
-
|
|
116
|
-
expect(appRenderCount).toBe(0)
|
|
117
|
-
|
|
118
|
-
await ReactTesting.waitFor(() => ReactTesting.screen.getByText((_) => _.startsWith('LiveStore.UnknownError')))
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('unmounts when store is shutdown', async () => {
|
|
122
|
-
let appRenderCount = 0
|
|
123
|
-
|
|
124
|
-
const allTodos$ = queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.rowSchema) })
|
|
125
|
-
|
|
126
|
-
const shutdownDeferred = Promise.withResolvers<void>()
|
|
127
|
-
|
|
128
|
-
const App = () => {
|
|
129
|
-
appRenderCount++
|
|
130
|
-
const { store } = LiveStoreReact.useStore()
|
|
131
|
-
|
|
132
|
-
React.useEffect(() => {
|
|
133
|
-
shutdownDeferred.promise.then(() => {
|
|
134
|
-
console.log('shutdown')
|
|
135
|
-
return store.shutdown()
|
|
136
|
-
})
|
|
137
|
-
}, [store])
|
|
138
|
-
|
|
139
|
-
const todos = store.useQuery(allTodos$)
|
|
140
|
-
|
|
141
|
-
return <div>{JSON.stringify(todos)}</div>
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const adapter = makeInMemoryAdapter()
|
|
145
|
-
|
|
146
|
-
const Root = () => {
|
|
147
|
-
return (
|
|
148
|
-
<WithStrictMode>
|
|
149
|
-
<LiveStoreProvider
|
|
150
|
-
schema={schema}
|
|
151
|
-
renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
|
|
152
|
-
adapter={adapter}
|
|
153
|
-
batchUpdates={batchUpdates}
|
|
154
|
-
>
|
|
155
|
-
<App />
|
|
156
|
-
</LiveStoreProvider>
|
|
157
|
-
</WithStrictMode>
|
|
158
|
-
)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
ReactTesting.render(<Root />)
|
|
162
|
-
|
|
163
|
-
expect(appRenderCount).toBe(0)
|
|
164
|
-
|
|
165
|
-
await ReactTesting.waitFor(() => ReactTesting.screen.getByText('[]'))
|
|
166
|
-
|
|
167
|
-
React.act(() => shutdownDeferred.resolve())
|
|
168
|
-
|
|
169
|
-
expect(appRenderCount).toBe(strictMode ? 2 : 1)
|
|
170
|
-
|
|
171
|
-
await ReactTesting.waitFor(() =>
|
|
172
|
-
ReactTesting.screen.getByText('LiveStore Shutdown due to manual shutdown', { exact: false }),
|
|
173
|
-
)
|
|
174
|
-
})
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it('should work two stores with the same storeId', async () => {
|
|
178
|
-
const allTodos$ = queryDb({ query: `select * from todos`, schema: Schema.Array(tables.todos.rowSchema) })
|
|
179
|
-
|
|
180
|
-
const appRenderCount = {
|
|
181
|
-
store1: 0,
|
|
182
|
-
store2: 0,
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const App = () => {
|
|
186
|
-
const { store } = LiveStoreReact.useStore()
|
|
187
|
-
const instanceId = store[StoreInternalsSymbol].clientSession.debugInstanceId as 'store1' | 'store2'
|
|
188
|
-
appRenderCount[instanceId]!++
|
|
189
|
-
|
|
190
|
-
const todos = store.useQuery(allTodos$)
|
|
191
|
-
|
|
192
|
-
return (
|
|
193
|
-
<div id={instanceId}>
|
|
194
|
-
<div role="heading">{instanceId}</div>
|
|
195
|
-
<div role="content">{JSON.stringify(todos)}</div>
|
|
196
|
-
<button onClick={() => store.commit(events.todoCreated({ id: 't1', text: 'buy milk', completed: false }))}>
|
|
197
|
-
create todo {instanceId}
|
|
198
|
-
</button>
|
|
199
|
-
</div>
|
|
200
|
-
)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const Root = () => {
|
|
204
|
-
const storeId = 'fixed-store-id'
|
|
205
|
-
return (
|
|
206
|
-
<div>
|
|
207
|
-
<LiveStoreProvider
|
|
208
|
-
storeId={storeId}
|
|
209
|
-
debug={{ instanceId: 'store1' }}
|
|
210
|
-
schema={schema}
|
|
211
|
-
adapter={makeInMemoryAdapter()}
|
|
212
|
-
batchUpdates={batchUpdates}
|
|
213
|
-
>
|
|
214
|
-
<App />
|
|
215
|
-
</LiveStoreProvider>
|
|
216
|
-
<LiveStoreProvider
|
|
217
|
-
storeId={storeId}
|
|
218
|
-
debug={{ instanceId: 'store2' }}
|
|
219
|
-
schema={schema}
|
|
220
|
-
adapter={makeInMemoryAdapter()}
|
|
221
|
-
batchUpdates={batchUpdates}
|
|
222
|
-
>
|
|
223
|
-
<App />
|
|
224
|
-
</LiveStoreProvider>
|
|
225
|
-
</div>
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const { container } = ReactTesting.render(<Root />)
|
|
230
|
-
|
|
231
|
-
await ReactTesting.waitFor(() => ReactTesting.screen.getByRole('heading', { name: 'store1' }))
|
|
232
|
-
await ReactTesting.waitFor(() => ReactTesting.screen.getByRole('heading', { name: 'store2' }))
|
|
233
|
-
|
|
234
|
-
expect(appRenderCount.store1).toBe(1)
|
|
235
|
-
expect(appRenderCount.store2).toBe(1)
|
|
236
|
-
|
|
237
|
-
ReactTesting.fireEvent.click(ReactTesting.screen.getByText('create todo store1'))
|
|
238
|
-
|
|
239
|
-
expect(appRenderCount.store1).toBe(2)
|
|
240
|
-
|
|
241
|
-
expect(container.querySelector('#store1 > div[role="content"]')?.textContent).toBe(
|
|
242
|
-
'[{"id":"t1","text":"buy milk","completed":false}]',
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
expect(container.querySelector('#store2 > div[role="content"]')?.textContent).toBe('[]')
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// TODO test that checks that there are no two exact same instances (i.e. same storeId, clientId, sessionId)
|