@livestore/livestore 0.3.0-dev.4 → 0.3.0-dev.5

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.
@@ -61,6 +61,5 @@ export const makeExecBeforeFirstRun =
61
61
 
62
62
  // NOTE It's important that we only mutate and don't refresh here, as this function is called during a render
63
63
  store.mutate({ otelContext, skipRefresh: true }, table.insert({ id, ...insertValues }))
64
- // store.mutateWithoutRefresh(table.insert({ id, ...insertValues }), { otelContext, coordinatorMode: 'default' })
65
64
  }
66
65
  }
@@ -140,9 +140,13 @@ export const createStore = <
140
140
 
141
141
  const shutdown = (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) =>
142
142
  Scope.close(lifetimeScope, Exit.failCause(cause)).pipe(
143
+ Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown', duration: 500 }),
144
+ Effect.timeout(1000),
145
+ Effect.catchTag('TimeoutException', () =>
146
+ Effect.logError('@livestore/livestore:shutdown: Timed out after 1 second'),
147
+ ),
143
148
  Effect.tap(() => (shutdownDeferred ? Deferred.failCause(shutdownDeferred, cause) : Effect.void)),
144
149
  Effect.tap(() => Effect.logDebug('LiveStore shutdown complete')),
145
- Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown', duration: 500 }),
146
150
  Effect.withSpan('livestore:shutdown'),
147
151
  )
148
152
 
@@ -38,12 +38,12 @@ export const connectDevtoolsToStore = ({
38
38
  store: IStore
39
39
  }) =>
