@livestore/livestore 0.2.0 → 0.3.0-dev.11

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 (163) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/SqliteDbWrapper.d.ts +54 -0
  3. package/dist/SqliteDbWrapper.d.ts.map +1 -0
  4. package/dist/SqliteDbWrapper.js +212 -0
  5. package/dist/SqliteDbWrapper.js.map +1 -0
  6. package/dist/SynchronousDatabaseWrapper.d.ts +20 -6
  7. package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
  8. package/dist/SynchronousDatabaseWrapper.js +38 -6
  9. package/dist/SynchronousDatabaseWrapper.js.map +1 -1
  10. package/dist/__tests__/fixture.d.ts +252 -0
  11. package/dist/__tests__/fixture.d.ts.map +1 -0
  12. package/dist/__tests__/fixture.js +18 -0
  13. package/dist/__tests__/fixture.js.map +1 -0
  14. package/dist/effect/LiveStore.d.ts +16 -12
  15. package/dist/effect/LiveStore.d.ts.map +1 -1
  16. package/dist/effect/LiveStore.js +14 -14
  17. package/dist/effect/LiveStore.js.map +1 -1
  18. package/dist/index.d.ts +6 -7
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +4 -4
  21. package/dist/index.js.map +1 -1
  22. package/dist/live-queries/base-class.d.ts +64 -21
  23. package/dist/live-queries/base-class.d.ts.map +1 -1
  24. package/dist/live-queries/base-class.js +56 -13
  25. package/dist/live-queries/base-class.js.map +1 -1
  26. package/dist/live-queries/computed.d.ts +7 -7
  27. package/dist/live-queries/computed.d.ts.map +1 -1
  28. package/dist/live-queries/computed.js +35 -11
  29. package/dist/live-queries/computed.js.map +1 -1
  30. package/dist/live-queries/{sql.d.ts → db-query.d.ts} +19 -14
  31. package/dist/live-queries/db-query.d.ts.map +1 -0
  32. package/dist/live-queries/db-query.js +244 -0
  33. package/dist/live-queries/db-query.js.map +1 -0
  34. package/dist/live-queries/db-query.test.d.ts +2 -0
  35. package/dist/live-queries/db-query.test.d.ts.map +1 -0
  36. package/dist/live-queries/db-query.test.js +123 -0
  37. package/dist/live-queries/db-query.test.js.map +1 -0
  38. package/dist/live-queries/db.d.ts +12 -15
  39. package/dist/live-queries/db.d.ts.map +1 -1
  40. package/dist/live-queries/db.js +72 -48
  41. package/dist/live-queries/db.js.map +1 -1
  42. package/dist/live-queries/db.test.js +18 -15
  43. package/dist/live-queries/db.test.js.map +1 -1
  44. package/dist/live-queries/graphql.d.ts +8 -8
  45. package/dist/live-queries/graphql.d.ts.map +1 -1
  46. package/dist/live-queries/graphql.js +35 -9
  47. package/dist/live-queries/graphql.js.map +1 -1
  48. package/dist/live-queries/make-ref.d.ts +20 -0
  49. package/dist/live-queries/make-ref.d.ts.map +1 -0
  50. package/dist/live-queries/make-ref.js +33 -0
  51. package/dist/live-queries/make-ref.js.map +1 -0
  52. package/dist/reactive.d.ts +15 -13
  53. package/dist/reactive.d.ts.map +1 -1
  54. package/dist/reactive.js +15 -9
  55. package/dist/reactive.js.map +1 -1
  56. package/dist/row-query-utils.d.ts +4 -4
  57. package/dist/row-query-utils.d.ts.map +1 -1
  58. package/dist/row-query-utils.js +14 -10
  59. package/dist/row-query-utils.js.map +1 -1
  60. package/dist/store/create-store.d.ts +13 -12
  61. package/dist/store/create-store.d.ts.map +1 -1
  62. package/dist/store/create-store.js +27 -33
  63. package/dist/store/create-store.js.map +1 -1
  64. package/dist/store/devtools.d.ts +3 -3
  65. package/dist/store/devtools.d.ts.map +1 -1
  66. package/dist/store/devtools.js +56 -34
  67. package/dist/store/devtools.js.map +1 -1
  68. package/dist/store/store-types.d.ts +18 -18
  69. package/dist/store/store-types.d.ts.map +1 -1
  70. package/dist/store/store-types.js.map +1 -1
  71. package/dist/store/store.d.ts +57 -38
  72. package/dist/store/store.d.ts.map +1 -1
  73. package/dist/store/store.js +225 -188
  74. package/dist/store/store.js.map +1 -1
  75. package/dist/store/store.test.d.ts +2 -0
  76. package/dist/store/store.test.d.ts.map +1 -0
  77. package/dist/store/store.test.js +27 -0
  78. package/dist/store/store.test.js.map +1 -0
  79. package/dist/utils/dev.d.ts.map +1 -1
  80. package/dist/utils/dev.js +3 -2
  81. package/dist/utils/dev.js.map +1 -1
  82. package/dist/utils/expo.d.ts +2 -0
  83. package/dist/utils/expo.d.ts.map +1 -0
  84. package/dist/utils/expo.js +8 -0
  85. package/dist/utils/expo.js.map +1 -0
  86. package/dist/utils/function-string.d.ts +7 -0
  87. package/dist/utils/function-string.d.ts.map +1 -0
  88. package/dist/utils/function-string.js +9 -0
  89. package/dist/utils/function-string.js.map +1 -0
  90. package/dist/utils/stack-info.d.ts.map +1 -1
  91. package/dist/utils/stack-info.js +6 -1
  92. package/dist/utils/stack-info.js.map +1 -1
  93. package/dist/utils/stack-info.test.js +54 -1
  94. package/dist/utils/stack-info.test.js.map +1 -1
  95. package/dist/utils/tests/fixture.d.ts +2 -6
  96. package/dist/utils/tests/fixture.d.ts.map +1 -1
  97. package/dist/utils/tests/fixture.js +7 -13
  98. package/dist/utils/tests/fixture.js.map +1 -1
  99. package/dist/utils/tests/mod.d.ts +1 -0
  100. package/dist/utils/tests/mod.d.ts.map +1 -1
  101. package/dist/utils/tests/mod.js +1 -0
  102. package/dist/utils/tests/mod.js.map +1 -1
  103. package/dist/utils/tests/otel.d.ts +60 -1
  104. package/dist/utils/tests/otel.d.ts.map +1 -1
  105. package/dist/utils/tests/otel.js +65 -4
  106. package/dist/utils/tests/otel.js.map +1 -1
  107. package/package.json +12 -12
  108. package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +59 -13
  109. package/src/ambient.d.ts +1 -1
  110. package/src/effect/LiveStore.ts +32 -33
  111. package/src/index.ts +14 -7
  112. package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +220 -69
  113. package/src/live-queries/base-class.ts +160 -40
  114. package/src/live-queries/computed.ts +45 -19
  115. package/src/live-queries/{db.test.ts → db-query.test.ts} +23 -12
  116. package/src/live-queries/{db.ts → db-query.ts} +124 -61
  117. package/src/live-queries/graphql.ts +47 -21
  118. package/src/live-queries/make-ref.ts +47 -0
  119. package/src/reactive.ts +52 -27
  120. package/src/row-query-utils.ts +29 -18
  121. package/src/store/create-store.ts +106 -113
  122. package/src/store/devtools.ts +65 -39
  123. package/src/store/store-types.ts +20 -18
  124. package/src/store/store.ts +361 -290
  125. package/src/utils/dev.ts +4 -2
  126. package/src/utils/function-string.ts +12 -0
  127. package/src/utils/stack-info.test.ts +58 -1
  128. package/src/utils/stack-info.ts +6 -1
  129. package/src/utils/tests/fixture.ts +6 -16
  130. package/src/utils/tests/mod.ts +1 -0
  131. package/src/utils/tests/otel.ts +71 -5
  132. package/dist/live-queries/sql.d.ts.map +0 -1
  133. package/dist/live-queries/sql.js +0 -175
  134. package/dist/live-queries/sql.js.map +0 -1
  135. package/dist/live-queries/sql.test.d.ts +0 -2
  136. package/dist/live-queries/sql.test.d.ts.map +0 -1
  137. package/dist/live-queries/sql.test.js +0 -285
  138. package/dist/live-queries/sql.test.js.map +0 -1
  139. package/dist/reactiveQueries/base-class.d.ts +0 -64
  140. package/dist/reactiveQueries/base-class.d.ts.map +0 -1
  141. package/dist/reactiveQueries/base-class.js +0 -31
  142. package/dist/reactiveQueries/base-class.js.map +0 -1
  143. package/dist/reactiveQueries/computed.d.ts +0 -26
  144. package/dist/reactiveQueries/computed.d.ts.map +0 -1
  145. package/dist/reactiveQueries/computed.js +0 -38
  146. package/dist/reactiveQueries/computed.js.map +0 -1
  147. package/dist/reactiveQueries/graphql.d.ts +0 -49
  148. package/dist/reactiveQueries/graphql.d.ts.map +0 -1
  149. package/dist/reactiveQueries/graphql.js +0 -122
  150. package/dist/reactiveQueries/graphql.js.map +0 -1
  151. package/dist/reactiveQueries/sql.d.ts +0 -62
  152. package/dist/reactiveQueries/sql.d.ts.map +0 -1
  153. package/dist/reactiveQueries/sql.js +0 -175
  154. package/dist/reactiveQueries/sql.js.map +0 -1
  155. package/dist/reactiveQueries/sql.test.d.ts +0 -2
  156. package/dist/reactiveQueries/sql.test.d.ts.map +0 -1
  157. package/dist/reactiveQueries/sql.test.js +0 -285
  158. package/dist/reactiveQueries/sql.test.js.map +0 -1
  159. package/dist/row-query.d.ts +0 -16
  160. package/dist/row-query.d.ts.map +0 -1
  161. package/dist/row-query.js +0 -30
  162. package/dist/row-query.js.map +0 -1
  163. package/src/global-state.ts +0 -20
