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