@livestore/livestore 0.0.56-dev.2 → 0.0.56-dev.3

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 (58) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
  3. package/dist/SynchronousDatabaseWrapper.js +9 -18
  4. package/dist/SynchronousDatabaseWrapper.js.map +1 -1
  5. package/dist/__tests__/react/fixture.d.ts +3 -4
  6. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  7. package/dist/__tests__/react/fixture.js +7 -5
  8. package/dist/__tests__/react/fixture.js.map +1 -1
  9. package/dist/effect/LiveStore.d.ts +4 -24
  10. package/dist/effect/LiveStore.d.ts.map +1 -1
  11. package/dist/effect/LiveStore.js +2 -1
  12. package/dist/effect/LiveStore.js.map +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/react/LiveStoreContext.d.ts +1 -2
  17. package/dist/react/LiveStoreContext.d.ts.map +1 -1
  18. package/dist/react/LiveStoreProvider.d.ts +5 -13
  19. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  20. package/dist/react/LiveStoreProvider.js +25 -12
  21. package/dist/react/LiveStoreProvider.js.map +1 -1
  22. package/dist/react/LiveStoreProvider.test.js +1 -1
  23. package/dist/react/LiveStoreProvider.test.js.map +1 -1
  24. package/dist/react/useQuery.test.js +7 -7
  25. package/dist/react/useQuery.test.js.map +1 -1
  26. package/dist/react/useRow.test.js +52 -46
  27. package/dist/react/useRow.test.js.map +1 -1
  28. package/dist/react/useTemporaryQuery.test.js +4 -4
  29. package/dist/react/useTemporaryQuery.test.js.map +1 -1
  30. package/dist/reactiveQueries/sql.test.js +34 -33
  31. package/dist/reactiveQueries/sql.test.js.map +1 -1
  32. package/dist/store-context.d.ts +26 -0
  33. package/dist/store-context.d.ts.map +1 -0
  34. package/dist/store-context.js +6 -0
  35. package/dist/store-context.js.map +1 -0
  36. package/dist/store-devtools.d.ts +1 -1
  37. package/dist/store-devtools.d.ts.map +1 -1
  38. package/dist/store-devtools.js +5 -0
  39. package/dist/store-devtools.js.map +1 -1
  40. package/dist/store.d.ts +8 -23
  41. package/dist/store.d.ts.map +1 -1
  42. package/dist/store.js +30 -35
  43. package/dist/store.js.map +1 -1
  44. package/package.json +5 -5
  45. package/src/SynchronousDatabaseWrapper.ts +10 -18
  46. package/src/__tests__/react/fixture.tsx +53 -50
  47. package/src/effect/LiveStore.ts +5 -34
  48. package/src/index.ts +2 -10
  49. package/src/react/LiveStoreProvider.test.tsx +1 -1
  50. package/src/react/LiveStoreProvider.tsx +37 -19
  51. package/src/react/useQuery.test.tsx +53 -51
  52. package/src/react/useRow.test.tsx +237 -224
  53. package/src/react/useTemporaryQuery.test.tsx +46 -45
  54. package/src/reactiveQueries/sql.test.ts +36 -33
  55. package/src/store-context.ts +23 -0
  56. package/src/store-devtools.ts +8 -0
  57. package/src/store.ts +55 -59
  58. package/vitest.config.js +1 -1
