@livestore/common 0.4.0 → 0.5.0-dev.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/ClientSessionLeaderThreadProxy.d.ts +1 -1
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/MaterializationJournal.d.ts +44 -0
- package/dist/MaterializationJournal.d.ts.map +1 -0
- package/dist/MaterializationJournal.js +112 -0
- package/dist/MaterializationJournal.js.map +1 -0
- package/dist/StateHead.d.ts +26 -0
- package/dist/StateHead.d.ts.map +1 -0
- package/dist/StateHead.js +50 -0
- package/dist/StateHead.js.map +1 -0
- package/dist/WorkerTransportError.d.ts +3 -3
- package/dist/WorkerTransportError.d.ts.map +1 -1
- package/dist/WorkerTransportError.js +3 -3
- package/dist/WorkerTransportError.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +1 -1
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +3 -3
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/adapter-types.d.ts +29 -57
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +20 -37
- package/dist/adapter-types.js.map +1 -1
- package/dist/debug-info.d.ts +56 -56
- package/dist/debug-info.d.ts.map +1 -1
- package/dist/debug-info.js +28 -41
- package/dist/debug-info.js.map +1 -1
- package/dist/defs.d.ts +9 -9
- package/dist/defs.js +2 -2
- package/dist/devtools/devtools-compatibility.test.js +1 -0
- package/dist/devtools/devtools-compatibility.test.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +520 -332
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.js +76 -74
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +27 -27
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +5 -5
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +405 -343
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +101 -89
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/devtools/devtools-sessioninfo.d.ts +18 -19
- package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
- package/dist/devtools/devtools-sessioninfo.js +8 -5
- package/dist/devtools/devtools-sessioninfo.js.map +1 -1
- package/dist/devtools/mod.d.ts +4 -5
- package/dist/devtools/mod.d.ts.map +1 -1
- package/dist/devtools/mod.js +9 -5
- package/dist/devtools/mod.js.map +1 -1
- package/dist/errors.d.ts +45 -69
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -16
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +66 -18
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +519 -461
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/RejectedPushError.d.ts +67 -36
- package/dist/leader-thread/RejectedPushError.d.ts.map +1 -1
- package/dist/leader-thread/RejectedPushError.js +24 -5
- package/dist/leader-thread/RejectedPushError.js.map +1 -1
- package/dist/leader-thread/connection.d.ts +1 -0
- package/dist/leader-thread/connection.d.ts.map +1 -1
- package/dist/leader-thread/connection.js +9 -8
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +4 -4
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +6 -6
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +33 -17
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +9 -5
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +25 -35
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js +7 -7
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -1
- package/dist/leader-thread/materialize-event.d.ts +2 -1
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +4 -0
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +18 -39
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -6
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -3
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/stream-events.d.ts +4 -7
- package/dist/leader-thread/stream-events.d.ts.map +1 -1
- package/dist/leader-thread/stream-events.js +13 -16
- package/dist/leader-thread/stream-events.js.map +1 -1
- package/dist/leader-thread/types.d.ts +32 -69
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js +5 -5
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/logging.d.ts +5 -30
- package/dist/logging.d.ts.map +1 -1
- package/dist/logging.js +1 -32
- package/dist/logging.js.map +1 -1
- package/dist/make-client-session.d.ts +1 -2
- package/dist/make-client-session.d.ts.map +1 -1
- package/dist/make-client-session.js +3 -3
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.d.ts +0 -1
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +7 -35
- package/dist/materializer-helper.js.map +1 -1
- package/dist/otel.d.ts +2 -3
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +2 -7
- package/dist/otel.js.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +28 -26
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef/define.d.ts +4 -4
- package/dist/schema/EventDef/define.d.ts.map +1 -1
- package/dist/schema/EventDef/define.js +5 -5
- package/dist/schema/EventDef/define.js.map +1 -1
- package/dist/schema/EventDef/define.test.d.ts +2 -0
- package/dist/schema/EventDef/define.test.d.ts.map +1 -0
- package/dist/schema/EventDef/define.test.js +23 -0
- package/dist/schema/EventDef/define.test.js.map +1 -0
- package/dist/schema/EventDef/deprecated.d.ts +8 -13
- package/dist/schema/EventDef/deprecated.d.ts.map +1 -1
- package/dist/schema/EventDef/deprecated.js +12 -13
- package/dist/schema/EventDef/deprecated.js.map +1 -1
- package/dist/schema/EventDef/deprecated.test.js +1 -1
- package/dist/schema/EventDef/deprecated.test.js.map +1 -1
- package/dist/schema/EventDef/event-def.d.ts +1 -1
- package/dist/schema/EventDef/event-def.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/client.d.ts +12 -12
- package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/client.js +8 -9
- package/dist/schema/EventSequenceNumber/client.js.map +1 -1
- package/dist/schema/EventSequenceNumber/global.d.ts +2 -2
- package/dist/schema/EventSequenceNumber/global.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/global.js +1 -1
- package/dist/schema/EventSequenceNumber/global.js.map +1 -1
- package/dist/schema/EventSequenceNumber.test.js +5 -5
- package/dist/schema/EventSequenceNumber.test.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.d.ts +50 -179
- package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/client.js +32 -29
- package/dist/schema/LiveStoreEvent/client.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.test.js +11 -2
- package/dist/schema/LiveStoreEvent/client.test.js.map +1 -1
- package/dist/schema/LiveStoreEvent/for-event-def.d.ts +7 -7
- package/dist/schema/LiveStoreEvent/for-event-def.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/global.d.ts +6 -6
- package/dist/schema/LiveStoreEvent/global.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/global.js +1 -1
- package/dist/schema/LiveStoreEvent/global.js.map +1 -1
- package/dist/schema/LiveStoreEvent/input.d.ts +5 -5
- package/dist/schema/LiveStoreEvent/input.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/input.js +3 -3
- package/dist/schema/LiveStoreEvent/input.js.map +1 -1
- package/dist/schema/mod.d.ts +1 -0
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +1 -0
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +3 -2
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts +12 -17
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +23 -20
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +29 -8
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts +13 -13
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.js +13 -15
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js +19 -43
- package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +57 -31
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +118 -72
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js +2 -2
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -2
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +13 -4
- 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 +9 -9
- 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 +8 -13
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +27 -16
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +13 -11
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +5 -3
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts +1 -3
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +1 -1
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/persistence.d.ts +11 -0
- package/dist/schema/state/sqlite/persistence.d.ts.map +1 -0
- package/dist/schema/state/sqlite/persistence.js +10 -0
- package/dist/schema/state/sqlite/persistence.js.map +1 -0
- package/dist/schema/state/sqlite/query-builder/api.d.ts +7 -7
- 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 +27 -23
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts +3 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +16 -14
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +362 -596
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts +102 -134
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.js +2 -2
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables/state-tables.d.ts +328 -106
- package/dist/schema/state/sqlite/system-tables/state-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables/state-tables.js +33 -1
- package/dist/schema/state/sqlite/system-tables/state-tables.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +16 -19
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +53 -3
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +153 -36
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts.map +1 -1
- package/dist/schema/unknown-events.js +2 -1
- package/dist/schema/unknown-events.js.map +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +2 -2
- package/dist/session-id-symbol.d.ts +29 -0
- package/dist/session-id-symbol.d.ts.map +1 -0
- package/dist/session-id-symbol.js +44 -0
- package/dist/session-id-symbol.js.map +1 -0
- package/dist/sql-queries/sql-queries.d.ts.map +1 -1
- package/dist/sql-queries/sql-queries.js +8 -10
- package/dist/sql-queries/sql-queries.js.map +1 -1
- package/dist/sql-queries/types.d.ts +6 -7
- package/dist/sql-queries/types.d.ts.map +1 -1
- package/dist/sql-queries/types.js.map +1 -1
- package/dist/sqlite-db-helper.d.ts +21 -1
- package/dist/sqlite-db-helper.d.ts.map +1 -1
- package/dist/sqlite-db-helper.js +37 -2
- package/dist/sqlite-db-helper.js.map +1 -1
- package/dist/sqlite-types.d.ts +3 -6
- package/dist/sqlite-types.d.ts.map +1 -1
- package/dist/sqlite-types.js +3 -3
- package/dist/sqlite-types.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +38 -16
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +196 -66
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +12 -18
- package/dist/sync/errors.d.ts.map +1 -1
- package/dist/sync/errors.js +2 -2
- package/dist/sync/errors.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +2 -3
- package/dist/sync/mock-sync-backend.d.ts.map +1 -1
- package/dist/sync/mock-sync-backend.js +54 -33
- package/dist/sync/mock-sync-backend.js.map +1 -1
- package/dist/sync/mock-sync-backend.test.d.ts +2 -0
- package/dist/sync/mock-sync-backend.test.d.ts.map +1 -0
- package/dist/sync/mock-sync-backend.test.js +49 -0
- package/dist/sync/mock-sync-backend.test.js.map +1 -0
- package/dist/sync/next/history-dag-common.d.ts +4 -0
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +52 -31
- 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 +2 -1
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +2 -2
- package/dist/sync/next/test/event-fixtures.d.ts +7 -7
- package/dist/sync/next/test/event-fixtures.js +1 -1
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/next/test/history-dag-common.test.d.ts +2 -0
- package/dist/sync/next/test/history-dag-common.test.d.ts.map +1 -0
- package/dist/sync/next/test/history-dag-common.test.js +73 -0
- package/dist/sync/next/test/history-dag-common.test.js.map +1 -0
- package/dist/sync/sync-backend-kv.d.ts.map +1 -1
- package/dist/sync/sync-backend-kv.js +4 -2
- package/dist/sync/sync-backend-kv.js.map +1 -1
- package/dist/sync/sync-backend.d.ts +15 -15
- package/dist/sync/sync-backend.d.ts.map +1 -1
- package/dist/sync/sync-backend.js +9 -5
- package/dist/sync/sync-backend.js.map +1 -1
- package/dist/sync/sync.d.ts +1 -1
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/syncstate.d.ts +97 -173
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +34 -37
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +100 -61
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/transport-chunking.d.ts +15 -16
- package/dist/sync/transport-chunking.d.ts.map +1 -1
- package/dist/sync/transport-chunking.js +23 -32
- package/dist/sync/transport-chunking.js.map +1 -1
- package/dist/util.d.ts +11 -4
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +19 -2
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +19 -37
- package/src/ClientSessionLeaderThreadProxy.ts +1 -1
- package/src/MaterializationJournal.ts +184 -0
- package/src/StateHead.ts +80 -0
- package/src/WorkerTransportError.ts +4 -4
- package/src/__tests__/fixture.ts +3 -3
- package/src/adapter-types.ts +24 -61
- package/src/debug-info.ts +50 -70
- package/src/defs.ts +2 -2
- package/src/devtools/devtools-compatibility.test.ts +1 -0
- package/src/devtools/devtools-messages-client-session.ts +56 -65
- package/src/devtools/devtools-messages-common.ts +12 -11
- package/src/devtools/devtools-messages-leader.ts +71 -69
- package/src/devtools/devtools-sessioninfo.ts +13 -11
- package/src/devtools/mod.ts +4 -5
- package/src/errors.ts +46 -34
- package/src/index.ts +8 -1
- package/src/leader-thread/LeaderSyncProcessor.ts +794 -691
- package/src/leader-thread/RejectedPushError.ts +40 -27
- package/src/leader-thread/connection.ts +12 -8
- package/src/leader-thread/eventlog.ts +7 -7
- package/src/leader-thread/leader-worker-devtools.ts +37 -19
- package/src/leader-thread/make-leader-thread-layer.test.ts +7 -7
- package/src/leader-thread/make-leader-thread-layer.ts +56 -53
- package/src/leader-thread/materialize-event.ts +13 -8
- package/src/leader-thread/recreate-db.ts +24 -59
- package/src/leader-thread/shutdown-channel.ts +3 -14
- package/src/leader-thread/stream-events.ts +104 -101
- package/src/leader-thread/types.ts +24 -62
- package/src/logging.ts +5 -52
- package/src/make-client-session.ts +6 -6
- package/src/materializer-helper.ts +8 -37
- package/src/otel.ts +5 -14
- package/src/rematerialize-from-eventlog.ts +97 -94
- package/src/schema/EventDef/define.test.ts +28 -0
- package/src/schema/EventDef/define.ts +20 -19
- package/src/schema/EventDef/deprecated.test.ts +1 -8
- package/src/schema/EventDef/deprecated.ts +17 -20
- package/src/schema/EventDef/event-def.ts +2 -4
- package/src/schema/EventSequenceNumber/client.ts +8 -9
- package/src/schema/EventSequenceNumber/global.ts +1 -1
- package/src/schema/EventSequenceNumber.test.ts +5 -5
- package/src/schema/LiveStoreEvent/client.test.ts +17 -4
- package/src/schema/LiveStoreEvent/client.ts +36 -35
- package/src/schema/LiveStoreEvent/for-event-def.ts +7 -7
- package/src/schema/LiveStoreEvent/global.ts +1 -1
- package/src/schema/LiveStoreEvent/input.ts +6 -6
- package/src/schema/mod.ts +1 -0
- package/src/schema/schema.ts +3 -2
- package/src/schema/state/sqlite/client-document-def.test.ts +32 -13
- package/src/schema/state/sqlite/client-document-def.ts +95 -80
- package/src/schema/state/sqlite/column-annotations.test.ts +26 -45
- package/src/schema/state/sqlite/column-annotations.ts +26 -30
- package/src/schema/state/sqlite/column-def.test.ts +132 -77
- package/src/schema/state/sqlite/column-def.ts +72 -37
- package/src/schema/state/sqlite/column-spec.test.ts +2 -2
- package/src/schema/state/sqlite/column-spec.ts +3 -2
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +15 -5
- package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +123 -63
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +35 -16
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +25 -31
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +35 -21
- package/src/schema/state/sqlite/mod.ts +2 -4
- package/src/schema/state/sqlite/persistence.ts +11 -0
- package/src/schema/state/sqlite/query-builder/api.ts +9 -9
- package/src/schema/state/sqlite/query-builder/astToSql.ts +31 -26
- package/src/schema/state/sqlite/query-builder/impl.test.ts +433 -669
- package/src/schema/state/sqlite/query-builder/impl.ts +19 -15
- package/src/schema/state/sqlite/schema-helpers.ts +1 -1
- package/src/schema/state/sqlite/system-tables/eventlog-tables.ts +2 -2
- package/src/schema/state/sqlite/system-tables/state-tables.ts +41 -1
- package/src/schema/state/sqlite/table-def.test.ts +204 -66
- package/src/schema/state/sqlite/table-def.ts +96 -45
- package/src/schema/unknown-events.ts +3 -2
- package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +2 -2
- package/src/session-id-symbol.ts +59 -0
- package/src/sql-queries/sql-queries.ts +8 -12
- package/src/sql-queries/types.ts +6 -7
- package/src/sqlite-db-helper.ts +64 -2
- package/src/sqlite-types.ts +5 -8
- package/src/sync/ClientSessionSyncProcessor.ts +303 -119
- package/src/sync/errors.ts +14 -12
- package/src/sync/mock-sync-backend.test.ts +78 -0
- package/src/sync/mock-sync-backend.ts +93 -47
- package/src/sync/next/history-dag-common.ts +60 -34
- package/src/sync/next/history-dag.ts +4 -1
- package/src/sync/next/test/compact-events.calculator.test.ts +2 -2
- package/src/sync/next/test/event-fixtures.ts +1 -1
- package/src/sync/next/test/history-dag-common.test.ts +87 -0
- package/src/sync/sync-backend-kv.ts +4 -2
- package/src/sync/sync-backend.ts +15 -15
- package/src/sync/sync.ts +1 -1
- package/src/sync/syncstate.test.ts +191 -140
- package/src/sync/syncstate.ts +63 -53
- package/src/sync/transport-chunking.ts +34 -39
- package/src/util.ts +18 -9
- package/src/version.ts +1 -2
- package/dist/sql-queries/misc.d.ts +0 -2
- package/dist/sql-queries/misc.d.ts.map +0 -1
- package/dist/sql-queries/misc.js +0 -2
- package/dist/sql-queries/misc.js.map +0 -1
- package/src/sql-queries/misc.ts +0 -2
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
import { LS_DEV, TRACE_VERBOSE } from '@livestore/utils'
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
Cause,
|
|
5
|
+
Deferred,
|
|
5
6
|
Effect,
|
|
6
7
|
Exit,
|
|
8
|
+
Filter,
|
|
7
9
|
FiberHandle,
|
|
8
10
|
Option,
|
|
9
11
|
Queue,
|
|
10
12
|
Schema,
|
|
13
|
+
Semaphore,
|
|
11
14
|
type Scope,
|
|
12
15
|
Stream,
|
|
13
16
|
Subscribable,
|
|
17
|
+
TxQueue,
|
|
14
18
|
} from '@livestore/utils/effect'
|
|
15
19
|
|
|
16
20
|
import type { ClientSession } from '../adapter-types.ts'
|
|
17
|
-
import
|
|
21
|
+
import { MaterializeError } from '../errors.ts'
|
|
18
22
|
import { isRejectedPushError } from '../leader-thread/RejectedPushError.ts'
|
|
19
23
|
import * as EventSequenceNumber from '../schema/EventSequenceNumber/mod.ts'
|
|
20
24
|
import * as LiveStoreEvent from '../schema/LiveStoreEvent/mod.ts'
|
|
21
25
|
import type { LiveStoreSchema } from '../schema/mod.ts'
|
|
26
|
+
import { resolveSessionIdSymbolInEventArgs } from '../session-id-symbol.ts'
|
|
27
|
+
import * as StateHead from '../StateHead.ts'
|
|
22
28
|
import * as SyncState from './syncstate.ts'
|
|
23
29
|
|
|
24
30
|
/** Serialize value to JSON string for trace attributes */
|
|
25
|
-
const jsonStringify = Schema.encodeSync(Schema.
|
|
31
|
+
const jsonStringify = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown))
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
34
|
* Rebase behaviour:
|
|
@@ -52,7 +58,7 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
52
58
|
clientSession: ClientSession
|
|
53
59
|
materializeEvent: (
|
|
54
60
|
eventEncoded: LiveStoreEvent.Client.EncodedWithMeta,
|
|
55
|
-
options: {
|
|
61
|
+
options: { materializerHashLeader: Option.Option<number> },
|
|
56
62
|
) => Effect.Effect<
|
|
57
63
|
{
|
|
58
64
|
writeTables: Set<string>
|
|
@@ -68,20 +74,25 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
68
74
|
refreshTables: (tables: Set<string>) => void
|
|
69
75
|
params: {
|
|
70
76
|
leaderPushBatchSize: number
|
|
71
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Test-only deterministic barriers, awaited at fixed points of the rebase critical section.
|
|
79
|
+
* A test can park the pull fiber at a chosen point, inject a concurrent operation
|
|
80
|
+
* (a synchronous `push` or a `shutdown`), then release the barrier — without relying on
|
|
81
|
+
* virtual-time scheduling. Unset in production, where each lookup resolves to `Effect.void`.
|
|
82
|
+
*/
|
|
83
|
+
rebaseBarriers?: Partial<Record<RebaseBarrierPoint, Effect.Effect<void>>>
|
|
72
84
|
}
|
|
73
85
|
/**
|
|
74
86
|
* Currently only used in the web adapter:
|
|
75
87
|
* If true, registers a beforeunload event listener to confirm unsaved changes.
|
|
76
88
|
*/
|
|
77
89
|
confirmUnsavedChanges: boolean
|
|
78
|
-
})
|
|
90
|
+
}) {
|
|
91
|
+
const stateHead = yield* StateHead.StateHead
|
|
79
92
|
const eventSchema = LiveStoreEvent.Client.makeSchemaMemo(schema)
|
|
80
93
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
key2: keyof ClientSessionSyncProcessorSimulationParams[TKey],
|
|
84
|
-
) => Effect.sleep((params.simulation?.[key]?.[key2] ?? 0) as number)
|
|
94
|
+
const rebaseBarrier = (point: RebaseBarrierPoint): Effect.Effect<void> =>
|
|
95
|
+
params.rebaseBarriers?.[point] ?? Effect.void
|
|
85
96
|
|
|
86
97
|
const syncStateRef = {
|
|
87
98
|
// The initial state is identical to the leader's initial state
|
|
@@ -95,13 +106,44 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
95
106
|
|
|
96
107
|
/** Only used for debugging / observability / testing, it's not relied upon for correctness of the sync processor. */
|
|
97
108
|
const syncStateUpdateQueue = yield* Queue.unbounded<SyncState.SyncState>()
|
|
98
|
-
const
|
|
109
|
+
const isClientOnlyEvent = (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) =>
|
|
99
110
|
schema.eventsDefsMap.get(eventEncoded.name)?.options.clientOnly ?? false
|
|
100
111
|
|
|
101
112
|
/** We're queuing push requests to reduce the number of messages sent to the leader by batching them */
|
|
102
|
-
const leaderPushQueue = yield*
|
|
113
|
+
const leaderPushQueue = yield* TxQueue.unbounded<LiveStoreEvent.Client.EncodedWithMeta, Cause.Done>()
|
|
114
|
+
/**
|
|
115
|
+
* Prevents pull reconciliation, push-rejection handling, and shutdown from running concurrently.
|
|
116
|
+
* These transitions inspect or update the pending events, leader push queue, and rejection state,
|
|
117
|
+
* so each must observe the others either fully before or fully after—not midway through bookkeeping.
|
|
118
|
+
*
|
|
119
|
+
* Regular local commits do not acquire this mutex; they remain synchronous and are incorporated
|
|
120
|
+
* when `reconcileLeaderPushQueue` reads the current pending events transactionally.
|
|
121
|
+
*/
|
|
122
|
+
const pullReconciliationMutex = yield* Semaphore.make(1)
|
|
123
|
+
const shutdownDone = yield* Deferred.make<void>()
|
|
124
|
+
const drainStartedSignal = yield* Deferred.make<void>()
|
|
125
|
+
const rejectionObserved = yield* Deferred.make<void>()
|
|
126
|
+
let shutdownStarted = false
|
|
127
|
+
let terminalPushCause: Cause.Cause<never> | undefined
|
|
128
|
+
let unresolvedRejection:
|
|
129
|
+
| {
|
|
130
|
+
readonly error: Error
|
|
131
|
+
readonly events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>
|
|
132
|
+
}
|
|
133
|
+
| undefined
|
|
134
|
+
let leaderPushingFiberHandle: FiberHandle.FiberHandle<void, never> | undefined
|
|
135
|
+
let pullingFiberHandle: FiberHandle.FiberHandle<void, never> | undefined
|
|
136
|
+
|
|
137
|
+
/** Rebuild the leader push queue from the current pending events without racing synchronous commits. */
|
|
138
|
+
const reconcileLeaderPushQueue = Effect.tx(
|
|
139
|
+
Effect.gen(function* () {
|
|
140
|
+
const livePending = syncStateRef.current.pending
|
|
141
|
+
yield* TxQueue.clear(leaderPushQueue)
|
|
142
|
+
yield* TxQueue.offerAll(leaderPushQueue, livePending)
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
103
145
|
|
|
104
|
-
const boot: ClientSessionSyncProcessor['boot'] = Effect.
|
|
146
|
+
const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
|
|
105
147
|
if (
|
|
106
148
|
confirmUnsavedChanges === true &&
|
|
107
149
|
typeof window !== 'undefined' &&
|
|
@@ -120,51 +162,100 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
120
162
|
)
|
|
121
163
|
}
|
|
122
164
|
|
|
123
|
-
const
|
|
165
|
+
const leaderPushingHandle = yield* FiberHandle.make<void, never>()
|
|
166
|
+
const pullingHandle = yield* FiberHandle.make<void, never>()
|
|
167
|
+
leaderPushingFiberHandle = leaderPushingHandle
|
|
168
|
+
pullingFiberHandle = pullingHandle
|
|
124
169
|
|
|
125
|
-
const backgroundLeaderPushing = Effect.gen(function* () {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
170
|
+
const backgroundLeaderPushing: Effect.Effect<void> = Effect.gen(function* () {
|
|
171
|
+
while (true) {
|
|
172
|
+
const batch = yield* TxQueue.takeBetween(leaderPushQueue, 1, params.leaderPushBatchSize).pipe(
|
|
173
|
+
Effect.catchIf(Cause.isDone, () => Effect.void),
|
|
174
|
+
)
|
|
175
|
+
if (batch === undefined) return
|
|
176
|
+
|
|
177
|
+
yield* clientSession.leaderThread.events.push(batch).pipe(
|
|
178
|
+
Effect.catchIf(isRejectedPushError, (error) =>
|
|
179
|
+
// A pull carrying the leader's corrective history can complete before or after this
|
|
180
|
+
// rejection response. Use the same mutex as pull reconciliation so this decision uses
|
|
181
|
+
// one stable view of pending events and cannot install a stale rejection fence after recovery.
|
|
182
|
+
pullReconciliationMutex
|
|
183
|
+
.withPermits(1)(
|
|
184
|
+
Effect.gen(function* () {
|
|
185
|
+
debugInfo.rejectCount++
|
|
186
|
+
if (shutdownStarted === true) return yield* Effect.die(error)
|
|
187
|
+
|
|
188
|
+
// A concurrent pull may have already confirmed or rebased this batch while the
|
|
189
|
+
// push response was in flight. In that case, rebuild the queue from the reconciled
|
|
190
|
+
// pending events instead of creating a fence that no future pull could release.
|
|
191
|
+
if (isRejectedBatchRecovered(batch, syncStateRef.current.pending) === true) {
|
|
192
|
+
yield* reconcileLeaderPushQueue
|
|
193
|
+
yield* Deferred.succeed(rejectionObserved, undefined)
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
unresolvedRejection = { error, events: batch }
|
|
198
|
+
yield* TxQueue.clear(leaderPushQueue)
|
|
199
|
+
yield* Deferred.succeed(rejectionObserved, undefined)
|
|
200
|
+
return true
|
|
201
|
+
}),
|
|
202
|
+
)
|
|
203
|
+
.pipe(
|
|
204
|
+
Effect.flatMap((shouldFence) =>
|
|
205
|
+
// Local commits remain synchronous and continue accumulating in pending/the FIFO,
|
|
206
|
+
// but this sole drain worker stays parked until pull reconciliation reseeds it.
|
|
207
|
+
shouldFence === true ? Effect.never : Effect.void,
|
|
208
|
+
),
|
|
209
|
+
),
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
}
|
|
133
213
|
}).pipe(
|
|
134
|
-
Effect.forever,
|
|
135
214
|
Effect.interruptible,
|
|
136
215
|
Effect.tapCauseLogPretty,
|
|
137
|
-
Effect.
|
|
216
|
+
Effect.catchCause((cause) => {
|
|
217
|
+
if (Cause.hasInterruptsOnly(cause) === true) return Effect.void
|
|
218
|
+
|
|
219
|
+
terminalPushCause ??= cause
|
|
220
|
+
return shutdownStarted === true
|
|
221
|
+
? Effect.void
|
|
222
|
+
: clientSession.shutdown(Exit.failCause(cause)).pipe(Effect.forkDetach, Effect.asVoid)
|
|
223
|
+
}),
|
|
138
224
|
)
|
|
139
225
|
|
|
140
|
-
yield* FiberHandle.run(
|
|
226
|
+
yield* FiberHandle.run(leaderPushingHandle, backgroundLeaderPushing)
|
|
141
227
|
|
|
142
228
|
// NOTE We need to lazily call `.pull` as we want the cursor to be updated
|
|
143
|
-
|
|
229
|
+
const backgroundPulling = Stream.suspend(() =>
|
|
144
230
|
clientSession.leaderThread.events.pull({ cursor: syncStateRef.current.upstreamHead }),
|
|
145
231
|
).pipe(
|
|
232
|
+
Stream.tap(() =>
|
|
233
|
+
clientSession.devtools.enabled === true ? clientSession.devtools.pullLatch.await : Effect.void,
|
|
234
|
+
),
|
|
146
235
|
Stream.tap(({ payload }) =>
|
|
147
236
|
Effect.gen(function* () {
|
|
148
237
|
// yield* Effect.logDebug('ClientSessionSyncProcessor:pull', payload)
|
|
149
238
|
|
|
150
|
-
|
|
151
|
-
yield* clientSession.devtools.pullLatch.await
|
|
152
|
-
}
|
|
153
|
-
|
|
239
|
+
const rejectionAtPullStart = unresolvedRejection
|
|
154
240
|
const mergeResult = yield* SyncState.merge({
|
|
155
241
|
syncState: syncStateRef.current,
|
|
156
242
|
payload,
|
|
157
|
-
|
|
243
|
+
isClientOnlyEvent,
|
|
158
244
|
isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
|
|
159
245
|
}).pipe(
|
|
160
|
-
Effect.
|
|
246
|
+
Effect.filterOrElse(
|
|
161
247
|
(r) => r._tag !== 'reject',
|
|
162
|
-
'Unexpected reject in client-session-sync-processor',
|
|
248
|
+
() => Effect.die(new Error('Unexpected reject in client-session-sync-processor')),
|
|
163
249
|
),
|
|
164
250
|
)
|
|
165
251
|
|
|
166
252
|
syncStateRef.current = mergeResult.newSyncState
|
|
167
253
|
|
|
254
|
+
const recoveredRejection =
|
|
255
|
+
rejectionAtPullStart !== undefined &&
|
|
256
|
+
unresolvedRejection === rejectionAtPullStart &&
|
|
257
|
+
isRejectedBatchRecovered(rejectionAtPullStart.events, mergeResult.newSyncState.pending) === true
|
|
258
|
+
|
|
168
259
|
if (mergeResult._tag === 'rebase') {
|
|
169
260
|
yield* Effect.spanEvent('merge:pull:rebase', {
|
|
170
261
|
payloadTag: payload._tag,
|
|
@@ -176,16 +267,10 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
176
267
|
|
|
177
268
|
debugInfo.rebaseCount++
|
|
178
269
|
|
|
179
|
-
|
|
270
|
+
// Barrier: before we interrupt the in-flight leader-push worker.
|
|
271
|
+
yield* rebaseBarrier('before_leader_push_fiber_interrupt')
|
|
180
272
|
|
|
181
|
-
yield* FiberHandle.clear(
|
|
182
|
-
|
|
183
|
-
if (SIMULATION_ENABLED === true) yield* simSleep('pull', '2_before_leader_push_queue_clear')
|
|
184
|
-
|
|
185
|
-
// Reset the leader push queue since we're rebasing and will push again
|
|
186
|
-
yield* BucketQueue.clear(leaderPushQueue)
|
|
187
|
-
|
|
188
|
-
if (SIMULATION_ENABLED === true) yield* simSleep('pull', '3_before_rebase_rollback')
|
|
273
|
+
yield* FiberHandle.clear(leaderPushingHandle)
|
|
189
274
|
|
|
190
275
|
if (LS_DEV === true) {
|
|
191
276
|
yield* Effect.logDebug(
|
|
@@ -195,6 +280,8 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
195
280
|
)
|
|
196
281
|
}
|
|
197
282
|
|
|
283
|
+
// Roll back the optimistic session changesets for the events this rebase discards.
|
|
284
|
+
// (Independent of the push queue below; order relative to the queue reconcile is irrelevant.)
|
|
198
285
|
for (let i = mergeResult.rollbackEvents.length - 1; i >= 0; i--) {
|
|
199
286
|
const event = mergeResult.rollbackEvents[i]!
|
|
200
287
|
if (event.meta.sessionChangeset._tag !== 'no-op' && event.meta.sessionChangeset._tag !== 'unset') {
|
|
@@ -202,14 +289,37 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
202
289
|
event.meta.sessionChangeset = { _tag: 'unset' }
|
|
203
290
|
}
|
|
204
291
|
}
|
|
292
|
+
if (mergeResult.rollbackEvents.length > 0) {
|
|
293
|
+
yield* stateHead
|
|
294
|
+
.set(mergeResult.rollbackEvents[0]!.parentSeqNum)
|
|
295
|
+
.pipe(Effect.mapError((cause) => MaterializeError.make({ cause })))
|
|
296
|
+
}
|
|
205
297
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
298
|
+
// Barrier: before the atomic queue reconciliation (the "discard + re-offer" step).
|
|
299
|
+
// A `push` admitted here appends its event to `syncStateRef.current.pending` AND to
|
|
300
|
+
// `leaderPushQueue`; the reconciliation below re-reads the current pending events, so
|
|
301
|
+
// that event is preserved rather than removed by the queue clear.
|
|
302
|
+
yield* rebaseBarrier('before_queue_reconcile')
|
|
303
|
+
|
|
304
|
+
// Atomic queue reconciliation. `push` runs via `Effect.runSyncWith` (a synchronous run,
|
|
305
|
+
// per the store's fully-synchronous commit contract), so it executes as an indivisible
|
|
306
|
+
// unit that can only interleave in THIS fiber's async gaps — never inside a synchronous
|
|
307
|
+
// stretch. By reading the current pending events, clearing, and re-offering with no async
|
|
308
|
+
// park between them, this block is atomic w.r.t. `push`. This replaces the blocking
|
|
309
|
+
// `pullReconciliationMutex` permit that previously forced `push` to wait: queue rebuilding
|
|
310
|
+
// and synchronous commits cannot interleave, without suspending the commit path.
|
|
311
|
+
//
|
|
312
|
+
// We re-read the current `syncStateRef.current.pending` instead of the stale
|
|
313
|
+
// `mergeResult.newSyncState.pending` snapshot captured at merge time, so any event a
|
|
314
|
+
// concurrent push appended during the async steps above (fiber interrupt / rollback /
|
|
315
|
+
// barriers) is included. `Effect.tx` commits the clear+offer as one transaction.
|
|
316
|
+
yield* reconcileLeaderPushQueue
|
|
317
|
+
|
|
318
|
+
// Barrier: before restarting the leader-push worker.
|
|
319
|
+
yield* rebaseBarrier('before_leader_push_fiber_run')
|
|
320
|
+
|
|
321
|
+
if (recoveredRejection === true) unresolvedRejection = undefined
|
|
322
|
+
yield* FiberHandle.run(leaderPushingHandle, backgroundLeaderPushing)
|
|
213
323
|
} else {
|
|
214
324
|
yield* Effect.spanEvent('merge:pull:advance', {
|
|
215
325
|
payloadTag: payload._tag,
|
|
@@ -219,11 +329,18 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
219
329
|
})
|
|
220
330
|
|
|
221
331
|
debugInfo.advanceCount++
|
|
332
|
+
|
|
333
|
+
if (recoveredRejection === true) {
|
|
334
|
+
yield* FiberHandle.clear(leaderPushingHandle)
|
|
335
|
+
yield* reconcileLeaderPushQueue
|
|
336
|
+
unresolvedRejection = undefined
|
|
337
|
+
yield* FiberHandle.run(leaderPushingHandle, backgroundLeaderPushing)
|
|
338
|
+
}
|
|
222
339
|
}
|
|
223
340
|
|
|
224
341
|
if (mergeResult.newEvents.length === 0) {
|
|
225
342
|
// If there are no new events, we need to update the sync state as well
|
|
226
|
-
yield*
|
|
343
|
+
yield* Queue.offer(syncStateUpdateQueue, mergeResult.newSyncState)
|
|
227
344
|
return
|
|
228
345
|
}
|
|
229
346
|
|
|
@@ -234,7 +351,6 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
234
351
|
sessionChangeset,
|
|
235
352
|
materializerHash,
|
|
236
353
|
} = yield* materializeEvent(event, {
|
|
237
|
-
withChangeset: true,
|
|
238
354
|
materializerHashLeader: event.meta.materializerHashLeader,
|
|
239
355
|
})
|
|
240
356
|
for (const table of newWriteTables) {
|
|
@@ -248,10 +364,11 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
248
364
|
refreshTables(writeTables)
|
|
249
365
|
|
|
250
366
|
// We're only triggering the sync state update after all events have been materialized
|
|
251
|
-
yield*
|
|
367
|
+
yield* Queue.offer(syncStateUpdateQueue, mergeResult.newSyncState)
|
|
252
368
|
}).pipe(
|
|
369
|
+
pullReconciliationMutex.withPermits(1),
|
|
253
370
|
Effect.tapCauseLogPretty,
|
|
254
|
-
Effect.
|
|
371
|
+
Effect.catchCause((cause) => clientSession.shutdown(Exit.failCause(cause))),
|
|
255
372
|
),
|
|
256
373
|
),
|
|
257
374
|
Stream.runDrain,
|
|
@@ -259,39 +376,82 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
259
376
|
Effect.interruptible,
|
|
260
377
|
Effect.withSpan('client-session-sync-processor:pull'),
|
|
261
378
|
Effect.tapCauseLogPretty,
|
|
262
|
-
Effect.forkScoped,
|
|
263
379
|
)
|
|
264
|
-
|
|
380
|
+
yield* FiberHandle.run(pullingHandle, backgroundPulling)
|
|
381
|
+
}).pipe(Effect.withSpan('client-session-sync-processor:boot'))
|
|
265
382
|
|
|
266
|
-
const
|
|
267
|
-
|
|
383
|
+
const runShutdown = Effect.fn('client-session-sync-processor:shutdown')(function* (
|
|
384
|
+
exit: Exit.Exit<unknown, unknown>,
|
|
268
385
|
) {
|
|
386
|
+
if (Exit.isFailure(exit) === true) {
|
|
387
|
+
if (pullingFiberHandle !== undefined) yield* FiberHandle.clear(pullingFiberHandle)
|
|
388
|
+
yield* pullReconciliationMutex.withPermits(1)(TxQueue.end(leaderPushQueue))
|
|
389
|
+
if (leaderPushingFiberHandle !== undefined) yield* FiberHandle.clear(leaderPushingFiberHandle)
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
yield* pullReconciliationMutex.withPermits(1)(
|
|
394
|
+
Effect.gen(function* () {
|
|
395
|
+
if (pullingFiberHandle !== undefined) yield* FiberHandle.clear(pullingFiberHandle)
|
|
396
|
+
yield* TxQueue.end(leaderPushQueue)
|
|
397
|
+
yield* Deferred.succeed(drainStartedSignal, undefined)
|
|
398
|
+
}),
|
|
399
|
+
)
|
|
400
|
+
if (leaderPushingFiberHandle !== undefined) {
|
|
401
|
+
if (unresolvedRejection === undefined) {
|
|
402
|
+
yield* FiberHandle.awaitEmpty(leaderPushingFiberHandle)
|
|
403
|
+
} else {
|
|
404
|
+
yield* FiberHandle.clear(leaderPushingFiberHandle)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (terminalPushCause !== undefined) return yield* Effect.failCause(terminalPushCause)
|
|
408
|
+
if (unresolvedRejection !== undefined) return yield* Effect.die(unresolvedRejection.error)
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
const shutdown: ClientSessionSyncProcessor['shutdown'] = (exit) =>
|
|
412
|
+
Effect.suspend(() => {
|
|
413
|
+
if (shutdownStarted === true) return Deferred.await(shutdownDone)
|
|
414
|
+
shutdownStarted = true
|
|
415
|
+
return runShutdown(exit).pipe(
|
|
416
|
+
Effect.exit,
|
|
417
|
+
Effect.tap((shutdownExit) => Deferred.done(shutdownDone, shutdownExit)),
|
|
418
|
+
Effect.forkDetach,
|
|
419
|
+
Effect.andThen(Deferred.await(shutdownDone)),
|
|
420
|
+
)
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
const encodeEvents: ClientSessionSyncProcessor['encodeEvents'] = Effect.fn(
|
|
424
|
+
'client-session-sync-processor:encode-events',
|
|
425
|
+
)(function* (events) {
|
|
269
426
|
let baseEventSequenceNumber = syncStateRef.current.localHead
|
|
270
427
|
return yield* Effect.forEach(events, ({ name, args }) =>
|
|
271
428
|
Effect.gen(function* () {
|
|
272
|
-
const eventDef = yield* Effect.
|
|
429
|
+
const eventDef = yield* Effect.fromNullishOr(schema.eventsDefsMap.get(name)).pipe(Effect.orDieDebugger)
|
|
273
430
|
const nextNumPair = EventSequenceNumber.Client.nextPair({
|
|
274
431
|
seqNum: baseEventSequenceNumber,
|
|
275
|
-
|
|
432
|
+
isClientOnly: eventDef.options.clientOnly,
|
|
276
433
|
rebaseGeneration: baseEventSequenceNumber.rebaseGeneration,
|
|
277
434
|
})
|
|
278
435
|
baseEventSequenceNumber = nextNumPair.seqNum
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
436
|
+
// Encoding known-valid domain data: an encode failure is an invariant violation (a defect),
|
|
437
|
+
// so `Effect.orDie` is the correct modeling — it keeps the typed error channel narrow.
|
|
438
|
+
const encoded = yield* Schema.encodeUnknownEffect(eventSchema)({
|
|
439
|
+
name,
|
|
440
|
+
// Client-document events expose SessionIdSymbol as an input placeholder, but encoded events are persisted
|
|
441
|
+
// and replayed by concrete id. Resolve during schema encoding so commit never mutates the caller's event.
|
|
442
|
+
args: resolveSessionIdSymbolInEventArgs(args, clientSession.sessionId),
|
|
443
|
+
...nextNumPair,
|
|
444
|
+
clientId: clientSession.clientId,
|
|
445
|
+
sessionId: clientSession.sessionId,
|
|
446
|
+
}).pipe(Effect.orDie)
|
|
447
|
+
return new LiveStoreEvent.Client.EncodedWithMeta(encoded)
|
|
288
448
|
}),
|
|
289
449
|
)
|
|
290
450
|
})
|
|
291
451
|
|
|
292
|
-
const materializeEvents: ClientSessionSyncProcessor['materializeEvents'] = Effect.fn(
|
|
293
|
-
events,
|
|
294
|
-
) {
|
|
452
|
+
const materializeEvents: ClientSessionSyncProcessor['materializeEvents'] = Effect.fn(
|
|
453
|
+
'client-session-sync-processor:materialize-events',
|
|
454
|
+
)(function* (events) {
|
|
295
455
|
const writeTables = new Set<string>()
|
|
296
456
|
for (const event of events) {
|
|
297
457
|
const {
|
|
@@ -299,7 +459,6 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
299
459
|
sessionChangeset,
|
|
300
460
|
materializerHash,
|
|
301
461
|
} = yield* materializeEvent(event, {
|
|
302
|
-
withChangeset: true,
|
|
303
462
|
materializerHashLeader: Option.none(),
|
|
304
463
|
})
|
|
305
464
|
for (const table of newWriteTables) {
|
|
@@ -311,35 +470,43 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
311
470
|
return { writeTables }
|
|
312
471
|
})
|
|
313
472
|
|
|
314
|
-
const push: ClientSessionSyncProcessor['push'] = Effect.fn('client-session-sync-processor:push')(
|
|
315
|
-
encodedEvents
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
|
|
322
|
-
}).pipe(
|
|
323
|
-
Effect.filterOrDieMessage(
|
|
324
|
-
(r) => r._tag === 'advance',
|
|
325
|
-
'Expected advance from local-push merge',
|
|
326
|
-
),
|
|
327
|
-
)
|
|
473
|
+
const push: ClientSessionSyncProcessor['push'] = Effect.fn('client-session-sync-processor:push')(
|
|
474
|
+
function* (encodedEvents) {
|
|
475
|
+
if (shutdownStarted === true) {
|
|
476
|
+
return yield* Effect.die(
|
|
477
|
+
new Error('Cannot push events after the client session sync processor starts shutting down'),
|
|
478
|
+
)
|
|
479
|
+
}
|
|
328
480
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
481
|
+
const mergeResult = yield* SyncState.merge({
|
|
482
|
+
syncState: syncStateRef.current,
|
|
483
|
+
payload: { _tag: 'local-push', newEvents: encodedEvents },
|
|
484
|
+
isClientOnlyEvent,
|
|
485
|
+
isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
|
|
486
|
+
}).pipe(
|
|
487
|
+
Effect.filterMapOrElse(Filter.tagged<typeof SyncState.MergeResult.Type>()('advance'), () =>
|
|
488
|
+
Effect.die(new Error('Expected advance from local-push merge')),
|
|
489
|
+
),
|
|
490
|
+
)
|
|
338
491
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
492
|
+
yield* Effect.annotateCurrentSpan({
|
|
493
|
+
batchSize: encodedEvents.length,
|
|
494
|
+
mergeResultTag: mergeResult._tag,
|
|
495
|
+
eventCounts: encodedEvents.reduce<Record<string, number>>((acc, event) => {
|
|
496
|
+
acc[event.name] = (acc[event.name] ?? 0) + 1
|
|
497
|
+
return acc
|
|
498
|
+
}, {}),
|
|
499
|
+
...(TRACE_VERBOSE === true ? { mergeResult: jsonStringify(mergeResult) } : {}),
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
syncStateRef.current = mergeResult.newSyncState
|
|
503
|
+
yield* Queue.offer(syncStateUpdateQueue, mergeResult.newSyncState)
|
|
504
|
+
const rejectedEvents = yield* TxQueue.offerAll(leaderPushQueue, mergeResult.newEvents)
|
|
505
|
+
if (rejectedEvents.length > 0) {
|
|
506
|
+
return yield* Effect.die(new Error('Leader push queue closed while accepting events'))
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
)
|
|
343
510
|
|
|
344
511
|
const debugInfo = {
|
|
345
512
|
rebaseCount: 0,
|
|
@@ -349,6 +516,7 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
349
516
|
|
|
350
517
|
return {
|
|
351
518
|
boot,
|
|
519
|
+
shutdown,
|
|
352
520
|
encodeEvents,
|
|
353
521
|
materializeEvents,
|
|
354
522
|
push,
|
|
@@ -357,13 +525,14 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
357
525
|
changes: Stream.fromQueue(syncStateUpdateQueue),
|
|
358
526
|
}),
|
|
359
527
|
debug: {
|
|
528
|
+
awaitDrainStarted: Deferred.await(drainStartedSignal),
|
|
529
|
+
awaitRejection: Deferred.await(rejectionObserved),
|
|
360
530
|
print: () =>
|
|
361
531
|
Effect.gen(function* () {
|
|
362
532
|
console.log('debugInfo', debugInfo)
|
|
363
533
|
console.log('syncState', syncStateRef.current)
|
|
364
|
-
const
|
|
365
|
-
console.log('pushQueueSize',
|
|
366
|
-
const pushQueueItems = yield* BucketQueue.peekAll(leaderPushQueue)
|
|
534
|
+
const pushQueueItems = yield* snapshotTxQueue(leaderPushQueue)
|
|
535
|
+
console.log('pushQueueSize', pushQueueItems.length)
|
|
367
536
|
console.log(
|
|
368
537
|
'pushQueueItems',
|
|
369
538
|
pushQueueItems.map((_) => _.toJSON()),
|
|
@@ -374,14 +543,33 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
|
|
|
374
543
|
} satisfies ClientSessionSyncProcessor
|
|
375
544
|
})
|
|
376
545
|
|
|
546
|
+
const snapshotTxQueue = <A, E>(queue: TxQueue.TxQueue<A, E>): Effect.Effect<ReadonlyArray<A>, E> =>
|
|
547
|
+
Effect.tx(
|
|
548
|
+
Effect.gen(function* () {
|
|
549
|
+
if ((yield* TxQueue.isOpen(queue)) === false) return []
|
|
550
|
+
|
|
551
|
+
const items = yield* TxQueue.clear(queue)
|
|
552
|
+
yield* TxQueue.offerAll(queue, items)
|
|
553
|
+
return items
|
|
554
|
+
}),
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
const isRejectedBatchRecovered = (
|
|
558
|
+
rejectedEvents: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
|
|
559
|
+
pendingEvents: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
|
|
560
|
+
): boolean =>
|
|
561
|
+
rejectedEvents.every(
|
|
562
|
+
(rejectedEvent) =>
|
|
563
|
+
pendingEvents.some((pendingEvent) => LiveStoreEvent.Client.isEqualEncoded(pendingEvent, rejectedEvent)) === false,
|
|
564
|
+
)
|
|
565
|
+
|
|
377
566
|
export interface ClientSessionSyncProcessor {
|
|
378
567
|
boot: Effect.Effect<void, never, Scope.Scope>
|
|
568
|
+
shutdown: (exit: Exit.Exit<unknown, unknown>) => Effect.Effect<void>
|
|
379
569
|
encodeEvents: (
|
|
380
570
|
events: ReadonlyArray<LiveStoreEvent.Input.Decoded>,
|
|
381
571
|
) => Effect.Effect<ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>>
|
|
382
|
-
push: (
|
|
383
|
-
events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
|
|
384
|
-
) => Effect.Effect<void>
|
|
572
|
+
push: (events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>) => Effect.Effect<void>
|
|
385
573
|
materializeEvents: (
|
|
386
574
|
events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
|
|
387
575
|
) => Effect.Effect<{ writeTables: Set<string> }, MaterializeError>
|
|
@@ -390,6 +578,8 @@ export interface ClientSessionSyncProcessor {
|
|
|
390
578
|
*/
|
|
391
579
|
syncState: Subscribable.Subscribable<SyncState.SyncState>
|
|
392
580
|
debug: {
|
|
581
|
+
awaitDrainStarted: Effect.Effect<void>
|
|
582
|
+
awaitRejection: Effect.Effect<void>
|
|
393
583
|
print: () => void
|
|
394
584
|
debugInfo: () => {
|
|
395
585
|
rebaseCount: number
|
|
@@ -398,17 +588,11 @@ export interface ClientSessionSyncProcessor {
|
|
|
398
588
|
}
|
|
399
589
|
}
|
|
400
590
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
export
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
'3_before_rebase_rollback': Schema.Int.pipe(Schema.between(0, 15)),
|
|
410
|
-
'4_before_leader_push_queue_offer': Schema.Int.pipe(Schema.between(0, 15)),
|
|
411
|
-
'5_before_leader_push_fiber_run': Schema.Int.pipe(Schema.between(0, 15)),
|
|
412
|
-
}),
|
|
413
|
-
})
|
|
414
|
-
type ClientSessionSyncProcessorSimulationParams = typeof ClientSessionSyncProcessorSimulationParams.Type
|
|
591
|
+
/**
|
|
592
|
+
* Injection points inside the rebase critical section where a test-only barrier may be awaited.
|
|
593
|
+
* Named for the step they precede so the deterministic no-loss tests can target the exact window.
|
|
594
|
+
*/
|
|
595
|
+
export type RebaseBarrierPoint =
|
|
596
|
+
| 'before_leader_push_fiber_interrupt'
|
|
597
|
+
| 'before_queue_reconcile'
|
|
598
|
+
| 'before_leader_push_fiber_run'
|