@livestore/livestore 0.0.13 → 0.0.14

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 (205) hide show
  1. package/README.md +18 -21
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/QueryCache.d.ts +1 -1
  4. package/dist/QueryCache.d.ts.map +1 -1
  5. package/dist/QueryCache.js.map +1 -1
  6. package/dist/__tests__/react/fixture.d.ts +5 -4
  7. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  8. package/dist/__tests__/react/fixture.js +13 -14
  9. package/dist/__tests__/react/fixture.js.map +1 -1
  10. package/dist/__tests__/react/useComponentState.test.d.ts +2 -0
  11. package/dist/__tests__/react/useComponentState.test.d.ts.map +1 -0
  12. package/dist/__tests__/react/useComponentState.test.js +68 -0
  13. package/dist/__tests__/react/useComponentState.test.js.map +1 -0
  14. package/dist/__tests__/react/useLQuery.test.d.ts +2 -0
  15. package/dist/__tests__/react/useLQuery.test.d.ts.map +1 -0
  16. package/dist/__tests__/react/useLQuery.test.js +38 -0
  17. package/dist/__tests__/react/useLQuery.test.js.map +1 -0
  18. package/dist/__tests__/react/useLiveStoreComponent.test.js +4 -9
  19. package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -1
  20. package/dist/__tests__/react/useQuery.test.d.ts +2 -0
  21. package/dist/__tests__/react/useQuery.test.d.ts.map +1 -0
  22. package/dist/__tests__/react/useQuery.test.js +33 -0
  23. package/dist/__tests__/react/useQuery.test.js.map +1 -0
  24. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +2 -0
  25. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +1 -0
  26. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +38 -0
  27. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +1 -0
  28. package/dist/__tests__/reactive.test.js +168 -95
  29. package/dist/__tests__/reactive.test.js.map +1 -1
  30. package/dist/__tests__/reactiveQueries/sql.test.d.ts +2 -0
  31. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +1 -0
  32. package/dist/__tests__/reactiveQueries/sql.test.js +337 -0
  33. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -0
  34. package/dist/effect/LiveStore.d.ts +3 -9
  35. package/dist/effect/LiveStore.d.ts.map +1 -1
  36. package/dist/effect/LiveStore.js +11 -7
  37. package/dist/effect/LiveStore.js.map +1 -1
  38. package/dist/inMemoryDatabase.d.ts +17 -21
  39. package/dist/inMemoryDatabase.d.ts.map +1 -1
  40. package/dist/inMemoryDatabase.js +2 -9
  41. package/dist/inMemoryDatabase.js.map +1 -1
  42. package/dist/index.d.ts +9 -7
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +7 -3
  45. package/dist/index.js.map +1 -1
  46. package/dist/migrations.d.ts +7 -0
  47. package/dist/migrations.d.ts.map +1 -1
  48. package/dist/migrations.js +18 -13
  49. package/dist/migrations.js.map +1 -1
  50. package/dist/react/LiveStoreProvider.d.ts +1 -3
  51. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  52. package/dist/react/LiveStoreProvider.js +13 -10
  53. package/dist/react/LiveStoreProvider.js.map +1 -1
  54. package/dist/react/index.d.ts +4 -4
  55. package/dist/react/index.d.ts.map +1 -1
  56. package/dist/react/index.js +3 -3
  57. package/dist/react/index.js.map +1 -1
  58. package/dist/react/useComponentState.d.ts +50 -0
  59. package/dist/react/useComponentState.d.ts.map +1 -0
  60. package/dist/react/useComponentState.js +248 -0
  61. package/dist/react/useComponentState.js.map +1 -0
  62. package/dist/react/useGlobalQuery.d.ts +2 -2
  63. package/dist/react/useGlobalQuery.d.ts.map +1 -1
  64. package/dist/react/useGlobalQuery.js +5 -2
  65. package/dist/react/useGlobalQuery.js.map +1 -1
  66. package/dist/react/useGraphQL.d.ts +5 -3
  67. package/dist/react/useGraphQL.d.ts.map +1 -1
  68. package/dist/react/useGraphQL.js +27 -7
  69. package/dist/react/useGraphQL.js.map +1 -1
  70. package/dist/react/useLiveStoreComponent.d.ts +14 -14
  71. package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
  72. package/dist/react/useLiveStoreComponent.js +151 -91
  73. package/dist/react/useLiveStoreComponent.js.map +1 -1
  74. package/dist/react/useQuery.d.ts +3 -0
  75. package/dist/react/useQuery.d.ts.map +1 -0
  76. package/dist/react/useQuery.js +42 -0
  77. package/dist/react/useQuery.js.map +1 -0
  78. package/dist/react/useTemporaryQuery.d.ts +8 -0
  79. package/dist/react/useTemporaryQuery.d.ts.map +1 -0
  80. package/dist/react/useTemporaryQuery.js +17 -0
  81. package/dist/react/useTemporaryQuery.js.map +1 -0
  82. package/dist/react/utils/extractNamesFromStackTrace.d.ts +3 -0
  83. package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +1 -0
  84. package/dist/react/utils/extractNamesFromStackTrace.js +40 -0
  85. package/dist/react/utils/extractNamesFromStackTrace.js.map +1 -0
  86. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +7 -0
  87. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +1 -0
  88. package/dist/react/utils/extractStackInfoFromStackTrace.js +40 -0
  89. package/dist/react/utils/extractStackInfoFromStackTrace.js.map +1 -0
  90. package/dist/reactive.d.ts +42 -48
  91. package/dist/reactive.d.ts.map +1 -1
  92. package/dist/reactive.js +293 -186
  93. package/dist/reactive.js.map +1 -1
  94. package/dist/reactiveQueries/base-class.d.ts +28 -20
  95. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  96. package/dist/reactiveQueries/base-class.js +25 -17
  97. package/dist/reactiveQueries/base-class.js.map +1 -1
  98. package/dist/reactiveQueries/graph.d.ts +10 -0
  99. package/dist/reactiveQueries/graph.d.ts.map +1 -0
  100. package/dist/reactiveQueries/graph.js +6 -0
  101. package/dist/reactiveQueries/graph.js.map +1 -0
  102. package/dist/reactiveQueries/graphql.d.ts +35 -18
  103. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  104. package/dist/reactiveQueries/graphql.js +91 -10
  105. package/dist/reactiveQueries/graphql.js.map +1 -1
  106. package/dist/reactiveQueries/js.d.ts +17 -13
  107. package/dist/reactiveQueries/js.d.ts.map +1 -1
  108. package/dist/reactiveQueries/js.js +31 -8
  109. package/dist/reactiveQueries/js.js.map +1 -1
  110. package/dist/reactiveQueries/sql.d.ts +22 -18
  111. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  112. package/dist/reactiveQueries/sql.js +81 -16
  113. package/dist/reactiveQueries/sql.js.map +1 -1
  114. package/dist/schema.d.ts +0 -2
  115. package/dist/schema.d.ts.map +1 -1
  116. package/dist/schema.js +3 -6
  117. package/dist/schema.js.map +1 -1
  118. package/dist/storage/in-memory/index.d.ts +2 -2
  119. package/dist/storage/in-memory/index.d.ts.map +1 -1
  120. package/dist/storage/in-memory/index.js.map +1 -1
  121. package/dist/storage/index.d.ts +2 -2
  122. package/dist/storage/index.d.ts.map +1 -1
  123. package/dist/storage/tauri/index.d.ts +2 -2
  124. package/dist/storage/tauri/index.d.ts.map +1 -1
  125. package/dist/storage/tauri/index.js.map +1 -1
  126. package/dist/storage/web-worker/index.d.ts +4 -4
  127. package/dist/storage/web-worker/index.d.ts.map +1 -1
  128. package/dist/storage/web-worker/index.js +3 -5
  129. package/dist/storage/web-worker/index.js.map +1 -1
  130. package/dist/storage/web-worker/worker.js +2 -2
  131. package/dist/storage/web-worker/worker.js.map +1 -1
  132. package/dist/store.d.ts +19 -52
  133. package/dist/store.d.ts.map +1 -1
  134. package/dist/store.js +323 -266
  135. package/dist/store.js.map +1 -1
  136. package/dist/util.d.ts +3 -1
  137. package/dist/util.d.ts.map +1 -1
  138. package/dist/util.js +2 -0
  139. package/dist/util.js.map +1 -1
  140. package/package.json +2 -1
  141. package/src/QueryCache.ts +1 -1
  142. package/src/__tests__/react/fixture.tsx +21 -16
  143. package/src/__tests__/react/{useLiveStoreComponent.test.tsx → useComponentState.test.tsx} +9 -20
  144. package/src/__tests__/react/useQuery.test.tsx +48 -0
  145. package/src/__tests__/react/utils/extractStackInfoFromStackTrace.test.ts +40 -0
  146. package/src/__tests__/reactive.test.ts +194 -142
  147. package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
  148. package/src/effect/LiveStore.ts +14 -18
  149. package/src/inMemoryDatabase.ts +22 -30
  150. package/src/index.ts +8 -6
  151. package/src/migrations.ts +39 -21
  152. package/src/react/LiveStoreProvider.tsx +13 -16
  153. package/src/react/index.ts +4 -8
  154. package/src/react/{useLiveStoreComponent.ts → useComponentState.ts} +98 -230
  155. package/src/react/useQuery.ts +58 -0
  156. package/src/react/useTemporaryQuery.ts +21 -0
  157. package/src/react/utils/extractStackInfoFromStackTrace.ts +47 -0
  158. package/src/reactive.ts +386 -267
  159. package/src/reactiveQueries/base-class.ts +61 -39
  160. package/src/reactiveQueries/graph.ts +15 -0
  161. package/src/reactiveQueries/graphql.ts +147 -31
  162. package/src/reactiveQueries/js.ts +54 -21
  163. package/src/reactiveQueries/sql.ts +128 -37
  164. package/src/schema.ts +2 -5
  165. package/src/storage/in-memory/index.ts +2 -2
  166. package/src/storage/index.ts +2 -2
  167. package/src/storage/tauri/index.ts +2 -2
  168. package/src/storage/web-worker/index.ts +6 -8
  169. package/src/storage/web-worker/worker.ts +2 -2
  170. package/src/store.ts +394 -418
  171. package/src/util.ts +8 -2
  172. package/dist/backends/base.d.ts +0 -13
  173. package/dist/backends/base.d.ts.map +0 -1
  174. package/dist/backends/base.js +0 -53
  175. package/dist/backends/base.js.map +0 -1
  176. package/dist/backends/in-memory/index.d.ts +0 -22
  177. package/dist/backends/in-memory/index.d.ts.map +0 -1
  178. package/dist/backends/in-memory/index.js +0 -45
  179. package/dist/backends/in-memory/index.js.map +0 -1
  180. package/dist/backends/index.d.ts +0 -41
  181. package/dist/backends/index.d.ts.map +0 -1
  182. package/dist/backends/index.js +0 -16
  183. package/dist/backends/index.js.map +0 -1
  184. package/dist/backends/tauri/index.d.ts +0 -21
  185. package/dist/backends/tauri/index.d.ts.map +0 -1
  186. package/dist/backends/tauri/index.js +0 -48
  187. package/dist/backends/tauri/index.js.map +0 -1
  188. package/dist/backends/utils/idb.d.ts +0 -10
  189. package/dist/backends/utils/idb.d.ts.map +0 -1
  190. package/dist/backends/utils/idb.js +0 -58
  191. package/dist/backends/utils/idb.js.map +0 -1
  192. package/dist/backends/web-worker/index.d.ts +0 -26
  193. package/dist/backends/web-worker/index.d.ts.map +0 -1
  194. package/dist/backends/web-worker/index.js +0 -63
  195. package/dist/backends/web-worker/index.js.map +0 -1
  196. package/dist/backends/web-worker/worker.d.ts +0 -17
  197. package/dist/backends/web-worker/worker.d.ts.map +0 -1
  198. package/dist/backends/web-worker/worker.js +0 -139
  199. package/dist/backends/web-worker/worker.js.map +0 -1
  200. package/dist/storage/base.d.ts +0 -10
  201. package/dist/storage/base.d.ts.map +0 -1
  202. package/dist/storage/base.js +0 -14
  203. package/dist/storage/base.js.map +0 -1
  204. package/src/react/useGlobalQuery.ts +0 -37
  205. package/src/react/useGraphQL.ts +0 -112