@@ -2,20 +2,18 @@ import type {
2
2
  Adapter,
3
3
  BootStatus,
4
4
  ClientSession,
5
- EventId,
6
5
  IntentionalShutdownCause,
7
6
  StoreDevtoolsChannel,
8
7
  } from '@livestore/common'
9
- import { UnexpectedError } from '@livestore/common'
10
- import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
11
- import { makeNoopTracer } from '@livestore/utils'
8
+ import { provideOtel, UnexpectedError } from '@livestore/common'
9
+ import type { EventId, LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
10
+ import { LS_DEV } from '@livestore/utils'
11
+ import type { Cause } from '@livestore/utils/effect'
12
12
  import {
13
- Cause,
14
13
  Deferred,
15
- Duration,
16
14
  Effect,
17
15
  Exit,
18
- FiberSet,
16
+ identity,
19
17
  Layer,
20
18
  Logger,
21
19
  LogLevel,
@@ -24,29 +22,32 @@ import {
24
22
  Queue,
25
23
  Runtime,
26
24
  Scope,
25
+ TaskTracing,
27
26
  } from '@livestore/utils/effect'
27
+ import { nanoid } from '@livestore/utils/nanoid'
28
28
  import * as otel from '@opentelemetry/api'
29
29
 
30
- import { globalReactivityGraph } from '../global-state.js'
31
- import type { ReactivityGraph } from '../live-queries/base-class.js'
30
+ import { LiveStoreContextRunning } from '../effect/index.js'
32
31
  import { connectDevtoolsToStore } from './devtools.js'
33
32
  import { Store } from './store.js'
34
- import type { BaseGraphQLContext, GraphQLOptions, OtelOptions } from './store-types.js'
33
+ import type { BaseGraphQLContext, GraphQLOptions, OtelOptions, ShutdownDeferred } from './store-types.js'
35
34
 
36
- export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> = {
35
+ export interface CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> {
37
36
  schema: TSchema
38
37
  adapter: Adapter
39
38
  storeId: string
40
- reactivityGraph?: ReactivityGraph
41
39
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
42
- otelOptions?: Partial<OtelOptions>
43
40
  boot?: (
44
41
  store: Store<TGraphQLContext, TSchema>,
45
42
  parentSpan: otel.Span,
46
- ) => void | Promise<void> | Effect.Effect<void, unknown, otel.Tracer>
43
+ ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
47
44
  batchUpdates?: (run: () => void) => void
48
45
  disableDevtools?: boolean
49
46
  onBootStatus?: (status: BootStatus) => void
47
+ shutdownDeferred?: ShutdownDeferred
48
+ debug?: {
49
+ instanceId?: string
50
+ }
50
51
  }
51
52
 
52
53
  /** Create a new LiveStore Store */
@@ -55,8 +56,12 @@ export const createStorePromise = async <
55
56
  TSchema extends LiveStoreSchema = LiveStoreSchema,
56
57
  >({
57
58
  signal,
59
+ otelOptions,
58
60
  ...options
59
- }: CreateStoreOptions<TGraphQLContext, TSchema> & { signal?: AbortSignal }): Promise<Store<TGraphQLContext, TSchema>> =>
61
+ }: CreateStoreOptions<TGraphQLContext, TSchema> & {
62
+ signal?: AbortSignal
63
+ otelOptions?: Partial<OtelOptions>
64
+ }): Promise<Store<TGraphQLContext, TSchema>> =>
60
65
  Effect.gen(function* () {
61
66
  const scope = yield* Scope.make()
62
67
  const runtime = yield* Effect.runtime()
@@ -67,12 +72,12 @@ export const createStorePromise = async <
67
72
  })
68
73
  }
