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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +1 -117
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/effect/LiveStore.d.ts +6 -6
  4. package/dist/effect/LiveStore.d.ts.map +1 -1
  5. package/dist/effect/LiveStore.js +1 -1
  6. package/dist/effect/LiveStore.js.map +1 -1
  7. package/dist/global-state.d.ts.map +1 -1
  8. package/dist/global-state.js +2 -1
  9. package/dist/global-state.js.map +1 -1
  10. package/dist/index.d.ts +11 -8
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +7 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/reactiveQueries/base-class.d.ts +5 -4
  15. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  16. package/dist/reactiveQueries/base-class.js.map +1 -1
  17. package/dist/reactiveQueries/computed.d.ts +35 -0
  18. package/dist/reactiveQueries/computed.d.ts.map +1 -0
  19. package/dist/reactiveQueries/computed.js +57 -0
  20. package/dist/reactiveQueries/computed.js.map +1 -0
  21. package/dist/reactiveQueries/graphql.d.ts +2 -1
  22. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  23. package/dist/reactiveQueries/graphql.js.map +1 -1
  24. package/dist/reactiveQueries/sql.d.ts +2 -2
  25. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  26. package/dist/reactiveQueries/sql.js +3 -3
  27. package/dist/reactiveQueries/sql.js.map +1 -1
  28. package/dist/reactiveQueries/sql.test.js +2 -2
  29. package/dist/reactiveQueries/sql.test.js.map +1 -1
  30. package/dist/row-query.d.ts +3 -2
  31. package/dist/row-query.d.ts.map +1 -1
  32. package/dist/row-query.js +14 -10
  33. package/dist/row-query.js.map +1 -1
  34. package/dist/store/create-store.d.ts +28 -0
  35. package/dist/store/create-store.d.ts.map +1 -0
  36. package/dist/store/create-store.js +85 -0
  37. package/dist/store/create-store.js.map +1 -0
  38. package/dist/store/devtools.d.ts +19 -0
  39. package/dist/store/devtools.d.ts.map +1 -0
  40. package/dist/store/devtools.js +141 -0
  41. package/dist/store/devtools.js.map +1 -0
  42. package/dist/store/store-context.d.ts +26 -0
  43. package/dist/store/store-context.d.ts.map +1 -0
  44. package/dist/store/store-context.js +6 -0
  45. package/dist/store/store-context.js.map +1 -0
  46. package/dist/store/store-types.d.ts +98 -0
  47. package/dist/store/store-types.d.ts.map +1 -0
  48. package/dist/store/store-types.js +6 -0
  49. package/dist/store/store-types.js.map +1 -0
  50. package/dist/store/store.d.ts +88 -0
  51. package/dist/store/store.d.ts.map +1 -0
  52. package/dist/store/store.js +367 -0
  53. package/dist/store/store.js.map +1 -0
  54. package/dist/store-devtools.d.ts +2 -2
  55. package/dist/store-devtools.d.ts.map +1 -1
  56. package/dist/store-devtools.js +2 -2
  57. package/dist/store-devtools.js.map +1 -1
  58. package/dist/store.d.ts +8 -8
  59. package/dist/store.d.ts.map +1 -1
  60. package/dist/store.js +54 -54
  61. package/dist/store.js.map +1 -1
  62. package/dist/utils/dev.d.ts +1 -0
  63. package/dist/utils/dev.d.ts.map +1 -1
  64. package/dist/utils/dev.js +5 -0
  65. package/dist/utils/dev.js.map +1 -1
  66. package/dist/{react/utils → utils}/stack-info.d.ts +1 -2
  67. package/dist/utils/stack-info.d.ts.map +1 -0
  68. package/dist/{react/utils → utils}/stack-info.js +1 -9
  69. package/dist/utils/stack-info.js.map +1 -0
  70. package/dist/utils/stack-info.test.d.ts.map +1 -0
  71. package/dist/{__tests__/react/utils → utils}/stack-info.test.js +1 -1
  72. package/dist/utils/stack-info.test.js.map +1 -0
  73. package/dist/utils/tests/fixture.d.ts +259 -0
  74. package/dist/utils/tests/fixture.d.ts.map +1 -0
  75. package/dist/utils/tests/fixture.js +32 -0
  76. package/dist/utils/tests/fixture.js.map +1 -0
  77. package/dist/utils/tests/mod.d.ts +3 -0
  78. package/dist/utils/tests/mod.d.ts.map +1 -0
  79. package/dist/utils/tests/mod.js +3 -0
  80. package/dist/utils/tests/mod.js.map +1 -0
  81. package/dist/utils/tests/otel.d.ts.map +1 -0
  82. package/dist/{__tests__/react/utils → utils/tests}/otel.js +3 -3
  83. package/dist/utils/tests/otel.js.map +1 -0
  84. package/package.json +13 -20
  85. package/src/ambient.d.ts +3 -1
  86. package/src/effect/LiveStore.ts +7 -7
  87. package/src/global-state.ts +5 -1
  88. package/src/index.ts +19 -7
  89. package/src/reactiveQueries/base-class.ts +5 -4
  90. package/src/reactiveQueries/{js.ts → computed.ts} +3 -3
  91. package/src/reactiveQueries/graphql.ts +2 -1
  92. package/src/reactiveQueries/sql.test.ts +2 -2
  93. package/src/reactiveQueries/sql.ts +5 -5
  94. package/src/row-query.ts +33 -17
  95. package/src/store/create-store.ts +214 -0
  96. package/src/{store-devtools.ts → store/devtools.ts} +9 -9
  97. package/src/store/store-types.ts +110 -0
  98. package/src/{store.ts → store/store.ts} +56 -415
  99. package/src/utils/dev.ts +6 -0
  100. package/src/{__tests__/react/utils → utils}/stack-info.test.ts +1 -1
  101. package/src/{react/utils → utils}/stack-info.ts +2 -12
  102. package/src/utils/tests/fixture.ts +73 -0
  103. package/src/utils/tests/mod.ts +2 -0
  104. package/src/{__tests__/react/utils → utils/tests}/otel.ts +4 -4
  105. package/tsconfig.json +1 -2
  106. package/vitest.config.js +0 -8
  107. package/dist/__tests__/react/fixture.d.ts +0 -461
  108. package/dist/__tests__/react/fixture.d.ts.map +0 -1
  109. package/dist/__tests__/react/fixture.js +0 -68
  110. package/dist/__tests__/react/fixture.js.map +0 -1
  111. package/dist/__tests__/react/utils/otel.d.ts.map +0 -1
  112. package/dist/__tests__/react/utils/otel.js.map +0 -1
  113. package/dist/__tests__/react/utils/stack-info.test.d.ts.map +0 -1
  114. package/dist/__tests__/react/utils/stack-info.test.js.map +0 -1
  115. package/dist/react/LiveStoreContext.d.ts +0 -7
  116. package/dist/react/LiveStoreContext.d.ts.map +0 -1
  117. package/dist/react/LiveStoreContext.js +0 -13
  118. package/dist/react/LiveStoreContext.js.map +0 -1
  119. package/dist/react/LiveStoreProvider.d.ts +0 -49
  120. package/dist/react/LiveStoreProvider.d.ts.map +0 -1
  121. package/dist/react/LiveStoreProvider.js +0 -169
  122. package/dist/react/LiveStoreProvider.js.map +0 -1
  123. package/dist/react/LiveStoreProvider.test.d.ts +0 -2
  124. package/dist/react/LiveStoreProvider.test.d.ts.map +0 -1
  125. package/dist/react/LiveStoreProvider.test.js +0 -62
  126. package/dist/react/LiveStoreProvider.test.js.map +0 -1
  127. package/dist/react/components/LiveList.d.ts +0 -21
  128. package/dist/react/components/LiveList.d.ts.map +0 -1
  129. package/dist/react/components/LiveList.js +0 -31
  130. package/dist/react/components/LiveList.js.map +0 -1
  131. package/dist/react/index.d.ts +0 -11
  132. package/dist/react/index.d.ts.map +0 -1
  133. package/dist/react/index.js +0 -10
  134. package/dist/react/index.js.map +0 -1
  135. package/dist/react/useAtom.d.ts +0 -10
  136. package/dist/react/useAtom.d.ts.map +0 -1
  137. package/dist/react/useAtom.js +0 -37
  138. package/dist/react/useAtom.js.map +0 -1
  139. package/dist/react/useLocalId.d.ts +0 -10
  140. package/dist/react/useLocalId.d.ts.map +0 -1
  141. package/dist/react/useLocalId.js +0 -22
  142. package/dist/react/useLocalId.js.map +0 -1
  143. package/dist/react/useQuery.d.ts +0 -9
  144. package/dist/react/useQuery.d.ts.map +0 -1
  145. package/dist/react/useQuery.js +0 -70
  146. package/dist/react/useQuery.js.map +0 -1
  147. package/dist/react/useQuery.test.d.ts +0 -2
  148. package/dist/react/useQuery.test.d.ts.map +0 -1
  149. package/dist/react/useQuery.test.js +0 -51
  150. package/dist/react/useQuery.test.js.map +0 -1
  151. package/dist/react/useRow.d.ts +0 -46
  152. package/dist/react/useRow.d.ts.map +0 -1
  153. package/dist/react/useRow.js +0 -94
  154. package/dist/react/useRow.js.map +0 -1
  155. package/dist/react/useRow.test.d.ts +0 -2
  156. package/dist/react/useRow.test.d.ts.map +0 -1
  157. package/dist/react/useRow.test.js +0 -208
  158. package/dist/react/useRow.test.js.map +0 -1
  159. package/dist/react/useTemporaryQuery.d.ts +0 -22
  160. package/dist/react/useTemporaryQuery.d.ts.map +0 -1
  161. package/dist/react/useTemporaryQuery.js +0 -75
  162. package/dist/react/useTemporaryQuery.js.map +0 -1
  163. package/dist/react/useTemporaryQuery.test.d.ts +0 -2
  164. package/dist/react/useTemporaryQuery.test.d.ts.map +0 -1
  165. package/dist/react/useTemporaryQuery.test.js +0 -59
  166. package/dist/react/useTemporaryQuery.test.js.map +0 -1
  167. package/dist/react/utils/stack-info.d.ts.map +0 -1
  168. package/dist/react/utils/stack-info.js.map +0 -1
  169. package/dist/react/utils/useStateRefWithReactiveInput.d.ts +0 -13
  170. package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +0 -1
  171. package/dist/react/utils/useStateRefWithReactiveInput.js +0 -38
  172. package/dist/react/utils/useStateRefWithReactiveInput.js.map +0 -1
  173. package/src/__tests__/react/fixture.tsx +0 -126
  174. package/src/react/LiveStoreContext.ts +0 -20
  175. package/src/react/LiveStoreProvider.test.tsx +0 -109
  176. package/src/react/LiveStoreProvider.tsx +0 -291
  177. package/src/react/__snapshots__/useRow.test.tsx.snap +0 -359
  178. package/src/react/components/LiveList.tsx +0 -84
  179. package/src/react/index.ts +0 -19
  180. package/src/react/useAtom.ts +0 -55
  181. package/src/react/useLocalId.ts +0 -34
  182. package/src/react/useQuery.test.tsx +0 -82
  183. package/src/react/useQuery.ts +0 -106
  184. package/src/react/useRow.test.tsx +0 -345
  185. package/src/react/useRow.ts +0 -180
  186. package/src/react/useTemporaryQuery.test.tsx +0 -98
  187. package/src/react/useTemporaryQuery.ts +0 -131
  188. package/src/react/utils/useStateRefWithReactiveInput.ts +0 -51
  189. package/src/store-context.ts +0 -23
  190. /package/dist/{__tests__/react/utils → utils}/stack-info.test.d.ts +0 -0
  191. /package/dist/{__tests__/react/utils → utils/tests}/otel.d.ts +0 -0
