@livestore/livestore 0.0.37 → 0.0.39-dev.2

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 (117) hide show
  1. package/README.md +3 -4
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/react/fixture.d.ts +97 -3
  4. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  5. package/dist/__tests__/react/fixture.js +10 -7
  6. package/dist/__tests__/react/fixture.js.map +1 -1
  7. package/dist/__tests__/react/useQuery.test.js +12 -23
  8. package/dist/__tests__/react/useQuery.test.js.map +1 -1
  9. package/dist/__tests__/react/useRow.test.js +4 -4
  10. package/dist/__tests__/react/useRow.test.js.map +1 -1
  11. package/dist/__tests__/reactiveQueries/sql.test.js +32 -35
  12. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -1
  13. package/dist/effect/LiveStore.d.ts +3 -2
  14. package/dist/effect/LiveStore.d.ts.map +1 -1
  15. package/dist/effect/LiveStore.js.map +1 -1
  16. package/dist/index.d.ts +6 -6
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/migrations.js +2 -2
  21. package/dist/migrations.js.map +1 -1
  22. package/dist/mutations.d.ts +3 -1
  23. package/dist/mutations.d.ts.map +1 -1
  24. package/dist/mutations.js +2 -2
  25. package/dist/mutations.js.map +1 -1
  26. package/dist/react/LiveStoreContext.d.ts +2 -2
  27. package/dist/react/LiveStoreContext.d.ts.map +1 -1
  28. package/dist/react/LiveStoreContext.js.map +1 -1
  29. package/dist/react/index.d.ts +1 -0
  30. package/dist/react/index.d.ts.map +1 -1
  31. package/dist/react/index.js +1 -0
  32. package/dist/react/index.js.map +1 -1
  33. package/dist/react/useAtom.d.ts +5 -0
  34. package/dist/react/useAtom.d.ts.map +1 -0
  35. package/dist/react/useAtom.js +16 -0
  36. package/dist/react/useAtom.js.map +1 -0
  37. package/dist/react/useQuery.d.ts +3 -3
  38. package/dist/react/useQuery.d.ts.map +1 -1
  39. package/dist/react/useQuery.js.map +1 -1
  40. package/dist/react/useRow.d.ts +5 -5
  41. package/dist/react/useRow.d.ts.map +1 -1
  42. package/dist/react/useRow.js +16 -30
  43. package/dist/react/useRow.js.map +1 -1
  44. package/dist/react/useTemporaryQuery.d.ts +3 -3
  45. package/dist/react/useTemporaryQuery.d.ts.map +1 -1
  46. package/dist/react/useTemporaryQuery.js.map +1 -1
  47. package/dist/reactiveQueries/base-class.d.ts +12 -4
  48. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  49. package/dist/reactiveQueries/base-class.js +1 -0
  50. package/dist/reactiveQueries/base-class.js.map +1 -1
  51. package/dist/reactiveQueries/graphql.d.ts +5 -5
  52. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  53. package/dist/reactiveQueries/graphql.js +11 -10
  54. package/dist/reactiveQueries/graphql.js.map +1 -1
  55. package/dist/reactiveQueries/js.d.ts +10 -7
  56. package/dist/reactiveQueries/js.d.ts.map +1 -1
  57. package/dist/reactiveQueries/js.js +19 -11
  58. package/dist/reactiveQueries/js.js.map +1 -1
  59. package/dist/reactiveQueries/sql.d.ts +21 -15
  60. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  61. package/dist/reactiveQueries/sql.js +50 -28
  62. package/dist/reactiveQueries/sql.js.map +1 -1
  63. package/dist/row-query.d.ts +22 -21
  64. package/dist/row-query.d.ts.map +1 -1
  65. package/dist/row-query.js +62 -47
  66. package/dist/row-query.js.map +1 -1
  67. package/dist/schema/index.d.ts +3 -2
  68. package/dist/schema/index.d.ts.map +1 -1
  69. package/dist/schema/index.js +3 -2
  70. package/dist/schema/index.js.map +1 -1
  71. package/dist/schema/parse-utils.d.ts +6 -0
  72. package/dist/schema/parse-utils.d.ts.map +1 -0
  73. package/dist/schema/parse-utils.js +59 -0
  74. package/dist/schema/parse-utils.js.map +1 -0
  75. package/dist/schema/system-tables.d.ts +24 -8
  76. package/dist/schema/system-tables.d.ts.map +1 -1
  77. package/dist/schema/table-def.d.ts +32 -7
  78. package/dist/schema/table-def.d.ts.map +1 -1
  79. package/dist/schema/table-def.js +18 -6
  80. package/dist/schema/table-def.js.map +1 -1
  81. package/dist/store.d.ts +4 -8
  82. package/dist/store.d.ts.map +1 -1
  83. package/dist/store.js +7 -8
  84. package/dist/store.js.map +1 -1
  85. package/dist/update-path.d.ts +52 -0
  86. package/dist/update-path.d.ts.map +1 -0
  87. package/dist/update-path.js +33 -0
  88. package/dist/update-path.js.map +1 -0
  89. package/dist/utils/util.d.ts +1 -0
  90. package/dist/utils/util.d.ts.map +1 -1
  91. package/dist/utils/util.js.map +1 -1
  92. package/package.json +4 -4
  93. package/src/__tests__/react/fixture.tsx +13 -7
  94. package/src/__tests__/react/useQuery.test.tsx +12 -29
  95. package/src/__tests__/react/useRow.test.tsx +5 -7
  96. package/src/__tests__/reactiveQueries/sql.test.ts +33 -35
  97. package/src/effect/LiveStore.ts +3 -2
  98. package/src/index.ts +6 -6
  99. package/src/migrations.ts +2 -2
  100. package/src/mutations.ts +8 -3
  101. package/src/react/LiveStoreContext.ts +3 -2
  102. package/src/react/index.ts +1 -0
  103. package/src/react/useAtom.ts +25 -0
  104. package/src/react/useQuery.ts +7 -7
  105. package/src/react/useRow.ts +27 -47
  106. package/src/react/useTemporaryQuery.ts +4 -6
  107. package/src/reactiveQueries/base-class.ts +18 -4
  108. package/src/reactiveQueries/graphql.ts +16 -14
  109. package/src/reactiveQueries/js.ts +36 -15
  110. package/src/reactiveQueries/sql.ts +77 -37
  111. package/src/row-query.ts +155 -113
  112. package/src/schema/index.ts +5 -4
  113. package/src/schema/parse-utils.ts +84 -0
  114. package/src/schema/table-def.ts +80 -12
  115. package/src/store.ts +14 -29
  116. package/src/update-path.ts +102 -0
  117. package/src/utils/util.ts +2 -0