@@ -1,14 +1,10 @@
1
1
  import type * as otel from '@opentelemetry/api'
2
- import { mapValues } from 'lodash-es'
3
2
  import type { ReactElement, ReactNode } from 'react'
4
3
  import React from 'react'
4
+ import initSqlite3Wasm from 'sqlite-esm'
5
5
 
6
6
  // TODO refactor so the `react` module doesn't depend on `effect` module
7
- import type {
8
- GlobalQueryDefs,
9
- LiveStoreContext as StoreContext_,
10
- LiveStoreCreateStoreOptions,
11
- } from '../effect/LiveStore.js'
7
+ import type { LiveStoreContext as StoreContext_, LiveStoreCreateStoreOptions } from '../effect/LiveStore.js'
12
8
  import type { InMemoryDatabase } from '../inMemoryDatabase.js'
13
9
  import type { Schema } from '../schema.js'
14
10
  import type { StorageInit } from '../storage/index.js'
@@ -16,11 +12,17 @@ import type { BaseGraphQLContext, GraphQLOptions } from '../store.js'
16
12
  import { createStore } from '../store.js'
17
13
  import { LiveStoreContext } from './LiveStoreContext.js'
18
14
 
15
+ // NOTE we're starting to initialize the sqlite wasm binary here (already before calling `createStore`),
16
+ // so that it's ready when we need it
17
+ const sqlite3Promise = initSqlite3Wasm({
18
+ print: (message) => console.log(`[livestore sqlite] ${message}`),
19
+ printErr: (message) => console.error(`[livestore sqlite] ${message}`),
20
+ })
21
+
19
22
  interface LiveStoreProviderProps<GraphQLContext> {
20
23
  schema: Schema
21
24
  loadStorage: () => StorageInit | Promise<StorageInit>
22
25
  boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
23
- globalQueryDefs: GlobalQueryDefs
24
26
  graphQLOptions?: GraphQLOptions<GraphQLContext>
25
27
  otelTracer?: otel.Tracer
26
28
  otelRootSpanContext?: otel.Context
@@ -29,7 +31,6 @@ interface LiveStoreProviderProps<GraphQLContext> {
29
31
 
30
32
  export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
31
33
  fallback,
32
- globalQueryDefs,
33
34
  loadStorage,
34
35
  graphQLOptions,
35
36
  otelTracer,
@@ -40,7 +41,6 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
40
41
  }: LiveStoreProviderProps<GraphQLContext> & { children?: ReactNode }): JSX.Element => {
41
42
  const store = useCreateStore({
42
43
  schema,
43
- globalQueryDefs,
44
44
  loadStorage,
45
45
  graphQLOptions,
46
46
  otelTracer,
@@ -59,7 +59,6 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
59
59
 
60
60
  const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
61
61
  schema,
62
- globalQueryDefs,
63
62
  loadStorage,
64
63
  graphQLOptions,
65
64
  otelTracer,
@@ -71,6 +70,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
71
70
  React.useEffect(() => {
72
71
  void (async () => {
73
72
  try {
73
+ const sqlite3 = await sqlite3Promise
74
74
  const store = await createStore({
75
75
  schema,
76
76
  loadStorage,
@@ -78,12 +78,9 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
78
78
  otelTracer,
79
79
  otelRootSpanContext,
80
80
  boot,
81
+ sqlite3,
81
82
  })
82
- store.otel.tracer.startActiveSpan('LiveStore:makeGlobalQueries', {}, store.otel.queriesSpanContext, (span) => {
83
- const globalQueries = mapValues(globalQueryDefs, (queryDef) => queryDef(store))
84
- setCtxValue({ store, globalQueries })
85
- span.end()
86
- })
83
+ setCtxValue({ store })
87
84
  } catch (e) {
88
85
  console.error(`Error creating LiveStore store:`, e)
89
86
  throw e
@@ -91,7 +88,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
91
88
  })()
92
89
 
93
90
  // TODO: do we need to return any cleanup function here?
94
- }, [schema, loadStorage, globalQueryDefs, graphQLOptions, otelTracer, otelRootSpanContext, boot])
91
+ }, [schema, loadStorage, graphQLOptions, otelTracer, otelRootSpanContext, boot])
95
92
 
96
93
  return ctxValue
97
94
  }
@@ -1,20 +1,16 @@
1
1
  export type {
2
- UseLiveStoreComponentProps as LiveStoreComponentConfig,
3
- ReactiveGraphQL,
4
- ReactiveSQL,
5
2
  Setters,
6
3
  ComponentKeyConfig,
7
- QueryResults,
8
4
  QueryDefinitions,
9
5
  ComponentColumns,
10
6
  GetStateType,
11
7
  GetStateTypeEncoded,
12
- } from './useLiveStoreComponent.js'
8
+ } from './useComponentState.js'
13
9
  export { LiveStoreContext, useStore } from './LiveStoreContext.js'