@@ -5,40 +5,12 @@ import { Context, Deferred, Duration, Effect, FiberSet, Layer, OtelTracer, pipe
5
5
  import * as otel from '@opentelemetry/api'
6
6
  import type { GraphQLSchema } from 'graphql'
7
7
 
8
- import type { LiveQuery } from '../reactiveQueries/base-class.js'
9
- import type { BaseGraphQLContext, GraphQLOptions, OtelOptions, Store } from '../store.js'
8
+ import type { BaseGraphQLContext } from '../store.js'
10
9
  import { createStore } from '../store.js'
10
+ import type { LiveStoreContextRunning as LiveStoreContextRunning_ } from '../store-context.js'
11
11
  import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
12
12
 
13
- // TODO get rid of `LiveStoreContext` wrapper and only expose the `Store` directly
14
- export type LiveStoreContext =
15
- | LiveStoreContextRunning
16
- | {
17
- stage: 'error'
18
- error: UnexpectedError | unknown
19
- }
20
- | {
21
- stage: 'shutdown'
22
- }
23
-
24
- export type LiveStoreContextRunning = {
25
- stage: 'running'
26
- store: Store
27
- }
28
-
29
- export type QueryDefinition = <TResult>(store: Store) => LiveQuery<TResult>
30
-
31
- export type LiveStoreCreateStoreOptions<GraphQLContext extends BaseGraphQLContext> = {
32
- schema: LiveStoreSchema
33
- graphQLOptions?: GraphQLOptions<GraphQLContext>
34
- otelOptions?: OtelOptions
35
- boot?: (db: BootDb, parentSpan: otel.Span) => void | Promise<void> | Effect.Effect<void, unknown, otel.Tracer>
36
- adapter: StoreAdapterFactory
37
- batchUpdates?: (run: () => void) => void
38
- disableDevtools?: boolean
39
- signal?: AbortSignal
40
- }
41
-
13
+ export type LiveStoreContextRunning = LiveStoreContextRunning_
42
14
  export const LiveStoreContextRunning = Context.GenericTag<LiveStoreContextRunning>(
43
15
  '@livestore/livestore/effect/LiveStoreContextRunning',
44
16
  )
@@ -48,8 +20,6 @@ export const DeferredStoreContext = Context.GenericTag<DeferredStoreContext>(
48
20
  '@livestore/livestore/effect/DeferredStoreContext',
49
21
  )
50
22
 
51
- // export const DeferredStoreContext = Effect.cached(Effect.flatMap(StoreContext, (_) => Effect.succeed(_)))
52
-
53
23
  export type LiveStoreContextProps<GraphQLContext extends BaseGraphQLContext> = {
54
24
  schema: LiveStoreSchema
55
25
  graphQLOptions?: {
@@ -114,7 +84,8 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
114
84
  onBootStatus,
115
85
  })
116
86
 
117
- window.__debugLiveStore = store
87
+ window.__debugLiveStore ??= {}
88
+ window.__debugLiveStore[schema.key] = store
118
89
 
119
90
  return { stage: 'running', store } satisfies LiveStoreContextRunning
120
91
  }),
package/src/index.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  export { Store, createStorePromise, createStore } from './store.js'
2
2
  export type { BaseGraphQLContext, QueryDebugInfo, RefreshReason } from './store.js'
3
3
 
4
- export type {
5
- QueryDefinition,
6
- LiveStoreCreateStoreOptions,
7
- LiveStoreContextRunning as LiveStoreContext,
8
- } from './effect/LiveStore.js'
4
+ export type { LiveStoreContextRunning as LiveStoreContext } from './effect/LiveStore.js'
9
5
 
10
6
  export { SynchronousDatabaseWrapper, emptyDebugInfo } from './SynchronousDatabaseWrapper.js'
11
7
 
@@ -52,8 +48,4 @@ export { SqliteAst, SqliteDsl } from 'effect-db-schema'
52
48
 
53
49
  export { deepEqual } from '@livestore/utils'
54
50
 
55
- export type {
56
- StoreAdapter as DatabaseImpl,
57
- StoreAdapterFactory as DatabaseFactory,
58
- PreparedStatement,
59
- } from '@livestore/common'
51
+ export type { StoreAdapter, StoreAdapterFactory, PreparedStatement } from '@livestore/common'
@@ -65,7 +65,7 @@ describe('LiveStoreProvider', () => {
65
65
 
66
66
  abortController.abort()
67
67
 
68
- await waitFor(() => screen.getByText('LiveStore Shutdown'))
68
+ await waitFor(() => screen.getByText('LiveStore Shutdown due to abort signal'))
69
69
  })
70
70
 