40
40
  Effect.gen(function* () {
41
- const appHostId = store.clientSession.coordinator.devtools.appHostId
42
-
43
41
  const reactivityGraphSubcriptions: SubMap = new Map()
44
42
  const liveQueriesSubscriptions: SubMap = new Map()
45
43
  const debugInfoHistorySubscriptions: SubMap = new Map()
46
44
 
45
+ const { clientId, sessionId } = store.clientSession
46
+
47
47
  yield* Effect.addFinalizer(() =>
48
48
  Effect.sync(() => {
49
49
  reactivityGraphSubcriptions.forEach((unsub) => unsub())
@@ -58,13 +58,13 @@ export const connectDevtoolsToStore = ({
58
58
  const onMessage = (decodedMessage: typeof Devtools.MessageToAppClientSession.Type) => {
59
59
  // console.debug('@livestore/livestore:store:devtools:onMessage', decodedMessage)
60
60
 
61
- if (decodedMessage.appHostId !== store.clientSession.coordinator.devtools.appHostId) {
61
+ if (decodedMessage.clientId !== clientId || decodedMessage.sessionId !== sessionId) {
62
62
  // console.log(`Unknown message`, event)
63
63
  return
64
64
  }
65
65
 
66
66
  if (decodedMessage._tag === 'LSD.Disconnect') {
67
- console.error('TODO handle disconnect properly in store')
67
+ // console.error('TODO handle disconnect properly in store')
68
68
  return
69
69
  }
70
70
 
@@ -85,7 +85,8 @@ export const connectDevtoolsToStore = ({
85
85
  Devtools.ReactivityGraphRes.make({
86
86
  reactivityGraph: store.reactivityGraph.getSnapshot({ includeResults }),
87
87
  requestId,
88
- appHostId,
88
+ clientId,
89
+ sessionId,
89
90
  liveStoreVersion,
90
91
  }),
91
92
  ),
@@ -108,7 +109,8 @@ export const connectDevtoolsToStore = ({
108
109
  Devtools.DebugInfoRes.make({
109
110
  debugInfo: store.syncDbWrapper.debugInfo,
110
111
  requestId,
111
- appHostId,
112
+ clientId,
113
+ sessionId,
112
114
  liveStoreVersion,
113
115
  }),
114
116
  )
@@ -133,7 +135,8 @@ export const connectDevtoolsToStore = ({
133
135
  Devtools.DebugInfoHistoryRes.make({
134
136
  debugInfoHistory: buffer,
135
137
  requestId,
136
- appHostId,
138
+ clientId,
139
+ sessionId,
137
140
  liveStoreVersion,
138
141
  }),
139
142
  )
@@ -168,13 +171,13 @@ export const connectDevtoolsToStore = ({
168
171
  }
169
172
  case 'LSD.DebugInfoResetReq': {
170
173
  store.syncDbWrapper.debugInfo.slowQueries.clear()
171
- sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, appHostId, liveStoreVersion }))
174
+ sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
172
175
  break
173
176
  }
174
177
  case 'LSD.DebugInfoRerunQueryReq': {
175
178
  const { queryStr, bindValues, queriedTables } = decodedMessage
176
179
  store.syncDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
177
- sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, appHostId, liveStoreVersion }))
180
+ sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
178
181
  break
179
182
  }
180
183
  case 'LSD.ReactivityGraphUnsubscribe': {
@@ -203,7 +206,8 @@ export const connectDevtoolsToStore = ({
203
206
  })),
204
207
  requestId,
205
208
  liveStoreVersion,
206
- appHostId,
209
+ clientId,
210
+ sessionId,
207
211
  }),
208
212
  ),
209
213
  { timeout: 500 },
@@ -75,7 +75,7 @@ export type RefreshReason =
75
75
  | {
76
76
  _tag: 'mutate'
77
77
  /** The mutations that were applied */
78
- mutations: ReadonlyArray<MutationEvent.Any | MutationEvent.PartialAny>
78
+ mutations: ReadonlyArray<MutationEvent.AnyDecoded | MutationEvent.PartialAny>
79
79
 
80
80
  /** The tables that were written to by the event */
81
81
  writeTables: ReadonlyArray<string>
@@ -7,10 +7,12 @@ import type {
7
7
  UnexpectedError,
8
8
  } from '@livestore/common'
9
9
  import {
10
+ Devtools,
10
11
  getExecArgsFromMutation,
11
12
  getResultSchema,
12
13
  IntentionalShutdownCause,
13
14
  isQueryBuilder,
15
+ liveStoreVersion,
14
16
  makeClientSessionSyncProcessor,
15
17
  prepareBindValues,
16
18
  QueryBuilderAstSymbol,
@@ -26,6 +28,7 @@ import {
26
28
  import { assertNever, isDevEnv } from '@livestore/utils'
27
29
  import type { Scope } from '@livestore/utils/effect'
28
30
  import { Cause, Data, Effect, Inspectable, MutableHashMap, Runtime, Schema } from '@livestore/utils/effect'
31
+ import { nanoid } from '@livestore/utils/nanoid'
29
32
  import * as otel from '@opentelemetry/api'
30
33
  import { type GraphQLSchema } from 'graphql'
31
34
 
@@ -101,15 +104,15 @@ export class Store<
101
104
 
102
105
  this.syncProcessor = makeClientSessionSyncProcessor({
103
106
  schema,
104
- initialLeaderHead: clientSession.coordinator.mutations.initialMutationEventId,
107
+ initialLeaderHead: clientSession.leaderThread.mutations.initialMutationEventId,
105
108
  // rebaseBehaviour: 'auto-rebase',
106
109
  pushToLeader: (batch) =>
107
- clientSession.coordinator.mutations.push(batch).pipe(
110
+ clientSession.leaderThread.mutations.push(batch).pipe(
108
111
  // NOTE we don't want to shutdown in case of an invalid push error, since it will be retried
109
112
  Effect.catchTag('InvalidPushError', Effect.ignoreLogged),
110
113
  this.runEffectFork,
111
114
  ),
112
- pullFromLeader: clientSession.coordinator.mutations.pull,
115
+ pullFromLeader: clientSession.leaderThread.mutations.pull,
113
116
  applyMutation: (mutationEventDecoded, { otelContext, withChangeset }) => {
114
117
  const mutationDef = schema.mutations.get(mutationEventDecoded.mutation)!
115
118
  const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
@@ -204,11 +207,7 @@ export class Store<
204
207
 
205
208
  if (graphQLOptions) {
206
209
  this.graphQLSchema = graphQLOptions.schema
207
- this.graphQLContext = graphQLOptions.makeContext(
208
- this.syncDbWrapper,
209
- this.otel.tracer,
210
- clientSession.coordinator.sessionId,
211
- )
210
+ this.graphQLContext = graphQLOptions.makeContext(this.syncDbWrapper, this.otel.tracer, clientSession.sessionId)
212
211
  }
213
212
 
214
213
  Effect.gen(this, function* () {
@@ -248,7 +247,7 @@ export class Store<
248
247
  }
249
248
 
250
249
  get sessionId(): string {
251
- return this.clientSession.coordinator.sessionId
250
+ return this.clientSession.sessionId
252
251
  }
253
252
 
254
253
  /**
@@ -363,7 +362,7 @@ export class Store<
363
362
  const { mutationsEvents, options } = this.getMutateArgs(firstMutationOrTxnFnOrOptions, restMutations)
364
363
 
365
364
  for (const mutationEvent of mutationsEvents) {
366
- replaceSessionIdSymbol(mutationEvent.args, this.clientSession.coordinator.sessionId)
365
+ replaceSessionIdSymbol(mutationEvent.args, this.clientSession.sessionId)
367
366
  }
368
367
 
369
368
  if (mutationsEvents.length === 0) return
@@ -400,7 +399,7 @@ export class Store<
400
399
  const applyMutations = () => this.syncProcessor.push(mutationsEvents, { otelContext })
401
400
 
402
401
  if (mutationsEvents.length > 1) {
403
- // TODO: what to do about coordinator transaction here?
402
+ // TODO: what to do about leader transaction here?
404
403
  return this.syncDbWrapper.txn(applyMutations)
405
404
  } else {
406
405
  return applyMutations()
@@ -464,79 +463,6 @@ export class Store<
464
463
  )
465
464
  }
466
465
 
467
- // #region mutateWithoutRefresh
468
- /**
469
- * Apply a mutation to the store.
470
- * Returns the tables that were affected by the event.
471
- * This is an internal method that doesn't trigger a refresh;
472
- * the caller must refresh queries after calling this method.
473
- */
474
- // private mutateWithoutRefresh = (
475
- // mutationEventDecoded: MutationEvent.ForSchema<TSchema> | MutationEvent.PartialForSchema<TSchema>,
476
- // options: {
477
- // otelContext: otel.Context
478
- // },
479
- // ): { writeTables: ReadonlySet<string>; durationMs: number } => {
480
- // // const mutationDef =
481
- // // this.schema.mutations.get(mutationEventDecoded.mutation) ??
482
- // // shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
483
-
484
- // // // const mutationEventDecoded: MutationEvent.ForSchema<TSchema> = isPartialMutationEvent(mutationEventDecoded_)
485
- // // // ? { ...mutationEventDecoded_, ...nextMutationEventId() }
486
- // // // : mutationEventDecoded_
487
-
488
- // // // NOTE we also need this temporary workaround here since some code-paths use `mutateWithoutRefresh` directly
489
- // // // e.g. the row-query functionality
490
- // // if (Predicate.hasProperty(mutationEventDecoded, 'id')) {
491
- // // if (MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id))) {
492
- // // // NOTE this data should never be used
493
- // // return { writeTables: new Set(), durationMs: 0 }
494
- // // } else {
495
- // // MutableHashMap.set(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id), mutationEventDecoded)
496
- // // }
497
- // // }
498
-
499
- // const { otelContext } = options
500
-
501
- // return this.otel.tracer.startActiveSpan(
502
- // 'LiveStore:mutateWithoutRefresh',
503
- // {
504
- // attributes: {
505
- // 'livestore.mutation': mutationEventDecoded.mutation,
506
- // // TODO(performance) add flag to disable this
507
- // 'livestore.args': JSON.stringify(mutationEventDecoded.args, null, 2),
508
- // },
509
- // },
510
- // otelContext,
511
- // (span) => {
512
- // const otelContext = otel.trace.setSpan(otel.context.active(), span)
513
-
514
- // const allWriteTables = new Set<string>()
515
- // let durationMsTotal = 0
516
-
517
- // replaceSessionIdSymbol(mutationEventDecoded.args, this.clientSession.coordinator.sessionId)
518
-
519
- // const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
520
-
521
- // for (const {
522
- // statementSql,
523
- // bindValues,
524
- // writeTables = this.syncDbWrapper.getTablesUsed(statementSql),
525
- // } of execArgsArr) {
526
- // const { durationMs } = this.syncDbWrapper.execute(statementSql, bindValues, writeTables, { otelContext })
527
-
528
- // durationMsTotal += durationMs
529
- // writeTables.forEach((table) => allWriteTables.add(table))
530
- // }
531
-
532
- // span.end()
533
-
534
- // return { writeTables: allWriteTables, durationMs: durationMsTotal }
535
- // },
536
- // )
537
- // }
538
- // #endregion mutateWithoutRefresh
539
-
540
466
  private makeTableRef = (tableName: string) =>
541
467
  this.reactivityGraph.makeRef(null, {
542
468
  equal: () => false,
@@ -546,21 +472,23 @@ export class Store<
546
472
 
547
473
  __devDownloadDb = (source: 'local' | 'leader' = 'local') => {
548
474
  Effect.gen(this, function* () {
549
- const data = source === 'local' ? this.syncDbWrapper.export() : yield* this.clientSession.coordinator.export
475
+ const data = source === 'local' ? this.syncDbWrapper.export() : yield* this.clientSession.leaderThread.export
550
476
  downloadBlob(data, `livestore-${Date.now()}.db`)
551
477
  }).pipe(this.runEffectFork)
552
478
  }
553
479
 
554
480
  __devDownloadMutationLogDb = () => {
555
481
  Effect.gen(this, function* () {
556
- const data = yield* this.clientSession.coordinator.getMutationLogData
482
+ const data = yield* this.clientSession.leaderThread.getMutationLogData
557
483
  downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
558
484
  }).pipe(this.runEffectFork)
559
485
  }
560
486
 
561
- __devHardReset = () => {
487
+ __devHardReset = (mode: 'all-data' | 'only-app-db' = 'all-data') => {
562
488
  Effect.gen(this, function* () {
563
- console.warn(`Not yet implemented`)
489
+ yield* this.clientSession.leaderThread.sendDevtoolsMessage(
490
+ Devtools.ResetAllDataReq.make({ liveStoreVersion, mode, requestId: nanoid() }),
491
+ )
564
492
  }).pipe(this.runEffectFork)
565
493
  }
566
494
 
@@ -568,13 +496,13 @@ export class Store<
568
496
  Effect.gen(this, function* () {
569
497
  const session = this.syncProcessor.syncStateRef.current
570
498
  console.log('Session sync state:', session)
571
- const leader = yield* this.clientSession.coordinator.getLeaderSyncState
499
+ const leader = yield* this.clientSession.leaderThread.getSyncState
572
500
  console.log('Leader sync state:', leader)
573
501
  }).pipe(this.runEffectFork)
574
502
  }
575
503
 
576
504
  __devShutdown = (cause?: Cause.Cause<UnexpectedError>) => {
577
- this.clientSession.coordinator
505
+ this.clientSession
578
506
  .shutdown(cause ?? Cause.fail(IntentionalShutdownCause.make({ reason: 'manual' })))
579
507
  .pipe(Effect.tapCauseLogPretty, Effect.provide(this.runtime), Effect.runFork)
580
508
  }