@livestore/common 0.4.0-dev.2 → 0.4.0-dev.21
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/ClientSessionLeaderThreadProxy.d.ts +20 -12
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/ClientSessionLeaderThreadProxy.js.map +1 -1
- package/dist/adapter-types.d.ts +14 -6
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/debug-info.d.ts.map +1 -1
- package/dist/debug-info.js +33 -6
- package/dist/debug-info.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +28 -23
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.js +2 -2
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +7 -14
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +1 -6
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +38 -29
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +9 -8
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/devtools/devtools-sessioninfo.d.ts +14 -2
- package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
- package/dist/devtools/devtools-sessioninfo.js +7 -4
- package/dist/devtools/devtools-sessioninfo.js.map +1 -1
- package/dist/devtools/mod.d.ts +13 -2
- package/dist/devtools/mod.d.ts.map +1 -1
- package/dist/devtools/mod.js +10 -3
- package/dist/devtools/mod.js.map +1 -1
- package/dist/errors.d.ts +52 -10
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +25 -6
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +41 -4
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +158 -75
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +21 -22
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +77 -20
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +2 -2
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +56 -45
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +6 -6
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +79 -27
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.d.ts +2 -0
- package/dist/leader-thread/make-leader-thread-layer.test.d.ts.map +1 -0
- package/dist/leader-thread/make-leader-thread-layer.test.js +32 -0
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -0
- package/dist/leader-thread/materialize-event.d.ts +3 -3
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +25 -11
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/mod.d.ts +1 -0
- package/dist/leader-thread/mod.d.ts.map +1 -1
- package/dist/leader-thread/mod.js +1 -0
- package/dist/leader-thread/mod.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts +2 -3
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +5 -5
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -2
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -2
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/stream-events.d.ts +56 -0
- package/dist/leader-thread/stream-events.d.ts.map +1 -0
- package/dist/leader-thread/stream-events.js +166 -0
- package/dist/leader-thread/stream-events.js.map +1 -0
- package/dist/leader-thread/types.d.ts +98 -20
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js +13 -0
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/logging.d.ts +40 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +33 -0
- package/dist/logging.js.map +1 -0
- package/dist/make-client-session.d.ts +5 -3
- package/dist/make-client-session.d.ts.map +1 -1
- package/dist/make-client-session.js +5 -2
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.d.ts +6 -6
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +20 -4
- package/dist/materializer-helper.js.map +1 -1
- package/dist/otel.d.ts +2 -1
- 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 +2 -2
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +29 -20
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef/define.d.ts +147 -0
- package/dist/schema/EventDef/define.d.ts.map +1 -0
- package/dist/schema/EventDef/define.js +139 -0
- package/dist/schema/EventDef/define.js.map +1 -0
- package/dist/schema/EventDef/event-def.d.ts +106 -0
- package/dist/schema/EventDef/event-def.d.ts.map +1 -0
- package/dist/schema/EventDef/event-def.js +2 -0
- package/dist/schema/EventDef/event-def.js.map +1 -0
- package/dist/schema/EventDef/facts.d.ts +118 -0
- package/dist/schema/EventDef/facts.d.ts.map +1 -0
- package/dist/schema/EventDef/facts.js +53 -0
- package/dist/schema/EventDef/facts.js.map +1 -0
- package/dist/schema/EventDef/materializer.d.ts +155 -0
- package/dist/schema/EventDef/materializer.d.ts.map +1 -0
- package/dist/schema/EventDef/materializer.js +83 -0
- package/dist/schema/EventDef/materializer.js.map +1 -0
- package/dist/schema/EventDef/mod.d.ts +5 -0
- package/dist/schema/EventDef/mod.d.ts.map +1 -0
- package/dist/schema/EventDef/mod.js +5 -0
- package/dist/schema/EventDef/mod.js.map +1 -0
- package/dist/schema/EventSequenceNumber/client.d.ts +136 -0
- package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -0
- package/dist/schema/EventSequenceNumber/client.js +193 -0
- package/dist/schema/EventSequenceNumber/client.js.map +1 -0
- package/dist/schema/EventSequenceNumber/global.d.ts +15 -0
- package/dist/schema/EventSequenceNumber/global.d.ts.map +1 -0
- package/dist/schema/EventSequenceNumber/global.js +14 -0
- package/dist/schema/EventSequenceNumber/global.js.map +1 -0
- package/dist/schema/EventSequenceNumber/mod.d.ts +37 -0
- package/dist/schema/EventSequenceNumber/mod.d.ts.map +1 -0
- package/dist/schema/EventSequenceNumber/mod.js +37 -0
- package/dist/schema/EventSequenceNumber/mod.js.map +1 -0
- package/dist/schema/EventSequenceNumber.test.js +43 -43
- package/dist/schema/EventSequenceNumber.test.js.map +1 -1
- package/dist/schema/{LiveStoreEvent.d.ts → LiveStoreEvent/client.d.ts} +89 -106
- package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -0
- package/dist/schema/{LiveStoreEvent.js → LiveStoreEvent/client.js} +74 -58
- package/dist/schema/LiveStoreEvent/client.js.map +1 -0
- package/dist/schema/LiveStoreEvent/for-event-def.d.ts +52 -0
- package/dist/schema/LiveStoreEvent/for-event-def.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent/for-event-def.js +2 -0
- package/dist/schema/LiveStoreEvent/for-event-def.js.map +1 -0
- package/dist/schema/LiveStoreEvent/global.d.ts +36 -0
- package/dist/schema/LiveStoreEvent/global.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent/global.js +31 -0
- package/dist/schema/LiveStoreEvent/global.js.map +1 -0
- package/dist/schema/LiveStoreEvent/input.d.ts +46 -0
- package/dist/schema/LiveStoreEvent/input.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent/input.js +26 -0
- package/dist/schema/LiveStoreEvent/input.js.map +1 -0
- package/dist/schema/LiveStoreEvent/mod.d.ts +5 -0
- package/dist/schema/LiveStoreEvent/mod.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent/mod.js +5 -0
- package/dist/schema/LiveStoreEvent/mod.js.map +1 -0
- package/dist/schema/events.d.ts +1 -1
- package/dist/schema/events.d.ts.map +1 -1
- package/dist/schema/events.js +1 -1
- package/dist/schema/events.js.map +1 -1
- package/dist/schema/mod.d.ts +6 -4
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +5 -4
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts +16 -1
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +27 -2
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts +36 -6
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +97 -6
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +16 -0
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.js +14 -6
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +69 -22
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +48 -10
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-spec.js +30 -12
- package/dist/schema/state/sqlite/column-spec.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js +23 -14
- package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +23 -6
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +14 -8
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +5 -3
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +2 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts +3 -3
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +3 -3
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +19 -11
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/astToSql.js +22 -15
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +6 -3
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +252 -88
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.d.ts +2 -2
- package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js +22 -12
- package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.test.d.ts +2 -0
- package/dist/schema/state/sqlite/schema-helpers.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/schema-helpers.test.js +36 -0
- package/dist/schema/state/sqlite/schema-helpers.test.js.map +1 -0
- package/dist/schema/state/sqlite/{system-tables.d.ts → system-tables/eventlog-tables.d.ts} +63 -456
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts.map +1 -0
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.js +54 -0
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.js.map +1 -0
- package/dist/schema/state/sqlite/system-tables/mod.d.ts +3 -0
- package/dist/schema/state/sqlite/system-tables/mod.d.ts.map +1 -0
- package/dist/schema/state/sqlite/system-tables/mod.js +3 -0
- package/dist/schema/state/sqlite/system-tables/mod.js.map +1 -0
- package/dist/schema/state/sqlite/system-tables/state-tables.d.ts +456 -0
- package/dist/schema/state/sqlite/system-tables/state-tables.d.ts.map +1 -0
- package/dist/schema/state/sqlite/system-tables/state-tables.js +55 -0
- package/dist/schema/state/sqlite/system-tables/state-tables.js.map +1 -0
- package/dist/schema/state/sqlite/table-def.d.ts +4 -4
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +2 -2
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +80 -0
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts +47 -0
- package/dist/schema/unknown-events.d.ts.map +1 -0
- package/dist/schema/unknown-events.js +69 -0
- package/dist/schema/unknown-events.js.map +1 -0
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.d.ts +2 -0
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.d.ts.map +1 -0
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +73 -0
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -0
- package/dist/schema-management/migrations.d.ts +32 -2
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +37 -5
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/schema-management/validate-schema.d.ts +3 -3
- package/dist/schema-management/validate-schema.d.ts.map +1 -1
- package/dist/schema-management/validate-schema.js +2 -2
- package/dist/schema-management/validate-schema.js.map +1 -1
- package/dist/sql-queries/sql-queries.d.ts.map +1 -1
- package/dist/sql-queries/sql-queries.js +11 -1
- package/dist/sql-queries/sql-queries.js.map +1 -1
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.js +2 -1
- package/dist/sql-queries/sql-query-builder.js.map +1 -1
- package/dist/sqlite-types.d.ts +3 -3
- package/dist/sqlite-types.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +11 -13
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +45 -42
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +66 -0
- package/dist/sync/errors.d.ts.map +1 -0
- package/dist/sync/errors.js +36 -0
- package/dist/sync/errors.js.map +1 -0
- package/dist/sync/index.d.ts +3 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +3 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +23 -0
- package/dist/sync/mock-sync-backend.d.ts.map +1 -0
- package/dist/sync/mock-sync-backend.js +114 -0
- package/dist/sync/mock-sync-backend.js.map +1 -0
- package/dist/sync/next/compact-events.d.ts.map +1 -1
- package/dist/sync/next/compact-events.js +6 -7
- package/dist/sync/next/compact-events.js.map +1 -1
- package/dist/sync/next/facts.d.ts +5 -5
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +1 -2
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +54 -15
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +198 -9
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +10 -8
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts +5 -5
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +5 -5
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/event-fixtures.d.ts +2 -2
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/event-fixtures.js +9 -9
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/sync-backend-kv.d.ts +7 -0
- package/dist/sync/sync-backend-kv.d.ts.map +1 -0
- package/dist/sync/sync-backend-kv.js +18 -0
- package/dist/sync/sync-backend-kv.js.map +1 -0
- package/dist/sync/sync-backend.d.ts +105 -0
- package/dist/sync/sync-backend.d.ts.map +1 -0
- package/dist/sync/sync-backend.js +61 -0
- package/dist/sync/sync-backend.js.map +1 -0
- package/dist/sync/sync.d.ts +9 -86
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js +2 -27
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +57 -44
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +50 -45
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +83 -46
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/transport-chunking.d.ts +36 -0
- package/dist/sync/transport-chunking.d.ts.map +1 -0
- package/dist/sync/transport-chunking.js +56 -0
- package/dist/sync/transport-chunking.js.map +1 -0
- 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 +6 -6
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/testing/event-factory.d.ts +68 -0
- package/dist/testing/event-factory.d.ts.map +1 -0
- package/dist/testing/event-factory.js +78 -0
- package/dist/testing/event-factory.js.map +1 -0
- package/dist/testing/mod.d.ts +2 -0
- package/dist/testing/mod.d.ts.map +1 -0
- package/dist/testing/mod.js +2 -0
- package/dist/testing/mod.js.map +1 -0
- package/dist/version.d.ts +16 -6
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +16 -6
- package/dist/version.js.map +1 -1
- package/package.json +7 -8
- package/src/ClientSessionLeaderThreadProxy.ts +20 -12
- package/src/adapter-types.ts +18 -6
- package/src/debug-info.ts +37 -6
- package/src/devtools/devtools-messages-client-session.ts +2 -2
- package/src/devtools/devtools-messages-common.ts +1 -8
- package/src/devtools/devtools-messages-leader.ts +9 -8
- package/src/devtools/devtools-sessioninfo.ts +8 -5
- package/src/devtools/mod.ts +11 -2
- package/src/errors.ts +38 -11
- package/src/index.ts +2 -1
- package/src/leader-thread/LeaderSyncProcessor.ts +277 -105
- package/src/leader-thread/eventlog.ts +113 -38
- package/src/leader-thread/leader-worker-devtools.ts +86 -55
- package/src/leader-thread/make-leader-thread-layer.test.ts +44 -0
- package/src/leader-thread/make-leader-thread-layer.ts +156 -37
- package/src/leader-thread/materialize-event.ts +37 -12
- package/src/leader-thread/mod.ts +1 -0
- package/src/leader-thread/recreate-db.ts +15 -7
- package/src/leader-thread/shutdown-channel.ts +16 -2
- package/src/leader-thread/stream-events.ts +201 -0
- package/src/leader-thread/types.ts +70 -20
- package/src/logging.ts +62 -0
- package/src/make-client-session.ts +9 -3
- package/src/materializer-helper.ts +27 -10
- package/src/otel.ts +10 -0
- package/src/rematerialize-from-eventlog.ts +37 -27
- package/src/schema/EventDef/define.ts +201 -0
- package/src/schema/EventDef/event-def.ts +120 -0
- package/src/schema/EventDef/facts.ts +135 -0
- package/src/schema/EventDef/materializer.ts +172 -0
- package/src/schema/EventDef/mod.ts +4 -0
- package/src/schema/EventSequenceNumber/client.ts +257 -0
- package/src/schema/EventSequenceNumber/global.ts +19 -0
- package/src/schema/EventSequenceNumber/mod.ts +37 -0
- package/src/schema/EventSequenceNumber.test.ts +70 -52
- package/src/schema/LiveStoreEvent/client.ts +221 -0
- package/src/schema/LiveStoreEvent/for-event-def.ts +60 -0
- package/src/schema/LiveStoreEvent/global.ts +45 -0
- package/src/schema/LiveStoreEvent/input.ts +63 -0
- package/src/schema/LiveStoreEvent/mod.ts +4 -0
- package/src/schema/events.ts +1 -1
- package/src/schema/mod.ts +6 -4
- package/src/schema/schema.ts +39 -3
- package/src/schema/state/sqlite/client-document-def.test.ts +19 -2
- package/src/schema/state/sqlite/client-document-def.ts +127 -25
- package/src/schema/state/sqlite/column-annotations.test.ts +1 -1
- package/src/schema/state/sqlite/column-annotations.ts +16 -6
- package/src/schema/state/sqlite/column-def.test.ts +62 -10
- package/src/schema/state/sqlite/column-def.ts +88 -21
- package/src/schema/state/sqlite/column-spec.test.ts +29 -16
- package/src/schema/state/sqlite/column-spec.ts +36 -11
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +26 -6
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +29 -12
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +12 -17
- package/src/schema/state/sqlite/mod.ts +4 -3
- package/src/schema/state/sqlite/query-builder/api.ts +25 -11
- package/src/schema/state/sqlite/query-builder/astToSql.ts +23 -14
- package/src/schema/state/sqlite/query-builder/impl.test.ts +305 -92
- package/src/schema/state/sqlite/query-builder/impl.ts +8 -3
- package/src/schema/state/sqlite/schema-helpers.test.ts +44 -0
- package/src/schema/state/sqlite/schema-helpers.ts +28 -20
- package/src/schema/state/sqlite/system-tables/eventlog-tables.ts +64 -0
- package/src/schema/state/sqlite/system-tables/mod.ts +2 -0
- package/src/schema/state/sqlite/system-tables/state-tables.ts +69 -0
- package/src/schema/state/sqlite/table-def.test.ts +101 -0
- package/src/schema/state/sqlite/table-def.ts +8 -6
- package/src/schema/unknown-events.ts +131 -0
- package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +86 -0
- package/src/schema-management/migrations.ts +41 -8
- package/src/schema-management/validate-schema.ts +3 -3
- package/src/sql-queries/sql-queries.ts +9 -1
- package/src/sql-queries/sql-query-builder.ts +2 -1
- package/src/sqlite-types.ts +3 -3
- package/src/sync/ClientSessionSyncProcessor.ts +69 -62
- package/src/sync/errors.ts +38 -0
- package/src/sync/index.ts +3 -0
- package/src/sync/mock-sync-backend.ts +184 -0
- package/src/sync/next/compact-events.ts +6 -7
- package/src/sync/next/facts.ts +7 -9
- package/src/sync/next/history-dag-common.ts +277 -26
- package/src/sync/next/history-dag.ts +16 -10
- package/src/sync/next/rebase-events.ts +11 -11
- package/src/sync/next/test/event-fixtures.ts +11 -11
- package/src/sync/sync-backend-kv.ts +22 -0
- package/src/sync/sync-backend.ts +185 -0
- package/src/sync/sync.ts +9 -91
- package/src/sync/syncstate.test.ts +96 -52
- package/src/sync/syncstate.ts +69 -58
- package/src/sync/transport-chunking.ts +90 -0
- package/src/sync/validate-push-payload.ts +8 -9
- package/src/testing/event-factory.ts +131 -0
- package/src/testing/mod.ts +1 -0
- package/src/version.ts +16 -6
- package/dist/schema/EventDef.d.ts +0 -123
- package/dist/schema/EventDef.d.ts.map +0 -1
- package/dist/schema/EventDef.js +0 -46
- package/dist/schema/EventDef.js.map +0 -1
- package/dist/schema/EventSequenceNumber.d.ts +0 -80
- package/dist/schema/EventSequenceNumber.d.ts.map +0 -1
- package/dist/schema/EventSequenceNumber.js +0 -139
- package/dist/schema/EventSequenceNumber.js.map +0 -1
- package/dist/schema/LiveStoreEvent.d.ts.map +0 -1
- package/dist/schema/LiveStoreEvent.js.map +0 -1
- package/dist/schema/state/sqlite/system-tables.d.ts.map +0 -1
- package/dist/schema/state/sqlite/system-tables.js +0 -79
- package/dist/schema/state/sqlite/system-tables.js.map +0 -1
- package/dist/schema-management/migrations.test.d.ts +0 -2
- package/dist/schema-management/migrations.test.d.ts.map +0 -1
- package/dist/schema-management/migrations.test.js +0 -52
- package/dist/schema-management/migrations.test.js.map +0 -1
- package/dist/sync/next/graphology.d.ts +0 -8
- package/dist/sync/next/graphology.d.ts.map +0 -1
- package/dist/sync/next/graphology.js +0 -30
- package/dist/sync/next/graphology.js.map +0 -1
- package/dist/sync/next/graphology_.d.ts +0 -3
- package/dist/sync/next/graphology_.d.ts.map +0 -1
- package/dist/sync/next/graphology_.js +0 -3
- package/dist/sync/next/graphology_.js.map +0 -1
- package/src/schema/EventDef.ts +0 -219
- package/src/schema/EventSequenceNumber.ts +0 -199
- package/src/schema/LiveStoreEvent.ts +0 -287
- package/src/schema/state/sqlite/system-tables.ts +0 -104
- package/src/sync/next/ambient.d.ts +0 -3
- package/src/sync/next/graphology.ts +0 -41
- package/src/sync/next/graphology_.ts +0 -2
|
@@ -2,27 +2,34 @@ import { casesHandled, isNotUndefined, LS_DEV, shouldNeverHappen, TRACE_VERBOSE
|
|
|
2
2
|
import type { HttpClient, Runtime, Scope, Tracer } from '@livestore/utils/effect'
|
|
3
3
|
import {
|
|
4
4
|
BucketQueue,
|
|
5
|
+
Cause,
|
|
5
6
|
Deferred,
|
|
7
|
+
Duration,
|
|
6
8
|
Effect,
|
|
7
9
|
Exit,
|
|
8
10
|
FiberHandle,
|
|
11
|
+
Layer,
|
|
9
12
|
Option,
|
|
10
13
|
OtelTracer,
|
|
11
|
-
pipe,
|
|
12
14
|
Queue,
|
|
13
15
|
ReadonlyArray,
|
|
16
|
+
Schedule,
|
|
14
17
|
Stream,
|
|
15
18
|
Subscribable,
|
|
16
19
|
SubscriptionRef,
|
|
17
20
|
} from '@livestore/utils/effect'
|
|
18
21
|
import type * as otel from '@opentelemetry/api'
|
|
19
|
-
|
|
20
|
-
import type { SqliteDb } from '../adapter-types.ts'
|
|
21
|
-
import { SyncError, UnexpectedError } from '../adapter-types.ts'
|
|
22
|
+
import { type IntentionalShutdownCause, type MaterializeError, type SqliteDb, UnknownError } from '../adapter-types.ts'
|
|
22
23
|
import { makeMaterializerHash } from '../materializer-helper.ts'
|
|
23
24
|
import type { LiveStoreSchema } from '../schema/mod.ts'
|
|
24
|
-
import { EventSequenceNumber,
|
|
25
|
-
import {
|
|
25
|
+
import { EventSequenceNumber, LiveStoreEvent, resolveEventDef, SystemTables } from '../schema/mod.ts'
|
|
26
|
+
import {
|
|
27
|
+
type InvalidPullError,
|
|
28
|
+
type InvalidPushError,
|
|
29
|
+
type IsOfflineError,
|
|
30
|
+
LeaderAheadError,
|
|
31
|
+
type SyncBackend,
|
|
32
|
+
} from '../sync/sync.ts'
|
|
26
33
|
import * as SyncState from '../sync/syncstate.ts'
|
|
27
34
|
import { sql } from '../util.ts'
|
|
28
35
|
import * as Eventlog from './eventlog.ts'
|
|
@@ -31,7 +38,7 @@ import type { InitialBlockingSyncContext, LeaderSyncProcessor } from './types.ts
|
|
|
31
38
|
import { LeaderThreadCtx } from './types.ts'
|
|
32
39
|
|
|
33
40
|
type LocalPushQueueItem = [
|
|
34
|
-
event: LiveStoreEvent.EncodedWithMeta,
|
|
41
|
+
event: LiveStoreEvent.Client.EncodedWithMeta,
|
|
35
42
|
deferred: Deferred.Deferred<void, LeaderAheadError> | undefined,
|
|
36
43
|
]
|
|
37
44
|
|
|
@@ -71,6 +78,7 @@ export const makeLeaderSyncProcessor = ({
|
|
|
71
78
|
initialBlockingSyncContext,
|
|
72
79
|
initialSyncState,
|
|
73
80
|
onError,
|
|
81
|
+
livePull,
|
|
74
82
|
params,
|
|
75
83
|
testing,
|
|
76
84
|
}: {
|
|
@@ -82,31 +90,67 @@ export const makeLeaderSyncProcessor = ({
|
|
|
82
90
|
onError: 'shutdown' | 'ignore'
|
|
83
91
|
params: {
|
|
84
92
|
/**
|
|
93
|
+
* Maximum number of local events to process per batch cycle.
|
|
94
|
+
*
|
|
95
|
+
* This controls how many events from client sessions are applied to the local state
|
|
96
|
+
* in a single iteration before yielding to allow potential backend pulls.
|
|
97
|
+
*
|
|
98
|
+
* **Trade-offs:**
|
|
99
|
+
* - **Lower values (1-5):** More responsive to remote updates since pull processing can
|
|
100
|
+
* interleave more frequently. Better for high-conflict scenarios where rebases are common.
|
|
101
|
+
* Slightly higher per-event overhead due to more frequent transaction commits.
|
|
102
|
+
*
|
|
103
|
+
* - **Higher values (10-50+):** Better throughput for bulk local writes as more events are
|
|
104
|
+
* batched into a single transaction. However, may delay remote update processing and
|
|
105
|
+
* increase rebase complexity if many local events queue up during a slow pull.
|
|
106
|
+
*
|
|
107
|
+
* - **Very high values (100+):** Risk of starvation for pull processing if local pushes
|
|
108
|
+
* arrive continuously. May cause larger rollbacks during rebases. Not recommended
|
|
109
|
+
* unless you have a write-heavy workload with minimal remote synchronization.
|
|
110
|
+
*
|
|
85
111
|
* @default 10
|
|
86
112
|
*/
|
|
87
113
|
localPushBatchSize?: number
|
|
88
114
|
/**
|
|
115
|
+
* Maximum number of events to push to the sync backend per batch.
|
|
116
|
+
*
|
|
117
|
+
* This controls how many events are sent in a single push request to the remote server.
|
|
118
|
+
*
|
|
119
|
+
* **Trade-offs:**
|
|
120
|
+
* - **Lower values (1-10):** Lower latency for each push operation. Faster feedback on
|
|
121
|
+
* push success/failure. Slightly higher network overhead due to more requests.
|
|
122
|
+
*
|
|
123
|
+
* - **Higher values (50-100):** Better network efficiency by amortizing request overhead.
|
|
124
|
+
* Preferred for high-throughput scenarios. May increase latency to first confirmation.
|
|
125
|
+
*
|
|
126
|
+
* - **Very high values (200+):** Risk of hitting server request size limits or timeouts.
|
|
127
|
+
* A single failed request loses the entire batch (will be retried). May cause memory
|
|
128
|
+
* pressure if events accumulate faster than they can be pushed.
|
|
129
|
+
*
|
|
89
130
|
* @default 50
|
|
90
131
|
*/
|
|
91
132
|
backendPushBatchSize?: number
|
|
92
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Whether the sync backend should reactively pull new events from the sync backend
|
|
136
|
+
* When `false`, the sync processor will only do an initial pull
|
|
137
|
+
*/
|
|
138
|
+
livePull: boolean
|
|
93
139
|
testing: {
|
|
94
140
|
delays?: {
|
|
95
141
|
localPushProcessing?: Effect.Effect<void>
|
|
96
142
|
}
|
|
97
143
|
}
|
|
98
|
-
}): Effect.Effect<LeaderSyncProcessor,
|
|
144
|
+
}): Effect.Effect<LeaderSyncProcessor, UnknownError, Scope.Scope> =>
|
|
99
145
|
Effect.gen(function* () {
|
|
100
|
-
const syncBackendPushQueue = yield* BucketQueue.make<LiveStoreEvent.EncodedWithMeta>()
|
|
101
|
-
const localPushBatchSize = params.localPushBatchSize ??
|
|
102
|
-
const backendPushBatchSize = params.backendPushBatchSize ??
|
|
146
|
+
const syncBackendPushQueue = yield* BucketQueue.make<LiveStoreEvent.Client.EncodedWithMeta>()
|
|
147
|
+
const localPushBatchSize = params.localPushBatchSize ?? 10
|
|
148
|
+
const backendPushBatchSize = params.backendPushBatchSize ?? 50
|
|
103
149
|
|
|
104
150
|
const syncStateSref = yield* SubscriptionRef.make<SyncState.SyncState | undefined>(undefined)
|
|
105
151
|
|
|
106
|
-
const isClientEvent = (eventEncoded: LiveStoreEvent.EncodedWithMeta) =>
|
|
107
|
-
|
|
108
|
-
return eventDef.options.clientOnly
|
|
109
|
-
}
|
|
152
|
+
const isClientEvent = (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) =>
|
|
153
|
+
schema.eventsDefsMap.get(eventEncoded.name)?.options.clientOnly ?? false
|
|
110
154
|
|
|
111
155
|
const connectedClientSessionPullQueues = yield* makePullQueueSet
|
|
112
156
|
|
|
@@ -135,9 +179,9 @@ export const makeLeaderSyncProcessor = ({
|
|
|
135
179
|
*
|
|
136
180
|
* Thus the purpose of the pushHeadRef is the guard the integrity of the local push queue
|
|
137
181
|
*/
|
|
138
|
-
const pushHeadRef = { current: EventSequenceNumber.ROOT }
|
|
139
|
-
const advancePushHead = (eventNum: EventSequenceNumber.
|
|
140
|
-
pushHeadRef.current = EventSequenceNumber.max(pushHeadRef.current, eventNum)
|
|
182
|
+
const pushHeadRef = { current: EventSequenceNumber.Client.ROOT }
|
|
183
|
+
const advancePushHead = (eventNum: EventSequenceNumber.Client.Composite) => {
|
|
184
|
+
pushHeadRef.current = EventSequenceNumber.Client.max(pushHeadRef.current, eventNum)
|
|
141
185
|
}
|
|
142
186
|
|
|
143
187
|
// NOTE: New events are only pushed to sync backend after successful local push processing
|
|
@@ -180,14 +224,32 @@ export const makeLeaderSyncProcessor = ({
|
|
|
180
224
|
const syncState = yield* syncStateSref
|
|
181
225
|
if (syncState === undefined) return shouldNeverHappen('Not initialized')
|
|
182
226
|
|
|
183
|
-
const
|
|
227
|
+
const resolution = yield* resolveEventDef(schema, {
|
|
228
|
+
operation: '@livestore/common:LeaderSyncProcessor:pushPartial',
|
|
229
|
+
event: {
|
|
230
|
+
name,
|
|
231
|
+
args,
|
|
232
|
+
clientId,
|
|
233
|
+
sessionId,
|
|
234
|
+
seqNum: syncState.localHead,
|
|
235
|
+
},
|
|
236
|
+
}).pipe(UnknownError.mapToUnknownError)
|
|
237
|
+
|
|
238
|
+
if (resolution._tag === 'unknown') {
|
|
239
|
+
// Ignore partial pushes for unrecognised events – they are still
|
|
240
|
+
// persisted server-side once a schema update ships.
|
|
241
|
+
return
|
|
242
|
+
}
|
|
184
243
|
|
|
185
|
-
const eventEncoded = new LiveStoreEvent.EncodedWithMeta({
|
|
244
|
+
const eventEncoded = new LiveStoreEvent.Client.EncodedWithMeta({
|
|
186
245
|
name,
|
|
187
246
|
args,
|
|
188
247
|
clientId,
|
|
189
248
|
sessionId,
|
|
190
|
-
...EventSequenceNumber.nextPair({
|
|
249
|
+
...EventSequenceNumber.Client.nextPair({
|
|
250
|
+
seqNum: syncState.localHead,
|
|
251
|
+
isClient: resolution.eventDef.options.clientOnly,
|
|
252
|
+
}),
|
|
191
253
|
})
|
|
192
254
|
|
|
193
255
|
yield* push([eventEncoded])
|
|
@@ -213,10 +275,10 @@ export const makeLeaderSyncProcessor = ({
|
|
|
213
275
|
// Rehydrate sync queue
|
|
214
276
|
if (initialSyncState.pending.length > 0) {
|
|
215
277
|
const globalPendingEvents = initialSyncState.pending
|
|
216
|
-
// Don't sync
|
|
278
|
+
// Don't sync client-local events
|
|
217
279
|
.filter((eventEncoded) => {
|
|
218
|
-
const
|
|
219
|
-
return eventDef.options.clientOnly === false
|
|
280
|
+
const eventDef = schema.eventsDefsMap.get(eventEncoded.name)
|
|
281
|
+
return eventDef === undefined ? true : eventDef.options.clientOnly === false
|
|
220
282
|
})
|
|
221
283
|
|
|
222
284
|
if (globalPendingEvents.length > 0) {
|
|
@@ -224,12 +286,31 @@ export const makeLeaderSyncProcessor = ({
|
|
|
224
286
|
}
|
|
225
287
|
}
|
|
226
288
|
|
|
227
|
-
const
|
|
289
|
+
const maybeShutdownOnError = (
|
|
290
|
+
cause: Cause.Cause<
|
|
291
|
+
| UnknownError
|
|
292
|
+
| IntentionalShutdownCause
|
|
293
|
+
| IsOfflineError
|
|
294
|
+
| InvalidPushError
|
|
295
|
+
| InvalidPullError
|
|
296
|
+
| MaterializeError
|
|
297
|
+
>,
|
|
298
|
+
) =>
|
|
228
299
|
Effect.gen(function* () {
|
|
229
|
-
if (onError === '
|
|
230
|
-
|
|
231
|
-
|
|
300
|
+
if (onError === 'ignore') {
|
|
301
|
+
if (LS_DEV) {
|
|
302
|
+
yield* Effect.logDebug(
|
|
303
|
+
`Ignoring sync error (${cause._tag === 'Fail' ? cause.error._tag : cause._tag})`,
|
|
304
|
+
Cause.pretty(cause),
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
return
|
|
232
308
|
}
|
|
309
|
+
|
|
310
|
+
const errorToSend = Cause.isFailType(cause) ? cause.error : UnknownError.make({ cause })
|
|
311
|
+
yield* shutdownChannel.send(errorToSend).pipe(Effect.orDie)
|
|
312
|
+
|
|
313
|
+
return yield* Effect.die(cause)
|
|
233
314
|
})
|
|
234
315
|
|
|
235
316
|
yield* backgroundApplyLocalPushes({
|
|
@@ -246,20 +327,19 @@ export const makeLeaderSyncProcessor = ({
|
|
|
246
327
|
testing: {
|
|
247
328
|
delay: testing?.delays?.localPushProcessing,
|
|
248
329
|
},
|
|
249
|
-
}).pipe(Effect.
|
|
330
|
+
}).pipe(Effect.catchAllCause(maybeShutdownOnError), Effect.forkScoped)
|
|
250
331
|
|
|
251
|
-
const backendPushingFiberHandle = yield* FiberHandle.make()
|
|
332
|
+
const backendPushingFiberHandle = yield* FiberHandle.make<void, never>()
|
|
252
333
|
const backendPushingEffect = backgroundBackendPushing({
|
|
253
334
|
syncBackendPushQueue,
|
|
254
335
|
otelSpan,
|
|
255
336
|
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
|
256
337
|
backendPushBatchSize,
|
|
257
|
-
}).pipe(Effect.
|
|
338
|
+
}).pipe(Effect.catchAllCause(maybeShutdownOnError))
|
|
258
339
|
|
|
259
340
|
yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect)
|
|
260
341
|
|
|
261
342
|
yield* backgroundBackendPulling({
|
|
262
|
-
initialBackendHead: initialSyncState.upstreamHead.global,
|
|
263
343
|
isClientEvent,
|
|
264
344
|
restartBackendPushing: (filteredRebasedPending) =>
|
|
265
345
|
Effect.gen(function* () {
|
|
@@ -276,13 +356,24 @@ export const makeLeaderSyncProcessor = ({
|
|
|
276
356
|
syncStateSref,
|
|
277
357
|
localPushesLatch,
|
|
278
358
|
pullLatch,
|
|
359
|
+
livePull,
|
|
279
360
|
dbState,
|
|
280
361
|
otelSpan,
|
|
281
362
|
initialBlockingSyncContext,
|
|
282
363
|
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
|
283
364
|
connectedClientSessionPullQueues,
|
|
284
365
|
advancePushHead,
|
|
285
|
-
}).pipe(
|
|
366
|
+
}).pipe(
|
|
367
|
+
Effect.retry({
|
|
368
|
+
// We want to retry pulling if we've lost connection to the sync backend
|
|
369
|
+
while: (cause) => cause._tag === 'IsOfflineError',
|
|
370
|
+
}),
|
|
371
|
+
Effect.catchAllCause(maybeShutdownOnError),
|
|
372
|
+
// Needed to avoid `Fiber terminated with an unhandled error` logs which seem to happen because of the `Effect.retry` above.
|
|
373
|
+
// This might be a bug in Effect. Only seems to happen in the browser.
|
|
374
|
+
Effect.provide(Layer.setUnhandledErrorLogLevel(Option.none())),
|
|
375
|
+
Effect.forkScoped,
|
|
376
|
+
)
|
|
286
377
|
|
|
287
378
|
return { initialLeaderHead: initialSyncState.localHead }
|
|
288
379
|
}).pipe(Effect.withSpanScoped('@livestore/common:LeaderSyncProcessor:boot'))
|
|
@@ -348,9 +439,9 @@ const backgroundApplyLocalPushes = ({
|
|
|
348
439
|
localPushesLatch: Effect.Latch
|
|
349
440
|
localPushesQueue: BucketQueue.BucketQueue<LocalPushQueueItem>
|
|
350
441
|
syncStateSref: SubscriptionRef.SubscriptionRef<SyncState.SyncState | undefined>
|
|
351
|
-
syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.EncodedWithMeta>
|
|
442
|
+
syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.Client.EncodedWithMeta>
|
|
352
443
|
schema: LiveStoreSchema
|
|
353
|
-
isClientEvent: (eventEncoded: LiveStoreEvent.EncodedWithMeta) => boolean
|
|
444
|
+
isClientEvent: (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) => boolean
|
|
354
445
|
otelSpan: otel.Span | undefined
|
|
355
446
|
connectedClientSessionPullQueues: PullQueueSet
|
|
356
447
|
localPushBatchSize: number
|
|
@@ -379,33 +470,58 @@ const backgroundApplyLocalPushes = ({
|
|
|
379
470
|
|
|
380
471
|
// Since the rebase generation might have changed since enqueuing, we need to filter out items with older generation
|
|
381
472
|
// It's important that we filter after we got localPushesLatch, otherwise we might filter with the old generation
|
|
382
|
-
const [
|
|
473
|
+
const [droppedItems, filteredItems] = ReadonlyArray.partition(
|
|
383
474
|
batchItems,
|
|
384
|
-
|
|
385
|
-
ReadonlyArray.unzip,
|
|
475
|
+
([eventEncoded]) => eventEncoded.seqNum.rebaseGeneration >= currentRebaseGeneration,
|
|
386
476
|
)
|
|
387
477
|
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
|
|
478
|
+
if (droppedItems.length > 0) {
|
|
479
|
+
otelSpan?.addEvent(`push:drop-old-generation`, {
|
|
480
|
+
droppedCount: droppedItems.length,
|
|
481
|
+
currentRebaseGeneration,
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Dropped pushes may still have a deferred awaiting completion.
|
|
486
|
+
* Fail it so the caller learns the leader advanced and resubmits with the updated generation.
|
|
487
|
+
*/
|
|
488
|
+
yield* Effect.forEach(
|
|
489
|
+
droppedItems.filter(
|
|
490
|
+
(item): item is [LiveStoreEvent.Client.EncodedWithMeta, Deferred.Deferred<void, LeaderAheadError>] =>
|
|
491
|
+
item[1] !== undefined,
|
|
492
|
+
),
|
|
493
|
+
([eventEncoded, deferred]) =>
|
|
494
|
+
Deferred.fail(
|
|
495
|
+
deferred,
|
|
496
|
+
LeaderAheadError.make({
|
|
497
|
+
minimumExpectedNum: syncState.localHead,
|
|
498
|
+
providedNum: eventEncoded.seqNum,
|
|
499
|
+
}),
|
|
500
|
+
),
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (filteredItems.length === 0) {
|
|
391
505
|
yield* pullLatch.open
|
|
392
506
|
continue
|
|
393
507
|
}
|
|
394
508
|
|
|
509
|
+
const [newEvents, deferreds] = ReadonlyArray.unzip(filteredItems)
|
|
510
|
+
|
|
395
511
|
const mergeResult = SyncState.merge({
|
|
396
512
|
syncState,
|
|
397
513
|
payload: { _tag: 'local-push', newEvents },
|
|
398
514
|
isClientEvent,
|
|
399
|
-
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
|
515
|
+
isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
|
|
400
516
|
})
|
|
401
517
|
|
|
402
518
|
switch (mergeResult._tag) {
|
|
403
|
-
case '
|
|
404
|
-
otelSpan?.addEvent(`push:
|
|
519
|
+
case 'unknown-error': {
|
|
520
|
+
otelSpan?.addEvent(`push:unknown-error`, {
|
|
405
521
|
batchSize: newEvents.length,
|
|
406
522
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
|
407
523
|
})
|
|
408
|
-
return yield* new
|
|
524
|
+
return yield* new UnknownError({ cause: mergeResult.message })
|
|
409
525
|
}
|
|
410
526
|
case 'rebase': {
|
|
411
527
|
return shouldNeverHappen('The leader thread should never have to rebase due to a local push')
|
|
@@ -474,10 +590,10 @@ const backgroundApplyLocalPushes = ({
|
|
|
474
590
|
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
|
475
591
|
})
|
|
476
592
|
|
|
477
|
-
// Don't sync
|
|
593
|
+
// Don't sync client-local events
|
|
478
594
|
const filteredBatch = mergeResult.newEvents.filter((eventEncoded) => {
|
|
479
|
-
const
|
|
480
|
-
return eventDef.options.clientOnly === false
|
|
595
|
+
const eventDef = schema.eventsDefsMap.get(eventEncoded.name)
|
|
596
|
+
return eventDef === undefined ? true : eventDef.options.clientOnly === false
|
|
481
597
|
})
|
|
482
598
|
|
|
483
599
|
yield* BucketQueue.offerAll(syncBackendPushQueue, filteredBatch)
|
|
@@ -490,13 +606,13 @@ const backgroundApplyLocalPushes = ({
|
|
|
490
606
|
})
|
|
491
607
|
|
|
492
608
|
type MaterializeEventsBatch = (_: {
|
|
493
|
-
batchItems: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>
|
|
609
|
+
batchItems: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>
|
|
494
610
|
/**
|
|
495
611
|
* The deferreds are used by the caller to know when the mutation has been processed.
|
|
496
612
|
* Indexes are aligned with `batchItems`
|
|
497
613
|
*/
|
|
498
614
|
deferreds: ReadonlyArray<Deferred.Deferred<void, LeaderAheadError> | undefined> | undefined
|
|
499
|
-
}) => Effect.Effect<void,
|
|
615
|
+
}) => Effect.Effect<void, MaterializeError, LeaderThreadCtx>
|
|
500
616
|
|
|
501
617
|
// TODO how to handle errors gracefully
|
|
502
618
|
const materializeEventsBatch: MaterializeEventsBatch = ({ batchItems, deferreds }) =>
|
|
@@ -536,44 +652,46 @@ const materializeEventsBatch: MaterializeEventsBatch = ({ batchItems, deferreds
|
|
|
536
652
|
attributes: { batchSize: batchItems.length },
|
|
537
653
|
}),
|
|
538
654
|
Effect.tapCauseLogPretty,
|
|
539
|
-
UnexpectedError.mapToUnexpectedError,
|
|
540
655
|
)
|
|
541
656
|
|
|
542
657
|
const backgroundBackendPulling = ({
|
|
543
|
-
initialBackendHead,
|
|
544
658
|
isClientEvent,
|
|
545
659
|
restartBackendPushing,
|
|
546
660
|
otelSpan,
|
|
547
661
|
dbState,
|
|
548
662
|
syncStateSref,
|
|
549
663
|
localPushesLatch,
|
|
664
|
+
livePull,
|
|
550
665
|
pullLatch,
|
|
551
666
|
devtoolsLatch,
|
|
552
667
|
initialBlockingSyncContext,
|
|
553
668
|
connectedClientSessionPullQueues,
|
|
554
669
|
advancePushHead,
|
|
555
670
|
}: {
|
|
556
|
-
|
|
557
|
-
isClientEvent: (eventEncoded: LiveStoreEvent.EncodedWithMeta) => boolean
|
|
671
|
+
isClientEvent: (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) => boolean
|
|
558
672
|
restartBackendPushing: (
|
|
559
|
-
filteredRebasedPending: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>,
|
|
560
|
-
) => Effect.Effect<void,
|
|
673
|
+
filteredRebasedPending: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
|
|
674
|
+
) => Effect.Effect<void, UnknownError, LeaderThreadCtx | HttpClient.HttpClient>
|
|
561
675
|
otelSpan: otel.Span | undefined
|
|
562
676
|
syncStateSref: SubscriptionRef.SubscriptionRef<SyncState.SyncState | undefined>
|
|
563
677
|
dbState: SqliteDb
|
|
564
678
|
localPushesLatch: Effect.Latch
|
|
565
679
|
pullLatch: Effect.Latch
|
|
680
|
+
livePull: boolean
|
|
566
681
|
devtoolsLatch: Effect.Latch | undefined
|
|
567
682
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
|
568
683
|
connectedClientSessionPullQueues: PullQueueSet
|
|
569
|
-
advancePushHead: (eventNum: EventSequenceNumber.
|
|
684
|
+
advancePushHead: (eventNum: EventSequenceNumber.Client.Composite) => void
|
|
570
685
|
}) =>
|
|
571
686
|
Effect.gen(function* () {
|
|
572
687
|
const { syncBackend, dbState: db, dbEventlog, schema } = yield* LeaderThreadCtx
|
|
573
688
|
|
|
574
689
|
if (syncBackend === undefined) return
|
|
575
690
|
|
|
576
|
-
const onNewPullChunk = (
|
|
691
|
+
const onNewPullChunk = (
|
|
692
|
+
newEvents: LiveStoreEvent.Client.EncodedWithMeta[],
|
|
693
|
+
pageInfo: SyncBackend.PullResPageInfo,
|
|
694
|
+
) =>
|
|
577
695
|
Effect.gen(function* () {
|
|
578
696
|
if (newEvents.length === 0) return
|
|
579
697
|
|
|
@@ -594,18 +712,18 @@ const backgroundBackendPulling = ({
|
|
|
594
712
|
syncState,
|
|
595
713
|
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents }),
|
|
596
714
|
isClientEvent,
|
|
597
|
-
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
|
715
|
+
isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
|
|
598
716
|
ignoreClientEvents: true,
|
|
599
717
|
})
|
|
600
718
|
|
|
601
719
|
if (mergeResult._tag === 'reject') {
|
|
602
720
|
return shouldNeverHappen('The leader thread should never reject upstream advances')
|
|
603
|
-
} else if (mergeResult._tag === '
|
|
604
|
-
otelSpan?.addEvent(`pull:
|
|
721
|
+
} else if (mergeResult._tag === 'unknown-error') {
|
|
722
|
+
otelSpan?.addEvent(`pull:unknown-error`, {
|
|
605
723
|
newEventsCount: newEvents.length,
|
|
606
724
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
|
607
725
|
})
|
|
608
|
-
return yield* new
|
|
726
|
+
return yield* new UnknownError({ cause: mergeResult.message })
|
|
609
727
|
}
|
|
610
728
|
|
|
611
729
|
const newBackendHead = newEvents.at(-1)!.seqNum
|
|
@@ -621,8 +739,8 @@ const backgroundBackendPulling = ({
|
|
|
621
739
|
})
|
|
622
740
|
|
|
623
741
|
const globalRebasedPendingEvents = mergeResult.newSyncState.pending.filter((event) => {
|
|
624
|
-
const
|
|
625
|
-
return eventDef.options.clientOnly === false
|
|
742
|
+
const eventDef = schema.eventsDefsMap.get(event.name)
|
|
743
|
+
return eventDef === undefined ? true : eventDef.options.clientOnly === false
|
|
626
744
|
})
|
|
627
745
|
yield* restartBackendPushing(globalRebasedPendingEvents)
|
|
628
746
|
|
|
@@ -644,6 +762,13 @@ const backgroundBackendPulling = ({
|
|
|
644
762
|
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
|
645
763
|
})
|
|
646
764
|
|
|
765
|
+
// Ensure push fiber is active after advance by restarting with current pending (non-client) events
|
|
766
|
+
const globalPendingEvents = mergeResult.newSyncState.pending.filter((event) => {
|
|
767
|
+
const eventDef = schema.eventsDefsMap.get(event.name)
|
|
768
|
+
return eventDef === undefined ? true : eventDef.options.clientOnly === false
|
|
769
|
+
})
|
|
770
|
+
yield* restartBackendPushing(globalPendingEvents)
|
|
771
|
+
|
|
647
772
|
yield* connectedClientSessionPullQueues.offer({
|
|
648
773
|
payload: SyncState.payloadFromMergeResult(mergeResult),
|
|
649
774
|
leaderHead: mergeResult.newSyncState.localHead,
|
|
@@ -654,10 +779,10 @@ const backgroundBackendPulling = ({
|
|
|
654
779
|
// `newEvents` instead which we filter via `mergeResult.confirmedEvents`
|
|
655
780
|
const confirmedNewEvents = newEvents.filter((event) =>
|
|
656
781
|
mergeResult.confirmedEvents.some((confirmedEvent) =>
|
|
657
|
-
EventSequenceNumber.isEqual(event.seqNum, confirmedEvent.seqNum),
|
|
782
|
+
EventSequenceNumber.Client.isEqual(event.seqNum, confirmedEvent.seqNum),
|
|
658
783
|
),
|
|
659
784
|
)
|
|
660
|
-
yield* Eventlog.updateSyncMetadata(confirmedNewEvents)
|
|
785
|
+
yield* Eventlog.updateSyncMetadata(confirmedNewEvents).pipe(UnknownError.mapToUnknownError)
|
|
661
786
|
}
|
|
662
787
|
}
|
|
663
788
|
|
|
@@ -671,18 +796,20 @@ const backgroundBackendPulling = ({
|
|
|
671
796
|
yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState)
|
|
672
797
|
|
|
673
798
|
// Allow local pushes to be processed again
|
|
674
|
-
if (
|
|
799
|
+
if (pageInfo._tag === 'NoMore') {
|
|
675
800
|
yield* localPushesLatch.open
|
|
676
801
|
}
|
|
677
802
|
})
|
|
678
803
|
|
|
679
|
-
const
|
|
804
|
+
const syncState = yield* syncStateSref
|
|
805
|
+
if (syncState === undefined) return shouldNeverHappen('Not initialized')
|
|
806
|
+
const cursorInfo = yield* Eventlog.getSyncBackendCursorInfo({ remoteHead: syncState.upstreamHead.global })
|
|
680
807
|
|
|
681
808
|
const hashMaterializerResult = makeMaterializerHash({ schema, dbState })
|
|
682
809
|
|
|
683
|
-
yield* syncBackend.pull(cursorInfo).pipe(
|
|
810
|
+
yield* syncBackend.pull(cursorInfo, { live: livePull }).pipe(
|
|
684
811
|
// TODO only take from queue while connected
|
|
685
|
-
Stream.tap(({ batch,
|
|
812
|
+
Stream.tap(({ batch, pageInfo }) =>
|
|
686
813
|
Effect.gen(function* () {
|
|
687
814
|
// yield* Effect.spanEvent('batch', {
|
|
688
815
|
// attributes: {
|
|
@@ -690,31 +817,31 @@ const backgroundBackendPulling = ({
|
|
|
690
817
|
// batch: TRACE_VERBOSE ? batch : undefined,
|
|
691
818
|
// },
|
|
692
819
|
// })
|
|
693
|
-
|
|
694
820
|
// NOTE we only want to take process events when the sync backend is connected
|
|
695
821
|
// (e.g. needed for simulating being offline)
|
|
696
822
|
// TODO remove when there's a better way to handle this in stream above
|
|
697
823
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
|
|
698
|
-
|
|
699
824
|
yield* onNewPullChunk(
|
|
700
825
|
batch.map((_) =>
|
|
701
|
-
LiveStoreEvent.EncodedWithMeta.fromGlobal(_.eventEncoded, {
|
|
826
|
+
LiveStoreEvent.Client.EncodedWithMeta.fromGlobal(_.eventEncoded, {
|
|
702
827
|
syncMetadata: _.metadata,
|
|
703
828
|
// TODO we can't really know the materializer result here yet beyond the first event batch item as we need to materialize it one by one first
|
|
704
829
|
// This is a bug and needs to be fixed https://github.com/livestorejs/livestore/issues/503#issuecomment-3114533165
|
|
705
|
-
materializerHashLeader: hashMaterializerResult(LiveStoreEvent.
|
|
830
|
+
materializerHashLeader: hashMaterializerResult(LiveStoreEvent.Global.toClientEncoded(_.eventEncoded)),
|
|
706
831
|
materializerHashSession: Option.none(),
|
|
707
832
|
}),
|
|
708
833
|
),
|
|
709
|
-
|
|
834
|
+
pageInfo,
|
|
710
835
|
)
|
|
711
|
-
|
|
712
|
-
yield* initialBlockingSyncContext.update({ processed: batch.length, remaining })
|
|
836
|
+
yield* initialBlockingSyncContext.update({ processed: batch.length, pageInfo })
|
|
713
837
|
}),
|
|
714
838
|
),
|
|
715
839
|
Stream.runDrain,
|
|
716
840
|
Effect.interruptible,
|
|
717
841
|
)
|
|
842
|
+
|
|
843
|
+
// Should only ever happen when livePull is false
|
|
844
|
+
yield* Effect.logDebug('backend-pulling finished', { livePull })
|
|
718
845
|
}).pipe(Effect.withSpan('@livestore/common:LeaderSyncProcessor:backend-pulling'))
|
|
719
846
|
|
|
720
847
|
const backgroundBackendPushing = ({
|
|
@@ -723,7 +850,7 @@ const backgroundBackendPushing = ({
|
|
|
723
850
|
devtoolsLatch,
|
|
724
851
|
backendPushBatchSize,
|
|
725
852
|
}: {
|
|
726
|
-
syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.EncodedWithMeta>
|
|
853
|
+
syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.Client.EncodedWithMeta>
|
|
727
854
|
otelSpan: otel.Span | undefined
|
|
728
855
|
devtoolsLatch: Effect.Latch | undefined
|
|
729
856
|
backendPushBatchSize: number
|
|
@@ -748,21 +875,57 @@ const backgroundBackendPushing = ({
|
|
|
748
875
|
batch: TRACE_VERBOSE ? JSON.stringify(queueItems) : undefined,
|
|
749
876
|
})
|
|
750
877
|
|
|
751
|
-
//
|
|
752
|
-
|
|
878
|
+
// Push with declarative retry/backoff using Effect schedules
|
|
879
|
+
// - Exponential backoff starting at 1s and doubling (1s, 2s, 4s, 8s, 16s, 30s ...)
|
|
880
|
+
// - Delay clamped at 30s (continues retrying at 30s)
|
|
881
|
+
// - Resets automatically after successful push
|
|
882
|
+
// TODO(metrics): expose counters/gauges for retry attempts and queue health via devtools/metrics
|
|
883
|
+
|
|
884
|
+
// Only retry for transient UnknownError cases
|
|
885
|
+
const isRetryable = (err: InvalidPushError | IsOfflineError) =>
|
|
886
|
+
err._tag === 'InvalidPushError' && err.cause._tag === 'LiveStore.UnknownError'
|
|
887
|
+
|
|
888
|
+
// Input: InvalidPushError | IsOfflineError, Output: Duration
|
|
889
|
+
const retrySchedule: Schedule.Schedule<Duration.DurationInput, InvalidPushError | IsOfflineError> =
|
|
890
|
+
Schedule.exponential(Duration.seconds(1)).pipe(
|
|
891
|
+
Schedule.andThenEither(Schedule.spaced(Duration.seconds(30))), // clamp at 30 second intervals
|
|
892
|
+
Schedule.compose(Schedule.elapsed),
|
|
893
|
+
Schedule.whileInput(isRetryable),
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
yield* Effect.gen(function* () {
|
|
897
|
+
const iteration = yield* Schedule.CurrentIterationMetadata
|
|
753
898
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
899
|
+
const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either)
|
|
900
|
+
|
|
901
|
+
const retries = iteration.recurrence
|
|
902
|
+
if (retries > 0 && pushResult._tag === 'Right') {
|
|
903
|
+
otelSpan?.addEvent('backend-push-retry-success', { retries, batchSize: queueItems.length })
|
|
757
904
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
905
|
+
|
|
906
|
+
if (pushResult._tag === 'Left') {
|
|
907
|
+
otelSpan?.addEvent('backend-push-error', {
|
|
908
|
+
error: pushResult.left.toString(),
|
|
909
|
+
retries,
|
|
910
|
+
batchSize: queueItems.length,
|
|
911
|
+
})
|
|
912
|
+
const error = pushResult.left
|
|
913
|
+
if (
|
|
914
|
+
error._tag === 'IsOfflineError' ||
|
|
915
|
+
(error._tag === 'InvalidPushError' && error.cause._tag === 'ServerAheadError')
|
|
916
|
+
) {
|
|
917
|
+
// It's a core part of the sync protocol that the sync backend will emit a new pull chunk alongside the ServerAheadError
|
|
918
|
+
yield* Effect.logDebug('handled backend-push-error (waiting for interupt caused by pull)', { error })
|
|
919
|
+
return yield* Effect.never
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
return yield* error
|
|
923
|
+
}
|
|
924
|
+
}).pipe(Effect.retry(retrySchedule))
|
|
762
925
|
}
|
|
763
926
|
}).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:LeaderSyncProcessor:backend-pushing'))
|
|
764
927
|
|
|
765
|
-
const trimChangesetRows = (db: SqliteDb, newHead: EventSequenceNumber.
|
|
928
|
+
const trimChangesetRows = (db: SqliteDb, newHead: EventSequenceNumber.Client.Composite) => {
|
|
766
929
|
// Since we're using the session changeset rows to query for the current head,
|
|
767
930
|
// we're keeping at least one row for the current head, and thus are using `<` instead of `<=`
|
|
768
931
|
db.execute(sql`DELETE FROM ${SystemTables.SESSION_CHANGESET_META_TABLE} WHERE seqNumGlobal < ${newHead.global}`)
|
|
@@ -770,16 +933,16 @@ const trimChangesetRows = (db: SqliteDb, newHead: EventSequenceNumber.EventSeque
|
|
|
770
933
|
|
|
771
934
|
interface PullQueueSet {
|
|
772
935
|
makeQueue: (
|
|
773
|
-
cursor: EventSequenceNumber.
|
|
936
|
+
cursor: EventSequenceNumber.Client.Composite,
|
|
774
937
|
) => Effect.Effect<
|
|
775
938
|
Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type }>,
|
|
776
|
-
|
|
939
|
+
UnknownError,
|
|
777
940
|
Scope.Scope | LeaderThreadCtx
|
|
778
941
|
>
|
|
779
942
|
offer: (item: {
|
|
780
943
|
payload: typeof SyncState.PayloadUpstream.Type
|
|
781
|
-
leaderHead: EventSequenceNumber.
|
|
782
|
-
}) => Effect.Effect<void,
|
|
944
|
+
leaderHead: EventSequenceNumber.Client.Composite
|
|
945
|
+
}) => Effect.Effect<void, UnknownError>
|
|
783
946
|
}
|
|
784
947
|
|
|
785
948
|
const makePullQueueSet = Effect.gen(function* () {
|
|
@@ -809,17 +972,17 @@ const makePullQueueSet = Effect.gen(function* () {
|
|
|
809
972
|
|
|
810
973
|
const payloadsSinceCursor = Array.from(cachedPayloads.entries())
|
|
811
974
|
.flatMap(([seqNumStr, payloads]) =>
|
|
812
|
-
payloads.map((payload) => ({ payload, seqNum: EventSequenceNumber.fromString(seqNumStr) })),
|
|
975
|
+
payloads.map((payload) => ({ payload, seqNum: EventSequenceNumber.Client.fromString(seqNumStr) })),
|
|
813
976
|
)
|
|
814
|
-
.filter(({ seqNum }) => EventSequenceNumber.isGreaterThan(seqNum, cursor))
|
|
815
|
-
.toSorted((a, b) => EventSequenceNumber.compare(a.seqNum, b.seqNum))
|
|
977
|
+
.filter(({ seqNum }) => EventSequenceNumber.Client.isGreaterThan(seqNum, cursor))
|
|
978
|
+
.toSorted((a, b) => EventSequenceNumber.Client.compare(a.seqNum, b.seqNum))
|
|
816
979
|
.map(({ payload }) => {
|
|
817
980
|
if (payload._tag === 'upstream-advance') {
|
|
818
981
|
return {
|
|
819
982
|
payload: {
|
|
820
983
|
_tag: 'upstream-advance' as const,
|
|
821
984
|
newEvents: ReadonlyArray.dropWhile(payload.newEvents, (eventEncoded) =>
|
|
822
|
-
EventSequenceNumber.isGreaterThanOrEqual(cursor, eventEncoded.seqNum),
|
|
985
|
+
EventSequenceNumber.Client.isGreaterThanOrEqual(cursor, eventEncoded.seqNum),
|
|
823
986
|
),
|
|
824
987
|
},
|
|
825
988
|
}
|
|
@@ -865,7 +1028,7 @@ const makePullQueueSet = Effect.gen(function* () {
|
|
|
865
1028
|
|
|
866
1029
|
const offer: PullQueueSet['offer'] = (item) =>
|
|
867
1030
|
Effect.gen(function* () {
|
|
868
|
-
const seqNumStr = EventSequenceNumber.toString(item.leaderHead)
|
|
1031
|
+
const seqNumStr = EventSequenceNumber.Client.toString(item.leaderHead)
|
|
869
1032
|
if (cachedPayloads.has(seqNumStr)) {
|
|
870
1033
|
cachedPayloads.get(seqNumStr)!.push(item.payload)
|
|
871
1034
|
} else {
|
|
@@ -890,26 +1053,35 @@ const makePullQueueSet = Effect.gen(function* () {
|
|
|
890
1053
|
}
|
|
891
1054
|
})
|
|
892
1055
|
|
|
1056
|
+
/**
|
|
1057
|
+
* Validate a client-provided batch before it is admitted to the leader queue.
|
|
1058
|
+
* Ensures the numbers form a strictly increasing chain and that the first
|
|
1059
|
+
* event sits ahead of the current push head.
|
|
1060
|
+
*/
|
|
893
1061
|
const validatePushBatch = (
|
|
894
|
-
batch: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>,
|
|
895
|
-
pushHead: EventSequenceNumber.
|
|
1062
|
+
batch: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
|
|
1063
|
+
pushHead: EventSequenceNumber.Client.Composite,
|
|
896
1064
|
) =>
|
|
897
1065
|
Effect.gen(function* () {
|
|
898
1066
|
if (batch.length === 0) {
|
|
899
1067
|
return
|
|
900
1068
|
}
|
|
901
1069
|
|
|
902
|
-
//
|
|
1070
|
+
// Example: session A already enqueued e1…e6 while session B (same client, different
|
|
1071
|
+
// session) still believes the head is e1 and submits [e2, e7, e8]. The numbers look
|
|
1072
|
+
// monotonic from B’s perspective, but we must reject and force B to rebase locally
|
|
1073
|
+
// so the leader never regresses.
|
|
903
1074
|
for (let i = 1; i < batch.length; i++) {
|
|
904
|
-
if (EventSequenceNumber.isGreaterThanOrEqual(batch[i - 1]!.seqNum, batch[i]!.seqNum)) {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1075
|
+
if (EventSequenceNumber.Client.isGreaterThanOrEqual(batch[i - 1]!.seqNum, batch[i]!.seqNum)) {
|
|
1076
|
+
return yield* LeaderAheadError.make({
|
|
1077
|
+
minimumExpectedNum: batch[i - 1]!.seqNum,
|
|
1078
|
+
providedNum: batch[i]!.seqNum,
|
|
1079
|
+
})
|
|
908
1080
|
}
|
|
909
1081
|
}
|
|
910
1082
|
|
|
911
1083
|
// Make sure smallest sequence number is > pushHead
|
|
912
|
-
if (EventSequenceNumber.isGreaterThanOrEqual(pushHead, batch[0]!.seqNum)) {
|
|
1084
|
+
if (EventSequenceNumber.Client.isGreaterThanOrEqual(pushHead, batch[0]!.seqNum)) {
|
|
913
1085
|
return yield* LeaderAheadError.make({
|
|
914
1086
|
minimumExpectedNum: pushHead,
|
|
915
1087
|
providedNum: batch[0]!.seqNum,
|