71
71
  it('error during boot', async () => {
@@ -1,4 +1,5 @@
1
- import { type BootDb, type BootStatus, type StoreAdapterFactory, UnexpectedError } from '@livestore/common'
1
+ import type { BootDb, BootStatus, IntentionalShutdownCause, StoreAdapterFactory } from '@livestore/common'
2
+ import { UnexpectedError } from '@livestore/common'
2
3
  import type { LiveStoreSchema } from '@livestore/common/schema'
3
4
  import { errorToString } from '@livestore/utils'
4
5
  import { Effect, FiberSet, Logger, LogLevel, Schema } from '@livestore/utils/effect'
@@ -6,15 +7,12 @@ import type * as otel from '@opentelemetry/api'
6
7
  import type { ReactElement, ReactNode } from 'react'
7
8
  import React from 'react'
8
9
 
9
- // TODO refactor so the `react` module doesn't depend on `effect` module
10
- import type { LiveStoreContext as StoreContext_, LiveStoreCreateStoreOptions } from '../effect/LiveStore.js'
11
- import type { BaseGraphQLContext, ForceStoreShutdown, GraphQLOptions, OtelOptions, StoreShutdown } from '../store.js'
10
+ import type { BaseGraphQLContext, CreateStoreOptions, GraphQLOptions, OtelOptions } from '../store.js'
12
11
  import { createStore } from '../store.js'
12
+ import type { LiveStoreContext as StoreContext_ } from '../store-context.js'
13
+ import { StoreAbort, StoreInterrupted } from '../store-context.js'
13
14
  import { LiveStoreContext } from './LiveStoreContext.js'
14
15
 
15
- export class StoreAbort extends Schema.TaggedError<StoreAbort>()('LiveStore.StoreAbort', {}) {}
16
- export class StoreInterrupted extends Schema.TaggedError<StoreInterrupted>()('LiveStore.StoreInterrupted', {}) {}
17
-
18
16
  interface LiveStoreProviderProps<GraphQLContext> {
19
17
  schema: LiveStoreSchema
20
18
  boot?: (db: BootDb, parentSpan: otel.Span) => void | Promise<void> | Effect.Effect<void, unknown, otel.Tracer>
@@ -22,7 +20,7 @@ interface LiveStoreProviderProps<GraphQLContext> {
22
20
  otelOptions?: OtelOptions
23
21
  renderLoading: (status: BootStatus) => ReactElement
24
22
  renderError?: (error: UnexpectedError | unknown) => ReactElement
25
- renderShutdown?: () => ReactElement
23
+ renderShutdown?: (cause: IntentionalShutdownCause | StoreAbort) => ReactElement
26
24
  adapter: StoreAdapterFactory
27
25
  batchUpdates?: (run: () => void) => void
28
26
  disableDevtools?: boolean
@@ -32,7 +30,18 @@ interface LiveStoreProviderProps<GraphQLContext> {
32
30
  const defaultRenderError = (error: UnexpectedError | unknown) => (
33
31
  <>{Schema.is(UnexpectedError)(error) ? error.toString() : errorToString(error)}</>
34
32
  )
35
- const defaultRenderShutdown = () => <>LiveStore Shutdown</>
33
+ const defaultRenderShutdown = (cause: IntentionalShutdownCause | StoreAbort) => {
34
+ const reason =
35
+ cause._tag === 'LiveStore.StoreAbort'
36
+ ? 'abort signal'
37
+ : cause.reason === 'devtools-import'
38
+ ? 'devtools import'
39
+ : cause.reason === 'devtools-reset'
40
+ ? 'devtools reset'
41
+ : 'unknown reason'
42
+
43
+ return <>LiveStore Shutdown due to {reason}</>
44
+ }
36
45
 
37
46
  export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
38
47
  renderLoading,
@@ -64,14 +73,15 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
64
73
  }
65
74
 
66
75
  if (storeCtx.stage === 'shutdown') {
67
- return renderShutdown()
76
+ return renderShutdown(storeCtx.cause)
68
77
  }
69
78
 
70
79
  if (storeCtx.stage !== 'running') {
71
80
  return renderLoading(storeCtx)
72
81
  }
73
82
 
74
- window.__debugLiveStore = storeCtx.store
83
+ window.__debugLiveStore ??= {}
84
+ window.__debugLiveStore[schema.key] = storeCtx.store
75
85
 
76
86
  return <LiveStoreContext.Provider value={storeCtx}>{children}</LiveStoreContext.Provider>
77
87
  }
@@ -96,8 +106,9 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
96
106
  adapter,
97
107
  batchUpdates,
98
108
  disableDevtools,
109
+ reactivityGraph,
99
110
  signal,
100
- }: LiveStoreCreateStoreOptions<GraphQLContext>) => {
111
+ }: CreateStoreOptions<GraphQLContext, LiveStoreSchema> & { signal?: AbortSignal }) => {
101
112
  const [_, rerender] = React.useState(0)
102
113
  const ctxValueRef = React.useRef<{
103
114
  value: StoreContext_ | BootStatus
@@ -119,6 +130,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
119
130
  adapter,
120
131
  batchUpdates,
121
132
  disableDevtools,
133
+ reactivityGraph,
122
134
  signal,
123
135
  })
124
136
 
@@ -126,7 +138,10 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
126
138
  Effect.gen(function* () {
127
139
  yield* FiberSet.clear(fiberSet)
128
140
  yield* FiberSet.run(fiberSet, Effect.fail(error))
129
- }).pipe(Effect.ignoreLogged, Effect.runFork)
141
+ }).pipe(
142
+ Effect.tapErrorCause((cause) => Effect.logDebug(`[@livestore/livestore/react] interupting`, cause)),
143
+ Effect.runFork,
144
+ )
130
145
 