69
74
 
70
- return yield* FiberSet.make().pipe(
71
- Effect.andThen((fiberSet) => createStore({ ...options, fiberSet })),
72
- Scope.extend(scope),
73
- )
75
+ return yield* createStore({ ...options }).pipe(Scope.extend(scope))
74
76
  }).pipe(
75
- Effect.withSpan('createStore'),
77
+ Effect.withSpan('createStore', {
78
+ attributes: { storeId: options.storeId, disableDevtools: options.disableDevtools },
79
+ }),
80
+ provideOtel({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer }),
76
81
  Effect.tapCauseLogPretty,
77
82
  Effect.annotateLogs({ thread: 'window' }),
78
83
  Effect.provide(Logger.pretty),
@@ -88,127 +93,115 @@ export const createStore = <
88
93
  adapter,
89
94
  storeId,
90
95
  graphQLOptions,
91
- otelOptions,
92
96
  boot,
93
- reactivityGraph = globalReactivityGraph,
94
97
  batchUpdates,
95
98
  disableDevtools,
96
99
  onBootStatus,
97
- fiberSet,
98
- }: CreateStoreOptions<TGraphQLContext, TSchema> & { fiberSet: FiberSet.FiberSet }): Effect.Effect<
100
+ shutdownDeferred,
101
+ debug,
102
+ }: CreateStoreOptions<TGraphQLContext, TSchema>): Effect.Effect<
99
103
  Store<TGraphQLContext, TSchema>,