14
10
  export { LiveStoreProvider } from './LiveStoreProvider.js'
15
- export { useLiveStoreComponent } from './useLiveStoreComponent.js'
16
- export { useGraphQL } from './useGraphQL.js'
17
- export { useGlobalQuery } from './useGlobalQuery.js'
11
+ export { useComponentState } from './useComponentState.js'
12
+ export { useQuery } from './useQuery.js'
13
+ export { useTemporaryQuery } from './useTemporaryQuery.js'
18
14
 
19
15
  // Needed to make TS happy
20
16
  export type { TypedDocumentNode } from '@graphql-typed-document-node/core'
@@ -1,9 +1,8 @@
1
- import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
2
- import type { LiteralUnion, PrettifyFlat } from '@livestore/utils'
1
+ import type { LiteralUnion } from '@livestore/utils'
3
2
  import { omit, shouldNeverHappen } from '@livestore/utils'
4
3
  import { Schema } from '@livestore/utils/effect'
5
4
  import * as otel from '@opentelemetry/api'
6
- import { SqliteDsl } from 'effect-db-schema'
5
+ import { SqliteAst, SqliteDsl } from 'effect-db-schema'
7
6
  import { isEqual, mapValues } from 'lodash-es'
8
7
  import type { DependencyList } from 'react'
