@livestore/livestore 0.0.0-snapshot-b2abb1712675b77ec94147aeb6c4ffa858b97daa → 0.0.0-snapshot-d494b38b4c747ce88538b162d3be1e7a74efe171

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.
@@ -8,7 +8,6 @@ import {
8
8
  } from '@livestore/common'
9
9
  import {
10
10
  Devtools,
11
- getDurationMsFromSpan,
12
11
  getExecStatementsFromMaterializer,
13
12
  getResultSchema,
14
13
  hashMaterializerResults,
@@ -115,70 +114,71 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
115
114
  schema,
116
115
  clientSession,
117
116
  runtime: effectContext.runtime,
118
- materializeEvent: (eventDecoded, { otelContext, withChangeset, materializerHashLeader }) => {
119
- const { eventDef, materializer } = getEventDef(schema, eventDecoded.name)
120
-
121
- const execArgsArr = getExecStatementsFromMaterializer({
122
- eventDef,
123
- materializer,
124
- dbState: this.sqliteDbWrapper,
125
- event: { decoded: eventDecoded, encoded: undefined },
126
- })
127
-
128
- const materializerHash = isDevEnv() ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none()
129
-
130
- if (
131
- materializerHashLeader._tag === 'Some' &&
132
- materializerHash._tag === 'Some' &&
133
- materializerHashLeader.value !== materializerHash.value
134
- ) {
135
- void this.shutdown(
136
- Cause.fail(
137
- UnexpectedError.make({
117
+ materializeEvent: Effect.fn('client-session-sync-processor:materialize-event')(
118
+ (eventDecoded, { withChangeset, materializerHashLeader }) =>
119
+ Effect.gen(this, function* () {
120
+ const { eventDef, materializer } = getEventDef(schema, eventDecoded.name)
121
+
122
+ const execArgsArr = getExecStatementsFromMaterializer({
123
+ eventDef,
124
+ materializer,
125
+ dbState: this.sqliteDbWrapper,
126
+ event: { decoded: eventDecoded, encoded: undefined },
127
+ })
128
+
129
+ const materializerHash = isDevEnv() ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none()
130
+
131
+ if (
132
+ materializerHashLeader._tag === 'Some' &&
133
+ materializerHash._tag === 'Some' &&
134
+ materializerHashLeader.value !== materializerHash.value
135
+ ) {
136
+ const error = UnexpectedError.make({
138
137
  cause: `Materializer hash mismatch detected for event "${eventDecoded.name}".`,
139
138
  note: `Please make sure your event materializer is a pure function without side effects.`,
140
- }),
141
- ),
142
- )
143
- }
144
-
145
- const writeTablesForEvent = new Set<string>()
146
-
147
- const exec = () => {
148
- for (const {
149
- statementSql,
150
- bindValues,
151
- writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
152
- } of execArgsArr) {
153
- try {
154
- this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables })
155
- } catch (cause) {
156
- throw UnexpectedError.make({
157
- cause,
158
- note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`,
159
139
  })
160
- }
161
140
 
162
- // durationMsTotal += durationMs
163
- for (const table of writeTables) {
164
- writeTablesForEvent.add(table)
141
+ // Fork the shutdown effect to run in the background as a daemon,
142
+ // ensuring it's not interrupted.
143
+ yield* Effect.forkDaemon(this.clientSession.shutdown(Cause.fail(error)))
144
+
145
+ // TODO: we should probably handle this more gracefully using Effect’s error channel
165
146
  }
166
- }
167
- }
168
147
 
169
- let sessionChangeset:
170
- | { _tag: 'sessionChangeset'; data: Uint8Array; debug: any }
171
- | { _tag: 'no-op' }
172
- | { _tag: 'unset' } = { _tag: 'unset' }
148
+ const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
149
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
150
+ return yield* Effect.sync(() => {
151
+ const writeTablesForEvent = new Set<string>()
152
+
153
+ const exec = () => {
154
+ for (const {
155
+ statementSql,
156
+ bindValues,
157
+ writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
158
+ } of execArgsArr) {
159
+ this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables })
160
+
161
+ // durationMsTotal += durationMs
162
+ for (const table of writeTables) {
163
+ writeTablesForEvent.add(table)
164
+ }
165
+ }
166
+ }
173
167
 
174
- if (withChangeset === true) {
175
- sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
176
- } else {
177
- exec()
178
- }
168
+ let sessionChangeset:
169
+ | { _tag: 'sessionChangeset'; data: Uint8Array; debug: any }
170
+ | { _tag: 'no-op' }
171
+ | { _tag: 'unset' } = { _tag: 'unset' }
172
+ if (withChangeset === true) {
173
+ sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
174
+ } else {
175
+ exec()
176
+ }
179
177
 
180
- return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash }
181
- },
178
+ return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash }
179
+ })
180
+ }),
181
+ ),
182
182
  rollback: (changeset) => {
183
183
  this.sqliteDbWrapper.rollback(changeset)
184
184
  },
@@ -552,89 +552,80 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
552
552
  } = (firstEventOrTxnFnOrOptions: any, ...restEvents: any[]) => {
553
553
  const { events, options } = this.getCommitArgs(firstEventOrTxnFnOrOptions, restEvents)
554
554
 
555
- for (const event of events) {
556
- replaceSessionIdSymbol(event.args, this.clientSession.sessionId)
557
- }
558
-
559
- if (events.length === 0) return
560
-
561
- const skipRefresh = options?.skipRefresh ?? false
562
-
563
- const commitsSpan = otel.trace.getSpan(this.otel.commitsSpanContext)!
564
- commitsSpan.addEvent('commit')
565
-
566
- // console.group('LiveStore.commit', { skipRefresh })
567
- // events.forEach((_) => console.debug(_.name, _.args))
568
- // console.groupEnd()
555
+ Runtime.runSync(
556
+ this.effectContext.runtime,
557
+ Effect.gen(this, function* () {
558
+ yield* Effect.sync(() => {
559
+ for (const event of events) {
560
+ replaceSessionIdSymbol(event.args, this.clientSession.sessionId)
561
+ }
562
+ })
569
563
 
570
- let durationMs: number
564
+ if (events.length === 0) return
571
565
 
572
- return this.otel.tracer.startActiveSpan(
573
- 'LiveStore:commit',
574
- {
575
- attributes: {
576
- 'livestore.eventsCount': events.length,
577
- 'livestore.eventTags': events.map((_) => _.name),
578
- 'livestore.commitLabel': options?.label,
579
- },
580
- links: options?.spanLinks,
581
- },
582
- options?.otelContext ?? this.otel.commitsSpanContext,
583
- (span) => {
584
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
566
+ const localRuntime = yield* Effect.runtime()
585
567
 
586
- try {
587
- // Materialize events to state
588
- const { writeTables } = (() => {
589
- try {
590
- const materializeEvents = () => {
591
- return Runtime.runSync(this.effectContext.runtime, this.syncProcessor.push(events, { otelContext }))
592
- }
568
+ const materializeEventsTx = Effect.try({
569
+ try: () => {
570
+ const materializeEvents = this.syncProcessor.push(events)
593
571
 
594
- if (events.length > 1) {
595
- return this.sqliteDbWrapper.txn(materializeEvents)
596
- } else {
597
- return materializeEvents()
598
- }
599
- } catch (e: any) {
600
- console.error(e)
601
- span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
602
- throw e
603
- } finally {
604
- span.end()
572
+ const runMaterializeEvents = () => {
573
+ return Runtime.runSync(localRuntime, materializeEvents)
605
574
  }
606
- })()
607
575
 
608
- const tablesToUpdate = [] as [Ref<null, ReactivityGraphContext, RefreshReason>, null][]
609
- for (const tableName of writeTables) {
610
- const tableRef = this.tableRefs[tableName]
611
- assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
612
- tablesToUpdate.push([tableRef!, null])
613
- }
614
-
615
- const debugRefreshReason = {
616
- _tag: 'commit' as const,
617
- events,
618
- writeTables: Array.from(writeTables),
619
- }
576
+ if (events.length > 1) {
577
+ // TODO: what to do about leader transaction here?
578
+ return this.sqliteDbWrapper.txn(runMaterializeEvents)
579
+ } else {
580
+ return runMaterializeEvents()
581
+ }
582
+ },
583
+ catch: (cause) => UnexpectedError.make({ cause }),
584
+ })
620
585
 
621
- // Update all table refs together in a batch, to only trigger one reactive update
622
- this.reactivityGraph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext, skipRefresh })
623
- } catch (e: any) {
624
- console.error(e)
625
- span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
626
- throw e
627
- } finally {
628
- span.end()
586
+ const { writeTables } = yield* materializeEventsTx
629
587
 
630
- durationMs = getDurationMsFromSpan(span)
588
+ const tablesToUpdate: [Ref<null, ReactivityGraphContext, RefreshReason>, null][] = []
589
+ for (const tableName of writeTables) {
590
+ const tableRef = this.tableRefs[tableName]
591
+ assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
592
+ tablesToUpdate.push([tableRef!, null])
631
593
  }
632
594
 
633
- return { durationMs }
634
- },
595
+ const debugRefreshReason: RefreshReason = {
596
+ _tag: 'commit',
597
+ events,
598
+ writeTables: Array.from(writeTables),
599
+ }
600
+ const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
601
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
602
+ const skipRefresh = options?.skipRefresh ?? false
603
+
604
+ // Update all table refs together in a batch, to only trigger one reactive update
605
+ yield* Effect.sync(() => {
606
+ this.reactivityGraph.setRefs(tablesToUpdate, {
607
+ debugRefreshReason,
608
+ otelContext,
609
+ skipRefresh,
610
+ })
611
+ })
612
+ }).pipe(
613
+ Effect.withSpan('LiveStore:commit', {
614
+ root: true,
615
+ attributes: {
616
+ 'livestore.eventsCount': events.length,
617
+ 'livestore.eventTags': events.map((_) => _.name),
618
+ 'livestore.commitLabel': options?.label,
619
+ },
620
+ links: options?.spanLinks?.map((link) => ({
621
+ _tag: 'SpanLink',
622
+ span: OtelTracer.makeExternalSpan(link.context),
623
+ attributes: link.attributes ?? {},
624
+ })),
625
+ }),
626
+ ),
635
627
  )
636
628
  }
637
- // #endregion commit
638
629
 
639
630
  /**
640
631
  * Returns an async iterable of events.