@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.
Files changed (97) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/StoreRegistryContext.d.ts +56 -0
  3. package/dist/StoreRegistryContext.d.ts.map +1 -0
  4. package/dist/StoreRegistryContext.js +61 -0
  5. package/dist/StoreRegistryContext.js.map +1 -0
  6. package/dist/__tests__/fixture.d.ts.map +1 -1
  7. package/dist/__tests__/fixture.js +1 -6
  8. package/dist/__tests__/fixture.js.map +1 -1
  9. package/dist/experimental/components/LiveList.d.ts +4 -2
  10. package/dist/experimental/components/LiveList.d.ts.map +1 -1
  11. package/dist/experimental/components/LiveList.js +6 -5
  12. package/dist/experimental/components/LiveList.js.map +1 -1
  13. package/dist/experimental/mod.d.ts +0 -1
  14. package/dist/experimental/mod.d.ts.map +1 -1
  15. package/dist/experimental/mod.js +0 -1
  16. package/dist/experimental/mod.js.map +1 -1
  17. package/dist/mod.d.ts +4 -3
  18. package/dist/mod.d.ts.map +1 -1
  19. package/dist/mod.js +3 -2
  20. package/dist/mod.js.map +1 -1
  21. package/dist/useClientDocument.d.ts.map +1 -1
  22. package/dist/useClientDocument.js +1 -4
  23. package/dist/useClientDocument.js.map +1 -1
  24. package/dist/useQuery.d.ts +1 -1
  25. package/dist/useQuery.d.ts.map +1 -1
  26. package/dist/useQuery.js +2 -5
  27. package/dist/useQuery.js.map +1 -1
  28. package/dist/useStore.d.ts +50 -46
  29. package/dist/useStore.d.ts.map +1 -1
  30. package/dist/useStore.js +66 -59
  31. package/dist/useStore.js.map +1 -1
  32. package/dist/useStore.test.d.ts.map +1 -0
  33. package/dist/{experimental/multi-store/useStore.test.js → useStore.test.js} +20 -22
  34. package/dist/useStore.test.js.map +1 -0
  35. package/package.json +7 -7
  36. package/src/StoreRegistryContext.tsx +69 -0
  37. package/src/__tests__/fixture.tsx +1 -13
  38. package/src/experimental/components/LiveList.tsx +13 -4
  39. package/src/experimental/mod.ts +0 -1
  40. package/src/mod.ts +4 -3
  41. package/src/useClientDocument.ts +1 -5
  42. package/src/useQuery.ts +2 -6
  43. package/src/{experimental/multi-store/useStore.test.tsx → useStore.test.tsx} +32 -30
  44. package/src/useStore.ts +94 -66
  45. package/dist/LiveStoreContext.d.ts +0 -40
  46. package/dist/LiveStoreContext.d.ts.map +0 -1
  47. package/dist/LiveStoreContext.js +0 -21
  48. package/dist/LiveStoreContext.js.map +0 -1
  49. package/dist/LiveStoreProvider.d.ts +0 -73
  50. package/dist/LiveStoreProvider.d.ts.map +0 -1
  51. package/dist/LiveStoreProvider.js +0 -233
  52. package/dist/LiveStoreProvider.js.map +0 -1
  53. package/dist/LiveStoreProvider.test.d.ts +0 -2
  54. package/dist/LiveStoreProvider.test.d.ts.map +0 -1
  55. package/dist/LiveStoreProvider.test.js +0 -117
  56. package/dist/LiveStoreProvider.test.js.map +0 -1
  57. package/dist/experimental/multi-store/StoreRegistry.d.ts +0 -105
  58. package/dist/experimental/multi-store/StoreRegistry.d.ts.map +0 -1
  59. package/dist/experimental/multi-store/StoreRegistry.js +0 -184
  60. package/dist/experimental/multi-store/StoreRegistry.js.map +0 -1
  61. package/dist/experimental/multi-store/StoreRegistry.test.d.ts +0 -2
  62. package/dist/experimental/multi-store/StoreRegistry.test.d.ts.map +0 -1
  63. package/dist/experimental/multi-store/StoreRegistry.test.js +0 -381
  64. package/dist/experimental/multi-store/StoreRegistry.test.js.map +0 -1
  65. package/dist/experimental/multi-store/StoreRegistryContext.d.ts +0 -10
  66. package/dist/experimental/multi-store/StoreRegistryContext.d.ts.map +0 -1
  67. package/dist/experimental/multi-store/StoreRegistryContext.js +0 -15
  68. package/dist/experimental/multi-store/StoreRegistryContext.js.map +0 -1
  69. package/dist/experimental/multi-store/mod.d.ts +0 -6
  70. package/dist/experimental/multi-store/mod.d.ts.map +0 -1
  71. package/dist/experimental/multi-store/mod.js +0 -6
  72. package/dist/experimental/multi-store/mod.js.map +0 -1
  73. package/dist/experimental/multi-store/storeOptions.d.ts +0 -4
  74. package/dist/experimental/multi-store/storeOptions.d.ts.map +0 -1
  75. package/dist/experimental/multi-store/storeOptions.js +0 -4
  76. package/dist/experimental/multi-store/storeOptions.js.map +0 -1
  77. package/dist/experimental/multi-store/types.d.ts +0 -25
  78. package/dist/experimental/multi-store/types.d.ts.map +0 -1
  79. package/dist/experimental/multi-store/types.js +0 -2
  80. package/dist/experimental/multi-store/types.js.map +0 -1
  81. package/dist/experimental/multi-store/useStore.d.ts +0 -11
  82. package/dist/experimental/multi-store/useStore.d.ts.map +0 -1
  83. package/dist/experimental/multi-store/useStore.js +0 -16
  84. package/dist/experimental/multi-store/useStore.js.map +0 -1
  85. package/dist/experimental/multi-store/useStore.test.d.ts.map +0 -1
  86. package/dist/experimental/multi-store/useStore.test.js.map +0 -1
  87. package/src/LiveStoreContext.ts +0 -41
  88. package/src/LiveStoreProvider.test.tsx +0 -248
  89. package/src/LiveStoreProvider.tsx +0 -430
  90. package/src/experimental/multi-store/StoreRegistry.test.ts +0 -518
  91. package/src/experimental/multi-store/StoreRegistry.ts +0 -253
  92. package/src/experimental/multi-store/StoreRegistryContext.tsx +0 -23
  93. package/src/experimental/multi-store/mod.ts +0 -5
  94. package/src/experimental/multi-store/storeOptions.ts +0 -8
  95. package/src/experimental/multi-store/types.ts +0 -37
  96. package/src/experimental/multi-store/useStore.ts +0 -26
  97. /package/dist/{experimental/multi-store/useStore.test.d.ts → useStore.test.d.ts} +0 -0
