@livestore/react 0.0.0-snapshot-8d3edf87cb1e88c7b67c5f3ea9d0b307253c33df
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 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/LiveStoreContext.d.ts +7 -0
- package/dist/LiveStoreContext.d.ts.map +1 -0
- package/dist/LiveStoreContext.js +13 -0
- package/dist/LiveStoreContext.js.map +1 -0
- package/dist/LiveStoreProvider.d.ts +49 -0
- package/dist/LiveStoreProvider.d.ts.map +1 -0
- package/dist/LiveStoreProvider.js +168 -0
- package/dist/LiveStoreProvider.js.map +1 -0
- package/dist/LiveStoreProvider.test.d.ts +2 -0
- package/dist/LiveStoreProvider.test.d.ts.map +1 -0
- package/dist/LiveStoreProvider.test.js +62 -0
- package/dist/LiveStoreProvider.test.js.map +1 -0
- package/dist/__tests__/fixture.d.ts +567 -0
- package/dist/__tests__/fixture.d.ts.map +1 -0
- package/dist/__tests__/fixture.js +61 -0
- package/dist/__tests__/fixture.js.map +1 -0
- package/dist/experimental/components/LiveList.d.ts +21 -0
- package/dist/experimental/components/LiveList.d.ts.map +1 -0
- package/dist/experimental/components/LiveList.js +31 -0
- package/dist/experimental/components/LiveList.js.map +1 -0
- package/dist/experimental/mod.d.ts +2 -0
- package/dist/experimental/mod.d.ts.map +1 -0
- package/dist/experimental/mod.js +2 -0
- package/dist/experimental/mod.js.map +1 -0
- package/dist/mod.d.ts +8 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +8 -0
- package/dist/mod.js.map +1 -0
- package/dist/useAtom.d.ts +10 -0
- package/dist/useAtom.d.ts.map +1 -0
- package/dist/useAtom.js +37 -0
- package/dist/useAtom.js.map +1 -0
- package/dist/useQuery.d.ts +9 -0
- package/dist/useQuery.d.ts.map +1 -0
- package/dist/useQuery.js +88 -0
- package/dist/useQuery.js.map +1 -0
- package/dist/useQuery.test.d.ts +2 -0
- package/dist/useQuery.test.d.ts.map +1 -0
- package/dist/useQuery.test.js +51 -0
- package/dist/useQuery.test.js.map +1 -0
- package/dist/useRow.d.ts +46 -0
- package/dist/useRow.d.ts.map +1 -0
- package/dist/useRow.js +96 -0
- package/dist/useRow.js.map +1 -0
- package/dist/useRow.test.d.ts +2 -0
- package/dist/useRow.test.d.ts.map +1 -0
- package/dist/useRow.test.js +212 -0
- package/dist/useRow.test.js.map +1 -0
- package/dist/useTemporaryQuery.d.ts +22 -0
- package/dist/useTemporaryQuery.d.ts.map +1 -0
- package/dist/useTemporaryQuery.js +75 -0
- package/dist/useTemporaryQuery.js.map +1 -0
- package/dist/useTemporaryQuery.test.d.ts +2 -0
- package/dist/useTemporaryQuery.test.d.ts.map +1 -0
- package/dist/useTemporaryQuery.test.js +59 -0
- package/dist/useTemporaryQuery.test.js.map +1 -0
- package/dist/utils/stack-info.d.ts +4 -0
- package/dist/utils/stack-info.d.ts.map +1 -0
- package/dist/utils/stack-info.js +11 -0
- package/dist/utils/stack-info.js.map +1 -0
- package/dist/utils/useStateRefWithReactiveInput.d.ts +13 -0
- package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -0
- package/dist/utils/useStateRefWithReactiveInput.js +38 -0
- package/dist/utils/useStateRefWithReactiveInput.js.map +1 -0
- package/package.json +54 -0
- package/src/LiveStoreContext.ts +19 -0
- package/src/LiveStoreProvider.test.tsx +109 -0
- package/src/LiveStoreProvider.tsx +295 -0
- package/src/__snapshots__/useRow.test.tsx.snap +359 -0
- package/src/__tests__/fixture.tsx +119 -0
- package/src/ambient.d.ts +2 -0
- package/src/experimental/components/LiveList.tsx +84 -0
- package/src/experimental/mod.ts +1 -0
- package/src/mod.ts +13 -0
- package/src/useAtom.ts +55 -0
- package/src/useQuery.test.tsx +82 -0
- package/src/useQuery.ts +122 -0
- package/src/useRow.test.tsx +346 -0
- package/src/useRow.ts +182 -0
- package/src/useTemporaryQuery.test.tsx +98 -0
- package/src/useTemporaryQuery.ts +131 -0
- package/src/utils/stack-info.ts +13 -0
- package/src/utils/useStateRefWithReactiveInput.ts +51 -0
- package/tsconfig.json +20 -0
- package/vitest.config.js +17 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { BootDb } from '@livestore/common'
|
|
2
|
+
import { sql } from '@livestore/common'
|
|
3
|
+
import { querySQL } from '@livestore/livestore'
|
|
4
|
+
import { Schema } from '@livestore/utils/effect'
|
|
5
|
+
import { makeInMemoryAdapter } from '@livestore/web'
|
|
6
|
+
import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'
|
|
7
|
+
import React from 'react'
|
|
8
|
+
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
|
|
9
|
+
import { describe, expect, it } from 'vitest'
|
|
10
|
+
|
|
11
|
+
import { schema, tables } from './__tests__/fixture.js'
|
|
12
|
+
import { LiveStoreProvider } from './LiveStoreProvider.js'
|
|
13
|
+
import * as LiveStoreReact from './mod.js'
|
|
14
|
+
|
|
15
|
+
describe('LiveStoreProvider', () => {
|
|
16
|
+
it('simple', async () => {
|
|
17
|
+
let appRenderCount = 0
|
|
18
|
+
|
|
19
|
+
const allTodos$ = querySQL(`select * from todos`, { schema: Schema.Array(tables.todos.schema) })
|
|
20
|
+
|
|
21
|
+
const App = () => {
|
|
22
|
+
appRenderCount++
|
|
23
|
+
|
|
24
|
+
const todos = LiveStoreReact.useQuery(allTodos$)
|
|
25
|
+
|
|
26
|
+
return <div>{JSON.stringify(todos)}</div>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const abortController = new AbortController()
|
|
30
|
+
|
|
31
|
+
const Root = ({ forceUpdate }: { forceUpdate: number }) => {
|
|
32
|
+
const bootCb = React.useCallback(
|
|
33
|
+
(db: BootDb) =>
|
|
34
|
+
db.execute(sql`INSERT OR IGNORE INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`),
|
|
35
|
+
[],
|
|
36
|
+
)
|
|
37
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
|
+
const adapterMemo = React.useMemo(() => makeInMemoryAdapter(), [forceUpdate])
|
|
39
|
+
return (
|
|
40
|
+
<LiveStoreProvider
|
|
41
|
+
schema={schema}
|
|
42
|
+
renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
|
|
43
|
+
adapter={adapterMemo}
|
|
44
|
+
boot={bootCb}
|
|
45
|
+
signal={abortController.signal}
|
|
46
|
+
batchUpdates={batchUpdates}
|
|
47
|
+
>
|
|
48
|
+
<App />
|
|
49
|
+
</LiveStoreProvider>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { rerender } = render(<Root forceUpdate={1} />)
|
|
54
|
+
|
|
55
|
+
expect(appRenderCount).toBe(0)
|
|
56
|
+
|
|
57
|
+
await waitForElementToBeRemoved(() => screen.getByText((_) => _.startsWith('Loading LiveStore')))
|
|
58
|
+
|
|
59
|
+
expect(appRenderCount).toBe(1)
|
|
60
|
+
|
|
61
|
+
rerender(<Root forceUpdate={2} />)
|
|
62
|
+
|
|
63
|
+
await waitFor(() => screen.getByText('Loading LiveStore: loading'))
|
|
64
|
+
await waitFor(() => screen.getByText((_) => _.includes('buy milk')))
|
|
65
|
+
|
|
66
|
+
expect(appRenderCount).toBe(2)
|
|
67
|
+
|
|
68
|
+
abortController.abort()
|
|
69
|
+
|
|
70
|
+
await waitFor(() => screen.getByText('LiveStore Shutdown due to abort signal'))
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('error during boot', async () => {
|
|
74
|
+
let appRenderCount = 0
|
|
75
|
+
|
|
76
|
+
const App = () => {
|
|
77
|
+
appRenderCount++
|
|
78
|
+
|
|
79
|
+
return <div>hello world</div>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const Root = ({ forceUpdate }: { forceUpdate: number }) => {
|
|
83
|
+
const bootCb = React.useCallback(
|
|
84
|
+
(db: BootDb) =>
|
|
85
|
+
db.execute(sql`INSERT INTO todos_mising_table (id, text, completed) VALUES ('t1', 'buy milk', 0);`),
|
|
86
|
+
[],
|
|
87
|
+
)
|
|
88
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
89
|
+
const adapterMemo = React.useMemo(() => makeInMemoryAdapter(), [forceUpdate])
|
|
90
|
+
return (
|
|
91
|
+
<LiveStoreProvider
|
|
92
|
+
schema={schema}
|
|
93
|
+
renderLoading={(status) => <div>Loading LiveStore: {status.stage}</div>}
|
|
94
|
+
adapter={adapterMemo}
|
|
95
|
+
boot={bootCb}
|
|
96
|
+
batchUpdates={batchUpdates}
|
|
97
|
+
>
|
|
98
|
+
<App />
|
|
99
|
+
</LiveStoreProvider>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
render(<Root forceUpdate={1} />)
|
|
104
|
+
|
|
105
|
+
expect(appRenderCount).toBe(0)
|
|
106
|
+
|
|
107
|
+
await waitFor(() => screen.getByText((_) => _.startsWith('LiveStore.UnexpectedError')))
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import type { Adapter, BootDb, BootStatus, IntentionalShutdownCause } from '@livestore/common'
|
|
2
|
+
import { UnexpectedError } from '@livestore/common'
|
|
3
|
+
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
4
|
+
import type {
|
|
5
|
+
BaseGraphQLContext,
|
|
6
|
+
CreateStoreOptions,
|
|
7
|
+
GraphQLOptions,
|
|
8
|
+
LiveStoreContext as StoreContext_,
|
|
9
|
+
OtelOptions,
|
|
10
|
+
} from '@livestore/livestore'
|
|
11
|
+
import { createStore, StoreAbort, StoreInterrupted } from '@livestore/livestore'
|
|
12
|
+
import { errorToString } from '@livestore/utils'
|
|
13
|
+
import { Effect, FiberSet, Logger, LogLevel, Schema } from '@livestore/utils/effect'
|
|
14
|
+
import type * as otel from '@opentelemetry/api'
|
|
15
|
+
import type { ReactElement, ReactNode } from 'react'
|
|
16
|
+
import React from 'react'
|
|
17
|
+
|
|
18
|
+
import { LiveStoreContext } from './LiveStoreContext.js'
|
|
19
|
+
|
|
20
|
+
interface LiveStoreProviderProps<GraphQLContext> {
|
|
21
|
+
schema: LiveStoreSchema
|
|
22
|
+
/**
|
|
23
|
+
* The `storeId` can be used to isolate multiple stores from each other.
|
|
24
|
+
* So it can be useful for multi-tenancy scenarios.
|
|
25
|
+
*
|
|
26
|
+
* The `storeId` is also used for persistence.
|
|
27
|
+
*
|
|
28
|
+
* Make sure to also provide `storeId` to `mountDevtools` in `_devtools.html`.
|
|
29
|
+
*
|
|
30
|
+
* @default 'default'
|
|
31
|
+
*/
|
|
32
|
+
storeId?: string
|
|
33
|
+
boot?: (db: BootDb, parentSpan: otel.Span) => void | Promise<void> | Effect.Effect<void, unknown, otel.Tracer>
|
|
34
|
+
graphQLOptions?: GraphQLOptions<GraphQLContext>
|
|
35
|
+
otelOptions?: OtelOptions
|
|
36
|
+
renderLoading: (status: BootStatus) => ReactElement
|
|
37
|
+
renderError?: (error: UnexpectedError | unknown) => ReactElement
|
|
38
|
+
renderShutdown?: (cause: IntentionalShutdownCause | StoreAbort) => ReactElement
|
|
39
|
+
adapter: Adapter
|
|
40
|
+
/**
|
|
41
|
+
* In order for LiveStore to apply multiple mutations in a single render,
|
|
42
|
+
* you need to pass the `batchUpdates` function from either `react-dom` or `react-native`.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* // With React DOM
|
|
46
|
+
* import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
|
|
47
|
+
*
|
|
48
|
+
* // With React Native
|
|
49
|
+
* import { unstable_batchedUpdates as batchUpdates } from 'react-native'
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
batchUpdates: (run: () => void) => void
|
|
53
|
+
disableDevtools?: boolean
|
|
54
|
+
signal?: AbortSignal
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const defaultRenderError = (error: UnexpectedError | unknown) => (
|
|
58
|
+
<>{Schema.is(UnexpectedError)(error) ? error.toString() : errorToString(error)}</>
|
|
59
|
+
)
|
|
60
|
+
const defaultRenderShutdown = (cause: IntentionalShutdownCause | StoreAbort) => {
|
|
61
|
+
const reason =
|
|
62
|
+
cause._tag === 'LiveStore.StoreAbort'
|
|
63
|
+
? 'abort signal'
|
|
64
|
+
: cause.reason === 'devtools-import'
|
|
65
|
+
? 'devtools import'
|
|
66
|
+
: cause.reason === 'devtools-reset'
|
|
67
|
+
? 'devtools reset'
|
|
68
|
+
: 'unknown reason'
|
|
69
|
+
|
|
70
|
+
return <>LiveStore Shutdown due to {reason}</>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
|
|
74
|
+
renderLoading,
|
|
75
|
+
renderError = defaultRenderError,
|
|
76
|
+
renderShutdown = defaultRenderShutdown,
|
|
77
|
+
graphQLOptions,
|
|
78
|
+
otelOptions,
|
|
79
|
+
children,
|
|
80
|
+
schema,
|
|
81
|
+
storeId = 'default',
|
|
82
|
+
boot,
|
|
83
|
+
adapter,
|
|
84
|
+
batchUpdates,
|
|
85
|
+
disableDevtools,
|
|
86
|
+
signal,
|
|
87
|
+
}: LiveStoreProviderProps<GraphQLContext> & { children?: ReactNode }): JSX.Element => {
|
|
88
|
+
const storeCtx = useCreateStore({
|
|
89
|
+
storeId,
|
|
90
|
+
schema,
|
|
91
|
+
graphQLOptions,
|
|
92
|
+
otelOptions,
|
|
93
|
+
boot,
|
|
94
|
+
adapter,
|
|
95
|
+
batchUpdates,
|
|
96
|
+
disableDevtools,
|
|
97
|
+
signal,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
if (storeCtx.stage === 'error') {
|
|
101
|
+
return renderError(storeCtx.error)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (storeCtx.stage === 'shutdown') {
|
|
105
|
+
return renderShutdown(storeCtx.cause)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (storeCtx.stage !== 'running') {
|
|
109
|
+
return renderLoading(storeCtx)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
globalThis.__debugLiveStore ??= {}
|
|
113
|
+
globalThis.__debugLiveStore[storeId] = storeCtx.store
|
|
114
|
+
|
|
115
|
+
return <LiveStoreContext.Provider value={storeCtx}>{children}</LiveStoreContext.Provider>
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type SchemaKey = string
|
|
119
|
+
const semaphoreMap = new Map<SchemaKey, Effect.Semaphore>()
|
|
120
|
+
|
|
121
|
+
const withSemaphore = (storeId: SchemaKey) => {
|
|
122
|
+
let semaphore = semaphoreMap.get(storeId)
|
|
123
|
+
if (!semaphore) {
|
|
124
|
+
semaphore = Effect.makeSemaphore(1).pipe(Effect.runSync)
|
|
125
|
+
semaphoreMap.set(storeId, semaphore)
|
|
126
|
+
}
|
|
127
|
+
return semaphore.withPermits(1)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
131
|
+
schema,
|
|
132
|
+
storeId,
|
|
133
|
+
graphQLOptions,
|
|
134
|
+
otelOptions,
|
|
135
|
+
boot,
|
|
136
|
+
adapter,
|
|
137
|
+
batchUpdates,
|
|
138
|
+
disableDevtools,
|
|
139
|
+
reactivityGraph,
|
|
140
|
+
signal,
|
|
141
|
+
}: CreateStoreOptions<GraphQLContext, LiveStoreSchema> & { signal?: AbortSignal }) => {
|
|
142
|
+
const [_, rerender] = React.useState(0)
|
|
143
|
+
const ctxValueRef = React.useRef<{
|
|
144
|
+
value: StoreContext_ | BootStatus
|
|
145
|
+
fiberSet: FiberSet.FiberSet | undefined
|
|
146
|
+
counter: number
|
|
147
|
+
}>({
|
|
148
|
+
value: { stage: 'loading' },
|
|
149
|
+
fiberSet: undefined,
|
|
150
|
+
counter: 0,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// console.debug(`useCreateStore (${ctxValueRef.current.counter})`, ctxValueRef.current.value.stage)
|
|
154
|
+
|
|
155
|
+
const inputPropsCacheRef = React.useRef({
|
|
156
|
+
schema,
|
|
157
|
+
graphQLOptions,
|
|
158
|
+
otelOptions,
|
|
159
|
+
boot,
|
|
160
|
+
adapter,
|
|
161
|
+
batchUpdates,
|
|
162
|
+
disableDevtools,
|
|
163
|
+
reactivityGraph,
|
|
164
|
+
signal,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const interrupt = (fiberSet: FiberSet.FiberSet, error: StoreAbort | StoreInterrupted) =>
|
|
168
|
+
Effect.gen(function* () {
|
|
169
|
+
yield* FiberSet.clear(fiberSet)
|
|
170
|
+
yield* FiberSet.run(fiberSet, Effect.fail(error))
|
|
171
|
+
}).pipe(
|
|
172
|
+
Effect.tapErrorCause((cause) => Effect.logDebug(`[@livestore/livestore/react] interupting`, cause)),
|
|
173
|
+
Effect.runFork,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if (
|
|
177
|
+
inputPropsCacheRef.current.schema !== schema ||
|
|
178
|
+
inputPropsCacheRef.current.graphQLOptions !== graphQLOptions ||
|
|
179
|
+
inputPropsCacheRef.current.otelOptions !== otelOptions ||
|
|
180
|
+
inputPropsCacheRef.current.boot !== boot ||
|
|
181
|
+
inputPropsCacheRef.current.adapter !== adapter ||
|
|
182
|
+
inputPropsCacheRef.current.batchUpdates !== batchUpdates ||
|
|
183
|
+
inputPropsCacheRef.current.disableDevtools !== disableDevtools ||
|
|
184
|
+
inputPropsCacheRef.current.reactivityGraph !== reactivityGraph ||
|
|
185
|
+
inputPropsCacheRef.current.signal !== signal
|
|
186
|
+
) {
|
|
187
|
+
inputPropsCacheRef.current = {
|
|
188
|
+
schema,
|
|
189
|
+
graphQLOptions,
|
|
190
|
+
otelOptions,
|
|
191
|
+
boot,
|
|
192
|
+
adapter,
|
|
193
|
+
batchUpdates,
|
|
194
|
+
disableDevtools,
|
|
195
|
+
reactivityGraph,
|
|
196
|
+
signal,
|
|
197
|
+
}
|
|
198
|
+
if (ctxValueRef.current.fiberSet !== undefined) {
|
|
199
|
+
interrupt(ctxValueRef.current.fiberSet, new StoreInterrupted())
|
|
200
|
+
ctxValueRef.current.fiberSet = undefined
|
|
201
|
+
}
|
|
202
|
+
ctxValueRef.current = { value: { stage: 'loading' }, fiberSet: undefined, counter: ctxValueRef.current.counter + 1 }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
React.useEffect(() => {
|
|
206
|
+
const counter = ctxValueRef.current.counter
|
|
207
|
+
|
|
208
|
+
const setContextValue = (value: StoreContext_ | BootStatus) => {
|
|
209
|
+
if (ctxValueRef.current.counter !== counter) return
|
|
210
|
+
ctxValueRef.current.value = value
|
|
211
|
+
rerender((c) => c + 1)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
signal?.addEventListener('abort', () => {
|
|
215
|
+
if (ctxValueRef.current.fiberSet !== undefined && ctxValueRef.current.counter === counter) {
|
|
216
|
+
interrupt(ctxValueRef.current.fiberSet, new StoreAbort())
|
|
217
|
+
ctxValueRef.current.fiberSet = undefined
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
Effect.gen(function* () {
|
|
222
|
+
const fiberSet = yield* FiberSet.make<
|
|
223
|
+
unknown,
|
|
224
|
+
UnexpectedError | IntentionalShutdownCause | StoreAbort | StoreInterrupted
|
|
225
|
+
>()
|
|
226
|
+
|
|
227
|
+
ctxValueRef.current.fiberSet = fiberSet
|
|
228
|
+
|
|
229
|
+
yield* Effect.gen(function* () {
|
|
230
|
+
const store = yield* createStore({
|
|
231
|
+
fiberSet,
|
|
232
|
+
schema,
|
|
233
|
+
storeId,
|
|
234
|
+
graphQLOptions,
|
|
235
|
+
otelOptions,
|
|
236
|
+
boot,
|
|
237
|
+
adapter,
|
|
238
|
+
reactivityGraph,
|
|
239
|
+
batchUpdates,
|
|
240
|
+
disableDevtools,
|
|
241
|
+
onBootStatus: (status) => {
|
|
242
|
+
if (ctxValueRef.current.value.stage === 'running' || ctxValueRef.current.value.stage === 'error') return
|
|
243
|
+
setContextValue(status)
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
setContextValue({ stage: 'running', store })
|
|
248
|
+
|
|
249
|
+
yield* Effect.never
|
|
250
|
+
}).pipe(Effect.scoped, FiberSet.run(fiberSet))
|
|
251
|
+
|
|
252
|
+
const shutdownContext = (cause: IntentionalShutdownCause | StoreAbort) =>
|
|
253
|
+
Effect.sync(() => setContextValue({ stage: 'shutdown', cause }))
|
|
254
|
+
|
|
255
|
+
yield* FiberSet.join(fiberSet).pipe(
|
|
256
|
+
Effect.catchTag('LiveStore.IntentionalShutdownCause', (cause) => shutdownContext(cause)),
|
|
257
|
+
Effect.catchTag('LiveStore.StoreAbort', (cause) => shutdownContext(cause)),
|
|
258
|
+
Effect.tapError((error) => Effect.sync(() => setContextValue({ stage: 'error', error }))),
|
|
259
|
+
Effect.tapDefect((defect) => Effect.sync(() => setContextValue({ stage: 'error', error: defect }))),
|
|
260
|
+
Effect.exit,
|
|
261
|
+
)
|
|
262
|
+
}).pipe(
|
|
263
|
+
Effect.scoped,
|
|
264
|
+
// NOTE we're running the code above in a semaphore to make sure a previous store is always fully
|
|
265
|
+
// shutdown before a new one is created - especially when shutdown logic is async. You can't trust `React.useEffect`.
|
|
266
|
+
// Thank you to Mattia Manzati for this idea.
|
|
267
|
+
withSemaphore(storeId),
|
|
268
|
+
Effect.tapCauseLogPretty,
|
|
269
|
+
Effect.annotateLogs({ thread: 'window' }),
|
|
270
|
+
Effect.provide(Logger.pretty),
|
|
271
|
+
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
272
|
+
Effect.runFork,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
return () => {
|
|
276
|
+
if (ctxValueRef.current.fiberSet !== undefined) {
|
|
277
|
+
interrupt(ctxValueRef.current.fiberSet, new StoreInterrupted())
|
|
278
|
+
ctxValueRef.current.fiberSet = undefined
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}, [
|
|
282
|
+
schema,
|
|
283
|
+
graphQLOptions,
|
|
284
|
+
otelOptions,
|
|
285
|
+
boot,
|
|
286
|
+
adapter,
|
|
287
|
+
batchUpdates,
|
|
288
|
+
disableDevtools,
|
|
289
|
+
signal,
|
|
290
|
+
reactivityGraph,
|
|
291
|
+
storeId,
|
|
292
|
+
])
|
|
293
|
+
|
|
294
|
+
return ctxValueRef.current.value
|
|
295
|
+
}
|