100
104
  UnexpectedError,
101
- Scope.Scope
102
- > => {
103
- const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
104
- const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
105
+ Scope.Scope | OtelTracer.OtelTracer
106
+ > =>
107
+ Effect.gen(function* () {
108
+ const lifetimeScope = yield* Scope.make()
105
109
 
106
- const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
107
- Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)),
108
- )
110
+ yield* Effect.addFinalizer((_) => Scope.close(lifetimeScope, _))
109
111
 
110
- return Effect.gen(function* () {
111
- const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
112
+ const debugInstanceId = debug?.instanceId ?? nanoid(10)
112
113
 
113
- const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
114
+ return yield* Effect.gen(function* () {
115
+ const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
116
+ const otelRootSpanContext = otel.trace.setSpan(otel.context.active(), span)
117
+ const otelTracer = yield* OtelTracer.OtelTracer
114
118
 
115
- yield* Queue.take(bootStatusQueue).pipe(
116
- Effect.tapSync((status) => onBootStatus?.(status)),
117
- Effect.tap((status) => (status.stage === 'done' ? Queue.shutdown(bootStatusQueue) : Effect.void)),
118
- Effect.forever,
119
- Effect.tapCauseLogPretty,
120
- Effect.forkScoped,
121
- )
119
+ const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
122
120
 
123
- const storeDeferred = yield* Deferred.make<Store>()
121
+ yield* Queue.take(bootStatusQueue).pipe(
122
+ Effect.tapSync((status) => onBootStatus?.(status)),
123
+ Effect.tap((status) => (status.stage === 'done' ? Queue.shutdown(bootStatusQueue) : Effect.void)),
124
+ Effect.forever,
125
+ Effect.tapCauseLogPretty,
126
+ Effect.forkScoped,
127
+ )
124
128
 
125
- const connectDevtoolsToStore_ = (storeDevtoolsChannel: StoreDevtoolsChannel) =>
126
- Effect.gen(function* () {
127
- const store = yield* Deferred.await(storeDeferred)
128
- yield* connectDevtoolsToStore({ storeDevtoolsChannel, store })
129
- })
129
+ const storeDeferred = yield* Deferred.make<Store>()
130
130
 
131
- const runtime = yield* Effect.runtime<Scope.Scope>()
132
-
133
- // TODO close parent scope? (Needs refactor with Mike Arnaldi)
134
- const shutdown = (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) =>
135
- Effect.gen(function* () {
136
- // NOTE we're calling `cause.toString()` here to avoid triggering a `console.error` in the grouped log
137
- const logCause =
138
- Cause.isFailType(cause) && cause.error._tag === 'LiveStore.IntentionalShutdownCause'
139
- ? cause.toString()
140
- : cause
141
- yield* Effect.logDebug(`Shutting down LiveStore`, logCause)
142
-
143
- FiberSet.clear(fiberSet).pipe(
144
- Effect.andThen(() => FiberSet.run(fiberSet, Effect.failCause(cause))),
145
- Effect.timeout(Duration.seconds(1)),
146
- Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown:clear-fiber-set', duration: 500 }),
147
- Effect.catchTag('TimeoutException', (err) =>
148
- Effect.logError('Store shutdown timed out. Forcing shutdown.', err).pipe(
149
- Effect.andThen(FiberSet.run(fiberSet, Effect.failCause(cause))),
150
- ),
131
+ const connectDevtoolsToStore_ = (storeDevtoolsChannel: StoreDevtoolsChannel) =>
132
+ Effect.gen(function* () {
133
+ const store = yield* storeDeferred
134
+ yield* connectDevtoolsToStore({ storeDevtoolsChannel, store })
135
+ })
136
+
137
+ const runtime = yield* Effect.runtime<Scope.Scope>()
138
+
139
+ const shutdown = (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) =>
140
+ Scope.close(lifetimeScope, Exit.failCause(cause)).pipe(
141
+ Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown', duration: 500 }),
142
+ Effect.timeout(1000),
143
+ Effect.catchTag('TimeoutException', () =>
144
+ Effect.logError('@livestore/livestore:shutdown: Timed out after 1 second'),
151
145
  ),
152
- Runtime.runFork(runtime), // NOTE we need to fork this separately otherwise it will also be interrupted
146
+ Effect.tap(() => (shutdownDeferred ? Deferred.failCause(shutdownDeferred, cause) : Effect.void)),
147
+ Effect.tap(() => Effect.logDebug('LiveStore shutdown complete')),
148
+ Effect.withSpan('livestore:shutdown'),
153
149
  )
154
- }).pipe(Effect.withSpan('livestore:shutdown'))
155
-
156
- const clientSession: ClientSession = yield* adapter({
157
- schema,
158
- storeId,
159
- devtoolsEnabled: disableDevtools !== true,
160
- bootStatusQueue,
161
- shutdown,
162
- connectDevtoolsToStore: connectDevtoolsToStore_,
163
- }).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
164
-
165
- // TODO fill up with unsynced mutation events from the client session
166
- const unsyncedMutationEvents = MutableHashMap.empty<EventId, MutationEvent.ForSchema<TSchema>>()
167
-
168
- const store = Store.createStore<TGraphQLContext, TSchema>(
169
- {
150
+
151
+ const clientSession: ClientSession = yield* adapter({
152
+ schema,
153
+ storeId,
154
+ devtoolsEnabled: disableDevtools !== true,
155
+ bootStatusQueue,
156
+ shutdown,
157
+ connectDevtoolsToStore: connectDevtoolsToStore_,
158
+ debugInstanceId,
159
+ }).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
160
+
161
+ // TODO fill up with unsynced mutation events from the client session
162
+ const unsyncedMutationEvents = MutableHashMap.empty<EventId.EventId, MutationEvent.ForSchema<TSchema>>()
163
+
164
+ const store = new Store<TGraphQLContext, TSchema>({
170
165
  clientSession,
171
166
  schema,
172
167
  graphQLOptions,
173
168
  otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
174
- reactivityGraph,
175
169
  disableDevtools,
176
170
  unsyncedMutationEvents,
177
- fiberSet,
171
+ lifetimeScope,
178
172
  runtime,
179
173
  // NOTE during boot we're not yet executing mutations in a batched context
180
174
  // but only set the provided `batchUpdates` function after boot
181
175
  batchUpdates: (run) => run(),
182
176
  storeId,
183
- },
184
- span,
185
- )
177
+ })
186
178
 
187
- if (boot !== undefined) {
188
- // TODO also incorporate `boot` function progress into `bootStatusQueue`
189
- yield* Effect.tryAll(() => boot(store, span)).pipe(
190
- UnexpectedError.mapToUnexpectedError,
191
- Effect.withSpan('createStore:boot'),
192
- )
193
- }
179
+ yield* store.boot
180
+
181
+ if (boot !== undefined) {
182
+ // TODO also incorporate `boot` function progress into `bootStatusQueue`
183
+ yield* Effect.tryAll(() => boot(store, span)).pipe(
184
+ UnexpectedError.mapToUnexpectedError,
185
+ Effect.provide(Layer.succeed(LiveStoreContextRunning, { stage: 'running', store: store as any as Store })),
186
+ Effect.withSpan('createStore:boot'),
187
+ )
188
+ }
194
189
 
195
- // NOTE it's important to yield here to allow the forked Effect in the store constructor to run
196
- yield* Effect.yieldNow()
190
+ // NOTE it's important to yield here to allow the forked Effect in the store constructor to run
191
+ yield* Effect.yieldNow()
197
192
 
198
- if (batchUpdates !== undefined) {
199
- // Replacing the default batchUpdates function with the provided one after boot
200
- store.reactivityGraph.context!.effectsWrapper = batchUpdates
201
- }
193
+ if (batchUpdates !== undefined) {
194
+ // Replacing the default batchUpdates function with the provided one after boot
195
+ store.reactivityGraph.context!.effectsWrapper = batchUpdates
196
+ }
202
197
 
203
- yield* Deferred.succeed(storeDeferred, store as any as Store)
198
+ yield* Deferred.succeed(storeDeferred, store as any as Store)
204
199
 
205
- return store
206
- }).pipe(
207
- Effect.withSpan('createStore', {
208
- parent: otelOptions?.rootSpanContext
209
- ? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext)!)
210
- : undefined,
211
- }),
212
- Effect.provide(TracingLive),
213
- )
214
- }
200
+ return store
201
+ }).pipe(
202
+ Effect.withSpan('createStore', { attributes: { debugInstanceId, storeId } }),
203
+ Effect.annotateLogs({ debugInstanceId, storeId }),
204
+ LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
205
+ Scope.extend(lifetimeScope),
206
+ )
207
+ })
@@ -6,14 +6,14 @@ import { Effect, Stream } from '@livestore/utils/effect'
6
6
 
