@livestore/livestore 0.0.39-dev.3 → 0.0.40

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 (215) hide show
  1. package/README.md +15 -24
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/react/fixture.d.ts +210 -35
  4. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  5. package/dist/__tests__/react/fixture.js +10 -29
  6. package/dist/__tests__/react/fixture.js.map +1 -1
  7. package/dist/cud.d.ts +28 -0
  8. package/dist/cud.d.ts.map +1 -0
  9. package/dist/cud.js +50 -0
  10. package/dist/cud.js.map +1 -0
  11. package/dist/cud.test.d.ts +2 -0
  12. package/dist/cud.test.d.ts.map +1 -0
  13. package/dist/cud.test.js +47 -0
  14. package/dist/cud.test.js.map +1 -0
  15. package/dist/inMemoryDatabase.d.ts +1 -1
  16. package/dist/inMemoryDatabase.d.ts.map +1 -1
  17. package/dist/inMemoryDatabase.js +1 -4
  18. package/dist/inMemoryDatabase.js.map +1 -1
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/migrations.d.ts.map +1 -1
  24. package/dist/migrations.js +11 -7
  25. package/dist/migrations.js.map +1 -1
  26. package/dist/query-info.d.ts +49 -0
  27. package/dist/query-info.d.ts.map +1 -0
  28. package/dist/{update-path.js → query-info.js} +4 -3
  29. package/dist/query-info.js.map +1 -0
  30. package/dist/react/LiveStoreContext.d.ts +0 -6
  31. package/dist/react/LiveStoreContext.d.ts.map +1 -1
  32. package/dist/react/LiveStoreContext.js.map +1 -1
  33. package/dist/react/useAtom.d.ts +2 -2
  34. package/dist/react/useAtom.d.ts.map +1 -1
  35. package/dist/react/useAtom.js +3 -3
  36. package/dist/react/useAtom.js.map +1 -1
  37. package/dist/react/useQuery.d.ts.map +1 -1
  38. package/dist/react/useQuery.test.d.ts.map +1 -0
  39. package/dist/{__tests__/react → react}/useQuery.test.js +8 -11
  40. package/dist/react/useQuery.test.js.map +1 -0
  41. package/dist/react/useRow.d.ts +2 -2
  42. package/dist/react/useRow.d.ts.map +1 -1
  43. package/dist/react/useRow.js +5 -5
  44. package/dist/react/useRow.js.map +1 -1
  45. package/dist/react/useRow.test.d.ts.map +1 -0
  46. package/dist/{__tests__/react → react}/useRow.test.js +14 -38
  47. package/dist/react/useRow.test.js.map +1 -0
  48. package/dist/react/useTemporaryQuery.d.ts +2 -2
  49. package/dist/react/useTemporaryQuery.d.ts.map +1 -1
  50. package/dist/reactive.d.ts +2 -2
  51. package/dist/reactive.d.ts.map +1 -1
  52. package/dist/reactive.js +50 -15
  53. package/dist/reactive.js.map +1 -1
  54. package/dist/reactive.test.d.ts.map +1 -0
  55. package/dist/{__tests__/reactive.test.js → reactive.test.js} +1 -1
  56. package/dist/reactive.test.js.map +1 -0
  57. package/dist/reactiveQueries/base-class.d.ts +16 -8
  58. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  59. package/dist/reactiveQueries/base-class.js +2 -1
  60. package/dist/reactiveQueries/base-class.js.map +1 -1
  61. package/dist/reactiveQueries/graphql.d.ts +15 -9
  62. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  63. package/dist/reactiveQueries/graphql.js +29 -8
  64. package/dist/reactiveQueries/graphql.js.map +1 -1
  65. package/dist/reactiveQueries/js.d.ts +8 -8
  66. package/dist/reactiveQueries/js.d.ts.map +1 -1
  67. package/dist/reactiveQueries/js.js +5 -4
  68. package/dist/reactiveQueries/js.js.map +1 -1
  69. package/dist/reactiveQueries/sql.d.ts +11 -11
  70. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  71. package/dist/reactiveQueries/sql.js +7 -6
  72. package/dist/reactiveQueries/sql.js.map +1 -1
  73. package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
  74. package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.js +44 -34
  75. package/dist/reactiveQueries/sql.test.js.map +1 -0
  76. package/dist/row-query.d.ts +5 -5
  77. package/dist/row-query.d.ts.map +1 -1
  78. package/dist/row-query.js +11 -9
  79. package/dist/row-query.js.map +1 -1
  80. package/dist/schema/index.d.ts +20 -7
  81. package/dist/schema/index.d.ts.map +1 -1
  82. package/dist/schema/index.js +18 -3
  83. package/dist/schema/index.js.map +1 -1
  84. package/dist/schema/mutations.d.ts +81 -0
  85. package/dist/schema/mutations.d.ts.map +1 -0
  86. package/dist/schema/mutations.js +29 -0
  87. package/dist/schema/mutations.js.map +1 -0
  88. package/dist/schema/parse-utils.d.ts +3 -6
  89. package/dist/schema/parse-utils.d.ts.map +1 -1
  90. package/dist/schema/parse-utils.js +2 -27
  91. package/dist/schema/parse-utils.js.map +1 -1
  92. package/dist/schema/system-tables.d.ts +8 -8
  93. package/dist/schema/table-def.d.ts +6 -6
  94. package/dist/schema/table-def.d.ts.map +1 -1
  95. package/dist/schema/table-def.js +2 -2
  96. package/dist/schema/table-def.js.map +1 -1
  97. package/dist/storage/in-memory/index.d.ts +4 -0
  98. package/dist/storage/in-memory/index.d.ts.map +1 -1
  99. package/dist/storage/in-memory/index.js +3 -0
  100. package/dist/storage/in-memory/index.js.map +1 -1
  101. package/dist/storage/index.d.ts +4 -0
  102. package/dist/storage/index.d.ts.map +1 -1
  103. package/dist/storage/tauri/index.d.ts +4 -0
  104. package/dist/storage/tauri/index.d.ts.map +1 -1
  105. package/dist/storage/tauri/index.js +6 -0
  106. package/dist/storage/tauri/index.js.map +1 -1
  107. package/dist/storage/utils/idb.d.ts +1 -0
  108. package/dist/storage/utils/idb.d.ts.map +1 -1
  109. package/dist/storage/utils/idb.js +11 -0
  110. package/dist/storage/utils/idb.js.map +1 -1
  111. package/dist/storage/web-worker/common.d.ts +11 -0
  112. package/dist/storage/web-worker/common.d.ts.map +1 -0
  113. package/dist/storage/web-worker/common.js +2 -0
  114. package/dist/storage/web-worker/common.js.map +1 -0
  115. package/dist/storage/web-worker/index.d.ts +14 -7
  116. package/dist/storage/web-worker/index.d.ts.map +1 -1
  117. package/dist/storage/web-worker/index.js +70 -14
  118. package/dist/storage/web-worker/index.js.map +1 -1
  119. package/dist/storage/web-worker/make-worker.d.ts +20 -0
  120. package/dist/storage/web-worker/make-worker.d.ts.map +1 -0
  121. package/dist/storage/web-worker/make-worker.js +155 -0
  122. package/dist/storage/web-worker/make-worker.js.map +1 -0
  123. package/dist/storage/web-worker/vite-dev-polyfill.d.ts +2 -0
  124. package/dist/storage/web-worker/vite-dev-polyfill.d.ts.map +1 -0
  125. package/dist/storage/web-worker/vite-dev-polyfill.js +35 -0
  126. package/dist/storage/web-worker/vite-dev-polyfill.js.map +1 -0
  127. package/dist/store.d.ts +32 -42
  128. package/dist/store.d.ts.map +1 -1
  129. package/dist/store.js +82 -131
  130. package/dist/store.js.map +1 -1
  131. package/dist/utils/dev.d.ts +3 -0
  132. package/dist/utils/dev.d.ts.map +1 -0
  133. package/dist/utils/dev.js +16 -0
  134. package/dist/utils/dev.js.map +1 -0
  135. package/dist/utils/util.d.ts +2 -0
  136. package/dist/utils/util.d.ts.map +1 -1
  137. package/dist/utils/util.js +2 -0
  138. package/dist/utils/util.js.map +1 -1
  139. package/package.json +26 -14
  140. package/src/__tests__/react/fixture.tsx +12 -30
  141. package/src/cud.test.ts +52 -0
  142. package/src/cud.ts +92 -0
  143. package/src/inMemoryDatabase.ts +2 -7
  144. package/src/index.ts +14 -8
  145. package/src/migrations.ts +10 -7
  146. package/src/{update-path.ts → query-info.ts} +18 -21
  147. package/src/react/LiveStoreContext.ts +0 -9
  148. package/src/react/useAtom.ts +6 -6
  149. package/src/{__tests__/react → react}/useQuery.test.tsx +11 -11
  150. package/src/react/useQuery.ts +1 -1
  151. package/src/{__tests__/react → react}/useRow.test.tsx +21 -39
  152. package/src/react/useRow.ts +10 -10
  153. package/src/{__tests__/reactive.test.ts → reactive.test.ts} +1 -1
  154. package/src/reactive.ts +60 -19
  155. package/src/reactiveQueries/base-class.ts +23 -9
  156. package/src/reactiveQueries/graphql.ts +49 -13
  157. package/src/reactiveQueries/js.ts +15 -13
  158. package/src/{__tests__/reactiveQueries → reactiveQueries}/sql.test.ts +44 -34
  159. package/src/reactiveQueries/sql.ts +24 -22
  160. package/src/row-query.ts +24 -22
  161. package/src/schema/index.ts +47 -11
  162. package/src/schema/mutations.ts +129 -0
  163. package/src/schema/parse-utils.ts +3 -45
  164. package/src/schema/table-def.ts +9 -2
  165. package/src/storage/in-memory/index.ts +7 -0
  166. package/src/storage/index.ts +8 -0
  167. package/src/storage/tauri/index.ts +10 -0
  168. package/src/storage/utils/idb.ts +14 -0
  169. package/src/storage/web-worker/common.ts +6 -0
  170. package/src/storage/web-worker/index.ts +86 -17
  171. package/src/storage/web-worker/make-worker.ts +214 -0
  172. package/src/storage/web-worker/vite-dev-polyfill.ts +33 -0
  173. package/src/store.ts +142 -212
  174. package/src/utils/dev.ts +23 -0
  175. package/src/utils/util.ts +4 -0
  176. package/dist/__tests__/mutations.test.d.ts +0 -2
  177. package/dist/__tests__/mutations.test.d.ts.map +0 -1
  178. package/dist/__tests__/mutations.test.js +0 -40
  179. package/dist/__tests__/mutations.test.js.map +0 -1
  180. package/dist/__tests__/react/useQuery.test.d.ts.map +0 -1
  181. package/dist/__tests__/react/useQuery.test.js.map +0 -1
  182. package/dist/__tests__/react/useRow.test.d.ts.map +0 -1
  183. package/dist/__tests__/react/useRow.test.js.map +0 -1
  184. package/dist/__tests__/reactive.test.d.ts.map +0 -1
  185. package/dist/__tests__/reactive.test.js.map +0 -1
  186. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +0 -1
  187. package/dist/__tests__/reactiveQueries/sql.test.js.map +0 -1
  188. package/dist/events.d.ts +0 -7
  189. package/dist/events.d.ts.map +0 -1
  190. package/dist/events.js +0 -2
  191. package/dist/events.js.map +0 -1
  192. package/dist/mutations.d.ts +0 -33
  193. package/dist/mutations.d.ts.map +0 -1
  194. package/dist/mutations.js +0 -38
  195. package/dist/mutations.js.map +0 -1
  196. package/dist/schema/action.d.ts +0 -30
  197. package/dist/schema/action.d.ts.map +0 -1
  198. package/dist/schema/action.js +0 -3
  199. package/dist/schema/action.js.map +0 -1
  200. package/dist/storage/web-worker/worker.d.ts +0 -13
  201. package/dist/storage/web-worker/worker.d.ts.map +0 -1
  202. package/dist/storage/web-worker/worker.js +0 -110
  203. package/dist/storage/web-worker/worker.js.map +0 -1
  204. package/dist/update-path.d.ts +0 -52
  205. package/dist/update-path.d.ts.map +0 -1
  206. package/dist/update-path.js.map +0 -1
  207. package/src/__tests__/mutations.test.ts +0 -43
  208. package/src/events.ts +0 -8
  209. package/src/mutations.ts +0 -79
  210. package/src/schema/action.ts +0 -41
  211. package/src/storage/web-worker/worker.ts +0 -141
  212. /package/dist/{__tests__/react → react}/useQuery.test.d.ts +0 -0
  213. /package/dist/{__tests__/react → react}/useRow.test.d.ts +0 -0
  214. /package/dist/{__tests__/reactive.test.d.ts → reactive.test.d.ts} +0 -0
  215. /package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.d.ts +0 -0
