@livestore/react 0.3.0-dev.18 → 0.3.0-dev.21

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.
@@ -2,9 +2,7 @@ import type { Adapter, BootStatus, IntentionalShutdownCause, MigrationsReport }
2
2
  import { provideOtel, UnexpectedError } from '@livestore/common'
3
3
  import type { LiveStoreSchema } from '@livestore/common/schema'
4
4
  import type {
5
- BaseGraphQLContext,
6
5
  CreateStoreOptions,
7
- GraphQLOptions,
8
6
  LiveStoreContext as StoreContext_,
9
7
  OtelOptions,
10
8
  ShutdownDeferred,
@@ -13,14 +11,25 @@ import type {
13
11
  import { createStore, StoreInterrupted } from '@livestore/livestore'
14
12
  import { errorToString, LS_DEV } from '@livestore/utils'
15
13
  import type { OtelTracer } from '@livestore/utils/effect'
16
- import { Deferred, Effect, Exit, identity, Logger, LogLevel, Schema, Scope, TaskTracing } from '@livestore/utils/effect'
14
+ import {
15
+ Cause,
16
+ Deferred,
17
+ Effect,
18
+ Exit,
19
+ identity,
20
+ Logger,
21
+ LogLevel,
22
+ Schema,
23
+ Scope,
24
+ TaskTracing,
25
+ } from '@livestore/utils/effect'
17
26
  import type * as otel from '@opentelemetry/api'
18
27
  import type { ReactElement, ReactNode } from 'react'
19
28
  import React from 'react'
20
29
 
21
30
  import { LiveStoreContext } from './LiveStoreContext.js'
22
31
 
23
- interface LiveStoreProviderProps<GraphQLContext extends BaseGraphQLContext> {
32
+ export interface LiveStoreProviderProps {
24
33
  schema: LiveStoreSchema
25
34
  /**
26
35
  * The `storeId` can be used to isolate multiple stores from each other.
@@ -34,10 +43,9 @@ interface LiveStoreProviderProps<GraphQLContext extends BaseGraphQLContext> {
34
43
  */
35
44
  storeId?: string
36
45
  boot?: (
37
- store: Store<GraphQLContext, LiveStoreSchema>,
46
+ store: Store<LiveStoreSchema>,
38
47
  ctx: { migrationsReport: MigrationsReport; parentSpan: otel.Span },
39
48
  ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer>
40
- graphQLOptions?: GraphQLOptions<GraphQLContext>
41
49
  otelOptions?: Partial<OtelOptions>
42
50
  renderLoading: (status: BootStatus) => ReactElement
43
51
  renderError?: (error: UnexpectedError | unknown) => ReactElement
@@ -58,6 +66,13 @@ interface LiveStoreProviderProps<GraphQLContext extends BaseGraphQLContext> {
58
66
  batchUpdates: (run: () => void) => void
59
67
  disableDevtools?: boolean
60
68
  signal?: AbortSignal
69
+ /**
70
+ * Currently only used in the web adapter:
71
+ * If true, registers a beforeunload event listener to confirm unsaved changes.
72
+ *
73
+ * @default true
74
+ */
75
+ confirmUnsavedChanges?: boolean
61
76
  }
62
77
 
63
78
  const defaultRenderError = (error: UnexpectedError | unknown) => (
@@ -78,11 +93,10 @@ const defaultRenderShutdown = (cause: IntentionalShutdownCause | StoreInterrupte
78
93
  return <>LiveStore Shutdown due to {reason}</>
79
94
  }
80
95
 
81
- export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
96
+ export const LiveStoreProvider = ({
82
97
  renderLoading,
83
98
  renderError = defaultRenderError,
84
99
  renderShutdown = defaultRenderShutdown,
85
- graphQLOptions,
86
100
  otelOptions,
87
101
  children,
88
102
  schema,
@@ -92,17 +106,18 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
92
106
  batchUpdates,
93
107
  disableDevtools,
94
108
  signal,
95
- }: LiveStoreProviderProps<GraphQLContext> & { children?: ReactNode }): React.ReactElement => {
109
+ confirmUnsavedChanges = true,
110
+ }: LiveStoreProviderProps & { children?: ReactNode }): React.ReactElement => {
96
111
  const storeCtx = useCreateStore({
97
112
  storeId,
98
113
  schema,
99
- graphQLOptions,
100
114
  otelOptions,
101
115
  boot,
102
116
  adapter,
103
117
  batchUpdates,
104
118
  disableDevtools,
105
119
  signal,
120
+ confirmUnsavedChanges,
106
121
  })
107
122
 
108
123
  if (storeCtx.stage === 'error') {
@@ -138,17 +153,19 @@ const withSemaphore = (storeId: SchemaKey) => {
138
153
  return semaphore.withPermits(1)
139
154
  }
140
155
 
141
- const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
156
+ const useCreateStore = ({
142
157
  schema,
143
158
  storeId,
144
- graphQLOptions,
145
159
  otelOptions,
146
160
  boot,
147
161
  adapter,
148
162
  batchUpdates,
149
163
  disableDevtools,
150
164
  signal,
151
- }: CreateStoreOptions<GraphQLContext, LiveStoreSchema> & {
165
+ context,
166
+ params,
167
+ confirmUnsavedChanges,
168
+ }: CreateStoreOptions<LiveStoreSchema> & {
152
169
  signal?: AbortSignal
153
170
  otelOptions?: Partial<OtelOptions>
154
171
  }) => {
@@ -169,13 +186,15 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
169
186
 
170
187
  const inputPropsCacheRef = React.useRef({
171
188
  schema,
172
- graphQLOptions,
173
189
  otelOptions,
174
190
  boot,
175
191
  adapter,
176
192
  batchUpdates,
177
193
  disableDevtools,
178
194
  signal,
195
+ context,
196
+ params,
197
+ confirmUnsavedChanges,
179
198
  })
180
199
 
181
200
  const interrupt = (
@@ -194,23 +213,27 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
194
213
 
195
214
  if (
196
215
  inputPropsCacheRef.current.schema !== schema ||
197
- inputPropsCacheRef.current.graphQLOptions !== graphQLOptions ||
198
216
  inputPropsCacheRef.current.otelOptions !== otelOptions ||
199
217
  inputPropsCacheRef.current.boot !== boot ||
200
218
  inputPropsCacheRef.current.adapter !== adapter ||
201
219
  inputPropsCacheRef.current.batchUpdates !== batchUpdates ||
202
220
  inputPropsCacheRef.current.disableDevtools !== disableDevtools ||
203
- inputPropsCacheRef.current.signal !== signal
221
+ inputPropsCacheRef.current.signal !== signal ||
222
+ inputPropsCacheRef.current.context !== context ||
223
+ inputPropsCacheRef.current.params !== params ||
224
+ inputPropsCacheRef.current.confirmUnsavedChanges !== confirmUnsavedChanges
204
225
  ) {
205
226
  inputPropsCacheRef.current = {
206
227
  schema,
207
- graphQLOptions,
208
228
  otelOptions,
209
229
  boot,
210
230
  adapter,
211
231
  batchUpdates,
212
232
  disableDevtools,
213
233
  signal,
234
+ context,
235
+ params,
236
+ confirmUnsavedChanges,
214
237
  }
215
238
  if (ctxValueRef.current.componentScope !== undefined && ctxValueRef.current.shutdownDeferred !== undefined) {
216
239
  interrupt(
@@ -268,12 +291,14 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
268
291
  const store = yield* createStore({
269
292
  schema,
270
293
  storeId,
271
- graphQLOptions,
272
294
  boot,
273
295
  adapter,
274
296
  batchUpdates,
275
297
  disableDevtools,
276
298
  shutdownDeferred,
299
+ context,
300
+ params,
301
+ confirmUnsavedChanges,
277
302
  onBootStatus: (status) => {
278
303
  if (ctxValueRef.current.value.stage === 'running' || ctxValueRef.current.value.stage === 'error') return
279
304
  // NOTE sometimes when status come in in rapid succession, only the last value will be rendered by React
@@ -288,7 +313,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
288
313
  Effect.sync(() => setContextValue({ stage: 'shutdown', cause }))
289
314
 
290
315
  yield* Deferred.await(shutdownDeferred).pipe(
291
- Effect.tapErrorCause((cause) => Effect.logDebug('[@livestore/livestore/react] shutdown', cause)),
316
+ Effect.tapErrorCause((cause) => Effect.logDebug('[@livestore/livestore/react] shutdown', Cause.pretty(cause))),
292
317
  Effect.catchTag('LiveStore.IntentionalShutdownCause', (cause) => shutdownContext(cause)),
293
318
  Effect.catchTag('LiveStore.StoreInterrupted', (cause) => shutdownContext(cause)),
294
319
  Effect.tapError((error) => Effect.sync(() => setContextValue({ stage: 'error', error }))),
@@ -322,7 +347,19 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
322
347
  ctxValueRef.current.shutdownDeferred = undefined
323
348
  }
324
349
  }
325
- }, [schema, graphQLOptions, otelOptions, boot, adapter, batchUpdates, disableDevtools, signal, storeId])
350
+ }, [
351
+ schema,
352
+ otelOptions,
353
+ boot,
354
+ adapter,
355
+ batchUpdates,
356
+ disableDevtools,
357
+ signal,
358
+ storeId,
359
+ context,
360
+ params,
361
+ confirmUnsavedChanges,
362
+ ])
326
363
 
327
364
  return ctxValueRef.current.value
328
365
  }
package/src/useAtom.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { DerivedMutationHelperFns, QueryInfo } from '@livestore/common'
2
2
  import type { DbSchema, SqliteDsl } from '@livestore/common/schema'
3
- import type { GetResult, LiveQueryDef, Store } from '@livestore/livestore'
3
+ import type { Store } from '@livestore/livestore'
4
+ import type { LiveQueries } from '@livestore/livestore/internal'
4
5
  import { shouldNeverHappen } from '@livestore/utils'
5
6
  import React from 'react'
6
7
 
@@ -10,13 +11,16 @@ import type { Dispatch, SetStateAction } from './useRow.js'
10
11
 
11
12
  export const useAtom = <
12
13
  // TODO also support colJsonValue
13
- TQuery extends LiveQueryDef<any, QueryInfo.Row | QueryInfo.Col>,
14
+ TQuery extends LiveQueries.LiveQueryDef<any, QueryInfo.Row | QueryInfo.Col>,
14
15
  >(
15
16
  queryDef: TQuery,
16
17
  options?: {
17
18
  store?: Store
18
19
  },
19
- ): [value: GetResult<TQuery>, setValue: Dispatch<SetStateAction<Partial<GetResult<TQuery>>>>] => {
20
+ ): [
21
+ value: LiveQueries.GetResult<TQuery>,
22
+ setValue: Dispatch<SetStateAction<Partial<LiveQueries.GetResult<TQuery>>>>,
23
+ ] => {
20
24
  const queryRef = useQueryRef(queryDef, { store: options?.store })
21
25
  const query$ = queryRef.queryRcRef.value
22
26
 
@@ -28,7 +32,7 @@ export const useAtom = <
28
32
  const { store } = useStore()
29
33
 
30
34
  // TODO make API equivalent to useRow
31
- const setValue = React.useMemo<Dispatch<SetStateAction<Partial<GetResult<TQuery>>>>>(
35
+ const setValue = React.useMemo<Dispatch<SetStateAction<Partial<LiveQueries.GetResult<TQuery>>>>>(
32
36
  () => (newValueOrFn: any) => {
33
37
  const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(queryRef.valueRef.current) : newValueOrFn
34
38
  const table = query$.queryInfo.table as DbSchema.TableDef &
package/src/useQuery.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { GetResult, LiveQuery, LiveQueryDef, LiveQueryDefAny, RcRef, Store } from '@livestore/livestore'
1
+ import type { LiveQuery, LiveQueryDef, Store } from '@livestore/livestore'
2
2
  import { extractStackInfoFromStackTrace, stackInfoToString } from '@livestore/livestore'
3
+ import type { LiveQueries } from '@livestore/livestore/internal'
3
4
  import { deepEqual, indent } from '@livestore/utils'
4
5
  import * as otel from '@opentelemetry/api'
5
6
  import React from 'react'
@@ -20,17 +21,17 @@ import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInp
20
21
  * }
21
22
  * ```
22
23
  */
23
- export const useQuery = <TQuery extends LiveQueryDefAny>(
24
+ export const useQuery = <TQuery extends LiveQueryDef.Any>(
24
25
  queryDef: TQuery,
25
26
  options?: { store?: Store },
26
- ): GetResult<TQuery> => useQueryRef(queryDef, options).valueRef.current
27
+ ): LiveQueries.GetResult<TQuery> => useQueryRef(queryDef, options).valueRef.current
27
28
 
28
- type GetQueryInfo<TQuery extends LiveQueryDefAny> =
29
+ type GetQueryInfo<TQuery extends LiveQueryDef.Any> =
29
30
  TQuery extends LiveQueryDef<infer _1, infer TQueryInfo> ? TQueryInfo : never
30
31
 
31
32
  /**
32
33
  */
33
- export const useQueryRef = <TQuery extends LiveQueryDefAny>(
34
+ export const useQueryRef = <TQuery extends LiveQueryDef.Any>(
34
35
  queryDef: TQuery,
35
36
  options?: {
36
37
  store?: Store
@@ -40,8 +41,8 @@ export const useQueryRef = <TQuery extends LiveQueryDefAny>(
40
41
  otelSpanName?: string
41
42
  },
42
43
  ): {
43
- valueRef: React.RefObject<GetResult<TQuery>>
44
- queryRcRef: RcRef<LiveQuery<GetResult<TQuery>, GetQueryInfo<TQuery>>>
44
+ valueRef: React.RefObject<LiveQueries.GetResult<TQuery>>
45
+ queryRcRef: LiveQueries.RcRef<LiveQuery<LiveQueries.GetResult<TQuery>, GetQueryInfo<TQuery>>>
45
46
  } => {
46
47
  const { store } = useStore({ store: options?.store })
47
48
 
@@ -76,7 +77,7 @@ export const useQueryRef = <TQuery extends LiveQueryDefAny>(
76
77
  // which takes care of disposing the queryRcRef
77
78
  () => {},
78
79
  )
79
- const query$ = queryRcRef.value as LiveQuery<GetResult<TQuery>, GetQueryInfo<TQuery>>
80
+ const query$ = queryRcRef.value as LiveQuery<LiveQueries.GetResult<TQuery>, GetQueryInfo<TQuery>>
80
81
 
81
82
  React.useDebugValue(`LiveStore:useQuery:${query$.id}:${query$.label}`)
82
83
  // console.debug(`LiveStore:useQuery:${query$.id}:${query$.label}`)
@@ -111,7 +112,7 @@ Stack trace:
111
112
  }, [otelContext, query$, stackInfo])
112
113
 
113
114
  // We know the query has a result by the time we use it; so we can synchronously populate a default state
114
- const [valueRef, setValue] = useStateRefWithReactiveInput<GetResult<TQuery>>(initialResult)
115
+ const [valueRef, setValue] = useStateRefWithReactiveInput<LiveQueries.GetResult<TQuery>>(initialResult)
115
116
 
116
117
  // TODO we probably need to change the order of `useEffect` calls, so we destroy the query at the end
117
118
  // before calling the LS `onEffect` on it