@@ -1,430 +0,0 @@
1
- import type { Adapter, BootStatus, IntentionalShutdownCause, MigrationsReport, SyncError } from '@livestore/common'
2
- import { LogConfig, provideOtel, UnknownError } from '@livestore/common'
3
- import type { LiveStoreSchema } from '@livestore/common/schema'
4
- import type {
5
- CreateStoreOptions,
6
- OtelOptions,
7
- ShutdownDeferred,
8
- Store,
9
- LiveStoreContext as StoreContext_,
10
- } from '@livestore/livestore'
11
- import { createStore, makeShutdownDeferred, StoreInterrupted } from '@livestore/livestore'
12
- import { errorToString, IS_REACT_NATIVE, LS_DEV, omitUndefineds } from '@livestore/utils'
13
- import type { OtelTracer } from '@livestore/utils/effect'
14
- import { Cause, Deferred, Effect, Exit, identity, Schema, Scope, TaskTracing } from '@livestore/utils/effect'
15
- import type * as otel from '@opentelemetry/api'
16
- import React from 'react'
17
-
18
- import { LiveStoreContext } from './LiveStoreContext.ts'
19
-
20
- export interface LiveStoreProviderProps<TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue>
21
- extends LogConfig.WithLoggerOptions {
22
- schema: LiveStoreSchema
23
- /**
24
- * The `storeId` can be used to isolate multiple stores from each other.
25
- * So it can be useful for multi-tenancy scenarios.
26
- *
27
- * The `storeId` is also used for persistence.
28
- *
29
- * Make sure to also configure `storeId` in LiveStore Devtools (e.g. in Vite plugin).
30
- *
31
- * @default 'default'
32
- */
33
- storeId?: string
34
- boot?: (
35
- store: Store<LiveStoreSchema>,
36
- ctx: { migrationsReport: MigrationsReport; parentSpan: otel.Span },
37
- ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer>
38
- otelOptions?: Partial<OtelOptions>
39
- renderLoading?: (status: BootStatus) => React.ReactNode
40
- renderError?: (error: UnknownError | unknown) => React.ReactNode
41
- renderShutdown?: (cause: IntentionalShutdownCause | StoreInterrupted | SyncError) => React.ReactNode
42
- adapter: Adapter
43
- /**
44
- * In order for LiveStore to apply multiple events in a single render,
45
- * you need to pass the `batchUpdates` function from either `react-dom` or `react-native`.
46
- *
47
- * ```ts
48
- * // With React DOM
49
- * import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
50
- *
51
- * // With React Native
52
- * import { unstable_batchedUpdates as batchUpdates } from 'react-native'
53
- * ```
54
- */
55
- batchUpdates: (run: () => void) => void
56
- disableDevtools?: boolean
57
- signal?: AbortSignal
58
- /**
59
- * Currently only used in the web adapter:
60
- * If true, registers a beforeunload event listener to confirm unsaved changes.
61
- *
62
- * @default true
63
- */
64
- confirmUnsavedChanges?: boolean
65
- /**
66
- * Advanced store parameters forwarded to `createStore`.
67
- * Currently supports:
68
- * - `leaderPushBatchSize`: max events pushed to the leader per write batch.
69
- * - `eventQueryBatchSize`: chunk size used when the stream replays confirmed events.
70
- */
71
- params?: CreateStoreOptions<LiveStoreSchema>['params']
72
- /**
73
- * Payload that will be passed to the sync backend when connecting
74
- *
75
- * @default undefined
76
- */
77
- syncPayloadSchema?: TSyncPayloadSchema
78
- syncPayload?: Schema.Schema.Type<TSyncPayloadSchema>
79
- debug?: {
80
- instanceId?: string
81
- }
82
- }
83
-
84
- const defaultRenderError = (error: UnknownError | unknown) =>
85
- IS_REACT_NATIVE ? null : Schema.is(UnknownError)(error) ? error.toString() : errorToString(error)
86
-
87
- const defaultRenderShutdown = (cause: IntentionalShutdownCause | StoreInterrupted | SyncError) => {
88
- const reason =
89
- cause._tag === 'LiveStore.StoreInterrupted'
90
- ? `interrupted due to: ${cause.reason}`
91
- : cause._tag === 'InvalidPushError' || cause._tag === 'InvalidPullError'
92
- ? `sync error: ${cause.cause}`
93
- : cause.reason === 'devtools-import'
94
- ? 'devtools import'
95
- : cause.reason === 'devtools-reset'
96
- ? 'devtools reset'
97
- : cause.reason === 'adapter-reset'
98
- ? 'adapter reset'
99
- : cause.reason === 'manual'
100
- ? 'manual shutdown'
101
- : 'unknown reason'
102
-
103
- return IS_REACT_NATIVE ? null : <>LiveStore Shutdown due to {reason}</>
104
- }
105
-
106
- const defaultRenderLoading = (status: BootStatus) =>
107
- IS_REACT_NATIVE ? null : <>LiveStore is loading ({status.stage})...</>
108
-
109
- export const LiveStoreProvider = <TSyncPayloadSchema extends Schema.Schema<any> = typeof Schema.JsonValue>({
110
- renderLoading = defaultRenderLoading,
111
- renderError = defaultRenderError,
112
- renderShutdown = defaultRenderShutdown,
113
- otelOptions,
114
- children,
115
- schema,
116
- storeId = 'default',
117
- boot,
118
- adapter,
119
- batchUpdates,
120
- disableDevtools,
121
- signal,
122
- confirmUnsavedChanges = true,
123
- params,
124
- syncPayload,
125
- syncPayloadSchema,
126
- debug,
127
- logger,
128
- logLevel,
129
- }: LiveStoreProviderProps<TSyncPayloadSchema> & React.PropsWithChildren): React.ReactNode => {
130
- const storeCtx = useCreateStore({
131
- storeId,
132
- schema,
133
- adapter,
134
- batchUpdates,
135
- confirmUnsavedChanges,
136
- ...omitUndefineds({
137
- otelOptions,
138
- boot,
139
- disableDevtools,
140
- params,
141
- signal,
142
- syncPayload,
143
- syncPayloadSchema,
144
- debug,
145
- }),
146
- logger,
147
- logLevel,
148
- })
149
-
150
- if (storeCtx.stage === 'error') {
151
- return renderError(storeCtx.error)
152
- }
153
-
154
- if (storeCtx.stage === 'shutdown') {
155
- return renderShutdown(storeCtx.cause)
156
- }
157
-
158
- if (storeCtx.stage !== 'running') {
159
- return renderLoading(storeCtx)
160
- }
161
-
162
- globalThis.__debugLiveStore ??= {}
163
- if (Object.keys(globalThis.__debugLiveStore).length === 0) {
164
- globalThis.__debugLiveStore._ = storeCtx.store
165
- }
166
- globalThis.__debugLiveStore[debug?.instanceId ?? storeId] = storeCtx.store
167
-
168
- return <LiveStoreContext.Provider value={storeCtx as TODO}>{children}</LiveStoreContext.Provider>
169
- }
170
-
171
- const useCreateStore = ({
172
- schema,
173
- storeId,
174
- otelOptions,
175
- boot,
176
- adapter,
177
- batchUpdates,
178
- disableDevtools,
179
- signal,
180
- context,
181
- params,
182
- confirmUnsavedChanges,
183
- syncPayload,
184
- syncPayloadSchema,
185
- debug,
186
- logger,
187
- logLevel,
188
- }: CreateStoreOptions<LiveStoreSchema> &
189
- LogConfig.WithLoggerOptions & {
190
- signal?: AbortSignal
191
- otelOptions?: Partial<OtelOptions>
192
- }) => {
193
- const [_, rerender] = React.useState(0)
194
- const ctxValueRef = React.useRef<{
195
- value: StoreContext_ | BootStatus
196
- componentScope: Scope.CloseableScope | undefined
197
- shutdownDeferred: ShutdownDeferred | undefined
198
- /** Used to wait for the previous shutdown deferred to fully complete before creating a new one */
199
- previousShutdownDeferred: ShutdownDeferred | undefined
200
- counter: number
201
- }>({
202
- value: { stage: 'loading' },
203
- componentScope: undefined,
204
- shutdownDeferred: undefined,
205
- previousShutdownDeferred: undefined,
206
- counter: 0,
207
- })
208
- const debugInstanceId = debug?.instanceId
209
-
210
- // console.debug(`useCreateStore (${ctxValueRef.current.counter})`, ctxValueRef.current.value.stage)
211
-
212
- const inputPropsCacheRef = React.useRef({
213
- schema,
214
- otelOptions,
215
- boot,
216
- adapter,
217
- batchUpdates,
218
- disableDevtools,
219
- signal,
220
- context,
221
- params,
222
- confirmUnsavedChanges,
223
- syncPayload,
224
- syncPayloadSchema,
225
- debugInstanceId,
226
- })
227
-
228
- const interrupt = React.useCallback(
229
- (componentScope: Scope.CloseableScope, shutdownDeferred: ShutdownDeferred, error: StoreInterrupted) =>
230
- Effect.gen(function* () {
231
- // console.log('[@livestore/livestore/react] interupting', error)
232
- yield* Scope.close(componentScope, Exit.fail(error))
233
- yield* Deferred.fail(shutdownDeferred, error)
234
- }).pipe(
235
- Effect.tapErrorCause((cause) => Effect.logDebug('[@livestore/livestore/react] interupting', cause)),
236
- Effect.runFork,
237
- ),
238
- [],
239
- )
240
-
241
- const inputPropChanges = {
242
- schema: inputPropsCacheRef.current.schema !== schema,
243
- otelOptions: inputPropsCacheRef.current.otelOptions !== otelOptions,
244
- boot: inputPropsCacheRef.current.boot !== boot,
245
- adapter: inputPropsCacheRef.current.adapter !== adapter,
246
- batchUpdates: inputPropsCacheRef.current.batchUpdates !== batchUpdates,
247
- disableDevtools: inputPropsCacheRef.current.disableDevtools !== disableDevtools,
248
- signal: inputPropsCacheRef.current.signal !== signal,
249
- context: inputPropsCacheRef.current.context !== context,
250
- params: inputPropsCacheRef.current.params !== params,
251
- confirmUnsavedChanges: inputPropsCacheRef.current.confirmUnsavedChanges !== confirmUnsavedChanges,
252
- syncPayload: inputPropsCacheRef.current.syncPayload !== syncPayload,
253
- syncPayloadSchema: inputPropsCacheRef.current.syncPayloadSchema !== syncPayloadSchema,
254
- debugInstanceId: inputPropsCacheRef.current.debugInstanceId !== debugInstanceId,
255
- }
256
-
257
- if (
258
- inputPropChanges.schema ||
259
- inputPropChanges.otelOptions ||
260
- inputPropChanges.boot ||
261
- inputPropChanges.adapter ||
262
- inputPropChanges.batchUpdates ||
263
- inputPropChanges.disableDevtools ||
264
- inputPropChanges.signal ||
265
- inputPropChanges.context ||
266
- inputPropChanges.params ||
267
- inputPropChanges.confirmUnsavedChanges ||
268
- inputPropChanges.syncPayload ||
269
- inputPropChanges.syncPayloadSchema
270
- ) {
271
- inputPropsCacheRef.current = {
272
- schema,
273
- otelOptions,
274
- boot,
275
- adapter,
276
- batchUpdates,
277
- disableDevtools,
278
- signal,
279
- context,
280
- params,
281
- confirmUnsavedChanges,
282
- syncPayload,
283
- syncPayloadSchema,
284
- debugInstanceId,
285
- }
286
- if (ctxValueRef.current.componentScope !== undefined && ctxValueRef.current.shutdownDeferred !== undefined) {
287
- const changedInputProps = Object.keys(inputPropChanges).filter(
288
- (key) => inputPropChanges[key as keyof typeof inputPropChanges],
289
- )
290
-
291
- interrupt(
292
- ctxValueRef.current.componentScope,
293
- ctxValueRef.current.shutdownDeferred,
294
- new StoreInterrupted({ reason: `re-rendering due to changed input props: ${changedInputProps.join(', ')}` }),
295
- )
296
- ctxValueRef.current.componentScope = undefined
297
- ctxValueRef.current.shutdownDeferred = undefined
298
- }
299
- ctxValueRef.current = {
300
- value: { stage: 'loading' },
301
- componentScope: undefined,
302
- shutdownDeferred: undefined,
303
- previousShutdownDeferred: ctxValueRef.current.shutdownDeferred,
304
- counter: ctxValueRef.current.counter + 1,
305
- }
306
- }
307
-
308
- React.useEffect(() => {
309
- const counter = ctxValueRef.current.counter
310
-
311
- const setContextValue = (value: StoreContext_ | BootStatus) => {
312
- if (ctxValueRef.current.counter !== counter) return
313
- ctxValueRef.current.value = value
314
- rerender((c) => c + 1)
315
- }
316
-
317
- signal?.addEventListener('abort', () => {
318
- if (
319
- ctxValueRef.current.componentScope !== undefined &&
320
- ctxValueRef.current.shutdownDeferred !== undefined &&
321
- ctxValueRef.current.counter === counter
322
- ) {
323
- interrupt(
324
- ctxValueRef.current.componentScope,
325
- ctxValueRef.current.shutdownDeferred,
326
- new StoreInterrupted({ reason: 'Aborted via provided AbortController' }),
327
- )
328
- ctxValueRef.current.componentScope = undefined
329
- ctxValueRef.current.shutdownDeferred = undefined
330
- }
331
- })
332
-
333
- const cancel = Effect.gen(function* () {
334
- // Wait for the previous store to fully shutdown before creating a new one
335
- if (ctxValueRef.current.previousShutdownDeferred) {
336
- yield* Deferred.await(ctxValueRef.current.previousShutdownDeferred)
337
- }
338
-
339
- const componentScope = yield* Scope.make().pipe(Effect.acquireRelease(Scope.close))
340
- const shutdownDeferred = yield* makeShutdownDeferred
341
-
342
- ctxValueRef.current.componentScope = componentScope
343
- ctxValueRef.current.shutdownDeferred = shutdownDeferred
344
-
345
- yield* Effect.gen(function* () {
346
- const store = yield* createStore({
347
- schema,
348
- storeId,
349
- adapter,
350
- shutdownDeferred,
351
- ...omitUndefineds({
352
- boot,
353
- batchUpdates,
354
- disableDevtools,
355
- context,
356
- params,
357
- confirmUnsavedChanges,
358
- syncPayload,
359
- syncPayloadSchema,
360
- }),
361
- onBootStatus: (status) => {
362
- if (ctxValueRef.current.value.stage === 'running' || ctxValueRef.current.value.stage === 'error') return
363
- // NOTE sometimes when status come in in rapid succession, only the last value will be rendered by React
364
- setContextValue(status)
365
- },
366
- debug: { ...omitUndefineds({ instanceId: debugInstanceId }) },
367
- }).pipe(Effect.tapErrorCause((cause) => Deferred.failCause(shutdownDeferred, cause)))
368
-
369
- setContextValue({ stage: 'running', store })
370
- }).pipe(Scope.extend(componentScope), Effect.forkIn(componentScope))
371
-
372
- const shutdownContext = (cause: IntentionalShutdownCause | StoreInterrupted | SyncError) =>
373
- Effect.sync(() => setContextValue({ stage: 'shutdown', cause }))
374
-
375
- yield* Deferred.await(shutdownDeferred).pipe(
376
- Effect.tapErrorCause((cause) => Effect.logDebug('[@livestore/livestore/react] shutdown', Cause.pretty(cause))),
377
- Effect.tap((intentionalShutdown) => shutdownContext(intentionalShutdown)),
378
- Effect.catchTag('InvalidPushError', (cause) => shutdownContext(cause)),
379
- Effect.catchTag('InvalidPullError', (cause) => shutdownContext(cause)),
380
- Effect.catchTag('LiveStore.StoreInterrupted', (cause) => shutdownContext(cause)),
381
- Effect.tapError((error) => Effect.sync(() => setContextValue({ stage: 'error', error }))),
382
- Effect.tapDefect((defect) => Effect.sync(() => setContextValue({ stage: 'error', error: defect }))),
383
- Effect.exit,
384
- )
385
- }).pipe(
386
- Effect.scoped,
387
- Effect.withSpan('@livestore/react:useCreateStore'),
388
- LS_DEV ? TaskTracing.withAsyncTaggingTracing((name: string) => (console as any).createTask(name)) : identity,
389
- provideOtel(omitUndefineds({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer })),
390
- Effect.tapCauseLogPretty,
391
- Effect.annotateLogs({ thread: 'window' }),
392
- LogConfig.withLoggerConfig({ logger, logLevel }, { threadName: 'window' }),
393
- Effect.runCallback,
394
- )
395
-
396
- return () => {
397
- cancel()
398
-
399
- if (ctxValueRef.current.componentScope !== undefined && ctxValueRef.current.shutdownDeferred !== undefined) {
400
- interrupt(
401
- ctxValueRef.current.componentScope,
402
- ctxValueRef.current.shutdownDeferred,
403
- new StoreInterrupted({ reason: 'unmounting component' }),
404
- )
405
- ctxValueRef.current.componentScope = undefined
406
- ctxValueRef.current.shutdownDeferred = undefined
407
- }
408
- }
409
- }, [
410
- schema,
411
- otelOptions,
412
- boot,
413
- adapter,
414
- batchUpdates,
415
- disableDevtools,
416
- signal,
417
- storeId,
418
- context,
419
- params,
420
- confirmUnsavedChanges,
421
- syncPayload,
422
- syncPayloadSchema,
423
- debugInstanceId,
424
- interrupt,
425
- logger,
426
- logLevel,
427
- ])
428
-
429
- return ctxValueRef.current.value
430
- }