package/src/reactive.ts CHANGED
@@ -24,7 +24,7 @@
24
24
  /* eslint-disable prefer-arrow/prefer-arrow-functions */
25
25
 
26
26
  import type { PrettifyFlat } from '@livestore/utils'
27
- import { pick, shouldNeverHappen } from '@livestore/utils'
27
+ import { shouldNeverHappen } from '@livestore/utils'
28
28
  import type * as otel from '@opentelemetry/api'
29
29
  import { isEqual } from 'lodash-es'
30
30
 
@@ -94,7 +94,7 @@ export type DebugThunkInfo<T extends string = string> = {
94
94
  }
95
95
 
96
96
  export type DebugRefreshReasonBase =
97
- /** Usually in response to some `applyEvent`/`applyEvents` with `skipRefresh: true` */
97
+ /** Usually in response to some `mutate` calls with `skipRefresh: true` */
98
98
  | {
99
99
  _tag: 'runDeferredEffects'
100
100
  originalRefreshReasons?: ReadonlyArray<DebugRefreshReasonBase>
@@ -135,7 +135,7 @@ const unknownRefreshReason = () => {
135
135
 
136
136
  export type SerializedAtom = Readonly<
137
137
  PrettifyFlat<
138
- Pick<Atom<unknown, unknown, any>, '_tag' | 'id' | 'label' | 'meta'> & {
138
+ Pick<Atom<unknown, unknown, any>, '_tag' | 'id' | 'label' | 'meta' | 'isDirty'> & {
139
139
  sub: ReadonlyArray<string>
140
140
  super: ReadonlyArray<string>
141
141
  }
@@ -162,17 +162,6 @@ const uniqueNodeId = () => `node-${++nodeIdCounter}`
162
162
  let refreshInfoIdCounter = 0
163
163
  const uniqueRefreshInfoId = () => `refresh-info-${++refreshInfoIdCounter}`
164
164
 
165
- const serializeAtom = (atom: Atom<any, unknown, any>): SerializedAtom => ({
166
- ...pick(atom, ['_tag', 'id', 'label', 'meta', 'isDirty']),
167
- sub: Array.from(atom.sub).map((a) => a.id),
168
- super: Array.from(atom.super).map((a) => a.id),
169
- })
170
-
171
- const serializeEffect = (effect: Effect): SerializedEffect => ({
172
- ...pick(effect, ['_tag', 'id', 'label']),
173
- sub: Array.from(effect.sub).map((a) => a.id),
174
- })
175
-
176
165
  let globalGraphIdCounter = 0
177
166
  const uniqueGraphId = () => `graph-${++globalGraphIdCounter}`
178
167
 
@@ -515,11 +504,25 @@ export class ReactiveGraph<
515
504
  subComp.super.delete(superComp)
516
505
  }
517
506
 
518
- getSnapshot = (): ReactiveGraphSnapshot => ({
519
- atoms: Array.from(this.atoms).map(serializeAtom),
520
- effects: Array.from(this.effects).map(serializeEffect),
521
- deferredEffects: Array.from(this.deferredEffects.keys()).map((_) => _.id),
522
- })
507
+ // NOTE This function is performance-optimized (i.e. not using `Array.from`)
508
+ getSnapshot = (): ReactiveGraphSnapshot => {
509
+ const atoms: SerializedAtom[] = []
510
+ for (const atom of this.atoms) {
511
+ atoms.push(serializeAtom(atom))
512
+ }
513
+
514
+ const effects: SerializedEffect[] = []
515
+ for (const effect of this.effects) {
516
+ effects.push(serializeEffect(effect))
517
+ }
518
+
519
+ const deferredEffects: string[] = []
520
+ for (const [effect] of this.deferredEffects) {
521
+ deferredEffects.push(effect.id)
522
+ }
523
+
524
+ return { atoms, effects, deferredEffects }
525
+ }
523
526
 
524
527
  subscribeToRefresh = (cb: () => void) => {
525
528
  this.refreshCallbacks.add(cb)
@@ -561,3 +564,41 @@ const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh:
561
564
  export const throwContextNotSetError = (graph: ReactiveGraph<any, any, any>): never => {
562
565
  throw new Error(`LiveStore Error: \`context\` not set on ReactiveGraph (${graph.id})`)
563
566
  }
567
+
568
+ // NOTE This function is performance-optimized (i.e. not using `pick` and `Array.from`)
569
+ const serializeAtom = (atom: Atom<any, unknown, any>): SerializedAtom => {
570
+ const sub: string[] = []
571
+ for (const a of atom.sub) {
572
+ sub.push(a.id)
573
+ }
574
+
575
+ const super_: string[] = []
576
+ for (const a of atom.super) {
577
+ super_.push(a.id)
578
+ }
579
+
580
+ return {
581
+ _tag: atom._tag,
582
+ id: atom.id,
583
+ label: atom.label,
584
+ meta: atom.meta,
585
+ isDirty: atom.isDirty,
586
+ sub,
587
+ super: super_,
588
+ }
589
+ }
590
+
591
+ // NOTE This function is performance-optimized (i.e. not using `pick` and `Array.from`)
592
+ const serializeEffect = (effect: Effect): SerializedEffect => {
593
+ const sub: string[] = []
594
+ for (const a of effect.sub) {
595
+ sub.push(a.id)
596
+ }
597
+
598
+ return {
599
+ _tag: effect._tag,
600
+ id: effect.id,
601
+ label: effect.label,
602
+ sub,
603
+ }
604
+ }
@@ -1,10 +1,10 @@
1
1
  import type * as otel from '@opentelemetry/api'
2
2
  import ReactDOM from 'react-dom'
3
3
 
4
+ import type { QueryInfo, QueryInfoNone } from '../query-info.js'
4
5
  import type { StackInfo } from '../react/utils/stack-info.js'
5
6
  import { type Atom, type GetAtom, ReactiveGraph, throwContextNotSetError, type Thunk } from '../reactive.js'
6
7
  import type { QueryDebugInfo, RefreshReason, Store } from '../store.js'
7
- import type { UpdatePathDesc, UpdatePathDescNone } from '../update-path.js'
8
8
 
9
9
  export type DbGraph = ReactiveGraph<RefreshReason, QueryDebugInfo, DbContext>
10
10
 
@@ -26,12 +26,14 @@ export type GetResult<TQuery extends LiveQueryAny> = TQuery extends LiveQuery<in
26
26
 
27
27
  let queryIdCounter = 0
28
28
 
29
- export type LiveQueryAny = LiveQuery<any, UpdatePathDesc>
29
+ export type LiveQueryAny = LiveQuery<any, QueryInfo>
30
30
 
31
- export interface LiveQuery<TResult, TUpdatePath extends UpdatePathDesc = UpdatePathDescNone> {
31
+ export interface LiveQuery<TResult, TQueryInfo extends QueryInfo = QueryInfoNone> {
32
32
  id: number
33
33
  _tag: 'js' | 'sql' | 'graphql'
34
- 'result!': TResult
34
+
35
+ /** This should only be used on a type-level and doesn't hold any value during runtime */
36
+ '__result!': TResult
35
37
 
36
38
  /** A reactive thunk representing the query results */
37
39
  results$: Thunk<TResult, DbContext, RefreshReason>
@@ -40,19 +42,29 @@ export interface LiveQuery<TResult, TUpdatePath extends UpdatePathDesc = UpdateP
40
42
 
41
43
  run: (otelContext?: otel.Context, debugRefreshReason?: RefreshReason) => TResult
42
44
 
45
+ runAndDestroy: (otelContext?: otel.Context, debugRefreshReason?: RefreshReason) => TResult
46
+
43
47
  destroy(): void
44
48
 
49
+ subscribe(
50
+ onNewValue: (value: TResult) => void,
51
+ onUnsubsubscribe?: () => void,
52
+ options?: { label?: string; otelContext?: otel.Context },
53
+ ): () => void
54
+
45
55
  activeSubscriptions: Set<StackInfo>
46
56
 
47
- updatePathDesc: TUpdatePath
57
+ queryInfo: TQueryInfo
48
58
 
49
59
  runs: number
60
+
61
+ executionTimes: number[]
50
62
  }
51
63
 
52
- export abstract class LiveStoreQueryBase<TResult, TUpdatePath extends UpdatePathDesc>
53
- implements LiveQuery<TResult, TUpdatePath>
64
+ export abstract class LiveStoreQueryBase<TResult, TQueryInfo extends QueryInfo>
65
+ implements LiveQuery<TResult, TQueryInfo>
54
66
  {
55
- 'result!'!: TResult
67
+ '__result!'!: TResult
56
68
  id = queryIdCounter++
57
69
  abstract _tag: 'js' | 'sql' | 'graphql'
58
70
 
@@ -65,12 +77,14 @@ export abstract class LiveStoreQueryBase<TResult, TUpdatePath extends UpdatePath
65
77
 
66
78
  protected abstract dbGraph: DbGraph
67
79
 
68
- abstract updatePathDesc: TUpdatePath
80
+ abstract queryInfo: TQueryInfo
69
81
 
70
82
  get runs() {
71
83
  return this.results$.recomputations
72
84
  }
73
85
 
86
+ executionTimes: number[] = []
87
+
74
88
  abstract destroy: () => void
75
89
 
76
90
  run = (otelContext?: otel.Context, debugRefreshReason?: RefreshReason): TResult =>
@@ -1,34 +1,43 @@
1
1
  import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
2
- import { assertNever, shouldNeverHappen } from '@livestore/utils'
2
+ import { shouldNeverHappen } from '@livestore/utils'
3
+ import { Schema, TreeFormatter } from '@livestore/utils/effect'
3
4
  import * as otel from '@opentelemetry/api'
4
5
  import * as graphql from 'graphql'
5
6
 
6
7
  import { globalDbGraph } from '../global-state.js'
8
+ import type { QueryInfoNone } from '../query-info.js'
7
9
  import type { Thunk } from '../reactive.js'
8
10
  import type { BaseGraphQLContext, RefreshReason, Store } from '../store.js'
9
- import type { UpdatePathDescNone } from '../update-path.js'
10
11
  import { getDurationMsFromSpan } from '../utils/otel.js'
11
12
  import type { DbContext, DbGraph, GetAtomResult, LiveQuery } from './base-class.js'
12
13
  import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
13
14
 
14
- export const queryGraphQL = <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(
15
+ export type MapResult<To, From> = ((res: From, get: GetAtomResult) => To) | Schema.Schema<never, From, To>
16
+
17
+ export const queryGraphQL = <
18
+ TResult extends Record<string, any>,
19
+ TVariableValues extends Record<string, any>,
20
+ TResultMapped extends Record<string, any> = TResult,
21
+ >(
15
22
  document: DocumentNode<TResult, TVariableValues>,
16
23
  genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
17
- { label, dbGraph }: { label?: string; dbGraph?: DbGraph } = {},
18
- ): LiveQuery<TResult, UpdatePathDescNone> => new LiveStoreGraphQLQuery({ document, genVariableValues, label, dbGraph })
24
+ { label, dbGraph, map }: { label?: string; dbGraph?: DbGraph; map?: MapResult<TResultMapped, TResult> } = {},
25
+ ): LiveQuery<TResultMapped, QueryInfoNone> =>
26
+ new LiveStoreGraphQLQuery({ document, genVariableValues, label, dbGraph, map })
19
27
 
20
28
  export class LiveStoreGraphQLQuery<
21
29
  TResult extends Record<string, any>,
22
30
  TVariableValues extends Record<string, any>,
23
31
  TContext extends BaseGraphQLContext,
24
- > extends LiveStoreQueryBase<TResult, UpdatePathDescNone> {
32
+ TResultMapped extends Record<string, any> = TResult,
33
+ > extends LiveStoreQueryBase<TResultMapped, QueryInfoNone> {
25
34
  _tag: 'graphql' = 'graphql'
26
35
 
27
36
  /** The abstract GraphQL query */
28
37
  document: DocumentNode<TResult, TVariableValues>
29
38
 
30
39
  /** A reactive thunk representing the query results */
31
- results$: Thunk<TResult, DbContext, RefreshReason>
40
+ results$: Thunk<TResultMapped, DbContext, RefreshReason>
32
41
 
33
42
  variableValues$: Thunk<TVariableValues, DbContext, RefreshReason>
34
43
 
@@ -36,18 +45,22 @@ export class LiveStoreGraphQLQuery<
36
45
 
37
46
  protected dbGraph: DbGraph
38
47
 
39
- updatePathDesc: UpdatePathDescNone = { _tag: 'None' }
48
+ queryInfo: QueryInfoNone = { _tag: 'None' }
49
+
50
+ private mapResult
40
51
 
41
52
  constructor({
42
53
  document,
43
54
  label,
44
55
  genVariableValues,
45
56
  dbGraph,
57
+ map,
46
58
  }: {
47
59
  document: DocumentNode<TResult, TVariableValues>
48
60
  genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues)
49
61
  label?: string
50
62
  dbGraph?: DbGraph
63
+ map?: MapResult<TResultMapped, TResult>
51
64
  }) {
52
65
  super()
53
66
 
@@ -58,6 +71,23 @@ export class LiveStoreGraphQLQuery<
58
71
 
59
72
  this.dbGraph = dbGraph ?? globalDbGraph
60
73
 
74
+ this.mapResult =
75
+ map === undefined
76
+ ? (res: TResult) => res as any as TResultMapped
77
+ : Schema.isSchema(map)
78
+ ? (res: TResult) => {
79
+ const parseResult = Schema.decodeEither(map as Schema.Schema<never, TResult, TResultMapped>)(res)
80
+ if (parseResult._tag === 'Left') {
81
+ console.error(`Error parsing GraphQL query result: ${TreeFormatter.formatError(parseResult.left)}`)
82
+ return shouldNeverHappen(`Error parsing SQL query result: ${parseResult.left}`)
83
+ } else {
84
+ return parseResult.right as TResultMapped
85
+ }
86
+ }
87
+ : typeof map === 'function'
88
+ ? map
89
+ : shouldNeverHappen(`Invalid map function ${map}`)
90
+
61
91
  // TODO don't even create a thunk if variables are static
62
92
  const variableValues$ = this.dbGraph.makeThunk(
63
93
  (get, _setDebugInfo, { rootOtelContext }, otelContext) => {
@@ -73,7 +103,7 @@ export class LiveStoreGraphQLQuery<
73
103
  this.variableValues$ = variableValues$
74
104
 
75
105
  const resultsLabel = `${labelWithDefault}:results`
76
- this.results$ = this.dbGraph.makeThunk<TResult>(
106
+ this.results$ = this.dbGraph.makeThunk<TResultMapped>(
77
107
  (get, setDebugInfo, { store, otelTracer, rootOtelContext }, otelContext) => {
78
108
  const variableValues = get(variableValues$)
79
109
  const { result, queriedTables, durationMs } = this.queryOnce({
@@ -82,13 +112,13 @@ export class LiveStoreGraphQLQuery<
82
112
  otelContext: otelContext ?? rootOtelContext,
83
113
  otelTracer,
84
114
  store: store as Store<TContext>,
115
+ get: makeGetAtomResult(get, otelContext ?? rootOtelContext),
85
116
  })
86
117
 
87
118
  // Add dependencies on any tables that were used
88
119
  for (const tableName of queriedTables) {
89
- const tableRef = store.tableRefs[tableName]
90
- assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
91
- get(tableRef!)
120
+ const tableRef = store.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`)
121
+ get(tableRef)
92
122
  }
93
123
 
94
124
  setDebugInfo({ _tag: 'graphql', label: resultsLabel, query: graphql.print(document), durationMs })
@@ -121,12 +151,14 @@ export class LiveStoreGraphQLQuery<
121
151
  otelTracer,
122
152
  variableValues,
123
153
  store,
154
+ get,
124
155
  }: {
125
156
  document: graphql.DocumentNode
126
157
  otelContext: otel.Context
127
158
  otelTracer: otel.Tracer
128
159
  variableValues: TVariableValues
129
160
  store: Store<TContext>
161
+ get: GetAtomResult
130
162
  }) => {
131
163
  const schema =
132
164
  store.graphQLSchema ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL schema")
@@ -161,10 +193,14 @@ export class LiveStoreGraphQLQuery<
161
193
 
162
194
  span.end()
163
195
 
196
+ const result = this.mapResult(res.data as unknown as TResult, get)
197
+
164
198
  const durationMs = getDurationMsFromSpan(span)
165
199
 
200
+ this.executionTimes.push(durationMs)
201
+
166
202
  return {
167
- result: res.data as unknown as TResult,
203
+ result,
168
204
  queriedTables: Array.from(context.queriedTables.values()),
169
205
  durationMs,
170
206
  }
@@ -1,32 +1,32 @@
1
1
  import * as otel from '@opentelemetry/api'
2
2
 
3
3
  import { globalDbGraph } from '../global-state.js'
4
+ import type { QueryInfo, QueryInfoNone } from '../query-info.js'
4
5
  import type { Thunk } from '../reactive.js'
5
6
  import type { RefreshReason } from '../store.js'
6
- import type { UpdatePathDesc, UpdatePathDescNone } from '../update-path.js'
7
7
  import { getDurationMsFromSpan } from '../utils/otel.js'
8
8
  import type { DbContext, DbGraph, GetAtomResult, LiveQuery } from './base-class.js'
9
9
  import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
10
10
 
11
- export const computed = <TResult, TUpdatePath extends UpdatePathDesc = UpdatePathDescNone>(
11
+ export const computed = <TResult, TQueryInfo extends QueryInfo = QueryInfoNone>(
12
12
  fn: (get: GetAtomResult) => TResult,
13
13
  options?: {
14
14
  label: string
15
15
  dbGraph?: DbGraph
16
- updatePathDesc?: TUpdatePath
16
+ queryInfo?: TQueryInfo
17
17
  },
18
- ): LiveQuery<TResult, TUpdatePath> =>
19
- new LiveStoreJSQuery<TResult, TUpdatePath>({
18
+ ): LiveQuery<TResult, TQueryInfo> =>
19
+ new LiveStoreJSQuery<TResult, TQueryInfo>({
20
20
  fn,
21
21
  label: options?.label ?? fn.toString(),
22
22
  dbGraph: options?.dbGraph,
23
- updatePathDesc: options?.updatePathDesc,
23
+ queryInfo: options?.queryInfo,
24
24
  })
25
25
 
26
- export class LiveStoreJSQuery<
26
+ export class LiveStoreJSQuery<TResult, TQueryInfo extends QueryInfo = QueryInfoNone> extends LiveStoreQueryBase<
27
27
  TResult,
28
- TUpdatePath extends UpdatePathDesc = UpdatePathDescNone,
29
- > extends LiveStoreQueryBase<TResult, TUpdatePath> {
28
+ TQueryInfo
29
+ > {
30
30
  _tag: 'js' = 'js'
31
31
 
32
32
  /** A reactive thunk representing the query results */
@@ -36,7 +36,7 @@ export class LiveStoreJSQuery<
36
36
 
37
37
  protected dbGraph: DbGraph
38
38
 
39
- updatePathDesc: TUpdatePath
39
+ queryInfo: TQueryInfo
40
40
 
41
41
  /**
42
42
  * Currently only used for "nested destruction" of piped queries
@@ -51,14 +51,14 @@ export class LiveStoreJSQuery<
51
51
  label,
52
52
  onDestroy,
53
53
  dbGraph,
54
- updatePathDesc,
54
+ queryInfo,
55
55
  }: {
56
56
  label: string
57
57
  fn: (get: GetAtomResult) => TResult
58
58
  /** Currently only used for "nested destruction" of piped queries */
59
59
  onDestroy?: () => void
60
60
  dbGraph?: DbGraph
61
- updatePathDesc?: TUpdatePath
61
+ queryInfo?: TQueryInfo
62
62
  }) {
63
63
  super()
64
64
 
@@ -66,7 +66,7 @@ export class LiveStoreJSQuery<
66
66
  this.label = label
67
67
 
68
68
  this.dbGraph = dbGraph ?? globalDbGraph
69
- this.updatePathDesc = updatePathDesc ?? ({ _tag: 'None' } as TUpdatePath)
69
+ this.queryInfo = queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
70
70
 
71
71
  const queryLabel = `${label}:results`
72
72
 
@@ -80,6 +80,8 @@ export class LiveStoreJSQuery<
80
80
 
81
81
  const durationMs = getDurationMsFromSpan(span)
82
82
 
83
+ this.executionTimes.push(durationMs)
84
+
83
85
  setDebugInfo({ _tag: 'js', label, query: fn.toString(), durationMs })
84
86
 
85
87
  return res
@@ -3,8 +3,8 @@ import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
3
3
  import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
4
4
  import { describe, expect, it } from 'vitest'
5
5
 
6
- import { computed, ParseUtils, querySQL, sql } from '../../index.js'
7
- import { makeTodoMvc, todos } from '../react/fixture.js'
6
+ import { makeTodoMvc, todos } from '../__tests__/react/fixture.js'
7
+ import { computed, ParseUtils, querySQL, rawSqlMutation, sql } from '../index.js'
8
8
 
9
9
  /*
10
10
  TODO write tests for:
@@ -40,10 +40,7 @@ describe('otel', () => {
40
40
  const query = querySQL(`select * from todos`, { queriedTables: new Set(['todos']) })
41
41
  expect(query.run()).toMatchInlineSnapshot('[]')
42
42
 
43
- store.applyEvent('livestore.RawSql', {
44
- sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
45
- writeTables: ['todos'],
46
- })
43
+ store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
47
44
 
48
45
  expect(query.run()).toMatchInlineSnapshot(`
49
46
  [
@@ -91,28 +88,36 @@ describe('otel', () => {
91
88
  },
92
89
  },
93
90
  {
94
- "_name": "LiveStore:applyEvents",
91
+ "_name": "LiveStore:mutations",
95
92
  "children": [
96
93
  {
97
- "_name": "LiveStore:applyEvent",
94
+ "_name": "LiveStore:mutate",
95
+ "attributes": {
96
+ "livestore.mutateLabel": "mutate",
97
+ },
98
98
  "children": [
99
99
  {
100
- "_name": "LiveStore:applyEventWithoutRefresh",
100
+ "_name": "LiveStore:processWrites",
101
101
  "attributes": {
102
- "livestore.actionType": "livestore.RawSql",
103
- "livestore.args": "{
104
- "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
105
- "writeTables": [
106
- "todos"
107
- ]
108
- }",
102
+ "livestore.mutateLabel": "mutate",
109
103
  },
110
104
  "children": [
111
105
  {
112
- "_name": "livestore.in-memory-db:execute",
106
+ "_name": "LiveStore:mutatetWithoutRefresh",
113
107
  "attributes": {
114
- "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
108
+ "livestore.args": "{
109
+ "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)"
110
+ }",
111
+ "livestore.mutation": "livestore.RawSql",
115
112
  },
113
+ "children": [
114
+ {
115
+ "_name": "livestore.in-memory-db:execute",
116
+ "attributes": {
117
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
118
+ },
119
+ },
120
+ ],
116
121
  },
117
122
  ],
118
123
  },
@@ -183,10 +188,7 @@ describe('otel', () => {
183
188
  }
184
189
  `)
185
190
 
186
- store.applyEvent('livestore.RawSql', {
187
- sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
188
- writeTables: ['todos'],
189
- })
191
+ store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
190
192
 
191
193
  expect(query.run()).toMatchInlineSnapshot(`
192
194
  {
@@ -232,28 +234,36 @@ describe('otel', () => {
232
234
  },
233
235
  },
234
236
  {
235
- "_name": "LiveStore:applyEvents",
237
+ "_name": "LiveStore:mutations",
236
238
  "children": [
237
239
  {
238
- "_name": "LiveStore:applyEvent",
240
+ "_name": "LiveStore:mutate",
241
+ "attributes": {
242
+ "livestore.mutateLabel": "mutate",
243
+ },
239
244
  "children": [
240
245
  {
241
- "_name": "LiveStore:applyEventWithoutRefresh",
246
+ "_name": "LiveStore:processWrites",
242
247
  "attributes": {
243
- "livestore.actionType": "livestore.RawSql",
244
- "livestore.args": "{
245
- "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
246
- "writeTables": [
247
- "todos"
248
- ]
249
- }",
248
+ "livestore.mutateLabel": "mutate",
250
249
  },
251
250
  "children": [
252
251
  {
253
- "_name": "livestore.in-memory-db:execute",
252
+ "_name": "LiveStore:mutatetWithoutRefresh",
254
253
  "attributes": {
255
- "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
254
+ "livestore.args": "{
255
+ "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)"
256
+ }",
257
+ "livestore.mutation": "livestore.RawSql",
256
258
  },
259
+ "children": [
260
+ {
261
+ "_name": "livestore.in-memory-db:execute",
262
+ "attributes": {
263
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
264
+ },
265
+ },
266
+ ],
257
267
  },
258
268
  ],
259
269
  },