131
146
  if (
132
147
  inputPropsCacheRef.current.schema !== schema ||
@@ -136,6 +151,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
136
151
  inputPropsCacheRef.current.adapter !== adapter ||
137
152
  inputPropsCacheRef.current.batchUpdates !== batchUpdates ||
138
153
  inputPropsCacheRef.current.disableDevtools !== disableDevtools ||
154
+ inputPropsCacheRef.current.reactivityGraph !== reactivityGraph ||
139
155
  inputPropsCacheRef.current.signal !== signal
140
156
  ) {
141
157
  inputPropsCacheRef.current = {
@@ -146,6 +162,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
146
162
  adapter,
147
163
  batchUpdates,
148
164
  disableDevtools,
165
+ reactivityGraph,
149
166
  signal,
150
167
  }
151
168
  if (ctxValueRef.current.fiberSet !== undefined) {
@@ -174,7 +191,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
174
191
  Effect.gen(function* () {
175
192
  const fiberSet = yield* FiberSet.make<
176
193
  unknown,
177
- UnexpectedError | ForceStoreShutdown | StoreAbort | StoreInterrupted | StoreShutdown
194
+ UnexpectedError | IntentionalShutdownCause | StoreAbort | StoreInterrupted
178
195
  >()
179
196
 
180
197
  ctxValueRef.current.fiberSet = fiberSet
@@ -187,6 +204,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
187
204
  otelOptions,
188
205
  boot,
189
206
  adapter,
207
+ reactivityGraph,
190
208
  batchUpdates,
191
209
  disableDevtools,
192
210
  onBootStatus: (status) => {
@@ -200,12 +218,12 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
200
218
  yield* Effect.never
201
219
  }).pipe(Effect.scoped, FiberSet.run(fiberSet))
202
220
 
203
- const shutdownContext = Effect.sync(() => setContextValue({ stage: 'shutdown' }))
221
+ const shutdownContext = (cause: IntentionalShutdownCause | StoreAbort) =>
222
+ Effect.sync(() => setContextValue({ stage: 'shutdown', cause }))
204
223
 
205
224
  yield* FiberSet.join(fiberSet).pipe(
206
- Effect.catchTag('LiveStore.StoreShutdown', () => shutdownContext),
207
- Effect.catchTag('LiveStore.ForceStoreShutdown', () => shutdownContext),
208
- Effect.catchTag('LiveStore.StoreAbort', () => shutdownContext),
225
+ Effect.catchTag('LiveStore.IntentionalShutdownCause', (cause) => shutdownContext(cause)),
226
+ Effect.catchTag('LiveStore.StoreAbort', (cause) => shutdownContext(cause)),
209
227
  Effect.tapError((error) => Effect.sync(() => setContextValue({ stage: 'error', error }))),
210
228
  Effect.tapDefect((defect) => Effect.sync(() => setContextValue({ stage: 'error', error: defect }))),
211
229
  Effect.exit,
@@ -229,7 +247,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
229
247
  ctxValueRef.current.fiberSet = undefined
230
248
  }
231
249
  }
232
- }, [schema, graphQLOptions, otelOptions, boot, adapter, batchUpdates, disableDevtools, signal])
250
+ }, [schema, graphQLOptions, otelOptions, boot, adapter, batchUpdates, disableDevtools, signal, reactivityGraph])
233
251
 
234
252
  return ctxValueRef.current.value
235
253
  }
@@ -1,4 +1,4 @@
1
- import { Schema } from '@livestore/utils/effect'
1
+ import { Effect, Schema } from '@livestore/utils/effect'
2
2
  import { renderHook } from '@testing-library/react'
3
3
  import React from 'react'
4
4
  import { describe, expect, it } from 'vitest'
@@ -8,73 +8,75 @@ import { querySQL } from '../reactiveQueries/sql.js'
8
8
  import * as LiveStoreReact from './index.js'
9
9
 
