@livestore/livestore 0.0.58-dev.8 → 0.1.0-dev.0

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 (191) hide show
  1. package/README.md +1 -117
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/effect/LiveStore.d.ts +6 -6
  4. package/dist/effect/LiveStore.d.ts.map +1 -1
  5. package/dist/effect/LiveStore.js +1 -1
  6. package/dist/effect/LiveStore.js.map +1 -1
  7. package/dist/global-state.d.ts.map +1 -1
  8. package/dist/global-state.js +2 -1
  9. package/dist/global-state.js.map +1 -1
  10. package/dist/index.d.ts +11 -8
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +7 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/reactiveQueries/base-class.d.ts +5 -4
  15. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  16. package/dist/reactiveQueries/base-class.js.map +1 -1
  17. package/dist/reactiveQueries/computed.d.ts +35 -0
  18. package/dist/reactiveQueries/computed.d.ts.map +1 -0
  19. package/dist/reactiveQueries/computed.js +57 -0
  20. package/dist/reactiveQueries/computed.js.map +1 -0
  21. package/dist/reactiveQueries/graphql.d.ts +2 -1
  22. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  23. package/dist/reactiveQueries/graphql.js.map +1 -1
  24. package/dist/reactiveQueries/sql.d.ts +2 -2
  25. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  26. package/dist/reactiveQueries/sql.js +3 -3
  27. package/dist/reactiveQueries/sql.js.map +1 -1
  28. package/dist/reactiveQueries/sql.test.js +2 -2
  29. package/dist/reactiveQueries/sql.test.js.map +1 -1
  30. package/dist/row-query.d.ts +3 -2
  31. package/dist/row-query.d.ts.map +1 -1
  32. package/dist/row-query.js +14 -10
  33. package/dist/row-query.js.map +1 -1
  34. package/dist/store/create-store.d.ts +28 -0
  35. package/dist/store/create-store.d.ts.map +1 -0
  36. package/dist/store/create-store.js +85 -0
  37. package/dist/store/create-store.js.map +1 -0
  38. package/dist/store/devtools.d.ts +19 -0
  39. package/dist/store/devtools.d.ts.map +1 -0
  40. package/dist/store/devtools.js +141 -0
  41. package/dist/store/devtools.js.map +1 -0
  42. package/dist/store/store-context.d.ts +26 -0
  43. package/dist/store/store-context.d.ts.map +1 -0
  44. package/dist/store/store-context.js +6 -0
  45. package/dist/store/store-context.js.map +1 -0
  46. package/dist/store/store-types.d.ts +98 -0
  47. package/dist/store/store-types.d.ts.map +1 -0
  48. package/dist/store/store-types.js +6 -0
  49. package/dist/store/store-types.js.map +1 -0
  50. package/dist/store/store.d.ts +88 -0
  51. package/dist/store/store.d.ts.map +1 -0
  52. package/dist/store/store.js +367 -0
  53. package/dist/store/store.js.map +1 -0
  54. package/dist/store-devtools.d.ts +2 -2
  55. package/dist/store-devtools.d.ts.map +1 -1
  56. package/dist/store-devtools.js +2 -2
  57. package/dist/store-devtools.js.map +1 -1
  58. package/dist/store.d.ts +8 -8
  59. package/dist/store.d.ts.map +1 -1
  60. package/dist/store.js +54 -54
  61. package/dist/store.js.map +1 -1
  62. package/dist/utils/dev.d.ts +1 -0
  63. package/dist/utils/dev.d.ts.map +1 -1
  64. package/dist/utils/dev.js +5 -0
  65. package/dist/utils/dev.js.map +1 -1
  66. package/dist/{react/utils → utils}/stack-info.d.ts +1 -2
  67. package/dist/utils/stack-info.d.ts.map +1 -0
  68. package/dist/{react/utils → utils}/stack-info.js +1 -9
  69. package/dist/utils/stack-info.js.map +1 -0
  70. package/dist/utils/stack-info.test.d.ts.map +1 -0
  71. package/dist/{__tests__/react/utils → utils}/stack-info.test.js +1 -1
  72. package/dist/utils/stack-info.test.js.map +1 -0
  73. package/dist/utils/tests/fixture.d.ts +259 -0
  74. package/dist/utils/tests/fixture.d.ts.map +1 -0
  75. package/dist/utils/tests/fixture.js +32 -0
  76. package/dist/utils/tests/fixture.js.map +1 -0
  77. package/dist/utils/tests/mod.d.ts +3 -0
  78. package/dist/utils/tests/mod.d.ts.map +1 -0
  79. package/dist/utils/tests/mod.js +3 -0
  80. package/dist/utils/tests/mod.js.map +1 -0
  81. package/dist/utils/tests/otel.d.ts.map +1 -0
  82. package/dist/{__tests__/react/utils → utils/tests}/otel.js +3 -3
  83. package/dist/utils/tests/otel.js.map +1 -0
  84. package/package.json +13 -20
  85. package/src/ambient.d.ts +3 -1
  86. package/src/effect/LiveStore.ts +7 -7
  87. package/src/global-state.ts +5 -1
  88. package/src/index.ts +19 -7
  89. package/src/reactiveQueries/base-class.ts +5 -4
  90. package/src/reactiveQueries/{js.ts → computed.ts} +3 -3
  91. package/src/reactiveQueries/graphql.ts +2 -1
  92. package/src/reactiveQueries/sql.test.ts +2 -2
  93. package/src/reactiveQueries/sql.ts +5 -5
  94. package/src/row-query.ts +33 -17
  95. package/src/store/create-store.ts +214 -0
  96. package/src/{store-devtools.ts → store/devtools.ts} +9 -9
  97. package/src/store/store-types.ts +110 -0
  98. package/src/{store.ts → store/store.ts} +56 -415
  99. package/src/utils/dev.ts +6 -0
  100. package/src/{__tests__/react/utils → utils}/stack-info.test.ts +1 -1
  101. package/src/{react/utils → utils}/stack-info.ts +2 -12
  102. package/src/utils/tests/fixture.ts +73 -0
  103. package/src/utils/tests/mod.ts +2 -0
  104. package/src/{__tests__/react/utils → utils/tests}/otel.ts +4 -4
  105. package/tsconfig.json +1 -2
  106. package/vitest.config.js +0 -8
  107. package/dist/__tests__/react/fixture.d.ts +0 -461
  108. package/dist/__tests__/react/fixture.d.ts.map +0 -1
  109. package/dist/__tests__/react/fixture.js +0 -68
  110. package/dist/__tests__/react/fixture.js.map +0 -1
  111. package/dist/__tests__/react/utils/otel.d.ts.map +0 -1
  112. package/dist/__tests__/react/utils/otel.js.map +0 -1
  113. package/dist/__tests__/react/utils/stack-info.test.d.ts.map +0 -1
  114. package/dist/__tests__/react/utils/stack-info.test.js.map +0 -1
  115. package/dist/react/LiveStoreContext.d.ts +0 -7
  116. package/dist/react/LiveStoreContext.d.ts.map +0 -1
  117. package/dist/react/LiveStoreContext.js +0 -13
  118. package/dist/react/LiveStoreContext.js.map +0 -1
  119. package/dist/react/LiveStoreProvider.d.ts +0 -49
  120. package/dist/react/LiveStoreProvider.d.ts.map +0 -1
  121. package/dist/react/LiveStoreProvider.js +0 -169
  122. package/dist/react/LiveStoreProvider.js.map +0 -1
  123. package/dist/react/LiveStoreProvider.test.d.ts +0 -2
  124. package/dist/react/LiveStoreProvider.test.d.ts.map +0 -1
  125. package/dist/react/LiveStoreProvider.test.js +0 -62
  126. package/dist/react/LiveStoreProvider.test.js.map +0 -1
  127. package/dist/react/components/LiveList.d.ts +0 -21
  128. package/dist/react/components/LiveList.d.ts.map +0 -1
  129. package/dist/react/components/LiveList.js +0 -31
  130. package/dist/react/components/LiveList.js.map +0 -1
  131. package/dist/react/index.d.ts +0 -11
  132. package/dist/react/index.d.ts.map +0 -1
  133. package/dist/react/index.js +0 -10
  134. package/dist/react/index.js.map +0 -1
  135. package/dist/react/useAtom.d.ts +0 -10
  136. package/dist/react/useAtom.d.ts.map +0 -1
  137. package/dist/react/useAtom.js +0 -37
  138. package/dist/react/useAtom.js.map +0 -1
  139. package/dist/react/useLocalId.d.ts +0 -10
  140. package/dist/react/useLocalId.d.ts.map +0 -1
  141. package/dist/react/useLocalId.js +0 -22
  142. package/dist/react/useLocalId.js.map +0 -1
  143. package/dist/react/useQuery.d.ts +0 -9
  144. package/dist/react/useQuery.d.ts.map +0 -1
  145. package/dist/react/useQuery.js +0 -70
  146. package/dist/react/useQuery.js.map +0 -1
  147. package/dist/react/useQuery.test.d.ts +0 -2
  148. package/dist/react/useQuery.test.d.ts.map +0 -1
  149. package/dist/react/useQuery.test.js +0 -51
  150. package/dist/react/useQuery.test.js.map +0 -1
  151. package/dist/react/useRow.d.ts +0 -46
  152. package/dist/react/useRow.d.ts.map +0 -1
  153. package/dist/react/useRow.js +0 -94
  154. package/dist/react/useRow.js.map +0 -1
  155. package/dist/react/useRow.test.d.ts +0 -2
  156. package/dist/react/useRow.test.d.ts.map +0 -1
  157. package/dist/react/useRow.test.js +0 -208
  158. package/dist/react/useRow.test.js.map +0 -1
  159. package/dist/react/useTemporaryQuery.d.ts +0 -22
  160. package/dist/react/useTemporaryQuery.d.ts.map +0 -1
  161. package/dist/react/useTemporaryQuery.js +0 -75
  162. package/dist/react/useTemporaryQuery.js.map +0 -1
  163. package/dist/react/useTemporaryQuery.test.d.ts +0 -2
  164. package/dist/react/useTemporaryQuery.test.d.ts.map +0 -1
  165. package/dist/react/useTemporaryQuery.test.js +0 -59
  166. package/dist/react/useTemporaryQuery.test.js.map +0 -1
  167. package/dist/react/utils/stack-info.d.ts.map +0 -1
  168. package/dist/react/utils/stack-info.js.map +0 -1
  169. package/dist/react/utils/useStateRefWithReactiveInput.d.ts +0 -13
  170. package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +0 -1
  171. package/dist/react/utils/useStateRefWithReactiveInput.js +0 -38
  172. package/dist/react/utils/useStateRefWithReactiveInput.js.map +0 -1
  173. package/src/__tests__/react/fixture.tsx +0 -126
  174. package/src/react/LiveStoreContext.ts +0 -20
  175. package/src/react/LiveStoreProvider.test.tsx +0 -109
  176. package/src/react/LiveStoreProvider.tsx +0 -291
  177. package/src/react/__snapshots__/useRow.test.tsx.snap +0 -359
  178. package/src/react/components/LiveList.tsx +0 -84
  179. package/src/react/index.ts +0 -19
  180. package/src/react/useAtom.ts +0 -55
  181. package/src/react/useLocalId.ts +0 -34
  182. package/src/react/useQuery.test.tsx +0 -82
  183. package/src/react/useQuery.ts +0 -106
  184. package/src/react/useRow.test.tsx +0 -345
  185. package/src/react/useRow.ts +0 -180
  186. package/src/react/useTemporaryQuery.test.tsx +0 -98
  187. package/src/react/useTemporaryQuery.ts +0 -131
  188. package/src/react/utils/useStateRefWithReactiveInput.ts +0 -51
  189. package/src/store-context.ts +0 -23
  190. /package/dist/{__tests__/react/utils → utils}/stack-info.test.d.ts +0 -0
  191. /package/dist/{__tests__/react/utils → utils/tests}/otel.d.ts +0 -0