9
8
  import React from 'react'
@@ -11,60 +10,22 @@ import { v4 as uuid } from 'uuid'
11
10
 
12
11
  import type { ComponentKey } from '../componentKey.js'
13
12
  import { labelForKey, tableNameForComponentKey } from '../componentKey.js'
14
- import type { GetAtom } from '../reactive.js'
15
- import type { LiveStoreGraphQLQuery } from '../reactiveQueries/graphql.js'
16
- import type { LiveStoreJSQuery } from '../reactiveQueries/js.js'
17
- import type { LiveStoreSQLQuery } from '../reactiveQueries/sql.js'
18
- import type { BaseGraphQLContext, LiveStoreQuery, QueryResult, Store } from '../store.js'
19
- import type { Bindable } from '../util.js'
13
+ import { migrateTable } from '../migrations.js'
14
+ import { LiveStoreJSQuery } from '../reactiveQueries/js.js'
15
+ import { LiveStoreSQLQuery } from '../reactiveQueries/sql.js'
16
+ import { SCHEMA_META_TABLE } from '../schema.js'
17
+ import type { BaseGraphQLContext, LiveStoreQuery, Store } from '../store.js'
20
18
  import { sql } from '../util.js'
21
19
  import { useStore } from './LiveStoreContext.js'
20
+ import { extractStackInfoFromStackTrace, originalStackLimit } from './utils/extractStackInfoFromStackTrace.js'
22
21
  import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInput.js'
23
22
 
24
23
  export interface QueryDefinitions {
25
24
  [queryName: string]: LiveStoreQuery
26
25
  }
27
26
 