@@ -1,139 +1,29 @@
1
- import type {
2
- BootDb,
3
- BootStatus,
4
- EventId,
5
- IntentionalShutdownCause,
6
- ParamsObject,
7
- PreparedBindValues,
8
- StoreAdapter,
9
- StoreAdapterFactory,
10
- StoreDevtoolsChannel,
11
- } from '@livestore/common'
12
- import { getExecArgsFromMutation, prepareBindValues, UnexpectedError } from '@livestore/common'
1
+ import type { ClientSession, ParamsObject } from '@livestore/common'
2
+ import { getExecArgsFromMutation, prepareBindValues, replaceSessionIdSymbol } from '@livestore/common'
13
3
  import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
14
4
  import {
5
+ isPartialMutationEvent,
15
6
  makeMutationEventSchemaMemo,
16
7
  SCHEMA_META_TABLE,
17
8
  SCHEMA_MUTATIONS_META_TABLE,
18
9
  SESSION_CHANGESET_META_TABLE,
19
10
  } from '@livestore/common/schema'
20
- import { assertNever, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
21
- import {
22
- Cause,
23
- Data,
24
- Deferred,
25
- Duration,
26
- Effect,
27
- Exit,
28
- FiberSet,
29
- Inspectable,
30
- Layer,
31
- Logger,
32
- LogLevel,
33
- MutableHashMap,
34
- OtelTracer,
35
- Queue,
36
- Runtime,
37
- Schema,
38
- Scope,
39
- Stream,
40
- } from '@livestore/utils/effect'
11
+ import { assertNever, shouldNeverHappen } from '@livestore/utils'
12
+ import type { Scope } from '@livestore/utils/effect'
13
+ import { Data, Effect, FiberSet, Inspectable, MutableHashMap, Runtime, Schema, Stream } from '@livestore/utils/effect'
41
14
  import * as otel from '@opentelemetry/api'
42
15
  import type { GraphQLSchema } from 'graphql'
43
16
 
44
- import { globalReactivityGraph } from './global-state.js'
45
- import type { StackInfo } from './react/utils/stack-info.js'
46
- import type { DebugRefreshReasonBase, Ref } from './reactive.js'
47
- import type { LiveQuery, QueryContext, ReactivityGraph } from './reactiveQueries/base-class.js'
48
- import { connectDevtoolsToStore } from './store-devtools.js'
49
- import { SynchronousDatabaseWrapper } from './SynchronousDatabaseWrapper.js'
50
- import { ReferenceCountedSet } from './utils/data-structures.js'
51
- import { downloadBlob } from './utils/dev.js'
52
- import { getDurationMsFromSpan } from './utils/otel.js'
53
-
54
- export type BaseGraphQLContext = {
55
- queriedTables: Set<string>
56
- /** Needed by Pothos Otel plugin for resolver tracing to work */
57
- otelContext?: otel.Context
58
- }
59
-
60
- export type GraphQLOptions<TContext> = {
61
- schema: GraphQLSchema
62
- makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer) => TContext
63
- }
17
+ import type { Ref } from '../reactive.js'
18
+ import type { LiveQuery, QueryContext, ReactivityGraph } from '../reactiveQueries/base-class.js'
19
+ import { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
20
+ import { ReferenceCountedSet } from '../utils/data-structures.js'
21
+ import { downloadBlob, exposeDebugUtils } from '../utils/dev.js'
22
+ import { getDurationMsFromSpan } from '../utils/otel.js'
23
+ import type { BaseGraphQLContext, RefreshReason, StoreMutateOptions, StoreOptions, StoreOtel } from './store-types.js'
64
24
 
65
- export type OtelOptions = {
66
- tracer: otel.Tracer
67
- rootSpanContext: otel.Context
68
- }
69
-
70
- export type StoreOptions<
71
- TGraphQLContext extends BaseGraphQLContext,
72
- TSchema extends LiveStoreSchema = LiveStoreSchema,
73
- > = {
74
- adapter: StoreAdapter
75
- schema: TSchema
76
- storeId: string
77
- // TODO remove graphql-related stuff from store and move to GraphQL query directly
78
- graphQLOptions?: GraphQLOptions<TGraphQLContext>
79
- otelOptions: OtelOptions
80
- reactivityGraph: ReactivityGraph
81
- disableDevtools?: boolean
82
- fiberSet: FiberSet.FiberSet
83
- runtime: Runtime.Runtime<Scope.Scope>
84
- batchUpdates: (runUpdates: () => void) => void
85
- currentMutationEventIdRef: { current: EventId }
86
- unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId, MutationEvent.ForSchema<TSchema>>
87
- }
88
-
89
- export type RefreshReason =
90
- | DebugRefreshReasonBase
91
- | {
92
- _tag: 'mutate'
93
- /** The mutations that were applied */
94
- mutations: ReadonlyArray<MutationEvent.Any>
95
-
96
- /** The tables that were written to by the event */
97
- writeTables: ReadonlyArray<string>
98
- }
99
- | {
100
- _tag: 'react'
101
- api: string
102
- label?: string
103
- stackInfo?: StackInfo
104
- }
105
- | { _tag: 'manual'; label?: string }
106
-
107
- export type QueryDebugInfo = {
108
- _tag: 'graphql' | 'sql' | 'js' | 'unknown'
109
- label: string
110
- query: string
111
- durationMs: number
112
- }
113
-
114
- export type StoreOtel = {
115
- tracer: otel.Tracer
116
- mutationsSpanContext: otel.Context
117
- queriesSpanContext: otel.Context
118
- }
119
-
120
- export type StoreMutateOptions = {
121
- label?: string
122
- skipRefresh?: boolean
123
- wasSyncMessage?: boolean
124
- /**
125
- * When set to `false` the mutation won't be persisted in the mutation log and sync server (but still synced).
126
- * This can be useful e.g. for fine-granular update events (e.g. position updates during drag & drop)
127
- *
128
- * @default true
129
- */
130
- persisted?: boolean
131
- }
132
-
133
- // eslint-disable-next-line unicorn/prefer-global-this
134
- if (import.meta.env.DEV && typeof window !== 'undefined') {
135
- // eslint-disable-next-line unicorn/prefer-global-this
136
- window.__debugDownloadBlob = downloadBlob
25
+ if (import.meta.env.DEV) {
26
+ exposeDebugUtils()
137
27
  }
138
28
 
139
29
  export class Store<
@@ -143,7 +33,7 @@ export class Store<
143
33
  readonly storeId: string
144
34
  reactivityGraph: ReactivityGraph
145
35
  syncDbWrapper: SynchronousDatabaseWrapper
146
- adapter: StoreAdapter
36
+ clientSession: ClientSession
147
37
  schema: LiveStoreSchema
148
38
  graphQLSchema?: GraphQLSchema
149
39
  graphQLContext?: TGraphQLContext
@@ -162,20 +52,17 @@ export class Store<
162
52
 
163
53
  // NOTE this is currently exposed for the Devtools databrowser to emit mutation events
164
54
  readonly __mutationEventSchema
165
-
166
- private currentMutationEventIdRef
167
55
  private unsyncedMutationEvents
168
56
 
169
57
  // #region constructor
170
58
  private constructor({
171
- adapter,
59
+ clientSession,
172
60
  schema,
173
61
  graphQLOptions,
174
62
  reactivityGraph,
175
63
  otelOptions,
176
64
  disableDevtools,
177
65
  batchUpdates,
178
- currentMutationEventIdRef,
179
66
  unsyncedMutationEvents,
180
67
  storeId,
181
68
  fiberSet,
@@ -185,11 +72,10 @@ export class Store<
185
72
 
186
73
  this.storeId = storeId
187
74
 
188
- this.currentMutationEventIdRef = currentMutationEventIdRef
189
75
  this.unsyncedMutationEvents = unsyncedMutationEvents
190
76
 
191
- this.syncDbWrapper = new SynchronousDatabaseWrapper({ otel: otelOptions, db: adapter.syncDb })
192
- this.adapter = adapter
77
+ this.syncDbWrapper = new SynchronousDatabaseWrapper({ otel: otelOptions, db: clientSession.syncDb })
78
+ this.clientSession = clientSession
193
79
  this.schema = schema
194
80
 
195
81
  this.fiberSet = fiberSet
@@ -247,11 +133,15 @@ export class Store<
247
133
 
248
134
  if (graphQLOptions) {
249
135
  this.graphQLSchema = graphQLOptions.schema
250
- this.graphQLContext = graphQLOptions.makeContext(this.syncDbWrapper, this.otel.tracer)
136
+ this.graphQLContext = graphQLOptions.makeContext(
137
+ this.syncDbWrapper,
138
+ this.otel.tracer,
139
+ clientSession.coordinator.sessionId,
140
+ )
251
141
  }
252
142
 
253
143
  Effect.gen(this, function* () {
254
- yield* this.adapter.coordinator.syncMutations.pipe(
144
+ yield* this.clientSession.coordinator.syncMutations.pipe(
255
145
  Stream.tapChunk((mutationsEventsDecodedChunk) =>
256
146
  Effect.sync(() => {
257
147
  this.mutate({ wasSyncMessage: true }, ...mutationsEventsDecodedChunk)
@@ -265,18 +155,20 @@ export class Store<
265
155
 
266
156
  yield* Effect.addFinalizer(() =>
267
157
  Effect.sync(() => {
158
+ // Remove all table refs from the reactivity graph
268
159
  for (const tableRef of Object.values(this.tableRefs)) {
269
160
  for (const superComp of tableRef.super) {
270
161
  this.reactivityGraph.removeEdge(superComp, tableRef)
271
162
  }
272
163
  }
273
164
 
165
+ // End the otel spans
274
166
  otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
275
167
  otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
276
168
  }),
277
169
  )
278
170
 
279
- yield* Effect.never
171
+ yield* Effect.never // to keep the scope alive and bind to the parent scope
280
172
  }).pipe(Effect.scoped, Effect.withSpan('LiveStore:constructor'), this.runEffectFork)
281
173
  }
282
174
  // #endregion constructor
@@ -295,6 +187,10 @@ export class Store<
295
187
  })
296
188
  }
297
189
 
190
+ get sessionId(): string {
191
+ return this.clientSession.coordinator.sessionId
192
+ }
193
+
298
194
  /**
299
195
  * Subscribe to the results of a query
300
196
  * Returns a function to cancel the subscription.
@@ -491,6 +387,7 @@ export class Store<
491
387
 
492
388
  return res
493
389
  }
390
+ // #endregion mutate
494
391
 
495
392
  /**
496
393
  * This can be used in combination with `skipRefresh` when applying mutations.
@@ -510,6 +407,7 @@ export class Store<
510
407
  )
511
408
  }
512
409
 
410
+ // #region mutateWithoutRefresh
513
411
  /**
514
412
  * Apply a mutation to the store.
515
413
  * Returns the tables that were affected by the event.
@@ -520,6 +418,7 @@ export class Store<
520
418
  mutationEventDecoded_: MutationEvent.ForSchema<TSchema> | MutationEvent.PartialForSchema<TSchema>,
521
419
  options: {
522
420
  otelContext: otel.Context
421
+ // TODO adjust `skip-persist` with new rebase sync strategy
523
422
  coordinatorMode: 'default' | 'skip-coordinator' | 'skip-persist'
524
423
  },
525
424
  ): { writeTables: ReadonlySet<string>; durationMs: number } => {
@@ -527,12 +426,18 @@ export class Store<
527
426
  this.schema.mutations.get(mutationEventDecoded_.mutation) ??
528
427
  shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded_.mutation}`)
529
428
 
530
- const mutationEventDecoded: MutationEvent.ForSchema<TSchema> = mutationEventDecoded_.hasOwnProperty('id')
531
- ? (mutationEventDecoded_ as MutationEvent.ForSchema<TSchema>)
532
- : {
533
- ...mutationEventDecoded_,
534
- ...this.getNextMutationEventId({ localOnly: mutationDef.options.localOnly }),
535
- }
429
+ // Needs to happen only for partial mutation events (thus a function)
430
+ const nextMutationEventId = () => {
431
+ const { id, parentId } = this.clientSession.coordinator
432
+ .nextMutationEventIdPair({ localOnly: mutationDef.options.localOnly })
433
+ .pipe(Effect.runSync)
434
+
435
+ return { id, parentId }
436
+ }
437
+
438
+ const mutationEventDecoded: MutationEvent.ForSchema<TSchema> = isPartialMutationEvent(mutationEventDecoded_)
439
+ ? { ...mutationEventDecoded_, ...nextMutationEventId() }
440
+ : mutationEventDecoded_
536
441
 
537
442
  // NOTE we also need this temporary workaround here since some code-paths use `mutateWithoutRefresh` directly
538
443
  // e.g. the row-query functionality
@@ -560,6 +465,8 @@ export class Store<
560
465
  const allWriteTables = new Set<string>()
561
466
  let durationMsTotal = 0
562
467
 
468
+ replaceSessionIdSymbol(mutationEventDecoded.args, this.clientSession.coordinator.sessionId)
469
+
563
470
  const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
564
471
 
565
472
  for (const {
@@ -579,7 +486,7 @@ export class Store<
579
486
 
580
487
  if (coordinatorMode !== 'skip-coordinator') {
581
488
  // Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
582
- this.adapter.coordinator
489
+ this.clientSession.coordinator
583
490
  .mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: coordinatorMode !== 'skip-persist' })
584
491
  .pipe(this.runEffectFork)
585
492
  }
@@ -593,7 +500,7 @@ export class Store<
593
500
  },
594
501
  )
595
502
  }
596
- // #endregion mutate
503
+ // #endregion mutateWithoutRefresh
597
504
 
598
505
  /**
599
506
  * Directly execute a SQL query on the Store.
@@ -608,24 +515,13 @@ export class Store<
608
515
  ) => {
609
516
  this.syncDbWrapper.execute(query, prepareBindValues(params, query), writeTables, { otelContext })
610
517
 
611
- this.adapter.coordinator.execute(query, prepareBindValues(params, query)).pipe(this.runEffectFork)
518
+ this.clientSession.coordinator.execute(query, prepareBindValues(params, query)).pipe(this.runEffectFork)
612
519
  }
613
520
 
614
521
  __select = (query: string, params: ParamsObject = {}) => {
615
522
  return this.syncDbWrapper.select(query, { bindValues: prepareBindValues(params, query) })
616
523
  }
617
524
 
618
- private getNextMutationEventId = ({ localOnly }: { localOnly: boolean }): { id: EventId; parentId: EventId } => {
619
- const id = this.adapter.coordinator.getNextMutationEventId({ localOnly }).pipe(Effect.runSync)
620
- const parentId = localOnly
621
- ? this.currentMutationEventIdRef.current
622
- : { global: this.currentMutationEventIdRef.current.global, local: 0 }
623
-
624
- this.currentMutationEventIdRef.current = id
625
-
626
- return { id, parentId }
627
- }
628
-
629
525
  private makeTableRef = (tableName: string) =>
630
526
  this.reactivityGraph.makeRef(null, {
631
527
  equal: () => false,
@@ -640,14 +536,16 @@ export class Store<
640
536
 
641
537
  __devDownloadMutationLogDb = () =>
642
538
  Effect.gen(this, function* () {
643
- const data = yield* this.adapter.coordinator.getMutationLogData
539
+ const data = yield* this.clientSession.coordinator.getMutationLogData
644
540
  downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
645
541
  }).pipe(this.runEffectFork)
646
542
 
543
+ __devCurrentMutationEventId = () => this.clientSession.coordinator.getCurrentMutationEventId.pipe(Effect.runSync)
544
+
647
545
  // NOTE This is needed because when booting a Store via Effect it seems to call `toJSON` in the error path
648
546
  toJSON = () => {
649
547
  return {
650
- _tag: 'Store',
548
+ _tag: 'livestore.Store',
651
549
  reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults: true }),
652
550
  }
653
551
  }
@@ -655,260 +553,3 @@ export class Store<
655
553
  private runEffectFork = <A, E>(effect: Effect.Effect<A, E, never>) =>
656
554
  effect.pipe(Effect.tapCauseLogPretty, FiberSet.run(this.fiberSet), Runtime.runFork(this.runtime))
657
555
  }
658
-
659
- export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> = {
660
- schema: TSchema
661
- adapter: StoreAdapterFactory
662
- storeId: string
663
- reactivityGraph?: ReactivityGraph
664
- graphQLOptions?: GraphQLOptions<TGraphQLContext>
665
- otelOptions?: Partial<OtelOptions>
666
- boot?: (db: BootDb, parentSpan: otel.Span) => void | Promise<void> | Effect.Effect<void, unknown, otel.Tracer>
667
- batchUpdates?: (run: () => void) => void
668
- disableDevtools?: boolean
669
- onBootStatus?: (status: BootStatus) => void
670
- }
671
-
672
- /** Create a new LiveStore Store */
673
- export const createStorePromise = async <
674
- TGraphQLContext extends BaseGraphQLContext,
675
- TSchema extends LiveStoreSchema = LiveStoreSchema,
676
- >({
677
- signal,
678
- ...options
679
- }: CreateStoreOptions<TGraphQLContext, TSchema> & { signal?: AbortSignal }): Promise<Store<TGraphQLContext, TSchema>> =>
680
- Effect.gen(function* () {
681
- const scope = yield* Scope.make()
682
- const runtime = yield* Effect.runtime()
683
-
684
- if (signal !== undefined) {
685
- signal.addEventListener('abort', () => {
686
- Scope.close(scope, Exit.void).pipe(Effect.tapCauseLogPretty, Runtime.runFork(runtime))
687
- })
688
- }
689
-
690
- return yield* FiberSet.make().pipe(
691
- Effect.andThen((fiberSet) => createStore({ ...options, fiberSet })),
692
- Scope.extend(scope),
693
- )
694
- }).pipe(
695
- Effect.withSpan('createStore'),
696
- Effect.tapCauseLogPretty,
697
- Effect.annotateLogs({ thread: 'window' }),
698
- Effect.provide(Logger.pretty),
699
- Logger.withMinimumLogLevel(LogLevel.Debug),
700
- Effect.runPromise,
701
- )
702
-
703
- // #region createStore
704
- export const createStore = <
705
- TGraphQLContext extends BaseGraphQLContext,
706
- TSchema extends LiveStoreSchema = LiveStoreSchema,
707
- >({
708
- schema,
709
- adapter: adapterFactory,
710
- storeId,
711
- graphQLOptions,
712
- otelOptions,
713
- boot,
714
- reactivityGraph = globalReactivityGraph,
715
- batchUpdates,
716
- disableDevtools,
717
- onBootStatus,
718
- fiberSet,
719
- }: CreateStoreOptions<TGraphQLContext, TSchema> & { fiberSet: FiberSet.FiberSet }): Effect.Effect<
720
- Store<TGraphQLContext, TSchema>,
721
- UnexpectedError,
722
- Scope.Scope
723
- > => {
724
- const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
725
- const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
726
-
727
- const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
728
- Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)),
729
- )
730
-
731
- return Effect.gen(function* () {
732
- const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
733
-
734
- const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
735
-
736
- yield* Queue.take(bootStatusQueue).pipe(
737
- Effect.tapSync((status) => onBootStatus?.(status)),
738
- Effect.tap((status) => (status.stage === 'done' ? Queue.shutdown(bootStatusQueue) : Effect.void)),
739
- Effect.forever,
740
- Effect.tapCauseLogPretty,
741
- Effect.forkScoped,
742
- )
743
-
744
- const storeDeferred = yield* Deferred.make<Store>()
745
-
746
- const connectDevtoolsToStore_ = (storeDevtoolsChannel: StoreDevtoolsChannel) =>
747
- Effect.gen(function* () {
748
- const store = yield* Deferred.await(storeDeferred)
749
- yield* connectDevtoolsToStore({ storeDevtoolsChannel, store })
750
- })
751
-
752
- const runtime = yield* Effect.runtime<Scope.Scope>()
753
-
754
- const runEffectFork = (effect: Effect.Effect<any, any, never>) =>
755
- effect.pipe(Effect.tapCauseLogPretty, FiberSet.run(fiberSet), Runtime.runFork(runtime))
756
-
757
- // TODO close parent scope? (Needs refactor with Mike A)
758
- const shutdown = (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) =>
759
- Effect.gen(function* () {
760
- // NOTE we're calling `cause.toString()` here to avoid triggering a `console.error` in the grouped log
761
- const logCause =
762
- Cause.isFailType(cause) && cause.error._tag === 'LiveStore.IntentionalShutdownCause'
763
- ? cause.toString()
764
- : cause
765
- yield* Effect.logDebug(`Shutting down LiveStore`, logCause)
766
-
767
- FiberSet.clear(fiberSet).pipe(
768
- Effect.andThen(() => FiberSet.run(fiberSet, Effect.failCause(cause))),
769
- Effect.timeout(Duration.seconds(1)),
770
- Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown:clear-fiber-set', duration: 500 }),
771
- Effect.catchTag('TimeoutException', (err) =>
772
- Effect.logError('Store shutdown timed out. Forcing shutdown.', err).pipe(
773
- Effect.andThen(FiberSet.run(fiberSet, Effect.failCause(cause))),
774
- ),
775
- ),
776
- Runtime.runFork(runtime), // NOTE we need to fork this separately otherwise it will also be interrupted
777
- )
778
- }).pipe(Effect.withSpan('livestore:shutdown'))
779
-
780
- const adapter: StoreAdapter = yield* adapterFactory({
781
- schema,
782
- storeId,
783
- devtoolsEnabled: disableDevtools !== true,
784
- bootStatusQueue,
785
- shutdown,
786
- connectDevtoolsToStore: connectDevtoolsToStore_,
787
- }).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
788
-
789
- const mutationEventSchema = makeMutationEventSchemaMemo(schema)
790
-
791
- // TODO get rid of this
792
- // const __processedMutationIds = new Set<number>()
793
-
794
- const currentMutationEventIdRef = { current: yield* adapter.coordinator.getCurrentMutationEventId }
795
-
796
- // TODO fill up with unsynced mutation events from the coordinator
797
- const unsyncedMutationEvents = MutableHashMap.empty<EventId, MutationEvent.ForSchema<TSchema>>()
798
-
799
- // TODO consider moving booting into the storage backend
800
- if (boot !== undefined) {
801
- let isInTxn = false
802
- let txnExecuteStmnts: [string, PreparedBindValues | undefined][] = []
803
-
804
- const bootDbImpl: BootDb = {
805
- _tag: 'BootDb',
806
- execute: (queryStr, bindValues) => {
807
- const stmt = adapter.syncDb.prepare(queryStr)
808
- stmt.execute(bindValues)
809
-
810
- if (isInTxn === true) {
811
- txnExecuteStmnts.push([queryStr, bindValues])
812
- } else {
813
- adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
814
- }
815
- },
816
- mutate: (...list) => {
817
- for (const mutationEventDecoded_ of list) {
818
- const mutationDef =
819
- schema.mutations.get(mutationEventDecoded_.mutation) ??
820
- shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded_.mutation}`)
821
-
822
- const parentId = mutationDef.options.localOnly
823
- ? currentMutationEventIdRef.current
824
- : { global: currentMutationEventIdRef.current.global, local: 0 }
825
-
826
- currentMutationEventIdRef.current = adapter.coordinator
827
- .getNextMutationEventId({ localOnly: mutationDef.options.localOnly })
828
- .pipe(Effect.runSync)
829
-
830
- const mutationEventDecoded = { ...mutationEventDecoded_, id: currentMutationEventIdRef.current, parentId }
831
-
832
- MutableHashMap.set(unsyncedMutationEvents, Data.struct(mutationEventDecoded.id), mutationEventDecoded)
833
-
834
- // __processedMutationIds.add(mutationEventDecoded.id.global)
835
-
836
- const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
837
- // const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
838
-
839
- for (const { statementSql, bindValues } of execArgsArr) {
840
- adapter.syncDb.execute(statementSql, bindValues)
841
- }
842
-
843
- const mutationEventEncoded = Schema.encodeUnknownSync(mutationEventSchema)(mutationEventDecoded)
844
-
845
- adapter.coordinator
846
- .mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: true })
847
- .pipe(runEffectFork)
848
- }
849
- },
850
- select: (queryStr, bindValues) => {
851
- const stmt = adapter.syncDb.prepare(queryStr)
852
- return stmt.select(bindValues)
853
- },
854
- txn: (callback) => {
855
- try {
856
- isInTxn = true
857
- // adapter.syncDb.execute('BEGIN TRANSACTION', undefined)
858
-
859
- callback()
860
-
861
- // adapter.syncDb.execute('COMMIT', undefined)
862
-
863
- // adapter.coordinator.execute('BEGIN', undefined, undefined)
864
- for (const [queryStr, bindValues] of txnExecuteStmnts) {
865
- adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
866
- }
867
- // adapter.coordinator.execute('COMMIT', undefined, undefined)
868
- } catch (e: any) {
869
- // adapter.syncDb.execute('ROLLBACK', undefined)
870
- throw e
871
- } finally {
872
- isInTxn = false
873
- txnExecuteStmnts = []
874
- }
875
- },
876
- }
877
-
878
- yield* Effect.tryAll(() => boot(bootDbImpl, span)).pipe(
879
- UnexpectedError.mapToUnexpectedError,
880
- Effect.withSpan('createStore:boot'),
881
- )
882
- }
883
-
884
- const store = Store.createStore<TGraphQLContext, TSchema>(
885
- {
886
- adapter,
887
- schema,
888
- graphQLOptions,
889
- otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
890
- reactivityGraph,
891
- disableDevtools,
892
- currentMutationEventIdRef,
893
- unsyncedMutationEvents,
894
- fiberSet,
895
- runtime,
896
- batchUpdates: batchUpdates ?? ((run) => run()),
897
- storeId,
898
- },
899
- span,
900
- )
901
-
902
- yield* Deferred.succeed(storeDeferred, store as any as Store)
903
-
904
- return store
905
- }).pipe(
906
- Effect.withSpan('createStore', {
907
- parent: otelOptions?.rootSpanContext
908
- ? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext)!)
909
- : undefined,
910
- }),
911
- Effect.provide(TracingLive),
912
- )
913
- }
914
- // #endregion createStore
package/src/utils/dev.ts CHANGED
@@ -22,3 +22,9 @@ export const downloadURL = (data: string, fileName: string) => {
22
22
  a.click()
23
23
  a.remove()
24
24
  }
25
+
26
+ export const exposeDebugUtils = () => {
27
+ if (import.meta.env.DEV) {
28
+ globalThis.__debugDownloadBlob = downloadBlob
29
+ }
30
+ }
@@ -1,6 +1,6 @@
1
1
  import { expect, it } from 'vitest'
2
2
 
3
- import { extractStackInfoFromStackTrace } from '../../../react/utils/stack-info.js'
3
+ import { extractStackInfoFromStackTrace } from './stack-info.js'
4
4
 
5
5
  it('RouteLink stacktrace', async () => {
6
6
  const stackTrace = `\
@@ -1,7 +1,3 @@
1
- import React from 'react'
2
-
3
- export const originalStackLimit = Error.stackTraceLimit
4
-
5
1
  export type StackInfo = {
6
2
  frames: StackFrame[]
7
3
  }
@@ -54,11 +50,5 @@ export const extractStackInfoFromStackTrace = (stackTrace: string): StackInfo =>
54
50
  return { frames }
55
51
  }
56
52
 
57
- export const useStackInfo = (): StackInfo =>
58
- React.useMemo(() => {
59
- Error.stackTraceLimit = 10
60
- // eslint-disable-next-line unicorn/error-message
61
- const stack = new Error().stack!
62
- Error.stackTraceLimit = originalStackLimit
63
- return extractStackInfoFromStackTrace(stack)
64
- }, [])
53
+ export const stackInfoToString = (stackInfo: StackInfo): string =>
54
+ stackInfo.frames.map((f) => `${f.name} (${f.filePath})`).join('\n')