@@ -1,84 +0,0 @@
1
- import React from 'react'
2
-
3
- import type { LiveQuery } from '../../reactiveQueries/base-class.js'
4
- import { computed } from '../../reactiveQueries/js.js'
5
- import { useQuery } from '../useQuery.js'
6
- import { useTemporaryQuery } from '../useTemporaryQuery.js'
7
-
8
- /*
9
- TODO:
10
- - [ ] Bring back incremental rendering (see https://github.com/livestorejs/livestore/pull/55)
11
- - [ ] Enable exit animations
12
- */
13
-
14
- export type LiveListProps<TItem> = {
15
- items$: LiveQuery<ReadonlyArray<TItem>>
16
- // TODO refactor render-flag to allow for transition animations on add/remove
17
- renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
18
- /** Needs to be unique across all list items */
19
- getKey: (item: TItem, index: number) => string | number
20
- }
21
-
22
- /**
23
- * This component is a helper component for rendering a list of items for a LiveQuery of an array of items.
24
- * The idea is that instead of letting React handle the rendering of the items array directly,
25
- * we derive a item LiveQuery for each item which moves the reactivity to the item level when a single item changes.
26
- *
27
- * In the future we want to make this component even more efficient by using incremental rendering (https://github.com/livestorejs/livestore/pull/55)
28
- * e.g. when an item is added/removed/moved to only re-render the affected DOM nodes.
29
- */
30
- export const LiveList = <TItem,>({ items$, renderItem, getKey }: LiveListProps<TItem>): React.ReactNode => {
31
- const [hasMounted, setHasMounted] = React.useState(false)
32
-
33
- React.useEffect(() => setHasMounted(true), [])
34
-
35
- const keysCb = React.useCallback(() => computed((get) => get(items$).map(getKey)), [getKey, items$])
36
- const keys = useTemporaryQuery(keysCb, 'fixed')
37
- const arr = React.useMemo(
38
- () =>
39
- keys.map(
40
- (key) =>
41
- // TODO figure out a way so that `item$` returns an ordered lookup map to more efficiently find the item by key
42
- [key, computed((get) => get(items$).find((item) => getKey(item, 0) === key)!) as LiveQuery<TItem>] as const,
43
- ),
44
- [getKey, items$, keys],
45
- )
46
-
47
- return (
48
- <>
49
- {arr.map(([key, item$], index) => (
50
- <ItemWrapperMemo
51
- key={key}
52
- itemKey={key}
53
- item$={item$}
54
- opts={{ isInitialListRender: !hasMounted, index }}
55
- renderItem={renderItem}
56
- />
57
- ))}
58
- </>
59
- )
60
- }
61
-
62
- const ItemWrapper = <TItem,>({
63
- item$,
64
- opts,
65
- renderItem,
66
- }: {
67
- itemKey: string | number
68
- item$: LiveQuery<TItem>
69
- opts: { index: number; isInitialListRender: boolean }
70
- renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
71
- }) => {
72
- const item = useQuery(item$)
73
-
74
- return <>{renderItem(item, opts)}</>
75
- }
76
-
77
- const ItemWrapperMemo = React.memo(
78
- ItemWrapper,
79
- (prev, next) =>
80
- prev.itemKey === next.itemKey &&
81
- prev.renderItem === prev.renderItem &&
82
- prev.opts.index === next.opts.index &&
83
- prev.opts.isInitialListRender === next.opts.isInitialListRender,
84
- ) as typeof ItemWrapper
@@ -1,19 +0,0 @@
1
- export { LiveStoreContext, useStore } from './LiveStoreContext.js'
2
- export { LiveStoreProvider } from './LiveStoreProvider.js'
3
- export { useQuery } from './useQuery.js'
4
- export { useTemporaryQuery } from './useTemporaryQuery.js'
5
- export { useStackInfo } from './utils/stack-info.js'
6
- export {
7
- useRow,
8
- type StateSetters,
9
- type SetStateAction,
10
- type Dispatch,
11
- type UseRowResult as UseStateResult,
12
- } from './useRow.js'
13
- export { useAtom } from './useAtom.js'
14
- export { useLocalId, getLocalId } from './useLocalId.js'
15
-
16
- export { LiveList, type LiveListProps } from './components/LiveList.js'
17
-
18
- // Needed to make TS happy
19
- export type { TypedDocumentNode } from '@graphql-typed-document-node/core'
@@ -1,55 +0,0 @@
1
- import { type QueryInfoCol, type QueryInfoRow } from '@livestore/common'
2
- import type { DbSchema } from '@livestore/common/schema'
3
- import React from 'react'
4
-
5
- import type { LiveQuery } from '../reactiveQueries/base-class.js'
6
- import { useStore } from './LiveStoreContext.js'
7
- import { useQueryRef } from './useQuery.js'
8
- import type { Dispatch, SetStateAction } from './useRow.js'
9
-
10
- export const useAtom = <
11
- TQuery extends LiveQuery<any, QueryInfoRow<TTableDef> | QueryInfoCol<TTableDef, any>>,
12
- TTableDef extends DbSchema.TableDef<
13
- DbSchema.DefaultSqliteTableDefConstrained,
14
- boolean,
15
- DbSchema.TableOptions & { deriveMutations: { enabled: true } }
16
- >,
17
- >(
18
- query$: TQuery,
19
- ): [value: TQuery['__result!'], setValue: Dispatch<SetStateAction<Partial<TQuery['__result!']>>>] => {
20
- const query$Ref = useQueryRef(query$)
21
-
22
- const { store } = useStore()
23
-
24
- // TODO make API equivalent to useRow
25
- const setValue = React.useMemo<Dispatch<SetStateAction<TQuery['__result!']>>>(() => {
26
- return (newValueOrFn: any) => {
27
- const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
28
-
29
- if (query$.queryInfo._tag === 'Row') {
30
- if (query$.queryInfo.table.options.isSingleton && query$.queryInfo.table.isSingleColumn) {
31
- store.mutate(query$.queryInfo.table.update(newValue))
32
- } else if (query$.queryInfo.table.options.isSingleColumn) {
33
- store.mutate(
34
- query$.queryInfo.table.update({ where: { id: query$.queryInfo.id }, values: { value: newValue } }),
35
- )
36
- } else {
37
- store.mutate(query$.queryInfo.table.update({ where: { id: query$.queryInfo.id }, values: newValue }))
38
- }
39
- } else {
40
- if (query$.queryInfo.table.options.isSingleton && query$.queryInfo.table.isSingleColumn) {
41
- store.mutate(query$.queryInfo.table.update({ [query$.queryInfo.column]: newValue }))
42
- } else {
43
- store.mutate(
44
- query$.queryInfo.table.update({
45
- where: { id: query$.queryInfo.id },
46
- values: { [query$.queryInfo.column]: newValue },
47
- }),
48
- )
49
- }
50
- }
51
- }
52
- }, [query$.queryInfo, query$Ref, store])
53
-
54
- return [query$Ref.current, setValue]
55
- }
@@ -1,34 +0,0 @@
1
- /* eslint-disable unicorn/prefer-global-this */
2
- import { cuid } from '@livestore/utils/cuid'
3
- import React from 'react'
4
-
5
- type LocalIdOptions = {
6
- key: string
7
- storageType: 'session' | 'local'
8
- storageKeyPrefix: string
9
- makeId: () => string
10
- }
11
-
12
- export const useLocalId = (opts?: Partial<LocalIdOptions>) => React.useMemo(() => getLocalId(opts), [opts])
13
-
14
- export const getLocalId = (opts?: Partial<LocalIdOptions>) => {
15
- // TODO find a better way to handle this
16
- // Currently `getLocalId` gets imported and called in some worker-side code
17
- // during development where Vite isn't tree-shaking yet.
18
- if (typeof window === 'undefined' || window.localStorage === undefined || window.sessionStorage === undefined) {
19
- return ''
20
- }
21
-
22
- const { key = '', storageType = 'session', storageKeyPrefix = 'livestore:localid:', makeId = cuid } = opts ?? {}
23
-
24
- const storage = storageType === 'session' ? window.sessionStorage : window.localStorage
25
- const fullKey = `${storageKeyPrefix}:${key}`
26
- const storedKey = storage.getItem(fullKey)
27
-
28
- if (storedKey) return storedKey
29
-
30
- const newKey = makeId()
31
- storage.setItem(fullKey, newKey)
32
-
33
- return newKey
34
- }
@@ -1,82 +0,0 @@
1
- import { Effect, Schema } from '@livestore/utils/effect'
2
- import { renderHook } from '@testing-library/react'
3
- import React from 'react'
4
- import { describe, expect, it } from 'vitest'
5
-
6
- import { makeTodoMvc, tables, todos } from '../__tests__/react/fixture.js'
7
- import { querySQL } from '../reactiveQueries/sql.js'
8
- import * as LiveStoreReact from './index.js'
9
-
10
- describe('useQuery', () => {
11
- it('simple', () =>
12
- Effect.gen(function* () {
13
- const { wrapper, store, makeRenderCount } = yield* makeTodoMvc()
14
-
15
- const renderCount = makeRenderCount()
16
-
17
- const allTodos$ = querySQL(`select * from todos`, { schema: Schema.Array(tables.todos.schema) })
18
-
19
- const { result } = renderHook(
20
- () => {
21
- renderCount.inc()
22
-
23
- return LiveStoreReact.useQuery(allTodos$)
24
- },
25
- { wrapper },
26
- )
27
-
28
- expect(result.current.length).toBe(0)
29
- expect(renderCount.val).toBe(1)
30
-
31
- React.act(() => store.mutate(todos.insert({ id: 't1', text: 'buy milk', completed: false })))
32
-
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))
37
-
38
- it('same `useQuery` hook invoked with different queries', () =>
39
- Effect.gen(function* () {
40
- const { wrapper, store, makeRenderCount } = yield* makeTodoMvc()
41
-
42
- const renderCount = makeRenderCount()
43
-
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
- })
52
-
53
- store.mutate(
54
- todos.insert({ id: 't1', text: 'buy milk', completed: false }),
55
- todos.insert({ id: 't2', text: 'buy eggs', completed: false }),
56
- )
57
-
58
- const { result, rerender } = renderHook(
59
- (todoId: string) => {
60
- renderCount.inc()
61
-
62
- const query$ = React.useMemo(() => (todoId === 't1' ? todo1$ : todo2$), [todoId])
63
-
64
- return LiveStoreReact.useQuery(query$)[0]!.text
65
- },
66
- { wrapper, initialProps: 't1' },
67
- )
68
-
69
- expect(result.current).toBe('buy milk')
70
- expect(renderCount.val).toBe(1)
71
-
72
- React.act(() => store.mutate(todos.update({ where: { id: 't1' }, values: { text: 'buy soy milk' } })))
73
-
74
- expect(result.current).toBe('buy soy milk')
75
- expect(renderCount.val).toBe(2)
76
-
77
- rerender('t2')
78
-
79
- expect(result.current).toBe('buy eggs')
80
- expect(renderCount.val).toBe(3)
81
- }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
82
- })
@@ -1,106 +0,0 @@
1
- import { deepEqual } from '@livestore/utils'
2
- import * as otel from '@opentelemetry/api'
3
- import React from 'react'
4
-
5
- import type { GetResult, LiveQueryAny } from '../reactiveQueries/base-class.js'
6
- import { useStore } from './LiveStoreContext.js'
7
- import { extractStackInfoFromStackTrace, originalStackLimit } from './utils/stack-info.js'
8
- import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInput.js'
9
-
10
- /**
11
- * NOTE Some folks have suggested to use `React.useSyncExternalStore`, however, it's not doing anything special
12
- * for what's needed here, so we handle everything manually.
13
- */
14
-
15
- /**
16
- * This is needed because the `React.useMemo` call below, can sometimes be called multiple times 🤷,
17
- * so we need to "cache" the fact that we've already started a span for this component.
18
- * The map entry is being removed again in the `React.useEffect` call below.
19
- */
20
- const spanAlreadyStartedCache = new Map<LiveQueryAny, { span: otel.Span; otelContext: otel.Context }>()
21
-
22
- export const useQuery = <TQuery extends LiveQueryAny>(query: TQuery): GetResult<TQuery> => useQueryRef(query).current
23
-
24
- /**
25
- *
26
- */
27
- export const useQueryRef = <TQuery extends LiveQueryAny>(
28
- query$: TQuery,
29
- parentOtelContext?: otel.Context,
30
- ): React.MutableRefObject<GetResult<TQuery>> => {
31
- const { store } = useStore()
32
-
33
- React.useDebugValue(`LiveStore:useQuery:${query$.id}:${query$.label}`)
34
- // console.debug(`LiveStore:useQuery:${query$.id}:${query$.label}`)
35
-
36
- const stackInfo = React.useMemo(() => {
37
- Error.stackTraceLimit = 10
38
- // eslint-disable-next-line unicorn/error-message
39
- const stack = new Error().stack!
40
- Error.stackTraceLimit = originalStackLimit
41
- return extractStackInfoFromStackTrace(stack)
42
- }, [])
43
-
44
- // The following `React.useMemo` and `React.useEffect` calls are used to start and end a span for the lifetime of this component.
45
- const { span, otelContext } = React.useMemo(() => {
46
- const existingSpan = spanAlreadyStartedCache.get(query$)
47
- if (existingSpan !== undefined) return existingSpan
48
-
49
- const span = store.otel.tracer.startSpan(
50
- `LiveStore:useQuery:${query$.label}`,
51
- { attributes: { label: query$.label, stackInfo: JSON.stringify(stackInfo) } },
52
- parentOtelContext ?? store.otel.queriesSpanContext,
53
- )
54
-
55
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
56
-
57
- spanAlreadyStartedCache.set(query$, { span, otelContext })
58
-
59
- return { span, otelContext }
60
- }, [parentOtelContext, query$, stackInfo, store.otel.queriesSpanContext, store.otel.tracer])
61
-
62
- const initialResult = React.useMemo(
63
- () =>
64
- query$.run(otelContext, {
65
- _tag: 'react',
66
- api: 'useQuery',
67
- label: query$.label,
68
- stackInfo,
69
- }),
70
- [otelContext, query$, stackInfo],
71
- )
72
-
73
- // We know the query has a result by the time we use it; so we can synchronously populate a default state
74
- const [valueRef, setValue] = useStateRefWithReactiveInput<GetResult<TQuery>>(initialResult)
75
-
76
- React.useEffect(
77
- () => () => {
78
- spanAlreadyStartedCache.delete(query$)
79
- span.end()
80
- },
81
- [query$, span],
82
- )
83
-
84
- // Subscribe to future updates for this query
85
- React.useEffect(() => {
86
- query$.activeSubscriptions.add(stackInfo)
87
-
88
- return store.subscribe(
89
- query$,
90
- (newValue) => {
91
- // NOTE: we return a reference to the result object within LiveStore;
92
- // this implies that app code must not mutate the results, or else
93
- // there may be weird reactivity bugs.
94
- if (deepEqual(newValue, valueRef.current) === false) {
95
- setValue(newValue)
96
- }
97
- },
98
- () => {
99
- query$.activeSubscriptions.delete(stackInfo)
100
- },
101
- { label: query$.label, otelContext },
102
- )
103
- }, [stackInfo, query$, setValue, store, valueRef, otelContext, span])
104
-
105
- return valueRef
106
- }