10
10
  describe('useQuery', () => {
11
- it('simple', async () => {
12
- const { wrapper, store, makeRenderCount } = await makeTodoMvc()
11
+ it('simple', () =>
12
+ Effect.gen(function* () {
13
+ const { wrapper, store, makeRenderCount } = yield* makeTodoMvc()
13
14
 
14
- const renderCount = makeRenderCount()
15
+ const renderCount = makeRenderCount()
15
16
 
16
- const allTodos$ = querySQL(`select * from todos`, { schema: Schema.Array(tables.todos.schema) })
17
+ const allTodos$ = querySQL(`select * from todos`, { schema: Schema.Array(tables.todos.schema) })
17
18
 
18
- const { result } = renderHook(
19
- () => {
20
- renderCount.inc()
19
+ const { result } = renderHook(
20
+ () => {
21
+ renderCount.inc()
21
22
 
22
- return LiveStoreReact.useQuery(allTodos$)
23
- },
24
- { wrapper },
25
- )
23
+ return LiveStoreReact.useQuery(allTodos$)
24
+ },
25
+ { wrapper },
26
+ )
26
27
 
27
- expect(result.current.length).toBe(0)
28
- expect(renderCount.val).toBe(1)
28
+ expect(result.current.length).toBe(0)
29
+ expect(renderCount.val).toBe(1)
29
30
 
30
- React.act(() => store.mutate(todos.insert({ id: 't1', text: 'buy milk', completed: false })))
31
+ React.act(() => store.mutate(todos.insert({ id: 't1', text: 'buy milk', completed: false })))
31
32
 
32
- expect(result.current.length).toBe(1)
33
- expect(result.current[0]!.text).toBe('buy milk')
34
- expect(renderCount.val).toBe(2)
35
- })
33
+ expect(result.current.length).toBe(1)
34
+ expect(result.current[0]!.text).toBe('buy milk')
35
+ expect(renderCount.val).toBe(2)
36
+ }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
36
37
 
37
- it('same `useQuery` hook invoked with different queries', async () => {
38
- const { wrapper, store, makeRenderCount } = await makeTodoMvc()
38
+ it('same `useQuery` hook invoked with different queries', () =>
39
+ Effect.gen(function* () {
40
+ const { wrapper, store, makeRenderCount } = yield* makeTodoMvc()
39
41
 
40
- const renderCount = makeRenderCount()
42
+ const renderCount = makeRenderCount()
41
43
 
42
- const todo1$ = querySQL(`select * from todos where id = 't1'`, {
43
- label: 'libraryTracksView1',
44
- schema: Schema.Array(tables.todos.schema),
45
- })
46
- const todo2$ = querySQL(`select * from todos where id = 't2'`, {
47
- label: 'libraryTracksView2',
48
- schema: Schema.Array(tables.todos.schema),
49
- })
44
+ const todo1$ = querySQL(`select * from todos where id = 't1'`, {
45
+ label: 'libraryTracksView1',
46
+ schema: Schema.Array(tables.todos.schema),
47
+ })
48
+ const todo2$ = querySQL(`select * from todos where id = 't2'`, {
49
+ label: 'libraryTracksView2',
50
+ schema: Schema.Array(tables.todos.schema),
51
+ })
50
52
 
51
- store.mutate(
52
- todos.insert({ id: 't1', text: 'buy milk', completed: false }),
53
- todos.insert({ id: 't2', text: 'buy eggs', completed: false }),
54
- )
53
+ store.mutate(
54
+ todos.insert({ id: 't1', text: 'buy milk', completed: false }),
55
+ todos.insert({ id: 't2', text: 'buy eggs', completed: false }),
56
+ )
55
57
 
56
- const { result, rerender } = renderHook(
57
- (todoId: string) => {
58
- renderCount.inc()
58
+ const { result, rerender } = renderHook(
59
+ (todoId: string) => {
60
+ renderCount.inc()
59
61
 
60
- const query$ = React.useMemo(() => (todoId === 't1' ? todo1$ : todo2$), [todoId])
62
+ const query$ = React.useMemo(() => (todoId === 't1' ? todo1$ : todo2$), [todoId])
61
63
 
62
- return LiveStoreReact.useQuery(query$)[0]!.text
63
- },
64
- { wrapper, initialProps: 't1' },
65
- )
64
+ return LiveStoreReact.useQuery(query$)[0]!.text
65
+ },
66
+ { wrapper, initialProps: 't1' },
67
+ )
66
68
 
67
- expect(result.current).toBe('buy milk')
68
- expect(renderCount.val).toBe(1)
69
+ expect(result.current).toBe('buy milk')
70
+ expect(renderCount.val).toBe(1)
69
71
 
70
- React.act(() => store.mutate(todos.update({ where: { id: 't1' }, values: { text: 'buy soy milk' } })))
72
+ React.act(() => store.mutate(todos.update({ where: { id: 't1' }, values: { text: 'buy soy milk' } })))
71
73
 
72
- expect(result.current).toBe('buy soy milk')
73
- expect(renderCount.val).toBe(2)
74
+ expect(result.current).toBe('buy soy milk')
75
+ expect(renderCount.val).toBe(2)
74
76
 
75
- rerender('t2')
77
+ rerender('t2')
76
78
 
77
- expect(result.current).toBe('buy eggs')
78
- expect(renderCount.val).toBe(3)
79
- })
79
+ expect(result.current).toBe('buy eggs')
80
+ expect(renderCount.val).toBe(3)
81
+ }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
80
82
  })