7
7
  import type { LiveQuery, ReactivityGraph } from '../live-queries/base-class.js'
8
8
  import { NOT_REFRESHED_YET } from '../reactive.js'
9
- import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
10
- import { emptyDebugInfo as makeEmptyDebugInfo } from '../SynchronousDatabaseWrapper.js'
9
+ import type { SqliteDbWrapper } from '../SqliteDbWrapper.js'
10
+ import { emptyDebugInfo as makeEmptyDebugInfo } from '../SqliteDbWrapper.js'
11
11
  import type { ReferenceCountedSet } from '../utils/data-structures.js'
12
12
 
13
13
  type IStore = {
14
14
  clientSession: ClientSession
15
15
  reactivityGraph: ReactivityGraph
16
- syncDbWrapper: SynchronousDatabaseWrapper
16
+ sqliteDbWrapper: SqliteDbWrapper
17
17
  activeQueries: ReferenceCountedSet<LiveQuery<any>>
18
18
  }
19
19
 
@@ -21,20 +21,29 @@ type Unsub = () => void
21
21
  type RequestId = string
22
22
  type SubMap = Map<RequestId, Unsub>
23
23
 
24
+ // When running this code in Node.js, we need to use `setTimeout` instead of `requestAnimationFrame`
25
+ const requestNextTick: (cb: () => void) => number =
26
+ globalThis.requestAnimationFrame === undefined
27
+ ? (cb: () => void) => setTimeout(cb, 1000) as unknown as number
28
+ : globalThis.requestAnimationFrame
29
+
30
+ const cancelTick: (id: number) => void =
31
+ globalThis.cancelAnimationFrame === undefined ? (id: number) => clearTimeout(id) : globalThis.cancelAnimationFrame
32
+
24
33
  export const connectDevtoolsToStore = ({
25
34
  storeDevtoolsChannel,
26
35
  store,
27
36
  }: {
28
- storeDevtoolsChannel: WebChannel.WebChannel<Devtools.MessageToAppHostStore, Devtools.MessageFromAppHostStore>
37
+ storeDevtoolsChannel: WebChannel.WebChannel<Devtools.MessageToAppClientSession, Devtools.MessageFromAppClientSession>
29
38
  store: IStore
30
39
  }) =>
