@livestore/common 0.4.0-dev.21 → 0.4.0-dev.23
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 +16 -9
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
- package/dist/ClientSessionLeaderThreadProxy.js.map +1 -1
- package/dist/WorkerTransportError.d.ts +11 -0
- package/dist/WorkerTransportError.d.ts.map +1 -0
- package/dist/WorkerTransportError.js +11 -0
- package/dist/WorkerTransportError.js.map +1 -0
- package/dist/adapter-types.d.ts +26 -3
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +27 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/bounded-collections.d.ts.map +1 -1
- package/dist/bounded-collections.js +6 -4
- package/dist/bounded-collections.js.map +1 -1
- package/dist/debug-info.js +4 -4
- package/dist/debug-info.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +42 -22
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.js +12 -1
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +12 -6
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +8 -3
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +45 -25
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +12 -1
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/devtools/mod.js +1 -1
- package/dist/devtools/mod.js.map +1 -1
- package/dist/errors.d.ts +15 -15
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -11
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +20 -6
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +283 -253
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/RejectedPushError.d.ts +107 -0
- package/dist/leader-thread/RejectedPushError.d.ts.map +1 -0
- package/dist/leader-thread/RejectedPushError.js +78 -0
- package/dist/leader-thread/RejectedPushError.js.map +1 -0
- package/dist/leader-thread/connection.js +1 -1
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +12 -11
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -2
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +34 -14
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +12 -5
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +12 -11
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -1
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +7 -4
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/recreate-db.js +1 -1
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -2
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -2
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/stream-events.d.ts.map +1 -1
- package/dist/leader-thread/stream-events.js +4 -3
- package/dist/leader-thread/stream-events.js.map +1 -1
- package/dist/leader-thread/types.d.ts +7 -6
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/logging.js +4 -4
- package/dist/logging.js.map +1 -1
- package/dist/make-client-session.js +2 -2
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.js +6 -6
- package/dist/materializer-helper.js.map +1 -1
- package/dist/otel.d.ts +1 -1
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +2 -2
- package/dist/otel.js.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts +1 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +11 -9
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef/define.d.ts +16 -2
- package/dist/schema/EventDef/define.d.ts.map +1 -1
- package/dist/schema/EventDef/define.js +5 -4
- package/dist/schema/EventDef/define.js.map +1 -1
- package/dist/schema/EventDef/deprecated.d.ts +99 -0
- package/dist/schema/EventDef/deprecated.d.ts.map +1 -0
- package/dist/schema/EventDef/deprecated.js +144 -0
- package/dist/schema/EventDef/deprecated.js.map +1 -0
- package/dist/schema/EventDef/deprecated.test.d.ts +2 -0
- package/dist/schema/EventDef/deprecated.test.d.ts.map +1 -0
- package/dist/schema/EventDef/deprecated.test.js +95 -0
- package/dist/schema/EventDef/deprecated.test.js.map +1 -0
- package/dist/schema/EventDef/event-def.d.ts +4 -0
- package/dist/schema/EventDef/event-def.d.ts.map +1 -1
- package/dist/schema/EventDef/mod.d.ts +1 -0
- package/dist/schema/EventDef/mod.d.ts.map +1 -1
- package/dist/schema/EventDef/mod.js +1 -0
- package/dist/schema/EventDef/mod.js.map +1 -1
- package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/client.js +11 -11
- package/dist/schema/EventSequenceNumber/client.js.map +1 -1
- package/dist/schema/EventSequenceNumber.test.js +1 -1
- package/dist/schema/EventSequenceNumber.test.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.d.ts +6 -6
- package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/client.js +6 -3
- package/dist/schema/LiveStoreEvent/client.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.test.d.ts +2 -0
- package/dist/schema/LiveStoreEvent/client.test.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent/client.test.js +83 -0
- package/dist/schema/LiveStoreEvent/client.test.js.map +1 -0
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +7 -4
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts +1 -0
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +34 -13
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +121 -2
- package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.js +1 -1
- package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js +1 -1
- package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +36 -34
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +7 -6
- package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-spec.js +8 -8
- package/dist/schema/state/sqlite/column-spec.js.map +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +2 -2
- 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 +2 -2
- 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 +11 -2
- 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 +1 -1
- 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 +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +3 -5
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +37 -13
- 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 +77 -7
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +28 -14
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +112 -3
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js +2 -2
- package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +5 -3
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +1 -1
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +57 -4
- package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
- package/dist/schema/unknown-events.d.ts +1 -1
- package/dist/schema/unknown-events.d.ts.map +1 -1
- package/dist/schema/unknown-events.js +1 -1
- package/dist/schema/unknown-events.js.map +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -1
- package/dist/schema-management/common.js +2 -2
- package/dist/schema-management/common.js.map +1 -1
- package/dist/schema-management/migrations.js +1 -1
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sql-queries/sql-queries.js +8 -6
- package/dist/sql-queries/sql-queries.js.map +1 -1
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.js.map +1 -1
- package/dist/sqlite-db-helper.js +3 -3
- package/dist/sqlite-db-helper.js.map +1 -1
- package/dist/sqlite-types.d.ts +2 -2
- package/dist/sqlite-types.d.ts.map +1 -1
- package/dist/sqlite-types.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +8 -9
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +93 -107
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/errors.d.ts +0 -38
- package/dist/sync/errors.d.ts.map +1 -1
- package/dist/sync/errors.js +3 -20
- package/dist/sync/errors.js.map +1 -1
- package/dist/sync/mock-sync-backend.d.ts +5 -3
- package/dist/sync/mock-sync-backend.d.ts.map +1 -1
- package/dist/sync/mock-sync-backend.js +70 -68
- package/dist/sync/mock-sync-backend.js.map +1 -1
- package/dist/sync/next/compact-events.js +6 -6
- package/dist/sync/next/compact-events.js.map +1 -1
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +6 -6
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +6 -6
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.js +3 -3
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.js +1 -1
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +2 -2
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +2 -2
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/event-fixtures.js +2 -2
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/sync-backend-kv.d.ts.map +1 -1
- package/dist/sync/sync-backend-kv.js.map +1 -1
- package/dist/sync/sync-backend.d.ts +3 -3
- package/dist/sync/sync-backend.d.ts.map +1 -1
- package/dist/sync/sync-backend.js +1 -1
- package/dist/sync/sync-backend.js.map +1 -1
- package/dist/sync/sync.d.ts +20 -0
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/syncstate.d.ts +4 -17
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +51 -74
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +112 -96
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/transport-chunking.js +3 -3
- package/dist/sync/transport-chunking.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts +2 -2
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +4 -6
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/util.js +2 -2
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +7 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +8 -4
- package/dist/version.js.map +1 -1
- package/package.json +66 -12
- package/src/ClientSessionLeaderThreadProxy.ts +16 -9
- package/src/WorkerTransportError.ts +12 -0
- package/src/adapter-types.ts +39 -3
- package/src/bounded-collections.ts +6 -5
- package/src/debug-info.ts +4 -4
- package/src/devtools/devtools-messages-client-session.ts +12 -0
- package/src/devtools/devtools-messages-common.ts +8 -4
- package/src/devtools/devtools-messages-leader.ts +12 -0
- package/src/devtools/mod.ts +1 -1
- package/src/errors.ts +18 -17
- package/src/index.ts +2 -0
- package/src/leader-thread/LeaderSyncProcessor.ts +417 -347
- package/src/leader-thread/RejectedPushError.ts +106 -0
- package/src/leader-thread/connection.ts +1 -1
- package/src/leader-thread/eventlog.ts +16 -14
- package/src/leader-thread/leader-worker-devtools.ts +107 -66
- package/src/leader-thread/make-leader-thread-layer.test.ts +1 -1
- package/src/leader-thread/make-leader-thread-layer.ts +41 -31
- package/src/leader-thread/materialize-event.ts +8 -4
- package/src/leader-thread/recreate-db.ts +1 -1
- package/src/leader-thread/shutdown-channel.ts +2 -6
- package/src/leader-thread/stream-events.ts +10 -5
- package/src/leader-thread/types.ts +7 -6
- package/src/logging.ts +4 -4
- package/src/make-client-session.ts +2 -2
- package/src/materializer-helper.ts +9 -9
- package/src/otel.ts +3 -2
- package/src/rematerialize-from-eventlog.ts +60 -60
- package/src/schema/EventDef/define.ts +22 -6
- package/src/schema/EventDef/deprecated.test.ts +129 -0
- package/src/schema/EventDef/deprecated.ts +175 -0
- package/src/schema/EventDef/event-def.ts +5 -0
- package/src/schema/EventDef/mod.ts +1 -0
- package/src/schema/EventSequenceNumber/client.ts +11 -11
- package/src/schema/EventSequenceNumber.test.ts +2 -1
- package/src/schema/LiveStoreEvent/client.test.ts +97 -0
- package/src/schema/LiveStoreEvent/client.ts +6 -3
- package/src/schema/schema.ts +9 -4
- package/src/schema/state/sqlite/client-document-def.test.ts +142 -3
- package/src/schema/state/sqlite/client-document-def.ts +37 -14
- package/src/schema/state/sqlite/column-annotations.test.ts +2 -1
- package/src/schema/state/sqlite/column-annotations.ts +2 -1
- package/src/schema/state/sqlite/column-def.test.ts +8 -6
- package/src/schema/state/sqlite/column-def.ts +41 -36
- package/src/schema/state/sqlite/column-spec.test.ts +3 -1
- package/src/schema/state/sqlite/column-spec.ts +9 -8
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -2
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +2 -1
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +13 -4
- package/src/schema/state/sqlite/db-schema/dsl/mod.ts +3 -3
- package/src/schema/state/sqlite/mod.ts +4 -5
- package/src/schema/state/sqlite/query-builder/api.ts +37 -8
- package/src/schema/state/sqlite/query-builder/astToSql.ts +87 -7
- package/src/schema/state/sqlite/query-builder/impl.test.ts +145 -3
- package/src/schema/state/sqlite/query-builder/impl.ts +26 -12
- package/src/schema/state/sqlite/schema-helpers.ts +2 -2
- package/src/schema/state/sqlite/table-def.test.ts +67 -4
- package/src/schema/state/sqlite/table-def.ts +8 -15
- package/src/schema/unknown-events.ts +2 -2
- package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +3 -1
- package/src/schema-management/common.ts +2 -2
- package/src/schema-management/migrations.ts +1 -1
- package/src/sql-queries/sql-queries.ts +10 -6
- package/src/sql-queries/sql-query-builder.ts +1 -0
- package/src/sqlite-db-helper.ts +3 -3
- package/src/sqlite-types.ts +3 -2
- package/src/sync/ClientSessionSyncProcessor.ts +142 -133
- package/src/sync/errors.ts +10 -22
- package/src/sync/mock-sync-backend.ts +139 -97
- package/src/sync/next/compact-events.ts +5 -5
- package/src/sync/next/facts.ts +7 -6
- package/src/sync/next/history-dag-common.ts +9 -6
- package/src/sync/next/history-dag.ts +3 -3
- package/src/sync/next/rebase-events.ts +1 -1
- package/src/sync/next/test/compact-events.calculator.test.ts +3 -2
- package/src/sync/next/test/compact-events.test.ts +4 -3
- package/src/sync/next/test/event-fixtures.ts +2 -2
- package/src/sync/sync-backend-kv.ts +1 -0
- package/src/sync/sync-backend.ts +5 -4
- package/src/sync/sync.ts +21 -0
- package/src/sync/syncstate.test.ts +513 -435
- package/src/sync/syncstate.ts +80 -86
- package/src/sync/transport-chunking.ts +3 -3
- package/src/sync/validate-push-payload.ts +4 -6
- package/src/util.ts +2 -2
- package/src/version.ts +8 -4
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Schema, Scope } from '@livestore/utils/effect'
|
|
2
|
-
import { Effect, Mailbox, Option, Queue, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
|
2
|
+
import { Effect, Mailbox, Option, Queue, Ref, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
|
3
|
+
|
|
3
4
|
import { UnknownError } from '../errors.ts'
|
|
4
5
|
import { EventSequenceNumber, type LiveStoreEvent } from '../schema/mod.ts'
|
|
5
|
-
import {
|
|
6
|
+
import { type BackendIdMismatchError, type ServerAheadError } from './errors.ts'
|
|
6
7
|
import * as SyncBackend from './sync-backend.ts'
|
|
7
8
|
import { validatePushPayload } from './validate-push-payload.ts'
|
|
8
9
|
|
|
@@ -12,10 +13,17 @@ export interface MockSyncBackend {
|
|
|
12
13
|
disconnect: Effect.Effect<void>
|
|
13
14
|
makeSyncBackend: Effect.Effect<SyncBackend.SyncBackend, UnknownError, Scope.Scope>
|
|
14
15
|
advance: (...batch: LiveStoreEvent.Global.Encoded[]) => Effect.Effect<void>
|
|
15
|
-
/** Fail the next N push calls with an
|
|
16
|
+
/** Fail the next N push calls with an UnknownError, ServerAheadError, BackendIdMismatchError, or custom error */
|
|
16
17
|
failNextPushes: (
|
|
17
18
|
count: number,
|
|
18
|
-
error?: (
|
|
19
|
+
error?: (
|
|
20
|
+
batch: ReadonlyArray<LiveStoreEvent.Global.Encoded>,
|
|
21
|
+
) => Effect.Effect<never, UnknownError | ServerAheadError | BackendIdMismatchError>,
|
|
22
|
+
) => Effect.Effect<void>
|
|
23
|
+
/** Fail the next N pull calls with an UnknownError, BackendIdMismatchError, or custom error */
|
|
24
|
+
failNextPulls: (
|
|
25
|
+
count: number,
|
|
26
|
+
error?: () => Effect.Effect<never, UnknownError | BackendIdMismatchError>,
|
|
19
27
|
) => Effect.Effect<void>
|
|
20
28
|
}
|
|
21
29
|
|
|
@@ -31,107 +39,136 @@ export const makeMockSyncBackend = (
|
|
|
31
39
|
options?: MockSyncBackendOptions,
|
|
32
40
|
): Effect.Effect<MockSyncBackend, UnknownError, Scope.Scope> =>
|
|
33
41
|
Effect.gen(function* () {
|
|
34
|
-
const
|
|
42
|
+
const span = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
43
|
+
const semaphore = yield* Effect.makeSemaphore(1)
|
|
44
|
+
|
|
45
|
+
// State refs
|
|
46
|
+
const syncHeadRef = yield* Ref.make(EventSequenceNumber.Client.ROOT.global)
|
|
47
|
+
const allEventsRef = yield* Ref.make<LiveStoreEvent.Global.Encoded[]>([])
|
|
48
|
+
const syncIsConnectedRef = yield* SubscriptionRef.make(options?.startConnected ?? false)
|
|
49
|
+
|
|
50
|
+
// Queues for streaming
|
|
35
51
|
const syncPullQueue = yield* Queue.unbounded<LiveStoreEvent.Global.Encoded>()
|
|
36
52
|
const pushedEventsQueue = yield* Mailbox.make<LiveStoreEvent.Global.Encoded>()
|
|
37
|
-
const syncIsConnectedRef = yield* SubscriptionRef.make(options?.startConnected ?? false)
|
|
38
|
-
const allEventsRef: { current: LiveStoreEvent.Global.Encoded[] } = { current: [] }
|
|
39
53
|
|
|
40
|
-
|
|
54
|
+
// Failure simulation state
|
|
55
|
+
const failPushRef = yield* Ref.make<
|
|
56
|
+
FailureState<UnknownError | ServerAheadError | BackendIdMismatchError, [ReadonlyArray<LiveStoreEvent.Global.Encoded>]>
|
|
57
|
+
>({ remaining: 0, error: undefined })
|
|
58
|
+
const failPullRef = yield* Ref.make<FailureState<UnknownError | BackendIdMismatchError, []>>({
|
|
59
|
+
remaining: 0,
|
|
60
|
+
error: undefined,
|
|
61
|
+
})
|
|
41
62
|
|
|
42
|
-
const
|
|
63
|
+
const nonLiveChunkSize = Math.max(1, options?.nonLiveChunkSize ?? 100)
|
|
64
|
+
|
|
65
|
+
/** Check and consume a simulated failure, returning the error effect if one should fire */
|
|
66
|
+
const checkFailure = <E, Args extends unknown[]>(
|
|
67
|
+
ref: Ref.Ref<FailureState<E, Args>>,
|
|
68
|
+
defaultError: E,
|
|
69
|
+
...args: Args
|
|
70
|
+
): Effect.Effect<void, E> =>
|
|
71
|
+
Ref.modify(ref, (state) => {
|
|
72
|
+
if (state.remaining <= 0) {
|
|
73
|
+
return [Option.none(), state] as const
|
|
74
|
+
}
|
|
75
|
+
const error = state.error?.(...args) ?? Effect.fail(defaultError)
|
|
76
|
+
return [Option.some(error), { ...state, remaining: state.remaining - 1 }] as const
|
|
77
|
+
}).pipe(
|
|
78
|
+
Effect.flatMap(
|
|
79
|
+
Option.match({
|
|
80
|
+
onNone: () => Effect.void,
|
|
81
|
+
onSome: (errorEffect) => errorEffect,
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const pullNonLive = (cursor: Option.Option<{ eventSequenceNumber: EventSequenceNumber.Global.Type }>) =>
|
|
87
|
+
Effect.gen(function* () {
|
|
88
|
+
const lastSeen = Option.match(cursor, {
|
|
89
|
+
onNone: () => EventSequenceNumber.Client.ROOT.global,
|
|
90
|
+
onSome: (_) => _.eventSequenceNumber,
|
|
91
|
+
})
|
|
92
|
+
const allEvents = yield* Ref.get(allEventsRef)
|
|
93
|
+
const slice = allEvents.filter((e) => e.seqNum > lastSeen)
|
|
94
|
+
|
|
95
|
+
// Split into chunks with remaining count for pageInfo
|
|
96
|
+
const chunks: Array<{ events: LiveStoreEvent.Global.Encoded[]; remaining: number }> = []
|
|
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
|
+
}).pipe(
|
|
110
|
+
Effect.map((chunks) =>
|
|
111
|
+
Stream.fromIterable(chunks).pipe(
|
|
112
|
+
Stream.map(({ events, remaining }) => ({
|
|
113
|
+
batch: events.map((eventEncoded) => ({ eventEncoded, metadata: Option.none() })),
|
|
114
|
+
pageInfo: remaining > 0 ? SyncBackend.pageInfoMoreKnown(remaining) : SyncBackend.pageInfoNoMore,
|
|
115
|
+
})),
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
Stream.fromEffect,
|
|
119
|
+
Stream.flatten(),
|
|
120
|
+
)
|
|
43
121
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
)
|
|
49
132
|
|
|
50
133
|
const makeSyncBackend = Effect.gen(function* () {
|
|
51
|
-
const nonLiveChunkSize = Math.max(1, options?.nonLiveChunkSize ?? 100)
|
|
52
|
-
|
|
53
134
|
// TODO consider making offline state actively error pull/push.
|
|
54
135
|
// Currently, offline only reflects in `isConnected`, while operations still succeed,
|
|
55
136
|
// mirroring how some real providers behave during transient disconnects.
|
|
56
|
-
return SyncBackend.of
|
|
137
|
+
return SyncBackend.of({
|
|
57
138
|
isConnected: syncIsConnectedRef,
|
|
58
139
|
connect: SubscriptionRef.set(syncIsConnectedRef, true),
|
|
59
140
|
ping: Effect.void,
|
|
60
|
-
pull: (cursor,
|
|
61
|
-
(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
),
|
|
71
|
-
)
|
|
72
|
-
: Stream.fromEffect(
|
|
73
|
-
Effect.sync(() => {
|
|
74
|
-
const lastSeen = cursor.pipe(
|
|
75
|
-
Option.match({
|
|
76
|
-
onNone: () => EventSequenceNumber.Client.ROOT.global,
|
|
77
|
-
onSome: (_) => _.eventSequenceNumber,
|
|
78
|
-
}),
|
|
79
|
-
)
|
|
80
|
-
// All events with seqNum greater than lastSeen
|
|
81
|
-
const slice = allEventsRef.current.filter((e) => e.seqNum > lastSeen)
|
|
82
|
-
// Split into configured chunk size
|
|
83
|
-
const chunks: { events: LiveStoreEvent.Global.Encoded[]; remaining: number }[] = []
|
|
84
|
-
for (let i = 0; i < slice.length; i += nonLiveChunkSize) {
|
|
85
|
-
const end = Math.min(i + nonLiveChunkSize, slice.length)
|
|
86
|
-
const remaining = Math.max(slice.length - end, 0)
|
|
87
|
-
chunks.push({ events: slice.slice(i, end), remaining })
|
|
88
|
-
}
|
|
89
|
-
if (chunks.length === 0) {
|
|
90
|
-
chunks.push({ events: [], remaining: 0 })
|
|
91
|
-
}
|
|
92
|
-
return chunks
|
|
93
|
-
}),
|
|
94
|
-
).pipe(
|
|
95
|
-
Stream.flatMap((chunks) =>
|
|
96
|
-
Stream.fromIterable(chunks).pipe(
|
|
97
|
-
Stream.map(({ events, remaining }) => ({
|
|
98
|
-
batch: events.map((eventEncoded) => ({ eventEncoded, metadata: Option.none() })),
|
|
99
|
-
pageInfo: remaining > 0 ? SyncBackend.pageInfoMoreKnown(remaining) : SyncBackend.pageInfoNoMore,
|
|
100
|
-
})),
|
|
101
|
-
),
|
|
102
|
-
),
|
|
103
|
-
)
|
|
104
|
-
).pipe(Stream.withSpan('MockSyncBackend:pull', { parent: span })),
|
|
141
|
+
pull: (cursor, pullOptions) =>
|
|
142
|
+
Stream.fromEffect(
|
|
143
|
+
checkFailure(
|
|
144
|
+
failPullRef,
|
|
145
|
+
new UnknownError({ cause: new Error('MockSyncBackend: simulated pull failure') }),
|
|
146
|
+
),
|
|
147
|
+
).pipe(
|
|
148
|
+
Stream.flatMap(() => (pullOptions?.live === true ? pullLive : pullNonLive(cursor))),
|
|
149
|
+
Stream.withSpan('MockSyncBackend:pull', { parent: span }),
|
|
150
|
+
),
|
|
105
151
|
push: (batch) =>
|
|
106
152
|
Effect.gen(function* () {
|
|
107
|
-
yield*
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return yield* maybeFail(batch)
|
|
116
|
-
}
|
|
117
|
-
return yield* new InvalidPushError({
|
|
118
|
-
cause: new UnknownError({ cause: new Error('MockSyncBackend: simulated push failure') }),
|
|
119
|
-
})
|
|
120
|
-
}
|
|
153
|
+
const currentHead = yield* Ref.get(syncHeadRef)
|
|
154
|
+
yield* validatePushPayload(batch, currentHead)
|
|
155
|
+
|
|
156
|
+
yield* checkFailure(
|
|
157
|
+
failPushRef,
|
|
158
|
+
new UnknownError({ cause: new Error('MockSyncBackend: simulated push failure') }),
|
|
159
|
+
batch,
|
|
160
|
+
)
|
|
121
161
|
|
|
122
162
|
yield* Effect.sleep(10).pipe(Effect.withSpan('MockSyncBackend:push:sleep')) // Simulate network latency
|
|
123
163
|
|
|
124
164
|
yield* pushedEventsQueue.offerAll(batch)
|
|
125
165
|
yield* syncPullQueue.offerAll(batch)
|
|
126
|
-
allEventsRef
|
|
127
|
-
|
|
128
|
-
syncEventSequenceNumberRef.current = batch.at(-1)!.seqNum
|
|
166
|
+
yield* Ref.update(allEventsRef, (events) => events.concat(batch))
|
|
167
|
+
yield* Ref.set(syncHeadRef, batch.at(-1)!.seqNum)
|
|
129
168
|
}).pipe(
|
|
130
169
|
Effect.withSpan('MockSyncBackend:push', {
|
|
131
170
|
parent: span,
|
|
132
|
-
attributes: {
|
|
133
|
-
nums: batch.map((_) => _.seqNum),
|
|
134
|
-
},
|
|
171
|
+
attributes: { nums: batch.map((_) => _.seqNum) },
|
|
135
172
|
}),
|
|
136
173
|
semaphore.withPermits(1),
|
|
137
174
|
),
|
|
@@ -148,8 +185,8 @@ export const makeMockSyncBackend = (
|
|
|
148
185
|
|
|
149
186
|
const advance = (...batch: LiveStoreEvent.Global.Encoded[]) =>
|
|
150
187
|
Effect.gen(function* () {
|
|
151
|
-
|
|
152
|
-
allEventsRef
|
|
188
|
+
yield* Ref.set(syncHeadRef, batch.at(-1)!.seqNum)
|
|
189
|
+
yield* Ref.update(allEventsRef, (events) => events.concat(batch))
|
|
153
190
|
yield* syncPullQueue.offerAll(batch)
|
|
154
191
|
}).pipe(
|
|
155
192
|
Effect.withSpan('MockSyncBackend:advance', {
|
|
@@ -159,26 +196,31 @@ export const makeMockSyncBackend = (
|
|
|
159
196
|
semaphore.withPermits(1),
|
|
160
197
|
)
|
|
161
198
|
|
|
162
|
-
const connect = SubscriptionRef.set(syncIsConnectedRef, true)
|
|
163
|
-
const disconnect = SubscriptionRef.set(syncIsConnectedRef, false)
|
|
164
|
-
|
|
165
199
|
const failNextPushes = (
|
|
166
200
|
count: number,
|
|
167
|
-
error?: (
|
|
168
|
-
|
|
169
|
-
Effect.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
201
|
+
error?: (
|
|
202
|
+
batch: ReadonlyArray<LiveStoreEvent.Global.Encoded>,
|
|
203
|
+
) => Effect.Effect<never, UnknownError | ServerAheadError | BackendIdMismatchError>,
|
|
204
|
+
) => Ref.set(failPushRef, { remaining: count, error })
|
|
205
|
+
|
|
206
|
+
const failNextPulls = (
|
|
207
|
+
count: number,
|
|
208
|
+
error?: () => Effect.Effect<never, UnknownError | BackendIdMismatchError>,
|
|
209
|
+
) => Ref.set(failPullRef, { remaining: count, error })
|
|
173
210
|
|
|
174
211
|
return {
|
|
175
|
-
syncEventSequenceNumberRef,
|
|
176
|
-
syncPullQueue,
|
|
177
212
|
pushedEvents: Mailbox.toStream(pushedEventsQueue),
|
|
178
|
-
connect,
|
|
179
|
-
disconnect,
|
|
213
|
+
connect: SubscriptionRef.set(syncIsConnectedRef, true),
|
|
214
|
+
disconnect: SubscriptionRef.set(syncIsConnectedRef, false),
|
|
180
215
|
makeSyncBackend,
|
|
181
216
|
advance,
|
|
182
217
|
failNextPushes,
|
|
218
|
+
failNextPulls,
|
|
183
219
|
}
|
|
184
220
|
}).pipe(Effect.withSpanScoped('MockSyncBackend'))
|
|
221
|
+
|
|
222
|
+
/** Internal state for simulating failures */
|
|
223
|
+
interface FailureState<E, Args extends unknown[]> {
|
|
224
|
+
remaining: number
|
|
225
|
+
error: ((...args: Args) => Effect.Effect<never, E>) | undefined
|
|
226
|
+
}
|
|
@@ -16,7 +16,7 @@ export const compactEvents = (inputDag: HistoryDag): { dag: HistoryDag; compacte
|
|
|
16
16
|
const dag = inputDag.copy()
|
|
17
17
|
const compactedEventCount = 0
|
|
18
18
|
|
|
19
|
-
const orderedEventSequenceNumberStrs = dag.topologicalNodeIds().
|
|
19
|
+
const orderedEventSequenceNumberStrs = dag.topologicalNodeIds().toReversed()
|
|
20
20
|
|
|
21
21
|
// drop root
|
|
22
22
|
orderedEventSequenceNumberStrs.pop()
|
|
@@ -29,7 +29,7 @@ export const compactEvents = (inputDag: HistoryDag): { dag: HistoryDag; compacte
|
|
|
29
29
|
const subDagsForEvent = Array.from(makeSubDagsForEvent(dag, eventNumStr))
|
|
30
30
|
for (const subDag of subDagsForEvent) {
|
|
31
31
|
let shouldRetry = true
|
|
32
|
-
while (shouldRetry) {
|
|
32
|
+
while (shouldRetry === true) {
|
|
33
33
|
const subDagsInHistory = findSubDagsInHistory(dag, subDag, eventNumStr)
|
|
34
34
|
|
|
35
35
|
// console.debug(
|
|
@@ -64,7 +64,7 @@ export const compactEvents = (inputDag: HistoryDag): { dag: HistoryDag; compacte
|
|
|
64
64
|
return { dag, compactedEventCount }
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function*
|
|
67
|
+
const makeSubDagsForEvent = function* (inputDag: HistoryDag, eventNumStr: string): Generator<HistoryDag> {
|
|
68
68
|
/** Map from eventNumStr to array of eventNumStrs that are dependencies */
|
|
69
69
|
let nextIterationEls: Map<string, string[]> = new Map([[eventNumStr, []]])
|
|
70
70
|
let previousDag: HistoryDag | undefined
|
|
@@ -131,7 +131,7 @@ const findSubDagsInHistory = (
|
|
|
131
131
|
allOutsideDependencies.push(outsideDependencies)
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
if (outsideDependencies.length === 0 && dagReplacesDag(subDag, targetSubDag)) {
|
|
134
|
+
if (outsideDependencies.length === 0 && dagReplacesDag(subDag, targetSubDag) === true) {
|
|
135
135
|
subDags.push(subDag)
|
|
136
136
|
} else {
|
|
137
137
|
break
|
|
@@ -171,7 +171,7 @@ const dagDependsOnDag = (dagA: HistoryDag, dagB: HistoryDag, inputDag: HistoryDa
|
|
|
171
171
|
for (const edgeEntryA of inputDag.inboundEdgeEntries(nodeAIdStr)) {
|
|
172
172
|
if (edgeEntryA.attributes.type === 'facts') {
|
|
173
173
|
const depNodeIdStr = edgeEntryA.target
|
|
174
|
-
if (dagB.hasNode(depNodeIdStr)) {
|
|
174
|
+
if (dagB.hasNode(depNodeIdStr) === true) {
|
|
175
175
|
return true
|
|
176
176
|
}
|
|
177
177
|
}
|
package/src/sync/next/facts.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { notYetImplemented } from '@livestore/utils'
|
|
2
|
+
|
|
2
3
|
import type {
|
|
3
4
|
EventDefFactInput,
|
|
4
5
|
EventDefFacts,
|
|
@@ -129,7 +130,7 @@ const isSubSetMapByValue = (setA: EventDefFacts, setB: EventDefFacts) => {
|
|
|
129
130
|
/** Check if setA is a subset of setB */
|
|
130
131
|
const isSubSetMapByKey = (setA: EventDefFacts, setB: EventDefFacts) => {
|
|
131
132
|
for (const [key, _value] of setA) {
|
|
132
|
-
if (
|
|
133
|
+
if (setB.has(key) === false) {
|
|
133
134
|
return false
|
|
134
135
|
}
|
|
135
136
|
}
|
|
@@ -167,7 +168,7 @@ export const factsToString = (facts: EventDefFacts) => {
|
|
|
167
168
|
|
|
168
169
|
export const factsIntersect = (setA: EventDefFacts, setB: EventDefFacts): boolean => {
|
|
169
170
|
for (const [key, _value] of setA) {
|
|
170
|
-
if (setB.has(key)) {
|
|
171
|
+
if (setB.has(key) === true) {
|
|
171
172
|
return true
|
|
172
173
|
}
|
|
173
174
|
}
|
|
@@ -198,7 +199,7 @@ export const getFactsGroupForEventArgs = ({
|
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
notYetImplemented(`getFactsGroupForEventArgs: ${prop.toString()} is not yet implemented`)
|
|
202
|
+
return notYetImplemented(`getFactsGroupForEventArgs: ${prop.toString()} is not yet implemented`)
|
|
202
203
|
},
|
|
203
204
|
})
|
|
204
205
|
|
|
@@ -215,9 +216,9 @@ export const getFactsGroupForEventArgs = ({
|
|
|
215
216
|
return map
|
|
216
217
|
}
|
|
217
218
|
const facts = {
|
|
218
|
-
modifySet: factsRes?.modify.set ? iterableToMap(factsRes.modify.set) : new Map(),
|
|
219
|
-
modifyUnset: factsRes?.modify.unset ? iterableToMap(factsRes.modify.unset) : new Map(),
|
|
220
|
-
depRequire: factsRes?.require ? iterableToMap(factsRes.require) : new Map(),
|
|
219
|
+
modifySet: factsRes?.modify.set !== undefined ? iterableToMap(factsRes.modify.set) : new Map(),
|
|
220
|
+
modifyUnset: factsRes?.modify.unset !== undefined ? iterableToMap(factsRes.modify.unset) : new Map(),
|
|
221
|
+
depRequire: factsRes?.require !== undefined ? iterableToMap(factsRes.require) : new Map(),
|
|
221
222
|
depRead,
|
|
222
223
|
}
|
|
223
224
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
2
|
import { Graph } from '@livestore/utils/effect'
|
|
3
|
+
|
|
3
4
|
import type { EventDefFactsGroup } from '../../schema/EventDef/mod.ts'
|
|
4
5
|
import * as EventSequenceNumber from '../../schema/EventSequenceNumber/mod.ts'
|
|
5
6
|
|
|
@@ -74,13 +75,13 @@ export class HistoryDag {
|
|
|
74
75
|
}) {
|
|
75
76
|
this.graph = graph
|
|
76
77
|
this.options = { ...defaultOptions, ...options }
|
|
77
|
-
this.idToIndex = idToIndex ? new Map(idToIndex) : new Map()
|
|
78
|
-
this.indexToId = indexToId ? new Map(indexToId) : new Map()
|
|
78
|
+
this.idToIndex = idToIndex !== undefined ? new Map(idToIndex) : new Map()
|
|
79
|
+
this.indexToId = indexToId !== undefined ? new Map(indexToId) : new Map()
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
static create(options?: Partial<HistoryDagOptions>): HistoryDag {
|
|
82
83
|
const graph = Graph.beginMutation(Graph.directed<HistoryDagNode, HistoryDagEdgeAttributes>())
|
|
83
|
-
return options ? new HistoryDag({ graph, options }) : new HistoryDag({ graph })
|
|
84
|
+
return options !== undefined ? new HistoryDag({ graph, options }) : new HistoryDag({ graph })
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
copy(): HistoryDag {
|
|
@@ -103,11 +104,13 @@ export class HistoryDag {
|
|
|
103
104
|
topologicalNodeIds(): Array<string> {
|
|
104
105
|
const walker = Graph.topo(this.graph)
|
|
105
106
|
const indices = Array.from(Graph.indices(walker))
|
|
106
|
-
return indices.map(
|
|
107
|
+
return indices.map(
|
|
108
|
+
(index) => this.indexToId.get(index) ?? shouldNeverHappen(`Missing node id for index ${String(index)}`),
|
|
109
|
+
)
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
addNode(id: string, attributes: HistoryDagNode): void {
|
|
110
|
-
if (this.idToIndex.has(id)) {
|
|
113
|
+
if (this.idToIndex.has(id) === true) {
|
|
111
114
|
shouldNeverHappen(`HistoryDag node ${id} already exists`)
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -183,7 +186,7 @@ export class HistoryDag {
|
|
|
183
186
|
return []
|
|
184
187
|
}
|
|
185
188
|
const incoming = this.graph.reverseAdjacency.get(index)
|
|
186
|
-
return incoming ? [...incoming] : []
|
|
189
|
+
return incoming !== undefined ? [...incoming] : []
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
outboundEdgeEntries(id: string): Array<HistoryDagEdgeEntry> {
|
|
@@ -43,12 +43,12 @@ export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skip
|
|
|
43
43
|
|
|
44
44
|
while (currentSeqNumStr !== EventSequenceNumber.Client.toString(rootParentNum)) {
|
|
45
45
|
const parentEdge = dag.inEdges(currentSeqNumStr).find((e) => dag.getEdgeAttribute(e, 'type') === 'parent')
|
|
46
|
-
if (
|
|
46
|
+
if (parentEdge == null) return null
|
|
47
47
|
|
|
48
48
|
const parentSeqNumStr = dag.source(parentEdge)
|
|
49
49
|
const parentNode = dag.getNodeAttributes(parentSeqNumStr)
|
|
50
50
|
|
|
51
|
-
if (parentNode.factsGroup.modifySet.has(factKey) || parentNode.factsGroup.modifyUnset.has(factKey)) {
|
|
51
|
+
if (parentNode.factsGroup.modifySet.has(factKey) === true || parentNode.factsGroup.modifyUnset.has(factKey) === true) {
|
|
52
52
|
return parentNode
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skip
|
|
|
58
58
|
return null
|
|
59
59
|
})()
|
|
60
60
|
|
|
61
|
-
if (depNode) {
|
|
61
|
+
if (depNode !== null) {
|
|
62
62
|
const depNodeIdStr = EventSequenceNumber.Client.toString(depNode.seqNum)
|
|
63
63
|
const nodeIdStr = EventSequenceNumber.Client.toString(node.seqNum)
|
|
64
64
|
if (dag.edges(depNodeIdStr, nodeIdStr).filter((e) => dag.getEdgeAttributes(e).type === 'facts').length === 0) {
|
|
@@ -31,7 +31,7 @@ export type RebaseOutput = {
|
|
|
31
31
|
export type RebaseFn = (input: RebaseInput) => RebaseOutput
|
|
32
32
|
|
|
33
33
|
export const defaultRebaseFn: RebaseFn = ({ pendingLocalEvents }) => {
|
|
34
|
-
if (pendingLocalEvents.some((_) => _.conflictType === 'missing-requirement')) {
|
|
34
|
+
if (pendingLocalEvents.some((_) => _.conflictType === 'missing-requirement') === true) {
|
|
35
35
|
throw new Error('missing-requirement conflicts must be resolved before rebasing')
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
1
3
|
import { defineEvent } from '@livestore/common/schema'
|
|
2
4
|
import { Schema } from '@livestore/utils/effect'
|
|
3
|
-
import { describe, expect, it } from 'vitest'
|
|
4
5
|
|
|
5
6
|
import { compactEvents } from '../compact-events.ts'
|
|
6
7
|
import { historyDagFromNodes } from '../history-dag.ts'
|
|
@@ -33,7 +34,7 @@ const eventDefs = {
|
|
|
33
34
|
schema: Schema.Struct({ value: Schema.Number }),
|
|
34
35
|
facts: ({ value }, currentFacts) => ({
|
|
35
36
|
modify: {
|
|
36
|
-
set: value === 0 || currentFacts.has(facts.multiplyByZero) ? [facts.multiplyByZero] : [],
|
|
37
|
+
set: value === 0 || currentFacts.has(facts.multiplyByZero) === true ? [facts.multiplyByZero] : [],
|
|
37
38
|
unset: value === 0 ? [] : [facts.multiplyByZero],
|
|
38
39
|
},
|
|
39
40
|
}),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { EventDefFacts } from '@livestore/common/schema'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import type { EventDefFacts } from '@livestore/common/schema'
|
|
4
|
+
|
|
4
5
|
import { compactEvents } from '../compact-events.ts'
|
|
5
|
-
import { historyDagFromNodes } from '../history-dag.ts'
|
|
6
6
|
import type { HistoryDagNode } from '../history-dag-common.ts'
|
|
7
7
|
import { EMPTY_FACT_VALUE } from '../history-dag-common.ts'
|
|
8
|
+
import { historyDagFromNodes } from '../history-dag.ts'
|
|
8
9
|
import { events as eventDefs, printEvent, toEventNodes } from './event-fixtures.ts'
|
|
9
10
|
|
|
10
11
|
const customStringify = (value: any): string => {
|
|
@@ -19,7 +20,7 @@ const customStringify = (value: any): string => {
|
|
|
19
20
|
if (type === 'number' || type === 'boolean') {
|
|
20
21
|
return String(value)
|
|
21
22
|
}
|
|
22
|
-
if (Array.isArray(value)) {
|
|
23
|
+
if (Array.isArray(value) === true) {
|
|
23
24
|
const elements = value.map((el) => customStringify(el))
|
|
24
25
|
return `[${elements.join(', ')}]`
|
|
25
26
|
}
|
|
@@ -4,9 +4,9 @@ import type { EventDef } from '../../../schema/EventDef/mod.ts'
|
|
|
4
4
|
import { defineEvent, defineFacts } from '../../../schema/EventDef/mod.ts'
|
|
5
5
|
import * as EventSequenceNumber from '../../../schema/EventSequenceNumber/mod.ts'
|
|
6
6
|
import { factsSnapshotForDag, getFactsGroupForEventArgs } from '../facts.ts'
|
|
7
|
-
import { historyDagFromNodes } from '../history-dag.ts'
|
|
8
7
|
import type { HistoryDagNode } from '../history-dag-common.ts'
|
|
9
8
|
import { rootEventNode } from '../history-dag-common.ts'
|
|
9
|
+
import { historyDagFromNodes } from '../history-dag.ts'
|
|
10
10
|
|
|
11
11
|
export const printEvent = ({ seqNum, parentSeqNum, factsGroup, ...rest }: HistoryDagNode) => ({
|
|
12
12
|
seqNum: EventSequenceNumber.Client.toString(seqNum),
|
|
@@ -45,7 +45,7 @@ export const events = {
|
|
|
45
45
|
// {
|
|
46
46
|
facts: ({ id }, currentFacts) =>
|
|
47
47
|
// TODO enable an API along the lines of `map.has(key, value)`
|
|
48
|
-
currentFacts.has(facts.todoExists(id)) && currentFacts.get(facts.todoIsWriteable(id, true)[0]) === false
|
|
48
|
+
currentFacts.has(facts.todoExists(id)) === true && currentFacts.get(facts.todoIsWriteable(id, true)[0]) === false
|
|
49
49
|
? { require: [facts.todoExists(id), facts.todoIsWriteable(id, true)] }
|
|
50
50
|
: { modify: { set: [facts.todoExists(id), facts.todoIsWriteable(id, true), facts.todoTextUpdated(id)] } },
|
|
51
51
|
}),
|
package/src/sync/sync-backend.ts
CHANGED
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
type Stream,
|
|
10
10
|
type SubscriptionRef,
|
|
11
11
|
} from '@livestore/utils/effect'
|
|
12
|
+
|
|
12
13
|
import type { UnknownError } from '../adapter-types.ts'
|
|
13
14
|
import type * as LiveStoreEvent from '../schema/LiveStoreEvent/mod.ts'
|
|
14
15
|
import type { EventSequenceNumber } from '../schema/mod.ts'
|
|
15
|
-
import type {
|
|
16
|
+
import type { BackendIdMismatchError, IsOfflineError, ServerAheadError } from './errors.ts'
|
|
16
17
|
|
|
17
18
|
export * from './sync-backend-kv.ts'
|
|
18
19
|
|
|
@@ -60,7 +61,7 @@ export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
|
|
|
60
61
|
*/
|
|
61
62
|
live?: boolean
|
|
62
63
|
},
|
|
63
|
-
) => Stream.Stream<PullResItem<TSyncMetadata>, IsOfflineError |
|
|
64
|
+
) => Stream.Stream<PullResItem<TSyncMetadata>, IsOfflineError | BackendIdMismatchError | UnknownError >
|
|
64
65
|
// TODO support transactions (i.e. group of mutation events which need to be applied together)
|
|
65
66
|
push: (
|
|
66
67
|
/**
|
|
@@ -69,7 +70,7 @@ export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
|
|
|
69
70
|
* - sequence numbers must be in ascending order
|
|
70
71
|
* */
|
|
71
72
|
batch: ReadonlyArray<LiveStoreEvent.Global.Encoded>,
|
|
72
|
-
) => Effect.Effect<void, IsOfflineError |
|
|
73
|
+
) => Effect.Effect<void, IsOfflineError | BackendIdMismatchError | UnknownError | ServerAheadError>
|
|
73
74
|
ping: Effect.Effect<void, IsOfflineError | UnknownError | Cause.TimeoutException>
|
|
74
75
|
// TODO also expose latency information additionally to whether the backend is connected
|
|
75
76
|
isConnected: SubscriptionRef.SubscriptionRef<boolean>
|
|
@@ -178,7 +179,7 @@ export const cursorFromPullResItem = <TSyncMetadata = Schema.JsonValue>(
|
|
|
178
179
|
metadata: Option.Option<TSyncMetadata>
|
|
179
180
|
}> => {
|
|
180
181
|
const lastEvent = item.batch.at(-1)
|
|
181
|
-
if (
|
|
182
|
+
if (lastEvent == null) {
|
|
182
183
|
return Option.none()
|
|
183
184
|
}
|
|
184
185
|
return Option.some({ eventSequenceNumber: lastEvent.eventEncoded.seqNum, metadata: lastEvent.metadata })
|
package/src/sync/sync.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './errors.ts'
|
|
|
2
2
|
export * as SyncBackend from './sync-backend.ts'
|
|
3
3
|
|
|
4
4
|
import type { Schema } from '@livestore/utils/effect'
|
|
5
|
+
|
|
5
6
|
import type { InitialSyncOptions } from '../leader-thread/types.ts'
|
|
6
7
|
import type { SyncBackendConstructor } from './sync-backend.ts'
|
|
7
8
|
|
|
@@ -19,6 +20,26 @@ export type SyncOptions<TPayload = Schema.JsonValue> = {
|
|
|
19
20
|
* @default 'ignore'
|
|
20
21
|
* */
|
|
21
22
|
onSyncError?: 'shutdown' | 'ignore'
|
|
23
|
+
/**
|
|
24
|
+
* What to do when the sync backend identity has changed (i.e. the backend was reset).
|
|
25
|
+
*
|
|
26
|
+
* This commonly happens during development when:
|
|
27
|
+
* - The sync backend state is deleted (e.g. `.wrangler/state` for Cloudflare)
|
|
28
|
+
* - Running with a `--reset` flag
|
|
29
|
+
* - Schema changes require re-backfilling data
|
|
30
|
+
*
|
|
31
|
+
* Options:
|
|
32
|
+
* - `'reset'`: Clear local storage (eventlog and state databases) and shutdown.
|
|
33
|
+
* The app will need to restart and will sync fresh data from the backend.
|
|
34
|
+
* This is the recommended option for development.
|
|
35
|
+
* - `'shutdown'`: Shutdown without clearing local storage.
|
|
36
|
+
* On restart, the client will still have stale data and hit the same error.
|
|
37
|
+
* - `'ignore'`: Log the error and continue running.
|
|
38
|
+
* The client will show stale data but keep running (effectively offline mode).
|
|
39
|
+
*
|
|
40
|
+
* @default 'reset'
|
|
41
|
+
*/
|
|
42
|
+
onBackendIdMismatch?: 'reset' | 'shutdown' | 'ignore'
|
|
22
43
|
/**
|
|
23
44
|
* Whether the sync backend should reactively pull new events from the sync backend
|
|
24
45
|
* @default true
|