28
- export type QueryResults<TQuery> = { [queryName in keyof TQuery]: PrettifyFlat<QueryResult<TQuery[queryName]>> }
29
-
30
- export type ReactiveSQL = <TResult>(
31
- genQuery: (get: GetAtom) => string,
32
- queriedTables: string[],
33
- bindValues?: Bindable | undefined,
34
- ) => LiveStoreSQLQuery<TResult>
35
- export type ReactiveGraphQL = <
36
- TResult extends Record<string, any>,
37
- TVariables extends Record<string, any>,
38
- TContext extends BaseGraphQLContext,
39
- >(
40
- query: DocumentNode<TResult, TVariables>,
41
- genVariableValues: (get: GetAtom) => TVariables,
42
- label?: string,
43
- ) => LiveStoreGraphQLQuery<TResult, TVariables, TContext>
44
-
45
- type RegisterSubscription = <TQuery extends LiveStoreQuery>(
46
- query: TQuery,
47
- onNewValue: (value: QueryResult<TQuery>) => void,
48
- onUnsubscribe?: () => void,
49
- ) => void
50
-
51
- type GenQueries<TQueries, TStateResult> = (args: {
52
- rxSQL: ReactiveSQL
53
- rxGraphQL: ReactiveGraphQL
54
- globalQueries: QueryDefinitions
55
- state$: LiveStoreJSQuery<TStateResult>
56
- /**
57
- * Registers a subscription.
58
- *
59
- * Passed down for some manual subscribing. Use carefully.
60
- */
61
- subscribe: RegisterSubscription
62
- isTemporaryQuery: boolean
63
- }) => TQueries
64
-
65
- export type UseLiveStoreComponentProps<TQueries, TColumns extends ComponentColumns> = {
66
- stateSchema?: SqliteDsl.TableDefinition<string, TColumns>
67
- queries?: GenQueries<TQueries, SqliteDsl.FromColumns.RowDecoded<TColumns>>
27
+ export type UseComponentStateProps<TStateColumns extends ComponentColumns> = {
28
+ schema?: SqliteDsl.TableDefinition<string, TStateColumns>
68
29
  reactDeps?: React.DependencyList
69
30
  componentKey: ComponentKeyConfig
70
31
  }
@@ -115,18 +76,17 @@ export type GetStateTypeEncoded<TTableDef extends SqliteDsl.TableDefinition<any,
115
76
  * @param config.componentKey A function that returns a unique key for this component.
116
77
  * @param config.reactDeps A list of React-level dependencies that will refresh the queries.
117
78
  */
118
- export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQueries extends QueryDefinitions>({
119
- stateSchema: stateSchema_,
120
- queries = () => ({}) as TQueries,
79
+ export const useComponentState = <TStateColumns extends ComponentColumns>({
80
+ schema: stateSchema_,
121
81
  componentKey: componentKeyConfig,
122
82
  reactDeps = [],
123
- }: UseLiveStoreComponentProps<TQueries, TColumns>): {
124
- queryResults: QueryResults<TQueries>
125
- state: SqliteDsl.FromColumns.RowDecoded<TColumns>
126
- setState: Setters<SqliteDsl.FromColumns.RowDecoded<TColumns>>
127
- useLiveStoreJsonState: UseLiveStoreJsonState<SqliteDsl.FromColumns.RowDecoded<TColumns>>
83
+ }: UseComponentStateProps<TStateColumns>): {
84
+ state$: LiveStoreJSQuery<SqliteDsl.FromColumns.RowDecoded<TStateColumns>>
85
+ state: SqliteDsl.FromColumns.RowDecoded<TStateColumns>
86
+ setState: Setters<SqliteDsl.FromColumns.RowDecoded<TStateColumns>>
87
+ useLiveStoreJsonState: UseLiveStoreJsonState<SqliteDsl.FromColumns.RowDecoded<TStateColumns>>
128
88
  } => {
129
- type TComponentState = SqliteDsl.FromColumns.RowDecoded<TColumns>
89
+ type TComponentState = SqliteDsl.FromColumns.RowDecoded<TStateColumns>
130
90
 
131
91
  // TODO validate schema to make sure each column has a default value
132
92
  // TODO we should clean up the state schema handling to remove this special handling for the `id` column
@@ -135,9 +95,8 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
135
95
  [stateSchema_],
136
96
  )
137
97
 
138
- // performance.mark('useLiveStoreComponent:start')
139
98
  const componentKey = useComponentKey(componentKeyConfig, reactDeps)
140
- const { store, globalQueries } = useStore()
99
+ const { store } = useStore()
141
100
 
142
101
  const componentKeyLabel = React.useMemo(() => labelForKey(componentKey), [componentKey])
143
102
 
@@ -147,7 +106,7 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
147
106
  if (existingSpan !== undefined) return existingSpan
148
107
 
149
108
  const span = store.otel.tracer.startSpan(
150
- `LiveStore:useLiveStoreComponent:${componentKeyLabel}`,
109
+ `LiveStore:useComponentState:${componentKeyLabel}`,
151
110
  {},
152
111
  store.otel.queriesSpanContext,
153
112
  )
@@ -167,41 +126,6 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
167
126
  [componentKeyLabel, span],
168
127
  )
169
128
 
170
- const generateQueries = React.useCallback(
171
- ({
172
- state$,
173
- otelContext,
174
- registerSubscription,
175
- isTemporaryQuery,
176
- }: {
177
- state$: LiveStoreJSQuery<TComponentState>
178
- otelContext: otel.Context
179
- registerSubscription: RegisterSubscription
180
- isTemporaryQuery: boolean
181
- }) =>
182
- queries({
183
- rxSQL: <T>(genQuery: (get: GetAtom) => string, queriedTables: string[], bindValues?: Bindable) =>
184
- store.querySQL<T>(genQuery, { queriedTables, bindValues, otelContext, componentKey }),
185
- rxGraphQL: <Result extends Record<string, any>, Variables extends Record<string, any>>(
186
- query: DocumentNode<Result, Variables>,
187
- genVariableValues: (get: GetAtom) => Variables,
188
- label?: string,
189
- ) => store.queryGraphQL(query, genVariableValues, { componentKey, label, otelContext }),
190
- globalQueries,
191
- state$,
192
- subscribe: registerSubscription,
193
- isTemporaryQuery,
194
- }),
195
-
196
- // NOTE: we don't include the queries function passed in by the user here;
197
- // the reason is that we don't want to force them to memoize that function.
198
- // Instead, we just assume that the function always has the same contents.
199
- // This makes sense for LiveStore because the component config should be static.
200
- // TODO: document this and consider whether it's the right API surface.
201
- // eslint-disable-next-line react-hooks/exhaustive-deps
202
- [store, componentKey, globalQueries],
203
- )
204
-
205
129
  const defaultComponentState = React.useMemo(() => {
206
130
  const defaultState = (
207
131
  stateSchema === undefined ? {} : mapValues(stateSchema.columns, (c) => c.default)
@@ -218,81 +142,68 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
218
142
  [stateSchema],
219
143
  )
220
144
 
221
- // Step 1:
222
- // Synchronously create state and queries for initial render pass.
223
- // We do this in a temporary query context which cleans up after itself, making it idempotent
224
- // TODO get rid of the temporary query workaround
225
- const { initialComponentState, initialQueryResults } = React.useMemo(() => {
226
- return store.otel.tracer.startActiveSpan('LiveStore:useLiveStoreComponent:initial', {}, otelContext, (span) => {
227
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
228
-
229
- return store.inTempQueryContext(() => {
230
- try {
231
- // create state query
232
- let state$: LiveStoreJSQuery<TComponentState>
233
- if (stateSchema === undefined) {
234
- // TODO don't set up a query if there's no state schema (keeps the graph more clean)
235
- state$ = store.queryJS(() => ({}), {
236
- componentKey,
237
- otelContext,
238
- }) as unknown as LiveStoreJSQuery<TComponentState>
239
- } else {
240
- const componentTableName = tableNameForComponentKey(componentKey)
241
- const whereClause = componentKey._tag === 'singleton' ? '' : `where id = '${componentKey.id}'`
242
- state$ = store
243
- .querySQL(() => sql`select * from ${componentTableName} ${whereClause} limit 1`, {
244
- queriedTables: [componentTableName],
245
- componentKey,
246
- label: `localState:query:${componentKeyLabel}`,
247
- otelContext,
248
- })
249
- // TODO consider to instead of just returning the default value, to write the default component state to the DB
250
- .pipe<TComponentState>((results) =>
251
- results.length === 1
252
- ? Schema.parseSync(componentStateEffectSchema)(results[0]!)
253
- : defaultComponentState,
254
- )
255
- }
256
- const initialComponentState = state$.results$.result
257
-
258
- const queries = generateQueries({
259
- state$: state$,
260
- otelContext,
261
- registerSubscription: () => {},
262
- isTemporaryQuery: true,
263
- })
264
- for (const [name, query] of Object.entries(queries)) {
265
- query.label = name
266
- }
267
- const initialQueryResults = mapValues(
268
- queries,
269
- (query) => query.results$.result,
270
- // TODO improve typing
271
- ) as unknown as QueryResults<TQueries>
272
-
273
- return { initialComponentState, initialQueryResults }
274
- } finally {
275
- span.end()
276
- }
145
+ const state$ = React.useMemo(() => {
146
+ console.log('useComponentState make state$', labelForKey(componentKey))
147
+ // create state query
148
+ if (stateSchema === undefined) {
149
+ // TODO don't set up a query if there's no state schema (keeps the graph more clean)
150
+ return new LiveStoreJSQuery({
151
+ fn: () => ({}) as TComponentState,
152
+ label: 'empty-component-state',
153
+ // otelContext,
154
+ // otelTracer: store.otel.tracer,
277
155
  })
278
- })
156
+ } else {
157
+ const componentTableName = tableNameForComponentKey(componentKey)
158
+ const whereClause = componentKey._tag === 'singleton' ? '' : `where id = '${componentKey.id}'`
159
+
160
+ // TODO find a better solution for this
161
+ if (store.tableRefs[componentTableName] === undefined) {
162
+ const schemaHash = SqliteAst.hash(stateSchema.ast)
163
+ const res = store.inMemoryDB.select<{ schemaHash: number }>(
164
+ sql`SELECT schemaHash FROM ${SCHEMA_META_TABLE} WHERE tableName = '${componentTableName}'`,
165
+ )
166
+ if (res.length === 0 || res[0]!.schemaHash !== schemaHash) {
167
+ migrateTable({ db: store._proxyDb, tableDef: stateSchema.ast, otelContext, schemaHash })
168
+ }
169
+
170
+ store.tableRefs[componentTableName] = store.graph.makeRef(null, {
171
+ equal: () => false,
172
+ label: componentTableName,
173
+ meta: { liveStoreRefType: 'table' },
174
+ })
175
+ }
176
+
177
+ return (
178
+ new LiveStoreSQLQuery({
179
+ label: `localState:query:${componentKeyLabel}`,
180
+ genQueryString: () => sql`select * from ${componentTableName} ${whereClause} limit 1`,
181
+ queriedTables: [componentTableName],
182
+ })
183
+ // TODO consider to instead of just returning the default value, to write the default component state to the DB
184
+ .pipe<TComponentState>((results) =>
185
+ results.length === 1 ? Schema.parseSync(componentStateEffectSchema)(results[0]!) : defaultComponentState,
186
+ )
187
+ )
188
+ }
279
189
  }, [
280
- store,
281
- otelContext,
282
- stateSchema,
283
- generateQueries,
284
190
  componentKey,
285
191
  componentKeyLabel,
286
192
  componentStateEffectSchema,
287
193
  defaultComponentState,
194
+ otelContext,
195
+ stateSchema,
196
+ store,
288
197
  ])
289
198
 
199
+ // Step 1:
200
+ // Synchronously create state and queries for initial render pass.
201
+ const initialComponentState = React.useMemo(() => state$.run(otelContext), [otelContext, state$])
202
+
290
203
  // Now that we've computed the initial state synchronously,
291
204
  // we can set up our useState calls w/ a default value populated...
292
205
  const [componentStateRef, setComponentState_] = useStateRefWithReactiveInput<TComponentState>(initialComponentState)
293
206
 
294
- const [queryResultsRef, setQueryResults_] = useStateRefWithReactiveInput<QueryResults<TQueries>>(initialQueryResults)
295
-
296
207
  const setState = (
297
208
  stateSchema === undefined
298
209
  ? {}
@@ -329,44 +240,30 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
329
240
  return store.applyEvent('updateComponentState', { componentKey, columnNames, ...columnValues })
330
241
  }
331
242
 
243
+ const subscriptionInfo = React.useMemo(() => {
244
+ Error.stackTraceLimit = 10
245
+ // eslint-disable-next-line unicorn/error-message
246
+ const stack = new Error().stack!
247
+ Error.stackTraceLimit = originalStackLimit
248
+ return { stack: extractStackInfoFromStackTrace(stack) }
249
+ }, [])
250
+
332
251
  // OK, now all the synchronous work is done;
333
252
  // time to set up our long-running queries in an effect
334
253
  React.useEffect(() => {
335
254
  return store.otel.tracer.startActiveSpan(
336
- 'LiveStore:useLiveStoreComponent:long-running',
255
+ 'LiveStore:useComponentState:long-running',
337
256
  { attributes: {} },
338
257
  otelContext,
339
258
  (span) => {
340
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
341
259
  const unsubs: (() => void)[] = []
342
260
 
343
- // create state query
344
- let state$: LiveStoreJSQuery<TComponentState>
345
- if (stateSchema === undefined) {
346
- // TODO remove this query
347
- state$ = store.queryJS(() => ({}) as TComponentState, {
348
- componentKey,
349
- otelContext,
350
- label: 'empty-component-state',
351
- })
352
- } else {
353
- const componentTableName = tableNameForComponentKey(componentKey)
261
+ if (stateSchema !== undefined) {
354
262
  insertRowForComponentInstance({ store, componentKey, stateSchema })
355
-
356
- const whereClause = componentKey._tag === 'singleton' ? '' : `where id = '${componentKey.id}'`
357
- state$ = store
358
- .querySQL<TComponentState>(() => sql`select * from ${componentTableName} ${whereClause} limit 1`, {
359
- queriedTables: [componentTableName],
360
- componentKey,
361
- label: `localState:query:${componentKeyLabel}`,
362
- otelContext,
363
- })
364
- // TODO consider to instead of just returning the default value, to write the default component state to the DB
365
- .pipe<TComponentState>((results) =>
366
- results.length === 1 ? Schema.parseSync(componentStateEffectSchema)(results[0]!) : defaultComponentState,
367
- )
368
263
  }
369
264
 
265
+ state$.activeSubscriptions.add(subscriptionInfo)
266
+
370
267
  unsubs.push(
371
268
  store.subscribe(
372
269
  state$,
@@ -376,44 +273,11 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
376
273
  }
377
274
  },
378
275
  undefined,
379
- { label: `useLiveStoreComponent:localState:subscribe:${state$.label}` },
276
+ { label: `useComponentState:localState:subscribe:${state$.label}` },
380
277
  ),
278
+ () => state$.activeSubscriptions.delete(subscriptionInfo),
381
279
  )
382
280
 
383
- const registerSubscription: RegisterSubscription = (query, callback, onUnsubscribe) => {
384
- unsubs.push(
385
- store.subscribe(
386
- query,
387
- (results) => {
388
- callback(results)
389
- },
390
- onUnsubscribe,
391
- { label: `useLiveStoreComponent:query:manual-subscribe:${query.label}` },
392
- ),
393
- )
394
- }
395
-
396
- const queries = generateQueries({ state$, otelContext, registerSubscription, isTemporaryQuery: false })
397
-
398
- for (const [key, query] of Object.entries(queries)) {
399
- // Use the field name given to this query in the useQueries hook as its label
400
- query.label = key
401
-
402
- unsubs.push(
403
- store.subscribe(
404
- query,
405
- (results) => {
406
- const newQueryResults = { ...queryResultsRef.current, [key]: results }
407
- if (isEqual(newQueryResults, queryResultsRef.current) === false) {
408
- setQueryResults_(newQueryResults)
409
- }
410
- },
411
- undefined,
412
- { label: `useLiveStoreComponent:query:subscribe:${query.label}` },
413
- ),
414
- )
415
- }
416
-
417
281
  return () => {
418
282
  for (const unsub of unsubs) {
419
283
  unsub()
@@ -425,24 +289,28 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
425
289
  )
426
290
  // NOTE excluding `setComponentState_` and `setQueryResults_` from the deps array as it seems to cause an infinite loop
427
291
  // This should probably be improved
428
- // eslint-disable-next-line react-hooks/exhaustive-deps
292
+ // TODO is this still true?
293
+ // // eslint-disable-next-line react-hooks/exhaustive-deps
429
294
  }, [
430
295
  store,
431
- componentKey,
296
+ subscriptionInfo,
432
297
  stateSchema,
433
298
  defaultComponentState,
434
- generateQueries,
435
299
  otelContext,
436
300
  componentStateRef,
437
- // setComponentState_,
438
- // setQueryResults_,
301
+ state$,
302
+ setComponentState_,
303
+ componentKey,
439
304
  ])
440
305
 
441
306
  // Very important: remove any queries / other resources associated w/ this component
442
- React.useEffect(() => () => store.unmountComponent(componentKey), [store, componentKey])
443
-
444
- // performance.mark('useLiveStoreComponent:end')
445
- // performance.measure(`useLiveStoreComponent:${componentKey.type}`, 'useLiveStoreComponent:start', 'useLiveStoreComponent:end')
307
+ React.useEffect(
308
+ () => () => {
309
+ console.log('useComponentState destroy', labelForKey(componentKey))
310
+ return state$.destroy()
311
+ },
312
+ [state$],
313
+ )
446
314
 
447
315
  const state = componentStateRef.current
448
316
 
@@ -472,7 +340,7 @@ export const useLiveStoreComponent = <TColumns extends ComponentColumns, TQuerie
472
340
  }
473
341
 
474
342
  return {
475
- queryResults: queryResultsRef.current,
343
+ state$,
476
344
  state,
477
345
  setState,
478
346
  useLiveStoreJsonState,
@@ -0,0 +1,58 @@
1
+ import { isEqual } from 'lodash-es'
2
+ import React from 'react'
3
+
4
+ import type { ILiveStoreQuery } from '../reactiveQueries/base-class.js'
5
+ import { useStore } from './LiveStoreContext.js'
6
+ import { extractStackInfoFromStackTrace, originalStackLimit } from './utils/extractStackInfoFromStackTrace.js'
7
+ import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInput.js'
8
+
9
+ export const useQuery = <TResult>(query: ILiveStoreQuery<TResult>): TResult => {
10
+ const { store } = useStore()
11
+
12
+ // TODO proper otel context
13
+ const initialResult = React.useMemo(() => query.run(), [query])
14
+
15
+ // We know the query has a result by the time we use it; so we can synchronously populate a default state
16
+ const [valueRef, setValue] = useStateRefWithReactiveInput<TResult>(initialResult)
17
+
18
+ const subscriptionInfo = React.useMemo(() => {
19
+ Error.stackTraceLimit = 10
20
+ // eslint-disable-next-line unicorn/error-message
21
+ const stack = new Error().stack!
22
+ Error.stackTraceLimit = originalStackLimit
23
+ return { stack: extractStackInfoFromStackTrace(stack) }
24
+ }, [])
25
+
26
+ // Subscribe to future updates for this query
27
+ React.useEffect(() => {
28
+ return store.otel.tracer.startActiveSpan(
29
+ `LiveStore:useQuery:${query.label}`,
30
+ // `LiveStore:useQuery:${labelForKey(query.componentKey)}:${query.label}`,
31
+ { attributes: { label: query.label } },
32
+ store.otel.queriesSpanContext,
33
+ (span) => {
34
+ query.activeSubscriptions.add(subscriptionInfo)
35
+ const unsub = store.subscribe(
36
+ query,
37
+ (v) => {
38
+ // NOTE: we return a reference to the result object within LiveStore;
39
+ // this implies that app code must not mutate the results, or else
40
+ // there may be weird reactivity bugs.
41
+ if (isEqual(v, valueRef.current) === false) {
42
+ setValue(v)
43
+ }
44
+ },
45
+ undefined,
46
+ { label: query.label },
47
+ )
48
+ return () => {
49
+ query.activeSubscriptions.delete(subscriptionInfo)
50
+ unsub()
51
+ span.end()
52
+ }
53
+ },
54
+ )
55
+ }, [subscriptionInfo, query, setValue, store, valueRef])
56
+
57
+ return valueRef.current
58
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react'
2
+
3
+ import type { ILiveStoreQuery } from '../reactiveQueries/base-class.js'
4
+ import { useQuery } from './useQuery.js'
5
+
6
+ /**
7
+ * Creates a query, subscribes and destroys it when the component unmounts.
8
+ *
9
+ * Make sure `makeQuery` is a memoized function.
10
+ */
11
+ export const useTemporaryQuery = <TResult>(makeQuery: () => ILiveStoreQuery<TResult>): TResult => {
12
+ const query = React.useMemo(() => makeQuery(), [makeQuery])
13
+
14
+ React.useEffect(() => {
15
+ return () => {
16
+ query.destroy()
17
+ }
18
+ }, [query])
19
+
20
+ return useQuery(query)
21
+ }