31
40
  Effect.gen(function* () {
32
- const appHostId = store.clientSession.coordinator.devtools.appHostId
33
-
34
41
  const reactivityGraphSubcriptions: SubMap = new Map()
35
42
  const liveQueriesSubscriptions: SubMap = new Map()
36
43
  const debugInfoHistorySubscriptions: SubMap = new Map()
37
44
 
45
+ const { clientId, sessionId } = store.clientSession
46
+
38
47
  yield* Effect.addFinalizer(() =>
39
48
  Effect.sync(() => {
40
49
  reactivityGraphSubcriptions.forEach((unsub) => unsub())
@@ -43,23 +52,28 @@ export const connectDevtoolsToStore = ({
43
52
  }),
44
53
  )
45
54
 
46
- const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
47
- storeDevtoolsChannel.send(message).pipe(Effect.tapCauseLogPretty, Effect.runSync)
55
+ const sendToDevtools = (message: Devtools.MessageFromAppClientSession) =>
56
+ storeDevtoolsChannel.send(message).pipe(Effect.tapCauseLogPretty, Effect.runFork)
48
57
 
49
- const onMessage = (decodedMessage: typeof Devtools.MessageToAppHostStore.Type) => {
50
- // console.log('storeMessagePort message', decodedMessage)
58
+ const onMessage = (decodedMessage: typeof Devtools.MessageToAppClientSession.Type) => {
59
+ // console.debug('@livestore/livestore:store:devtools:onMessage', decodedMessage)
51
60
 
52
- if (decodedMessage.appHostId !== store.clientSession.coordinator.devtools.appHostId) {
61
+ if (decodedMessage.clientId !== clientId || decodedMessage.sessionId !== sessionId) {
53
62
  // console.log(`Unknown message`, event)
54
63
  return
55
64
  }
56
65
 
66
+ if (decodedMessage._tag === 'LSD.Disconnect') {
67
+ // console.error('TODO handle disconnect properly in store')
68
+ return
69
+ }
70
+
57
71
  const requestId = decodedMessage.requestId
58
72
 
59
73
  const requestIdleCallback = globalThis.requestIdleCallback ?? ((cb: () => void) => cb())
60
74
 
61
75
  switch (decodedMessage._tag) {
62
- case 'LSD.ReactivityGraphSubscribe': {
76
+ case 'LSD.ClientSession.ReactivityGraphSubscribe': {
63
77
  const includeResults = decodedMessage.includeResults
64
78
 
65
79
  const send = () =>
@@ -71,7 +85,8 @@ export const connectDevtoolsToStore = ({
71
85
  Devtools.ReactivityGraphRes.make({
72
86
  reactivityGraph: store.reactivityGraph.getSnapshot({ includeResults }),
73
87
  requestId,
74
- appHostId,
88
+ clientId,
89
+ sessionId,
75
90
  liveStoreVersion,
76
91
  }),
77
92
  ),
@@ -89,37 +104,39 @@ export const connectDevtoolsToStore = ({
89
104
 
90
105
  break
91
106
  }
92
- case 'LSD.DebugInfoReq': {
107
+ case 'LSD.ClientSession.DebugInfoReq': {
93
108
  sendToDevtools(
94
109
  Devtools.DebugInfoRes.make({
95
- debugInfo: store.syncDbWrapper.debugInfo,
110
+ debugInfo: store.sqliteDbWrapper.debugInfo,
96
111
  requestId,
97
- appHostId,
112
+ clientId,
113
+ sessionId,
98
114
  liveStoreVersion,
99
115
  }),
100
116
  )
101
117
  break
102
118
  }
103
- case 'LSD.DebugInfoHistorySubscribe': {
119
+ case 'LSD.ClientSession.DebugInfoHistorySubscribe': {
104
120
  const buffer: DebugInfo[] = []
105
121
  let hasStopped = false
106
- let rafHandle: number | undefined
122
+ let tickHandle: number | undefined
107
123
 
108
124
  const tick = () => {
109
- buffer.push(store.syncDbWrapper.debugInfo)
125
+ buffer.push(store.sqliteDbWrapper.debugInfo)
110
126
 
111
127
  // NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
112
128
  // will get the empty debug info
113
129
  // TODO We need to come up with a more graceful way to do store. Probably via a single global
114
130
  // `requestAnimationFrame` loop that is passed in somehow.
115
- store.syncDbWrapper.debugInfo = makeEmptyDebugInfo()
131
+ store.sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
116
132
 
117
133
  if (buffer.length > 10) {
118
134
  sendToDevtools(
119
135
  Devtools.DebugInfoHistoryRes.make({
120
136
  debugInfoHistory: buffer,
121
137
  requestId,
122
- appHostId,
138
+ clientId,
139
+ sessionId,
123
140
  liveStoreVersion,
124
141
  }),
125
142
  )
@@ -127,16 +144,17 @@ export const connectDevtoolsToStore = ({
127
144
  }
128
145
 
129
146
  if (hasStopped === false) {
130
- rafHandle = requestAnimationFrame(tick)
147
+ tickHandle = requestNextTick(tick)
131
148
  }
132
149
  }
133
150
 
134
- rafHandle = requestAnimationFrame(tick)
151
+ tickHandle = requestNextTick(tick)
135
152
 
136
153
  const unsub = () => {
137
154
  hasStopped = true
138
- if (rafHandle !== undefined) {
139
- cancelAnimationFrame(rafHandle)
155
+ if (tickHandle !== undefined) {
156
+ cancelTick(tickHandle)
157
+ tickHandle = undefined
140
158
  }
141
159
  }
142
160
 
@@ -144,27 +162,31 @@ export const connectDevtoolsToStore = ({
144
162
 
145
163
  break
146
164
  }
147
- case 'LSD.DebugInfoHistoryUnsubscribe': {
148
- debugInfoHistorySubscriptions.get(requestId)!()
165
+ case 'LSD.ClientSession.DebugInfoHistoryUnsubscribe': {
166
+ // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
167
+ // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
168
+ debugInfoHistorySubscriptions.get(requestId)?.()
149
169
  debugInfoHistorySubscriptions.delete(requestId)
150
170
  break
151
171
  }
152
- case 'LSD.DebugInfoResetReq': {
153
- store.syncDbWrapper.debugInfo.slowQueries.clear()
154
- sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, appHostId, liveStoreVersion }))
172
+ case 'LSD.ClientSession.DebugInfoResetReq': {
173
+ store.sqliteDbWrapper.debugInfo.slowQueries.clear()
174
+ sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
155
175
  break
156
176
  }
157
- case 'LSD.DebugInfoRerunQueryReq': {
177
+ case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
158
178
  const { queryStr, bindValues, queriedTables } = decodedMessage
159
- store.syncDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
160
- sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, appHostId, liveStoreVersion }))
179
+ store.sqliteDbWrapper.select(queryStr, bindValues, { queriedTables, skipCache: true })
180
+ sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
161
181
  break
162
182
  }
163
- case 'LSD.ReactivityGraphUnsubscribe': {
164
- reactivityGraphSubcriptions.get(requestId)!()
183
+ case 'LSD.ClientSession.ReactivityGraphUnsubscribe': {
184
+ // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
185
+ // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
186
+ reactivityGraphSubcriptions.get(requestId)?.()
165
187
  break
166
188
  }
167
- case 'LSD.LiveQueriesSubscribe': {
189
+ case 'LSD.ClientSession.LiveQueriesSubscribe': {
168
190
  const send = () =>
169
191
  requestIdleCallback(
170
192
  () =>
@@ -184,7 +206,8 @@ export const connectDevtoolsToStore = ({
184
206
  })),
185
207
  requestId,
186
208
  liveStoreVersion,
187
- appHostId,
209
+ clientId,
210
+ sessionId,
188
211
  }),
189
212
  ),
190
213
  { timeout: 500 },
@@ -199,8 +222,10 @@ export const connectDevtoolsToStore = ({
199
222
 
200
223
  break
201
224
  }
202
- case 'LSD.LiveQueriesUnsubscribe': {
203
- liveQueriesSubscriptions.get(requestId)!()
225
+ case 'LSD.ClientSession.LiveQueriesUnsubscribe': {
226
+ // NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
227
+ // WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
228
+ liveQueriesSubscriptions.get(requestId)?.()
204
229
  liveQueriesSubscriptions.delete(requestId)
205
230
  break
206
231
  }
@@ -209,6 +234,7 @@ export const connectDevtoolsToStore = ({
209
234
  }
210
235
 
211
236
  yield* storeDevtoolsChannel.listen.pipe(
237
+ // Stream.tapLogWithLabel('@livestore/livestore:store:devtools:onMessage'),
212
238
  Stream.flatten(),
213
239
  Stream.tapSync((message) => onMessage(message)),
214
240
  Stream.runDrain,
@@ -1,13 +1,12 @@
1
- import type { ClientSession, EventId, IntentionalShutdownCause, UnexpectedError } from '@livestore/common'
2
- import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
3
- import type { FiberSet, MutableHashMap, Runtime, Scope } from '@livestore/utils/effect'
1
+ import type { ClientSession, IntentionalShutdownCause, UnexpectedError } from '@livestore/common'
2
+ import type { EventId, LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
3
+ import type { Deferred, MutableHashMap, Runtime, Scope } from '@livestore/utils/effect'
4
4
  import { Schema } from '@livestore/utils/effect'
5
5
  import type * as otel from '@opentelemetry/api'
6
6
  import type { GraphQLSchema } from 'graphql'
7
7
 
8
- import type { ReactivityGraph } from '../live-queries/base-class.js'
9
8
  import type { DebugRefreshReasonBase } from '../reactive.js'
10
- import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
9
+ import type { SqliteDbWrapper } from '../SqliteDbWrapper.js'
11
10
  import type { StackInfo } from '../utils/stack-info.js'
12
11
  import type { Store } from './store.js'
13
12
 
@@ -25,6 +24,11 @@ export type LiveStoreContext =
25
24
  export class StoreAbort extends Schema.TaggedError<StoreAbort>()('LiveStore.StoreAbort', {}) {}
26
25
  export class StoreInterrupted extends Schema.TaggedError<StoreInterrupted>()('LiveStore.StoreInterrupted', {}) {}
27
26
 
27
+ export type ShutdownDeferred = Deferred.Deferred<
28
+ void,
29
+ UnexpectedError | IntentionalShutdownCause | StoreInterrupted | StoreAbort
30
+ >
31
+
28
32
  export type LiveStoreContextRunning = {
29
33
  stage: 'running'
30
34
  store: Store
@@ -38,7 +42,7 @@ export type BaseGraphQLContext = {
38
42
 
39
43
  export type GraphQLOptions<TContext> = {
40
44
  schema: GraphQLSchema
41
- makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer, sessionId: string) => TContext
45
+ makeContext: (db: SqliteDbWrapper, tracer: otel.Tracer, sessionId: string) => TContext
42
46
  }
43
47
 
44
48
  export type OtelOptions = {
@@ -56,12 +60,12 @@ export type StoreOptions<
56
60
  // TODO remove graphql-related stuff from store and move to GraphQL query directly
57
61
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
58
62
  otelOptions: OtelOptions
59
- reactivityGraph: ReactivityGraph
60
63
  disableDevtools?: boolean
61
- fiberSet: FiberSet.FiberSet
64
+ lifetimeScope: Scope.Scope
62
65
  runtime: Runtime.Runtime<Scope.Scope>
63
66
  batchUpdates: (runUpdates: () => void) => void
64
- unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId, MutationEvent.ForSchema<TSchema>>
67
+ // TODO validate whether we still need this
68
+ unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId.EventId, MutationEvent.ForSchema<TSchema>>
65
69
  }
66
70
 
67
71
  export type RefreshReason =
@@ -69,7 +73,7 @@ export type RefreshReason =
69
73
  | {
70
74
  _tag: 'mutate'
71
75
  /** The mutations that were applied */
72
- mutations: ReadonlyArray<MutationEvent.Any>
76
+ mutations: ReadonlyArray<MutationEvent.AnyDecoded | MutationEvent.PartialAnyDecoded>
73
77
 
74
78
  /** The tables that were written to by the event */
75
79
  writeTables: ReadonlyArray<string>
@@ -81,6 +85,8 @@ export type RefreshReason =
81
85
  label?: string
82
86
  stackInfo?: StackInfo
83
87
  }
88
+ | { _tag: 'subscribe.initial'; label?: string }
89
+ | { _tag: 'subscribe.update'; label?: string }
84
90
  | { _tag: 'manual'; label?: string }
85
91
 
86
92
  export type QueryDebugInfo = {
@@ -99,12 +105,8 @@ export type StoreOtel = {
99
105
  export type StoreMutateOptions = {
100
106
  label?: string
101
107
  skipRefresh?: boolean
102
- wasSyncMessage?: boolean
103
- /**
104
- * When set to `false` the mutation won't be persisted in the mutation log and sync server (but still synced).
105
- * This can be useful e.g. for fine-granular update events (e.g. position updates during drag & drop)
106
- *
107
- * @default true
108
- */
109
- persisted?: boolean
108
+ spanLinks?: otel.Link[]
109
+ otelContext?: otel.Context
110
110
  }
111
+
112
+ export type Unsubscribe = () => void