@livestore/react 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.
- 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.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 +50 -46
- package/dist/useStore.d.ts.map +1 -1
- package/dist/useStore.js +66 -59
- package/dist/useStore.js.map +1 -1
- package/dist/useStore.test.d.ts.map +1 -0
- package/dist/{experimental/multi-store/useStore.test.js → useStore.test.js} +20 -22
- 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 +1 -5
- package/src/useQuery.ts +2 -6
- package/src/{experimental/multi-store/useStore.test.tsx → useStore.test.tsx} +32 -30
- package/src/useStore.ts +94 -66
- package/dist/LiveStoreContext.d.ts +0 -40
- package/dist/LiveStoreContext.d.ts.map +0 -1
- package/dist/LiveStoreContext.js +0 -21
- package/dist/LiveStoreContext.js.map +0 -1
- package/dist/LiveStoreProvider.d.ts +0 -73
- package/dist/LiveStoreProvider.d.ts.map +0 -1
- package/dist/LiveStoreProvider.js +0 -233
- 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 -105
- package/dist/experimental/multi-store/StoreRegistry.d.ts.map +0 -1
- package/dist/experimental/multi-store/StoreRegistry.js +0 -184
- 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 -381
- 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 -25
- 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 -16
- 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.map +0 -1
- package/src/LiveStoreContext.ts +0 -41
- package/src/LiveStoreProvider.test.tsx +0 -248
- package/src/LiveStoreProvider.tsx +0 -430
- package/src/experimental/multi-store/StoreRegistry.test.ts +0 -518
- package/src/experimental/multi-store/StoreRegistry.ts +0 -253
- 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 -37
- package/src/experimental/multi-store/useStore.ts +0 -26
- /package/dist/{experimental/multi-store/useStore.test.d.ts → useStore.test.d.ts} +0 -0
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
import { makeInMemoryAdapter } from '@livestore/adapter-web'
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
type RegistryStoreOptions,
|
|
4
|
+
type Store,
|
|
5
|
+
StoreInternalsSymbol,
|
|
6
|
+
StoreRegistry,
|
|
7
|
+
storeOptions,
|
|
8
|
+
} from '@livestore/livestore'
|
|
4
9
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
5
10
|
import { act, type RenderHookResult, type RenderResult, render, renderHook, waitFor } from '@testing-library/react'
|
|
6
11
|
import * as React from 'react'
|
|
7
12
|
import { describe, expect, it } from 'vitest'
|
|
8
|
-
import { schema } from '
|
|
9
|
-
import { StoreRegistry } from './StoreRegistry.ts'
|
|
13
|
+
import { schema } from './__tests__/fixture.tsx'
|
|
10
14
|
import { StoreRegistryProvider } from './StoreRegistryContext.tsx'
|
|
11
|
-
import { storeOptions } from './storeOptions.ts'
|
|
12
|
-
import type { CachedStoreOptions } from './types.ts'
|
|
13
15
|
import { useStore } from './useStore.ts'
|
|
14
16
|
|
|
15
17
|
describe('experimental useStore', () => {
|
|
16
18
|
it('should return the same promise instance for concurrent getOrLoadStore calls', async () => {
|
|
17
|
-
const
|
|
19
|
+
const storeRegistry = new StoreRegistry()
|
|
18
20
|
const options = testStoreOptions()
|
|
19
21
|
|
|
20
22
|
// Make two concurrent calls during loading
|
|
21
|
-
const firstStore =
|
|
22
|
-
const secondStore =
|
|
23
|
+
const firstStore = storeRegistry.getOrLoadPromise(options)
|
|
24
|
+
const secondStore = storeRegistry.getOrLoadPromise(options)
|
|
23
25
|
|
|
24
26
|
// Both should be promises (store is loading)
|
|
25
27
|
expect(firstStore).toBeInstanceOf(Promise)
|
|
@@ -35,13 +37,13 @@ describe('experimental useStore', () => {
|
|
|
35
37
|
})
|
|
36
38
|
|
|
37
39
|
it('works with Suspense boundary', async () => {
|
|
38
|
-
const
|
|
40
|
+
const storeRegistry = new StoreRegistry()
|
|
39
41
|
const options = testStoreOptions()
|
|
40
42
|
|
|
41
43
|
let view: RenderResult | undefined
|
|
42
44
|
await act(async () => {
|
|
43
45
|
view = render(
|
|
44
|
-
<StoreRegistryProvider storeRegistry={
|
|
46
|
+
<StoreRegistryProvider storeRegistry={storeRegistry}>
|
|
45
47
|
<React.Suspense fallback={<div data-testid="fallback" />}>
|
|
46
48
|
<StoreConsumer options={options} />
|
|
47
49
|
</React.Suspense>
|
|
@@ -58,11 +60,11 @@ describe('experimental useStore', () => {
|
|
|
58
60
|
})
|
|
59
61
|
|
|
60
62
|
it('does not re-suspend on subsequent renders when store is already loaded', async () => {
|
|
61
|
-
const
|
|
63
|
+
const storeRegistry = new StoreRegistry()
|
|
62
64
|
const options = testStoreOptions()
|
|
63
65
|
|
|
64
|
-
const Wrapper = ({ opts }: { opts:
|
|
65
|
-
<StoreRegistryProvider storeRegistry={
|
|
66
|
+
const Wrapper = ({ opts }: { opts: RegistryStoreOptions<typeof schema> }) => (
|
|
67
|
+
<StoreRegistryProvider storeRegistry={storeRegistry}>
|
|
66
68
|
<React.Suspense fallback={<div data-testid="fallback" />}>
|
|
67
69
|
<StoreConsumer options={opts} />
|
|
68
70
|
</React.Suspense>
|
|
@@ -92,19 +94,19 @@ describe('experimental useStore', () => {
|
|
|
92
94
|
})
|
|
93
95
|
|
|
94
96
|
it('throws when store loading fails', async () => {
|
|
95
|
-
const
|
|
97
|
+
const storeRegistry = new StoreRegistry()
|
|
96
98
|
const badOptions = testStoreOptions({
|
|
97
99
|
// @ts-expect-error - intentionally passing invalid adapter to trigger error
|
|
98
100
|
adapter: null,
|
|
99
101
|
})
|
|
100
102
|
|
|
101
103
|
// Pre-load the store to cache the error (error happens synchronously)
|
|
102
|
-
expect(() =>
|
|
104
|
+
expect(() => storeRegistry.getOrLoadPromise(badOptions)).toThrow()
|
|
103
105
|
|
|
104
106
|
// Now when useStore tries to get it, it should throw synchronously
|
|
105
107
|
expect(() =>
|
|
106
108
|
renderHook(() => useStore(badOptions), {
|
|
107
|
-
wrapper: makeProvider(
|
|
109
|
+
wrapper: makeProvider(storeRegistry),
|
|
108
110
|
}),
|
|
109
111
|
).toThrow()
|
|
110
112
|
})
|
|
@@ -113,13 +115,13 @@ describe('experimental useStore', () => {
|
|
|
113
115
|
{ label: 'non-strict mode', strictMode: false },
|
|
114
116
|
{ label: 'strict mode', strictMode: true },
|
|
115
117
|
])('works in $label', async ({ strictMode }) => {
|
|
116
|
-
const
|
|
118
|
+
const storeRegistry = new StoreRegistry()
|
|
117
119
|
const options = testStoreOptions()
|
|
118
120
|
|
|
119
|
-
let hook: RenderHookResult<Store<typeof schema>,
|
|
121
|
+
let hook: RenderHookResult<Store<typeof schema>, RegistryStoreOptions<typeof schema>> | undefined
|
|
120
122
|
await act(async () => {
|
|
121
123
|
hook = renderHook(() => useStore(options), {
|
|
122
|
-
wrapper: makeProvider(
|
|
124
|
+
wrapper: makeProvider(storeRegistry, { suspense: true }),
|
|
123
125
|
reactStrictMode: strictMode,
|
|
124
126
|
})
|
|
125
127
|
})
|
|
@@ -133,16 +135,16 @@ describe('experimental useStore', () => {
|
|
|
133
135
|
})
|
|
134
136
|
|
|
135
137
|
it('handles switching between different storeId values', async () => {
|
|
136
|
-
const
|
|
138
|
+
const storeRegistry = new StoreRegistry()
|
|
137
139
|
|
|
138
140
|
const optionsA = testStoreOptions({ storeId: 'store-a' })
|
|
139
141
|
const optionsB = testStoreOptions({ storeId: 'store-b' })
|
|
140
142
|
|
|
141
|
-
let hook: RenderHookResult<Store<typeof schema>,
|
|
143
|
+
let hook: RenderHookResult<Store<typeof schema>, RegistryStoreOptions<typeof schema>> | undefined
|
|
142
144
|
await act(async () => {
|
|
143
145
|
hook = renderHook((opts) => useStore(opts), {
|
|
144
146
|
initialProps: optionsA,
|
|
145
|
-
wrapper: makeProvider(
|
|
147
|
+
wrapper: makeProvider(storeRegistry, { suspense: true }),
|
|
146
148
|
})
|
|
147
149
|
})
|
|
148
150
|
const { result, rerender, unmount } = hook ?? shouldNeverHappen('renderHook failed')
|
|
@@ -173,10 +175,10 @@ describe('experimental useStore', () => {
|
|
|
173
175
|
// useStore doesn't handle unusedCacheTime=0 correctly because retain is called in useEffect (after render)
|
|
174
176
|
// See https://github.com/livestorejs/livestore/issues/916
|
|
175
177
|
it.skip('should load store with unusedCacheTime set to 0', async () => {
|
|
176
|
-
const
|
|
178
|
+
const storeRegistry = new StoreRegistry({ defaultOptions: { unusedCacheTime: 0 } })
|
|
177
179
|
const options = testStoreOptions({ unusedCacheTime: 0 })
|
|
178
180
|
|
|
179
|
-
const StoreConsumerWithVerification = ({ opts }: { opts:
|
|
181
|
+
const StoreConsumerWithVerification = ({ opts }: { opts: RegistryStoreOptions<typeof schema> }) => {
|
|
180
182
|
const store = useStore(opts)
|
|
181
183
|
// Verify store is usable - access internals to confirm it's not disposed
|
|
182
184
|
const clientSession = store[StoreInternalsSymbol].clientSession
|
|
@@ -186,7 +188,7 @@ describe('experimental useStore', () => {
|
|
|
186
188
|
let view: RenderResult | undefined
|
|
187
189
|
await act(async () => {
|
|
188
190
|
view = render(
|
|
189
|
-
<StoreRegistryProvider storeRegistry={
|
|
191
|
+
<StoreRegistryProvider storeRegistry={storeRegistry}>
|
|
190
192
|
<React.Suspense fallback={<div data-testid="fallback" />}>
|
|
191
193
|
<StoreConsumerWithVerification opts={options} />
|
|
192
194
|
</React.Suspense>
|
|
@@ -211,15 +213,15 @@ describe('experimental useStore', () => {
|
|
|
211
213
|
})
|
|
212
214
|
})
|
|
213
215
|
|
|
214
|
-
const StoreConsumer = ({ options }: { options:
|
|
216
|
+
const StoreConsumer = ({ options }: { options: RegistryStoreOptions<any> }) => {
|
|
215
217
|
useStore(options)
|
|
216
218
|
return <div data-testid="ready" />
|
|
217
219
|
}
|
|
218
220
|
|
|
219
221
|
const makeProvider =
|
|
220
|
-
(
|
|
222
|
+
(storeRegistry: StoreRegistry, { suspense = false }: { suspense?: boolean } = {}) =>
|
|
221
223
|
({ children }: { children: React.ReactNode }) => {
|
|
222
|
-
let content = <StoreRegistryProvider storeRegistry={
|
|
224
|
+
let content = <StoreRegistryProvider storeRegistry={storeRegistry}>{children}</StoreRegistryProvider>
|
|
223
225
|
|
|
224
226
|
if (suspense) {
|
|
225
227
|
content = <React.Suspense fallback={null}>{content}</React.Suspense>
|
|
@@ -230,7 +232,7 @@ const makeProvider =
|
|
|
230
232
|
|
|
231
233
|
let testStoreCounter = 0
|
|
232
234
|
|
|
233
|
-
const testStoreOptions = (overrides: Partial<
|
|
235
|
+
const testStoreOptions = (overrides: Partial<RegistryStoreOptions<typeof schema>> = {}) =>
|
|
234
236
|
storeOptions({
|
|
235
237
|
storeId: overrides.storeId ?? `test-store-${testStoreCounter++}`,
|
|
236
238
|
schema,
|
package/src/useStore.ts
CHANGED
|
@@ -1,88 +1,116 @@
|
|
|
1
1
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
2
|
-
import type { Store } from '@livestore/livestore'
|
|
2
|
+
import type { RegistryStoreOptions, Store } from '@livestore/livestore'
|
|
3
|
+
import type { Schema } from '@livestore/utils/effect'
|
|
3
4
|
import React from 'react'
|
|
4
|
-
|
|
5
|
-
import type { ReactApi } from './LiveStoreContext.ts'
|
|
6
|
-
import { LiveStoreContext } from './LiveStoreContext.ts'
|
|
5
|
+
import { useStoreRegistry } from './StoreRegistryContext.tsx'
|
|
7
6
|
import { useClientDocument } from './useClientDocument.ts'
|
|
8
7
|
import { useQuery } from './useQuery.ts'
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* This is called automatically by `useStore()` and `LiveStoreProvider`. You typically
|
|
14
|
-
* don't need to call it directly unless you're building custom integrations.
|
|
10
|
+
* Returns a store instance augmented with hooks (`store.useQuery()` and `store.useClientDocument()`) for reactive queries.
|
|
15
11
|
*
|
|
16
12
|
* @example
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*/
|
|
23
|
-
export const withReactApi = <TSchema extends LiveStoreSchema>(store: Store<TSchema>): Store<TSchema> & ReactApi => {
|
|
24
|
-
// @ts-expect-error TODO properly implement this
|
|
25
|
-
|
|
26
|
-
store.useQuery = (queryable) => useQuery(queryable, { store })
|
|
27
|
-
// @ts-expect-error TODO properly implement this
|
|
28
|
-
|
|
29
|
-
store.useClientDocument = (table, idOrOptions, options) => useClientDocument(table, idOrOptions, options, { store })
|
|
30
|
-
return store as Store<TSchema> & ReactApi
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Returns the current Store instance from React context, augmented with React-specific methods.
|
|
35
|
-
*
|
|
36
|
-
* Use this hook when you need direct access to the Store for operations like
|
|
37
|
-
* `store.commit()`, `store.subscribe()`, or accessing `store.sessionId`.
|
|
38
|
-
*
|
|
39
|
-
* For reactive queries, prefer `useQuery()` or `useClientDocument()` which handle
|
|
40
|
-
* subscriptions and re-renders automatically.
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function Issue() {
|
|
15
|
+
* // Suspends until loaded or returns immediately if already loaded
|
|
16
|
+
* const issueStore = useStore(issueStoreOptions('abc123'))
|
|
17
|
+
* const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
|
|
41
18
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
19
|
+
* const toggleStatus = () =>
|
|
20
|
+
* issueStore.commit(
|
|
21
|
+
* issueEvents.issueStatusChanged({
|
|
22
|
+
* id: issue.id,
|
|
23
|
+
* status: issue.status === 'done' ? 'todo' : 'done',
|
|
24
|
+
* }),
|
|
25
|
+
* )
|
|
46
26
|
*
|
|
47
|
-
* const
|
|
48
|
-
*
|
|
49
|
-
*
|
|
27
|
+
* const preloadParentIssue = (issueId: string) =>
|
|
28
|
+
* storeRegistry.preload({
|
|
29
|
+
* ...issueStoreOptions(issueId),
|
|
30
|
+
* unusedCacheTime: 10_000,
|
|
31
|
+
* })
|
|
50
32
|
*
|
|
51
|
-
* return
|
|
33
|
+
* return (
|
|
34
|
+
* <>
|
|
35
|
+
* <h2>{issue.title}</h2>
|
|
36
|
+
* <button onClick={() => toggleStatus()}>Toggle Status</button>
|
|
37
|
+
* <button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
|
|
38
|
+
* </>
|
|
39
|
+
* )
|
|
52
40
|
* }
|
|
53
41
|
* ```
|
|
54
42
|
*
|
|
55
|
-
* @
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* ```
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* ```ts
|
|
65
|
-
* // Use with an explicit store instance (bypasses context)
|
|
66
|
-
* const { store } = useStore({ store: myExternalStore })
|
|
67
|
-
* ```
|
|
43
|
+
* @remarks
|
|
44
|
+
* - Suspends until the store is loaded.
|
|
45
|
+
* - Store is cached by its `storeId` in the `StoreRegistry`. Multiple calls with the same `storeId` return the same store instance.
|
|
46
|
+
* - Store is cached as long as it's being used, and after `unusedCacheTime` expires (default `60_000` ms in browser, `Infinity` in non-browser)
|
|
47
|
+
* - Default store options can be configured in `StoreRegistry` constructor.
|
|
48
|
+
* - 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.
|
|
68
49
|
*
|
|
69
|
-
* @
|
|
50
|
+
* @typeParam TSchema - The schema type for the store
|
|
51
|
+
* @returns The loaded store instance augmented with React hooks
|
|
52
|
+
* @throws unknown - store loading error or if called outside `<StoreRegistryProvider>`
|
|
70
53
|
*/
|
|
71
|
-
export const useStore =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
54
|
+
export const useStore = <
|
|
55
|
+
TSchema extends LiveStoreSchema,
|
|
56
|
+
TContext = {},
|
|
57
|
+
TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue,
|
|
58
|
+
>(
|
|
59
|
+
options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>,
|
|
60
|
+
): Store<TSchema, TContext> & ReactApi => {
|
|
61
|
+
const storeRegistry = useStoreRegistry()
|
|
75
62
|
|
|
76
|
-
//
|
|
77
|
-
|
|
63
|
+
// NOTE: retain() is called in useEffect (after render), while getOrLoadPromise() is called
|
|
64
|
+
// in useMemo (during render). This creates a timing gap where with very short unusedCacheTime
|
|
65
|
+
// values (e.g., 0), the store could theoretically be disposed before the effect fires.
|
|
66
|
+
// In practice, this is not an issue with the default 60s cache time, but it becomes an issue when
|
|
67
|
+
// `unusedCacheTime` is configured to values less than ~100ms.
|
|
68
|
+
// See https://github.com/livestorejs/livestore/issues/916
|
|
69
|
+
React.useEffect(() => storeRegistry.retain(options), [storeRegistry, options])
|
|
78
70
|
|
|
79
|
-
|
|
80
|
-
throw new Error(`useStore can only be used inside StoreContext.Provider`)
|
|
81
|
-
}
|
|
71
|
+
const storeOrPromise = React.useMemo(() => storeRegistry.getOrLoadPromise(options), [storeRegistry, options])
|
|
82
72
|
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
const store = storeOrPromise instanceof Promise ? React.use(storeOrPromise) : storeOrPromise
|
|
74
|
+
|
|
75
|
+
// Expose store on the global object for browser console debugging.
|
|
76
|
+
globalThis.__debugLiveStore ??= {}
|
|
77
|
+
if (Object.keys(globalThis.__debugLiveStore).length === 0) {
|
|
78
|
+
globalThis.__debugLiveStore._ = store
|
|
85
79
|
}
|
|
80
|
+
globalThis.__debugLiveStore[options.debug?.instanceId ?? options.storeId] = store
|
|
81
|
+
|
|
82
|
+
return withReactApi(store)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* React-specific methods added to the Store when used via React hooks.
|
|
87
|
+
*
|
|
88
|
+
* These methods are attached by `withReactApi()` and `useStore()`, allowing you
|
|
89
|
+
* to call `store.useQuery()` and `store.useClientDocument()` directly on the
|
|
90
|
+
* Store instance.
|
|
91
|
+
*/
|
|
92
|
+
export type ReactApi = {
|
|
93
|
+
/** Hook version of query subscription—re-renders component when query result changes */
|
|
94
|
+
useQuery: typeof useQuery
|
|
95
|
+
/** Hook for reading and writing client-document tables with React state semantics */
|
|
96
|
+
useClientDocument: typeof useClientDocument
|
|
97
|
+
}
|
|
86
98
|
|
|
87
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Augments a Store instance with React-specific methods (`useQuery`, `useClientDocument`).
|
|
101
|
+
*
|
|
102
|
+
* This is called automatically by `useStore()`. You typically don't need to call it
|
|
103
|
+
* directly unless you're building custom integrations.
|
|
104
|
+
*
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
export const withReactApi = <TSchema extends LiveStoreSchema, TContext = {}>(
|
|
108
|
+
store: Store<TSchema, TContext>,
|
|
109
|
+
): Store<TSchema, TContext> & ReactApi => {
|
|
110
|
+
// @ts-expect-error TODO properly implement this
|
|
111
|
+
store.useQuery = (queryable) => useQuery(queryable, { store })
|
|
112
|
+
|
|
113
|
+
// @ts-expect-error TODO properly implement this
|
|
114
|
+
store.useClientDocument = (table, idOrOptions, options) => useClientDocument(table, idOrOptions, options, { store })
|
|
115
|
+
return store as Store<TSchema, TContext> & ReactApi
|
|
88
116
|
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { LiveStoreContextRunning } from '@livestore/livestore';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import type { useClientDocument } from './useClientDocument.ts';
|
|
4
|
-
import type { useQuery } from './useQuery.ts';
|
|
5
|
-
/**
|
|
6
|
-
* React-specific methods added to the Store when used via React hooks.
|
|
7
|
-
*
|
|
8
|
-
* These methods are attached by `withReactApi()` and `useStore()`, allowing you
|
|
9
|
-
* to call `store.useQuery()` and `store.useClientDocument()` directly on the
|
|
10
|
-
* Store instance.
|
|
11
|
-
*/
|
|
12
|
-
export type ReactApi = {
|
|
13
|
-
/** Hook version of query subscription—re-renders component when query result changes */
|
|
14
|
-
useQuery: typeof useQuery;
|
|
15
|
-
/** Hook for reading and writing client-document tables with React state semantics */
|
|
16
|
-
useClientDocument: typeof useClientDocument;
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* React context for accessing the LiveStore instance.
|
|
20
|
-
*
|
|
21
|
-
* This context is provided by `<LiveStoreProvider>` and consumed by hooks like
|
|
22
|
-
* `useStore()`, `useQuery()`, and `useClientDocument()`.
|
|
23
|
-
*
|
|
24
|
-
* The context value is `undefined` until the Store has finished booting,
|
|
25
|
-
* then transitions to `{ stage: 'running', store: ... }`.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```tsx
|
|
29
|
-
* // Typically you don't use this directly—use useStore() instead
|
|
30
|
-
* const context = React.useContext(LiveStoreContext)
|
|
31
|
-
* if (context?.stage === 'running') {
|
|
32
|
-
* console.log('Store ready:', context.store.storeId)
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export declare const LiveStoreContext: React.Context<{
|
|
37
|
-
stage: "running";
|
|
38
|
-
store: LiveStoreContextRunning["store"] & ReactApi;
|
|
39
|
-
} | undefined>;
|
|
40
|
-
//# sourceMappingURL=LiveStoreContext.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LiveStoreContext.d.ts","sourceRoot":"","sources":["../src/LiveStoreContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAA;AACnE,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,wFAAwF;IACxF,QAAQ,EAAE,OAAO,QAAQ,CAAA;IACzB,qFAAqF;IACrF,iBAAiB,EAAE,OAAO,iBAAiB,CAAA;CAC5C,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,gBAAgB;WAClB,SAAS;WAAS,uBAAuB,CAAC,OAAO,CAAC,GAAG,QAAQ;cAC5D,CAAA"}
|
package/dist/LiveStoreContext.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
/**
|
|
3
|
-
* React context for accessing the LiveStore instance.
|
|
4
|
-
*
|
|
5
|
-
* This context is provided by `<LiveStoreProvider>` and consumed by hooks like
|
|
6
|
-
* `useStore()`, `useQuery()`, and `useClientDocument()`.
|
|
7
|
-
*
|
|
8
|
-
* The context value is `undefined` until the Store has finished booting,
|
|
9
|
-
* then transitions to `{ stage: 'running', store: ... }`.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```tsx
|
|
13
|
-
* // Typically you don't use this directly—use useStore() instead
|
|
14
|
-
* const context = React.useContext(LiveStoreContext)
|
|
15
|
-
* if (context?.stage === 'running') {
|
|
16
|
-
* console.log('Store ready:', context.store.storeId)
|
|
17
|
-
* }
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
export const LiveStoreContext = React.createContext(undefined);
|
|
21
|
-
//# sourceMappingURL=LiveStoreContext.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LiveStoreContext.js","sourceRoot":"","sources":["../src/LiveStoreContext.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AAmBzB;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC,aAAa,CAEjD,SAAS,CAAC,CAAA"}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import type { Adapter, BootStatus, IntentionalShutdownCause, MigrationsReport, SyncError } from '@livestore/common';
|
|
2
|
-
import { LogConfig, UnknownError } from '@livestore/common';
|
|
3
|
-
import type { LiveStoreSchema } from '@livestore/common/schema';
|
|
4
|
-
import type { CreateStoreOptions, OtelOptions, Store } from '@livestore/livestore';
|
|
5
|
-
import { StoreInterrupted } from '@livestore/livestore';
|
|
6
|
-
import type { OtelTracer } from '@livestore/utils/effect';
|
|
7
|
-
import { Effect, Schema } from '@livestore/utils/effect';
|
|
8
|
-
import type * as otel from '@opentelemetry/api';
|
|
9
|
-
import React from 'react';
|
|
10
|
-
export interface LiveStoreProviderProps<TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue> extends LogConfig.WithLoggerOptions {
|
|
11
|
-
schema: LiveStoreSchema;
|
|
12
|
-
/**
|
|
13
|
-
* The `storeId` can be used to isolate multiple stores from each other.
|
|
14
|
-
* So it can be useful for multi-tenancy scenarios.
|
|
15
|
-
*
|
|
16
|
-
* The `storeId` is also used for persistence.
|
|
17
|
-
*
|
|
18
|
-
* Make sure to also configure `storeId` in LiveStore Devtools (e.g. in Vite plugin).
|
|
19
|
-
*
|
|
20
|
-
* @default 'default'
|
|
21
|
-
*/
|
|
22
|
-
storeId?: string;
|
|
23
|
-
boot?: (store: Store<LiveStoreSchema>, ctx: {
|
|
24
|
-
migrationsReport: MigrationsReport;
|
|
25
|
-
parentSpan: otel.Span;
|
|
26
|
-
}) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer>;
|
|
27
|
-
otelOptions?: Partial<OtelOptions>;
|
|
28
|
-
renderLoading?: (status: BootStatus) => React.ReactNode;
|
|
29
|
-
renderError?: (error: UnknownError | unknown) => React.ReactNode;
|
|
30
|
-
renderShutdown?: (cause: IntentionalShutdownCause | StoreInterrupted | SyncError) => React.ReactNode;
|
|
31
|
-
adapter: Adapter;
|
|
32
|
-
/**
|
|
33
|
-
* In order for LiveStore to apply multiple events in a single render,
|
|
34
|
-
* you need to pass the `batchUpdates` function from either `react-dom` or `react-native`.
|
|
35
|
-
*
|
|
36
|
-
* ```ts
|
|
37
|
-
* // With React DOM
|
|
38
|
-
* import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
|
|
39
|
-
*
|
|
40
|
-
* // With React Native
|
|
41
|
-
* import { unstable_batchedUpdates as batchUpdates } from 'react-native'
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
batchUpdates: (run: () => void) => void;
|
|
45
|
-
disableDevtools?: boolean;
|
|
46
|
-
signal?: AbortSignal;
|
|
47
|
-
/**
|
|
48
|
-
* Currently only used in the web adapter:
|
|
49
|
-
* If true, registers a beforeunload event listener to confirm unsaved changes.
|
|
50
|
-
*
|
|
51
|
-
* @default true
|
|
52
|
-
*/
|
|
53
|
-
confirmUnsavedChanges?: boolean;
|
|
54
|
-
/**
|
|
55
|
-
* Advanced store parameters forwarded to `createStore`.
|
|
56
|
-
* Currently supports:
|
|
57
|
-
* - `leaderPushBatchSize`: max events pushed to the leader per write batch.
|
|
58
|
-
* - `eventQueryBatchSize`: chunk size used when the stream replays confirmed events.
|
|
59
|
-
*/
|
|
60
|
-
params?: CreateStoreOptions<LiveStoreSchema>['params'];
|
|
61
|
-
/**
|
|
62
|
-
* Payload that will be passed to the sync backend when connecting
|
|
63
|
-
*
|
|
64
|
-
* @default undefined
|
|
65
|
-
*/
|
|
66
|
-
syncPayloadSchema?: TSyncPayloadSchema;
|
|
67
|
-
syncPayload?: Schema.Schema.Type<TSyncPayloadSchema>;
|
|
68
|
-
debug?: {
|
|
69
|
-
instanceId?: string;
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
export declare const LiveStoreProvider: <TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue>({ renderLoading, renderError, renderShutdown, otelOptions, children, schema, storeId, boot, adapter, batchUpdates, disableDevtools, signal, confirmUnsavedChanges, params, syncPayload, syncPayloadSchema, debug, logger, logLevel, }: LiveStoreProviderProps<TSyncPayloadSchema> & React.PropsWithChildren) => React.ReactNode;
|
|
73
|
-
//# sourceMappingURL=LiveStoreProvider.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LiveStoreProvider.d.ts","sourceRoot":"","sources":["../src/LiveStoreProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AACnH,OAAO,EAAE,SAAS,EAAe,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EAEX,KAAK,EAEN,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAqC,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE1F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAmB,MAAM,EAAkB,MAAM,EAAsB,MAAM,yBAAyB,CAAA;AAC7G,OAAO,KAAK,KAAK,IAAI,MAAM,oBAAoB,CAAA;AAC/C,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,MAAM,WAAW,sBAAsB,CAAC,kBAAkB,SAAS,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,MAAM,CAAC,SAAS,CAC7G,SAAQ,SAAS,CAAC,iBAAiB;IACnC,MAAM,EAAE,eAAe,CAAA;IACvB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,CACL,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,EAC7B,GAAG,EAAE;QAAE,gBAAgB,EAAE,gBAAgB,CAAC;QAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAA;KAAE,KAC/D,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,UAAU,CAAC,CAAA;IAC/E,WAAW,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IAClC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,KAAK,CAAC,SAAS,CAAA;IACvD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,KAAK,KAAK,CAAC,SAAS,CAAA;IAChE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,wBAAwB,GAAG,gBAAgB,GAAG,SAAS,KAAK,KAAK,CAAC,SAAS,CAAA;IACpG,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;;;;;;;OAWG;IACH,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,IAAI,KAAK,IAAI,CAAA;IACvC,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAA;IACtD;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,kBAAkB,CAAA;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;IACpD,KAAK,CAAC,EAAE;QACN,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AA2BD,eAAO,MAAM,iBAAiB,GAAI,kBAAkB,SAAS,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,MAAM,CAAC,SAAS,EAAE,uOAoBxG,sBAAsB,CAAC,kBAAkB,CAAC,GAAG,KAAK,CAAC,iBAAiB,KAAG,KAAK,CAAC,SAwC/E,CAAA"}
|