@@ -1,12 +1,13 @@
1
1
  import React, { useContext } from 'react'
2
2
 
3
3
  import type { LiveStoreContext as LiveStoreContext_ } from '../effect/LiveStore.js'
4
- import type { LiveStoreQuery } from '../store.js'
4
+ import type { LiveQuery } from '../reactiveQueries/base-class.js'
5
5
 
6
+ // TODO remove this?
6
7
  declare global {
7
8
  // NOTE Can be extended
8
9
  interface LiveStoreQueryTypes {
9
- [key: string]: LiveStoreQuery
10
+ [key: string]: LiveQuery<any>
10
11
  }
11
12
  }
12
13
 
@@ -10,6 +10,7 @@ export {
10
10
  type Dispatch,
11
11
  type UseRowResult as UseStateResult,
12
12
  } from './useRow.js'
13
+ export { useAtom } from './useAtom.js'
13
14
 
14
15
  // Needed to make TS happy
15
16
  export type { TypedDocumentNode } from '@graphql-typed-document-node/core'
@@ -0,0 +1,25 @@
1
+ import React from 'react'
2
+
3
+ import type { LiveQuery } from '../reactiveQueries/base-class.js'
4
+ import { storeEventForUpdatePath, type UpdatePathDescCol, type UpdatePathDescRow } from '../update-path.js'
5
+ import { useStore } from './LiveStoreContext.js'
6
+ import { useQueryRef } from './useQuery.js'
7
+ import type { Dispatch, SetStateAction } from './useRow.js'
8
+
9
+ export const useAtom = <TQuery extends LiveQuery<any, UpdatePathDescRow<any> | UpdatePathDescCol<any, any>>>(
10
+ query$: TQuery,
11
+ ): [value: TQuery['result!'], setValue: Dispatch<SetStateAction<TQuery['result!']>>] => {
12
+ const query$Ref = useQueryRef(query$)
13
+
14
+ const { store } = useStore()
15
+
16
+ const setValue = React.useMemo<Dispatch<SetStateAction<TQuery['result!']>>>(() => {
17
+ return (newValueOrFn: any) => {
18
+ const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
19
+
20
+ store.applyEvents([storeEventForUpdatePath(query$.updatePathDesc!, newValue)])
21
+ }
22
+ }, [query$.updatePathDesc, query$Ref, store])
23
+
24
+ return [query$Ref.current, setValue]
25
+ }
@@ -2,7 +2,7 @@ import * as otel from '@opentelemetry/api'
2
2
  import { isEqual } from 'lodash-es'
