From 4225655a7ad78f77d95efc64aace4a9a47b09f0e Mon Sep 17 00:00:00 2001 From: Igor Gassmann Date: Fri, 13 Jun 2025 12:04:45 +0200 Subject: [PATCH 1/3] refactor: migrate ClientSessionSyncProcessor.materializeEvent to Effect --- .../src/sync/ClientSessionSyncProcessor.ts | 8 +- .../__snapshots__/db-query.test.ts.snap | 35 +++++ .../@livestore/livestore/src/store/store.ts | 136 ++++++++++-------- .../useClientDocument.test.tsx.snap | 20 +++ 4 files changed, 132 insertions(+), 67 deletions(-) diff --git a/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts b/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts index ac8d30c37..21a37cead 100644 --- a/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts +++ b/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts @@ -53,11 +53,11 @@ export const makeClientSessionSyncProcessor = ({ materializeEvent: ( eventDecoded: LiveStoreEvent.AnyDecoded, options: { otelContext: otel.Context; withChangeset: boolean; materializerHashLeader: Option.Option }, - ) => { + ) => Effect.Effect<{ writeTables: Set sessionChangeset: { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } | { _tag: 'no-op' } | { _tag: 'unset' } materializerHash: Option.Option - } + }> rollback: (changeset: Uint8Array) => void refreshTables: (tables: Set) => void span: otel.Span @@ -151,7 +151,7 @@ export const makeClientSessionSyncProcessor = ({ writeTables: newWriteTables, sessionChangeset, materializerHash, - } = materializeEvent(decodedEventDef, { + } = yield* materializeEvent(decodedEventDef, { otelContext, withChangeset: true, materializerHashLeader: Option.none(), @@ -301,7 +301,7 @@ export const makeClientSessionSyncProcessor = ({ writeTables: newWriteTables, sessionChangeset, materializerHash, - } = materializeEvent(decodedEventDef, { + } = yield* materializeEvent(decodedEventDef, { otelContext, withChangeset: true, materializerHashLeader: event.meta.materializerHashLeader, diff --git a/packages/@livestore/livestore/src/live-queries/__snapshots__/db-query.test.ts.snap b/packages/@livestore/livestore/src/live-queries/__snapshots__/db-query.test.ts.snap index 69df5d85c..17a600822 100644 --- a/packages/@livestore/livestore/src/live-queries/__snapshots__/db-query.test.ts.snap +++ b/packages/@livestore/livestore/src/live-queries/__snapshots__/db-query.test.ts.snap @@ -25,6 +25,11 @@ exports[`otel > QueryBuilder subscription - basic functionality 1`] = ` "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "@livestore/common:LeaderSyncProcessor:push", @@ -144,6 +149,11 @@ exports[`otel > QueryBuilder subscription - direct table subscription 1`] = ` "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "@livestore/common:LeaderSyncProcessor:push", @@ -263,12 +273,22 @@ exports[`otel > QueryBuilder subscription - unsubscribe functionality 1`] = ` "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "client-session-sync-processor:push", "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "@livestore/common:LeaderSyncProcessor:push", @@ -438,6 +458,11 @@ exports[`otel > otel 3`] = ` "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "@livestore/common:LeaderSyncProcessor:push", @@ -719,6 +744,11 @@ exports[`otel > with thunks 7`] = ` "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "@livestore/common:LeaderSyncProcessor:push", @@ -834,6 +864,11 @@ exports[`otel > with thunks with query builder and without labels 3`] = ` "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "@livestore/common:LeaderSyncProcessor:push", diff --git a/packages/@livestore/livestore/src/store/store.ts b/packages/@livestore/livestore/src/store/store.ts index 29415b34f..5b9faa2fb 100644 --- a/packages/@livestore/livestore/src/store/store.ts +++ b/packages/@livestore/livestore/src/store/store.ts @@ -128,72 +128,80 @@ export class Store { - const { eventDef, materializer } = getEventDef(schema, eventDecoded.name) - - const execArgsArr = getExecStatementsFromMaterializer({ - eventDef, - materializer, - dbState: this.sqliteDbWrapper, - event: { decoded: eventDecoded, encoded: undefined }, - }) - - const materializerHash = isDevEnv() ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none() - - if ( - materializerHashLeader._tag === 'Some' && - materializerHash._tag === 'Some' && - materializerHashLeader.value !== materializerHash.value - ) { - void this.shutdown( - Cause.fail( - UnexpectedError.make({ - cause: `Materializer hash mismatch detected for event "${eventDecoded.name}".`, - note: `Please make sure your event materializer is a pure function without side effects.`, - }), - ), - ) - } - - const writeTablesForEvent = new Set() - - const exec = () => { - for (const { - statementSql, - bindValues, - writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql), - } of execArgsArr) { - try { - this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables }) - } catch (cause) { - throw UnexpectedError.make({ - cause, - note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`, - }) - } - - // durationMsTotal += durationMs - for (const table of writeTables) { - writeTablesForEvent.add(table) + materializeEvent: Effect.fn('client-session-sync-processor:materialize-event')( + (eventDecoded, { otelContext, withChangeset, materializerHashLeader }) => + Effect.gen(this, function* () { + const { eventDef, materializer } = getEventDef(schema, eventDecoded.name) + + const execArgsArr = getExecStatementsFromMaterializer({ + eventDef, + materializer, + dbState: this.sqliteDbWrapper, + event: { decoded: eventDecoded, encoded: undefined }, + }) + + const materializerHash = isDevEnv() ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none() + + if ( + materializerHashLeader._tag === 'Some' && + materializerHash._tag === 'Some' && + materializerHashLeader.value !== materializerHash.value + ) { + // Fork the shutdown effect to run in the background as a daemon, ensuring it's not interrupted. + // TODO: we should probably handle this more gracefully using Effect’s error channel + yield* Effect.forkDaemon( + this.shutdown( + Cause.fail( + UnexpectedError.make({ + cause: `Materializer hash mismatch detected for event "${eventDecoded.name}".`, + note: `Please make sure your event materializer is a pure function without side effects.`, + }), + ), + ), + ) } - this.sqliteDbWrapper.debug.head = eventDecoded.seqNum - } - } - - let sessionChangeset: - | { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } - | { _tag: 'no-op' } - | { _tag: 'unset' } = { _tag: 'unset' } + return yield* Effect.sync(() => { + const writeTablesForEvent = new Set() + + const exec = () => { + for (const { + statementSql, + bindValues, + writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql), + } of execArgsArr) { + try { + this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables }) + } catch (cause) { + throw UnexpectedError.make({ + cause, + note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`, + }) + } + + // durationMsTotal += durationMs + for (const table of writeTables) { + writeTablesForEvent.add(table) + } + + this.sqliteDbWrapper.debug.head = eventDecoded.seqNum + } + } - if (withChangeset === true) { - sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset - } else { - exec() - } + let sessionChangeset: + | { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } + | { _tag: 'no-op' } + | { _tag: 'unset' } = { _tag: 'unset' } + if (withChangeset === true) { + sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset + } else { + exec() + } - return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash } - }, + return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash } + }) + }), + ), rollback: (changeset) => { this.sqliteDbWrapper.rollback(changeset) }, @@ -749,7 +757,9 @@ export class Store): Effect.Effect => { - this.checkShutdown('shutdown') + if (this.isShutdown) { + return Effect.void + } this.isShutdown = true return this.clientSession.shutdown( diff --git a/packages/@livestore/react/src/__snapshots__/useClientDocument.test.tsx.snap b/packages/@livestore/react/src/__snapshots__/useClientDocument.test.tsx.snap index 7d62734e0..a1b17bcb0 100644 --- a/packages/@livestore/react/src/__snapshots__/useClientDocument.test.tsx.snap +++ b/packages/@livestore/react/src/__snapshots__/useClientDocument.test.tsx.snap @@ -25,12 +25,22 @@ exports[`useClientDocument > otel > should update the data based on component ke "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "client-session-sync-processor:push", "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "@livestore/common:LeaderSyncProcessor:push", @@ -263,12 +273,22 @@ exports[`useClientDocument > otel > should update the data based on component ke "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "client-session-sync-processor:push", "attributes": { "batchSize": 1, }, + "children": [ + { + "_name": "client-session-sync-processor:materialize-event", + }, + ], }, { "_name": "@livestore/common:LeaderSyncProcessor:push", From 9644d7ce868de1630349341e8140eab8535a22ab Mon Sep 17 00:00:00 2001 From: Igor Gassmann Date: Tue, 17 Jun 2025 11:29:36 +0200 Subject: [PATCH 2/3] refactor: link "livestore.in-memory-db:execute" to "client-session-sync-processor:materialize-event" span --- .../src/sync/ClientSessionSyncProcessor.ts | 18 +-- .../__snapshots__/db-query.test.ts.snap | 112 +++++++++--------- .../@livestore/livestore/src/store/store.ts | 6 +- .../useClientDocument.test.tsx.snap | 96 +++++++-------- 4 files changed, 112 insertions(+), 120 deletions(-) diff --git a/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts b/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts index 21a37cead..3ac1c5bb4 100644 --- a/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts +++ b/packages/@livestore/common/src/sync/ClientSessionSyncProcessor.ts @@ -13,7 +13,7 @@ import { Stream, Subscribable, } from '@livestore/utils/effect' -import * as otel from '@opentelemetry/api' +import type * as otel from '@opentelemetry/api' import { type ClientSession, SyncError, type UnexpectedError } from '../adapter-types.ts' import * as EventSequenceNumber from '../schema/EventSequenceNumber.ts' @@ -52,7 +52,7 @@ export const makeClientSessionSyncProcessor = ({ runtime: Runtime.Runtime materializeEvent: ( eventDecoded: LiveStoreEvent.AnyDecoded, - options: { otelContext: otel.Context; withChangeset: boolean; materializerHashLeader: Option.Option }, + options: { withChangeset: boolean; materializerHashLeader: Option.Option }, ) => Effect.Effect<{ writeTables: Set sessionChangeset: { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } | { _tag: 'no-op' } | { _tag: 'unset' } @@ -96,10 +96,7 @@ export const makeClientSessionSyncProcessor = ({ /** We're queuing push requests to reduce the number of messages sent to the leader by batching them */ const leaderPushQueue = BucketQueue.make().pipe(Effect.runSync) - const push: ClientSessionSyncProcessor['push'] = Effect.fn('client-session-sync-processor:push')(function* ( - batch, - { otelContext }, - ) { + const push: ClientSessionSyncProcessor['push'] = Effect.fn('client-session-sync-processor:push')(function* (batch) { // TODO validate batch let baseEventSequenceNumber = syncStateRef.current.localHead @@ -152,7 +149,6 @@ export const makeClientSessionSyncProcessor = ({ sessionChangeset, materializerHash, } = yield* materializeEvent(decodedEventDef, { - otelContext, withChangeset: true, materializerHashLeader: Option.none(), }) @@ -176,8 +172,6 @@ export const makeClientSessionSyncProcessor = ({ rejectCount: 0, } - const otelContext = otel.trace.setSpan(otel.context.active(), span) - const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () { if (confirmUnsavedChanges && typeof window !== 'undefined' && typeof window.addEventListener === 'function') { const onBeforeUnload = (event: BeforeUnloadEvent) => { @@ -302,7 +296,6 @@ export const makeClientSessionSyncProcessor = ({ sessionChangeset, materializerHash, } = yield* materializeEvent(decodedEventDef, { - otelContext, withChangeset: true, materializerHashLeader: event.meta.materializerHashLeader, }) @@ -359,10 +352,7 @@ export const makeClientSessionSyncProcessor = ({ } export interface ClientSessionSyncProcessor { - push: ( - batch: ReadonlyArray, - options: { otelContext: otel.Context }, - ) => Effect.Effect< + push: (batch: ReadonlyArray) => Effect.Effect< { writeTables: Set }, diff --git a/packages/@livestore/livestore/src/live-queries/__snapshots__/db-query.test.ts.snap b/packages/@livestore/livestore/src/live-queries/__snapshots__/db-query.test.ts.snap index 17a600822..c20e14ddb 100644 --- a/packages/@livestore/livestore/src/live-queries/__snapshots__/db-query.test.ts.snap +++ b/packages/@livestore/livestore/src/live-queries/__snapshots__/db-query.test.ts.snap @@ -28,6 +28,14 @@ exports[`otel > QueryBuilder subscription - basic functionality 1`] = ` "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", + }, + }, + ], }, ], }, @@ -60,14 +68,6 @@ exports[`otel > QueryBuilder subscription - basic functionality 1`] = ` ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", - }, - }, - ], }, ], }, @@ -152,6 +152,14 @@ exports[`otel > QueryBuilder subscription - direct table subscription 1`] = ` "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", + }, + }, + ], }, ], }, @@ -184,14 +192,6 @@ exports[`otel > QueryBuilder subscription - direct table subscription 1`] = ` ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", - }, - }, - ], }, ], }, @@ -276,6 +276,14 @@ exports[`otel > QueryBuilder subscription - unsubscribe functionality 1`] = ` "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", + }, + }, + ], }, ], }, @@ -287,6 +295,14 @@ exports[`otel > QueryBuilder subscription - unsubscribe functionality 1`] = ` "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", + }, + }, + ], }, ], }, @@ -326,14 +342,6 @@ exports[`otel > QueryBuilder subscription - unsubscribe functionality 1`] = ` ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", - }, - }, - ], }, { "_name": "LiveStore:commit", @@ -343,14 +351,6 @@ exports[`otel > QueryBuilder subscription - unsubscribe functionality 1`] = ` ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", - }, - }, - ], }, ], }, @@ -461,6 +461,14 @@ exports[`otel > otel 3`] = ` "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", + }, + }, + ], }, ], }, @@ -493,14 +501,6 @@ exports[`otel > otel 3`] = ` ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", - }, - }, - ], }, ], }, @@ -747,6 +747,14 @@ exports[`otel > with thunks 7`] = ` "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", + }, + }, + ], }, ], }, @@ -779,14 +787,6 @@ exports[`otel > with thunks 7`] = ` ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", - }, - }, - ], }, ], }, @@ -867,6 +867,14 @@ exports[`otel > with thunks with query builder and without labels 3`] = ` "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", + }, + }, + ], }, ], }, @@ -899,14 +907,6 @@ exports[`otel > with thunks with query builder and without labels 3`] = ` ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": "INSERT INTO 'todos' (id, text, completed) VALUES (?, ?, ?)", - }, - }, - ], }, ], }, diff --git a/packages/@livestore/livestore/src/store/store.ts b/packages/@livestore/livestore/src/store/store.ts index 5b9faa2fb..1e181277f 100644 --- a/packages/@livestore/livestore/src/store/store.ts +++ b/packages/@livestore/livestore/src/store/store.ts @@ -129,7 +129,7 @@ export class Store + (eventDecoded, { withChangeset, materializerHashLeader }) => Effect.gen(this, function* () { const { eventDef, materializer } = getEventDef(schema, eventDecoded.name) @@ -161,6 +161,8 @@ export class Store { const writeTablesForEvent = new Set() @@ -641,7 +643,7 @@ export class Store { try { const materializeEvents = () => { - return Runtime.runSync(this.effectContext.runtime, this.syncProcessor.push(events, { otelContext })) + return Runtime.runSync(this.effectContext.runtime, this.syncProcessor.push(events)) } if (events.length > 1) { diff --git a/packages/@livestore/react/src/__snapshots__/useClientDocument.test.tsx.snap b/packages/@livestore/react/src/__snapshots__/useClientDocument.test.tsx.snap index a1b17bcb0..d3cfbcba4 100644 --- a/packages/@livestore/react/src/__snapshots__/useClientDocument.test.tsx.snap +++ b/packages/@livestore/react/src/__snapshots__/useClientDocument.test.tsx.snap @@ -28,6 +28,18 @@ exports[`useClientDocument > otel > should update the data based on component ke "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": " + INSERT INTO 'UserInfo' (id, value) + VALUES (?, ?) + ON CONFLICT (id) DO UPDATE SET value = json_set(json_set(value, ?, json(?)), ?, json(?)) + ", + }, + }, + ], }, ], }, @@ -39,6 +51,18 @@ exports[`useClientDocument > otel > should update the data based on component ke "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": " + INSERT INTO 'UserInfo' (id, value) + VALUES (?, ?) + ON CONFLICT (id) DO UPDATE SET value = json_set(value, ?, json(?)) + ", + }, + }, + ], }, ], }, @@ -78,18 +102,6 @@ exports[`useClientDocument > otel > should update the data based on component ke ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": " - INSERT INTO 'UserInfo' (id, value) - VALUES (?, ?) - ON CONFLICT (id) DO UPDATE SET value = json_set(value, ?, json(?)) - ", - }, - }, - ], }, ], }, @@ -120,18 +132,6 @@ exports[`useClientDocument > otel > should update the data based on component ke ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": " - INSERT INTO 'UserInfo' (id, value) - VALUES (?, ?) - ON CONFLICT (id) DO UPDATE SET value = json_set(json_set(value, ?, json(?)), ?, json(?)) - ", - }, - }, - ], }, { "_name": "db:SELECT * FROM 'UserInfo' WHERE id = ?", @@ -276,6 +276,18 @@ exports[`useClientDocument > otel > should update the data based on component ke "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": " + INSERT INTO 'UserInfo' (id, value) + VALUES (?, ?) + ON CONFLICT (id) DO UPDATE SET value = json_set(json_set(value, ?, json(?)), ?, json(?)) + ", + }, + }, + ], }, ], }, @@ -287,6 +299,18 @@ exports[`useClientDocument > otel > should update the data based on component ke "children": [ { "_name": "client-session-sync-processor:materialize-event", + "children": [ + { + "_name": "livestore.in-memory-db:execute", + "attributes": { + "sql.query": " + INSERT INTO 'UserInfo' (id, value) + VALUES (?, ?) + ON CONFLICT (id) DO UPDATE SET value = json_set(value, ?, json(?)) + ", + }, + }, + ], }, ], }, @@ -326,18 +350,6 @@ exports[`useClientDocument > otel > should update the data based on component ke ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": " - INSERT INTO 'UserInfo' (id, value) - VALUES (?, ?) - ON CONFLICT (id) DO UPDATE SET value = json_set(value, ?, json(?)) - ", - }, - }, - ], }, ], }, @@ -368,18 +380,6 @@ exports[`useClientDocument > otel > should update the data based on component ke ], "livestore.eventsCount": 1, }, - "children": [ - { - "_name": "livestore.in-memory-db:execute", - "attributes": { - "sql.query": " - INSERT INTO 'UserInfo' (id, value) - VALUES (?, ?) - ON CONFLICT (id) DO UPDATE SET value = json_set(json_set(value, ?, json(?)), ?, json(?)) - ", - }, - }, - ], }, { "_name": "db:SELECT * FROM 'UserInfo' WHERE id = ?", From 68da629311dde1f4c8283b8333afe231d647b725 Mon Sep 17 00:00:00 2001 From: Igor Gassmann Date: Thu, 31 Jul 2025 12:37:44 +0200 Subject: [PATCH 3/3] refactor: remove unnecessary `Effect.sync`s --- .../@livestore/livestore/src/store/store.ts | 69 +++++++++---------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/packages/@livestore/livestore/src/store/store.ts b/packages/@livestore/livestore/src/store/store.ts index 1e181277f..e8cea14d6 100644 --- a/packages/@livestore/livestore/src/store/store.ts +++ b/packages/@livestore/livestore/src/store/store.ts @@ -163,45 +163,44 @@ export class Store { - const writeTablesForEvent = new Set() - - const exec = () => { - for (const { - statementSql, - bindValues, - writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql), - } of execArgsArr) { - try { - this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables }) - } catch (cause) { - throw UnexpectedError.make({ - cause, - note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`, - }) - } - - // durationMsTotal += durationMs - for (const table of writeTables) { - writeTablesForEvent.add(table) - } - - this.sqliteDbWrapper.debug.head = eventDecoded.seqNum + + const writeTablesForEvent = new Set() + + const exec = () => { + for (const { + statementSql, + bindValues, + writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql), + } of execArgsArr) { + try { + this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables }) + } catch (cause) { + throw UnexpectedError.make({ + cause, + note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`, + }) } - } - let sessionChangeset: - | { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } - | { _tag: 'no-op' } - | { _tag: 'unset' } = { _tag: 'unset' } - if (withChangeset === true) { - sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset - } else { - exec() + // durationMsTotal += durationMs + for (const table of writeTables) { + writeTablesForEvent.add(table) + } + + this.sqliteDbWrapper.debug.head = eventDecoded.seqNum } + } - return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash } - }) + let sessionChangeset: + | { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } + | { _tag: 'no-op' } + | { _tag: 'unset' } = { _tag: 'unset' } + if (withChangeset === true) { + sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset + } else { + exec() + } + + return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash } }), ), rollback: (changeset) => {