@livestore/common 0.3.0-dev.8 → 0.3.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/fixture.d.ts +83 -221
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +33 -11
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/adapter-types.d.ts +120 -64
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +39 -8
- package/dist/adapter-types.js.map +1 -1
- package/dist/bounded-collections.d.ts.map +1 -1
- package/dist/debug-info.d.ts +1 -1
- package/dist/debug-info.d.ts.map +1 -1
- package/dist/debug-info.js +1 -0
- package/dist/debug-info.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +390 -0
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -0
- package/dist/devtools/devtools-messages-client-session.js +97 -0
- package/dist/devtools/devtools-messages-client-session.js.map +1 -0
- package/dist/devtools/devtools-messages-common.d.ts +68 -0
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -0
- package/dist/devtools/devtools-messages-common.js +60 -0
- package/dist/devtools/devtools-messages-common.js.map +1 -0
- package/dist/devtools/devtools-messages-leader.d.ts +394 -0
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -0
- package/dist/devtools/devtools-messages-leader.js +147 -0
- package/dist/devtools/devtools-messages-leader.js.map +1 -0
- package/dist/devtools/devtools-messages.d.ts +3 -580
- package/dist/devtools/devtools-messages.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.js +3 -174
- package/dist/devtools/devtools-messages.js.map +1 -1
- package/dist/devtools/devtools-sessioninfo.d.ts +32 -0
- package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -0
- package/dist/devtools/devtools-sessioninfo.js +36 -0
- package/dist/devtools/devtools-sessioninfo.js.map +1 -0
- package/dist/devtools/mod.d.ts +55 -0
- package/dist/devtools/mod.d.ts.map +1 -0
- package/dist/devtools/mod.js +33 -0
- package/dist/devtools/mod.js.map +1 -0
- package/dist/index.d.ts +7 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -9
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +45 -30
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +484 -321
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/connection.d.ts +34 -6
- package/dist/leader-thread/connection.d.ts.map +1 -1
- package/dist/leader-thread/connection.js +22 -7
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +27 -0
- package/dist/leader-thread/eventlog.d.ts.map +1 -0
- package/dist/leader-thread/eventlog.js +119 -0
- package/dist/leader-thread/eventlog.js.map +1 -0
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +155 -80
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +22 -9
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +67 -45
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/materialize-event.d.ts +16 -0
- package/dist/leader-thread/materialize-event.d.ts.map +1 -0
- package/dist/leader-thread/materialize-event.js +109 -0
- package/dist/leader-thread/materialize-event.js.map +1 -0
- package/dist/leader-thread/mod.d.ts +1 -1
- package/dist/leader-thread/mod.d.ts.map +1 -1
- package/dist/leader-thread/mod.js +1 -1
- package/dist/leader-thread/mod.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts +4 -2
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +28 -32
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -5
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -4
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/types.d.ts +85 -38
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js +1 -3
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/make-client-session.d.ts +23 -0
- package/dist/make-client-session.d.ts.map +1 -0
- package/dist/make-client-session.js +57 -0
- package/dist/make-client-session.js.map +1 -0
- package/dist/materializer-helper.d.ts +23 -0
- package/dist/materializer-helper.d.ts.map +1 -0
- package/dist/materializer-helper.js +86 -0
- package/dist/materializer-helper.js.map +1 -0
- package/dist/otel.d.ts +2 -0
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +5 -0
- package/dist/otel.js.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts +14 -0
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -0
- package/dist/rematerialize-from-eventlog.js +64 -0
- package/dist/rematerialize-from-eventlog.js.map +1 -0
- package/dist/schema/EventDef.d.ts +146 -0
- package/dist/schema/EventDef.d.ts.map +1 -0
- package/dist/schema/EventDef.js +58 -0
- package/dist/schema/EventDef.js.map +1 -0
- package/dist/schema/EventSequenceNumber.d.ts +57 -0
- package/dist/schema/EventSequenceNumber.d.ts.map +1 -0
- package/dist/schema/EventSequenceNumber.js +82 -0
- package/dist/schema/EventSequenceNumber.js.map +1 -0
- package/dist/schema/EventSequenceNumber.test.d.ts +2 -0
- package/dist/schema/EventSequenceNumber.test.d.ts.map +1 -0
- package/dist/schema/EventSequenceNumber.test.js +11 -0
- package/dist/schema/EventSequenceNumber.test.js.map +1 -0
- package/dist/schema/LiveStoreEvent.d.ts +257 -0
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent.js +117 -0
- package/dist/schema/LiveStoreEvent.js.map +1 -0
- package/dist/schema/events.d.ts +2 -0
- package/dist/schema/events.d.ts.map +1 -0
- package/dist/schema/events.js +2 -0
- package/dist/schema/events.js.map +1 -0
- package/dist/schema/mod.d.ts +8 -6
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +8 -6
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts +50 -32
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +36 -43
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/mod.d.ts +3 -0
- package/dist/schema/state/mod.d.ts.map +1 -0
- package/dist/schema/state/mod.js +3 -0
- package/dist/schema/state/mod.js.map +1 -0
- package/dist/schema/state/sqlite/client-document-def.d.ts +223 -0
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -0
- package/dist/schema/state/sqlite/client-document-def.js +170 -0
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -0
- package/dist/schema/state/sqlite/client-document-def.test.d.ts +2 -0
- package/dist/schema/state/sqlite/client-document-def.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/client-document-def.test.js +201 -0
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +69 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +71 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts +3 -0
- package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/validate.js +12 -0
- package/dist/schema/state/sqlite/db-schema/ast/validate.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +90 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +87 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.d.ts +2 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +29 -0
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +90 -0
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +41 -0
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/hash.d.ts +2 -0
- package/dist/schema/state/sqlite/db-schema/hash.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/hash.js +14 -0
- package/dist/schema/state/sqlite/db-schema/hash.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/mod.d.ts +3 -0
- package/dist/schema/state/sqlite/db-schema/mod.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/mod.js +3 -0
- package/dist/schema/state/sqlite/db-schema/mod.js.map +1 -0
- package/dist/schema/state/sqlite/mod.d.ts +17 -0
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -0
- package/dist/schema/state/sqlite/mod.js +41 -0
- package/dist/schema/state/sqlite/mod.js.map +1 -0
- package/dist/schema/state/sqlite/query-builder/api.d.ts +294 -0
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -0
- package/dist/schema/state/sqlite/query-builder/api.js +6 -0
- package/dist/schema/state/sqlite/query-builder/api.js.map +1 -0
- package/dist/schema/state/sqlite/query-builder/astToSql.d.ts +7 -0
- package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -0
- package/dist/schema/state/sqlite/query-builder/astToSql.js +190 -0
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -0
- package/dist/schema/state/sqlite/query-builder/impl.d.ts +7 -0
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -0
- package/dist/schema/state/sqlite/query-builder/impl.js +286 -0
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -0
- package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +87 -0
- package/dist/schema/state/sqlite/query-builder/impl.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/query-builder/impl.test.js +563 -0
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -0
- package/dist/{query-builder → schema/state/sqlite/query-builder}/mod.d.ts +7 -0
- package/dist/schema/state/sqlite/query-builder/mod.d.ts.map +1 -0
- package/dist/{query-builder → schema/state/sqlite/query-builder}/mod.js +7 -0
- package/dist/schema/state/sqlite/query-builder/mod.js.map +1 -0
- package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -0
- package/dist/schema/{schema-helpers.js → state/sqlite/schema-helpers.js} +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js.map +1 -0
- package/dist/schema/state/sqlite/system-tables.d.ts +574 -0
- package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -0
- package/dist/schema/state/sqlite/system-tables.js +88 -0
- package/dist/schema/state/sqlite/system-tables.js.map +1 -0
- package/dist/schema/state/sqlite/table-def.d.ts +84 -0
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -0
- package/dist/schema/state/sqlite/table-def.js +36 -0
- package/dist/schema/state/sqlite/table-def.js.map +1 -0
- package/dist/schema-management/common.d.ts +7 -7
- package/dist/schema-management/common.d.ts.map +1 -1
- package/dist/schema-management/common.js.map +1 -1
- package/dist/schema-management/migrations.d.ts +6 -6
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +27 -18
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/schema-management/validate-schema.d.ts +8 -0
- package/dist/schema-management/validate-schema.d.ts.map +1 -0
- package/dist/schema-management/validate-schema.js +39 -0
- package/dist/schema-management/validate-schema.js.map +1 -0
- package/dist/sql-queries/misc.d.ts.map +1 -1
- package/dist/sql-queries/sql-queries.d.ts +1 -1
- package/dist/sql-queries/sql-queries.d.ts.map +1 -1
- package/dist/sql-queries/sql-queries.js.map +1 -1
- package/dist/sql-queries/sql-query-builder.d.ts +1 -1
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.js.map +1 -1
- package/dist/sql-queries/types.d.ts +2 -1
- package/dist/sql-queries/types.d.ts.map +1 -1
- package/dist/sql-queries/types.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +40 -19
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +150 -72
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/compact-events.d.ts.map +1 -1
- package/dist/sync/next/compact-events.js +38 -35
- package/dist/sync/next/compact-events.js.map +1 -1
- package/dist/sync/next/facts.d.ts +21 -21
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +11 -11
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +9 -7
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +10 -5
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.d.ts +0 -2
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +16 -14
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts +10 -8
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +18 -10
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +39 -34
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +77 -77
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/{mutation-fixtures.d.ts → event-fixtures.d.ts} +35 -25
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -0
- package/dist/sync/next/test/{mutation-fixtures.js → event-fixtures.js} +81 -38
- package/dist/sync/next/test/event-fixtures.js.map +1 -0
- package/dist/sync/next/test/mod.d.ts +1 -1
- package/dist/sync/next/test/mod.d.ts.map +1 -1
- package/dist/sync/next/test/mod.js +1 -1
- package/dist/sync/next/test/mod.js.map +1 -1
- package/dist/sync/sync.d.ts +46 -21
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js +10 -6
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +213 -82
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +337 -139
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +309 -286
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts +2 -2
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +4 -4
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/util.d.ts +2 -2
- package/dist/util.d.ts.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +10 -4
- package/src/__tests__/fixture.ts +36 -15
- package/src/adapter-types.ts +107 -68
- package/src/debug-info.ts +1 -0
- package/src/devtools/devtools-messages-client-session.ts +142 -0
- package/src/devtools/devtools-messages-common.ts +115 -0
- package/src/devtools/devtools-messages-leader.ts +191 -0
- package/src/devtools/devtools-messages.ts +3 -246
- package/src/devtools/devtools-sessioninfo.ts +101 -0
- package/src/devtools/mod.ts +59 -0
- package/src/index.ts +7 -9
- package/src/leader-thread/LeaderSyncProcessor.ts +738 -477
- package/src/leader-thread/connection.ts +54 -9
- package/src/leader-thread/eventlog.ts +199 -0
- package/src/leader-thread/leader-worker-devtools.ts +227 -104
- package/src/leader-thread/make-leader-thread-layer.ts +121 -72
- package/src/leader-thread/materialize-event.ts +173 -0
- package/src/leader-thread/mod.ts +1 -1
- package/src/leader-thread/recreate-db.ts +33 -38
- package/src/leader-thread/shutdown-channel.ts +2 -4
- package/src/leader-thread/types.ts +94 -48
- package/src/make-client-session.ts +136 -0
- package/src/materializer-helper.ts +138 -0
- package/src/otel.ts +8 -0
- package/src/rematerialize-from-eventlog.ts +117 -0
- package/src/schema/EventDef.ts +227 -0
- package/src/schema/EventSequenceNumber.test.ts +12 -0
- package/src/schema/EventSequenceNumber.ts +121 -0
- package/src/schema/LiveStoreEvent.ts +240 -0
- package/src/schema/events.ts +1 -0
- package/src/schema/mod.ts +8 -6
- package/src/schema/schema.ts +88 -84
- package/src/schema/state/mod.ts +2 -0
- package/src/schema/state/sqlite/client-document-def.test.ts +238 -0
- package/src/schema/state/sqlite/client-document-def.ts +444 -0
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +142 -0
- package/src/schema/state/sqlite/db-schema/ast/validate.ts +13 -0
- package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +206 -0
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +35 -0
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +242 -0
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +222 -0
- package/src/schema/state/sqlite/db-schema/hash.ts +14 -0
- package/src/schema/state/sqlite/db-schema/mod.ts +2 -0
- package/src/schema/state/sqlite/mod.ts +73 -0
- package/src/schema/state/sqlite/query-builder/api.ts +440 -0
- package/src/schema/state/sqlite/query-builder/astToSql.ts +232 -0
- package/src/schema/state/sqlite/query-builder/impl.test.ts +617 -0
- package/src/schema/state/sqlite/query-builder/impl.ts +351 -0
- package/src/{query-builder → schema/state/sqlite/query-builder}/mod.ts +7 -0
- package/src/schema/{schema-helpers.ts → state/sqlite/schema-helpers.ts} +1 -1
- package/src/schema/state/sqlite/system-tables.ts +117 -0
- package/src/schema/state/sqlite/table-def.ts +197 -0
- package/src/schema-management/common.ts +7 -7
- package/src/schema-management/migrations.ts +37 -31
- package/src/schema-management/validate-schema.ts +61 -0
- package/src/sql-queries/sql-queries.ts +1 -1
- package/src/sql-queries/sql-query-builder.ts +1 -2
- package/src/sql-queries/types.ts +3 -1
- package/src/sync/ClientSessionSyncProcessor.ts +220 -94
- package/src/sync/next/compact-events.ts +38 -35
- package/src/sync/next/facts.ts +43 -41
- package/src/sync/next/history-dag-common.ts +17 -10
- package/src/sync/next/history-dag.ts +16 -17
- package/src/sync/next/rebase-events.ts +29 -17
- package/src/sync/next/test/compact-events.calculator.test.ts +46 -46
- package/src/sync/next/test/compact-events.test.ts +79 -79
- package/src/sync/next/test/event-fixtures.ts +226 -0
- package/src/sync/next/test/mod.ts +1 -1
- package/src/sync/sync.ts +46 -21
- package/src/sync/syncstate.test.ts +346 -320
- package/src/sync/syncstate.ts +422 -230
- package/src/sync/validate-push-payload.ts +6 -6
- package/src/version.ts +2 -2
- package/dist/derived-mutations.d.ts +0 -109
- package/dist/derived-mutations.d.ts.map +0 -1
- package/dist/derived-mutations.js +0 -54
- package/dist/derived-mutations.js.map +0 -1
- package/dist/derived-mutations.test.d.ts +0 -2
- package/dist/derived-mutations.test.d.ts.map +0 -1
- package/dist/derived-mutations.test.js +0 -93
- package/dist/derived-mutations.test.js.map +0 -1
- package/dist/devtools/devtools-bridge.d.ts +0 -13
- package/dist/devtools/devtools-bridge.d.ts.map +0 -1
- package/dist/devtools/devtools-bridge.js +0 -2
- package/dist/devtools/devtools-bridge.js.map +0 -1
- package/dist/devtools/devtools-window-message.d.ts +0 -29
- package/dist/devtools/devtools-window-message.d.ts.map +0 -1
- package/dist/devtools/devtools-window-message.js +0 -33
- package/dist/devtools/devtools-window-message.js.map +0 -1
- package/dist/devtools/index.d.ts +0 -42
- package/dist/devtools/index.d.ts.map +0 -1
- package/dist/devtools/index.js +0 -48
- package/dist/devtools/index.js.map +0 -1
- package/dist/init-singleton-tables.d.ts +0 -4
- package/dist/init-singleton-tables.d.ts.map +0 -1
- package/dist/init-singleton-tables.js +0 -16
- package/dist/init-singleton-tables.js.map +0 -1
- package/dist/leader-thread/apply-mutation.d.ts +0 -11
- package/dist/leader-thread/apply-mutation.d.ts.map +0 -1
- package/dist/leader-thread/apply-mutation.js +0 -107
- package/dist/leader-thread/apply-mutation.js.map +0 -1
- package/dist/leader-thread/leader-sync-processor.d.ts +0 -47
- package/dist/leader-thread/leader-sync-processor.d.ts.map +0 -1
- package/dist/leader-thread/leader-sync-processor.js +0 -430
- package/dist/leader-thread/leader-sync-processor.js.map +0 -1
- package/dist/leader-thread/mutationlog.d.ts +0 -10
- package/dist/leader-thread/mutationlog.d.ts.map +0 -1
- package/dist/leader-thread/mutationlog.js +0 -28
- package/dist/leader-thread/mutationlog.js.map +0 -1
- package/dist/leader-thread/pull-queue-set.d.ts +0 -7
- package/dist/leader-thread/pull-queue-set.d.ts.map +0 -1
- package/dist/leader-thread/pull-queue-set.js +0 -39
- package/dist/leader-thread/pull-queue-set.js.map +0 -1
- package/dist/mutation.d.ts +0 -20
- package/dist/mutation.d.ts.map +0 -1
- package/dist/mutation.js +0 -57
- package/dist/mutation.js.map +0 -1
- package/dist/query-builder/api.d.ts +0 -190
- package/dist/query-builder/api.d.ts.map +0 -1
- package/dist/query-builder/api.js +0 -8
- package/dist/query-builder/api.js.map +0 -1
- package/dist/query-builder/impl.d.ts +0 -12
- package/dist/query-builder/impl.d.ts.map +0 -1
- package/dist/query-builder/impl.js +0 -244
- package/dist/query-builder/impl.js.map +0 -1
- package/dist/query-builder/impl.test.d.ts +0 -2
- package/dist/query-builder/impl.test.d.ts.map +0 -1
- package/dist/query-builder/impl.test.js +0 -212
- package/dist/query-builder/impl.test.js.map +0 -1
- package/dist/query-builder/mod.d.ts.map +0 -1
- package/dist/query-builder/mod.js.map +0 -1
- package/dist/query-info.d.ts +0 -38
- package/dist/query-info.d.ts.map +0 -1
- package/dist/query-info.js +0 -7
- package/dist/query-info.js.map +0 -1
- package/dist/rehydrate-from-mutationlog.d.ts +0 -14
- package/dist/rehydrate-from-mutationlog.d.ts.map +0 -1
- package/dist/rehydrate-from-mutationlog.js +0 -66
- package/dist/rehydrate-from-mutationlog.js.map +0 -1
- package/dist/schema/EventId.d.ts +0 -39
- package/dist/schema/EventId.d.ts.map +0 -1
- package/dist/schema/EventId.js +0 -38
- package/dist/schema/EventId.js.map +0 -1
- package/dist/schema/EventId.test.d.ts +0 -2
- package/dist/schema/EventId.test.d.ts.map +0 -1
- package/dist/schema/EventId.test.js +0 -11
- package/dist/schema/EventId.test.js.map +0 -1
- package/dist/schema/MutationEvent.d.ts +0 -166
- package/dist/schema/MutationEvent.d.ts.map +0 -1
- package/dist/schema/MutationEvent.js +0 -72
- package/dist/schema/MutationEvent.js.map +0 -1
- package/dist/schema/MutationEvent.test.d.ts +0 -2
- package/dist/schema/MutationEvent.test.d.ts.map +0 -1
- package/dist/schema/MutationEvent.test.js +0 -2
- package/dist/schema/MutationEvent.test.js.map +0 -1
- package/dist/schema/mutations.d.ts +0 -107
- package/dist/schema/mutations.d.ts.map +0 -1
- package/dist/schema/mutations.js +0 -42
- package/dist/schema/mutations.js.map +0 -1
- package/dist/schema/schema-helpers.d.ts.map +0 -1
- package/dist/schema/schema-helpers.js.map +0 -1
- package/dist/schema/system-tables.d.ts +0 -399
- package/dist/schema/system-tables.d.ts.map +0 -1
- package/dist/schema/system-tables.js +0 -59
- package/dist/schema/system-tables.js.map +0 -1
- package/dist/schema/table-def.d.ts +0 -156
- package/dist/schema/table-def.d.ts.map +0 -1
- package/dist/schema/table-def.js +0 -79
- package/dist/schema/table-def.js.map +0 -1
- package/dist/schema-management/validate-mutation-defs.d.ts +0 -8
- package/dist/schema-management/validate-mutation-defs.d.ts.map +0 -1
- package/dist/schema-management/validate-mutation-defs.js +0 -39
- package/dist/schema-management/validate-mutation-defs.js.map +0 -1
- package/dist/sync/client-session-sync-processor.d.ts +0 -45
- package/dist/sync/client-session-sync-processor.d.ts.map +0 -1
- package/dist/sync/client-session-sync-processor.js +0 -131
- package/dist/sync/client-session-sync-processor.js.map +0 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts.map +0 -1
- package/dist/sync/next/test/mutation-fixtures.js.map +0 -1
- package/src/derived-mutations.test.ts +0 -101
- package/src/derived-mutations.ts +0 -170
- package/src/devtools/devtools-bridge.ts +0 -14
- package/src/devtools/devtools-window-message.ts +0 -27
- package/src/devtools/index.ts +0 -48
- package/src/init-singleton-tables.ts +0 -24
- package/src/leader-thread/apply-mutation.ts +0 -161
- package/src/leader-thread/mutationlog.ts +0 -46
- package/src/leader-thread/pull-queue-set.ts +0 -58
- package/src/mutation.ts +0 -91
- package/src/query-builder/api.ts +0 -289
- package/src/query-builder/impl.test.ts +0 -239
- package/src/query-builder/impl.ts +0 -285
- package/src/query-info.ts +0 -78
- package/src/rehydrate-from-mutationlog.ts +0 -119
- package/src/schema/EventId.test.ts +0 -12
- package/src/schema/EventId.ts +0 -60
- package/src/schema/MutationEvent.ts +0 -181
- package/src/schema/mutations.ts +0 -192
- package/src/schema/system-tables.ts +0 -105
- package/src/schema/table-def.ts +0 -343
- package/src/schema-management/validate-mutation-defs.ts +0 -63
- package/src/sync/next/test/mutation-fixtures.ts +0 -224
- package/tsconfig.json +0 -11
- /package/dist/schema/{schema-helpers.d.ts → state/sqlite/schema-helpers.d.ts} +0 -0
@@ -1,344 +1,492 @@
|
|
1
|
-
import { shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils';
|
2
|
-
import { BucketQueue, Deferred, Effect, Exit,
|
1
|
+
import { casesHandled, isNotUndefined, LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils';
|
2
|
+
import { BucketQueue, Deferred, Effect, Exit, FiberHandle, OtelTracer, Queue, ReadonlyArray, Stream, Subscribable, SubscriptionRef, } from '@livestore/utils/effect';
|
3
3
|
import { UnexpectedError } from '../adapter-types.js';
|
4
|
-
import {
|
5
|
-
import {
|
6
|
-
import { InvalidPushError } from '../sync/sync.js';
|
4
|
+
import { EventSequenceNumber, getEventDef, LiveStoreEvent, SystemTables } from '../schema/mod.js';
|
5
|
+
import { LeaderAheadError } from '../sync/sync.js';
|
7
6
|
import * as SyncState from '../sync/syncstate.js';
|
8
7
|
import { sql } from '../util.js';
|
9
|
-
import
|
10
|
-
import {
|
11
|
-
import { getBackendHeadFromDb, getLocalHeadFromDb, getMutationEventsSince, updateBackendHead } from './mutationlog.js';
|
8
|
+
import * as Eventlog from './eventlog.js';
|
9
|
+
import { rollback } from './materialize-event.js';
|
12
10
|
import { LeaderThreadCtx } from './types.js';
|
13
11
|
/**
|
14
|
-
* The
|
15
|
-
*
|
16
|
-
* The leader sync processor is also responsible for
|
17
|
-
* - broadcasting mutations to client sessions via the pull queues.
|
18
|
-
* - pushing mutations to the sync backend
|
12
|
+
* The LeaderSyncProcessor manages synchronization of events between
|
13
|
+
* the local state and the sync backend, ensuring efficient and orderly processing.
|
19
14
|
*
|
20
|
-
* In the
|
15
|
+
* In the LeaderSyncProcessor, pulling always has precedence over pushing.
|
21
16
|
*
|
22
|
-
*
|
23
|
-
* -
|
24
|
-
* -
|
25
|
-
* -
|
17
|
+
* Responsibilities:
|
18
|
+
* - Queueing incoming local events in a localPushesQueue.
|
19
|
+
* - Broadcasting events to client sessions via pull queues.
|
20
|
+
* - Pushing events to the sync backend.
|
26
21
|
*
|
27
|
-
*
|
28
|
-
* - in-sync: fully synced with remote, now idling
|
29
|
-
* - applying-syncstate-advance (with pointer to current progress in case of rebase interrupt)
|
22
|
+
* Notes:
|
30
23
|
*
|
31
|
-
*
|
32
|
-
* -
|
33
|
-
*
|
34
|
-
*
|
24
|
+
* local push processing:
|
25
|
+
* - localPushesQueue:
|
26
|
+
* - Maintains events in ascending order.
|
27
|
+
* - Uses `Deferred` objects to resolve/reject events based on application success.
|
28
|
+
* - Processes events from the queue, applying events in batches.
|
29
|
+
* - Controlled by a `Latch` to manage execution flow.
|
30
|
+
* - The latch closes on pull receipt and re-opens post-pull completion.
|
31
|
+
* - Processes up to `maxBatchSize` events per cycle.
|
35
32
|
*
|
36
|
-
*
|
37
|
-
*
|
38
|
-
*
|
39
|
-
* -
|
40
|
-
*
|
33
|
+
* Currently we're advancing the db read model and eventlog in lockstep, but we could also decouple this in the future
|
34
|
+
*
|
35
|
+
* Tricky concurrency scenarios:
|
36
|
+
* - Queued local push batches becoming invalid due to a prior local push item being rejected.
|
37
|
+
* Solution: Introduce a generation number for local push batches which is used to filter out old batches items in case of rejection.
|
41
38
|
*
|
42
|
-
* Backend pushing:
|
43
|
-
* - continously push to backend
|
44
|
-
* - only interrupted and restarted on rebase
|
45
39
|
*/
|
46
|
-
export const makeLeaderSyncProcessor = ({ schema,
|
47
|
-
const
|
48
|
-
const
|
49
|
-
const
|
50
|
-
const
|
51
|
-
|
52
|
-
|
40
|
+
export const makeLeaderSyncProcessor = ({ schema, dbEventlogMissing, dbEventlog, dbState, dbStateMissing, initialBlockingSyncContext, onError, params, testing, }) => Effect.gen(function* () {
|
41
|
+
const syncBackendPushQueue = yield* BucketQueue.make();
|
42
|
+
const localPushBatchSize = params.localPushBatchSize ?? 10;
|
43
|
+
const backendPushBatchSize = params.backendPushBatchSize ?? 50;
|
44
|
+
const syncStateSref = yield* SubscriptionRef.make(undefined);
|
45
|
+
const isClientEvent = (eventEncoded) => {
|
46
|
+
const { eventDef } = getEventDef(schema, eventEncoded.name);
|
47
|
+
return eventDef.options.clientOnly;
|
53
48
|
};
|
54
|
-
const
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
TODO
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
}
|
84
|
-
|
85
|
-
|
86
|
-
counterRef++;
|
87
|
-
// TODO validate batch
|
49
|
+
const connectedClientSessionPullQueues = yield* makePullQueueSet;
|
50
|
+
/**
|
51
|
+
* Tracks generations of queued local push events.
|
52
|
+
* If a local-push batch is rejected, all subsequent push queue items with the same generation are also rejected,
|
53
|
+
* even if they would be valid on their own.
|
54
|
+
*/
|
55
|
+
// TODO get rid of this in favour of the `mergeGeneration` event sequence number field
|
56
|
+
const currentLocalPushGenerationRef = { current: 0 };
|
57
|
+
const mergeCounterRef = { current: dbStateMissing ? 0 : yield* getMergeCounterFromDb(dbState) };
|
58
|
+
const mergePayloads = new Map();
|
59
|
+
// This context depends on data from `boot`, we should find a better implementation to avoid this ref indirection.
|
60
|
+
const ctxRef = {
|
61
|
+
current: undefined,
|
62
|
+
};
|
63
|
+
const localPushesQueue = yield* BucketQueue.make();
|
64
|
+
const localPushesLatch = yield* Effect.makeLatch(true);
|
65
|
+
const pullLatch = yield* Effect.makeLatch(true);
|
66
|
+
/**
|
67
|
+
* Additionally to the `syncStateSref` we also need the `pushHeadRef` in order to prevent old/duplicate
|
68
|
+
* events from being pushed in a scenario like this:
|
69
|
+
* - client session A pushes e1
|
70
|
+
* - leader sync processor takes a bit and hasn't yet taken e1 from the localPushesQueue
|
71
|
+
* - client session B also pushes e1 (which should be rejected)
|
72
|
+
*
|
73
|
+
* Thus the purpoe of the pushHeadRef is the guard the integrity of the local push queue
|
74
|
+
*/
|
75
|
+
const pushHeadRef = { current: EventSequenceNumber.ROOT };
|
76
|
+
const advancePushHead = (eventNum) => {
|
77
|
+
pushHeadRef.current = EventSequenceNumber.max(pushHeadRef.current, eventNum);
|
78
|
+
};
|
79
|
+
// NOTE: New events are only pushed to sync backend after successful local push processing
|
80
|
+
const push = (newEvents, options) => Effect.gen(function* () {
|
88
81
|
if (newEvents.length === 0)
|
89
82
|
return;
|
90
|
-
|
91
|
-
|
92
|
-
const
|
93
|
-
const
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
if (updateResult._tag === 'rebase') {
|
100
|
-
return shouldNeverHappen('The leader thread should never have to rebase due to a local push');
|
83
|
+
yield* validatePushBatch(newEvents, pushHeadRef.current);
|
84
|
+
advancePushHead(newEvents.at(-1).seqNum);
|
85
|
+
const waitForProcessing = options?.waitForProcessing ?? false;
|
86
|
+
const generation = currentLocalPushGenerationRef.current;
|
87
|
+
if (waitForProcessing) {
|
88
|
+
const deferreds = yield* Effect.forEach(newEvents, () => Deferred.make());
|
89
|
+
const items = newEvents.map((eventEncoded, i) => [eventEncoded, deferreds[i], generation]);
|
90
|
+
yield* BucketQueue.offerAll(localPushesQueue, items);
|
91
|
+
yield* Effect.all(deferreds);
|
101
92
|
}
|
102
|
-
else
|
103
|
-
|
104
|
-
|
105
|
-
_tag: 'LeaderAhead',
|
106
|
-
minimumExpectedId: updateResult.expectedMinimumId,
|
107
|
-
providedId: newEvents.at(0).id,
|
108
|
-
},
|
109
|
-
}));
|
93
|
+
else {
|
94
|
+
const items = newEvents.map((eventEncoded) => [eventEncoded, undefined, generation]);
|
95
|
+
yield* BucketQueue.offerAll(localPushesQueue, items);
|
110
96
|
}
|
111
|
-
|
112
|
-
yield* Ref.set(stateRef, {
|
113
|
-
_tag: 'applying-syncstate-advance',
|
114
|
-
origin: 'push',
|
115
|
-
syncState: updateResult.newSyncState,
|
116
|
-
fiber,
|
117
|
-
});
|
118
|
-
// console.log('setRef:applying-syncstate-advance after push', counter)
|
119
|
-
yield* connectedClientSessionPullQueues.offer({
|
120
|
-
payload: { _tag: 'upstream-advance', newEvents: updateResult.newEvents },
|
121
|
-
remaining: 0,
|
122
|
-
});
|
123
|
-
spanRef.current?.addEvent('local-push', {
|
124
|
-
batchSize: newEvents.length,
|
125
|
-
updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
|
126
|
-
});
|
127
|
-
// Don't sync localOnly mutations
|
128
|
-
const filteredBatch = updateResult.newEvents.filter((mutationEventEncoded) => {
|
129
|
-
const mutationDef = schema.mutations.get(mutationEventEncoded.mutation);
|
130
|
-
return mutationDef.options.localOnly === false;
|
131
|
-
});
|
132
|
-
yield* BucketQueue.offerAll(syncBackendQueue, filteredBatch);
|
133
|
-
yield* fiber; // Waiting for the mutation to be applied
|
134
|
-
}).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:local-push', {
|
97
|
+
}).pipe(Effect.withSpan('@livestore/common:LeaderSyncProcessor:push', {
|
135
98
|
attributes: {
|
136
99
|
batchSize: newEvents.length,
|
137
100
|
batch: TRACE_VERBOSE ? newEvents : undefined,
|
138
101
|
},
|
139
|
-
links:
|
140
|
-
? [{ _tag: 'SpanLink', span: OtelTracer.makeExternalSpan(spanRef.current.spanContext()), attributes: {} }]
|
141
|
-
: undefined,
|
102
|
+
links: ctxRef.current?.span ? [{ _tag: 'SpanLink', span: ctxRef.current.span, attributes: {} }] : undefined,
|
142
103
|
}));
|
143
|
-
const pushPartial = (
|
144
|
-
const
|
145
|
-
if (
|
104
|
+
const pushPartial = ({ event: { name, args }, clientId, sessionId }) => Effect.gen(function* () {
|
105
|
+
const syncState = yield* syncStateSref;
|
106
|
+
if (syncState === undefined)
|
146
107
|
return shouldNeverHappen('Not initialized');
|
147
|
-
const
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
108
|
+
const { eventDef } = getEventDef(schema, name);
|
109
|
+
const eventEncoded = new LiveStoreEvent.EncodedWithMeta({
|
110
|
+
name,
|
111
|
+
args,
|
112
|
+
clientId,
|
113
|
+
sessionId,
|
114
|
+
...EventSequenceNumber.nextPair(syncState.localHead, eventDef.options.clientOnly),
|
152
115
|
});
|
153
|
-
yield* push([
|
154
|
-
}).pipe(Effect.catchTag('
|
116
|
+
yield* push([eventEncoded]);
|
117
|
+
}).pipe(Effect.catchTag('LeaderAheadError', Effect.orDie));
|
155
118
|
// Starts various background loops
|
156
|
-
const boot =
|
157
|
-
const span = yield*
|
158
|
-
|
159
|
-
const
|
160
|
-
const
|
119
|
+
const boot = Effect.gen(function* () {
|
120
|
+
const span = yield* Effect.currentSpan.pipe(Effect.orDie);
|
121
|
+
const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(Effect.catchAll(() => Effect.succeed(undefined)));
|
122
|
+
const { devtools, shutdownChannel } = yield* LeaderThreadCtx;
|
123
|
+
const runtime = yield* Effect.runtime();
|
124
|
+
ctxRef.current = {
|
125
|
+
otelSpan,
|
126
|
+
span,
|
127
|
+
devtoolsLatch: devtools.enabled ? devtools.syncBackendLatch : undefined,
|
128
|
+
runtime,
|
129
|
+
};
|
130
|
+
const initialLocalHead = dbEventlogMissing ? EventSequenceNumber.ROOT : Eventlog.getClientHeadFromDb(dbEventlog);
|
131
|
+
const initialBackendHead = dbEventlogMissing
|
132
|
+
? EventSequenceNumber.ROOT.global
|
133
|
+
: Eventlog.getBackendHeadFromDb(dbEventlog);
|
161
134
|
if (initialBackendHead > initialLocalHead.global) {
|
162
135
|
return shouldNeverHappen(`During boot the backend head (${initialBackendHead}) should never be greater than the local head (${initialLocalHead.global})`);
|
163
136
|
}
|
164
|
-
const
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
// On the leader we don't need a rollback tail beyond `pending` items
|
171
|
-
rollbackTail: [],
|
172
|
-
upstreamHead: { global: initialBackendHead, local: EventId.localDefault },
|
137
|
+
const pendingEvents = dbEventlogMissing
|
138
|
+
? []
|
139
|
+
: yield* Eventlog.getEventsSince({ global: initialBackendHead, client: EventSequenceNumber.clientDefault });
|
140
|
+
const initialSyncState = new SyncState.SyncState({
|
141
|
+
pending: pendingEvents,
|
142
|
+
upstreamHead: { global: initialBackendHead, client: EventSequenceNumber.clientDefault },
|
173
143
|
localHead: initialLocalHead,
|
174
|
-
};
|
144
|
+
});
|
175
145
|
/** State transitions need to happen atomically, so we use a Ref to track the state */
|
176
|
-
yield*
|
177
|
-
applyMutationItemsRef.current = yield* makeApplyMutationItems({ stateRef, semaphore });
|
146
|
+
yield* SubscriptionRef.set(syncStateSref, initialSyncState);
|
178
147
|
// Rehydrate sync queue
|
179
|
-
if (
|
180
|
-
const
|
181
|
-
// Don't sync
|
182
|
-
.filter((
|
183
|
-
const
|
184
|
-
return
|
148
|
+
if (pendingEvents.length > 0) {
|
149
|
+
const globalPendingEvents = pendingEvents
|
150
|
+
// Don't sync clientOnly events
|
151
|
+
.filter((eventEncoded) => {
|
152
|
+
const { eventDef } = getEventDef(schema, eventEncoded.name);
|
153
|
+
return eventDef.options.clientOnly === false;
|
185
154
|
});
|
186
|
-
|
155
|
+
if (globalPendingEvents.length > 0) {
|
156
|
+
yield* BucketQueue.offerAll(syncBackendPushQueue, globalPendingEvents);
|
157
|
+
}
|
187
158
|
}
|
159
|
+
const shutdownOnError = (cause) => Effect.gen(function* () {
|
160
|
+
if (onError === 'shutdown') {
|
161
|
+
yield* shutdownChannel.send(UnexpectedError.make({ cause }));
|
162
|
+
yield* Effect.die(cause);
|
163
|
+
}
|
164
|
+
});
|
165
|
+
yield* backgroundApplyLocalPushes({
|
166
|
+
localPushesLatch,
|
167
|
+
localPushesQueue,
|
168
|
+
pullLatch,
|
169
|
+
syncStateSref,
|
170
|
+
syncBackendPushQueue,
|
171
|
+
schema,
|
172
|
+
isClientEvent,
|
173
|
+
otelSpan,
|
174
|
+
currentLocalPushGenerationRef,
|
175
|
+
connectedClientSessionPullQueues,
|
176
|
+
mergeCounterRef,
|
177
|
+
mergePayloads,
|
178
|
+
localPushBatchSize,
|
179
|
+
testing: {
|
180
|
+
delay: testing?.delays?.localPushProcessing,
|
181
|
+
},
|
182
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped);
|
188
183
|
const backendPushingFiberHandle = yield* FiberHandle.make();
|
189
|
-
|
184
|
+
const backendPushingEffect = backgroundBackendPushing({
|
185
|
+
syncBackendPushQueue,
|
186
|
+
otelSpan,
|
187
|
+
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
188
|
+
backendPushBatchSize,
|
189
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError));
|
190
|
+
yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect);
|
190
191
|
yield* backgroundBackendPulling({
|
191
|
-
dbReady,
|
192
192
|
initialBackendHead,
|
193
|
-
|
193
|
+
isClientEvent,
|
194
194
|
restartBackendPushing: (filteredRebasedPending) => Effect.gen(function* () {
|
195
195
|
// Stop current pushing fiber
|
196
196
|
yield* FiberHandle.clear(backendPushingFiberHandle);
|
197
|
-
// Reset the sync queue
|
198
|
-
yield* BucketQueue.clear(
|
199
|
-
yield* BucketQueue.offerAll(
|
197
|
+
// Reset the sync backend push queue
|
198
|
+
yield* BucketQueue.clear(syncBackendPushQueue);
|
199
|
+
yield* BucketQueue.offerAll(syncBackendPushQueue, filteredRebasedPending);
|
200
200
|
// Restart pushing fiber
|
201
|
-
yield* FiberHandle.run(backendPushingFiberHandle,
|
201
|
+
yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect);
|
202
202
|
}),
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
203
|
+
syncStateSref,
|
204
|
+
localPushesLatch,
|
205
|
+
pullLatch,
|
206
|
+
otelSpan,
|
207
207
|
initialBlockingSyncContext,
|
208
|
-
|
209
|
-
|
208
|
+
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
209
|
+
connectedClientSessionPullQueues,
|
210
|
+
mergeCounterRef,
|
211
|
+
mergePayloads,
|
212
|
+
advancePushHead,
|
213
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped);
|
214
|
+
return { initialLeaderHead: initialLocalHead };
|
215
|
+
}).pipe(Effect.withSpanScoped('@livestore/common:LeaderSyncProcessor:boot'));
|
216
|
+
const pull = ({ cursor }) => Effect.gen(function* () {
|
217
|
+
const queue = yield* pullQueue({ cursor });
|
218
|
+
return Stream.fromQueue(queue);
|
219
|
+
}).pipe(Stream.unwrapScoped);
|
220
|
+
const pullQueue = ({ cursor }) => {
|
221
|
+
const runtime = ctxRef.current?.runtime ?? shouldNeverHappen('Not initialized');
|
222
|
+
return Effect.gen(function* () {
|
223
|
+
const queue = yield* connectedClientSessionPullQueues.makeQueue;
|
224
|
+
const payloadsSinceCursor = Array.from(mergePayloads.entries())
|
225
|
+
.map(([mergeCounter, payload]) => ({ payload, mergeCounter }))
|
226
|
+
.filter(({ mergeCounter }) => mergeCounter > cursor.mergeCounter)
|
227
|
+
.toSorted((a, b) => a.mergeCounter - b.mergeCounter)
|
228
|
+
.map(({ payload, mergeCounter }) => {
|
229
|
+
if (payload._tag === 'upstream-advance') {
|
230
|
+
return {
|
231
|
+
payload: {
|
232
|
+
_tag: 'upstream-advance',
|
233
|
+
newEvents: ReadonlyArray.dropWhile(payload.newEvents, (eventEncoded) => EventSequenceNumber.isGreaterThanOrEqual(cursor.eventNum, eventEncoded.seqNum)),
|
234
|
+
},
|
235
|
+
mergeCounter,
|
236
|
+
};
|
237
|
+
}
|
238
|
+
else {
|
239
|
+
return { payload, mergeCounter };
|
240
|
+
}
|
241
|
+
});
|
242
|
+
yield* queue.offerAll(payloadsSinceCursor);
|
243
|
+
return queue;
|
244
|
+
}).pipe(Effect.provide(runtime));
|
245
|
+
};
|
246
|
+
const syncState = Subscribable.make({
|
247
|
+
get: Effect.gen(function* () {
|
248
|
+
const syncState = yield* syncStateSref;
|
249
|
+
if (syncState === undefined)
|
250
|
+
return shouldNeverHappen('Not initialized');
|
251
|
+
return syncState;
|
252
|
+
}),
|
253
|
+
changes: syncStateSref.changes.pipe(Stream.filter(isNotUndefined)),
|
254
|
+
});
|
210
255
|
return {
|
256
|
+
pull,
|
257
|
+
pullQueue,
|
211
258
|
push,
|
212
259
|
pushPartial,
|
213
260
|
boot,
|
214
|
-
syncState
|
215
|
-
|
216
|
-
if (state._tag === 'init')
|
217
|
-
return shouldNeverHappen('Not initialized');
|
218
|
-
return state.syncState;
|
219
|
-
}),
|
261
|
+
syncState,
|
262
|
+
getMergeCounter: () => mergeCounterRef.current,
|
220
263
|
};
|
221
264
|
});
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
const applyMutation = yield* makeApplyMutation;
|
227
|
-
return ({ batchItems }) => Effect.gen(function* () {
|
228
|
-
const state = yield* Ref.get(stateRef);
|
229
|
-
if (state._tag !== 'applying-syncstate-advance') {
|
230
|
-
// console.log('applyMutationItems: counter', counter)
|
231
|
-
return shouldNeverHappen(`Expected to be applying-syncstate-advance but got ${state._tag}`);
|
265
|
+
const backgroundApplyLocalPushes = ({ localPushesLatch, localPushesQueue, pullLatch, syncStateSref, syncBackendPushQueue, schema, isClientEvent, otelSpan, currentLocalPushGenerationRef, connectedClientSessionPullQueues, mergeCounterRef, mergePayloads, localPushBatchSize, testing, }) => Effect.gen(function* () {
|
266
|
+
while (true) {
|
267
|
+
if (testing.delay !== undefined) {
|
268
|
+
yield* testing.delay.pipe(Effect.withSpan('localPushProcessingDelay'));
|
232
269
|
}
|
233
|
-
|
234
|
-
|
235
|
-
yield*
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
270
|
+
const batchItems = yield* BucketQueue.takeBetween(localPushesQueue, 1, localPushBatchSize);
|
271
|
+
// Wait for the backend pulling to finish
|
272
|
+
yield* localPushesLatch.await;
|
273
|
+
// Prevent backend pull processing until this local push is finished
|
274
|
+
yield* pullLatch.close;
|
275
|
+
// Since the generation might have changed since enqueuing, we need to filter out items with older generation
|
276
|
+
// It's important that we filter after we got localPushesLatch, otherwise we might filter with the old generation
|
277
|
+
const filteredBatchItems = batchItems
|
278
|
+
.filter(([_1, _2, generation]) => generation === currentLocalPushGenerationRef.current)
|
279
|
+
.map(([eventEncoded, deferred]) => [eventEncoded, deferred]);
|
280
|
+
if (filteredBatchItems.length === 0) {
|
281
|
+
// console.log('dropping old-gen batch', currentLocalPushGenerationRef.current)
|
282
|
+
// Allow the backend pulling to start
|
283
|
+
yield* pullLatch.open;
|
284
|
+
continue;
|
285
|
+
}
|
286
|
+
const [newEvents, deferreds] = ReadonlyArray.unzip(filteredBatchItems);
|
287
|
+
const syncState = yield* syncStateSref;
|
288
|
+
if (syncState === undefined)
|
289
|
+
return shouldNeverHappen('Not initialized');
|
290
|
+
const mergeResult = SyncState.merge({
|
291
|
+
syncState,
|
292
|
+
payload: { _tag: 'local-push', newEvents },
|
293
|
+
isClientEvent,
|
294
|
+
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
295
|
+
});
|
296
|
+
const mergeCounter = yield* incrementMergeCounter(mergeCounterRef);
|
297
|
+
switch (mergeResult._tag) {
|
298
|
+
case 'unexpected-error': {
|
299
|
+
otelSpan?.addEvent(`[${mergeCounter}]:push:unexpected-error`, {
|
300
|
+
batchSize: newEvents.length,
|
301
|
+
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
302
|
+
});
|
303
|
+
return yield* Effect.fail(mergeResult.cause);
|
304
|
+
}
|
305
|
+
case 'rebase': {
|
306
|
+
return shouldNeverHappen('The leader thread should never have to rebase due to a local push');
|
307
|
+
}
|
308
|
+
case 'reject': {
|
309
|
+
otelSpan?.addEvent(`[${mergeCounter}]:push:reject`, {
|
310
|
+
batchSize: newEvents.length,
|
311
|
+
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
312
|
+
});
|
313
|
+
// TODO: how to test this?
|
314
|
+
currentLocalPushGenerationRef.current++;
|
315
|
+
const nextGeneration = currentLocalPushGenerationRef.current;
|
316
|
+
const providedNum = newEvents.at(0).seqNum;
|
317
|
+
// All subsequent pushes with same generation should be rejected as well
|
318
|
+
// We're also handling the case where the localPushQueue already contains events
|
319
|
+
// from the next generation which we preserve in the queue
|
320
|
+
const remainingEventsMatchingGeneration = yield* BucketQueue.takeSplitWhere(localPushesQueue, (item) => item[2] >= nextGeneration);
|
321
|
+
// TODO we still need to better understand and handle this scenario
|
322
|
+
if (LS_DEV && (yield* BucketQueue.size(localPushesQueue)) > 0) {
|
323
|
+
console.log('localPushesQueue is not empty', yield* BucketQueue.size(localPushesQueue));
|
324
|
+
debugger;
|
325
|
+
}
|
326
|
+
const allDeferredsToReject = [
|
327
|
+
...deferreds,
|
328
|
+
...remainingEventsMatchingGeneration.map(([_, deferred]) => deferred),
|
329
|
+
].filter(isNotUndefined);
|
330
|
+
yield* Effect.forEach(allDeferredsToReject, (deferred) => Deferred.fail(deferred, LeaderAheadError.make({
|
331
|
+
minimumExpectedNum: mergeResult.expectedMinimumId,
|
332
|
+
providedNum,
|
333
|
+
// nextGeneration,
|
334
|
+
})));
|
335
|
+
// Allow the backend pulling to start
|
336
|
+
yield* pullLatch.open;
|
337
|
+
// In this case we're skipping state update and down/upstream processing
|
338
|
+
// We've cleared the local push queue and are now waiting for new local pushes / backend pulls
|
339
|
+
continue;
|
340
|
+
}
|
341
|
+
case 'advance': {
|
342
|
+
break;
|
343
|
+
}
|
344
|
+
default: {
|
345
|
+
casesHandled(mergeResult);
|
247
346
|
}
|
248
|
-
// TODO re-introduce this
|
249
|
-
// if (i < batchItems.length - 1) {
|
250
|
-
// yield* Ref.set(stateRef, { ...state, proccesHead: batchItems[i + 1]!.id })
|
251
|
-
// }
|
252
347
|
}
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
348
|
+
yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState);
|
349
|
+
yield* connectedClientSessionPullQueues.offer({
|
350
|
+
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }),
|
351
|
+
mergeCounter,
|
352
|
+
});
|
353
|
+
mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }));
|
354
|
+
otelSpan?.addEvent(`[${mergeCounter}]:push:advance`, {
|
355
|
+
batchSize: newEvents.length,
|
356
|
+
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
357
|
+
});
|
358
|
+
// Don't sync clientOnly events
|
359
|
+
const filteredBatch = mergeResult.newEvents.filter((eventEncoded) => {
|
360
|
+
const { eventDef } = getEventDef(schema, eventEncoded.name);
|
361
|
+
return eventDef.options.clientOnly === false;
|
362
|
+
});
|
363
|
+
yield* BucketQueue.offerAll(syncBackendPushQueue, filteredBatch);
|
364
|
+
yield* materializeEventsBatch({ batchItems: mergeResult.newEvents, deferreds });
|
365
|
+
// Allow the backend pulling to start
|
366
|
+
yield* pullLatch.open;
|
367
|
+
}
|
261
368
|
});
|
262
|
-
|
263
|
-
|
369
|
+
// TODO how to handle errors gracefully
|
370
|
+
const materializeEventsBatch = ({ batchItems, deferreds }) => Effect.gen(function* () {
|
371
|
+
const { dbState: db, dbEventlog, materializeEvent } = yield* LeaderThreadCtx;
|
372
|
+
// NOTE We always start a transaction to ensure consistency between db and eventlog (even for single-item batches)
|
373
|
+
db.execute('BEGIN TRANSACTION', undefined); // Start the transaction
|
374
|
+
dbEventlog.execute('BEGIN TRANSACTION', undefined); // Start the transaction
|
375
|
+
yield* Effect.addFinalizer((exit) => Effect.gen(function* () {
|
376
|
+
if (Exit.isSuccess(exit))
|
377
|
+
return;
|
378
|
+
// Rollback in case of an error
|
379
|
+
db.execute('ROLLBACK', undefined);
|
380
|
+
dbEventlog.execute('ROLLBACK', undefined);
|
381
|
+
}));
|
382
|
+
for (let i = 0; i < batchItems.length; i++) {
|
383
|
+
const { sessionChangeset } = yield* materializeEvent(batchItems[i]);
|
384
|
+
batchItems[i].meta.sessionChangeset = sessionChangeset;
|
385
|
+
if (deferreds?.[i] !== undefined) {
|
386
|
+
yield* Deferred.succeed(deferreds[i], void 0);
|
387
|
+
}
|
388
|
+
}
|
389
|
+
db.execute('COMMIT', undefined); // Commit the transaction
|
390
|
+
dbEventlog.execute('COMMIT', undefined); // Commit the transaction
|
391
|
+
}).pipe(Effect.uninterruptible, Effect.scoped, Effect.withSpan('@livestore/common:LeaderSyncProcessor:materializeEventItems', {
|
392
|
+
attributes: { batchSize: batchItems.length },
|
393
|
+
}), Effect.tapCauseLogPretty, UnexpectedError.mapToUnexpectedError);
|
394
|
+
const backgroundBackendPulling = ({ initialBackendHead, isClientEvent, restartBackendPushing, otelSpan, syncStateSref, localPushesLatch, pullLatch, devtoolsLatch, initialBlockingSyncContext, connectedClientSessionPullQueues, mergeCounterRef, mergePayloads, advancePushHead, }) => Effect.gen(function* () {
|
395
|
+
const { syncBackend, dbState: db, dbEventlog, schema } = yield* LeaderThreadCtx;
|
264
396
|
if (syncBackend === undefined)
|
265
397
|
return;
|
266
|
-
const cursorInfo = yield* getCursorInfo(initialBackendHead);
|
267
398
|
const onNewPullChunk = (newEvents, remaining) => Effect.gen(function* () {
|
268
399
|
if (newEvents.length === 0)
|
269
400
|
return;
|
270
|
-
|
271
|
-
|
272
|
-
return shouldNeverHappen('Not initialized');
|
273
|
-
// const counter = state.counter + 1
|
274
|
-
if (state._tag === 'applying-syncstate-advance') {
|
275
|
-
if (state.origin === 'push') {
|
276
|
-
yield* Fiber.interrupt(state.fiber);
|
277
|
-
// In theory we should force-take the semaphore here, but as it's still taken,
|
278
|
-
// it's already in the right state we want it to be in
|
279
|
-
}
|
280
|
-
else {
|
281
|
-
// Wait for previous advance to finish
|
282
|
-
yield* semaphore.take(1);
|
283
|
-
}
|
401
|
+
if (devtoolsLatch !== undefined) {
|
402
|
+
yield* devtoolsLatch.await;
|
284
403
|
}
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
404
|
+
// Prevent more local pushes from being processed until this pull is finished
|
405
|
+
yield* localPushesLatch.close;
|
406
|
+
// Wait for pending local pushes to finish
|
407
|
+
yield* pullLatch.await;
|
408
|
+
const syncState = yield* syncStateSref;
|
409
|
+
if (syncState === undefined)
|
410
|
+
return shouldNeverHappen('Not initialized');
|
411
|
+
const mergeResult = SyncState.merge({
|
412
|
+
syncState,
|
413
|
+
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents }),
|
414
|
+
isClientEvent,
|
415
|
+
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
416
|
+
ignoreClientEvents: true,
|
292
417
|
});
|
293
|
-
|
418
|
+
const mergeCounter = yield* incrementMergeCounter(mergeCounterRef);
|
419
|
+
if (mergeResult._tag === 'reject') {
|
294
420
|
return shouldNeverHappen('The leader thread should never reject upstream advances');
|
295
421
|
}
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
422
|
+
else if (mergeResult._tag === 'unexpected-error') {
|
423
|
+
otelSpan?.addEvent(`[${mergeCounter}]:pull:unexpected-error`, {
|
424
|
+
newEventsCount: newEvents.length,
|
425
|
+
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
426
|
+
});
|
427
|
+
return yield* Effect.fail(mergeResult.cause);
|
428
|
+
}
|
429
|
+
const newBackendHead = newEvents.at(-1).seqNum;
|
430
|
+
Eventlog.updateBackendHead(dbEventlog, newBackendHead);
|
431
|
+
if (mergeResult._tag === 'rebase') {
|
432
|
+
otelSpan?.addEvent(`[${mergeCounter}]:pull:rebase`, {
|
300
433
|
newEventsCount: newEvents.length,
|
301
434
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
302
|
-
rollbackCount:
|
303
|
-
|
435
|
+
rollbackCount: mergeResult.rollbackEvents.length,
|
436
|
+
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
304
437
|
});
|
305
|
-
const
|
306
|
-
const
|
307
|
-
return
|
438
|
+
const globalRebasedPendingEvents = mergeResult.newSyncState.pending.filter((event) => {
|
439
|
+
const { eventDef } = getEventDef(schema, event.name);
|
440
|
+
return eventDef.options.clientOnly === false;
|
308
441
|
});
|
309
|
-
yield* restartBackendPushing(
|
310
|
-
if (
|
311
|
-
yield* rollback({
|
442
|
+
yield* restartBackendPushing(globalRebasedPendingEvents);
|
443
|
+
if (mergeResult.rollbackEvents.length > 0) {
|
444
|
+
yield* rollback({
|
445
|
+
dbState: db,
|
446
|
+
dbEventlog,
|
447
|
+
eventNumsToRollback: mergeResult.rollbackEvents.map((_) => _.seqNum),
|
448
|
+
});
|
312
449
|
}
|
313
450
|
yield* connectedClientSessionPullQueues.offer({
|
314
|
-
payload: {
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
},
|
320
|
-
remaining,
|
451
|
+
payload: SyncState.PayloadUpstreamRebase.make({
|
452
|
+
newEvents: mergeResult.newEvents,
|
453
|
+
rollbackEvents: mergeResult.rollbackEvents,
|
454
|
+
}),
|
455
|
+
mergeCounter,
|
321
456
|
});
|
457
|
+
mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamRebase.make({
|
458
|
+
newEvents: mergeResult.newEvents,
|
459
|
+
rollbackEvents: mergeResult.rollbackEvents,
|
460
|
+
}));
|
322
461
|
}
|
323
462
|
else {
|
324
|
-
|
463
|
+
otelSpan?.addEvent(`[${mergeCounter}]:pull:advance`, {
|
325
464
|
newEventsCount: newEvents.length,
|
326
|
-
|
465
|
+
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
327
466
|
});
|
328
467
|
yield* connectedClientSessionPullQueues.offer({
|
329
|
-
payload: {
|
330
|
-
|
468
|
+
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }),
|
469
|
+
mergeCounter,
|
331
470
|
});
|
471
|
+
mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }));
|
472
|
+
if (mergeResult.confirmedEvents.length > 0) {
|
473
|
+
// `mergeResult.confirmedEvents` don't contain the correct sync metadata, so we need to use
|
474
|
+
// `newEvents` instead which we filter via `mergeResult.confirmedEvents`
|
475
|
+
const confirmedNewEvents = newEvents.filter((event) => mergeResult.confirmedEvents.some((confirmedEvent) => EventSequenceNumber.isEqual(event.seqNum, confirmedEvent.seqNum)));
|
476
|
+
yield* Eventlog.updateSyncMetadata(confirmedNewEvents);
|
477
|
+
}
|
332
478
|
}
|
479
|
+
// Removes the changeset rows which are no longer needed as we'll never have to rollback beyond this point
|
333
480
|
trimChangesetRows(db, newBackendHead);
|
334
|
-
|
335
|
-
yield*
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
}
|
481
|
+
advancePushHead(mergeResult.newSyncState.localHead);
|
482
|
+
yield* materializeEventsBatch({ batchItems: mergeResult.newEvents, deferreds: undefined });
|
483
|
+
yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState);
|
484
|
+
// Allow local pushes to be processed again
|
485
|
+
if (remaining === 0) {
|
486
|
+
yield* localPushesLatch.open;
|
487
|
+
}
|
341
488
|
});
|
489
|
+
const cursorInfo = yield* Eventlog.getSyncBackendCursorInfo(initialBackendHead);
|
342
490
|
yield* syncBackend.pull(cursorInfo).pipe(
|
343
491
|
// TODO only take from queue while connected
|
344
492
|
Stream.tap(({ batch, remaining }) => Effect.gen(function* () {
|
@@ -348,85 +496,100 @@ const backgroundBackendPulling = ({ dbReady, initialBackendHead, isLocalEvent, r
|
|
348
496
|
// batch: TRACE_VERBOSE ? batch : undefined,
|
349
497
|
// },
|
350
498
|
// })
|
351
|
-
//
|
352
|
-
yield* dbReady;
|
353
|
-
// NOTE we only want to take process mutations when the sync backend is connected
|
499
|
+
// NOTE we only want to take process events when the sync backend is connected
|
354
500
|
// (e.g. needed for simulating being offline)
|
355
501
|
// TODO remove when there's a better way to handle this in stream above
|
356
502
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true);
|
357
|
-
yield* onNewPullChunk(batch.map((_) =>
|
503
|
+
yield* onNewPullChunk(batch.map((_) => LiveStoreEvent.EncodedWithMeta.fromGlobal(_.eventEncoded, _.metadata)), remaining);
|
358
504
|
yield* initialBlockingSyncContext.update({ processed: batch.length, remaining });
|
359
505
|
})), Stream.runDrain, Effect.interruptible);
|
360
|
-
}).pipe(Effect.withSpan('@livestore/common:
|
361
|
-
const
|
362
|
-
const
|
363
|
-
.select(sql `SELECT * FROM ${SESSION_CHANGESET_META_TABLE} WHERE (idGlobal, idLocal) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.local})`).join(', ')})`)
|
364
|
-
.map((_) => ({ id: { global: _.idGlobal, local: _.idLocal }, changeset: _.changeset, debug: _.debug }))
|
365
|
-
.toSorted((a, b) => EventId.compare(a.id, b.id));
|
366
|
-
// Apply changesets in reverse order
|
367
|
-
for (let i = rollbackEvents.length - 1; i >= 0; i--) {
|
368
|
-
const { changeset } = rollbackEvents[i];
|
369
|
-
if (changeset !== null) {
|
370
|
-
db.makeChangeset(changeset).invert().apply();
|
371
|
-
}
|
372
|
-
}
|
373
|
-
// Delete the changeset rows
|
374
|
-
db.execute(sql `DELETE FROM ${SESSION_CHANGESET_META_TABLE} WHERE (idGlobal, idLocal) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.local})`).join(', ')})`);
|
375
|
-
// Delete the mutation log rows
|
376
|
-
dbLog.execute(sql `DELETE FROM ${MUTATION_LOG_META_TABLE} WHERE (idGlobal, idLocal) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.local})`).join(', ')})`);
|
377
|
-
}).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:rollback', {
|
378
|
-
attributes: { count: eventIdsToRollback.length },
|
379
|
-
}));
|
380
|
-
const getCursorInfo = (remoteHead) => Effect.gen(function* () {
|
381
|
-
const { dbLog } = yield* LeaderThreadCtx;
|
382
|
-
if (remoteHead === EventId.ROOT.global)
|
383
|
-
return Option.none();
|
384
|
-
const MutationlogQuerySchema = Schema.Struct({
|
385
|
-
syncMetadataJson: Schema.parseJson(Schema.Option(Schema.JsonValue)),
|
386
|
-
}).pipe(Schema.pluck('syncMetadataJson'), Schema.Array, Schema.head);
|
387
|
-
const syncMetadataOption = yield* Effect.sync(() => dbLog.select(sql `SELECT syncMetadataJson FROM ${MUTATION_LOG_META_TABLE} WHERE idGlobal = ${remoteHead} ORDER BY idLocal ASC LIMIT 1`)).pipe(Effect.andThen(Schema.decode(MutationlogQuerySchema)), Effect.map(Option.flatten), Effect.orDie);
|
388
|
-
return Option.some({
|
389
|
-
cursor: { global: remoteHead, local: EventId.localDefault },
|
390
|
-
metadata: syncMetadataOption,
|
391
|
-
});
|
392
|
-
}).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:getCursorInfo', { attributes: { remoteHead } }));
|
393
|
-
const backgroundBackendPushing = ({ dbReady, syncBackendQueue, span, }) => Effect.gen(function* () {
|
394
|
-
const { syncBackend, dbLog } = yield* LeaderThreadCtx;
|
506
|
+
}).pipe(Effect.withSpan('@livestore/common:LeaderSyncProcessor:backend-pulling'));
|
507
|
+
const backgroundBackendPushing = ({ syncBackendPushQueue, otelSpan, devtoolsLatch, backendPushBatchSize, }) => Effect.gen(function* () {
|
508
|
+
const { syncBackend } = yield* LeaderThreadCtx;
|
395
509
|
if (syncBackend === undefined)
|
396
510
|
return;
|
397
|
-
yield* dbReady;
|
398
511
|
while (true) {
|
399
512
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true);
|
400
|
-
|
401
|
-
const queueItems = yield* BucketQueue.takeBetween(syncBackendQueue, 1, 50);
|
513
|
+
const queueItems = yield* BucketQueue.takeBetween(syncBackendPushQueue, 1, backendPushBatchSize);
|
402
514
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true);
|
403
|
-
|
515
|
+
if (devtoolsLatch !== undefined) {
|
516
|
+
yield* devtoolsLatch.await;
|
517
|
+
}
|
518
|
+
otelSpan?.addEvent('backend-push', {
|
404
519
|
batchSize: queueItems.length,
|
405
520
|
batch: TRACE_VERBOSE ? JSON.stringify(queueItems) : undefined,
|
406
521
|
});
|
407
522
|
// TODO handle push errors (should only happen during concurrent pull+push)
|
408
523
|
const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either);
|
409
524
|
if (pushResult._tag === 'Left') {
|
410
|
-
|
525
|
+
if (LS_DEV) {
|
526
|
+
yield* Effect.logDebug('handled backend-push-error', { error: pushResult.left.toString() });
|
527
|
+
}
|
528
|
+
otelSpan?.addEvent('backend-push-error', { error: pushResult.left.toString() });
|
411
529
|
// wait for interrupt caused by background pulling which will then restart pushing
|
412
530
|
return yield* Effect.never;
|
413
531
|
}
|
414
|
-
const { metadata } = pushResult.right;
|
415
|
-
// TODO try to do this in a single query
|
416
|
-
for (let i = 0; i < queueItems.length; i++) {
|
417
|
-
const mutationEventEncoded = queueItems[i];
|
418
|
-
yield* execSql(dbLog, ...updateRows({
|
419
|
-
tableName: MUTATION_LOG_META_TABLE,
|
420
|
-
columns: mutationLogMetaTable.sqliteDef.columns,
|
421
|
-
where: { idGlobal: mutationEventEncoded.id.global, idLocal: mutationEventEncoded.id.local },
|
422
|
-
updateValues: { syncMetadataJson: metadata[i] },
|
423
|
-
}));
|
424
|
-
}
|
425
532
|
}
|
426
|
-
}).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:
|
533
|
+
}).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:LeaderSyncProcessor:backend-pushing'));
|
427
534
|
const trimChangesetRows = (db, newHead) => {
|
428
535
|
// Since we're using the session changeset rows to query for the current head,
|
429
536
|
// we're keeping at least one row for the current head, and thus are using `<` instead of `<=`
|
430
|
-
db.execute(sql `DELETE FROM ${SESSION_CHANGESET_META_TABLE} WHERE
|
537
|
+
db.execute(sql `DELETE FROM ${SystemTables.SESSION_CHANGESET_META_TABLE} WHERE seqNumGlobal < ${newHead.global}`);
|
431
538
|
};
|
539
|
+
const makePullQueueSet = Effect.gen(function* () {
|
540
|
+
const set = new Set();
|
541
|
+
yield* Effect.addFinalizer(() => Effect.gen(function* () {
|
542
|
+
for (const queue of set) {
|
543
|
+
yield* Queue.shutdown(queue);
|
544
|
+
}
|
545
|
+
set.clear();
|
546
|
+
}));
|
547
|
+
const makeQueue = Effect.gen(function* () {
|
548
|
+
const queue = yield* Queue.unbounded().pipe(Effect.acquireRelease(Queue.shutdown));
|
549
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => set.delete(queue)));
|
550
|
+
set.add(queue);
|
551
|
+
return queue;
|
552
|
+
});
|
553
|
+
const offer = (item) => Effect.gen(function* () {
|
554
|
+
// Short-circuit if the payload is an empty upstream advance
|
555
|
+
if (item.payload._tag === 'upstream-advance' && item.payload.newEvents.length === 0) {
|
556
|
+
return;
|
557
|
+
}
|
558
|
+
for (const queue of set) {
|
559
|
+
yield* Queue.offer(queue, item);
|
560
|
+
}
|
561
|
+
});
|
562
|
+
return {
|
563
|
+
makeQueue,
|
564
|
+
offer,
|
565
|
+
};
|
566
|
+
});
|
567
|
+
const incrementMergeCounter = (mergeCounterRef) => Effect.gen(function* () {
|
568
|
+
const { dbState } = yield* LeaderThreadCtx;
|
569
|
+
mergeCounterRef.current++;
|
570
|
+
dbState.execute(sql `INSERT OR REPLACE INTO ${SystemTables.LEADER_MERGE_COUNTER_TABLE} (id, mergeCounter) VALUES (0, ${mergeCounterRef.current})`);
|
571
|
+
return mergeCounterRef.current;
|
572
|
+
});
|
573
|
+
const getMergeCounterFromDb = (dbState) => Effect.gen(function* () {
|
574
|
+
const result = dbState.select(sql `SELECT mergeCounter FROM ${SystemTables.LEADER_MERGE_COUNTER_TABLE} WHERE id = 0`);
|
575
|
+
return result[0]?.mergeCounter ?? 0;
|
576
|
+
});
|
577
|
+
const validatePushBatch = (batch, pushHead) => Effect.gen(function* () {
|
578
|
+
if (batch.length === 0) {
|
579
|
+
return;
|
580
|
+
}
|
581
|
+
// Make sure batch is monotonically increasing
|
582
|
+
for (let i = 1; i < batch.length; i++) {
|
583
|
+
if (EventSequenceNumber.isGreaterThanOrEqual(batch[i - 1].seqNum, batch[i].seqNum)) {
|
584
|
+
shouldNeverHappen(`Events must be ordered in monotonically ascending order by eventNum. Received: [${batch.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`);
|
585
|
+
}
|
586
|
+
}
|
587
|
+
// Make sure smallest sequence number is > pushHead
|
588
|
+
if (EventSequenceNumber.isGreaterThanOrEqual(pushHead, batch[0].seqNum)) {
|
589
|
+
return yield* LeaderAheadError.make({
|
590
|
+
minimumExpectedNum: pushHead,
|
591
|
+
providedNum: batch[0].seqNum,
|
592
|
+
});
|
593
|
+
}
|
594
|
+
});
|
432
595
|
//# sourceMappingURL=LeaderSyncProcessor.js.map
|