3
3
  import React from 'react'
4
4
 
5
- import type { ILiveStoreQuery } from '../reactiveQueries/base-class.js'
5
+ import type { GetResult, LiveQuery, LiveQueryAny } from '../reactiveQueries/base-class.js'
6
6
  import { useStore } from './LiveStoreContext.js'
7
7
  import { extractStackInfoFromStackTrace, originalStackLimit } from './utils/stack-info.js'
8
8
  import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInput.js'
@@ -12,14 +12,14 @@ import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInp
12
12
  * so we need to "cache" the fact that we've already started a span for this component.
13
13
  * The map entry is being removed again in the `React.useEffect` call below.
14
14
  */
15
- const spanAlreadyStartedCache = new Map<ILiveStoreQuery<any>, { span: otel.Span; otelContext: otel.Context }>()
15
+ const spanAlreadyStartedCache = new Map<LiveQueryAny, { span: otel.Span; otelContext: otel.Context }>()
16
16
 
17
- export const useQuery = <TResult>(query: ILiveStoreQuery<TResult>): TResult => useQueryRef(query).current
17
+ export const useQuery = <TQuery extends LiveQueryAny>(query: TQuery): GetResult<TQuery> => useQueryRef(query).current
18
18
 
19
- export const useQueryRef = <TResult>(
20
- query: ILiveStoreQuery<TResult>,
19
+ export const useQueryRef = <TQuery extends LiveQueryAny>(
20
+ query: TQuery,
21
21
  parentOtelContext?: otel.Context,
22
- ): React.MutableRefObject<TResult> => {
22
+ ): React.MutableRefObject<GetResult<TQuery>> => {
23
23
  const { store } = useStore()
24
24
 
25
25
  React.useDebugValue(`LiveStore:useQuery:${query.id}:${query.label}`)
@@ -62,7 +62,7 @@ export const useQueryRef = <TResult>(
62
62
  )
63
63
 
64
64
  // We know the query has a result by the time we use it; so we can synchronously populate a default state
65
- const [valueRef, setValue] = useStateRefWithReactiveInput<TResult>(initialResult)
65
+ const [valueRef, setValue] = useStateRefWithReactiveInput<GetResult<TQuery>>(initialResult)
66
66
 
67
67
  React.useEffect(
68
68
  () => () => {
@@ -1,21 +1,21 @@
1
- import { Schema } from '@livestore/utils/effect'
2
1
  import * as otel from '@opentelemetry/api'
3
2
  import type { SqliteDsl } from 'effect-db-schema'
4
3
  import { mapValues } from 'lodash-es'
5
4
  import React from 'react'
6
5
 
7
- import type { DbGraph } from '../index.js'
8
- import type { LiveStoreJSQuery } from '../reactiveQueries/js.js'
9
- import type { RowQueryArgs, RowResult } from '../row-query.js'
6
+ import type { DbGraph, LiveQuery } from '../index.js'
7
+ import type { RowResult } from '../row-query.js'
10
8
  import { rowQuery } from '../row-query.js'
11
- import type { DefaultSqliteTableDef, TableDef, TableOptions } from '../schema/table-def.js'
9
+ import { type DefaultSqliteTableDef, type TableDef, tableIsSingleton, type TableOptions } from '../schema/table-def.js'
10
+ import type { UpdatePathDesc } from '../update-path.js'
11
+ import { storeEventForUpdatePath } from '../update-path.js'
12
12
  import { useStore } from './LiveStoreContext.js'
13
13
  import { useQueryRef } from './useQuery.js'
14
14
 
15
15
  export type UseRowResult<TTableDef extends TableDef> = [
16
16
  row: RowResult<TTableDef>,
17
17
  setRow: StateSetters<TTableDef>,
18
- query$: LiveStoreJSQuery<RowResult<TTableDef>>,
18
+ query$: LiveQuery<RowResult<TTableDef>, UpdatePathDesc>,
19
19
  ]
20
20
 
21
21
  export type UseRowOptionsDefaulValues<TTableDef extends TableDef> = {
@@ -31,7 +31,7 @@ export type UseRowOptionsBase = {
31
31
  *
32
32
  * - `row` is the current value of the row (fully decoded according to the table schema)
33
33
  * - `setRow` is a function that can be used to update the row (values will be encoded according to the table schema)
34
- * - `query$` is a `LiveStoreJSQuery` that e.g. can be used to subscribe to changes to the row
34
+ * - `query$` is a `LiveQuery` that e.g. can be used to subscribe to changes to the row
35
35
  *
36
36
  * If the table is a singleton table, `useRow` can be called without an `id` argument. Otherwise, the `id` argument is required.
37
37
  */
@@ -51,12 +51,12 @@ export const useRow: {
51
51
  idOrOptions?: string | UseRowOptionsBase,
52
52
  options_?: UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>,
53
53
  ): UseRowResult<TTableDef> => {
54
- const sqliteTableDef = table.schema
54
+ const sqliteTableDef = table.sqliteDef
55
55
  const id = typeof idOrOptions === 'string' ? idOrOptions : undefined
56
56
  const options: (UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>) | undefined =
57
57
  typeof idOrOptions === 'string' ? options_ : idOrOptions
58
58
  const { defaultValues, dbGraph } = options ?? {}
59
- type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['schema']['columns']>
59
+ type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['sqliteDef']['columns']>
60
60
 
61
61
  const { store } = useStore()
62
62
 
@@ -69,22 +69,26 @@ export const useRow: {
69
69
  cachedItem.span.addEvent('new-subscriber', { reactId })
70
70
 
71
71
  return {
72
- query$: cachedItem.query$ as LiveStoreJSQuery<RowResult<TTableDef>>,
72
+ query$: cachedItem.query$ as LiveQuery<RowResult<TTableDef>, UpdatePathDesc>,
73
73
  otelContext: cachedItem.otelContext,
74
74
  }
75
75
  }
76
76
 
77
77
  const span = store.otel.tracer.startSpan(
78
- `LiveStore:useState:${table.schema.name}${id === undefined ? '' : `:${id}`}`,
78
+ `LiveStore:useState:${table.sqliteDef.name}${id === undefined ? '' : `:${id}`}`,
79
79
  { attributes: { id } },
80
80
  store.otel.queriesSpanContext,
81
81
  )
82
82
 
83
83
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
84
84
 
85
- const query$ = table.options.isSingleton
86
- ? rowQuery({ table, store, otelContext, defaultValues, dbGraph } as RowQueryArgs<TTableDef>)
87
- : rowQuery({ table, store, id, otelContext, defaultValues, dbGraph } as RowQueryArgs<TTableDef>)
85
+ const query$ = tableIsSingleton(table)
86
+ ? (rowQuery(table, { otelContext, dbGraph }) as LiveQuery<RowResult<TTableDef>, UpdatePathDesc>)
87
+ : (rowQuery(table as TTableDef & { options: { isSingleton: false } }, id!, {
88
+ otelContext,
89
+ defaultValues: defaultValues!,
90
+ dbGraph,
91
+ }) as any as LiveQuery<RowResult<TTableDef>, UpdatePathDesc>)
88
92
 
89
93
  rcCache.set(table, id ?? 'singleton', query$, reactId, otelContext, span)
90
94
 
@@ -105,7 +109,7 @@ export const useRow: {
105
109
  [table, id, reactId],
106
110
  )
107
111
 
108
- const query$Ref = useQueryRef(query$, otelContext)
112
+ const query$Ref = useQueryRef(query$, otelContext) as React.MutableRefObject<RowResult<TTableDef>>
109
113
 
110
114
  const setState = React.useMemo<StateSetters<TTableDef>>(() => {
111
115
  if (table.isSingleColumn) {
@@ -113,14 +117,7 @@ export const useRow: {
113
117
  const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
114
118
  if (query$Ref.current === newValue) return
115
119
 
116
- const encodedValue = Schema.encodeSync(sqliteTableDef.columns['value']!.schema)(newValue)
117
-
118
- store.applyEvent('livestore.UpdateComponentState', {
119
- tableName: sqliteTableDef.name,
120
- columnNames: ['value'],
121
- id,
122
- bindValues: { ['value']: encodedValue },
123
- })
120
+ store.applyEvents([storeEventForUpdatePath(query$.updatePathDesc!, { value: newValue })])
124
121
  }
125
122
  } else {
126
123
  const setState = // TODO: do we have a better type for the values that can go in SQLite?
@@ -133,14 +130,7 @@ export const useRow: {
133
130
  // @ts-expect-error TODO fix typing
134
131
  if (query$Ref.current[columnName] === newValue) return
135
132
 
136
- const encodedValue = Schema.encodeSync(column.schema)(newValue)
137
-
138
- store.applyEvent('livestore.UpdateComponentState', {
139
- tableName: sqliteTableDef.name,
140
- columnNames: [columnName],
141
- id,
142
- bindValues: { [columnName]: encodedValue },
143
- })
133
+ store.applyEvents([storeEventForUpdatePath(query$.updatePathDesc!, { [columnName]: newValue })])
144
134
  })
145
135
 
146
136
  setState.setMany = (columnValuesOrFn: Partial<TComponentState>) => {
@@ -157,22 +147,12 @@ export const useRow: {
157
147
  return
158
148
  }
159
149
 
160
- const columnNames = Object.keys(columnValues)
161
- const bindValues = mapValues(columnValues, (value, columnName) =>
162
- Schema.encodeSync(sqliteTableDef.columns[columnName]!.schema)(value),
163
- )
164
-
165
- store.applyEvent('livestore.UpdateComponentState', {
166
- tableName: sqliteTableDef.name,
167
- columnNames,
168
- id,
169
- bindValues,
170
- })
150
+ store.applyEvents([storeEventForUpdatePath(query$.updatePathDesc!, { columnValues })])
171
151
  }
172
152
 
173
153
  return setState as any
174
154
  }
175
- }, [table.isSingleColumn, id, sqliteTableDef.columns, sqliteTableDef.name, store, query$Ref])
155
+ }, [query$.updatePathDesc, query$Ref, sqliteTableDef.columns, store, table.isSingleColumn])
176
156
 
177
157
  return [query$Ref.current, setState, query$]
178
158
  }
@@ -198,11 +178,11 @@ class RCCache {
198
178
  reactIds: Set<string>
199
179
  span: otel.Span
200
180
  otelContext: otel.Context
201
- query$: LiveStoreJSQuery<any>
181
+ query$: LiveQuery<any, any>
202
182
  }
203
183
  >
204
184
  >()
205
- private reverseCache = new Map<LiveStoreJSQuery<any>, [TableDef, string]>()
185
+ private reverseCache = new Map<LiveQuery<any, any>, [TableDef, string]>()
206
186
 
207
187
  get = (table: TableDef, id: string) => {
208
188
  const queries = this.cache.get(table)
@@ -213,7 +193,7 @@ class RCCache {
213
193
  set = (
214
194
  table: TableDef,
215
195
  id: string,
216
- query$: LiveStoreJSQuery<any>,
196
+ query$: LiveQuery<any, any>,
217
197
  reactId: string,
218
198
  otelContext: otel.Context,
219
199
  span: otel.Span,
@@ -227,7 +207,7 @@ class RCCache {
227
207
  this.reverseCache.set(query$, [table, id])
228
208
  }
229
209
 
230
- delete = (query$: LiveStoreJSQuery<any>) => {
210
+ delete = (query$: LiveQuery<any, any>) => {
231
211
  const item = this.reverseCache.get(query$)
232
212
  if (item === undefined) return
233
213
 
@@ -1,25 +1,23 @@
1
1
  import React from 'react'
2
2
 
3
- import type { ILiveStoreQuery } from '../reactiveQueries/base-class.js'
3
+ import type { LiveQuery } from '../reactiveQueries/base-class.js'
4
4
  import { useQueryRef } from './useQuery.js'
5
5
 
6
6
  /**
7
7
  * This is needed because the `React.useMemo` call below, can sometimes be called multiple times 🤷.
8
8
  * The map entry is being removed again in the `React.useEffect` call below.
9
9
  */
10
- const queryCache = new Map<() => ILiveStoreQuery<any>, { reactIds: Set<string>; query$: ILiveStoreQuery<any> }>()
10
+ const queryCache = new Map<() => LiveQuery<any>, { reactIds: Set<string>; query$: LiveQuery<any> }>()
11
11
 
12
12
  /**
13
13
  * Creates a query, subscribes and destroys it when the component unmounts.
14
14
  *
15
15
  * Make sure `makeQuery` is a memoized function.
16
16
  */
17
- export const useTemporaryQuery = <TResult>(makeQuery: () => ILiveStoreQuery<TResult>): TResult =>
17
+ export const useTemporaryQuery = <TResult>(makeQuery: () => LiveQuery<TResult>): TResult =>
18
18
  useTemporaryQueryRef(makeQuery).current
19
19
 
20
- export const useTemporaryQueryRef = <TResult>(
21
- makeQuery: () => ILiveStoreQuery<TResult>,
22
- ): React.MutableRefObject<TResult> => {
20
+ export const useTemporaryQueryRef = <TResult>(makeQuery: () => LiveQuery<TResult>): React.MutableRefObject<TResult> => {
23
21
  const reactId = React.useId()
24
22
 
25
23
  const query$ = React.useMemo(() => {
@@ -4,7 +4,7 @@ import ReactDOM from 'react-dom'
4
4
  import type { StackInfo } from '../react/utils/stack-info.js'
5
5
  import { type Atom, type GetAtom, ReactiveGraph, throwContextNotSetError, type Thunk } from '../reactive.js'
6
6
  import type { QueryDebugInfo, RefreshReason, Store } from '../store.js'
7
- import type { LiveStoreJSQuery } from './js.js'
7
+ import type { UpdatePathDesc, UpdatePathDescNone } from '../update-path.js'
8
8
 
9
9
  export type DbGraph = ReactiveGraph<RefreshReason, QueryDebugInfo, DbContext>
10
10
 
@@ -22,10 +22,16 @@ export type DbContext = {
22
22
 
23
23
  export type UnsubscribeQuery = () => void
24
24
 
25
+ export type GetResult<TQuery extends LiveQueryAny> = TQuery extends LiveQuery<infer TResult> ? TResult : unknown
26
+
25
27
  let queryIdCounter = 0
26
28
 
27
- export interface ILiveStoreQuery<TResult> {
29
+ export type LiveQueryAny = LiveQuery<any, UpdatePathDesc>
30
+
31
+ export interface LiveQuery<TResult, TUpdatePath extends UpdatePathDesc = UpdatePathDescNone> {
28
32
  id: number
33
+ _tag: 'js' | 'sql' | 'graphql'
34
+ 'result!': TResult
29
35
 
30
36
  /** A reactive thunk representing the query results */
31
37
  results$: Thunk<TResult, DbContext, RefreshReason>
@@ -37,10 +43,16 @@ export interface ILiveStoreQuery<TResult> {
37
43
  destroy(): void
38
44
 
39
45
  activeSubscriptions: Set<StackInfo>
46
+
47
+ updatePathDesc: TUpdatePath
40
48
  }
41
49
 
42
- export abstract class LiveStoreQueryBase<TResult> implements ILiveStoreQuery<TResult> {
50
+ export abstract class LiveStoreQueryBase<TResult, TUpdatePath extends UpdatePathDesc>
51
+ implements LiveQuery<TResult, TUpdatePath>
52
+ {
53
+ 'result!'!: TResult
43
54
  id = queryIdCounter++
55
+ abstract _tag: 'js' | 'sql' | 'graphql'
44
56
 
45
57
  /** Human-readable label for the query for debugging */
46
58
  abstract label: string
@@ -51,6 +63,8 @@ export abstract class LiveStoreQueryBase<TResult> implements ILiveStoreQuery<TRe
51
63
 
52
64
  protected abstract dbGraph: DbGraph
53
65
 
66
+ abstract updatePathDesc: TUpdatePath
67
+
54
68
  get runs() {
55
69
  return this.results$.recomputations
56
70
  }
@@ -75,7 +89,7 @@ export abstract class LiveStoreQueryBase<TResult> implements ILiveStoreQuery<TRe
75
89
  throwContextNotSetError(this.dbGraph)
76
90
  }
77
91
 
78
- export type GetAtomResult = <T>(atom: Atom<T, any, RefreshReason> | LiveStoreJSQuery<T>) => T
92
+ export type GetAtomResult = <T>(atom: Atom<T, any, RefreshReason> | LiveQuery<T, any>) => T
79
93
 
80
94
  export const makeGetAtomResult = (get: GetAtom, otelContext: otel.Context) => {
81
95
  const getAtom: GetAtomResult = (atom) => {
@@ -6,22 +6,22 @@ import * as graphql from 'graphql'
6
6
  import { globalDbGraph } from '../global-state.js'
7
7
  import type { Thunk } from '../reactive.js'
8
8
  import type { BaseGraphQLContext, RefreshReason, Store } from '../store.js'
9
+ import type { UpdatePathDescNone } from '../update-path.js'
9
10
  import { getDurationMsFromSpan } from '../utils/otel.js'
10
- import type { DbContext, DbGraph, GetAtomResult } from './base-class.js'
11
+ import type { DbContext, DbGraph, GetAtomResult, LiveQuery } from './base-class.js'
11
12
  import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
12
- import { LiveStoreJSQuery } from './js.js'
13
13
 
14
14
  export const queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
15
15
  document: DocumentNode<TResult, TVariableValues>,
16
16
  genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
17
17
  { label, dbGraph }: { label?: string; dbGraph?: DbGraph } = {},
18
- ) => new LiveStoreGraphQLQuery({ document, genVariableValues, label, dbGraph })
18
+ ): LiveQuery<TResult, UpdatePathDescNone> => new LiveStoreGraphQLQuery({ document, genVariableValues, label, dbGraph })
19
19
 
20
20
  export class LiveStoreGraphQLQuery<
21
21
  TResult extends Record<string, any>,
22
22
  TVariableValues extends Record<string, any>,
23
23
  TContext extends BaseGraphQLContext,
24
- > extends LiveStoreQueryBase<TResult> {
24
+ > extends LiveStoreQueryBase<TResult, UpdatePathDescNone> {
25
25
  _tag: 'graphql' = 'graphql'
26
26
 
27
27
  /** The abstract GraphQL query */
@@ -36,6 +36,8 @@ export class LiveStoreGraphQLQuery<
36
36
 
37
37
  protected dbGraph: DbGraph
38
38
 
39
+ updatePathDesc: UpdatePathDescNone = { _tag: 'None' }
40
+
39
41
  constructor({
40
42
  document,
41
43
  label,
@@ -102,16 +104,16 @@ export class LiveStoreGraphQLQuery<
102
104
  * Returns a new reactive query that contains the result of
103
105
  * running an arbitrary JS computation on the results of this SQL query.
104
106
  */
105
- pipe = <U>(fn: (result: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
106
- new LiveStoreJSQuery({
107
- fn: (get) => {
108
- const results = get(this.results$)
109
- return fn(results, get)
110
- },
111
- label: `${this.label}:js`,
112
- onDestroy: () => this.destroy(),
113
- dbGraph: this.dbGraph,
114
- })
107
+ // pipe = <U>(fn: (result: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
108
+ // new LiveStoreJSQuery({
109
+ // fn: (get) => {
110
+ // const results = get(this.results$)
111
+ // return fn(results, get)
112
+ // },
113
+ // label: `${this.label}:js`,
114
+ // onDestroy: () => this.destroy(),
115
+ // dbGraph: this.dbGraph,
116
+ // })
115
117
 
116
118
  queryOnce = ({
117
119
  document,
@@ -3,14 +3,30 @@ import * as otel from '@opentelemetry/api'
3
3
  import { globalDbGraph } from '../global-state.js'
4
4
  import type { Thunk } from '../reactive.js'
5
5
  import type { RefreshReason } from '../store.js'
6
+ import type { UpdatePathDesc, UpdatePathDescNone } from '../update-path.js'
6
7
  import { getDurationMsFromSpan } from '../utils/otel.js'
7
- import type { DbContext, DbGraph, GetAtomResult } from './base-class.js'
8
+ import type { DbContext, DbGraph, GetAtomResult, LiveQuery } from './base-class.js'
8
9
  import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
9
10
 
10
- export const queryJS = <TResult>(fn: (get: GetAtomResult) => TResult, options: { label: string; dbGraph?: DbGraph }) =>
11
- new LiveStoreJSQuery<TResult>({ fn, label: options.label, dbGraph: options.dbGraph })
12
-
13
- export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase<TResult> {
11
+ export const computed = <TResult, TUpdatePath extends UpdatePathDesc = UpdatePathDescNone>(
12
+ fn: (get: GetAtomResult) => TResult,
13
+ options?: {
14
+ label: string
15
+ dbGraph?: DbGraph
16
+ updatePathDesc?: TUpdatePath
17
+ },
18
+ ): LiveQuery<TResult, TUpdatePath> =>
19
+ new LiveStoreJSQuery<TResult, TUpdatePath>({
20
+ fn,
21
+ label: options?.label ?? fn.toString(),
22
+ dbGraph: options?.dbGraph,
23
+ updatePathDesc: options?.updatePathDesc,
24
+ })
25
+
26
+ export class LiveStoreJSQuery<
27
+ TResult,
28
+ TUpdatePath extends UpdatePathDesc = UpdatePathDescNone,
29
+ > extends LiveStoreQueryBase<TResult, TUpdatePath> {
14
30
  _tag: 'js' = 'js'
15
31
 
16
32
  /** A reactive thunk representing the query results */
@@ -20,6 +36,8 @@ export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase<TResult> {
20
36
 
21
37
  protected dbGraph: DbGraph
22
38
 
39
+ updatePathDesc: TUpdatePath
40
+
23
41
  /**
24
42
  * Currently only used for "nested destruction" of piped queries
25
43
  *
@@ -33,12 +51,14 @@ export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase<TResult> {
33
51
  label,
34
52
  onDestroy,
35
53
  dbGraph,
54
+ updatePathDesc,
36
55
  }: {
37
56
  label: string
38
57
  fn: (get: GetAtomResult) => TResult
39
58
  /** Currently only used for "nested destruction" of piped queries */
40
59
  onDestroy?: () => void
41
60
  dbGraph?: DbGraph
61
+ updatePathDesc?: TUpdatePath
42
62
  }) {
43
63
  super()
44
64
 
@@ -46,6 +66,7 @@ export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase<TResult> {
46
66
  this.label = label
47
67
 
48
68
  this.dbGraph = dbGraph ?? globalDbGraph
69
+ this.updatePathDesc = updatePathDesc ?? ({ _tag: 'None' } as TUpdatePath)
49
70
 
50
71
  const queryLabel = `${label}:results`
51
72
 
@@ -67,16 +88,16 @@ export class LiveStoreJSQuery<TResult> extends LiveStoreQueryBase<TResult> {
67
88
  )
68
89
  }
69
90
 
70
- pipe = <U>(fn: (result: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
71
- new LiveStoreJSQuery({
72
- fn: (get) => {
73
- const results = get(this.results$)
74
- return fn(results, get)
75
- },
76
- label: `${this.label}:js`,
77
- onDestroy: () => this.destroy(),
78
- dbGraph: this.dbGraph,
79
- })
91
+ // pipe = <U>(fn: (result: TResult, get: GetAtomResult) => U): LiveStoreJSQuery<U> =>
92
+ // new LiveStoreJSQuery({
93
+ // fn: (get) => {
94
+ // const results = get(this.results$)
95
+ // return fn(results, get)
96
+ // },
97
+ // label: `${this.label}:js`,
98
+ // onDestroy: () => this.destroy(),
99
+ // dbGraph: this.dbGraph,
100
+ // })
80
101
 
81
102
  destroy = () => {
82
103
  this.dbGraph.destroyNode(this.results$)