@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
package/src/sync/errors.ts
CHANGED
|
@@ -2,14 +2,15 @@ import { Schema } from '@livestore/utils/effect'
|
|
|
2
2
|
|
|
3
3
|
import { EventSequenceNumber } from '../schema/mod.ts'
|
|
4
4
|
|
|
5
|
-
export class IsOfflineError extends Schema.TaggedError<IsOfflineError>(
|
|
6
|
-
'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
5
|
+
export class IsOfflineError extends Schema.TaggedError<IsOfflineError>('~@livestore/common/IsOfflineError')(
|
|
6
|
+
'IsOfflineError',
|
|
7
|
+
{
|
|
8
|
+
cause: Schema.Defect(),
|
|
9
|
+
},
|
|
10
|
+
) {}
|
|
10
11
|
|
|
11
12
|
/** Unique ID generated by the backend when its created. Used to check whether the backend identity has changed. */
|
|
12
|
-
export const BackendId = Schema.String.
|
|
13
|
+
export const BackendId = Schema.String.annotate({ title: '@livestore/sync-cf:BackendId' })
|
|
13
14
|
|
|
14
15
|
export class BackendIdMismatchError extends Schema.TaggedError<BackendIdMismatchError>(
|
|
15
16
|
'~@livestore/common/BackendIdMismatchError',
|
|
@@ -18,9 +19,10 @@ export class BackendIdMismatchError extends Schema.TaggedError<BackendIdMismatch
|
|
|
18
19
|
received: BackendId,
|
|
19
20
|
}) {}
|
|
20
21
|
|
|
21
|
-
export class ServerAheadError extends Schema.TaggedError<ServerAheadError>(
|
|
22
|
-
'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
22
|
+
export class ServerAheadError extends Schema.TaggedError<ServerAheadError>('~@livestore/common/ServerAheadError')(
|
|
23
|
+
'ServerAheadError',
|
|
24
|
+
{
|
|
25
|
+
minimumExpectedNum: EventSequenceNumber.Global.Schema,
|
|
26
|
+
providedNum: EventSequenceNumber.Global.Schema,
|
|
27
|
+
},
|
|
28
|
+
) {}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
2
|
+
import { Effect, Fiber, Option, Stream } from '@livestore/utils/effect'
|
|
3
|
+
|
|
4
|
+
import { EventSequenceNumber, type LiveStoreEvent } from '../schema/mod.ts'
|
|
5
|
+
import { makeMockSyncBackend } from './mock-sync-backend.ts'
|
|
6
|
+
|
|
7
|
+
Vitest.describe('makeMockSyncBackend', () => {
|
|
8
|
+
Vitest.live('broadcasts live events to every backend connection', () =>
|
|
9
|
+
Effect.gen(function* () {
|
|
10
|
+
const mockBackend = yield* makeMockSyncBackend({ startConnected: true })
|
|
11
|
+
const backendA = yield* mockBackend.makeSyncBackend
|
|
12
|
+
const backendB = yield* mockBackend.makeSyncBackend
|
|
13
|
+
const nextBatch = (backend: typeof backendA) =>
|
|
14
|
+
backend.pull(Option.none(), { live: true }).pipe(
|
|
15
|
+
Stream.filter((item) => item.batch.length > 0),
|
|
16
|
+
Stream.runFirstUnsafe,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
const pullA = yield* nextBatch(backendA).pipe(Effect.forkScoped)
|
|
20
|
+
const pullB = yield* nextBatch(backendB).pipe(Effect.forkScoped)
|
|
21
|
+
const event = makeEvent(1)
|
|
22
|
+
|
|
23
|
+
yield* mockBackend.advance(event)
|
|
24
|
+
|
|
25
|
+
Vitest.expect((yield* Fiber.join(pullA)).batch.map((item) => item.eventEncoded)).toEqual([event])
|
|
26
|
+
Vitest.expect((yield* Fiber.join(pullB)).batch.map((item) => item.eventEncoded)).toEqual([event])
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
Vitest.live('seeds a new backend connection with existing live events', () =>
|
|
31
|
+
Effect.gen(function* () {
|
|
32
|
+
const mockBackend = yield* makeMockSyncBackend({ startConnected: true })
|
|
33
|
+
const event = makeEvent(1)
|
|
34
|
+
yield* mockBackend.advance(event)
|
|
35
|
+
|
|
36
|
+
const backend = yield* mockBackend.makeSyncBackend
|
|
37
|
+
const item = yield* backend.pull(Option.none(), { live: true }).pipe(
|
|
38
|
+
Stream.filter((item) => item.batch.length > 0),
|
|
39
|
+
Stream.runFirstUnsafe,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
Vitest.expect(item.batch.map((entry) => entry.eventEncoded)).toEqual([event])
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
Vitest.live('filters seeded live events against the requested cursor', () =>
|
|
47
|
+
Effect.gen(function* () {
|
|
48
|
+
const mockBackend = yield* makeMockSyncBackend({ startConnected: true })
|
|
49
|
+
const existing = makeEvent(1)
|
|
50
|
+
const next = makeEvent(2)
|
|
51
|
+
yield* mockBackend.advance(existing)
|
|
52
|
+
|
|
53
|
+
const backend = yield* mockBackend.makeSyncBackend
|
|
54
|
+
const cursor = Option.some({
|
|
55
|
+
eventSequenceNumber: existing.seqNum,
|
|
56
|
+
metadata: Option.none(),
|
|
57
|
+
})
|
|
58
|
+
const pull = yield* backend.pull(cursor, { live: true }).pipe(
|
|
59
|
+
Stream.filter((item) => item.batch.length > 0),
|
|
60
|
+
Stream.runFirstUnsafe,
|
|
61
|
+
Effect.forkScoped,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
yield* mockBackend.advance(next)
|
|
65
|
+
|
|
66
|
+
Vitest.expect((yield* Fiber.join(pull)).batch.map((entry) => entry.eventEncoded)).toEqual([next])
|
|
67
|
+
}),
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const makeEvent = (seqNum: number): LiveStoreEvent.Global.Encoded => ({
|
|
72
|
+
name: 'v1.TestEvent',
|
|
73
|
+
args: { id: `event-${seqNum}` },
|
|
74
|
+
seqNum: EventSequenceNumber.Global.make(seqNum),
|
|
75
|
+
parentSeqNum: EventSequenceNumber.Global.make(seqNum - 1),
|
|
76
|
+
clientId: 'client-a',
|
|
77
|
+
sessionId: 'session-a',
|
|
78
|
+
})
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
type Schema,
|
|
3
|
+
type Scope,
|
|
4
|
+
Effect,
|
|
5
|
+
Option,
|
|
6
|
+
Queue,
|
|
7
|
+
Ref,
|
|
8
|
+
Semaphore,
|
|
9
|
+
Stream,
|
|
10
|
+
SubscriptionRef,
|
|
11
|
+
} from '@livestore/utils/effect'
|
|
3
12
|
|
|
4
13
|
import { UnknownError } from '../errors.ts'
|
|
5
14
|
import { EventSequenceNumber, type LiveStoreEvent } from '../schema/mod.ts'
|
|
6
|
-
import {
|
|
15
|
+
import type { BackendIdMismatchError, ServerAheadError } from './errors.ts'
|
|
7
16
|
import * as SyncBackend from './sync-backend.ts'
|
|
8
17
|
import { validatePushPayload } from './validate-push-payload.ts'
|
|
9
18
|
|
|
@@ -40,7 +49,7 @@ export const makeMockSyncBackend = (
|
|
|
40
49
|
): Effect.Effect<MockSyncBackend, UnknownError, Scope.Scope> =>
|
|
41
50
|
Effect.gen(function* () {
|
|
42
51
|
const span = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
43
|
-
const semaphore = yield*
|
|
52
|
+
const semaphore = yield* Semaphore.make(1)
|
|
44
53
|
|
|
45
54
|
// State refs
|
|
46
55
|
const syncHeadRef = yield* Ref.make(EventSequenceNumber.Client.ROOT.global)
|
|
@@ -48,12 +57,22 @@ export const makeMockSyncBackend = (
|
|
|
48
57
|
const syncIsConnectedRef = yield* SubscriptionRef.make(options?.startConnected ?? false)
|
|
49
58
|
|
|
50
59
|
// Queues for streaming
|
|
51
|
-
const
|
|
52
|
-
const pushedEventsQueue = yield*
|
|
60
|
+
const syncPullQueues = new Set<Queue.Queue<LiveStoreEvent.Global.Encoded>>()
|
|
61
|
+
const pushedEventsQueue = yield* Queue.unbounded<LiveStoreEvent.Global.Encoded>()
|
|
62
|
+
|
|
63
|
+
yield* Effect.addFinalizer(() =>
|
|
64
|
+
Effect.gen(function* () {
|
|
65
|
+
for (const queue of syncPullQueues) yield* Queue.shutdown(queue)
|
|
66
|
+
syncPullQueues.clear()
|
|
67
|
+
}),
|
|
68
|
+
)
|
|
53
69
|
|
|
54
70
|
// Failure simulation state
|
|
55
71
|
const failPushRef = yield* Ref.make<
|
|
56
|
-
FailureState<
|
|
72
|
+
FailureState<
|
|
73
|
+
UnknownError | ServerAheadError | BackendIdMismatchError,
|
|
74
|
+
[ReadonlyArray<LiveStoreEvent.Global.Encoded>]
|
|
75
|
+
>
|
|
57
76
|
>({ remaining: 0, error: undefined })
|
|
58
77
|
const failPullRef = yield* Ref.make<FailureState<UnknownError | BackendIdMismatchError, []>>({
|
|
59
78
|
remaining: 0,
|
|
@@ -85,27 +104,12 @@ export const makeMockSyncBackend = (
|
|
|
85
104
|
|
|
86
105
|
const pullNonLive = (cursor: Option.Option<{ eventSequenceNumber: EventSequenceNumber.Global.Type }>) =>
|
|
87
106
|
Effect.gen(function* () {
|
|
88
|
-
const lastSeen =
|
|
89
|
-
onNone: () => EventSequenceNumber.Client.ROOT.global,
|
|
90
|
-
onSome: (_) => _.eventSequenceNumber,
|
|
91
|
-
})
|
|
107
|
+
const lastSeen = cursorPosition(cursor)
|
|
92
108
|
const allEvents = yield* Ref.get(allEventsRef)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
for (let i = 0; i < slice.length; i += nonLiveChunkSize) {
|
|
98
|
-
const end = Math.min(i + nonLiveChunkSize, slice.length)
|
|
99
|
-
chunks.push({
|
|
100
|
-
events: slice.slice(i, end),
|
|
101
|
-
remaining: Math.max(slice.length - end, 0),
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
// Always return at least one empty chunk
|
|
105
|
-
if (chunks.length === 0) {
|
|
106
|
-
chunks.push({ events: [], remaining: 0 })
|
|
107
|
-
}
|
|
108
|
-
return chunks
|
|
109
|
+
return chunkEvents(
|
|
110
|
+
allEvents.filter((event) => event.seqNum > lastSeen),
|
|
111
|
+
nonLiveChunkSize,
|
|
112
|
+
)
|
|
109
113
|
}).pipe(
|
|
110
114
|
Effect.map((chunks) =>
|
|
111
115
|
Stream.fromIterable(chunks).pipe(
|
|
@@ -119,18 +123,38 @@ export const makeMockSyncBackend = (
|
|
|
119
123
|
Stream.flatten(),
|
|
120
124
|
)
|
|
121
125
|
|
|
122
|
-
const pullLive = Stream.concat(
|
|
123
|
-
Stream.make(SyncBackend.pullResItemEmpty()),
|
|
124
|
-
Stream.fromQueue(syncPullQueue).pipe(
|
|
125
|
-
Stream.chunks,
|
|
126
|
-
Stream.map((chunk) => ({
|
|
127
|
-
batch: [...chunk].map((eventEncoded) => ({ eventEncoded, metadata: Option.none() })),
|
|
128
|
-
pageInfo: SyncBackend.pageInfoNoMore,
|
|
129
|
-
})),
|
|
130
|
-
),
|
|
131
|
-
)
|
|
132
|
-
|
|
133
126
|
const makeSyncBackend = Effect.gen(function* () {
|
|
127
|
+
// Every backend connection needs its own live cursor. A shared queue would
|
|
128
|
+
// load-balance events across Clients instead of broadcasting the log.
|
|
129
|
+
const syncPullQueue = yield* Effect.acquireRelease(
|
|
130
|
+
Queue.unbounded<LiveStoreEvent.Global.Encoded>(),
|
|
131
|
+
Queue.shutdown,
|
|
132
|
+
)
|
|
133
|
+
yield* semaphore.withPermits(1)(
|
|
134
|
+
Effect.gen(function* () {
|
|
135
|
+
const existingEvents = yield* Ref.get(allEventsRef)
|
|
136
|
+
syncPullQueues.add(syncPullQueue)
|
|
137
|
+
yield* Queue.offerAll(syncPullQueue, existingEvents)
|
|
138
|
+
}),
|
|
139
|
+
)
|
|
140
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => syncPullQueues.delete(syncPullQueue)))
|
|
141
|
+
|
|
142
|
+
const pullLive = (cursor: Option.Option<{ eventSequenceNumber: EventSequenceNumber.Global.Type }>) => {
|
|
143
|
+
const lastSeen = cursorPosition(cursor)
|
|
144
|
+
return Stream.concat(
|
|
145
|
+
Stream.make(SyncBackend.pullResItemEmpty()),
|
|
146
|
+
Stream.fromQueue(syncPullQueue).pipe(
|
|
147
|
+
Stream.chunks,
|
|
148
|
+
Stream.map((chunk) => ({
|
|
149
|
+
batch: [...chunk]
|
|
150
|
+
.filter((eventEncoded) => eventEncoded.seqNum > lastSeen)
|
|
151
|
+
.map((eventEncoded) => ({ eventEncoded, metadata: Option.none() })),
|
|
152
|
+
pageInfo: SyncBackend.pageInfoNoMore,
|
|
153
|
+
})),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
134
158
|
// TODO consider making offline state actively error pull/push.
|
|
135
159
|
// Currently, offline only reflects in `isConnected`, while operations still succeed,
|
|
136
160
|
// mirroring how some real providers behave during transient disconnects.
|
|
@@ -145,7 +169,7 @@ export const makeMockSyncBackend = (
|
|
|
145
169
|
new UnknownError({ cause: new Error('MockSyncBackend: simulated pull failure') }),
|
|
146
170
|
),
|
|
147
171
|
).pipe(
|
|
148
|
-
Stream.flatMap(() => (pullOptions?.live === true ? pullLive : pullNonLive(cursor))),
|
|
172
|
+
Stream.flatMap(() => (pullOptions?.live === true ? pullLive(cursor) : pullNonLive(cursor))),
|
|
149
173
|
Stream.withSpan('MockSyncBackend:pull', { parent: span }),
|
|
150
174
|
),
|
|
151
175
|
push: (batch) =>
|
|
@@ -161,8 +185,11 @@ export const makeMockSyncBackend = (
|
|
|
161
185
|
|
|
162
186
|
yield* Effect.sleep(10).pipe(Effect.withSpan('MockSyncBackend:push:sleep')) // Simulate network latency
|
|
163
187
|
|
|
164
|
-
yield*
|
|
165
|
-
yield*
|
|
188
|
+
yield* Queue.offerAll(pushedEventsQueue, batch)
|
|
189
|
+
yield* Effect.forEach(syncPullQueues, (queue) => Queue.offerAll(queue, batch), {
|
|
190
|
+
concurrency: 'unbounded',
|
|
191
|
+
discard: true,
|
|
192
|
+
})
|
|
166
193
|
yield* Ref.update(allEventsRef, (events) => events.concat(batch))
|
|
167
194
|
yield* Ref.set(syncHeadRef, batch.at(-1)!.seqNum)
|
|
168
195
|
}).pipe(
|
|
@@ -187,7 +214,10 @@ export const makeMockSyncBackend = (
|
|
|
187
214
|
Effect.gen(function* () {
|
|
188
215
|
yield* Ref.set(syncHeadRef, batch.at(-1)!.seqNum)
|
|
189
216
|
yield* Ref.update(allEventsRef, (events) => events.concat(batch))
|
|
190
|
-
yield*
|
|
217
|
+
yield* Effect.forEach(syncPullQueues, (queue) => Queue.offerAll(queue, batch), {
|
|
218
|
+
concurrency: 'unbounded',
|
|
219
|
+
discard: true,
|
|
220
|
+
})
|
|
191
221
|
}).pipe(
|
|
192
222
|
Effect.withSpan('MockSyncBackend:advance', {
|
|
193
223
|
parent: span,
|
|
@@ -203,13 +233,11 @@ export const makeMockSyncBackend = (
|
|
|
203
233
|
) => Effect.Effect<never, UnknownError | ServerAheadError | BackendIdMismatchError>,
|
|
204
234
|
) => Ref.set(failPushRef, { remaining: count, error })
|
|
205
235
|
|
|
206
|
-
const failNextPulls = (
|
|
207
|
-
|
|
208
|
-
error?: () => Effect.Effect<never, UnknownError | BackendIdMismatchError>,
|
|
209
|
-
) => Ref.set(failPullRef, { remaining: count, error })
|
|
236
|
+
const failNextPulls = (count: number, error?: () => Effect.Effect<never, UnknownError | BackendIdMismatchError>) =>
|
|
237
|
+
Ref.set(failPullRef, { remaining: count, error })
|
|
210
238
|
|
|
211
239
|
return {
|
|
212
|
-
pushedEvents:
|
|
240
|
+
pushedEvents: Stream.fromQueue(pushedEventsQueue),
|
|
213
241
|
connect: SubscriptionRef.set(syncIsConnectedRef, true),
|
|
214
242
|
disconnect: SubscriptionRef.set(syncIsConnectedRef, false),
|
|
215
243
|
makeSyncBackend,
|
|
@@ -224,3 +252,21 @@ interface FailureState<E, Args extends unknown[]> {
|
|
|
224
252
|
remaining: number
|
|
225
253
|
error: ((...args: Args) => Effect.Effect<never, E>) | undefined
|
|
226
254
|
}
|
|
255
|
+
|
|
256
|
+
const cursorPosition = (
|
|
257
|
+
cursor: Option.Option<{ eventSequenceNumber: EventSequenceNumber.Global.Type }>,
|
|
258
|
+
): EventSequenceNumber.Global.Type =>
|
|
259
|
+
Option.match(cursor, {
|
|
260
|
+
onNone: () => EventSequenceNumber.Client.ROOT.global,
|
|
261
|
+
onSome: (_) => _.eventSequenceNumber,
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
const chunkEvents = (events: ReadonlyArray<LiveStoreEvent.Global.Encoded>, chunkSize: number) => {
|
|
265
|
+
const chunks: Array<{ events: ReadonlyArray<LiveStoreEvent.Global.Encoded>; remaining: number }> = []
|
|
266
|
+
for (let index = 0; index < events.length; index += chunkSize) {
|
|
267
|
+
const end = Math.min(index + chunkSize, events.length)
|
|
268
|
+
chunks.push({ events: events.slice(index, end), remaining: Math.max(events.length - end, 0) })
|
|
269
|
+
}
|
|
270
|
+
if (chunks.length === 0) chunks.push({ events: [], remaining: 0 })
|
|
271
|
+
return chunks
|
|
272
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
|
-
import { Graph } from '@livestore/utils/effect'
|
|
2
|
+
import { Graph, Option } from '@livestore/utils/effect'
|
|
3
3
|
|
|
4
4
|
import type { EventDefFactsGroup } from '../../schema/EventDef/mod.ts'
|
|
5
5
|
import * as EventSequenceNumber from '../../schema/EventSequenceNumber/mod.ts'
|
|
@@ -60,6 +60,8 @@ export class HistoryDag {
|
|
|
60
60
|
private readonly options: HistoryDagOptions
|
|
61
61
|
private readonly idToIndex: Map<string, Graph.NodeIndex>
|
|
62
62
|
private readonly indexToId: Map<Graph.NodeIndex, string>
|
|
63
|
+
private readonly incomingEdges: Map<Graph.NodeIndex, Set<Graph.EdgeIndex>>
|
|
64
|
+
private readonly outgoingEdges: Map<Graph.NodeIndex, Set<Graph.EdgeIndex>>
|
|
63
65
|
private readonly graph: Graph.MutableDirectedGraph<HistoryDagNode, HistoryDagEdgeAttributes>
|
|
64
66
|
|
|
65
67
|
private constructor({
|
|
@@ -77,6 +79,8 @@ export class HistoryDag {
|
|
|
77
79
|
this.options = { ...defaultOptions, ...options }
|
|
78
80
|
this.idToIndex = idToIndex !== undefined ? new Map(idToIndex) : new Map()
|
|
79
81
|
this.indexToId = indexToId !== undefined ? new Map(indexToId) : new Map()
|
|
82
|
+
this.incomingEdges = new Map()
|
|
83
|
+
this.outgoingEdges = new Map()
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
static create(options?: Partial<HistoryDagOptions>): HistoryDag {
|
|
@@ -88,11 +92,11 @@ export class HistoryDag {
|
|
|
88
92
|
const clone = HistoryDag.create(this.options)
|
|
89
93
|
|
|
90
94
|
for (const [id, index] of this.idToIndex) {
|
|
91
|
-
const node = this.
|
|
95
|
+
const node = this.nodeAt(index, `HistoryDag.copy missing node for ${id}`)
|
|
92
96
|
clone.addNode(id, cloneHistoryDagNode(node))
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
for (const edge of this.graph
|
|
99
|
+
for (const [, edge] of Graph.edges(this.graph)) {
|
|
96
100
|
const sourceId = this.indexToId.get(edge.source) ?? shouldNeverHappen('HistoryDag.copy missing source id')
|
|
97
101
|
const targetId = this.indexToId.get(edge.target) ?? shouldNeverHappen('HistoryDag.copy missing target id')
|
|
98
102
|
clone.addEdge(sourceId, targetId, { ...edge.data })
|
|
@@ -117,6 +121,8 @@ export class HistoryDag {
|
|
|
117
121
|
const nodeIndex = Graph.addNode(this.graph, attributes)
|
|
118
122
|
this.idToIndex.set(id, nodeIndex)
|
|
119
123
|
this.indexToId.set(nodeIndex, id)
|
|
124
|
+
this.incomingEdges.set(nodeIndex, new Set())
|
|
125
|
+
this.outgoingEdges.set(nodeIndex, new Set())
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
hasNode(id: string): boolean {
|
|
@@ -129,8 +135,7 @@ export class HistoryDag {
|
|
|
129
135
|
return shouldNeverHappen(`HistoryDag node ${id} not found`)
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
|
|
133
|
-
return node ?? shouldNeverHappen(`HistoryDag node data missing for ${id}`)
|
|
138
|
+
return this.nodeAt(index, `HistoryDag node data missing for ${id}`)
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
nodes(): IterableIterator<string> {
|
|
@@ -140,7 +145,7 @@ export class HistoryDag {
|
|
|
140
145
|
nodeEntries(): IterableIterator<{ key: string; attributes: HistoryDagNode }> {
|
|
141
146
|
return function* (this: HistoryDag) {
|
|
142
147
|
for (const [id, index] of this.idToIndex) {
|
|
143
|
-
const attributes = this.
|
|
148
|
+
const attributes = this.nodeAt(index, `HistoryDag node data missing for ${id}`)
|
|
144
149
|
yield { key: id, attributes }
|
|
145
150
|
}
|
|
146
151
|
}.call(this)
|
|
@@ -158,7 +163,15 @@ export class HistoryDag {
|
|
|
158
163
|
return shouldNeverHappen(`HistoryDag edge references unknown nodes: ${sourceId} -> ${targetId}`)
|
|
159
164
|
}
|
|
160
165
|
|
|
161
|
-
|
|
166
|
+
const edgeIndex = Graph.addEdge(this.graph, sourceIndex, targetIndex, attributes)
|
|
167
|
+
const outgoingEdges = this.outgoingEdges.get(sourceIndex)
|
|
168
|
+
const incomingEdges = this.incomingEdges.get(targetIndex)
|
|
169
|
+
if (outgoingEdges === undefined || incomingEdges === undefined) {
|
|
170
|
+
return shouldNeverHappen(`HistoryDag edge index missing for ${sourceId} -> ${targetId}`)
|
|
171
|
+
}
|
|
172
|
+
outgoingEdges.add(edgeIndex)
|
|
173
|
+
incomingEdges.add(edgeIndex)
|
|
174
|
+
return edgeIndex
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
edges(sourceId: string, targetId: string): Array<Graph.EdgeIndex> {
|
|
@@ -169,14 +182,9 @@ export class HistoryDag {
|
|
|
169
182
|
return []
|
|
170
183
|
}
|
|
171
184
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return adjacency.filter((edgeIndex) => {
|
|
178
|
-
const edge = this.graph.edges.get(edgeIndex)
|
|
179
|
-
return edge !== undefined && edge.target === targetIndex
|
|
185
|
+
return Array.from(this.outgoingEdges.get(sourceIndex) ?? []).filter((edgeIndex) => {
|
|
186
|
+
const edge = Option.getOrUndefined(Graph.getEdge(this.graph, edgeIndex))
|
|
187
|
+
return edge?.target === targetIndex
|
|
180
188
|
})
|
|
181
189
|
}
|
|
182
190
|
|
|
@@ -185,8 +193,7 @@ export class HistoryDag {
|
|
|
185
193
|
if (index === undefined) {
|
|
186
194
|
return []
|
|
187
195
|
}
|
|
188
|
-
|
|
189
|
-
return incoming !== undefined ? [...incoming] : []
|
|
196
|
+
return [...(this.incomingEdges.get(index) ?? [])]
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
outboundEdgeEntries(id: string): Array<HistoryDagEdgeEntry> {
|
|
@@ -195,12 +202,7 @@ export class HistoryDag {
|
|
|
195
202
|
return []
|
|
196
203
|
}
|
|
197
204
|
|
|
198
|
-
|
|
199
|
-
if (adjacency === undefined) {
|
|
200
|
-
return []
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return adjacency
|
|
205
|
+
return [...(this.outgoingEdges.get(index) ?? [])]
|
|
204
206
|
.map((edgeIndex) => this.edgeEntry(edgeIndex))
|
|
205
207
|
.filter((entry): entry is HistoryDagEdgeEntry => entry !== undefined)
|
|
206
208
|
}
|
|
@@ -211,19 +213,13 @@ export class HistoryDag {
|
|
|
211
213
|
return []
|
|
212
214
|
}
|
|
213
215
|
|
|
214
|
-
|
|
215
|
-
if (adjacency === undefined) {
|
|
216
|
-
return []
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return adjacency
|
|
216
|
+
return [...(this.incomingEdges.get(index) ?? [])]
|
|
220
217
|
.map((edgeIndex) => this.edgeEntry(edgeIndex))
|
|
221
218
|
.filter((entry): entry is HistoryDagEdgeEntry => entry !== undefined)
|
|
222
219
|
}
|
|
223
220
|
|
|
224
221
|
getEdgeAttributes(edgeIndex: Graph.EdgeIndex): HistoryDagEdgeAttributes {
|
|
225
|
-
|
|
226
|
-
return edge?.data ?? shouldNeverHappen(`HistoryDag edge ${edgeIndex} not found`)
|
|
222
|
+
return this.edgeAt(edgeIndex, `HistoryDag edge ${edgeIndex} not found`).data
|
|
227
223
|
}
|
|
228
224
|
|
|
229
225
|
getEdgeAttribute<TKey extends keyof HistoryDagEdgeAttributes>(
|
|
@@ -235,13 +231,13 @@ export class HistoryDag {
|
|
|
235
231
|
}
|
|
236
232
|
|
|
237
233
|
source(edgeIndex: Graph.EdgeIndex): string {
|
|
238
|
-
const edge = this.graph
|
|
234
|
+
const edge = Option.getOrUndefined(Graph.getEdge(this.graph, edgeIndex))
|
|
239
235
|
const sourceId = edge !== undefined ? this.indexToId.get(edge.source) : undefined
|
|
240
236
|
return sourceId ?? shouldNeverHappen(`HistoryDag edge ${edgeIndex} missing source`)
|
|
241
237
|
}
|
|
242
238
|
|
|
243
239
|
target(edgeIndex: Graph.EdgeIndex): string {
|
|
244
|
-
const edge = this.graph
|
|
240
|
+
const edge = Option.getOrUndefined(Graph.getEdge(this.graph, edgeIndex))
|
|
245
241
|
const targetId = edge !== undefined ? this.indexToId.get(edge.target) : undefined
|
|
246
242
|
return targetId ?? shouldNeverHappen(`HistoryDag edge ${edgeIndex} missing target`)
|
|
247
243
|
}
|
|
@@ -252,9 +248,20 @@ export class HistoryDag {
|
|
|
252
248
|
return
|
|
253
249
|
}
|
|
254
250
|
|
|
251
|
+
for (const edgeIndex of this.outgoingEdges.get(index) ?? []) {
|
|
252
|
+
const edge = this.edgeAt(edgeIndex, `HistoryDag edge ${edgeIndex} missing while dropping ${id}`)
|
|
253
|
+
removeEdgeIndex(this.incomingEdges, edge.target, edgeIndex)
|
|
254
|
+
}
|
|
255
|
+
for (const edgeIndex of this.incomingEdges.get(index) ?? []) {
|
|
256
|
+
const edge = this.edgeAt(edgeIndex, `HistoryDag edge ${edgeIndex} missing while dropping ${id}`)
|
|
257
|
+
removeEdgeIndex(this.outgoingEdges, edge.source, edgeIndex)
|
|
258
|
+
}
|
|
259
|
+
|
|
255
260
|
Graph.removeNode(this.graph, index)
|
|
256
261
|
this.idToIndex.delete(id)
|
|
257
262
|
this.indexToId.delete(index)
|
|
263
|
+
this.incomingEdges.delete(index)
|
|
264
|
+
this.outgoingEdges.delete(index)
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
get size(): number {
|
|
@@ -262,7 +269,7 @@ export class HistoryDag {
|
|
|
262
269
|
}
|
|
263
270
|
|
|
264
271
|
private edgeEntry(edgeIndex: Graph.EdgeIndex): HistoryDagEdgeEntry | undefined {
|
|
265
|
-
const edge = this.graph
|
|
272
|
+
const edge = Option.getOrUndefined(Graph.getEdge(this.graph, edgeIndex))
|
|
266
273
|
if (edge === undefined) {
|
|
267
274
|
return undefined
|
|
268
275
|
}
|
|
@@ -281,6 +288,25 @@ export class HistoryDag {
|
|
|
281
288
|
attributes: edge.data,
|
|
282
289
|
}
|
|
283
290
|
}
|
|
291
|
+
|
|
292
|
+
private nodeAt(index: Graph.NodeIndex, message: string): HistoryDagNode {
|
|
293
|
+
return Option.getOrElse(Graph.getNode(this.graph, index), () => shouldNeverHappen(message))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private edgeAt(edgeIndex: Graph.EdgeIndex, message: string): Graph.Edge<HistoryDagEdgeAttributes> {
|
|
297
|
+
return Option.getOrElse(Graph.getEdge(this.graph, edgeIndex), () => shouldNeverHappen(message))
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const removeEdgeIndex = (
|
|
302
|
+
edgeIndices: Map<Graph.NodeIndex, Set<Graph.EdgeIndex>>,
|
|
303
|
+
nodeIndex: Graph.NodeIndex,
|
|
304
|
+
edgeIndex: Graph.EdgeIndex,
|
|
305
|
+
): void => {
|
|
306
|
+
const indices = edgeIndices.get(nodeIndex) ?? shouldNeverHappen(`HistoryDag edge index missing for node ${nodeIndex}`)
|
|
307
|
+
if (indices.delete(edgeIndex) === false) {
|
|
308
|
+
shouldNeverHappen(`HistoryDag edge ${edgeIndex} missing from node ${nodeIndex}`)
|
|
309
|
+
}
|
|
284
310
|
}
|
|
285
311
|
|
|
286
312
|
export const emptyHistoryDag = (): HistoryDag => HistoryDag.create({ allowSelfLoops: false })
|
|
@@ -48,7 +48,10 @@ export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skip
|
|
|
48
48
|
const parentSeqNumStr = dag.source(parentEdge)
|
|
49
49
|
const parentNode = dag.getNodeAttributes(parentSeqNumStr)
|
|
50
50
|
|
|
51
|
-
if (
|
|
51
|
+
if (
|
|
52
|
+
parentNode.factsGroup.modifySet.has(factKey) === true ||
|
|
53
|
+
parentNode.factsGroup.modifyUnset.has(factKey) === true
|
|
54
|
+
) {
|
|
52
55
|
return parentNode
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -27,11 +27,11 @@ const facts = {
|
|
|
27
27
|
const eventDefs = {
|
|
28
28
|
add: defineEvent({
|
|
29
29
|
name: 'add',
|
|
30
|
-
schema: Schema.Struct({ value: Schema.
|
|
30
|
+
schema: Schema.Struct({ value: Schema.Finite }),
|
|
31
31
|
}),
|
|
32
32
|
multiply: defineEvent({
|
|
33
33
|
name: 'multiply',
|
|
34
|
-
schema: Schema.Struct({ value: Schema.
|
|
34
|
+
schema: Schema.Struct({ value: Schema.Finite }),
|
|
35
35
|
facts: ({ value }, currentFacts) => ({
|
|
36
36
|
modify: {
|
|
37
37
|
set: value === 0 || currentFacts.has(facts.multiplyByZero) === true ? [facts.multiplyByZero] : [],
|
|
@@ -146,7 +146,7 @@ export const toEventNodes = (
|
|
|
146
146
|
const eventDef = eventDefs[partialEvent.name]!
|
|
147
147
|
const eventNum = EventSequenceNumber.Client.nextPair({
|
|
148
148
|
seqNum: currentEventSequenceNumber,
|
|
149
|
-
|
|
149
|
+
isClientOnly: eventDef.options.clientOnly,
|
|
150
150
|
}).seqNum
|
|
151
151
|
currentEventSequenceNumber = eventNum
|
|
152
152
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import * as EventSequenceNumber from '../../../schema/EventSequenceNumber/mod.ts'
|
|
4
|
+
import { HistoryDag, type HistoryDagNode } from '../history-dag-common.ts'
|
|
5
|
+
|
|
6
|
+
const makeNode = (global: number, parentGlobal = global - 1): HistoryDagNode => ({
|
|
7
|
+
seqNum: EventSequenceNumber.Client.Composite.make({ global, client: 0 }),
|
|
8
|
+
parentSeqNum: EventSequenceNumber.Client.Composite.make({ global: parentGlobal, client: 0 }),
|
|
9
|
+
name: `event-${global}`,
|
|
10
|
+
args: {},
|
|
11
|
+
factsGroup: {
|
|
12
|
+
depRead: new Map(),
|
|
13
|
+
depRequire: new Map(),
|
|
14
|
+
modifySet: new Map(),
|
|
15
|
+
modifyUnset: new Map(),
|
|
16
|
+
},
|
|
17
|
+
meta: undefined,
|
|
18
|
+
clientId: 'client-id',
|
|
19
|
+
sessionId: 'session-id',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('HistoryDag', () => {
|
|
23
|
+
it('preserves parallel edge order and removes incident edges with a node', () => {
|
|
24
|
+
const dag = HistoryDag.create()
|
|
25
|
+
dag.addNode('a', makeNode(1))
|
|
26
|
+
dag.addNode('b', makeNode(2))
|
|
27
|
+
dag.addNode('c', makeNode(3))
|
|
28
|
+
|
|
29
|
+
const parentEdge = dag.addEdge('a', 'b', { type: 'parent' })
|
|
30
|
+
const factsEdge = dag.addEdge('a', 'b', { type: 'facts' })
|
|
31
|
+
const incomingEdge = dag.addEdge('c', 'b', { type: 'facts' })
|
|
32
|
+
|
|
33
|
+
expect(dag.edges('a', 'b')).toEqual([parentEdge, factsEdge])
|
|
34
|
+
expect(dag.inEdges('b')).toEqual([parentEdge, factsEdge, incomingEdge])
|
|
35
|
+
expect(dag.inboundEdgeEntries('b')).toEqual([
|
|
36
|
+
{ edge: parentEdge, source: 'a', target: 'b', attributes: { type: 'parent' } },
|
|
37
|
+
{ edge: factsEdge, source: 'a', target: 'b', attributes: { type: 'facts' } },
|
|
38
|
+
{ edge: incomingEdge, source: 'c', target: 'b', attributes: { type: 'facts' } },
|
|
39
|
+
])
|
|
40
|
+
expect(dag.outboundEdgeEntries('a')).toEqual([
|
|
41
|
+
{ edge: parentEdge, source: 'a', target: 'b', attributes: { type: 'parent' } },
|
|
42
|
+
{ edge: factsEdge, source: 'a', target: 'b', attributes: { type: 'facts' } },
|
|
43
|
+
])
|
|
44
|
+
expect(dag.source(parentEdge)).toBe('a')
|
|
45
|
+
expect(dag.target(parentEdge)).toBe('b')
|
|
46
|
+
expect(dag.getEdgeAttribute(parentEdge, 'type')).toBe('parent')
|
|
47
|
+
|
|
48
|
+
dag.dropNode('b')
|
|
49
|
+
|
|
50
|
+
expect(dag.edges('a', 'b')).toEqual([])
|
|
51
|
+
expect(dag.outboundEdgeEntries('a')).toEqual([])
|
|
52
|
+
expect(dag.outboundEdgeEntries('c')).toEqual([])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('keeps the adjacency indexes consistent for self-loops', () => {
|
|
56
|
+
const dag = HistoryDag.create({ allowSelfLoops: true })
|
|
57
|
+
dag.addNode('a', makeNode(1))
|
|
58
|
+
|
|
59
|
+
const edge = dag.addEdge('a', 'a', { type: 'facts' })
|
|
60
|
+
|
|
61
|
+
expect(dag.edges('a', 'a')).toEqual([edge])
|
|
62
|
+
expect(dag.inEdges('a')).toEqual([edge])
|
|
63
|
+
expect(dag.inboundEdgeEntries('a')).toEqual([{ edge, source: 'a', target: 'a', attributes: { type: 'facts' } }])
|
|
64
|
+
|
|
65
|
+
dag.dropNode('a')
|
|
66
|
+
|
|
67
|
+
expect(dag.size).toBe(0)
|
|
68
|
+
expect(dag.edges('a', 'a')).toEqual([])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('copies nodes, facts, and edges without sharing mutable data', () => {
|
|
72
|
+
const dag = HistoryDag.create()
|
|
73
|
+
const nodeA = makeNode(1)
|
|
74
|
+
nodeA.factsGroup.modifySet = new Map([['key', 'value']])
|
|
75
|
+
dag.addNode('a', nodeA)
|
|
76
|
+
dag.addNode('b', makeNode(2))
|
|
77
|
+
dag.addEdge('a', 'b', { type: 'parent' })
|
|
78
|
+
|
|
79
|
+
const copy = dag.copy()
|
|
80
|
+
const copiedNode = copy.getNodeAttributes('a')
|
|
81
|
+
|
|
82
|
+
expect(copiedNode).not.toBe(nodeA)
|
|
83
|
+
expect(copiedNode.factsGroup.modifySet).not.toBe(nodeA.factsGroup.modifySet)
|
|
84
|
+
expect(copy.outboundEdgeEntries('a')).toMatchObject([{ source: 'a', target: 'b', attributes: { type: 'parent' } }])
|
|
85
|
+
expect(copy.getEdgeAttributes(copy.edges('a', 'b')[0]!)).not.toBe(dag.getEdgeAttributes(dag.edges('a', 'b')[0]!))
|
|
86
|
+
})
|
|
87
|
+
})
|