@livestore/common 0.3.0-dev.9 → 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 +36 -11
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +426 -252
- 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 +79 -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 +149 -73
- 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 +193 -84
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +305 -151
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +267 -303
- 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 +664 -394
- 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 +84 -46
- 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 +218 -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 +312 -345
- package/src/sync/syncstate.ts +414 -224
- 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 -167
- 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 -185
- 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,270 +1,406 @@
|
|
1
|
-
import { isNotUndefined, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils';
|
2
|
-
import { BucketQueue, Deferred, Effect, Exit, FiberHandle,
|
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 LeaderSyncProcessor manages synchronization of
|
12
|
+
* The LeaderSyncProcessor manages synchronization of events between
|
15
13
|
* the local state and the sync backend, ensuring efficient and orderly processing.
|
16
14
|
*
|
17
15
|
* In the LeaderSyncProcessor, pulling always has precedence over pushing.
|
18
16
|
*
|
19
17
|
* Responsibilities:
|
20
|
-
* - Queueing incoming local
|
21
|
-
* - Broadcasting
|
22
|
-
* - Pushing
|
18
|
+
* - Queueing incoming local events in a localPushesQueue.
|
19
|
+
* - Broadcasting events to client sessions via pull queues.
|
20
|
+
* - Pushing events to the sync backend.
|
23
21
|
*
|
24
22
|
* Notes:
|
25
23
|
*
|
26
24
|
* local push processing:
|
27
|
-
* -
|
25
|
+
* - localPushesQueue:
|
28
26
|
* - Maintains events in ascending order.
|
29
27
|
* - Uses `Deferred` objects to resolve/reject events based on application success.
|
30
|
-
* - Processes events from the
|
28
|
+
* - Processes events from the queue, applying events in batches.
|
31
29
|
* - Controlled by a `Latch` to manage execution flow.
|
32
30
|
* - The latch closes on pull receipt and re-opens post-pull completion.
|
33
31
|
* - Processes up to `maxBatchSize` events per cycle.
|
34
32
|
*
|
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.
|
38
|
+
*
|
35
39
|
*/
|
36
|
-
export const makeLeaderSyncProcessor = ({ schema,
|
37
|
-
const
|
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;
|
38
44
|
const syncStateSref = yield* SubscriptionRef.make(undefined);
|
39
|
-
const
|
40
|
-
const
|
41
|
-
return
|
45
|
+
const isClientEvent = (eventEncoded) => {
|
46
|
+
const { eventDef } = getEventDef(schema, eventEncoded.name);
|
47
|
+
return eventDef.options.clientOnly;
|
48
|
+
};
|
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,
|
42
62
|
};
|
43
|
-
const spanRef = { current: undefined };
|
44
63
|
const localPushesQueue = yield* BucketQueue.make();
|
45
64
|
const localPushesLatch = yield* Effect.makeLatch(true);
|
46
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
|
47
80
|
const push = (newEvents, options) => Effect.gen(function* () {
|
48
|
-
// TODO validate batch
|
49
81
|
if (newEvents.length === 0)
|
50
82
|
return;
|
83
|
+
yield* validatePushBatch(newEvents, pushHeadRef.current);
|
84
|
+
advancePushHead(newEvents.at(-1).seqNum);
|
51
85
|
const waitForProcessing = options?.waitForProcessing ?? false;
|
52
|
-
const
|
53
|
-
? yield* Effect.forEach(newEvents, () => Deferred.make())
|
54
|
-
: newEvents.map((_) => undefined);
|
55
|
-
// TODO validate batch ordering
|
56
|
-
const mappedEvents = newEvents.map((mutationEventEncoded, i) => new MutationEvent.EncodedWithMeta({
|
57
|
-
...mutationEventEncoded,
|
58
|
-
meta: { deferred: deferreds[i] },
|
59
|
-
}));
|
60
|
-
yield* BucketQueue.offerAll(localPushesQueue, mappedEvents);
|
86
|
+
const generation = currentLocalPushGenerationRef.current;
|
61
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);
|
62
91
|
yield* Effect.all(deferreds);
|
63
92
|
}
|
64
|
-
|
93
|
+
else {
|
94
|
+
const items = newEvents.map((eventEncoded) => [eventEncoded, undefined, generation]);
|
95
|
+
yield* BucketQueue.offerAll(localPushesQueue, items);
|
96
|
+
}
|
97
|
+
}).pipe(Effect.withSpan('@livestore/common:LeaderSyncProcessor:push', {
|
65
98
|
attributes: {
|
66
99
|
batchSize: newEvents.length,
|
67
100
|
batch: TRACE_VERBOSE ? newEvents : undefined,
|
68
101
|
},
|
69
|
-
links:
|
70
|
-
? [{ _tag: 'SpanLink', span: OtelTracer.makeExternalSpan(spanRef.current.spanContext()), attributes: {} }]
|
71
|
-
: undefined,
|
102
|
+
links: ctxRef.current?.span ? [{ _tag: 'SpanLink', span: ctxRef.current.span, attributes: {} }] : undefined,
|
72
103
|
}));
|
73
|
-
const pushPartial = (
|
104
|
+
const pushPartial = ({ event: { name, args }, clientId, sessionId }) => Effect.gen(function* () {
|
74
105
|
const syncState = yield* syncStateSref;
|
75
106
|
if (syncState === undefined)
|
76
107
|
return shouldNeverHappen('Not initialized');
|
77
|
-
const
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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),
|
82
115
|
});
|
83
|
-
yield* push([
|
84
|
-
}).pipe(Effect.catchTag('
|
116
|
+
yield* push([eventEncoded]);
|
117
|
+
}).pipe(Effect.catchTag('LeaderAheadError', Effect.orDie));
|
85
118
|
// Starts various background loops
|
86
|
-
const boot =
|
87
|
-
const span = yield*
|
88
|
-
|
89
|
-
const
|
90
|
-
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);
|
91
134
|
if (initialBackendHead > initialLocalHead.global) {
|
92
135
|
return shouldNeverHappen(`During boot the backend head (${initialBackendHead}) should never be greater than the local head (${initialLocalHead.global})`);
|
93
136
|
}
|
94
|
-
const
|
95
|
-
|
96
|
-
|
97
|
-
}).pipe(Effect.map(ReadonlyArray.map((_) => new MutationEvent.EncodedWithMeta(_))));
|
137
|
+
const pendingEvents = dbEventlogMissing
|
138
|
+
? []
|
139
|
+
: yield* Eventlog.getEventsSince({ global: initialBackendHead, client: EventSequenceNumber.clientDefault });
|
98
140
|
const initialSyncState = new SyncState.SyncState({
|
99
|
-
pending:
|
100
|
-
|
101
|
-
rollbackTail: [],
|
102
|
-
upstreamHead: { global: initialBackendHead, local: EventId.localDefault },
|
141
|
+
pending: pendingEvents,
|
142
|
+
upstreamHead: { global: initialBackendHead, client: EventSequenceNumber.clientDefault },
|
103
143
|
localHead: initialLocalHead,
|
104
144
|
});
|
105
145
|
/** State transitions need to happen atomically, so we use a Ref to track the state */
|
106
146
|
yield* SubscriptionRef.set(syncStateSref, initialSyncState);
|
107
147
|
// Rehydrate sync queue
|
108
|
-
if (
|
109
|
-
const
|
110
|
-
// Don't sync
|
111
|
-
.filter((
|
112
|
-
const
|
113
|
-
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;
|
114
154
|
});
|
115
|
-
|
155
|
+
if (globalPendingEvents.length > 0) {
|
156
|
+
yield* BucketQueue.offerAll(syncBackendPushQueue, globalPendingEvents);
|
157
|
+
}
|
116
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
|
+
});
|
117
165
|
yield* backgroundApplyLocalPushes({
|
118
166
|
localPushesLatch,
|
119
167
|
localPushesQueue,
|
120
168
|
pullLatch,
|
121
169
|
syncStateSref,
|
122
|
-
|
170
|
+
syncBackendPushQueue,
|
123
171
|
schema,
|
124
|
-
|
125
|
-
|
126
|
-
|
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);
|
127
183
|
const backendPushingFiberHandle = yield* FiberHandle.make();
|
128
|
-
|
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);
|
129
191
|
yield* backgroundBackendPulling({
|
130
|
-
dbReady,
|
131
192
|
initialBackendHead,
|
132
|
-
|
193
|
+
isClientEvent,
|
133
194
|
restartBackendPushing: (filteredRebasedPending) => Effect.gen(function* () {
|
134
195
|
// Stop current pushing fiber
|
135
196
|
yield* FiberHandle.clear(backendPushingFiberHandle);
|
136
|
-
// Reset the sync queue
|
137
|
-
yield* BucketQueue.clear(
|
138
|
-
yield* BucketQueue.offerAll(
|
197
|
+
// Reset the sync backend push queue
|
198
|
+
yield* BucketQueue.clear(syncBackendPushQueue);
|
199
|
+
yield* BucketQueue.offerAll(syncBackendPushQueue, filteredRebasedPending);
|
139
200
|
// Restart pushing fiber
|
140
|
-
yield* FiberHandle.run(backendPushingFiberHandle,
|
201
|
+
yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect);
|
141
202
|
}),
|
142
203
|
syncStateSref,
|
143
204
|
localPushesLatch,
|
144
205
|
pullLatch,
|
145
|
-
|
206
|
+
otelSpan,
|
146
207
|
initialBlockingSyncContext,
|
147
|
-
|
148
|
-
|
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
|
+
});
|
149
255
|
return {
|
256
|
+
pull,
|
257
|
+
pullQueue,
|
150
258
|
push,
|
151
259
|
pushPartial,
|
152
260
|
boot,
|
153
|
-
syncState
|
154
|
-
|
155
|
-
const syncState = yield* syncStateSref;
|
156
|
-
if (syncState === undefined)
|
157
|
-
return shouldNeverHappen('Not initialized');
|
158
|
-
return syncState;
|
159
|
-
}),
|
160
|
-
changes: syncStateSref.changes.pipe(Stream.filter(isNotUndefined)),
|
161
|
-
}),
|
261
|
+
syncState,
|
262
|
+
getMergeCounter: () => mergeCounterRef.current,
|
162
263
|
};
|
163
264
|
});
|
164
|
-
const backgroundApplyLocalPushes = ({ localPushesLatch, localPushesQueue, pullLatch, syncStateSref,
|
165
|
-
const { connectedClientSessionPullQueues } = yield* LeaderThreadCtx;
|
166
|
-
const applyMutationItems = yield* makeApplyMutationItems;
|
265
|
+
const backgroundApplyLocalPushes = ({ localPushesLatch, localPushesQueue, pullLatch, syncStateSref, syncBackendPushQueue, schema, isClientEvent, otelSpan, currentLocalPushGenerationRef, connectedClientSessionPullQueues, mergeCounterRef, mergePayloads, localPushBatchSize, testing, }) => Effect.gen(function* () {
|
167
266
|
while (true) {
|
168
|
-
|
169
|
-
|
267
|
+
if (testing.delay !== undefined) {
|
268
|
+
yield* testing.delay.pipe(Effect.withSpan('localPushProcessingDelay'));
|
269
|
+
}
|
270
|
+
const batchItems = yield* BucketQueue.takeBetween(localPushesQueue, 1, localPushBatchSize);
|
170
271
|
// Wait for the backend pulling to finish
|
171
272
|
yield* localPushesLatch.await;
|
172
|
-
// Prevent
|
273
|
+
// Prevent backend pull processing until this local push is finished
|
173
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);
|
174
287
|
const syncState = yield* syncStateSref;
|
175
288
|
if (syncState === undefined)
|
176
289
|
return shouldNeverHappen('Not initialized');
|
177
|
-
const
|
290
|
+
const mergeResult = SyncState.merge({
|
178
291
|
syncState,
|
179
292
|
payload: { _tag: 'local-push', newEvents },
|
180
|
-
|
181
|
-
isEqualEvent:
|
293
|
+
isClientEvent,
|
294
|
+
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
182
295
|
});
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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);
|
346
|
+
}
|
210
347
|
}
|
211
|
-
yield* SubscriptionRef.set(syncStateSref,
|
348
|
+
yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState);
|
212
349
|
yield* connectedClientSessionPullQueues.offer({
|
213
|
-
payload: {
|
214
|
-
|
350
|
+
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }),
|
351
|
+
mergeCounter,
|
215
352
|
});
|
216
|
-
|
353
|
+
mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }));
|
354
|
+
otelSpan?.addEvent(`[${mergeCounter}]:push:advance`, {
|
217
355
|
batchSize: newEvents.length,
|
218
|
-
|
356
|
+
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
219
357
|
});
|
220
|
-
// Don't sync
|
221
|
-
const filteredBatch =
|
222
|
-
const
|
223
|
-
return
|
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;
|
224
362
|
});
|
225
|
-
yield* BucketQueue.offerAll(
|
226
|
-
yield*
|
363
|
+
yield* BucketQueue.offerAll(syncBackendPushQueue, filteredBatch);
|
364
|
+
yield* materializeEventsBatch({ batchItems: mergeResult.newEvents, deferreds });
|
227
365
|
// Allow the backend pulling to start
|
228
366
|
yield* pullLatch.open;
|
229
367
|
}
|
230
368
|
});
|
231
369
|
// TODO how to handle errors gracefully
|
232
|
-
const
|
233
|
-
const
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
})
|
246
|
-
|
247
|
-
|
248
|
-
yield*
|
249
|
-
if (meta?.deferred) {
|
250
|
-
yield* Deferred.succeed(meta.deferred, void 0);
|
251
|
-
}
|
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);
|
252
387
|
}
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
});
|
259
|
-
const backgroundBackendPulling = ({
|
260
|
-
const { syncBackend, db,
|
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;
|
261
396
|
if (syncBackend === undefined)
|
262
397
|
return;
|
263
|
-
const cursorInfo = yield* getCursorInfo(initialBackendHead);
|
264
|
-
const applyMutationItems = yield* makeApplyMutationItems;
|
265
398
|
const onNewPullChunk = (newEvents, remaining) => Effect.gen(function* () {
|
266
399
|
if (newEvents.length === 0)
|
267
400
|
return;
|
401
|
+
if (devtoolsLatch !== undefined) {
|
402
|
+
yield* devtoolsLatch.await;
|
403
|
+
}
|
268
404
|
// Prevent more local pushes from being processed until this pull is finished
|
269
405
|
yield* localPushesLatch.close;
|
270
406
|
// Wait for pending local pushes to finish
|
@@ -272,62 +408,85 @@ const backgroundBackendPulling = ({ dbReady, initialBackendHead, isLocalEvent, r
|
|
272
408
|
const syncState = yield* syncStateSref;
|
273
409
|
if (syncState === undefined)
|
274
410
|
return shouldNeverHappen('Not initialized');
|
275
|
-
const
|
276
|
-
const updateResult = SyncState.updateSyncState({
|
411
|
+
const mergeResult = SyncState.merge({
|
277
412
|
syncState,
|
278
|
-
payload: {
|
279
|
-
|
280
|
-
isEqualEvent:
|
281
|
-
|
413
|
+
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents }),
|
414
|
+
isClientEvent,
|
415
|
+
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
416
|
+
ignoreClientEvents: true,
|
282
417
|
});
|
283
|
-
|
418
|
+
const mergeCounter = yield* incrementMergeCounter(mergeCounterRef);
|
419
|
+
if (mergeResult._tag === 'reject') {
|
284
420
|
return shouldNeverHappen('The leader thread should never reject upstream advances');
|
285
421
|
}
|
286
|
-
|
287
|
-
|
288
|
-
if (updateResult._tag === 'rebase') {
|
289
|
-
span?.addEvent('backend-pull:rebase', {
|
422
|
+
else if (mergeResult._tag === 'unexpected-error') {
|
423
|
+
otelSpan?.addEvent(`[${mergeCounter}]:pull:unexpected-error`, {
|
290
424
|
newEventsCount: newEvents.length,
|
291
425
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
292
|
-
rollbackCount: updateResult.eventsToRollback.length,
|
293
|
-
updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
|
294
426
|
});
|
295
|
-
|
296
|
-
|
297
|
-
|
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`, {
|
433
|
+
newEventsCount: newEvents.length,
|
434
|
+
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
435
|
+
rollbackCount: mergeResult.rollbackEvents.length,
|
436
|
+
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
437
|
+
});
|
438
|
+
const globalRebasedPendingEvents = mergeResult.newSyncState.pending.filter((event) => {
|
439
|
+
const { eventDef } = getEventDef(schema, event.name);
|
440
|
+
return eventDef.options.clientOnly === false;
|
298
441
|
});
|
299
|
-
yield* restartBackendPushing(
|
300
|
-
if (
|
301
|
-
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
|
+
});
|
302
449
|
}
|
303
450
|
yield* connectedClientSessionPullQueues.offer({
|
304
|
-
payload: {
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
},
|
310
|
-
remaining,
|
451
|
+
payload: SyncState.PayloadUpstreamRebase.make({
|
452
|
+
newEvents: mergeResult.newEvents,
|
453
|
+
rollbackEvents: mergeResult.rollbackEvents,
|
454
|
+
}),
|
455
|
+
mergeCounter,
|
311
456
|
});
|
457
|
+
mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamRebase.make({
|
458
|
+
newEvents: mergeResult.newEvents,
|
459
|
+
rollbackEvents: mergeResult.rollbackEvents,
|
460
|
+
}));
|
312
461
|
}
|
313
462
|
else {
|
314
|
-
|
463
|
+
otelSpan?.addEvent(`[${mergeCounter}]:pull:advance`, {
|
315
464
|
newEventsCount: newEvents.length,
|
316
|
-
|
465
|
+
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
317
466
|
});
|
318
467
|
yield* connectedClientSessionPullQueues.offer({
|
319
|
-
payload: {
|
320
|
-
|
468
|
+
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }),
|
469
|
+
mergeCounter,
|
321
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
|
+
}
|
322
478
|
}
|
479
|
+
// Removes the changeset rows which are no longer needed as we'll never have to rollback beyond this point
|
323
480
|
trimChangesetRows(db, newBackendHead);
|
324
|
-
|
325
|
-
yield*
|
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
|
326
485
|
if (remaining === 0) {
|
327
|
-
// Allow local pushes to be processed again
|
328
486
|
yield* localPushesLatch.open;
|
329
487
|
}
|
330
488
|
});
|
489
|
+
const cursorInfo = yield* Eventlog.getSyncBackendCursorInfo(initialBackendHead);
|
331
490
|
yield* syncBackend.pull(cursorInfo).pipe(
|
332
491
|
// TODO only take from queue while connected
|
333
492
|
Stream.tap(({ batch, remaining }) => Effect.gen(function* () {
|
@@ -337,85 +496,100 @@ const backgroundBackendPulling = ({ dbReady, initialBackendHead, isLocalEvent, r
|
|
337
496
|
// batch: TRACE_VERBOSE ? batch : undefined,
|
338
497
|
// },
|
339
498
|
// })
|
340
|
-
//
|
341
|
-
yield* dbReady;
|
342
|
-
// 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
|
343
500
|
// (e.g. needed for simulating being offline)
|
344
501
|
// TODO remove when there's a better way to handle this in stream above
|
345
502
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true);
|
346
|
-
yield* onNewPullChunk(batch.map((_) =>
|
503
|
+
yield* onNewPullChunk(batch.map((_) => LiveStoreEvent.EncodedWithMeta.fromGlobal(_.eventEncoded, _.metadata)), remaining);
|
347
504
|
yield* initialBlockingSyncContext.update({ processed: batch.length, remaining });
|
348
505
|
})), Stream.runDrain, Effect.interruptible);
|
349
|
-
}).pipe(Effect.withSpan('@livestore/common:
|
350
|
-
const
|
351
|
-
const
|
352
|
-
.select(sql `SELECT * FROM ${SESSION_CHANGESET_META_TABLE} WHERE (idGlobal, idLocal) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.local})`).join(', ')})`)
|
353
|
-
.map((_) => ({ id: { global: _.idGlobal, local: _.idLocal }, changeset: _.changeset, debug: _.debug }))
|
354
|
-
.toSorted((a, b) => EventId.compare(a.id, b.id));
|
355
|
-
// Apply changesets in reverse order
|
356
|
-
for (let i = rollbackEvents.length - 1; i >= 0; i--) {
|
357
|
-
const { changeset } = rollbackEvents[i];
|
358
|
-
if (changeset !== null) {
|
359
|
-
db.makeChangeset(changeset).invert().apply();
|
360
|
-
}
|
361
|
-
}
|
362
|
-
// Delete the changeset rows
|
363
|
-
db.execute(sql `DELETE FROM ${SESSION_CHANGESET_META_TABLE} WHERE (idGlobal, idLocal) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.local})`).join(', ')})`);
|
364
|
-
// Delete the mutation log rows
|
365
|
-
dbLog.execute(sql `DELETE FROM ${MUTATION_LOG_META_TABLE} WHERE (idGlobal, idLocal) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.local})`).join(', ')})`);
|
366
|
-
}).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:rollback', {
|
367
|
-
attributes: { count: eventIdsToRollback.length },
|
368
|
-
}));
|
369
|
-
const getCursorInfo = (remoteHead) => Effect.gen(function* () {
|
370
|
-
const { dbLog } = yield* LeaderThreadCtx;
|
371
|
-
if (remoteHead === EventId.ROOT.global)
|
372
|
-
return Option.none();
|
373
|
-
const MutationlogQuerySchema = Schema.Struct({
|
374
|
-
syncMetadataJson: Schema.parseJson(Schema.Option(Schema.JsonValue)),
|
375
|
-
}).pipe(Schema.pluck('syncMetadataJson'), Schema.Array, Schema.head);
|
376
|
-
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);
|
377
|
-
return Option.some({
|
378
|
-
cursor: { global: remoteHead, local: EventId.localDefault },
|
379
|
-
metadata: syncMetadataOption,
|
380
|
-
});
|
381
|
-
}).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:getCursorInfo', { attributes: { remoteHead } }));
|
382
|
-
const backgroundBackendPushing = ({ dbReady, syncBackendQueue, span, }) => Effect.gen(function* () {
|
383
|
-
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;
|
384
509
|
if (syncBackend === undefined)
|
385
510
|
return;
|
386
|
-
yield* dbReady;
|
387
511
|
while (true) {
|
388
512
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true);
|
389
|
-
|
390
|
-
const queueItems = yield* BucketQueue.takeBetween(syncBackendQueue, 1, 50);
|
513
|
+
const queueItems = yield* BucketQueue.takeBetween(syncBackendPushQueue, 1, backendPushBatchSize);
|
391
514
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true);
|
392
|
-
|
515
|
+
if (devtoolsLatch !== undefined) {
|
516
|
+
yield* devtoolsLatch.await;
|
517
|
+
}
|
518
|
+
otelSpan?.addEvent('backend-push', {
|
393
519
|
batchSize: queueItems.length,
|
394
520
|
batch: TRACE_VERBOSE ? JSON.stringify(queueItems) : undefined,
|
395
521
|
});
|
396
522
|
// TODO handle push errors (should only happen during concurrent pull+push)
|
397
523
|
const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either);
|
398
524
|
if (pushResult._tag === 'Left') {
|
399
|
-
|
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() });
|
400
529
|
// wait for interrupt caused by background pulling which will then restart pushing
|
401
530
|
return yield* Effect.never;
|
402
531
|
}
|
403
|
-
const { metadata } = pushResult.right;
|
404
|
-
// TODO try to do this in a single query
|
405
|
-
for (let i = 0; i < queueItems.length; i++) {
|
406
|
-
const mutationEventEncoded = queueItems[i];
|
407
|
-
yield* execSql(dbLog, ...updateRows({
|
408
|
-
tableName: MUTATION_LOG_META_TABLE,
|
409
|
-
columns: mutationLogMetaTable.sqliteDef.columns,
|
410
|
-
where: { idGlobal: mutationEventEncoded.id.global, idLocal: mutationEventEncoded.id.local },
|
411
|
-
updateValues: { syncMetadataJson: metadata[i] },
|
412
|
-
}));
|
413
|
-
}
|
414
532
|
}
|
415
|
-
}).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:
|
533
|
+
}).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:LeaderSyncProcessor:backend-pushing'));
|
416
534
|
const trimChangesetRows = (db, newHead) => {
|
417
535
|
// Since we're using the session changeset rows to query for the current head,
|
418
536
|
// we're keeping at least one row for the current head, and thus are using `<` instead of `<=`
|
419
|
-
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}`);
|
420
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
|
+
});
|
421
595
|
//# sourceMappingURL=LeaderSyncProcessor.js.map
|